Skip to content
k-abram edited this page Jul 20, 2019 · 6 revisions

Stepper Language Reference

Structure

Stepper attempts to mimic constructs from popular programming languages. The structure of a well formed Stepper program is

    <Annotation+>
    stepper <program-name> {
        statement+
    } 

where Annotation is

   @[ID] ( scalar )

Annotations allowed at the top level are:

  • @Comment("comment"): Human readable description of the step function.
  • @TimeoutSeconds(3600): Timeout for the step function in seconds
  • @Version("1.0"): Amazon States Language (currently version 1.0)

program-name should be a valid ASL state name. State names not explicitly defined are derived from this. This is also used as the name of the step-function when the Stepper compiler is used to create the step-function in AWS.

Here is a minimal Stepper program showing the use of annotations.

    @Comment("This is a minimal stepper program")
    @TimeoutSeconds(1800)
    @Version("1.0")
    stepper Minimal {
      a = "Hello World";
    }

ASL doesn't work directly with variables. Instead, ASL requires you to manipulate json attributes using InputPath, OutputPath and ResultPath. This makes for a non-standard programming model. Stepper allows you to use variables just as you would in a conventional programming language. Behind the scenes, Stepper uses the ASL path constructs to bring about the desired outcome. This does mean that all Stepper variables are held active in the State and therefore must comply with the 32,768 data size limit.

Stepper variables should follow Javascript variable naming conventions (camel case). To declare a variable, simply reference it and assign a value to it using an expression or a task output. Assignment expressions should end with a semicolon.

    stepper assignment {
        a = 5.2;
        b = 10;
        c = "Hello World";
        d.e.f = true;
        e.f.g = false;
    }

In the above code, variable a has a value of 5.2 (float), b has a value of 10 (integer), c is a string, d is a javascript object that contains another javascript object e that has a property f with value true. Similarly, e has object f with property g set to false.

You can have expressions in your assignment. Any valid Javascript expression is a valid Stepper expression with a few limitations noted below.

stepper Expressions {
        a = 1; b = 2; c = 3;
        d = "Hello world";
        value1 = a * b + c - d.length;
        value1 += a * b ;
        a -= a * b -c;
        c /= a * b +;
    }

Stepper supports assignment and compound assignment (+=, -=, *= and /=). Calls to built-in Javascript functions and properties are allowed. However Stepper does not support pre/post increment operators (a++, ++a) and calls to functions that produce side-effects (e.g., total = students.push("joe"); - the push method mutates students and returns the new length). Indexed assignments (a[i] = 3) is also not supported due to underlying limitations of ASL.

Talk about variables, naming convention, assignment of values and expressions supported.

Branching in Step Functions is not intuitive. One has to use the "Choice" state type and branch out to various other steps based on predicates reduced to cumbersome JSON constructs. Stepper allow you to use multiple branching constructs as you would in a regular programming language.

if else

   if (x > d.length + 10) {
      // logic
   } else {
      // logicd
   }

The if condition can be followed by a single statement or statement block. Any Javascript compliant expression can be used in the condition. However, methods that mutate state and unary operators (increment/decrement) are not allowed. You can also chain multiple if statements with if, else if constructs.

The when construct in Stepper is like the switch construct of C/Java. However, unlike C/Java, each case statement is actually an expression. The general syntax of when is

    when {
      case <expression> : <statement-block>,
      case <expression> : <statement-block>,
      ...
      else statement-block 
    }

Again, expression have Javascript syntax with the same restrictions as if/else predicates. The else block is optional and is equivalent to the default block of switch statements.

Stepper also supports the goto statement to cause execution to jump around arbitrarily. As with any programming language that supports goto, the usual warnings apply. The syntax for goto is

    goto "<label-name>"

Labels are associated with statements and can be inserted by adding a @Label annotation before a given statement.

    stepper assignment {
        a = 5.2;
        @Label("assign b") b = 10;
        e.f.g = false;
    }

The label annotation is used as the state name in the generated ASL. Label annotations can also be added before if statements and before each case keyword of the when construct. Every stepper program has a special state derived by adding ".success" to the name of the stepper program (assignment.success in our example above) that is the last step of the state machine and is setup as a success ASL state.

Stepper allows you to express loops and iterations more naturally without having to manually create cumbersome constructs in ASL. Stepper supports the traditional for loop, the iterative variant for in and while loops.

Looping over a range

To loop over a range, use the for loop


    for (a = 0 to 10 step 2) {
      <statement+>
    }

The initial value, final value and step can all be expressions. Since Step Functions don't have variable scopes, the loop variable remains with its last value after the loop terminates.

Looping over collections

for loops are often used to iterate over collections. To loop over a javascript array, Stepper supports the for in construct

    for (n in names) {
      <statement+>
    }

The while loop evaluates a boolean predicate and executes the body of the loop as long as the predicate is true.


    while (x < s.length) {
      <statement+>
    }

Tasks are the mainstay of Step Functions. Tasks allow you to call activities, insert into SQS, read from Dynamo and make calls to Lambdas. Stepper provides features that makes using Task states easier. Here is an example of making a call to insert a value into a queue:

  task {
        "Resource": "arn:aws:states:::sqs:sendMessage",
        "Parameters": {
            "MessageBody": "0",
            "QueueUrl": "https://sqs.us-east-1.amazonaws.com/1570xxx/fibo"
        }
    }

As you can see, the basic structure is very similar to the ASL construct. However, with Stepper tasks, you can get a return value by simple assignment:

  entry = 5;
  sqs = task {
        "Resource": "arn:aws:states:::sqs:sendMessage",
        "Parameters": {
            "MessageBody.$": "$.entry",
            "QueueUrl": "https://sqs.us-east-1.amazonaws.com/1570xxx/fibo"
        }
    }

The assignment creates a result path of sqs that will contain the returned value. Calls to task should have a return value assignement or a ResultPath json element. Otherwise, an element with the same name as the state will get the return value.

To give the task with a very specific state name, simply add the label annotation.

  @Label("mySqsTask")
  sqs = task {
        "Resource": "arn:aws:states:::sqs:sendMessage",
        "Parameters": {
            "MessageBody.$": "$.entry",
            "QueueUrl": "https://sqs.us-east-1.amazonaws.com/1570xxx/fibo"
        }
    }

The wait construct in Stepper is also a simplified version of the ASL state.

    wait {
      "Seconds": 10 // or "Timestamp": <constant or variable>
    }

fail construct

Like the wait statement, the fail construct in Stepper is a simplified version of the ASL state.

    fail {
      "Cause": "Invalid response.",
      "Error": "ErrorA"
    }

As with wait, you do not provide a state name. Stepper does not support the success step. To stop the state machine from a particular step, either set the branching logic so that the particular step leads to the end of the program or put in an explicit goto statement to special state of ".success".

retry logic

Step Functions support declaring retry logic by error type. Stepper allows you to use annotations to describe the same.

  stepper mytask {
        @RetryOnError("abc", "def") { "IntervalSeconds": 3, "MaxAttempts": 4, "BackoffRate": 5 }
        @RetryOnError("pqr") { "IntervalSeconds": 6 }
        @Label("hello")
        task {
            ...
        }
    }

The syntax for the retry annotation is

@RetryOnError("", "", ...) { // encoding of IntervalSeconds, MaxAttempts and BackoffRate as json attributes }

Note: IntervalSeconds is optional and defaults to 1, MaxAttempts defaults to 3 and BackoffRate defaults to 2.

try/catch construct for error handling.

Step Function Task and Parallel states allow for the definition a catcher using a "Catch" attribute. The "Catch" attribute allows one to jump to a different state upon encountering a particular exception. Stepper models the catcher using tradition try/catch construct.

  stepper Simple {
        a = 5;
        try {
            b = 3 * a;
            d = b * b;
        } catch ("e1", "e2") { errorInfo ->
            c = 3;
            goto "g1";
        } catch ("e3", "e4") {
            fail;
        }
        @Label("g1")
        d = 10;
    }

In the example above, two assignment statements have been wrapped with a try/catch construct. The catch keyword is followed by a list of errors to which it applies. As seen, you can have multiple catch clauses. The catch clause can also declare a variable (errorInfo in the example above) that will be propagated to the next statement. The catch clause must terminate with a goto or fail statement.

The Parallel state in Step Functions allow you to declare two or more child step-functions that will be executed in parallel. The syntax in ASL is particularly cumbersome as you have to embed the child step functions within the Parallel task's definition. Stepper allows you to write separate Stepper programs for each child step-function to be run in parallel and invoke them by reference.

    stepper Para {
        a = 5 * 2;
        @Label("a")
        @RetryOnError("pqr") { "IntervalSeconds": 6 }
        b = parallel("first.stepper", "second.stepper")
    }

    stepper First { // defined in file named first.stepper
        a = 5 * 5;
        b = 10;
    }
    stepper Second { // defined in file name second.stepper
        a = 15 * 5;
        b = 20;
    }

In the example above, the Stepper program Para makes a parallel call to two other stepper programs defined in files called first.stepper and second.stepper. Those files are expected to be in the same directory as Para. However, files can be loaded from the JVM classpath by prefixing it with classpath://