hissp.compiler module#
The Hissp data-structure language compiler and associated helper functions.
- hissp.compiler.ENV: ContextVar[dict[str, Any]] = <ContextVar name='ENV'>#
Expansion environment.
Sometimes a macro needs the current environment when expanding, instead of its defining environment. Rather than pass in an implicit argument to all macros, it’s available here.
readerlessandmacroexpanduse this automatically.
- hissp.compiler.MAX_PROTOCOL = 5#
Compiler pickle protocol limit.
When there is no known literal syntax for an
atom, the compiler emits apickle.loadsexpression as a fallback. This is the highest pickle protocol it’s allowed to use. The compiler may use Protocol 0 instead when it has a shorterrepr, due to the inefficient escapes required for non-printing bytes.A lower number may be necessary if the compiled output is expected to run on an earlier Python version than the compiler.
- hissp.compiler.macro_context(env: dict[str, Any])[source]#
Sets
ENVduring macroexpansions.Does nothing if
envis already the current context.
- exception hissp.compiler.CompileError[source]#
Bases:
SyntaxErrorCatch-all exception for compilation failures.
- exception hissp.compiler.PostCompileWarning[source]#
Bases:
WarningForm 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
warningsfor how.
- class hissp.compiler.Compiler(qualname: str = '__main__', env: dict[str, Any] | None = None, evaluate: bool = True)[source]#
Bases:
objectThe Hissp recursive-descent compiler.
Translates the Hissp data-structure language into a functional subset of Python.
- static new_env(name: str, doc: str | None = None) dict[str, Any][source]#
“Imports” the named module, creating it if necessary.
Dynamically created modules have a
None__spec__. After creating thetypes.ModuleTypeusing name and doc, it initializes an empty__annotations__, a__package__based on name (assumes module is not itself a package), and a__builtins__.Returns the module’s
__dict__.
- compile(forms: Iterable) str[source]#
Compile multiple forms, and execute them if evaluate mode enabled.
- compile_form(form) str[source]#
Compile Hissp
formto the equivalent Python code in a string.tupleandstrhave special evaluation rules, otherwise it’s anatomthat represents itself.
- special(form: tuple) str[source]#
Try to compile as a
special form, elseinvocation().The two special forms are
quoteandlambda.A quote form evaluates to its argument, treated as literal data, not evaluated. Notice the difference in the
readerlesscompiled output:>>> print(readerless(('print',42,))) # function call print( (42)) >>> print(readerless(('quote',('print',42,),))) # tuple ('print', (42),)
- lambda_(form: tuple) str[source]#
Compile the anonymous function
special form.- (lambda (<parameters>)
<body>)
The
parameters tupleis divided into (<singles> : <pairs>)Parameter types are the same as Python’s. For example,
>>> print(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.>>> print(readerless( ... ('lambda', (':','a',1, ':/',':?', ':*',':?', 'b',':?', 'c',2,),), ... )) ( lambda a=(1), /, *, b, c=(2): ())
The
:may be omitted if there are no paired parameters.>>> print(readerless(('lambda', ('a','b','c',':',),),)) (lambda a, b, c: ()) >>> print(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: ())'
- expand_macro(form: tuple) str | Sentinel[source]#
Macroexpand and start over with
compile_form, if macro.
- classmethod get_macro(symbol: object, env: dict[str, Any])[source]#
Returns the macro function for
symbolgiven theenv.Returns
Noneifsymbolisn’t a macro identifier.
- call(form: Iterable) str[source]#
Compile call form.
Any tuple that is not quoted,
(), or aspecialform ormacrois a run-time call form. It has three parts:(<callable> <singles> : <pairs>).
Each argument pairs with a keyword or
control wordtarget. The:?target passes positionally (implied for singles).For example:
>>> print(readerless( ... ('print',1,2,3 ... ,':','sep',('quote',":",), 'end',('quote',"\n\n",),) ... )) print( (1), (2), (3), sep=':', end='\n\n')
Either <singles> or <pairs> 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 <pairs> part is empty:>>> readerless(('foo',),) 'foo()' >>> print(readerless(('foo','bar',),),) foo( bar)
Use the
:*and:**targets for position and keyword unpacking, respectively:>>> print(readerless( ... ('print',':',':*',[1,2], 'a',3, ':*',[4], ':**',{'sep':':','end':'\n\n'},), ... )) print( *[1, 2], a=(3), *[4], **{'sep': ':', 'end': '\n\n'})
Method calls are similar to function calls:
(.<method name> <self> <args> : <kwargs>).
A method on the first (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'
- fragment(code: str) str[source]#
Compile a
fragment atom. This preprocessing step converts afully-qualified identifierormodule handleinto an import. No further compilation is necessary. The contents are assumed to be Python code already.
- static qualified_identifier(qualname: str, code: str) str[source]#
Compile
fully-qualified identifierinto import and attribute.
- static module_identifier(code: str) str[source]#
Compile a
module handleto an import.
- atomic(form) str[source]#
Compile forms that evaluate to themselves.
Returns a literal if possible, otherwise falls back to
pickle:>>> readerless(-4.2j) '((-0-4.2j))' >>> print(readerless(float('nan'))) # nan __import__('pickle').loads(b'Fnan\n.') >>> 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\ng0\na.') >>> spam = [[]] * 3 # duplicated refs >>> print(readerless(spam)) # [[], [], []] __import__('pickle').loads(b'(l(lp0\nag0\nag0\na.')
- hissp.compiler.readerless(form: object, env: dict[str, Any] | None = None) str[source]#
Compile a Hissp form to Python without evaluating it.
Returns the compiled Python in a string.
Unless an alternative
envis specified, uses the currentENV(available in amacro_context) when available, otherwise uses the calling frame’s globals.
- hissp.compiler.evaluate(form: object, env: dict[str, Any] | None = None)[source]#
Convenience function to evaluate a Hissp form.
Unless an alternative
envis specified, uses the currentENV(available in amacro_context) when available, otherwise uses the calling frame’s globals.>>> evaluate(('operator..mul',6,7)) 42
- hissp.compiler.execute(*forms: object, env: dict[str, Any] | None = None) str[source]#
Convenience function to compile and execute Hissp forms.
Returns the compiled Python in a string.
Unless an alternative
envis specified, uses the currentENV(available in amacro_context) when available, otherwise uses the calling frame’s globals.>>> print(execute( ... ('hissp.._macro_.define','FACTOR',7,), ... ('hissp.._macro_.define','result',('operator..mul','FACTOR',6,),), ... )) # hissp.._macro_.define __import__('builtins').globals().update( FACTOR=(7)) # hissp.._macro_.define __import__('builtins').globals().update( result=__import__('operator').mul( FACTOR, (6))) >>> result 42
- hissp.compiler.is_node(form: object) TypeGuard[tuple][source]#
Determines if form is a nonempty tuple (not an
atom).
- hissp.compiler.is_import(form: object) TypeGuard[str][source]#
Determines if form is a
module handleor hasfull qualification.
- hissp.compiler.is_control(form: object) TypeGuard[str][source]#
Determines if form is a
control word.
- hissp.compiler.macroexpand1(form, env: dict[str, Any] | None = None)[source]#
Macroexpand outermost form once.
If form is not a macro form, returns it unaltered.
Unless an alternative
envis specified, uses the currentENV(available in amacro_context) when available, otherwise uses the calling frame’s globals.
- hissp.compiler.macroexpand(form, env: dict[str, typing.Any] | None = None, *, preprocess=<function <lambda>>)[source]#
Repeatedly macroexpand outermost form until not a macro form.
If form is not a macro form, returns it unaltered.
Unless an alternative
envis specified, uses the currentENV(available in amacro_context) when available, otherwise uses the calling frame’s globals.preprocess(which defaults to identity function) is called on the form before each expansion step.
- hissp.compiler.macroexpand_all(form, env: dict[str, typing.Any] | None = None, *, preprocess=<function <lambda>>, postprocess=<function <lambda>>)[source]#
Recursively macroexpand everything possible from the outside-in.
Pipes outer form through preprocess,
macroexpand(), and postprocess, then recurs intosubforms of the resulting expansion, if applicable.Pre/postprocess are called with
macro_contextso, e.g.,macroexpand1may be called by preprocess to handle intermediate expansions.If expansion is not a
macro form, returns it. As in the compiler, lambda parameter names are not considered expandable subforms, but default expressions are.Unless an alternative
envis specified, uses the currentENV(available in amacro_context) when available, otherwise uses the calling frame’s globals.