VTC Examples Purpose ------- This file is intended to help programmers learn how to effectively use VTC by providing examples. It is assumed that the programmer is somewhat familiar with the basics of the language, which are described in vtc.doc. Text processing --------------- This is an example of a function which processes a string. It converts sequences beginning with '^' in a string into control characters. // Translate ^x sequences in a string func ctrl(str) [r, ptr] { r = ""; // Initialize return string while (ptr = strchr(str, '^')) { strcat(r, str, ptr - str); // Copy up to ^ if (*++ptr == '^') // Compress ^^ to ^ strcat(r, "^"); else if (*ptr == '?') // ^? is a DEL strcat(r, "\177"); else if (*ptr) // Regular control char r[strlen(r)] = ucase(*ptr) - 'A' + 1; str = ptr + 1; // Move past ^ combo } r += str; // Copy last part of str return r; } This function illustrates a general optimization principle: avoid traversing a string when possible. VT can traverse the string with a primitive like strchr() much more quickly than VTC can with a loop. The time difference can be important in a frequently-used function. Text processing functions can take advantage of VTC's memory management in a number of ways. First, you don't ever need to worry about allocating memory for a string, or reading or writing out of the bounds of allocated memory. The statement 'r[strlen(r)] = ...;' causes VT to extend the string as necessary to add the extra character, and to add its own null terminator at the end. You can also count on the elements beyond the end of a string and before the beginning to be 0. The only thing to watch for is writing before the beginning of a string, which will produce an error. Another convenience is the ability to call ctrl(), use the value, and forget about it. VT will automatically clean it up. For instance, bind(ctrl(s), funcptr); will not leak memory. Sometimes it is necessary to traverse a string, as in the following function, which converts control characters in a string to sequences beginning with '^'. func dispstr(str) [r] { for (r = ""; *str; str++) { // Traverse str if (c > 31 && c != 127) // Ordinary char, just copy *r++ = *str; else { // Write as control char *r++ = '^'; *r++ = (*str == 127) ? '?' : *str + 'A' - 1; } } return base(r); } This could be done more efficiently using strcspn(), but the code would be highly unreadable. Also, since this routine is likely only to be used in I/O routines, speed is probably not important. Note the use of the base() primitive, which allows us to forget about the beginning of the string pointed to by r until the end of the function. Finally, here is an application of VTC string management to formatting: // Return a string of a given length func field(str, len) [r] { strcpy(r = "", str); // Copy string r[len] = '\0'; // Lengthen or shorten return r; } If str is longer than len, the second statement will shorten it, as in C. If str is shorter, the second statement will add spaces up until len. Variable-argument functions --------------------------- Often it is natural to want to design a function that works on a variable number of arguments. For instance, the following function simplifies the task of constructing an array: // Return a table containing the arguments as elements func table() [t] { t = alloc(argc); acopy(t, argv, argc); return t; } Because VT places the arguments to a function call in an array, we can use the acopy() primitive to copy this array into a newly-allocated table. This is much preferrable to assigning the elements one-by-one. Optional arguments are also useful in improving the flexibility of a function interface. Consider a function getline(), which reads a line from a remote and aborts the current function if the remote disconnects before the line could be read. If we do not specify an argument for the remote connection, it should default to the current remote. func getline(/ rmt) [line] { rmt ?:= cur_rmt; // Default to current remote line = read(rmt); if (line) // Line successfully read return line; abort(); // Remote connection closed } The argument after the '/' in the argument list is optional. If we call getline() with no arguments, rmt defaults to NULL. We can initialize it using the '?:=' operator, which changes a variable's value only if it contains a false value (NULL or 0). You can also specify multiple optional arguments for functions, as well as mandatory and optional arguments. The general syntax is: func name(arg1, arg2, ... / optarg1, optarg2, ...) Remote I/O and text formatting: two-column WHO list --------------------------------------------------- The 'extras' directory in the VT archive contains code to get command responses from remote connections that are based on MUDs. This example obtains a WHO list from a mud and formats it into two columns. two_col_who() makes use of regexps to to its formatting. This is often a useful technique for pulling pieces out of a line that has a complicated syntax. Parts of a line matching parenthesized expressions in a regexp can be pulled out with regmatch(). two_col_who() also makes use of the list-handling primitives in the distribution's util.vtc. A 'list' in this sense is a VTC array such that if l is a list, *l is the length of the list and l[1..*l] are the elements in l. add_list(list, elem) adds an element to the end of a list. add_list(list, elem, pos) inserts an element at in the list. del_list(list, pos) deletes the element at . First, if you have not already done so: Load("rmtio"); Then: wpat = regcomp("^([^ ]+) +(([0-9]+d)? [^ ]+ +[^ ]+)"); func two_col_who(/ rmt, win) [who, i, width, name, conn, mid, p] { // Get WHO list who = get_cmd_response("WHO", rmt); del_list(who, 1); // Strip off header del_list(who, *who); // Strip off tail // sort(who); would go here (see below) // Reformat lines width = cols / 2; for (i = 1; i <= *who; i++) { if (!regexec(wpat, who[i])) return output("Error: invalid who list entry\n"); name = regmatch(wpat, 1); // Name field conn = regmatch(wpat, 2); // Connect+idle time who[i] = field(name, width - 15 - strlen(conn)) + conn; } // Now form second column from latter half of list mid = (*who + 1) / 2; // Number of rows in two-column list for (i = 1; i <= mid; i++) { // The strcpy() will add spaces as needed. if (who[mid + i]) strcpy(who[i] + width, who[mid + i]); } // Display the list for (i = 1; i <= mid; i++) output(who[i] + "\n", S_NORM, win); } Changing the format of the two-column list is fairly easy; simply modify the regexp and/or the first for loop to produce lines of the desired format. If we wish to sort the list by names, we can do so with a simple insertion sort: func sort(l) [i, j] { // Insertion sort using stricmp() for (i = 1; i <= *l; i++) { for (j = 1; j < i && stricmp(l[j], l[i]) < 0; j++); if (i != j) { add_list(l, l[i], j); del_list(l, i + 1); } } } and then add 'sort(who);' in the place marked in the function above. Scheme interpreter ------------------ The file scheme.vtc is a documented example of a large-scale VTC application, a Scheme interpreter. It illustrates a number of VTC features not presented in this file.