IENJINIA Programming Language FAQ

  1. What is IPL?

    IPL is the IENJINIA Programming Language used in the IENJINIA Devkit to write games for the IENJINIA Virtual Console. It is a very simple interpreted language with dynamic typing, static scoping and automatic garbage collection.

  2. Why did you switch from BeanShell to IPL?

    BeanShell is a Java interpreter which can be used with a simplified syntax (untyped variables, methods and expressions outside of classes), but its semantics are not so simple and we found out that this was causing a lot of confusion for people who are just starting to learn about computer programming. IPL is a small language which is very similar to BeanShell's simplified syntax but with easier to understand semantics because it doesn't attempt to be a Java interpreter.

  3. What data types are available in IPL?

    A value in IPL can be of any of these types:

    • Number (signed 32 bit integer or double precision floating point)
    • String
    • Boolean
    • Single dimensional array (you can use an array of arrays to emulate multi dimensional arrays)
    • Environment (a set of name/value pairs)
    • Method (methods can be asigned to variables, passed as arguments to other methods, or returned as the value of a method call)
    • Null
    • Void (uninitialized variables and the value of calling a method without a return statement)

  4. How can I write an integer constant?

    You can write it as:

    • A decimal: 1, 23, -40, etc.
    • An octal: 0644, 023, etc.
    • An hexadecimal: 0xffa0, 0x7FCD, etc.
    • A character: 'A', 'z', etc.
      You can use '\'' for the single quote character, and '\\' for the backslash character.

  5. How can I write a floating point constant?

    You can write it as: 1.2, -3.456, 1.2e10, -4.5E-20, etc.

  6. How can I write a string constant?

    You can write it as: "Hello", "Goodbye", etc.
    You can use \" to include a double quote inside a string, and \\ to include a backslash.

  7. How can I write a boolean constant?

    You can write: true or false.

  8. How can I write a null constant?

    You can write: null.

  9. What is a valid name for a variable or a method?

    You can use any sequence of letters, digits and _ (underscore) that doesn't begin with a digit and isn't a reserved keyword. Case (upper or lower) is significant. You should also avoid using the names of predefined methods.

  10. Which are the reserved keywords in IPL?

    They are:

    • array
    • break
    • case
    • continue
    • default
    • do
    • else
    • false
    • final
    • for
    • if
    • lambda
    • new
    • null
    • return
    • switch
    • synchronized
    • this
    • true
    • var
    • while

  11. Which are the predefined methods in IPL?

    IPL has this predefined methods:

    • acos(n)
      Returns the arc cosine of an angle, in the range of 0.0 through pi.
    • asin(n)
      Returns the arc sine of an angle, in the range of -pi/2 through pi/2.
    • atan(n)
      Returns the arc tangent of an angle, in the range of -pi/2 through pi/2.
    • atan2(y, x)
      Converts rectangular coordinates (x, y) to polar (r, theta) by computing an arc tangent of y/x in the range of -pi to pi.
    • appendChar(str, ch)
      Returns the string str with the number ch appended at the end as a character.
    • arrayCopy(src, srcOffset, dest, destOffset, count)
      Copies count elements from the array src starting at srcOffset to the array dest starting at destOffset.
    • ceil(n)
      Returns the smallest (closest to negative infinity) number value that is not less than the argument and is equal to a mathematical integer.
    • charAt(str, pos)
      Returns the integer value of the character at position pos in the string str (positions start at 0).
    • cos(n)
      Returns the trigonometric cosine of an angle.
    • exp(n)
      Returns Euler's number e raised to the power of n.
    • floor(n)
      Returns the largest (closest to positive infinity) number value that is not greater than the argument and is equal to a mathematical integer.
    • frandom()
      Returns a random number uniformly distributed between 0.0 and 1.0.
    • isArray(expr)
      Returns true if the value of expr is an array.
    • isBoolean(expr)
      Returns true if the value of expr is a boolean.
    • isEnv(expr)
      Returns true if the value of expr is an environment.
    • isMethod(expr)
      Returns true if the value of expr is a method.
    • isNumber(expr)
      Returns true if the value of expr is a number.
    • isString(expr)
      Returns true if the value of expr is a string.
    • lenght(x)
      If x is an array it returns the size of the array. If x is a string it returns the number of characters in the string.
    • log(n)
      Returns the natural logarithm (base e) of n.
    • pow(a, b)
      Returns the value of the first argument raised to the power of the second argument.
    • print(expr)
      Prints the value of expr to the standard output.
    • random(n)
      Returns an integer random number between 0 and n - 1.
    • round(n)
      Returns the closest integer to n.
    • sin(n)
      Returns the trigonometric sine of an angle.
    • source(filename)
      Interprets, in the global environment, the contents of the file with name filename.
    • sqrt(n)
      Returns the correctly rounded positive square root of a number.
    • tan(n)
      Returns the trigonometric tangent of an angle.

    Furthermore the IENJINIA Devkit and the IENJINIA Virtual Console also have this predefined IPL methods:

    • arrayPoke(addr, src, srcOffset, count)
      Copies count bytes (the eight less significant bits of each number) from the array src starting at offset srcOffset to the IAVC memory starting at address addr.
    • clear()
      Fills the 768 bytes of visible screen RAM with the number 32 (a space) to clear the screen. Note: this method assumes that the starting offset of screen RAM hasn't been modified (locations 0x8c0 and 0x8c1 contain zeroes).
    • displayByte(c, r, b)
      Stores the byte b (its eight least significant bits) at row r, column c of screen RAM (rows and columns are numbered from zero). Note: this method assumes that the starting offset of screen RAM hasn't been modified (locations 0x8c0 and 0x8c1 contain zeroes).
    • displayMsg(c, r, msg)
      Displays the string msg (by storing the eight leat significant bits of each character in screen RAM) at row r, column c (rows and columns are numbered from zero). Note: this method assumes that the starting offset of screen RAM hasn't been modified (locations 0x8c0 and 0x8c1 contain zeroes).
    • loadModule(name)
      Loads a Java class name.class which must implement the com.ienjinia.vc.language.vm.JavaModule interface:
      public interface JavaModule {
      
          public void initModule(Interpreter interpreter);
      
      }
      
      And calls its initModule method to allow it access the current Interpreter instance (typically to make available some IPL methods implemented in Java).
    • note(nt)
      Uses the sound generator in the IAVC to play the musical note indicated by the string nt. The string must start with an upper case letter between A and G (the note) followed by a digit between 1 and 7 (the octave). A # (sharp) or a b (flat) may be used between the letter and the digit.
    • peek(addr)
      Reads a byte from address addr in the IAVC address space and returns it as an unsigned number.
    • poke(addr, b)
      Stores the eight least significant bits of the number b at address addr in the IAVC address space.
    • readButton()
      Waits in a loop until the value at address 0x8c4 in the IAVC address space contains a zero (no controller buttons pressed), then it waits until the value at address 0x8c4 is different from zero (a controller button is pressed) and returns that value as an unsigned number.
    • readFile(filename)
      Reads the contents of the file named filename into an array of numbers and returns the array. Each byte in the file is stored as an unsigned number in the array. Calling this method creates new array, the length of the array matches the number of bytes in the file.
    • setBackground(r, g, b)
      Sets the value of the first colormap register (background) in the IAVC using the 5 least significant bits from each of r (red), g (green), and b (blue).
    • setForeground(r, g, b)
      Sets the value of the second colormap register (foreground) in the IAVC using the 5 least significant bits from each of r (red), g (green), and b (blue).
    • sleep(millis)
      Waits for least millis milliseconds.

  12. What operators are available in IPL?

    The available operators are (from higher to lower priority):

    • [] (array reference), . (environment reference)
    • - (unary), ++, --, ~, !
    • *, /, %
    • +, -
    • <<, >>, >>>
    • <, >, <=, >=
    • ==, !=
    • &
    • ^
    • |
    • &&
    • ||
    • ?:
    • =, +=, -=, *=, /=, %=, <<=, >>=, >>>=, &=, |=, ^=

    Comments:

    1. The expression on the left of the [] operator must evaluate to an array. The expression between the square brackets must evaluate to a number, if it is a floating point number the fractional part is ignored.
    2. The expression on the left of the . operator must evaluate to an environment. Only a name can be used on its right (which can be followed by [] or .).
    3. The expressions on both sides of the == and != operators can be of any type.
    4. The expressions on both sides of the && and || operators, and after the ! operator must evaluate to a boolean value.
    5. The expression before a ?: operator must evaluate to a boolean value. The other two expressions can evaluate to any type, and only one of them will be evaluated.
    6. The expression on the left of the + operator can evaluate to a string, in which case the operation is a string concatenation and the expression on the right of the operator can evaluate to any type.
    7. All the other operators require their operands to be numbers. If a floating point number is used with the %, <<, >>, >>>, &, |, or ~ operators its fractional part is ignored. For all other arithmetic operators, if either of their operands is a floating point number then the operation is done on floating point values, otherwise it is done on integer values.
    8. All assigment operators associate right to left. All the other binary operators associate left to right. The evaluation of expressions using the && and || operators is terminated as soon as the final result can be determined, all remaining expressions are not evaluated.

  13. What kinds of statements are available in IPL?

    The available statements are:

    • ;
    • { ( statement )* }
    • expr ;
    • var name [ = expr ] (, name [ = expr ])* ;
    • final name = expr (, name = expr )* ;
    • if ( expr ) statement [ else statement ]
    • [ label : ] while ( expr ) statement
    • [ label : ] do statement while ( expr );
    • [ label : ] for ( [ for-init ] ; [ expr ] ; [ for-update ] ) statement
    • switch ( expr ) { ( (case expr | default ) : statement-list )* }
    • break [ label ] ;
    • continue [ label ] ;
    • return [ expr ] ;
    • name ( [ args ] ) { statement-list }
    • final name ( [ args ] ) { statement-list }
    • synchronized ( expr ) statement

    Comments:

    1. A loop variable can be local to a for statement, for example:
      for (var i = 0; i < 10; i++)
          print(i);
      
    2. The cases in a switch statement are expressions to be evaluated at runtime, so it is valid to do something like this:
      switch (x) {
          case a * 2:
              print("Double of a");
              break;
      
          case a / 2:
              print("Half of a");
              break;
      
          default:
              print("I don't know");
      }
      
    3. There can be at most one default in a switch statement, and it must always be the last option.
    4. In IPL a method definition is a statement. It binds the method code to the method name in the current environment.
    5. Although there is no way in IPL to create another execution thread, the vbi() (vertical blank interrupt) and sfi() (sound frame interrupt) methods do run in other threads, and using a synchronized statement might be necessary when updating a shared data structure.

  14. How can I create an array?

    There are two ways to create an array in IPL:

    1. new array [ expr ] ( [ expr ] )*

      Examples:

      var z = new array[10];
      x = new array[2][3];
      y = new array[size * 3];
      

      Note: new array can only be used by itself on the right of an assigment. It cannot be part of an expression.

    2. [ [ expr ( , expr )* ] ]

      Examples:

      a = [1, 2, 3, 4];
      b = [x * 2, y - 1];
      c = [10, 20, [30, 40], 50];
      d = ["one", "two", "tree"];
      e = [{name: "John", age: 23},
           {name: "Tim", age: 17},
           {name: "Jill", age: 20}
          ];
      total = sum([10.2, 4.5, 6.7, 8.9]);
      empty = [];
      

  15. How can I define a method with a variable number of arguments?

    You can't. But, you can achieve a similar result by using arrays. For example:

    sum(numbers) {
        var total = 0;
        var n = length(numbers);
        for (var i = 0; i < n; i++)
            total += numbers[i];
        return  total;
    }
    
    print(sum([1, 2, 3, 4, 5]);
    

  16. Can I define a recursive method?

    Yes, you can. Example:

    fact(n) {
        return n == 0 ? 1 : n * fact(n - 1);
    }
    
    print(fact(5));
    

  17. How does IPL find the value of a variable?

    In IPL variable values are stored in a environment. An environment is a set of name/value pairs.

    Environments can be nested. A new nested environment is created when:

    1. Entering a block statement (a list of statements between { and }).
    2. Executing a method call.
    3. Executing a for statement.

    A nested environment keeps a reference to its parent (the environment in which it was created). Environments are statically scoped, this means that the parent environment is defined by its "location" in the source code.

    IPL first looks for a variable in the current environment, if the variable isn't there IPL looks for it (recursively) in the parent environment. If there isn't a variable with that name in any of the scoped environments, IPL creates it in the current environment.

  18. How can I create a local variable?

    Just use the keywords var or final (for a constant) to create it in the current environment.

    Examples:

    var x;
    var y = 1;
    final PI = 3.1415926;
    

    You can't change the value of a variable declared as final, and you can't declare it again, not even in a nested environment.

  19. Is there any way to have dynamic scoping in IPL?

    You can simulate dynamic scoping in IPL by using this to explicitly pass the current environment to another method.

    Example:

    var x = 10;
    
    f(env) {
        print(x);
        print(env.x);
    }
    
    g() {
        var x = 1;
        f(this);
    }
    

    Calling g() will print 10 and then 1.

  20. Is there any way to do object oriented programming in IPL?

    You can encapsulate data and methods together (without any inheritance) by declaring them inside a method's environment and returning this.

    Example:

    Counter() {
        var value = 0;
    
        increment() {
            value++;
        }
    
        getValue() {
           return value;
        }
    
        return this;
    }
    
    c = Counter();
    c.increment();
    print(c.getValue());
    

  21. Is there any way in IPL to create something similar to a struct in C?

    You can use an environment. An environment can be created in any expression by enclosing a set of name/value pairs between { and }.

    Example:

    person1 = {name: "John", age: 20};
    print(person1.name);
    print(person1.age);
    person2 = {name: "Jill", age: person1 - 2};
    

    Note: an environment created in this manner does not have a parent environment.

  22. How can I declare a variable or method as private in an environment?

    There is no way to declare a variable or method as private, but you can obtain the same result by returning an environment that only contains what you want to be visible.

    Example:

    // A stack implemented as a linked list.
    Stack() {
        // Pointer to the top node in the stack.
        var top = null;
        
        // Is the stack empty?
        isEmpty() {
            return top == null;
        }
        
        // Push a value on top of the stack.
        push(x) {
            top = {value: x, next: top};
        }
        
        // Return the top of the stack (and remove it from the stack).
        pop() {
            var v = top.value;
            top = top.next; 
            return v;
        }
        
        // Return an environment that only contains references to isEmpty, push and pop.
        // This implicitly makes the variable "top" private.
        return {
            isEmpty: isEmpty,
            push: push,
            pop: pop
        };
    }
    
    s = Stack();
    for (var i = 0; i < 10; i++)
        s.push(i);
    while (!s.isEmpty())
        print(s.pop());
    

  23. Why does IPL allow a method as value in an expression?

    Because this can be used in many interesting ways. One of them is to simulate private variables and methods. Another one is to be able to write a method that receives another method as an argument. For example:

    // This isn't a good algorithm to do numeric integration, but at least it is simple.
    integrate(f, lower, upper, dx) {
        var sum = 0;
        for (var x = lower; x <= upper; x += dx)
            sum += f(x) * dx;
        return sum;
    }
    
    double(x) {
        return x * 2;
    }
    
    print(integrate(double, 0, 3, 0.001));
    

    Furthermore, with lambda you can create an anonymous method:

    print(integrate(lambda(x) { return 3 * x * x; },
                    0, 2, 0.001));
    

    On top of that you can use the fact that a lambda method keeps a reference to the environment in which it was defined (this is called a closure):

    addN(n) {
        return lambda(x) { return x + n; };
    }
    
    add1 = addN(1);
    add10 = addN(10);
    print(add1(5));
    print(add10(5));
    

  24. Are variables and methods in different namespaces?

    No. They share the same namespace (environment), if you define a method like this:

    double(x) {
        return x * 2;
    }
    

    It is equivalent to:

    var double = lambda(x) { return x * 2; };
    

    If you are concerned about accidently overwriting a method definition by using a variable with the same name (which shouldn't happen if you choose meaningful names for your variables and methods) you can declare the method as final:

    final double(x) {
        return x * 2;
    }
    
    double = 4;  // This will throw a runtime error
    

    Which is equivalent to:

    final double = lambda(x) { return x * 2; };