VTC Language Description Description ----------- This file is intended mainly as a description of the VTC extension language for users not familiar with C. The file vt.doc contains a fairly complete description of VTC for those who already know C. Learning VTC might help to learn some of the basics of C, but readers should be aware that VTC is a very simplified and less restrictive language. This file assumes that the user knows how to enter commands to the VTC interpreter. Read vt.doc for instructions on basic operations in the client. vt.doc also contains more in-depth descriptions of some VTC features. VTC SYNTAX Constants --------- One of the basic building blocks of VTC is constants. VTC has four types of constants: integer constants, character constants, string constants, and named constants. Integer constants can be specified in three ways: in decimal, in hexadecimal, or in octal. A decimal integer constant is just a series of decimal digits not beginning with 0, e.g. 12, 273, or 65. An octal integer constant is a series of octal digits beginning with 0. For instance, 0100 is the same as the decimal integer constant 64. A hexadecimal constant is a series of hexadecimal digits beginning with 0x or 0X. For instance, 0xA00 is the same as the decimal constant 2560. The alphabetical hexadecimal digits can be either uppercase or lowercase, so the previous example could have been written 0xa00. The following are all valid integer constants: 26 040 0x7b4 14 Most integer constants are entered in decimal. Once the compiler has read an integer constant, it does not remember what form it is in; the integer constants 0100 and 64 act exactly the same. Character constants are integer constants written in ASCII form. Character constants are written as single quotes surrounding an ASCII character. For instance, 'a' and 'd' specify the ASCII codes for a and d. The backslash (\) is treated specially. The following backslash codes can be used as character constants: '\n' Newline (line feed) '\t' Tab '\b' Backspace '\v' Vertical tab '\r' Carriage return '\f' Form feed '\\' Literal backslash (\) '\'' ' '\"' " The ' and " characters do not need to be escaped with a backslash in character constants, although the " character needs to be escaped inside a string constant (q.v.). A sequence like '\(' which is not defined above is not translated, and is thus illegal as a character constant because it is two separate characters. You can specify an ASCII value directly by using \ followed by up to three octal digits. For instance, '\33' codes for the ASCII escape character. String constants are a sequence of characters inside double quotes ("). For instance, "foo", "\33[H", or "Done.\n". A sequence like "\(" in a string constant is legal, and is left as a backslash followed by a left parenthesis. Named constants are integer constants with names attached to them. They are built into the compiler, and are in all capital letters. For instance, K_BPSC is a named constant that translates to 8, a number which has a special meaning to some primitives like bind(). The named constants are listed in vt.doc. Identifiers ----------- Names of variables, functions, and some other objects are all expressed by identifiers. An identifier is an alphabetical letter or underline followed by a sequence of alphabetical letters, underlines, or digits. The following are valid identifiers: abcde _foo_bar The2ndThingOnTheList The following are invalid identifiers: 2ndThingOnTheList Charles'sName My-Foot Identifiers are case-sensitive, so "Foobar" and "foobar" are different. Expressions ----------- A major part of the VTC syntax are constructs called expressions. Expressions are defined recursively, so expressions can contain other expressions. Every expression has a data type and a value. Constants are the simplest type of expression. An integer constant has a data type of "integer" and a value corresponding to the value of the integer constant. A string constant has a data type of string pointer; its value is a pointer to a copy of the string. The evaluation of an expression can involve performing an action. For instance, the expression "++a" increases the value of a by one, and returns the new value. This is called a side-effect. Side-effects are important considerations when considering whether certain control structures will evaluate certain expressions. One 'side-effect' we usually try to avoid is a run-time error, and so we try to avoid evaluating expressions which will result in them. VTC has the following data types: Integer Primitive pointer Remote pointer Window pointer Key binding pointer File pointer String pointer Array (or variable) pointer Function pointer Regexp pointer Association type Property list pointer Null (no value) The simplest type of directive to the VTC parser is an expression-statement. This consists of an expression followed by a ';'. The result of an expression-statement is to evaluate the expression and discard the value. The following parser directives have no effect because they are expression-statements whose expressions have no side-effects: 4; 10; "foobar"; Function calls and built-in variables ------------------------------------- A function call is a type of expression with the form: function-name(argument, argument, ...) The function name can be the name of a primitive function (a function predefined by VT) or of a uesr-defined function. Each argument is an expression. The following is a syntactically correct expression: foo(1, bar(3, 4, baz(), "a"), "b") A function may perform an action. This is a side-effect of the expression which calls the function. A function also returns a value, which can be used as part of another expression. In the above example, the return value of the function call to baz() is used as an argument to the function call to bar(), which has four arguments. The return value of bar() is used in turn as an argument in the function call to foo(), which has three arguments. Closely related to function calls are built-in variables. These have return values which can vary from invocation to invocation, but never take any arguments. They have the simple form: builtin-name As an example, consider the statement: split(active, 10); "split" is the name of a primitive function that splits an output window. "active" is the name of a built-in variable that returns a pointer to the active window. The effect of the statement is to split the active window at line 10. Variables --------- VTC has several types of variables: global variables, parameter variables, and local variables. Parameter variables and local variables are similar to global variables, and will be discussed in the section about functions. Variables are named constructs that contain a data value. They can be assigned a value using the assignment operator: variable-name = expression The above pattern is an expression involving the binary operator '='. The '=' operator assigns the value of the expression on the right hand side to the variable on the left hand side, and returns that value. Global variables do not need to be declared. They are created automatically as their names are used and initialized to NULL, the data value of null type. A variable's value can be used in other expressions by referring to it by its name. A variable name is an expression whose value is the contents of the variable. For example, using "foo" as a variable name: foo = "Hello, world.\n"; echo(foo); This would write the line "Hello, world." to the active window. Binary operators ---------------- Function calls are written in prefix order, so that compiler reads the name of a function and then a list of arguments to operate on. Binary operators are called in infix order. They operate on two arguments, with the operator placed between them. A commonly-used group of binary operators are the arithmetic operators: * / % + - (multiplication, division, modulus, addition and subtraction). They can be used to write familiar-looking mathematical formulae. For instance, (3 + 4) is an integer expression with the value 7, and (9 % 4) is an integer expression with the value 1. Infix notation is ambiguous; for instance, the expression (3 - 4 - 5) could be evaluated as (3 - (4 - 5)) or as ((3 - 4) - 5). Ambiguities in the binary operator notation are resolved by two properties of operators, precedence and association. Parentheses can also be used to group expressions to resolve conflicts. Operators are grouped into precedence levels. The multiplicative arithmetic operators *, / and % are all on the same level of precedence. They have higher precedence than the additive operators + and -. So the expression (3 + 4 * 5) is evaluated as (3 + 20) and not as (7 * 5). One could write "((3 + 4) * 5)" to force the compiler to evaluate the addition first. Within precedence groups, operators can associate left-to-right or right-to-left. All binary operators except for the assignment operators associate left-to-right. (3 - 4 - 5) is the same as ((3 - 4) - 5), and evaluates to -6. VTC has the following binary operators, in order of precedence, from highest to lowest: Multiplication: * / % Addition: + - Bitwise shift: << >> Relational: < <= > >= Equality: == != Bitwise and: & Bitwise xor: ^ Bitwise or: | Logical and: && Logical or: || Lookup: -> Assignment: = += -= *= /= %= &= != <<= >>= ?:= The relational and equality operators return a result of 1 if the condition they test for is true, and 0 if not. So, the expression (-1 == 3) has the value 0, while the expression (3 < 6) has the value 1. The logical and and logical or operators (&& and ||) behave specially. If the left-hand argument is sufficient to determine the value of the expression--that is, if the expression to the left of a && is false, or the expression to the left of || is true--then the right hand side is not evaluated. Errors and side-effects that would have ocurred in evaluating the right-hand expression will not occur in this case. All three operators return 1 if the logical relationship is true and 0 if the relationship is false. The assignment operators are different from other binary operators in three ways. First, they associate right-to-left. "a = b = 3;" assigns 3 to both a and b and is the same as "a = (b = 3);". Second, the argument on the left hand side of an assignment operator is not an expression but an lvalues. An lvalue is usually a variable name. "b = 3;" is a valid VTC statement, while "b + 1 = 3;" is not. Third, the assignment operators have side-effects as their primary purpose. The other operators can produce run-time errors, and the -> operator can have a side-effect relating to order of name association in property lists, but these effects incidental to the functions of the non-assignment operators. The assignment operators other than = are abbreviations. "a -= 3;" is equivalent to "a = a - (3);" and so forth. "a ?:= 3;" is equivalent to "a = a ? : (3);", described below. The comma (,) is a binary operator with lower precedence than the assignment operators. Inside an argument list, it separates arguments. Outside of an argument list, a list of expressions separated by commas evaluates to the value of the last expression, after all the other expressions are evaluated and their values discarded. For example, (3, 4, x + 2, itoa(7), 6) evaluates to 6. Unary operators --------------- VTC also has unary operators that apply to only one argument. They are appended to the left side of an expression. They have higher precedence than binary operators, and are evaluated right-to-left. They are: ++ -- increment and decrement (lvalues only) ! Negation (true --> 0, false --> 1) ~ Bitwise one's complement + Nothing (no operation) - Additive inverse (3 --> -3) * Dereference (see pointers, below) & Address (lvalues only; see pointers) The ++ and -- operators can also be appended to the right side of an lvalue. They are called the postdecrement or postincrement operators when appended to the right side of an lvalue, and the predecrement or preincrement operators when appended to the left side. If a is a variable, then "++a" increments the contents of a by one (a side-effect) and has the value of a after the increment operation. "a++" increments the contents of a by one and has the value of a before the increment operation. The same applies to decrement. So the following code produces the output "3": a = 3; echo(itoa(a++)); While this code produces the output "4": a = 3; echo(itoa(++a)); In both cases, a has the value 4 after the code is executed. The conditional operator and expression truth --------------------------------------------- An expression is false if it is NULL or the integer 0. Otherwise, it is true. The conditional operator takes three arguments in the form "a ? b : c", where , , and are expressions. If is true, then is evaluated and its value is returned. Otherwise, is evaluated and its value is returned. The conditional operator associates right-to-left and has lower priority than all of the binary operators except the assignment operators and the comma operator. The second argument to the conditional can be omitted. The expression a ? : c has the value of if is true, and the value of otherwise. The interpreter only evaluates if is false. Order of evaluation ------------------- Arguments to binary operators and the conditional operator are evaluated left-to-right. Any side-effects in the left argument occur before side-effects in the right argument. Arguments to functions are also evaluated left-to-right. Statements ---------- Statements are directives to the VTC interpreter. Like expressions, they are defined recursively, so statements can contain other statements. The expression-statement has already been described. The following are examples of useful expression-statements: a = 3; echo(itoa(a++)); Another form of statement is the compound statement. It is a list of any number of statements surrounded by { and }. { a = 3; echo(itoa(a++)); } is a valid compound statement. There are two kinds of conditional statements: if (expression) statement causes statement to be executed if expression is true. if (expression) statement1 else statement2 causes statement1 to be executed if expression is true, and statement2 to be executed otherwise. Any of these statements could be compound statements. For example: if (new = split(win, row)) { set_obj(new, new_pager(new)); set_termread(win, .std_termread); } A common pitfall with if-else statements is: if (a) if (b) c; else d; The indentation would suggest that the "else" is supposed to associate with the first if statement. It will instead associate with the second if statement. There are two ways to resolve this: if (a) { if (b) c; } else d; or: if (!(a)) d; else if (b) c; The second method uses a common construction, the "else if". This is used often enough that the indentation scheme is usually changed to account for it. According to common indentation system, according to which a line is indented by a tab for each level of nested flow-control depth, you might write the following code: if (a) b; else if (c) d; else if (e) f; else g; Usually, that code would be written: if (a) b; else if (c) d; else if (e) f; else g; This more elegantly conveys the effect of the code, which is to evaluate the conditions (, and ) until one is true, and execute either , , or accordingly. The while statement is a loop statement, and has the following format: while (expression) statement This causes the statement to be executed as long as the expression is true. If the expression is false the first time it is evaluated, the statement is never executed. The do-while loop: do statement while (expression) also executes a statement as long as an expression is true, but the statement is always executed at least once, before the expression is ever evaluated. Different types of flow control statements can be combined: while (input_waiting(rmt)) { if (line = read(rmt)) add_hist(obj(rmt)[1], line); else abort(); } The for statement is a loop statement with the format: for (expr1; expr2; expr3) statement expr1 is evaluated immediately and its value is discarded. The interpreter then executes the statement as long as expr2 is true. expr3 is evaluated after each iteration of statement. For-loops can be rewritten as while loops (this is not a perfect isomorphism, since break and continue statements will not behave quite the same): expr1; while (expr2) { statement; expr3; } The logic behind the organization of the for statement is: expr1 is the initialization expression. If you are writing a for statement to count from 1 to 10, using the variable "i" as a counter, then expr1 would be "i = 1". expr2 is the condition; for our example, we would use, "i <= 10". expr3 is the increment expression. In this case, it would be "i++". The statement is the body of the loop; if we wanted to output the value of i on a line each time it counted, we would use "echo(itoa(i), "\n");" for our statement. So, we have: for (i = 1; i <= 10; i++) echo(itoa(i), "\n"); For statements are convenient for separating the information about how long the loop will run from the action that is taken at each stage of the loop. A label can be attached to any statement: label : statement The label is an identifier. You can jump to a statement using a goto statement: goto label; Goto statements can confuse the meaning of code and should be avoided as much as possible. A goto label must be in the same function or command as the goto statement that jumps to it. The break and continue statements can be used within the statement part of while, do-while and for loops. They have the forms break; and continue; The break statement terminates the loop. The continue statement jumps to the end of the statement part of the loop and continues execution of the loop. In a for-loop, this means evaluating the iteration expression before checking the condition expression. The return statement can be either: return; or return expression; This instructs the interpreter to exit the current function, returning the value of expression if one is given. If the return statement is used inside a command (rather than a function), it will terminate the command. Functions --------- Functions are blocks of code that are associated with a name, and can be used run by calling them by name. The form of a function definition is: func name(parameters) [local vars] compound statement The word "func" is literal. The parameter names are a list of identifiers separated by commas, as are the local variable names. If a function does nothing but return an expression, there is a short form: func name(parameters) --> expr Parameter variables refer to the arguments to the function. It is a run-time error to call a function with fewer arguments than there are parameter variables in the function's definition. All arguments are passed by value, so a function can reassign to its parameter variables without affecting the values of any variables whose values were passed to the function as arguments. As an example, the following: func switch(a, b) [temp] { temp = a; a = b; b = temp; } will perform no useful task, since switch() knows only the values of the arguments. It is okay to call a function with more arguments than it has parameter variables. A function can use the argc and argv builtins (see vt.doc) to access its arguments directly. Local variables are variables whose scope (the area one can use them in) is restricted to the function. They are also distinct from any other variable in the program in each invocation of the function. A local variable "i" in one function will not conflict with a local variable "i" in some other function, and the local variables of a function that calls itself, or of two functions that call each other, will not conflict with each other. This is important for creating recursive functions, which can call themselves repeatedly. Pointers and lvalues -------------------- VTC has several types of pointers. These can be categorized into two classes. Pointers to primitives, remote connections, windows, key bindings, files, functions, regexps, association types, and property lists are used by the interpreter to refer to objects, and can only be manipulated by use of primitives (except for the -> operator in the case of association types and property lists, q.v.). For instance, the primitive split() returns a pointer to the window that it creates, and other primitives such as win_top() and close() will accept this pointer as an argument. Strings and arrays are used for storing data. A string pointer points to a character value within a string, and an array pointer points to a data element within an array. Both can both be dereferenced by the * unary operator to yield the value that they point to. A string constant such as "foobar" is an expression with a string pointer type. If we set: stringvar = "foobar"; Then stringvar's value will be a string pointer, which can be visualized like so: ------------------------- | f | o | o | b | a | r | ------------------------- ^---stringvar Now *stringvar has the value 'f'. You can access further elements of foobar using pointer arithmetic. In the above example, (stringvar + 4) is a string pointer pointing to the 'b' in "foobar". If we set: stringvar += 4; Then we can visualize foobar as: ------------------------- | f | o | o | b | a | r | ------------------------- ^---stringvar And *stringvar has the value 'b'. One can access string elements behind stringvar by subtracting from it; *(stringvar - 2) has the value 'o', since (stringvar - 2) points to the first 'o' in "foobar". A third operation on pointers is pointer subtraction. Suppose we set: stringvar2 = stringvar - 2; Now stringvar points to the 'b' and stringvar2 points to the 'o' two spaces behind it. We can find the distance between stringvar and stringvar2 by subtracting them: (stringvar2 - stringvar) has the value 2. The - operator can only be used on string pointers that point to the same string. Pointer arithmetic has the following operations: pointer + integer --> pointer pointer - integer --> pointer pointer - pointer --> integer These operations apply to string pointers and array pointers. An array pointer acts just like a string pointer, except that array elements are not restricted to being characters. Relational and equality operators (<, <=, >=, >, ==, !=) also apply to pointers that point inside the same string or array. It is a run-time error to use <, <=, >=, or > on pointers that point inside different strings or arrays. The == and != operators will always return false if their arguments are pointers from different strings or arrays, or are of different types. The [] brackets are used to do array references. The expression: a[b] is equivalent to: *((a) + (b)) That is, in the example above, stringvar[2] would have the value 'r', since it is the same as *(stringvar + 2). An lvalue is defined as an object that one can meaningfully find the address of. The two types of lvalues are variable names and dereferenced pointers. Since 'stringvar' is a variable name, it was legal to use it on the left-hand side of the '=' operator. One could also write: *stringvar = 'm' which changes the 'b' in "foobar" to 'm'. The & operator can be used to get the address of an lvalue. This is usually an array pointer, since variables are treated as elements in an array. We could set: ptrtostringvar = &stringvar; Now we could do: *ptrtostringvar = "snafu"; And stringvar would now point to the 's' in "snafu". & can also return a string pointer; for instance, &(*(stringvar + 2)) would be a string pointer pointing to the 'a' in "snafu". This is more commonly written: &stringvar[2] It could, of course, also be written as (stringvar + 2). We can write a swap() function using pointers: func swap(a, b) [t] { t = *a; *a = *b; *b = t; } Now if we wanted to switch the values of stringvar and stringvar2, we would write: swap(&stringvar1, &stringvar2); Strings ------- Strings are blocks of data containing character values. A character value is an integer data value restricted (usually) to 256 values, of which 0-127 are the most commonly used. Character constants are in this range, and are the most common thing stored in a string. A string is null-terminated; that is, it has meaningful data values only up to its first 0 value. So if we write: stringvar = "foobar"; stringvar[2] = 0; Then stringvar now points to "fo", and the rest of the contents of the string are irrecoverable. The length of a string pointed to by a string pointer is defined as the number of characters between the pointer and a 0 value, and is returned by the strlen() primitive. After the above code has been executed, strlen(stringvar) is 2, and strlen(stringvar + 1) is 1. Reading before the beginning or beyond the end of a string yields a 0 value. This means stringvar[3] and stringvar[-1] have the value 0, and both strlen(&stringvar[3]) and strlen(&stringvar[-1]) are 0. Attempting to write (assign) before the beginning of a string is a run-time error. Writing beyond the end of a string is legal, and causes the string to be lengthened, using spaces to fill in the intervening values. The statement: stringvar[10] = 'a'; causes stringvar to point to "fo a". The + operator can be used to concatenate springs. Two string pointers can be added together to return another string. For instance, one could write: a = "foo"; b = "bar"; c = a + b; and c would point to a string "foobar";. It is important to distinguish between separate copies of a string. The strdup() primitive returns a copy of a string. If we write: a = "foo"; b = a + 1; *b = 'b'; a++; then *a is 'b'. We could also write: a = "foo"; b = strdup(a + 1); *b = 'b'; a++; and *a is still 'o', since it was a copy of a that was modified, not the original. The += operator can be used with strings. If we write: stringvar += stringexpr; then stringexpr is concatenated onto the contents of stringvar. This is equivalent to: stringvar = stringvar + (stringexpr); Note that this is not quite the same as strcat(stringvar, stringexpr); because the += operator produces a new string, while strcat() acts on the old string. If we write: a = "foo"; b = a + 4; strcat(a, "bar"); then b points to the "ar" at the end of "foobar". If we write: a = "foo"; b = a + 4; a += "bar"; then *b is 0, since it's beyond the end of "foo". a no longer points within the same string as b. Property lists -------------- Property lists vaguely mirror structures in C. The idea is, you define an association type, use it to create a property list, and then you can refer to elements in the property list by name. For instance, suppose we wanted to create a data type called "pair", with a "car" and a "cdr" element. We would write: Tpair = new_assoc(); // ... some_pair = alloc(2, Tpair); some_pair->car = ...; some_pair->cdr = ...; The alloc(number, assoc) call creates a property list. A plist is composed of an array and an association type. The association type is not terribly important; it contains the size of the array ('car' in this plist might point to element 0 in the array, whereas 'name' might also point to element 0 in another plist of another association type) and defines the order of elements in the array, which is useful when we want to get at the array directly using the base() primitive. See vt.doc for details. Other data types ---------------- The other data types are generally related to specific client features, and are described in vt.doc. See the sections in that file. vt.doc also contains documentation on primitives. Operators --------- This is a list of the ways binary operators can be used in VTC, excluding the assignment operators. "ptr" refers to an array pointer or string pointer. With the exception of the + operator, operations involving two pointer types require that the pointers by of the same type, and that they point inside the same array or string. The + operator can take two string pointers that point inside different strings, but cannot operate on two array pointers. "int" refers to an integer, and "bool" refers to a boolean value. As a result, a boolean value is either 0 or 1; as an argument, it can be any data type. int * int --> int (integer multiplication) int / int --> int (integer division) int % int --> int (integer modulus) int + int --> int (integer addition) ptr + int --> ptr (pointer addition) ptr + ptr --> ptr (string concatenation) int - int --> int (integer subtraction) ptr - int --> ptr (pointer subtraction) ptr - ptr --> int (distance between pointers) int << int --> int (bitwise left shift) int >> int --> int (bitwise right shift) int < int --> bool (integer comparison) ptr < ptr --> bool (pointer comparison) (<=, >=, >, ==, and != act in the same way as <) int & int --> int (bitwise and) int ^ int --> int (bitwise xor) int | int --> int (bitwise or) bool && bool --> bool (logical and) bool ^^ bool --> bool (logical xor) bool || bool --> bool (logical or) The == and != operators can also be used to test for equality of pointers other than string and array pointers, and of course NULL == NULL. Expressions of two different data types are always unequal.