def construct(self): """ Construct a resolved expression for this. :rtype: EqExpr """ from langkit.expressions.structs import Cast lhs = construct(self.lhs) rhs = construct(self.rhs) # 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)
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)
def check_source_language(predicate, message, severity=Severity.error, do_raise=True): """ Check predicates related to the user's input in the input language definition. Show error messages and eventually terminate if those error messages are critical. :param bool predicate: The predicate to check. :param str message: The base message to display if predicate happens to be false. :param Severity severity: The severity of the diagnostic. """ global EMIT_PARSABLE_ERRORS severity = assert_type(severity, Severity) if not predicate: if EMIT_PARSABLE_ERRORS: print "{}: {}".format(get_parsable_location(), message) else: print_context() print "{}{}: {}".format(" " if context_stack else '', format_severity(severity), message) if severity == Severity.error and do_raise: raise DiagnosticError() elif severity == Severity.non_blocking_error: Diagnostics.has_pending_error = True
def check_source_language(predicate, message, severity=Severity.error, do_raise=True): """ Check predicates related to the user's input in the input language definition. Show error messages and eventually terminate if those error messages are critical. :param bool predicate: The predicate to check. :param str message: The base message to display if predicate happens to be false. :param Severity severity: The severity of the diagnostic. :param bool do_raise: If True, raise a DiagnosticError if predicate happens to be false. """ severity = assert_type(severity, Severity) indent = ' ' * 4 if not predicate: message_lines = message.splitlines() message = '\n'.join(message_lines[:1] + [indent + line for line in message_lines[1:]]) if Diagnostics.style != DiagnosticStyle.default: print('{}: {}'.format(get_parsable_location(), message)) else: print_context() print('{}{}: {}'.format(indent if context_stack else '', format_severity(severity), message)) if severity == Severity.error and do_raise: raise DiagnosticError() elif severity == Severity.non_blocking_error: Diagnostics.has_pending_error = True
def do_prepare(self): argspec = inspect.getargspec(self.expr_fn) check_multiple([ (len(argspec.args) in (1, 2), 'Invalid collection iteration lambda: only one ' 'or two parameters expected'), (not argspec.varargs and not argspec.keywords, 'Invalid collection iteration lambda: no *args or **kwargs'), (not argspec.defaults, 'Invalid collection iteration lambda: No default values allowed ' 'for arguments') ]) self.requires_index = len(argspec.args) == 2 self.element_var = AbstractVariable( names.Name("Item_{}".format(next(CollectionExpression._counter))), ) if self.requires_index: self.index_var = AbstractVariable( names.Name('I'), type=LongType, create_local=True ) expr = self.expr_fn(self.index_var, self.element_var) else: expr = self.expr_fn(self.element_var) self.expr = assert_type(expr, AbstractExpression)
def as_bool(self, dest=None): """ Returns the self parser, modified to return a bool rather than the sub-parser result. The result will be true if the parse was successful, false otherwise. This is typically useful to store specific tokens as attributes, for example in Ada, you'll mark a subprogram as overriding with the "overriding" keyword, and we want to store that in the tree as a boolean attribute, so we'll use:: Opt("overriding").as_bool() :param CompiledType|None dest: If not None, then it is expected that this is an EnumType with qualifier = True. In this cases, the result will be booleanized. :rtype: Opt """ if dest is None: base, alt_true, alt_false = (BoolType, BoolType, BoolType) else: base = assert_type(dest, ASTNode) assert base.is_bool_node alt_true, alt_false = base._alternatives new = copy_with(self, _booleanize=(base, alt_true, alt_false)) return new
def check_source_language(predicate, message, severity=Severity.error): """ Check predicates related to the user's input in the input language definition. Show error messages and eventually terminate if those error messages are critical. :param bool predicate: The predicate to check. :param str message: The base message to display if predicate happens to be false. :param Severity severity: The severity of the diagnostic. """ # PyCharm's static analyzer thinks Severity.foo is an int because of the # class declaration, it it's really an instance of Severity instead. severity = assert_type(severity, Severity) if not predicate: print_errors() print "{}{}: {}".format( " " if context_stack else '', format_severity(severity), message ) if severity == Severity.error: raise DiagnosticError() elif severity == Severity.non_blocking_error: Diagnostics.has_pending_error = True
def check_source_language(predicate, message, severity=Severity.error, do_raise=True, ok_for_codegen=False): """ Check predicates related to the user's input in the input language definition. Show error messages and eventually terminate if those error messages are critical. :param bool predicate: The predicate to check. :param str message: The base message to display if predicate happens to be false. :param Severity severity: The severity of the diagnostic. :param bool do_raise: If True, raise a DiagnosticError if predicate happens to be false. :param bool ok_for_codegen: If True, allow checks to be performed during code generation. This is False by default as it should be an exceptional situation: we want, when possible, most checks to be performed before we attempt to emit the generated library (for --check-only). """ from langkit.compile_context import get_context def context_from_node(node): return '{}:{}'.format(get_filename(node.unit.filename), node.sloc_range.start) if not ok_for_codegen: ctx = get_context(or_none=True) assert ctx is None or ctx.emitter is None severity = assert_type(severity, Severity) indent = ' ' * 4 if not predicate: message_lines = message.splitlines() message = '\n'.join( message_lines[:1] + [indent + line for line in message_lines[1:]] ) context = get_structured_context() if not isinstance(context, list): print('{}: {}: {}'.format(context_from_node(context), format_severity(severity), message)) elif Diagnostics.style != DiagnosticStyle.default: print('{}: {}'.format(get_parsable_location(), message)) else: print_context(context) print('{}{}: {}'.format( indent if context_stack else '', format_severity(severity), message )) if severity == Severity.error and do_raise: raise DiagnosticError() elif severity == Severity.non_blocking_error: Diagnostics.has_pending_error = True
def check_type(obj: Any, typ: Type[T], message: Opt[str] = None) -> T: """ Like utils.assert_type, but produces a client error instead. :param obj: The object to check. :param typ: The expected type of obj. :param str|None message: The base message to display if type check fails. """ try: return assert_type(obj, typ) except AssertionError as e: message = "{}\n{}".format(e.args[0], message) if message else e.args[0] error(message)
def check_type(obj, typ, message=None): """ Like utils.assert_type, but produces a client error instead. :param Any obj: The object to check. :param T typ: Type parameter. The expected type of obj. :param str|None message: The base message to display if type check fails. :rtype: T """ try: return assert_type(obj, typ) except AssertionError as e: message = "{}\n{}".format(e.message, message) if message else e.message check_source_language(False, message)
def check_source_language(predicate: bool, message: str, severity: Severity = Severity.error, do_raise: bool = True, ok_for_codegen: bool = False) -> None: """ Check predicates related to the user's input in the input language definition. Show error messages and eventually terminate if those error messages are critical. :param predicate: The predicate to check. :param message: The base message to display if predicate happens to be false. :param severity: The severity of the diagnostic. :param do_raise: If True, raise a DiagnosticError if predicate happens to be false. :param ok_for_codegen: If True, allow checks to be performed during code generation. This is False by default as it should be an exceptional situation: we want, when possible, most checks to be performed before we attempt to emit the generated library (for --check-only). """ from langkit.compile_context import get_context_or_none if not ok_for_codegen: ctx = get_context_or_none() assert ctx is None or ctx.emitter is None severity = assert_type(severity, Severity) indent = ' ' * 4 if not predicate: message_lines = message.splitlines() message = '\n'.join(message_lines[:1] + [indent + line for line in message_lines[1:]]) if Diagnostics.style != DiagnosticStyle.default: print('{}: {}'.format(get_parsable_location(), message)) else: print_error(message, get_current_location(), severity) if severity == Severity.error and do_raise: raise DiagnosticError() elif severity == Severity.non_blocking_error: Diagnostics.has_pending_error = True
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) elif then_expr.type.matches(Symbol): default_expr = LiteralExpr(Symbol.nullexpr(), Symbol) 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)
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)
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
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