# Single-line comment

# Auto is a text-processing and input automation language designed to replace expect.

# Variable assignment
# Supported types are strings and numbers
a = "foo"
b = 'bar'
c = 3

# Strings evaluate anything in \( ) as an expression
x = "a is: \(a), b is: \(b), and c is: \(c)"

# Block strings are delimited by <""
y = <""
    The delimiters must be vertically aligned.
    Leading whitespace before the delimiters is trimmed, as well as the
    newlines at the start and end of the block string.
    <""

# Words with spaces are legal variable names
my variable = "bar"

# The with block sets variables in a local scope
with x="hello", y=" world!" {
    print x + y
}

## Built-in Operations

# Spawn a new process to interact with
spawn "sh"

# Print to the screen
print "hello there"

# Built-in color support for printing
# Available colors: black, red, green, yellow, blue, magenta, cyan, white
print "pretty colors!" in red

# Compound statements using parentheses
print ("And" in red) and (" we can write compound statements!" in blue)

# Send keystrokes to the connected process
type "ls\n"
type a + b + "\n" # Concatenate variables
type ctrl-C # Control codes

# Drop into the REPL
breakpoint

# Sleep for a given number of seconds
sleep 3.5

# Wait for the attached process to exit
wait

# Allow the user to interact with the attached process until exit
interact

# exit the program
exit
exit 1 # return code

## Text matching

# Match statement scans the process output for a matching string
spawn "bash"
match "$" # Wait for shell prompt

# Multi-armed match for conditional execution
type 'echo $(($RANDOM%2))\n' # Print 1 or 0 randomly
match {
    "0" {
        print "heads!"
    }
    "1" {
        print "tails!"
    }
}

## Timeout

# Timeout for match branches (in seconds)
timeout = 30
match {
    "Segmentation Fault" {
        print "your code broke!"
    }
    timeout { # Branch for timeout
        print "your code hung!"
    }
}

## Match patterns

# Patterns match text using rules defined as EXAMPLE => PATTERN
# The EXAMPLE is the text to match
# The PATTERN defines the matching rules

# Match the string "$" literally
sh := {
    "$" => "$"
}

# Match either "$" or "#"
two shells := {
    "$" => "$"
    "#" => "#"
}

# Match any lowercase letter using a character range
any lowercase letter := {
    "a" => "a".."z"
}

# Match 1 or more digits using repetition (+)
number := {
    "25" => "0".."9"+
}

# Match 0 or more exclamation marks using repetition (*)
greeting := {
    "hello!!" => "hello" "!"*
}

# Match an optional carriage return using ?
lowercase line := {
    "pwd\n" => "a".."z"* "\r"? "\n"
}

# Match alphanumeric characters using logical OR (|)
alphanumeric := {
    "w" => "a".."z" | "A".."Z" | "0".."9"
}

# Match anything in brackets using negative lookahead and the 'any' keyword
brackets := {
    "[hello]" => "[" ((not "[") any)* "]"
}

# Match hex literals with specific width using range mutliplication (*)
byte := {
    "0xff" => "0x" 2*("a".."f")
}
long literal := {
    "0xdeadbeef" => "0x" (4..8)*("a".."f")
}
maybe infinite literal := {
    "0xdeadbeeeeeeef" => "0x" (2..)*("a".."f")
}

# Conditional execution based on patterns
match {
    greeting {
        print "hi!"
    }
    number {
        print "that sure is a number!"
    }
}

## Pattern parameters

# Patterns can have sub-patterns defined in { }
# Sub-patterns are given a name, like 'version' in this example:
bash := {
    "bash-3.2$" => "bash-" version "$" {
        version {
            "3.2" => "0".."9"+ "." "0".."9"+
        }
    }
}

# Patterns can also be embedded into string literals with \( )
bash := {
    "bash-3.2$" => "bash-\(version)$" {
        version {
            "3.2" => '\("0".."9"+).\("0".."9"+)'
        }
    }
}

# Patterns that don't match the example string will fail to compile:
bash := {
    "bash-3.12$" => "bash-" version "$" {
        version {
            "3.12" => "0".."9"+ "." "0".."9"
                                         #  ^^  note the missing plus
        }
    }
}
# Output:
# :::Runtime Error:::
# Example string for symbol 'version' in 'bash' does not match the given rule:
# The string '3.12' does not match the rule ( "0".."9"+ "." "0".."9" )
#
# Line 10, col 9:
#   10 |  "3.12" => "0".."9"+ "." "0".."9"

# If there's only one sub-pattern, the sub-pattern declaration is optional
bash := {
    "bash-3.2$" => "bash-" version "$" {
        "3.2" => "0".."9"+ "." "0".."9"+ # No 'version' declaration needed
    }
}

# Use sub-patterns in match branches
match {
    bash => version {
        print "The bash version is " + version + "!"
    }
}

# Match specific sub-pattern values
match {
    bash => version => "3.2" { # Specific version
        print "bash 3.2 is unsupported."
    }

    bash => version { # Fallthrough case
        print "The bash version is " + version + "!"
    }
}

# Define nested sub-patterns
bash := {
   "root@host:~/Documents/stuff$" => username "@" hostname ":" path "$" {
       username { "root" => "0".."z"+ }
       hostname { "host" => "0".."z"+ }
       path {
           "~/Documents/stuff" => "~/" (directory character)+ {
               "D" => "0".."z"
               "/" => "/"
           }
       }
    }
}

# Match multiple sub-patterns using tuples
match {
    bash => (username, hostname) => ("root", "host") {
        print "I am root"
    }
    bash => (username, hostname) {
        print "I am " + username + " of " + hostname
    }
}

# You don't need to specify any sub-patterns you don't care about:
match {
    bash => path => "/var/log" {
        type "cat syslog\n"
    }
    bash { # Ignore other sub-patterns
        type "cd /var/log\n"
    }
}

# Sub-pattern values can be bound to custom names
match {
    bash (as b) {
        print "the bash prompt is '\(b)'"
    }
}

# For entries with a repeating suffix (+ or *), separate patterns for the
# first and last elements in the sequence can be specified using:
#   (first: PATTERN) and (last: PATTERN)

# This matches directory paths that don't end with a slash
path := {
    "/home/user/Documents" => root (directory entry)+ {
        root {
            "/" => "/"
        }
        directory entry {
            "Documents/" => name "/" (last: name) {
                "Documents" => "0".."z"+
            }
        }
    }
}

## Control flow

# The spin block is an infinite loop
spin {
    match {
        bash => path => "/" {
            print "arrived at root"
            break # Exit loop
        }
        bash => path => "~" {
            print "arrived at home"
            break
        }
        bash {
            type "cd ..\n"
            continue # Next iteration
        }
    }
}

# the if block works like the match statement,
# but operates on a string instead of the input stream
if "hello" {
    "he" { # won't run, entire string must be matched
        print "he"
    }
    "hello" {
        print "hello there!"
    }
    bash => path {
        print path
    }
    else { # use else block instead of timeout for no-match case
        print "??"
    }
}

# the split block allows partial matches, and re-assigns the variable to the
# remaning string after finding a match
line = "bash-3.12$ ls"
split line {
    bash {
        print "command is: '\(line)'" # prints "command is: ' ls'"
    }
    else { print "no bash prompt found" }
}

## Tasks

# Tasks are similar to functions in other languages
task greet (name) {
    print "hello, " + name + "!"
}

# Execute the task
greet (name)

# Parentheses are optional for literal arguments
greet "world!"

# All parameters after the first need a label
task send (cmd) with message: (msg) { # Labels must end with a colon
    match bash
    type cmd + "\n"
    print msg
    return # Exit task
}

send "pwd" with message: "we did it!"

# Task with no parameters
task sayhi {
    print "hello!"
}

sayhi