123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- #!/usr/bin/env attl
- #
- # Welcome to ATTL! This is an executable tutorial for the ATTL language.
- # You will need to get the ATTL interpreter and install it for your platform
- # to run this script.
- # What is the line on top of the script? It is there to allow the ATTL script
- # to be executed directly on Unix like OS.
- #
- # Attl works in two phases: parse and evaluate.
- #
- # In the parse phase ATTL reads in the script and parses it,
- # which means it breaks it down into a format it can interpret. If an error
- # occurs in this phase, ATTL will report that error and the script will
- # do nothing at all.
- #
- # In the evaluate phase, ATTL then interprets or evaluates the parsed script.
- # If an error occurs the script will still partially be executed,
- # and such errors may be caught and handled.
- #
- # Now let's start exploring the ATTL language itself.
- # In front of this line and those above, you can see there is a # mark.
- # This is to indicate that the line is a comment.
- # ATTL parses comments but they do nothing during evaluation.
- # This is useful to explain what an ATTL script does like in this tutorial.
- #
- # Comments run from the # mark until the end of the line.
- # Comments may also be indented with spaces.
- # For example: this is also comment
- #
- # To make ATTL actually do something, we need to write a command in the script.
- # Let's start with the traditional "Hello World!" below:
- print "Hello World!\n"
- # The command above consists of an order print, and it's parameter
- # "Hello world\n" which is a literal text, with a new line character in it at
- # the end.
- # This commands orders the ATTL interpreter to print the text "Hello World"
- # followed by a new line to the interpreter's standard output.
- #
- # Commands in ATTL consist of an order followed optionally by parameters
- # Every parameter has to have whitespace in front of it, so ATTL can find it.
- # That's why it's not allowed to have whitespace at the end of the line.
- # Attl will give a parse error since it expects a parameter to follow the space.
- # An Attl command must be terminated by a newline or a semicolon.
- print "Hello " ; print "World!\n"
- # Some arguments take multiple parameters.
- # For example, print can also take multiple parameters.
- print "Hello $1\n" "World!"
- #
- # The $1 in the string above is an example of string interpolation.
- # This is explained later, below in this tutorial. For now, note that
- # print will replace $1, $2, ... with the text representation of it's 1st,
- # 2nd, and so forth parameter.
- #
- # All commands in ATTL have this same form. Unlike many other programming
- # languages, there are no special constructs. The evaluation of an Attl script
- # is fully determined by the available commands.
- # In this tutorial we look at how ATTL works with the builtin commands it
- # provides, but when ATTL is used as an embedded language, it might work
- # differently if the commands are different or not available.
- #
- # The effect of ATTL commands also depend on the Environment in which they
- # are executed. ATTL command can write to the ATTL environment's output,
- # read from the ATTL's environment input, and store data in the environment
- # using variables. Variabes are described later.
- #
- # Let's continue with some examples of commands.
- # For example this is how do do 1 + 2 in ATTL:
- iadd 1 2
- # As you can see attl has no operators, just commands, so we use the command
- # iadd, short for integer add to add 1 and 2 together.
- # You will notice that when running this script, this iadd seems to do nothing
- # This is because we don't print the value anywhere.
- # Let's see how to do this.
- print "1+2=$1\n" [iadd 1 2]
- # The command above prints 1+2=3. But, what are the square brackets [] for?
- # Let's see what happens when we remove them:
- print "1+2=$1\n" iadd 1 2
- # Then the command prints 1+3=iadd
- # The brackets [] are to indicate to ATTL that it should not take what is
- # between them as parameters of the first command in the line, but that it
- # starts a new command of which the value should be taken and used as
- # a parameter. Such a command between [] is called an Evaluation.
- #
- # Evaluations are eagerly evaluated when they are in an argument list.
- # This means they are evaluated before the command receives them as arguments.
- #
- # Evaluations may be nested as much as you like. For instance:
- print "((5+4)*3)-1)/2=$1\n" [idiv [isub [imul [iadd 5 4] 3] 1] 2]
- # .. prints ((5+4)*3)-1)/2=13
- #
- # While this seems a bit cumbersome compared to operators, it keeps ATTL simple.
- # I do not recommend using ATTL for intensive maths.
- #
- # From this we can see that attl commands not only have effects such as writing
- # to the output, they also produce a result. If the ATTL command is successful
- # it returns a Value. If it not successful, it fails, which causes execution to
- # end, unless a rescue command is used (see later).
- # All commands either return a value or fail. Even print returns the amount of
- # bytes written, as follows:
- #
- print "Written $1 bytes\n" [print "Hello World\n"]
- #
- # The basic data which ATTL operates on, and which commands return
- # is called a Value. Unlike languages like TCL, ATTL Values are typed.
- # To see the type of a value use the typeof command
- print "type of 1: $1\n" [typeof 1]
- print "type of \"hi\": $1\n" [typeof "hi"]
- #
- # ATTL supports the following types by default:
- # * String: for literal text, in UTF-8 format.
- write "String µ出来る\n"
- # The above should write String µ出来るif your terminal supports it.
- # Normal strings support the following escape sequences:
- # \a: bell character
- # \b: backspace character
- # \e: escape character
- # \f: form feed character
- # \n: new line character
- # \r: carriage return character
- # \t: tab character
- # \\: a \ character
- # \": a " character
- # For instance, on a terminal which suports ANSI color the following prints
- # foo bar\ in red:
- print "escapes: \nX\b\e[31mfoo\tbar\\\e[0m\n\r"
- # Strings can run over multiple lines
- # A string can also run over multiple lines if it is a raw string between ``
- # Raw strings accept no escape characters and always terminate on a `.
- # There is no way to put a `in a raw string, but a raw string ans a normal
- # string can be appended to each other to allow this if the sadd
- # command is available.
- print `A
- "raw"
- string
- \\ no escapes \"
- `
- # * Int: which is for integer numbers.
- print "Int: $1\n" 123
- # * Word: which is for names.
- # Words consist have any non-symbol, non whitespace character, _ and /
- print "Word: $1\n" iAmA_Word/7
- # * Bool: for boolean values. $true and $false are default boolean variables.
- # Words consist have any non-symbol, non whitespace character, _ and /
- print "Bool: $1 $2\n" $true $false
- # * List: for lists of ATTL values.
- # lists are created wth the list command
- print "List: $1\n" [list 1 2 3]
- # * Map: for a lookup map by string to value.
- # maps are created wth the map command. The key of maps must be strings.
- print "Map: $1\n" [map "a" 1 "b" 2]
- # * Block: for blocks of ATTL commands, between {}.
- # More about blocks later.
- print "Block: $1\n" {print "block ";print "world"}
- # * Type: for types.
- # Types can be defined with the command type. they are useful for defining
- # overloads. More on overloads later.
- print "Type: $1\n" [typeof [type "Type"]]
- #
- # When embedding ATTL, the embedding program can define other types.
- #
- # We can store values the ATTL environment as variables.
- # In ATTL, a variable must first be declared and initialized with let
- # before using it.
- #
- print "Hello $1 variable\n" $variable
- # This prints Hello !nil because the variable has not been defined yet.
- # To define a variabe with a value, use let
- let variable "World"
- # To get the value of the variable use get in an evaluation:
- print "Hello Variable: $1\n" [get variable]
- # [get variablename] is a common operation so we can abbreviate it using
- # a dollar sign: $variablename
- print "Hello Variable: $1\n" $variable
- # It's possible to repeat the dollar sign for indirect variable expansion
- let name variable
- print "Hello indirect variable: $1\n" $$name
- # String interpolation.
- # print as a command, performs string interpolation. That means it looks for
- # any dolar signs in the text and replaces them with the value of the
- # following variable in the ATTL environment.
- print "Hello Variable: $variable\n"
- # The variable for interpolation can also be indicated by a ${variable}
- # This is useful to if the variable must befollowed by a non-space:
- print "Hello Variable: ${variable}Record\n"
- # To obtain a $ in an interpolated value, just double it.
- print "Hello dollar: $$${1}\n" 35
- # ATTL does not do string interpolation automatically, it is up
- # to the commands that take strings to do this if needed.
- # To interpolate strings ourselves, we can use the expand command
- set variable "expanded"
- let expanded [expand "Hello $variable\n"]
- print $expanded
- # Blocks are lists of commands. Block are not evaluated when passed as
- # a parameter to a command, but passed as such, and can be used to
- # implement flow control using commands, or to define new commands.
- # Blocks can run over multiple lines, the newlines in a block do NOT
- # terminate the top level command in front of the block.
- # The value of executing a block is set by the last command in the block,
- # or the return command if present.
- # Blocks also have anonymous parameters that are evaluated upon execution
- # of the block.
- let block {
- # Blocks receive their anyonymous parameters in $1..$argc, and
- # as a list in $argv
- let p1 $1
- print "hello $p1 $argc $argv"
- print " block\n"
- return 77
- }
- # Blocks stored in variables can be executed as if they were commands,
- # and can take parameters
- block "param1"
- # And blocks evaluate to their last command or return statement.
- print "block value: $1\n" [block]
- # Variables are dynamically and block-scoped.
- # When using set, and get, the innermost variable defined with let
- # will be used to set or get the value.
- # XXX bug here it seems
- let foo 1
- #{
- # let foo 2
- # {
- # let foo 3
- # print "$foo\n"
- # }
- # print "$foo\n"
- #}
- print "$foo\n"
- # compare the above with the below:
- let bar 1;{set bar 2;{set bar 3;print "$bar\n"};print "$bar\n"};print "$bar\n"
- # You can define commands yourself with the builtin "to" command.
- # This allows you to use named parameters in the block.
- # Parameters are passed by value, but using word parameters, you can implement
- # pass by name parameters as well.
- to set_print name value {
- print "Setting $name from $1 to $value\n" $$name
- set $name $value
- return 0
- }
- let quux 123
- set_print quux 321
- print "quux: $quux\n"
- # Flow control can be done with the built in if command.
- if [igt $quux 200] {
- print "OK\n"
- } else {
- print "HUH?\n"
- }
- # ...or with the switch comand. The default word is used to mark the default
- # block. Other values are compared by string equality.
- switch $quux default {
- print "NOK?\n"
- } 123 {
- print "ALSO NOT OK\n"
- } 321 {
- print "OK\n"
- }
- # The overload command allows you to use the same command for different types
- # by redirecting them to a type-specific command
- overload add iadd Int Int
- overload add sadd String String
- print [add 10 20]
- print [add "10" "20"]
- # When an error occurs, it can be caught by setting up a rescue block with
- # the rescue command. The rescue block itself has block scope, that is
- # it applies to the block in which the rescue command is called.
- # To cause an error, use the fail command.
- to rescue_fail {
- rescue {
- let e $1
- print "Error rescued: $e\n"
- }
- fail "Synthetic error"
- }
- rescue_fail
- # There are many other builtin commands available. See their description
- # by using the help or help all command.
- # help all
- help help
|