Beispiel #1
0
def compile_dsl_module(dsl_module, dsl_locals=None, dsl_globals=None):
    """
    Returns something that can be evaluated.
    """

    # It's a module compilation, so create a new namespace "context".
    if dsl_locals is None:
        dsl_locals = {}
    dsl_locals = DslNamespace(dsl_locals)
    if dsl_globals is None:
        dsl_globals = {}
    dsl_globals = DslNamespace(dsl_globals)

    # Can't do much with an empty module.
    if len(dsl_module.body) == 0:
        raise DslSyntaxError('empty module', node=dsl_module.node)

    function_defs, expressions = extract_defs_and_exprs(
        dsl_module, dsl_globals)

    # Handle different combinations of functions and module level expressions in different ways.
    # Todo: Simplify this, but support library files first?
    # Can't meaningfully evaluate more than one expression (since assignments are not supported).
    if len(expressions) > 1:
        raise DslSyntaxError('more than one expression in module',
                             node=expressions[1].node)

    # Can't meaningfully evaluate more than one function def without a module level expression.
    elif len(expressions) == 0 and len(function_defs) > 1:
        second_def = function_defs[1]
        raise DslSyntaxError(
            'more than one function def in module without an expression',
            '"def %s"' % second_def.name,
            node=function_defs[1].node)

    # If it's just a module with one function, then return the function def.
    elif len(expressions) == 0 and len(function_defs) == 1:
        return function_defs[0]

    # If there is one expression, reduce it with the function defs that it calls.
    else:
        assert len(expressions) == 1
        dsl_expr = expressions[0]
        # assert isinstance(dsl_expr, DslExpression), dsl_expr

        # Compile the module for a single threaded recursive operation (faster but not distributed,
        # so also limited in space and perhaps time). For smaller computations only.
        dsl_obj = dsl_expr.substitute_names(dsl_globals.combine(dsl_locals))
        return dsl_obj
Beispiel #2
0
    def parse(self, dsl_source, filename='<unknown>'):
        """
        Creates a DSL Module object from a DSL source text.
        """
        if not isinstance(dsl_source, six.string_types):
            raise DslSyntaxError("Can't dsl_parse non-string object", dsl_source)

        # assert isinstance(dsl_source, six.string_types)
        try:
            # Parse as Python source code, into a Python abstract syntax tree.
            ast_module = ast.parse(dsl_source, filename=filename, mode='exec')
        except SyntaxError as e:
            raise DslSyntaxError("DSL source code is not valid Python code: {}".format(dsl_source), e)

        # Generate Quant DSL from Python AST.
        return self.visitAstNode(ast_module)
Beispiel #3
0
    def visitCall(self, node):
        """
        Visitor method for ast.Call.

        Returns a built-in DSL expression, or a DSL FunctionCall if the name refers to a user
        defined function.
        """
        if node.keywords:
            raise DslSyntaxError(
                "Calling with keywords is not currently supported (positional args only)."
            )
        if node.starargs:
            raise DslSyntaxError(
                "Calling with starargs is not currently supported (positional args only)."
            )
        if node.kwargs:
            raise DslSyntaxError(
                "Calling with kwargs is not currently supported (positional args only)."
            )

        # Collect the call arg expressions (whose values will be passed into the call when it is made).
        call_arg_exprs = [self.visitAstNode(arg) for arg in node.args]

        # Check the called node is an ast.Name.
        called_node = node.func
        # assert isinstance(called_node, ast.Name)
        called_node_name = called_node.id

        # Construct a DSL object for this call.
        try:
            # Resolve the name with a new instance of a DSL class.
            dsl_class = self.dsl_classes[called_node_name]
        except KeyError:
            # Resolve as a FunctionCall, and expect
            # to resolve the name to a function def later.
            dsl_name_class = self.dsl_classes['Name']
            dsl_args = [
                dsl_name_class(called_node_name, node=called_node),
                call_arg_exprs
            ]
            return self.dsl_classes['FunctionCall'](node=node, *dsl_args)
        else:
            dsl_object_class = self.dsl_classes['DslObject']
            assert issubclass(dsl_class, dsl_object_class), dsl_class
            return dsl_class(node=node, *call_arg_exprs)
Beispiel #4
0
    def visitUnaryOp(self, node):
        """
        Visitor method for ast.UnaryOp.

        Returns a specific DSL UnaryOp object (e.g UnarySub), along with the operand.
        """
        # assert isinstance(node, ast.UnaryOp)
        args = [self.visitAstNode(node.operand)]
        if isinstance(node.op, ast.USub):
            dsl_class = self.dsl_classes['UnarySub']
        else:
            raise DslSyntaxError("Unsupported unary operator token: %s" % node.op)
        return dsl_class(node=node, *args)
Beispiel #5
0
def extract_defs_and_exprs(dsl_module, dsl_globals):
    # Pick out the expressions and function defs from the module body.
    function_defs = []
    expressions = []
    for dsl_obj in dsl_module.body:

        if isinstance(dsl_obj, FunctionDef):
            function_defs.append(dsl_obj)
        elif isinstance(dsl_obj, DslExpression):
            expressions.append(dsl_obj)
        else:
            raise DslSyntaxError("'%s' not allowed in module" % type(dsl_obj),
                                 dsl_obj,
                                 node=dsl_obj.node)

    return function_defs, expressions
Beispiel #6
0
def find_module_path(name):
    # Find path.

    if six.PY2:
        try:
            module = import_module(name)
        except SyntaxError as e:
            raise DslSyntaxError("Can't import {}: {}".format(name, e))
        path = module.__file__.strip('c')
    elif six.PY3:
        if find_loader:
            loader = find_loader(name)
            path = loader.path
        else:
            spec = find_spec(name)
            path = spec.origin

    assert path.endswith('.py'), path
    return path
Beispiel #7
0
    def visitBoolOp(self, node):
        """
        Visitor method for ast.BoolOp.

        Returns a specific DSL BoolOp object (e.g And), along with the left and right operands.
        """
        # assert isinstance(node, ast.BoolOp)
        type_map = {
            ast.And: self.dsl_classes['And'],
            ast.Or: self.dsl_classes['Or'],
        }
        try:
            dsl_class = type_map[type(node.op)]
        except KeyError:
            raise DslSyntaxError("Unsupported boolean operator token: %s" %
                                 node.op)
        else:
            values = [self.visitAstNode(v) for v in node.values]
            args = [values]
            return dsl_class(node=node, *args)
Beispiel #8
0
    def visitAstNode(self, node):
        """
        Identifies which "visit" method to call, according to type of node being visited.

        Returns the result of calling the identified "visit" method.
        """
        # assert isinstance(node, ast.AST)

        # Construct the "visit" method name.
        dsl_element_name = node.__class__.__name__
        method_name = 'visit' + dsl_element_name

        # Try to get_quantdsl_app the "visit" method object.
        try:
            method = getattr(self, method_name)
        except AttributeError:
            msg = "element '%s' is not supported (visit method '%s' not found on parser): %s" % (
                dsl_element_name, method_name, node)
            raise DslSyntaxError(msg)

        # Call the "visit" method object, and return the result of visiting the node.
        return method(node=node)
Beispiel #9
0
    def visitBinOp(self, node):
        """
        Visitor method for ast.BinOp.

        Returns a specific DSL BinOp object (e.g Add), along with the left and right operands.
        """
        # assert isinstance(node, ast.BinOp)
        type_map = {
            ast.Add: self.dsl_classes['Add'],
            ast.Sub: self.dsl_classes['Sub'],
            ast.Mult: self.dsl_classes['Mult'],
            ast.Div: self.dsl_classes['Div'],
            ast.Pow: self.dsl_classes['Pow'],
            ast.Mod: self.dsl_classes['Mod'],
            ast.FloorDiv: self.dsl_classes['FloorDiv'],
        }
        try:
            dsl_class = type_map[type(node.op)]
        except KeyError:
            raise DslSyntaxError("Unsupported binary operator token", node.op, node=node)
        args = [self.visitAstNode(node.left), self.visitAstNode(node.right)]
        return dsl_class(node=node, *args)