CX is a simple JavaScript-like language parser and interpreter written in java.
It uses simple read-ahead parsing algorithm that can be easily customized to support additional language structured (like SQL).
The interpreter is context/closures based similar to lisp that gives the name of the language:
CX stands for CONTEXT.
Main advantages of the language are:
C/Java/JavaScript
semanticsCX is case sensitive.
All statements MUST be completed with semicolon (C-like statements).
if(condition)true_statement [ *else* false_statement ]
for(initialization; condition; iteration) statement
for(values_and_keys : list_or_map) statement
while(condition) statement
do statement // infinite loop for Knuth [loop while repeat] structure
do statement while(condition)
switch(statement){case value: statement ... [ default: statement ] }
try statement catch(Exception_name) statement [ finally statement ] // optional: parser.supportTryCatchThrow = true;
break [ condition ] // for Knuth [loop while repeat] structure
continue [ condition ] // for Knuth [loop while repeat] structure
return [ value ]
throw Exception_name // optional: parser.supportTryCatchThrow = true;
var // for variable definition in the current context
function(args) statement // for lambda function
function name(args) statement // for named function
[1,2,"test",'string'] //for array definition
new {a:4, str: “str”, foo:function(){return 0;} } // for objects/maps definition
Operator Type | Operator | Associativity |
---|---|---|
Expression Operators | () [] . expr++ expr-- | left-to-right |
Unary Operators | + - ! ~ ++expr --expr | right-to-left |
Binary Operators | * / % | left-to-right |
+ - | ||
<< >> >>> | ||
< > <= >= | ||
== != | ||
& | ^ | ||
&& || | ||
?? | ||
Ternary Operator | ? : | right-to-left |
Assignment Operators | = += -= *= /= %= >>= <<= &= ^= |= | right-to-left |
SQL escape (optional) | var_name := sql expression ; | right-to-left |
NB:
Unit test were added for clearing the semantics.
SQL escaping is turned on by: parser.supportSQLEscaping = true;
Variables have no type attached, and any value can be stored in any variable.
Variables declared with var are defined directly in the current context/block scoping/hoisting, and will hide any existing variables with the same name in the parent contexts.
var i = 2;
{
var i = 4; // i in the current context will hide the i in the parent context
j = 5; // j will be created in the current context
}
// i = 2
// j = null
in CX we assume the following as equal bottom elements in the semantic:
null == false == 0 == 0.0 == "" == ''
Any used uninitialized variable is null.
Boolean datatype constants are true and false. they are cast to numbers as 1 and 0 respectively.
Internal implementation uses only Long and Double presentation for numbers.
345; // an "integer", although there is only one numeric type in JavaScript
34.5; // a floating-point number
-3.45e2; // another floating-point, equivalent to 345
0xFF; // a hexadecimal integer equal to 255, digits represented by the ...
0xab; // ... letters A-F may be upper or lowercase
A string in CX is a immutable multi-line sequence of characters. Strings can be created directly by placing the series of characters between double or single quotes.
Opening quotes inside the string must be escaped.
var helloWorld= "Hello, world!";
var anotherString = 'So Long, and Thanks for All the Fish';
var multiLineString = " this is a
multi line 'string' with all characters
between the opening quotes (\' or \") "
You can access individual characters within a string using:
var h = helloWorld[0]; // 0-based index
var r = helloWorld[-3]; // reverse indexing from the end of the string
var n = helloWorld[-100];// anything outside the length of the string is null
Arrays are designed to store values indexed by integer keys.
var array = ['string', 1, 2.3, , 4 ]; // empty elements are null
array += 42; // add element to the array
array = array + 'another string'; // add another element
b = array[0]; // 'string'
b = array[-2]; // 42 - reverse indexing
b = array[100]; // null - out of index
Objects are defined by :
var obj = new { a: 0; setA: function(value){a = value;} };
var objEmpty = {};// new can be omitted after assignment
var arrayWithObjects = [new {}, new {}];
Objects are implemented and behave as map<string, object="">.</string,>
var obj = new { a: 0; str: 'string', 42: 'meaning of life' };
obj.a; // 0
obj[str]; // 'string'
obj['str']; // 'string'
var id = 42;
obj[""+id]; // 'meaning of life'
obj[id]; // null - all keys are strings
Every function is just a predefined closure with finite dynamic variables represented by the function parameters.
// variable reverencing to a function with two parameters
var add1 = function(x, y) { return x + y; };
// named functions does not require closing column
function add2(x, y) { return x + y; }
a = add1(3,4); // a = 7
b = add2(2,2); // b = 4
// function translate context is the instance of the obj variable
var obj = { base: 5, function translate(a){return a + base;}};
a = obj.translate(4); // a = 9
obj.base = 10;
a = obj.translate(4); // a = 14
CX supports the following unary arithmetic operators:
+ Unary conversion of parsable number to its absolute value
- Unary negation (reverses the sign of a number)
~ Unary bit complement of a parsable number to long
++ Increment parsable number (can be prefix or postfix) else null
-- Decrement parsable number (can be prefix or postfix) else null
// Examples
str = '-42';
val1 = +str; //val1 = 42
n = -12.34;
val2 = +n; // val2 = 12.34
++str; // str = -41
val3 = --'13'; // val3 = 12
val4 = '13'++; // val4 = '13'
val5 = ~~'-5'; // val5 = -5
val6 = ~~'3.14'; // val6 = 3
CX supports the following binary arithmetic operators:
+ Addition
- Subtraction
* Multiplication
/ Division (returns a floating-point value)
% Modulus (returns the remainder)
Table of results by type:
left\right | null | Boolean b | Number b | String b | Array b | Object b | else
---------------|-------------|----------------|-------------------|-------------------|-------------------|--------------------|------
Boolean a + | a | a xor b | a xor (b != 0) | a xor notEmpty(b) | a xor notEmpty(b) | a xor notEmpty(b) | null
Boolean a - | a | a xor b | a xor (b != 0) | a xor notEmpty(b) | a xor notEmpty(b) | a xor notEmpty(b) | null
Boolean a * | a | a and b | a and (b != 0) | a and notEmpty(b) | a and notEmpty(b) | a and notEmpty(b) | null
Boolean a / | a | a or b | a or (b != 0) | a or notEmpty(b) | a or notEmpty(b) | a or notEmpty(b) | null
Boolean a % | a | null | null | null | null | null | null
Number a + | a | a + b ? 1:0 | a + b | a + (Number)b | null | null | null
Number a - | a | a - b ? 1:0 | a - b | a - (Number)b | null | null | null
Number a * | a | a * b ? 1:0 | a * b | a * (Number)b | null | null | null
Number a / | a | a / b ? 1:0 | a / b | a / (Number)b | null | null | null
Number a % | a | a % b ? 1:0 | (long)a % (long)b | (long)a % (long)b | null | null | null
String a + | a | a +(String)b | a +(String)b | a +(String)b | a +(String)b | a +(String)b | null
Array a + | a.add(null) | a.add(b) | a.add(b) | a.add(b) | a.add(b) | a.add(b) | null
---------------|-------------|----------------|-------------------|-------------------|-------------------|--------------------|------
else | null | null | null | null | null | null | null
CX supports the following unary and binary bit operators:
~ Not - converts to Long and inverts the bits
& And - converts to Long and apply operation
| Or - converts to Long and apply operation
^ Xor - converts to Long and apply operation
<< Shift left - converts to Long and shift left with zero fill
>> Shift right (sign-propagating)
// converts to Long and copies of the leftmost bit (sign bit) are shifted in from the left.
>>> Shift right - converts to Long and shift right with zero fill
// For positive numbers, >> and >>> yield the same result.
CX supports the following assignments:
= Assign
+= Add and assign
-= Subtract and assign
*= Multiply and assign
/= Divide and assign
%= Modulus and assign
All these operator assignments are converted to their long for:
a += b; // executed as : a = a + b;
CX supports the following comparisons that produce Boolean result:
== Equal
!= Not equal
> Greater than
>= Greater than or equal to
< Less than
<= Less than or equal to
CX supports the following Logical operators:
! Not/Negation // true if parameter is not: null, false, 0, 0.0, "", '', [], {}
&& logical And - converts to Boolean and apply operation
|| logical Or - converts to Boolean and apply operation
? Ternary operator as defined in C // result = condition ? expression : alternative;
?? isNull operator as defined in C# // result = expression ?? alternative;
Examples:
var a = true;
var b = a ? "" : "otherwise"; // b = ""
var c = b ?? 5; // c = 5 since b is empty string
A pair of curly brackets { } and an enclosed sequence of statements constitute a compound statement, which can be used wherever a statement can be used.
if (expr) {
//statements;
} [ else { // else part is optional not like the ? ternary operator
//statements;
} ]
switch (expr) {
case SOMEVALUE:
//statements;
break;
case ANOTHERVALUE:
//statements;
break;
default:
//statements;
break;
}
The syntax of the CX for loop is as follows:
for (initial; condition; loop statement) {
/*
statements will be executed every time
the for{} loop cycles, while the
condition is satisfied
*/
// break
// continue
}
The syntax of the CX for in loop is as follows:
for (variable : some_array_or_object) {
//variable = {each element of the array} or {each key in the object}
// break
// continue
}
The syntax of the CX While loop is as follows:
while (condition) {
statement1;
statement2;
statement3;
...
// break
// continue
}
The syntax of the CX Do ... while loop is as follows:
do {
statement1;
statement2;
statement3;
...
// break
// continue
} while (condition);
The syntax of the CX Do ... loop is as follows:
do {
...
break (condition);
...
continue (condition);
...
}
This structure is totaly influenced by the article : Structured Programming with go to Statements by Donald Knuth,
where the proposed variant (loop until repeat)for loops that are more readable that (do while) and (while) with additional boolean variables is
loop
...
while (condition);
...
repeat;
With such structure the both C/C++/Java/Javascript structures for (do while) and (while) can be easily expressed by setting the while condition in the beginning or at the end of the cycle.
The current CX implementation covers this structure by adding condition parameter to break and continue and introducess infinite loop in the form of *do {}.
This leads to the convenient form:
do { // loop
...
break (condition); // while (not ());
...
continue (condition);
...
} // repeat
A function is a combination of context with parent context to the current place of definition, statements sequence, (possibly empty) parameter list and optionally a given a name. A function may define its local variables via var.
function gcd(A, B) {
var diff = A- B;
if (diff == 0)
return A;
return diff > 0 ? gcd(B, diff) : gcd(A, -diff);
}
var result = gcd(60, 40); // result = 20
Functions are contexts with execution statements and may be assigned to other variables.
var mygcd = gcd; // mygcd is a reference to the same function as gcd.
var result = mygcd(60, 40); // result = 20
If function exits without a return statement, the result of the function will be the execution context (the this of the function).
obj = {
r:5,
inc:function(){r++;}, // no return gives the obj instance
dec:function(){r--;} // no return gives the obj instance
};
obj.inc().inc().inc().dec().inc(); // chain calls using the function context
r = obj.r; // r = 8
The number of arguments given when calling a function may not necessarily correspond to the number of arguments in the function definition; a named argument in the definition that does not have a matching argument in the call will have the value null. Within the function, the arguments may also be accessed through the arguments object; this provides access to all arguments using indices (e.g. arguments[0]
, arguments[1]
, ... arguments[n]
), including those beyond the number of named arguments (since the arguments list has a .length property).
function argsCount(){ arguments.length;}
var result1 = f(1,2,3,4); // result1 == 4
var result2 = f(); // result2 == 0
In CX objects are reference to a context. and can be altered as maps.
Creation of object can be done as:
var obj1 = new {}; // obj1 is an empty object
var obj2 = {}; // obj2 is an empty object - after assignment new can be ommited
Access and change of context variables can be expressed like:
obj2['message'] = 'string'; // define new key 'message' with value 'string'
var obj3 = new obj2 {getMessage: function(){ return message;} };
var msg1 = obj3.getMessage(); // msg1 = string
var msg2 = obj3['message']; // msg2 = string
var msg3 = obj3.message; // msg3 = string
In CX inheritance is done via:
var newObject = new oldObject {};
All context content of the oldObject is flatten into the newObject: only the parents are merged into the new instance and functions' context are updated to be to the new object.
obj = {
key:'value',
setValue: function(value){
key = value;
}
};
obj.setValue('oldValue');
newObj = new obj{}; // a new instance of obj
newObj.setValue('newValue');
key1 = obj.key; // key1 = 'oldValue'
key2 = newObj.key; // key2 = 'newValue'
//in case we want to use the parent functions or variables
// the following is possible but not recommended
newObj.key = null; // this will undefine the value in the newObj
// and will let the newObj to access the parent variable
key3 = newObj.key; // key3 = 'oldValue'
the same is valid for the functions in case there is a need to use global functions from the parent.
eval (expression) Evaluates expression string parameter, which can include assignment statements. Variables local to functions can be referenced by the expression.
a = 5;
eval('a++;'); // a = 6
inc = eval('function _(x){return ++x;};');
a = inc(a); // a = 7
CX includes a try ... catch ... finally exception handling statement to handle run-time errors.
this functionality should be enabled in the parser via: parser.supportTryCatchThrow = true;
try {
// Statements in which exceptions might be thrown
throw ExceptionName(expression); // an exception with name "ExceptionName" and value = eval(expression)
// will be thrown
} catch(ExceptionName variableName) {
// Statements that execute in the event of an exception
// in current case variableName will contain the exception value
} catch(vocalName) {
// any internal exception will be cougth here
} finally {
// Statements that execute afterward either way
}
Either the catch or the finally clause may be omitted. The catch arguments are required, it is the name of the exception that is thrown and the local variable that will contain the exception message.
Examples:
function x(){
try{
return 1;
}finally{
return 2;
}
}
r = x(); // r = 2
try{
throw MyException;
}catch(MyException e){
// e = null
}
try{
throw MyException(5);
}catch(MyException e){
// e = 5
}
}
Handlers are custom implementation (like plug-ins or add-ons)
that can be dynamically attached to a Context via method addHandler.
All handlers needs to implement cx.runtime.Handler interface.
with the current CX interpreter there are several handlers already implemented:
String handler provides the following methods:
trim();
substring(start);
substring(start,end);
replace(from,to);
indexOf(char);
lastIndexOf(char);
startsWith(string);
endsWith(string);
toLowerCase();
toUpperCase();
that can be applied to any object. Examples:
var str = ' trim ';
str = str.trim(); // str = 'trim'
str = 'smallCammelCase'.toLowerCase(); // str = 'smallcammelcase'
Date Handler provides the following object generated by static method newDate() or newDate(string, format):
Date = {
int year, month, day, hour, minute, second, millisecond;
int zone;
long time; // the milliseconds after 1970-01-01
}
and static method formatDate() for creating a string representation.
Examples:
var date = newDate();
date.year=2013;
date.month=07;
date.day=08 ;
var datestr = formatDate(date,'yyyy-MM-dd'); // datestr = '2013-07-08'
var date = newDate('08/07/2013','dd/MM/yyyy');
var datestr = formatDate(date,'yyyy-MM-dd'); // datestr = '2013-07-08'
Math Handler provides the global object Math with methods:
Math {
random(); abs(number);
ceil(number); floor(number); round(number);
sqrt(number);
max(number,...); min(number,...);
isNaN(number);
parseInteger(string); parseDouble(string);
}
and static method formatDate() for creating a string representation.
Examples:
var d = Math.round(0.53); // d = 1
var d = Math.floor(0.23); // d = 0.0
var d = Math.max(-1,0,1,2,4); // d = 4.0
var d = Math.min(2,4,6,8,9); // d = 2.0
var d = Math.parseInteger('2.3'); // d = 2
Object Handler provides a wrapper around POJO objects. An object is registered by:
TestClass{
public String method1(String value) {..}
public long method2(long value) {...}
public Object methodList(List<?> list, int i) {...}
public Object methodMap(Map<?, ?> map, Object key) {...}
}
...
Context cx = new Context();
TestClass instance = new TestClass();
cx.addHandler(new ObjectHandler(instance, "obj"));
Then in the CX you may use the object like:
var str = obj.method1('5');
obj.method2(5);
obj.methodList([1,2,3],1);
obj.methodMap({a:1, b:2,c:3},'b');
Database Handler provides a wrapper around JDBC implementations. provided methods are:
// if the registered handler is with name Database then
var db = Database.create(); // create JDBC instance
var db = Database.create(connectionString); // create JDBC instance
db.setProperty(key,value); // may be used before open/connect if JDBC driver requires properties
db.setConnectionString = "new connection string"; // may be used
// the following four methods are initializing the connection
// and return true if everything is ok
// otherwize the error is in db.error
db.open();
db.open(connectionstring);
db.connect();
db.connect(connectionString);
// when db connection is ok the following methods can be used
db.execute(sql);
db.execute(sql, callback(parameters, that, match, the, select, columns) {...});
db.commit();
db.rollback();
// to close the connection:
db.close();
}
Handler can be added to the Context like:
Class.forName("org.sqlite.JDBC"); // load JDBC driver to register connection string handler
Context cx = new Context();
cx.addHandler(new DatabaseHandler("Database")); // register database handler under name "Database"
To enable SQL syntax set parser.supportSQLEscaping = true;.
Then in the CX you may use the object like:
var db = Database.create('jdbc:sqlite:test_db.sqlite');
if(db.connect()){
sql := CREATE TABLE IF NOT EXISTS 'TestTable' ( 'number' INT , 'string' VARCHAR );
if(!db.execute(sql)){
throw Error("Create fails: " + db.error);
}
number = 42;
string = 'just a string';
sql := INSERT INTO 'TestTable' ( 'number', 'string' ) VALUES (number, string);
if(!db.execute(sql)){
throw Error("insert fails: " + db.error);
}
sql := SELECT 'number', 'string' FROM 'TestTable';
var result = db.execute(sql,
function(id, name){ // parameters should match the SELECT
// do here what you need to do
}
);
if(!result){
throw Error("select fails: " + db.error);
}
if(!db.commit()){
throw Error("commit fails: " + db.error);
}
if(!db.rollback()){
throw Error("rollback fails: " + db.error);
}
if(!db.close()){
throw Error("close fails: " + db.error);
}
}