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.
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.
What data types are available in IPL?
A value in IPL can be of any of these types:
return statement)How can I write an integer constant?
You can write it as:
1, 23, -40, etc.0644, 023, etc.0xffa0, 0x7FCD, etc.'A', 'z', etc.'\'' for the single quote character, and '\\' for the backslash character.How can I write a floating point constant?
You can write it as: 1.2, -3.456, 1.2e10, -4.5E-20, etc.
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.
How can I write a boolean constant?
You can write: true or false.
How can I write a null constant?
You can write: null.
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.
Which are the reserved keywords in IPL?
They are:
arraybreakcasecontinuedefaultdoelsefalsefinalforiflambdanewnullreturnswitchsynchronizedthistruevarwhileWhich are the predefined methods in IPL?
IPL has this predefined methods:
acos(n)asin(n)atan(n)atan2(y, x)appendChar(str, ch)str with the number ch appended at the end as a character.
arrayCopy(src, srcOffset, dest, destOffset, count)count elements from the array src starting at srcOffset to the array dest starting at destOffset.
ceil(n)charAt(str, pos)pos in the string str (positions start at 0).
cos(n)exp(n)n.
floor(n)frandom()0.0 and 1.0.
isArray(expr)true if the value of expr is an array.
isBoolean(expr)true if the value of expr is a boolean.
isEnv(expr)true if the value of expr is an environment.
isMethod(expr)true if the value of expr is a method.
isNumber(expr)true if the value of expr is a number.
isString(expr)true if the value of expr is a string.
lenght(x)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)n.
pow(a, b)print(expr)expr to the standard output.
random(n)0 and n - 1.
round(n)n.
sin(n)source(filename)filename.
sqrt(n)tan(n)Furthermore the IENJINIA Devkit and the IENJINIA Virtual Console also have this predefined IPL methods:
arrayPoke(addr, src, srcOffset, count)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()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)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)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)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)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)addr in the IAVC address space and returns it as an unsigned number.
poke(addr, b)b at address addr in the IAVC address space.
readButton()readFile(filename)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)r (red), g (green), and b (blue).
setForeground(r, g, b)r (red), g (green), and b (blue).
sleep(millis)millis milliseconds.
What operators are available in IPL?
The available operators are (from higher to lower priority):
[] (array reference), . (environment reference)- (unary), ++, --, ~, !*, /, %+, -<<, >>, >>><, >, <=, >===, !=&^|&&||?:=, +=, -=, *=, /=, %=, <<=, >>=, >>>=, &=, |=, ^=Comments:
[] 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.. operator must evaluate to an environment. Only a name can be used on its right (which can be followed by [] or .).== and != operators can be of any type.&& and || operators, and after the ! operator must evaluate to a boolean value.
?: operator must evaluate to a boolean value.
The other two expressions can evaluate to any type, and only one of them will be evaluated.+ 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.%, <<, >>, >>>, &, |, 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.&& and || operators is terminated as soon as the final result can be determined, all remaining expressions are not evaluated.What kinds of statements are available in IPL?
The available statements are:
;{ ( statement )* };var name [ = expr ] (, name [ = expr ])* ;final name = expr (, name = expr )* ;if ( expr ) statement [ else statement ]: ] while ( expr ) statement: ] do statement while ( expr );: ] for ( [ for-init ] ; [ expr ] ; [ for-update ] ) statement
switch ( expr ) { ( (case expr | default ) : statement-list )* }break [ label ] ;continue [ label ] ;return [ expr ] ;( [ args ] ) { statement-list }final name ( [ args ] ) { statement-list }synchronized ( expr ) statement
Comments:
for statement, for example:
for (var i = 0; i < 10; i++)
print(i);
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");
}
default in a switch statement, and it must always be the last option.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.
There are two ways to create an array in IPL:
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.
[ [ 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 = [];
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]);
Can I define a recursive method?
Yes, you can. Example:
fact(n) {
return n == 0 ? 1 : n * fact(n - 1);
}
print(fact(5));
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:
{ and }).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.
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.
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.
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());
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.
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());
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));
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; };