The L or Tcl for

Oscar Bonilla, Tim Daly,Jr.,Larry McVoy BitMover, Inc. 300 Orchard City Drive,Suite 132 Campbell, CA 95008

Jeffre y Hobbs ActiveState Software Inc. 1700-409 Granville Street Vancouver,BC, Canada V6C 1T2

[email protected]

ABSTRACT

This paper describes a newprogramming language called L. Lisacompiled-to-byte-code language with the unusual twist that it compiles to Tcl byte codes and by doing so leverages the entire Tcl runtime. Lisdesigned to peacefully coexist with Tcl rather than replace Tcl. Lfunc- tions may call Tcl procs and vice versa. Theymay also coexist in the same source file. Lisa static weakly typed language with int, float, string, struct, array,and hash as first-class objects. The L syntax is reminiscent of C with a tinybit of C++ thrown in. The implementation consists primarily of a simple compiler that Tcl invokeswheneverL is encountered. The L code is parsed by a Bison-generated parser into an abstract syntax tree (AST), which is type-checked and then translated into Tcl byte code. Upon its execu- tion, L code is indistinguishable from Tcl code, which makes for easy interoperability. Lisopen source software, and it is made available under the same license as Tcl/ with the hope that people will find it useful and it may encourage more people to join the Tcl/Tk com- munity. -2-

“It’slikeperl without the nastiest bits.”

-- Donal K. Fellows (on the #tcl IRC channel)

1. Introduction conventions, current status, features we have not yet BitMoversoftware is produced using a conservative done but want to do, licensing and availability,and a development methodology.All development goes summary.There is an appendix with some small through a stringent process that relies heavily on peer working program examples. reviewand extensive regression tests to ensure quality products. 2. L overview Because of the stability requirements of our market, Lisactually a very small addition to the Tcl system. we read code much more than we write it. Spot If we divide the Tcl system into logical parts this checks indicate that we spend at least 10 times as becomes obvious: much time reading and reviewing as we do writing. Subsection Percentage of Tcl/Tk 8.5 Naturally,wetend to optimize heavily for the read path rather than the write path. Tcl parser/compiler <= 1% Lparser/compiler <= 1% Foryears we have used the Tcl/Tk system for our Tcl runtime 48% graphical user interfaces. Weperiodically consider Tk 51% the alternativesand have consistently found that short of doing native implementations, the Tcl/Tk system is The parser and compiler are quite small when com- still the best choice from a development cost point of pared to the code that implements the runtime and the view. Our estimate is that it would cost roughly six libraries (in both Tcl and L it is less than 10K lines of times as much to develop and maintain native GUIs code). Because the parser/compiler is such a small instead of using a single Tcl source base for all plat- part of the system, it is reasonable to add an alterna- forms. However, the maintenance of our Tcl source tive parser/compiler to the system and let them both base has recently become problematic because two run side by side. That is L in a nutshell. It is the things happened: small amount of effort required to leverage a large •Our Tcl source base grewpast a manageable size amount of value embodied in the runtime and (for us). libraries. •Our peer reviewsystem could not handle Tcl code. The L compiler creates an abstract syntax tree from L We hav e about 25,000 lines lines of Tcl, implement- source and compiles that to byte codes. The byte ing about a dozen graphical interfaces for browsing codes generated are standard Tcl byte codes, follow- code, checking in code, viewing changes, etc.1 Main- ing Tcl call/return conventions and using Tcl vari- taining and extending the Tcl source base has become ables. Because we are careful not to break anyTcl unmanageable, and when the reviewprocess was rules, L functions may call Tcl procs and vice versa. added to the mix, the costs became too high. This allows L to use the extensive,mature Tcl/Tk run- time and libraries unmodified. This has been a problem for us for years and we were forced to come up with a better answer.Weinv esti- 3. Unique design gated the alternativesbut in the end the Tcl runtime and the Tk widgets were too compelling. We solved As we dive deeper into the L syntax and semantics it our problems by marrying a language syntax we felt would be easy to be drawn into a discussion of whyL waswell suited for fast reviewing and understanding is better or whyTcl is better.Todosowould be to with what we feel is the best GUI toolkit and runtime miss an important point. Regardless of the merits of available today. each language, the value of L is that it demonstrates a newway to leverage and reuse existing code. With a The rest of the paper is divided into sections that dis- relatively small amount of effort, we have lev eraged cuss the following topics: an overviewofL,why the L over1.4 million lines of source making up the Tcl/Tk approach is interesting, whyother runtimes were not system plus some extensions. chosen, whynot pure Tcl, whynot native GUIs, L language details such as types, calling/return The existence of L opens the door to anynumber of domain-specific languages being added to the Tcl run- 1 This number is artificially lowbecause we have time system. been holding offonanumber of GUIs until we had a better answer.Had we not been holding back, 100,000 Forexample, consider the GDB debugger.GDB lets lines is more likely where we would be. users type C, C++, etc., at it and run the code. Doing -3-

so means GDB has to provide an and a drawbacks: runtime. Rather than building one, GDB could reuse Data structures. Probably the single largest problem the ideas and code pioneered by the L effort. Having we found with Tcl was the lack of a C-style struct, awell maintained runtime with the option of creating i.e., a centralized collection of variables with annota- an arbitrary syntax to use that runtime is useful for tions indicating whytheyare there. These are com- anysort of debugger or runtime inspector.Lis just monly emulated in Tcl with associative arrays. That one example of a different syntax leveraging the isn’tgood enough because the ‘‘struct fields’’are scat- Tcl/Tk system; we are confident there will be others. tered all overthe source base rather than being in one place, laid out with types and comments. To para- 4. Alternative runtimes phrase Fred Brooks: “Show me your code and conceal Once the idea of adding a different parser/compiler to your data structures, and I shall continue to be mysti- ascripting language is understood, the question fied. Show me your data structures, and I won’tusu- becomes: whyTcl rather than some other runtime ally need your code; it’ll be obvious.”Brooks1975a such as , Python, Ruby,Java, or others? We Lint. It is impossible to write a syntax checker or a looked briefly at that question. Our need was for a lint-liketool for Tcl that works 100% of the time well supported, mature runtime that supported script- unless that tool is actually running the program it is ing GUI interfaces and was extensible from C. checking. Even an interpreter-based tool would have We dismissed Java because the runtime is too large the problem that it is not practical to force the applica- and the GUI toolkits are weak, both in features and in tion through all possible code paths. It is worth not- performance. The other runtimes addressed the GUI ing that this problem is present in all dynamic lan- issues mostly by providing Tk bindings (and in some guages and object-oriented languages have the same cases or Gtk bindings). Anysystem that is using problem; you can’tjust look at the code and know Tk bindings is already dragging along a Tcl inter- what it is doing. preter to run the Tk code. It seemed likeawaste to Reviewing. As mentioned previously,atBitMover have a different interpreter just for the GUIs. It has we do a lot of peer reviewaswell as other forms of also been our experience that the only way to build code reading. Forthe same reasons that it is difficult robust software systems is to have the minimum num- to write a lint-liketool for Tcl, it is difficult for a ber of ‘‘moving parts.’’ Having twointerpreters is an human to look at Tcl and understand what it is doing. unnecessary complication. The verbose style of operations in Tcl, e.g., But evenifthere were a good runtime with a good lset fib $i \ GUI interface, there was another requirement we felt [expr \ wasonly well addressed by Tcl. Tcl has been {[lindex $fib [expr {$i-1}]] + designed from the onset to be an extendable language. [lindex $fib [expr {$i-2}]]}] The original vision was that Tcl was glue and all the vs heavy lifting would be done by C extensions to the language. The internal Tcl code is fairly small and fib[i] = fib[i-1] + fib[i-2]; quite pleasant to use; adding extensions is straightfor- tend to obscure what is actually being said in the ward and natural. We needed to takeadvantage of code. this feature of the Tcl system and other runtimes made Optimization. Optimizing Tcl is more challenging this difficult. than optimizing a ‘‘weaker’’language such as L. Manywell understood optimization techniques could 5. L vs pureTcl be applied to the compilation of L, resulting in a sig- Manyinthe Tcl community may question whether nificant performance increase for some programs. As there is anyvalue in an alternate syntax for the Tcl an example, due to the static of L, we runtime. After all, Tcl is a powerful, dynamic lan- believe it’spossible to makeLimmune to ‘‘shimmer- guage and manysignificant applications are based on ing.’’Wiki2005a Tcl. We tend to viewTcl more likeassembly language on We agree that Tcl is powerful, but that power comes at steroids. It is a powerful tool and when that power is acost. Tcl’sdynamic nature makes it impossible to needed it is appreciated. But most of the time we are detect evensimple parse errors, such as typos, without doing fairly simplistic programming deliberately so it running the program. is easy to read, and we find that a static language with Although there are advantages to the dynamic astatic type system is much easier for us to read and approach in language design, there are also easier for a compiler to optimize and check. -4-

6. L vs native GUIs defined(). Abuilt-in that indicates if the variable This question gets raised at least once a year here: passed is defined. The following tests for the exis- whynot do native GUIs? It is certainly possible to do tence of the field in the hash, and the existence of the so. Wehav e done implementations of several of our array element, respectively. GUIs in other toolkits. The arguments for doing so defined(foo{"bar"}) are compelling: better look and feel, native behavior, defined(stuff[3]) etc. Strings. Strings are first-class objects likeany other The reasons for staying with Tcl/Tk are simple: base type. One implication of this is that unlikeC Cost. The cost of creating 2-4 different implementa- strings, which are pointers, if you want to pass a refer- tions of each GUI interface is probably 3 times what it ence to the string you must do so explicitly. took us to get where we are today.But the cost does not end there. The cost extends to testing the GUIs on 7.2. Unimplemented Cfeatures each platform as well as putting processes in place to Ldoes not have bit fields, enums, unions, or C-style makesure that the GUIs march forward in sync, i.e., if pointers. L currently does not have a C-likeprepro- the Java revtool gets a newfeature, that same feature cessor,though one is planned. needs to be added to the , Windows, and GUIs. When we add up all the costs, it looks more 7.3. Extensions to Tcl like6times the effort. Type checking. Lhas a weak static type system, Functionality. Every time we go look at the other which makes it possible to do type checking at com- toolkits we find that theyare not as powerful as the Tk pile time. Note that L’s type system is independent of toolkit. In particular,the canvasand text widgets are Tcl’sruntime type system, although the twocan inter- more useful than anyothers we have found. operate. Variables in L may not change types, unlike That said, a large drawback of the Tk approach is the Tcl variables, which are strings except when they’re lack of a complete widget set in the core. In order to not (as with floats, ints, lists, etc.) get the functionality needed, a ragtag group of exten- Structs. C-style structs are part of L. ATcl API is sions, with partially overlapping features, need to be provided that supports getting and setting fields as combined into a Tcl/Tk ‘‘distribution.’’ Welook for- well as introspection. ward to the day that this issue is resolved. References. Pass by reference in Tcl is possible but awkward. Attempts have been made to improve itin 7. L language details TclWiki2005b buttheyare unsatisfying. We think our In this section we coversome of the differences from syntax is cleaner and easier to read. C, differences from Tcl, types, call/return conven- Function prototypes. Currently these are used to get tions, expressions, and control flow. type checking when calling Tcl built-ins. For exam- ple, we can prototype gets() as 7.1. Extensions to C extern int gets(FILE, string &); Regex. Luses Perl’ssyntax for regular expressions in to always require gets to be called with twoargu- statements, but it uses Tcl’sregular expression engine. ments. Wecould also prototype gets() as So you may say: extern string gets(FILE); if (a =˜ /${}/) {... to makeitreturn a string. If prototypes are missing, L to get the same results as Tcl’s treats undefined functions as external Tcl functions if {[regexp $r $a]} {... that return poly and takeavariable number of argu- Associative arrays. We call these hashes in L to dis- ments of type poly. tinguish them from traditional C-style arrays. The keys and values are strings. Arrays grow. If you assign into an array past the last element the array grows as needed. Manyconstructs that would normally use C pointers, such as linked lists or trees, can be constructed with an array of structures linked via indices rather than pointers. -5-

7.4. Types 7.4.2. Tclish types poly. This is a generic type that is likeaTcl variable 7.4.1. Simple types on which no type checking is done. Normal variables int. Integer types in L are likeCintegers: theyare cause compile-time errors if theyattempt to change sized to the machine'sword size (at least 32 bits and types; a poly variable suppresses the static type check- possibly 64). Integers in L are initialized to 0, even ing so that a variable can switch from one type to for local variables. another,e.g. ¯oat to array or to int, etc. The following int a =5; is legalcode: int b; // defaults to 0 poly unchecked; string s; Anyconstant that looks likeanint is typed as an int. float. Floating-point numbers in L are at least double- unchecked = 1; precision IEEE 754. Floats are initialized to 0.0, even unchecked = "Hey there"; for local variables. unchecked = 3.14; // cast is required Anyconstant that looks likea¯oat is typed as a ¯oat. s=(string)unchecked; Note that this means that assigning an integer to a ¯oat is only legalbecause of automatic type conver- var. This is a compromise variable type. It is type- sion. checked but the type is not set until the ®rst assign- float f =1;//converts to 1.0 ment. The type is determined from the assignment float g; // defaults to 0.0 and may not change. The following throws an error: float pi =3.14159265; var late_binding; string. The string type is the same as a Tcl string but late_binding = 1; different from a C string. Strings are not null-termi- late_binding = "Hey there"; nated as theyare in C, nor are theyarrays of bytes. L As we noted above,constant types are intuited. This strings are Tcl strings, which are UTF-8 encoded and might cause problems with var variables. For exam- have a known length. Lstrings are initialized to the ple, this throws an error: empty string. var f =1;//fisnow an int To iterate overeach character in a string, use the f="pi"; // int/string error de®ned() operator: int i; butthis works ®ne: string s ="astring"; var f =1.0; for (i = 0; defined(s[i]); i++) { f+=3.14; printf("s[%d]=%s\n", i, s[i]); } 7.4.3. Magic Note that there is no separate character type in L. :constant. ManyTcl/Tk interfaces takekey/value When indexing into a string, each character is merely pairs that look like astring of length 1. This also means that there is no text .t -bg white -fg black need to use special single-quoted syntax for character literals: which in L might look like str[i] = "c"; text(".t", "-bg", "white", "-fg", "black"); Lprovides a special escape sequence, ${, which − allows embedding code in strings. All the text from We wanted a way to makethe whatever stand out ${ to the matching } is collected and evaluated. Its from the values being passed as an argument to − value is then substituted into the string: whatever.Wedecide to do that likethis: int i = 41; text(".t", :bg, "white", :fg, "black"); printf("41 + 1 is ${i + 1}\n"); When the parser sees an identi®er in a function call prints: that has a leading colon, L treats it as if it were a 41 + 1 is 42 quoted string with the colon replaced by a dash. -6-

7.4.4. Compound types It is possible to iterate overeach value in a hash using array. Arrays are likeCarrays in syntax but are aforeach loop: implemented as Tcl lists under the covers. Array ele- foreach (h as k => v) { ments are homogeneous; all elements must share the printf("%s => %s\n", k, v); same type. Array assignments in declarations are sup- } ported for globals and locals: struct. Structs are collections of typed variables, as in string foo[] ={"Hi", "there" }; C. Declarations are the same as C declarations. int bar[] ={1,2,3,4}; Struct assignments in declarations are supported for int i; globals and locals: int total =0; typedef struct { for (i = 0; defined(bar[i]); i++) { int a; total += bar[i]; float b; } string c; Arrays are dynamically grown and cannot be sparse. }eg; int a[2]; eg s ={1,3.14, "hi there" }; a[0] = 10; Structures are implemented as Tcl lists just likeL a[100] = 20; // allowed arrays. The names are translated into integer indices After the previous code has been executed, a has 101 by the L compiler.Since it is just a Tcl list, an L elements. a[1 ] to a[99]hav e the value 0, which is the structure can be passed to anyTcl proc that expects a default initial value for integers. list. The de®ned operator is an easy way to check if an It is likely that we will extend the struct construct to indexisoutside the array bounds: have initializers, e.g., // prints 'no' typedef struct { if (defined(a[101])) { int a =1; printf("yes\n"); float b =3.14; }else { string c ="hi there"; printf("no\n"); }eg; } eg foo; hash. Hashes are associative arrays, indexedby puts(foo.a); // prints 1 strings and returning string values. Theyare imple- mented by Tcl dictionaries under the covers. Hash 7.5. Passing semantics assignments in declarations are supported for globals ACprogrammer,looking at Tcl, would think that the and locals and followthe Perl syntax: Tcl model is pass by value. While Tcl has no way to hash h ={"key" => "val", pass a C-style pointer to an object, it does have a way "key2" => "val2" }; to fakeitwith something called upvar.Lwants pass h{"foo"} = "bar"; by value but it also wants to provide pass by refer- if (defined(h{"blech"})) { ence. This section describes howweused the Tcl sys- printf("blech is not a key!\n"); tem to provide the L passing semantics. It amounts to } alittle syntactic sugar on top of upvar. The de®ned operator can also be used to check if a 7.5.1. By value keyispresent in a hash: Lobeys Tcl'ssemantics for pass by value. Parameter // prints no passing looks likeitdoes in C: if (defined(foo{"k"})) { int i =1234; printf("yes\n"); }else { foo(i, 0xdeadbeef, "string"); printf("no\n"); Lprograms typically do not pass compound types by } value to other L functions (but see the (tcl)cast below for howtopass them to Tcl procs). -7-

7.5.2. By reference 7.5.3. L pointers The Tcl system has a way of passing by reference that While the upvar trick works nicely for manycases, might appear strange to C programmers. there is still a need for real pointers. When creating a proc foo {ref} { widget, such as an entry box, it would be natural to upvar $ref pointer have a struct that contained all the things related to that widget such as its path, the variable that the entry set pointer 1 box sets, etc., likeso: } widgets(entry &e) The upvar creates a reference to the vari- { able in the caller'scontext and places it in pointer. e.frame = frame(".f"); Assignments to pointer are the same as if the assign- e.entry = entry("${top}.entry"); ment were done in the caller'scontext (after evaluat- e.entry("configure", ing the right-hand side). :textvariable, &e.textvar); } We used this mechanism to emulate pass by reference in L. We call it ``pass by name''because the caller is Our trick of making an ampersand mean ``push the putting the name of the variable on the stack and the variable name on the stack''does not work here for callee is doing an automatic upvar to create the refer- multiple reasons. First, the variable in this case is a ence. The syntax looks like: structure ®eld, which is an element of a Tcl list. There is currently no way to pass a list element as a void foo(int &ref) −variable argument; Tcl does not support that. Sec- { ond, −variable arguments must be accessible at the ref = 1234; global . There is no guarantee that the name } passed in makes sense at the global scope. int a =19; What is needed is a way to takeanLvariable and turn it into something that Tcl can ®nd out of the event foo(&a); loop. The natural answer is some kind of pointer. puts(a); We created a newTcl object type to hold all the infor- and that prints mation related to a pointer.The information looks 1234 like: Arrays and hashes do not takethe ampersand because struct pointer { theyare trying to behave likeCarrays, i.e., theyare int depth; // upvar #depth already references. string name; // var pointed to void clear(int v[]) string index; // optional index { }; int i; The depth ®eld is used to get to the call frame where for (i = 0; defined(v[i]); i++) { the variable being pointed at was declared. ForGUI v[i] = 0; code likethe example above,the depth is almost } always 0, indicating a global. The string is the name } of the variable to which the pointer refers. If the underlying type of the variable is a list (remember that int junk[] ={1,2,3}; structs are implemented as lists) then the indexisthe clear(junk);// junk = { 0, 0, 0 } indexinto that list. The indexisastring because in the future we intend to makepointers into hashes Note that strings, unlikeinC,are ®rst-class objects work. and are not references. If you want to modify a string, you must pass it by reference. Forexample, to use the Tcl built-in for reading a line of input you have todothis: string buf; // buf is an out parameter gets(stdin, &buf); -8-

There is a newTcl command, pointer,which may be string v[] ={"hi", "good day" }; used to manipulate pointers from Tcl directly.The puts((tcl)v); following code creates a pointer,points it at the last element of the list l,uses the pointer to get the value prints of the variable pointed at, and uses the pointer to set hi {good day} the value of the variable pointed at to foo.When we (L). There may be times when a Tcl proc is returning are done, $l contains abfoo. acomplexstructure to us and we want to cast it from set l [list a b c] the Tcl list to our structure: set p [pointer create l] #lang(tcl) pointer index $p 2 proc demo {} { pointer get $p #prints c return [list {good day} sir] pointer set $p foo } Let'snow consider the widget example above, remembering that it had a variable reference #lang(L) &e. textvar.The compiler provides some magic to v=(L)demo(); treat that construct as an L pointer.When the com- printf("%s %s\n", v[0], v[1]); piler sees a string constant of the form −.*variable2 prints and the next token is an L variable with a leading good day sir ampersand, the compiler automatically wraps the vari- Note: doing this sort of thing puts you at the mercyof able in an L pointer. the Tcl code which knows nothing about the L type system. 7.5.4. Returnvalues Because returns are by value in L, and Tcl also returns 7.7. Operators by value, no changes were required to makereturns Lsupports most of the operators in the C program- work in L. ming language, as well as several of the most useful It is worth noting, especially for C programmers, that operators from Perl. In this section we do a quick run there is a sneakyway to do an allocation. When a through all of the operators in L and discuss some of is returned, the return bumps the refer- their more subtle aspects in depth. ence count. Without that bump, the local variable in Much of this section is cribbed from the C reference question would have been freed along with anyother manual.Kernighan1978a locals that were on the callee'sstack. Tcl objects are reference counted so the variable will get freed when 7.7.1. Arithmetic operators the caller is ®nished with it. string[] The binary arithmetic operators in L are +, -, *, /, and vector(int n) %(modulus). Theywork as in C with the C prece- { dence rules. string v[]; 7.7.2. True vs. false // Allocate 0..n-1 All of the relational and logical operators are part of v[n - 1] = ""; an expression and that expression evaluates to either return (v); true or false. } In L, there is only one false value. This is different string foo[] =v(100); from Tcl, which allows manyfalse values, such as the strings ``false''and ``off.'' The false value in L is 0, or,equivalently,``0''. Anyvalue other than 0 is con- 7.6. Casts sidered true. (tcl). There are times when we need to pass a com- if (0) { pound object (array,hash) as a string. AnyTcl proc printf("consequent\n"); that expects to see a string on the stack will want this. }else { The (tcl)cast is used to do this. printf("alternative\n"); } 2 Remember that : foo token is just syntactic sugar for ``− foo.'' prints: alternative -9-

7.7.3. Numeric Comparison 7.7.7. Assignment Operators These all work as in C with the C precedence rules. lvalue = expr Relational operators lvalue += expr lvalue -= expr expr > expr lvalue *= expr expr >= expr lvalue /= expr expr < expr lvalue %= expr expr <= expr lvalue <<= expr Equality operators lvalue >>= expr expr == expr lvalue &= expr expr != expr lvalue |= expr lvalue ˆ= expr Logical Operators The && and || operators short-circuit as in C. 7.7.8. Ternary Operator && expr expr expr ? expr : expr expr || expr !expr 7.8. ReservedWords 7.7.4. operators These are L's reserved words: Stolen from Perl, the ®rst form is true if regex is a break case continue defined do regular expression that matches string.The second else float for foreach if int L form is true if regex is a regular expression that does poly return string struct switch not match string.The //construct is an alias for a tcl typedef unless until var void double quoted string, which means that all or part of while the string may be an interpolated variable (or expres- sion). The m|| construct is also from perl; it means 7.9. Control flow use the vertical bars instead of slashes (frequently use- Conditional statements ful when dealing with path names). if ( expr ) statement string =˜ /regex/ if ( expr ) statement else statement string !˜ /regex/ unless ( expr ) statement string =˜ m|${expr}| In all cases expr is evaluated and if it returns anything other than zero, then the ®rst if statement is executed. 7.7.5. Increment and Decrement Operators If it returns zero, then the else statement or the unless As in C, with the value returned either before or after statement is executed. the increment or decrement. While/until statements lvalue++ while ( expr ) statement ++lvalue until ( expr ) statement lvalue-- The expr is evaluated and statement is executed --lvalue repeatedly while expr is non-zero in the while case, or zero in the until case. 7.7.6. Bitwise Operators do statements expr & expr do statement while ( expr ) expr | expr do statement until ( expr ) expr ˆ expr expr << expr statement is executed repeatedly while expr is non- expr >> expr zero in the while case, or until non-zero in the until ˜expr case. -10-

forstatement return

for ( exp1opt ; exp2opt ; exp3opt ) statement return; All expressions are optional. Other than the continue return ( expr ); statement, which in this case executes exp3, this is the In the first case the return value is undefined. In the same as second, the return value is expr. exp1; while ( exp2 ){ 7.10. Changes to Tcl statement In the course of implementing L, twosmall but impor- exp3; tant changes were made to Tcl that could affect all Tcl } programs, although we don’texpect the effects to be visible. foreach statement foreach (h as key => val) statement 7.10.1. Top-levelCompilation foreach (p in v) statement Top-levelcode in Tcl, i.e., code that isn’tcontained in The first statement iterates overeach key/value pair in aproc body,isnow passed to the byte-code compiler. the hash h.The key/value pair is placed in key and We require this so that the L compiler can emit byte val and then statement is executed. Behavior is unde- code for top-levelLcode. It could be useful in the fined if keysare inserted or deleted in h in statement. future for saving Tcl byte code between invocations, The second statement sets p to each element of v, similar to the TclPro compiler. calling statement once per element. switch statement 7.10.2. Changes to the Tcl Parser switch ( expr ) statement The #lang(tcl)string forces the language to be Tcl, expr must evaluate to an int or a string.Any state- the #lang(L)forces the language to be L. It is ment within statement may contain one or more allowed to have snippets of both L and Tcl in the labeled statements of the form same source file. case constant − expr: statement When Tcl starts up with a file argument, if the file case /constant − expr/: statement ends in . l then #lang(L)isimplicit. The default is to case : statement start up in Tcl mode. There may be at most one statement of the form: Tcl’s Tcl_ParseCommand has been modified to rec- default: statement ognize a comment with a special form. Wheneverthe parser sees #lang(L)itstops normal parsing and When the switch statement is run, expr is evaluated inserts twotokens into the token stream. The first and jumps to the case label that matches. Case labels token is a call to the LCompileCommand function and may be double-quoted string constants, integer con- the second is the text after the #lang(L)comment up stants (not floats), constant regular expressions to the next #lang(tcl)comment or end-of-file. (/.*.[ch]/ ), or constant globs (<*.[ch]>). If no label matches, then if the default label exists, a jump to the default label occurs. As in C, control continues to 8. Status flowpast labels; see the “break statement” for exiting from a switch. The L language is under active dev elopment and the speed of development is increasing. Our expectation break is that we will have a usable system in 1-2 months. break ; Our goal is to be rewriting our GUI tools in L early in causes termination of the smallest enclosing while, 2007. There is a mailing list, [email protected], until, do, for,orswitch statement. and an IRC channel, ##l on Freenode.People are continue welcome to join either. continue ; 9. Futurework causes control to pass to the loop-continuation portion of the smallest enclosing while, until, do,orfor loop. 9.1. Scoping LikeaCsource file, a scope provides a container for private and/or public variables and/or functions. This could be used to provide a self-contained ‘‘class.’’ -11-

9.2. Pre-compiled modules anymore pats on the back, but here is one more. We Imagine that each scope is a module and each module are definitely C fans. can be pre-compiled. The on-disk format is in sec- Rob Netzer,Brian Griffin, and Mark Roseman were tions: there is a byte-code section and a sort of table helpful in talking overvarious language problems and of contents which can be thought of as a header file ideas. containing function prototypes. John Ousterhout for Tcl/Tk, introduced in 1988 and still going strong. 9.3. Optimizations Kennan Rossi for being there as always with editorial The dynamic nature of Tcl means that manytradi- help. tional compiler optimization techniques cannot be used. L compiles the source to an abstract syntax tree This paper was typeset using groffand as always we and could takeadvantage of a number of well known thank Joe Ossana for troffand James Clark for groff. optimizations. These include: constant subexpression elimination, dead code removal, strength reduction, References loop invariant code motion, tail-call optimization, Brooks1975a. code hoisting, and others.Muchnick1997a Fred Brooks, The Mythical Man Month (1975). The lack of general C-likepointers in L greatly sim- Wiki2005a. plifies alias analysis and makes it possible to be more The Tcler’sWiki, “shimmering,” aggressive when applying optimizations.Hind2001a, Mar- http://wiki.tcl.tk/3033 (Dec 2005). lowe1993a Wiki2005b. The Tcler’sWiki, “use_ref,” 9.4. Debugging http://wiki.tcl.tk/15120 (Dec 2005). The static nature of L code would makeitpossible to Kernighan1978a. create a mapping between L source code and Tcl byte Brian W.Kernighan and Dennis M. Ritchie, The codes such that traditional debugging techniques CPro gramming Language, Prentice-Hall, Inc. could be used. One possible approach would be to (1978). instrument the generated byte code to invoke a debug- Muchnick1997a. ger every time an L statement completes. StevenS.Muchnick, Advanced Compiler Design and Implementation, MorganKaufmann 10. Licensing and availability (1997). The license is the Tcl license; L is part of Tcl as far as Hind2001a. we are concerned. Michael Hind, Pointer Analysis: Haven'tWe The source is maintained in a BitKeeper repository Solved This Problem Yet?" (2001). which is an import of the CVS Tcl repository.For the Marlowe1993a. 3people in the world who won’tuse BK, we will do Thomas J. Marlowe, Jong-Deok Choi, William nightly tarballs and makethem available on our FTP G. Landi, Michael G. Burke, Barbara G. Ryder, server. and Paul Carini, “Pointer-Induced Aliasing: A Clarification,” ACMSIGPLAN Notices, Volume 11. Conclusion 28, No. 9 (September 1993). This paper has described the L programming lan- guage. The Llanguage is unique in that it is an alter- nate syntax which peacefully coexists with the Tcl/Tk system and leverages all of that system. Over the course of the next year we to use L to rewrite our GUI systems. Giventhat L is a young lan- guage, we expect that it will continue to evolveaswe use it. It is likely that we will publish an updated ver- sion of this paper after the language stabilizes.

12. Acknowledgements The L language draws heavily from the C language. It’shard to imagine that Brian, Dennis and Ken want -12-

Appendix - code samples Fibonacci main() Asimple cat { int int fib[] = fib(100); main(int ac, string av[]) for (i=0; defined(fib[i]); i++) { { printf("%d\t%d\n", i, fib[i]); int i; } FILE fd; }

if (ac == 1) { int[] puts(:nonewline, read(stdin)); fib(int n) return (0); { } int fib[] ={0,1}; for (i = 1; defined(av[i]); i++) { int i; fd = open(av[i], "r"); puts(:nonewline, read(fd)); for (i=2; i= right) if (ac == 1) { return; rc = grep(regex, stdin) ? 0 : 1; swap(v, left, (left + right)/2); return (rc); last = left; }else { for (i = left+1; i<= right; i++) rc = 1; if (v[i] < v[left]) for (i = 2; i < ac; i++) { swap(v, ++last, i); fd = open(av[i], "r"); swap(v, left, last); if (grep(regex, fd))) rc = 0; qsort(v, left, last-1); close(fd); qsort(v, last+1, right); } } return (rc); } /* swap: interchange v[i] and v[j] */ void swap(int v[], int i, int j) } { int temp; int temp = v[i]; grep(string regex, FILE in) v[i] = v[j]; { v[j] = temp; string buf; } int matches =0; while (gets(in, &buf) >= 0) { if (buf =Ä /${regex}/) { printf("%s\n", buf); matches++; } } return (matches); }