# 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"
# Spawn a new process using the string contents as an executable script
run
<""
#!/usr/bin/env python
print("Hello from python!")
<""
# 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