Klein is a small, mostly functional programming language that is designed specifically to be used as a manageable source language in a course on compiler design and implementation. Though small and simple, the language is Turing-complete. Klein was motivated by Doug Baldwin's old teaching language MinimL.
For more on the name Klein and its logo, see the endnotes.
Here are a complete grammar for Klein and a list of syntax features not included in the grammar.
Following the grammar and list of syntax features are informal and possibly incomplete textual descriptions of many of the language's features. The purpose of these sections is to clarify the syntax of the language. They are not sufficient on their own; they complement the formal definition. If you have any questions, please ask sooner rather than later.
Here is the grammar for Klein. In this grammar, ε, the lowercase Greek letter epsilon, stands for the empty string. It indicates that nothing is a legal alternative.
<PROGRAM> ::= <DEFINITIONS> <DEFINITIONS> ::= ε | <DEF> <DEFINITIONS> <DEF> ::= function <IDENTIFIER> ( <FORMALS> ) : <TYPE> <BODY> <FORMALS> ::= ε | <NONEMPTYFORMALS> <NONEMPTYFORMALS> ::= <FORMAL> | <FORMAL> , <NONEMPTYFORMALS> <FORMAL> ::= <IDENTIFIER> : <TYPE> <BODY> ::= <PRINT-STATEMENT> <BODY> | <EXPR> <TYPE> ::= integer | boolean <EXPR> ::= <EXPR> < <SIMPLE-EXPR> | <EXPR> = <SIMPLE-EXPR> | <SIMPLE-EXPR> <SIMPLE-EXPR> ::= <SIMPLE-EXPR> or <TERM> | <SIMPLE-EXPR> + <TERM> | <SIMPLE-EXPR> - <TERM> | <TERM> <TERM> ::= <TERM> and <FACTOR> | <TERM> * <FACTOR> | <TERM> / <FACTOR> | <FACTOR> <FACTOR> ::= if <EXPR> then <EXPR> else <EXPR> | not <FACTOR> | <IDENTIFIER> ( <ACTUALS> ) | <IDENTIFIER> | <LITERAL> | - <FACTOR> | ( <EXPR> ) <ACTUALS> ::= ε | <NONEMPTYACTUALS> <NONEMPTYACTUALS> ::= <EXPR> | <EXPR> , <NONEMPTYACTUALS> <LITERAL> ::= <NUMBER> | <BOOLEAN> <PRINT-STATEMENT> ::= print ( <EXPR> )
Note: The whitespace in the grammar is intended to aid readability. It is not significant.
These are the reserved words of Klein:
integer boolean true false if then else not and or function printprint is a primitive identifier. true and false are boolean literals. The rest are keywords.
Klein reserved words may not be used as user-defined names of functions or formal parameters.
Klein reserved words and identifiers are case-sensitive. Upper- and lower-case letters are not considered equivalent.
Identifiers must be no longer than 256 characters.
These are the primitive operators and punctuation marks of Klein:
+ - * / < = ( ) , : (* *)Klein operators and punctuation are self-delimiting.
Integer literals must be in the range from 0 to 231-1, inclusive.
A comment in Klein begins with the characters (* and continues up to the next occurrence of the characters *). Any characters inside a comment are ignored.
A function may have zero or more formal parameters. The scope of a formal parameter is the body of the function. Arguments are passed by value.
Binary operators and function calls evaluate their arguments from left to right.
Whitespace consists only of blanks, tabs, and the end-of-line characters \n and \r. It serves to separate tokens. Whitespace characters may not appear inside a literal, identifier, keyword, or operator. Otherwise, whitespace is insignificant.
All data in Klein are integers or booleans, and nearly every element in a program is an expression that produces an integer result or a boolean result.
There are only two boolean values. The two primitive boolean literals are true and false.
Integer literals are strings of digits. There are no leading plus or minus signs to indicate positive or negative values; all integer literals are positive. Leading zeros are not permitted for non-zero integer literals.
Klein supports integer values in the range -231 to 231-1.
User-defined identifiers are strings beginning with a letter and consisting of letters, digits, and the underscore ( _ ).
The language provides the following kinds of expression.
Adds, subtracts, multiplies, or divides two integers.
x + y x - y x * y x / y
Compares two integers, yielding one of the boolean values true or false. < yields true if its left operand is less than its right operand, and false otherwise. = yields true if its left operand has the same value as its right operand, and false otherwise.
x < y x = y
Negates a single boolean value, or computes the disjunction or conjunction of two boolean values. The unary not yields true if its operand is false, and false otherwise. or yields true if either its left operand or its right operand yields true, and false otherwise. and yields true if both its left operand and its right operand yield true, and false otherwise.
not x x or y x and y
or and and short-circuit evaluation when possible.
Evaluates a test expression, and uses its value to select one of two other expressions to evaluate. Yields the value of the first of these expressions if the test expression produces a true value, and the value of the second if the test expression yields a false value. The else clause is required.
if flag < 0 then x + y else x - y
produces the sum of x and y if flag is less than 0; otherwise, it produces their difference.
Applies a function to zero or more arguments, and yields the value of the expression in the body of the function. All functions return either an integer value or a boolean value; Klein has no notion of a "void" function.
f( x+y, 1 )computes the sum of x and y, passes that value and a 1 to the function f, and produces the value returned by applying the function to its arguments.
Compound expressions can be nested to any depth.
Note that the only user-defined identifiers in Klein are the names of functions and the names of formal parameters to functions. There are no "variables".
Each function declaration consists of the function's name, its formal parameters and their types, the type of the function, and its body.
Function names are unique.
A function may refer only to its formal parameters and to other functions.
A Klein function may call itself.
For the purposes of user interaction, Klein provides the primitive function print(expression). For example:
print( x+y )
print writes its argument on standard output, followed by a new line character.
Unlike all user-defined functions, the value of a call to print is undefined. For this reason, if a function contains a call to print, that call must come at the top of the function body.
A Klein program consists of a sequence of function definitions. Every program must define a function named main, which is called first when the program runs.
Users may provide arguments to main on the command line. The result returned by main is printed on standard output.
For example, here is a complete Klein program that computes the absolute value of its argument:
function main( n : integer ) : integer if n < 0 then -n else n
If this program were compiled into an executable file named abs, then running it under Unix might look something like this:
mac os x > abs -3 3
The Language's Name
I chose the name to indicate the size of the language. klein is the German word for "small" or "little". It is one of the first German words I learned back in the eighth grade.
There are a number of projects in the software world named "Klein". In particular, this language is not affiliated in anyway with the now-defunct Klein project at Sun Microsystems, which was "a Self virtual machine written fully in Self". Self is a very cool prototype-based object-oriented programming language -- check it out.
The Language's Logo
The Klein logo is a picture of a bottle whose neck feeds in on itself. This is an example of a Klein bottle, "a non-orientable surface ... in which notions of left and right cannot be consistently defined". A Möbius strip is a non-orientable surface with boundary; a Klein bottle is a non-orientable surface without a boundary.
The bottle is not called "Klein" because it is small. It shares its name with German mathematician Felix Klein, who first described this mathematical object in 1882. I chose the logo because it happens to have an attractive visual representation.