diff --git a/.gitignore b/.gitignore index 16e4b82..fa01827 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ tags .idea/ +target +.artifactory +*~ diff --git a/README.md b/README.md index de8896e..3aa6fb3 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,21 @@ for WDL as well as Parsers in [Java](java) and Python using [PyWDL](https://gith For an implementation of an engine that can run WDL, try [Cromwell](http://github.com/broadinstitute/cromwell) -Overview --------- + + +* [Overview](#Overview) +* [Getting Started with WDL](#getting-started-with-wdl) + * [Hello World WDL](#hello-world-wdl) + * [Modifying Task Outputs](#modifying-task-outputs) + * [Referencing Files on Disk](#referencing-files-on-disk) + * [Using Globs to Specify Output](#using-globs-to-specify-output) + * [Using String Interpolation](#using-string-interpolation) + * [Aliasing Calls](#aliasing-calls) + * [Specifying Inputs and Using Declarations](#specifying-inputs-and-using-declarations) + * [Using Files as Inputs](#using-files-as-inputs) + * [Scatter/Gather](#scattergather) + +# Overview The Workflow Description Language is a domain specific language for describing tasks and workflows. @@ -88,7 +101,402 @@ workflow wf { } ``` -Architecture ------------- +# Getting Started with WDL + +We'll use Cromwell to run these examples but you can use any WDL engine of your choice. If you don't already have a reference to the Cromwell JAR file, one can be [downloaded](https://github.com/broadinstitute/cromwell/releases) + +## Hello World WDL + +Create a WDL simple file and save it as `hello.wdl`, for example: + +``` +task hello { + String name + + command { + echo 'hello ${name}!' + } + output { + File response = stdout() + } +} + +workflow test { + call hello +} +``` + +Generate a template `hello.json` file with the `inputs` subcommand: + +``` +$ java -jar cromwell.jar inputs hello.wdl +{ + "test.hello.name": "String" +} +``` + +WDL has a concept of fully-qualified names. In the above output, `test.hello.name` is a fully-qualified name which should be read as: the `name` input on the `hello` call within workflow `test`. Fully-qualified names are used to unambiguously refer to specific elements of a workflow. All inputs are specified by fully-qualified names and all outputs are returned as fully-qualified names. + +Modify this and save it to `hello.json`: + +``` +{ + "test.hello.name": "world" +} +``` + +Then, use the `run` subcommand to run the workflow: + +``` +$ java -jar cromwell.jar run hello.wdl hello.json +... truncated ... +{ + "test.hello.response": "/home/user/test/c1d15098-bb57-4a0e-bc52-3a8887f7b439/call-hello/stdout8818073565713629828.tmp" +} +``` + +Since the `hello` task returns a `File`, the result is a file that contains the string "hello world!" in it. + +## Modifying Task Outputs + +Currently the `hello` task returns a `File` with the greeting in it, but what if we wanted to return a `String` instead? + This can be done by utilizing the `read_string()` function: + +``` +task hello { + command { + echo 'hello ${name}!' + } + output { + String response = read_string(stdout()) + } +} + +workflow test { + call hello +} +``` + +Now when this is run, we get the string output for `test.hello.response`: + +``` +$ java -jar cromwell.jar run hello.wdl hello.json +... truncated ... +{ + "test.hello.response": "Hello world!" +} +``` + +`read_string` is a function in the [standard library](https://github.com/broadinstitute/wdl/blob/wdl2/SPEC.md#standard-library), which provides other useful functions for converting outputs to WDL data types. + +## Referencing Files on Disk + +So far we've only been dealing with the standard output of a command, but what if it writes a file to disk? Consider this example: + +``` +task hello { + command { + echo 'hello ${name}!' + } + output { + String response = read_string(stdout()) + } +} + +workflow test { + call hello +} +``` + +Now when this is run, we get the string output for `test.hello.response`: + +``` +$ java -jar cromwell.jar run hello.wdl hello.json +... truncated ... +{ + "test.hello.response": "Hello world!" +} +``` + +`read_string` is a function in the [standard library](https://github.com/broadinstitute/wdl/blob/wdl2/SPEC.md#standard-library), which provides other useful functions for converting outputs to WDL data types. + +## Using Globs to Specify Output + +We can use the glob() function to read multiple files at once: + +``` +task globber { + command <<< + for i in `seq 1 5` + do + mkdir out-$i + echo "globbing is my number $i best hobby" > out-$i/$i.txt + done + >>> + output { + Array[File] outFiles = glob("out-*/*.txt") + } +} + +workflow test { + call globber +} +``` + +The `outFiles` output array will contain all files found by evaluating the specified glob. + +## Using String Interpolation + +Sometimes, an output file is named as a function of one of its inputs. + +``` +task hello { + command { + echo 'hello ${name}!' > ${name}.txt + } + output { + String response = read_string("${name}.txt") + } +} + +workflow test { + call hello +} +``` + +Here the inputs and outputs are exactly the same as previous examples, however the intermediate output file name of this task is named differently for every invocation. + +## Aliasing Calls + +Say we wanted to call the `hello` task twice. Simply adding two `call hello` statements to the body of `workflow test` would result in non-unique fully-qualified names. To resolve this issue, `call` statements can be aliased using an `as` clause: + +``` +task hello { + command { + echo 'hello ${name}!' + } + output { + String response = read_string(stdout()) + } +} + +workflow test { + call hello + call hello as hello2 +} +``` + +Now, we need to specify a value for `test.hello2.name` in the hello.json file" + +``` +{ + "test.hello.name": "world", + "test.hello2.name": "boston" +} +``` + +Running this workflow now produces two outputs: + +``` +$ java -jar cromwell.jar run hello.wdl hello.json +... truncated ... +{ + "test.hello.response": "Hello world!", + "test.hello2.response": "Hello boston!" +} +``` + +## Specifying Inputs and Using Declarations + +A `call` can have an optional section to define inputs. As seen below, the key/value pairs represent the name of the input on the left-hand side and the expression for the input's value on the right-hand side: + +``` +task hello { + String name + String salutation + + command { + echo '${salutation} ${name}!' + } + output { + String response = read_string(stdout()) + } +} + +workflow test { + call hello { + input: salutation="greetings" + } + call hello as hello2 +} +``` + +Now, the `hello.json` would require three inputs: + +``` +{ + "test.hello.name": "world", + "test.hello2.name": "boston", + "test.hello2.salutation": "hello" +} +``` + +Running this workflow still gives us the two greetings we expect: + +``` +$ java -jar cromwell.jar run hello.wdl hello.json +... truncated ... +{ + "test.hello.response": "greetings world!", + "test.hello2.response": "hello boston!" +} +``` + +What if we wanted to parameterize the greeting and make it used for all invocations of task `hello`? In this situation, a declaration can be used: + +``` +task hello { + command { + echo '${salutation}, ${name}!' + } + output { + String response = read_string(stdout()) + } +} + +workflow test { + String greeting + call hello { + input: salutation=greeting + } + call hello as hello2 { + input: salutation=greeting + " and nice to meet you" + } +} +``` + +`String greeting` is referenced to satisfy the "salutation" parameter to both invocations of the `hello` task. + +The inputs required to run this would be: + +``` +{ + "test.hello.name": "world", + "test.hello2.name": "boston", + "test.greeting": "hello" +} +``` + +And this would produce the following outputs when run + +``` +$ java -jar cromwell.jar run hello.wdl hello.json +... truncated ... +{ + "test.hello.response": "hello, world!", + "test.hello2.response": "hello and nice to meet you, boston!" +} +``` + +## Using Files as Inputs + +So far every example has used the default type of `String` for every input. Passing files along to tasks is simply a matter of defining the input type as `File`: + +``` +task grep { + File file + + command { + grep -c '^...$' ${file} + } + output { + Int count = read_int(stdout()) + } +} + +workflow test { + call grep +} +``` + +The `read_int()` function here would read the contents of its parameter, and interpret the first line as an integer and return that value as a WDL `Int` type. + +If I specified a file called `test_file` with the contents of: + +``` +foo +bar +baz +quux +``` + +And then the inputs JSON file would be: + +``` +{ + "test.grep.file": "test_file" +} +``` + +The result of running this would be: + +``` +$ java -jar cromwell.jar run grep.wdl grep.json +... truncated ... +{ + "test.grep.count": 3 +} +``` + +## Scatter/Gather + +Scatter blocks can be used to run the same call multiple times but only varying a specific parameter on each invocation. Consider the following example: + +``` +task prepare { + command <<< + python -c "print('one\ntwo\nthree\nfour')" + >>> + output { + Array[String] array = read_lines(stdout()) + } +} + +task analysis { + String str + command <<< + python -c "print('_${str}_')" + >>> + output { + String out = read_string(stdout()) + } +} + +task gather { + Array[String] array + command <<< + echo ${sep=' ' array} + >>> + output { + String str = read_string(stdout()) + } +} + +workflow example { + call prepare + scatter (x in prepare.array) { + call analysis {input: str=x} + } + call gather {input: array=analysis.out} +} +``` + +This example calls the `analysis` task once for each element in the array that the `prepare` task outputs. The resulting outputs of this workflow would be: -![WDL Arch](http://i.imgur.com/OYtIYjf.png) +``` +{ + "example.analysis.out": ["_one_", "_two_", "_three_", "_four_"], + "example.gather.str": "_one_ _two_ _three_ _four_", + "example.prepare.array": ["one", "two", "three", "four"] +} +``` diff --git a/scala/wdl4s/README.md b/scala/wdl4s/README.md new file mode 100644 index 0000000..5b322ed --- /dev/null +++ b/scala/wdl4s/README.md @@ -0,0 +1,59 @@ +# Scala API Usage + +The main entry point into the parser is the `WdlNamespace` object. A WDL file is considered a namespace, and other namespaces can be included by using the `import` statement (but only with an `as` clause). + +```scala +import java.io.File +import wdlscala.NamespaceWithWorkflow + +object main { + def main(args: Array[String]) { + val ns = NamespaceWithWorkflow.load(""" + |task a { + | command { ps } + |} + |workflow wf { + | call a + |}""".stripMargin) + + println(s"Workflow: ${ns.workflow.name}") + ns.workflow.calls foreach {call => + println(s"Call: ${call.name}") + } + + ns.tasks foreach {task => + println(s"Task: ${task.name}") + println(s"Command: ${task.commandTemplate}") + } + } +} +``` + +To access only the parser, use the `AstTools` library, as follows: + +```scala +import java.io.File +import wdlscala.AstTools +import wdlscala.AstTools.EnhancedAstNode + +object main { + def main(args: Array[String]) { + /* Create syntax tree from contents of file */ + val ast = AstTools.getAst(new File(args(0))) + + /* Second parameter is a descriptor about where the first string came from. + * Most of the time this would be the URI of where the text was loaded from, + * but there are no restrictions on what the string can be. + */ + val ast2 = AstTools.getAst("workflow simple {}", "string") + + /* Print the AST */ + println(ast.toPrettyString) + + /* Traverse the tree to find all Task definitions */ + val taskAsts = AstTools.findAsts(ast, "Task") foreach {ast => + println(s"Task name: ${ast.getAttribute("name").sourceString}") + } + } +} +``` \ No newline at end of file diff --git a/scala/wdl4s/build.sbt b/scala/wdl4s/build.sbt new file mode 100644 index 0000000..ea305c7 --- /dev/null +++ b/scala/wdl4s/build.sbt @@ -0,0 +1,66 @@ +import sbt.Keys._ +import sbtassembly.MergeStrategy + +name := "wdl4s" + +version := "0.1" + +organization := "org.broadinstitute" + +scalaVersion := "2.11.7" + +assemblyJarName in assembly := "wdl4s-" + version.value + ".jar" + +logLevel in assembly := Level.Info + +val DowngradedSprayV = "1.3.1" + +libraryDependencies ++= Seq( + "com.typesafe.scala-logging" %% "scala-logging" % "3.1.0", + "io.spray" %% "spray-json" % DowngradedSprayV, + "org.scalaz" %% "scalaz-core" % "7.2.0", + "commons-codec" % "commons-codec" % "1.10", + "commons-io" % "commons-io" % "2.4", + "org.apache.commons" % "commons-lang3" % "3.4", + "com.github.pathikrit" %% "better-files" % "2.13.0", + //---------- Test libraries -------------------// + "org.scalatest" %% "scalatest" % "2.2.5" % Test +) + +val customMergeStrategy: String => MergeStrategy = { + case x if Assembly.isConfigFile(x) => + MergeStrategy.concat + case PathList(ps@_*) if Assembly.isReadme(ps.last) || Assembly.isLicenseFile(ps.last) => + MergeStrategy.rename + case PathList("META-INF", path@_*) => + path map { + _.toLowerCase + } match { + case ("manifest.mf" :: Nil) | ("index.list" :: Nil) | ("dependencies" :: Nil) => + MergeStrategy.discard + case ps@(x :: xs) if ps.last.endsWith(".sf") || ps.last.endsWith(".dsa") => + MergeStrategy.discard + case "plexus" :: xs => + MergeStrategy.discard + case "spring.tooling" :: xs => + MergeStrategy.discard + case "services" :: xs => + MergeStrategy.filterDistinctLines + case ("spring.schemas" :: Nil) | ("spring.handlers" :: Nil) => + MergeStrategy.filterDistinctLines + case _ => MergeStrategy.deduplicate + } + case "asm-license.txt" | "overview.html" | "cobertura.properties" => + MergeStrategy.discard + case _ => MergeStrategy.deduplicate +} + +assemblyMergeStrategy in assembly := customMergeStrategy + +// The reason why -Xmax-classfile-name is set is because this will fail +// to build on Docker otherwise. The reason why it's 200 is because it +// fails if the value is too close to 256 (even 254 fails). For more info: +// +// https://github.com/sbt/sbt-assembly/issues/69 +// https://github.com/scala/pickling/issues/10 +scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature", "-Xmax-classfile-name", "200") diff --git a/scala/wdl4s/project/plugins.sbt b/scala/wdl4s/project/plugins.sbt new file mode 100644 index 0000000..f1d6672 --- /dev/null +++ b/scala/wdl4s/project/plugins.sbt @@ -0,0 +1,9 @@ +addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.6.4") + +addSbtPlugin("com.github.gseitz" % "sbt-release" % "0.8.3") + +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.1") + +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.0.4") + +addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.0.0") diff --git a/scala/wdl4s/src/main/java/wdl4s/parser/MemorySize.java b/scala/wdl4s/src/main/java/wdl4s/parser/MemorySize.java new file mode 100644 index 0000000..2a0423c --- /dev/null +++ b/scala/wdl4s/src/main/java/wdl4s/parser/MemorySize.java @@ -0,0 +1,35 @@ +package wdl4s.parser; + +/** + * Convenience enum for parsing memory specifications. If feels like there should be a preexisting library + * for this but I couldn't find one. + */ +public enum MemorySize { + Bytes(1, "B"), + KiB(1_000, "KiB"), + MiB(KiB.multiplier * KiB.multiplier, "MiB"), + GiB(KiB.multiplier * MiB.multiplier, "GiB"), + TiB(KiB.multiplier * GiB.multiplier, "TiB"), + KB(1 << 10, "KB"), + MB(KB.multiplier * KB.multiplier, "M", "MB"), + GB(KB.multiplier * MB.multiplier, "G", "GB"), + TB(KB.multiplier * GB.multiplier, "T", "TB"); + + public final long multiplier; + public final String[] suffixes; + + MemorySize(long multiplier, String... suffixes) { + this.multiplier = multiplier; + this.suffixes = suffixes; + } + + /** Convert from the units of this memory size to individual bytes. */ + public double toBytes(double prefix) { + return prefix * multiplier; + } + + /** Convert from individual bytes to units of this memory size. */ + public double fromBytes(double bytes) { + return bytes / multiplier; + } +} diff --git a/scala/wdl4s/src/main/java/wdl4s/parser/WdlParser.java b/scala/wdl4s/src/main/java/wdl4s/parser/WdlParser.java new file mode 100644 index 0000000..8c3aedd --- /dev/null +++ b/scala/wdl4s/src/main/java/wdl4s/parser/WdlParser.java @@ -0,0 +1,7213 @@ + +/* + * This file was generated by Hermes Parser Generator on Tue Dec 1 13:51:09 2015 + * + * Hermes command: hermes generate src/main/resources/grammar.hgr --language=java --directory=src/main/java --name=wdl --java-package=wdl_scala.parser --java-use-apache-commons --java-imports=org.apache.commons.lang3.StringEscapeUtils --header + * Run from: ../../.. (relative to this file) + * Hermes version: hermes-parser 2.0rc5 + * + * !!! DO NOT CHANGE THIS FILE DIRECTLY !!! + * + * If you wish to change something in this file, either change the grammar and + * re-generate this file, or change the templates in Hermes and regenerate. + * See the Hermes repository: http://github.com/scottfrazer/hermes + */ +package wdl4s.parser; +import java.util.*; +import java.io.IOException; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.nio.*; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang3.StringEscapeUtils; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.lang.reflect.Method; +public class WdlParser { + private static Map> nonterminal_first; + private static Map> nonterminal_follow; + private static Map> rule_first; + private static Map> nonterminal_rules; + private static Map rules; + public static WdlTerminalMap terminal_map = new WdlTerminalMap(WdlTerminalIdentifier.values()); + public WdlParser() { + try { + lexer_init(); + } catch(Exception e) {} + } + public static String join(Collection s, String delimiter) { + StringBuilder builder = new StringBuilder(); + Iterator iter = s.iterator(); + while (iter.hasNext()) { + builder.append(iter.next()); + if (!iter.hasNext()) { + break; + } + builder.append(delimiter); + } + return builder.toString(); + } + public static String getIndentString(int spaces) { + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < spaces; i++) { + sb.append(' '); + } + return sb.toString(); + } + public static String readStdin() throws IOException { + InputStreamReader stream = new InputStreamReader(System.in, "utf-8"); + char buffer[] = new char[System.in.available()]; + try { + stream.read(buffer, 0, System.in.available()); + } finally { + stream.close(); + } + return new String(buffer); + } + public static String readFile(String path) throws IOException { + FileInputStream stream = new FileInputStream(new File(path)); + try { + FileChannel fc = stream.getChannel(); + MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); + /* Instead of using default, pass in a decoder. */ + return Charset.defaultCharset().decode(bb).toString(); + } + finally { + stream.close(); + } + } + public static class SyntaxError extends Exception { + public SyntaxError(String message) { + super(message); + } + } + public interface SyntaxErrorFormatter { + /* Called when the parser runs out of tokens but isn't finished parsing. */ + String unexpectedEof(String method, List expected, List nt_rules); + /* Called when the parser finished parsing but there are still tokens left in the stream. */ + String excessTokens(String method, Terminal terminal); + /* Called when the parser is expecting one token and gets another. */ + String unexpectedSymbol(String method, Terminal actual, List expected, String rule); + /* Called when the parser is expecing a tokens but there are no more tokens. */ + String noMoreTokens(String method, TerminalIdentifier expecting, Terminal last); + /* Invalid terminal is found in the token stream. */ + String invalidTerminal(String method, Terminal invalid); + } + public static class TokenStream extends ArrayList { + private int index; + public TokenStream(List terminals) { + super(terminals); + reset(); + } + public TokenStream() { + reset(); + } + public void reset() { + this.index = 0; + } + public Terminal advance() { + this.index += 1; + return this.current(); + } + public Terminal current() { + try { + return this.get(this.index); + } catch (IndexOutOfBoundsException e) { + return null; + } + } + public Terminal last() { + return this.get(this.size() - 1); + } + } + public static class NonTerminal { + private int id; + private String string; + NonTerminal(int id, String string) { + this.id = id; + this.string = string; + } + public int getId() { + return this.id; + } + public String getString() { + return this.string; + } + public String toString() { + return this.string; + } + } + public interface AstTransform {} + public static class AstTransformNodeCreator implements AstTransform { + private String name; + private LinkedHashMap parameters; + AstTransformNodeCreator(String name, LinkedHashMap parameters) { + this.name = name; + this.parameters = parameters; + } + public Map getParameters() { + return this.parameters; + } + public String getName() { + return this.name; + } + public String toString() { + LinkedList items = new LinkedList(); + for (final Map.Entry entry : this.parameters.entrySet()) { + items.add(entry.getKey() + "=$" + entry.getValue().toString()); + } + return "AstNodeCreator: " + this.name + "( " + join(items, ", ") + " )"; + } + } + public static class AstTransformSubstitution implements AstTransform { + private int index; + AstTransformSubstitution(int index) { + this.index = index; + } + public int getIndex() { + return this.index; + } + public String toString() { + return "AstSubstitution: $" + Integer.toString(this.index); + } + } + public interface AstNode { + public String toString(); + public String toPrettyString(); + public String toPrettyString(int indent); + } + public static class AstList extends ArrayList implements AstNode { + public String toString() { + return "[" + join(this, ", ") + "]"; + } + public String toPrettyString() { + return toPrettyString(0); + } + public String toPrettyString(int indent) { + String spaces = getIndentString(indent); + if (this.size() == 0) { + return spaces + "[]"; + } + ArrayList elements = new ArrayList(); + for ( AstNode node : this ) { + elements.add(node.toPrettyString(indent + 2)); + } + return spaces + "[\n" + join(elements, ",\n") + "\n" + spaces + "]"; + } + } + public static class Ast implements AstNode { + private String name; + private Map attributes; + Ast(String name, Map attributes) { + this.name = name; + this.attributes = attributes; + } + public AstNode getAttribute(String name) { + return this.attributes.get(name); + } + public Map getAttributes() { + return this.attributes; + } + public String getName() { + return this.name; + } + public String toString() { + Formatter formatter = new Formatter(new StringBuilder(), Locale.US); + LinkedList attributes = new LinkedList(); + for (final Map.Entry attribute : this.attributes.entrySet()) { + final String name = attribute.getKey(); + final AstNode node = attribute.getValue(); + final String nodeStr = (node == null) ? "None" : node.toString(); + attributes.add(name + "=" + nodeStr); + } + formatter.format("(%s: %s)", this.name, join(attributes, ", ")); + return formatter.toString(); + } + public String toPrettyString() { + return toPrettyString(0); + } + public String toPrettyString(int indent) { + String spaces = getIndentString(indent); + ArrayList children = new ArrayList(); + for( Map.Entry attribute : this.attributes.entrySet() ) { + String valueString = attribute.getValue() == null ? "None" : attribute.getValue().toPrettyString(indent + 2).trim(); + children.add(spaces + " " + attribute.getKey() + "=" + valueString); + } + return spaces + "(" + this.name + ":\n" + join(children, ",\n") + "\n" + spaces + ")"; + } + } + public interface ParseTreeNode { + public AstNode toAst(); + public String toString(); + public String toPrettyString(); + public String toPrettyString(int indent); + } + public static class Terminal implements AstNode, ParseTreeNode + { + private int id; + private String terminal_str; + private String source_string; + private String resource; + private int line; + private int col; + public Terminal(int id, String terminal_str, String source_string, String resource, int line, int col) { + this.id = id; + this.terminal_str = terminal_str; + this.source_string = source_string; + this.resource = resource; + this.line = line; + this.col = col; + } + public int getId() { + return this.id; + } + public String getTerminalStr() { + return this.terminal_str; + } + public String getSourceString() { + return this.source_string; + } + public String getResource() { + return this.resource; + } + public int getLine() { + return this.line; + } + public int getColumn() { + return this.col; + } + public String toString() { + byte[] source_string_bytes; + try { + source_string_bytes = this.getSourceString().getBytes("UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + source_string_bytes = this.getSourceString().getBytes(); + } + return String.format("<%s:%d:%d %s \"%s\">", + this.getResource(), + this.getLine(), + this.getColumn(), + this.getTerminalStr(), + Base64.encodeBase64String(source_string_bytes) + ); + } + public String toPrettyString() { + return toPrettyString(0); + } + public String toPrettyString(int indent) { + return getIndentString(indent) + this.toString(); + } + public AstNode toAst() { return this; } + } + public static class ParseTree implements ParseTreeNode { + private NonTerminal nonterminal; + private ArrayList children; + private boolean isExpr, isNud, isPrefix, isInfix, isExprNud; + private int nudMorphemeCount; + private Terminal listSeparator; + private boolean list; + private AstTransform astTransform; + ParseTree(NonTerminal nonterminal) { + this.nonterminal = nonterminal; + this.children = new ArrayList(); + this.astTransform = null; + this.isExpr = false; + this.isNud = false; + this.isPrefix = false; + this.isInfix = false; + this.isExprNud = false; + this.nudMorphemeCount = 0; + this.listSeparator = null; + this.list = false; + } + public void setExpr(boolean value) { this.isExpr = value; } + public void setNud(boolean value) { this.isNud = value; } + public void setPrefix(boolean value) { this.isPrefix = value; } + public void setInfix(boolean value) { this.isInfix = value; } + public void setExprNud(boolean value) { this.isExprNud = value; } + public void setAstTransformation(AstTransform value) { this.astTransform = value; } + public void setNudMorphemeCount(int value) { this.nudMorphemeCount = value; } + public void setList(boolean value) { this.list = value; } + public void setListSeparator(Terminal value) { this.listSeparator = value; } + public int getNudMorphemeCount() { return this.nudMorphemeCount; } + public List getChildren() { return this.children; } + public boolean isInfix() { return this.isInfix; } + public boolean isPrefix() { return this.isPrefix; } + public boolean isExpr() { return this.isExpr; } + public boolean isNud() { return this.isNud; } + public boolean isExprNud() { return this.isExprNud; } + public void add(ParseTreeNode tree) { + if (this.children == null) { + this.children = new ArrayList(); + } + this.children.add(tree); + } + private boolean isCompoundNud() { + if ( this.children.size() > 0 && this.children.get(0) instanceof ParseTree ) { + ParseTree child = (ParseTree) this.children.get(0); + if ( child.isNud() && !child.isPrefix() && !this.isExprNud() && !this.isInfix() ) { + return true; + } + } + return false; + } + public AstNode toAst() { + if ( this.list == true ) { + AstList astList = new AstList(); + int end = this.children.size() - 1; + if ( this.children.size() == 0 ) { + return astList; + } + for (int i = 0; i < this.children.size() - 1; i++) { + if (this.children.get(i) instanceof Terminal && this.listSeparator != null && ((Terminal)this.children.get(i)).id == this.listSeparator.id) + continue; + astList.add(this.children.get(i).toAst()); + } + astList.addAll((AstList) this.children.get(this.children.size() - 1).toAst()); + return astList; + } else if ( this.isExpr ) { + if ( this.astTransform instanceof AstTransformSubstitution ) { + AstTransformSubstitution astSubstitution = (AstTransformSubstitution) astTransform; + return this.children.get(astSubstitution.getIndex()).toAst(); + } else if ( this.astTransform instanceof AstTransformNodeCreator ) { + AstTransformNodeCreator astNodeCreator = (AstTransformNodeCreator) this.astTransform; + LinkedHashMap parameters = new LinkedHashMap(); + ParseTreeNode child; + for ( final Map.Entry parameter : astNodeCreator.getParameters().entrySet() ) { + String name = parameter.getKey(); + int index = parameter.getValue().intValue(); + if ( index == '$' ) { + child = this.children.get(0); + } else if ( this.isCompoundNud() ) { + ParseTree firstChild = (ParseTree) this.children.get(0); + if ( index < firstChild.getNudMorphemeCount() ) { + child = firstChild.getChildren().get(index); + } else { + index = index - firstChild.getNudMorphemeCount() + 1; + child = this.children.get(index); + } + } else if ( this.children.size() == 1 && !(this.children.get(0) instanceof ParseTree) && !(this.children.get(0) instanceof List) ) { + // TODO: I don't think this should ever be called + child = this.children.get(0); + } else { + child = this.children.get(index); + } + parameters.put(name, child.toAst()); + } + return new Ast(astNodeCreator.getName(), parameters); + } + } else { + AstTransformSubstitution defaultAction = new AstTransformSubstitution(0); + AstTransform action = this.astTransform != null ? this.astTransform : defaultAction; + if (this.children.size() == 0) return null; + if (action instanceof AstTransformSubstitution) { + AstTransformSubstitution astSubstitution = (AstTransformSubstitution) action; + return this.children.get(astSubstitution.getIndex()).toAst(); + } else if (action instanceof AstTransformNodeCreator) { + AstTransformNodeCreator astNodeCreator = (AstTransformNodeCreator) action; + LinkedHashMap evaluatedParameters = new LinkedHashMap(); + for ( Map.Entry baseParameter : astNodeCreator.getParameters().entrySet() ) { + String name = baseParameter.getKey(); + int index2 = baseParameter.getValue().intValue(); + evaluatedParameters.put(name, this.children.get(index2).toAst()); + } + return new Ast(astNodeCreator.getName(), evaluatedParameters); + } + } + return null; + } + public String toString() { + ArrayList children = new ArrayList(); + for (ParseTreeNode child : this.children) { + children.add(child.toString()); + } + return "(" + this.nonterminal.getString() + ": " + join(children, ", ") + ")"; + } + public String toPrettyString() { + return toPrettyString(0); + } + public String toPrettyString(int indent) { + if (this.children.size() == 0) { + return "(" + this.nonterminal.toString() + ": )"; + } + String spaces = getIndentString(indent); + ArrayList children = new ArrayList(); + for ( ParseTreeNode node : this.children ) { + String sub = node.toPrettyString(indent + 2).trim(); + children.add(spaces + " " + sub); + } + return spaces + "(" + this.nonterminal.toString() + ":\n" + join(children, ",\n") + "\n" + spaces + ")"; + } + } + private static class ParserContext { + public TokenStream tokens; + public SyntaxErrorFormatter error_formatter; + public String nonterminal; + public String rule; + public ParserContext(TokenStream tokens, SyntaxErrorFormatter error_formatter) { + this.tokens = tokens; + this.error_formatter = error_formatter; + } + } + private static class DefaultSyntaxErrorFormatter implements SyntaxErrorFormatter { + public String unexpectedEof(String method, List expected, List nt_rules) { + return "Error: unexpected end of file"; + } + public String excessTokens(String method, Terminal terminal) { + return "Finished parsing without consuming all tokens."; + } + public String unexpectedSymbol(String method, Terminal actual, List expected, String rule) { + ArrayList expected_terminals = new ArrayList(); + for ( TerminalIdentifier e : expected ) { + expected_terminals.add(e.string()); + } + return String.format( + "Unexpected symbol (line %d, col %d) when parsing parse_%s. Expected %s, got %s.", + actual.getLine(), actual.getColumn(), method, join(expected_terminals, ", "), actual.toPrettyString() + ); + } + public String noMoreTokens(String method, TerminalIdentifier expecting, Terminal last) { + return "No more tokens. Expecting " + expecting.string(); + } + public String invalidTerminal(String method, Terminal invalid) { + return "Invalid symbol ID: "+invalid.getId()+" ("+invalid.getTerminalStr()+")"; + } + } + public interface TerminalMap { + TerminalIdentifier get(String string); + TerminalIdentifier get(int id); + boolean isValid(String string); + boolean isValid(int id); + } + public static class WdlTerminalMap implements TerminalMap { + private Map id_to_term; + private Map str_to_term; + WdlTerminalMap(WdlTerminalIdentifier[] terminals) { + id_to_term = new HashMap(); + str_to_term = new HashMap(); + for( WdlTerminalIdentifier terminal : terminals ) { + Integer id = new Integer(terminal.id()); + String str = terminal.string(); + id_to_term.put(id, terminal); + str_to_term.put(str, terminal); + } + } + public TerminalIdentifier get(String string) { return this.str_to_term.get(string); } + public TerminalIdentifier get(int id) { return this.id_to_term.get(id); } + public boolean isValid(String string) { return this.str_to_term.containsKey(string); } + public boolean isValid(int id) { return this.id_to_term.containsKey(id); } + } + public interface TerminalIdentifier { + public int id(); + public String string(); + } + public enum WdlTerminalIdentifier implements TerminalIdentifier { + TERMINAL_QMARK(0, "qmark"), + TERMINAL_COMMA(1, "comma"), + TERMINAL_WHILE(2, "while"), + TERMINAL_LPAREN(3, "lparen"), + TERMINAL_AS(4, "as"), + TERMINAL_LBRACE(5, "lbrace"), + TERMINAL_ASTERISK(6, "asterisk"), + TERMINAL_FQN(7, "fqn"), + TERMINAL_IF(8, "if"), + TERMINAL_TYPE_E(9, "type_e"), + TERMINAL_CALL(10, "call"), + TERMINAL_EQUAL(11, "equal"), + TERMINAL_SLASH(12, "slash"), + TERMINAL_PERCENT(13, "percent"), + TERMINAL_GT(14, "gt"), + TERMINAL_IMPORT(15, "import"), + TERMINAL_RSQUARE(16, "rsquare"), + TERMINAL_CMD_PARAM_END(17, "cmd_param_end"), + TERMINAL_DOUBLE_AMPERSAND(18, "double_ampersand"), + TERMINAL_COLON(19, "colon"), + TERMINAL_RPAREN(20, "rparen"), + TERMINAL_IN(21, "in"), + TERMINAL_DOUBLE_EQUAL(22, "double_equal"), + TERMINAL_PARAMETER_META(23, "parameter_meta"), + TERMINAL_CMD_ATTR_HINT(24, "cmd_attr_hint"), + TERMINAL_RAW_CMD_END(25, "raw_cmd_end"), + TERMINAL_IDENTIFIER(26, "identifier"), + TERMINAL_INTEGER(27, "integer"), + TERMINAL_LTEQ(28, "lteq"), + TERMINAL_INPUT(29, "input"), + TERMINAL_RBRACE(30, "rbrace"), + TERMINAL_TYPE(31, "type"), + TERMINAL_STRING(32, "string"), + TERMINAL_GTEQ(33, "gteq"), + TERMINAL_PLUS(34, "plus"), + TERMINAL_LT(35, "lt"), + TERMINAL_RAW_CMD_START(36, "raw_cmd_start"), + TERMINAL_DASH(37, "dash"), + TERMINAL_FLOAT(38, "float"), + TERMINAL_CMD_PART(39, "cmd_part"), + TERMINAL_TASK(40, "task"), + TERMINAL_DOT(41, "dot"), + TERMINAL_DOUBLE_PIPE(42, "double_pipe"), + TERMINAL_E(43, "e"), + TERMINAL_NOT_EQUAL(44, "not_equal"), + TERMINAL_WORKFLOW(45, "workflow"), + TERMINAL_CMD_PARAM_START(46, "cmd_param_start"), + TERMINAL_NOT(47, "not"), + TERMINAL_OBJECT(48, "object"), + TERMINAL_OUTPUT(49, "output"), + TERMINAL_SCATTER(50, "scatter"), + TERMINAL_RAW_COMMAND(51, "raw_command"), + TERMINAL_LSQUARE(52, "lsquare"), + TERMINAL_RUNTIME(53, "runtime"), + TERMINAL_META(54, "meta"), + TERMINAL_BOOLEAN(55, "boolean"), + END_SENTINAL(-3, "END_SENTINAL"); + private final int id; + private final String string; + WdlTerminalIdentifier(int id, String string) { + this.id = id; + this.string = string; + } + public int id() {return id;} + public String string() {return string;} + } + /* table[nonterminal][terminal] = rule */ + private static final int[][] table = { + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 70, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 41, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, 143, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 144, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, 95, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 96, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, -1, -1, -1, -1, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 81, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 51, -1, -1, -1, 54, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, 39, -1, 39, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 36, -1, 39, 39, -1, -1, -1, -1, 39, -1, 39, -1, -1, 39, 39, -1, -1, -1, -1, 39, -1, -1, -1, 39, 39, -1, -1, -1, 39, -1, -1, 39 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 56, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 57, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 58, -1, -1, -1, -1, -1, -1, -1, 57, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, 101, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, 78, -1, 77, 78, -1, -1, 78, 78, 78, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 78, 78, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 78, 78, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, -1, -1, -1, -1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, 42, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, 42, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, 88, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 89, 89, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, 67, -1, -1, -1, -1, -1, 67, 67, 67, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 68, 67, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 67, 67, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, 86, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, 108, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 109, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 49, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 32, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 29, -1, -1, -1, -1, -1, -1, 29, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 12, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 12, -1, -1, -1, -1, 12, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 106, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 105, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, 107, -1, -1, -1, -1, -1, -1, 110, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 107, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, 60, -1, -1, -1, -1, -1, 60, 60, 60, 59, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 60, -1, -1, -1, -1, -1, 60, 60, 60, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 60, 60, 60, -1, 60, 60, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 52, -1, -1, -1, 53, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, 94, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 97, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 34, -1, -1, -1, -1, -1, -1, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 23, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 87, -1, -1, 90, 90, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 5, -1, -1, -1, -1, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, 38, -1, 38, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 37, -1, 38, 38, -1, -1, -1, -1, 38, -1, 38, -1, -1, 38, 38, -1, -1, -1, -1, 38, -1, -1, -1, 38, 38, -1, -1, -1, 38, -1, -1, 38 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, 43, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 44, 43, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 92, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, 47, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 47, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, 85, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, 129, -1, 129, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 132, -1, -1, -1, 132, -1, -1, -1, -1, -1, 129, 129, -1, -1, -1, -1, 129, -1, 129, -1, -1, 129, 129, -1, -1, -1, -1, 129, -1, -1, -1, 129, 129, -1, -1, -1, 129, -1, -1, 129 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 136, -1, -1, -1, 139, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 102, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 50, -1 }, + { -1, -1, -1, 65, -1, 65, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 65, 65, -1, -1, -1, -1, 65, -1, 65, -1, -1, 65, 65, -1, -1, -1, -1, 65, -1, -1, -1, 65, 65, -1, -1, -1, 65, -1, -1, 65 }, + { -1, -1, -1, -1, 93, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 19, -1, -1, -1, -1, -1, -1, 22, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 19, -1, 19, -1, 19, 19, -1 }, + { -1, -1, -1, -1, -1, 55, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 40, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, 80, -1, -1, 79, -1, -1, 80, 80, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 80, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 80, 80, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, 104, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 64, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, 130, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 131, -1, -1, -1, 131, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, 142, -1, 142, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 142, 142, -1, -1, 145, -1, 142, -1, 142, -1, -1, 142, 142, -1, -1, -1, -1, 142, -1, -1, -1, 142, 142, -1, -1, -1, 142, -1, -1, 142 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, 16, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 17, -1, -1, -1, -1, -1, 17, -1, 16, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 17, -1, 17, -1, 17, 17, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 30, -1, -1, -1, -1, -1, -1, 30, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 18, -1, -1, -1, -1, -1, 18, -1, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 18, -1, 18, -1, 18, 18, -1 }, + { -1, -1, 73, -1, -1, -1, -1, -1, 74, 72, 71, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 72, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 76, 75, -1, -1, -1, -1, -1 }, + { -1, -1, 103, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 48, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 27, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 25, -1, 24, -1, 26, 28, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 83, 84, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8, -1, -1, -1, -1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, 61, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 61, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 20, -1, -1, -1, -1, -1, -1, 21, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 20, -1, 20, -1, 20, 20, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 46, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, 66, -1, -1, -1, -1, -1, 66, 66, 66, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 69, 66, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 66, 66, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 98, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, 137, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 138, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 33, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1, -1, -1, 100, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 100, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 99, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + }; + static { + Map> map = new HashMap>(); + map.put(56, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(57, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_ATTR_HINT, + })); + map.put(58, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + })); + map.put(59, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_COMMA, + })); + map.put(60, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_FQN, + })); + map.put(61, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IMPORT, + })); + map.put(62, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CALL, + })); + map.put(63, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(64, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_ATTR_HINT, + })); + map.put(65, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(66, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_QMARK, + WdlTerminalIdentifier.TERMINAL_PLUS, + })); + map.put(67, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_FQN, + })); + map.put(68, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_AS, + })); + map.put(69, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(70, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + })); + map.put(71, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_COMMA, + })); + map.put(72, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_CALL, + })); + map.put(73, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_LBRACE, + })); + map.put(74, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_COMMA, + })); + map.put(75, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + })); + map.put(76, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_PARAM_START, + WdlTerminalIdentifier.TERMINAL_CMD_PART, + })); + map.put(77, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_AS, + })); + map.put(78, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(79, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_SCATTER, + })); + map.put(80, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + })); + map.put(81, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_EQUAL, + })); + map.put(82, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(83, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(84, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_FQN, + })); + map.put(85, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_PARAM_START, + WdlTerminalIdentifier.TERMINAL_CMD_PART, + })); + map.put(86, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IMPORT, + })); + map.put(87, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IMPORT, + })); + map.put(88, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + })); + map.put(89, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(90, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(91, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_ATTR_HINT, + })); + map.put(92, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + })); + map.put(93, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(94, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + })); + map.put(95, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_INPUT, + })); + map.put(96, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_INPUT, + })); + map.put(97, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(98, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(99, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_DOT, + })); + map.put(100, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_META, + })); + map.put(101, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(102, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_AS, + })); + map.put(103, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_META, + })); + map.put(104, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_LBRACE, + })); + map.put(105, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_PARAM_START, + })); + map.put(106, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_LBRACE, + })); + map.put(107, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IF, + })); + map.put(108, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_QMARK, + WdlTerminalIdentifier.TERMINAL_PLUS, + })); + map.put(109, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_COMMA, + })); + map.put(110, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(111, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(112, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + })); + map.put(113, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_PARAM_START, + WdlTerminalIdentifier.TERMINAL_CMD_PART, + })); + map.put(114, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + })); + map.put(115, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_CALL, + })); + map.put(116, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_WHILE, + })); + map.put(117, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RUNTIME, + })); + map.put(118, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_META, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + })); + map.put(119, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_INPUT, + })); + map.put(120, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + WdlTerminalIdentifier.TERMINAL_IMPORT, + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(121, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + })); + map.put(122, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_META, + })); + map.put(123, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + })); + map.put(124, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_EQUAL, + })); + map.put(125, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_CALL, + })); + map.put(126, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + })); + map.put(127, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_AS, + })); + map.put(128, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_COMMA, + })); + map.put(129, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + })); + map.put(130, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_DOT, + })); + nonterminal_first = Collections.unmodifiableMap(map); + } + static { + Map> map = new HashMap>(); + map.put(56, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(57, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_ATTR_HINT, + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(58, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_QMARK, + WdlTerminalIdentifier.TERMINAL_COMMA, + WdlTerminalIdentifier.TERMINAL_RSQUARE, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_PLUS, + })); + map.put(59, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(60, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(61, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(62, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_CALL, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(63, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(64, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(65, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RBRACE, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(66, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(67, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_FQN, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(68, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_CALL, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(69, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(70, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(71, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_INPUT, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(72, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(73, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_CALL, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(74, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RSQUARE, + })); + map.put(75, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_RBRACE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_META, + })); + map.put(76, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RAW_CMD_END, + })); + map.put(77, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + WdlTerminalIdentifier.TERMINAL_IMPORT, + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(78, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_COMMA, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(79, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_CALL, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(80, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RSQUARE, + })); + map.put(81, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_INPUT, + WdlTerminalIdentifier.TERMINAL_RBRACE, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_CALL, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_META, + })); + map.put(82, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(83, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_META, + WdlTerminalIdentifier.TERMINAL_COMMA, + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_ASTERISK, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_CALL, + WdlTerminalIdentifier.TERMINAL_SLASH, + WdlTerminalIdentifier.TERMINAL_PERCENT, + WdlTerminalIdentifier.TERMINAL_GT, + WdlTerminalIdentifier.TERMINAL_RSQUARE, + WdlTerminalIdentifier.TERMINAL_CMD_PARAM_END, + WdlTerminalIdentifier.TERMINAL_DOUBLE_AMPERSAND, + WdlTerminalIdentifier.TERMINAL_COLON, + WdlTerminalIdentifier.TERMINAL_RPAREN, + WdlTerminalIdentifier.TERMINAL_DOUBLE_EQUAL, + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_CMD_ATTR_HINT, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_LTEQ, + WdlTerminalIdentifier.TERMINAL_INPUT, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_GTEQ, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LT, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_DOUBLE_PIPE, + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_NOT_EQUAL, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_RBRACE, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(84, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(85, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_PARAM_START, + WdlTerminalIdentifier.TERMINAL_RAW_CMD_END, + WdlTerminalIdentifier.TERMINAL_CMD_PART, + })); + map.put(86, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + WdlTerminalIdentifier.TERMINAL_IMPORT, + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(87, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(88, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(89, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_INPUT, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(90, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(91, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(92, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(93, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_COMMA, + WdlTerminalIdentifier.TERMINAL_INPUT, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(94, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(95, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(96, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_INPUT, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(97, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RPAREN, + WdlTerminalIdentifier.TERMINAL_RSQUARE, + })); + map.put(98, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(99, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_FQN, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(100, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_RBRACE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_META, + })); + map.put(101, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_COMMA, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(102, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_CALL, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(103, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(104, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_RBRACE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_META, + })); + map.put(105, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_PARAM_START, + WdlTerminalIdentifier.TERMINAL_RAW_CMD_END, + WdlTerminalIdentifier.TERMINAL_CMD_PART, + })); + map.put(106, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_CALL, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(107, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_CALL, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(108, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(109, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RPAREN, + WdlTerminalIdentifier.TERMINAL_RSQUARE, + })); + map.put(110, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(111, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(112, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_META, + WdlTerminalIdentifier.TERMINAL_INPUT, + })); + map.put(113, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RAW_CMD_END, + })); + map.put(114, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_META, + WdlTerminalIdentifier.TERMINAL_INPUT, + })); + map.put(115, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_CALL, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(116, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_CALL, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(117, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_RBRACE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_META, + })); + map.put(118, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + WdlTerminalIdentifier.TERMINAL_RBRACE, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_META, + })); + map.put(119, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(120, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(121, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_INPUT, + WdlTerminalIdentifier.TERMINAL_RBRACE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + WdlTerminalIdentifier.TERMINAL_CALL, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_META, + })); + map.put(122, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(123, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_RBRACE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_META, + })); + map.put(124, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_INPUT, + WdlTerminalIdentifier.TERMINAL_RBRACE, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_CALL, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_META, + })); + map.put(125, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(126, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_CALL, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(127, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + WdlTerminalIdentifier.TERMINAL_IMPORT, + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(128, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + map.put(129, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_RBRACE, + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_META, + })); + map.put(130, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_FQN, + WdlTerminalIdentifier.TERMINAL_RBRACE, + })); + nonterminal_follow = Collections.unmodifiableMap(map); + } + static { + Map> map = new HashMap>(); + map.put(0, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IMPORT, + })); + map.put(1, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IMPORT, + })); + map.put(2, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(3, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(4, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(5, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(6, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(7, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(8, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + WdlTerminalIdentifier.TERMINAL_IMPORT, + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(9, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(10, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + })); + map.put(11, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_AS, + })); + map.put(12, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(13, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IMPORT, + })); + map.put(14, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_AS, + })); + map.put(15, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + })); + map.put(16, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + })); + map.put(17, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(18, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(19, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_META, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + })); + map.put(20, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + WdlTerminalIdentifier.TERMINAL_RUNTIME, + WdlTerminalIdentifier.TERMINAL_META, + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + })); + map.put(21, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(22, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(23, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TASK, + })); + map.put(24, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + })); + map.put(25, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + })); + map.put(26, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RUNTIME, + })); + map.put(27, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + })); + map.put(28, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_META, + })); + map.put(29, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_PARAM_START, + WdlTerminalIdentifier.TERMINAL_CMD_PART, + })); + map.put(30, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_PARAM_START, + WdlTerminalIdentifier.TERMINAL_CMD_PART, + })); + map.put(31, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(32, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(33, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + })); + map.put(34, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_PART, + })); + map.put(35, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_PARAM_START, + })); + map.put(36, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_ATTR_HINT, + })); + map.put(37, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_ATTR_HINT, + })); + map.put(38, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(39, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(40, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_PARAM_START, + })); + map.put(41, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CMD_ATTR_HINT, + })); + map.put(42, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + })); + map.put(43, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + })); + map.put(44, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(45, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(46, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + })); + map.put(47, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + })); + map.put(48, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_RUNTIME, + })); + map.put(49, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + })); + map.put(50, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_META, + })); + map.put(51, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(52, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(53, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(54, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(55, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_LBRACE, + })); + map.put(56, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(57, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_QMARK, + WdlTerminalIdentifier.TERMINAL_PLUS, + })); + map.put(58, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(59, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_EQUAL, + })); + map.put(60, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(61, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + })); + map.put(62, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_EQUAL, + })); + map.put(63, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_QMARK, + })); + map.put(64, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_PLUS, + })); + map.put(65, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(66, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_CALL, + })); + map.put(67, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + WdlTerminalIdentifier.TERMINAL_WHILE, + WdlTerminalIdentifier.TERMINAL_SCATTER, + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + WdlTerminalIdentifier.TERMINAL_IF, + WdlTerminalIdentifier.TERMINAL_CALL, + })); + map.put(68, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(69, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(70, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + })); + map.put(71, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CALL, + })); + map.put(72, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + })); + map.put(73, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_WHILE, + })); + map.put(74, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IF, + })); + map.put(75, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_SCATTER, + })); + map.put(76, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + })); + map.put(77, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_AS, + })); + map.put(78, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(79, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_LBRACE, + })); + map.put(80, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(81, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_CALL, + })); + map.put(82, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_INPUT, + })); + map.put(83, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_INPUT, + })); + map.put(84, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(85, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(86, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_LBRACE, + })); + map.put(87, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(88, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_COMMA, + })); + map.put(89, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(90, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(91, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_INPUT, + })); + map.put(92, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(93, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_AS, + })); + map.put(94, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_FQN, + })); + map.put(95, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_FQN, + })); + map.put(96, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(97, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(98, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OUTPUT, + })); + map.put(99, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_DOT, + })); + map.put(100, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(101, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_FQN, + })); + map.put(102, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_DOT, + })); + map.put(103, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_WHILE, + })); + map.put(104, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IF, + })); + map.put(105, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_SCATTER, + })); + map.put(106, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(107, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + WdlTerminalIdentifier.TERMINAL_TYPE_E, + })); + map.put(108, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_COMMA, + })); + map.put(109, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(110, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(111, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + })); + map.put(112, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_TYPE, + })); + map.put(113, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(114, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(115, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(116, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(117, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(118, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(119, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(120, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(121, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(122, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(123, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(124, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(125, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(126, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_NOT, + })); + map.put(127, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_PLUS, + })); + map.put(128, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_DASH, + })); + map.put(129, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(130, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_COMMA, + })); + map.put(131, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(132, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(133, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(134, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(135, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(136, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(137, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_COMMA, + })); + map.put(138, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(139, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(140, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_OBJECT, + })); + map.put(141, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_LSQUARE, + })); + map.put(142, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_E, + WdlTerminalIdentifier.TERMINAL_LPAREN, + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + WdlTerminalIdentifier.TERMINAL_INTEGER, + WdlTerminalIdentifier.TERMINAL_NOT, + WdlTerminalIdentifier.TERMINAL_OBJECT, + WdlTerminalIdentifier.TERMINAL_LBRACE, + WdlTerminalIdentifier.TERMINAL_STRING, + WdlTerminalIdentifier.TERMINAL_DASH, + WdlTerminalIdentifier.TERMINAL_PLUS, + WdlTerminalIdentifier.TERMINAL_LSQUARE, + WdlTerminalIdentifier.TERMINAL_FLOAT, + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(143, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_COMMA, + })); + map.put(144, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(145, Arrays.asList(new TerminalIdentifier[] { + })); + map.put(146, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_LBRACE, + })); + map.put(147, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_LPAREN, + })); + map.put(148, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_STRING, + })); + map.put(149, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + })); + map.put(150, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + })); + map.put(151, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_INTEGER, + })); + map.put(152, Arrays.asList(new TerminalIdentifier[] { + WdlTerminalIdentifier.TERMINAL_FLOAT, + })); + rule_first = Collections.unmodifiableMap(map); + } + static { + Map> map = new HashMap>(); + map.put(56, new ArrayList()); + map.put(57, new ArrayList()); + map.put(58, new ArrayList()); + map.put(59, new ArrayList()); + map.put(60, new ArrayList()); + map.put(61, new ArrayList()); + map.put(62, new ArrayList()); + map.put(63, new ArrayList()); + map.put(64, new ArrayList()); + map.put(65, new ArrayList()); + map.put(66, new ArrayList()); + map.put(67, new ArrayList()); + map.put(68, new ArrayList()); + map.put(69, new ArrayList()); + map.put(70, new ArrayList()); + map.put(71, new ArrayList()); + map.put(72, new ArrayList()); + map.put(73, new ArrayList()); + map.put(74, new ArrayList()); + map.put(75, new ArrayList()); + map.put(76, new ArrayList()); + map.put(77, new ArrayList()); + map.put(78, new ArrayList()); + map.put(79, new ArrayList()); + map.put(80, new ArrayList()); + map.put(81, new ArrayList()); + map.put(82, new ArrayList()); + map.put(83, new ArrayList()); + map.put(84, new ArrayList()); + map.put(85, new ArrayList()); + map.put(86, new ArrayList()); + map.put(87, new ArrayList()); + map.put(88, new ArrayList()); + map.put(89, new ArrayList()); + map.put(90, new ArrayList()); + map.put(91, new ArrayList()); + map.put(92, new ArrayList()); + map.put(93, new ArrayList()); + map.put(94, new ArrayList()); + map.put(95, new ArrayList()); + map.put(96, new ArrayList()); + map.put(97, new ArrayList()); + map.put(98, new ArrayList()); + map.put(99, new ArrayList()); + map.put(100, new ArrayList()); + map.put(101, new ArrayList()); + map.put(102, new ArrayList()); + map.put(103, new ArrayList()); + map.put(104, new ArrayList()); + map.put(105, new ArrayList()); + map.put(106, new ArrayList()); + map.put(107, new ArrayList()); + map.put(108, new ArrayList()); + map.put(109, new ArrayList()); + map.put(110, new ArrayList()); + map.put(111, new ArrayList()); + map.put(112, new ArrayList()); + map.put(113, new ArrayList()); + map.put(114, new ArrayList()); + map.put(115, new ArrayList()); + map.put(116, new ArrayList()); + map.put(117, new ArrayList()); + map.put(118, new ArrayList()); + map.put(119, new ArrayList()); + map.put(120, new ArrayList()); + map.put(121, new ArrayList()); + map.put(122, new ArrayList()); + map.put(123, new ArrayList()); + map.put(124, new ArrayList()); + map.put(125, new ArrayList()); + map.put(126, new ArrayList()); + map.put(127, new ArrayList()); + map.put(128, new ArrayList()); + map.put(129, new ArrayList()); + map.put(130, new ArrayList()); + map.get(61).add("$_gen0 = $import $_gen1"); + map.get(87).add("$_gen1 = $import $_gen1"); + map.get(87).add("$_gen1 = :_empty"); + map.get(61).add("$_gen0 = :_empty"); + map.get(111).add("$_gen2 = $workflow_or_task $_gen3"); + map.get(90).add("$_gen3 = $workflow_or_task $_gen3"); + map.get(90).add("$_gen3 = :_empty"); + map.get(111).add("$_gen2 = :_empty"); + map.get(120).add("$document = $_gen0 $_gen2 -> Document( imports=$0, definitions=$1 )"); + map.get(69).add("$workflow_or_task = $workflow"); + map.get(69).add("$workflow_or_task = $task"); + map.get(77).add("$_gen4 = $import_namespace"); + map.get(77).add("$_gen4 = :_empty"); + map.get(86).add("$import = :import :string $_gen4 -> Import( uri=$1, namespace=$2 )"); + map.get(127).add("$import_namespace = :as :identifier -> $1"); + map.get(114).add("$_gen5 = $declaration $_gen6"); + map.get(112).add("$_gen6 = $declaration $_gen6"); + map.get(112).add("$_gen6 = :_empty"); + map.get(114).add("$_gen5 = :_empty"); + map.get(103).add("$_gen7 = $sections $_gen8"); + map.get(122).add("$_gen8 = $sections $_gen8"); + map.get(122).add("$_gen8 = :_empty"); + map.get(103).add("$_gen7 = :_empty"); + map.get(88).add("$task = :task :identifier :lbrace $_gen5 $_gen7 :rbrace -> Task( name=$1, declarations=$3, sections=$4 )"); + map.get(118).add("$sections = $command"); + map.get(118).add("$sections = $outputs"); + map.get(118).add("$sections = $runtime"); + map.get(118).add("$sections = $parameter_meta"); + map.get(118).add("$sections = $meta"); + map.get(76).add("$_gen9 = $command_part $_gen10"); + map.get(113).add("$_gen10 = $command_part $_gen10"); + map.get(113).add("$_gen10 = :_empty"); + map.get(76).add("$_gen9 = :_empty"); + map.get(129).add("$command = :raw_command :raw_cmd_start $_gen9 :raw_cmd_end -> RawCommand( parts=$2 )"); + map.get(85).add("$command_part = :cmd_part"); + map.get(85).add("$command_part = $cmd_param"); + map.get(64).add("$_gen11 = $cmd_param_kv $_gen12"); + map.get(91).add("$_gen12 = $cmd_param_kv $_gen12"); + map.get(91).add("$_gen12 = :_empty"); + map.get(64).add("$_gen11 = :_empty"); + map.get(105).add("$cmd_param = :cmd_param_start $_gen11 $e :cmd_param_end -> CommandParameter( attributes=$1, expr=$2 )"); + map.get(57).add("$cmd_param_kv = :cmd_attr_hint :identifier :equal $e -> CommandParameterAttr( key=$1, value=$3 )"); + map.get(70).add("$_gen13 = $output_kv $_gen14"); + map.get(92).add("$_gen14 = $output_kv $_gen14"); + map.get(92).add("$_gen14 = :_empty"); + map.get(70).add("$_gen13 = :_empty"); + map.get(123).add("$outputs = :output :lbrace $_gen13 :rbrace -> Outputs( attributes=$2 )"); + map.get(94).add("$output_kv = $type_e :identifier :equal $e -> Output( type=$0, var=$1, expression=$3 )"); + map.get(117).add("$runtime = :runtime $map -> Runtime( map=$1 )"); + map.get(75).add("$parameter_meta = :parameter_meta $map -> ParameterMeta( map=$1 )"); + map.get(100).add("$meta = :meta $map -> Meta( map=$1 )"); + map.get(63).add("$_gen15 = $kv $_gen16"); + map.get(82).add("$_gen16 = $kv $_gen16"); + map.get(82).add("$_gen16 = :_empty"); + map.get(63).add("$_gen15 = :_empty"); + map.get(104).add("$map = :lbrace $_gen15 :rbrace -> $1"); + map.get(65).add("$kv = :identifier :colon $e -> RuntimeAttribute( key=$0, value=$2 )"); + map.get(66).add("$_gen17 = $postfix_quantifier"); + map.get(66).add("$_gen17 = :_empty"); + map.get(81).add("$_gen18 = $setter"); + map.get(81).add("$_gen18 = :_empty"); + map.get(121).add("$declaration = $type_e $_gen17 :identifier $_gen18 -> Declaration( type=$0, postfix=$1, name=$2, expression=$3 )"); + map.get(124).add("$setter = :equal $e -> $1"); + map.get(108).add("$postfix_quantifier = :qmark"); + map.get(108).add("$postfix_quantifier = :plus"); + map.get(101).add("$map_kv = $e :colon $e -> MapLiteralKv( key=$0, value=$2 )"); + map.get(125).add("$_gen19 = $wf_body_element $_gen20"); + map.get(72).add("$_gen20 = $wf_body_element $_gen20"); + map.get(72).add("$_gen20 = :_empty"); + map.get(125).add("$_gen19 = :_empty"); + map.get(56).add("$workflow = :workflow :identifier :lbrace $_gen19 :rbrace -> Workflow( name=$1, body=$3 )"); + map.get(115).add("$wf_body_element = $call"); + map.get(115).add("$wf_body_element = $declaration"); + map.get(115).add("$wf_body_element = $while_loop"); + map.get(115).add("$wf_body_element = $if_stmt"); + map.get(115).add("$wf_body_element = $scatter"); + map.get(115).add("$wf_body_element = $wf_outputs"); + map.get(68).add("$_gen21 = $alias"); + map.get(68).add("$_gen21 = :_empty"); + map.get(106).add("$_gen22 = $call_body"); + map.get(106).add("$_gen22 = :_empty"); + map.get(62).add("$call = :call :fqn $_gen21 $_gen22 -> Call( task=$1, alias=$2, body=$3 )"); + map.get(95).add("$_gen23 = $call_input $_gen24"); + map.get(119).add("$_gen24 = $call_input $_gen24"); + map.get(119).add("$_gen24 = :_empty"); + map.get(95).add("$_gen23 = :_empty"); + map.get(73).add("$call_body = :lbrace $_gen5 $_gen23 :rbrace -> CallBody( declarations=$1, io=$2 )"); + map.get(89).add("$_gen25 = $mapping $_gen26"); + map.get(71).add("$_gen26 = :comma $mapping $_gen26"); + map.get(71).add("$_gen26 = :_empty"); + map.get(89).add("$_gen25 = :_empty"); + map.get(96).add("$call_input = :input :colon $_gen25 -> Inputs( map=$2 )"); + map.get(93).add("$mapping = :identifier :equal $e -> IOMapping( key=$0, value=$2 )"); + map.get(102).add("$alias = :as :identifier -> $1"); + map.get(84).add("$_gen27 = $wf_output $_gen28"); + map.get(60).add("$_gen28 = $wf_output $_gen28"); + map.get(60).add("$_gen28 = :_empty"); + map.get(84).add("$_gen27 = :_empty"); + map.get(126).add("$wf_outputs = :output :lbrace $_gen27 :rbrace -> WorkflowOutputs( outputs=$2 )"); + map.get(130).add("$_gen29 = $wf_output_wildcard"); + map.get(130).add("$_gen29 = :_empty"); + map.get(67).add("$wf_output = :fqn $_gen29 -> WorkflowOutput( fqn=$0, wildcard=$1 )"); + map.get(99).add("$wf_output_wildcard = :dot :asterisk -> $1"); + map.get(116).add("$while_loop = :while :lparen $e :rparen :lbrace $_gen19 :rbrace -> WhileLoop( expression=$2, body=$5 )"); + map.get(107).add("$if_stmt = :if :lparen $e :rparen :lbrace $_gen19 :rbrace -> If( expression=$2, body=$5 )"); + map.get(79).add("$scatter = :scatter :lparen :identifier :in $e :rparen :lbrace $_gen19 :rbrace -> Scatter( item=$2, collection=$4, body=$7 )"); + map.get(78).add("$object_kv = :identifier :colon $e -> ObjectKV( key=$0, value=$2 )"); + map.get(80).add("$_gen30 = $type_e $_gen31"); + map.get(74).add("$_gen31 = :comma $type_e $_gen31"); + map.get(74).add("$_gen31 = :_empty"); + map.get(80).add("$_gen30 = :_empty"); + map.get(58).add("$type_e = :type <=> :lsquare $_gen30 :rsquare -> Type( name=$0, subtype=$2 )"); + map.get(58).add("$type_e = :type"); + map.get(83).add("$e = $e :double_pipe $e -> LogicalOr( lhs=$0, rhs=$2 )"); + map.get(83).add("$e = $e :double_ampersand $e -> LogicalAnd( lhs=$0, rhs=$2 )"); + map.get(83).add("$e = $e :double_equal $e -> Equals( lhs=$0, rhs=$2 )"); + map.get(83).add("$e = $e :not_equal $e -> NotEquals( lhs=$0, rhs=$2 )"); + map.get(83).add("$e = $e :lt $e -> LessThan( lhs=$0, rhs=$2 )"); + map.get(83).add("$e = $e :lteq $e -> LessThanOrEqual( lhs=$0, rhs=$2 )"); + map.get(83).add("$e = $e :gt $e -> GreaterThan( lhs=$0, rhs=$2 )"); + map.get(83).add("$e = $e :gteq $e -> GreaterThanOrEqual( lhs=$0, rhs=$2 )"); + map.get(83).add("$e = $e :plus $e -> Add( lhs=$0, rhs=$2 )"); + map.get(83).add("$e = $e :dash $e -> Subtract( lhs=$0, rhs=$2 )"); + map.get(83).add("$e = $e :asterisk $e -> Multiply( lhs=$0, rhs=$2 )"); + map.get(83).add("$e = $e :slash $e -> Divide( lhs=$0, rhs=$2 )"); + map.get(83).add("$e = $e :percent $e -> Remainder( lhs=$0, rhs=$2 )"); + map.get(83).add("$e = :not $e -> LogicalNot( expression=$1 )"); + map.get(83).add("$e = :plus $e -> UnaryPlus( expression=$1 )"); + map.get(83).add("$e = :dash $e -> UnaryNegation( expression=$1 )"); + map.get(97).add("$_gen32 = $e $_gen33"); + map.get(109).add("$_gen33 = :comma $e $_gen33"); + map.get(109).add("$_gen33 = :_empty"); + map.get(97).add("$_gen32 = :_empty"); + map.get(83).add("$e = :identifier <=> :lparen $_gen32 :rparen -> FunctionCall( name=$0, params=$2 )"); + map.get(83).add("$e = :identifier <=> :lsquare $e :rsquare -> ArrayOrMapLookup( lhs=$0, rhs=$2 )"); + map.get(83).add("$e = :identifier <=> :dot :identifier -> MemberAccess( lhs=$0, rhs=$2 )"); + map.get(98).add("$_gen34 = $object_kv $_gen35"); + map.get(128).add("$_gen35 = :comma $object_kv $_gen35"); + map.get(128).add("$_gen35 = :_empty"); + map.get(98).add("$_gen34 = :_empty"); + map.get(83).add("$e = :object :lbrace $_gen34 :rbrace -> ObjectLiteral( map=$2 )"); + map.get(83).add("$e = :lsquare $_gen32 :rsquare -> ArrayLiteral( values=$1 )"); + map.get(110).add("$_gen36 = $map_kv $_gen37"); + map.get(59).add("$_gen37 = :comma $map_kv $_gen37"); + map.get(59).add("$_gen37 = :_empty"); + map.get(110).add("$_gen36 = :_empty"); + map.get(83).add("$e = :lbrace $_gen36 :rbrace -> MapLiteral( map=$1 )"); + map.get(83).add("$e = :lparen $e :rparen -> $1"); + map.get(83).add("$e = :string"); + map.get(83).add("$e = :identifier"); + map.get(83).add("$e = :boolean"); + map.get(83).add("$e = :integer"); + map.get(83).add("$e = :float"); + nonterminal_rules = Collections.unmodifiableMap(map); + } + static { + Map map = new HashMap(); + map.put(new Integer(0), "$_gen0 = $import $_gen1"); + map.put(new Integer(1), "$_gen1 = $import $_gen1"); + map.put(new Integer(2), "$_gen1 = :_empty"); + map.put(new Integer(3), "$_gen0 = :_empty"); + map.put(new Integer(4), "$_gen2 = $workflow_or_task $_gen3"); + map.put(new Integer(5), "$_gen3 = $workflow_or_task $_gen3"); + map.put(new Integer(6), "$_gen3 = :_empty"); + map.put(new Integer(7), "$_gen2 = :_empty"); + map.put(new Integer(8), "$document = $_gen0 $_gen2 -> Document( imports=$0, definitions=$1 )"); + map.put(new Integer(9), "$workflow_or_task = $workflow"); + map.put(new Integer(10), "$workflow_or_task = $task"); + map.put(new Integer(11), "$_gen4 = $import_namespace"); + map.put(new Integer(12), "$_gen4 = :_empty"); + map.put(new Integer(13), "$import = :import :string $_gen4 -> Import( uri=$1, namespace=$2 )"); + map.put(new Integer(14), "$import_namespace = :as :identifier -> $1"); + map.put(new Integer(15), "$_gen5 = $declaration $_gen6"); + map.put(new Integer(16), "$_gen6 = $declaration $_gen6"); + map.put(new Integer(17), "$_gen6 = :_empty"); + map.put(new Integer(18), "$_gen5 = :_empty"); + map.put(new Integer(19), "$_gen7 = $sections $_gen8"); + map.put(new Integer(20), "$_gen8 = $sections $_gen8"); + map.put(new Integer(21), "$_gen8 = :_empty"); + map.put(new Integer(22), "$_gen7 = :_empty"); + map.put(new Integer(23), "$task = :task :identifier :lbrace $_gen5 $_gen7 :rbrace -> Task( name=$1, declarations=$3, sections=$4 )"); + map.put(new Integer(24), "$sections = $command"); + map.put(new Integer(25), "$sections = $outputs"); + map.put(new Integer(26), "$sections = $runtime"); + map.put(new Integer(27), "$sections = $parameter_meta"); + map.put(new Integer(28), "$sections = $meta"); + map.put(new Integer(29), "$_gen9 = $command_part $_gen10"); + map.put(new Integer(30), "$_gen10 = $command_part $_gen10"); + map.put(new Integer(31), "$_gen10 = :_empty"); + map.put(new Integer(32), "$_gen9 = :_empty"); + map.put(new Integer(33), "$command = :raw_command :raw_cmd_start $_gen9 :raw_cmd_end -> RawCommand( parts=$2 )"); + map.put(new Integer(34), "$command_part = :cmd_part"); + map.put(new Integer(35), "$command_part = $cmd_param"); + map.put(new Integer(36), "$_gen11 = $cmd_param_kv $_gen12"); + map.put(new Integer(37), "$_gen12 = $cmd_param_kv $_gen12"); + map.put(new Integer(38), "$_gen12 = :_empty"); + map.put(new Integer(39), "$_gen11 = :_empty"); + map.put(new Integer(40), "$cmd_param = :cmd_param_start $_gen11 $e :cmd_param_end -> CommandParameter( attributes=$1, expr=$2 )"); + map.put(new Integer(41), "$cmd_param_kv = :cmd_attr_hint :identifier :equal $e -> CommandParameterAttr( key=$1, value=$3 )"); + map.put(new Integer(42), "$_gen13 = $output_kv $_gen14"); + map.put(new Integer(43), "$_gen14 = $output_kv $_gen14"); + map.put(new Integer(44), "$_gen14 = :_empty"); + map.put(new Integer(45), "$_gen13 = :_empty"); + map.put(new Integer(46), "$outputs = :output :lbrace $_gen13 :rbrace -> Outputs( attributes=$2 )"); + map.put(new Integer(47), "$output_kv = $type_e :identifier :equal $e -> Output( type=$0, var=$1, expression=$3 )"); + map.put(new Integer(48), "$runtime = :runtime $map -> Runtime( map=$1 )"); + map.put(new Integer(49), "$parameter_meta = :parameter_meta $map -> ParameterMeta( map=$1 )"); + map.put(new Integer(50), "$meta = :meta $map -> Meta( map=$1 )"); + map.put(new Integer(51), "$_gen15 = $kv $_gen16"); + map.put(new Integer(52), "$_gen16 = $kv $_gen16"); + map.put(new Integer(53), "$_gen16 = :_empty"); + map.put(new Integer(54), "$_gen15 = :_empty"); + map.put(new Integer(55), "$map = :lbrace $_gen15 :rbrace -> $1"); + map.put(new Integer(56), "$kv = :identifier :colon $e -> RuntimeAttribute( key=$0, value=$2 )"); + map.put(new Integer(57), "$_gen17 = $postfix_quantifier"); + map.put(new Integer(58), "$_gen17 = :_empty"); + map.put(new Integer(59), "$_gen18 = $setter"); + map.put(new Integer(60), "$_gen18 = :_empty"); + map.put(new Integer(61), "$declaration = $type_e $_gen17 :identifier $_gen18 -> Declaration( type=$0, postfix=$1, name=$2, expression=$3 )"); + map.put(new Integer(62), "$setter = :equal $e -> $1"); + map.put(new Integer(63), "$postfix_quantifier = :qmark"); + map.put(new Integer(64), "$postfix_quantifier = :plus"); + map.put(new Integer(65), "$map_kv = $e :colon $e -> MapLiteralKv( key=$0, value=$2 )"); + map.put(new Integer(66), "$_gen19 = $wf_body_element $_gen20"); + map.put(new Integer(67), "$_gen20 = $wf_body_element $_gen20"); + map.put(new Integer(68), "$_gen20 = :_empty"); + map.put(new Integer(69), "$_gen19 = :_empty"); + map.put(new Integer(70), "$workflow = :workflow :identifier :lbrace $_gen19 :rbrace -> Workflow( name=$1, body=$3 )"); + map.put(new Integer(71), "$wf_body_element = $call"); + map.put(new Integer(72), "$wf_body_element = $declaration"); + map.put(new Integer(73), "$wf_body_element = $while_loop"); + map.put(new Integer(74), "$wf_body_element = $if_stmt"); + map.put(new Integer(75), "$wf_body_element = $scatter"); + map.put(new Integer(76), "$wf_body_element = $wf_outputs"); + map.put(new Integer(77), "$_gen21 = $alias"); + map.put(new Integer(78), "$_gen21 = :_empty"); + map.put(new Integer(79), "$_gen22 = $call_body"); + map.put(new Integer(80), "$_gen22 = :_empty"); + map.put(new Integer(81), "$call = :call :fqn $_gen21 $_gen22 -> Call( task=$1, alias=$2, body=$3 )"); + map.put(new Integer(82), "$_gen23 = $call_input $_gen24"); + map.put(new Integer(83), "$_gen24 = $call_input $_gen24"); + map.put(new Integer(84), "$_gen24 = :_empty"); + map.put(new Integer(85), "$_gen23 = :_empty"); + map.put(new Integer(86), "$call_body = :lbrace $_gen5 $_gen23 :rbrace -> CallBody( declarations=$1, io=$2 )"); + map.put(new Integer(87), "$_gen25 = $mapping $_gen26"); + map.put(new Integer(88), "$_gen26 = :comma $mapping $_gen26"); + map.put(new Integer(89), "$_gen26 = :_empty"); + map.put(new Integer(90), "$_gen25 = :_empty"); + map.put(new Integer(91), "$call_input = :input :colon $_gen25 -> Inputs( map=$2 )"); + map.put(new Integer(92), "$mapping = :identifier :equal $e -> IOMapping( key=$0, value=$2 )"); + map.put(new Integer(93), "$alias = :as :identifier -> $1"); + map.put(new Integer(94), "$_gen27 = $wf_output $_gen28"); + map.put(new Integer(95), "$_gen28 = $wf_output $_gen28"); + map.put(new Integer(96), "$_gen28 = :_empty"); + map.put(new Integer(97), "$_gen27 = :_empty"); + map.put(new Integer(98), "$wf_outputs = :output :lbrace $_gen27 :rbrace -> WorkflowOutputs( outputs=$2 )"); + map.put(new Integer(99), "$_gen29 = $wf_output_wildcard"); + map.put(new Integer(100), "$_gen29 = :_empty"); + map.put(new Integer(101), "$wf_output = :fqn $_gen29 -> WorkflowOutput( fqn=$0, wildcard=$1 )"); + map.put(new Integer(102), "$wf_output_wildcard = :dot :asterisk -> $1"); + map.put(new Integer(103), "$while_loop = :while :lparen $e :rparen :lbrace $_gen19 :rbrace -> WhileLoop( expression=$2, body=$5 )"); + map.put(new Integer(104), "$if_stmt = :if :lparen $e :rparen :lbrace $_gen19 :rbrace -> If( expression=$2, body=$5 )"); + map.put(new Integer(105), "$scatter = :scatter :lparen :identifier :in $e :rparen :lbrace $_gen19 :rbrace -> Scatter( item=$2, collection=$4, body=$7 )"); + map.put(new Integer(106), "$object_kv = :identifier :colon $e -> ObjectKV( key=$0, value=$2 )"); + map.put(new Integer(107), "$_gen30 = $type_e $_gen31"); + map.put(new Integer(108), "$_gen31 = :comma $type_e $_gen31"); + map.put(new Integer(109), "$_gen31 = :_empty"); + map.put(new Integer(110), "$_gen30 = :_empty"); + map.put(new Integer(111), "$type_e = :type <=> :lsquare $_gen30 :rsquare -> Type( name=$0, subtype=$2 )"); + map.put(new Integer(112), "$type_e = :type"); + map.put(new Integer(113), "$e = $e :double_pipe $e -> LogicalOr( lhs=$0, rhs=$2 )"); + map.put(new Integer(114), "$e = $e :double_ampersand $e -> LogicalAnd( lhs=$0, rhs=$2 )"); + map.put(new Integer(115), "$e = $e :double_equal $e -> Equals( lhs=$0, rhs=$2 )"); + map.put(new Integer(116), "$e = $e :not_equal $e -> NotEquals( lhs=$0, rhs=$2 )"); + map.put(new Integer(117), "$e = $e :lt $e -> LessThan( lhs=$0, rhs=$2 )"); + map.put(new Integer(118), "$e = $e :lteq $e -> LessThanOrEqual( lhs=$0, rhs=$2 )"); + map.put(new Integer(119), "$e = $e :gt $e -> GreaterThan( lhs=$0, rhs=$2 )"); + map.put(new Integer(120), "$e = $e :gteq $e -> GreaterThanOrEqual( lhs=$0, rhs=$2 )"); + map.put(new Integer(121), "$e = $e :plus $e -> Add( lhs=$0, rhs=$2 )"); + map.put(new Integer(122), "$e = $e :dash $e -> Subtract( lhs=$0, rhs=$2 )"); + map.put(new Integer(123), "$e = $e :asterisk $e -> Multiply( lhs=$0, rhs=$2 )"); + map.put(new Integer(124), "$e = $e :slash $e -> Divide( lhs=$0, rhs=$2 )"); + map.put(new Integer(125), "$e = $e :percent $e -> Remainder( lhs=$0, rhs=$2 )"); + map.put(new Integer(126), "$e = :not $e -> LogicalNot( expression=$1 )"); + map.put(new Integer(127), "$e = :plus $e -> UnaryPlus( expression=$1 )"); + map.put(new Integer(128), "$e = :dash $e -> UnaryNegation( expression=$1 )"); + map.put(new Integer(129), "$_gen32 = $e $_gen33"); + map.put(new Integer(130), "$_gen33 = :comma $e $_gen33"); + map.put(new Integer(131), "$_gen33 = :_empty"); + map.put(new Integer(132), "$_gen32 = :_empty"); + map.put(new Integer(133), "$e = :identifier <=> :lparen $_gen32 :rparen -> FunctionCall( name=$0, params=$2 )"); + map.put(new Integer(134), "$e = :identifier <=> :lsquare $e :rsquare -> ArrayOrMapLookup( lhs=$0, rhs=$2 )"); + map.put(new Integer(135), "$e = :identifier <=> :dot :identifier -> MemberAccess( lhs=$0, rhs=$2 )"); + map.put(new Integer(136), "$_gen34 = $object_kv $_gen35"); + map.put(new Integer(137), "$_gen35 = :comma $object_kv $_gen35"); + map.put(new Integer(138), "$_gen35 = :_empty"); + map.put(new Integer(139), "$_gen34 = :_empty"); + map.put(new Integer(140), "$e = :object :lbrace $_gen34 :rbrace -> ObjectLiteral( map=$2 )"); + map.put(new Integer(141), "$e = :lsquare $_gen32 :rsquare -> ArrayLiteral( values=$1 )"); + map.put(new Integer(142), "$_gen36 = $map_kv $_gen37"); + map.put(new Integer(143), "$_gen37 = :comma $map_kv $_gen37"); + map.put(new Integer(144), "$_gen37 = :_empty"); + map.put(new Integer(145), "$_gen36 = :_empty"); + map.put(new Integer(146), "$e = :lbrace $_gen36 :rbrace -> MapLiteral( map=$1 )"); + map.put(new Integer(147), "$e = :lparen $e :rparen -> $1"); + map.put(new Integer(148), "$e = :string"); + map.put(new Integer(149), "$e = :identifier"); + map.put(new Integer(150), "$e = :boolean"); + map.put(new Integer(151), "$e = :integer"); + map.put(new Integer(152), "$e = :float"); + rules = Collections.unmodifiableMap(map); + } + public static boolean is_terminal(int id) { + return 0 <= id && id <= 55; + } + public ParseTree parse(TokenStream tokens) throws SyntaxError { + return parse(tokens, new DefaultSyntaxErrorFormatter()); + } + public ParseTree parse(List tokens) throws SyntaxError { + return parse(new TokenStream(tokens)); + } + public ParseTree parse(TokenStream tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(tokens, error_formatter); + ParseTree tree = parse_document(ctx); + if (ctx.tokens.current() != null) { + StackTraceElement[] stack = Thread.currentThread().getStackTrace(); + throw new SyntaxError(ctx.error_formatter.excessTokens(stack[1].getMethodName(), ctx.tokens.current())); + } + return tree; + } + public ParseTree parse(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + return parse(new TokenStream(tokens), error_formatter); + } + private static Terminal expect(ParserContext ctx, TerminalIdentifier expecting) throws SyntaxError { + Terminal current = ctx.tokens.current(); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.noMoreTokens(ctx.nonterminal, expecting, ctx.tokens.last())); + } + if (current.getId() != expecting.id()) { + ArrayList expectedList = new ArrayList(); + expectedList.add(expecting); + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol(ctx.nonterminal, current, expectedList, ctx.rule)); + } + Terminal next = ctx.tokens.advance(); + if ( next != null && !is_terminal(next.getId()) ) { + throw new SyntaxError(ctx.error_formatter.invalidTerminal(ctx.nonterminal, next)); + } + return current; + } + private static Map infix_binding_power_e; + private static Map prefix_binding_power_e; + static { + Map map = new HashMap(); + map.put(42, 2000); /* $e = $e :double_pipe $e -> LogicalOr( lhs=$0, rhs=$2 ) */ + map.put(18, 3000); /* $e = $e :double_ampersand $e -> LogicalAnd( lhs=$0, rhs=$2 ) */ + map.put(22, 4000); /* $e = $e :double_equal $e -> Equals( lhs=$0, rhs=$2 ) */ + map.put(44, 4000); /* $e = $e :not_equal $e -> NotEquals( lhs=$0, rhs=$2 ) */ + map.put(35, 5000); /* $e = $e :lt $e -> LessThan( lhs=$0, rhs=$2 ) */ + map.put(28, 5000); /* $e = $e :lteq $e -> LessThanOrEqual( lhs=$0, rhs=$2 ) */ + map.put(14, 5000); /* $e = $e :gt $e -> GreaterThan( lhs=$0, rhs=$2 ) */ + map.put(33, 5000); /* $e = $e :gteq $e -> GreaterThanOrEqual( lhs=$0, rhs=$2 ) */ + map.put(34, 6000); /* $e = $e :plus $e -> Add( lhs=$0, rhs=$2 ) */ + map.put(37, 6000); /* $e = $e :dash $e -> Subtract( lhs=$0, rhs=$2 ) */ + map.put(6, 7000); /* $e = $e :asterisk $e -> Multiply( lhs=$0, rhs=$2 ) */ + map.put(12, 7000); /* $e = $e :slash $e -> Divide( lhs=$0, rhs=$2 ) */ + map.put(13, 7000); /* $e = $e :percent $e -> Remainder( lhs=$0, rhs=$2 ) */ + map.put(3, 9000); /* $e = :identifier <=> :lparen list(nt=$e, sep=:comma, min=0, sep_terminates=False) :rparen -> FunctionCall( name=$0, params=$2 ) */ + map.put(52, 10000); /* $e = :identifier <=> :lsquare $e :rsquare -> ArrayOrMapLookup( lhs=$0, rhs=$2 ) */ + map.put(41, 11000); /* $e = :identifier <=> :dot :identifier -> MemberAccess( lhs=$0, rhs=$2 ) */ + infix_binding_power_e = Collections.unmodifiableMap(map); + } + static { + Map map = new HashMap(); + map.put(47, 8000); /* $e = :not $e -> LogicalNot( expression=$1 ) */ + map.put(34, 8000); /* $e = :plus $e -> UnaryPlus( expression=$1 ) */ + map.put(37, 8000); /* $e = :dash $e -> UnaryNegation( expression=$1 ) */ + prefix_binding_power_e = Collections.unmodifiableMap(map); + } + static int get_infix_binding_power_e(int terminal_id) { + if (infix_binding_power_e.containsKey(terminal_id)) { + return infix_binding_power_e.get(terminal_id); + } + return 0; + } + static int get_prefix_binding_power_e(int terminal_id) { + if (prefix_binding_power_e.containsKey(terminal_id)) { + return prefix_binding_power_e.get(terminal_id); + } + return 0; + } + public ParseTree parse_e(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_e_internal(ctx, 0); + } + public static ParseTree parse_e(ParserContext ctx) throws SyntaxError { + return parse_e_internal(ctx, 0); + } + public static ParseTree parse_e_internal(ParserContext ctx, int rbp) throws SyntaxError { + ParseTree left = nud_e(ctx); + if ( left instanceof ParseTree ) { + left.setExpr(true); + left.setNud(true); + } + while (ctx.tokens.current() != null && rbp < get_infix_binding_power_e(ctx.tokens.current().getId())) { + left = led_e(left, ctx); + } + if (left != null) { + left.setExpr(true); + } + return left; + } + private static ParseTree nud_e(ParserContext ctx) throws SyntaxError { + ParseTree tree = new ParseTree( new NonTerminal(83, "e") ); + Terminal current = ctx.tokens.current(); + ctx.nonterminal = "e"; + if (current == null) { + return tree; + } + else if (rule_first.get(126).contains(terminal_map.get(current.getId()))) { + /* (126) $e = :not $e -> LogicalNot( expression=$1 ) */ + ctx.rule = rules.get(126); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("expression", 1); + tree.setAstTransformation(new AstTransformNodeCreator("LogicalNot", parameters)); + tree.setNudMorphemeCount(2); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_NOT)); + tree.add(parse_e_internal(ctx, get_prefix_binding_power_e(47))); + tree.setPrefix(true); + } + else if (rule_first.get(127).contains(terminal_map.get(current.getId()))) { + /* (127) $e = :plus $e -> UnaryPlus( expression=$1 ) */ + ctx.rule = rules.get(127); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("expression", 1); + tree.setAstTransformation(new AstTransformNodeCreator("UnaryPlus", parameters)); + tree.setNudMorphemeCount(2); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_PLUS)); + tree.add(parse_e_internal(ctx, get_prefix_binding_power_e(34))); + tree.setPrefix(true); + } + else if (rule_first.get(128).contains(terminal_map.get(current.getId()))) { + /* (128) $e = :dash $e -> UnaryNegation( expression=$1 ) */ + ctx.rule = rules.get(128); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("expression", 1); + tree.setAstTransformation(new AstTransformNodeCreator("UnaryNegation", parameters)); + tree.setNudMorphemeCount(2); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_DASH)); + tree.add(parse_e_internal(ctx, get_prefix_binding_power_e(37))); + tree.setPrefix(true); + } + else if (rule_first.get(133).contains(terminal_map.get(current.getId()))) { + /* (133) $e = :identifier <=> :lparen $_gen32 :rparen -> FunctionCall( name=$0, params=$2 ) */ + ctx.rule = rules.get(133); + tree.setAstTransformation(new AstTransformSubstitution(0)); + tree.setNudMorphemeCount(1); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_IDENTIFIER)); + } + else if (rule_first.get(134).contains(terminal_map.get(current.getId()))) { + /* (134) $e = :identifier <=> :lsquare $e :rsquare -> ArrayOrMapLookup( lhs=$0, rhs=$2 ) */ + ctx.rule = rules.get(134); + tree.setAstTransformation(new AstTransformSubstitution(0)); + tree.setNudMorphemeCount(1); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_IDENTIFIER)); + } + else if (rule_first.get(135).contains(terminal_map.get(current.getId()))) { + /* (135) $e = :identifier <=> :dot :identifier -> MemberAccess( lhs=$0, rhs=$2 ) */ + ctx.rule = rules.get(135); + tree.setAstTransformation(new AstTransformSubstitution(0)); + tree.setNudMorphemeCount(1); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_IDENTIFIER)); + } + else if (rule_first.get(140).contains(terminal_map.get(current.getId()))) { + /* (140) $e = :object :lbrace $_gen34 :rbrace -> ObjectLiteral( map=$2 ) */ + ctx.rule = rules.get(140); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("map", 2); + tree.setAstTransformation(new AstTransformNodeCreator("ObjectLiteral", parameters)); + tree.setNudMorphemeCount(4); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_OBJECT)); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_LBRACE)); + tree.add(parse__gen34(ctx)); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_RBRACE)); + } + else if (rule_first.get(141).contains(terminal_map.get(current.getId()))) { + /* (141) $e = :lsquare $_gen32 :rsquare -> ArrayLiteral( values=$1 ) */ + ctx.rule = rules.get(141); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("values", 1); + tree.setAstTransformation(new AstTransformNodeCreator("ArrayLiteral", parameters)); + tree.setNudMorphemeCount(3); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_LSQUARE)); + tree.add(parse__gen32(ctx)); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_RSQUARE)); + } + else if (rule_first.get(146).contains(terminal_map.get(current.getId()))) { + /* (146) $e = :lbrace $_gen36 :rbrace -> MapLiteral( map=$1 ) */ + ctx.rule = rules.get(146); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("map", 1); + tree.setAstTransformation(new AstTransformNodeCreator("MapLiteral", parameters)); + tree.setNudMorphemeCount(3); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_LBRACE)); + tree.add(parse__gen36(ctx)); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_RBRACE)); + } + else if (rule_first.get(147).contains(terminal_map.get(current.getId()))) { + /* (147) $e = :lparen $e :rparen -> $1 */ + ctx.rule = rules.get(147); + tree.setAstTransformation(new AstTransformSubstitution(1)); + tree.setNudMorphemeCount(3); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_LPAREN)); + tree.add(parse_e(ctx)); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_RPAREN)); + } + else if (rule_first.get(148).contains(terminal_map.get(current.getId()))) { + /* (148) $e = :string */ + ctx.rule = rules.get(148); + tree.setAstTransformation(new AstTransformSubstitution(0)); + tree.setNudMorphemeCount(1); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_STRING)); + } + else if (rule_first.get(149).contains(terminal_map.get(current.getId()))) { + /* (149) $e = :identifier */ + ctx.rule = rules.get(149); + tree.setAstTransformation(new AstTransformSubstitution(0)); + tree.setNudMorphemeCount(1); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_IDENTIFIER)); + } + else if (rule_first.get(150).contains(terminal_map.get(current.getId()))) { + /* (150) $e = :boolean */ + ctx.rule = rules.get(150); + tree.setAstTransformation(new AstTransformSubstitution(0)); + tree.setNudMorphemeCount(1); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_BOOLEAN)); + } + else if (rule_first.get(151).contains(terminal_map.get(current.getId()))) { + /* (151) $e = :integer */ + ctx.rule = rules.get(151); + tree.setAstTransformation(new AstTransformSubstitution(0)); + tree.setNudMorphemeCount(1); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_INTEGER)); + } + else if (rule_first.get(152).contains(terminal_map.get(current.getId()))) { + /* (152) $e = :float */ + ctx.rule = rules.get(152); + tree.setAstTransformation(new AstTransformSubstitution(0)); + tree.setNudMorphemeCount(1); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_FLOAT)); + } + return tree; + } + private static ParseTree led_e(ParseTree left, ParserContext ctx) throws SyntaxError { + ParseTree tree = new ParseTree( new NonTerminal(83, "e") ); + Terminal current = ctx.tokens.current(); + ctx.nonterminal = "e"; + int modifier; + if (current.getId() == 42) { + /* $e = $e :double_pipe $e -> LogicalOr( lhs=$0, rhs=$2 ) */ + ctx.rule = rules.get(113); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("lhs", 0); + parameters.put("rhs", 2); + tree.setAstTransformation(new AstTransformNodeCreator("LogicalOr", parameters)); + tree.setExprNud(true); + tree.add(left); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_DOUBLE_PIPE)); + modifier = 0; + tree.setInfix(true); + tree.add(parse_e_internal(ctx, get_infix_binding_power_e(42) - modifier)); + return tree; + } + if (current.getId() == 18) { + /* $e = $e :double_ampersand $e -> LogicalAnd( lhs=$0, rhs=$2 ) */ + ctx.rule = rules.get(114); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("lhs", 0); + parameters.put("rhs", 2); + tree.setAstTransformation(new AstTransformNodeCreator("LogicalAnd", parameters)); + tree.setExprNud(true); + tree.add(left); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_DOUBLE_AMPERSAND)); + modifier = 0; + tree.setInfix(true); + tree.add(parse_e_internal(ctx, get_infix_binding_power_e(18) - modifier)); + return tree; + } + if (current.getId() == 22) { + /* $e = $e :double_equal $e -> Equals( lhs=$0, rhs=$2 ) */ + ctx.rule = rules.get(115); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("lhs", 0); + parameters.put("rhs", 2); + tree.setAstTransformation(new AstTransformNodeCreator("Equals", parameters)); + tree.setExprNud(true); + tree.add(left); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_DOUBLE_EQUAL)); + modifier = 0; + tree.setInfix(true); + tree.add(parse_e_internal(ctx, get_infix_binding_power_e(22) - modifier)); + return tree; + } + if (current.getId() == 44) { + /* $e = $e :not_equal $e -> NotEquals( lhs=$0, rhs=$2 ) */ + ctx.rule = rules.get(116); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("lhs", 0); + parameters.put("rhs", 2); + tree.setAstTransformation(new AstTransformNodeCreator("NotEquals", parameters)); + tree.setExprNud(true); + tree.add(left); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_NOT_EQUAL)); + modifier = 0; + tree.setInfix(true); + tree.add(parse_e_internal(ctx, get_infix_binding_power_e(44) - modifier)); + return tree; + } + if (current.getId() == 35) { + /* $e = $e :lt $e -> LessThan( lhs=$0, rhs=$2 ) */ + ctx.rule = rules.get(117); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("lhs", 0); + parameters.put("rhs", 2); + tree.setAstTransformation(new AstTransformNodeCreator("LessThan", parameters)); + tree.setExprNud(true); + tree.add(left); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_LT)); + modifier = 0; + tree.setInfix(true); + tree.add(parse_e_internal(ctx, get_infix_binding_power_e(35) - modifier)); + return tree; + } + if (current.getId() == 28) { + /* $e = $e :lteq $e -> LessThanOrEqual( lhs=$0, rhs=$2 ) */ + ctx.rule = rules.get(118); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("lhs", 0); + parameters.put("rhs", 2); + tree.setAstTransformation(new AstTransformNodeCreator("LessThanOrEqual", parameters)); + tree.setExprNud(true); + tree.add(left); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_LTEQ)); + modifier = 0; + tree.setInfix(true); + tree.add(parse_e_internal(ctx, get_infix_binding_power_e(28) - modifier)); + return tree; + } + if (current.getId() == 14) { + /* $e = $e :gt $e -> GreaterThan( lhs=$0, rhs=$2 ) */ + ctx.rule = rules.get(119); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("lhs", 0); + parameters.put("rhs", 2); + tree.setAstTransformation(new AstTransformNodeCreator("GreaterThan", parameters)); + tree.setExprNud(true); + tree.add(left); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_GT)); + modifier = 0; + tree.setInfix(true); + tree.add(parse_e_internal(ctx, get_infix_binding_power_e(14) - modifier)); + return tree; + } + if (current.getId() == 33) { + /* $e = $e :gteq $e -> GreaterThanOrEqual( lhs=$0, rhs=$2 ) */ + ctx.rule = rules.get(120); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("lhs", 0); + parameters.put("rhs", 2); + tree.setAstTransformation(new AstTransformNodeCreator("GreaterThanOrEqual", parameters)); + tree.setExprNud(true); + tree.add(left); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_GTEQ)); + modifier = 0; + tree.setInfix(true); + tree.add(parse_e_internal(ctx, get_infix_binding_power_e(33) - modifier)); + return tree; + } + if (current.getId() == 34) { + /* $e = $e :plus $e -> Add( lhs=$0, rhs=$2 ) */ + ctx.rule = rules.get(121); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("lhs", 0); + parameters.put("rhs", 2); + tree.setAstTransformation(new AstTransformNodeCreator("Add", parameters)); + tree.setExprNud(true); + tree.add(left); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_PLUS)); + modifier = 0; + tree.setInfix(true); + tree.add(parse_e_internal(ctx, get_infix_binding_power_e(34) - modifier)); + return tree; + } + if (current.getId() == 37) { + /* $e = $e :dash $e -> Subtract( lhs=$0, rhs=$2 ) */ + ctx.rule = rules.get(122); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("lhs", 0); + parameters.put("rhs", 2); + tree.setAstTransformation(new AstTransformNodeCreator("Subtract", parameters)); + tree.setExprNud(true); + tree.add(left); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_DASH)); + modifier = 0; + tree.setInfix(true); + tree.add(parse_e_internal(ctx, get_infix_binding_power_e(37) - modifier)); + return tree; + } + if (current.getId() == 6) { + /* $e = $e :asterisk $e -> Multiply( lhs=$0, rhs=$2 ) */ + ctx.rule = rules.get(123); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("lhs", 0); + parameters.put("rhs", 2); + tree.setAstTransformation(new AstTransformNodeCreator("Multiply", parameters)); + tree.setExprNud(true); + tree.add(left); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_ASTERISK)); + modifier = 0; + tree.setInfix(true); + tree.add(parse_e_internal(ctx, get_infix_binding_power_e(6) - modifier)); + return tree; + } + if (current.getId() == 12) { + /* $e = $e :slash $e -> Divide( lhs=$0, rhs=$2 ) */ + ctx.rule = rules.get(124); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("lhs", 0); + parameters.put("rhs", 2); + tree.setAstTransformation(new AstTransformNodeCreator("Divide", parameters)); + tree.setExprNud(true); + tree.add(left); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_SLASH)); + modifier = 0; + tree.setInfix(true); + tree.add(parse_e_internal(ctx, get_infix_binding_power_e(12) - modifier)); + return tree; + } + if (current.getId() == 13) { + /* $e = $e :percent $e -> Remainder( lhs=$0, rhs=$2 ) */ + ctx.rule = rules.get(125); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("lhs", 0); + parameters.put("rhs", 2); + tree.setAstTransformation(new AstTransformNodeCreator("Remainder", parameters)); + tree.setExprNud(true); + tree.add(left); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_PERCENT)); + modifier = 0; + tree.setInfix(true); + tree.add(parse_e_internal(ctx, get_infix_binding_power_e(13) - modifier)); + return tree; + } + if (current.getId() == 3) { + /* $e = :identifier <=> :lparen $_gen32 :rparen -> FunctionCall( name=$0, params=$2 ) */ + ctx.rule = rules.get(133); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("name", 0); + parameters.put("params", 2); + tree.setAstTransformation(new AstTransformNodeCreator("FunctionCall", parameters)); + tree.add(left); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_LPAREN)); + tree.add(parse__gen32(ctx)); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_RPAREN)); + return tree; + } + if (current.getId() == 52) { + /* $e = :identifier <=> :lsquare $e :rsquare -> ArrayOrMapLookup( lhs=$0, rhs=$2 ) */ + ctx.rule = rules.get(134); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("lhs", 0); + parameters.put("rhs", 2); + tree.setAstTransformation(new AstTransformNodeCreator("ArrayOrMapLookup", parameters)); + tree.add(left); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_LSQUARE)); + modifier = 0; + tree.add(parse_e_internal(ctx, get_infix_binding_power_e(52) - modifier)); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_RSQUARE)); + return tree; + } + if (current.getId() == 41) { + /* $e = :identifier <=> :dot :identifier -> MemberAccess( lhs=$0, rhs=$2 ) */ + ctx.rule = rules.get(135); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("lhs", 0); + parameters.put("rhs", 2); + tree.setAstTransformation(new AstTransformNodeCreator("MemberAccess", parameters)); + tree.add(left); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_DOT)); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_IDENTIFIER)); + return tree; + } + return tree; + } + private static Map infix_binding_power_type_e; + private static Map prefix_binding_power_type_e; + static { + Map map = new HashMap(); + map.put(52, 1000); /* $type_e = :type <=> :lsquare list(nt=$type_e, sep=:comma, min=0, sep_terminates=False) :rsquare -> Type( name=$0, subtype=$2 ) */ + infix_binding_power_type_e = Collections.unmodifiableMap(map); + } + static { + Map map = new HashMap(); + prefix_binding_power_type_e = Collections.unmodifiableMap(map); + } + static int get_infix_binding_power_type_e(int terminal_id) { + if (infix_binding_power_type_e.containsKey(terminal_id)) { + return infix_binding_power_type_e.get(terminal_id); + } + return 0; + } + static int get_prefix_binding_power_type_e(int terminal_id) { + if (prefix_binding_power_type_e.containsKey(terminal_id)) { + return prefix_binding_power_type_e.get(terminal_id); + } + return 0; + } + public ParseTree parse_type_e(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_type_e_internal(ctx, 0); + } + public static ParseTree parse_type_e(ParserContext ctx) throws SyntaxError { + return parse_type_e_internal(ctx, 0); + } + public static ParseTree parse_type_e_internal(ParserContext ctx, int rbp) throws SyntaxError { + ParseTree left = nud_type_e(ctx); + if ( left instanceof ParseTree ) { + left.setExpr(true); + left.setNud(true); + } + while (ctx.tokens.current() != null && rbp < get_infix_binding_power_type_e(ctx.tokens.current().getId())) { + left = led_type_e(left, ctx); + } + if (left != null) { + left.setExpr(true); + } + return left; + } + private static ParseTree nud_type_e(ParserContext ctx) throws SyntaxError { + ParseTree tree = new ParseTree( new NonTerminal(58, "type_e") ); + Terminal current = ctx.tokens.current(); + ctx.nonterminal = "type_e"; + if (current == null) { + return tree; + } + if (rule_first.get(111).contains(terminal_map.get(current.getId()))) { + /* (111) $type_e = :type <=> :lsquare $_gen30 :rsquare -> Type( name=$0, subtype=$2 ) */ + ctx.rule = rules.get(111); + tree.setAstTransformation(new AstTransformSubstitution(0)); + tree.setNudMorphemeCount(1); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_TYPE)); + } + else if (rule_first.get(112).contains(terminal_map.get(current.getId()))) { + /* (112) $type_e = :type */ + ctx.rule = rules.get(112); + tree.setAstTransformation(new AstTransformSubstitution(0)); + tree.setNudMorphemeCount(1); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_TYPE)); + } + return tree; + } + private static ParseTree led_type_e(ParseTree left, ParserContext ctx) throws SyntaxError { + ParseTree tree = new ParseTree( new NonTerminal(58, "type_e") ); + Terminal current = ctx.tokens.current(); + ctx.nonterminal = "type_e"; + int modifier; + if (current.getId() == 52) { + /* $type_e = :type <=> :lsquare $_gen30 :rsquare -> Type( name=$0, subtype=$2 ) */ + ctx.rule = rules.get(111); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("name", 0); + parameters.put("subtype", 2); + tree.setAstTransformation(new AstTransformNodeCreator("Type", parameters)); + tree.add(left); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_LSQUARE)); + tree.add(parse__gen30(ctx)); + tree.add(expect(ctx, WdlTerminalIdentifier.TERMINAL_RSQUARE)); + return tree; + } + return tree; + } + public ParseTree parse_workflow(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_workflow(ctx); + } + private static ParseTree parse_workflow(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[0][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(56, "workflow")); + ctx.nonterminal = "workflow"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "workflow", + nonterminal_first.get(56), + nonterminal_rules.get(56) + )); + } + if (rule == 70) { + /* $workflow = :workflow :identifier :lbrace $_gen19 :rbrace -> Workflow( name=$1, body=$3 ) */ + ctx.rule = rules.get(70); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("name", 1); + parameters.put("body", 3); + tree.setAstTransformation(new AstTransformNodeCreator("Workflow", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_WORKFLOW); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_IDENTIFIER); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_LBRACE); + tree.add(next); + subtree = parse__gen19(ctx); + tree.add(subtree); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_RBRACE); + tree.add(next); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "workflow", + current, + nonterminal_first.get(56), + rules.get(70) + )); + } + public ParseTree parse_cmd_param_kv(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_cmd_param_kv(ctx); + } + private static ParseTree parse_cmd_param_kv(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[1][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(57, "cmd_param_kv")); + ctx.nonterminal = "cmd_param_kv"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "cmd_param_kv", + nonterminal_first.get(57), + nonterminal_rules.get(57) + )); + } + if (rule == 41) { + /* $cmd_param_kv = :cmd_attr_hint :identifier :equal $e -> CommandParameterAttr( key=$1, value=$3 ) */ + ctx.rule = rules.get(41); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("key", 1); + parameters.put("value", 3); + tree.setAstTransformation(new AstTransformNodeCreator("CommandParameterAttr", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_CMD_ATTR_HINT); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_IDENTIFIER); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_EQUAL); + tree.add(next); + subtree = parse_e(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "cmd_param_kv", + current, + nonterminal_first.get(57), + rules.get(41) + )); + } + public ParseTree parse__gen37(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen37(ctx); + } + private static ParseTree parse__gen37(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[3][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(59, "_gen37")); + ctx.nonterminal = "_gen37"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(59).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(59).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 143) { + /* $_gen37 = :comma $map_kv $_gen37 */ + ctx.rule = rules.get(143); + tree.setAstTransformation(new AstTransformSubstitution(0)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_COMMA); + tree.add(next); + tree.setListSeparator(next); + subtree = parse_map_kv(ctx); + tree.add(subtree); + subtree = parse__gen37(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen28(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen28(ctx); + } + private static ParseTree parse__gen28(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[4][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(60, "_gen28")); + ctx.nonterminal = "_gen28"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(60).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(60).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 95) { + /* $_gen28 = $wf_output $_gen28 */ + ctx.rule = rules.get(95); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_wf_output(ctx); + tree.add(subtree); + subtree = parse__gen28(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen0(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen0(ctx); + } + private static ParseTree parse__gen0(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[5][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(61, "_gen0")); + ctx.nonterminal = "_gen0"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(61).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(61).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 0) { + /* $_gen0 = $import $_gen1 */ + ctx.rule = rules.get(0); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_import(ctx); + tree.add(subtree); + subtree = parse__gen1(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_call(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_call(ctx); + } + private static ParseTree parse_call(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[6][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(62, "call")); + ctx.nonterminal = "call"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "call", + nonterminal_first.get(62), + nonterminal_rules.get(62) + )); + } + if (rule == 81) { + /* $call = :call :fqn $_gen21 $_gen22 -> Call( task=$1, alias=$2, body=$3 ) */ + ctx.rule = rules.get(81); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("task", 1); + parameters.put("alias", 2); + parameters.put("body", 3); + tree.setAstTransformation(new AstTransformNodeCreator("Call", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_CALL); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_FQN); + tree.add(next); + subtree = parse__gen21(ctx); + tree.add(subtree); + subtree = parse__gen22(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "call", + current, + nonterminal_first.get(62), + rules.get(81) + )); + } + public ParseTree parse__gen15(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen15(ctx); + } + private static ParseTree parse__gen15(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[7][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(63, "_gen15")); + ctx.nonterminal = "_gen15"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(63).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(63).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 51) { + /* $_gen15 = $kv $_gen16 */ + ctx.rule = rules.get(51); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_kv(ctx); + tree.add(subtree); + subtree = parse__gen16(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen11(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen11(ctx); + } + private static ParseTree parse__gen11(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[8][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(64, "_gen11")); + ctx.nonterminal = "_gen11"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(64).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(64).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 36) { + /* $_gen11 = $cmd_param_kv $_gen12 */ + ctx.rule = rules.get(36); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_cmd_param_kv(ctx); + tree.add(subtree); + subtree = parse__gen12(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_kv(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_kv(ctx); + } + private static ParseTree parse_kv(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[9][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(65, "kv")); + ctx.nonterminal = "kv"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "kv", + nonterminal_first.get(65), + nonterminal_rules.get(65) + )); + } + if (rule == 56) { + /* $kv = :identifier :colon $e -> RuntimeAttribute( key=$0, value=$2 ) */ + ctx.rule = rules.get(56); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("key", 0); + parameters.put("value", 2); + tree.setAstTransformation(new AstTransformNodeCreator("RuntimeAttribute", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_IDENTIFIER); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_COLON); + tree.add(next); + subtree = parse_e(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "kv", + current, + nonterminal_first.get(65), + rules.get(56) + )); + } + public ParseTree parse__gen17(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen17(ctx); + } + private static ParseTree parse__gen17(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[10][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(66, "_gen17")); + ctx.nonterminal = "_gen17"; + tree.setList(false); + if ( current != null && + !nonterminal_first.get(66).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(66).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 57) { + /* $_gen17 = $postfix_quantifier */ + ctx.rule = rules.get(57); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_postfix_quantifier(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_wf_output(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_wf_output(ctx); + } + private static ParseTree parse_wf_output(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[11][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(67, "wf_output")); + ctx.nonterminal = "wf_output"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "wf_output", + nonterminal_first.get(67), + nonterminal_rules.get(67) + )); + } + if (rule == 101) { + /* $wf_output = :fqn $_gen29 -> WorkflowOutput( fqn=$0, wildcard=$1 ) */ + ctx.rule = rules.get(101); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("fqn", 0); + parameters.put("wildcard", 1); + tree.setAstTransformation(new AstTransformNodeCreator("WorkflowOutput", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_FQN); + tree.add(next); + subtree = parse__gen29(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "wf_output", + current, + nonterminal_first.get(67), + rules.get(101) + )); + } + public ParseTree parse__gen21(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen21(ctx); + } + private static ParseTree parse__gen21(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[12][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(68, "_gen21")); + ctx.nonterminal = "_gen21"; + tree.setList(false); + if ( current != null && + !nonterminal_first.get(68).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(68).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 77) { + /* $_gen21 = $alias */ + ctx.rule = rules.get(77); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_alias(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_workflow_or_task(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_workflow_or_task(ctx); + } + private static ParseTree parse_workflow_or_task(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[13][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(69, "workflow_or_task")); + ctx.nonterminal = "workflow_or_task"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "workflow_or_task", + nonterminal_first.get(69), + nonterminal_rules.get(69) + )); + } + if (rule == 9) { + /* $workflow_or_task = $workflow */ + ctx.rule = rules.get(9); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_workflow(ctx); + tree.add(subtree); + return tree; + } + else if (rule == 10) { + /* $workflow_or_task = $task */ + ctx.rule = rules.get(10); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_task(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "workflow_or_task", + current, + nonterminal_first.get(69), + rules.get(10) + )); + } + public ParseTree parse__gen13(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen13(ctx); + } + private static ParseTree parse__gen13(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[14][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(70, "_gen13")); + ctx.nonterminal = "_gen13"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(70).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(70).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 42) { + /* $_gen13 = $output_kv $_gen14 */ + ctx.rule = rules.get(42); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_output_kv(ctx); + tree.add(subtree); + subtree = parse__gen14(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen26(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen26(ctx); + } + private static ParseTree parse__gen26(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[15][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(71, "_gen26")); + ctx.nonterminal = "_gen26"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(71).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(71).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 88) { + /* $_gen26 = :comma $mapping $_gen26 */ + ctx.rule = rules.get(88); + tree.setAstTransformation(new AstTransformSubstitution(0)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_COMMA); + tree.add(next); + tree.setListSeparator(next); + subtree = parse_mapping(ctx); + tree.add(subtree); + subtree = parse__gen26(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen20(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen20(ctx); + } + private static ParseTree parse__gen20(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[16][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(72, "_gen20")); + ctx.nonterminal = "_gen20"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(72).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(72).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 67) { + /* $_gen20 = $wf_body_element $_gen20 */ + ctx.rule = rules.get(67); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_wf_body_element(ctx); + tree.add(subtree); + subtree = parse__gen20(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_call_body(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_call_body(ctx); + } + private static ParseTree parse_call_body(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[17][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(73, "call_body")); + ctx.nonterminal = "call_body"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "call_body", + nonterminal_first.get(73), + nonterminal_rules.get(73) + )); + } + if (rule == 86) { + /* $call_body = :lbrace $_gen5 $_gen23 :rbrace -> CallBody( declarations=$1, io=$2 ) */ + ctx.rule = rules.get(86); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("declarations", 1); + parameters.put("io", 2); + tree.setAstTransformation(new AstTransformNodeCreator("CallBody", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_LBRACE); + tree.add(next); + subtree = parse__gen5(ctx); + tree.add(subtree); + subtree = parse__gen23(ctx); + tree.add(subtree); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_RBRACE); + tree.add(next); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "call_body", + current, + nonterminal_first.get(73), + rules.get(86) + )); + } + public ParseTree parse__gen31(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen31(ctx); + } + private static ParseTree parse__gen31(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[18][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(74, "_gen31")); + ctx.nonterminal = "_gen31"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(74).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(74).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 108) { + /* $_gen31 = :comma $type_e $_gen31 */ + ctx.rule = rules.get(108); + tree.setAstTransformation(new AstTransformSubstitution(0)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_COMMA); + tree.add(next); + tree.setListSeparator(next); + subtree = parse_type_e(ctx); + tree.add(subtree); + subtree = parse__gen31(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_parameter_meta(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_parameter_meta(ctx); + } + private static ParseTree parse_parameter_meta(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[19][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(75, "parameter_meta")); + ctx.nonterminal = "parameter_meta"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "parameter_meta", + nonterminal_first.get(75), + nonterminal_rules.get(75) + )); + } + if (rule == 49) { + /* $parameter_meta = :parameter_meta $map -> ParameterMeta( map=$1 ) */ + ctx.rule = rules.get(49); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("map", 1); + tree.setAstTransformation(new AstTransformNodeCreator("ParameterMeta", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_PARAMETER_META); + tree.add(next); + subtree = parse_map(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "parameter_meta", + current, + nonterminal_first.get(75), + rules.get(49) + )); + } + public ParseTree parse__gen9(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen9(ctx); + } + private static ParseTree parse__gen9(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[20][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(76, "_gen9")); + ctx.nonterminal = "_gen9"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(76).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(76).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 29) { + /* $_gen9 = $command_part $_gen10 */ + ctx.rule = rules.get(29); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_command_part(ctx); + tree.add(subtree); + subtree = parse__gen10(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen4(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen4(ctx); + } + private static ParseTree parse__gen4(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[21][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(77, "_gen4")); + ctx.nonterminal = "_gen4"; + tree.setList(false); + if ( current != null && + !nonterminal_first.get(77).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(77).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 11) { + /* $_gen4 = $import_namespace */ + ctx.rule = rules.get(11); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_import_namespace(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_object_kv(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_object_kv(ctx); + } + private static ParseTree parse_object_kv(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[22][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(78, "object_kv")); + ctx.nonterminal = "object_kv"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "object_kv", + nonterminal_first.get(78), + nonterminal_rules.get(78) + )); + } + if (rule == 106) { + /* $object_kv = :identifier :colon $e -> ObjectKV( key=$0, value=$2 ) */ + ctx.rule = rules.get(106); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("key", 0); + parameters.put("value", 2); + tree.setAstTransformation(new AstTransformNodeCreator("ObjectKV", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_IDENTIFIER); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_COLON); + tree.add(next); + subtree = parse_e(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "object_kv", + current, + nonterminal_first.get(78), + rules.get(106) + )); + } + public ParseTree parse_scatter(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_scatter(ctx); + } + private static ParseTree parse_scatter(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[23][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(79, "scatter")); + ctx.nonterminal = "scatter"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "scatter", + nonterminal_first.get(79), + nonterminal_rules.get(79) + )); + } + if (rule == 105) { + /* $scatter = :scatter :lparen :identifier :in $e :rparen :lbrace $_gen19 :rbrace -> Scatter( item=$2, collection=$4, body=$7 ) */ + ctx.rule = rules.get(105); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("item", 2); + parameters.put("collection", 4); + parameters.put("body", 7); + tree.setAstTransformation(new AstTransformNodeCreator("Scatter", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_SCATTER); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_LPAREN); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_IDENTIFIER); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_IN); + tree.add(next); + subtree = parse_e(ctx); + tree.add(subtree); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_RPAREN); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_LBRACE); + tree.add(next); + subtree = parse__gen19(ctx); + tree.add(subtree); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_RBRACE); + tree.add(next); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "scatter", + current, + nonterminal_first.get(79), + rules.get(105) + )); + } + public ParseTree parse__gen30(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen30(ctx); + } + private static ParseTree parse__gen30(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[24][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(80, "_gen30")); + ctx.nonterminal = "_gen30"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(80).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(80).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 107) { + /* $_gen30 = $type_e $_gen31 */ + ctx.rule = rules.get(107); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_type_e(ctx); + tree.add(subtree); + subtree = parse__gen31(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen18(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen18(ctx); + } + private static ParseTree parse__gen18(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[25][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(81, "_gen18")); + ctx.nonterminal = "_gen18"; + tree.setList(false); + if ( current != null && + !nonterminal_first.get(81).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(81).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 59) { + /* $_gen18 = $setter */ + ctx.rule = rules.get(59); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_setter(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen16(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen16(ctx); + } + private static ParseTree parse__gen16(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[26][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(82, "_gen16")); + ctx.nonterminal = "_gen16"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(82).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(82).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 52) { + /* $_gen16 = $kv $_gen16 */ + ctx.rule = rules.get(52); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_kv(ctx); + tree.add(subtree); + subtree = parse__gen16(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen27(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen27(ctx); + } + private static ParseTree parse__gen27(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[28][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(84, "_gen27")); + ctx.nonterminal = "_gen27"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(84).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(84).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 94) { + /* $_gen27 = $wf_output $_gen28 */ + ctx.rule = rules.get(94); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_wf_output(ctx); + tree.add(subtree); + subtree = parse__gen28(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_command_part(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_command_part(ctx); + } + private static ParseTree parse_command_part(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[29][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(85, "command_part")); + ctx.nonterminal = "command_part"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "command_part", + nonterminal_first.get(85), + nonterminal_rules.get(85) + )); + } + if (rule == 34) { + /* $command_part = :cmd_part */ + ctx.rule = rules.get(34); + tree.setAstTransformation(new AstTransformSubstitution(0)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_CMD_PART); + tree.add(next); + return tree; + } + else if (rule == 35) { + /* $command_part = $cmd_param */ + ctx.rule = rules.get(35); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_cmd_param(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "command_part", + current, + nonterminal_first.get(85), + rules.get(35) + )); + } + public ParseTree parse_import(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_import(ctx); + } + private static ParseTree parse_import(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[30][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(86, "import")); + ctx.nonterminal = "import"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "import", + nonterminal_first.get(86), + nonterminal_rules.get(86) + )); + } + if (rule == 13) { + /* $import = :import :string $_gen4 -> Import( uri=$1, namespace=$2 ) */ + ctx.rule = rules.get(13); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("uri", 1); + parameters.put("namespace", 2); + tree.setAstTransformation(new AstTransformNodeCreator("Import", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_IMPORT); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_STRING); + tree.add(next); + subtree = parse__gen4(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "import", + current, + nonterminal_first.get(86), + rules.get(13) + )); + } + public ParseTree parse__gen1(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen1(ctx); + } + private static ParseTree parse__gen1(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[31][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(87, "_gen1")); + ctx.nonterminal = "_gen1"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(87).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(87).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 1) { + /* $_gen1 = $import $_gen1 */ + ctx.rule = rules.get(1); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_import(ctx); + tree.add(subtree); + subtree = parse__gen1(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_task(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_task(ctx); + } + private static ParseTree parse_task(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[32][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(88, "task")); + ctx.nonterminal = "task"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "task", + nonterminal_first.get(88), + nonterminal_rules.get(88) + )); + } + if (rule == 23) { + /* $task = :task :identifier :lbrace $_gen5 $_gen7 :rbrace -> Task( name=$1, declarations=$3, sections=$4 ) */ + ctx.rule = rules.get(23); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("name", 1); + parameters.put("declarations", 3); + parameters.put("sections", 4); + tree.setAstTransformation(new AstTransformNodeCreator("Task", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_TASK); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_IDENTIFIER); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_LBRACE); + tree.add(next); + subtree = parse__gen5(ctx); + tree.add(subtree); + subtree = parse__gen7(ctx); + tree.add(subtree); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_RBRACE); + tree.add(next); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "task", + current, + nonterminal_first.get(88), + rules.get(23) + )); + } + public ParseTree parse__gen25(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen25(ctx); + } + private static ParseTree parse__gen25(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[33][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(89, "_gen25")); + ctx.nonterminal = "_gen25"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(89).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(89).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 87) { + /* $_gen25 = $mapping $_gen26 */ + ctx.rule = rules.get(87); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_mapping(ctx); + tree.add(subtree); + subtree = parse__gen26(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen3(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen3(ctx); + } + private static ParseTree parse__gen3(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[34][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(90, "_gen3")); + ctx.nonterminal = "_gen3"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(90).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(90).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 5) { + /* $_gen3 = $workflow_or_task $_gen3 */ + ctx.rule = rules.get(5); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_workflow_or_task(ctx); + tree.add(subtree); + subtree = parse__gen3(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen12(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen12(ctx); + } + private static ParseTree parse__gen12(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[35][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(91, "_gen12")); + ctx.nonterminal = "_gen12"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(91).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(91).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 37) { + /* $_gen12 = $cmd_param_kv $_gen12 */ + ctx.rule = rules.get(37); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_cmd_param_kv(ctx); + tree.add(subtree); + subtree = parse__gen12(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen14(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen14(ctx); + } + private static ParseTree parse__gen14(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[36][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(92, "_gen14")); + ctx.nonterminal = "_gen14"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(92).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(92).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 43) { + /* $_gen14 = $output_kv $_gen14 */ + ctx.rule = rules.get(43); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_output_kv(ctx); + tree.add(subtree); + subtree = parse__gen14(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_mapping(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_mapping(ctx); + } + private static ParseTree parse_mapping(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[37][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(93, "mapping")); + ctx.nonterminal = "mapping"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "mapping", + nonterminal_first.get(93), + nonterminal_rules.get(93) + )); + } + if (rule == 92) { + /* $mapping = :identifier :equal $e -> IOMapping( key=$0, value=$2 ) */ + ctx.rule = rules.get(92); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("key", 0); + parameters.put("value", 2); + tree.setAstTransformation(new AstTransformNodeCreator("IOMapping", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_IDENTIFIER); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_EQUAL); + tree.add(next); + subtree = parse_e(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "mapping", + current, + nonterminal_first.get(93), + rules.get(92) + )); + } + public ParseTree parse_output_kv(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_output_kv(ctx); + } + private static ParseTree parse_output_kv(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[38][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(94, "output_kv")); + ctx.nonterminal = "output_kv"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "output_kv", + nonterminal_first.get(94), + nonterminal_rules.get(94) + )); + } + if (rule == 47) { + /* $output_kv = $type_e :identifier :equal $e -> Output( type=$0, var=$1, expression=$3 ) */ + ctx.rule = rules.get(47); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("type", 0); + parameters.put("var", 1); + parameters.put("expression", 3); + tree.setAstTransformation(new AstTransformNodeCreator("Output", parameters)); + subtree = parse_type_e(ctx); + tree.add(subtree); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_IDENTIFIER); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_EQUAL); + tree.add(next); + subtree = parse_e(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "output_kv", + current, + nonterminal_first.get(94), + rules.get(47) + )); + } + public ParseTree parse__gen23(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen23(ctx); + } + private static ParseTree parse__gen23(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[39][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(95, "_gen23")); + ctx.nonterminal = "_gen23"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(95).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(95).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 82) { + /* $_gen23 = $call_input $_gen24 */ + ctx.rule = rules.get(82); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_call_input(ctx); + tree.add(subtree); + subtree = parse__gen24(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_call_input(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_call_input(ctx); + } + private static ParseTree parse_call_input(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[40][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(96, "call_input")); + ctx.nonterminal = "call_input"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "call_input", + nonterminal_first.get(96), + nonterminal_rules.get(96) + )); + } + if (rule == 91) { + /* $call_input = :input :colon $_gen25 -> Inputs( map=$2 ) */ + ctx.rule = rules.get(91); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("map", 2); + tree.setAstTransformation(new AstTransformNodeCreator("Inputs", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_INPUT); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_COLON); + tree.add(next); + subtree = parse__gen25(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "call_input", + current, + nonterminal_first.get(96), + rules.get(91) + )); + } + public ParseTree parse__gen32(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen32(ctx); + } + private static ParseTree parse__gen32(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[41][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(97, "_gen32")); + ctx.nonterminal = "_gen32"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(97).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(97).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 129) { + /* $_gen32 = $e $_gen33 */ + ctx.rule = rules.get(129); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_e(ctx); + tree.add(subtree); + subtree = parse__gen33(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen34(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen34(ctx); + } + private static ParseTree parse__gen34(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[42][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(98, "_gen34")); + ctx.nonterminal = "_gen34"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(98).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(98).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 136) { + /* $_gen34 = $object_kv $_gen35 */ + ctx.rule = rules.get(136); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_object_kv(ctx); + tree.add(subtree); + subtree = parse__gen35(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_wf_output_wildcard(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_wf_output_wildcard(ctx); + } + private static ParseTree parse_wf_output_wildcard(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[43][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(99, "wf_output_wildcard")); + ctx.nonterminal = "wf_output_wildcard"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "wf_output_wildcard", + nonterminal_first.get(99), + nonterminal_rules.get(99) + )); + } + if (rule == 102) { + /* $wf_output_wildcard = :dot :asterisk -> $1 */ + ctx.rule = rules.get(102); + tree.setAstTransformation(new AstTransformSubstitution(1)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_DOT); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_ASTERISK); + tree.add(next); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "wf_output_wildcard", + current, + nonterminal_first.get(99), + rules.get(102) + )); + } + public ParseTree parse_meta(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_meta(ctx); + } + private static ParseTree parse_meta(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[44][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(100, "meta")); + ctx.nonterminal = "meta"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "meta", + nonterminal_first.get(100), + nonterminal_rules.get(100) + )); + } + if (rule == 50) { + /* $meta = :meta $map -> Meta( map=$1 ) */ + ctx.rule = rules.get(50); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("map", 1); + tree.setAstTransformation(new AstTransformNodeCreator("Meta", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_META); + tree.add(next); + subtree = parse_map(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "meta", + current, + nonterminal_first.get(100), + rules.get(50) + )); + } + public ParseTree parse_map_kv(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_map_kv(ctx); + } + private static ParseTree parse_map_kv(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[45][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(101, "map_kv")); + ctx.nonterminal = "map_kv"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "map_kv", + nonterminal_first.get(101), + nonterminal_rules.get(101) + )); + } + if (rule == 65) { + /* $map_kv = $e :colon $e -> MapLiteralKv( key=$0, value=$2 ) */ + ctx.rule = rules.get(65); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("key", 0); + parameters.put("value", 2); + tree.setAstTransformation(new AstTransformNodeCreator("MapLiteralKv", parameters)); + subtree = parse_e(ctx); + tree.add(subtree); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_COLON); + tree.add(next); + subtree = parse_e(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "map_kv", + current, + nonterminal_first.get(101), + rules.get(65) + )); + } + public ParseTree parse_alias(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_alias(ctx); + } + private static ParseTree parse_alias(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[46][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(102, "alias")); + ctx.nonterminal = "alias"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "alias", + nonterminal_first.get(102), + nonterminal_rules.get(102) + )); + } + if (rule == 93) { + /* $alias = :as :identifier -> $1 */ + ctx.rule = rules.get(93); + tree.setAstTransformation(new AstTransformSubstitution(1)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_AS); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_IDENTIFIER); + tree.add(next); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "alias", + current, + nonterminal_first.get(102), + rules.get(93) + )); + } + public ParseTree parse__gen7(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen7(ctx); + } + private static ParseTree parse__gen7(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[47][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(103, "_gen7")); + ctx.nonterminal = "_gen7"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(103).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(103).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 19) { + /* $_gen7 = $sections $_gen8 */ + ctx.rule = rules.get(19); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_sections(ctx); + tree.add(subtree); + subtree = parse__gen8(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_map(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_map(ctx); + } + private static ParseTree parse_map(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[48][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(104, "map")); + ctx.nonterminal = "map"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "map", + nonterminal_first.get(104), + nonterminal_rules.get(104) + )); + } + if (rule == 55) { + /* $map = :lbrace $_gen15 :rbrace -> $1 */ + ctx.rule = rules.get(55); + tree.setAstTransformation(new AstTransformSubstitution(1)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_LBRACE); + tree.add(next); + subtree = parse__gen15(ctx); + tree.add(subtree); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_RBRACE); + tree.add(next); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "map", + current, + nonterminal_first.get(104), + rules.get(55) + )); + } + public ParseTree parse_cmd_param(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_cmd_param(ctx); + } + private static ParseTree parse_cmd_param(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[49][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(105, "cmd_param")); + ctx.nonterminal = "cmd_param"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "cmd_param", + nonterminal_first.get(105), + nonterminal_rules.get(105) + )); + } + if (rule == 40) { + /* $cmd_param = :cmd_param_start $_gen11 $e :cmd_param_end -> CommandParameter( attributes=$1, expr=$2 ) */ + ctx.rule = rules.get(40); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("attributes", 1); + parameters.put("expr", 2); + tree.setAstTransformation(new AstTransformNodeCreator("CommandParameter", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_CMD_PARAM_START); + tree.add(next); + subtree = parse__gen11(ctx); + tree.add(subtree); + subtree = parse_e(ctx); + tree.add(subtree); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_CMD_PARAM_END); + tree.add(next); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "cmd_param", + current, + nonterminal_first.get(105), + rules.get(40) + )); + } + public ParseTree parse__gen22(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen22(ctx); + } + private static ParseTree parse__gen22(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[50][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(106, "_gen22")); + ctx.nonterminal = "_gen22"; + tree.setList(false); + if ( current != null && + !nonterminal_first.get(106).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(106).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 79) { + /* $_gen22 = $call_body */ + ctx.rule = rules.get(79); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_call_body(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_if_stmt(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_if_stmt(ctx); + } + private static ParseTree parse_if_stmt(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[51][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(107, "if_stmt")); + ctx.nonterminal = "if_stmt"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "if_stmt", + nonterminal_first.get(107), + nonterminal_rules.get(107) + )); + } + if (rule == 104) { + /* $if_stmt = :if :lparen $e :rparen :lbrace $_gen19 :rbrace -> If( expression=$2, body=$5 ) */ + ctx.rule = rules.get(104); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("expression", 2); + parameters.put("body", 5); + tree.setAstTransformation(new AstTransformNodeCreator("If", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_IF); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_LPAREN); + tree.add(next); + subtree = parse_e(ctx); + tree.add(subtree); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_RPAREN); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_LBRACE); + tree.add(next); + subtree = parse__gen19(ctx); + tree.add(subtree); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_RBRACE); + tree.add(next); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "if_stmt", + current, + nonterminal_first.get(107), + rules.get(104) + )); + } + public ParseTree parse_postfix_quantifier(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_postfix_quantifier(ctx); + } + private static ParseTree parse_postfix_quantifier(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[52][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(108, "postfix_quantifier")); + ctx.nonterminal = "postfix_quantifier"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "postfix_quantifier", + nonterminal_first.get(108), + nonterminal_rules.get(108) + )); + } + if (rule == 63) { + /* $postfix_quantifier = :qmark */ + ctx.rule = rules.get(63); + tree.setAstTransformation(new AstTransformSubstitution(0)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_QMARK); + tree.add(next); + return tree; + } + else if (rule == 64) { + /* $postfix_quantifier = :plus */ + ctx.rule = rules.get(64); + tree.setAstTransformation(new AstTransformSubstitution(0)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_PLUS); + tree.add(next); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "postfix_quantifier", + current, + nonterminal_first.get(108), + rules.get(64) + )); + } + public ParseTree parse__gen33(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen33(ctx); + } + private static ParseTree parse__gen33(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[53][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(109, "_gen33")); + ctx.nonterminal = "_gen33"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(109).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(109).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 130) { + /* $_gen33 = :comma $e $_gen33 */ + ctx.rule = rules.get(130); + tree.setAstTransformation(new AstTransformSubstitution(0)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_COMMA); + tree.add(next); + tree.setListSeparator(next); + subtree = parse_e(ctx); + tree.add(subtree); + subtree = parse__gen33(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen36(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen36(ctx); + } + private static ParseTree parse__gen36(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[54][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(110, "_gen36")); + ctx.nonterminal = "_gen36"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(110).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(110).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 142) { + /* $_gen36 = $map_kv $_gen37 */ + ctx.rule = rules.get(142); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_map_kv(ctx); + tree.add(subtree); + subtree = parse__gen37(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen2(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen2(ctx); + } + private static ParseTree parse__gen2(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[55][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(111, "_gen2")); + ctx.nonterminal = "_gen2"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(111).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(111).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 4) { + /* $_gen2 = $workflow_or_task $_gen3 */ + ctx.rule = rules.get(4); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_workflow_or_task(ctx); + tree.add(subtree); + subtree = parse__gen3(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen6(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen6(ctx); + } + private static ParseTree parse__gen6(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[56][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(112, "_gen6")); + ctx.nonterminal = "_gen6"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(112).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(112).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 16) { + /* $_gen6 = $declaration $_gen6 */ + ctx.rule = rules.get(16); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_declaration(ctx); + tree.add(subtree); + subtree = parse__gen6(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen10(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen10(ctx); + } + private static ParseTree parse__gen10(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[57][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(113, "_gen10")); + ctx.nonterminal = "_gen10"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(113).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(113).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 30) { + /* $_gen10 = $command_part $_gen10 */ + ctx.rule = rules.get(30); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_command_part(ctx); + tree.add(subtree); + subtree = parse__gen10(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse__gen5(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen5(ctx); + } + private static ParseTree parse__gen5(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[58][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(114, "_gen5")); + ctx.nonterminal = "_gen5"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(114).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(114).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 15) { + /* $_gen5 = $declaration $_gen6 */ + ctx.rule = rules.get(15); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_declaration(ctx); + tree.add(subtree); + subtree = parse__gen6(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_wf_body_element(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_wf_body_element(ctx); + } + private static ParseTree parse_wf_body_element(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[59][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(115, "wf_body_element")); + ctx.nonterminal = "wf_body_element"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "wf_body_element", + nonterminal_first.get(115), + nonterminal_rules.get(115) + )); + } + if (rule == 71) { + /* $wf_body_element = $call */ + ctx.rule = rules.get(71); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_call(ctx); + tree.add(subtree); + return tree; + } + else if (rule == 72) { + /* $wf_body_element = $declaration */ + ctx.rule = rules.get(72); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_declaration(ctx); + tree.add(subtree); + return tree; + } + else if (rule == 73) { + /* $wf_body_element = $while_loop */ + ctx.rule = rules.get(73); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_while_loop(ctx); + tree.add(subtree); + return tree; + } + else if (rule == 74) { + /* $wf_body_element = $if_stmt */ + ctx.rule = rules.get(74); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_if_stmt(ctx); + tree.add(subtree); + return tree; + } + else if (rule == 75) { + /* $wf_body_element = $scatter */ + ctx.rule = rules.get(75); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_scatter(ctx); + tree.add(subtree); + return tree; + } + else if (rule == 76) { + /* $wf_body_element = $wf_outputs */ + ctx.rule = rules.get(76); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_wf_outputs(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "wf_body_element", + current, + nonterminal_first.get(115), + rules.get(76) + )); + } + public ParseTree parse_while_loop(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_while_loop(ctx); + } + private static ParseTree parse_while_loop(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[60][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(116, "while_loop")); + ctx.nonterminal = "while_loop"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "while_loop", + nonterminal_first.get(116), + nonterminal_rules.get(116) + )); + } + if (rule == 103) { + /* $while_loop = :while :lparen $e :rparen :lbrace $_gen19 :rbrace -> WhileLoop( expression=$2, body=$5 ) */ + ctx.rule = rules.get(103); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("expression", 2); + parameters.put("body", 5); + tree.setAstTransformation(new AstTransformNodeCreator("WhileLoop", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_WHILE); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_LPAREN); + tree.add(next); + subtree = parse_e(ctx); + tree.add(subtree); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_RPAREN); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_LBRACE); + tree.add(next); + subtree = parse__gen19(ctx); + tree.add(subtree); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_RBRACE); + tree.add(next); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "while_loop", + current, + nonterminal_first.get(116), + rules.get(103) + )); + } + public ParseTree parse_runtime(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_runtime(ctx); + } + private static ParseTree parse_runtime(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[61][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(117, "runtime")); + ctx.nonterminal = "runtime"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "runtime", + nonterminal_first.get(117), + nonterminal_rules.get(117) + )); + } + if (rule == 48) { + /* $runtime = :runtime $map -> Runtime( map=$1 ) */ + ctx.rule = rules.get(48); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("map", 1); + tree.setAstTransformation(new AstTransformNodeCreator("Runtime", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_RUNTIME); + tree.add(next); + subtree = parse_map(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "runtime", + current, + nonterminal_first.get(117), + rules.get(48) + )); + } + public ParseTree parse_sections(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_sections(ctx); + } + private static ParseTree parse_sections(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[62][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(118, "sections")); + ctx.nonterminal = "sections"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "sections", + nonterminal_first.get(118), + nonterminal_rules.get(118) + )); + } + if (rule == 24) { + /* $sections = $command */ + ctx.rule = rules.get(24); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_command(ctx); + tree.add(subtree); + return tree; + } + else if (rule == 25) { + /* $sections = $outputs */ + ctx.rule = rules.get(25); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_outputs(ctx); + tree.add(subtree); + return tree; + } + else if (rule == 26) { + /* $sections = $runtime */ + ctx.rule = rules.get(26); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_runtime(ctx); + tree.add(subtree); + return tree; + } + else if (rule == 27) { + /* $sections = $parameter_meta */ + ctx.rule = rules.get(27); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_parameter_meta(ctx); + tree.add(subtree); + return tree; + } + else if (rule == 28) { + /* $sections = $meta */ + ctx.rule = rules.get(28); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_meta(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "sections", + current, + nonterminal_first.get(118), + rules.get(28) + )); + } + public ParseTree parse__gen24(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen24(ctx); + } + private static ParseTree parse__gen24(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[63][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(119, "_gen24")); + ctx.nonterminal = "_gen24"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(119).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(119).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 83) { + /* $_gen24 = $call_input $_gen24 */ + ctx.rule = rules.get(83); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_call_input(ctx); + tree.add(subtree); + subtree = parse__gen24(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_document(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_document(ctx); + } + private static ParseTree parse_document(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[64][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(120, "document")); + ctx.nonterminal = "document"; + tree.setList(false); + if ( current != null && + !nonterminal_first.get(120).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(120).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 8) { + /* $document = $_gen0 $_gen2 -> Document( imports=$0, definitions=$1 ) */ + ctx.rule = rules.get(8); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("imports", 0); + parameters.put("definitions", 1); + tree.setAstTransformation(new AstTransformNodeCreator("Document", parameters)); + subtree = parse__gen0(ctx); + tree.add(subtree); + subtree = parse__gen2(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_declaration(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_declaration(ctx); + } + private static ParseTree parse_declaration(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[65][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(121, "declaration")); + ctx.nonterminal = "declaration"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "declaration", + nonterminal_first.get(121), + nonterminal_rules.get(121) + )); + } + if (rule == 61) { + /* $declaration = $type_e $_gen17 :identifier $_gen18 -> Declaration( type=$0, postfix=$1, name=$2, expression=$3 ) */ + ctx.rule = rules.get(61); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("type", 0); + parameters.put("postfix", 1); + parameters.put("name", 2); + parameters.put("expression", 3); + tree.setAstTransformation(new AstTransformNodeCreator("Declaration", parameters)); + subtree = parse_type_e(ctx); + tree.add(subtree); + subtree = parse__gen17(ctx); + tree.add(subtree); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_IDENTIFIER); + tree.add(next); + subtree = parse__gen18(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "declaration", + current, + nonterminal_first.get(121), + rules.get(61) + )); + } + public ParseTree parse__gen8(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen8(ctx); + } + private static ParseTree parse__gen8(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[66][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(122, "_gen8")); + ctx.nonterminal = "_gen8"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(122).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(122).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 20) { + /* $_gen8 = $sections $_gen8 */ + ctx.rule = rules.get(20); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_sections(ctx); + tree.add(subtree); + subtree = parse__gen8(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_outputs(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_outputs(ctx); + } + private static ParseTree parse_outputs(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[67][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(123, "outputs")); + ctx.nonterminal = "outputs"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "outputs", + nonterminal_first.get(123), + nonterminal_rules.get(123) + )); + } + if (rule == 46) { + /* $outputs = :output :lbrace $_gen13 :rbrace -> Outputs( attributes=$2 ) */ + ctx.rule = rules.get(46); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("attributes", 2); + tree.setAstTransformation(new AstTransformNodeCreator("Outputs", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_OUTPUT); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_LBRACE); + tree.add(next); + subtree = parse__gen13(ctx); + tree.add(subtree); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_RBRACE); + tree.add(next); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "outputs", + current, + nonterminal_first.get(123), + rules.get(46) + )); + } + public ParseTree parse_setter(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_setter(ctx); + } + private static ParseTree parse_setter(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[68][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(124, "setter")); + ctx.nonterminal = "setter"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "setter", + nonterminal_first.get(124), + nonterminal_rules.get(124) + )); + } + if (rule == 62) { + /* $setter = :equal $e -> $1 */ + ctx.rule = rules.get(62); + tree.setAstTransformation(new AstTransformSubstitution(1)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_EQUAL); + tree.add(next); + subtree = parse_e(ctx); + tree.add(subtree); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "setter", + current, + nonterminal_first.get(124), + rules.get(62) + )); + } + public ParseTree parse__gen19(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen19(ctx); + } + private static ParseTree parse__gen19(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[69][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(125, "_gen19")); + ctx.nonterminal = "_gen19"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(125).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(125).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 66) { + /* $_gen19 = $wf_body_element $_gen20 */ + ctx.rule = rules.get(66); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_wf_body_element(ctx); + tree.add(subtree); + subtree = parse__gen20(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_wf_outputs(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_wf_outputs(ctx); + } + private static ParseTree parse_wf_outputs(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[70][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(126, "wf_outputs")); + ctx.nonterminal = "wf_outputs"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "wf_outputs", + nonterminal_first.get(126), + nonterminal_rules.get(126) + )); + } + if (rule == 98) { + /* $wf_outputs = :output :lbrace $_gen27 :rbrace -> WorkflowOutputs( outputs=$2 ) */ + ctx.rule = rules.get(98); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("outputs", 2); + tree.setAstTransformation(new AstTransformNodeCreator("WorkflowOutputs", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_OUTPUT); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_LBRACE); + tree.add(next); + subtree = parse__gen27(ctx); + tree.add(subtree); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_RBRACE); + tree.add(next); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "wf_outputs", + current, + nonterminal_first.get(126), + rules.get(98) + )); + } + public ParseTree parse_import_namespace(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_import_namespace(ctx); + } + private static ParseTree parse_import_namespace(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[71][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(127, "import_namespace")); + ctx.nonterminal = "import_namespace"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "import_namespace", + nonterminal_first.get(127), + nonterminal_rules.get(127) + )); + } + if (rule == 14) { + /* $import_namespace = :as :identifier -> $1 */ + ctx.rule = rules.get(14); + tree.setAstTransformation(new AstTransformSubstitution(1)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_AS); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_IDENTIFIER); + tree.add(next); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "import_namespace", + current, + nonterminal_first.get(127), + rules.get(14) + )); + } + public ParseTree parse__gen35(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen35(ctx); + } + private static ParseTree parse__gen35(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[72][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(128, "_gen35")); + ctx.nonterminal = "_gen35"; + tree.setList(true); + if ( current != null && + !nonterminal_first.get(128).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(128).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 137) { + /* $_gen35 = :comma $object_kv $_gen35 */ + ctx.rule = rules.get(137); + tree.setAstTransformation(new AstTransformSubstitution(0)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_COMMA); + tree.add(next); + tree.setListSeparator(next); + subtree = parse_object_kv(ctx); + tree.add(subtree); + subtree = parse__gen35(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + public ParseTree parse_command(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse_command(ctx); + } + private static ParseTree parse_command(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[73][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(129, "command")); + ctx.nonterminal = "command"; + tree.setList(false); + if (current == null) { + throw new SyntaxError(ctx.error_formatter.unexpectedEof( + "command", + nonterminal_first.get(129), + nonterminal_rules.get(129) + )); + } + if (rule == 33) { + /* $command = :raw_command :raw_cmd_start $_gen9 :raw_cmd_end -> RawCommand( parts=$2 ) */ + ctx.rule = rules.get(33); + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("parts", 2); + tree.setAstTransformation(new AstTransformNodeCreator("RawCommand", parameters)); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_RAW_COMMAND); + tree.add(next); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_RAW_CMD_START); + tree.add(next); + subtree = parse__gen9(ctx); + tree.add(subtree); + next = expect(ctx, WdlTerminalIdentifier.TERMINAL_RAW_CMD_END); + tree.add(next); + return tree; + } + throw new SyntaxError(ctx.error_formatter.unexpectedSymbol( + "command", + current, + nonterminal_first.get(129), + rules.get(33) + )); + } + public ParseTree parse__gen29(List tokens, SyntaxErrorFormatter error_formatter) throws SyntaxError { + ParserContext ctx = new ParserContext(new TokenStream(tokens), error_formatter); + return parse__gen29(ctx); + } + private static ParseTree parse__gen29(ParserContext ctx) throws SyntaxError { + Terminal current = ctx.tokens.current(); + Terminal next; + ParseTree subtree; + int rule = (current != null) ? table[74][current.getId()] : -1; + ParseTree tree = new ParseTree( new NonTerminal(130, "_gen29")); + ctx.nonterminal = "_gen29"; + tree.setList(false); + if ( current != null && + !nonterminal_first.get(130).contains(terminal_map.get(current.getId())) && + nonterminal_follow.get(130).contains(terminal_map.get(current.getId())) ) { + return tree; + } + if (current == null) { + return tree; + } + if (rule == 99) { + /* $_gen29 = $wf_output_wildcard */ + ctx.rule = rules.get(99); + tree.setAstTransformation(new AstTransformSubstitution(0)); + subtree = parse_wf_output_wildcard(ctx); + tree.add(subtree); + return tree; + } + return tree; + } + /* Section: Lexer */ + private Map> regex = null; + private interface LexerOutput {} + private class LexerRegexOutput implements LexerOutput { + public WdlTerminalIdentifier terminal; + public int group; + public Method function; + LexerRegexOutput(WdlTerminalIdentifier terminal, int group, Method function) { + this.terminal = terminal; + this.group = group; + this.function = function; + } + public String toString() { + return String.format("", this.terminal, this.group, this.function); + } + } + private class LexerStackPush implements LexerOutput { + public String mode; + LexerStackPush(String mode) { + this.mode = mode; + } + } + private class LexerAction implements LexerOutput { + public String action; + LexerAction(String action) { + this.action = action; + } + } + private class HermesRegex { + public Pattern pattern; + public List outputs; + HermesRegex(Pattern pattern, List outputs) { + this.pattern = pattern; + this.outputs = outputs; + } + public String toString() { + return String.format("", this.pattern, this.outputs); + } + } + private class LineColumn { + public int line, col; + public LineColumn(int line, int col) { + this.line = line; + this.col = col; + } + public String toString() { + return String.format("", this.line, this.col); + } + } + private class LexerContext { + public String string; + public String resource; + public int line; + public int col; + public Stack stack; + public Object context; + public List terminals; + LexerContext(String string, String resource) { + this.string = string; + this.resource = resource; + this.line = 1; + this.col = 1; + this.stack = new Stack(); + this.stack.push("default"); + this.terminals = new ArrayList(); + } + public void advance(String match) { + LineColumn lc = advance_line_col(match, match.length()); + this.line = lc.line; + this.col = lc.col; + this.string = this.string.substring(match.length()); + } + public LineColumn advance_line_col(String match, int length) { + LineColumn lc = new LineColumn(this.line, this.col); + for (int i = 0; i < length && i < match.length(); i++) { + if (match.charAt(i) == '\n') { + lc.line += 1; + lc.col = 1; + } else { + lc.col += 1; + } + } + return lc; + } + } + private void emit(LexerContext lctx, TerminalIdentifier terminal, String source_string, int line, int col) { + lctx.terminals.add(new Terminal(terminal.id(), terminal.string(), source_string, lctx.resource, line, col)); + } + /** + * The default function that is called on every regex match during lexical analysis. + * By default, this simply calls the emit() function with all of the same parameters. + * This can be overridden in the grammar file to provide a different default action. + * + * @param lctx The current state of the lexical analyzer + * @param terminal The current terminal that was matched + * @param source_string The source code that was matched + * @param line The line where the match happened + * @param col The column where the match happened + * @return void + */ + public void default_action(LexerContext lctx, TerminalIdentifier terminal, String source_string, int line, int col) { + emit(lctx, terminal, source_string, line, col); + } + /* START USER CODE */ + private class WdlContext { + public String wf_or_task = null; +} +public Object init() { + return new WdlContext(); +} +public void workflow(LexerContext ctx, TerminalIdentifier terminal, String source_string, int line, int col) { + ((WdlContext) ctx.context).wf_or_task = "workflow"; + default_action(ctx, terminal, source_string, line, col); +} +public void task(LexerContext ctx, TerminalIdentifier terminal, String source_string, int line, int col) { + ((WdlContext) ctx.context).wf_or_task = "task"; + default_action(ctx, terminal, source_string, line, col); +} +public void output(LexerContext ctx, TerminalIdentifier terminal, String source_string, int line, int col) { + WdlContext user_ctx = (WdlContext) ctx.context; + if (user_ctx.wf_or_task != null && user_ctx.wf_or_task.equals("workflow")) { + ctx.stack.push("wf_output"); + } + default_action(ctx, terminal, source_string, line, col); +} +public void unescape(LexerContext ctx, TerminalIdentifier terminal, String source_string, int line, int col) { + default_action(ctx, terminal, StringEscapeUtils.unescapeJava(source_string.substring(1, source_string.length() - 1)), line, col); +} + /* END USER CODE */ + public void destroy(Object context) { + return; + } + private Method getFunction(String name) throws SyntaxError { + try { + return getClass().getMethod( + name, + LexerContext.class, + TerminalIdentifier.class, + String.class, + int.class, + int.class + ); + } catch (NoSuchMethodException e) { + throw new SyntaxError("No such method: " + name); + } + } + private void lexer_init() throws SyntaxError { + this.regex = new HashMap>(); + this.regex.put("default", Arrays.asList(new HermesRegex[] { + new HermesRegex( + Pattern.compile("\\s+"), + Arrays.asList(new LexerOutput[] { + }) + ), + new HermesRegex( + Pattern.compile("/\\*(.*?)\\*/", Pattern.DOTALL), + Arrays.asList(new LexerOutput[] { + }) + ), + new HermesRegex( + Pattern.compile("#.*"), + Arrays.asList(new LexerOutput[] { + }) + ), + new HermesRegex( + Pattern.compile("task(?![a-zA-Z0-9_])"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_TASK, + 0, + getFunction("task") + ), + }) + ), + new HermesRegex( + Pattern.compile("(call)\\s+"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_CALL, + 1, + getFunction("default_action") + ), + new LexerStackPush("task_fqn"), + }) + ), + new HermesRegex( + Pattern.compile("workflow(?![a-zA-Z0-9_])"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_WORKFLOW, + 0, + getFunction("workflow") + ), + }) + ), + new HermesRegex( + Pattern.compile("import(?![a-zA-Z0-9_])"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_IMPORT, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("input(?![a-zA-Z0-9_])"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_INPUT, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("output(?![a-zA-Z0-9_])"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_OUTPUT, + 0, + getFunction("output") + ), + }) + ), + new HermesRegex( + Pattern.compile("as(?![a-zA-Z0-9_])"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_AS, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("if(?![a-zA-Z0-9_])"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_IF, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("while(?![a-zA-Z0-9_])"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_WHILE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("runtime(?![a-zA-Z0-9_])"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_RUNTIME, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("scatter(?![a-zA-Z0-9_])"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_SCATTER, + 0, + getFunction("default_action") + ), + new LexerStackPush("scatter"), + }) + ), + new HermesRegex( + Pattern.compile("command\\s*(?=<<<)"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + 0, + getFunction("default_action") + ), + new LexerStackPush("raw_command2"), + }) + ), + new HermesRegex( + Pattern.compile("command\\s*(?=\\{)"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_RAW_COMMAND, + 0, + getFunction("default_action") + ), + new LexerStackPush("raw_command"), + }) + ), + new HermesRegex( + Pattern.compile("parameter_meta(?![a-zA-Z0-9_])"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_PARAMETER_META, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("meta(?![a-zA-Z0-9_])"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_META, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("(true|false)(?![a-zA-Z0-9_])"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("(object)\\s*(\\{)"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_OBJECT, + 0, + getFunction("default_action") + ), + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_LBRACE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("(Array|Map|Object|Boolean|Int|Float|Uri|File|String)(?![a-zA-Z0-9_])(?![a-zA-Z0-9_])"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_TYPE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("[a-zA-Z]([a-zA-Z0-9_])*"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\\"(?>[^\\\\\\\"\\n]|\\\\[\\\"\\'nrbtfav\\\\?]|\\\\[0-7]{1,3}|\\\\x[0-9a-fA-F]+|\\\\[uU]([0-9a-fA-F]{4})([0-9a-fA-F]{4})?)*\\\""), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_STRING, + 0, + getFunction("unescape") + ), + }) + ), + new HermesRegex( + Pattern.compile("'(?>[^\\\\\\'\\n]|\\\\[\\\"\\'nrbtfav\\\\?]|\\\\[0-7]{1,3}|\\\\x[0-9a-fA-F]+|\\\\[uU]([0-9a-fA-F]{4})([0-9a-fA-F]{4})?)*'"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_STRING, + 0, + getFunction("unescape") + ), + }) + ), + new HermesRegex( + Pattern.compile(":"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_COLON, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile(","), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_COMMA, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("=="), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_DOUBLE_EQUAL, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\|\\|"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_DOUBLE_PIPE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\&\\&"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_DOUBLE_AMPERSAND, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("!="), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_NOT_EQUAL, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("="), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_EQUAL, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\."), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_DOT, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\{"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_LBRACE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\}"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_RBRACE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\("), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_LPAREN, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\)"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_RPAREN, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\["), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_LSQUARE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\]"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_RSQUARE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\+"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_PLUS, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\*"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_ASTERISK, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("-"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_DASH, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("/"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_SLASH, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("%"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_PERCENT, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("<="), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_LTEQ, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("<"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_LT, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile(">="), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_GTEQ, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile(">"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_GT, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("!"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_NOT, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\?"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_QMARK, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("-?[0-9]+\\.[0-9]+"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_FLOAT, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("[0-9]+"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_INTEGER, + 0, + getFunction("default_action") + ), + }) + ), + })); + this.regex.put("wf_output", Arrays.asList(new HermesRegex[] { + new HermesRegex( + Pattern.compile("\\s+"), + Arrays.asList(new LexerOutput[] { + }) + ), + new HermesRegex( + Pattern.compile("\\{"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_LBRACE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\}"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_RBRACE, + 0, + getFunction("default_action") + ), + new LexerAction("pop"), + }) + ), + new HermesRegex( + Pattern.compile(","), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_COMMA, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\."), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_DOT, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\*"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_ASTERISK, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("[a-zA-Z]([a-zA-Z0-9_])*(\\.[a-zA-Z]([a-zA-Z0-9_])*)*"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_FQN, + 0, + getFunction("default_action") + ), + }) + ), + })); + this.regex.put("task_fqn", Arrays.asList(new HermesRegex[] { + new HermesRegex( + Pattern.compile("\\s+"), + Arrays.asList(new LexerOutput[] { + }) + ), + new HermesRegex( + Pattern.compile("[a-zA-Z]([a-zA-Z0-9_])*(\\.[a-zA-Z]([a-zA-Z0-9_])*)*"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_FQN, + 0, + getFunction("default_action") + ), + new LexerAction("pop"), + }) + ), + })); + this.regex.put("scatter", Arrays.asList(new HermesRegex[] { + new HermesRegex( + Pattern.compile("\\s+"), + Arrays.asList(new LexerOutput[] { + }) + ), + new HermesRegex( + Pattern.compile("\\)"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_RPAREN, + 0, + getFunction("default_action") + ), + new LexerAction("pop"), + }) + ), + new HermesRegex( + Pattern.compile("\\("), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_LPAREN, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\."), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_DOT, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\["), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_LSQUARE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\]"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_RSQUARE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("in(?![a-zA-Z0-9_])"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_IN, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("[a-zA-Z]([a-zA-Z0-9_])*"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + 0, + getFunction("default_action") + ), + }) + ), + })); + this.regex.put("raw_command", Arrays.asList(new HermesRegex[] { + new HermesRegex( + Pattern.compile("\\{"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_RAW_CMD_START, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\}"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_RAW_CMD_END, + 0, + getFunction("default_action") + ), + new LexerAction("pop"), + }) + ), + new HermesRegex( + Pattern.compile("\\$\\{"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_CMD_PARAM_START, + 0, + getFunction("default_action") + ), + new LexerStackPush("cmd_param"), + }) + ), + new HermesRegex( + Pattern.compile("(.*?)(?=\\$\\{|\\})", Pattern.DOTALL), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_CMD_PART, + 0, + getFunction("default_action") + ), + }) + ), + })); + this.regex.put("raw_command2", Arrays.asList(new HermesRegex[] { + new HermesRegex( + Pattern.compile("<<<"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_RAW_CMD_START, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile(">>>"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_RAW_CMD_END, + 0, + getFunction("default_action") + ), + new LexerAction("pop"), + }) + ), + new HermesRegex( + Pattern.compile("\\$\\{"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_CMD_PARAM_START, + 0, + getFunction("default_action") + ), + new LexerStackPush("cmd_param"), + }) + ), + new HermesRegex( + Pattern.compile("(.*?)(?=\\$\\{|>>>)", Pattern.DOTALL), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_CMD_PART, + 0, + getFunction("default_action") + ), + }) + ), + })); + this.regex.put("cmd_param", Arrays.asList(new HermesRegex[] { + new HermesRegex( + Pattern.compile("\\s+"), + Arrays.asList(new LexerOutput[] { + }) + ), + new HermesRegex( + Pattern.compile("\\}"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_CMD_PARAM_END, + 0, + getFunction("default_action") + ), + new LexerAction("pop"), + }) + ), + new HermesRegex( + Pattern.compile("\\["), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_LSQUARE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\]"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_RSQUARE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("="), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_EQUAL, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\+"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_PLUS, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\*"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_ASTERISK, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("[0-9]+"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_INTEGER, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("[a-zA-Z]([a-zA-Z0-9_])*(?=\\s*=)"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_CMD_ATTR_HINT, + -1, + getFunction("default_action") + ), + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("(true|false)(?![a-zA-Z0-9_])"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_BOOLEAN, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("(Array|Map|Object|Boolean|Int|Float|Uri|File|String)(?![a-zA-Z0-9_])(?![a-zA-Z0-9_])"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_TYPE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("[a-zA-Z]([a-zA-Z0-9_])*"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_IDENTIFIER, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile(":"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_COLON, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile(","), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_COMMA, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\."), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_DOT, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("=="), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_DOUBLE_EQUAL, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\|\\|"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_DOUBLE_PIPE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\&\\&"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_DOUBLE_AMPERSAND, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("!="), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_NOT_EQUAL, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("="), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_EQUAL, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\."), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_DOT, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\{"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_LBRACE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\("), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_LPAREN, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\)"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_RPAREN, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\["), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_LSQUARE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\]"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_RSQUARE, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\+"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_PLUS, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\*"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_ASTERISK, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("-"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_DASH, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("/"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_SLASH, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("%"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_PERCENT, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("<="), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_LTEQ, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("<"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_LT, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile(">="), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_GTEQ, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile(">"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_GT, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("!"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_NOT, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("\\\"(?>[^\\\\\\\"\\n]|\\\\[\\\"\\'nrbtfav\\\\?]|\\\\[0-7]{1,3}|\\\\x[0-9a-fA-F]+|\\\\[uU]([0-9a-fA-F]{4})([0-9a-fA-F]{4})?)*\\\""), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_STRING, + 0, + getFunction("unescape") + ), + }) + ), + new HermesRegex( + Pattern.compile("'(?>[^\\\\\\'\\n]|\\\\[\\\"\\'nrbtfav\\\\?]|\\\\[0-7]{1,3}|\\\\x[0-9a-fA-F]+|\\\\[uU]([0-9a-fA-F]{4})([0-9a-fA-F]{4})?)*'"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_STRING, + 0, + getFunction("unescape") + ), + }) + ), + new HermesRegex( + Pattern.compile("-?[0-9]+\\.[0-9]+"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_FLOAT, + 0, + getFunction("default_action") + ), + }) + ), + new HermesRegex( + Pattern.compile("[0-9]+"), + Arrays.asList(new LexerOutput[] { + new LexerRegexOutput( + WdlTerminalIdentifier.TERMINAL_INTEGER, + 0, + getFunction("default_action") + ), + }) + ), + })); + } + private void unrecognized_token(String string, int line, int col) throws SyntaxError { + String[] a = string.split("\n"); + String bad_line = string.split("\n")[line-1]; + StringBuffer spaces = new StringBuffer(); + for (int i = 0; i < col-1; i++) { + spaces.append(' '); + } + String message = String.format( + "Unrecognized token on line %d, column %d:\n\n%s\n%s^", + line, col, bad_line, spaces + ); + throw new SyntaxError(message); + } + private int next(LexerContext lctx) throws SyntaxError { + String mode = lctx.stack.peek(); + for (int i = 0; i < this.regex.get(mode).size(); i++) { + HermesRegex regex = this.regex.get(mode).get(i); + Matcher matcher = regex.pattern.matcher(lctx.string); + if (matcher.lookingAt()) { + for (LexerOutput output : regex.outputs) { + if (output instanceof LexerStackPush) { + lctx.stack.push(((LexerStackPush) output).mode); + } else if (output instanceof LexerAction) { + LexerAction action = (LexerAction) output; + if (!action.action.equals("pop")) { + throw new SyntaxError("Invalid action"); + } + if (action.action.equals("pop")) { + if (lctx.stack.empty()) { + throw new SyntaxError("Stack empty, cannot pop"); + } + lctx.stack.pop(); + } + } else if (output instanceof LexerRegexOutput) { + LexerRegexOutput regex_output = (LexerRegexOutput) output; + int group_line = lctx.line; + int group_col = lctx.col; + if (regex_output.group > 0) { + LineColumn lc = lctx.advance_line_col(matcher.group(0), matcher.start(regex_output.group)); + group_line = lc.line; + group_col = lc.col; + } + try { + String source_string = (regex_output.group >= 0) ? matcher.group(regex_output.group) : ""; + regex_output.function.invoke( + this, + lctx, + regex_output.terminal, + source_string, + group_line, + group_col + ); + } catch (Exception e) { + e.printStackTrace(); + throw new SyntaxError("Invalid method: " + regex_output.function); + } + } + } + lctx.advance(matcher.group(0)); + return matcher.group(0).length(); + } + } + return 0; + } + /** + * Lexically analyze WDL source code, return a sequence of tokens. Output of this + * method should be used to construct a TerminalStream and then pass that to parse() + * + * @param string The WDL source code to analyze + * @param resource A descriptor of where this code came from (usually a file path) + * @return List of Terminal objects. + * @throws SyntaxError If part of the source code could not lexically analyzed + */ + public List lex(String string, String resource) throws SyntaxError { + LexerContext lctx = new LexerContext(string, resource); + Object context = this.init(); + lctx.context = context; + String string_copy = new String(string); + if (this.regex == null) { + lexer_init(); + } + while (lctx.string.length() > 0) { + int match_length = this.next(lctx); + if (match_length == 0) { + this.unrecognized_token(string_copy, lctx.line, lctx.col); + } + } + this.destroy(context); + return lctx.terminals; + } + /* Section: Main */ +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/AstTools.scala b/scala/wdl4s/src/main/scala/wdl4s/AstTools.scala new file mode 100644 index 0000000..d6d172f --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/AstTools.scala @@ -0,0 +1,220 @@ +package wdl4s + +import java.io.File + +import wdl4s.types._ +import wdl4s.values._ +import wdl4s.parser.WdlParser +import wdl4s.parser.WdlParser._ +import wdl4s.util.FileUtil._ + +import scala.collection.JavaConverters._ + +object AstTools { + implicit class EnhancedAstNode(val astNode: AstNode) extends AnyVal { + def findAsts(name: String): Seq[Ast] = AstTools.findAsts(astNode, name) + def findAstsWithTrail(name: String, trail: Seq[AstNode] = Seq.empty): Map[Ast, Seq[AstNode]] = { + AstTools.findAstsWithTrail(astNode, name, trail) + } + def findTerminals(): Seq[Terminal] = AstTools.findTerminals(astNode) + def findTopLevelMemberAccesses(): Iterable[Ast] = AstTools.findTopLevelMemberAccesses(astNode) + def sourceString: String = astNode.asInstanceOf[Terminal].getSourceString + def astListAsVector(): Seq[AstNode] = astNode.asInstanceOf[AstList].asScala.toVector + def wdlType(wdlSyntaxErrorFormatter: WdlSyntaxErrorFormatter): WdlType = { + astNode match { + case t: Terminal => + t.getSourceString match { + case WdlFileType.toWdlString => WdlFileType + case WdlStringType.toWdlString => WdlStringType + case WdlIntegerType.toWdlString => WdlIntegerType + case WdlFloatType.toWdlString => WdlFloatType + case WdlBooleanType.toWdlString => WdlBooleanType + case WdlObjectType.toWdlString => WdlObjectType + case "Array" => throw new SyntaxError(wdlSyntaxErrorFormatter.arrayMustHaveATypeParameter(t)) + } + case a: Ast => + val subtypes = a.getAttribute("subtype").astListAsVector + val typeTerminal = a.getAttribute("name").asInstanceOf[Terminal] + a.getAttribute("name").sourceString match { + case "Array" => + if (subtypes.size != 1) throw new SyntaxError(wdlSyntaxErrorFormatter.arrayMustHaveOnlyOneTypeParameter(typeTerminal)) + val member = subtypes.head.wdlType(wdlSyntaxErrorFormatter) + WdlArrayType(member) + case "Map" => + if (subtypes.size != 2) throw new SyntaxError(wdlSyntaxErrorFormatter.mapMustHaveExactlyTwoTypeParameters(typeTerminal)) + val keyType = subtypes(0).wdlType(wdlSyntaxErrorFormatter) + val valueType = subtypes(1).wdlType(wdlSyntaxErrorFormatter) + WdlMapType(keyType, valueType) + } + case null => WdlStringType + case _ => throw new UnsupportedOperationException("Implement this later for compound types") + } + } + + def wdlValue(wdlType: WdlType, wdlSyntaxErrorFormatter: WdlSyntaxErrorFormatter): WdlValue = { + + def astToMap(ast: Ast) = { + val mapType = wdlType.asInstanceOf[WdlMapType] + val elements = ast.getAttribute("map").asInstanceOf[AstList].asScala.toVector.map({ kvnode => + val k = kvnode.asInstanceOf[Ast].getAttribute("key").wdlValue(mapType.keyType, wdlSyntaxErrorFormatter) + val v = kvnode.asInstanceOf[Ast].getAttribute("value").wdlValue(mapType.valueType, wdlSyntaxErrorFormatter) + k -> v + }).toMap + + WdlMap(mapType, elements) + } + + def astToObject(ast: Ast) = { + val elements = ast.getAttribute("map").asInstanceOf[AstList].asScala.toVector.map({ kvnode => + val k = kvnode.asInstanceOf[Ast].getAttribute("key").sourceString + val v = kvnode.asInstanceOf[Ast].getAttribute("value").wdlValue(WdlStringType, wdlSyntaxErrorFormatter) + k -> v + }).toMap + + WdlObject(elements) + } + + astNode match { + case t: Terminal if t.getTerminalStr == "string" && wdlType == WdlStringType => WdlString(t.getSourceString) + case t: Terminal if t.getTerminalStr == "string" && wdlType == WdlFileType => WdlFile(t.getSourceString) + case t: Terminal if t.getTerminalStr == "integer" && wdlType == WdlIntegerType => WdlInteger(t.getSourceString.toInt) + case t: Terminal if t.getTerminalStr == "float" && wdlType == WdlFloatType => WdlFloat(t.getSourceString.toDouble) + case t: Terminal if t.getTerminalStr == "boolean" && wdlType == WdlBooleanType => t.getSourceString.toLowerCase match { + case "true" => WdlBoolean.True + case "false" => WdlBoolean.False + } + // TODO: The below cases, ArrayLiteral and MapLiteral, ObjectLiteral are brittle. They recursively call this wdlValue(). + // However, those recursive calls might contain full-on expressions instead of just other literals. This + // whole thing ought to be part of the regular expression evaluator, though I imagine that's non-trivial. + case a: Ast if a.getName == "ArrayLiteral" && wdlType.isInstanceOf[WdlArrayType] => + val arrType = wdlType.asInstanceOf[WdlArrayType] + val elements = a.getAttribute("values").astListAsVector map {node => node.wdlValue(arrType.memberType, wdlSyntaxErrorFormatter)} + WdlArray(arrType, elements) + case a: Ast if a.getName == "MapLiteral" && wdlType.isInstanceOf[WdlMapType] => astToMap(a) + case a: Ast if a.getName == "ObjectLiteral" && wdlType == WdlObjectType => astToObject(a) + case _ => throw new SyntaxError(s"Could not convert AST to a $wdlType (${Option(astNode).getOrElse("No AST").toString})") + } + } + } + + implicit class EnhancedAstSeq(val astSeq: Seq[Ast]) extends AnyVal { + def duplicatesByName: Seq[Ast] = { + astSeq.groupBy(_.getAttribute("name").sourceString).collect({case (_ ,v) if v.size > 1 => v.head}).toVector + } + } + + object AstNodeName { + val Task = "Task" + val Workflow = "Workflow" + val Command = "RawCommand" + val Output = "Output" + val CommandParameter = "CommandParameter" + val Call = "Call" + val IOMapping = "IOMapping" + val Inputs = "Inputs" + val MemberAccess = "MemberAccess" + val Runtime = "Runtime" + val Declaration = "Declaration" + val WorkflowOutput = "WorkflowOutput" + val Scatter = "Scatter" + } + + def getAst(wdlSource: WdlSource, resource: String): Ast = { + val parser = new WdlParser() + val tokens = parser.lex(wdlSource, resource) + val terminalMap = (tokens.asScala.toVector map {(_, wdlSource)}).toMap + val syntaxErrorFormatter = new WdlSyntaxErrorFormatter(terminalMap) + parser.parse(tokens, syntaxErrorFormatter).toAst.asInstanceOf[Ast] + } + + /** + * Given a WDL file, this will simply parse it and return the syntax tree + * @param wdlFile The file to parse + * @return an Abstract Syntax Tree (WdlParser.Ast) representing the structure of the code + * @throws WdlParser.SyntaxError if there was a problem parsing the source code + */ + def getAst(wdlFile: File): Ast = getAst(wdlFile.slurp, wdlFile.getName) + + def findAsts(ast: AstNode, name: String): Seq[Ast] = { + ast match { + case x: Ast => + val thisAst = if (x.getName.equals(name)) Seq(x) else Seq.empty[Ast] + x.getAttributes.values.asScala.flatMap(findAsts(_, name)).toSeq ++ thisAst + case x: AstList => x.asScala.toVector.flatMap(findAsts(_, name)).toSeq + case x: Terminal => Seq.empty[Ast] + case _ => Seq.empty[Ast] + } + } + + def findAstsWithTrail(ast: AstNode, name: String, trail: Seq[AstNode] = Seq.empty): Map[Ast, Seq[AstNode]] = { + ast match { + case x: Ast => + val thisAst = if (x.getName.equals(name)) Map(x -> trail) else Map.empty[Ast, Seq[AstNode]] + combine(x.getAttributes.values.asScala.flatMap{_.findAstsWithTrail(name, trail :+ x)}.toMap, thisAst) + case x: AstList => x.asScala.toVector.flatMap{_.findAstsWithTrail(name, trail :+ x)}.toMap + case x: Terminal => Map.empty[Ast, Seq[AstNode]] + case _ => Map.empty[Ast, Seq[AstNode]] + } + } + + def findTerminals(ast: AstNode): Seq[Terminal] = { + ast match { + case x: Ast => x.getAttributes.values.asScala.flatMap(findTerminals).toSeq + case x: AstList => x.asScala.toVector.flatMap(findTerminals).toSeq + case x: Terminal => Seq(x) + case _ => Seq.empty[Terminal] + } + } + + /* + All MemberAccess ASTs that are not contained in other MemberAccess ASTs + + The reason this returns a collection would be expressions such as "a.b.c + a.b.d", each one of those + would have its own MemberAccess - "a.b.c" and "a.b.d" + */ + def findTopLevelMemberAccesses(expr: AstNode): Iterable[Ast] = expr.findAstsWithTrail("MemberAccess").filterNot { + case(k, v) => v exists { + case a: Ast => a.getName == "MemberAccess" + case _ => false + } + }.keys + + /** + * Given a Call AST, this will validate that there is 0 or 1 'input' section with non-empty + * key/value pairs (if the input section is specified). This will then return a Seq of + * IOMapping(key= value=) + * + * @param ast + * @param wdlSyntaxErrorFormatter + * @return Seq[Ast] where the AST is a IOMapping(key= value=) + */ + def callInputSectionIOMappings(ast: Ast, wdlSyntaxErrorFormatter: WdlSyntaxErrorFormatter): Seq[Ast] = { + val callTaskName = ast.getAttribute("task").asInstanceOf[Terminal] + + /* Filter out all empty 'input' sections first */ + val callInputSections = ast.findAsts(AstNodeName.Inputs).map {inputSectionAst => + inputSectionAst.getAttribute("map").findAsts(AstNodeName.IOMapping) match { + case kvPairs: Seq[Ast] if kvPairs.isEmpty => throw new SyntaxError(wdlSyntaxErrorFormatter.emptyInputSection(callTaskName)) + case _ => inputSectionAst + } + } + + /* Then, make sure there is at most one 'input' section defined, then return the a Seq of IOMapping(key=, value=) ASTs*/ + callInputSections match { + case asts: Seq[Ast] if asts.size == 1 => asts.head.getAttribute("map").findAsts(AstNodeName.IOMapping) + case asts: Seq[Ast] if asts.isEmpty => Seq.empty[Ast] + case asts: Seq[Ast] => + /* Uses of .head here are assumed by the above code that ensures that there are no empty maps */ + val secondInputSectionIOMappings = asts(1).getAttribute("map").astListAsVector + val firstKeyTerminal = secondInputSectionIOMappings.head.asInstanceOf[Ast].getAttribute("key").asInstanceOf[Terminal] + throw new SyntaxError(wdlSyntaxErrorFormatter.multipleInputStatementsOnCall(firstKeyTerminal)) + } + } + + def terminalMap(ast: Ast, source: WdlSource) = (findTerminals(ast) map {(_, source)}).toMap + + private def combine[T, U](map1: Map[T, Seq[U]], map2: Map[T, Seq[U]]): Map[T, Seq[U]] = { + map1 ++ map2.map{ case (k,v) => k -> (v ++ map1.getOrElse(k, Seq.empty)) } + } +} + diff --git a/scala/wdl4s/src/main/scala/wdl4s/Call.scala b/scala/wdl4s/src/main/scala/wdl4s/Call.scala new file mode 100644 index 0000000..7922094 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/Call.scala @@ -0,0 +1,107 @@ +package wdl4s + +import wdl4s.AstTools.EnhancedAstNode +import wdl4s.expression.WdlFunctions +import wdl4s.values.WdlValue +import wdl4s.parser.WdlParser.{Ast, SyntaxError, Terminal} +import scala.util.Try +import scala.language.postfixOps + +object Call { + def apply(ast: Ast, + namespaces: Seq[WdlNamespace], + tasks: Seq[Task], + wdlSyntaxErrorFormatter: WdlSyntaxErrorFormatter, + parent: Option[Scope]): Call = { + val alias: Option[String] = ast.getAttribute("alias") match { + case x: Terminal => Option(x.getSourceString) + case _ => None + } + + val taskName = ast.getAttribute("task").sourceString + + val task = WdlNamespace.findTask(taskName, namespaces, tasks) getOrElse { + throw new SyntaxError(wdlSyntaxErrorFormatter.callReferencesBadTaskName(ast, taskName)) + } + + val callInputSectionMappings = processCallInput(ast, wdlSyntaxErrorFormatter) + + callInputSectionMappings foreach { case (taskParamName, expression) => + task.inputs find { taskInput => taskInput.name == taskParamName } getOrElse { + /* + FIXME-ish + It took me a while to figure out why this next part is necessary and it's kind of hokey. + All the syntax error formatting requires ASTs and this is a way to get the input's AST back. + Perhaps an intermediary object that contains the AST as well and then the normal Map in the Call? + + FIXME: It'd be good to break this whole thing into smaller chunks + */ + val callInput = AstTools.callInputSectionIOMappings(ast, wdlSyntaxErrorFormatter) find { + _.getAttribute("key").sourceString == taskParamName + } getOrElse { + throw new SyntaxError(s"Can't find call input: $taskParamName") + } + throw new SyntaxError(wdlSyntaxErrorFormatter.callReferencesBadTaskInput(callInput, task.ast)) + } + } + + val prerequisiteCallNames = callInputSectionMappings.values flatMap { _.prerequisiteCallNames } toSet + + new Call(alias, taskName, task, prerequisiteCallNames, callInputSectionMappings, parent) + } + + private def processCallInput(ast: Ast, + wdlSyntaxErrorFormatter: WdlSyntaxErrorFormatter): Map[String, WdlExpression] = { + AstTools.callInputSectionIOMappings(ast, wdlSyntaxErrorFormatter) map { a => + val key = a.getAttribute("key").sourceString + val expression = new WdlExpression(a.getAttribute("value")) + (key, expression) + } toMap + } +} + +/** + * Represents a `call` block in a WDL workflow. Calls wrap tasks + * and optionally provide a subset of the inputs for the task (as inputMappings member). + * All remaining inputs to the task that are not declared in the `input` section + * of the `call` block are called unsatisfiedInputs + * + * @param alias The alias for this call. If two calls in a workflow use the same task + * then one of them needs to be aliased to a different name + * @param task The task that this `call` will invoke + * @param inputMappings A map of task-local input names and corresponding expression for the + * value of those inputs + */ +case class Call(alias: Option[String], + taskFqn: FullyQualifiedName, + task: Task, + prerequisiteCallNames: Set[LocallyQualifiedName], + inputMappings: Map[String, WdlExpression], + parent: Option[Scope]) extends Scope { + val unqualifiedName: String = alias getOrElse taskFqn + + override lazy val prerequisiteScopes: Set[Scope] = { + val parent = this.parent.get // FIXME: In a world where Call knows it has a parent this wouldn't be icky + val parentPrereq = if (parent == this.rootWorkflow) Nil else Set(parent) + prerequisiteCalls ++ parentPrereq + } + + /** + * Returns a Seq[WorkflowInput] representing the inputs to the call that are + * needed before its command can be constructed. This excludes inputs that + * are satisfied via the 'input' section of the Call definition. + */ + def unsatisfiedInputs: Seq[WorkflowInput] = for { + i <- task.inputs if !inputMappings.contains(i.name) + } yield WorkflowInput(s"$fullyQualifiedName.${i.name}", i.wdlType, i.postfixQuantifier) + + override def toString: String = s"[Call name=$unqualifiedName, task=$task]" + + /** + * Instantiate the abstract command line corresponding to this call using the specified inputs. + */ + def instantiateCommandLine(inputs: CallInputs, functions: WdlFunctions[WdlValue]): Try[String] = + task.instantiateCommand(inputs, functions) + + override def rootWorkflow: Workflow = parent map { _.rootWorkflow } getOrElse { throw new IllegalStateException("Call not in workflow") } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/Declaration.scala b/scala/wdl4s/src/main/scala/wdl4s/Declaration.scala new file mode 100644 index 0000000..2a98091 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/Declaration.scala @@ -0,0 +1,49 @@ +package wdl4s + +import wdl4s.AstTools.EnhancedAstNode +import wdl4s.types.WdlType +import wdl4s.parser.WdlParser.{Ast, AstNode} + +object Declaration { + def apply(ast: Ast, scopeFqn: FullyQualifiedName, wdlSyntaxErrorFormatter: WdlSyntaxErrorFormatter): Declaration = { + Declaration( + scopeFqn, + ast.getAttribute("type").wdlType(wdlSyntaxErrorFormatter), + Option(ast.getAttribute("postfix")).map(_.sourceString), + ast.getAttribute("name").sourceString, + ast.getAttribute("expression") match { + case a: AstNode => Some(WdlExpression(a)) + case _ => None + } + ) + } +} + +/** + * Represents a declaration which can show up in a workflow or a task context. For example + * + * task test { + * File test_file + * command { ... } + * } + * + * workflow wf { + * String wf_string = "abc" + * call test { input: s=wf_string } + * } + * + * Both the definition of test_file and wf_string are declarations + */ +case class Declaration(scopeFqn: FullyQualifiedName, wdlType: WdlType, postfixQuantifier: Option[String], name: String, expression: Option[WdlExpression]) { + def asWorkflowInput: Option[WorkflowInput] = expression match { + case Some(expr) => None + case None => Some(WorkflowInput(fullyQualifiedName, wdlType, postfixQuantifier)) + } + + def asTaskInput: Option[TaskInput] = expression match { + case Some(expr) => None + case None => Some(TaskInput(name, wdlType, postfixQuantifier)) + } + + def fullyQualifiedName: FullyQualifiedName = s"$scopeFqn.$name" +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/Executable.scala b/scala/wdl4s/src/main/scala/wdl4s/Executable.scala new file mode 100644 index 0000000..6301268 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/Executable.scala @@ -0,0 +1,3 @@ +package wdl4s + +trait Executable diff --git a/scala/wdl4s/src/main/scala/wdl4s/Import.scala b/scala/wdl4s/src/main/scala/wdl4s/Import.scala new file mode 100644 index 0000000..20fece6 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/Import.scala @@ -0,0 +1,17 @@ +package wdl4s + +import wdl4s.AstTools.EnhancedAstNode +import wdl4s.parser.WdlParser.{Ast, AstNode} + +object Import { + def apply(astNode: AstNode): Import = { + val uri = astNode.asInstanceOf[Ast].getAttribute("uri").sourceString + val importNamespace = Option(astNode.asInstanceOf[Ast].getAttribute("namespace")) + Import(uri, importNamespace) + } +} + +// FIXME: I dislike dragging the AST along but it's necessary for "compile" time error syntax highlighting, argh +case class Import(uri: String, namespaceAst: Option[AstNode]) { + val namespace: Option[String] = namespaceAst map { _.sourceString } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/MemberAccess.scala b/scala/wdl4s/src/main/scala/wdl4s/MemberAccess.scala new file mode 100644 index 0000000..c4cc3ce --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/MemberAccess.scala @@ -0,0 +1,41 @@ +package wdl4s + +import wdl4s.parser.WdlParser.{Terminal, Ast} +import wdl4s.AstTools.EnhancedAstNode + +/** + * Represents a member-access construct holding the left and right hand sides, e.g. + * "foo.blah" would deconstruct into "foo" and "blah". + * + * The right-hand side of a member-access AST should always be interpreted as a String + * Sometimes, the left-hand side is itself a MemberAccess AST, like in the expression + * for `call t1` below. In that example, "ns.ns2.task_name" would be the left hand side + * In the `call t2` example, "alias" would be the left hand side + * + * import "test.wdl" as ns + * workflow w { + * call ns.ns2.task_name + * call t1 { + * input: x=ns.ns2.task_name.output + * } + * + * call ns.ns2.task_name as alias + * call t2 { + * input: y=alias.output + * } + *} + */ +case class MemberAccess(lhs: String, rhs: String) + +object MemberAccess { + def apply(ast: Ast): MemberAccess = { + val rhs = ast.getAttribute("rhs").sourceString + + val lhs = ast.getAttribute("lhs") match { + case a: Ast => WdlExpression.toString(a) + case terminal: Terminal => terminal.sourceString + } + + MemberAccess(lhs, rhs) + } +} \ No newline at end of file diff --git a/scala/wdl4s/src/main/scala/wdl4s/RuntimeAttributes.scala b/scala/wdl4s/src/main/scala/wdl4s/RuntimeAttributes.scala new file mode 100644 index 0000000..989144f --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/RuntimeAttributes.scala @@ -0,0 +1,76 @@ +package wdl4s + +import wdl4s.AstTools.{AstNodeName, EnhancedAstNode} +import wdl4s.expression.NoFunctions +import wdl4s.values._ +import wdl4s.parser.WdlParser.{Ast, AstList} +import wdl4s.parser.MemorySize + +import scala.collection.JavaConverters._ +import scala.language.postfixOps +import scalaz.Scalaz._ +import scalaz._ + +case class RuntimeAttributes(attrs: Map[String, String]) + +object RuntimeAttributes { + def apply(ast: Ast): RuntimeAttributes = { + val asts = ast.findAsts(AstNodeName.Runtime) + if (asts.size > 1) throw new UnsupportedOperationException("Only one runtime block may be defined per task") + val astList = asts.headOption map { _.getAttribute("map").asInstanceOf[AstList] } + val attrMap = astList map processRuntimeAttributes getOrElse Map.empty[String, String] + attrMap.get("memory") foreach { validateMemory } + + RuntimeAttributes(attrMap) + } + + /** + * Checks that a provided memory value is valid, if not throws an IllegalArgumentException + */ + private def validateMemory(value: String): Unit = { + validateMemStringInGb (value) match { + case Failure(f) => throw new IllegalArgumentException("Invalid memory value specified:\n" + f.toList.mkString("\n")) + case Success(_) => () + } + } + + private def validateMemStringInGb(mem: String): ValidationNel[String, Double] = { + val memoryPattern = """(\d+)\s*(\w+)""".r + mem match { + case memoryPattern(amountString, unitString) => + val amount = amountString.parseLong leftMap { _.getMessage } toValidationNel + val unit = validateMemoryUnit(unitString) + (amount |@| unit) { (a, u) => MemorySize.GB.fromBytes(u.toBytes(a)) } + case _ => s"$mem should be of the form X Unit where X is a number, e.g. 8 GB".failureNel + } + } + + private def validateMemoryUnit(unit: String): ValidationNel[String, MemorySize] = { + MemorySize.values find { _.suffixes.contains(unit) } match { + case Some(s) => s.successNel + case None => s"$unit is an invalid memory unit".failureNel + } + } + + private def processRuntimeAttributes(astList: AstList): Map[String, String] = { + astList.asScala.toVector map { a => processRuntimeAttribute(a.asInstanceOf[Ast]) } toMap + } + + private def processRuntimeAttribute(ast: Ast): (String, String) = { + val key = ast.getAttribute("key").sourceString + val seq = Option(ast.getAttribute("value")) map { valAttr => + WdlExpression.evaluate(valAttr, NoLookup, NoFunctions) match { + case scala.util.Success(wdlPrimitive: WdlPrimitive) => Seq(wdlPrimitive.valueString) + case scala.util.Success(wdlArray: WdlArray) => wdlArray.value.map(_.valueString) + case scala.util.Success(wdlValue: WdlValue) => + throw new IllegalArgumentException( + s"WdlType not supported for $key, ${wdlValue.wdlType}: ${wdlValue.valueString}") + case null => + throw new IllegalArgumentException(s"value was null: $key}") + case scala.util.Failure(f) => throw f + } + } flatMap { _.headOption } getOrElse "" + + (key, seq) + } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/Scatter.scala b/scala/wdl4s/src/main/scala/wdl4s/Scatter.scala new file mode 100644 index 0000000..1e3e817 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/Scatter.scala @@ -0,0 +1,31 @@ +package wdl4s + +import wdl4s.parser.WdlParser.{Ast, Terminal} + +object Scatter { + val FQNIdentifier = "$scatter" + + /** + * @param index Index of the scatter block. The index is computed during tree generation to reflect wdl scatter blocks structure. + */ + def apply(ast: Ast, index: Int, parent: Option[Scope]): Scatter = { + val item = ast.getAttribute("item").asInstanceOf[Terminal].getSourceString + new Scatter(index, item, WdlExpression(ast.getAttribute("collection")), parent) + } +} + +/** + * Scatter class. + * @param index Index of the scatter block. The index is computed during tree generation to reflect wdl scatter blocks structure. + * @param item Item which this block is scattering over + * @param collection Wdl Expression corresponding to the collection this scatter is looping through + * @param parent Parent of this scatter + */ +case class Scatter(index: Int, item: String, collection: WdlExpression, parent: Option[Scope]) extends Scope { + val unqualifiedName = s"${Scatter.FQNIdentifier}_$index" + override def appearsInFqn = false + override val prerequisiteCallNames = collection.prerequisiteCallNames + override lazy val prerequisiteScopes = prerequisiteCalls + + override def rootWorkflow: Workflow = parent map { _.rootWorkflow } getOrElse { throw new IllegalStateException("Call not in workflow") } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/Scope.scala b/scala/wdl4s/src/main/scala/wdl4s/Scope.scala new file mode 100644 index 0000000..4d13c80 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/Scope.scala @@ -0,0 +1,124 @@ +package wdl4s + +import scala.annotation.tailrec +import scala.language.postfixOps + +object Scope { + /** + * Collect Calls from a Seq of Scopes. + * @param scopes scopes to loop through + * @return Scopes instances that are Calls + */ + def collectCalls(scopes: Seq[Scope]): Seq[Call] = scopes collect { case s: Call => s } + + /** + * Collect all Calls from the given scope. + * @param scopes scope to gather Calls from + * @param calls for recursivity. Should be passed Nil in most cases. + * @return all Calls inside the scope + */ + @tailrec + def collectAllCalls(scopes: Seq[Scope], calls: Seq[Call]): Seq[Call] = scopes match { + case Nil => calls + case l => collectAllCalls(l.flatMap(_.children), calls ++ collectCalls(l)) + } + + /** + * Collect Scatters from a Seq of Scopes. + * @param scopes scopes to loop through + * @return Scopes instances that are Scatters + */ + def collectScatters(scopes: Seq[Scope]): Seq[Scatter] = scopes collect { case s: Scatter => s } + + /** + * Collect all Scatters from the given scope. + * @param scopes scope to gather Scatters from + * @param scatters for recursivity. Should be passed Nil in most cases. + * @return all Scatters inside the scope + */ + @tailrec + def collectAllScatters(scopes: Seq[Scope], scatters: Seq[Scatter]): Seq[Scatter] = scopes match { + case Nil => scatters + case l => collectAllScatters(l.flatMap(_.children), scatters ++ collectScatters(l)) + } + + @tailrec + def fullyQualifiedNameBuilder(scope: Option[Scope], fqn: String, fullDisplay: Boolean, leaf: Boolean): String = { + scope match { + case Some(x: Scope) => + fullyQualifiedNameBuilder( + x.parent, + (if (fullDisplay || x.appearsInFqn || leaf) s".${x.unqualifiedName}" else "") + fqn, + fullDisplay, + leaf = false) + case None => fqn.tail //Strip away the first "." of the name + } + } +} + +trait Scope { + def unqualifiedName: LocallyQualifiedName + def appearsInFqn: Boolean = true + val parent: Option[Scope] + private var _children: Seq[Scope] = Seq.empty + def children: Seq[Scope] = _children + + def children_=[Child <: Scope](children: Seq[Child]): Unit = { + if (this._children.isEmpty) { + this._children = children + } else throw new UnsupportedOperationException("children is write-once") + } + + def fullyQualifiedName = + Scope.fullyQualifiedNameBuilder(Option(this), "", fullDisplay = false, leaf = true) + + def fullyQualifiedNameWithIndexScopes = + Scope.fullyQualifiedNameBuilder(Option(this), "", fullDisplay = true, leaf = true) + + /** + * Convenience method to collect Calls from within a scope. + * @return all calls contained in this scope (recursively) + */ + def collectAllCalls = Scope.collectAllCalls(Seq(this), Nil) + + /** + * Convenience method to collect Scatters from within a scope. + * @return all scatters contained in this scope (recursively) + */ + def collectAllScatters = Scope.collectAllScatters(Seq(this), Nil) + + /* + * Calls and scatters are accessed frequently so this avoids traversing the whole children tree every time. + * Lazy because children are not provided at instantiation but rather later during tree building process. + * This prevents evaluation from being done before children have been set. + * + * FIXME: In a world where Scope wasn't monolithic, these would be moved around + */ + lazy val calls: Seq[Call] = collectAllCalls + lazy val scatters: Seq[Scatter] = collectAllScatters + + def rootWorkflow: Workflow + + // FIXME: In a world where Scope wasn't monolithic, these would be moved out of here + def prerequisiteScopes: Set[Scope] + def prerequisiteCallNames: Set[LocallyQualifiedName] + + /** + * Returns a set of Calls corresponding to the prerequisiteCallNames + * + * Dropping any unfound Calls to the floor but we're already validating that all calls are sane at ingest. + * It's icky because it relies on that validation not changing, but ... + */ + lazy val prerequisiteCalls: Set[Scope] = prerequisiteCallNames flatMap rootWorkflow.callByName + def callByName(callName: LocallyQualifiedName): Option[Call] = calls find { _.unqualifiedName == callName } + + def ancestry: Seq[Scope] = parent match { + case Some(p) => Seq(p) ++ p.ancestry + case None => Seq.empty[Scope] + } + + def closestCommonAncestor(other: Scope): Option[Scope] = { + val otherAncestry = other.ancestry + ancestry find { otherAncestry.contains(_) } + } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/Task.scala b/scala/wdl4s/src/main/scala/wdl4s/Task.scala new file mode 100644 index 0000000..605a5c3 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/Task.scala @@ -0,0 +1,153 @@ +package wdl4s + +import java.util.regex.Pattern + +import wdl4s.AstTools.{AstNodeName, EnhancedAstNode} +import wdl4s.command.{CommandPart, ParameterCommandPart, StringCommandPart} +import wdl4s.expression.WdlFunctions +import wdl4s.values.WdlValue +import wdl4s.parser.WdlParser._ + +import scala.annotation.tailrec +import scala.collection.JavaConverters._ +import scala.language.postfixOps +import scala.util.Try + +object Task { + val Ws = Pattern.compile("[\\ \\t]+") + def apply(ast: Ast, wdlSyntaxErrorFormatter: WdlSyntaxErrorFormatter): Task = { + val name = ast.getAttribute("name").asInstanceOf[Terminal].getSourceString + val declarations = ast.findAsts(AstNodeName.Declaration).map(Declaration(_, "name", wdlSyntaxErrorFormatter)) + val commandAsts = ast.findAsts(AstNodeName.Command) + if (commandAsts.size != 1) throw new UnsupportedOperationException("Expecting exactly one Command section") + val commandTemplate = commandAsts.head.getAttribute("parts").asInstanceOf[AstList].asScala.toVector map { + case x: Terminal => new StringCommandPart(x.getSourceString) + case x: Ast => ParameterCommandPart(x, wdlSyntaxErrorFormatter) + } + + val outputs = ast.findAsts(AstNodeName.Output) map { TaskOutput(_, declarations, wdlSyntaxErrorFormatter) } + Task(name, declarations, commandTemplate, outputs, ast) + } +} + +/** + * Represents a `task` declaration in a WDL file + * + * @param name Name of the task + * @param declarations Any declarations (e.g. String something = "hello") defined in the task + * @param commandTemplate Sequence of command pieces, essentially a parsed command template + * @param outputs Set of defined outputs in the `output` section of the task + * @param ast The syntax tree from which this was built. + */ +case class Task(name: String, + declarations: Seq[Declaration], + commandTemplate: Seq[CommandPart], + outputs: Seq[TaskOutput], + ast: Ast) extends Executable { + import Task._ + /** + * Attributes defined in the runtime {...} section of a WDL task + */ + val runtimeAttributes = RuntimeAttributes(ast) + + /** + * Inputs to this task, as locally qualified names + * + * @return Seq[TaskInput] where TaskInput contains the input + * name & type as well as any postfix quantifiers (?, +) + */ + val inputs: Seq[TaskInput] = for (declaration <- declarations; input <- declaration.asTaskInput) yield input + // TODO: I think TaskInput can be replaced by Declaration + + /** + * Given a map of task-local parameter names and WdlValues, create a command String. + * + * Instantiating a command line is the process of taking a command in this form: + * + * {{{ + * sh script.sh ${var1} -o ${var2} + * }}} + * + * This command is stored as a `Seq[CommandPart]` in the `Command` class (e.g. [sh script.sh, ${var1}, -o, ${var2}]). + * Then, given a map of variable -> value: + * + * {{{ + * { + * "var1": "foo", + * "var2": "bar" + * } + * }}} + * + * It calls instantiate() on each part, and passes this map. The ParameterCommandPart are the ${var1} and ${var2} + * pieces and they lookup var1 and var2 in that map. + * + * The command that's returned from Command.instantiate() is: + * + * + * {{{sh script.sh foo -o bar}}} + * + * @param parameters Parameter values + * @return String instantiation of the command + */ + def instantiateCommand(parameters: CallInputs, functions: WdlFunctions[WdlValue]): Try[String] = { + Try(normalize(commandTemplate.map(_.instantiate(declarations, parameters, functions)).mkString(""))) + } + + def commandTemplateString: String = normalize(commandTemplate.map(_.toString).mkString) + + /** + * 1) Remove all leading newline chars + * 2) Remove all trailing newline AND whitespace chars + * 3) Remove all *leading* whitespace that's common among every line in the input string + * + * For example, the input string: + * + * " + * first line + * second line + * third line + * + * " + * + * Would be normalized to: + * + * "first line + * second line + * third line" + * + * @param s String to process + * @return String which has common leading whitespace removed from each line + */ + private def normalize(s: String): String = { + val trimmed = stripAll(s, "\r\n", "\r\n \t") + val parts = trimmed.split("[\\r\\n]+") + val indent = parts.map(leadingWhitespaceCount).min + parts.map(_.substring(indent)).mkString("\n") + } + + private def leadingWhitespaceCount(s: String): Int = { + val matcher = Ws.matcher(s) + if (matcher.lookingAt) matcher.end else 0 + } + + private def stripAll(s: String, startChars: String, endChars: String): String = { + /* https://stackoverflow.com/questions/17995260/trimming-strings-in-scala */ + @tailrec + def start(n: Int): String = { + if (n == s.length) "" + else if (startChars.indexOf(s.charAt(n)) < 0) end(n, s.length) + else start(1 + n) + } + + @tailrec + def end(a: Int, n: Int): String = { + if (n <= a) s.substring(a, n) + else if (endChars.indexOf(s.charAt(n - 1)) < 0) s.substring(a, n) + else end(a, n - 1) + } + + start(0) + } + + override def toString: String = s"[Task name=$name commandTemplate=$commandTemplate}]" +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/TaskInput.scala b/scala/wdl4s/src/main/scala/wdl4s/TaskInput.scala new file mode 100644 index 0000000..da78c4d --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/TaskInput.scala @@ -0,0 +1,5 @@ +package wdl4s + +import wdl4s.types.WdlType + +case class TaskInput(name: String, wdlType: WdlType, postfixQuantifier: Option[String] = None) diff --git a/scala/wdl4s/src/main/scala/wdl4s/TaskOutput.scala b/scala/wdl4s/src/main/scala/wdl4s/TaskOutput.scala new file mode 100644 index 0000000..ffb57e5 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/TaskOutput.scala @@ -0,0 +1,42 @@ +package wdl4s + +import wdl4s.AstTools.EnhancedAstNode +import wdl4s.expression.{NoFunctions, WdlStandardLibraryFunctionsType} +import wdl4s.types.WdlType +import wdl4s.parser.WdlParser.{Ast, SyntaxError, Terminal} +import com.typesafe.scalalogging.LazyLogging + +import scala.util.{Failure, Success} + +object TaskOutput extends LazyLogging { + def apply(ast: Ast, declarations: Seq[Declaration], syntaxErrorFormatter: WdlSyntaxErrorFormatter): TaskOutput = { + val wdlType = ast.getAttribute("type").wdlType(syntaxErrorFormatter) + val name = ast.getAttribute("var").sourceString + val expression = WdlExpression(ast.getAttribute("expression")) + + // .get below because contract with the lookup() function is that it signals + // an error by throwing an exception which is wrapped in a Try() in the + // evaluator code + def lookup(n: String) = declarations.find(_.name == n).get.wdlType + + def badCoercionException(expressionWdlType: WdlType) = throw new SyntaxError(syntaxErrorFormatter.taskOutputExpressionTypeDoesNotMatchDeclaredType( + ast.getAttribute("var").asInstanceOf[Terminal], + wdlType, + expressionWdlType + )) + + expression.evaluateType(lookup, new WdlStandardLibraryFunctionsType) match { + case Success(expressionWdlType) if !wdlType.isCoerceableFrom(expressionWdlType) => badCoercionException(expressionWdlType) + case Success(expressionWdlType) => + val expressionValue = expression.evaluate((s: String) => throw new Throwable("not implemented"), NoFunctions) + expressionValue match { + case Success(value) if wdlType.coerceRawValue(value).isFailure => badCoercionException(expressionWdlType) + case _ => + } + case Failure(ex) => logger.error(s"Could not determine type of expression: ${expression.toWdlString}") + } + new TaskOutput(name, wdlType, expression) + } +} + +case class TaskOutput(name: String, wdlType: WdlType, expression: WdlExpression) diff --git a/scala/wdl4s/src/main/scala/wdl4s/UnsatisfiedInputsException.scala b/scala/wdl4s/src/main/scala/wdl4s/UnsatisfiedInputsException.scala new file mode 100644 index 0000000..1d9b6d0 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/UnsatisfiedInputsException.scala @@ -0,0 +1,5 @@ +package wdl4s + +class UnsatisfiedInputsException(message: String, cause: Throwable) extends RuntimeException(message, cause) { + def this(message: String) = this(message, null) +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/WdlExpression.scala b/scala/wdl4s/src/main/scala/wdl4s/WdlExpression.scala new file mode 100644 index 0000000..2eea319 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/WdlExpression.scala @@ -0,0 +1,213 @@ +package wdl4s + +import wdl4s.AstTools.EnhancedAstNode +import wdl4s.WdlExpression._ +import wdl4s.expression.{FileEvaluator, TypeEvaluator, ValueEvaluator, WdlFunctions} +import wdl4s.formatter.{NullSyntaxHighlighter, SyntaxHighlighter} +import wdl4s.types._ +import wdl4s.values._ +import wdl4s.parser.WdlParser +import wdl4s.parser.WdlParser.{Ast, AstList, AstNode, Terminal} + +import scala.collection.JavaConverters._ +import scala.language.postfixOps +import scala.util.{Failure, Success, Try} + +class WdlExpressionException(message: String = null, cause: Throwable = null) extends RuntimeException(message, cause) +case class VariableNotFoundException(variable: String, cause: Throwable = null) extends Exception(s"Variable '$variable' not found", cause) + +object WdlExpression { + + implicit class AstForExpressions(val ast: Ast) extends AnyVal { + def isFunctionCall: Boolean = ast.getName == "FunctionCall" + def isBinaryOperator: Boolean = BinaryOperators.contains(ast.getName) + def isUnaryOperator: Boolean = UnaryOperators.contains(ast.getName) + def functionName: String = ast.getAttribute("name").asInstanceOf[Terminal].getSourceString + def isMemberAccess: Boolean = ast.getName == "MemberAccess" + def isArrayLiteral: Boolean = ast.getName == "ArrayLiteral" + def isMapLiteral: Boolean = ast.getName == "MapLiteral" + def isObjectLiteral: Boolean = ast.getName == "ObjectLiteral" + def isArrayOrMapLookup: Boolean = ast.getName == "ArrayOrMapLookup" + def params = ast.getAttribute("params").asInstanceOf[AstList].asScala.toVector + def name = ast.getAttribute("name").asInstanceOf[Terminal].getSourceString + def isFunctionCallWithOneParameter = ast.isFunctionCall && ast.params.size == 1 + def isFunctionCallWithOneFileParameter = isFunctionCallWithOneParameter && WdlFunctionsWithSingleFileParameter.contains(ast.functionName) + def isGlobFunctionCall = isFunctionCallWithOneParameter && "glob".equals(ast.functionName) + } + + implicit class AstNodeForExpressions(val astNode: AstNode) extends AnyVal { + def containsFunctionCalls: Boolean = + astNode match { + case a: Ast if a.isFunctionCall => true + case a: Ast if a.isBinaryOperator => + val lhs = a.getAttribute("lhs") + val rhs = a.getAttribute("rhs") + lhs.containsFunctionCalls || rhs.containsFunctionCalls + case a: Ast if a.isUnaryOperator => + val rhs = a.getAttribute("expression") + rhs.containsFunctionCalls + case _ => false + } + } + + val parser = new WdlParser() + + /** Maps from a locally qualified name to a WdlValue. */ + type ScopedLookupFunction = String => WdlValue + + val BinaryOperators = Set( + "Add", "Subtract", "Multiply", "Divide", "Remainder", + "GreaterThan", "LessThan", "GreaterThanOrEqual", "LessThanOrEqual", + "Equals", "NotEquals", "LogicalAnd", "LogicalOr" + ) + + val UnaryOperators = Set("LogicalNot", "UnaryPlus", "UnaryNegation") + + val WdlFunctionsWithSingleFileParameter: Seq[String] = Seq( + "read_int", + "read_string", + "read_float", + "read_boolean", + "read_lines", + "read_map", + "read_object", + "read_tsv" + ) + + def evaluate(ast: AstNode, lookup: ScopedLookupFunction, functions: WdlFunctions[WdlValue]): Try[WdlValue] = + ValueEvaluator(lookup, functions).evaluate(ast) + + def evaluateFiles(ast: AstNode, lookup: ScopedLookupFunction, functions: WdlFunctions[WdlValue], coerceTo: WdlType = WdlAnyType) = + FileEvaluator(ValueEvaluator(lookup, functions), coerceTo).evaluate(ast) + + def evaluateType(ast: AstNode, lookup: (String) => WdlType, functions: WdlFunctions[WdlType]) = + TypeEvaluator(lookup, functions).evaluate(ast) + + /** + * Provides a lookup function that will attempt to resolve an identifier as follows: + * + * 1) Resolve as a task input parameter + * 2) Resolve as a declaration + * + * For example: + * + * task test { + * String x = "x" + * String y = x + "y" + * + * command { + * echo ${y + "z"} + * } + * } + * + * when evaluating the expression `y + "z"`, lookup("y") will be called which first tries resolveParameter("y") and fails + * Then tries resolveDeclaration("y") which find declaration for String y and evaluate the expression `x + "y"`. In + * the process of evaluating that it needs to call lookup("x") which gets it's value from resolveDeclaration("x") = WdlString("x") + * + * This will allow the expression `y + "z"` to evaluate to the string "xyz" + */ + def standardLookupFunction(parameters: Map[String, WdlValue], declarations: Seq[Declaration], functions: WdlFunctions[WdlValue]): String => WdlValue = { + def resolveParameter(name: String): Try[WdlValue] = parameters.get(name) match { + case Some(value) => Success(value) + case None => Failure(new WdlExpressionException(s"Could not resolve variable '$name' as an input parameter")) + } + def resolveDeclaration(lookup: ScopedLookupFunction)(name: String) = declarations.find(_.name == name) match { + case Some(d) => d.expression.map(_.evaluate(lookup, functions)).getOrElse(Failure(new WdlExpressionException(s"Could not evaluate declaration: $d"))) + case None => Failure(new WdlExpressionException(s"Could not resolve variable '$name' as a declaration")) + } + def lookup(key: String): WdlValue = { + val attemptedResolution = Stream(resolveParameter _, resolveDeclaration(lookup) _) map { _(key) } find { _.isSuccess } + attemptedResolution.getOrElse(throw new VariableNotFoundException(key)).get + } + lookup + } + + def fromString(expression: WdlSource): WdlExpression = { + val tokens = parser.lex(expression, "string") + val terminalMap = (tokens.asScala.toVector map {(_, expression)}).toMap + val parseTree = parser.parse_e(tokens, new WdlSyntaxErrorFormatter(terminalMap)) + new WdlExpression(parseTree.toAst) + } + + def toString(ast: AstNode, highlighter: SyntaxHighlighter = NullSyntaxHighlighter): String = { + ast match { + case t: Terminal if Seq("identifier", "integer", "float", "boolean").contains(t.getTerminalStr) => t.getSourceString + case t: Terminal if t.getTerminalStr == "string" => s""""${t.getSourceString.replaceAll("\"", "\\" + "\"")}"""" + case a:Ast if a.isBinaryOperator => + val lhs = toString(a.getAttribute("lhs"), highlighter) + val rhs = toString(a.getAttribute("rhs"), highlighter) + a.getName match { + case "Add" => s"$lhs + $rhs" + case "Subtract" => s"$lhs - $rhs" + case "Multiply" => s"$lhs * $rhs" + case "Divide" => s"$lhs / $rhs" + case "Remainder" => s"$lhs % $rhs" + case "Equals" => s"$lhs == $rhs" + case "NotEquals" => s"$lhs != $rhs" + case "LessThan" => s"$lhs < $rhs" + case "LessThanOrEqual" => s"$lhs <= $rhs" + case "GreaterThan" => s"$lhs > $rhs" + case "GreaterThanOrEqual" => s"$lhs >= $rhs" + case "LogicalOr" => s"$lhs || $rhs" + case "LogicalAnd" => s"$lhs && $rhs" + } + case a: Ast if a.isUnaryOperator => + val expression = toString(a.getAttribute("expression"), highlighter) + a.getName match { + case "LogicalNot" => s"!$expression" + case "UnaryPlus" => s"+$expression" + case "UnaryNegation" => s"-$expression" + } + case a: Ast if a.isArrayLiteral => + val evaluatedElements = a.getAttribute("values").astListAsVector map {x => toString(x, highlighter)} + s"[${evaluatedElements.mkString(",")}]" + case a: Ast if a.isMapLiteral => + val evaluatedMap = a.getAttribute("map").astListAsVector map { kv => + val key = toString(kv.asInstanceOf[Ast].getAttribute("key"), highlighter) + val value = toString(kv.asInstanceOf[Ast].getAttribute("value"), highlighter) + s"$key:$value" + } + s"{${evaluatedMap.mkString(",")}}" + case a: Ast if a.isMemberAccess => + val lhs = toString(a.getAttribute("lhs"), highlighter) + val rhs = toString(a.getAttribute("rhs"), highlighter) + s"$lhs.$rhs" + case a: Ast if a.isArrayOrMapLookup => + val lhs = toString(a.getAttribute("lhs"), highlighter) + val rhs = toString(a.getAttribute("rhs"), highlighter) + s"$lhs[$rhs]" + case a: Ast if a.isFunctionCall => + val params = a.params map { a => toString(a, highlighter) } + s"${highlighter.function(a.name)}(${params.mkString(", ")})" + } + } +} + +case class WdlExpression(ast: AstNode) extends WdlValue { + override val wdlType = WdlExpressionType + + def evaluate(lookup: ScopedLookupFunction, functions: WdlFunctions[WdlValue]): Try[WdlValue] = + WdlExpression.evaluate(ast, lookup, functions) + + def evaluateFiles(lookup: ScopedLookupFunction, functions: WdlFunctions[WdlValue], coerceTo: WdlType): Try[Seq[WdlFile]] = + WdlExpression.evaluateFiles(ast, lookup, functions, coerceTo) + + def evaluateType(lookup: (String) => WdlType, functions: WdlFunctions[WdlType]): Try[WdlType] = + WdlExpression.evaluateType(ast, lookup, functions) + + def containsFunctionCall = ast.containsFunctionCalls + + def toString(highlighter: SyntaxHighlighter): String = { + WdlExpression.toString(ast, highlighter) + } + + override def toWdlString: String = toString(NullSyntaxHighlighter) + + def prerequisiteCallNames: Set[LocallyQualifiedName] = this.toMemberAccesses map { _.lhs } + def toMemberAccesses: Set[MemberAccess] = AstTools.findTopLevelMemberAccesses(ast) map { MemberAccess(_) } toSet +} + +case object NoLookup extends ScopedLookupFunction { + def apply(value: String): WdlValue = + throw new UnsupportedOperationException(s"No identifiers should be looked up: $value") +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/WdlNamespace.scala b/scala/wdl4s/src/main/scala/wdl4s/WdlNamespace.scala new file mode 100644 index 0000000..e6217df --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/WdlNamespace.scala @@ -0,0 +1,370 @@ +package wdl4s + +import java.io.File + +import wdl4s.AstTools.{AstNodeName, EnhancedAstNode, EnhancedAstSeq} +import wdl4s.expression.WdlStandardLibraryFunctions +import wdl4s.types._ +import wdl4s.values._ +import wdl4s.parser.WdlParser._ +import wdl4s.parser.WdlParser +import wdl4s.util.FileUtil.EnhancedFile + +import scala.collection.JavaConverters._ +import scala.language.postfixOps +import scala.util.{Failure, Success, Try} + +/** + * Define WdlNamespace as a sum type w/ two states - one containing a local workflow and one without. + * The latter is a valid state for a WDL file, however only the former can be requested to be run, so + * any constructs (e.g. WorkflowManagerActor) expecting to run a workflow should only take the `NamespaceWithWorkflow` + */ +sealed trait WdlNamespace extends WdlValue { + final val wdlType = WdlNamespaceType + + def ast: Ast // FIXME: I think this is only used by the syntax highlighting, can it go away once we're built? + def importedAs: Option[String] // Used when imported with `as` + def imports: Seq[Import] // FIXME: Change to Set? + def namespaces: Seq[WdlNamespace] // FIXME: Change to Set? FIXME: Rename to importedNamespaces? + def tasks: Seq[Task] // FIXME: Change to Set? + def terminalMap: Map[Terminal, WdlSource] + + // Convenience method for findTask in the context of this namespace + def findTask(name: String): Option[Task] = WdlNamespace.findTask(name, namespaces, tasks) +} + +/** + * A valid Namespace which doesn't have a locally defined Workflow. This should pass any validity checking but is not + * directly runnable by `WorkflowManagerActor` + */ +case class NamespaceWithoutWorkflow(importedAs: Option[String], + imports: Seq[Import], + namespaces: Seq[WdlNamespace], + tasks: Seq[Task], + terminalMap: Map[Terminal, WdlSource], + ast: Ast) extends WdlNamespace +/** Represents a WdlNamespace which has a local workflow, i.e. a directly runnable namespace */ +case class NamespaceWithWorkflow(importedAs: Option[String], + workflow: Workflow, + imports: Seq[Import], + namespaces: Seq[WdlNamespace], + tasks: Seq[Task], + terminalMap: Map[Terminal, WdlSource], + wdlSyntaxErrorFormatter: WdlSyntaxErrorFormatter, + ast: Ast) extends WdlNamespace { + /** + * Confirm all required inputs are present and attempt to coerce raw inputs to `WdlValue`s. + * This can fail if required raw inputs are missing or if the values for a specified raw input + * cannot be coerced to the target type of the input as specified in the namespace. + */ + def coerceRawInputs(rawInputs: WorkflowRawInputs): Try[WorkflowCoercedInputs] = { + def coerceRawInput(input: WorkflowInput): Try[Option[WdlValue]] = input.fqn match { + case _ if rawInputs.contains(input.fqn) => + val rawValue = rawInputs.get(input.fqn).get + input.wdlType.coerceRawValue(rawValue) match { + case Success(value) => Success(Some(value)) + case _ => Failure(new UnsatisfiedInputsException(s"Could not coerce value for '${input.fqn}' into: ${input.wdlType}")) + } + case _ => + input.optional match { + case true => Success(None) + case _ => Failure(new UnsatisfiedInputsException(s"Required workflow input '${input.fqn}' not specified.")) + } + } + + val tryCoercedValues = workflow.inputs map { case (fqn, input) => fqn -> coerceRawInput(input) } + + val (successes, failures) = tryCoercedValues.partition { case (_, tryValue) => tryValue.isSuccess } + if (failures.isEmpty) { + Try(for { + (key, tryValue) <- successes + optionValue = tryValue.get if tryValue.get.isDefined + } yield key -> optionValue.get) + } else { + val message = failures.values.collect { case f: Failure[_] => f.exception.getMessage }.mkString("\n") + Failure(new UnsatisfiedInputsException(s"The following errors occurred while processing your inputs:\n\n$message")) + } + } + + private def declarationLookupFunction(decl: Declaration, inputs: Map[FullyQualifiedName, WdlValue]): String => WdlValue ={ + def identifierLookup(string: String): WdlValue = { + + /* This is a scope hierarchy to search for the variable `string`. If `decl.scopeFqn` == "a.b.c" + * then `hierarchy` should be Seq("a.b.c", "a.b", "a") + */ + val hierarchy = decl.scopeFqn.split("\\.").reverse.tails.toSeq.map {_.reverse.mkString(".")} + + /* Attempt to resolve the string in each scope */ + val attemptedValues = hierarchy.map {scope => inputs.get(s"$scope.$string")} + attemptedValues.flatten.headOption.getOrElse { + throw new WdlExpressionException(s"Could not find a value for $string") + } + } + identifierLookup + } + + /* Some declarations need a value from the user and some have an expression attached to them. + * For the declarations that have an expression attached to it already, evaluate the expression + * and return the value for storage in the symbol store + */ + def staticDeclarationsRecursive(userInputs: WorkflowCoercedInputs, wdlFunctions: WdlStandardLibraryFunctions): Try[WorkflowCoercedInputs] = { + import scala.collection.mutable + val collected = mutable.Map[String, WdlValue]() + val allDeclarations = workflow.declarations ++ workflow.calls.flatMap {_.task.declarations} + + val evaluatedDeclarations = allDeclarations.filter {_.expression.isDefined}.map {decl => + val value = decl.expression.get.evaluate(declarationLookupFunction(decl, collected.toMap ++ userInputs), wdlFunctions) + collected += (decl.fullyQualifiedName -> value.get) + val coercedValue = value match { + case Success(s) => decl.wdlType.coerceRawValue(s) + case f => f + } + decl.fullyQualifiedName -> coercedValue + }.toMap + + val (successes, failures) = evaluatedDeclarations.partition {case (_, tryValue) => tryValue.isSuccess} + if (failures.isEmpty) { + Success(successes.map {case (k,v) => k -> v.get}) + } else { + val message = failures.values.collect {case f: Failure[_] => f.exception.getMessage}.mkString("\n") + Failure(new UnsatisfiedInputsException(s"Could not evaluate some declaration expressions:\n\n$message")) + } + } + + /** + * Given a Fully-Qualified Name, return the Scope object that + * corresponds to this FQN. + */ + def resolve(fqn: FullyQualifiedName): Option[Scope] = + (Seq(workflow) ++ workflow.calls ++ workflow.scatters).find(s => s.fullyQualifiedName == fqn || s.fullyQualifiedNameWithIndexScopes == fqn) +} + +/** + * Main interface into the `wdl_scala` package. + * + * Example usage: + * + * {{{ + * val namespace = WdlNamespace.process(new File("/path/to/file.wdl")) + * binding.workflow.calls foreach { call => + * println(call) + * } + * }}} + */ +object WdlNamespace { + /** + * Given a pointer to a WDL file, parse the text and build Workflow and Task + * objects. + * + * @param wdlFile The file to parse/process + * @return WdlBinding object with the parsed results + * @throws WdlParser.SyntaxError if there was a problem parsing the source code + * @throws UnsupportedOperationException if an error occurred constructing the + * Workflow and Task objects + * + */ + def load(wdlFile: File): WdlNamespace = { + load(readFile(wdlFile), wdlFile.toString, localImportResolver, None) + } + + def load(wdlFile: File, importResolver: ImportResolver): WdlNamespace = { + load(readFile(wdlFile), wdlFile.toString, importResolver, None) + } + + def load(wdlSource: WdlSource): WdlNamespace = { + load(wdlSource, "string", localImportResolver, None) + } + + def load(wdlSource: WdlSource, importResolver: ImportResolver): WdlNamespace = { + load(wdlSource, "string", importResolver, None) + } + + def load(wdlSource: WdlSource, resource: String): WdlNamespace = { + load(wdlSource, resource, localImportResolver, None) + } + + def load(wdlSource: WdlSource, resource: String, importResolver: ImportResolver): WdlNamespace = { + load(wdlSource, resource, importResolver, None) + } + + private def load(wdlSource: WdlSource, resource: String, importResolver: ImportResolver, importedAs: Option[String]): WdlNamespace = { + WdlNamespace(AstTools.getAst(wdlSource, resource), wdlSource, importResolver, importedAs) + } + + /** + * Validates the following things about the AST: + * + * 1) Tasks do not have duplicate inputs + * 2) Tasks in this namespace have unique names + * 3) Tasks and namespaces don't have overlapping names (FIXME: Likely has to do w/ DSDEEPB-726) + */ + def apply(ast: Ast, source: WdlSource, importResolver: ImportResolver, namespace: Option[String]): WdlNamespace = { + /** + * All `import` statement strings at the top of the document + */ + val imports = ast.getAttribute("imports").asInstanceOf[AstList].asScala map {x => Import(x)} + + /* WdlBinding objects for each import statement */ + val namespaces: Seq[WdlNamespace] = {for { + i <- imports + source = importResolver(i.uri) if source.length > 0 + } yield WdlNamespace.load(source, i.uri, importResolver, i.namespace)}.toSeq + + /* Create a map of Terminal -> WdlBinding */ + val terminalMap = AstTools.terminalMap(ast, source) + val combinedTerminalMap = ((namespaces map {x => x.terminalMap}) ++ Seq(terminalMap)) reduce (_ ++ _) + val wdlSyntaxErrorFormatter = new WdlSyntaxErrorFormatter(combinedTerminalMap) + + /** + * All imported `task` definitions for `import` statements without a namespace (e.g. no `as` clause) + * These tasks are considered to be in this current workspace + */ + val importedTasks: Seq[Task] = namespaces flatMap { b => + b.importedAs match { + case None => b.tasks + case _ => Seq.empty[Task] + } + } + + /** + * All `task` definitions defined in the WDL file (i.e. not imported) + */ + val localTasks: Seq[Task] = ast.findAsts(AstNodeName.Task) map {Task(_, wdlSyntaxErrorFormatter)} + + /** + * All `task` definitions, including local and imported ones + */ + val tasks: Seq[Task] = localTasks ++ importedTasks + + /* + * Ensure that no namespaces collide with task names. + * + * It'd be simpler to get this via the `namespaces` themselves but don't have access to the correct AST, which is + * required by the error syntax highlighter :/ (FIXME: Or do I?) + */ + for { + i <- imports + namespaceAst <- i.namespaceAst + task <- findTask(namespaceAst.sourceString, namespaces, tasks) + } yield {throw new SyntaxError(wdlSyntaxErrorFormatter.taskAndNamespaceHaveSameName(task.ast, namespaceAst.asInstanceOf[Terminal]))} + + // Detect duplicated task names + val dupeTaskAstsByName = tasks.map(_.ast).duplicatesByName + if (dupeTaskAstsByName.nonEmpty) { + throw new SyntaxError(wdlSyntaxErrorFormatter.duplicateTask(dupeTaskAstsByName)) + } + + // FIXME: Here's where I'd toSet stuff after duplications are detected + ast.findAsts(AstNodeName.Workflow) match { + case Nil => NamespaceWithoutWorkflow(namespace, imports, namespaces, tasks, terminalMap, ast) + case Seq(x) => NamespaceWithWorkflow(ast, x, namespace, imports, namespaces, tasks, terminalMap, wdlSyntaxErrorFormatter) + case doh => throw new SyntaxError(wdlSyntaxErrorFormatter.tooManyWorkflows(doh.asJava)) + } + } + + + /** + * Given a name, a collection of WdlNamespaces and a collection of Tasks will attempt to find a Task + * with that name within the WdlNamespaces + */ + def findTask(name: String, namespaces: Seq[WdlNamespace], tasks: Seq[Task]): Option[Task] = { + if (name.contains(".")) { + val parts = name.split("\\.", 2) + /* This is supposed to resolve a dot-notation'd string (e.g. "a.b.c") by recursively + * traversing child namespaces or resolving to a task. + * + * For example: + * findTasks("a.b.c") would first find namespace "a" and then return a.findTasks("b.c") + * a.findTasks("b.c") would call a.b.findTasks("c") + * a.b.findTasks("c") would return the task named "c" in the "b" namespace + */ + namespaces find {_.importedAs == Some(parts(0))} flatMap {x => findTask(parts(1), x.namespaces, x.tasks)} + } else tasks.find(_.name == name) + } + + private def localImportResolver(path: String): WdlSource = readFile(new File(path)) + private def readFile(wdlFile: File): WdlSource = wdlFile.slurp +} + +object NamespaceWithWorkflow { + def load(wdlSource: WdlSource): NamespaceWithWorkflow = from(WdlNamespace.load(wdlSource)) + def load(wdlSource: WdlSource, importResolver: ImportResolver): NamespaceWithWorkflow = { + NamespaceWithWorkflow.from(WdlNamespace.load(wdlSource, importResolver)) + } + /** + * Used to safely cast a WdlNamespace to a NamespaceWithWorkflow. Throws an IllegalArgumentException if another + * form of WdlNamespace is passed to it + */ + private def from(namespace: WdlNamespace): NamespaceWithWorkflow = { + namespace match { + case n: NamespaceWithWorkflow => n + case _ => throw new IllegalArgumentException("Namespace does not have a local workflow to run") + } + } + + /** + * Validates: + * 1) All `Call` blocks reference tasks that exist + * 2) All `Call` inputs reference actual variables on the corresponding task + * 3) Calls do not reference the same task input more than once + * 4) `Call` input expressions (right-hand side) should only use the MemberAccess + * syntax (e.g: x.y) on WdlObjects (which include other `Call` invocations) + * 5) `Call` input expressions (right-hand side) should only reference identifiers + * that will resolve when evaluated + */ + def apply(ast: Ast, workflowAst: Ast, namespace: Option[String], imports: Seq[Import], + namespaces: Seq[WdlNamespace], tasks: Seq[Task], terminalMap: Map[Terminal, WdlSource], + wdlSyntaxErrorFormatter: WdlSyntaxErrorFormatter): NamespaceWithWorkflow = { + /* + * Ensure that no namespaces collide with workflow names. + * + * It'd be simpler to get this via the `namespaces` themselves but don't have access to the correct AST, which is + * required by the error syntax highlighter :/ (FIXME: Or do I?) + */ + for { + i <- imports + namespaceAst <- i.namespaceAst + if namespaceAst.sourceString == workflowAst.getAttribute("name").sourceString + } yield {throw new SyntaxError(wdlSyntaxErrorFormatter.workflowAndNamespaceHaveSameName(workflowAst, namespaceAst.asInstanceOf[Terminal]))} + + val workflow: Workflow = Workflow(workflowAst, namespaces, tasks, wdlSyntaxErrorFormatter) + + // Make sure that all MemberAccess ASTs refer to valid calls and those have valid output names + for { + call <- workflow.calls + (name, expression) <- call.inputMappings + memberAccessAst <- expression.ast.findTopLevelMemberAccesses() + } validateMemberAccessAst(memberAccessAst, workflow, wdlSyntaxErrorFormatter) + + new NamespaceWithWorkflow(namespace, workflow, imports, namespaces, tasks, terminalMap, wdlSyntaxErrorFormatter, ast) + } + + /** + * Ensures that the lhs corresponds to a call and the rhs corresponds to one of its outputs. We're only checking + * top level MemberAccess ASTs because the sub-ASTs don't make sense w/o the context of the parent. For example + * if we have "input: var=ns.ns1.my_task" it does not make sense to validate "ns1.my_task" by itself as it only + * makes sense to validate that "ns.ns1.my_task" as a whole is coherent + * + * Run for its side effect (i.e. Exception) but we were previously using a Try and immediately calling .get on it + * so it's the same thing + */ + def validateMemberAccessAst(memberAccessAst: Ast, + workflow: Workflow, + errorFormatter: WdlSyntaxErrorFormatter): Unit = { + val memberAccess = MemberAccess(memberAccessAst) + /* + This is a shortcut - it's only looking at the workflow's locally qualified name and not the fully qualified + name. This is ok for now because we do not support nested workflows and all call names must be unique within + a workflow, however if we support nested workflows this will no longer work properly. + */ + val call = workflow.calls find { _.unqualifiedName == memberAccess.lhs } + + call match { + case Some(c) if c.task.outputs.exists(_.name == memberAccess.rhs) => () + case Some(c) => + throw new SyntaxError(errorFormatter.memberAccessReferencesBadTaskInput(memberAccessAst)) + case None => + throw new SyntaxError(errorFormatter.undefinedMemberAccess(memberAccessAst)) + } + } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/WdlSyntaxErrorFormatter.scala b/scala/wdl4s/src/main/scala/wdl4s/WdlSyntaxErrorFormatter.scala new file mode 100644 index 0000000..90f2a96 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/WdlSyntaxErrorFormatter.scala @@ -0,0 +1,207 @@ +package wdl4s + +import wdl4s.types.WdlType +import wdl4s.parser.WdlParser._ + +import scala.collection.JavaConverters._ + +case class WdlSyntaxErrorFormatter(terminalMap: Map[Terminal, WdlSource]) extends SyntaxErrorFormatter { + + private def pointToSource(t: Terminal): String = s"${line(t)}\n${" " * (t.getColumn - 1)}^" + private def line(t:Terminal): String = terminalMap.get(t).get.split("\n")(t.getLine - 1) + + def unexpectedEof(method: String, expected: java.util.List[TerminalIdentifier], nt_rules: java.util.List[String]): String = "ERROR: Unexpected end of file" + + def excessTokens(method: String, terminal: Terminal): String = { + s"""ERROR: Finished parsing without consuming all tokens. + | + |${pointToSource(terminal)} + """.stripMargin + } + + def unexpectedSymbol(method: String, actual: Terminal, expected: java.util.List[TerminalIdentifier], rule: String): String = { + val expectedTokens = expected.asScala.map(_.string).mkString(", ") + s"""ERROR: Unexpected symbol (line ${actual.getLine}, col ${actual.getColumn}) when parsing '$method'. + | + |Expected $expectedTokens, got ${actual.toPrettyString}. + | + |${pointToSource(actual)} + | + |$rule + """.stripMargin + } + + def noMoreTokens(method: String, expecting: TerminalIdentifier, last: Terminal): String = { + s"""ERROR: No more tokens. Expecting ${expecting.string} + | + |${pointToSource(last)} + """.stripMargin + } + + def invalidTerminal(method: String, invalid: Terminal): String = { + s"""ERROR: Invalid symbol ID: ${invalid.getId} (${invalid.getTerminalStr}) + | + |${pointToSource(invalid)} + """.stripMargin + } + + def tooManyWorkflows(workflowAsts: java.util.List[Ast]): String = { + val otherWorkflows = workflowAsts.asScala.map({ ast => + val name: Terminal = ast.getAttribute("name").asInstanceOf[Terminal] + s"""Prior workflow definition (line ${name.getLine} col ${name.getColumn}): + | + |${pointToSource(name)} + """.stripMargin + }).mkString("\n") + + s"""ERROR: Only one workflow definition allowed, found ${workflowAsts.size} workflows: + | + |$otherWorkflows + """.stripMargin + } + + def duplicateTask(taskAsts: Seq[Ast]): String = { + val otherTasks = taskAsts.map({ ast => + val name: Terminal = ast.getAttribute("name").asInstanceOf[Terminal] + s"""Prior task definition (line ${name.getLine} col ${name.getColumn}): + | + |${pointToSource(name)} + """.stripMargin + }).mkString("\n") + + s"""ERROR: Two tasks defined with the name '${taskAsts.head.getAttribute("name").asInstanceOf[Terminal].getSourceString}': + | + |$otherTasks + """.stripMargin + } + + def callReferencesBadTaskName(callAst: Ast, taskName: String): String = { + val callTask: Terminal = callAst.getAttribute("task").asInstanceOf[Terminal] + s"""ERROR: Call references a task ($taskName) that doesn't exist (line ${callTask.getLine}, col ${callTask.getColumn}) + | + |${pointToSource(callTask)} + """.stripMargin + } + + def callReferencesBadTaskInput(callInputAst: Ast, taskAst: Ast): String = { + val callParameter: Terminal = callInputAst.getAttribute("key").asInstanceOf[Terminal] + val taskName: Terminal = taskAst.getAttribute("name").asInstanceOf[Terminal] + s"""ERROR: Call references an input on task '${taskName.getSourceString}' that doesn't exist (line ${callParameter.getLine}, col ${callParameter.getColumn}) + | + |${pointToSource(callParameter)} + | + |Task defined here (line ${taskName.getLine}, col ${taskName.getColumn}): + | + |${pointToSource(taskName)} + """.stripMargin + } + + def taskAndNamespaceHaveSameName(taskAst: Ast, namespace: Terminal): String = { + val taskName = taskAst.getAttribute("name").asInstanceOf[Terminal] + s"""ERROR: Task and namespace have the same name: + | + |Task defined here (line ${taskName.getLine}, col ${taskName.getColumn}): + | + |${pointToSource(taskName)} + | + |Import statement defined here (line ${namespace.getLine}, col ${namespace.getColumn}): + | + |${pointToSource(namespace)} + """.stripMargin + } + + def workflowAndNamespaceHaveSameName(workflowAst: Ast, namespace: Terminal): String = { + val workflowName = workflowAst.getAttribute("name").asInstanceOf[Terminal] + s"""ERROR: Task and namespace have the same name: + | + |Task defined here (line ${workflowName.getLine}, col ${workflowName.getColumn}): + | + |${pointToSource(workflowName)} + | + |Import statement defined here (line ${namespace.getLine}, col ${namespace.getColumn}): + | + |${pointToSource(namespace)} + """.stripMargin + } + + def multipleCallsAndHaveSameName(names: Seq[Terminal]): String = { + val duplicatedCallNames = names.map {name => + s"""Call statement here (line ${name.getLine}, column ${name.getColumn}): + | + |${pointToSource(name)} + """.stripMargin + } + + s"""ERROR: Two or more calls have the same name: + | + |${duplicatedCallNames.mkString("\n")} + """.stripMargin + } + + def multipleInputStatementsOnCall(secondInputStatement: Terminal): String = { + s"""ERROR: Call has multiple 'input' sections defined: + | + |${pointToSource(secondInputStatement)} + | + |Instead of multiple 'input' sections, use commas to separate the values. + """.stripMargin + } + + def emptyInputSection(callTaskName: Terminal) = { + s"""ERROR: empty "input" section for call '${callTaskName.getSourceString}': + | + |${pointToSource(callTaskName)} + """.stripMargin + } + + def undefinedMemberAccess(ast: Ast): String = { + val rhsAst = ast.getAttribute("rhs").asInstanceOf[Terminal] + s"""ERROR: Expression will not evaluate (line ${rhsAst.getLine}, col ${rhsAst.getColumn}): + | + |${pointToSource(rhsAst)} + """.stripMargin + } + + def memberAccessReferencesBadTaskInput(ast: Ast): String = { + val rhsAst = ast.getAttribute("rhs").asInstanceOf[Terminal] + s"""ERROR: Expression reference input on task that doesn't exist (line ${rhsAst.getLine}, col ${rhsAst.getColumn}): + | + |${pointToSource(rhsAst)} + """.stripMargin + } + + def arrayMustHaveOnlyOneTypeParameter(arrayDecl: Terminal): String = { + s"""ERROR: Array type should only have one parameterized type (line ${arrayDecl.getLine}, col ${arrayDecl.getColumn}): + | + |${pointToSource(arrayDecl)} + """.stripMargin + } + + def mapMustHaveExactlyTwoTypeParameters(mapDecl: Terminal): String = { + s"""ERROR: Map type should have two parameterized types (line ${mapDecl.getLine}, col ${mapDecl.getColumn}): + | + |${pointToSource(mapDecl)} + """.stripMargin + } + + def arrayMustHaveATypeParameter(arrayDecl: Terminal): String = { + s"""ERROR: Array type should have exactly one parameterized type (line ${arrayDecl.getLine}, col ${arrayDecl.getColumn}): + | + |${pointToSource(arrayDecl)} + """.stripMargin + } + + def taskOutputExpressionTypeDoesNotMatchDeclaredType(outputName: Terminal, outputType: WdlType, expressionType: WdlType) = { + s"""ERROR: ${outputName.getSourceString} is declared as a ${outputType.toWdlString} but the expression evaluates to a ${expressionType.toWdlString}: + | + |${pointToSource(outputName)} + """.stripMargin + } + + def trueAndFalseAttributesAreRequired(firstAttribute: Terminal) = { + s"""ERROR: both 'true' and 'false' attributes must be specified if either is specified: + | + |${pointToSource(firstAttribute)} + """.stripMargin + } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/Workflow.scala b/scala/wdl4s/src/main/scala/wdl4s/Workflow.scala new file mode 100644 index 0000000..7a85f66 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/Workflow.scala @@ -0,0 +1,173 @@ +package wdl4s + +import wdl4s.AstTools.{AstNodeName, EnhancedAstNode} +import wdl4s.types.WdlType +import wdl4s.parser.WdlParser.{Ast, AstList, SyntaxError, Terminal} + +import scala.collection.JavaConverters._ +import scala.collection.mutable +import scala.language.postfixOps + +object Workflow { + + def apply(ast: Ast, + namespaces: Seq[WdlNamespace], + tasks: Seq[Task], + wdlSyntaxErrorFormatter: WdlSyntaxErrorFormatter): Workflow = { + ast.getName match { + case AstNodeName.Workflow => + generateWorkflowScopes(ast, namespaces, tasks, wdlSyntaxErrorFormatter) + case nonWorkflowAst => + throw new UnsupportedOperationException(s"Ast is not a 'Workflow Ast' but a '$nonWorkflowAst Ast'") + } + } + + private def generateWorkflowScopes(workflowAst: Ast, + namespaces: Seq[WdlNamespace], + tasks: Seq[Task], + wdlSyntaxErrorFormatter: WdlSyntaxErrorFormatter): Workflow = { + + /** + * Incremented indexes per type of class. Currently only used by Scatter. + * Indexes are incremented just before use + recursion (prefix, vs. infix or postfix), + * thus "start" at zero, but default to -1. + */ + val scopeIndexes: mutable.Map[Class[_ <: Scope], Int] = mutable.HashMap.empty.withDefaultValue(-1) + + /** + * Retrieve the list of children ASTs from the AST body. + * Return empty seq if body is null or is not a list. + */ + def getChildrenList(ast: Ast): Seq[Ast] = { + val body = Option(ast.getAttribute("body")).filter(_.isInstanceOf[AstList]).map(_.astListAsVector) + body match { + case Some(asts) => asts collect { case node: Ast if Seq(AstNodeName.Call, AstNodeName.Scatter).contains(node.getName) => node } + case None => Seq.empty[Ast] + } + } + + /** + * Sets the child scopes on this parent from the ast. + * @param parentAst Parent ast. + * @param parentScope Parent scope. + */ + def setChildScopes(parentAst: Ast, parentScope: Scope): Unit = { + parentScope.children = for { + child <- getChildrenList(parentAst) + scope = generateScopeTree(child, namespaces, tasks, wdlSyntaxErrorFormatter, parentScope) + } yield scope + } + + /** + * Generate a scope from the ast parameter and all its descendants recursively. + * @return Scope equivalent + */ + def generateScopeTree(node: Ast, + namespaces: Seq[WdlNamespace], + tasks: Seq[Task], + wdlSyntaxErrorFormatter: WdlSyntaxErrorFormatter, + parent: Scope): Scope = { + + val scope: Scope = node.getName match { + case AstNodeName.Call => + Call(node, namespaces, tasks, wdlSyntaxErrorFormatter, Option(parent)) + case AstNodeName.Scatter => + scopeIndexes(classOf[Scatter]) += 1 + Scatter(node, scopeIndexes(classOf[Scatter]), Option(parent)) + } + // Generate and set children recursively + setChildScopes(node, scope) + scope + } + + val workflow = Workflow(workflowAst, wdlSyntaxErrorFormatter) + setChildScopes(workflowAst, workflow) + workflow + } + + private def apply(ast: Ast, wdlSyntaxErrorFormatter: WdlSyntaxErrorFormatter): Workflow = { + val name = ast.getAttribute("name").asInstanceOf[Terminal].getSourceString + val declarations = ast.findAsts(AstNodeName.Declaration).map(Declaration(_, name, wdlSyntaxErrorFormatter)) + val callNames = ast.findAsts(AstNodeName.Call).map {call => + Option(call.getAttribute("alias")).getOrElse(call.getAttribute("task")) + } + val workflowOutputsDecls = ast.findAsts(AstNodeName.WorkflowOutput) map { wfOutput => + val wildcard = Option(wfOutput.getAttribute("wildcard")).map(_.sourceString).getOrElse("").nonEmpty + val outputFqn = name + "." + wfOutput.getAttribute("fqn").sourceString + WorkflowOutputDeclaration(outputFqn, wildcard) + } + + callNames groupBy { _.sourceString } foreach { + case (_, terminals) if terminals.size > 1 => + throw new SyntaxError(wdlSyntaxErrorFormatter.multipleCallsAndHaveSameName(terminals.asInstanceOf[Seq[Terminal]])) + case _ => + } + + new Workflow(name, declarations, workflowOutputsDecls) + } +} + +/** + * Represents a `workflow` definition in WDL which currently + * only supports a set of `call` declarations and a name for + * the workflow + * + * @param unqualifiedName The name of the workflow + */ +case class Workflow(unqualifiedName: String, + declarations: Seq[Declaration], + workflowOutputDecls: Seq[WorkflowOutputDeclaration]) extends Executable with Scope { + override val prerequisiteScopes = Set.empty[Scope] + override val prerequisiteCallNames = Set.empty[String] + override val parent: Option[Scope] = None + + /** + * FQNs for all inputs to this workflow and their associated types and possible postfix quantifiers. + * + * @return a Map[FullyQualifiedName, WorkflowInput] representing the + * inputs that the user needs to provide to this workflow + */ + def inputs: Map[FullyQualifiedName, WorkflowInput] = { + val callInputs = for { call <- calls; input <- call.unsatisfiedInputs } yield input + val declarationInputs = for { declaration <- declarations; input <- declaration.asWorkflowInput } yield input + (callInputs ++ declarationInputs) map { input => input.fqn -> input } toMap + } + + /** + * All outputs for this workflow and their associated types + * + * @return a Map[FullyQualifiedName, WdlType] representing the union + * of all outputs from all `call`s within this workflow + */ + lazy val outputs: Seq[ReportableSymbol] = { + + case class PotentialReportableSymbol(name: String, wdlType: WdlType, matchWorkflowOutputWildcards: Boolean) + + // Build a list of ALL potentially reportable symbols, and whether they're allowed to match + // wildcards in the workflow's output {...} spec. + val outputs: Seq[PotentialReportableSymbol] = for { + call: Call <- calls + output <- call.task.outputs + } yield PotentialReportableSymbol(s"${call.fullyQualifiedName}.${output.name}", output.wdlType, matchWorkflowOutputWildcards = true) + + val inputs: Seq[PotentialReportableSymbol] = for { + call: Call <- calls + input <- call.task.inputs + } yield PotentialReportableSymbol(s"${call.fullyQualifiedName}.${input.name}", input.wdlType, matchWorkflowOutputWildcards = false) + + val filtered = if (workflowOutputDecls isEmpty) { + outputs + } else { + (outputs ++ inputs) filter { + case PotentialReportableSymbol(fqn, wdlType, wildcardsAllowed) => + workflowOutputDecls.isEmpty || workflowOutputDecls.exists(_.outputMatchesDeclaration(fqn, wildcardsAllowed)) + } + } + + filtered map { case PotentialReportableSymbol(fqn, value, wildcardAllowed) => ReportableSymbol(fqn, value) } + } + + override def rootWorkflow: Workflow = this +} + +case class ReportableSymbol(fullyQualifiedName: FullyQualifiedName, wdlType: WdlType) \ No newline at end of file diff --git a/scala/wdl4s/src/main/scala/wdl4s/WorkflowInput.scala b/scala/wdl4s/src/main/scala/wdl4s/WorkflowInput.scala new file mode 100644 index 0000000..294b212 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/WorkflowInput.scala @@ -0,0 +1,10 @@ +package wdl4s + +import wdl4s.types.WdlType + +case class WorkflowInput(fqn: FullyQualifiedName, wdlType: WdlType, postfixQuantifier: Option[String]) { + val optional = postfixQuantifier match { + case Some(s) if s == "?" => true + case _ => false + } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/WorkflowOutputDeclaration.scala b/scala/wdl4s/src/main/scala/wdl4s/WorkflowOutputDeclaration.scala new file mode 100644 index 0000000..ecf5901 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/WorkflowOutputDeclaration.scala @@ -0,0 +1,12 @@ +package wdl4s + +case class WorkflowOutputDeclaration(fqn: String, wildcard: Boolean) { + + def outputMatchesDeclaration(outputFqn: String, wildcardsAllowed: Boolean): Boolean = { + if (wildcard) { + wildcardsAllowed && outputFqn.startsWith(fqn) + } else { + outputFqn.equals(fqn) + } + } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/command/CommandPart.scala b/scala/wdl4s/src/main/scala/wdl4s/command/CommandPart.scala new file mode 100644 index 0000000..f6756fe --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/command/CommandPart.scala @@ -0,0 +1,9 @@ +package wdl4s.command + +import wdl4s.expression.WdlFunctions +import wdl4s.values.WdlValue +import wdl4s.Declaration + +trait CommandPart { + def instantiate(declarations: Seq[Declaration], parameters: Map[String, WdlValue], functions: WdlFunctions[WdlValue]): String +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/command/ParameterCommandPart.scala b/scala/wdl4s/src/main/scala/wdl4s/command/ParameterCommandPart.scala new file mode 100644 index 0000000..4122695 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/command/ParameterCommandPart.scala @@ -0,0 +1,54 @@ +package wdl4s.command + +import wdl4s.AstTools.EnhancedAstNode +import wdl4s._ +import wdl4s.expression.WdlFunctions +import wdl4s.values._ +import wdl4s.parser.WdlParser.{Terminal, SyntaxError, Ast} +import scala.language.postfixOps + +import scala.util.{Failure, Success} + +object ParameterCommandPart { + def apply(ast: Ast, wdlSyntaxErrorFormatter: WdlSyntaxErrorFormatter): ParameterCommandPart = { + val attributes = ast.getAttribute("attributes").astListAsVector map { a => + val ast = a.asInstanceOf[Ast] + (ast.getAttribute("key").sourceString, ast.getAttribute("value").sourceString) + } toMap + val expression = WdlExpression(ast.getAttribute("expr")) + if ((attributes.contains("true") && !attributes.contains("false")) || (attributes.contains("false") && !attributes.contains("true"))) { + // .head because we can't get here without there being at least one attribute + val firstAttr = ast.getAttribute("attributes").astListAsVector.head.asInstanceOf[Ast].getAttribute("key").asInstanceOf[Terminal] + throw new SyntaxError(wdlSyntaxErrorFormatter.trueAndFalseAttributesAreRequired(firstAttr)) + } + new ParameterCommandPart(attributes, expression) + } +} + +case class ParameterCommandPart(attributes: Map[String, String], expression: WdlExpression) extends CommandPart { + def attributesToString: String = if (attributes.nonEmpty) attributes.map({case (k,v) => s"$k=${WdlString(v).toWdlString}"}).mkString(" ") + " " else "" + override def toString: String = "${" + s"$attributesToString${expression.toWdlString}" + "}" + + override def instantiate(declarations: Seq[Declaration], parameters: Map[String, WdlValue], functions: WdlFunctions[WdlValue]): String = { + val value = expression.evaluate(WdlExpression.standardLookupFunction(parameters, declarations, functions), functions) match { + case Success(v) => v + case Failure(f) => f match { + case v: VariableNotFoundException => declarations.find(_.name == v.variable) match { + /* Allow an expression to fail evaluation if one of the variables that it requires is optional (the type has ? after it, e.g. String?) */ + case Some(d) if d.postfixQuantifier.contains("?") => if (attributes.contains("default")) WdlString(attributes.get("default").head) else WdlString("") + case Some(d) => throw new UnsupportedOperationException(s"Parameter ${v.variable} is required, but no value is specified") + case None => throw new UnsupportedOperationException(s"Could not find declaration for ${v.variable}") + } + case e => throw new UnsupportedOperationException(s"Could not evaluate expression: ${expression.toWdlString}", e) + } + } + + value match { + case b: WdlBoolean if attributes.contains("true") && attributes.contains("false") => if (b.value) attributes.get("true").head else attributes.get("false").head + case p: WdlPrimitive => p.valueString + case a: WdlArray if attributes.contains("sep") => a.value.map(_.valueString).mkString(attributes.get("sep").head) + case a: WdlArray => throw new UnsupportedOperationException(s"Expression '${expression.toString}' evaluated to an Array but no 'sep' was specified") + case _ => throw new UnsupportedOperationException(s"Could not string-ify value: $value") + } + } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/command/StringCommandPart.scala b/scala/wdl4s/src/main/scala/wdl4s/command/StringCommandPart.scala new file mode 100644 index 0000000..8df9db4 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/command/StringCommandPart.scala @@ -0,0 +1,11 @@ +package wdl4s.command + +import wdl4s.expression.WdlFunctions +import wdl4s.values.WdlValue +import wdl4s.Declaration + +case class StringCommandPart(literal: String) extends CommandPart { + override def toString: String = literal + + override def instantiate(declarations: Seq[Declaration], parameters: Map[String, WdlValue], functions: WdlFunctions[WdlValue]): String = literal +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/expression/Evaluator.scala b/scala/wdl4s/src/main/scala/wdl4s/expression/Evaluator.scala new file mode 100644 index 0000000..8091cde --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/expression/Evaluator.scala @@ -0,0 +1,16 @@ +package wdl4s.expression + +import wdl4s.parser.WdlParser.AstNode + +import scala.language.postfixOps +import scala.util.Try + +trait Evaluator { + type T + type LookupFunction = String => T + type Functions = WdlFunctions[T] + def lookup: LookupFunction + def functions: Functions + def evaluate(ast: AstNode): Try[T] +} + diff --git a/scala/wdl4s/src/main/scala/wdl4s/expression/FileEvaluator.scala b/scala/wdl4s/src/main/scala/wdl4s/expression/FileEvaluator.scala new file mode 100644 index 0000000..dc463ef --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/expression/FileEvaluator.scala @@ -0,0 +1,141 @@ +package wdl4s.expression + +import wdl4s.AstTools.EnhancedAstNode +import wdl4s.WdlExpression._ +import wdl4s.WdlExpressionException +import wdl4s.types._ +import wdl4s.values._ +import wdl4s.parser.WdlParser.{Ast, AstNode} +import wdl4s.util.TryUtil + +import scala.language.postfixOps +import scala.util.{Failure, Success, Try} + +class FileEvaluatorWdlFunctions extends WdlFunctions[Seq[WdlFile]] + +/** + * This evaluator will take a WdlExpression and determine all of the static files that + * are referenced in this expression. + * + * This utilizes a ValueEvaluator to try to statically evaluate parts of the expression + * into WdlValues. See the evalValueToWdlFile() function. + * + * The coerceTo parameter is for the case where an output might be coerceable to a File. + * Consider the following output section for a task: + * + * output { + * File a = "a.txt" + * String b = "b.txt" + * } + * + * In the first case, the coerceTo would be WdlFileType and the calling evaluate("a.txt") would + * return a Seq(WdlFile("a.txt")). In the second case, coerceTo would be WdlStringType and + * evaluate("b.txt") would return Seq(). + */ +case class FileEvaluator(valueEvaluator: ValueEvaluator, coerceTo: WdlType = WdlAnyType) extends Evaluator { + override type T = Seq[WdlFile] + override def lookup = (s:String) => Seq.empty[WdlFile] + override val functions = new FileEvaluatorWdlFunctions() + + private def evalValue(ast: AstNode): Try[WdlValue] = valueEvaluator.evaluate(ast) + + private def evalValueToWdlFile(ast: AstNode): Try[WdlFile] = { + evalValue(ast) match { + case Success(p: WdlPrimitive) => Success(WdlFile(p.valueString)) + case Success(_) => Failure(new WdlExpressionException(s"Expecting a primitive type from AST:\n${ast.toPrettyString}")) + case Failure(e) => Failure(e) + } + } + + private def findWdlFiles(value: WdlValue, coerce: Boolean = true): Seq[WdlFile] = { + val coercedValue = if (coerce) coerceTo.coerceRawValue(value) else Success(value) + coercedValue match { + case Success(f: WdlFile) => Seq(f) + case Success(a: WdlArray) => + a.value.flatMap(findWdlFiles(_, coerce=false)) + case Success(m: WdlMap) => + m.value flatMap { case (k, v) => Seq(k, v) } flatMap(findWdlFiles(_, coerce=false)) toSeq + case _ => Seq.empty[WdlFile] + } + } + + override def evaluate(ast: AstNode): Try[Seq[WdlFile]] = { + /** + * First check if the top-level expression evaluates to a + * literal value. If it does, return all WdlFiles referenced + * in that literal value + */ + valueEvaluator.evaluate(ast) match { + case Success(v) => Success(findWdlFiles(v)) + case Failure(ex) => evaluateRecursive(ast) + } + } + + /** + * The pattern below is to try to evaluate all ASTs to a static value first, via + * evalValueToWdlFile(). If this succeeds, return that value. Otherwise, call + * this function recursively. + */ + private def evaluateRecursive(ast: AstNode): Try[Seq[WdlFile]] = { + ast match { + case a: Ast if a.isGlobFunctionCall => + evalValueToWdlFile(a.params.head) map { wdlFile => Seq(WdlGlobFile(wdlFile.value)) } + + case a: Ast if a.isFunctionCallWithOneFileParameter => + evalValueToWdlFile(a.params.head) match { + case Success(v) => Success(Seq(v)) + case _ => evaluateRecursive(a.params.head) + } + case a: Ast if a.isBinaryOperator => + evalValueToWdlFile(a) match { + case Success(f: WdlFile) => Success(Seq(f)) + case _ => (evaluateRecursive(a.getAttribute("lhs")), evaluateRecursive(a.getAttribute("rhs"))) match { + case (Success(a:Seq[WdlFile]), Success(b:Seq[WdlFile])) => Success(a ++ b) + case _ => Failure(new WdlExpressionException(s"Could not evaluate:\n${a.toPrettyString}")) + } + } + case a: Ast if a.isUnaryOperator => + evalValueToWdlFile(a) match { + case Success(f: WdlFile) => Success(Seq(f)) + case _ => + evaluateRecursive(a.getAttribute("expression")) match { + case Success(a:Seq[WdlFile]) => Success(a) + case _ => Failure(new WdlExpressionException(s"Could not evaluate:\n${a.toPrettyString}")) + } + } + case a: Ast if a.isArrayOrMapLookup => + evalValue(a) match { + case Success(f: WdlFile) => Success(Seq(f)) + case _ => evaluateRecursive(a.getAttribute("rhs")) + } + case a: Ast if a.isMemberAccess => + evalValue(a) match { + case Success(f: WdlFile) => Success(Seq(f)) + case _ => Success(Seq.empty[WdlFile]) + } + case a: Ast if a.isArrayLiteral => + val values = a.getAttribute("values").astListAsVector().map(evaluateRecursive) + TryUtil.sequence(values) match { + case Success(v) => Success(v.flatten) + case f => f.map(_.flatten) + } + case a: Ast if a.isMapLiteral => + val evaluatedMap = a.getAttribute("map").astListAsVector map { kv => + val key = evaluateRecursive(kv.asInstanceOf[Ast].getAttribute("key")) + val value = evaluateRecursive(kv.asInstanceOf[Ast].getAttribute("value")) + key -> value + } + + val flattenedTries = evaluatedMap flatMap { case (k,v) => Seq(k,v) } + flattenedTries partition {_.isSuccess} match { + case (_, failures) if failures.nonEmpty => + val message = failures.collect { case f: Failure[_] => f.exception.getMessage }.mkString("\n") + Failure(new WdlExpressionException(s"Could not evaluate expression:\n$message")) + case (successes, _) => + Success(successes.flatMap(_.get)) + } + case _ => Success(Seq.empty[WdlFile]) + } + } +} + diff --git a/scala/wdl4s/src/main/scala/wdl4s/expression/TypeEvaluator.scala b/scala/wdl4s/src/main/scala/wdl4s/expression/TypeEvaluator.scala new file mode 100644 index 0000000..9df02fb --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/expression/TypeEvaluator.scala @@ -0,0 +1,100 @@ +package wdl4s.expression + +import wdl4s.AstTools.EnhancedAstNode +import wdl4s.WdlExpression._ +import wdl4s.types._ +import wdl4s.{WdlExpressionException, WdlNamespace} +import wdl4s.parser.WdlParser.{Ast, AstNode, Terminal} +import wdl4s.util.TryUtil + +import scala.util.{Failure, Success, Try} + +case class TypeEvaluator(override val lookup: String => WdlType, override val functions: WdlFunctions[WdlType]) extends Evaluator { + override type T = WdlType + + override def evaluate(ast: AstNode): Try[WdlType] = ast match { + case t: Terminal if t.getTerminalStr == "identifier" => Try(lookup(t.getSourceString)) + case t: Terminal if t.getTerminalStr == "integer" => Success(WdlIntegerType) + case t: Terminal if t.getTerminalStr == "float" => Success(WdlFloatType) + case t: Terminal if t.getTerminalStr == "boolean" => Success(WdlBooleanType) + case t: Terminal if t.getTerminalStr == "string" => Success(WdlStringType) + case a: Ast if a.isBinaryOperator => + val lhs = evaluate(a.getAttribute("lhs")) + val rhs = evaluate(a.getAttribute("rhs")) + a.getName match { + case "Add" => for(l <- lhs; r <- rhs) yield l.add(r).get + case "Subtract" => for(l <- lhs; r <- rhs) yield l.subtract(r).get + case "Multiply" => for(l <- lhs; r <- rhs) yield l.multiply(r).get + case "Divide" => for(l <- lhs; r <- rhs) yield l.divide(r).get + case "Remainder" => for(l <- lhs; r <- rhs) yield l.mod(r).get + case "Equals" => for(l <- lhs; r <- rhs) yield l.equals(r).get + case "NotEquals" => for(l <- lhs; r <- rhs) yield l.notEquals(r).get + case "LessThan" => for(l <- lhs; r <- rhs) yield l.lessThan(r).get + case "LessThanOrEqual" => for(l <- lhs; r <- rhs) yield l.lessThanOrEqual(r).get + case "GreaterThan" => for(l <- lhs; r <- rhs) yield l.greaterThan(r).get + case "GreaterThanOrEqual" => for(l <- lhs; r <- rhs) yield l.greaterThanOrEqual(r).get + case "LogicalOr" => for(l <- lhs; r <- rhs) yield l.or(r).get + case "LogicalAnd" => for(l <- lhs; r <- rhs) yield l.and(r).get + case _ => Failure(new WdlExpressionException(s"Invalid operator: ${a.getName}")) + } + case a: Ast if a.isUnaryOperator => + val expression = evaluate(a.getAttribute("expression")) + a.getName match { + case "LogicalNot" => for(e <- expression) yield e.not.get + case "UnaryPlus" => for(e <- expression) yield e.unaryPlus.get + case "UnaryNegation" => for(e <- expression) yield e.unaryMinus.get + case _ => Failure(new WdlExpressionException(s"Invalid operator: ${a.getName}")) + } + case a: Ast if a.isArrayLiteral => + val evaluatedElements = a.getAttribute("values").astListAsVector map evaluate + for { + elements <- TryUtil.sequence(evaluatedElements) + subtype <- WdlType.homogeneousTypeFromTypes(elements) + } yield WdlArrayType(subtype) + case a: Ast if a.isMapLiteral => + val evaluatedMap = a.getAttribute("map").astListAsVector map { kv => + val key = evaluate(kv.asInstanceOf[Ast].getAttribute("key")) + val value = evaluate(kv.asInstanceOf[Ast].getAttribute("value")) + key -> value + } + + val flattenedTries = evaluatedMap flatMap { case (k,v) => Seq(k,v) } + flattenedTries partition {_.isSuccess} match { + case (_, failures) if failures.nonEmpty => + val message = failures.collect { case f: Failure[_] => f.exception.getMessage }.mkString("\n") + Failure(new WdlExpressionException(s"Could not evaluate expression:\n$message")) + case (successes, _) => + for { + keyType <- WdlType.homogeneousTypeFromTypes(evaluatedMap map { case (k, v) => k.get}) + valueType <- WdlType.homogeneousTypeFromTypes(evaluatedMap map { case (k, v) => v.get}) + } yield WdlMapType(keyType, valueType) + } + case a: Ast if a.isMemberAccess => + a.getAttribute("rhs") match { + case rhs: Terminal if rhs.getTerminalStr == "identifier" => + evaluate(a.getAttribute("lhs")).flatMap { + case o: WdlCallOutputsObjectType => + o.call.task.outputs.find(_.name == rhs.getSourceString) match { + case Some(taskOutput) => evaluate(taskOutput.expression.ast) + case None => Failure(new WdlExpressionException(s"Could not find key ${rhs.getSourceString}")) + } + case ns: WdlNamespace => Success(lookup(ns.importedAs.map {n => s"$n.${rhs.getSourceString}"}.getOrElse(rhs.getSourceString))) + case _ => Failure(new WdlExpressionException("Left-hand side of expression must be a WdlObject or Namespace")) + } + case _ => Failure(new WdlExpressionException("Right-hand side of expression must be identifier")) + } + case a: Ast if a.isArrayOrMapLookup => + (evaluate(a.getAttribute("lhs")), evaluate(a.getAttribute("rhs"))) match { + case (Success(a: WdlArrayType), Success(WdlIntegerType)) => Success(a.memberType) + case (Success(m: WdlMapType), Success(v: WdlType)) => Success(m.valueType) + case (Failure(ex), _) => Failure(ex) + case (_, Failure(ex)) => Failure(ex) + case (_, _) => Failure(new WdlExpressionException(s"Can't index ${a.toPrettyString}")) + } + case a: Ast if a.isFunctionCall => + val name = a.getAttribute("name").sourceString + val params = a.params map evaluate + functions.getFunction(name)(params) + } +} + diff --git a/scala/wdl4s/src/main/scala/wdl4s/expression/ValueEvaluator.scala b/scala/wdl4s/src/main/scala/wdl4s/expression/ValueEvaluator.scala new file mode 100644 index 0000000..ff7d0f2 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/expression/ValueEvaluator.scala @@ -0,0 +1,130 @@ +package wdl4s.expression + +import wdl4s.AstTools.EnhancedAstNode +import wdl4s.WdlExpression._ +import wdl4s.types._ +import wdl4s.values.{WdlValue, _} +import wdl4s.{WdlExpression, WdlExpressionException, WdlNamespace} +import wdl4s.parser.WdlParser.{Ast, AstNode, Terminal} +import wdl4s.util.TryUtil + +import scala.util.{Failure, Success, Try} + +case class ValueEvaluator(override val lookup: String => WdlValue, override val functions: WdlFunctions[WdlValue]) extends Evaluator { + override type T = WdlValue + + private def replaceInterpolationTag(string: Try[WdlString], tag: String): Try[WdlString] = { + val expr = WdlExpression.fromString(tag.substring(2, tag.length - 1)) + (expr.evaluate(lookup, functions), string) match { + case (Success(value), Success(str)) => Success(WdlString(str.value.replace(tag, value.valueString))) + case (Failure(ex), _) => Failure(ex) + case (_, Failure(ex)) => Failure(ex) + } + } + + private def interpolate(str: String): Try[WdlString] = + "\\$\\{\\s*([^\\}]*)\\s*\\}".r.findAllIn(str).foldLeft(Try(WdlString(str)))(replaceInterpolationTag) + + override def evaluate(ast: AstNode): Try[WdlValue] = { + ast match { + case t: Terminal if t.getTerminalStr == "identifier" => Try(lookup(t.getSourceString)) + case t: Terminal if t.getTerminalStr == "integer" => Success(WdlInteger(t.getSourceString.toInt)) + case t: Terminal if t.getTerminalStr == "float" => Success(WdlFloat(t.getSourceString.toDouble)) + case t: Terminal if t.getTerminalStr == "boolean" => Success(WdlBoolean(t.getSourceString == "true")) + case t: Terminal if t.getTerminalStr == "string" => interpolate(t.getSourceString) + case a: Ast if a.isBinaryOperator => + val lhs = evaluate(a.getAttribute("lhs")) + val rhs = evaluate(a.getAttribute("rhs")) + a.getName match { + case "Add" => for(l <- lhs; r <- rhs) yield l.add(r).get + case "Subtract" => for(l <- lhs; r <- rhs) yield l.subtract(r).get + case "Multiply" => for(l <- lhs; r <- rhs) yield l.multiply(r).get + case "Divide" => for(l <- lhs; r <- rhs) yield l.divide(r).get + case "Remainder" => for(l <- lhs; r <- rhs) yield l.mod(r).get + case "Equals" => for(l <- lhs; r <- rhs) yield l.equals(r).get + case "NotEquals" => for(l <- lhs; r <- rhs) yield l.notEquals(r).get + case "LessThan" => for(l <- lhs; r <- rhs) yield l.lessThan(r).get + case "LessThanOrEqual" => for(l <- lhs; r <- rhs) yield l.lessThanOrEqual(r).get + case "GreaterThan" => for(l <- lhs; r <- rhs) yield l.greaterThan(r).get + case "GreaterThanOrEqual" => for(l <- lhs; r <- rhs) yield l.greaterThanOrEqual(r).get + case "LogicalOr" => for(l <- lhs; r <- rhs) yield l.or(r).get + case "LogicalAnd" => for(l <- lhs; r <- rhs) yield l.and(r).get + case _ => Failure(new WdlExpressionException(s"Invalid operator: ${a.getName}")) + } + case a: Ast if a.isUnaryOperator => + val expression = evaluate(a.getAttribute("expression")) + a.getName match { + case "LogicalNot" => for(e <- expression) yield e.not.get + case "UnaryPlus" => for(e <- expression) yield e.unaryPlus.get + case "UnaryNegation" => for(e <- expression) yield e.unaryMinus.get + case _ => Failure(new WdlExpressionException(s"Invalid operator: ${a.getName}")) + } + case a: Ast if a.isArrayLiteral => + val evaluatedElements = a.getAttribute("values").astListAsVector map evaluate + for { + elements <- TryUtil.sequence(evaluatedElements) + subtype <- WdlType.homogeneousTypeFromValues(elements) + } yield WdlArray(WdlArrayType(subtype), elements) + case a: Ast if a.isMapLiteral => + val evaluatedMap = a.getAttribute("map").astListAsVector map { kv => + val key = evaluate(kv.asInstanceOf[Ast].getAttribute("key")) + val value = evaluate(kv.asInstanceOf[Ast].getAttribute("value")) + key -> value + } + val flattenedTries = evaluatedMap flatMap { case (k,v) => Seq(k,v) } + flattenedTries partition {_.isSuccess} match { + case (_, failures) if failures.nonEmpty => + val message = failures.collect { case f: Failure[_] => f.exception.getMessage }.mkString("\n") + Failure(new WdlExpressionException(s"Could not evaluate expression:\n$message")) + case (successes, _) => + WdlMapType(WdlAnyType, WdlAnyType).coerceRawValue(evaluatedMap.map({ case (k, v) => k.get -> v.get }).toMap) + } + case a: Ast if a.isMemberAccess => + a.getAttribute("rhs") match { + case rhs:Terminal if rhs.getTerminalStr == "identifier" => + evaluate(a.getAttribute("lhs")).flatMap { + case o: WdlObjectLike => + o.value.get(rhs.getSourceString) match { + case Some(v:WdlValue) => Success(v) + case None => Failure(new WdlExpressionException(s"Could not find key ${rhs.getSourceString}")) + } + case a: WdlArray if a.wdlType == WdlArrayType(WdlObjectType) => + /** + * This case is for slicing an Array[Object], used mainly for scatter-gather. + * For example, if 'call foo' was in a scatter block, foo's outputs (e.g. Int x) + * would be an Array[Int]. If a downstream call has an input expression "foo.x", + * then 'foo' would evaluate to an Array[Objects] and foo.x would result in an + * Array[Int] + */ + Success(a map {_.asInstanceOf[WdlObject].value.get(rhs.sourceString).get}) + case ns: WdlNamespace => Success(lookup(ns.importedAs.map {n => s"$n.${rhs.getSourceString}"}.getOrElse(rhs.getSourceString))) + case _ => Failure(new WdlExpressionException("Left-hand side of expression must be a WdlObject or Namespace")) + } + case _ => Failure(new WdlExpressionException("Right-hand side of expression must be identifier")) + } + case a: Ast if a.isArrayOrMapLookup => + val index = evaluate(a.getAttribute("rhs")) + val mapOrArray = evaluate(a.getAttribute("lhs")) + (mapOrArray, index) match { + case (Success(a: WdlArray), Success(i: WdlInteger)) => + Try(a.value(i.value)) match { + case s:Success[WdlValue] => s + case Failure(ex) => Failure(new WdlExpressionException(s"Failed to find index $index on array:\n\n$mapOrArray\n\n${ex.getMessage}")) + } + case (Success(m: WdlMap), Success(v: WdlValue)) => + m.value.get(v) match { + case Some(value) => Success(value) + case _ => Failure(new WdlExpressionException(s"Failed to find a key '$index' on a map:\n\n$mapOrArray")) + } + case (Failure(ex), _) => Failure(ex) + case (_, Failure(ex)) => Failure(ex) + case (_, _) => Failure(new WdlExpressionException(s"Can't index $mapOrArray with index $index")) + } + case a: Ast if a.isFunctionCall => + val name = a.getAttribute("name").sourceString + val params = a.params map evaluate + functions.getFunction(name)(params) + } + } +} + diff --git a/scala/wdl4s/src/main/scala/wdl4s/expression/WdlFunctions.scala b/scala/wdl4s/src/main/scala/wdl4s/expression/WdlFunctions.scala new file mode 100644 index 0000000..d8f866d --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/expression/WdlFunctions.scala @@ -0,0 +1,38 @@ +package wdl4s.expression + +import scala.language.postfixOps +import scala.util.{Failure, Try} + +trait WdlFunctions[T] { + type WdlFunction = Seq[Try[T]] => Try[T] + + /** + * Extract a single `WdlValue` from the specified `Seq`, returning `Failure` if the parameters + * represent something other than a single `WdlValue`. + */ + protected def extractSingleArgument(params: Seq[Try[T]]): Try[T] = { + if (params.length != 1) Failure(new UnsupportedOperationException("Expected one argument, got " + params.length)) + else params.head + } + + /* Returns one of the standard library functions (defined above) by name */ + def getFunction(name: String): WdlFunction = { + val method = getClass.getMethod(name, classOf[Seq[Try[T]]]) + args => method.invoke(this, args).asInstanceOf[Try[T]] + } + + /* + * Below are methods that can be overridden, if necessary, by engine implementations of the standard library + * to accommodate particularities of the engine's backend. + */ + /** + * Path where to write files created by standard functions (write_*). + */ + def tempFilePath: String = throw new NotImplementedError("write_* functions are not supported by this implementation") + + /** + * Path where to glob from when the glob standard function evaluates. + */ + def globPath(glob: String): String = throw new NotImplementedError("glob function is not supported by this implementation") +} + diff --git a/scala/wdl4s/src/main/scala/wdl4s/expression/WdlStandardLibraryFunctions.scala b/scala/wdl4s/src/main/scala/wdl4s/expression/WdlStandardLibraryFunctions.scala new file mode 100644 index 0000000..e0392ae --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/expression/WdlStandardLibraryFunctions.scala @@ -0,0 +1,134 @@ +package wdl4s.expression + +import wdl4s.types._ +import wdl4s.values._ +import wdl4s.{TsvSerializable, WdlExpressionException} + +import scala.language.postfixOps +import scala.util.{Failure, Success, Try} + +trait WdlStandardLibraryFunctions extends WdlFunctions[WdlValue] { + def fileContentsToString(path: String): String = readFile(path) + def readFile(path: String): String + def writeTempFile(path: String, prefix: String, suffix: String, content: String): String + def stdout(params: Seq[Try[WdlValue]]): Try[WdlFile] + def stderr(params: Seq[Try[WdlValue]]): Try[WdlFile] + def glob(path: String, pattern: String): Seq[String] + def write_tsv(params: Seq[Try[WdlValue]]): Try[WdlFile] + def write_json(params: Seq[Try[WdlValue]]): Try[WdlFile] + + def read_objects(params: Seq[Try[WdlValue]]): Try[WdlArray] = extractObjects(params) map { WdlArray(WdlArrayType(WdlObjectType), _) } + def read_string(params: Seq[Try[WdlValue]]): Try[WdlString] = readContentsFromSingleFileParameter(params).map(s => WdlString(s.trim)) + def read_json(params: Seq[Try[WdlValue]]): Try[WdlValue] + def read_int(params: Seq[Try[WdlValue]]): Try[WdlInteger] = read_string(params) map { s => WdlInteger(s.value.trim.toInt) } + def read_float(params: Seq[Try[WdlValue]]): Try[WdlFloat] = read_string(params) map { s => WdlFloat(s.value.trim.toDouble) } + + def write_lines(params: Seq[Try[WdlValue]]): Try[WdlFile] = writeToTsv(params, classOf[WdlArray]) + def write_map(params: Seq[Try[WdlValue]]): Try[WdlFile] = writeToTsv(params, classOf[WdlMap]) + def write_object(params: Seq[Try[WdlValue]]): Try[WdlFile] = writeToTsv(params, classOf[WdlObject]) + def write_objects(params: Seq[Try[WdlValue]]): Try[WdlFile] = writeToTsv(params, classOf[WdlArray]) + + def read_lines(params: Seq[Try[WdlValue]]): Try[WdlArray] = { + for { + contents <- readContentsFromSingleFileParameter(params) + lines = contents.split("\n") + } yield WdlArray(WdlArrayType(WdlStringType), lines map WdlString) + } + + def read_map(params: Seq[Try[WdlValue]]): Try[WdlMap] = { + for { + contents <- readContentsFromSingleFileParameter(params) + wdlMap <- WdlMap.fromTsv(contents) + } yield wdlMap + } + + def read_object(params: Seq[Try[WdlValue]]): Try[WdlObject] = { + extractObjects(params) map { + case array if array.length == 1 => array.head + case _ => throw new IllegalArgumentException("read_object yields an Object and thus can only read 2-rows TSV files. Try using read_objects instead.") + } + } + + def read_tsv(params: Seq[Try[WdlValue]]): Try[WdlArray] = { + for { + contents <- readContentsFromSingleFileParameter(params) + wdlArray = WdlArray.fromTsv(contents) + } yield wdlArray + } + + def read_boolean(params: Seq[Try[WdlValue]]): Try[WdlBoolean] = { + read_string(params) map { s => WdlBoolean(java.lang.Boolean.parseBoolean(s.value.trim.toLowerCase)) } + } + + def glob(params: Seq[Try[WdlValue]]): Try[WdlArray] = { + for { + singleArgument <- extractSingleArgument(params) + globVal = singleArgument.valueString + files = glob(globPath(globVal), globVal) + wdlFiles = files map { WdlFile(_, isGlob = false) } + } yield WdlArray(WdlArrayType(WdlFileType), wdlFiles toSeq) + } + + /** + * Asserts that the parameter list contains a single parameter which will be interpreted + * as a File and attempts to read the contents of that file and returns back the contents + * as a String + */ + private def readContentsFromSingleFileParameter(params: Seq[Try[WdlValue]]): Try[String] = { + for { + singleArgument <- extractSingleArgument(params) + string = fileContentsToString(singleArgument.valueString) + } yield string + } + + private def extractObjects(params: Seq[Try[WdlValue]]): Try[Array[WdlObject]] = for { + contents <- readContentsFromSingleFileParameter(params) + wdlObjects <- WdlObject.fromTsv(contents) + } yield wdlObjects + + private def writeContent(baseName: String, content: String): Try[WdlFile] = { + Try(WdlFile(writeTempFile(tempFilePath, s"$baseName.", ".tmp", content))) + } + + private def writeToTsv(params: Seq[Try[WdlValue]], wdlClass: Class[_ <: WdlValue with TsvSerializable]) = { + for { + singleArgument <- extractSingleArgument(params) + downcast <- Try(wdlClass.cast(singleArgument)) + tsvSerialized <- downcast.tsvSerialize + file <- writeContent(wdlClass.getSimpleName.toLowerCase, tsvSerialized) + } yield file + } +} + +class WdlStandardLibraryFunctionsType extends WdlFunctions[WdlType] { + def stdout(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlFileType) + def stderr(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlFileType) + def read_lines(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlArrayType(WdlStringType)) + def read_tsv(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlArrayType(WdlArrayType(WdlStringType))) + def read_map(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlMapType(WdlStringType, WdlStringType)) + def read_object(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlObjectType) + def read_objects(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlArrayType(WdlObjectType)) + def read_json(params: Seq[Try[WdlType]]): Try[WdlType] = Failure(new WdlExpressionException("Return type of read_json() can't be known statically")) + def read_int(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlIntegerType) + def read_string(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlStringType) + def read_float(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlFloatType) + def read_boolean(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlBooleanType) + def write_lines(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlFileType) + def write_tsv(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlFileType) + def write_map(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlFileType) + def write_object(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlFileType) + def write_objects(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlFileType) + def write_json(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlFileType) + def glob(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlArrayType(WdlFileType)) +} + +case object NoFunctions extends WdlStandardLibraryFunctions { + override def glob(path: String, pattern: String): Seq[String] = throw new NotImplementedError() + override def readFile(path: String): String = throw new NotImplementedError() + override def writeTempFile(path: String, prefix: String, suffix: String, content: String): String = throw new NotImplementedError() + override def stdout(params: Seq[Try[WdlValue]]): Try[WdlFile] = Failure(new NotImplementedError()) + override def stderr(params: Seq[Try[WdlValue]]): Try[WdlFile] = Failure(new NotImplementedError()) + override def read_json(params: Seq[Try[WdlValue]]): Try[WdlValue] = Failure(new NotImplementedError()) + override def write_tsv(params: Seq[Try[WdlValue]]): Try[WdlFile] = Failure(new NotImplementedError()) + override def write_json(params: Seq[Try[WdlValue]]): Try[WdlFile] = Failure(new NotImplementedError()) +} \ No newline at end of file diff --git a/scala/wdl4s/src/main/scala/wdl4s/formatter/SyntaxFormatter.scala b/scala/wdl4s/src/main/scala/wdl4s/formatter/SyntaxFormatter.scala new file mode 100644 index 0000000..fb2e467 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/formatter/SyntaxFormatter.scala @@ -0,0 +1,167 @@ +package wdl4s.formatter + +import wdl4s.AstTools.EnhancedAstNode +import wdl4s._ +import wdl4s.command.StringCommandPart +import wdl4s.types.WdlType +import wdl4s.parser.WdlParser.{Ast, AstList, AstNode} +import wdl4s.util.TerminalUtil + +import scala.collection.JavaConverters._ + +trait SyntaxHighlighter { + def keyword(s: String): String = s + def name(s: String): String = s + def section(s: String): String = s + def wdlType(t: WdlType): String = t.toWdlString + def variable(s: String): String = s + def alias(s: String): String = s + def command(s: String): String = s + def function(s: String): String = s +} + +object NullSyntaxHighlighter extends SyntaxHighlighter + +object AnsiSyntaxHighlighter extends SyntaxHighlighter { + override def keyword(s: String): String = TerminalUtil.highlight(214, s) + override def name(s: String): String = TerminalUtil.highlight(253, s) + override def section(s: String): String = keyword(s) + override def wdlType(t: WdlType): String = TerminalUtil.highlight(33, t.toWdlString) + override def variable(s: String): String = TerminalUtil.highlight(112, s) + override def alias(s: String): String = s + override def command(s: String): String = s + override def function(s: String): String = TerminalUtil.highlight(13, s) +} + +object HtmlSyntaxHighlighter extends SyntaxHighlighter { + def wrap(s: String, cls: String) = s"""$s""" + override def keyword(s: String): String = wrap(s, "keyword") + override def name(s: String): String = wrap(s, "name") + override def section(s: String): String = wrap(s, "section") + override def wdlType(t: WdlType): String = wrap(t.toWdlString, "type") + override def variable(s: String): String = wrap(s, "variable") + override def alias(s: String): String = wrap(s, "alias") + override def command(s: String): String = wrap(s, "command") + override def function(s: String): String = wrap(s, "function") +} + +class SyntaxFormatter(highlighter: SyntaxHighlighter = NullSyntaxHighlighter) { + val indentLevel = 2 + + private def indent(s: String, i: Int): String = { + s.split("\n").map {" " * (i * indentLevel) + _}.mkString("\n") + } + + def format(namespace: WdlNamespace): String = { + val imports = namespace.imports.map(formatImport) match { + case v if v.nonEmpty => v.mkString("\n") + "\n\n" + case v => "" + } + + /* + TODO/FIXME: If 'definitions' is really a function of `namespace` then `WdlNamespace should have a func which + does the first part, and `NamespaceWithWorkflow` override it, call super and then adds on the second part + */ + + val namespaceDefinitions = namespace.ast.getAttribute("definitions").asInstanceOf[AstList].asScala.toVector + + val taskDefinitions = namespaceDefinitions collect { case a: Ast if a.getName == "Task" => + formatTask(namespace.findTask(a.getAttribute("name").sourceString).getOrElse(throw new UnsupportedOperationException("Shouldn't happen"))) + } + + val workflowDefinitions = namespace match { + case n: NamespaceWithWorkflow => namespaceDefinitions collect {case a: Ast if a.getName == "Workflow" => formatWorkflow(n.workflow)} + case _ => Vector.empty[AstNode] + } + val definitions = taskDefinitions ++ workflowDefinitions + + s"$imports${definitions.mkString("\n\n")}" + } + + private def formatImport(imp: Import): String = { + val namespace = imp.namespace.map{ns => s" as $ns"}.getOrElse("") + s"${highlighter.keyword("import")} '${imp.uri}'$namespace" + } + + private def formatTask(task: Task): String = { + val outputs = if (task.outputs.nonEmpty) formatOutputs(task.outputs, 1) else "" + val command = formatCommandSection(task, 1) + val declarations = task.declarations.map(formatDeclaration(_, 1)) match { + case x: Seq[String] if x.nonEmpty => x.mkString("\n") + case _ => "" + } + val sections = List(declarations, command, outputs).filter(_.nonEmpty) + val header = s"""${highlighter.keyword("task")} ${highlighter.name(task.name)} { + |${sections.mkString("\n")} + |}""" + .stripMargin + header + } + + private def formatCommandSection(task: Task, level:Int): String = { + val (sdelim: String, edelim: String) = + if (task.commandTemplate.collect({case s:StringCommandPart => s.literal}).mkString.contains("}")) ("<<<", ">>>") + else ("{", "}") + + val section = s"""${highlighter.section("command")} $sdelim + |${indent(highlighter.command(task.commandTemplateString), 1)} + |$edelim""" + indent(section.stripMargin, level) + } + + private def formatOutputs(outputs: Seq[TaskOutput], level:Int): String = { + val section = s"""${highlighter.section("output")} { + |${outputs.map(formatOutput(_, 1)).mkString("\n")} + |}""" + indent(section.stripMargin, level) + } + + private def formatOutput(output: TaskOutput, level:Int): String = { + indent(s"${highlighter.wdlType(output.wdlType)} ${highlighter.variable(output.name)} = ${output.expression.toString(highlighter)}", level) + } + + private def formatWorkflow(workflow: Workflow): String = { + val declarations = workflow.declarations.map(formatDeclaration(_, 1)) + val children = workflow.children.map(formatScope(_, 1)) + val sections = (declarations ++ children).filter(_.nonEmpty) + s"""${highlighter.keyword("workflow")} ${highlighter.name(workflow.unqualifiedName)} { + |${sections.mkString("\n")} + |}""".stripMargin + } + + private def formatDeclaration(decl: Declaration, level: Int): String = { + val expression = decl.expression.map(e => s" = ${e.toWdlString}").getOrElse("") + indent(s"${highlighter.wdlType(decl.wdlType)} ${highlighter.variable(decl.name)}$expression", level) + } + + private def formatScope(scope: Scope, level: Int): String = scope match { + case c: Call => formatCall(c, level) + case s: Scatter => formatScatter(s, level) + } + + private def formatCall(call: Call, level: Int): String = { + val header = s"${highlighter.keyword("call")} ${highlighter.name(call.task.name)}${formatCallAlias(call)}" + if (call.inputMappings.isEmpty) { + indent(header, level) + } else { + val inputString = call.inputMappings.map {case (k, v) => + s"$k=${v.toString(highlighter)}" + }.mkString(", ") + indent(s"""$header { + | input: $inputString + |}""".stripMargin, level) + } + } + + private def formatScatter(scatter: Scatter, level: Int): String = { + val children = scatter.children.map(formatScope(_, 1)) + indent( + s"""${highlighter.keyword("scatter")} (${scatter.item} in ${scatter.collection.toString(highlighter)}) { + |${children.mkString("\n")} + |}""".stripMargin, level) + } + + private def formatCallAlias(call: Call): String = { + call.alias.map {a => s" as ${highlighter.alias(a)}"}.getOrElse("") + } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/package.scala b/scala/wdl4s/src/main/scala/wdl4s/package.scala new file mode 100644 index 0000000..0a0e22f --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/package.scala @@ -0,0 +1,19 @@ +import wdl4s.values.WdlValue + +import scala.util.Try + +package object wdl4s { + type WdlSource = String + type WdlJson = String + type WorkflowRawInputs = Map[FullyQualifiedName, Any] + type WorkflowCoercedInputs = Map[FullyQualifiedName, WdlValue] + type FullyQualifiedName = String + type LocallyQualifiedName = String + type CallInputs = Map[String, WdlValue] + type ImportResolver = String => WdlSource + + trait TsvSerializable { + def tsvSerialize: Try[String] + } + +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/types/WdlAnyType.scala b/scala/wdl4s/src/main/scala/wdl4s/types/WdlAnyType.scala new file mode 100644 index 0000000..ca51175 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/types/WdlAnyType.scala @@ -0,0 +1,47 @@ +package wdl4s.types + +case object WdlAnyType extends WdlType { + val toWdlString: String = s"Any" + + /** + * WdlAnyType does behave slightly differently than the other WdlTypes. + * For something like WdlInteger, the coercion function acts as follows: + * + * "Give me one of x different type of objects that are compatible with + * WdlInteger, and I'll give you a WdlInteger back" + * + * WdlAnyType's coercion semantics are more like: + * + * "Give me anything at all, and I've got a heuristic that will return you + * the WdlValue that's able to accept this Any value." + * + * So you give this coercion() function a String "foobar", and it returns + * WdlString("foobar"), you give it a JsNumber(2), it returns WdlInteger(2). + * + * This is used when evaluating literal expressions. For example, a user + * might do this in source code: + * + *
+   * Map[Int, String] first = {"foo": 2, 3.14: "bar"}
+   * Map[Float, String] second = {"foo": "bar"}
+   * 
+ * + * When we're simply parsing the expression {"foo": 2, 3.14: "bar"}, there + * are two levels of coercion happening. First it just tries to coerce this + * into ANY Map[K, V] type. It uses Map[Any, Any].coerce for that. The first + * example above would fail this coercion stage. The second would pass and + * return a Map[String, String]. + * + * The second coercion that happens is the coercion to the target type. + * In the example above, second starts out coerced as Map[String, String], + * and then it is Map[Float, String].coerce is called on that map. This step + * should fail at this stage. + */ + override protected def coercion = { + case any: Any => + /* This does throw an exception if it couldn't coerce (.get is intentional) */ + WdlType.wdlTypeCoercionOrder.map(_.coerceRawValue(any)).find(_.isSuccess).getOrElse( + throw new UnsupportedOperationException(s"Could not coerce $any into a WDL type") + ).get + } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/types/WdlArrayType.scala b/scala/wdl4s/src/main/scala/wdl4s/types/WdlArrayType.scala new file mode 100644 index 0000000..5f6deb9 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/types/WdlArrayType.scala @@ -0,0 +1,55 @@ +package wdl4s.types + +import wdl4s.values.{WdlArray, WdlFile, WdlString, WdlValue} +import wdl4s.util.TryUtil +import spray.json.JsArray + +import scala.util.{Failure, Success} + +case class WdlArrayType(memberType: WdlType) extends WdlType { + val toWdlString: String = s"Array[${memberType.toWdlString}]" + + private def coerceIterable(values: Seq[Any]): WdlArray = values match { + case s:Seq[Any] if s.nonEmpty => + val coerced = s.map {memberType.coerceRawValue(_).get} + WdlArray(WdlArrayType(coerced.head.wdlType), coerced) + case _ => WdlArray(WdlArrayType(memberType), Seq()) + } + + override protected def coercion = { + case s: Seq[Any] => coerceIterable(s) + case js: JsArray => coerceIterable(js.elements) + case wdlArray: WdlArray if wdlArray.wdlType.memberType == WdlStringType && memberType == WdlFileType => + WdlArray(WdlArrayType(WdlFileType), wdlArray.value.map(str => WdlFile(str.asInstanceOf[WdlString].value)).toList) + case wdlArray: WdlArray if wdlArray.wdlType.memberType == memberType => wdlArray + case wdlArray: WdlArray if wdlArray.wdlType.memberType == WdlAnyType => coerceIterable(wdlArray.value) + case wdlArray: WdlArray if wdlArray.wdlType.memberType.isInstanceOf[WdlArrayType] && memberType.isInstanceOf[WdlArrayType] => + TryUtil.sequence(wdlArray.value.map(memberType.coerceRawValue)) match { + case Success(values) => WdlArray(WdlArrayType(memberType), values) + case Failure(ex) => throw ex + } + case wdlArray: WdlArray if memberType.isCoerceableFrom(wdlArray.wdlType.memberType) => + wdlArray.map(v => memberType.coerceRawValue(v).get) // .get because isCoerceableFrom should make it safe + case wdlValue: WdlValue if memberType.isCoerceableFrom(wdlValue.wdlType) => + memberType.coerceRawValue(wdlValue) match { + case Success(coercedValue) => WdlArray(this, Seq(coercedValue)) + case Failure(ex) => throw ex + } + } + + override def isCoerceableFrom(otherType: WdlType): Boolean = otherType match { + case a: WdlArrayType => memberType.isCoerceableFrom(a.memberType) + case _ => false + } +} + +object WdlArrayType { + + implicit class WdlArrayEnhanced(wdlType: WdlType) extends WdlType { + + override protected def coercion: PartialFunction[Any, WdlValue] = wdlType.coercion + override def toWdlString: String = wdlType.toWdlString + + def isAnArrayOf(genericType: WdlType) = wdlType.isInstanceOf[WdlArrayType] && wdlType.asInstanceOf[WdlArrayType].memberType == genericType + } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/types/WdlBooleanType.scala b/scala/wdl4s/src/main/scala/wdl4s/types/WdlBooleanType.scala new file mode 100644 index 0000000..e243100 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/types/WdlBooleanType.scala @@ -0,0 +1,34 @@ +package wdl4s.types + +import wdl4s.values.WdlBoolean +import spray.json.{JsBoolean, JsString} + +import scala.util.{Try, Success} + +case object WdlBooleanType extends WdlPrimitiveType { + val toWdlString: String = "Boolean" + + override protected def coercion = { + case b: Boolean => WdlBoolean(b) + case s: String if s.equalsIgnoreCase("true") => WdlBoolean.True + case s: String if s.equalsIgnoreCase("false") => WdlBoolean.False + case s: JsString if s.value.equalsIgnoreCase("true") => WdlBoolean.True + case s: JsString if s.value.equalsIgnoreCase("false") => WdlBoolean.False + case s: JsBoolean => WdlBoolean(s.value) + case b: WdlBoolean => b + } + + override def fromWdlString(rawString: String) = WdlBoolean(rawString.toBoolean) + + private def comparisonOperator(rhs: WdlType, symbol: String): Try[WdlType] = rhs match { + case WdlBooleanType => Success(WdlBooleanType) + case _ => invalid(s"$this $symbol $rhs") + } + + override def equals(rhs: WdlType): Try[WdlType] = comparisonOperator(rhs, "==") + override def lessThan(rhs: WdlType): Try[WdlType] = comparisonOperator(rhs, "<") + override def greaterThan(rhs: WdlType): Try[WdlType] = comparisonOperator(rhs, ">") + override def or(rhs: WdlType): Try[WdlType] = comparisonOperator(rhs, "||") + override def and(rhs: WdlType): Try[WdlType] = comparisonOperator(rhs, "&&") + override def not: Try[WdlType] = Success(WdlBooleanType) +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/types/WdlExpressionType.scala b/scala/wdl4s/src/main/scala/wdl4s/types/WdlExpressionType.scala new file mode 100644 index 0000000..4e9f9ca --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/types/WdlExpressionType.scala @@ -0,0 +1,13 @@ +package wdl4s.types + +import wdl4s.WdlExpression + +case object WdlExpressionType extends WdlType { + override def toWdlString: String = "Expression" + + override protected def coercion = { + case s: String if s.startsWith("%expr:") => WdlExpression.fromString(s.replace("%expr:", "")) + } + + override def fromWdlString(rawString: String) = WdlExpression.fromString(rawString) +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/types/WdlFileType.scala b/scala/wdl4s/src/main/scala/wdl4s/types/WdlFileType.scala new file mode 100644 index 0000000..8abac83 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/types/WdlFileType.scala @@ -0,0 +1,28 @@ +package wdl4s.types + +import wdl4s.values.{WdlFile, WdlString} +import spray.json.JsString + +import scala.util.{Success, Try} + +case object WdlFileType extends WdlPrimitiveType { + val toWdlString: String = "File" + + override protected def coercion = { + case s: String => WdlFile(s) + case s: JsString => WdlFile(s.value) + case s: WdlString => WdlFile(s.valueString) + case f: WdlFile => f + } + + override def add(rhs: WdlType): Try[WdlType] = rhs match { + case WdlStringType => Success(WdlFileType) + case _ => invalid(s"$this + $rhs") + } + + override def equals(rhs: WdlType): Try[WdlType] = rhs match { + case WdlFileType => Success(WdlBooleanType) + case WdlStringType => Success(WdlBooleanType) + case _ => invalid(s"$this == $rhs") + } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/types/WdlFloatType.scala b/scala/wdl4s/src/main/scala/wdl4s/types/WdlFloatType.scala new file mode 100644 index 0000000..9434749 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/types/WdlFloatType.scala @@ -0,0 +1,48 @@ +package wdl4s.types + +import wdl4s.values.{WdlFloat, WdlString} +import spray.json.{JsNumber, JsString} + +import scala.util.{Try, Success} + +case object WdlFloatType extends WdlPrimitiveType { + val toWdlString: String = "Float" + + override protected def coercion = { + case d: Double => WdlFloat(d) + case n: JsNumber => WdlFloat(n.value.doubleValue()) + case f: WdlFloat => f + case s: String => WdlFloat(s.toDouble) + case s: JsString => WdlFloat(s.value.toDouble) + case s: WdlString => WdlFloat(s.value.toDouble) + } + + override def fromWdlString(rawString: String) = WdlFloat(rawString.toFloat) + + private def binaryOperator(rhs: WdlType, symbol: String): Try[WdlType] = rhs match { + case WdlIntegerType => Success(WdlFloatType) + case WdlFloatType => Success(WdlFloatType) + case _ => invalid(s"$this $symbol $rhs") + } + + private def comparisonOperator(rhs: WdlType, symbol: String): Try[WdlType] = rhs match { + case WdlIntegerType => Success(WdlBooleanType) + case WdlFloatType => Success(WdlBooleanType) + case _ => invalid(s"$this $symbol $rhs") + } + + override def add(rhs: WdlType): Try[WdlType] = rhs match { + case WdlStringType => Success(WdlStringType) + case t => binaryOperator(t, "+") + } + + override def subtract(rhs: WdlType): Try[WdlType] = binaryOperator(rhs, "-") + override def multiply(rhs: WdlType): Try[WdlType] = binaryOperator(rhs, "*") + override def divide(rhs: WdlType): Try[WdlType] = binaryOperator(rhs, "/") + override def mod(rhs: WdlType): Try[WdlType] = binaryOperator(rhs, "%") + override def equals(rhs: WdlType): Try[WdlType] = comparisonOperator(rhs, "==") + override def lessThan(rhs: WdlType): Try[WdlType] = comparisonOperator(rhs, "<") + override def greaterThan(rhs: WdlType): Try[WdlType] = comparisonOperator(rhs, ">") + override def unaryPlus: Try[WdlType] = Success(WdlFloatType) + override def unaryMinus: Try[WdlType] = Success(WdlFloatType) +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/types/WdlIntegerType.scala b/scala/wdl4s/src/main/scala/wdl4s/types/WdlIntegerType.scala new file mode 100644 index 0000000..3eada4a --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/types/WdlIntegerType.scala @@ -0,0 +1,49 @@ +package wdl4s.types + +import wdl4s.values.{WdlInteger, WdlString} +import spray.json.{JsNumber, JsString} + +import scala.util.{Try, Success} + +case object WdlIntegerType extends WdlPrimitiveType { + val toWdlString: String = "Int" + + override protected def coercion = { + case i: Integer => WdlInteger(i) + case n: JsNumber => WdlInteger(n.value.intValue()) + case i: WdlInteger => i + case s: WdlString => WdlInteger(s.value.toInt) + case s: String => WdlInteger(s.toInt) + case s: JsString => WdlInteger(s.value.toInt) + } + + override def fromWdlString(rawString: String) = WdlInteger(rawString.toInt) + + private def binaryOperator(rhs: WdlType, symbol: String): Try[WdlType] = rhs match { + case WdlIntegerType => Success(WdlIntegerType) + case WdlFloatType => Success(WdlFloatType) + case _ => invalid(s"$this $symbol $rhs") + } + + private def comparisonOperator(rhs: WdlType, symbol: String): Try[WdlType] = rhs match { + case WdlIntegerType => Success(WdlBooleanType) + case WdlFloatType => Success(WdlBooleanType) + case _ => invalid(s"$this $symbol $rhs") + } + + + override def add(rhs: WdlType): Try[WdlType] = rhs match { + case WdlStringType => Success(WdlStringType) + case t => binaryOperator(t, "+") + } + + override def subtract(rhs: WdlType): Try[WdlType] = binaryOperator(rhs, "-") + override def multiply(rhs: WdlType): Try[WdlType] = binaryOperator(rhs, "*") + override def divide(rhs: WdlType): Try[WdlType] = binaryOperator(rhs, "/") + override def mod(rhs: WdlType): Try[WdlType] = binaryOperator(rhs, "%") + override def equals(rhs: WdlType): Try[WdlType] = comparisonOperator(rhs, "==") + override def lessThan(rhs: WdlType): Try[WdlType] = comparisonOperator(rhs, "<") + override def greaterThan(rhs: WdlType): Try[WdlType] = comparisonOperator(rhs, ">") + override def unaryPlus: Try[WdlType] = Success(WdlIntegerType) + override def unaryMinus: Try[WdlType] = Success(WdlIntegerType) +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/types/WdlMapType.scala b/scala/wdl4s/src/main/scala/wdl4s/types/WdlMapType.scala new file mode 100644 index 0000000..af01891 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/types/WdlMapType.scala @@ -0,0 +1,22 @@ +package wdl4s.types + +import wdl4s.values._ +import spray.json.JsObject + +case class WdlMapType(keyType: WdlType, valueType: WdlType) extends WdlType { + val toWdlString: String = s"Map[${keyType.toWdlString}, ${valueType.toWdlString}]" + + override protected def coercion = { + case m: Map[_, _] if m.nonEmpty => WdlMap.coerceMap(m, this) + case m: Map[_, _] if m.isEmpty => WdlMap(WdlMapType(keyType, valueType), Map()) + case js: JsObject if js.fields.nonEmpty => WdlMap.coerceMap(js.fields, this) + case wdlMap: WdlMap => WdlMap.coerceMap(wdlMap.value, this) + case o: WdlObject => WdlMap.coerceMap(o.value, this) + } + + override def isCoerceableFrom(otherType: WdlType): Boolean = otherType match { + case m: WdlMapType => keyType.isCoerceableFrom(m.keyType) && valueType.isCoerceableFrom(m.valueType) + case WdlObjectType => keyType.isCoerceableFrom(WdlStringType) && valueType.isCoerceableFrom(WdlStringType) + case _ => false + } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/types/WdlNamespaceType.scala b/scala/wdl4s/src/main/scala/wdl4s/types/WdlNamespaceType.scala new file mode 100644 index 0000000..bfe5d62 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/types/WdlNamespaceType.scala @@ -0,0 +1,13 @@ +package wdl4s.types + +import wdl4s.WdlNamespace + +case object WdlNamespaceType extends WdlType { + override def toWdlString: String = "Namespace" + + override protected def coercion = { + case n: WdlNamespace => n + } + + override def fromWdlString(rawString: String) = ??? +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/types/WdlObjectType.scala b/scala/wdl4s/src/main/scala/wdl4s/types/WdlObjectType.scala new file mode 100644 index 0000000..74f1cfd --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/types/WdlObjectType.scala @@ -0,0 +1,58 @@ +package wdl4s.types + +import wdl4s.Call +import wdl4s.values._ +import spray.json.JsObject + +import scala.util.{Try, Failure, Success} + +case object WdlObjectType extends WdlType { + val toWdlString: String = "Object" + + private def handleCoercionFailures(tries: Try[_]*) = { + val errorMessages = tries collect { + case Failure(f) => f.getMessage + } mkString "," + + throw new UnsupportedOperationException(s"Coercion failed: $errorMessages") + } + + override protected def coercion = { + case o: WdlObject => o + case m: WdlMap if isMapCoercable(m) => + val coercedMap = m.value map { + case (k, v) => toWdlString(k) -> toWdlString(v) + } collect { + case (Success(k), Success(v)) => k.value -> v + case (k, v) => handleCoercionFailures(k, v) + } + + WdlObject(coercedMap) + case js: JsObject => + val coercedMap = WdlMap.coerceMap(js.fields, WdlMapType(WdlStringType, WdlAnyType)).value map { + // get is safe because coerceMap above would have failed already if k was not coerceable to WdlString + case (k, v) => toWdlString(k).get.value -> v + } + + WdlObject(coercedMap) + } + + private def toWdlString(v: WdlValue) = WdlStringType.coerceRawValue(v).map(_.asInstanceOf[WdlString]) + + override def isCoerceableFrom(otherType: WdlType) = otherType match { + case WdlObjectType => true + case t: WdlMapType if isMapTypeCoercable(t) => true + case _ => false + } + + def isMapTypeCoercable(t: WdlMapType) = WdlStringType.isCoerceableFrom(t.keyType) && WdlStringType.isCoerceableFrom(t.valueType) + def isMapCoercable(m: WdlMap) = isMapTypeCoercable(m.wdlType) +} + +case class WdlCallOutputsObjectType(call: Call) extends WdlType { + val toWdlString: String = "Object" + + override protected def coercion = { + case o: WdlCallOutputsObject => o + } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/types/WdlPrimitiveType.scala b/scala/wdl4s/src/main/scala/wdl4s/types/WdlPrimitiveType.scala new file mode 100644 index 0000000..104ba3a --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/types/WdlPrimitiveType.scala @@ -0,0 +1,20 @@ +package wdl4s.types + +abstract class WdlPrimitiveType extends WdlType { + lazy val coercionMap: Map[WdlType, Seq[WdlType]] = Map( + // From type -> To type + WdlStringType -> Seq(WdlStringType, WdlIntegerType, WdlFloatType, WdlFileType, WdlBooleanType), + WdlFileType -> Seq(WdlStringType, WdlFileType), + WdlIntegerType -> Seq(WdlStringType, WdlIntegerType, WdlFloatType), + WdlFloatType -> Seq(WdlStringType, WdlFloatType), + WdlBooleanType -> Seq(WdlStringType, WdlBooleanType) + ) + + override def isCoerceableFrom(otherType: WdlType): Boolean = { + coercionMap.get(otherType) match { + case Some(types) => types contains this + case None => false + } + } +} + diff --git a/scala/wdl4s/src/main/scala/wdl4s/types/WdlStringType.scala b/scala/wdl4s/src/main/scala/wdl4s/types/WdlStringType.scala new file mode 100644 index 0000000..62a2d67 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/types/WdlStringType.scala @@ -0,0 +1,31 @@ +package wdl4s.types + +import wdl4s.values.{WdlFile, WdlFloat, WdlInteger, WdlString} +import spray.json.JsString + +import scala.util.{Try, Success} + +case object WdlStringType extends WdlPrimitiveType { + val toWdlString: String = "String" + + override protected def coercion = { + case s: String => WdlString(s) + case s: JsString => WdlString(s.value) + case s: WdlString => s + case f: WdlFile => WdlString(f.value) + } + + private def comparisonOperator(rhs: WdlType, symbol: String): Try[WdlType] = rhs match { + case WdlStringType => Success(WdlBooleanType) + case _ => invalid(s"$this $symbol $rhs") + } + + override def add(rhs: WdlType): Try[WdlType] = rhs match { + case WdlStringType | WdlIntegerType | WdlFloatType | WdlFileType => Success(WdlStringType) + case _ => invalid(s"$this + $rhs") + } + + override def equals(rhs: WdlType): Try[WdlType] = comparisonOperator(rhs, "==") + override def lessThan(rhs: WdlType): Try[WdlType] = comparisonOperator(rhs, "<") + override def greaterThan(rhs: WdlType): Try[WdlType] = comparisonOperator(rhs, ">") +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/types/WdlType.scala b/scala/wdl4s/src/main/scala/wdl4s/types/WdlType.scala new file mode 100644 index 0000000..692ced4 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/types/WdlType.scala @@ -0,0 +1,123 @@ +package wdl4s.types + +import wdl4s.AstTools.EnhancedAstNode +import wdl4s.values._ +import wdl4s.{WdlExpressionException, WdlSource, WdlSyntaxErrorFormatter} +import wdl4s.parser.WdlParser + +import scala.collection.JavaConverters._ +import scala.util.{Failure, Success, Try} + +class WdlTypeException(message: String) extends RuntimeException(message) + +trait WdlType { + + /** + * Method to be overridden by implementation classes defining a partial function + * for the conversion of raw input values to specific implementation class value types. + * i.e. `WdlBooleanType` should define a partial function that knows how to + * construct `WdlBoolean`s for inputs of supported types and contents. Values for which + * the partial function is not defined are assumed to not be convertible to the target type. + */ + protected def coercion: PartialFunction[Any, WdlValue] + + /** + * Public interface for a `Try`-wrapped conversion of an input of type `Any` to + * a `WdlValue`. + */ + def coerceRawValue(any: Any): Try[WdlValue] = { + any match { + case v: WdlValue if v.wdlType == this => Success(v) + case a if !coercion.isDefinedAt(any) => Failure(new IllegalArgumentException(s"No coercion defined from '$any' of type '${any.getClass}' to ${getClass.getSimpleName}.")) + case _ => Try(coercion(any)) + } + } + + def isCoerceableFrom(otherType: WdlType): Boolean = false + + def toWdlString: String + + /** + * Converts WDL source into a WdlValue of this type, if possible. + * + * @param wdlSource source code representing the WdlValue + * @return The WdlValue + */ + //TODO: return a Try ? + def fromWdlString(wdlSource: WdlSource): WdlValue = { + val tokens = WdlType.parser.lex(wdlSource, "string") + val terminalMap = tokens.asScala.toVector.map {(_, wdlSource)}.toMap + val wdlSyntaxErrorFormatter = new WdlSyntaxErrorFormatter(terminalMap) + + /* Parsing as an expression is not sufficient... only a subset of these + * ASTs are valid as WdlValues and this distinction is done in the + * .wdlValue() method. + */ + val ast = WdlType.parser.parse_e(tokens, wdlSyntaxErrorFormatter).toAst + + ast.wdlValue(this, wdlSyntaxErrorFormatter) + } + + def invalid(operation: String) = Failure(new WdlExpressionException(s"Cannot perform operation: $operation")) + def add(rhs: WdlType): Try[WdlType] = invalid(s"$this + $rhs") + def subtract(rhs: WdlType): Try[WdlType] = invalid(s"$this - $rhs") + def multiply(rhs: WdlType): Try[WdlType] = invalid(s"$this * $rhs") + def divide(rhs: WdlType): Try[WdlType] = invalid(s"$this / $rhs") + def mod(rhs: WdlType): Try[WdlType] = invalid(s"$this % $rhs") + def equals(rhs: WdlType): Try[WdlType] = invalid(s"$this == $rhs") + def notEquals(rhs: WdlType): Try[WdlType] = equals(rhs) map {x => WdlBooleanType} + def lessThan(rhs: WdlType): Try[WdlType] = invalid(s"$this < $rhs") + def lessThanOrEqual(rhs: WdlType): Try[WdlType] = (lessThan(rhs), equals(rhs)) match { + case (Success(b:WdlType), _) if b == WdlBooleanType => Success(WdlBooleanType) + case (_, Success(b:WdlType)) if b == WdlBooleanType => Success(WdlBooleanType) + case (_, _) => invalid(s"$this <= $rhs") + } + def greaterThan(rhs: WdlType): Try[WdlType] = invalid(s"$this > $rhs") + def greaterThanOrEqual(rhs: WdlType): Try[WdlType] = (greaterThan(rhs), equals(rhs)) match { + case (Success(b:WdlType), _) if b == WdlBooleanType => Success(WdlBooleanType) + case (_, Success(b:WdlType)) if b == WdlBooleanType => Success(WdlBooleanType) + case (_, _) => invalid(s"$this >= $rhs") + } + def or(rhs: WdlType): Try[WdlType] = invalid(s"$this || $rhs") + def and(rhs: WdlType): Try[WdlType] = invalid(s"$this && $rhs") + def not: Try[WdlType] = invalid(s"!$this") + def unaryPlus: Try[WdlType] = invalid(s"+$this") + def unaryMinus: Try[WdlType] = invalid(s"-$this") +} + +object WdlType { + val parser = new WdlParser() + + /* This is in the order of coercion from non-wdl types */ + val wdlTypeCoercionOrder: Seq[WdlType] = Seq( + WdlStringType, WdlIntegerType, WdlFloatType, WdlMapType(WdlAnyType, WdlAnyType), + WdlArrayType(WdlAnyType), WdlBooleanType, WdlObjectType + ) + + def homogeneousTypeFromValues(values: Iterable[WdlValue]): Try[WdlType] = + homogeneousTypeFromTypes(values.map(_.wdlType)) + + def homogeneousTypeFromTypes(types: Iterable[WdlType]): Try[WdlType] = { + types.toSet match { + case s if s.isEmpty => Failure(new WdlTypeException(s"Can't have empty Array/Map declarations (can't infer type)")) + case s if s.size == 1 => Success(s.head) + case _ => Failure(new WdlTypeException("Arrays/Maps must have homogeneous types")) + } + } + + + def fromWdlString(wdlString: String): WdlType = { + wdlString match { + case "Expression" => WdlExpressionType + case _ => + val tokens = parser.lex(wdlString, "string") + val terminalMap = tokens.asScala.toVector.map {(_, wdlString)}.toMap + val wdlSyntaxErrorFormatter = new WdlSyntaxErrorFormatter(terminalMap) + + /* parse_type_e() is the parse function for the $type_e nonterminal in grammar.hgr */ + val ast = parser.parse_type_e(tokens, wdlSyntaxErrorFormatter).toAst + + ast.wdlType(wdlSyntaxErrorFormatter) + } + } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/types/WdlTypeJsonFormatter.scala b/scala/wdl4s/src/main/scala/wdl4s/types/WdlTypeJsonFormatter.scala new file mode 100644 index 0000000..06661fb --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/types/WdlTypeJsonFormatter.scala @@ -0,0 +1,22 @@ +package wdl4s.types + +import wdl4s.{FullyQualifiedName, WorkflowInput} +import spray.json._ + +object WdlTypeJsonFormatter extends DefaultJsonProtocol { + implicit object WdlTypeJsonFormat extends RootJsonFormat[WdlType] { + def write(wdlType: WdlType) = JsString(wdlType.toWdlString) + def read(value: JsValue) = ??? + } + + implicit object WorkflowInputJsonFormat extends RootJsonFormat[Map[FullyQualifiedName, WorkflowInput]] { + def write(inputs: Map[FullyQualifiedName, WorkflowInput]) = { + JsObject(inputs map { case (fqn, input) => + val optional = if (input.optional) "(optional) " else "" + fqn -> JsString(s"$optional${input.wdlType.toWdlString}") + }) + } + def read(value: JsValue) = ??? + } +} + diff --git a/scala/wdl4s/src/main/scala/wdl4s/util/FileUtil.scala b/scala/wdl4s/src/main/scala/wdl4s/util/FileUtil.scala new file mode 100644 index 0000000..e7a3558 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/util/FileUtil.scala @@ -0,0 +1,24 @@ +package wdl4s.util + +import java.io.File +import better.files._ +import scala.util.{Failure, Success, Try} + +object FileUtil { + // FIXME: Keep? + def parseTsv(tsv: String): Try[Array[Array[String]]] = { + val table = tsv.split("\n").map(_.split("\t")) + table.map(_.size).toSet match { + case s if s.size > 1 => Failure(new UnsupportedOperationException("TSV is not uniform")) + case _ => Success(table) + } + } + + implicit class EnhancedFile(val file: File) extends AnyVal { + /** Read an entire file into a string, closing the underlying stream. */ + def slurp: String = { + // TODO: deprecate slurp, and java.io.File in general? + file.toPath.contentAsString + } + } +} \ No newline at end of file diff --git a/scala/wdl4s/src/main/scala/wdl4s/util/TerminalUtil.scala b/scala/wdl4s/src/main/scala/wdl4s/util/TerminalUtil.scala new file mode 100644 index 0000000..22b92e0 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/util/TerminalUtil.scala @@ -0,0 +1,20 @@ +package wdl4s.util + +/** + * FIXME: Anything in here should be triaged - a) is it used and b) shared stuff moves to lenthall. + */ +object TerminalUtil { + def highlight(colorCode:Int, string:String) = s"\033[38;5;${colorCode}m$string\033[0m" + def mdTable(rows: Seq[Seq[String]], header: Seq[String]): String = { + def maxWidth(lengths: Seq[Seq[Int]], column: Int) = lengths.map { length => length(column) }.max + val widths = (rows :+ header).map { row => row.map { s => s.length } } + val maxWidths = widths.head.indices.map { column => maxWidth(widths, column) }.toSeq + val tableHeader = header.indices.map { i => header(i).padTo(maxWidths(i), " ").mkString("") }.mkString("|") + val tableDivider = header.indices.map { i => "-" * maxWidths(i) }.mkString("|") + val tableRows = rows.map { row => + val mdRow = row.indices.map { i => row(i).padTo(maxWidths(i), " ").mkString("") }.mkString("|") + s"|$mdRow|" + } + s"|$tableHeader|\n|$tableDivider|\n${tableRows.mkString("\n")}\n" + } +} \ No newline at end of file diff --git a/scala/wdl4s/src/main/scala/wdl4s/util/TryUtil.scala b/scala/wdl4s/src/main/scala/wdl4s/util/TryUtil.scala new file mode 100644 index 0000000..6305e14 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/util/TryUtil.scala @@ -0,0 +1,42 @@ +package wdl4s.util + +import java.io.{PrintWriter, StringWriter} + +import scala.language.postfixOps +import scala.util.{Success, Failure, Try} + +/** + * FIXME: To the extent this stuff remains, overlap w/ lenthall + */ + +case class AggregatedException(exceptions: Seq[Throwable], prefixError: String = "") extends Exception { + override def getMessage: String = { + prefixError + exceptions.map(_.getMessage).mkString("\n") + } +} + +object TryUtil { + private def stringifyFailure[T](failure: Try[T]): String = { + val stringWriter = new StringWriter() + val writer = new PrintWriter(stringWriter) + failure.recover { case e => e.printStackTrace(writer)} + writer.flush() + writer.close() + stringWriter.toString + } + + def stringifyFailures[T](possibleFailures: Traversable[Try[T]]): Traversable[String] = + possibleFailures.collect { case failure: Failure[T] => stringifyFailure(failure) } + + private def sequenceIterable[T](tries: Iterable[Try[_]], unbox: () => T, prefixErrorMessage: String) = { + tries collect { case f: Failure[_] => f } match { + case failures if failures.nonEmpty => Failure(new AggregatedException(failures map { _.exception } toSeq, prefixErrorMessage)) + case _ => Success(unbox()) + } + } + + def sequence[T](tries: Seq[Try[T]], prefixErrorMessage: String = ""): Try[Seq[T]] = { + def unbox = tries map { _.get } + sequenceIterable(tries, unbox _, prefixErrorMessage) + } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/values/WdlArray.scala b/scala/wdl4s/src/main/scala/wdl4s/values/WdlArray.scala new file mode 100644 index 0000000..e48a39b --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/values/WdlArray.scala @@ -0,0 +1,47 @@ +package wdl4s.values + +import wdl4s.TsvSerializable +import wdl4s.types.{WdlStringType, WdlArrayType, WdlObjectType, WdlPrimitiveType} + +import scala.language.postfixOps +import scala.util.{Failure, Success, Try} + +object WdlArray { + def fromTsv(tsv: String): WdlArray = { + WdlArray(WdlArrayType(WdlArrayType(WdlStringType)), tsv.replaceAll("[\r\n]+$", "").split("[\n\r]").toSeq map { line => + WdlArray(WdlArrayType(WdlStringType), line.split("\t").toSeq.map(WdlString)) + }) + } +} + +case class WdlArray(wdlType: WdlArrayType, value: Seq[WdlValue]) extends WdlValue with TsvSerializable { + val typesUsedInValue = Set(value map {_.wdlType}: _*) + if (typesUsedInValue.size == 1 && typesUsedInValue.head != wdlType.memberType) { + throw new UnsupportedOperationException(s"Could not construct array of type $wdlType with this value: $value") + } + if (typesUsedInValue.size > 1) { + throw new UnsupportedOperationException(s"Cannot construct array with a mixed types: $value") + } + + override def toWdlString: String = s"[${value.map(_.toWdlString).mkString(", ")}]" + override def toString = toWdlString + + def map[R <: WdlValue](f: WdlValue => R): WdlArray = { + value.map{f} match { + case s: Seq[R] if s.nonEmpty => WdlArray(WdlArrayType(s.head.wdlType), s) + case _ => this + } + } + + def tsvSerialize: Try[String] = { + wdlType.memberType match { + case t: WdlPrimitiveType => Success(value.map(_.valueString).mkString("\n")) + case WdlObjectType => WdlObject.tsvSerializeArray(value map { _.asInstanceOf[WdlObject] }) + case _ => Failure(new UnsupportedOperationException("Can only TSV serialize an Array[Primitive] or Array[Object]")) + } + } + + override def collectAsSeq[T <: WdlValue](filterFn: PartialFunction[WdlValue, T]): Seq[T] = { + value flatMap { _.collectAsSeq(filterFn) } + } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/values/WdlBoolean.scala b/scala/wdl4s/src/main/scala/wdl4s/values/WdlBoolean.scala new file mode 100644 index 0000000..12d9bf2 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/values/WdlBoolean.scala @@ -0,0 +1,46 @@ +package wdl4s.values + +import wdl4s.types.WdlBooleanType + +import scala.util.{Success, Try} + +object WdlBoolean { + val True = new WdlBoolean(true) + val False = new WdlBoolean(false) + def apply(value: Boolean) = if (value) WdlBoolean.True else WdlBoolean.False +} + +/** The constructor is private to force access through the companion + * object `apply` which ensures the use of one of the canonical instances. + */ +class WdlBoolean private(val value: Boolean) extends WdlPrimitive { + val wdlType = WdlBooleanType + + override def equals(rhs: WdlValue): Try[WdlBoolean] = rhs match { + case r:WdlBoolean => Success(WdlBoolean(value == r.value)) + case _ => invalid(s"$value || $rhs") + } + + override def lessThan(rhs: WdlValue): Try[WdlBoolean] = rhs match { + case r:WdlBoolean => Success(WdlBoolean(value < r.value)) + case _ => invalid(s"$value < $rhs") + } + + override def greaterThan(rhs: WdlValue): Try[WdlBoolean] = rhs match { + case r:WdlBoolean => Success(WdlBoolean(value > r.value)) + case _ => invalid(s"$value > $rhs") + } + + override def or(rhs: WdlValue): Try[WdlBoolean] = rhs match { + case r:WdlBoolean => Success(WdlBoolean(value || r.value)) + case _ => invalid(s"$value || $rhs") + } + + override def and(rhs: WdlValue): Try[WdlBoolean] = rhs match { + case r:WdlBoolean => Success(WdlBoolean(value && r.value)) + case _ => invalid(s"$value && $rhs") + } + + override def not: Try[WdlValue] = Success(WdlBoolean(!value)) + override def toWdlString = value.toString +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/values/WdlFile.scala b/scala/wdl4s/src/main/scala/wdl4s/values/WdlFile.scala new file mode 100644 index 0000000..8870347 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/values/WdlFile.scala @@ -0,0 +1,46 @@ +package wdl4s.values + +import wdl4s.types.{WdlFileType, WdlType} + +import scala.util.{Success, Try} + +object WdlFile { + def appendPathsWithSlashSeparators(path1: String, path2: String) = { + if (path1.endsWith("/") || path2.startsWith("/")) path1 + path2 + else path1 + "/" + path2 + } + + def apply(value: String, isGlob: Boolean = false): WdlFile = if (isGlob) WdlGlobFile(value) else WdlSingleFile(value) +} + +sealed trait WdlFile extends WdlPrimitive { + val value: String + val wdlType: WdlType = WdlFileType + + def isGlob: Boolean = this match { + case _: WdlGlobFile => true + case _ => false + } + + override def add(rhs: WdlValue): Try[WdlValue] = rhs match { + case r: WdlString => Success(WdlFile(value + r.value)) + case _ => invalid(s"$value + $rhs") + } + + override def equals(rhs: WdlValue): Try[WdlBoolean] = rhs match { + case r: WdlFile => Success(WdlBoolean(value.equals(r.value) && isGlob.equals(r.isGlob))) + case r: WdlString => Success(WdlBoolean(value.toString.equals(r.value.toString) && !isGlob)) + case _ => invalid(s"$value == $rhs") + } + + override def valueString = value.toString +} + +case class WdlSingleFile(value: String) extends WdlFile { + override def toWdlString = "\"" + value.toString + "\"" +} + +case class WdlGlobFile(value: String) extends WdlFile { + override def toWdlString = "glob(\"" + value.toString + "\")" +} + diff --git a/scala/wdl4s/src/main/scala/wdl4s/values/WdlFloat.scala b/scala/wdl4s/src/main/scala/wdl4s/values/WdlFloat.scala new file mode 100644 index 0000000..1a1c8f8 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/values/WdlFloat.scala @@ -0,0 +1,74 @@ +package wdl4s.values + +import wdl4s.WdlExpressionException +import wdl4s.types.WdlFloatType + +import scala.util.{Failure, Success, Try} + +case class WdlFloat(value: Double) extends WdlPrimitive { + val wdlType = WdlFloatType + override def add(rhs: WdlValue): Try[WdlValue] = { + rhs match { + case r:WdlFloat => Success(WdlFloat(value + r.value)) + case r:WdlInteger => Success(WdlFloat(value + r.value)) + case r:WdlString => Success(WdlString(value + r.value)) + case _ => invalid(s"$this + $rhs") + } + } + override def subtract(rhs: WdlValue): Try[WdlValue] = { + rhs match { + case r:WdlFloat => Success(WdlFloat(value - r.value)) + case r:WdlInteger => Success(WdlFloat(value - r.value)) + case _ => invalid(s"$this - $rhs") + } + } + override def multiply(rhs: WdlValue): Try[WdlValue] = { + rhs match { + case r:WdlFloat => Success(WdlFloat(value * r.value)) + case r:WdlInteger => Success(WdlFloat(value * r.value)) + case _ => invalid(s"$this * $rhs") + } + } + override def divide(rhs: WdlValue): Try[WdlValue] = { + rhs match { + case r:WdlFloat if r.value == 0.0 => Failure(new WdlExpressionException("Divide by zero")) + case r:WdlFloat => Success(WdlFloat(value / r.value)) + case r:WdlInteger if r.value == 0 => Failure(new WdlExpressionException("Divide by zero")) + case r:WdlInteger => Success(WdlFloat(value / r.value)) + case _ => invalid(s"$this / $rhs") + } + } + override def mod(rhs: WdlValue): Try[WdlValue] = { + rhs match { + case r:WdlFloat if r.value == 0.0 => Failure(new WdlExpressionException("Divide by zero")) + case r:WdlFloat => Success(WdlFloat(value % r.value)) + case r:WdlInteger if r.value == 0 => Failure(new WdlExpressionException("Divide by zero")) + case r:WdlInteger => Success(WdlFloat(value % r.value)) + case _ => invalid(s"$this % $rhs") + } + } + override def equals(rhs: WdlValue): Try[WdlBoolean] = { + rhs match { + case r:WdlFloat => Success(WdlBoolean(value == r.value)) + case r:WdlInteger => Success(WdlBoolean(value == r.value)) + case _ => invalid(s"$this == $rhs") + } + } + override def lessThan(rhs: WdlValue): Try[WdlBoolean] = { + rhs match { + case r:WdlFloat => Success(WdlBoolean(value < r.value)) + case r:WdlInteger => Success(WdlBoolean(value < r.value)) + case _ => invalid(s"$this < $rhs") + } + } + override def greaterThan(rhs: WdlValue): Try[WdlBoolean] = { + rhs match { + case r:WdlFloat => Success(WdlBoolean(value > r.value)) + case r:WdlInteger => Success(WdlBoolean(value > r.value)) + case _ => invalid(s"$this > $rhs") + } + } + override def unaryPlus: Try[WdlValue] = Success(WdlFloat(math.abs(value))) + override def unaryMinus: Try[WdlValue] = Success(WdlFloat(-value)) + override def toWdlString = value.toString +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/values/WdlInteger.scala b/scala/wdl4s/src/main/scala/wdl4s/values/WdlInteger.scala new file mode 100644 index 0000000..33b3f00 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/values/WdlInteger.scala @@ -0,0 +1,67 @@ +package wdl4s.values + +import wdl4s.WdlExpressionException +import wdl4s.types.WdlIntegerType + +import scala.util.{Failure, Success, Try} + +case class WdlInteger(value: Integer) extends WdlPrimitive { + val wdlType = WdlIntegerType + + override def add(rhs: WdlValue): Try[WdlValue] = rhs match { + case r:WdlInteger => Success(WdlInteger(value + r.value)) + case r:WdlString => Success(WdlString(value + r.value)) + case r:WdlFloat => Success(WdlFloat(value + r.value)) + case _ => invalid(s"$value + $rhs") + } + + override def subtract(rhs: WdlValue): Try[WdlValue] = rhs match { + case r:WdlInteger => Success(WdlInteger(value - r.value)) + case r:WdlFloat => Success(WdlFloat(value - r.value)) + case _ => invalid(s"$value - $rhs") + } + + override def multiply(rhs: WdlValue): Try[WdlValue] = rhs match { + case r:WdlInteger => Success(WdlInteger(value * r.value)) + case r:WdlFloat => Success(WdlFloat(value * r.value)) + case _ => invalid(s"$value * $rhs") + } + + override def divide(rhs: WdlValue): Try[WdlValue] = rhs match { + case r:WdlInteger if r.value == 0 => Failure(new WdlExpressionException(s"Divide by zero error: $value / $rhs")) + case r:WdlInteger => Success(WdlInteger(value / r.value)) + case r:WdlFloat if r.value == 0.toDouble => Failure(new WdlExpressionException(s"Divide by zero error: $value / $rhs")) + case r:WdlFloat => Success(WdlFloat(value / r.value)) + case _ => invalid(s"$value / $rhs") + } + + override def mod(rhs: WdlValue): Try[WdlValue] = rhs match { + case r:WdlInteger if r.value == 0 => Failure(new WdlExpressionException(s"Divide by zero error: $value / $rhs")) + case r:WdlInteger => Success(WdlInteger(value % r.value)) + case r:WdlFloat if r.value == 0.toDouble => Failure(new WdlExpressionException(s"Divide by zero error: $value / $rhs")) + case r:WdlFloat => Success(WdlFloat(value % r.value)) + case _ => invalid(s"$value % $rhs") + } + + override def equals(rhs: WdlValue): Try[WdlBoolean] = rhs match { + case r:WdlInteger => Success(WdlBoolean(value == r.value)) + case r:WdlFloat => Success(WdlBoolean(value == r.value)) + case _ => invalid(s"$value == $rhs") + } + + override def lessThan(rhs: WdlValue): Try[WdlBoolean] = rhs match { + case r:WdlInteger => Success(WdlBoolean(value < r.value)) + case r:WdlFloat => Success(WdlBoolean(value < r.value)) + case _ => invalid(s"$value < $rhs") + } + + override def greaterThan(rhs: WdlValue): Try[WdlBoolean] = rhs match { + case r:WdlInteger => Success(WdlBoolean(value > r.value)) + case r:WdlFloat => Success(WdlBoolean(value > r.value)) + case _ => invalid(s"$value > $rhs") + } + + override def unaryPlus: Try[WdlValue] = Success(WdlInteger(math.abs(value))) + override def unaryMinus: Try[WdlValue] = Success(WdlInteger(-value)) + override def toWdlString = value.toString +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/values/WdlMap.scala b/scala/wdl4s/src/main/scala/wdl4s/values/WdlMap.scala new file mode 100644 index 0000000..b48dad5 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/values/WdlMap.scala @@ -0,0 +1,80 @@ +package wdl4s.values + +import wdl4s.TsvSerializable +import wdl4s.types.{WdlAnyType, WdlMapType, WdlPrimitiveType, WdlType} +import wdl4s.util.{FileUtil, TryUtil} + +import scala.language.postfixOps +import scala.util.{Failure, Success, Try} + +object WdlMap { + def coerceMap(m: Map[_, _], wdlMapType: WdlMapType): WdlMap = { + val coerced = m map { case(k, v) => wdlMapType.keyType.coerceRawValue(k) -> wdlMapType.valueType.coerceRawValue(v) } + val failures = coerced flatMap { case(k,v) => Seq(k,v) } collect { case f:Failure[_] => f } + failures match { + case f: Iterable[Failure[_]] if f.nonEmpty => + throw new UnsupportedOperationException(s"Failed to coerce one or more keys or values for creating a ${wdlMapType.toWdlString}:\n${TryUtil.stringifyFailures(f)}}") + case _ => + val mapCoerced = coerced map { case (k, v) => k.get -> v.get } + + // Yes, throw an exception if keyType or valueType can't be determined + val keyType = WdlType.homogeneousTypeFromValues(mapCoerced map { case (k, v) => k }).get + val valueType = WdlType.homogeneousTypeFromValues(mapCoerced map { case (k, v) => v }).get + + WdlMap(WdlMapType(keyType, valueType), mapCoerced) + } + } + + def fromTsv(tsv: String, wdlMapType: WdlMapType = WdlMapType(WdlAnyType, WdlAnyType)): Try[WdlMap] = { + FileUtil.parseTsv(tsv) match { + case Success(table) if table.isEmpty => Success(WdlMap(wdlMapType, Map.empty[WdlValue, WdlValue])) + case Success(table) if table.head.length != 2 => Failure(new UnsupportedOperationException("TSV must be 2 columns to convert to a Map")) + case Success(table) => Try(coerceMap(table.map(row => row(0) -> row(1)).toMap, wdlMapType)) + case Failure(e) => Failure(e) + } + } +} + +case class WdlMap(wdlType: WdlMapType, value: Map[WdlValue, WdlValue]) extends WdlValue with TsvSerializable { + val typesUsedInKey = value.map { case (k,v) => k.wdlType }.toSet + + if (typesUsedInKey.size == 1 && typesUsedInKey.head != wdlType.keyType) + throw new UnsupportedOperationException(s"Could not construct a $wdlType as this value: $value") + + if (typesUsedInKey.size > 1) + throw new UnsupportedOperationException(s"Cannot construct $wdlType with mixed types: $value") + + val typesUsedInValue = value.map { case (k,v) => v.wdlType }.toSet + + if (typesUsedInValue.size == 1 && typesUsedInValue.head != wdlType.valueType) + throw new UnsupportedOperationException(s"Could not construct a $wdlType as this value: $value") + + if (typesUsedInValue.size > 1) + throw new UnsupportedOperationException(s"Cannot construct $wdlType with mixed types: $value") + + override def toWdlString: String = + "{" + value.map {case (k,v) => s"${k.toWdlString}: ${v.toWdlString}"}.mkString(", ") + "}" + + def tsvSerialize: Try[String] = { + (wdlType.keyType, wdlType.valueType) match { + case (wdlTypeKey: WdlPrimitiveType, wdlTypeValue: WdlPrimitiveType) => + Success(value.map({case (k, v) => s"${k.valueString}\t${v.valueString}"}).mkString("\n")) + case _ => + Failure(new UnsupportedOperationException("Can only TSV serialize a Map[Primitive, Primitive]")) + } + } + + def map(f: PartialFunction[((WdlValue, WdlValue)), (WdlValue, WdlValue)]): WdlMap = { + value map f match { + case m: Map[WdlValue, WdlValue] if m.nonEmpty => WdlMap(WdlMapType(m.head._1.wdlType, m.head._2.wdlType), m) + case _ => this + } + } + + override def collectAsSeq[T <: WdlValue](filterFn: PartialFunction[WdlValue, T]): Seq[T] = { + val collected = value flatMap { + case (k, v) => Seq(k.collectAsSeq(filterFn), v.collectAsSeq(filterFn)) + } + collected.flatten.toSeq + } +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/values/WdlObject.scala b/scala/wdl4s/src/main/scala/wdl4s/values/WdlObject.scala new file mode 100644 index 0000000..bd2d731 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/values/WdlObject.scala @@ -0,0 +1,87 @@ +package wdl4s.values + +import wdl4s.types._ +import wdl4s.{Call, TsvSerializable} +import wdl4s.util.FileUtil +import scala.util.{Failure, Success, Try} + +trait WdlObjectLike { + def value: Map[String, WdlValue] +} + +object WdlObject { + + def coerceObject(m: Map[String, String]): WdlObject = { + val coerced = WdlMap.coerceMap(m, WdlMapType(WdlStringType, WdlAnyType)).value map { + case (k, v) => k.valueString -> v + } + + WdlObject(coerced) + } + + def fromTsv(tsv: String): Try[Array[WdlObject]] = { + FileUtil.parseTsv(tsv) match { + case Success(table) if table.isEmpty => Failure(new UnsupportedOperationException("TSV file was empty or could not be parsed.")) + case Success(table) if table.length < 2 => Failure(new UnsupportedOperationException("TSV must be 2 rows (or more) to convert to an Object (Array[Object])")) + case Success(table) => Try { + table.tail map { line => coerceObject((table.head zip line).toMap) } + } + case Failure(e) => Failure(e) + } + } + + //TODO: Try to stream this out to avoid memory overhead + def tsvSerializeArray(input: Seq[WdlObject]): Try[String] = { + + /** + * Validates that all objects have the same attributes. + */ + def attributesMatch(objects: Seq[WdlObject]): Boolean = { + val attributesSet = objects map { _.orderedAttributes } + val intersection = attributesSet reduce (_.intersect(_)) + attributesSet forall { _ == intersection } + } + + input match { + case Nil => Failure(new UnsupportedOperationException("Cannot write empty objects array.")) + case objects if attributesMatch(objects) => + /* Note: this is arbitrary as it takes the first object in the array as a reference for the attributes. + * It has no impact though as we first made sure that all objects have the same attributes. + */ + val attributes = objects.head.orderedAttributes + val attributesLine = attributes.mkString("\t") + val valuesLines = objects map { obj => + attributes map { obj.value(_).valueString } mkString "\t" + } mkString "\n" + + Success(s"$attributesLine\n$valuesLines") + case _ => Failure(new UnsupportedOperationException("Could not serialize array: Objects in the array have different attributes.")) + } + } + +} + +case class WdlObject(value: Map[String, WdlValue]) extends WdlValue with WdlObjectLike with TsvSerializable { + val wdlType = WdlObjectType + + override def toWdlString: String = + "object {" + value.map {case (k, v) => s"$k: ${v.toWdlString}"}.mkString(", ") + "}" + + lazy val orderedAttributes = value.keySet.toSeq + lazy val orderedValues = orderedAttributes map { value(_) } + + def tsvSerialize: Try[String] = Try { + val keysLine = orderedAttributes.mkString("\t") + val values = orderedValues map { + case v if v.isInstanceOf[WdlPrimitive] => v.valueString + case _ => throw new UnsupportedOperationException("Can only TSV serialize an Object with Primitive values.") + } + + s"$keysLine\n${values.mkString("\t")}" + } +} + +case class WdlCallOutputsObject(call: Call, outputs: Map[String, WdlValue]) extends WdlValue with WdlObjectLike { + val wdlType = WdlCallOutputsObjectType(call) + val value = outputs +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/values/WdlPrimitive.scala b/scala/wdl4s/src/main/scala/wdl4s/values/WdlPrimitive.scala new file mode 100644 index 0000000..ffad924 --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/values/WdlPrimitive.scala @@ -0,0 +1,3 @@ +package wdl4s.values + +trait WdlPrimitive extends WdlValue \ No newline at end of file diff --git a/scala/wdl4s/src/main/scala/wdl4s/values/WdlString.scala b/scala/wdl4s/src/main/scala/wdl4s/values/WdlString.scala new file mode 100644 index 0000000..a584a8e --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/values/WdlString.scala @@ -0,0 +1,35 @@ +package wdl4s.values + +import wdl4s.types.WdlStringType +import org.apache.commons.lang3.StringEscapeUtils +import scala.util.{Success, Try} + +case class WdlString(value: String) extends WdlPrimitive { + val wdlType = WdlStringType + + override def add(rhs: WdlValue): Try[WdlValue] = rhs match { + case r: WdlString => Success(WdlString(value + r.value)) + case r: WdlInteger => Success(WdlString(value + r.value)) + case r: WdlFloat => Success(WdlString(value + r.value)) + case r: WdlFile => Success(WdlString(value + r.value)) + case _ => invalid(s"$value + $rhs") + } + + override def equals(rhs: WdlValue): Try[WdlBoolean] = rhs match { + case r: WdlString => Success(WdlBoolean(value == r.value)) + case _ => invalid(s"$value == $rhs") + } + + override def lessThan(rhs: WdlValue): Try[WdlBoolean] = rhs match { + case r: WdlString => Success(WdlBoolean(value < r.value)) + case _ => invalid(s"$value < $rhs") + } + + override def greaterThan(rhs: WdlValue): Try[WdlBoolean] = rhs match { + case r: WdlString => Success(WdlBoolean(value > r.value)) + case _ => invalid(s"$value > $rhs") + } + + override def toWdlString = "\"" + StringEscapeUtils.escapeJava(value) + "\"" + override def valueString = value +} diff --git a/scala/wdl4s/src/main/scala/wdl4s/values/WdlValue.scala b/scala/wdl4s/src/main/scala/wdl4s/values/WdlValue.scala new file mode 100644 index 0000000..771794a --- /dev/null +++ b/scala/wdl4s/src/main/scala/wdl4s/values/WdlValue.scala @@ -0,0 +1,46 @@ +package wdl4s.values + +import wdl4s.types.WdlType +import wdl4s.WdlExpressionException +import scala.util.{Failure, Try} + +trait WdlValue { + val wdlType: WdlType + def invalid(operation: String) = Failure(new WdlExpressionException(s"Cannot perform operation: $operation")) + def add(rhs: WdlValue): Try[WdlValue] = invalid(s"$this + $rhs") + def subtract(rhs: WdlValue): Try[WdlValue] = invalid(s"$this - $rhs") + def multiply(rhs: WdlValue): Try[WdlValue] = invalid(s"$this * $rhs") + def divide(rhs: WdlValue): Try[WdlValue] = invalid(s"$this / $rhs") + def mod(rhs: WdlValue): Try[WdlValue] = invalid(s"$this % $rhs") + def equals(rhs: WdlValue): Try[WdlBoolean] = invalid(s"$this == $rhs") + def notEquals(rhs: WdlValue): Try[WdlBoolean] = equals(rhs).map{x => WdlBoolean(!x.value)} + def lessThan(rhs: WdlValue): Try[WdlBoolean] = invalid(s"$this < $rhs") + def lessThanOrEqual(rhs: WdlValue): Try[WdlBoolean] = + Try(WdlBoolean(Seq(lessThan _, equals _).exists{ p => p(rhs).get == WdlBoolean.True })) + def greaterThan(rhs: WdlValue): Try[WdlBoolean] = invalid(s"$this > $rhs") + def greaterThanOrEqual(rhs: WdlValue): Try[WdlBoolean] = + Try(WdlBoolean(Seq(greaterThan _, equals _).exists{ p => p(rhs).get == WdlBoolean.True })) + def or(rhs: WdlValue): Try[WdlBoolean] = invalid(s"$this || $rhs") + def and(rhs: WdlValue): Try[WdlBoolean] = invalid(s"$this && $rhs") + def not: Try[WdlValue] = invalid(s"!$this") + def unaryPlus: Try[WdlValue] = invalid(s"+$this") + def unaryMinus: Try[WdlValue] = invalid(s"-$this") + def typeName: String = wdlType.getClass.getSimpleName + + /* This emits valid WDL source. WdlString("foobar") -> "foobar" (quotes included) */ + def toWdlString: String = ??? + + /* This emits the value as a string. In other words, the String value that + * would be inserted into the command line. + * + * WdlString("foobar") -> foobar + * + * toWdlString is a good approximate implementation, though not sufficient + * for types like WdlString where extra syntax is added on + */ + def valueString: String = toWdlString + + def collectAsSeq[T <: WdlValue](filterFn: PartialFunction[WdlValue, T]): Seq[T] = { + if (filterFn.isDefinedAt(this)) Seq(filterFn(this)) else Nil + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/AstSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/AstSpec.scala new file mode 100644 index 0000000..f52c44a --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/AstSpec.scala @@ -0,0 +1,22 @@ +package wdl4s + +import org.scalatest.{FlatSpec, Matchers} + +class AstSpec extends FlatSpec with Matchers { + val namespace = WdlNamespace.load(SampleWdl.ThreeStep.wdlSource()) + + "Parser" should "produce AST with 3 Task nodes" in { + AstTools.findAsts(namespace.ast, "Task").size shouldEqual 3 + } + + it should "produce AST with 1 Workflow node" in { + AstTools.findAsts(namespace.ast, "Workflow").size shouldEqual 1 + } + + it should "produce AST with 3 Call nodes in the Workflow node" in { + AstTools.findAsts( + AstTools.findAsts(namespace.ast, "Workflow").head, + "Call" + ).size shouldEqual 3 + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/FqnResolverSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/FqnResolverSpec.scala new file mode 100644 index 0000000..c89d12f --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/FqnResolverSpec.scala @@ -0,0 +1,55 @@ +package wdl4s + +import org.scalatest.prop.TableDrivenPropertyChecks._ +import org.scalatest.{FlatSpec, Matchers} + +class FqnResolverSpec extends FlatSpec with Matchers { + val namespace = NamespaceWithWorkflow.load(SampleWdl.NestedScatterWdl.wdlSource()) + val callFqns = Table( + "fqn", + "w.A", + "w.B", + "w.C", + "w.D", + "w.E", + "w.F", + "w.G", + "w.H" + ) + + val scatterFqns = Table( + "fqn", + "w.$scatter_0", + "w.$scatter_0.$scatter_1", + "w.$scatter_0.$scatter_2", + "w.$scatter_3" + ) + + it should "be able to resolve a Workflow FQN" in { + namespace.resolve("w") match { + case Some(wf: Workflow) => wf.fullyQualifiedName shouldEqual "w" + case Some(scope: Scope) => fail(s"FQN 'w' was expected to be a Workflow reference, but instead it references: $scope") + case None => fail(s"Expecting to resolve FQN 'w' into a Workflow") + } + } + + forAll(callFqns) { (fqn: String) => + it should s"be able to resolve Call FQN: $fqn" in { + namespace.resolve(fqn) match { + case Some(call: Call) => call.fullyQualifiedName shouldEqual fqn + case Some(scope: Scope) => fail(s"FQN '$fqn' was expected to be a Call reference, but instead it references: $scope") + case None => fail(s"Expecting to resolve FQN '$fqn' into a Call") + } + } + } + + forAll(scatterFqns) { (fqn: String) => + it should s"be able to resolve Scatter FQN: $fqn" in { + namespace.resolve(fqn) match { + case Some(scatter: Scatter) => scatter.fullyQualifiedNameWithIndexScopes shouldEqual fqn + case Some(scope: Scope) => fail(s"FQN '$fqn' was expected to be a Scatter reference, but instead it references: $scope") + case None => fail(s"Expecting to resolve FQN '$fqn' into a Scatter block") + } + } + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/NestedScatterSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/NestedScatterSpec.scala new file mode 100644 index 0000000..ba90d5a --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/NestedScatterSpec.scala @@ -0,0 +1,68 @@ +package wdl4s + +import org.scalatest.{FlatSpec, Matchers} + +class NestedScatterSpec extends FlatSpec with Matchers { + val namespace = NamespaceWithWorkflow.load(SampleWdl.NestedScatterWdl.wdlSource()) + + it should "Have four 'children' objects" in { + namespace.workflow.children.size shouldEqual 4 + } + + it should "Have two 'direct Call descendents' objects" in { + Scope.collectCalls(namespace.workflow.children).size shouldEqual 2 + } + + it should "Have two 'direct Scatter descendents' objects" in { + Scope.collectScatters(namespace.workflow.children).size shouldEqual 2 + } + + it should "Have eight 'Call' objects" in { + namespace.workflow.calls.size shouldEqual 8 + } + + it should "Have four 'Scatter' objects" in { + namespace.workflow.scatters.size shouldEqual 4 + } + + it should "Have 'Scatter' objects indexed properly" in { + namespace.workflow.scatters.head.index shouldEqual 0 + namespace.workflow.scatters(1).index shouldEqual 3 + namespace.workflow.scatters(2).index shouldEqual 1 + namespace.workflow.scatters(3).index shouldEqual 2 + } + + it should "Not appear in Calls FQNs" in { + val calls: Seq[Call] = namespace.workflow.calls + calls.find(_.fullyQualifiedName == "w.A") shouldBe defined + calls.find(_.fullyQualifiedName == "w.B") shouldBe defined + calls.find(_.fullyQualifiedName == "w.C") shouldBe defined + calls.find(_.fullyQualifiedName == "w.E") shouldBe defined + calls.find(_.fullyQualifiedName == "w.G") shouldBe defined + calls.find(_.fullyQualifiedName == "w.H") shouldBe defined + calls.find(_.fullyQualifiedName == "w.F") shouldBe defined + calls.find(_.fullyQualifiedName == "w.D") shouldBe defined + } + + it should "Have correct FQNs for Scatter blocks" in { + namespace.workflow.scatters.find(_.fullyQualifiedNameWithIndexScopes == "w.$scatter_0") shouldBe defined + namespace.workflow.scatters.find(_.fullyQualifiedNameWithIndexScopes == "w.$scatter_0.$scatter_1") shouldBe defined + namespace.workflow.scatters.find(_.fullyQualifiedNameWithIndexScopes == "w.$scatter_0.$scatter_2") shouldBe defined + namespace.workflow.scatters.find(_.fullyQualifiedNameWithIndexScopes == "w.$scatter_3") shouldBe defined + } + + it should "Instantiate Scatters with correct item attributes" in { + namespace.workflow.scatters.find(_.unqualifiedName == "$scatter_0").get.item shouldEqual "item" + namespace.workflow.scatters.find(_.unqualifiedName == "$scatter_1").get.item shouldEqual "itemB" + namespace.workflow.scatters.find(_.unqualifiedName == "$scatter_2").get.item shouldEqual "itemB" + namespace.workflow.scatters.find(_.unqualifiedName == "$scatter_3").get.item shouldEqual "item" + } + + it should "Instantiate Scatters with correct collection attributes" in { + namespace.workflow.scatters.find(_.unqualifiedName == "$scatter_0").get.collection.toWdlString shouldEqual "A.A_out" + namespace.workflow.scatters.find(_.unqualifiedName == "$scatter_1").get.collection.toWdlString shouldEqual "B.B_out" + namespace.workflow.scatters.find(_.unqualifiedName == "$scatter_2").get.collection.toWdlString shouldEqual "B.B_out" + namespace.workflow.scatters.find(_.unqualifiedName == "$scatter_3").get.collection.toWdlString shouldEqual "A.A_out" + } + +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/ParameterCommandPartSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/ParameterCommandPartSpec.scala new file mode 100644 index 0000000..3224e24 --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/ParameterCommandPartSpec.scala @@ -0,0 +1,126 @@ +package wdl4s + +import wdl4s.command.ParameterCommandPart +import wdl4s.expression.NoFunctions +import wdl4s.types.{WdlIntegerType, WdlArrayType, WdlStringType} +import wdl4s.values._ +import wdl4s.parser.WdlParser.SyntaxError +import org.scalatest.{FlatSpec, Matchers} + +import scala.util.{Try, Failure} + +class ParameterCommandPartSpec extends FlatSpec with Matchers { + val wdl = + """task param_test { + | String a + | String b + | Array[String] c + | Int? d + | Array[Int] e + | Boolean f + | + | command <<< + | ./binary ${a} ${"-p " + b} ${sep="," c} ${default=9 d} ${sep="\t" e} ${true="--true" false="--false" f} + | >>> + |} + | + |workflow wf {call param_test} + """.stripMargin + val namespace = WdlNamespace.load(wdl) + val task = namespace.tasks find {_.name == "param_test"} getOrElse { + fail("task 'param_test' not found") + } + + val paramsByName = task.commandTemplate.collect {case p: ParameterCommandPart => p}.map {p => p} + "Template variables" should "Stringify correctly" in { + paramsByName.size shouldEqual 6 + paramsByName.head.toString shouldEqual "${a}" + paramsByName(1).toString shouldEqual "${\"-p \" + b}" + paramsByName(2).toString shouldEqual "${sep=\",\" c}" + paramsByName(3).toString shouldEqual "${default=\"9\" d}" + paramsByName(4).toString shouldEqual "${sep=\"\\t\" e}" + paramsByName(5).toString shouldEqual "${true=\"--true\" false=\"--false\" f}" + } + + "Command instantiation" should "succeed if given valid inputs" in { + task.instantiateCommand(Map( + "a" -> WdlString("a_val"), + "b" -> WdlString("b_val"), + "c" -> WdlArray(WdlArrayType(WdlStringType), Seq(WdlString("c0"), WdlString("c1"), WdlString("c2"))), + "d" -> WdlInteger(1), + "e" -> WdlArray(WdlArrayType(WdlIntegerType), Seq(0, 1, 2).map(WdlInteger(_))), + "f" -> WdlBoolean.False + ), NoFunctions).get shouldEqual "./binary a_val -p b_val c0,c1,c2 1 0\t1\t2 --false" + } + + it should "succeed if omitting an optional input" in { + task.instantiateCommand(Map( + "a" -> WdlString("a_val"), + "b" -> WdlString("b_val"), + "c" -> WdlArray(WdlArrayType(WdlStringType), Seq(WdlString("c0"), WdlString("c1"), WdlString("c2"))), + "e" -> WdlArray(WdlArrayType(WdlIntegerType), Seq(0, 1, 2).map(WdlInteger(_))), + "f" -> WdlBoolean.True + ), NoFunctions).get shouldEqual "./binary a_val -p b_val c0,c1,c2 9 0\t1\t2 --true" + } + + it should "succeed if providing an array with one element" in { + task.instantiateCommand(Map( + "a" -> WdlString("a_val"), + "b" -> WdlString("b_val"), + "c" -> WdlArray(WdlArrayType(WdlStringType), Seq(WdlString("c0"))), + "d" -> WdlInteger(1), + "e" -> WdlArray(WdlArrayType(WdlIntegerType), Seq()), + "f" -> WdlBoolean.True + ), NoFunctions).get shouldEqual "./binary a_val -p b_val c0 1 --true" + } + + it should "succeed if providing an array with zero elements" in { + task.instantiateCommand(Map( + "a" -> WdlString("a_val"), + "b" -> WdlString("b_val"), + "c" -> WdlArray(WdlArrayType(WdlStringType), Seq()), + "d" -> WdlInteger(1), + "e" -> WdlArray(WdlArrayType(WdlIntegerType), Seq()), + "f" -> WdlBoolean.True + ), NoFunctions).get shouldEqual "./binary a_val -p b_val 1 --true" + } + + it should "raise exception if a required input is missing" in { + task.instantiateCommand(Map("a" -> WdlString("a_val")), NoFunctions) match { + case Failure(f) => // expected + case _ => fail("Expected an exception") + } + } + + it should "raise exception if a parameter is an expression" in { + task.instantiateCommand(Map( + "a" -> WdlString("a_val"), + "b" -> WdlExpression.fromString("'a'+'b'"), + "c" -> WdlArray(WdlArrayType(WdlStringType), Seq()), + "d" -> WdlInteger(1), + "e" -> WdlArray(WdlArrayType(WdlIntegerType), Seq()) + ), NoFunctions) match { + case Failure(f) => // expected + case _ => fail("Expected an exception") + } + } + + it should "raise exception if 'true' attribute is specified but 'false' is not" in { + Try( + WdlNamespace.load( + """task param_test { + | Boolean f + | + | command <<< + | ./binary ${true="--true" f} + | >>> + |} + | + |workflow wf {call param_test} + """.stripMargin) + ) match { + case Failure(s: SyntaxError) => // expected + case _ => fail("Expecting a syntax error") + } + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/PrerequisiteScopesSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/PrerequisiteScopesSpec.scala new file mode 100644 index 0000000..4e7baa3 --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/PrerequisiteScopesSpec.scala @@ -0,0 +1,58 @@ +package wdl4s + +import wdl4s.SampleWdl.ScatterWdl +import org.scalatest.{FlatSpec, Matchers} + +class PrerequisiteScopesSpec extends FlatSpec with Matchers { + val namespace = NamespaceWithWorkflow.load((new ScatterWdl).wdlSource()) + val workflow = namespace.workflow + val allCalls = workflow.collectAllCalls + val allScatters = workflow.collectAllScatters + + def scopesByName(name: String) = workflow.callByName(name).get.prerequisiteScopes + + "ScatterWdl" should "have five calls" in { + allCalls.size shouldEqual 5 + } + + it should "have one scatter block" in { + allScatters.size shouldEqual 1 + } + + it should "have the scatter block with one prereq" in { + allScatters.head.prerequisiteScopes.size shouldEqual 1 + } + + it should "have the scatter block with A as a prereq" in { + val z = allScatters.head.prerequisiteScopes.head.fullyQualifiedName shouldEqual "w.A" + } + + it should "have A not depend on anything" in { + scopesByName("A") shouldBe empty + } + + it should "have B depend on the scatter" in { + val scopes = scopesByName("B") + scopes.size shouldEqual 1 + scopes.head.unqualifiedName shouldBe "$scatter_0" + } + + it should "have C depend on the scatter and B" in { + val scopes = scopesByName("C") + scopes.size shouldEqual 2 + scopes find { _.unqualifiedName == "$scatter_0" } shouldBe defined + scopes find { _.unqualifiedName == "B"} shouldBe defined + } + + it should "have D depend on B" in { + val scopes = scopesByName("D") + scopes.size shouldEqual 1 + scopes.head.unqualifiedName shouldBe "B" + } + + it should "have E depend on the scatter" in { + val scopes = scopesByName("E") + scopes.size shouldEqual 1 + scopes.head.unqualifiedName shouldBe "$scatter_0" + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/RuntimeAttributeSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/RuntimeAttributeSpec.scala new file mode 100644 index 0000000..3dd2331 --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/RuntimeAttributeSpec.scala @@ -0,0 +1,136 @@ +package wdl4s + +import org.scalatest.{EitherValues, Matchers, FlatSpec} +import RuntimeAttributeSpec._ + +object RuntimeAttributeSpec { + val WorkflowWithRuntime = + """ + |task ps { + | command { + | ps + | } + | output { + | File procs = stdout() + | } + | runtime { + | docker: "ubuntu:latest" + | } + |} + | + |task cgrep { + | String pattern + | File in_file + | command { + | grep '${pattern}' ${in_file} | wc -l + | } + | output { + | Int count = read_int(stdout()) + | } + | runtime { + | docker: "ubuntu:latest" + | } + |} + | + |task wc { + | File in_file + | command { + | cat ${in_file} | wc -l + | } + | output { + | Int count = read_int(stdout()) + | } + | runtime { + | docker: "ubuntu:latest" + | } + |} + | + |workflow three_step { + | call ps + | call cgrep { + | input: in_file=ps.procs + | } + | call wc { + | input: in_file=ps.procs + | } + |} + | + """.stripMargin + + val WorkflowWithoutRuntime = + """ + |task hello { + | String addressee + | command { + | echo "Hello ${addressee}!" + | } + | output { + | String salutation = read_string(stdout()) + | } + |} + | + |workflow hello { + | call hello + |} + """.stripMargin + + val WorkflowWithMessedUpMemory = + """ + |task messed_up_memory { + | command { + | echo "YO" + | } + | runtime { + | memory: "HI TY" + | } + |} + | + |workflow great_googly_moogly { + | call messed_up_memory + |} + """.stripMargin + val WorkflowWithMessedUpMemoryUnit = + """ + |task messed_up_memory { + | command { + | echo "YO" + | } + | runtime { + | memory: "5 TY" + | } + |} + | + |workflow great_googly_moogly { + | call messed_up_memory + |} + """.stripMargin + } + +class RuntimeAttributeSpec extends FlatSpec with Matchers with EitherValues { + val NamespaceWithRuntime = NamespaceWithWorkflow.load(WorkflowWithRuntime) + val NamespaceWithoutRuntime = NamespaceWithWorkflow.load(WorkflowWithoutRuntime) + + "WDL file with runtime attributes" should "have attribute maps" in { + NamespaceWithRuntime.tasks.forall(_.runtimeAttributes.attrs.nonEmpty) should be(true) + } + + "WDL file without runtime attributes" should "not have attribute maps" in { + NamespaceWithoutRuntime.tasks.forall(_.runtimeAttributes.attrs.isEmpty) should be(true) + } + + "WDL file with a seriously screwed up memory runtime" should "not parse" in { + val ex = intercept[IllegalArgumentException] { + val namespaceWithBorkedMemory = NamespaceWithWorkflow.load(WorkflowWithMessedUpMemory) + } + + ex.getMessage should include ("should be of the form X Unit") + } + + "WDL file with an invalid memory unit" should "say so" in { + val ex = intercept[IllegalArgumentException] { + val namespaceWithBorkedMemory = NamespaceWithWorkflow.load(WorkflowWithMessedUpMemoryUnit) + } + + ex.getMessage should include ("is an invalid memory unit") + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/SameNameParametersSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/SameNameParametersSpec.scala new file mode 100644 index 0000000..e3c05d1 --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/SameNameParametersSpec.scala @@ -0,0 +1,27 @@ +package wdl4s + +import wdl4s.expression.NoFunctions +import wdl4s.types.WdlStringType +import wdl4s.values.WdlString +import org.scalatest.{FlatSpec, Matchers} + +class SameNameParametersSpec extends FlatSpec with Matchers { + val namespace1 = NamespaceWithWorkflow.load( + """ + |task test { + | String x + | command { ./script ${x} ${x} ${x} } + |} + |workflow wf { call test } + """.stripMargin + ) + val task = namespace1.findTask("test").get + + "A task with command that uses the same parameter more than once" should "only count it as one input" in { + task.inputs shouldEqual Seq(TaskInput(name="x", wdlType=WdlStringType)) + } + + it should "instantiate the command with duplicated parameter names properly" in { + task.instantiateCommand(Map("x" -> WdlString("foo")), NoFunctions).get shouldEqual "./script foo foo foo" + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/SampleWdl.scala b/scala/wdl4s/src/test/scala/wdl4s/SampleWdl.scala new file mode 100644 index 0000000..b3dae9b --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/SampleWdl.scala @@ -0,0 +1,228 @@ +package wdl4s + +import java.nio.file.{Files, Path} + +import scala.language.postfixOps + +// FIXME: Figure out if anything can be removed from cromwell completely or pulled from here + +trait SampleWdl extends TestFileUtil { + def wdlSource(runtime: String = ""): WdlSource + def rawInputs: WorkflowRawInputs + + def createFileArray(base: Path): Unit = { + createFile("f1", base, "line1\nline2\n") + createFile("f2", base, "line3\nline4\n") + createFile("f3", base, "line5\n") + } + + def cleanupFileArray(base: Path) = { + deleteFile(base.resolve("f1")) + deleteFile(base.resolve("f2")) + deleteFile(base.resolve("f3")) + } + + def deleteFile(path: Path) = Files.delete(path) +} + +object SampleWdl { + trait ThreeStepTemplate extends SampleWdl { + override def wdlSource(runtime: String = "") = sourceString() + private val outputSectionPlaceholder = "OUTPUTSECTIONPLACEHOLDER" + def sourceString(outputsSection: String = "") = { + val withPlaceholders = + """ + |task ps { + | command { + | ps + | } + | output { + | File procs = stdout() + | } + |} + | + |task cgrep { + | String pattern + | File in_file + | + | command { + | grep '${pattern}' ${in_file} | wc -l + | } + | output { + | Int count = read_int(stdout()) + | } + |} + | + |task wc { + | File in_file + | command { + | cat ${in_file} | wc -l + | } + | output { + | Int count = read_int(stdout()) + | } + |} + | + |workflow three_step { + | call ps + | call cgrep { + | input: in_file = ps.procs + | } + | call wc { + | input: in_file = ps.procs + | } + | """ + outputSectionPlaceholder + """ + |} + | + """ + withPlaceholders.stripMargin.replace(outputSectionPlaceholder, outputsSection) + } + + val PatternKey ="three_step.cgrep.pattern" + override lazy val rawInputs = Map(PatternKey -> "...") + } + + object ThreeStep extends ThreeStepTemplate + + + object NestedScatterWdl extends SampleWdl { + override def wdlSource(runtime: String = "") = + """ + task A { + | command { + | echo -n -e "jeff\nchris\nmiguel\nthibault\nkhalid\nscott" + | } + | output { + | Array[String] A_out = read_lines(stdout()) + | } + |} + | + |task B { + | String B_in + | command { + | python -c "print(len('${B_in}'))" + | } + | output { + | Int B_out = read_int(stdout()) + | } + |} + | + |task C { + | Int C_in + | command { + | python -c "print(${C_in}*100)" + | } + | output { + | Int C_out = read_int(stdout()) + | } + |} + | + |task D { + | Array[Int] D_in + | command { + | python -c "print(${sep = '+' D_in})" + | } + | output { + | Int D_out = read_int(stdout()) + | } + |} + | + |task E { + | command { + | python -c "import random; print(random.randint(1,100))" + | } + | output { + | Int E_out = read_int(stdout()) + | } + |} + | + |workflow w { + | call A + | scatter (item in A.A_out) { + | call B {input: B_in = item} + | call C {input: C_in = B.B_out} + | call E + | scatter (itemB in B.B_out) { + | call E as G + | } + | scatter (itemB in B.B_out) { + | call E as H + | } + | } + | scatter (item in A.A_out) { + | call E as F + | } + | call D {input: D_in = B.B_out} + |} + """.stripMargin + override lazy val rawInputs = Map("" -> "...") + } + + + class ScatterWdl extends SampleWdl { + val tasks = """task A { + | command { + | echo -n -e "jeff\nchris\nmiguel\nthibault\nkhalid\nscott" + | } + | output { + | Array[String] A_out = read_lines(stdout()) + | } + |} + | + |task B { + | String B_in + | command { + | python -c "print(len('${B_in}'))" + | } + | output { + | Int B_out = read_int(stdout()) + | } + |} + | + |task C { + | Int C_in + | command { + | python -c "print(${C_in}*100)" + | } + | output { + | Int C_out = read_int(stdout()) + | } + |} + | + |task D { + | Array[Int] D_in + | command { + | python -c "print(${sep = '+' D_in})" + | } + | output { + | Int D_out = read_int(stdout()) + | } + |} + | + |task E { + | command { + | python -c "print(9)" + | } + | output { + | Int E_out = read_int(stdout()) + | } + |} + """.stripMargin + + override def wdlSource(runtime: String = "") = + s"""$tasks + | + |workflow w { + | call A + | scatter (item in A.A_out) { + | call B {input: B_in = item} + | call C {input: C_in = B.B_out} + | call E + | } + | call D {input: D_in = B.B_out} + |} + """.stripMargin + + override lazy val rawInputs = Map.empty[String, String] + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/ScopeSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/ScopeSpec.scala new file mode 100644 index 0000000..1d74f7e --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/ScopeSpec.scala @@ -0,0 +1,90 @@ +package wdl4s + +import wdl4s.AstTools.AstNodeName +import wdl4s.parser.WdlParser.Ast +import org.scalatest.{FlatSpec, Matchers} + +class ScopeSpec extends FlatSpec with Matchers { + + val namespace = NamespaceWithWorkflow.load(SampleWdl.NestedScatterWdl.wdlSource()) + val calls: Seq[Call] = namespace.workflow.calls + val scatters: Seq[Scatter] = namespace.workflow.scatters + val scatter0: Scatter = scatters.find(_.unqualifiedName == "$scatter_0").get + val scatter1: Scatter = scatters.find(_.unqualifiedName == "$scatter_1").get + val scatter2: Scatter = scatters.find(_.unqualifiedName == "$scatter_2").get + val scatter3: Scatter = scatters.find(_.unqualifiedName == "$scatter_3").get + val callA: Call = calls.find(_.fullyQualifiedName == "w.A").get + val callB: Call = calls.find(_.fullyQualifiedName == "w.B").get + val callC: Call = calls.find(_.fullyQualifiedName == "w.C").get + val callD: Call = calls.find(_.fullyQualifiedName == "w.D").get + val callE: Call = calls.find(_.fullyQualifiedName == "w.E").get + val callF: Call = calls.find(_.fullyQualifiedName == "w.F").get + val callG: Call = calls.find(_.fullyQualifiedName == "w.G").get + val callH: Call = calls.find(_.fullyQualifiedName == "w.H").get + + it should "Have correct parent hierarchy" in { + callA.parent.get shouldEqual namespace.workflow + callB.parent.get shouldEqual scatter0 + callC.parent.get shouldEqual scatter0 + callE.parent.get shouldEqual scatter0 + callG.parent.get shouldEqual scatter1 + callH.parent.get shouldEqual scatter2 + callF.parent.get shouldEqual scatter3 + callD.parent.get shouldEqual namespace.workflow + + scatter0.parent.get shouldEqual namespace.workflow + scatter1.parent.get shouldEqual scatter0 + scatter2.parent.get shouldEqual scatter0 + scatter3.parent.get shouldEqual namespace.workflow + + namespace.workflow.parent shouldEqual None + } + + it should "Have correct parent/child relationships" in { + namespace.workflow.children shouldEqual Seq(callA, scatter0, scatter3, callD) + + scatter0.children shouldEqual Seq(callB, callC, callE, scatter1, scatter2) + scatter1.children shouldEqual Seq(callG) + scatter2.children shouldEqual Seq(callH) + scatter3.children shouldEqual Seq(callF) + + callA.children shouldBe empty + callB.children shouldBe empty + callC.children shouldBe empty + callD.children shouldBe empty + callE.children shouldBe empty + callF.children shouldBe empty + callG.children shouldBe empty + callH.children shouldBe empty + } + + it should "Have correct ancestry for each Scope" in { + callA.ancestry shouldEqual Seq(namespace.workflow) + callB.ancestry shouldEqual Seq(scatter0, namespace.workflow) + callC.ancestry shouldEqual Seq(scatter0, namespace.workflow) + callE.ancestry shouldEqual Seq(scatter0, namespace.workflow) + callG.ancestry shouldEqual Seq(scatter1, scatter0, namespace.workflow) + callH.ancestry shouldEqual Seq(scatter2, scatter0, namespace.workflow) + callF.ancestry shouldEqual Seq(scatter3, namespace.workflow) + callD.ancestry shouldEqual Seq(namespace.workflow) + } + + it should "Be able to determine common ancestor between two Scopes" in { + callA.closestCommonAncestor(callH) shouldEqual Some(namespace.workflow) + callH.closestCommonAncestor(callA) shouldEqual Some(namespace.workflow) + callB.closestCommonAncestor(callC) shouldEqual Some(scatter0) + callC.closestCommonAncestor(callB) shouldEqual Some(scatter0) + callG.closestCommonAncestor(callH) shouldEqual Some(scatter0) + } + + it should "throw an exception if trying to re-assign children on a scope" in { + the [UnsupportedOperationException] thrownBy { namespace.workflow.children = Seq.empty } should have message "children is write-once" + } + + it should "throw an exception if trying to generate a workflow from a non-workflow ast" in { + val callAst: Ast = AstTools.findAsts(namespace.ast, AstNodeName.Call).head + the [UnsupportedOperationException] thrownBy { + Workflow(callAst, namespace.namespaces, namespace.tasks, namespace.wdlSyntaxErrorFormatter) + } should have message "Ast is not a 'Workflow Ast' but a 'Call Ast'" + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/SyntaxErrorSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/SyntaxErrorSpec.scala new file mode 100644 index 0000000..4dc58c6 --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/SyntaxErrorSpec.scala @@ -0,0 +1,260 @@ +package wdl4s + +import wdl4s.parser.WdlParser.SyntaxError +import org.scalatest.{FlatSpec, Matchers} + +class SyntaxErrorSpec extends FlatSpec with Matchers { + val psTaskWdl = """ + |task ps { + | command { + | ps + | } + | output { + | File procs = stdout() + | } + |}""".stripMargin + + val cgrepTaskWdl = """ + |task cgrep { + | String pattern + | File in_file + | command { + | grep '${pattern}' ${in_file} | wc -l + | } + | output { + | Int count = read_int(stdout()) + | } + |}""".stripMargin + + def resolver(importUri: String): WdlSource = { + importUri match { + case "ps" => psTaskWdl + case "cgrep" => cgrepTaskWdl + case _ => throw new RuntimeException(s"Can't resolve $importUri") + } + } + + private def expectError(wdl: String) = { + try { + val namespace = WdlNamespace.load(wdl, resolver _) + fail("Exception expected") + } catch { + case x: SyntaxError => // expected + } + } + + "WDL syntax checker" should "detect error when call references bad input" in { + expectError(""" + |task ps { + | command { + | ps + | } + | output { + | File procs = stdout() + | } + |} + |task cgrep { + | String pattern + | File in_file + | command { + | grep '${pattern}' ${in_file} | wc -l + | } + | output { + | Int count = read_int(stdout()) + | } + |} + |workflow three_step { + | call ps + | call cgrep { + | input: BADin_file=ps.procs + | } + |}""".stripMargin) + } + + it should "detect error when call references bad task" in { + expectError(""" + |task ps { + | command { + | ps + | } + | output { + | File procs = stdout() + | } + |} + |task cgrep { + | String pattern + | File in_file + | command { + | grep '${pattern}' ${in_file} | wc -l + | } + | output { + | Int count = read_int(stdout()) + | } + |} + |workflow three_step { + | call ps + | call cgrepBAD { + | input: in_file=ps.procs + | } + |}""".stripMargin) + } + it should "detect error when more than one workflow is defined" in { + expectError(""" + |task ps { + | command { + | ps + | } + | output { + | File procs = stdout() + | } + |} + |task cgrep { + | File in_file + | String pattern + | command { + | grep '${pattern}' ${in_file} | wc -l + | } + | output { + | Int count = read_int(stdout()) + | } + |} + |workflow three_step { + | call ps + | call cgrep { + | input: in_file=ps.procs + | } + |} + |workflow BAD {}""".stripMargin) + } + it should "detect error when namespace and task have the same name" in { + expectError(""" + |import "ps" as ps + |task ps {command {ps}} + |workflow three_step { + | call ps + |} + |""".stripMargin) + } + it should "detect error when namespace and workflow have the same name" in { + expectError(""" + |import "ps" as ps + |workflow ps { + | call ps + |} + |""".stripMargin) + } + it should "detect error when two tasks have the same name" in { + expectError(""" + |import "ps" + |task ps {command {ps}} + |workflow three_step { + | call ps + |} + |""".stripMargin) + } + it should "detect error a MemberAccess references a non-existent input on a task" in { + expectError(""" + |import "ps" + |import "cgrep" + |workflow three_step { + | call ps + | call cgrep { + | input: pattern=ps.BAD + | } + |} + |""".stripMargin) + } + it should "detect error a MemberAccess references a non-existent left-hand side" in { + expectError(""" + |import "ps" + |import "cgrep" + |workflow three_step { + | call ps + | call cgrep { + | input: pattern=psBAD.procs + | } + |} + |""".stripMargin) + } + it should "detect unexpected EOF" in { + expectError("workflow") + } + it should "detect unexpected symbol" in { + expectError("workflow foo workflow") + } + it should "detect extraneous symbols" in { + expectError("workflow foo {}}") + } + it should "detect when two call definitions have the same name" in { + expectError( + """ + |task x { + | command { ps } + |} + | + |workflow wf { + | call x + | call x + |} + """.stripMargin) + } + it should "detect when there are more than one 'input' sections in a call" in { + expectError( + """ + |task x { + | String a + | String b + | command { ./script ${a} ${b} } + |} + | + |workflow wf { + | call x { + | input: a = "a" + | input: b = "b" + | } + |} + """.stripMargin) + } + it should "detect when there are two workflows defined in a WDL file" in { + expectError( + """workflow w {} + |workflow x {} + """.stripMargin) + } + it should "detect when a Map does not have two parameterized types" in { + expectError( + """workflow w { + | Map[Int] i + |} + """.stripMargin) + } + it should "detect when task output section declares an output with incompatible types" in { + expectError( + """task a { + | command { ./script } + | output { + | Array[String] x = "bad value" + | } + |} + | + |workflow w { + | call a + |} + """.stripMargin) + } + it should "detect when task output section declares an output with incompatible types 2" in { + expectError( + """task a { + | command { ./script } + | output { + | Int x = "bad value" + | } + |} + | + |workflow w { + | call a + |} + """.stripMargin) + } +} + diff --git a/scala/wdl4s/src/test/scala/wdl4s/SyntaxHighlightSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/SyntaxHighlightSpec.scala new file mode 100644 index 0000000..4829e52 --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/SyntaxHighlightSpec.scala @@ -0,0 +1,203 @@ +package wdl4s + +import wdl4s.formatter.{AnsiSyntaxHighlighter, HtmlSyntaxHighlighter, SyntaxFormatter} +import org.scalatest.{Matchers, WordSpecLike} + +class SyntaxHighlightSpec extends Matchers with WordSpecLike { + + "SyntaxFormatter for simple workflow" should { + val namespace = WdlNamespace.load( + """task t { + | String f + | Int p + | command { + | ./cmd ${f} ${p} + | } + |} + |workflow w { + | Array[String] a = ["foo", "bar", "baz"] + | call t + | call t as u { + | input: f="abc", p=2 + | } + | scatter (b in a) { + | call t as v { + | input: f=t, p=3 + | } + | } + |} + """.stripMargin + ) + + val console = + """\u001b[38;5;214mtask\u001b[0m \u001b[38;5;253mt\u001b[0m { + | \u001b[38;5;33mString\u001b[0m \u001b[38;5;112mf\u001b[0m + | \u001b[38;5;33mInt\u001b[0m \u001b[38;5;112mp\u001b[0m + | \u001b[38;5;214mcommand\u001b[0m { + | ./cmd ${f} ${p} + | } + |} + | + |\u001b[38;5;214mworkflow\u001b[0m \u001b[38;5;253mw\u001b[0m { + | \u001b[38;5;33mArray[String]\u001b[0m \u001b[38;5;112ma\u001b[0m = ["foo","bar","baz"] + | \u001b[38;5;214mcall\u001b[0m \u001b[38;5;253mt\u001b[0m + | \u001b[38;5;214mcall\u001b[0m \u001b[38;5;253mt\u001b[0m as u { + | input: f="abc", p=2 + | } + | \u001b[38;5;214mscatter\u001b[0m (b in a) { + | \u001b[38;5;214mcall\u001b[0m \u001b[38;5;253mt\u001b[0m as v { + | input: f=t, p=3 + | } + | } + |}""".stripMargin + + val html = + """task t { + | String f + | Int p + | command { + | ./cmd ${f} ${p} + | } + |} + | + |workflow w { + | Array[String] a = ["foo","bar","baz"] + | call t + | call t as u { + | input: f="abc", p=2 + | } + | scatter (b in a) { + | call t as v { + | input: f=t, p=3 + | } + | } + |}""".stripMargin + + "format to console properly" in { + val actual = new SyntaxFormatter(AnsiSyntaxHighlighter).format(namespace) + actual shouldEqual console + } + + "format to HTML properly" in { + val actual = new SyntaxFormatter(HtmlSyntaxHighlighter).format(namespace) + actual shouldEqual html + } + } + + "SyntaxFormatter for more feature-rich workflow" should { + val namespace = WdlNamespace.load( + """import "foo.wdl" as foo_ns + | + |task t { + | String f + | Int p + | command { + | ./cmd ${f} ${p} + | } + |} + | + |task s { + | Array[File] input_file + | command <<< + | cat ${sep=' ' input_file} | awk '{s+=$1} END {print s}' + | >>> + | output { + | String s = read_string(stdout()) + | } + |} + | + |task r { + | command { python -c "import random; print(random.randint(1,100))" } + |} + | + |workflow w { + | Int p = 2+2 + | call t + | call t as u { + | input: f="abc", p=p + | } + |}""".stripMargin, + importResolver = (s:String) => "" + ) + + val console = + """\u001b[38;5;214mimport\u001b[0m 'foo.wdl' as foo_ns + | + |\u001b[38;5;214mtask\u001b[0m \u001b[38;5;253mt\u001b[0m { + | \u001b[38;5;33mString\u001b[0m \u001b[38;5;112mf\u001b[0m + | \u001b[38;5;33mInt\u001b[0m \u001b[38;5;112mp\u001b[0m + | \u001b[38;5;214mcommand\u001b[0m { + | ./cmd ${f} ${p} + | } + |} + | + |\u001b[38;5;214mtask\u001b[0m \u001b[38;5;253ms\u001b[0m { + | \u001b[38;5;33mArray[File]\u001b[0m \u001b[38;5;112minput_file\u001b[0m + | \u001b[38;5;214mcommand\u001b[0m <<< + | cat ${sep=" " input_file} | awk '{s+=$1} END {print s}' + | >>> + | \u001b[38;5;214moutput\u001b[0m { + | \u001b[38;5;33mString\u001b[0m \u001b[38;5;112ms\u001b[0m = \u001b[38;5;13mread_string\u001b[0m(\u001b[38;5;13mstdout\u001b[0m()) + | } + |} + | + |\u001b[38;5;214mtask\u001b[0m \u001b[38;5;253mr\u001b[0m { + | \u001b[38;5;214mcommand\u001b[0m { + | python -c "import random; print(random.randint(1,100))" + | } + |} + | + |\u001b[38;5;214mworkflow\u001b[0m \u001b[38;5;253mw\u001b[0m { + | \u001b[38;5;33mInt\u001b[0m \u001b[38;5;112mp\u001b[0m = 2 + 2 + | \u001b[38;5;214mcall\u001b[0m \u001b[38;5;253mt\u001b[0m + | \u001b[38;5;214mcall\u001b[0m \u001b[38;5;253mt\u001b[0m as u { + | input: f="abc", p=p + | } + |}""".stripMargin + + val html = + """import 'foo.wdl' as foo_ns + | + |task t { + | String f + | Int p + | command { + | ./cmd ${f} ${p} + | } + |} + | + |task s { + | Array[File] input_file + | command <<< + | cat ${sep=" " input_file} | awk '{s+=$1} END {print s}' + | >>> + | output { + | String s = read_string(stdout()) + | } + |} + | + |task r { + | command { + | python -c "import random; print(random.randint(1,100))" + | } + |} + | + |workflow w { + | Int p = 2 + 2 + | call t + | call t as u { + | input: f="abc", p=p + | } + |}""".stripMargin + + "format to console properly" in { + val actual = new SyntaxFormatter(AnsiSyntaxHighlighter).format(namespace) + actual shouldEqual console + } + + "format to HTML properly" in { + val actual = new SyntaxFormatter(HtmlSyntaxHighlighter).format(namespace) + actual shouldEqual html + } + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/TestFileUtil.scala b/scala/wdl4s/src/test/scala/wdl4s/TestFileUtil.scala new file mode 100644 index 0000000..868823d --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/TestFileUtil.scala @@ -0,0 +1,31 @@ +package wdl4s + +import java.io.{FileWriter, File} +import java.nio.file.{Files, Path} + +import wdl4s.values._ + +trait TestFileUtil { + private def write(file: File, contents: String) = { + val writer = new FileWriter(file) + writer.write(contents) + writer.flush() + writer.close() + file + } + + def createCannedFile(prefix: String, contents: String, dir: Option[Path] = None): File = { + val suffix = ".out" + val file = dir match { + case Some(path) => Files.createTempFile(path, prefix, suffix) + case None => Files.createTempFile(prefix, suffix) + } + write(file.toFile, contents) + } + + def createFile(name: String, dir: Path, contents: String) = { + dir.toFile.mkdirs() + write(dir.resolve(name).toFile, contents) + } +} + diff --git a/scala/wdl4s/src/test/scala/wdl4s/ThreeStepImportNamespaceAliasSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/ThreeStepImportNamespaceAliasSpec.scala new file mode 100644 index 0000000..a3475c0 --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/ThreeStepImportNamespaceAliasSpec.scala @@ -0,0 +1,90 @@ +package wdl4s + +import org.scalatest.{FlatSpec, Matchers} + +class ThreeStepImportNamespaceAliasSpec extends FlatSpec with Matchers { + val psTaskWdl = """ + |task ps { + | command { + | ps + | } + | output { + | File procs = stdout() + | } + |}""".stripMargin + + val cgrepTaskWdl = """ + |task cgrep { + | String pattern + | File in_file + | command { + | grep '${pattern}' ${in_file} | wc -l + | } + | output { + | Int count = read_int(stdout()) + | } + |}""".stripMargin + + val wcTaskWdl = """ + |task wc { + | File in_file + | command { + | cat ${in_file} | wc -l + | } + | output { + | Int count = read_int(stdout()) + | } + |}""".stripMargin + + val workflowWdl = """ + |import "ps" as ns1 + |import "cgrep" as ns2 + |import "wc" as ns3 + | + |workflow three_step { + | call ns1.ps as a1 + | call ns2.cgrep as a2 { + | input: in_file=a1.procs + | } + | call ns3.wc { + | input: in_file=a1.procs + | } + |}""".stripMargin + + def resolver(importUri: String): WdlSource = { + importUri match { + case "ps" => psTaskWdl + case "cgrep" => cgrepTaskWdl + case "wc" => wcTaskWdl + case _ => throw new RuntimeException(s"Can't resolve $importUri") + } + } + + val namespace = NamespaceWithWorkflow.load(workflowWdl, resolver _) + + "WDL file with imports" should "Have 0 tasks (3 tasks are in separate namespace)" in { + namespace.tasks.size shouldEqual 0 + } + + it should "Have 3 imported WdlBindings" in { + namespace.namespaces.size shouldEqual 3 + } + it should "Have 3 imported WdlBindings with tasks 'ps', 'cgrep', and 'wc'" in { + namespace.namespaces flatMap {_.tasks} map {_.name} shouldEqual Seq("ps", "cgrep", "wc") + } + it should "Have 3 calls in the workflow, 2 of them aliased" in { + namespace.workflow.calls map {_.unqualifiedName} shouldEqual Seq("a1", "a2", "ns3.wc") + } + it should "Throw an exception if the import resolver fails to resolve an import" in { + def badResolver(s: String): String = { + throw new RuntimeException(s"Can't Resolve") + } + try { + val badBinding = WdlNamespace.load(workflowWdl, badResolver _) + fail("Expecting an exception to be thrown when using badResolver") + } catch { + case _: RuntimeException => + } + } +} + diff --git a/scala/wdl4s/src/test/scala/wdl4s/ThreeStepImportNamespaceSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/ThreeStepImportNamespaceSpec.scala new file mode 100644 index 0000000..79edc73 --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/ThreeStepImportNamespaceSpec.scala @@ -0,0 +1,87 @@ +package wdl4s + +import org.scalatest.{FlatSpec, Matchers} + +class ThreeStepImportNamespaceSpec extends FlatSpec with Matchers { + val psTaskWdl = """ + |task ps { + | command { + | ps + | } + | output { + | File procs = stdout() + | } + |}""".stripMargin + + val cgrepTaskWdl = """ + |task cgrep { + | String pattern + | String in_file + | command { + | grep '${pattern}' ${in_file} | wc -l + | } + | output { + | Int count = read_int(stdout()) + | } + |}""".stripMargin + + val wcTaskWdl = """ + |task wc { + | File in_file + | command { + | cat ${in_file} | wc -l + | } + | output { + | Int count = read_int(stdout()) + | } + |}""".stripMargin + + val workflowWdl = """ + |import "ps" as ns1 + |import "cgrep" as ns2 + |import "wc" as ns3 + | + |workflow three_step { + | call ns1.ps + | call ns2.cgrep { + | input: in_file=ns1.ps.procs + | } + | call ns3.wc { + | input: in_file=ns1.ps.procs + | } + |}""".stripMargin + + def resolver(importUri: String): WdlSource = { + importUri match { + case "ps" => psTaskWdl + case "cgrep" => cgrepTaskWdl + case "wc" => wcTaskWdl + case _ => throw new RuntimeException(s"Can't resolve $importUri") + } + } + + val namespace = NamespaceWithWorkflow.load(workflowWdl, resolver _) + + + "WDL file with imports" should "Have 0 tasks (3 tasks are in separate namespace)" in { + namespace.tasks.size shouldEqual 0 + } + it should "Have 3 imported WdlBindings" in { + namespace.namespaces.size shouldEqual 3 + } + it should "Have 3 imported WdlBindings with tasks 'ps', 'cgrep', and 'wc'" in { + namespace.namespaces flatMap {_.tasks} map {_.name} shouldEqual Seq("ps", "cgrep", "wc") + } + it should "Throw an exception if the import resolver fails to resolve an import" in { + def badResolver(s: String): String = { + throw new RuntimeException(s"Can't Resolve") + } + try { + val badBinding = WdlNamespace.load(workflowWdl, badResolver _) + fail("Expecting an exception to be thrown when using badResolver") + } catch { + case _: RuntimeException => + } + } +} + diff --git a/scala/wdl4s/src/test/scala/wdl4s/ThreeStepImportSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/ThreeStepImportSpec.scala new file mode 100644 index 0000000..2ce487f --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/ThreeStepImportSpec.scala @@ -0,0 +1,86 @@ +package wdl4s + +import org.scalatest.{FlatSpec, Matchers} + +class ThreeStepImportSpec extends FlatSpec with Matchers { + val psTaskWdl = """ + |task ps { + | command { + | ps + | } + | output { + | File procs = stdout() + | } + |}""".stripMargin + + val cgrepTaskWdl = """ + |task cgrep { + | String pattern + | File in_file + | command { + | grep '${pattern}' ${in_file} | wc -l + | } + | output { + | Int count = read_int(stdout()) + | } + |}""".stripMargin + + val wcTaskWdl = """ + |task wc { + | File in_file + | command { + | cat ${in_file} | wc -l + | } + | output { + | Int count = read_int(stdout()) + | } + |}""".stripMargin + + val workflowWdl = """ + |import "ps" + |import "cgrep" + |import "wc" + | + |workflow three_step { + | call ps + | call cgrep { + | input: in_file=ps.procs + | } + | call wc { + | input: in_file=ps.procs + | } + |}""".stripMargin + + def resolver(importUri: String): String = { + importUri match { + case "ps" => psTaskWdl + case "cgrep" => cgrepTaskWdl + case "wc" => wcTaskWdl + case _ => throw new RuntimeException(s"Can't resolve $importUri") + } + } + + val namespace = NamespaceWithWorkflow.load(workflowWdl, resolver _) + + "WDL file with imports" should "Have 3 tasks" in { + namespace.tasks.size shouldEqual 3 + } + + it should "Have 3 imported WdlBindings" in { + namespace.namespaces.size shouldEqual 3 + } + it should "Have tasks with the names 'ps', 'cgrep' and 'wc'" in { + namespace.tasks map {_.name} shouldEqual Seq("ps", "cgrep", "wc") + } + it should "Throw an exception if the import resolver fails to resolve an import" in { + def badResolver(s: String): String = { + throw new RuntimeException(s"Can't Resolve") + } + try { + val badBinding = WdlNamespace.load(workflowWdl, badResolver _) + fail("Expecting an exception to be thrown when using badResolver") + } catch { + case _: RuntimeException => + } + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/ThreeStepSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/ThreeStepSpec.scala new file mode 100644 index 0000000..6d7f283 --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/ThreeStepSpec.scala @@ -0,0 +1,62 @@ +package wdl4s + +import wdl4s.expression.NoFunctions +import wdl4s.types.{WdlFileType, WdlIntegerType, WdlStringType} +import wdl4s.values.{WdlFile, WdlString} +import org.scalatest.{FlatSpec, Matchers} + +class ThreeStepSpec extends FlatSpec with Matchers { + val namespace = NamespaceWithWorkflow.load(SampleWdl.ThreeStep.wdlSource()) + + "Binding Workflow" should "Have correct name for workflow" in { + namespace.workflow.unqualifiedName shouldEqual "three_step" + } + it should "Have correct FQN" in { + namespace.workflow.fullyQualifiedName shouldEqual "three_step" + } + it should "Have no parent" in { + namespace.workflow.parent shouldEqual None + } + it should "Have three 'Call' objects" in { + namespace.workflow.calls.size shouldEqual 3 + } + it should "Have three outputs" in { + namespace.workflow.outputs.size shouldEqual 3 + } + + "Binding Tasks" should "Have three task definitions" in { + namespace.tasks.size shouldEqual 3 + } + + it should "Have a task with name 'wc'" in { + val task = namespace.findTask("wc") getOrElse fail("No 'wc' task found") + task.name shouldEqual "wc" + task.inputs shouldEqual Vector(TaskInput("in_file", WdlFileType, postfixQuantifier=None)) + task.instantiateCommand(Map("in_file" -> WdlFile("/path/to/file")), NoFunctions).get shouldEqual "cat /path/to/file | wc -l" + task.outputs.size shouldEqual 1 + task.outputs.head.name shouldEqual "count" + task.outputs.head.wdlType shouldEqual WdlIntegerType + } + it should "Have a task with name 'cgrep'" in { + val task = namespace.findTask("cgrep") getOrElse fail("No 'cgrep' task found") + task.name shouldEqual "cgrep" + task.inputs shouldEqual Vector( + TaskInput("pattern", WdlStringType, postfixQuantifier=None), + TaskInput("in_file", WdlFileType, postfixQuantifier=None) + ) + task.instantiateCommand(Map("pattern" -> WdlString("^...$"), "in_file" -> WdlFile("/path/to/file")), + NoFunctions).get shouldEqual "grep '^...$' /path/to/file | wc -l" + task.outputs.size shouldEqual 1 + task.outputs.head.name shouldEqual "count" + task.outputs.head.wdlType shouldEqual WdlIntegerType + } + it should "Have a task with name 'ps'" in { + val task = namespace.findTask("ps") getOrElse fail("No 'ps' task found") + task.name shouldEqual "ps" + task.inputs shouldEqual Vector() + task.instantiateCommand(Map(), NoFunctions).get shouldEqual "ps" + task.outputs.size shouldEqual 1 + task.outputs.head.name shouldEqual "procs" + task.outputs.head.wdlType shouldEqual WdlFileType + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/expression/FileEvaluatorSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/expression/FileEvaluatorSpec.scala new file mode 100644 index 0000000..f96f7f8 --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/expression/FileEvaluatorSpec.scala @@ -0,0 +1,56 @@ +package wdl4s.expression + +import wdl4s.WdlExpression +import wdl4s.types._ +import wdl4s.values._ +import org.scalatest.prop.TableDrivenPropertyChecks._ +import org.scalatest.{FlatSpec, Matchers} + +import scala.language.postfixOps + +class FileEvaluatorSpec extends FlatSpec with Matchers { + val expr: String => WdlExpression = WdlExpression.fromString + def noLookup(String: String): WdlValue = fail("No identifiers should be looked up in this test") + + val expressions = Table( + ("expression", "files", "wdlType"), + ("1 + 1", Seq.empty[WdlFile], WdlIntegerType), + ("stdout() + stderr()", Seq.empty[WdlFile], WdlFileType), + ("""read_int("myfile.txt")""", Seq(WdlSingleFile("myfile.txt")), WdlIntegerType), + ("""read_int("/bin/bash/" + "myfile.txt")""", Seq(WdlSingleFile("/bin/bash/myfile.txt")), WdlIntegerType), + ("read_int(stdout())", Seq.empty[WdlFile], WdlIntegerType), + ("read_int(stdout() + 3)", Seq.empty[WdlFile], WdlIntegerType), + ("""read_int("/etc/" + read_int("somefile") + ".txt"))""", Seq(WdlSingleFile("somefile")), WdlIntegerType), + ("""-read_int("/etc/file1")""", Seq(WdlSingleFile("/etc/file1")), WdlIntegerType), + ("""read_int("/etc/file1") + read_string("/bin/file2")""", Seq(WdlSingleFile("/etc/file1"), WdlSingleFile("/bin/file2")), WdlStringType), + ("""read_int("/etc/file1") + read_string("/bin/file2") + read_string("/bin/file3") + read_string("/bin/file4") + read_string("/bin/file5")""", Seq( + WdlSingleFile("/etc/file1"), + WdlSingleFile("/bin/file2"), + WdlSingleFile("/bin/file3"), + WdlSingleFile("/bin/file4"), + WdlSingleFile("/bin/file5") + ), WdlStringType), + (""" "foo" + "bar" """, Seq(WdlSingleFile("foobar")), WdlFileType), + (""" "foo" + "bar" """, Seq.empty[WdlFile], WdlStringType), + (""" ["a", "b", "c"] """, Seq(WdlSingleFile("a"), WdlSingleFile("b"), WdlSingleFile("c")), WdlArrayType(WdlFileType)), + (""" ["a", "b", "c"] """, Seq.empty[WdlFile], WdlArrayType(WdlStringType)), + (""" {"a": "1", "b": "2", "c":"3"} """, Seq( + WdlSingleFile("a"), + WdlSingleFile("1"), + WdlSingleFile("b"), + WdlSingleFile("2"), + WdlSingleFile("c"), + WdlSingleFile("3") + ), WdlMapType(WdlFileType, WdlFileType)), + (""" [read_string("x"), read_string("y")] """, Seq(WdlSingleFile("x"), WdlSingleFile("y")), WdlArrayType(WdlStringType)), + (""" {read_int("a"): read_string("x"), 4: read_string("y")} """, Seq(WdlSingleFile("a"), WdlSingleFile("x"), WdlSingleFile("y")), WdlArrayType(WdlStringType)), + (""" glob("out-*.txt") """, Seq(WdlGlobFile("out-*.txt")), WdlFileType), + (""" read_tsv("my_file") """, Seq(WdlSingleFile("my_file")), WdlFileType) + ) + + forAll (expressions) { (expression, files, wdlType) => + it should s"evaluate $expression (coerced to: $wdlType) => $files" in { + expr(expression).evaluateFiles(noLookup, NoFunctions, wdlType).get shouldEqual files + } + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/expression/TypeEvaluatorSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/expression/TypeEvaluatorSpec.scala new file mode 100644 index 0000000..17ccf6f --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/expression/TypeEvaluatorSpec.scala @@ -0,0 +1,403 @@ +package wdl4s.expression + +import wdl4s.types._ +import wdl4s.values._ +import wdl4s.{SampleWdl, NamespaceWithWorkflow, WdlExpression} +import org.scalatest.prop.TableDrivenPropertyChecks._ +import org.scalatest.{FlatSpec, Matchers} + +import scala.util.{Failure, Success, Try} + +class TypeEvaluatorSpec extends FlatSpec with Matchers { + val expr: String => WdlExpression = WdlExpression.fromString + val namespace = NamespaceWithWorkflow.load(SampleWdl.ThreeStep.wdlSource()) + + def noLookup(String: String): WdlType = fail("No identifiers should be looked up in this test") + + def identifierLookup(String: String): WdlType = { + String match { + case "cgrep" => WdlCallOutputsObjectType(namespace.workflow.calls.find(_.unqualifiedName == "cgrep").get) + case "ps" => WdlCallOutputsObjectType(namespace.workflow.calls.find(_.unqualifiedName == "ps").get) + } + } + + def identifierEval(exprStr: String): WdlPrimitiveType = expr(exprStr).evaluateType(identifierLookup, new WdlStandardLibraryFunctionsType).asInstanceOf[Try[WdlPrimitiveType]].get + def identifierEvalError(exprStr: String): Unit = { + expr(exprStr).evaluateType(identifierLookup, new WdlStandardLibraryFunctionsType).asInstanceOf[Try[WdlPrimitive]] match { + case Failure(ex) => // Expected + case Success(badValue) => fail(s"Operation was supposed to fail, instead I got value: $badValue") + } + } + + private def operate(lhs: WdlType, op: String, rhs: WdlType): Try[WdlType] = op match { + case "+" => lhs.add(rhs) + case "-" => lhs.subtract(rhs) + case "*" => lhs.multiply(rhs) + case "/" => lhs.divide(rhs) + case "%" => lhs.mod(rhs) + case "==" => lhs.equals(rhs) + case "!=" => lhs.notEquals(rhs) + case "<" => lhs.lessThan(rhs) + case "<=" => lhs.lessThanOrEqual(rhs) + case ">" => lhs.greaterThan(rhs) + case ">=" => lhs.greaterThanOrEqual(rhs) + case "||" => lhs.or(rhs) + case "&&" => lhs.and(rhs) + case _ => fail(s"unexpected operator: $op") + } + + val validOperations = Table( + ("lhs", "op", "rhs", "result"), + (WdlIntegerType, "+", WdlIntegerType, WdlIntegerType), + (WdlIntegerType, "+", WdlFloatType, WdlFloatType), + (WdlIntegerType, "+", WdlStringType, WdlStringType), + (WdlIntegerType, "-", WdlIntegerType, WdlIntegerType), + (WdlIntegerType, "-", WdlFloatType, WdlFloatType), + (WdlIntegerType, "*", WdlIntegerType, WdlIntegerType), + (WdlIntegerType, "*", WdlFloatType, WdlFloatType), + (WdlIntegerType, "/", WdlIntegerType, WdlIntegerType), + (WdlIntegerType, "/", WdlFloatType, WdlFloatType), + (WdlIntegerType, "%", WdlIntegerType, WdlIntegerType), + (WdlIntegerType, "%", WdlFloatType, WdlFloatType), + (WdlIntegerType, "==", WdlIntegerType, WdlBooleanType), + (WdlIntegerType, "==", WdlFloatType, WdlBooleanType), + (WdlIntegerType, "!=", WdlIntegerType, WdlBooleanType), + (WdlIntegerType, "!=", WdlFloatType, WdlBooleanType), + (WdlIntegerType, "<", WdlIntegerType, WdlBooleanType), + (WdlIntegerType, "<", WdlFloatType, WdlBooleanType), + (WdlIntegerType, "<=", WdlIntegerType, WdlBooleanType), + (WdlIntegerType, "<=", WdlFloatType, WdlBooleanType), + (WdlIntegerType, ">", WdlIntegerType, WdlBooleanType), + (WdlIntegerType, ">", WdlFloatType, WdlBooleanType), + (WdlIntegerType, ">=", WdlIntegerType, WdlBooleanType), + (WdlIntegerType, ">=", WdlFloatType, WdlBooleanType), + (WdlFloatType, "+", WdlIntegerType, WdlFloatType), + (WdlFloatType, "+", WdlFloatType, WdlFloatType), + (WdlFloatType, "+", WdlStringType, WdlStringType), + (WdlFloatType, "-", WdlIntegerType, WdlFloatType), + (WdlFloatType, "-", WdlFloatType, WdlFloatType), + (WdlFloatType, "*", WdlIntegerType, WdlFloatType), + (WdlFloatType, "*", WdlFloatType, WdlFloatType), + (WdlFloatType, "/", WdlIntegerType, WdlFloatType), + (WdlFloatType, "/", WdlFloatType, WdlFloatType), + (WdlFloatType, "%", WdlIntegerType, WdlFloatType), + (WdlFloatType, "%", WdlFloatType, WdlFloatType), + (WdlFloatType, "==", WdlIntegerType, WdlBooleanType), + (WdlFloatType, "==", WdlFloatType, WdlBooleanType), + (WdlFloatType, "!=", WdlIntegerType, WdlBooleanType), + (WdlFloatType, "!=", WdlFloatType, WdlBooleanType), + (WdlFloatType, "<", WdlIntegerType, WdlBooleanType), + (WdlFloatType, "<", WdlFloatType, WdlBooleanType), + (WdlFloatType, "<=", WdlIntegerType, WdlBooleanType), + (WdlFloatType, "<=", WdlFloatType, WdlBooleanType), + (WdlFloatType, ">", WdlIntegerType, WdlBooleanType), + (WdlFloatType, ">", WdlFloatType, WdlBooleanType), + (WdlFloatType, ">=", WdlIntegerType, WdlBooleanType), + (WdlFloatType, ">=", WdlFloatType, WdlBooleanType), + (WdlStringType, "+", WdlIntegerType, WdlStringType), + (WdlStringType, "+", WdlFloatType, WdlStringType), + (WdlStringType, "+", WdlStringType, WdlStringType), + (WdlStringType, "+", WdlFileType, WdlStringType), + (WdlStringType, "==", WdlStringType, WdlBooleanType), + (WdlStringType, "!=", WdlStringType, WdlBooleanType), + (WdlStringType, "<", WdlStringType, WdlBooleanType), + (WdlStringType, "<=", WdlStringType, WdlBooleanType), + (WdlStringType, ">", WdlStringType, WdlBooleanType), + (WdlStringType, ">=", WdlStringType, WdlBooleanType), + (WdlFileType, "+", WdlStringType, WdlFileType), + (WdlFileType, "==", WdlStringType, WdlBooleanType), + (WdlFileType, "==", WdlFileType, WdlBooleanType), + (WdlFileType, "!=", WdlStringType, WdlBooleanType), + (WdlFileType, "!=", WdlFileType, WdlBooleanType), + (WdlFileType, "<=", WdlStringType, WdlBooleanType), + (WdlFileType, "<=", WdlFileType, WdlBooleanType), + (WdlFileType, ">=", WdlStringType, WdlBooleanType), + (WdlFileType, ">=", WdlFileType, WdlBooleanType), + (WdlBooleanType, "==", WdlBooleanType, WdlBooleanType), + (WdlBooleanType, "!=", WdlBooleanType, WdlBooleanType), + (WdlBooleanType, "<", WdlBooleanType, WdlBooleanType), + (WdlBooleanType, "<=", WdlBooleanType, WdlBooleanType), + (WdlBooleanType, ">", WdlBooleanType, WdlBooleanType), + (WdlBooleanType, ">=", WdlBooleanType, WdlBooleanType), + (WdlBooleanType, "||", WdlBooleanType, WdlBooleanType), + (WdlBooleanType, "&&", WdlBooleanType, WdlBooleanType) + ) + + val invalidOperations = Table( + ("lhs", "op", "rhs"), + (WdlIntegerType, "+", WdlFileType), + (WdlIntegerType, "+", WdlBooleanType), + (WdlIntegerType, "-", WdlStringType), + (WdlIntegerType, "-", WdlFileType), + (WdlIntegerType, "-", WdlBooleanType), + (WdlIntegerType, "*", WdlStringType), + (WdlIntegerType, "*", WdlFileType), + (WdlIntegerType, "*", WdlBooleanType), + (WdlIntegerType, "/", WdlStringType), + (WdlIntegerType, "/", WdlFileType), + (WdlIntegerType, "/", WdlBooleanType), + (WdlIntegerType, "%", WdlStringType), + (WdlIntegerType, "%", WdlFileType), + (WdlIntegerType, "%", WdlBooleanType), + (WdlIntegerType, "==", WdlStringType), + (WdlIntegerType, "==", WdlFileType), + (WdlIntegerType, "==", WdlBooleanType), + (WdlIntegerType, "!=", WdlStringType), + (WdlIntegerType, "!=", WdlFileType), + (WdlIntegerType, "!=", WdlBooleanType), + (WdlIntegerType, "<", WdlStringType), + (WdlIntegerType, "<", WdlFileType), + (WdlIntegerType, "<", WdlBooleanType), + (WdlIntegerType, "<=", WdlStringType), + (WdlIntegerType, "<=", WdlFileType), + (WdlIntegerType, "<=", WdlBooleanType), + (WdlIntegerType, ">", WdlStringType), + (WdlIntegerType, ">", WdlFileType), + (WdlIntegerType, ">", WdlBooleanType), + (WdlIntegerType, ">=", WdlStringType), + (WdlIntegerType, ">=", WdlFileType), + (WdlIntegerType, ">=", WdlBooleanType), + (WdlIntegerType, "||", WdlIntegerType), + (WdlIntegerType, "||", WdlFloatType), + (WdlIntegerType, "||", WdlStringType), + (WdlIntegerType, "||", WdlFileType), + (WdlIntegerType, "||", WdlBooleanType), + (WdlIntegerType, "&&", WdlIntegerType), + (WdlIntegerType, "&&", WdlFloatType), + (WdlIntegerType, "&&", WdlStringType), + (WdlIntegerType, "&&", WdlFileType), + (WdlIntegerType, "&&", WdlBooleanType), + (WdlFloatType, "+", WdlFileType), + (WdlFloatType, "+", WdlBooleanType), + (WdlFloatType, "-", WdlStringType), + (WdlFloatType, "-", WdlFileType), + (WdlFloatType, "-", WdlBooleanType), + (WdlFloatType, "*", WdlStringType), + (WdlFloatType, "*", WdlFileType), + (WdlFloatType, "*", WdlBooleanType), + (WdlFloatType, "/", WdlStringType), + (WdlFloatType, "/", WdlFileType), + (WdlFloatType, "/", WdlBooleanType), + (WdlFloatType, "%", WdlStringType), + (WdlFloatType, "%", WdlFileType), + (WdlFloatType, "%", WdlBooleanType), + (WdlFloatType, "==", WdlStringType), + (WdlFloatType, "==", WdlFileType), + (WdlFloatType, "==", WdlBooleanType), + (WdlFloatType, "!=", WdlStringType), + (WdlFloatType, "!=", WdlFileType), + (WdlFloatType, "!=", WdlBooleanType), + (WdlFloatType, "<", WdlStringType), + (WdlFloatType, "<", WdlFileType), + (WdlFloatType, "<", WdlBooleanType), + (WdlFloatType, "<=", WdlStringType), + (WdlFloatType, "<=", WdlFileType), + (WdlFloatType, "<=", WdlBooleanType), + (WdlFloatType, ">", WdlStringType), + (WdlFloatType, ">", WdlFileType), + (WdlFloatType, ">", WdlBooleanType), + (WdlFloatType, ">=", WdlStringType), + (WdlFloatType, ">=", WdlFileType), + (WdlFloatType, ">=", WdlBooleanType), + (WdlFloatType, "||", WdlIntegerType), + (WdlFloatType, "||", WdlFloatType), + (WdlFloatType, "||", WdlStringType), + (WdlFloatType, "||", WdlFileType), + (WdlFloatType, "||", WdlBooleanType), + (WdlFloatType, "&&", WdlIntegerType), + (WdlFloatType, "&&", WdlFloatType), + (WdlFloatType, "&&", WdlStringType), + (WdlFloatType, "&&", WdlFileType), + (WdlFloatType, "&&", WdlBooleanType), + (WdlStringType, "+", WdlBooleanType), + (WdlStringType, "-", WdlIntegerType), + (WdlStringType, "-", WdlFloatType), + (WdlStringType, "-", WdlStringType), + (WdlStringType, "-", WdlFileType), + (WdlStringType, "-", WdlBooleanType), + (WdlStringType, "*", WdlIntegerType), + (WdlStringType, "*", WdlFloatType), + (WdlStringType, "*", WdlStringType), + (WdlStringType, "*", WdlFileType), + (WdlStringType, "*", WdlBooleanType), + (WdlStringType, "/", WdlIntegerType), + (WdlStringType, "/", WdlFloatType), + (WdlStringType, "/", WdlStringType), + (WdlStringType, "/", WdlFileType), + (WdlStringType, "/", WdlBooleanType), + (WdlStringType, "%", WdlIntegerType), + (WdlStringType, "%", WdlFloatType), + (WdlStringType, "%", WdlStringType), + (WdlStringType, "%", WdlFileType), + (WdlStringType, "%", WdlBooleanType), + (WdlStringType, "==", WdlIntegerType), + (WdlStringType, "==", WdlFloatType), + (WdlStringType, "==", WdlFileType), + (WdlStringType, "==", WdlBooleanType), + (WdlStringType, "!=", WdlIntegerType), + (WdlStringType, "!=", WdlFloatType), + (WdlStringType, "!=", WdlFileType), + (WdlStringType, "!=", WdlBooleanType), + (WdlStringType, "<", WdlIntegerType), + (WdlStringType, "<", WdlFloatType), + (WdlStringType, "<", WdlFileType), + (WdlStringType, "<", WdlBooleanType), + (WdlStringType, "<=", WdlIntegerType), + (WdlStringType, "<=", WdlFloatType), + (WdlStringType, "<=", WdlFileType), + (WdlStringType, "<=", WdlBooleanType), + (WdlStringType, ">", WdlIntegerType), + (WdlStringType, ">", WdlFloatType), + (WdlStringType, ">", WdlFileType), + (WdlStringType, ">", WdlBooleanType), + (WdlStringType, ">=", WdlIntegerType), + (WdlStringType, ">=", WdlFloatType), + (WdlStringType, ">=", WdlFileType), + (WdlStringType, ">=", WdlBooleanType), + (WdlStringType, "||", WdlIntegerType), + (WdlStringType, "||", WdlFloatType), + (WdlStringType, "||", WdlStringType), + (WdlStringType, "||", WdlFileType), + (WdlStringType, "||", WdlBooleanType), + (WdlStringType, "&&", WdlIntegerType), + (WdlStringType, "&&", WdlFloatType), + (WdlStringType, "&&", WdlStringType), + (WdlStringType, "&&", WdlFileType), + (WdlStringType, "&&", WdlBooleanType), + (WdlFileType, "+", WdlFileType), + (WdlFileType, "+", WdlIntegerType), + (WdlFileType, "+", WdlFloatType), + (WdlFileType, "+", WdlBooleanType), + (WdlFileType, "-", WdlIntegerType), + (WdlFileType, "-", WdlFloatType), + (WdlFileType, "-", WdlStringType), + (WdlFileType, "-", WdlFileType), + (WdlFileType, "-", WdlBooleanType), + (WdlFileType, "*", WdlIntegerType), + (WdlFileType, "*", WdlFloatType), + (WdlFileType, "*", WdlStringType), + (WdlFileType, "*", WdlFileType), + (WdlFileType, "*", WdlBooleanType), + (WdlFileType, "/", WdlIntegerType), + (WdlFileType, "/", WdlFloatType), + (WdlFileType, "/", WdlStringType), + (WdlFileType, "/", WdlFileType), + (WdlFileType, "/", WdlBooleanType), + (WdlFileType, "%", WdlIntegerType), + (WdlFileType, "%", WdlFloatType), + (WdlFileType, "%", WdlStringType), + (WdlFileType, "%", WdlFileType), + (WdlFileType, "%", WdlBooleanType), + (WdlFileType, "==", WdlIntegerType), + (WdlFileType, "==", WdlFloatType), + (WdlFileType, "==", WdlBooleanType), + (WdlFileType, "!=", WdlIntegerType), + (WdlFileType, "!=", WdlFloatType), + (WdlFileType, "!=", WdlBooleanType), + (WdlFileType, "<", WdlIntegerType), + (WdlFileType, "<", WdlFloatType), + (WdlFileType, "<", WdlStringType), + (WdlFileType, "<", WdlFileType), + (WdlFileType, "<", WdlBooleanType), + (WdlFileType, "<=", WdlIntegerType), + (WdlFileType, "<=", WdlFloatType), + (WdlFileType, "<=", WdlBooleanType), + (WdlFileType, ">", WdlIntegerType), + (WdlFileType, ">", WdlFloatType), + (WdlFileType, ">", WdlStringType), + (WdlFileType, ">", WdlFileType), + (WdlFileType, ">", WdlBooleanType), + (WdlFileType, ">=", WdlIntegerType), + (WdlFileType, ">=", WdlFloatType), + (WdlFileType, ">=", WdlBooleanType), + (WdlFileType, "||", WdlIntegerType), + (WdlFileType, "||", WdlFloatType), + (WdlFileType, "||", WdlStringType), + (WdlFileType, "||", WdlFileType), + (WdlFileType, "||", WdlBooleanType), + (WdlFileType, "&&", WdlIntegerType), + (WdlFileType, "&&", WdlFloatType), + (WdlFileType, "&&", WdlStringType), + (WdlFileType, "&&", WdlFileType), + (WdlFileType, "&&", WdlBooleanType), + (WdlBooleanType, "+", WdlIntegerType), + (WdlBooleanType, "+", WdlFloatType), + (WdlBooleanType, "+", WdlStringType), + (WdlBooleanType, "+", WdlFileType), + (WdlBooleanType, "+", WdlBooleanType), + (WdlBooleanType, "-", WdlIntegerType), + (WdlBooleanType, "-", WdlFloatType), + (WdlBooleanType, "-", WdlStringType), + (WdlBooleanType, "-", WdlFileType), + (WdlBooleanType, "-", WdlBooleanType), + (WdlBooleanType, "*", WdlIntegerType), + (WdlBooleanType, "*", WdlFloatType), + (WdlBooleanType, "*", WdlStringType), + (WdlBooleanType, "*", WdlFileType), + (WdlBooleanType, "*", WdlBooleanType), + (WdlBooleanType, "/", WdlIntegerType), + (WdlBooleanType, "/", WdlFloatType), + (WdlBooleanType, "/", WdlStringType), + (WdlBooleanType, "/", WdlFileType), + (WdlBooleanType, "/", WdlBooleanType), + (WdlBooleanType, "%", WdlIntegerType), + (WdlBooleanType, "%", WdlFloatType), + (WdlBooleanType, "%", WdlStringType), + (WdlBooleanType, "%", WdlFileType), + (WdlBooleanType, "%", WdlBooleanType), + (WdlBooleanType, "==", WdlIntegerType), + (WdlBooleanType, "==", WdlFloatType), + (WdlBooleanType, "==", WdlStringType), + (WdlBooleanType, "==", WdlFileType), + (WdlBooleanType, "!=", WdlIntegerType), + (WdlBooleanType, "!=", WdlFloatType), + (WdlBooleanType, "!=", WdlStringType), + (WdlBooleanType, "!=", WdlFileType), + (WdlBooleanType, "<", WdlIntegerType), + (WdlBooleanType, "<", WdlFloatType), + (WdlBooleanType, "<", WdlStringType), + (WdlBooleanType, "<", WdlFileType), + (WdlBooleanType, "<=", WdlIntegerType), + (WdlBooleanType, "<=", WdlFloatType), + (WdlBooleanType, "<=", WdlStringType), + (WdlBooleanType, "<=", WdlFileType), + (WdlBooleanType, ">", WdlIntegerType), + (WdlBooleanType, ">", WdlFloatType), + (WdlBooleanType, ">", WdlStringType), + (WdlBooleanType, ">", WdlFileType), + (WdlBooleanType, ">=", WdlIntegerType), + (WdlBooleanType, ">=", WdlFloatType), + (WdlBooleanType, ">=", WdlStringType), + (WdlBooleanType, ">=", WdlFileType), + (WdlBooleanType, "||", WdlIntegerType), + (WdlBooleanType, "||", WdlFloatType), + (WdlBooleanType, "||", WdlStringType), + (WdlBooleanType, "||", WdlFileType), + (WdlBooleanType, "&&", WdlIntegerType), + (WdlBooleanType, "&&", WdlFloatType), + (WdlBooleanType, "&&", WdlStringType), + (WdlBooleanType, "&&", WdlFileType) + ) + + forAll (validOperations) { (lhs, op, rhs, expectedType) => + it should s"validate the output type for the expression: $lhs $op $rhs = $expectedType" in { + operate(lhs, op, rhs) shouldEqual Success(expectedType) + } + } + + forAll (invalidOperations) { (lhs, op, rhs) => + it should s"not allow the expression: $lhs $op $rhs" in { + operate(lhs, op, rhs) should be(a[Failure[_]]) + } + } + + "Expression Evaluator with Object as LHS" should "Lookup object string attribute" in { + identifierEval("cgrep.count") shouldEqual WdlIntegerType + } + it should "Lookup object integer attribute" in { + identifierEval("ps.procs") shouldEqual WdlFileType + } + it should "Error if key doesn't exist" in { + identifierEvalError("ps.badkey") + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/expression/ValueEvaluatorSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/expression/ValueEvaluatorSpec.scala new file mode 100644 index 0000000..90a5bd3 --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/expression/ValueEvaluatorSpec.scala @@ -0,0 +1,386 @@ +package wdl4s.expression + +import wdl4s.WdlExpression +import wdl4s.types._ +import wdl4s.values._ +import org.scalatest.prop.TableDrivenPropertyChecks._ +import org.scalatest.{FlatSpec, Matchers} + +import scala.util.{Failure, Success, Try} + +class ValueEvaluatorSpec extends FlatSpec with Matchers { + val expr: String => WdlExpression = WdlExpression.fromString + + def noLookup(String: String): WdlValue = fail("No identifiers should be looked up in this test") + def noTypeLookup(String: String): WdlType = fail("No identifiers should be looked up in this test") + + def identifierLookup(name: String): WdlValue = name match { + case "a" => WdlInteger(1) + case "b" => WdlInteger(2) + case "s" => WdlString("s") + case "array_str" => WdlArray(WdlArrayType(WdlStringType), Seq("foo", "bar", "baz").map(WdlString)) + case "map_str_int" => WdlMap(WdlMapType(WdlStringType, WdlIntegerType), Map( + WdlString("a") -> WdlInteger(0), + WdlString("b") -> WdlInteger(1), + WdlString("c") -> WdlInteger(2) + )) + case "o" => WdlObject(Map("key1" -> WdlString("value1"), "key2" -> WdlInteger(9))) + case "etc_f" => WdlFile("/etc") + case "etc2_f" => WdlFile("/etc2") + case "etc_s" => WdlString("/etc") + case "sudoers_f" => WdlFile("/sudoers") + case "sudoers_s" => WdlString("/sudoers") + } + + + def identifierTypeLookup(name: String): WdlType = identifierLookup(name).wdlType + + class TestValueFunctions extends WdlStandardLibraryFunctions { + override def glob(path: String, pattern: String): Seq[String] = throw new NotImplementedError() + override def readFile(path: String): String = throw new NotImplementedError() + override def writeTempFile(path: String, prefix: String, suffix: String, content: String): String = throw new NotImplementedError() + override def stdout(params: Seq[Try[WdlValue]]): Try[WdlFile] = Failure(new NotImplementedError()) + override def stderr(params: Seq[Try[WdlValue]]): Try[WdlFile] = Failure(new NotImplementedError()) + override def read_json(params: Seq[Try[WdlValue]]): Try[WdlValue] = Failure(new NotImplementedError()) + override def write_tsv(params: Seq[Try[WdlValue]]): Try[WdlFile] = Failure(new NotImplementedError()) + override def write_json(params: Seq[Try[WdlValue]]): Try[WdlFile] = Failure(new NotImplementedError()) + + def b(params: Seq[Try[WdlValue]]): Try[WdlValue] = + Success(WdlInteger(params.head.asInstanceOf[Try[WdlInteger]].get.value + 1)) + + def append(params: Seq[Try[WdlValue]]): Try[WdlValue] = + Success(WdlString(params.map(_.asInstanceOf[Try[WdlString]].get.value).mkString)) + } + + class TestTypeFunctions extends WdlStandardLibraryFunctionsType { + def b(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlIntegerType) + def append(params: Seq[Try[WdlType]]): Try[WdlType] = Success(WdlStringType) + } + + def constEval(exprStr: String): WdlPrimitive = expr(exprStr).evaluate(noLookup, new TestValueFunctions()).asInstanceOf[Try[WdlPrimitive]].get + def constEvalType(exprStr: String): WdlType = expr(exprStr).evaluateType(identifierTypeLookup, new TestTypeFunctions).asInstanceOf[Try[WdlType]].get + def constEvalError(exprStr: String): Unit = { + expr(exprStr).evaluate(noLookup, new TestValueFunctions()).asInstanceOf[Try[WdlPrimitive]] match { + case Failure(ex) => // Expected + case Success(value) => fail(s"Operation was supposed to fail, instead I got value: $value") + } + } + def identifierEval(exprStr: String): WdlPrimitive = expr(exprStr).evaluate(identifierLookup, new TestValueFunctions()).asInstanceOf[Try[WdlPrimitive]].get + def identifierEvalError(exprStr: String): Unit = { + expr(exprStr).evaluate(identifierLookup, new TestValueFunctions()).asInstanceOf[Try[WdlPrimitive]] match { + case Failure(ex) => // Expected + case Success(value) => fail(s"Operation was supposed to fail, instead I got value: $value") + } + } + + val constantExpressions = Table( + ("expression", "value"), + + // Integers + ("1+2", WdlInteger(3)), + (""" 1 + "String" """, WdlString("1String")), + ("1+2.3", WdlFloat(3.3)), + ("3-5", WdlInteger(-2)), + ("10-6.7", WdlFloat(3.3)), + ("8 * 7", WdlInteger(56)), + ("5 * 7.2", WdlFloat(36.toDouble)), + ("80 / 6", WdlInteger(13)), + ("25/2.0", WdlFloat(12.5)), + ("10 % 3", WdlInteger(1)), + ("10 % 3.5", WdlFloat(3.0)), + (" 24 == 24 ", WdlBoolean.True), + (" 24 == 26 ", WdlBoolean.False), + (" 1 != 0 ", WdlBoolean.True), + (" 1 != 1 ", WdlBoolean.False), + ("4 < 3", WdlBoolean.False), + ("3 < 4", WdlBoolean.True), + ("4 < 5.0", WdlBoolean.True), + ("3 <= 4", WdlBoolean.True), + ("3 <= 3.0", WdlBoolean.True), + ("4 > 3", WdlBoolean.True), + ("4 > 3.0", WdlBoolean.True), + ("4 >= 4", WdlBoolean.True), + ("4 >= 4.0", WdlBoolean.True), + ("-1 + -3", WdlInteger(-4)), + ("+1", WdlInteger(1)), + + // Floats + ("1.0+2", WdlFloat(3.toDouble)), + (""" 1.0 + "String" """, WdlString("1.0String")), + ("1.0+2.3", WdlFloat(3.3)), + ("3.0-5", WdlFloat(-2.toDouble)), + ("10.0-6.7", WdlFloat(3.3)), + ("8.0 * 7", WdlFloat(56.toDouble)), + ("5.0 * 7.2", WdlFloat(36.toDouble)), + ("25.0 / 4", WdlFloat(6.25)), + ("25.0/2.0", WdlFloat(12.5)), + ("10.0 % 3", WdlFloat(1.toDouble)), + ("10.0 % 3.5", WdlFloat(3.0)), + ("24.0 == 24 ", WdlBoolean.True), + ("24.0 == 24.0 ", WdlBoolean.True), + ("24.0 == 26 ", WdlBoolean.False), + ("1.0 != 0 ", WdlBoolean.True), + ("1.0 != 0.0 ", WdlBoolean.True), + ("1.0 != 1 ", WdlBoolean.False), + ("4.0 < 3", WdlBoolean.False), + ("4.0 < 3.0", WdlBoolean.False), + ("3.0 < 4", WdlBoolean.True), + ("4.0 < 5.0", WdlBoolean.True), + ("3.0 <= 4", WdlBoolean.True), + ("3.0 <= 3.0", WdlBoolean.True), + ("4.0 > 3", WdlBoolean.True), + ("4.0 > 3.0", WdlBoolean.True), + ("4.0 >= 4", WdlBoolean.True), + ("4.0 >= 4.0", WdlBoolean.True), + ("+1.0", WdlFloat(1.0)), + ("-1.0", WdlFloat(-1.0)), + + // Booleans + ("false == false ", WdlBoolean.True), + ("false == true ", WdlBoolean.False), + ("true != false ", WdlBoolean.True), + ("true != true ", WdlBoolean.False), + ("true < false", WdlBoolean.False), + ("false <= false ", WdlBoolean.True), + ("true <= false", WdlBoolean.False), + ("true > false", WdlBoolean.True), + ("false >= false ", WdlBoolean.True), + ("false >= true ", WdlBoolean.False), + ("false || true ", WdlBoolean.True), + ("false || false ", WdlBoolean.False), + ("false && true ", WdlBoolean.False), + ("true && true ", WdlBoolean.True), + ("!true", WdlBoolean.False), + ("!false", WdlBoolean.True), + + // Strings + (""" "String" + 456 """, WdlString("String456")), + (""" "hello" + " world" """, WdlString("hello world")), + (""" "hello" + 2.1 """, WdlString("hello2.1")), + (""" "hello" == "hello" """, WdlBoolean.True), + (""" "hello" == "hello2" """, WdlBoolean.False), + (""" "hello" != "hello" """, WdlBoolean.False), + (""" "hello" != "hello2" """, WdlBoolean.True), + (""" "abc" < "def" """, WdlBoolean.True), + (""" "abc" <= "def" """, WdlBoolean.True), + (""" "abc" > "def" """, WdlBoolean.False), + (""" "abc" >= "def" """, WdlBoolean.False), + + // Order of Operations + ("1+2*3", WdlInteger(7)), + ("1+2==3", WdlBoolean.True), + ("(1+2)*3", WdlInteger(9)) + ) + + val badExpressions = Table( + ("expression"), + + // Integers + ("1+true"), + ("1-true"), + (""" 1-"s" """), + ("1*true"), + (""" 1*"s" """), + ("1 / 0"), + ("1 / 0.0"), + ("25/0.0"), + ("1/true"), + (""" 1/"s" """), + ("1%false"), + (""" 1%"s" """), + ("1%0"), + (" 24 == false "), + (""" 1 == "s" """), + (" 24 != false "), + (""" 1 != "s" """), + (" 24 < false "), + (""" 1 < "s" """), + (" 24 <= false "), + (""" 1 <= "s" """), + ("4 > false"), + (""" 1 > "s" """), + ("4 >= false"), + (""" 1 >= "s" """), + + // Floats + ("1.0+true"), + ("1.0-true"), + (""" 1.0-"s" """), + ("1.0*true"), + (""" 1.0*"s" """), + ("1.0/true"), + ("1.0/0.0"), + ("1.0/0"), + (""" 1.0/"s" """), + ("10.0 % 0"), + ("10.0 % 0.0"), + ("1.0%false"), + (""" 1.0%"s" """), + ("24.0 == false "), + (""" 1.0 == "s" """), + ("24.0 != false "), + (""" 1.0 != "s" """), + ("24.0 < false "), + (""" 1.0 < "s" """), + ("24.0 <= false "), + (""" 1.0 <= "s" """), + ("4.0 > false"), + (""" 1.0 > "s" """), + ("4.0 >= false"), + (""" 1.0 >= "s" """), + + // Booleans + (""" true + "String" """), + ("true+2"), + ("true+2.3"), + ("false+true"), + ("false-5"), + ("false-6.6"), + ("true-true"), + (""" true-"s" """), + ("false * 7"), + ("false * 7.2"), + ("false*true"), + (""" false*"s" """), + ("false / 4"), + ("false/2.0"), + ("false/true"), + (""" true/"s" """), + ("true % 3"), + ("true % 3.5"), + ("false%false"), + (""" true % "s" """), + ("true == 24 "), + ("true == 24.0 "), + ("""true == "s" """), + ("true != 0 "), + ("true != 0.0 "), + ("""true != "s" """), + ("true < 3"), + ("true < 3.0"), + ("true < 5.0"), + ("""true < "s" """), + ("true <= 4"), + ("true <= 3.0"), + ("""true <= "s" """), + ("true > 3"), + ("true > 3.0"), + ("true >= 4"), + ("true >= 4.0"), + ("""true >= "s" """), + ("true || 4"), + ("true || 4.0"), + ("""true || "s" """), + ("true && 4"), + ("true && 4.0"), + ("""true && "s" """), + + // Strings + (""" "hello" + true """), + (""" "hello" == true """), + (""" "hello" != true """), + (""" "hello" < true """), + (""" "hello" > true """) + ) + + val identifierLookupExpressions = Table( + ("expression", "value"), + + // Lookup Variables + ("a", WdlInteger(1)), + ("a + 10", WdlInteger(11)), + ("a + b", WdlInteger(3)), + ("s + a", WdlString("s1")), + ("o.key1", WdlString("value1")), + ("o.key2", WdlInteger(9)), + + // Call Functions + ("b(1)", WdlInteger(2)), + ("b(1) + 10", WdlInteger(12)), + (""" append("hello ", "world") """, WdlString("hello world")), + (""" append("a", "b", "c", "d") """, WdlString("abcd")), + + // String Interpolation + ("\"prefix.${a}.suffix\"", WdlString("prefix.1.suffix")), + ("\"prefix.${a}${a}.suffix${a}\"", WdlString("prefix.11.suffix1")), + ("\"${b}prefix.${b}${a}${a}.suffix${a}\"", WdlString("2prefix.211.suffix1")), + ("\"${s}...${s}\"", WdlString("s...s")), + + // Array Indexing + ("array_str[0]", WdlString("foo")), + ("array_str[1]", WdlString("bar")), + ("array_str[2]", WdlString("baz")), + + // Map Indexing + ("""map_str_int["a"]""", WdlInteger(0)), + ("""map_str_int["b"]""", WdlInteger(1)), + ("""map_str_int["c"]""", WdlInteger(2)), + + // Files -- 'etc_f', 'etc2_f', and 'sudoers_f' are all variables that resolve to WdlFile + ("etc_f + sudoers_s", WdlFile("/etc/sudoers")), + (""" "/foo" + etc_f """, WdlString("/foo/etc")), + ("etc_f == etc_f", WdlBoolean.True), + ("etc_f == etc2_f", WdlBoolean.False), + ("etc_f != etc2_f", WdlBoolean.True), + ("etc_f == etc_s", WdlBoolean.True), + + // String escaping + (""" "abc" """, WdlString("abc")), + (""" "a\nb" """, WdlString("a\nb")), + (""" "a\nb\t" """, WdlString("a\nb\t")), + (""" "a\n\"b\t\"" """, WdlString("a\n\"b\t\"")), + (""" "be \u266f or be \u266e, just don't be \u266d" """, WdlString("be \u266f or be \u266e, just don't be \u266d")) + ) + + val badIdentifierExpressions = Table( + ("expression"), + ("etc_f + 1"), + ("etc_f == 1"), + ("0.key3"), + ("array_str[3]"), + ("""map_str_int["d"]""") + ) + + forAll (constantExpressions) { (expression, value) => + it should s"evaluate $expression into ${value.valueString} (${value.wdlType.toWdlString})" in { + constEval(expression) shouldEqual value + } + } + + forAll (constantExpressions) { (expression, value) => + it should s"evaluate $expression into type ${value.wdlType.toWdlString}" in { + constEvalType(expression) shouldEqual value.wdlType + } + } + + forAll (identifierLookupExpressions) { (expression, value) => + it should s"evaluate $expression into ${value.valueString} (${value.wdlType.toWdlString})" in { + identifierEval(expression) shouldEqual value + } + } + + forAll (identifierLookupExpressions) { (expression, value) => + it should s"evaluate $expression into type ${value.wdlType.toWdlString}" in { + // need to skip the object expressions because we don't know the types of sub-objects + if (!expression.startsWith("o.key")) constEvalType(expression) shouldEqual value.wdlType + } + } + + forAll (badExpressions) { (expression) => + it should s"error when evaluating: $expression" in { + constEvalError(expression) + } + } + + forAll (badIdentifierExpressions) { (expression) => + it should s"error when evaluating: $expression" in { + identifierEvalError(expression) + } + } + + "A string with special characters in it" should "convert to escape sequences when converted to WDL" in { + WdlString("a\nb").toWdlString shouldEqual "\"a\\nb\"" + WdlString("a\nb\t").toWdlString shouldEqual "\"a\\nb\\t\"" + WdlString("be \u266f or be \u266e, just don't be \u266d").toWdlString shouldEqual "\"be \\u266F or be \\u266E, just don't be \\u266D\"" + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/expression/WdlExpressionSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/expression/WdlExpressionSpec.scala new file mode 100644 index 0000000..55decd5 --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/expression/WdlExpressionSpec.scala @@ -0,0 +1,79 @@ +package wdl4s.expression + +import wdl4s.WdlExpression +import org.scalatest.{FlatSpec, Matchers} + +class WdlExpressionSpec extends FlatSpec with Matchers { + val expr: String => WdlExpression = WdlExpression.fromString + + /* String-ification */ + "Expression Evaluator string-ifier" should "Make strings out of + expressions" in { + expr("1 + 2").toWdlString shouldEqual "1 + 2" + } + it should "Make strings out of - expressions" in { + expr("1 - 2").toWdlString shouldEqual "1 - 2" + } + it should "Make strings out of * expressions" in { + expr("1 * 2").toWdlString shouldEqual "1 * 2" + } + it should "Make strings out of / expressions" in { + expr("1 / 2").toWdlString shouldEqual "1 / 2" + } + it should "Make strings out of % expressions" in { + expr("1 % 2").toWdlString shouldEqual "1 % 2" + } + it should "Make strings out of < expressions" in { + expr("1 < 2").toWdlString shouldEqual "1 < 2" + } + it should "Make strings out of <= expressions" in { + expr("1 <= 2").toWdlString shouldEqual "1 <= 2" + } + it should "Make strings out of > expressions" in { + expr("1 > 2").toWdlString shouldEqual "1 > 2" + } + it should "Make strings out of >= expressions" in { + expr("1 >= 2").toWdlString shouldEqual "1 >= 2" + } + it should "Make strings out of == expressions" in { + expr("1 == 2").toWdlString shouldEqual "1 == 2" + } + it should "Make strings out of != expressions" in { + expr("1 != 2").toWdlString shouldEqual "1 != 2" + } + it should "Make strings out of && expressions" in { + expr("1 && 2").toWdlString shouldEqual "1 && 2" + } + it should "Make strings out of || expressions" in { + expr("1 || 2").toWdlString shouldEqual "1 || 2" + } + it should "Make strings out of expression with strings in it" in { + expr("\"a\" + \"b\"").toWdlString shouldEqual "\"a\" + \"b\"" + } + it should "Make strings out of expression with floats in it" in { + expr("1.1 + 2.2").toWdlString shouldEqual "1.1 + 2.2" + } + it should "Make strings out of expression with identifiers in it" in { + expr("foo + bar").toWdlString shouldEqual "foo + bar" + } + it should "Make strings out of member access expressions" in { + expr("a.b.c").toWdlString shouldEqual "a.b.c" + } + it should "Make strings out of function calls" in { + expr("a(b, c)").toWdlString shouldEqual "a(b, c)" + } + it should "Make strings out of array/map lookups" in { + expr("a[0]").toWdlString shouldEqual "a[0]" + } + it should "Make strings out of unary minus" in { + expr("-2").toWdlString shouldEqual "-2" + } + it should "Make strings out of unary plus" in { + expr("+2").toWdlString shouldEqual "+2" + } + it should "Make strings out of logical not" in { + expr("!2").toWdlString shouldEqual "!2" + } + it should "Make strings out of booleans" in { + expr("true != false").toWdlString shouldEqual "true != false" + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/types/WdlArrayTypeSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/types/WdlArrayTypeSpec.scala new file mode 100644 index 0000000..5e07a48 --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/types/WdlArrayTypeSpec.scala @@ -0,0 +1,87 @@ +package wdl4s.types + +import wdl4s.values.{WdlArray, WdlInteger, WdlString} +import wdl4s.parser.WdlParser.SyntaxError +import org.scalatest.{FlatSpec, Matchers} +import spray.json.{JsArray, JsNumber} + +import scala.util.{Failure, Success} + +class WdlArrayTypeSpec extends FlatSpec with Matchers { + val intArray = WdlArray(WdlArrayType(WdlIntegerType), Seq(WdlInteger(1), WdlInteger(2), WdlInteger(3))) + "WdlArray" should "stringify its value" in { + intArray.toWdlString shouldEqual "[1, 2, 3]" + } + "WdlArrayType" should "coerce Seq(1,2,3) into a WdlArray" in { + WdlArrayType(WdlIntegerType).coerceRawValue(Seq(1,2,3)) match { + case Success(array) => array shouldEqual intArray + case Failure(f) => fail(s"exception while coercing array: $f") + } + } + it should "coerce a JsArray into a WdlArray" in { + WdlArrayType(WdlIntegerType).coerceRawValue(JsArray(JsNumber(1), JsNumber(2), JsNumber(3))) match { + case Success(array) => array shouldEqual intArray + case Failure(f) => fail(s"exception while coercing JsArray: $f") + } + } + it should "coerce single values into one-element arrays" in { + WdlArrayType(WdlStringType).coerceRawValue(WdlString("edamame is tasty")) match { + case Success(array) => array shouldEqual WdlArray(WdlArrayType(WdlStringType), Seq(WdlString("edamame is tasty"))) + case Failure(f) => fail("exception coercing single value to array", f) + } + } + it should "stringify its type" in { + intArray.wdlType.toWdlString shouldEqual "Array[Int]" + } + it should "convert WDL source code to WdlArray" in { + WdlArrayType(WdlIntegerType).fromWdlString("[1,2,3]") shouldEqual intArray + } + it should "NOT successfully convert WDL source code to WdlArray if passed a bogus AST" in { + try { + WdlArrayType(WdlIntegerType).fromWdlString("workflow wf{}") + fail("should not have succeeded") + } catch { + case _: SyntaxError => // expected + } + } + it should "NOT successfully convert WDL source code to WdlArray if passed a bogus AST (2)" in { + try { + WdlArrayType(WdlIntegerType).fromWdlString("100") + fail("should not have succeeded") + } catch { + case _: SyntaxError => // expected + } + } + it should "NOT successfully convert WDL source code to WdlArray if passed a bogus AST (3)" in { + try { + WdlArrayType(WdlIntegerType).fromWdlString("[1,stdout()]") + fail("should not have succeeded") + } catch { + case _: SyntaxError => // expected + } + } + it should "NOT successfully convert WDL source code to WdlArray if passed a bogus AST (4)" in { + try { + WdlArrayType(WdlIntegerType).fromWdlString("[1,var]") + fail("should not have succeeded") + } catch { + case _: SyntaxError => // expected + } + } + it should "detect invalid array construction if there are mixed types" in { + try { + WdlArray(WdlArrayType(WdlStringType), Seq(WdlString("foo"), WdlInteger(2))) + fail("Invalid array initialization should have failed") + } catch { + case _: UnsupportedOperationException => // expected + } + } + it should "detect invalid array construction if type does not match the input array type" in { + try { + WdlArray(WdlArrayType(WdlStringType), Seq(WdlInteger(2))) + fail("Invalid array initialization should have failed") + } catch { + case _: UnsupportedOperationException => // expected + } + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/types/WdlMapTypeSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/types/WdlMapTypeSpec.scala new file mode 100644 index 0000000..21b9cf5 --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/types/WdlMapTypeSpec.scala @@ -0,0 +1,99 @@ +package wdl4s.types + +import wdl4s.values.{WdlObject, WdlMap, WdlInteger, WdlString} +import wdl4s.parser.WdlParser.SyntaxError +import org.scalatest.{FlatSpec, Matchers} +import spray.json.{JsObject, JsArray, JsNumber} + +import scala.util.{Failure, Success} + +class WdlMapTypeSpec extends FlatSpec with Matchers { + val stringIntMap = WdlMap(WdlMapType(WdlStringType, WdlIntegerType), Map( + WdlString("a") -> WdlInteger(1), + WdlString("b") -> WdlInteger(2), + WdlString("c") -> WdlInteger(3) + )) + val coerceableObject = WdlObject(Map( + "a" -> WdlString("1"), + "b" -> WdlString("2"), + "c" -> WdlString("3") + )) + + "WdlMap" should "stringify its value" in { + stringIntMap.toWdlString shouldEqual "{\"a\": 1, \"b\": 2, \"c\": 3}" + } + "WdlMapType" should "coerce Map(\"a\":1, \"b\":2, \"c\": 3) into a WdlMap" in { + WdlMapType(WdlStringType, WdlIntegerType).coerceRawValue(Map("a" -> 1, "b" -> 2, "c" -> 3)) match { + case Success(array) => array shouldEqual stringIntMap + case Failure(f) => fail(s"exception while coercing map: $f") + } + } + it should "coerce a JsObject into a WdlMap" in { + WdlMapType(WdlStringType, WdlIntegerType).coerceRawValue(JsObject(Map("a" -> JsNumber(1), "b" -> JsNumber(2), "c" -> JsNumber(3)))) match { + case Success(array) => array shouldEqual stringIntMap + case Failure(f) => fail(s"exception while coercing JsObject: $f") + } + } + it should "stringify its type" in { + stringIntMap.wdlType.toWdlString shouldEqual "Map[String, Int]" + } + it should "convert WDL source code to WdlMap" in { + WdlMapType(WdlStringType, WdlIntegerType).fromWdlString("{\"a\": 1, \"b\": 2, \"c\": 3}") shouldEqual stringIntMap + } + it should "coerce a coerceable object into a WdlMap" in { + WdlMapType(WdlStringType, WdlIntegerType).coerceRawValue(coerceableObject) match { + case Success(v) => + v.wdlType shouldEqual WdlMapType(WdlStringType, WdlIntegerType) + v.toWdlString shouldEqual stringIntMap.toWdlString + case Failure(f) => fail("Failed to coerce a map to an object") + } + } + it should "NOT successfully convert WDL source code to WdlMap if passed a bogus AST" in { + try { + WdlMapType(WdlStringType, WdlIntegerType).fromWdlString("workflow wf{}") + fail("should not have succeeded") + } catch { + case _: SyntaxError => // expected + } + } + it should "NOT successfully convert WDL source code to WdlMap if passed a bogus AST (2)" in { + try { + WdlMapType(WdlStringType, WdlIntegerType).fromWdlString("100") + fail("should not have succeeded") + } catch { + case _: SyntaxError => // expected + } + } + it should "NOT successfully convert WDL source code to WdlMap if passed a bogus AST (3)" in { + try { + WdlMapType(WdlStringType, WdlIntegerType).fromWdlString("{1:x(),2:stdout()}") + fail("should not have succeeded") + } catch { + case _: SyntaxError => // expected + } + } + it should "NOT successfully convert WDL source code to WdlMap if passed a bogus AST (4)" in { + try { + WdlMapType(WdlStringType, WdlIntegerType).fromWdlString("{1:var,2:var}") + fail("should not have succeeded") + } catch { + case _: SyntaxError => // expected + } + } + it should "detect invalid map construction if there are mixed types" in { + try { + WdlMap(WdlMapType(WdlStringType, WdlStringType), Map(WdlInteger(0) -> WdlString("foo"), WdlString("x") -> WdlInteger(2))) + fail("Map initialization should have failed") + } catch { + case _: UnsupportedOperationException => // expected + } + } + it should "detect invalid map construction if type does not match the input map type" in { + try { + WdlMap(WdlMapType(WdlStringType, WdlStringType), Map(WdlInteger(2) -> WdlInteger(3))) + fail("Invalid map initialization should have failed") + } catch { + case _: UnsupportedOperationException => // expected + } + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/types/WdlObjectTypeSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/types/WdlObjectTypeSpec.scala new file mode 100644 index 0000000..5972405 --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/types/WdlObjectTypeSpec.scala @@ -0,0 +1,106 @@ +package wdl4s.types + +import wdl4s.values.{WdlInteger, WdlString, WdlMap, WdlObject} +import wdl4s.parser.WdlParser.SyntaxError +import org.scalatest.{FlatSpec, Matchers} + +import scala.util.{Failure, Success} + +class WdlObjectTypeSpec extends FlatSpec with Matchers { + val abcObject = WdlObject(Map( + "a" -> WdlString("one"), + "b" -> WdlString("two"), + "c" -> WdlString("three") + )) + + val coerceableMap = WdlMap(WdlMapType(WdlStringType, WdlStringType), Map( + WdlString("a") -> WdlString("one"), + WdlString("b") -> WdlString("two"), + WdlString("c") -> WdlString("three")) + ) + + val nonCoerceableMap1 = WdlMap(WdlMapType(WdlIntegerType, WdlStringType), Map( + WdlInteger(1) -> WdlString("one"), + WdlInteger(2) -> WdlString("two"), + WdlInteger(3) -> WdlString("three")) + ) + + val nonCoerceableMap2 = WdlMap(WdlMapType(WdlStringType, WdlObjectType), Map( + WdlString("a") -> WdlObject(Map.empty), + WdlString("b") -> WdlObject(Map.empty), + WdlString("c") -> WdlObject(Map.empty)) + ) + + "WdlObject" should "stringify its value" in { + abcObject.toWdlString shouldEqual "object {a: \"one\", b: \"two\", c: \"three\"}" + } + + it should "stringify its type" in { + abcObject.wdlType.toWdlString shouldEqual "Object" + } + + it should "convert WDL source code to WdlMap" in { + WdlObjectType.fromWdlString("object {a: \"one\", b: \"two\", c: \"three\"}") shouldEqual abcObject + } + + it should "coerce a coerceable map into a WdlObject" in { + WdlObjectType.coerceRawValue(coerceableMap) match { + case Success(v) => + v.wdlType shouldEqual WdlObjectType + v.toWdlString shouldEqual abcObject.toWdlString + case Failure(f) => fail("Failed to coerce a map to an object") + } + } + + it should "NOT successfully coerce a NON coerceable map into a WdlObject" in { + WdlObjectType.coerceRawValue(nonCoerceableMap1) match { + case Success(v) => fail("should not have succeeded") + case Failure(f) => // expected + } + } + + it should "NOT successfully coerce a NON coerceable map into a WdlObject (2)" in { + WdlObjectType.coerceRawValue(nonCoerceableMap2) match { + case Success(v) => fail("should not have succeeded") + case Failure(f) => // expected + } + } + + it should "NOT successfully convert WDL source code to WdlMap if passed a bogus AST" in { + try { + WdlObjectType.fromWdlString("workflow wf{}") + fail("should not have succeeded") + } catch { + case _: SyntaxError => // expected + } + } + + it should "NOT successfully convert WDL source code to WdlMap if passed a bogus AST (2)" in { + try { + WdlObjectType.fromWdlString("100") + fail("should not have succeeded") + } catch { + case _: SyntaxError => // expected + } + } + + it should "NOT successfully convert WDL source code to WdlMap if passed a bogus AST (3)" in { + try { + WdlObjectType.fromWdlString("{1:x(),2:stdout()}") + fail("should not have succeeded") + } catch { + case _: SyntaxError => // expected + } + } + + it should "NOT successfully convert WDL source code to WdlMap if passed a bogus AST (4)" in { + try { + WdlObjectType.fromWdlString("{1:var,2:var}") + fail("should not have succeeded") + } catch { + case _: SyntaxError => // expected + } + } + + +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/types/WdlTypeSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/types/WdlTypeSpec.scala new file mode 100644 index 0000000..c9f49c9 --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/types/WdlTypeSpec.scala @@ -0,0 +1,96 @@ +package wdl4s.types + +import wdl4s.values._ +import wdl4s.parser.WdlParser.SyntaxError +import org.scalatest.prop.TableDrivenPropertyChecks._ +import org.scalatest.prop.Tables.Table +import org.scalatest.{FlatSpec, Matchers} +import spray.json.JsString + +class WdlTypeSpec extends FlatSpec with Matchers { + "WdlType class" should "stringify WdlBoolean to 'Boolean'" in { + WdlBooleanType.toWdlString shouldEqual "Boolean" + } + it should "stringify WdlInteger to 'Integer'" in { + WdlIntegerType.toWdlString shouldEqual "Int" + } + it should "stringify WdlFloat to 'Float'" in { + WdlFloatType.toWdlString shouldEqual "Float" + } + it should "stringify WdlObject to 'Object'" in { + WdlObjectType.toWdlString shouldEqual "Object" + } + it should "stringify WdlString to 'String'" in { + WdlStringType.toWdlString shouldEqual "String" + } + it should "stringify WdlFile to 'File'" in { + WdlFileType.toWdlString shouldEqual "File" + } + + "WdlBoolean" should "support expected coercions" in { + WdlBooleanType.coerceRawValue("true").get shouldEqual WdlBoolean.True + WdlBooleanType.coerceRawValue("FALSE").get shouldEqual WdlBoolean.False + WdlBooleanType.coerceRawValue(false).get shouldEqual WdlBoolean.False + + WdlBooleanType.coerceRawValue("I like turtles").isFailure shouldBe true + } + + "WdlString" should "support expected coercions" in { + WdlStringType.coerceRawValue("foo").get shouldEqual WdlString("foo") + WdlStringType.coerceRawValue(-1).isFailure shouldBe true + } + + "WdlFile" should "support expected coercions" in { + WdlFileType.coerceRawValue("/etc/passwd").get shouldEqual WdlFile("/etc/passwd") + WdlFileType.coerceRawValue(-1).isFailure shouldBe true + } + + "WdlInteger" should "support expected coercions" in { + WdlIntegerType.coerceRawValue(42).get shouldEqual WdlInteger(42) + WdlIntegerType.coerceRawValue("42").get shouldEqual WdlInteger(42) + WdlIntegerType.coerceRawValue(JsString("42")).get shouldEqual WdlInteger(42) + WdlIntegerType.coerceRawValue("FAIL").isFailure shouldBe true + } + + "WdlFloatType" should "support expected coercions" in { + WdlFloatType.coerceRawValue(33.3).get shouldEqual WdlFloat(33.3) + WdlFloatType.coerceRawValue("33.3").get shouldEqual WdlFloat(33.3) + WdlFloatType.coerceRawValue(JsString("33.3")).get shouldEqual WdlFloat(33.3) + WdlFloatType.coerceRawValue("FAIL").isFailure shouldBe true + } + + val wdlValueRawStrings = Table( + ("WdlSource", "WdlType"), + ("String", WdlStringType), + ("Int", WdlIntegerType), + ("File", WdlFileType), + ("Boolean", WdlBooleanType), + ("Float", WdlFloatType), + ("Array[Int]", WdlArrayType(WdlIntegerType)), + ("Array[Array[String]]", WdlArrayType(WdlArrayType(WdlStringType))) + ) + + forAll(wdlValueRawStrings) { (wdlSource, wdlType) => + it should s"return the WDL type $wdlType from WDL source string: $wdlSource" in { + WdlType.fromWdlString(wdlSource) shouldEqual wdlType + } + } + + it should "reject an array type without a member type" in { + try { + WdlType.fromWdlString("Array") + fail("Should not be able to parse: `Array`") + } catch { + case _: SyntaxError => // expected + } + } + + it should "reject an array type with more than one parameterized type" in { + try { + WdlType.fromWdlString("Array[String, Int]") + fail("Should not be able to parse: `Array[String, Int]`") + } catch { + case _: SyntaxError => // expected + } + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/values/WdlObjectSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/values/WdlObjectSpec.scala new file mode 100644 index 0000000..15b96bf --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/values/WdlObjectSpec.scala @@ -0,0 +1,78 @@ +package wdl4s.values + +import wdl4s.types.{WdlArrayType, WdlObjectType} +import org.scalatest.{FlatSpec, Matchers, TryValues} + +class WdlObjectSpec extends FlatSpec with Matchers with TryValues { + val correctTSV = "one\ttwo\tthree\tfour\none\tfour\tnine\tsixteen" + val emptyTSV = "" + val oneRowTSV = "one\ttwo\tthree\tfour" + val nonHomogeneousTS = "onet\ttwo\tthree\none\ttwo" + val arrayTSV = correctTSV + "\none\teight\ttwentyseven\tsixtyfour" + + it should "read an Object from a correct TSV file" in { + val parsed = WdlObject.fromTsv(correctTSV) + parsed should be a 'success + val array: Array[WdlObject] = parsed.success.value + array should have size 1 + + //Attributes + array.head.value should contain key "one" + array.head.value should contain key "two" + array.head.value should contain key "three" + array.head.value should contain key "four" + + //Values + array.head.value.get("one") shouldBe Some(WdlString("one")) + array.head.value.get("two") shouldBe Some(WdlString("four")) + array.head.value.get("three") shouldBe Some(WdlString("nine")) + array.head.value.get("four") shouldBe Some(WdlString("sixteen")) + } + + it should "NOT read from a TSV file with less than 2 rows" in { + WdlObject.fromTsv(emptyTSV) should be a 'failure + WdlObject.fromTsv(oneRowTSV) should be a 'failure + } + + it should "NOT read from a non homogeneous TSV file" in { + WdlObject.fromTsv(nonHomogeneousTS) should be a 'failure + } + + it should "serialize to TSV" in { + val obj = WdlObject.fromTsv(correctTSV).get.head + val serialized = obj.tsvSerialize + serialized should be a 'success + serialized.success.value shouldEqual correctTSV + } + + it should "read a WdlArray[WdlObject] from a correct TSV file" in { + val parsed = WdlObject.fromTsv(arrayTSV) + parsed should be a 'success + val array: Array[WdlObject] = parsed.success.value + array should have size 2 + + //Attributes + array foreach { _.value should contain key "one" } + array foreach { _.value should contain key "two" } + array foreach { _.value should contain key "three" } + array foreach { _.value should contain key "four" } + + //Values + array.head.value.get("one") shouldBe Some(WdlString("one")) + array.head.value.get("two") shouldBe Some(WdlString("four")) + array.head.value.get("three") shouldBe Some(WdlString("nine")) + array.head.value.get("four") shouldBe Some(WdlString("sixteen")) + + array(1).value.get("one") shouldBe Some(WdlString("one")) + array(1).value.get("two") shouldBe Some(WdlString("eight")) + array(1).value.get("three") shouldBe Some(WdlString("twentyseven")) + array(1).value.get("four") shouldBe Some(WdlString("sixtyfour")) + } + + it should "serialize a WdlArray[WdlObject] to TSV" in { + val array = WdlArray(WdlArrayType(WdlObjectType), WdlObject.fromTsv(arrayTSV).get) + val serialized = array.tsvSerialize + serialized should be a 'success + serialized.success.value shouldEqual arrayTSV + } +} diff --git a/scala/wdl4s/src/test/scala/wdl4s/values/WdlValueSpec.scala b/scala/wdl4s/src/test/scala/wdl4s/values/WdlValueSpec.scala new file mode 100644 index 0000000..e933cc6 --- /dev/null +++ b/scala/wdl4s/src/test/scala/wdl4s/values/WdlValueSpec.scala @@ -0,0 +1,90 @@ +package wdl4s.values + +import wdl4s.WdlExpression +import wdl4s.types.{WdlMapType, WdlStringType} +import org.scalatest.prop.TableDrivenPropertyChecks +import org.scalatest.{FlatSpec, Matchers} + +class WdlValueSpec extends FlatSpec with Matchers { + + import TableDrivenPropertyChecks._ + + behavior of "WdlValue" + + val wdlValueRawStrings = Table( + ("wdlValue", "rawString"), + (WdlBoolean.False, "false"), + (WdlBoolean.True, "true"), + (WdlFile("hello/world/path"), "\"hello/world/path\""), + (WdlFile("hello/world/string"), "\"hello/world/string\""), + (WdlFloat(0.0), "0.0"), + (WdlFloat(-0.0), "-0.0"), + (WdlFloat(Double.PositiveInfinity), "Infinity"), + (WdlFloat(Double.NegativeInfinity), "-Infinity"), + (WdlInteger(0), "0"), + (WdlInteger(Int.MaxValue), "2147483647"), + (WdlInteger(Int.MinValue), "-2147483648"), + (WdlString(""), "\"\""), + (WdlObject(Map("one" -> WdlString("two"))), "object {one: \"two\"}"), + (WdlMap(WdlMapType(WdlStringType, WdlStringType), Map(WdlString("one") -> WdlString("two"))), "{\"one\": \"two\"}") + ) + + forAll(wdlValueRawStrings) { (wdlValue, rawString) => + it should s"exactly convert a ${wdlValue.typeName} to/from WDL source '$rawString'" in { + val valueAsWdlSource = wdlValue.toWdlString + valueAsWdlSource should be(rawString) + + val wdlType = wdlValue.wdlType + val wdlSourceAsValue = wdlType.fromWdlString(valueAsWdlSource) + wdlSourceAsValue should be(wdlValue) + wdlSourceAsValue.wdlType should be(wdlType) + } + } + + val wdlFloatSpecials = Table( + ("wdlValue", "rawString", "validateFloat"), + (WdlFloat(Double.MinPositiveValue), "4.9E-324", { d: Double => d == 0.0 }), + (WdlFloat(Double.NaN), "NaN", { d: Double => d.isNaN }), + (WdlFloat(Double.MaxValue), "1.7976931348623157E308", { d: Double => d.isPosInfinity }), + (WdlFloat(Double.MinValue), "-1.7976931348623157E308", { d: Double => d.isNegInfinity })) + + forAll(wdlFloatSpecials) { (wdlValue, rawString, validateFloat) => + it should s"convert a special ${wdlValue.typeName} to/from raw string '$rawString'" in { + val toRawString = wdlValue.toWdlString + toRawString should be(rawString) + + val wdlType = wdlValue.wdlType + val fromRawString = wdlType.fromWdlString(toRawString) + // Test that this is a special conversion, and is not + // expected to be equal after a round-trip conversion. + fromRawString shouldNot be(wdlValue) + validateFloat(fromRawString.value) should be(right = true) + fromRawString.wdlType should be(wdlType) + } + } + + val wdlExpressionRawStrings = Table( + ("wdlValue", "rawString"), + (WdlExpression.fromString(" 1 != 0 "), "1 != 0"), + (WdlExpression.fromString("10 % 3.5"), "10 % 3.5"), + (WdlExpression.fromString("10 % 3"), "10 % 3"), + (WdlExpression.fromString("10-6.7"), "10 - 6.7"), + (WdlExpression.fromString(""" 1 + "String" """), """1 + "String""""), + (WdlExpression.fromString("a + b"), "a + b"), + (WdlExpression.fromString("a(b, c)"), "a(b, c)"), + (WdlExpression.fromString("\"a\" + \"b\""), "\"a\" + \"b\""), + (WdlExpression.fromString("a.b.c"), "a.b.c")) + + forAll(wdlExpressionRawStrings) { (wdlValue, rawString) => + it should s"resemble a ${wdlValue.typeName} to/from raw string '$rawString'" in { + val toRawString = wdlValue.toWdlString + toRawString should be(rawString) + + val wdlType = wdlValue.wdlType + val fromRawString = wdlType.fromWdlString(toRawString) + fromRawString shouldNot be(wdlValue) + fromRawString.toWdlString should be(wdlValue.toWdlString) + fromRawString.wdlType should be(wdlType) + } + } +}