Dark mode

RK 1.2

Language Reference


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.

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
  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.

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:

  1. a user-defined procedure of that name;
  2. an array variable of that name (which requires one subscript);
  3. 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.

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 %g and 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