Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
mlin committed Nov 20, 2018
1 parent 67d6ad2 commit 6397d54
Show file tree
Hide file tree
Showing 11 changed files with 322 additions and 120 deletions.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ typecheck:
pylint:
pylint -d cyclic-import,empty-docstring,missing-docstring,invalid-name WDL

autopep8:
autopep8 --aggressive --aggressive --in-place WDL/*.py

docker:
docker build -t miniwdl .

Expand All @@ -35,4 +38,4 @@ doc:

docs: doc

.PHONY: typecheck pylint test docker doc docs pypi_test pypi bdist
.PHONY: typecheck pylint autopep8 test docker doc docs pypi_test pypi bdist
27 changes: 19 additions & 8 deletions WDL/CLI.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,17 @@ def main(args=None):
subparsers.dest = 'command'

check_parser = subparsers.add_parser(
'check', help='Load and typecheck a WDL document; show an outline with lint warnings')
'check',
help='Load and typecheck a WDL document; show an outline with lint warnings')
check_parser.add_argument('uri', metavar='URI',
type=str, help="WDL document filename/URI")
check_parser.add_argument('-p', '--path', metavar='DIR', type=str,
action='append', help="local directory to search for imports")
check_parser.add_argument(
'-p',
'--path',
metavar='DIR',
type=str,
action='append',
help="local directory to search for imports")

args = parser.parse_args(args if args is not None else sys.argv[1:])

Expand Down Expand Up @@ -58,7 +64,7 @@ def check(args):


def outline(obj, level, file=sys.stdout):
s = ''.join(' ' for i in range(level*4))
s = ''.join(' ' for i in range(level * 4))

first_descent = []

Expand All @@ -70,15 +76,16 @@ def descend(dobj=None, first_descent=first_descent):
s, node.pos.line, node.pos.column, klass, msg), file=file)
first_descent.append(False)
if dobj:
outline(dobj, level+1, file=file)
outline(dobj, level + 1, file=file)

# document
if isinstance(obj, WDL.Document):
# workflow
if obj.workflow:
descend(obj.workflow)
# tasks
for task in sorted(obj.tasks, key=lambda task: (not task.called, task.name)):
for task in sorted(obj.tasks, key=lambda task: (
not task.called, task.name)):
descend(task)
# imports
for uri, namespace, subdoc in sorted(obj.imports, key=lambda t: t[1]):
Expand All @@ -97,8 +104,12 @@ def descend(dobj=None, first_descent=first_descent):
print("{}workflow {} (not called)".format(s, obj.name), file=file)
# task
elif isinstance(obj, WDL.Task):
print("{}task {}{}".format(s, obj.name,
" (not called)" if not obj.called else ""), file=file)
print(
"{}task {}{}".format(
s,
obj.name,
" (not called)" if not obj.called else ""),
file=file)
for decl in obj.inputs + obj.postinputs + obj.outputs:
descend(decl)
# call
Expand Down
3 changes: 2 additions & 1 deletion WDL/Env.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ def bind(name: str, rhs: R, tree: 'Tree[R]') -> 'Tree[R]':
return [Binding(name, rhs)] + tree


def namespace(namespace: str, bindings: 'Tree[R]', tree: 'Tree[R]') -> 'Tree[R]':
def namespace(namespace: str,
bindings: 'Tree[R]', tree: 'Tree[R]') -> 'Tree[R]':
"""Prepend a namespace to an environment"""
return [Namespace(namespace, bindings)] + tree

Expand Down
30 changes: 22 additions & 8 deletions WDL/Error.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ def __init__(self, filename: str) -> None:


class ImportError(Exception):
def __init__(self, document: str, import_uri: str, message: Optional[str] = None) -> None:
def __init__(self, document: str, import_uri: str,
message: Optional[str] = None) -> None:
msg = "Failed to import {} to {}".format(import_uri, document)
if message:
msg = msg + ": " + message
Expand All @@ -36,8 +37,15 @@ def __init__(self, pos: SourcePosition) -> None:

def __lt__(self, rhs) -> bool:
if isinstance(rhs, SourceNode):
return ((self.pos.filename, self.pos.line, self.pos.column, self.pos.end_line, self.pos.end_column) <
(rhs.pos.filename, rhs.pos.line, rhs.pos.column, rhs.pos.end_line, rhs.pos.end_column))
return ((self.pos.filename,
self.pos.line,
self.pos.column,
self.pos.end_line,
self.pos.end_column) < (rhs.pos.filename,
rhs.pos.line,
rhs.pos.column,
rhs.pos.end_line,
rhs.pos.end_column))
return False

def __eq__(self, rhs) -> bool:
Expand All @@ -47,7 +55,8 @@ def __eq__(self, rhs) -> bool:
class Base(Exception):
node: Optional[SourceNode]

def __init__(self, node: Union[SourceNode, SourcePosition], message: str) -> None:
def __init__(self, node: Union[SourceNode,
SourcePosition], message: str) -> None:
if isinstance(node, SourceNode):
self.node = node
self.pos = node.pos
Expand Down Expand Up @@ -82,7 +91,8 @@ def __init__(self, node: SourceNode) -> None:


class StaticTypeMismatch(Base):
def __init__(self, node: SourceNode, expected: T.Base, actual: T.Base, message: Optional[str] = None) -> None:
def __init__(self, node: SourceNode, expected: T.Base,
actual: T.Base, message: Optional[str] = None) -> None:
msg = "Expected {} instead of {}".format(str(expected), str(actual))
if message is not None:
msg = msg + " " + message
Expand Down Expand Up @@ -120,8 +130,11 @@ def __init__(self, node: SourceNode, name: str) -> None:


class MissingInput(Base):
def __init__(self, node: SourceNode, name: str, inputs: Iterable[str]) -> None:
super().__init__(node, "Call {} missing required input(s) {}".format(name, ', '.join(inputs)))
def __init__(self, node: SourceNode, name: str,
inputs: Iterable[str]) -> None:
super().__init__(
node, "Call {} missing required input(s) {}".format(
name, ', '.join(inputs)))


class NullValue(Base):
Expand All @@ -130,5 +143,6 @@ def __init__(self, node: SourceNode) -> None:


class MultipleDefinitions(Base):
def __init__(self, node: Union[SourceNode, SourcePosition], message: str) -> None:
def __init__(self, node: Union[SourceNode,
SourcePosition], message: str) -> None:
super().__init__(node, message)
67 changes: 47 additions & 20 deletions WDL/Expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ class Placeholder(Base):
Expression for evaluation
"""

def __init__(self, pos: SourcePosition, options: Dict[str, str], expr: Base) -> None:
def __init__(self, pos: SourcePosition,
options: Dict[str, str], expr: Base) -> None:
super().__init__(pos)
self.options = options
self.expr = expr
Expand All @@ -168,20 +169,32 @@ def _infer_type(self, type_env: Env.Types) -> T.Base:
self.expr.infer_type(type_env)
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'")
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")
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")
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")
raise Error.StaticTypeMismatch(
self,
T.Boolean(),
self.expr.type,
"command placeholder with only one of 'true' and 'false' options")
return T.String()

def eval(self, env: Env.Values) -> V.String:
Expand All @@ -194,7 +207,8 @@ def eval(self, env: Env.Values) -> V.String:
if isinstance(v, V.String):
return v
if isinstance(v, V.Array):
return V.String(self.options['sep'].join(str(item.value) for item in v.value))
return V.String(self.options['sep'].join(
str(item.value) for item in v.value))
if v == V.Boolean(True) and 'true' in self.options:
return V.String(self.options['true'])
if v == V.Boolean(False) and 'false' in self.options:
Expand All @@ -211,7 +225,8 @@ class String(Base):
The parts list begins and ends with matching single- or double- quote marks. Between these is a sequence of literal strings and/or interleaved placeholder expressions. Escape sequences in the literals have NOT been decoded."""

def __init__(self, pos: SourcePosition, parts: List[Union[str, Placeholder]]) -> None:
def __init__(self, pos: SourcePosition,
parts: List[Union[str, Placeholder]]) -> None:
super().__init__(pos)
self.parts = parts

Expand All @@ -232,7 +247,7 @@ def eval(self, env: Env.Values) -> V.String:
if isinstance(part, Placeholder):
# evaluate interpolated expression & stringify
ans.append(part.eval(env).value)
elif type(part) == str:
elif isinstance(part, str):
# use python builtins to decode escape sequences
ans.append(str.encode(part).decode('unicode_escape'))
else:
Expand Down Expand Up @@ -303,7 +318,8 @@ def typecheck(self, expected: Optional[T.Base]) -> Base:
def eval(self, env: Env.Values) -> V.Array:
""
assert isinstance(self.type, T.Array)
return V.Array(self.type, [item.eval(env).coerce(self.type.item_type) for item in self.items])
return V.Array(self.type, [item.eval(env).coerce(
self.type.item_type) for item in self.items])

# If

Expand All @@ -330,7 +346,8 @@ class IfThenElse(Base):
Expression evaluated when the condition is false
"""

def __init__(self, pos: SourcePosition, condition: Base, consequent: Base, alternative: Base) -> None:
def __init__(self, pos: SourcePosition, condition: Base,
consequent: Base, alternative: Base) -> None:
super().__init__(pos)
self.condition = condition
self.consequent = consequent
Expand All @@ -343,12 +360,20 @@ def _infer_type(self, type_env: Env.Types) -> T.Base:
self_type = self.consequent.infer_type(type_env).type
assert isinstance(self_type, T.Base)
self.alternative.infer_type(type_env)
# unify inferred consequent & alternative types wrt quantifiers & float promotion
if isinstance(self_type, T.Int) and isinstance(self.alternative.type, T.Float):
# unify inferred consequent & alternative types wrt quantifiers & float
# promotion
if isinstance(self_type, T.Int) and isinstance(
self.alternative.type, T.Float):
self_type = T.Float(optional=self_type.optional)
if self.alternative.type.optional:
self_type = self_type.copy(optional=True)
if isinstance(self_type, T.Array) and isinstance(self.consequent.type, T.Array) and isinstance(self.alternative.type, T.Array):
if isinstance(
self_type,
T.Array) and isinstance(
self.consequent.type,
T.Array) and isinstance(
self.alternative.type,
T.Array):
self_type = self_type.copy(nonempty=( # pyre-fixme
self.consequent.type.nonempty and self.alternative.type.nonempty)) # pyre-fixme
try:
Expand All @@ -362,7 +387,7 @@ def _infer_type(self, type_env: Env.Types) -> T.Base:
def eval(self, env: Env.Values) -> V.Base:
""
try:
if self.condition.eval(env).expect(T.Boolean()).value != False:
if self.condition.eval(env).expect(T.Boolean()).value:
ans = self.consequent.eval(env)
else:
ans = self.alternative.eval(env)
Expand Down Expand Up @@ -411,7 +436,8 @@ class Apply(Base):

function: _Function

def __init__(self, pos: SourcePosition, function: str, arguments: List[Base]) -> None:
def __init__(self, pos: SourcePosition, function: str,
arguments: List[Base]) -> None:
super().__init__(pos)
try:
self.function = _stdlib[function]
Expand Down Expand Up @@ -533,7 +559,8 @@ class Map(Base):
Expressions for the map literal keys and values
"""

def __init__(self, pos: SourcePosition, items: List[Tuple[Base, Base]]) -> None:
def __init__(self, pos: SourcePosition,
items: List[Tuple[Base, Base]]) -> None:
super().__init__(pos)
self.items = items

Expand Down
Loading

0 comments on commit 6397d54

Please sign in to comment.