RK 1.2 is a small, explicit, block-structured language designed by Reynold Kenny in 1972. It is intended for short computational programs: tables of numbers, character data, formatted reports, recursive procedures and modest interactive tools. There are four value types, three loop forms, a value-selection statement, value parameters and a compact library of built-in functions.
This document defines the language. The formal grammar is in the Formal Grammar.
1. Source Form
An RK source file has the extension .rk and is read as text.
- Blanks, tabs, carriage returns and form feeds separate tokens.
- A newline ends a simple statement. A semicolon may be used instead.
- Keywords are written in uppercase and are reserved. The lexer is
case-sensitive:
PROGRAMis a keyword, whileprogramis an ordinary identifier. Identifiers themselves may use either case. - Identifiers begin with a letter or
_and continue with letters, digits or_. At most 63 characters are significant in the current implementation. /* ... */starts a comment. Comments may span several lines and may appear between any two tokens. An unterminated comment is an error.
Example:
/* A comment may occupy
several lines. */
PROGRAM "EXAMPLE" {
INTEGER N := 1; PRINT N /* semicolon separates statements */
}
FINISH
Statements must be separated by a newline or a semicolon. A WHEN statement includes its optional OTHERWISE block; separators may appear between the closing brace of a branch and OTHERWISE, or OTHERWISE may follow the brace directly. REPEAT, WHILE, FOR and CASE are complete after their final closing brace or, for REPEAT, after the UNTIL condition. A separator is not required before the closing brace of the enclosing block.
2. Program Shape
A source file contains zero or more procedure declarations followed by exactly one program:
PROCEDURE NAME[parameters] {
statements
}
PROGRAM "NAME" {
statements
}
FINISH
The quoted program name is required and may be up to 255 characters. It names the program for readers; it does not select a file or an entry point. All procedures are known to the whole file, so a procedure may be called before its textual declaration and may call itself. Procedures may not be nested. Two procedures may not have the same name.
Constants and variables are declarations inside a block; they are not declared at file scope between procedures.
Nothing may follow FINISH except blanks, comments and statement separators.
3. Types and Values
INTEGER
A signed 32-bit integer in the range -2147483648 through 2147483647. Integer
arithmetic that leaves this range is a runtime error. Integer literals are
unsigned decimal sequences no larger than 2147483647; a leading - is
the unary minus operator, so the smallest value is normally written as an
expression such as -2147483647 - 1. Input may contain the full 32-bit
range, including -2147483648.
REAL
A double-precision floating-point number. A real literal contains a decimal point and may have an exponent:
3.5 0.25 .5 12.0 1.5e2 2.0E-3
At least one digit is required after the decimal point; digits before the
point are optional, so .5 is a valid literal while 1. is not.
The host double type is used, so precision and range follow the host; values
beyond about 1.0e308 are rejected as out of range. REAL values print
with the C %g format, which normally shows six significant digits and
omits a trailing .0. Thus 3.5 prints as 3.5 and 3.0 prints as 3.
Arithmetic mixing INTEGER and REAL produces REAL. Integer division is used only when both operands are integers or characters. A REAL variable accepts integer, character or real values. An INTEGER variable does not accept a REAL value; use ROUND, FLOOR, CEIL or TRUNC to convert one.
CHARACTER
One character, stored as a value from 0 through 255. A character literal is written in single quotes:
'A' '7' '\n' '\'' '\\'
A character participates in arithmetic and comparison as its numeric value. When an integer is assigned to a CHARACTER, the integer must be in the range 0 through 255. A REAL value cannot be assigned directly to a CHARACTER.
STRING
A fixed-capacity sequence of characters. The capacity is given in brackets at declaration:
STRING NAME[32] /* up to 32 characters */ STRING TITLE /* default capacity 255 */
String values are copied on assignment and parameter passing. A string literal may contain up to 255 characters, may not contain an unescaped newline and may use the escape sequences
\n newline \t tab \r carriage return \\ backslash \' single quote \" double quote
There is no escape for a zero byte, and RK strings do not contain an embedded zero. A string expression may not exceed 255 characters.
TRUE and FALSE
TRUE and FALSE are integer constants equal to 1 and 0.
Truth
A condition is true when an integer or character is non-zero, a real is not 0.0, or a string is not empty. Conditions produce integers 1 or 0.
4. Operators and Expressions
Expressions are built from literals, variable names, array subscripts, procedure and built-in calls, parentheses and the following operators, from highest to lowest precedence:
Level Operators Associativity Meaning 1 ( ) [ ] - grouping, subscript, call 2 - NOT right numeric and logical negation 3 * / % left multiplication, division, remainder 4 + - left addition, subtraction, concatenation 5 < > <= >= left ordered comparison 6 = != left equality and inequality 7 AND left logical conjunction 8 OR left logical disjunction
AND and OR evaluate their left operand first and stop when the result is already known. They produce 1 or 0. NOT produces 1 or 0.
Relational and equality operators also associate to the left. Chained comparisons therefore compare a 0/1 result with the next operand; write parentheses when that is not what is intended.
= is equality in expressions. Assignment is always written with SET.
There is no == operator.
Arithmetic
*, / and % normally require numeric operands
(integers, characters or reals). If either operand of *, +,
- or / is REAL, the operation is performed in real
arithmetic and produces REAL.
Division truncates toward zero when both operands are integers. The integer remainder has the sign of the dividend:
17 / 5 = 3 -17 / 5 = -3 17 % 5 = 2 -17 % 5 = -2
% requires integer or character operands; it is not defined for REAL.
Division or remainder by zero is a runtime error, as is signed integer
overflow. A REAL result that cannot be represented is also an error.
+ with at least one string operand performs concatenation. The other
operand is converted to text first: an integer becomes its decimal form, a
character becomes a one-character string and a real becomes its %g
form. The result is limited to 255 characters.
Comparison
If both operands are strings, comparison is byte-wise lexicographic comparison. If neither is a string, comparison is numeric; INTEGER, CHARACTER and REAL values may be compared with one another. Comparing a string with a number is a runtime error.
5. Declarations, Constants and Assignment
A declaration names one variable and optionally initialises it:
INTEGER COUNT INTEGER TOTAL := 0 REAL RATIO := 0.5 CHARACTER LETTER := 'A' STRING NAME[32] STRING CITY[16] := "PARIS"
The initialiser is evaluated when the declaration is executed. The declared
name must not already be declared in the same block. := is only
allowed in a declaration; it is not an assignment operator.
A constant is declared with CONSTANT followed by a type, a name, and a required initialiser:
CONSTANT INTEGER LIMIT := 10 CONSTANT REAL HALF := 0.5 CONSTANT STRING TITLE := "REPORT"
Constants are read-only. SET, ACCEPT and a FOR control variable cannot change one. Constants follow the same block scope as variables and may not be arrays. They are evaluated when their declaration is executed, so a procedure can use a constant declared earlier in the main program body but not one whose declaration has not yet run.
After declaration, SET changes the value:
SET COUNT = COUNT + 1 SET RATIO = 2.5 SET LETTER = 'B' SET NAME = "ADA"
SET requires an existing, non-constant variable and performs the same type checking as initialisation. An integer variable accepts an integer or a character. A real variable accepts an integer, character or real. A character variable accepts a character, or an integer in the range 0 through 255. A string variable accepts only a string, and the string must fit its capacity. A string is never silently converted to a number.
6. Arrays
INTEGER, REAL and CHARACTER declarations may include a fixed array size in brackets:
INTEGER VALUES[10] REAL SAMPLES[16] CHARACTER BUFFER[80]
The size must be a positive decimal literal no greater than 1024. Arrays are zero-initialised. Subscripts count from zero and must be an INTEGER or CHARACTER value within range; REAL subscripts are not accepted. Arrays cannot be initialised in a declaration, assigned as a whole, returned whole or passed to a procedure.
SET VALUES[3] = 42 SET SAMPLES[0] = 1.5 PRINT VALUES[3] PRINT SAMPLES[0]
For STRING, the bracketed number is the string capacity, not an array size. RK has no arrays of strings.
7. Statements
The simple statements are declarations, SET, PRINT, ACCEPT, BREAK, CONTINUE, RETURN and procedure or built-in calls. The compound statements are WHEN, REPEAT, WHILE, FOR and CASE. Statements may be written one per line or separated by semicolons.
PRINT PRINT EXPRESSION PRINT EXPRESSION, EXPRESSION, ...
With no expression, PRINT writes a blank line. Otherwise it evaluates each
expression and writes its textual form followed by a newline. Values in a
comma-separated list are separated by one space. An integer is written in
decimal, a character as that character, a real with %g and a string as
its characters.
ACCEPT
ACCEPT VARIABLE ACCEPT ARRAY[SUBSCRIPT]
Reads one line from standard input and stores it in the variable.
- For an INTEGER, the line must contain optional leading blanks, an optional
+or-, one or more digits and optional trailing blanks. Anything else is an error. The value must fit in 32 bits. - For a REAL, the line is parsed as a floating-point number with the host
strtodrules, including a decimal point and exponent. Leading and trailing blanks are allowed. Anything else is an error. - For a CHARACTER, the first non-blank character of the line is stored. An empty line is an error.
- For a STRING, the whole line (without its newline) is stored. A line longer than the declared capacity is an error.
- End of input is an error.
In the current implementation an input line may be at most 511 characters; a longer line is rejected rather than truncated.
WHEN
WHEN [CONDITION] {
statements
} OTHERWISE {
statements
}
The OTHERWISE part is optional. If the condition is true the first block is executed; otherwise the OTHERWISE block is executed when present. OTHERWISE belongs to the nearest preceding WHEN and may be written on the same line as the closing brace or on a later line. There is no ELSE WHEN form; nest a WHEN inside OTHERWISE instead.
REPEAT
REPEAT {
statements
} UNTIL [CONDITION]
Executes the block, then tests the condition. If the condition is false the block is executed again. The body therefore always runs at least once.
WHILE
WHILE [CONDITION] {
statements
}
Tests the condition before each execution of the block. If the condition is false initially, the body does not run.
FOR
FOR VARIABLE FROM START TO LIMIT {
statements
}
FOR VARIABLE FROM START TO LIMIT STEP STEPVALUE {
statements
}
The control variable must already be a declared, non-constant INTEGER or REAL scalar. START, LIMIT and STEP are evaluated once, before the loop begins. STEP defaults to 1. A positive step counts upward and a negative step counts downward; zero is an error. The loop includes LIMIT when it is reached. For an INTEGER variable, START, LIMIT and STEP must be integer or character values. For a REAL variable they may be real or integer.
On normal completion the control variable normally holds the first value past the limit. If that value cannot be represented, the loop stops with the control variable at the last value. If the loop exits through BREAK it holds the value at the break. Changing the control variable inside the body is allowed but discouraged; the loop tests its current value.
CASE
CASE EXPRESSION {
OF LABEL {
statements
}
OF LABEL, LABEL {
statements
}
DEFAULT {
statements
}
}
The selector expression is evaluated once. Each OF label is a literal: an
integer, real, character or string literal, TRUE or FALSE, or a numeric literal
with a leading minus sign. Variables and general expressions are not permitted
as labels. The labels are tested in order and the first matching arm runs. A
matching arm does not fall through to the next arm. The optional DEFAULT arm
runs when no label matches. If there is no match and no DEFAULT, the statement
does nothing. If two labels have the same value, the first one wins. Labelled
values are compared with the same rules as the = operator.
BREAK and CONTINUE
BREAK and CONTINUE are permitted only inside REPEAT, WHILE or FOR. BREAK leaves the nearest enclosing loop. CONTINUE abandons the rest of the current body and begins the next iteration. In REPEAT, CONTINUE goes on to test the UNTIL condition. In FOR, CONTINUE performs the control variable update before the next test. There is no GOTO.
Procedure and Built-in Calls as Statements
A procedure or built-in call may stand alone as a statement:
CLEAR[] PRINT_SUM[2, 3] TEXT[3.5]
The brackets are required even when there are no arguments. A built-in used as a statement performs its argument checking and discards the result.
RETURN
RETURN RETURN EXPRESSION
RETURN is permitted only inside a procedure. It leaves the procedure immediately, returning the value of its expression, or integer 0 when no expression is given. A procedure that reaches the end of its body without a RETURN also returns integer 0. Return values are not declared; a procedure may return an INTEGER, REAL, CHARACTER or STRING value. A procedure can call itself.
8. Procedures
PROCEDURE SUM[INTEGER A, INTEGER B] {
RETURN A + B
}
Parameters are written as a type followed by a name. There may be at most 32 parameters, and no two parameters may have the same name. Every call must supply exactly the declared number of arguments. Arguments are passed by value, including strings; a procedure cannot change a caller's variable through a parameter.
The brackets used for parameters and calls contain the same kind of comma-separated list. A procedure call uses brackets exactly where an array subscript would appear:
REAL R := SUM[12, 30] SET VALUES[I] = SUM[1, 2]
At execution time a bracketed name is resolved in this order:
- a user-defined procedure of that name;
- an array variable of that name (which requires one subscript);
- a built-in function of that name.
A scalar variable with the same name does not hide a built-in. Procedure names, array names and built-in names therefore occupy overlapping spaces; using the same name for different purposes is confusing and not recommended.
9. Scope and Lifetime
RK uses block scope.
- Each
{ ... }block has its own set of declarations. - A declaration is visible from the point where it is executed to the end of its block.
- An inner block may declare a name already used in an outer block; the inner declaration hides the outer one until the inner block ends.
- Declaring the same name twice in the same block is an error.
- Procedure parameters are local to the procedure and hide variables of the same name. The procedure body is itself a block, so a declaration there can shadow a parameter; this is allowed but discouraged.
- A procedure can see the variables and constants declared directly in the main program body. These act as program-level or global names. It cannot see names local to a caller's nested block.
- Program variables and constants exist only after their declarations have executed. A procedure called before the relevant declaration will not find the name.
- Variables and constants disappear when their block finishes.
Because declarations are executable statements, a variable declared after a statement is not available to that statement, and a declaration inside a loop is carried out afresh on each iteration.
10. Built-in Functions
Built-ins use the same bracket call syntax as procedures. Their names are not reserved: a user procedure or array of the same name takes precedence, and a scalar variable of the same name does not. All built-ins check their argument count and types and report runtime errors with line numbers.
Numeric Built-ins
ABS[x]- Absolute value. Preserves INTEGER for integer or character input and REAL for real input. ABS of the smallest integer is an error.
MIN[a, b],MAX[a, b]- Smaller or larger of two numeric values. The result is REAL if either argument is REAL, otherwise INTEGER.
SQRT[x]- Square root as a REAL value. A negative argument is an error.
POW[a, b]- a raised to the power b as a REAL value. Results that are not representable are errors.
ROUND[x]- Nearest integer, with halves rounded away from zero. Returns INTEGER.
FLOOR[x]- Largest integer not greater than x. Returns INTEGER.
CEIL[x]- Smallest integer not less than x. Returns INTEGER.
TRUNC[x]- Integer part of x, truncating toward zero. Returns INTEGER.
SGN[x]- -1, 0 or 1 according to the sign of x. Returns INTEGER.
PI[]- 3.141592653589793 as a REAL value. Takes no arguments.
String Built-ins
LEN[s]- Number of characters in string s. Returns INTEGER.
UPPER[s],LOWER[s]- Copy of s converted to uppercase or lowercase ASCII letters.
LEFT[s, n],RIGHT[s, n]- First or last n characters of s. n must not be negative. If n is larger than the string, the whole string is returned.
MID[s, start, n]- n characters of s beginning at zero-based position start. Negative positions or lengths are errors. Positions or lengths beyond the end are clamped to the end of the string.
FIND[s, part]- Zero-based position of the first occurrence of part in s, or -1 if it is not present. The empty string is found at position 0.
CHR[n]- One character as a CHARACTER with code n, which must be between 0 and 255.
CODE[c]- Numeric code of CHARACTER c. Returns INTEGER.
TEXT[x]- Text form of x as a STRING. INTEGER uses decimal, REAL uses
%gand CHARACTER uses the character itself. NUMBER[s]- Parses string s as a REAL value. The accepted form is the same as ACCEPT for a REAL variable, including leading and trailing blanks, a decimal point and an exponent. A malformed string is an error.
Built-in string results are limited to 255 characters. For example:
PRINT SQRT[81] PRINT UPPER["mixed"] PRINT MID["ABCDEF", 2, 3] PRINT "RESULT: " + ROUND[2.6]
11. Errors
Lexical, syntax and runtime errors are reported on standard error and terminate the interpreter with exit status 1:
RK: line 7: syntax error: expected ']' after WHEN condition RK: line 3: runtime error: division by zero RK: line 11: runtime error: undefined name TOTAL RK: line 4: runtime error: FOR step must not be zero
Lexical and syntax errors are detected before the program runs. Runtime errors include undefined names, duplicate declarations, assignment to a constant, type mismatches, array subscript errors, string overflow, wrong argument counts, bad input, division by zero, invalid built-in arguments and numeric overflow.
A successful run exits with status 0. A source file that cannot be opened is reported without a line number.
12. Implementation Limits
Integer range -2147483648 through 2147483647 Real range magnitude up to about 1.0e308 Real precision host double, about 15 decimal digits Identifier length 63 characters String literal 255 characters String capacity 255 characters String default capacity 255 characters Array elements 1024 Procedure parameters 32 Input line 511 characters Nesting limited by the host stack
There are no pointer values, no dynamic arrays, no records and no file I/O statements in RK 1.2.
13. Complete Example
The root file example.rk is an interactive calculator. This shorter
program demonstrates constants, REAL values, FOR, CASE and built-ins:
PROGRAM "AVERAGE" {
CONSTANT INTEGER COUNT := 5
REAL TOTAL := 0.0
REAL X
INTEGER I
FOR I FROM 1 TO COUNT {
PRINT "NUMBER:"
ACCEPT X
SET TOTAL = TOTAL + X
}
CASE SGN[TOTAL] {
OF -1 {
PRINT "NEGATIVE TOTAL"
}
OF 0 {
PRINT "ZERO TOTAL"
}
DEFAULT {
PRINT "AVERAGE: " + (TOTAL / COUNT)
}
}
}
FINISH