Programmiersprachen – Programming Languages
LVA 185.208, VU, 3 ECTS, 2016 S
"".
An application of the concatenation operator + to two property lists results in a new property list containing the properties of the two operands.
In general, if a property of the same name occurs in both operands, the value is taken from the second operand.
If an operand is a block, corresponding properties are added by the execution of commands (see below).
A string is a special property list having a sequence of characters beside properties.
When concatenating two strings, the character sequence in the resulting string is the concatenation of the character sequences of the arguments.
When concatenating a string with a non-string property list, the resulting string has the same character sequence as the string operand.
Properties are taken from both arguments as for all applications of +.
There are string literals in the usual syntax: "abc 123".
Since strings are built-in property lists while all other property lists are defined by programmers, strings contain almost all built-in functionality of the language as properties.
Programmers get access to build-in functionality by using properties of string literals.
A block is essentially a sequence of commands that can be used to initialize a new property list.
The commands are executed when the block is used as an operand of +.
The left-hand side of + is executed first (starting with a new empty property list), the right-hand side afterward (starting with the result of applying the left-hand side).
There are several kinds of commands:
+ has the same effect as if + had been applied to the replacement specified in the return statement.
When executing the return statement in a block to the right of +, the result of applying + is the replacement specified in the return statement.
A + B where A is a property list containing a property for each argument, and B is a block where commands use these properties.
In A + B + C (or equivalently (A + B) + C) block C is applied to the result of A + B, whereas in A + (B + C) block C is applied to the properties specified by B and the result is concatenated to A.
block ::= '{' { command } '}'
command ::= '[' guard ':' { command } ']'
| [ { '*' } name '=' ] expression ';'
| '^' expression ';'
guard ::= expression ( '=' | '#' ) expression [ ',' guard ]
expression ::= ( string_literal | block | { '*' } name | '(' expression ')' ) { '.' name } [ '+' expression ]
A program is a block.
When executing the program, this block is applied to a property list constructed from named command-line arguments.
A guard X = Y is satisfied if X and Y have equal strings, and X # Y is satisfied if X = Y is not satisfied.
A guard can be composed of several string comparisons separated by commas; it is satisfied only if each of the string comparisons is satisfied.
For program simplicity several commands can depend on the same guard.
While = is a comparison operator in a guard, it is an assignment operator in a command:
Within a command Y = X; the left-hand side Y specifies a property, and X its new value.
A simple command of the form X; computes X, but does not store the resulting value anywhere;
it is useful if the computation of X has side effects.
A return statement ^X; replaces the current property list with X and terminates the execution of the block.
Each expression belongs to the block determined by the innermost braces surrounding the expression.
This is the current block.
A simple name used as expression refers to a property of the property list constructed by executing the current block.
Blocks can occur within blocks.
To refer to a property of an outer block we use *-notation:
A name preceded by * refers to a name of the block directly surrounding the current block, a name preceded by ** to the block surrounding the block surrounding the current block, and so on.
A name of the form x.y.z (or *x.y.z) refers to a property of name z in a property list referred to by x.y (or *x.y) in the property list referred to by x (or *x).
The syntax of commands is restricted such that new values can only be assigned to properties easily visible in the program text, i.e., properties determined without
.
For comments we use a Lisp-like style:
Everything between an occurrence of .% and the end of the same line is a comment.
Here is an example of a program with a routine computing the maximum of three numbers:
{
max = { % number a, number b, number c -> number or "error"
[ c = "" : c = "0"; ] % by default use "0" if c not specified
ab = {} + ("test " + a + " -le " + b).syscall;
[ ab = "1" :
ac = {} + ("test " + a + " -le " + c).syscall;
[ ac = "1" : ^c; ]
[ ac = "0" : ^a; ]
][ ab = "0" :
^ { a = *a; b = *c; c = *b; } + *max;
]
^ "error";
};
result = { a = "12"; b = "27"; c = "18"; } + max;
{} + ("echo " + result).syscall;
{} + ("echo " + ({ a = "-12"; b = "-27"; } + max)).syscall;
}
Because there is no numerical type in the language, we use strings instead.
And because there are no appropriate comparison operators, we invoke foreign programs to perform the comparison.
First, we assign a block to the property max.
Then, we call max on a property list containing a property for each parameter expected by max (this is a, b and c) and assign the resulting value to result.
The Linux command echo x just writes x to the standard output.
We invoke this Linux command by calling syscall (a parameter-less block assumed to be built-in in strings) on a string containing the Linux command.
The last command also writes the result of a call of max to standard output, in this case without specifying a property for parameter c and without storing the result in a property.
The first guarded command in max assigns "0" to c if c had no value (or the invalid value "") before.
We use the Linux command test for comparing integer values.
For example test 1 -le 2 gives us the return code "0" (yes, 1 is less than or equal to 2),
test 2 -le 1 gives us the return code "1" (no, 2 is not less than or equal to 1),
and test a -le b gives us a return code of "2" or larger (inappropriate arguments or another error).
The return code of comparing the values of the properties a and b is stored in property ab.
Depending on the return code a further comparison is necessary before max can return the correct string as result.
A recursive call of max occurs in one case.
In this call we have to use *-notation because the corresponding properties belong to outer blocks.
If a return code of test signals an error (neither "0" nor "1"), then max returns "error".
Here is another example to show how the property lists can be used to simulate objects:
{
error = "0";
fact = { % number num -> number; set error if something is wrong
continue = {} + ("test 1 -ge " + num).syscall;
[ continue = "0" :
[ {} + ("test 0 = " + num).syscall # "0" :
*error = "1";
]
^ "1";
]
nextnum = { in = *num + "-1"; } + "bc -lq".iosyscall;
[ nextnum.result = "0" :
result = { in = { num = **nextnum.out; } + **fact + "*" + *nextnum.out; } + "bc -lq".iosyscall;
[ result.result = "0" :
^ result.out;
]
]
*error = "2";
^ "0";
};
value = { num = "5"; } + fact;
[ error = "0" : { in = "result: " + *value; } + "echo".iosyscall; ]
[ error # "0" : { in = "error #" + *error; } + "echo".iosyscall; ]
}
In some sense, error is an object variable accessed in the method fact (in a similar way as character sequences of strings are accessed in syscall).
We assume iosyscall to be a further property of string; this block takes the whole contents of an input stream as a parameter.
Furthermore, iosyscall probably does not use a return statement.
Instead, it just produces a property list (as stored in nextnum or result).
The property result accessible through result.result or nextnum.result contains the return code of the Linux command, and the property out the complete standard output.
There could also be a property err with the standard error output (not used here).
As we see, the boarders between objects and methods are blurred when using property lists.
Built-in functionality is located as properties in strings.
We used syscall and iosyscall as examples.
Please design and implement your own list of properties that you regard as necessary or useful.
Details given above describe an example of a language you shall develop. Please feel free to change details and develop your own language. However, your language must be based on property lists, strings and guarded commands. It must be able to invoke other programs.