Clef Programming Languages

Purpose

Clef is just a toy programming language that is designed to help demonstrate some of the principles of computation theory. It's called clef for no particular reason except that this name admits obvious variants for identifying variants of the language. Clef is not intended for general-purpose programming. Instead, it's specially designed as a tool for looking at different abstract notions of computation.

Clef has four interesting variants. Treble clef adds some features that make it easy to treat clef programs, themselves, as data. Thus, a treble clef program could take another treble clef program as input and produce a new program as output. The other three variants, alto, tenor and bass cleff can be seen as simpler programming languages with fewer features. These simpler languages help in the investigation of simpler models of computation.

Language Overview

Clef is an untyped, interpreted language written by me. As a result, it's not particularly efficient. The language was originally designed to resemble pascal as much as possible. This goal is balanced with the desire to provide a simple, uniform syntax. More recently, a more C-like syntax has been adopted. However, there remain a few Pascal-like constructs in the language.

Clef is an interpreted language. An interpreter for the language is located in the path /home/faculty/sturgill/public/bin/clef. You run a clef program by supplying it as an argument to the clef interpreter.

Standard Clef

The basic programming language is simply called clef. Although most work will be done in treble clef, clef itself is just powerful enough to seem like a real programming language. We hope to convince ourselves that any formal problem we can solve using a real programming language, can also be solved with clef. The equivalent clef program will probably not be quite as nice, but it should be able to get the job done.

One of the more interesting features of the basic language is that it is impossible to write a bad program. The language is defined by a context-free grammar and any program consistent with that grammar will run without producing errors. Of course, such a program may not actually do anything useful, but it is guaranteed to run.

Furthermore, it can be very hard to develop programs in a language that never produces errors. To help with this, clef supports a few command-line flags to specify the treatment of some things that are ordinarily considered errors. The -errors flag tells clef to halt whenever it encounters an error. The -warnings flag tells clef to just report errors and keep going. Finally, the -ignore tells clef to just ignore potential errors and keep going.

The following sections give a brief introduction to cleff. A few simple example programs are available at the end of this section.

Comments

Clef uses the one-line comment style of C++. A comment is introduced with a double slash (//) and it persists until the end of the input line.

Built-in Types

Identifiers in clef are untyped, but any values hold will fall into one of the following categories. Note that, since clef identifiers are not typed, you don't have to declare them. The only places you need to worry about declaring them is as local variables and parameters to procedures.

Expressions

Expressions generate new values from existing ones. Clef supports the following kinds of expressions:

Statements

As in C, statements control the flow of computation. Most statements must be terminated by a semicolon. Clef supports the following statement types:

Function Definitions

The syntax for user-defined functions in clef is similar to C. The greatest differences are rooted in the fact that identifiers in clef don't have types. Thus, a function definition might look something like:
square(val)
{
   return val * val;
}
Observe that the parameter of this function is declared without a type. Also, the function, itself, is not given a return type.

If a function needs local variables other than its formal parameters, these can be declared before the first opening curly brace. The syntax for local variables is more reminiscent of Pascal. They keyword var introduces a list of local variables. Names of local variables are separated with commas and the entire var declaration is terminated with a semicolon.

Built-in Functions

Clef provides a small set of built-in functions and procedures. The procedures write and writeln behave much as they would in Pascal. Both take an arbitrary number of expressions and print their values. The writeln procedure prints a newline after its arguments, while write does not. One notable difference between these procedures and their Pascal counterparts is that clef does not add any extra spaces when printing integers. Fofr example, writeln(1, 2); would produce 12, while writeln(1, " ", 2); would produce 1 2.

The function read() returns one value, which it reads from input. The type of value returned will depend on the input. Read is capable of processing integer, symbol or array input. Clef output and input formats are the same. Thus, an array printed as output of one clef program can be used as input to another.

Clef includes special functions geared toward language recognition. The functions accept() and reject() are used to accept or reject the input. If you must write a program that decides a particular language, your program should eventually call accept() on inputs that are in the language and reject() otherwise. Ordinarily, both accept() and reject() print an appropriate message and cause the program to terminate.

The enumerate procedure is just like writeln (i.e. it takes an arbitrary number of arguments). It is simply a specialized facility for enumerating a set. Why would you want this instead of just writeln? Well, in this class, well be concerned with a lot of formal details about computation. Using enumerate instead of writeln when listing the elements of a set is really just a policy. When you put enumerate in your code, you are saying that each call to enumerate prints out exactly one member of the set. The writeln function does not imply anything like this so we have a special enumerate function that carries this meaning. Although it functions jus like writeln, it is taken to mean something slightly different when we look at a program. In summary, if you are given the problem of enumerating a particular language, you should use one call of the enumerate procedure to write out each member of this language.

Clef also supports an include directive. This is much like the C include. It is used like:

#include "filename"
With the quotes included. Also the word include has to appear at the start of a line. When include is reached, the parser immediately parses the contents of filename and then returns to parsing the original file once it's done.

Clef Programs

A clef program consists of an optional list of user-defined functions followed by a compound statement. Informally, the compond statement is the body of the program. This is best illustrated with some examples.

Example Programs


//
// Accept the input string if it is a palindrome.
//

// read a value
{
  palin = read();

  length = palin[0];

  // walk through the first half and compare to last half

  point = 1;  point = 1;
  while( point <= length / 2 ) {
    if( palin[point] != palin[length - point + 1] )
      reject();

    point = point + 1;
  }

  accept();
}

//
// Determine the prime factorization of the input.
// It's not particularly efficient, but it makes a good example.
//

//
// Print the smallest factor in val and return the remainder.
//
fact(val)
var
   candidate;  // Candidate for a factor for val.
{
  candidate = 2;

  while( candidate < val  &&
         val % candidate != 0 )
     candidate = candidate + 1;

  writeln(candidate);
  return val / candidate;
}

{
   // read a value
   val = read();

   // Keep dividing out the smallest factor.

   while( val > 1 )
      val = fact(val);
}

//
// This program enumerates the set of all primes.
//

//
// Return true if val is prime.
//
isprime(val)
var
  fact,   // Potential factor for val
  pflag;  // True as long as we have not yet found an actual factor
{
   fact = 2;
   pflag = true;

   // Stop when we find a factor or once fact gets too big
   while( pflag &am;& fact * fact <= val ){
      if( val % fact == 0)
         pflag = false;
      fact = fact + 1;
   }

   return pflag;
}

{
  num = 2;

  // Check all integers 2 or greater for primeness.
  while( true ){
    if( isprime(num) )
      enumerate(num);

    num = num + 1;
  }
}


Treble Clef

Treble clef is where the real fun starts. Most of our interesting programming will be done in this language. This language represents a fairly small extensions to standard clef, but these extension make it easy to write some fairly interesting programs.

Built-in Types

The most fundamental extensions present in treble clef are in the area of supported types. While standard clef allows only three types of objects (integers, symbols and arrays), treble clef programs can operate on values that are, themselves, programs or program fragments. The scope of this extended type facility will become evident when the new built-in functions are discussed.

Built-in Functions

The identify function allows a program to determine the type of a value. Identify takes two arguments. The first is a symbol indicating a type and the second is a value. Identify returns true if the value of its second argument matches the type given in its first argument. The following table outlines the type names for the various built-in types as reported by identify. A given value may qualify as a number of different types.
Type Example Type Name
integer 5 IntegerType
symbol 'blue' SymbolType
array "Help" ArraylType
identifier a IdentifierType
array reference a[5] ArrayRefType
function call square(7) FunctionCallType
assignment a = 1 + 2 AssignmentType
if statement if( a < 5 ) ... IfStmtType
while statement while( a < 5 ) ... WhileStmtType
return statement return a; ReturnStmtType
compound statement { ... } CompoundStmtType
function definition square(a) { ... } FunctionType
program definition ... { ... } ProgramType

In treble clef, you can not only determine the types of different values by using identify, you an also take them appart. For example, a while statement is made up of a conditional part (an expression) and another statement. The function extract lets you pull out the individual components of the more complicated values (those after the first three). Extract takes two arguments. The second indicates the object from which you want to pull a component. The first indicates which component you want. The following table describes the different parts that make up each type of programatic element in cleff:
Type Part Names
identifier NamePart
array reference LvaluePart IndexPart
function call NamePart ArgPart
assignment LvaluePart SourcePart
if statement ConditionPart ThenPart ElsePart
while statement ConditionPart DoPart
return statement SourcePart
compound statement ListPart
function definition NamePart ParamPart VarPart BodyPart
program definition ListPart BodyPart

Finally, there is a new function called create that makes values other integers, symbols and arrays. Create takes a variable number of arguments, depending on the first argument. The first argument specifies the type of object you want to create. It's the same as those given in the table above. Each type of value you might want to produce will require different arguments. In fact, the things you have to put into an object when you create it are exactly the tings you can take out of it via extraction. The order of items used in the above table corresponds to the order of arguments to create. Some of this will be made more clear in the examples given in the first homework.

A note about lists

You will notice that many of the value types include a list component. This list is stored as an array. As with strings, the zero index of this array stores the length of the list and indeces 1 .. length contain the actual contents. Thus, if you wanted to make a compound statement, you would fill an array (say stmtlist ) with a collection of statements, put the number of statements at index zero and then call create(CompoundStmtType, stmtlist) .

A note if-then-else

When creating an if statement, you may or may not have an else part. If there is no else part, just pass in nil as the fourth argument to create. The same goes for extract. If there is no else part then extract(ElsePart, ...) will return nil.

A note about read

In treble clef, the read function is capable of processing whole programs as input in addition to integer, symbol and array input.

A great example

The file "/home/faculty/sturgill/public/clef/transform.C" is a great example of uning these treble clef features. It is a recursive collection of features that are capable of breaking apart any clef program fragment into its components and then putting it back together. Consequently, it uses identify, extract and create for all value types. The clef program "/home/faculty/sturgill/public/clef/nominus.p" uses the functions in transform.p to remove all uses of subtraction from a clef program.

Alto Clef



Tenor Clef



Bass Clef