Overview of the Gilda Programming Language by Bradley Berg February 5, 2014 Copyright 2014, Bradley Berg; all rights are reserved. I. INTRODUCTION Gilda is a general purpose procedural programming language intended for writing production quality programs. Constructs were selected based on clarity and practicality. Features were kept to a minimum and the resulting notations are easy to learn. Notations were devised to reduce overhead to write reliable and portable programs. Special attention was given to improving software maintainability; particularly by including provisions for managing side effects. The language design evolved by incorporating results from use on a substantial code base. Alternate choices were tried in small experiments and more promising notations were tested on the larger code base. New features were not added unless they significantly reduced the size of the code base or resulted in code that was easier to work with. This overview briefly describes the language and will give you a good idea how Gilda programs are constructed. It is not intended to be a programming guide or tutorial. Instead it is a quick introduction to some of the more interesting language features. Many aspects of the language are quite conventional and are not mentioned here. II. METHOD PREAMBLE Method preambles are heart of the design of any procedural language. They define interfaces for libraries and program components. Programmers reference them routinely to communicate with each other throughout all phases of software development. Preambles contain a set of declarations while method bodies consist of executable statements. Method signatures are part of the preamble and define its external interface; how parameters are passed and its exception behavior. The remainder of the preamble declares local and global variables used in the body. A. Method Signature Parameters Parameters are declared with Entry, Exit, or Change keywords. Entry parameters declare inputs and can not be modified by the method. Outputs are declared with Exit parameters. Change parameters can be modified by the body and are input and output parameters. Gilda signatures were designed to support automatic generation of documentation, menus, and command line programs. Each parameter declaration has a name, type, an optional default value, and an optional comment. In this example the comment is proceeded by the colon and is associated with the parameter so it can be used by utilities that generate code and documentation. entry Size = 0 byte :The length of each widget. Strings are an intrinsic type rather than composed from arrays or an add on class. As a built in type, their definition and use is consistent over all Gilda source code. Since strings are so ubiquitous this means signatures are consistent and tools will know their semantics so they can support signatures with strings. Gilda includes several functions to manipulate strings as well. Intrinsic variable length strings are not only convenient, but can be optimized by the compiler. A high performance internal representation for strings is used and a dedicated memory manager was devised for fast string memory management. B. Exception Signature Exception handling is the most difficult aspect of programming. It is essential for writing reliable and robust programs. Writing code that behaves well under failing conditions is not only hard, but can result in programs that are much larger and complex. Gilda employs a novel exception mechanism instead of the conventional try-throw-catch model. The primary difference is that exceptions are not named. Instead the data state is used to raise and disambiguate exceptions. Named exceptions do not scale well and are difficult to change. They introduce unnecessary control coupling similar to the way that program branch labels do on Goto statements. Program faults are exceptions that are unexpected under correct program operation. These are typically due to internal integrity checks, corrupt memory, or hardware failures. The remaining exceptions are managed exceptions. In any particular case the programmer decides which category to use. The two types of exceptions have distinct control paths. Any method can potentially fail due to a fault. The programmer can not be aware of many faults as they arfe commonly due to programmer error. For this reason faults are not part of the signature. A method can be declared with the Safe keyword to denote that it does not raise any managed exceptions. Faults can still be raised even if a method is exception safe. A stronger restriction is denoted by the keyword Pure; which means that the method will only modify Change or Exit parameters. It is also prevented from changing global variables, but it may read them. Pure methods are also restricted from performing physical input and output operations. The Safe and Pure declarations let maintainers know that these methods have limited or no side effects to simplify program analysis by either developers or tools. When a method can raise managed exceptions it will either have no tag or can use the keyword Signal. In the first case handlers up the call chain disambiguate the exception by context. The Signal keyword indicates that additional context is saved that can be accessed only by handlers. Designated members of the class containing the method are denoted as Signal variables. Signals are used to declare more complex exceptions or exceptions that are exported from a library. Exception state is contained in data object classes rather than as named exceptions representing the control state of a method. Since Signal variables are declared in classes thay can also be inherited. Note that there is information about specific exceptions that is not specified in a method signature. In Gilda changes to exceptions impact handlers or class declarations and do not affect either the way that methods are specified or called. This avoids the ripple effect that occurs up the call chain with changes in exception behavior. C. Preconditions Method signatures also include preconditions; which are a set of conditions that must be met before a method body can be executed. They are useful for validating input parameters and global variables. When calling methods preconditions specify constraints that help determine how to set up parameters. The precondition block can also be designated as Safe or Pure. This means that as long as the preconditions are met the method will not raise managed exceptions. The only managed exceptions that can be raised are specified by the preconditions. When a precondition fails it can either raise a fault or a managed exception. Preconditions are therefore another way to raise an exception in a method. Additionally a context string can be provided that can be used by handlers. precondition pure :A pure method if preconditions are met. Name <> ""; ERROR: A name was not given. Size > 3 fault; FAULT: The size must be over 3. Id <> 0 pass Id; ERROR: The ID is unknown. . :A period ends a precondition block. The comments on conditions after the semicolon are also passed to handlers as a text string. This is very convenient for reporting simple messages and documents the code as well. In the example above the Name is first tested to see if it is set and a managed exception is raised if it fails. Then an integrity check is made on the Size variable and a program fault is raised if it is not met. Finally if the Id is not set the exception context string is set with to the signed decimal value of the Id and a managed exception is raised. Handlers can then access the context variable. D. Functions and Subroutines Methods can be invoked either as functions or subroutine calls. Functions are always implicitly pure. They can not change their input parameters or any globals and can not perform physical input or output. Since functions appear in expressions this means that expressions and conditions are free of side effects. When maintaining code constraints on side effect make it much easier to read. Furthermore, it is well known that subtle bugs can be introduced in programs when these kinds of side effects are permitted. Constructs that use expressions like the right hand side of assignments and conditional statements are subsequently side effect free. function DO_SOMETHING: Begin a function declaration. entry Name string, &Inputs are declared first. Value = "" string :Optional parameters have defaults. exit Result string :The last parameter is the output. This function could be called as: R = Do_Something( N, V ) Methods on the other hand are assumed to raise managed exceptions by default. For other exception behavior they need to be explicitly declared with Pure, Safe, or Signal keywords. method DO_BY_CALL safe: This method does not raise exceptions. : It may perform I/O or change globals. exit Result string :Parameters are declared in any order. entry Name string change Value = "" string :Change parameters are only in methods. This method could be called as: DO_BY_CALL R, N, V III. METHOD BODY The syntax for Gilda was designed to place pseudo code comments to the right of the code. In many languages this is unattractive and is hence is unpopular. Commenting code bodies in general is not always popular. If you do like to comment your code pseudo code works quite well with Gilda statements. Another consideration is that the notation was designed to make it easily formatted by programming tools. Other than quoted text the code is not sensitive to case. You can enter the code as lower case text and an editor with a built in pretty printer can change the case and indent code blocks. A. Control Statements Gilda includes typical conditional branch (If) and loop constructs along with iterative loops. Several subtle design flaws in iterative loops are avoided by carefully defining loop semantics. The programming errors that result from these flaws are in turn avoided. DO <Variable> = <Start> to <Maximum> [ by <Step> ] The chief problem in integer iterators is proper termination. Integer iterators can unexpectedly wrap if the maximum is near the maximum integer size. Floating point iterators can suffer from accumulated epsilon errors that can differ over multiple platforms. Gilda iterators are specified so that iterators behave consistently and as expected. For convenience loop constructs are include for scanning characters in a string and elements of an array vector. A sequence loop construct is also included. Sequences implement the idiom - begin; iterate; and terminate. sequence REVERSE pure: Return characters in a string in reverse. entry Text string :Input string. exit Character string :A sequence of characters. DO I = length( Text ) to 1: DO over the string in reverse, Character = byte( Text, I ); Get the next character. YIELD; Return it and resume here next call. - :Loops end with a dash. return; Terminate the sequence. This code snippet shows how the sequence can be used: R = ""; Clear the result. DO C from Reverse( Name ): DO over the Name in reverse, R != C; Append each character. - After the loop the variable R will be set to the string in reverse. A short hand sequence notation is available for data structures. Each structure can declare a default sequence. A common usage is in container classes. For example, a queue class might declare a default sequence to iterate over the queue from front to back. DO E from In_Queue: DO over elements in the queue In_Queue, PRINT E; List each element. - A rationale for the design of the loop constructs is presented in the paper,
B. Assert Statement Assertions are used to raise exceptions and work the same way as preconditions. When an assertion is not met control is passed to handlers. Handlers are coded after method bodies and are lexically distinct. Separating them out from bodies results in clearer code and avoids extensive re-factoring when modifying exception behavior. A detailed analysis of Gilda's exception mechanism is in the paper,
C. Trace Statement For convenience Trace statements are used for printing out values. Using print statements for debugging may not be state of the art, but is widely used. This simple example illustrates the usage. trace`on I, Id, Text The "on" switch enables the Trace statement. It can be removed to ignore the statement. It is often useful to leave disabled Traces in the code for future debugging. Trace statements can be globally disabled as well. Output is written to the standard error stream, but may also be directed to a log file. It is prefixed with the method name and line number of the statement. The output might look like: My_Method@42: I = 6 Id = Active Text = x y z IV. CLASSES The Gilda class structure constitutes an implicit build system. The compiler can build complete programs and libraries without the use of an external build system (e.g. Make files). Name spaces are implemented as libraries. That is each library has its own name space; which is declared in the library`s root class. Building libraries and resolving global name collisions are managed using the same notation. V. CONCLUSION Over the years thousands of programming languages have been created. There is even more diversity of opinion about what makes an appealing programming language. Regardless of how well versed you are in software engineering or language design, there is no substitute for working with a language on practical programs. This is particularly true of languages with novel constructs such as Gilda's exception handling. Hopefully this introduction will peak your curiosity enough to try it out.