# 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