Skip to main content

Simplified WSL Syntax

Simplified WSL provides a more concise, pipeline-style syntax for defining workflows. This alternative syntax reduces boilerplate and makes simple workflows more readable.

Overview

While standard WSL uses explicit workflow, state, and transition blocks, Simplified WSL uses a more compact notation with:

  • Pipeline operators (->) for chaining actions
  • Error binding (<-) for error handling
  • Definition statements (def) for reusable handlers
  • Inline action chaining without explicit state names
  • Optional module declaration - derives name from filename if omitted

Module Declaration

The module keyword is optional in SimplifiedWSL. If omitted:

  • When using a .swsl file, the module name is automatically derived from the filename (without extension)
  • When parsing without a filename, the default module name is main

With explicit module:

module payment_flow

action.Call() -> .

Without module (using filename):

// File: payment_flow.swsl
// Module name will be "payment_flow"

action.Call() -> .

Without module (no filename):

// Module name will default to "main"

action.Call() -> .

Basic Syntax

Standard WSL vs Simplified WSL

Standard WSL:

module example
import services/common

const {
msg: "Hello World",
code: 200
}

workflow hello {
start: Speak

state Speak {
action converse/speak.Say(on: "message", v: $constants.msg)
on success -> Respond
on error -> HandleError
}

state Respond {
action services/common/response.ResponseValue(
message: $response.message,
statusCode: $constants.code
)
end ok
}

state HandleError {
action services/common/response.ResponseValue(
message: "error",
statusCode: 500
)
end error
}
}

Simplified WSL:

import services/common

const {
msg: "Hello World",
code: 200
}

// Define error handler first
def services/common/errors.OnAnyError(msg: "error", statusCode: 400) as errorHandler -> services/common/response.ResponseValue(message: $error.message, statusCode: 500) -> .

// Main action flow with error binding
converse/speak.Say(on: "message", v: $constants.msg) as response <- errorHandler -> services/common/response.ResponseValue(message: $response.message, statusCode: $constants.code) -> .

Key Concepts

1. Pipeline Operator (->)

Chain actions together in a linear flow:

action1() -> action2() -> action3() -> .

The dot (.) at the end signifies the end of the pipeline.

2. Error Binding (<-)

Bind an error handler to an action:

mainAction() as result <- errorHandler -> nextAction() -> .

If mainAction() fails, execution goes to errorHandler.

3. Definition Statement (def)

Define reusable action sequences or handlers:

def handlerName(param1: value1, param2: value2) as alias -> action1() -> action2() -> .

Important: The def keyword creates action templates/aliases that are NOT executed as separate workflow states. Instead, they are inlined at the point where they are referenced. This is particularly useful for defining reusable error handlers.

// Define an error handler template
def errors.OnAnyError(msg: "error") as errorHandler -> .

// The workflow starts with mainAction(), NOT errorHandler
// errorHandler is only executed if mainAction() fails
mainAction() <- errorHandler -> nextAction() -> .

In this example:

  • The def creates a template called errorHandler
  • It does NOT run as the first state of the workflow
  • When referenced with <- errorHandler, the error handler action is inlined at that point
  • The workflow execution begins with mainAction(), not errorHandler

4. Action Aliasing (as)

Capture action results for use in subsequent steps:

action() as result -> nextAction(data: $result.value) -> .

5. Nested Structures in Action Arguments

SimplifiedWSL fully supports complex nested structures in action arguments, allowing you to pass hierarchical configuration directly to your actions:

Simple nested object:

payment.Process(
amount: 100,
metadata: {
user: "test@example.com",
timestamp: 1234567890
}
) -> .

Complex nested structure with arrays:

api.Request(
method: "POST",
url: "https://api.example.com/data",
config: {
timeout: 5000,
retries: 3,
headers: [
{ key: "Content-Type", value: "application/json" },
{ key: "Accept", value: "application/json" },
{ key: "Authorization", value: "Bearer token123" }
],
settings: {
cache: true,
validate: false,
limits: [500, 1000, 5000]
}
}
) -> .

Using constants with nested structures:

const {
paymentConfig: {
provider: "stripe",
currency: "USD",
retries: 3
}
}

payment.Process(
amount: 100,
metadata: {
user: "test@example.com",
tags: ["priority", "verified"]
},
config: $constants.paymentConfig
) -> .

Type conversions:

  • String literals → string
  • Integer numbers → int64
  • Floating-point numbers → float64
  • true/falsebool
  • nullnil
  • Objects {}map[string]interface{}
  • Arrays [][]interface{}

Workflow Types & Hierarchical Execution

SimplifiedWSL supports workflow type declarations for hierarchical execution. You can declare a workflow as a feature, solution, workflow, or custom type.

Workflow Type Declaration

Declare the workflow type at the module level, optionally with a name:

module payment_processing

// Type only (workflow name defaults to module name)
feature

// Type with explicit name
feature payment_processor

// Other supported types
solution
workflow
microservice // Custom type

Workflow Types

Solution

Top-level orchestration of features and workflows:

module ecommerce

solution checkout_solution

const {
timeout: 5000
}

def errors.Critical(level: "critical") as errorHandler -> .

# Orchestrate features
payment.ProcessFeature() <- errorHandler ->
notification.SendFeature() <- errorHandler -> .

Feature

Mid-level orchestration of workflows:

module payment

feature payment_processor

const {
retries: 3
}

def errors.Log() as errorHandler -> .

# Orchestrate workflows
payment.Validate() <- errorHandler ->
payment.Process(retries: $constants.retries) <- errorHandler -> .

Workflow

Low-level execution of actions (default):

module validation

workflow validate_payment

def errors.OnError() as errorHandler -> .

payment.CheckAmount() <- errorHandler ->
payment.CheckBalance() <- errorHandler -> .

Context Sharing

All components in the hierarchy share the same WorkerSessionContext:

# In solution: set values
context.Set(key: "user_id", value: "12345") ->
context.Set(key: "amount", value: 100.00) ->

# Call feature
payment.ProcessFeature() -> .

# In feature/workflow: access values
payment.Process(
userId: $context.user_id,
amount: $context.amount
) -> .

Hierarchical Call Patterns

# Solution can call features and workflows
payment.ProcessFeature() ->
notification.SendWorkflow() ->
services/common.Response() -> .

# Feature can call workflows
validate.ValidatePayment() ->
process.ProcessPayment() -> .

# Workflow calls actions only
payment.CheckAmount() ->
payment.Process() -> .

For more details, see the Hierarchical Execution Guide.

Complete Examples

Example 1: Simple API Call

Simplified WSL:

import services/http

const {
apiUrl: "https://api.example.com/users",
timeout: 5000
}

// Define error handler
def http/errors.HandleError() as errorHandler -> response/Error(code: 500) -> .

// Main flow
http/client.Get(url: $constants.apiUrl, timeout: $constants.timeout) as apiResponse <- errorHandler -> response/Success(data: $apiResponse.body) -> .

Example 2: Data Processing Pipeline

import data/processor
import data/validator

const {
batchSize: 100,
maxRetries: 3
}

// Define validation error handler
def validator.HandleInvalid(message: "Validation failed") as validationError -> response/Error(code: 400, message: $error.details) -> .

// Define processing error handler
def processor.HandleProcessingError(retries: $constants.maxRetries) as processingError -> response/Error(code: 500, message: "Processing failed") -> .

// Main pipeline
data/input.Fetch() as rawData <- validationError ->
validator.Validate(data: $rawData) as validData <- processingError ->
processor.Process(data: $validData, batchSize: $constants.batchSize) as result ->
response/Success(processed: $result.count) -> .

Example 3: Multi-Step Transformation

import transform/csv
import transform/json
import storage/s3

const {
bucket: "my-bucket",
format: "json"
}

// Define handlers
def errors.OnTransformError() as transformError -> response/Error(code: 500, message: "Transform failed") -> .
def errors.OnStorageError() as storageError -> response/Error(code: 500, message: "Storage failed") -> .

// Pipeline with multiple error handlers
csv.Read(path: "input.csv") as csvData <- transformError ->
transform/json.Convert(data: $csvData) as jsonData <- storageError ->
storage/s3.Upload(bucket: $constants.bucket, data: $jsonData) as uploadResult ->
response/Success(location: $uploadResult.url) -> .

Example 4: Conditional Processing

import processor
import validator

const {
threshold: 100
}

// Main flow with inline conditional
validator.Check(data: $input) as checkResult <- errorHandler ->
processor.Route(
data: $checkResult,
condition: $checkResult.size > $constants.threshold,
onTrue: processor/parallel.Process,
onFalse: processor/single.Process
) as result ->
response/Success(processed: $result) -> .

Syntax Reference

Operators

OperatorDescriptionExample
->Pipeline/chain actionsaction1() -> action2()
<-Bind error handleraction() <- errorHandler
asAlias action resultaction() as result
.End pipelineaction() -> .
``Type cast

Keywords

KeywordDescriptionExample
defDefine reusable handlerdef handler() as name -> ...
importImport modulesimport services/common
constDefine constantsconst { key: value }

Structure

// Imports
import module/path

// Constants
const {
key: value
}

// Definitions
def handlerAction() as handlerName -> action() -> .

// Main pipeline
action1() as result <- errorHandler ->
action2(data: $result) ->
action3() -> .

Advanced Features

Nested Pipelines

// Outer pipeline
action1() as data ->
(
// Nested pipeline
transform1($data) ->
transform2() ->
transform3()
) as transformed ->
action2(result: $transformed) -> .

Multiple Error Handlers

// Different error handlers for different stages
action1() as step1 <- errorHandler1 ->
action2(data: $step1) as step2 <- errorHandler2 ->
action3(data: $step2) <- errorHandler3 -> .

Handler Composition

// Define composed handlers
def baseHandler() as base -> logError() -> .
def extendedHandler() as extended <- base -> notifyAdmin() -> .

// Use in pipeline
mainAction() <- extendedHandler -> nextAction() -> .

Type Casting

Use the pipe operator for type conversions:

const {
count: "42",
price: "19.99"
}

// Type casting in pipelines
validator.Check(count: $constants.count|int, price: $constants.price|float) ->
processor.Process() -> .

Best Practices

1. Use for Simple Workflows

Simplified syntax is ideal for straightforward, linear workflows:

// Good use case
fetch() -> transform() -> validate() -> save() -> .

2. Define Reusable Handlers

Extract common error handling:

def errors.StandardError() as stdError ->
logger.Log(level: "error") ->
response/Error(code: 500) -> .

3. Keep Pipelines Readable

Break long pipelines into multiple lines:

action1() as result1 ->
action2(data: $result1) as result2 ->
action3(data: $result2) as result3 ->
action4(data: $result3) -> .

4. Use Meaningful Aliases

Choose descriptive names for captured results:

// Good
user/fetch.GetUser() as currentUser ->
profile/load.LoadProfile(userId: $currentUser.id) as userProfile ->

// Avoid
user/fetch.GetUser() as x ->
profile/load.LoadProfile(userId: $x.id) as y ->

Organize error handlers and utilities together:

// Error handlers
def errors.ValidationError() as validationErr -> ...
def errors.ProcessingError() as processingErr -> ...
def errors.StorageError() as storageErr -> ...

// Main pipeline
...

When to Use Simplified WSL

Good Use Cases

Simple linear workflows - Straight-through processing
Data transformation pipelines - ETL-style operations
API integrations - Fetch, transform, respond
Quick prototypes - Rapid workflow development

When to Use Standard WSL

Complex branching logic - Multiple conditional paths
State machines - Workflows with many states and transitions
Long-running processes - Workflows with extensive orchestration
Team projects - When explicit structure aids collaboration

Comparison Table

FeatureStandard WSLSimplified WSL
SyntaxVerbose, explicitConcise, pipeline-style
StatesNamed statesImplicit/inline
TransitionsExplicit on success/errorPipeline operators
Error HandlingPer-stateBound handlers
ReadabilityMore explicitMore compact
Best ForComplex workflowsSimple pipelines

Migration Between Syntaxes

You can mix both syntaxes in a project. Convert workflows based on complexity:

From Simplified to Standard

When a workflow grows complex, convert to standard WSL:

// Simplified (getting complex)
action1() <- err1 -> action2() <- err2 -> action3() <- err3 -> .

// Better as Standard WSL
workflow complex_flow {
start: Action1

state Action1 {
action action1()
on success -> Action2
on error -> Error1
}
// ... clearer structure
}

Editor Support

.swsl files are first-class in both official editor plugins, with dedicated grammars, snippets for def, feature, solution, hierarchical calls (workflow:, feature:, solution:), and the pipeline operators (->, <-, .). See IDE Plugins for installation.