def _call_eager(self, expr: E.Apply, arguments: List[V.Base]) -> V.Base: ans_type = self.infer_type(expr) if not isinstance(ans_type, T.String): return super()._call_eager(expr, arguments) # TODO: in a command interpolation, return missing if either operand is missing ans = self.op( str(arguments[0].coerce(T.String()).value), str(arguments[1].coerce(T.String()).value) ) assert isinstance(ans, str) return V.String(ans)
def infer_type(self, expr: E.Apply) -> T.Base: if len(expr.arguments) != 2: raise Error.WrongArity(expr, 2) expr.arguments[0].typecheck(T.String()) expr.arguments[1].typecheck(T.Array(T.String())) return T.Array( T.String(), nonempty=( isinstance(expr.arguments[1].type, T.Array) and expr.arguments[1].type.nonempty ), )
def __call__(self, expr: E.Apply, env: E.Env) -> V.Base: ans_type = self.infer_type(expr) if not isinstance(ans_type, T.String): return super().__call__(expr, env) # TODO: return missing if either operand is missing ans = self.op( str(expr.arguments[0].eval(env).coerce(T.String()).value), str(expr.arguments[1].eval(env).coerce(T.String()).value), ) assert isinstance(ans, str) return V.String(ans)
def _infer_type(self, type_env: Env.Types) -> T.Base: if not self.items: return T.Array(None) for item in self.items: item.infer_type(type_env, self._check_quant) # Start by assuming the type of the first item is the item type item_type: T.Base = self.items[0].type # Allow a mixture of Int and Float to construct Array[Float] if isinstance(item_type, T.Int): for item in self.items: if isinstance(item.type, T.Float): item_type = T.Float() # If any item is String, assume item type is String # If any item has optional quantifier, assume item type is optional # If all items have nonempty quantifier, assume item type is nonempty all_nonempty = len(self.items) > 0 for item in self.items: if isinstance(item.type, T.String): item_type = T.String(optional=item_type.optional) if item.type.optional: item_type = item_type.copy(optional=True) if isinstance(item.type, T.Array) and not item.type.nonempty: all_nonempty = False if isinstance(item_type, T.Array): item_type = item_type.copy(nonempty=all_nonempty) # Check all items are coercible to item_type for item in self.items: try: item.typecheck(item_type) except Error.StaticTypeMismatch: self._type = T.Array(item_type, optional=False, nonempty=True) raise Error.StaticTypeMismatch( self, item_type, item.type, "(inconsistent types within array)") from None return T.Array(item_type, optional=False, nonempty=True)
def _infer_type(self, type_env: Env.Types) -> T.Base: self.expr.infer_type(type_env, self._check_quant) if isinstance(self.expr.type, T.Array): if "sep" not in self.options: raise Error.StaticTypeMismatch( self, T.Array(None), self.expr.type, "array command placeholder must have 'sep'") # if sum(1 for t in [T.Int, T.Float, T.Boolean, T.String, T.File] if isinstance(self.expr.type.item_type, t)) == 0: # raise Error.StaticTypeMismatch(self, T.Array(None), self.expr.type, "cannot use array of complex types for command placeholder") elif "sep" in self.options: raise Error.StaticTypeMismatch( self, T.Array(None), self.expr.type, "command placeholder has 'sep' option for non-Array expression", ) if "true" in self.options or "false" in self.options: if not isinstance(self.expr.type, T.Boolean): raise Error.StaticTypeMismatch( self, T.Boolean(), self.expr.type, "command placeholder 'true' and 'false' options used with non-Boolean expression", ) if not ("true" in self.options and "false" in self.options): raise Error.StaticTypeMismatch( self, T.Boolean(), self.expr.type, "command placeholder with only one of 'true' and 'false' options", ) return T.String()
def infer_type(self, expr: E.Apply) -> T.Base: assert len(expr.arguments) == 2 t2 = None if isinstance(expr.arguments[0].type, T.String): t2 = expr.arguments[1].type elif isinstance(expr.arguments[1].type, T.String): t2 = expr.arguments[0].type if t2 is None: # neither operand is a string; defer to _ArithmeticOperator return super().infer_type(expr) if not t2.coerces(T.String(optional=True)): raise Error.IncompatibleOperand( expr, "Cannot add/concatenate {} and {}".format( str(expr.arguments[0].type), str(expr.arguments[1].type)), ) return T.String()
def infer_type(self, expr: E.Apply) -> T.Base: if not expr.arguments: raise Error.WrongArity(expr, 1) if not expr.arguments[0].type.coerces(T.File(optional=True)): if isinstance(expr.arguments[0].type, T.Array): if expr.arguments[0].type.optional or not expr.arguments[0].type.item_type.coerces( T.File(optional=True) ): raise Error.StaticTypeMismatch( expr.arguments[0], T.Array(T.File(optional=True)), expr.arguments[0].type ) else: raise Error.StaticTypeMismatch( expr.arguments[0], T.File(optional=True), expr.arguments[0].type ) if len(expr.arguments) == 2: if expr.arguments[1].type != T.String(): raise Error.StaticTypeMismatch( expr.arguments[1], T.String(), expr.arguments[1].type ) elif len(expr.arguments) > 2: raise Error.WrongArity(expr, 2) return T.Float()
def typecheck(self, check_quant: bool = True) -> None: # First collect a type environment for all the input & postinput # declarations, so that we're prepared for possible forward-references # in their right-hand side expressions. type_env = [] for decl in self.inputs + self.postinputs: type_env = decl.add_to_type_env(type_env) # Pass through input & postinput declarations again, typecheck their # right-hand side expressions against the type environment. for decl in self.inputs + self.postinputs: decl.typecheck(type_env, check_quant) # TODO: detect circular dependencies among input & postinput decls # Typecheck the command (string) self.command.infer_type(type_env, check_quant).typecheck(T.String()) # Typecheck runtime expressions for _, runtime_expr in self.runtime.items(): runtime_expr.infer_type(type_env, check_quant).typecheck(T.String()) # Add output declarations to type environment for decl in self.outputs: type_env = decl.add_to_type_env(type_env) # Typecheck the output expressions for decl in self.outputs: decl.typecheck(type_env, check_quant)
def from_json(type: T.Base, value: Any) -> Base: """ Instantiate a WDL value of the specified type from a parsed JSON value (str, int, float, list, dict, or null). :raise WDL.Error.InputError: if the given value isn't coercible to the specified type """ if isinstance(type, T.Boolean) and value in [True, False]: return Boolean(value) if isinstance(type, T.Int) and isinstance(value, int): return Int(value) if isinstance(type, T.Float) and isinstance(value, (float, int)): return Float(float(value)) if isinstance(type, T.File) and isinstance(value, str): return File(value) if isinstance(type, T.String) and isinstance(value, str): return String(value) if isinstance(type, T.Array) and isinstance(value, list): return Array(type, [from_json(type.item_type, item) for item in value]) if isinstance(type, T.Map) and type.item_type[0] == T.String() and isinstance( value, dict): items = [] for k, v in value.items(): assert isinstance(k, str) items.append((from_json(type.item_type[0], k), from_json(type.item_type[1], v))) return Map(type, items) if (isinstance(type, T.StructInstance) and isinstance(value, dict) and type.members and set(type.members.keys()) == set(value.keys())): items = {} for k, v in value.items(): assert isinstance(k, str) items[k] = from_json(type.members[k], v) return Struct(T.Object(type.members), items) if type.optional and value is None: return Null() raise Error.InputError("couldn't construct {} from input {}".format( str(type), json.dumps(value)))
def __init__(self, value: str) -> None: super().__init__(T.String(), value)
def string_type(self, items, meta): optional = False if items and items[0].value == "?": optional = True return T.String(optional)
def _infer_type(self, type_env: Env.Types) -> T.Base: for part in self.parts: if isinstance(part, Placeholder): part.infer_type(type_env, self._check_quant) return T.String()
def _call_eager(self, expr: E.Apply, arguments: List[V.Base]) -> V.Base: pfx = arguments[0].coerce(T.String()).value return V.Array( T.Array(T.String()), [V.String(pfx + s.coerce(T.String()).value) for s in arguments[1].value], )
def __init__(self): # language built-ins self._at = _At() self._land = _And() self._lor = _Or() self._negate = StaticFunction( "_negate", [T.Boolean()], T.Boolean(), lambda x: V.Boolean(not x.value) ) self._add = _AddOperator() self._sub = _ArithmeticOperator("-", lambda l, r: l - r) self._mul = _ArithmeticOperator("*", lambda l, r: l * r) self._div = _ArithmeticOperator("/", lambda l, r: l // r) self._rem = StaticFunction( "_rem", [T.Int(), T.Int()], T.Int(), lambda l, r: V.Int(l.value % r.value) ) self._eqeq = _ComparisonOperator("==", lambda l, r: l == r) self._neq = _ComparisonOperator("!=", lambda l, r: l != r) self._lt = _ComparisonOperator("<", lambda l, r: l < r) self._lte = _ComparisonOperator("<=", lambda l, r: l <= r) self._gt = _ComparisonOperator(">", lambda l, r: l > r) self._gte = _ComparisonOperator(">=", lambda l, r: l >= r) # static stdlib functions for (name, argument_types, return_type, F) in [ ("floor", [T.Float()], T.Int(), lambda v: V.Int(math.floor(v.value))), ("ceil", [T.Float()], T.Int(), lambda v: V.Int(math.ceil(v.value))), ("round", [T.Float()], T.Int(), lambda v: V.Int(round(v.value))), ("length", [T.Array(T.Any())], T.Int(), lambda v: V.Int(len(v.value))), ("sub", [T.String(), T.String(), T.String()], T.String(), _sub), ("basename", [T.String(), T.String(optional=True)], T.String(), _basename), ( "defined", [T.Any(optional=True)], T.Boolean(), lambda v: V.Boolean(not isinstance(v, V.Null)), ), # context-dependent: ("write_lines", [T.Array(T.String())], T.File(), _notimpl), ("write_tsv", [T.Array(T.Array(T.String()))], T.File(), _notimpl), ("write_map", [T.Map((T.Any(), T.Any()))], T.File(), _notimpl), ("write_json", [T.Any()], T.File(), _notimpl), ("stdout", [], T.File(), _notimpl), ("stderr", [], T.File(), _notimpl), ("glob", [T.String()], T.Array(T.File()), _notimpl), ("read_int", [T.File()], T.Int(), _notimpl), ("read_boolean", [T.File()], T.Boolean(), _notimpl), ("read_string", [T.File()], T.String(), _notimpl), ("read_float", [T.File()], T.Float(), _notimpl), ("read_array", [T.File()], T.Array(T.Any()), _notimpl), ("read_map", [T.File()], T.Map((T.Any(), T.Any())), _notimpl), ("read_lines", [T.File()], T.Array(T.Any()), _notimpl), ("read_tsv", [T.File()], T.Array(T.Array(T.String())), _notimpl), ("read_json", [T.File()], T.Any(), _notimpl), ]: setattr(self, name, StaticFunction(name, argument_types, return_type, F)) # polymorphically typed stdlib functions which require specialized # infer_type logic self.range = _Range() self.prefix = _Prefix() self.size = _Size() self.select_first = _SelectFirst() self.select_all = _SelectAll() self.zip = _Zip() self.cross = _Zip() # FIXME self.flatten = _Flatten() self.transpose = _Transpose()
] ans: V.Base = self.F(*argument_values) return ans.coerce(self.return_type) def _notimpl(one: Any = None, two: Any = None) -> None: exec("raise NotImplementedError()") _static_functions: List[Tuple[str, List[T.Base], T.Base, Any]] = [ ("_negate", [T.Boolean()], T.Boolean(), lambda x: V.Boolean(not x.value)), # pyre-fixme ("_rem", [T.Int(), T.Int()], T.Int(), lambda l, r: V.Int(l.value % r.value)), # pyre-fixme ("stdout", [], T.File(), _notimpl), ("basename", [T.String(), T.String(optional=True)], T.String(), _notimpl), # note: size() can take an empty value and probably returns 0 in that case. # e.g. https://github.com/DataBiosphere/topmed-workflows/blob/31ba8a714b36ada929044f2ba3d130936e6c740e/CRAM-no-header-md5sum/md5sum/CRAM_md5sum.wdl#L39 ("size", [T.File(optional=True), T.String(optional=True)], T.Float(), _notimpl), ("ceil", [T.Float()], T.Int(), _notimpl), ("round", [T.Float()], T.Int(), _notimpl), ("glob", [T.String()], T.Array(T.File()), _notimpl), ("read_int", [T.String()], T.Int(), _notimpl), ("read_boolean", [T.String()], T.Boolean(), _notimpl), ("read_string", [T.String()], T.String(), _notimpl), ("read_float", [T.String()], T.Float(), _notimpl), ("read_array", [T.String()], T.Array(None), _notimpl), ("read_map", [T.String()], T.Map(None), _notimpl), ("read_lines", [T.String()], T.Array(None), _notimpl), ("read_tsv", [T.String()], T.Array(T.Array(T.String())), _notimpl),