Source code for tyrian.compiler

# standard libary
import os
import types
import marshal
from types import CodeType
from py_compile import wr_long, MAGIC

# application specific
from .nodes import (
    Node,
    IDNode,
    SymbolNode,
    ListNode,
    NumberNode,
    StringNode,
    AST
)
from .utils import logger, enforce_types

# third party
from peak.util.assembler import (
    Code,
    Global,
    Const,
    Local
)

logger = logger.getChild('Compiler')


[docs]class Compiler(object): """ Handles compilation of :py:class:`AST <tyrian.nodes.AST>`'s """ def __init__(self): self.locals = set() @enforce_types
[docs] def compile_parse_tree(self, filename: str, parse_tree) -> Code: """ Takes a filename and a parse_tree and returns a BytecodeAssembler Code object :param filename: filename of file to compile :param parse_tree: parse_tree to compile :rtype: Code """ filename = os.path.abspath(filename) line_no = -1 code = Code() code.set_lineno(line_no) code = self.bootstrap_obj(code) line_no += 1 code.co_filename = filename line_no, code = self.compile_parse_tree_internal( codeobject=code, parse_tree=parse_tree, line_no=line_no, filename=filename ) code.return_(None) return code
@enforce_types def compile_parse_tree_internal(self, codeobject: Code, parse_tree: AST, line_no: int, filename: str) -> tuple: """ Compiles a single AST :param codeobject: Code instance to output opcodes to :param parse_tree: parse_tree to compile :param line_no: current line number :rtype: tuple """ assert isinstance(parse_tree, AST) for element in parse_tree.content: line_no, codeobject = self.compile_single( codeobject=codeobject, filename=filename, element=element, line_no=line_no, result_required=False, scope=[] ) return line_no, codeobject @enforce_types def compile_single(self, codeobject: Code, filename: str, element: Node, line_no: int, result_required: bool, scope: list) -> tuple: """ compiles a single Node """ codeobject.set_lineno(element.content[0].line_no) if isinstance(element.content[0], (IDNode, SymbolNode)): if element.content[0].content == 'defun': # wahey! creating a function! line_no, codeobject = self.compile_function( codeobject, filename, element, line_no ) elif element.content[0].content == 'lambda': raise Exception((element, element.content)) elif element.content[0].content in ('let', 'defparameter', 'defvar'): # inline variable assignments line_no, codeobject = self.handle_variable_assignment( codeobject, filename, element, line_no, scope ) else: # whahey! calling a function! line_no, codeobject = self.call_function( codeobject, filename, element, line_no, scope ) if not result_required: # if the result aint required, clean up the stack codeobject.POP_TOP() else: raise Exception('{} -> {}'.format(element, element.content)) return line_no, codeobject @enforce_types def handle_variable_assignment(self, codeobject: Code, filename: str, element: Node, line_no: int, scope: list) -> tuple: """ Handles an inline variable assignment """ function_name, name, args = element.content name = name.content if isinstance(args, ListNode): # if it has to evaluated first, do so logger.debug('subcall: {} -> {}, with scope {}'.format( args, args.content, scope)) line_no, codeobject = self.compile_single( codeobject=codeobject, filename=filename, element=args, line_no=line_no, result_required=True, scope=scope ) elif isinstance(args, (NumberNode, StringNode)): # if it is simply a literal, treat it as such codeobject.LOAD_CONST(args.content) # global or local if function_name.content == 'let': codeobject.STORE_FAST(name) self.locals.add(name) elif function_name.content in ('defparameter', 'defvar'): codeobject.STORE_GLOBAL(name) else: raise Exception(function_name) return line_no, codeobject @enforce_types def call_function(self, codeobject: Code, filename: str, element: Node, line_no: int, scope: list) -> tuple: """ Generates code to call a function, with possible nested calls as function arguments """ name, *args = element.content name = name.content codeobject(Global(name)) for arg in args: if isinstance(arg, (IDNode, SymbolNode)): if arg.content in scope: codeobject.LOAD_FAST(arg.content) elif arg.content in self.locals: codeobject(Local(arg.content)) else: codeobject.LOAD_GLOBAL(arg.content) elif isinstance(arg, ListNode): logger.debug('subcall -> {}, with scope {}'.format( arg.content, scope) ) line_no, codeobject = self.compile_single( codeobject=codeobject, filename=filename, element=arg, line_no=line_no, result_required=True, scope=scope ) elif isinstance(arg, (NumberNode, StringNode)): codeobject(Const(arg.content)) else: raise Exception(arg) codeobject.CALL_FUNCTION(len(args)) return line_no, codeobject @enforce_types def compile_function(self, codeobject: Code, filename: str, element: Node, line_no: int) -> tuple: """ 'Compiles' function, using the last functions return value in the function body as the return value for the function proper """ _, name, args, *body = element.content name = name.content args = args.content args = [arg.content for arg in args] func_code = codeobject.nested(name, args) if body and any(el.content for el in body): *body, return_func = [el for el in body if el.content] # compile all bar the last statement for body_frag in body: line_no, func_code = self.compile_single( codeobject=func_code, filename=filename, element=body_frag, line_no=line_no, result_required=False, scope=args ) # compile the last statement, and ask for the result value line_no, func_code = self.compile_single( codeobject=func_code, filename=filename, element=return_func, line_no=line_no, result_required=True, scope=args ) func_code.RETURN_VALUE() else: func_code.return_() # inject the function into the codeobject codeobject = self.inject_function_code( codeobject=codeobject, function_codeobj=func_code.code(codeobject), name=name ) return line_no, codeobject @enforce_types def inject_function(self, codeobject: Code, function: types.FunctionType, name: str=None) -> Code: """ Injects a python land function via inject_function_code """ name = name if name else function.__name__ codeobject = self.inject_function_code( codeobject=codeobject, function_codeobj=function.__code__, name=name ) return codeobject @enforce_types def inject_function_code(self, codeobject: Code, function_codeobj: CodeType, name: str=None) -> Code: """ Injects a code object as a function """ codeobject.LOAD_CONST(function_codeobj) codeobject.LOAD_CONST(name) codeobject.MAKE_FUNCTION(0) codeobject.STORE_GLOBAL(name) return codeobject
[docs] def write_code_to_file(self, codeobject: types.CodeType, filehandler=None, filename: str=None): """ Write a code object to the specified filehandler """ st = os.stat(filename or __file__) size = st.st_size & 0xFFFFFFFF timestamp = int(st.st_mtime) # write a placeholder for the MAGIC filehandler.write(b'\0\0\0\0') wr_long(filehandler, timestamp) wr_long(filehandler, size) marshal.dump(codeobject, filehandler) filehandler.flush() # write the magic to the start filehandler.seek(0, 0) filehandler.write(MAGIC)
@enforce_types def bootstrap_obj(self, codeobject: Code) -> Code: """ Injects all the library functions """ from .lisp_runtime.registry import lisp_registry assert lisp_registry for name, function in lisp_registry.items(): codeobject = self.inject_function(codeobject, function, name) return codeobject
Read the Docs v: latest
Versions
latest
Downloads
PDF
HTML
Epub
On Read the Docs
Project Home
Builds

Free document hosting provided by Read the Docs.