Example #1
0
    def _check_match_coverage(self, input_type):
        """
        Given some input type for this match expression, make sure the set of
        matchers cover all cases. check_source_language will raise an error if
        it's not the case. Also emit warnings for unreachable matchers.

        :param ASTNode input_type: Type parameter.
        :rtype: None
        """

        type_set = TypeSet()

        for i, (t, _, _) in enumerate(self.matchers, 1):
            t_name = 'default one' if t is None else t.name().camel
            check_source_language(not type_set.include(t or input_type),
                                  'The #{} matcher ({}) is unreachable'
                                  ' as all previous matchers cover all the'
                                  ' nodes it can match'.format(i, t_name),
                                  Severity.warning)

        mm = sorted(type_set.unmatched_types(input_type),
                    key=lambda cls: cls.hierarchical_name())

        check_source_language(
            not mm,
            'The following AST nodes have no handler: {} (all {} subclasses'
            ' require one)'.format(
                ', '.join(t.name().camel for t in mm),
                input_type.name().camel
            )
        )
Example #2
0
    def construct(self):
        """
        Construct a resolved expression for this.

        :rtype: IfExpr
        """
        def construct_op(op):
            return construct(op, lambda t: t in (BoolType, EquationType),
                             "Operands of binary logic operator must be of "
                             "boolean or equation type, got {expr_type}")

        lhs, rhs = map(construct_op, [self.lhs, self.rhs])

        check_source_language(
            lhs.type is rhs.type, "Left and right operands to binary logic "
            "operator should have the same type"
        )

        if lhs.type is BoolType:
            # Boolean case
            if self.kind == self.AND:
                then = rhs
                else_then = LiteralExpr('False', BoolType)
            else:
                then = LiteralExpr('True', BoolType)
                else_then = rhs
            return If.Expr(lhs, then, else_then, BoolType)
        else:
            # Equation case
            return BuiltinCallExpr(
                names.Name("Logic") + names.Name.from_lower(self.kind),
                EquationType, [lhs, rhs],
                '{}_Pred'.format(self.kind.capitalize())
            )
Example #3
0
    def do_prepare(self):
        self.matchers = []

        for i, match_fn in enumerate(self.matchers_functions):
            argspec = inspect.getargspec(match_fn)
            check_source_language(
                len(argspec.args) == 1 and
                not argspec.varargs and
                not argspec.keywords and
                (not argspec.defaults or len(argspec.defaults) < 2),
                'Invalid matcher lambda'
            )

            if argspec.defaults:
                match_type = resolve_type(argspec.defaults[0])
                check_source_language(
                    issubclass(match_type, ASTNode) and
                    match_type != ASTNode,
                    'Invalid matching type: {}'.format(
                        match_type.name().camel
                    )
                )
            else:
                match_type = None

            match_var = AbstractVariable(
                names.Name('Match_{}'.format(i)),
                type=match_type,
                create_local=True
            )
            self.matchers.append((match_type, match_var, match_fn(match_var)))
Example #4
0
    def prepare(self):
        """
        Method call by CompileCtx.compute_properties. Used to check that
        properties generated by the env spec are conforming.

        :rtype: bool
        """
        for key_prop, val_prop, _, _, _ in self.envs_expressions:
            with key_prop.diagnostic_context():
                check_source_language(
                    key_prop.type.matches(Symbol) or
                    key_prop.type.matches(Symbol.array_type()),
                    'The key expression in environment specification must be'
                    ' either a symbol or an array of symbol: got {}'
                    ' instead'.format(
                        key_prop.type.name().camel
                    )
                )

                check_source_language(
                    val_prop.type.matches(T.root_node)
                    or (val_prop.type.is_collection
                        and val_prop.type.element_type().matches(T.root_node)),
                    'The val expression in environment specification must be'
                    ' either a node or an array of nodes: got {}'
                    ' instead'.format(
                        val_prop.type.name().camel
                    )
                )
Example #5
0
    def construct(self):
        """
        Construct a resolved expression for this map operation.

        :rtype: MapExpr
        """
        (collection_expr,
         expr,
         element_var,
         index_var,
         iter_scope) = self.construct_common()

        check_source_language(
            not self.concat or expr.type.is_collection(),
            'Cannot mapcat with expressions returning {} values (collections'
            ' expected instead)'.format(expr.type.name())
        )

        with iter_scope.use():
            filter_expr = (construct(self.filter_expr, BoolType)
                           if self.filter_expr else None)

            take_while_expr = (construct(self.take_while_expr, BoolType)
                               if self.take_while_expr else None)

        return Map.Expr(element_var, index_var, collection_expr, expr,
                        iter_scope, filter_expr, self.concat,
                        take_while_expr)
Example #6
0
    def construct(self):
        """
        Construct a resolved expression for this.

        :rtype: EqExpr
        """
        def construct_logic_eq(lhs, rhs):
            if lhs.type == LogicVarType:
                check_source_language(
                    rhs.type == LogicVarType or rhs.type.matches(ASTNode),
                    "Operands to a logic equality operator should be either "
                    "a logic variable or an ASTNode, got {}".format(rhs.type)
                )

                # Cast the ast node type if necessary
                if (rhs.type.matches(ASTNode) and rhs.type != T.root_node):
                    rhs = Cast.Expr(rhs, T.root_node)

                return BuiltinCallExpr("Equals", EquationType, [lhs, rhs],
                                       "Equals_Pred")
            else:
                return None

        from langkit.expressions.structs import Cast
        lhs = construct(self.lhs)
        rhs = construct(self.rhs)

        # We don't care about associacivity in logic eq, so lhs and rhs
        # might be passed in reverse order.
        logic = construct_logic_eq(lhs, rhs) or construct_logic_eq(rhs, lhs)
        if logic:
            return logic

        # Don't use CompiledType.matches since in the generated code, we need
        # both operands to be *exactly* the same types, so handle specifically
        # each case.
        if issubclass(lhs.type, ASTNode):
            # Handle checks between two subclasses without explicit casts. In
            # order to help users to detect dubious checks, forbid operands
            # that can never be equal because they have no subclass in common.
            if issubclass(lhs.type, rhs.type):
                lhs = Cast.Expr(lhs, assert_type(rhs.type, ASTNode))
            elif issubclass(rhs.type, lhs.type):
                rhs = Cast.Expr(rhs, assert_type(lhs.type, ASTNode))
            else:
                check_source_language(
                    False, '{} and {} values are never equal'.format(
                        lhs.type.name().camel, rhs.type.name().camel
                    )
                )
        else:
            check_source_language(
                lhs.type == rhs.type,
                'Incompatible types for equality: {} and {}'.format(
                    lhs.type.name().camel, rhs.type.name().camel
                )
            )

        return self.make_expr(lhs, rhs)
Example #7
0
    def add_rules(self, **kwargs):
        """
        Add rules to the grammar.  The keyword arguments will provide a name to
        rules.

        :param dict[str, Parser] kwargs: The rules to add to the grammar.
        """
        import ast
        loc = extract_library_location()

        class GetTheCall(ast.NodeVisitor):
            """
            Helper visitor that will get the corresponding add_rule call in the
            source, so that we're then able to extract the precise line where
            each rule is added.
            """
            def __init__(self):
                self.the_call = None

            def visit_Call(self, call):
                if (
                    isinstance(call.func, ast.Attribute)
                    and call.func.attr == 'add_rules'
                    # Traceback locations are very imprecise, and python's ast
                    # doesn't have an end location for nodes, so we'll keep
                    # taking add_rules call, and the last is necessarily the
                    # good one.
                    and call.lineno <= loc.line
                ):
                    self.the_call = call

        caller_location = extract_library_location()
        the_call = GetTheCall()
        with open(caller_location.file) as f:
            file_ast = ast.parse(f.read(), f.name)
            the_call.visit(file_ast)

        # We're gonna use the keyword arguments to find back the precise line
        # where the rule was declared.
        keywords = {kw.arg: kw.value for kw in the_call.the_call.keywords}

        for name, rule in kwargs.items():
            rule.set_name(names.Name.from_lower(name))
            rule.set_grammar(self)
            rule.set_location(Location(loc.file, keywords[name].lineno, ""))
            rule.is_root = True

            with Context("In definition of rule '{}'".format(name), loc):
                check_source_language(
                    name not in self.rules,
                    "Rule '{}' is already present in the grammar".format(name)
                )

            self.rules[name] = rule
Example #8
0
    def do_prepare(self):
        check_source_language(issubclass(self.struct_type, Struct), (
            "Invalid type, expected struct type, got {}".format(
                self.struct_type.name().camel
            )
        ))

        check_source_language(not issubclass(self.struct_type, ASTNode), (
            "Invalid type, expected struct type, got {} which is an "
            "ASTNode".format(
                self.struct_type.name().camel
            )
        ))
Example #9
0
    def construct(self):
        exprs = [construct(e) for e in self.exprs]

        prop_types = [self.pred_property.struct] + [
            a.type for a in self.pred_property.explicit_arguments
        ]

        # Separate logic variable expressions from extra argument expressions
        logic_var_exprs, closure_exprs = funcy.split_by(
            lambda e: e.type == LogicVarType, exprs
        )

        check_source_language(
            len(logic_var_exprs) > 0, "Predicate instantiation should have at "
            "least one logic variable expression"
        )

        check_source_language(
            all(e.type != LogicVarType for e in closure_exprs), "Logic "
            "variable expressions should be grouped at the beginning, and "
            "should not appear after non logic variable expressions"
        )

        for i, (expr, arg_type) in enumerate(zip(exprs, prop_types)):
            if expr.type == LogicVarType:
                check_source_language(
                    arg_type.matches(T.root_node), "Argument #{} of predicate "
                    "is a logic variable, the corresponding property formal "
                    "has type {}, but should be a descendent of {}".format(
                        i, arg_type.name().camel, T.root_node.name().camel
                    )
                )
            else:
                check_source_language(
                    expr.type.matches(arg_type), "Argument #{} of predicate "
                    "has type {}, should be {}".format(
                        i, expr.type.name().camel, arg_type.name().camel
                    )
                )

        pred_id = self.pred_property.do_generate_logic_predicate(*[
            e.type for e in closure_exprs
        ])

        closure_exprs.append(construct(Env))

        logic_var_exprs.append(
            BasicExpr("{}_Predicate_Caller'({})".format(
                pred_id, ", ".join(
                    ["{}" for _ in range(len(closure_exprs) - 1)]
                    + ["Env => {}"]
                )
            ), type=None, sub_exprs=closure_exprs)
        )

        return BuiltinCallExpr(
            "{}_Pred.Create".format(pred_id), EquationType, logic_var_exprs,
            result_var_name="Pred"
        )
Example #10
0
    def construct(self):
        """
        Construct a resolved expression for this.

        :rtype: IfExpr
        """
        then = construct(self.then)
        else_then = construct(self.else_then)
        check_source_language(
            then.type.matches(else_then.type),
            "Mismatching types in If expression: {} and {}".format(
                then.type.name().camel, else_then.type.name().camel
            )
        )
        rtype = then.type.unify(else_then.type)
        return If.Expr(construct(self.cond, BoolType), then, else_then, rtype)
Example #11
0
        def construct_logic_eq(lhs, rhs):
            if lhs.type == LogicVarType:
                check_source_language(
                    rhs.type == LogicVarType or rhs.type.matches(ASTNode),
                    "Operands to a logic equality operator should be either "
                    "a logic variable or an ASTNode, got {}".format(rhs.type)
                )

                # Cast the ast node type if necessary
                if (rhs.type.matches(ASTNode) and rhs.type != T.root_node):
                    rhs = Cast.Expr(rhs, T.root_node)

                return BuiltinCallExpr("Equals", EquationType, [lhs, rhs],
                                       "Equals_Pred")
            else:
                return None
Example #12
0
    def construct(self):
        # Add var_expr to the scope for this Then expression
        PropertyDef.get_scope().add(self.var_expr.local_var)

        # Accept as a prefix:
        # * any pointer, since it can be checked against "null";
        # * any Struct, since its "Is_Null" field can be checked.
        expr = construct(self.expr,
                         lambda cls: cls.is_ptr or issubclass(cls, Struct))
        self.var_expr.set_type(expr.type)

        then_expr = construct(self.then_expr)

        # Affect default value to the fallback expression. For the moment,
        # only booleans and structs are handled.
        if self.default_val is None:
            if then_expr.type.matches(BoolType):
                default_expr = construct(False)
            elif issubclass(then_expr.type, Struct):
                default_expr = construct(No(
                    # Because we're doing issubclass instead of isinstance,
                    # PyCharm do not understand that then_exp.type is a Struct,
                    # so the following is necessary not to have warnings.
                    assert_type(then_expr.type, Struct)
                ))
            elif then_expr.type.matches(LexicalEnvType):
                default_expr = construct(EmptyEnv)
            else:
                # The following is not actually used but PyCharm's typer
                # requires it.
                default_expr = None

                check_source_language(
                    False,
                    "Then expression should have a default value provided, "
                    "in cases where the provided function's return type is "
                    "not Bool, here {}".format(then_expr.type.name().camel)
                )
        else:
            default_expr = construct(self.default_val, then_expr.type)

        return Then.Expr(expr, construct(self.var_expr), then_expr,
                         default_expr)
Example #13
0
    def construct(self):
        """
        Construct a resolved expression that is the result of testing the kind
        of a node.

        :rtype: IsAExpr
        """
        expr = construct(self.expr)
        astnodes = [resolve_type(a) for a in self.astnodes]
        for a in astnodes:
            check_source_language(
                issubclass(a, ASTNode),
                "Expected ASTNode subclass, got {}".format(a)
            )
            check_source_language(a.matches(expr.type), (
                'When testing the dynamic subtype of an AST node, the type to'
                ' check must be a subclass of the value static type.'
            ))
        return IsA.Expr(expr, astnodes)
Example #14
0
    def get_rule(self, rule_name):
        """
        Helper to return the rule corresponding to rule_name. The benefit of
        using this helper is that it will raise a helpful error diagnostic.

        :param str rule_name: The rule to get.
        """
        if rule_name not in self.rules:
            close_matches = difflib.get_close_matches(
                rule_name, self.rules.keys()
            )
            check_source_language(
                False, "Wrong rule name: '{}'. {}".format(
                    rule_name,
                    "Did you mean '{}'?".format(close_matches[0])
                    if close_matches else ""
                )
            )
        return self.rules[rule_name]
Example #15
0
    def construct(self):
        """
        Construct a resolved expression for this quantifier expression.

        :rtype: QuantifierExpr
        """
        (collection_expr,
         expr,
         element_var,
         index_var,
         iter_scope) = self.construct_common()

        check_source_language(
            expr.type.matches(BoolType),
            "Wrong type for expression in quantifier: expected bool, "
            "got {}".format(expr.type.name().camel)
        )

        return Quantifier.Expr(self.kind, collection_expr, expr,
                               element_var, index_var, iter_scope)
Example #16
0
    def construct(self):
        """
        Construct a resolved expression that is the result of casting a AST
        node.

        :rtype: CastExpr
        """
        expr = construct(
            self.expr,
            lambda t: self.astnode.matches(t) or t.matches(self.astnode),
            'Cannot cast {{expr_type}} to {}: only (up/down)casting is '
            'allowed'.format(
                self.astnode.name().camel
            )
        )
        check_source_language(
            expr.type != self.astnode,
            'Casting to the same type',
            severity=Severity.warning
        )
        return Cast.Expr(expr, self.astnode, do_raise=self.do_raise)
Example #17
0
    def __init__(self,
                 add_env=False,
                 add_to_env=None,
                 ref_envs=None,
                 initial_env=None,
                 env_hook_arg=None,
                 call_parents=True):
        """

        :param bool add_env: Wether to add a new scoped lexical environment.
            The new environment will be linked to the corresponding AST node
            and will have the AST node's lexical environment as a parent.

        :param add_to_env: Eiter an AddToEnv named tuple, or a list of them.
            Used to add elements to the lexical environment. See add_to_env's
            doc for more details.
        :type add_to_env: AddToEnv|[AddToEnv]

        :param AbstractExpression ref_envs: if an AbstractExpression returning
            a list of environments is supplied, the topmost environment in the
            environment resolution will be altered to include the list of
            environments as referenced environments. TODO: Not yet implemented!

        :param AbstractExpression initial_env: If supplied, this env will be
            used as the lexical environment to execute the rest of the actions.
            For example, if you pass an initial_env, and add_env, then an env
            will be added to the env passed as initial_env, and the node
            concerned by this env specification will have initial_env as a
            parent indirectly.

        :param AbstractExpression env_hook_arg: Does nothing if left to None.
            If supplied, it must be an abstract expression that resolves to a
            node. This expression will be evaluated and passed to the
            environment hook.
        """

        self.ast_node = None
        """
        ASTNode subclass associated to this environment specification.
        Initialized when creating ASTNode subclasses.
        :type: langkit.compiled_types.ASTNode
        """

        self._add_env = add_env
        ":type: bool"

        # The following attributes (unresolved_*) contain abstract expressions
        # used to describe various environment behaviors. They all have
        # corresponding attributes that embed them as properties: see below.

        self._unresolved_initial_env = initial_env
        ":type: AbstractExpression"

        self._unresolved_envs_expressions = []
        ":type: list[AddToEnv]"

        self.envs_expressions = []
        ":type: list[AddToEnv]"

        if add_to_env:
            check_source_language(
                isinstance(add_to_env, AddToEnv)
                or isinstance(add_to_env, list),
                "Wrong parameter for add_to_env: Expected AddToEnv named-tuple"
                " or list of AddToEnv")

            self._unresolved_envs_expressions = ([add_to_env] if isinstance(
                add_to_env, AddToEnv) else add_to_env)

        self._unresolved_ref_envs = ref_envs
        ":type: AbstractExpression"

        self._unresolved_env_hook_arg = env_hook_arg
        ":type: AbstractExpression"

        # These are the property attributes

        self.initial_env = None
        ":type: PropertyDef"

        self.ref_envs = None
        ":type: PropertyDef"

        self.env_hook_arg = None
        ":type: PropertyDef"

        self.has_post_actions = False

        self.call_parents = call_parents
        "Whether to call parents env specs or not"
Example #18
0
    def run(self, argv=None):
        parsed_args = self.args_parser.parse_args(argv)

        from langkit import diagnostics
        diagnostics.EMIT_PARSABLE_ERRORS = parsed_args.parsable_errors

        if parsed_args.profile:
            import cProfile
            import pstats

            pr = cProfile.Profile()
            pr.enable()

        # If asked to, setup the exception hook as a last-chance handler to
        # invoke a debugger in case of uncaught exception.
        if parsed_args.debug:
            # Try to use IPython's debugger if it is available, otherwise
            # fallback to PDB.
            try:
                # noinspection PyPackageRequirements
                from IPython.core import ultratb
            except ImportError:
                ultratb = None  # To keep PyCharm happy...

                def excepthook(type, value, tb):
                    import traceback
                    traceback.print_exception(type, value, tb)
                    pdb.post_mortem(tb)

                sys.excepthook = excepthook
            else:
                sys.excepthook = ultratb.FormattedTB(mode='Verbose',
                                                     color_scheme='Linux',
                                                     call_pdb=1)
            del ultratb

        self.dirs.set_build_dir(parsed_args.build_dir)
        install_dir = getattr(parsed_args, 'install-dir', None)
        if install_dir:
            self.dirs.set_install_dir(install_dir)

        # Compute code coverage in the code generator if asked to
        if parsed_args.func == self.do_generate and parsed_args.coverage:
            try:
                cov = Coverage(self.dirs)
            except Exception as exc:
                import traceback
                print >> sys.stderr, 'Coverage not available:'
                traceback.print_exc(exc)
                sys.exit(1)

            cov.start()
        else:
            cov = None

        # noinspection PyBroadException
        try:
            parsed_args.func(parsed_args)
        except DiagnosticError:
            if parsed_args.debug:
                raise
            print >> sys.stderr, col('Errors, exiting', Colors.FAIL)
            sys.exit(1)
        except Exception, e:
            if parsed_args.debug:
                raise
            import traceback
            ex_type, ex, tb = sys.exc_info()
            if e.args[0] == 'invalid syntax':
                loc = Location(e.filename, e.lineno, "")
            else:
                loc = extract_library_location(traceback.extract_tb(tb))
            with Context("", loc, "recovery"):
                check_source_language(False, str(e), do_raise=False)
            if parsed_args.verbosity.debug:
                traceback.print_exc()

            print >> sys.stderr, col('Internal error! Exiting', Colors.FAIL)
            sys.exit(1)
Example #19
0
 def check_type_compatibility(is_valid):
     check_source_language(
         is_valid, 'Incompatible types for equality: {} and {}'.format(
             lhs.type.dsl_name, rhs.type.dsl_name))
Example #20
0
    def construct(self):
        """
        Constructs a resolved expression that is the result of:

        - Resolving the receiver;
        - Getting its corresponding field.

        :rtype: FieldAccessExpr
        """

        is_deref = False
        receiver_expr = construct(self.receiver)

        if issubclass(receiver_expr.type, AnalysisUnitType):
            return construct_analysis_unit_property(receiver_expr, self.field,
                                                    self.arguments)

        check_source_language(
            issubclass(receiver_expr.type, Struct),
            '{} values have no field (accessed field was {})'.format(
                receiver_expr.type.name().camel,
                self.field
            )
        )

        to_get = receiver_expr.type.get_abstract_fields_dict().get(self.field,
                                                                   None)
        ":type: AbstractNodeField"

        # If still not found, maybe the receiver is an env el, in which case we
        # want to do implicit dereference.
        if not to_get and receiver_expr.type.is_env_element_type:
            to_get = receiver_expr.type.el_type.get_abstract_fields_dict().get(
                self.field, None
            )
            is_deref = bool(to_get)

        # If still not found, we have a problem
        check_source_language(
            to_get is not None, "Type {} has no '{}' field or property".format(
                receiver_expr.type.__name__, self.field
            )
        )

        check_source_language(
            not to_get.is_internal,
            '{} is for internal use only'.format(to_get.qualname)
        )

        # If the field is a property that take an implicit env argument, make
        # sure we have one to provide.
        check_source_language(
            not to_get.is_property or
            not to_get.has_implicit_env or
            Env.has_ambient_env,
            'This property has no implicit environment parameter whereas {}'
            ' expects one: please use the eval_in_env construct to bind an'
            ' environment first.'.format(to_get.qualname)
        )

        # Check that this property actually accepts these arguments and that
        # they are correctly typed.
        check_source_language(
            len(self.arguments) == len(to_get.explicit_arguments),
            'Invalid number of arguments in the call to {}:'
            ' {} expected but got {}'.format(
                to_get.qualname,
                len(to_get.explicit_arguments),
                len(self.arguments),
            )
        )

        arg_exprs = [
            construct(
                actual, formal.type,
                custom_msg='Invalid {} actual (#{}) for {}:'.format(
                    formal.name, i, to_get.qualname,
                ) + ' expected {expected} but got {expr_type}'
            ) for i, (actual, formal) in enumerate(
                zip(self.arguments, to_get.explicit_arguments), 1
            )
        ]

        ret = FieldAccess.Expr(receiver_expr, to_get, arg_exprs, is_deref)
        return ret
Example #21
0
    def do_prepare(self):
        self.dest_type = resolve_type(self.dest_type)

        check_source_language(
            self.dest_type.is_ast_node or self.dest_type.is_entity_type,
            "One can only cast to an ASTNode subtype or to an entity")
Example #22
0
    def construct(self):
        """
        :rtype: StructExpr
        """
        if self.struct_type.is_ast_node:
            check_source_language(
                not self.struct_type.is_list_type,
                'List node synthetization is not supported for now')
            check_source_language(
                PropertyDef.get().memoized,
                'Node synthetization can only happen inside a memoized'
                ' property')

        # Make sure the provided set of fields matches the one the struct
        # needs.
        def error_if_not_empty(name_set, message):
            check_source_language(
                not name_set,
                ('{}: {}'.format(message, ', '.join(name
                                                    for name in name_set))))

        # Create a dict of field names to fields in the struct type

        def is_required(f):
            if isinstance(f, BuiltinField):
                # BuiltinFields are actually stored fields only for structure
                # types (not for nodes).
                return self.struct_type.is_struct_type

            elif isinstance(f, Field):
                return not f.null

            else:
                return isinstance(f, UserField)

        required_fields = {
            f._name.lower: f
            for f in self.struct_type.get_abstract_node_data()
            if is_required(f)
        }

        error_if_not_empty(
            set(required_fields) - set(self.field_values.keys()),
            'Values are missing for {} fields'.format(
                self.struct_type.dsl_name))
        error_if_not_empty(
            set(self.field_values.keys()) - set(required_fields),
            'Extraneous fields for {}'.format(self.struct_type.dsl_name))

        # At this stage, we know that the user has only provided fields that
        # are valid for the struct type.
        provided_fields = {
            required_fields[name].name: construct(
                value, required_fields[name].type,
                'Wrong type for field {}: expected {{expected}}, '
                'got {{expr_type}}'.format(name))
            for name, value in self.field_values.items()
        }

        expr_cls = (New.NodeExpr
                    if self.struct_type.is_ast_node else New.StructExpr)
        return expr_cls(self.struct_type, provided_fields, abstract_expr=self)
Example #23
0
    def process_subclass(mcs, name, bases, dct, is_root):
        from langkit.envs import EnvSpec

        location = extract_library_location()
        base = bases[0]
        is_list_type = issubclass(base, _ASTNodeList)
        is_root_list_type = base is _ASTNodeList

        node_ctx = Context('in {}'.format(name), location)

        with node_ctx:
            check_source_language(
                len(bases) == 1, 'ASTNode subclasses must have exactly one'
                ' base class')
            if mcs.root_type is not None:
                check_source_language(
                    base is not ASTNode,
                    'Only one class can derive from ASTNode (previous was:'
                    ' {})'.format(mcs.root_type.__name__))

            env_spec = dct.pop('env_spec', None)
            check_source_language(
                env_spec is None or isinstance(env_spec, EnvSpec),
                'Invalid environment specification: {}'.format(env_spec))

            annotations = dct.pop('annotations', None)

        # If this is a list type, determine the corresponding element type
        if is_root_list_type:
            element_type = dct.pop('_element_type')
        elif is_list_type:
            element_type = base._element_type
        else:
            element_type = None

        # Determine if this is a token node
        with node_ctx:
            is_token_node = dct.pop('token_node', None)
            check_source_language(
                is_token_node is None or isinstance(is_token_node, bool),
                'The "token_node" field, when present, must contain a boolean')

            # If "token_node" allocation is left to None, inherit it (default
            # is False).
            if is_token_node is None:
                is_token_node = bool(base._is_token_node)

            # Otherwise, make sure that all derivations of a token node are
            # token nodes themselves.
            elif not is_token_node:
                check_source_language(
                    is_token_node == base._is_token_node,
                    '"token_node" annotation inconsistent with inherited AST'
                    ' node')

        fields = ASTNode.collect_fields(name, location, dct, AbstractNodeData)

        # AST list types are not allowed to have syntax fields
        if is_list_type:
            syntax_fields = [f_n for f_n, f_v in fields if not f_v.is_property]
            with node_ctx:
                check_source_language(
                    not syntax_fields,
                    'ASTNode list types are not allowed to have fields'
                    ' (here: {})'.format(', '.join(sorted(syntax_fields))))

        DSLType._import_base_type_info(name, location, dct)
        dct['_fields'] = fields
        dct['_base'] = base
        dct['_env_spec'] = env_spec
        dct['_is_token_node'] = is_token_node

        # Make sure subclasses don't inherit the "list_type" cache from their
        # base classes.
        dct['_list_type'] = None
        dct['_element_type'] = element_type
        dct['_annotations'] = annotations
Example #24
0
def create_lexer(ctx, lkt_units):
    """
    Create and populate a lexer from a Lktlang unit.

    :param list[liblktlang.AnalysisUnit] lkt_units: Non-empty list of analysis
        units where to look for the grammar.
    :rtype: langkit.lexer.Lexer
    """
    import liblktlang

    # Look for the LexerDecl node in top-level lists
    full_lexer = find_toplevel_decl(ctx, lkt_units, liblktlang.LexerDecl,
                                    'lexer')
    with ctx.lkt_context(full_lexer):
        lexer_annot = parse_annotations(ctx, lexer_annotations, full_lexer)

    patterns = {}
    """
    Mapping from pattern names to the corresponding regular expression.

    :type: dict[names.Name, str]
    """

    token_family_sets = {}
    """
    Mapping from token family names to the corresponding sets of tokens that
    belong to this family.

    :type: dict[names.Name, Token]
    """

    token_families = {}
    """
    Mapping from token family names to the corresponding token families.  We
    build this late, once we know all tokens and all families.

    :type: dict[names.Name, TokenFamily]
    """

    tokens = {}
    """
    Mapping from token names to the corresponding tokens.

    :type: dict[names.Name, Token]
    """

    rules = []
    pre_rules = []
    """
    Lists of regular and pre lexing rules for this lexer.

    :type: list[(langkit.lexer.Matcher, langkit.lexer.Action)]
    """

    newline_after = []
    """
    List of tokens after which we must introduce a newline during unparsing.

    :type: list[Token]
    """
    def ignore_constructor(start_ignore_layout, end_ignore_layout):
        """
        Adapter to build a Ignore instance with the same API as WithText
        constructors.
        """
        del start_ignore_layout, end_ignore_layout
        return Ignore()

    def process_family(f):
        """
        Process a LexerFamilyDecl node. Register the token family and process
        the rules it contains.

        :type f: liblktlang.LexerFamilyDecl
        """
        with ctx.lkt_context(f):
            # Create the token family, if needed
            name = names.Name.from_lower(text_as_str(f.f_syn_name))
            token_set = token_family_sets.setdefault(name, set())

            for r in f.f_rules:
                check_source_language(
                    isinstance(r.f_decl, liblktlang.GrammarRuleDecl),
                    'Only lexer rules allowed in family blocks')
                process_token_rule(r, token_set)

    def process_token_rule(r, token_set=None):
        """
        Process the full declaration of a GrammarRuleDecl node: create the
        token it declares and lower the optional associated lexing rule.

        :param liblktlang.FullDecl r: Full declaration for the GrammarRuleDecl
            to process.
        :param None|set[TokenAction] token_set: If this declaration appears in
            the context of a token family, this adds the new token to this set.
            Must be left to None otherwise.
        """
        with ctx.lkt_context(r):
            rule_annot = parse_annotations(ctx, token_annotations, r)

            # Gather token action info from the annotations. If absent,
            # fallback to WithText.
            token_cons = None
            start_ignore_layout = False
            end_ignore_layout = False
            if 'ignore' in rule_annot:
                token_cons = ignore_constructor
            for name in ('text', 'trivia', 'symbol'):
                try:
                    start_ignore_layout, end_ignore_layout = rule_annot[name]
                except KeyError:
                    continue

                check_source_language(token_cons is None,
                                      'At most one token action allowed')
                token_cons = token_cls_map[name]
            is_pre = rule_annot.get('pre_rule', False)
            if token_cons is None:
                token_cons = WithText

            # Create the token and register it where needed: the global token
            # mapping, its token family (if any) and the "newline_after" group
            # if the corresponding annotation is present.
            token_lower_name = text_as_str(r.f_decl.f_syn_name)
            token_name = names.Name.from_lower(token_lower_name)

            check_source_language(
                token_lower_name not in ('termination', 'lexing_failure'),
                '{} is a reserved token name'.format(token_lower_name))
            check_source_language(token_name not in tokens,
                                  'Duplicate token name')

            token = token_cons(start_ignore_layout, end_ignore_layout)
            tokens[token_name] = token
            if token_set is not None:
                token_set.add(token)
            if 'newline_after' in rule_annot:
                newline_after.append(token)

            # Lower the lexing rule, if present
            matcher_expr = r.f_decl.f_expr
            if matcher_expr is not None:
                rule = (lower_matcher(matcher_expr), token)
                if is_pre:
                    pre_rules.append(rule)
                else:
                    rules.append(rule)

    def process_pattern(full_decl):
        """
        Process a pattern declaration.

        :param liblktlang.FullDecl r: Full declaration for the ValDecl to
            process.
        """
        parse_annotations(ctx, [], full_decl)
        decl = full_decl.f_decl
        lower_name = text_as_str(decl.f_syn_name)
        name = names.Name.from_lower(lower_name)

        with ctx.lkt_context(decl):
            check_source_language(name not in patterns,
                                  'Duplicate pattern name')
            check_source_language(
                decl.f_decl_type is None,
                'Patterns must have automatic types in'
                ' lexers')
            check_source_language(
                isinstance(decl.f_val, liblktlang.StringLit)
                and decl.f_val.p_is_regexp_literal,
                'Pattern string literal expected')
            # TODO: use StringLit.p_denoted_value when properly implemented
            patterns[name] = pattern_as_str(decl.f_val)

    def lower_matcher(expr):
        """
        Lower a token matcher to our internals.

        :type expr: liblktlang.GrammarExpr
        :rtype: langkit.lexer.Matcher
        """
        with ctx.lkt_context(expr):
            if isinstance(expr, liblktlang.TokenLit):
                return Literal(json.loads(text_as_str(expr)))
            elif isinstance(expr, liblktlang.TokenNoCaseLit):
                return NoCaseLit(json.loads(text_as_str(expr)))
            elif isinstance(expr, liblktlang.TokenPatternLit):
                return Pattern(pattern_as_str(expr))
            else:
                check_source_language(False, 'Invalid lexing expression')

    def lower_token_ref(ref):
        """
        Return the Token that `ref` refers to.

        :type ref: liblktlang.RefId
        :rtype: Token
        """
        with ctx.lkt_context(ref):
            token_name = names.Name.from_lower(text_as_str(ref))
            check_source_language(token_name in tokens,
                                  'Unknown token: {}'.format(token_name.lower))
            return tokens[token_name]

    def lower_family_ref(ref):
        """
        Return the TokenFamily that `ref` refers to.

        :type ref: liblktlang.RefId
        :rtype: TokenFamily
        """
        with ctx.lkt_context(ref):
            name_lower = text_as_str(ref)
            name = names.Name.from_lower(name_lower)
            check_source_language(
                name in token_families,
                'Unknown token family: {}'.format(name_lower))
            return token_families[name]

    def lower_case_alt(alt):
        """
        Lower the alternative of a case lexing rule.

        :type alt: liblktlang.BaseLexerCaseRuleAlt
        :rtype: Alt
        """
        prev_token_cond = None
        if isinstance(alt, liblktlang.LexerCaseRuleCondAlt):
            prev_token_cond = [
                lower_token_ref(ref) for ref in alt.f_cond_exprs
            ]
        return Alt(prev_token_cond=prev_token_cond,
                   send=lower_token_ref(alt.f_send.f_sent),
                   match_size=int(alt.f_send.f_match_size.text))

    # Go through all rules to register tokens, their token families and lexing
    # rules.
    for full_decl in full_lexer.f_decl.f_rules:
        with ctx.lkt_context(full_decl):
            if isinstance(full_decl, liblktlang.LexerFamilyDecl):
                # This is a family block: go through all declarations inside it
                process_family(full_decl)

            elif isinstance(full_decl, liblktlang.FullDecl):
                # There can be various types of declarations in lexers...
                decl = full_decl.f_decl

                if isinstance(decl, liblktlang.GrammarRuleDecl):
                    # Here, we have a token declaration, potentially associated
                    # with a lexing rule.
                    process_token_rule(full_decl)

                elif isinstance(decl, liblktlang.ValDecl):
                    # This is the declaration of a pattern
                    process_pattern(full_decl)

                else:
                    check_source_language(False,
                                          'Unexpected declaration in lexer')

            elif isinstance(full_decl, liblktlang.LexerCaseRule):
                syn_alts = list(full_decl.f_alts)

                # This is a rule for conditional lexing: lower its matcher and
                # its alternative rules.
                matcher = lower_matcher(full_decl.f_expr)
                check_source_language(
                    len(syn_alts) == 2 and isinstance(
                        syn_alts[0], liblktlang.LexerCaseRuleCondAlt)
                    and isinstance(syn_alts[1],
                                   liblktlang.LexerCaseRuleDefaultAlt),
                    'Invalid case rule topology')
                rules.append(
                    Case(matcher, lower_case_alt(syn_alts[0]),
                         lower_case_alt(syn_alts[1])))

            else:
                # The grammar should make the following dead code
                assert False, 'Invalid lexer rule: {}'.format(full_decl)

    # Create the LexerToken subclass to define all tokens and token families
    items = {}
    for name, token in tokens.items():
        items[name.camel] = token
    for name, token_set in token_family_sets.items():
        tf = TokenFamily(*list(token_set))
        token_families[name] = tf
        items[name.camel] = tf
    token_class = type('Token', (LexerToken, ), items)

    # Create the Lexer instance and register all patterns and lexing rules
    result = Lexer(token_class, 'track_indent' in lexer_annot, pre_rules)
    for name, regexp in patterns.items():
        result.add_patterns((name.lower, regexp))
    result.add_rules(*rules)

    # Register spacing/newline rules
    for tf1, tf2 in lexer_annot.get('spacing', []):
        result.add_spacing((lower_family_ref(tf1), lower_family_ref(tf2)))
    result.add_newline_after(*newline_after)

    return result
Example #25
0
    def do_perf_test(self, args):
        """
        Run the performance regression testsuite.
        """
        from time import time

        self.set_context(args)

        def file_lines(filename):
            with open(filename) as f:
                return len(list(f))

        check_source_language(
            not os.path.isabs(args.build_dir),
            "--build-dir should be a relative path for perf testsuite"
        )

        work_dir = os.path.abspath(args.work_dir)
        variant_name = args.build_dir
        report_file = os.path.join(work_dir,
                                   'report-{}.txt'.format(variant_name))
        args.build_dir = os.path.join(work_dir, args.build_dir)

        if not args.no_recompile:
            # The perf testsuite only needs the "parse" main program
            args.disable_mains = self.main_programs - {'parse'}

            # Build libadalang in production mode inside of the perf testsuite
            # directory.
            self.dirs.set_build_dir(args.build_dir)
            args.build_mode = 'prod'
            self._mkdir(args.build_dir)
            self.do_make(args)

        # Checkout the code bases that we will use for the perf testsuite
        source_dir = os.path.join(work_dir, "source")
        try:
            os.mkdir(source_dir)
        except OSError:
            pass
        os.chdir(source_dir)
        if not os.path.exists('gnat'):
            subprocess.check_call([
                'svn', 'co',
                'svn+ssh://svn.us.adacore.com/Dev/trunk/gnat',
                '-r', '314163',
                '--ignore-externals'
            ])
        if not os.path.exists('gps'):
            subprocess.check_call(['git', 'clone',
                                   'ssh://review.eu.adacore.com:29418/gps'])
        os.chdir('gps')
        subprocess.check_call(['git', 'checkout',
                               '00b73897a867514732d48ae1429faf97fb07ad7c'])
        os.chdir('..')

        # Make a list of every ada file

        # Exclude some files that are contained here but that we do not parse
        # correctly.
        excluded_patterns = ['@', 'a-numeri', 'rad-project']
        ada_files = filter(
            lambda f: all(map(lambda p: p not in f, excluded_patterns)),
            self._find_ada_sources(source_dir)
        )
        file_list_name = 'ada_file_list'
        with open(file_list_name, 'w') as file_list:
            for f in ada_files:
                file_list.write(f + '\n')

        # Get a count of the total number of ada source lines
        lines_count = sum(map(file_lines, ada_files))

        with open(report_file, 'w') as f:
            def write_report(text, color=None):
                if color:
                    printcol(text, color)
                else:
                    print(text)
                print(text, file=f)

            write_report('=================================', Colors.HEADER)
            write_report('= Performance testsuite results =', Colors.HEADER)
            write_report('=================================', Colors.HEADER)
            write_report('')
            write_report('Name: {}'.format(variant_name))
            write_report('Scenario: {}'.format(args.scenario))
            write_report('')
            elapsed_list = []
            parse_args = ['{}/bin/parse'.format(args.build_dir), '-s', '-F',
                          file_list_name]
            if args.scenario == self.PERF_PARSE_AND_TRAVERSE:
                parse_args.append('-C')
            if args.with_trivia:
                parse_args.append('-P')
            for _ in range(args.nb_runs):
                # Execute parse on the file list and get the elapsed time
                t = time()
                subprocess.check_call(parse_args)
                elapsed = time() - t
                elapsed_list.append(elapsed)

                # Print a very basic report
                write_report(
                    'Parsed {0} lines of Ada code in {1:.2f} seconds'.format(
                        lines_count, elapsed
                    )
                )

            write_report('')
            write_report('= Performance summary =', Colors.OKGREEN)
            write_report(
                'Mean time to parse {0} lines of code:'
                ' {1:.2f} seconds'.format(
                    lines_count, sum(elapsed_list) / float(len(elapsed_list))
                )
            )
Example #26
0
    def from_parser(node, parser):
        """
        Given a parser that creates a specific type of parse node, return the
        corresponding unparser. Emit a user diagnostic if this transformation
        cannot be made.

        :param ASTNodeType node: Parse node that `parser` emits.
        :param Parser parser: Parser for which we want to create an unparser.
        :rtype: NodeUnparser
        """
        assert not node.abstract and not node.synthetic, (
            'Invalid unparser request for {}'.format(node.dsl_name)
        )
        parser = unwrap(parser)

        with parser.diagnostic_context:
            if node.is_token_node:
                return NodeUnparser._from_token_node_parser(node, parser)

            if isinstance(parser, _Transform):
                return NodeUnparser._from_transform_parser(node, parser)

            if isinstance(parser, List):
                check_source_language(
                    isinstance(parser.parser, (Defer, List, Null, Or,
                                               _Transform)),
                    'Unparsers generation require list parsers to directly'
                    ' build nodes for each list item'
                )
                return ListNodeUnparser(
                    node,
                    TokenUnparser.from_parser(parser.sep)
                )

            if isinstance(parser, Opt):
                if parser._booleanize:
                    # This is a special parser: when the subparser succeeds,
                    # the "present" alternative is created to hold its result,
                    # otherwise the "absent" alternative is created (and no
                    # token are consumed).
                    #
                    # So in both cases, we create an unparser, but we emit
                    # tokens only for the "present" alternative.
                    result = RegularNodeUnparser(node)
                    if node is parser._booleanize.alt_present.type:
                        NodeUnparser._emit_to_token_sequence(parser.parser,
                                                             result.pre_tokens)
                    return result

                else:
                    return NodeUnparser.from_parser(node, parser.parser)

            if isinstance(parser, Null):
                return NullNodeUnparser(node)

            check_source_language(
                False,
                'Unsupported parser for unparsers generation: {}'.format(
                    parser
                )
            )
Example #27
0
    def _parse_sequence(cls, stream):
        """
        Parse a sequence of regexps. Stop at the first unmatched parenthesis or
        at the first top-level pipe character.

        :param file stream: Input regexp stream.
        :rtype: RegexpCollection.Parser
        """
        subparsers = []
        while True:
            if stream.eof or stream.next_is('|', ')'):
                break

            elif stream.next_is('('):
                # Nested group: recursively parse alternatives
                stream.read()
                subparsers.append(cls._parse_or(stream))
                check_source_language(stream.next_is(')'),
                                      'unbalanced parenthesis')
                stream.read()

            elif stream.next_is('['):
                # Parse a range of characters
                subparsers.append(cls._parse_range(stream))

            elif stream.next_is('{'):
                # Parse a reference to a named pattern
                stream.read()
                name = ''
                while not stream.eof and not stream.next_is('}'):
                    name += stream.read()
                check_source_language(stream.next_is('}'),
                                      'unbalanced bracket')
                stream.read()
                check_source_language(rule_name_re.match(name),
                                      'invalid rule name: {}'.format(name))
                subparsers.append(cls.Defer(name))

            elif stream.next_is('*', '+', '?'):
                # Repeat the previous sequence item
                check_source_language(subparsers, 'nothing to repeat')
                check_source_language(
                    not isinstance(subparsers[-1], cls.Repeat),
                    'multiple repeat')
                wrapper = {
                    '*': lambda p: cls.Repeat(p),
                    '+': lambda p: cls.Sequence([p, cls.Repeat(p)]),
                    '?': lambda p: cls.Opt(p)
                }[stream.read()]
                subparsers[-1] = wrapper(subparsers[-1])

            elif stream.next_is('.'):
                # Generally, "." designates any character *except* newlines. Do
                # the same here.
                stream.read()
                subparsers.append(cls.Range(CharSet('\n').negation))

            elif stream.next_is('^', '$'):
                check_source_language(
                    False, 'matching beginning or ending is unsupported')

            elif stream.next_is('\\'):
                # Parse an escape sequence. In can be a Unicode character, a
                # Unicode property or a simple escape sequence.
                stream.read()

                # \p and \P refer to character sets from Unicode general
                # categories.
                if stream.next_is('p', 'P'):
                    action = stream.read()

                    # Read the category name, which must appear between curly
                    # brackets.
                    category = ''
                    check_source_language(
                        stream.next_is('{'),
                        'incomplete Unicode category matcher')
                    stream.read()
                    while not stream.eof and not stream.next_is('}'):
                        category += stream.read()
                    check_source_language(
                        stream.next_is('}'),
                        'incomplete Unicode category matcher')
                    stream.read()

                    try:
                        char_set = CharSet.for_category(category)
                    except KeyError:
                        check_source_language(
                            False,
                            'invalid Unicode category: {}'.format(category))
                    if action == 'P':
                        char_set = char_set.negation
                    subparsers.append(cls.Range(char_set))

                else:
                    stream.go_back()
                    subparsers.append(
                        cls.Range(CharSet.from_int(cls._read_escape(stream))))

            else:
                subparsers.append(cls.Range(CharSet(stream.read())))

        return cls.Sequence(subparsers)
Example #28
0
 def check(self):
     # Check is not normally called on this, so if it is called it means
     # that a CallEnvHook instance has found its way into a regular action
     # list.
     check_source_language(
         "set_initial_env must be first in the action list")
Example #29
0
 def check(self):
     # Check is not normally called on this, so if it is called it means
     # that a SetInitialEnv instance has found its way into a regular action
     # list.
     check_source_language(
         "set_initial_env can only be preceded by call_env_hook")
Example #30
0
    def construct(self):
        """
        Construct a resolved expression for this.

        :rtype: ResolvedExpression
        """
        # Add the variables created for this expression to the current scope
        scope = PropertyDef.get_scope()
        for _, var, _ in self.matchers:
            scope.add(var.local_var)

        matched_expr = construct(self.matched_expr)
        check_source_language(issubclass(matched_expr.type, ASTNode)
                              or matched_expr.type.is_env_element_type,
                              'Match expressions can only work on AST nodes '
                              'or env elements')

        # Create a local variable so that in the generated code, we don't have
        # to re-compute the prefix for each type check.
        matched_abstract_var = AbstractVariable(
            names.Name('Match_Prefix'),
            type=matched_expr.type,
            create_local=True
        )
        PropertyDef.get_scope().add(matched_abstract_var.local_var)
        matched_var = construct(matched_abstract_var)

        constructed_matchers = []

        # Check (i.e. raise an error if no true) the set of matchers is valid:

        # * all matchers must target allowed types, i.e. input type subclasses;
        for typ, var, expr in self.matchers:
            if typ is not None:
                check_source_language(
                    typ.matches(matched_expr.type),
                    'Cannot match {} (input type is {})'.format(
                        typ.name().camel,
                        matched_expr.type.name().camel
                    )
                )
            else:
                # The default matcher (if any) matches the most general type,
                # which is the input type.
                var.set_type(matched_expr.type)
            constructed_matchers.append((construct(var), construct(expr)))

        # * all possible input types must have at least one matcher. Also warn
        #   if some matchers are unreachable.
        self._check_match_coverage(matched_expr.type)

        # Compute the return type as the unification of all branches
        _, expr = constructed_matchers[-1]
        rtype = expr.type
        for _, expr in constructed_matchers:
            check_source_language(
                expr.type.matches(rtype), "Wrong type for match result"
                " expression: got {} but expected {} or sub/supertype".format(
                    expr.type.name().camel, rtype.name().camel
                )
            )
            rtype = expr.type.unify(rtype)

        # This is the expression execution will reach if we have a bug in our
        # code (i.e. if matchers did not cover all cases).
        result = UnreachableExpr(rtype)

        # Wrap this "failing" expression with all the cases to match in the
        # appropriate order, so that in the end the first matchers are tested
        # first.
        for match_var, expr in reversed(constructed_matchers):
            casted = Cast.Expr(matched_var,
                               match_var.type,
                               result_var=match_var)
            guard = Not.make_expr(
                Eq.make_expr(
                    casted, LiteralExpr(casted.type.nullexpr(), casted.type)
                )
            )
            if expr.type != rtype:
                # We already checked that type matches, so only way this is
                # true is if expr.type is an ASTNode type derived from
                # rtype. In that case, we need an explicity upcast.
                expr = Cast.Expr(expr, rtype)

            result = If.Expr(guard, expr, result, rtype)

        return Let.Expr(
            [matched_var],
            [matched_expr],
            BindingScope(result,
                         [construct(var) for _, var, _ in self.matchers])
        )
Example #31
0
    def construct_common(self):
        """
        Construct and return the expressions commonly needed by collection
        expression subclasses.

        :rtype: CollectionExpression.ConstructCommonResult
        """
        current_scope = PropertyDef.get_scope()

        # First, build the collection expression. From the result, we can
        # deduce the type of the element variable.
        collection_expr = construct(self.collection)
        with_entities = collection_expr.type.is_entity_type
        if with_entities:
            saved_entity_coll_expr, collection_expr, entity_info = (
                collection_expr.destructure_entity())
            collection_expr = SequenceExpr(saved_entity_coll_expr,
                                           collection_expr)

        check_source_language(
            collection_expr.type.is_collection,
            'Cannot iterate on {}, which is not a collection'.format(
                collection_expr.type.dsl_name))

        elt_type = collection_expr.type.element_type
        if with_entities:
            elt_type = elt_type.entity
        self.element_var.set_type(elt_type)

        # List of "element" iteration variables
        elt_vars = [construct(self.element_var)]

        # List of initializing expressions for them
        elt_var_inits = []

        if with_entities:
            entity_var = elt_vars[-1]
            node_var = AbstractVariable(names.Name('Bare') +
                                        self.element_var._name,
                                        type=elt_type.element_type)
            elt_var_inits.append(
                make_as_entity(construct(node_var), entity_info=entity_info))
            elt_vars.append(construct(node_var))

        # If we are iterating over an AST list, then we get root grammar typed
        # values. We need to convert them to the more specific type to get the
        # rest of the expression machinery work.
        if collection_expr.type.is_list_type:
            typed_elt_var = elt_vars[-1]
            untyped_elt_var = AbstractVariable(
                names.Name('Untyped') + self.element_var._name,
                type=get_context().root_grammar_class)
            # Initialize the former last variable with a cast from the new last
            # variable and push the new last variable.
            elt_var_inits.append(
                UncheckedCastExpr(construct(untyped_elt_var),
                                  typed_elt_var.type))
            elt_vars.append(construct(untyped_elt_var))

        # Only then we can build the inner expression
        with current_scope.new_child() as inner_scope:
            inner_expr = construct(self.expr)

        if with_entities:
            entity_var.abstract_var.create_local_variable(inner_scope)
        if collection_expr.type.is_list_type:
            typed_elt_var.abstract_var.create_local_variable(inner_scope)

        if self.index_var:
            self.index_var.add_to_scope(inner_scope)

        elt_var_inits.append(None)

        return self.ConstructCommonResult(
            collection_expr, funcy.lzip(elt_vars, elt_var_inits),
            construct(self.index_var) if self.index_var else None, inner_expr,
            inner_scope)
Example #32
0
 def do_prepare(self):
     self.astnode = resolve_type(self.astnode)
     check_source_language(self.astnode.matches(ASTNode), (
         "One can only cast to an ASTNode subtype"
     ))
Example #33
0
    def process_token_rule(r, token_set=None):
        """
        Process the full declaration of a GrammarRuleDecl node: create the
        token it declares and lower the optional associated lexing rule.

        :param liblktlang.FullDecl r: Full declaration for the GrammarRuleDecl
            to process.
        :param None|set[TokenAction] token_set: If this declaration appears in
            the context of a token family, this adds the new token to this set.
            Must be left to None otherwise.
        """
        with ctx.lkt_context(r):
            rule_annot = parse_annotations(ctx, token_annotations, r)

            # Gather token action info from the annotations. If absent,
            # fallback to WithText.
            token_cons = None
            start_ignore_layout = False
            end_ignore_layout = False
            if 'ignore' in rule_annot:
                token_cons = ignore_constructor
            for name in ('text', 'trivia', 'symbol'):
                try:
                    start_ignore_layout, end_ignore_layout = rule_annot[name]
                except KeyError:
                    continue

                check_source_language(token_cons is None,
                                      'At most one token action allowed')
                token_cons = token_cls_map[name]
            is_pre = rule_annot.get('pre_rule', False)
            if token_cons is None:
                token_cons = WithText

            # Create the token and register it where needed: the global token
            # mapping, its token family (if any) and the "newline_after" group
            # if the corresponding annotation is present.
            token_lower_name = text_as_str(r.f_decl.f_syn_name)
            token_name = names.Name.from_lower(token_lower_name)

            check_source_language(
                token_lower_name not in ('termination', 'lexing_failure'),
                '{} is a reserved token name'.format(token_lower_name))
            check_source_language(token_name not in tokens,
                                  'Duplicate token name')

            token = token_cons(start_ignore_layout, end_ignore_layout)
            tokens[token_name] = token
            if token_set is not None:
                token_set.add(token)
            if 'newline_after' in rule_annot:
                newline_after.append(token)

            # Lower the lexing rule, if present
            matcher_expr = r.f_decl.f_expr
            if matcher_expr is not None:
                rule = (lower_matcher(matcher_expr), token)
                if is_pre:
                    pre_rules.append(rule)
                else:
                    rules.append(rule)
Example #34
0
 def lib_name(self, lib_name):
     check_source_language(self.LIB_NAME_RE.match(lib_name),
                           'Invalid library name: {}'.format(lib_name))
     self._lib_name = lib_name
Example #35
0
    def lower(rule):
        """
        Helper to lower one parser.

        :param liblktlang.GrammarExpr rule: Grammar rule to lower.
        :rtype: Parser
        """
        # For convenience, accept null input rules, as we generally want to
        # forward them as-is to the lower level parsing machinery.
        if rule is None:
            return None

        loc = ctx.lkt_loc(rule)
        with ctx.lkt_context(rule):
            if isinstance(rule, liblktlang.ParseNodeExpr):
                node = resolve_node_ref(rule.f_node_name)

                # Lower the subparsers
                subparsers = [
                    lower(subparser) for subparser in rule.f_sub_exprs
                ]

                # Qualifier nodes are a special case: we produce one subclass
                # or the other depending on whether the subparsers accept the
                # input.
                if node._type.is_bool_node:
                    return Opt(*subparsers, location=loc).as_bool(node)

                # Likewise for enum nodes
                elif node._type.base and node._type.base.is_enum_node:
                    return _Transform(_Row(*subparsers, location=loc),
                                      node.type_ref,
                                      location=loc)

                # For other nodes, always create the node when the subparsers
                # accept the input.
                else:
                    return _Transform(parser=_Row(*subparsers),
                                      typ=node,
                                      location=loc)

            elif isinstance(rule, liblktlang.GrammarToken):
                token_name = rule.f_token_name.text
                try:
                    val = tokens[token_name]
                except KeyError:
                    check_source_language(
                        False, 'Unknown token: {}'.format(token_name))

                match_text = ''
                if rule.f_expr:
                    # The grammar is supposed to mainain this invariant
                    assert isinstance(rule.f_expr, liblktlang.TokenLit)
                    match_text = denoted_string_literal(rule.f_expr)

                return _Token(val=val, match_text=match_text, location=loc)

            elif isinstance(rule, liblktlang.TokenLit):
                return _Token(denoted_string_literal(rule), location=loc)

            elif isinstance(rule, liblktlang.GrammarList):
                return List(lower(rule.f_expr),
                            empty_valid=rule.f_kind.text == '*',
                            list_cls=resolve_node_ref(rule.f_list_type),
                            sep=lower(rule.f_sep),
                            location=loc)

            elif isinstance(
                    rule,
                (liblktlang.GrammarImplicitPick, liblktlang.GrammarPick)):
                return Pick(*[lower(subparser) for subparser in rule.f_exprs],
                            location=loc)

            elif isinstance(rule, liblktlang.GrammarRuleRef):
                return getattr(grammar, rule.f_node_name.text)

            elif isinstance(rule, liblktlang.GrammarOrExpr):
                return Or(
                    *[lower(subparser) for subparser in rule.f_sub_exprs],
                    location=loc)

            elif isinstance(rule, liblktlang.GrammarOpt):
                return Opt(lower(rule.f_expr), location=loc)

            elif isinstance(rule, liblktlang.GrammarOptGroup):
                return Opt(*[lower(subparser) for subparser in rule.f_expr],
                           location=loc)

            elif isinstance(rule, liblktlang.GrammarExprList):
                return Pick(*[lower(subparser) for subparser in rule],
                            location=loc)

            elif isinstance(rule, liblktlang.GrammarDiscard):
                return Discard(lower(rule.f_expr), location=loc)

            elif isinstance(rule, liblktlang.GrammarNull):
                return Null(resolve_node_ref(rule.f_name), location=loc)

            elif isinstance(rule, liblktlang.GrammarSkip):
                return Skip(resolve_node_ref(rule.f_name), location=loc)

            elif isinstance(rule, liblktlang.GrammarDontSkip):
                return DontSkip(lower(rule.f_expr),
                                lower(rule.f_dont_skip),
                                location=loc)

            elif isinstance(rule, liblktlang.GrammarPredicate):
                check_source_language(
                    isinstance(rule.f_prop_ref, liblktlang.DotExpr),
                    'Invalid property reference')
                node = resolve_node_ref(rule.f_prop_ref.f_prefix)
                prop_name = rule.f_prop_ref.f_suffix.text
                try:
                    prop = getattr(node, prop_name)
                except AttributeError:
                    check_source_language(
                        False, '{} has no {} property'.format(
                            node._name.camel_with_underscores, prop_name))
                return Predicate(lower(rule.f_expr), prop, location=loc)

            else:
                raise NotImplementedError('unhandled parser: {}'.format(rule))
Example #36
0
 def error_if_not_empty(name_set, message):
     check_source_language(
         not name_set,
         ('{}: {}'.format(message, ', '.join(name
                                             for name in name_set))))
Example #37
0
    def construct_common(self) -> CollectionExpression.ConstructCommonResult:
        """
        Construct and return the expressions commonly needed by collection
        expression subclasses.
        """
        assert self.element_var is not None

        current_scope = PropertyDef.get_scope()

        # Because of the discrepancy between the storage type in list nodes
        # (always root nodes) and the element type that user code deals with
        # (non-root list elements and/or entities), we may need to introduce
        # variables and initializing expressions. This is what the code below
        # does.

        # First, build the collection expression. From the result, we can
        # deduce the type of the user element variable.
        collection_expr = construct(self.collection)

        # If the collection is actually an entity, unwrap the bare list node
        # and save the entity info for later.
        with_entities = collection_expr.type.is_entity_type
        if with_entities:
            saved_entity_coll_expr, collection_expr, entity_info = (
                collection_expr.destructure_entity()
            )
            collection_expr = SequenceExpr(saved_entity_coll_expr,
                                           collection_expr)

        check_source_language(
            collection_expr.type.is_collection,
            'Cannot iterate on {}, which is not a collection'.format(
                collection_expr.type.dsl_name
            )
        )

        # Now that potential entity types are unwrapped, we can look for its
        # element type.
        elt_type = collection_expr.type.element_type
        if with_entities:
            elt_type = elt_type.entity
        self.element_var.set_type(elt_type)
        user_element_var = construct(self.element_var)

        # List of element variables, and the associated initialization
        # expressions (when applicable).
        #
        # Start with the only element variable that exists at this point: the
        # one that the user code for each iteration uses directly. When
        # relevant, each step in the code below creates a new variable N and
        # initialize variable N-1 from it.
        element_vars: List[InitializedVar] = [InitializedVar(user_element_var)]

        # Node lists contain bare nodes: if the user code deals with entities,
        # create a variable to hold a bare node and initialize the user
        # variable using it.
        if with_entities:
            entity_var = element_vars[-1]
            node_var = AbstractVariable(
                names.Name('Bare') + self.element_var._name,
                type=elt_type.element_type
            )
            entity_var.init_expr = make_as_entity(
                construct(node_var), entity_info=entity_info
            )
            element_vars.append(InitializedVar(construct(node_var)))

        # Node lists contain root nodes: if the user code deals with non-root
        # nodes, create a variable to hold the root bare node and initialize
        # the non-root node using it.
        if (
            collection_expr.type.is_list_type
            and not collection_expr.type.is_root_node
        ):
            typed_elt_var = element_vars[-1]
            untyped_elt_var = AbstractVariable(
                names.Name('Untyped') + self.element_var._name,
                type=get_context().root_grammar_class
            )
            typed_elt_var.init_expr = UncheckedCastExpr(
                construct(untyped_elt_var), typed_elt_var.var.type
            )
            element_vars.append(InitializedVar(construct(untyped_elt_var)))

        # Keep track of the ultimate "codegen" element variable. Unlike all
        # other iteration variable, it is the only one that will be defined by
        # the "for" loop in Ada (the other ones must be declared as regular
        # local variables).
        codegen_element_var = element_vars[-1].var

        # Create a scope to contain the code that runs during an iteration and
        # lower the iteration expression.
        with current_scope.new_child() as inner_scope:
            inner_expr = construct(self.expr)

        # Build the list of all iteration variables
        iter_vars = list(element_vars)
        index_var = None
        if self.index_var:
            index_var = construct(self.index_var)
            iter_vars.append(InitializedVar(index_var))

        # Create local variables for all iteration variables that need it
        for v in iter_vars:
            if v.var != codegen_element_var:
                v.var.abstract_var.create_local_variable(inner_scope)

        return self.ConstructCommonResult(
            collection_expr,
            codegen_element_var,
            user_element_var,
            index_var,
            iter_vars,
            inner_expr,
            inner_scope,
        )
Example #38
0
 def check_array(typ: CompiledType) -> None:
     check_source_language(
         typ.is_array_type,
         "Expected array type, got {}".format(typ.dsl_name)
     )
Example #39
0
    def compute_types(self):
        """
        Compute various information related to compiled types, that needs to be
        available for code generation.
        """

        # Get the list of ASTNode types from the Struct metaclass
        from langkit.compiled_types import (
            EnvElement, LexicalEnvType, StructMetaclass
        )

        self.astnode_types = list(StructMetaclass.astnode_types)

        # Here we're skipping Struct because it's not a real type in
        # generated code. We're also putting env_metadata and EnvElement in
        # the beginning and in the right dependency order (the metadata
        # type before the env element type).
        # TODO: Using a dependency order topological sort wouldn't hurt at
        # some point.
        self.struct_types = [
            t for t in StructMetaclass.struct_types
            if t not in [EnvElement, StructMetaclass.env_metadata]
        ]
        self.struct_types.insert(0, EnvElement)

        if StructMetaclass.env_metadata:
            self.struct_types = (
                [StructMetaclass.env_metadata] + self.struct_types
            )

        self.root_grammar_class = StructMetaclass.root_grammar_class
        self.env_metadata = StructMetaclass.env_metadata
        self.env_element = EnvElement

        # The Group lexical environment operation takes an array of lexical
        # envs, so we always need to generate the corresponding array type.
        self.array_types.add(LexicalEnvType.array_type())

        # Likewise for the EnvElement array type: LexicalEnv.get returns it.
        # No need to bind anything if the language specification did not
        # specify any EnvElement, though.
        if self.env_element:
            self.array_types.add(EnvElement.array_type())

        # Sort them in dependency order as required but also then in
        # alphabetical order so that generated declarations are kept in a
        # relatively stable order. This is really useful for debugging
        # purposes.
        keys = {
            cls: cls.hierarchical_name()
            for cls in self.astnode_types
        }
        self.astnode_types.sort(key=lambda cls: keys[cls])

        # Check that the environment hook is bound if the language spec uses
        # it.
        if self.env_hook_subprogram is None:
            for t in self.astnode_types:
                with t.diagnostic_context():
                    check_source_language(
                        t.env_spec is None or not t.env_spec.env_hook_enabled,
                        'Cannot invoke the environment hook if'
                        ' CompileContext.bind_env_hook has not been called'
                    )
Example #40
0
    def process_subclass(mcs, name, bases, dct, is_root):
        from langkit.envs import EnvSpec

        location = extract_library_location()
        base = bases[0]
        is_list_type = issubclass(base, _ASTNodeList)
        is_root_list_type = base is _ASTNodeList

        node_ctx = Context('in {}'.format(name), location)

        with node_ctx:
            check_source_language(
                len(bases) == 1, 'ASTNode subclasses must have exactly one'
                ' base class')
            if mcs.root_type is not None:
                check_source_language(
                    base is not ASTNode,
                    'Only one class can derive from ASTNode (previous was:'
                    ' {})'.format(mcs.root_type.__name__))

            env_spec = dct.pop('env_spec', None)
            check_source_language(
                env_spec is None or isinstance(env_spec, EnvSpec),
                'Invalid environment specification: {}'.format(env_spec))

            annotations = dct.pop('annotations', None)

        # If this is a list type, determine the corresponding element type
        if is_list_type:
            element_type = (dct.pop('_element_type')
                            if is_root_list_type else base._element_type)
            allowed_field_types = PropertyDef
        else:
            element_type = None
            allowed_field_types = AbstractNodeData

        # Determine if this is a token node
        with node_ctx:
            is_token_node = dct.pop('token_node', None)
            check_source_language(
                is_token_node is None or isinstance(is_token_node, bool),
                'The "token_node" field, when present, must contain a boolean')

            # If "token_node" allocation is left to None, inherit it (default
            # is False).
            if is_token_node is None:
                is_token_node = bool(base._is_token_node)

            if is_token_node:
                allowed_field_types = (_UserField, PropertyDef)
            else:
                # Make sure that all derivations of a token node are token
                # nodes themselves.
                check_source_language(
                    not base._is_token_node,
                    '"token_node" annotation inconsistent with inherited AST'
                    ' node')

        # Handle enum nodes
        with node_ctx:
            # Forbid inheriting from an enum node
            check_source_language(
                not base._is_enum_node,
                'Inheriting from an enum node is forbidden.')

            # Determine if this is an enum node
            is_enum_node = dct.pop('enum_node', False)
            check_source_language(
                isinstance(is_enum_node, bool),
                'The "enum_node" field, when present, must contain a boolean')

            if is_enum_node:
                qualifier = dct.pop('qualifier', False)
                if qualifier:
                    alternatives = ['present', 'absent']
                else:
                    alternatives = dct.pop('alternatives', None)
                    check_source_language(alternatives is not None,
                                          'Missing "alternatives" field')
                    check_source_language(
                        isinstance(alternatives, list)
                        and all(isinstance(alt, str) for alt in alternatives),
                        'The "alternatives" field must contain a list of '
                        'strings')

                alts = [
                    _EnumNodeAlternative(names.Name.from_lower(alt))
                    for alt in alternatives
                ]

                allowed_field_types = (_UserField, PropertyDef)

        fields = ASTNode.collect_fields(name, location, dct,
                                        allowed_field_types)

        DSLType._import_base_type_info(name, location, dct)

        if is_enum_node:
            mcs.import_enum_node_attributes(dct, qualifier, alts, fields)

        dct['_fields'] = fields
        dct['_base'] = base
        dct['_env_spec'] = env_spec
        dct['_is_token_node'] = is_token_node
        dct['_is_enum_node'] = is_enum_node

        # Make sure subclasses don't inherit the "list_type" cache from their
        # base classes.
        dct['_list_type'] = None
        dct['_element_type'] = element_type
        dct['_annotations'] = annotations

        cls = type.__new__(mcs, name, bases, dct)

        mcs.astnode_types.append(cls)

        # Create the corresponding ASTNodeType subclass
        if cls._base is _ASTNodeList:
            # Only root list types are supposed to directly subclass
            # _ASTNodeList.
            element_type = cls._element_type._resolve()
            assert element_type
            astnode_type = element_type.list
        else:
            astnode_type = ASTNodeType(
                cls._name,
                cls._location,
                cls._doc,
                base=None if is_root else cls._base._resolve(),
                fields=cls._fields,
                env_spec=cls._env_spec,
                annotations=cls._annotations,

                # Only enum nodes are abstract at this point
                is_abstract=cls._is_enum_node,
                is_enum_node=cls._is_enum_node,
                is_bool_node=cls._is_enum_node and cls._qualifier,
                is_token_node=cls._is_token_node)

        astnode_type.dsl_decl = cls
        cls._type = astnode_type

        if is_enum_node:
            mcs.create_enum_node_alternatives(cls, astnode_type)

        return cls
Example #41
0
    def construct(self):
        """
        Constructs a resolved expression that is the result of:

        - Resolving the receiver;
        - Getting its corresponding field.

        :rtype: FieldAccessExpr
        """

        receiver_expr = construct(self.receiver)
        check_source_language(
            issubclass(receiver_expr.type, Struct),
            '{} values have no field (accessed field was {})'.format(
                receiver_expr.type.name().camel,
                self.field
            )
        )

        to_get = assert_type(
            receiver_expr.type, Struct
        ).get_abstract_fields_dict().get(self.field, None)
        ":type: AbstractNodeField"

        # If still not found, there's a problem
        check_source_language(
            to_get is not None, "Type {} has no '{}' field or property".format(
                receiver_expr.type.__name__, self.field
            )
        )

        check_source_language(
            not to_get.is_internal,
            '{} is for internal use only'.format(to_get.qualname)
        )

        # Check that this property actually accepts these arguments and that
        # they are correctly typed.
        check_source_language(
            len(self.arguments) == len(to_get.explicit_arguments),
            'Invalid number of arguments in the call to {}:'
            ' {} expected but got {}'.format(
                to_get.qualname,
                len(to_get.explicit_arguments),
                len(self.arguments),
            )
        )

        arg_exprs = [
            construct(
                actual, formal.type,
                custom_msg='Invalid {} actual (#{}) for {}:'.format(
                    formal.name, i, to_get.qualname,
                ) + ' expected {expected} but got {expr_type}'
            ) for i, (actual, formal) in enumerate(
                zip(self.arguments, to_get.explicit_arguments), 1
            )
        ]

        ret = FieldAccess.Expr(receiver_expr, to_get, arg_exprs)
        return ret
Example #42
0
    def _compile(self):
        """
        Compile the language specification: perform legality checks and type
        inference.
        """
        # Compile the first time, do nothing next times
        if self.compiled:
            return
        self.compiled = True

        assert self.grammar, "Set grammar before compiling"

        if not self.grammar.rules.get(self.main_rule_name, None):
            close_matches = difflib.get_close_matches(
                self.main_rule_name, self.grammar.rules.keys()
            )

            with self.grammar.context():
                check_source_language(
                    False,
                    'Invalid rule name specified for main rule: "{}". '
                    '{}'.format(
                        self.main_rule_name,
                        'Did you mean "{}"?'.format(close_matches[0])
                        if close_matches else ""
                    )
                )

        unreferenced_rules = self.grammar.get_unreferenced_rules()

        check_source_language(
            not unreferenced_rules, "The following parsing rules are not "
            "used: {}".format(", ".join(sorted(unreferenced_rules))),
            severity=Severity.warning
        )

        # Compute type information, so that it is available for further
        # compilation stages.
        self.compute_types()
        errors_checkpoint()

        if self.verbosity.info:
            printcol("Compiling the grammar...", Colors.OKBLUE)

        with names.camel_with_underscores:
            # Compute the type of fields for types used in the grammar
            for r_name, r in self.grammar.rules.items():
                r.compute_fields_types()

            # Compute properties information, so that it is available for
            # further compilation stages.
            self.compute_properties()
            errors_checkpoint()

            for r_name, r in self.grammar.rules.items():
                r.compile()
                self.rules_to_fn_names[r_name] = r

        unresolved_types = set([t for t in self.astnode_types
                                if not t.is_type_resolved])
        check_source_language(
            not unresolved_types,
            "The following ASTNode subclasses are not type resolved. They are"
            " not used by the grammar, and their types not annotated:"
            " {}".format(", ".join(t.name().camel for t in unresolved_types))
        )

        astnodes_files = {
            path.abspath(inspect.getsourcefile(n)) for n in self.astnode_types
        }

        if self.annotate_fields_types:
            # Only import lib2to3 if the users needs it
            import lib2to3.main

            lib2to3.main.main(
                "langkit",
                ["-f", "annotate_fields_types",
                 "--no-diff", "-w"] + list(astnodes_files)
            )

        for i, astnode in enumerate(
            (astnode
             for astnode in self.astnode_types
             if not astnode.abstract),
            # Compute kind constants for all ASTNode concrete subclasses.
            # Start with 2: the constant 0 is reserved as an
            # error/uninitialized code and the constant 1 is reserved for all
            # ASTList nodes.
            start=2
        ):
            self.node_kind_constants[astnode] = i

        # Now that all Struct subclasses referenced by the grammar have been
        # typed, iterate over all declared subclasses to register the ones that
        # are unreachable from the grammar.  TODO: this kludge will eventually
        # disappear as part of OC22-016.
        for t in self.struct_types + self.astnode_types:
            t.add_to_context()

        errors_checkpoint()
Example #43
0
    def _emit_to_field_unparser(parser, field_unparser, pre_tokens,
                                post_tokens):
        """
        Considering ``field_unparser`` as a field unparser we are in the
        process of elaborating, and ``pre_tokens`` and ``post_tokens`` as the
        token sequences that surround this field, extract information from the
        given ``parser`` to complete them.

        If ``parser`` is anything else than a Null parser, set
        ``field_unparser.always_absent`` to True.

        Emit a user diagnostic if ``parser`` is too complex for this analysis.

        :param Parser parser: Parser to analyze.
        :param FieldUnparser field_unparser: Field unparser to complete.
        :param TokenSequenceUnparser pre_tokens: Token sequences to contain the
            list of tokens that appear before the field, whether or not the
            field is present. Tokens are inserted at the end of this sequence.
        :param TokenSequenceUnparser post_tokens: Token sequences to contain
            the list of tokens that appear after the field, whether or not the
            field is present. Tokens are inserted at the beginning of this
            sequence.
        """
        parser = unwrap(parser)

        # As all fields are nodes, previous validation passes made sure that
        # `parser` yields a parse node (potentially a null one).

        if isinstance(parser, (Defer, List, Null, _Transform)):
            # Field parsing goes directly to node creation, so there is no
            # pre/post sequences of tokens.
            field_unparser.always_absent = (field_unparser.always_absent
                                            and isinstance(parser, Null))

        elif isinstance(parser, Opt):
            if not parser._booleanize:
                # Because we are in an Opt parser, we now know that this field
                # is optionnal, so it can be absent.
                field_unparser.always_absent = False
                field_unparser.empty_list_is_absent = parser.type.is_list_type

                # Starting from here, tokens to be unparsed in
                # ``parser.parser`` must be unparsed iff the field is present,
                # so respectively prepend and append token sequences in the
                # recursion to the field unparser itself.
                pre_tokens = TokenSequenceUnparser()
                post_tokens = TokenSequenceUnparser()
                NodeUnparser._emit_to_field_unparser(parser.parser,
                                                     field_unparser,
                                                     pre_tokens, post_tokens)
                field_unparser.pre_tokens = (pre_tokens +
                                             field_unparser.pre_tokens)
                field_unparser.post_tokens = (field_unparser.post_tokens +
                                              post_tokens)

        elif isinstance(parser, Or):
            # Just check that all subparsers create nodes, and thus that there
            # is nothing specific to do here: the unparser will just recurse on
            # this field.
            field_unparser.always_absent = False
            for subparser in parser.parsers:
                # Named parsing rules always create nodes, so we don't need to
                # check Defer parsers. Skip parsers also create nodes, but most
                # importantly they trigger a parsing error, so unparsers can
                # ignore them.
                if not isinstance(subparser, (Defer, Skip)):
                    NodeUnparser.from_parser(subparser.type, subparser)

        elif isinstance(parser, _Extract):
            field_unparser.always_absent = False
            pre_toks, node_parser, post_toks = NodeUnparser._split_extract(
                parser)

            # Pre and post-tokens from this _Extract parser appear whether or
            # not the parsed field is present, so they go in ``pre_tokens`` and
            # ``post_tokens``, not in the field unparser itself.
            pre_tokens.tokens = pre_tokens.tokens + pre_toks.tokens
            post_tokens.tokens = post_toks.tokens + post_tokens.tokens
            NodeUnparser._emit_to_field_unparser(node_parser, field_unparser,
                                                 pre_tokens, post_tokens)

        else:
            check_source_language(
                False, 'Unsupported parser for node field: {}'.format(parser))
Example #44
0
 def check_never_equal(can_be_equal):
     check_source_language(
         can_be_equal, '{} and {} values are never equal'.format(
             lhs.type.dsl_name, rhs.type.dsl_name))
Example #45
0
 def error_if_not_empty(name_set, message):
     check_source_language(not name_set, ('{}: {}'.format(
         message, ', '.join(name for name in name_set)
     )))
Example #46
0
    def run(self, argv=None):
        parsed_args = self.args_parser.parse_args(argv)

        for trace in parsed_args.trace:
            print("Trace {} is activated".format(trace))
            Log.enable(trace)

        Diagnostics.set_style(parsed_args.diagnostic_style)

        if parsed_args.profile:
            import cProfile
            import pstats

            pr = cProfile.Profile()
            pr.enable()

        # Set the verbosity
        self.verbosity = parsed_args.verbosity

        self.no_ada_api = parsed_args.no_ada_api

        # If asked to, setup the exception hook as a last-chance handler to
        # invoke a debugger in case of uncaught exception.
        if parsed_args.debug:
            # Try to use IPython's debugger if it is available, otherwise
            # fallback to PDB.
            try:
                # noinspection PyPackageRequirements
                from IPython.core import ultratb
            except ImportError:
                ultratb = None  # To keep PyCharm happy...

                def excepthook(type, value, tb):
                    traceback.print_exception(type, value, tb)
                    pdb.post_mortem(tb)

                sys.excepthook = excepthook
            else:
                sys.excepthook = ultratb.FormattedTB(mode='Verbose',
                                                     color_scheme='Linux',
                                                     call_pdb=1)
            del ultratb

        self.dirs.set_build_dir(parsed_args.build_dir)
        install_dir = getattr(parsed_args, 'install-dir', None)
        if install_dir:
            self.dirs.set_install_dir(install_dir)

        if getattr(parsed_args, 'list_warnings', False):
            WarningSet.print_list()
            return

        # noinspection PyBroadException
        try:
            parsed_args.func(parsed_args)

        except DiagnosticError:
            if parsed_args.debug:
                raise
            if parsed_args.verbosity.debug or parsed_args.full_error_traces:
                traceback.print_exc()
            print(col('Errors, exiting', Colors.FAIL), file=sys.stderr)
            sys.exit(1)

        except Exception as e:
            if parsed_args.debug:
                raise
            ex_type, ex, tb = sys.exc_info()

            # If we have a syntax error, we know for sure the last stack frame
            # points to the code that must be fixed. Otherwise, point to the
            # top-most stack frame that does not belong to Langkit.
            if e.args and e.args[0] == 'invalid syntax':
                loc = Location(e.filename, e.lineno)
            else:
                loc = extract_library_location(traceback.extract_tb(tb))
            with Context("", loc, "recovery"):
                check_source_language(False, str(e), do_raise=False)

            # Keep Langkit bug "pretty" for users: display the Python stack
            # trace only when requested.
            if parsed_args.verbosity.debug or parsed_args.full_error_traces:
                traceback.print_exc()

            print(col('Internal error! Exiting', Colors.FAIL), file=sys.stderr)
            sys.exit(1)

        finally:
            if parsed_args.profile:
                pr.disable()
                ps = pstats.Stats(pr)
                ps.dump_stats('langkit.prof')
Example #47
0
    def _resolve_property(name: str, prop_ref: _Any,
                          arity: int) -> Optional[PropertyDef]:
        """
        Resolve the ``prop`` property reference (if any, built in the DSL) to
        the referenced property. If it is present, check its signature.

        :param name: Name of the property in the DSL construct. Used to format
            the error message.
        :param prop_ref: Property reference to resolve.
        :param arity: Expected number of entity arguments for this property
            ("Self" included).
        """
        from langkit.expressions import FieldAccess

        # First, resolve the property

        prop: PropertyDef

        if prop_ref is None:
            return None

        elif isinstance(prop_ref, FieldAccess):
            node_data = prop_ref.resolve_field()
            if isinstance(node_data, PropertyDef):
                prop = node_data
            else:
                error(f"{name} must be a property")

        elif isinstance(prop_ref, T.Defer):
            prop = prop_ref.get()

        elif isinstance(prop_ref, PropertyDef):
            prop = prop_ref

        else:
            error(
                f"{name} must be either a FieldAccess resolving to a property,"
                " or a direct reference to a property")

        # Second, check its signature

        prop = prop.root_property
        assert prop.struct
        check_source_language(
            prop.struct.matches(T.root_node),
            f"{name} must belong to a subtype of {T.root_node.dsl_name}",
        )

        # Check that it takes the expected number of arguments. "Self" counts
        # as an implicit argument, so we expect at least ``arity - 1`` natural
        # arguments.
        n_args = arity - 1
        entity_args = prop.natural_arguments[:n_args]
        extra_args = prop.natural_arguments[n_args:]
        check_source_language(
            len(entity_args) == n_args
            and all(arg.type.is_entity_type for arg in entity_args),
            f"{name} property must accept {n_args} entity arguments (only"
            f" {len(entity_args)} found)",
        )

        # The other argumenst must be optional
        check_source_language(
            all(arg.default_value is not None for arg in extra_args),
            f"extra arguments for {name} must be optional",
        )

        # Check the property return type
        check_source_language(
            prop.type.matches(T.root_node.entity),
            f"{name} must return a subtype of {T.entity.dsl_name}",
        )

        # Check that all dynamic variables for this property are bound in the
        # current expression context.
        DynamicVariable.check_call_bindings(prop,
                                            f"In call to {{prop}} as {name}")

        # Third, generate a functor for this property, so that equations can
        # refer to it.
        from langkit.compile_context import get_context
        get_context().do_generate_logic_functors(prop, arity)

        return prop
Example #48
0
 def check(token):
     check_source_language(
         token not in (self.tokens.Termination,
                       self.tokens.LexingFailure),
         '{} is reserved for automatic actions only'.format(
             token.dsl_name))
Example #49
0
    def construct(self):
        check_multiple([
            (self.pred_property.type.matches(T.Bool),
             'Predicate property must return a boolean, got {}'.format(
                 self.pred_property.type.dsl_name)),
            (self.pred_property.struct.matches(T.root_node),
             'Predicate property must belong to a subtype of {}'.format(
                 T.root_node.dsl_name)),
        ])

        # Separate logic variable expressions from extra argument expressions
        exprs = [construct(e) for e in self.exprs]
        logic_var_exprs, closure_exprs = funcy.lsplit_by(
            lambda e: e.type == T.LogicVar, exprs)
        check_source_language(
            len(logic_var_exprs) > 0, "Predicate instantiation should have at "
            "least one logic variable expression")
        check_source_language(
            all(e.type != T.LogicVar for e in closure_exprs),
            'Logic variable expressions should be grouped at the beginning,'
            ' and should not appear after non logic variable expressions')

        # Make sure this predicate will work on clean logic variables
        logic_var_exprs = [ResetLogicVar(expr) for expr in logic_var_exprs]

        # Compute the list of arguments to pass to the property (Self
        # included).
        args = (
            [Argument(names.Name('Self'), self.pred_property.struct.entity)] +
            self.pred_property.natural_arguments)

        # Then check that 1) all extra passed actuals match what the property
        # arguments expect and that 2) arguments left without an actual have a
        # default value.
        default_passed_args = 0
        for i, (expr, arg) in enumerate(zip_longest(exprs, args)):

            if expr is None:
                check_source_language(
                    arg.default_value is not None,
                    'Missing an actual for argument #{} ({})'.format(
                        i, arg.name.lower))
                default_passed_args += 1
                continue

            check_source_language(
                arg is not None,
                'Too many actuals: at most {} expected, got {}'.format(
                    len(args), len(exprs)))

            if expr.type == T.LogicVar:
                check_source_language(
                    arg.type.matches(T.root_node.entity),
                    "Argument #{} of predicate "
                    "is a logic variable, the corresponding property formal "
                    "has type {}, but should be a descendent of {}".format(
                        i, arg.type.dsl_name, T.root_node.entity.dsl_name))
            else:
                check_source_language(
                    expr.type.matches(arg.type), "Argument #{} of predicate "
                    "has type {}, should be {}".format(i, expr.type.dsl_name,
                                                       arg.type.dsl_name))

        DynamicVariable.check_call_bindings(self.pred_property,
                                            'In predicate property {prop}')

        # Append dynamic variables to embed their values in the closure
        closure_exprs.extend(
            construct(dynvar) for dynvar in self.pred_property.dynamic_vars)

        pred_id = self.pred_property.do_generate_logic_predicate(
            tuple(e.type for e in closure_exprs), default_passed_args)

        args = " ({})".format(', '.join(
            ["{}"
             for _ in range(len(closure_exprs))])) if closure_exprs else ""
        predicate_expr = untyped_literal_expr(
            f"Create_{pred_id}_Predicate{args}", operands=closure_exprs)

        return Predicate.Expr(self.pred_property,
                              pred_id,
                              logic_var_exprs,
                              predicate_expr,
                              abstract_expr=self)
Example #50
0
 def check_array(typ):
     check_source_language(
         typ.is_array_type,
         "Expected array type, got {}".format(typ.dsl_name))
Example #51
0
    def construct(self):
        """
        Construct a resolved expression for this.

        :rtype: ResolvedExpression
        """
        # Add the variables created for this expression to the current scope
        scope = PropertyDef.get_scope()
        for _, v, _ in self.matchers:
            scope.add(v.local_var)

        matched_expr = construct(self.matched_expr)
        check_source_language(issubclass(matched_expr.type, ASTNode),
                              'Match expressions can only work on AST nodes')

        # Yes, the assertion below is what we just checked above, but unlike
        # check_source_language, assert_type provides type information to
        # PyCharm's static analyzer.
        matched_type = assert_type(matched_expr.type, ASTNode)

        constructed_matchers = []

        # Check (i.e. raise an error if no true) the set of matchers is valid:

        # * all matchers must target allowed types, i.e. input type subclasses;
        for t, v, e in self.matchers:
            if t is not None:
                check_source_language(
                    t.matches(matched_expr.type),
                    'Cannot match {} (input type is {})'.format(
                        t.name().camel,
                        matched_expr.type.name().camel
                    )
                )
            else:
                # The default matcher (if any) matches the most general type,
                # which is the input type.
                v.set_type(matched_expr.type)
            constructed_matchers.append((construct(v), construct(e)))

        # * all possible input types must have at least one matcher. Also warn
        #   if some matchers are unreachable.
        self._check_match_coverage(matched_type)

        # Compute the return type as the unification of all branches
        _, expr = constructed_matchers[-1]
        rtype = expr.type
        for _, expr in constructed_matchers:
            check_source_language(
                expr.type.matches(rtype), "Wrong type for match expression : "
                "{}, expected {} or sub/supertype".format(
                    expr.type.name().camel, rtype.name().camel
                )
            )
            rtype = expr.type.unify(rtype)

        # This is the expression execution will reach if we have a bug in our
        # code (i.e. if matchers did not cover all cases).
        result = UnreachableExpr(rtype)

        # Wrap this "failing" expression with all the cases to match in the
        # appropriate order, so that in the end the first matchers are tested
        # first.
        for match_var, expr in reversed(constructed_matchers):
            casted = Cast.Expr(matched_expr,
                               match_var.type,
                               result_var=match_var)
            guard = Not.make_expr(
                Eq.make_expr(casted, LiteralExpr('null', casted.type))
            )
            if expr.type != rtype:
                # We already checked that type matches, so only way this is
                # true is if expr.type is an ASTNode type derived from
                # rtype. In that case, we need an explicity upcast.
                expr = Cast.Expr(expr, rtype)

            result = If.Expr(guard, expr, result, rtype)

        return result
Example #52
0
from __future__ import absolute_import, division, print_function

from langkit import compiled_types, expressions
from langkit.diagnostics import check_source_language, Severity
from langkit.utils import dispatch_on_type

try:
    from docutils.core import publish_parts
except ImportError:  # no-code-coverage
    check_source_language(
        False,
        "Missing docutils to properly render sphinx doc. Install the "
        "docutils package",
        severity=Severity.warning
    )

    # Provide a stub implementation for publish_parts
    def publish_parts(x, *args, **kwargs):
        return {'html_body': x}


def trim_docstring_lines(docstring):
    """
    This function will return a trimmed version of the docstring, removing
    whitespace at the beginning of lines depending on the offset of the first
    line.

    :type docstring: str
    """

    # Remove leading newline if needed
Example #53
0
    def __init__(self,
                 add_env=False,
                 add_to_env=None,
                 ref_envs=None,
                 initial_env=None,
                 env_hook_arg=None):
        """

        :param bool add_env: Wether to add a new scoped lexical environment.
            The new environment will be linked to the corresponding AST node
            and will have the AST node's lexical environment as a parent.

        :param add_to_env: Eiter an AddToEnv named tuple, or a list of them.
            Used to add elements to the lexical environment. See add_to_env's
            doc for more details.
        :type add_to_env: AddToEnv|[AddToEnv]

        :param AbstractExpression ref_envs: if an AbstractExpression returning
            a list of environments is supplied, the topmost environment in the
            environment resolution will be altered to include the list of
            environments as referenced environments. TODO: Not yet implemented!

        :param AbstractExpression initial_env: If supplied, this env will be
            used as the lexical environment to execute the rest of the actions.
            For example, if you pass an initial_env, and add_env, then an env
            will be added to the env passed as initial_env, and the node
            concerned by this env specification will have initial_env as a
            parent indirectly.

        :param AbstractExpression env_hook_arg: Does nothing if left to None.
            If supplied, it must be an abstract expression that resolves to a
            node. This expression will be evaluated and passed to the
            environment hook.
        """

        self.ast_node = None
        """
        ASTNode subclass associated to this environment specification.
        Initialized when creating ASTNode subclasses.
        :type: langkit.compiled_types.ASTNode
        """

        self._add_env = add_env
        ":type: bool"

        # The following attributes (unresolved_*) contain abstract expressions
        # used to describe various environment behaviors. They all have
        # corresponding attributes that embed them as properties: see below.

        self._unresolved_initial_env = initial_env
        ":type: AbstractExpression"

        self._unresolved_envs_expressions = []
        ":type: list[AddToEnv]"

        self.envs_expressions = []
        ":type: list[AddToEnv]"

        if add_to_env:
            check_source_language(
                isinstance(add_to_env, AddToEnv)
                or isinstance(add_to_env, list),
                "Wrong parameter for add_to_env: Expected AddToEnv named-tuple"
                " or list of AddToEnv"
            )

            self._unresolved_envs_expressions = (
                [add_to_env] if isinstance(add_to_env, AddToEnv)
                else add_to_env
            )

        self._unresolved_ref_envs = ref_envs
        ":type: AbstractExpression"

        self._unresolved_env_hook_arg = env_hook_arg
        ":type: AbstractExpression"

        # These are the property attributes

        self.initial_env = None
        ":type: PropertyDef"

        self.ref_envs = None
        ":type: PropertyDef"

        self.env_hook_arg = None
        ":type: PropertyDef"

        self.has_post_actions = False