CLooP

From Esolang
Jump to navigation Jump to search

CLooP is a C-like language, inspired by Douglas Hofstadter's languages BLooP, FLooP and GLooP (used in his book Gödel, Escher, Bach). It was created in December 2008 by User:Alksentrs.

CLooP has three dialects, each with a different computational power, chosen by a pragma at the start of each program.

Lexical syntax

Very similar to Java or C.

Keywords:

break      goto
const      if
continue   infinity
else       int
for        pforeach
foreach    return
forever    with

Predeclared identifiers (note that * is not valid syntax):

true   false   io.*   conv.*

The sets of valid and reserved identifiers is the same as in C, but . is allowed in names as well.

The top level

At the beginning of the file, there is a dialect pragma. This is followed by a list of global variable declarations and function declarations.

Dialect pragma

One of:

  • #bound n;    ("BLooP mode"; n is a positive integer)
  • #no bound;   ("FLooP mode")
  • #hyper;      ("GLooP mode")

These specify the computational power of the language (see below).

Global variables

Like in C/Java, you write the type of the new variable, followed by its name, then an optional initializer, then ;.

The valid types are:

  • unbounded integer (int)
  • immutable unbounded integer (const int)
  • array of n elements of type T (T[n])

Because global variables and arrays are created and initialized at program startup, their initializers and array sizes (n in the above) must be constant expressions, and can use any expression whose value is known at compile-time.

Note: An array variable is a reference to some dynamically-allocated memory (the way Java does it). An array variable can refer to different arrays, which can be different sizes. The size of the array is stored with its data (in position −1, but you should use one of the # operators to access it). An array can be "infinite" in length; use infinity for its size. An implementation may manage this by extending (and maybe moving) the array as needed.

The comma can be used to combine several declarations into one, like in C.

Examples of global variable declarations:

int a;
int b = 5;
const int c = 4;
int[4] d;
int[c] e = { 2, 4, c };
const int f[8] = { 0, 1, 2, 3, 4, 5, 6, 7 };    // you can put the [] after f
int[] g = { 3, -1 };         // size can be inferred
int h = 0xf;                 // hexadecimal
int i = 'A';                 // 'A' is a const int equal to 65
int j[] = "Hello, world\n";  // i.e. int[13] j = {72,101,108,108,111,44,32,119,111,114,108,100,10};
int k, l[3];                 // integer k, array of integers l
int[infinity] m;             // infinite array of integers
int[][5] n;                  // array of 5 arrays
int[][] p = { "-o", "myout", "-v" };     // array of arrays
int[] const [] q;            // mutable array of constant arrays of mutable integers

Functions

Again, like in C/Java:

return_type name_of_function( ) compound_statement
return_type name_of_function(arg1) compound_statement
return_type name_of_function(arg1, arg2) compound_statement
etc

Arguments are passed by value. (Arrays are effectively passed by reference, and can be modified by the function, if the element type isn't const.)

Examples:

void f1(int a1, int a2) { } // no-op function; 2 integer arguments

int f2() { return 3; }      // function that returns 3; no arguments

// return the n-th number in the Fibonacci sequence
int fib(int n) {
   int a = 0;
   int b = 1;
   if (n < 0) return -1;
   else for (n) {
      int c = a+b;
      a = b;
      b = c;
   }
   return a;
}
int sum(const int[] arr) {
  int s = 0;
  foreach (int i = 1 .. #arr)
     s += arr[i-1];
  return s;
}

Compound statements

A compound statement consists of a {, a list of statements and (local) declarations, and then a }.

A local declaration looks exactly the same as a global declaration, except it is lexically scoped to the block. A local variable has automatic storage [it is allocated/deallocated when program flow enters/leaves its scope].

The initializer of a local variable can be any expression (not necessarily const).

Statements

A statement looks like one of the following:

  • compound_statement

Run the statements in the compound statement.

  • ;

The empty statement: do nothing.

  • label: statement

Provide a label to a statement. These are visible throughout a function body. If a loop is labelled, you can break/continue it from within nested loops.

  • expression ;

Evaluate the expression, and ignore the result.

  • return;
  • return expression;

Exit a function, optionally providing a return value.

  • break;
  • break label;
  • break keyword;

Prematurely end a loop. No argument: innermost loop; label argument: loop with that label; keyword argument: innermost loop using that keyword.

  • continue;
  • continue label;
  • continue keyword;

Prematurely end a single iteration of a loop.

  • goto label;

Jump to a label. You can only jump forwards, not backwards.

  • for (expression) statement

Repeat the statement expression times. Negative values are not allowed.

  • forever statement

Short for for (infinity).

  • foreach (variable: expression .. expression) statement
  • foreach (type variable: expression .. expression) statement

Repeat the statement for each value in the given range, assigning the variable the value each time. The second expression can be infinity, in which case the loop goes on forever, unless it is broken out of. If a type is given, the variable is declared, and is scoped to the loop body. In all cases, the variable is const inside the loop body.

  • pforeach (variable: expression .. expression) statement
  • pforeach (type variable: expression .. expression) statement

Same as above, but the statements can be executed in parallel (and so cannot depend on each other). continue will end the current thread; breaking from and returning from within a pforeach are undefined actions. infinity is only allowed sometimes (see below).

  • if (expression) statement

If the expression is non-zero, do the statement. Otherwise do nothing.

  • if (expression) statement else statement

If the expression is non-zero, do the first statement. Otherwise do the second.

  • with identifier statement

Inside the statement, every variable starting with . has the identifier's name prefixed to it.

Example

a: for (2) {
   b: forever {
      c: foreach (int i = 0..1) {
         d: for (i) {
            break;           // break from 'd: for (i)' loop
            break d;         //  "
            break for;       //  "
            
            break c;         // break from 'c: foreach' loop
            break foreach;   //  "
            
            break b;         // break from 'b: forever' loop
            break forever;   //  "
            
            break a;         // break from 'a: for (2)' loop
         }
      }
   }
}

goto a;  // ** not allowed (backwards jump) **
goto e:  // OK

if (true)
   e: ;

Expressions

An expression can be one of:

  • a numeric literal, in decimal
  • a numeric literal, in hex (start it with 0x)
  • a character literal, in single quotes (equal to the Unicode codepoint of the character)
  • an array literal, a ,-separated list of expressions, in braces
  • a string literal, a C string (which is an array of the codepoints of the characters)
  • infinity
  • true
  • false
  • the name of a variable
  • an operator applied to some expression(s).

Most of the operators from C are available, along with a few extra ones (#, **, ^^, &&=, ^^= and ||=):

  • increment and decrement (postfix and prefix ++ and --)
  • number of elements in an array (prefix #)
  • function call* (postfix ( ))
  • array subscripting, from zero (postfix [ ])
  • index of the last element of an array (postfix #)
  • unary plus and minus (prefix + and -)
  • bitwise and logical NOT (prefix ! and ~)
  • raise to a power (infix **)
  • multiply, divide and modulo (infix *, / and %)
  • add and subtract (infix + and -)
  • bitwise shift (infix << and >>)
  • relations, returning 0/1 (infix <, >, <=, >=, == and !=)
  • bitwise boolean (infix &, ^ and |)
  • logical boolean (infix &&, ^^ and ||)
  • ternary (? :)
  • assignment (infix =, +=, -=, *=, /=, %=, <<=, >>=, &=, ^=, |=, &&=, ^^= and ||=)
  • comma operator (infix ,)

* A function cannot call itself (directly or indirectly). The graph of function calls must be a finite tree, rooted at main.

True, false and infinity

The following is implicitly present in any program:

const int false = 0;
const int true = 1;

infinity is not an integer, but can be used in some places where integers are allowed. In array bounds, it means an auto-extending array of arbitrary length (applying # to such an array will give some unspecified value). In loop bounds, its meaning varies with the dialect pragma.

I/O, etc.

There are some pre-declared functions for I/O, etc:

with conv
{
      int .intFrom.str(const int[] str, int radix) {
         // Convert string to int
      }
      
      int .intFrom.decimal(const int[] str) {
         return .intFrom.str(str, 10);
      }
      
      int[] .strFrom.int(int val, int radix) {
         // Convert int to string
      }
      
      int[] .decimalFrom.int(int val) {
         return .strFrom.int(val, 10);
      }
}

with io.put
{
      void .char(int ch) {
         // implementation-specific, e.g.:
         __internal.stdout.putChar(ch);
      }
      
      void .nl() {
         .char('\n');
      }
      
      void .str(const int[] str) {
         foreach (int i = 0..str#)
            .char(str[i]);
      }
      
      void .strnl(const int[] str) {
         .str(str);
         .nl();
      }
      
      void .int(int num, int radix) {
         .str(conv.strFrom.int(num, radix));
      
      void .decimal(int num) {
         .str(conv.decimalFrom.int(num));
      }
}

const int io.eof = -1;

with io.get
{
      // returns io.eof to mean EOF
      int .char() {
         // implementation-specific, e.g.:
         return __internal.stdin.getChar();
      }
      
      int[] .str(int numChars) {
         int[numChars] str;
         foreach (int i = 0..str#) {
            int ch = .char();
            str[i] = ch;
            if (ch == io.eof) {
               str# = i;      // truncate array
               break;
            }
         }
         return str;
      }
      
      int[] .strnl(int numChars) {
         int[numChars] str;
         foreach (int i = 0..str#) {
            int ch = .char();
            str[i] = ch;
            if (ch == io.eof || ch == '\n') {
               str# = i;     // truncate array
               break;
            }
         }
         return str;
      }
      
      int .decimal(int numChars, int radix) {
         return conv.intFrom.str(.str(numChars), radix);
      }
      
      int .decimal(int numChars) {
         return conv.intFrom.decimal(.str(numChars));
      }
}

Entry point

int main(int[][] args)
{
   // etc.
}

Computational class

The computational power of this language varies with the dialect pragma.

#bound n;

In loop bounds, infinity means n, where n was the bound given in the dialect pragma. (In array bounds, it still means infinity.)

Every loop can take at most a finite number of iterations. Therefore, this dialect is not Turing-complete, and can only compute primitive recursive functions. However, if the bound is set to a high enough value (say 10109, well into the Heat Death of our universe, if each iteration takes at least a billionth of a second), then any for (infinity) loop is practically infinite.

In fact, every halting program halts in some finite time, so there is a bound within which it will halt.

It is recommended that an implementation be able to accept insanely large loop bounds without crashing.

#no bound;

In loop bounds, infinity actually means infinity, so a forever loop actually loops forever. This dialect is Turing-complete. The pforeach loop does not allow infinity and so can only do a finite amount of work.

#hyper;

Same as #no bound;, but the pforeach loop does allow infinity and so can do an infinite amount of work in a finite time. This dialect is believed to be unimplementable on any normal computer.

Examples

Hello, world

#bound 1;

int main(int[][] args) {
   io.put.strnl("Hello, world");
   return 0;
}

Cat

#no bound;

int main(int[][] args) {
   forever {
      int ch = io.get.char();
      if (ch == io.eof) break;
      io.put.char(ch);
   }
}

External resources