File: src/js/Xpresion.js

Recommend this page to a friend!
  Classes of Nikos M.  >  Xpresion PHP Parser Engine  >  src/js/Xpresion.js  >  Download  
File: src/js/Xpresion.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Xpresion PHP Parser Engine
Parse expressions that may contain variables
Author: By
Last change:
Date: 1 year ago
Size: 93,588 bytes
 

Contents

Class file image Download
/**
*
*   Xpresion
*   Simple eXpression parser engine with variables and custom functions support for PHP, Python, Node.js and Browser
*   @version: 1.0.1
*
*   https://github.com/foo123/Xpresion
*
**/
!function( root, name, factory ){
"use strict";
if ( ('undefined'!==typeof Components)&&('object'===typeof Components.classes)&&('object'===typeof Components.classesByID)&&Components.utils&&('function'===typeof Components.utils['import']) ) /* XPCOM */
    (root.$deps = root.$deps||{}) && (root.EXPORTED_SYMBOLS = [name]) && (root[name] = root.$deps[name] = factory.call(root));
else if ( ('object'===typeof module)&&module.exports ) /* CommonJS */
    (module.$deps = module.$deps||{}) && (module.exports = module.$deps[name] = factory.call(root));
else if ( ('undefined'!==typeof System)&&('function'===typeof System.register)&&('function'===typeof System['import']) ) /* ES6 module */
    System.register(name,[],function($__export){$__export(name, factory.call(root));});
else if ( ('function'===typeof define)&&define.amd&&('function'===typeof require)&&('function'===typeof require.specified)&&require.specified(name) /*&& !require.defined(name)*/ ) /* AMD */
    define(name,['module'],function(module){factory.moduleUri = module.uri; return factory.call(root);});
else if ( !(name in root) ) /* Browser/WebWorker/.. */
    (root[name] = factory.call(root)||1)&&('function'===typeof(define))&&define.amd&&define(function(){return root[name];} );
}(  /* current root */          'undefined' !== typeof self ? self : this,
    /* module name */           "Xpresion",
    /* module factory */        function ModuleFactory__Xpresion( undef ){
"use strict";

var __version__ = "1.0.1",
    PROTO = 'prototype', hasOwnProperty = Object[PROTO].hasOwnProperty, toString = Object[PROTO].toString,
    toJSON = JSON.stringify, Keys = Object.keys, Extend = Object.create,
    floor = Math.floor, round = Math.round, abs = Math.abs, max = Math.max,
    NEWLINE = /\n\r|\r\n|\n|\r/g, SQUOTE = /'/g,
    EMPTY_TOKEN,
    default_date_locale = {
     meridian: { am:'am', pm:'pm', AM:'AM', PM:'PM' }
    ,ordinal: { ord:{1:'st',2:'nd',3:'rd'}, nth:'th' }
    ,timezone: [ 'UTC','EST','MDT' ]
    ,timezone_short: [ 'UTC','EST','MDT' ]
    ,day: [ 'Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday' ]
    ,day_short: [ 'Sun','Mon','Tue','Wed','Thu','Fri','Sat' ]
    ,month: [ 'January','February','March','April','May','June','July','August','September','October','November','December' ]
    ,month_short: [ 'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec' ]
    },
    CHAR = 'charAt', CHARCODE = 'charCodeAt',
    trim_re = /^\s+|\s+$/g,
    trim = String[PROTO].trim
        ? function( s ){ return s.trim(); }
        : function( s ){ return s.replace(trim_re, ''); },
    __inited = false, __configured = false, TPL_ID = 0
;

// https://github.com/foo123/GrammarTemplate
function HAS( o, x )
{
    return o && hasOwnProperty.call(o, x) ? 1 : 0;
}
function pad( s, n, z, pad_right )
{
    var ps = String(s);
    z = z || '0';
    if ( pad_right ) while ( ps.length < n ) ps += z;
    else while ( ps.length < n ) ps = z + ps;
    return ps;
}
function guid( )
{
    guid.GUID += 1;
    return pad(new Date().getTime().toString(16),12)+'--'+pad(guid.GUID.toString(16),4);
}
guid.GUID = 0;
function is_array( x )
{
    return (x instanceof Array) || ('[object Array]' === toString.call(x));
}
/*function is_string( x )
{
    return (x instanceof String) || ('[object String]' === toString.call(x));
}*/
function compute_alignment( s, i, l )
{
    var alignment = '', c;
    while ( i < l )
    {
        c = s[CHAR](i);
        if ( (" " === c) || ("\r" === c) || ("\t" === c) || ("\v" === c) || ("\0" === c) )
        {
            alignment += c;
            i += 1;
        }
        else
        {
            break;
        }
    }
    return alignment;
}
function align( s, alignment )
{
    var aligned, c, i, l = s.length;
    if ( l && alignment.length )
    {
        aligned = '';
        for(i=0; i<l; i++)
        {
            c = s[CHAR](i);
            aligned += c;
            if ( "\n" === c ) aligned += alignment;
        }
    }
    else
    {
        aligned = s;
    }
    return aligned;
}
function walk( obj, keys, keys_alt, obj_alt )
{
    var o, l, i, k, found = 0;
    if ( keys )
    {
        o = obj;
        l = keys.length;
        i = 0;
        found = 1;
        while( i < l )
        {
            k = keys[i++];
            if ( (null != o) && (null != o[k]) )
            {
                o = o[k];
            }
            else
            {
                found = 0;
                break;
            }
        }
    }
    if ( !found && keys_alt )
    {
        o = obj;
        l = keys_alt.length;
        i = 0;
        found = 1;
        while( i < l )
        {
            k = keys_alt[i++];
            if ( (null != o) && (null != o[k]) )
            {
                o = o[k];
            }
            else
            {
                found = 0;
                break;
            }
        }
    }
    if ( !found && (null != obj_alt) && (obj_alt !== obj) )
    {
        if ( keys )
        {
            o = obj_alt;
            l = keys.length;
            i = 0;
            found = 1;
            while( i < l )
            {
                k = keys[i++];
                if ( (null != o) && (null != o[k]) )
                {
                    o = o[k];
                }
                else
                {
                    found = 0;
                    break;
                }
            }
        }
        if ( !found && keys_alt )
        {
            o = obj_alt;
            l = keys_alt.length;
            i = 0;
            found = 1;
            while( i < l )
            {
                k = keys_alt[i++];
                if ( (null != o) && (null != o[k]) )
                {
                    o = o[k];
                }
                else
                {
                    found = 0;
                    break;
                }
            }
        }
    }
    return found ? o : null;
}
function StackEntry( stack, value )
{
    this.prev = stack || null;
    this.value = value || null;
}
function TplEntry( node, tpl )
{
    if ( tpl ) tpl.next = this;
    this.node = node || null;
    this.prev = tpl || null;
    this.next = null;
}

function multisplit( tpl, delims, postop )
{
    var IDL = delims[0], IDR = delims[1],
        OBL = delims[2], OBR = delims[3],
        lenIDL = IDL.length, lenIDR = IDR.length,
        lenOBL = OBL.length, lenOBR = OBR.length,
        ESC = '\\', OPT = '?', OPTR = '*', NEG = '!', DEF = '|', COMMENT = '#',
        TPL = ':=', REPL = '{', REPR = '}', DOT = '.', REF = ':', ALGN = '@', //NOTALGN = '&',
        COMMENT_CLOSE = COMMENT+OBR,
        default_value = null, negative = 0, optional = 0,
        nested, aligned = 0, localised = 0, start_i, end_i, template,
        argument, p, stack, c, a, b, s, l = tpl.length, i, j, jl,
        subtpl, arg_tpl, cur_tpl, start_tpl, cur_arg, opt_args,
        roottpl, block, cur_block, prev_arg, prev_opt_args,
        delim1 = [IDL, lenIDL, IDR, lenIDR], delim2 = [OBL, lenOBL, OBR, lenOBR],
        delim_order = [null,0,null,0,null,0,null,0], delim;

    postop = true === postop;
    a = new TplEntry({type: 0, val: '', algn: ''});
    cur_arg = {
        type    : 1,
        name    : null,
        key     : null,
        stpl    : null,
        dval    : null,
        opt     : 0,
        neg     : 0,
        algn    : 0,
        loc     : 0,
        start   : 0,
        end     : 0
    };
    roottpl = a; block = null;
    opt_args = null; subtpl = {}; cur_tpl = null; arg_tpl = {}; start_tpl = null;

    // hard-coded merge-sort for arbitrary delims parsing based on str len
    if ( delim1[1] < delim1[3] )
    {
        s = delim1[0]; delim1[2] = delim1[0]; delim1[0] = s;
        i = delim1[1]; delim1[3] = delim1[1]; delim1[1] = i;
    }
    if ( delim2[1] < delim2[3] )
    {
        s = delim2[0]; delim2[2] = delim2[0]; delim2[0] = s;
        i = delim2[1]; delim2[3] = delim2[1]; delim2[1] = i;
    }
    start_i = 0; end_i = 0; i = 0;
    while ( (4 > start_i) && (4 > end_i) )
    {
        if ( delim1[start_i+1] < delim2[end_i+1] )
        {
            delim_order[i] = delim2[end_i];
            delim_order[i+1] = delim2[end_i+1];
            end_i += 2;
        }
        else
        {
            delim_order[i] = delim1[start_i];
            delim_order[i+1] = delim1[start_i+1];
            start_i += 2;
        }
        i += 2;
    }
    while ( 4 > start_i )
    {
        delim_order[i] = delim1[start_i];
        delim_order[i+1] = delim1[start_i+1];
        start_i += 2; i += 2;
    }
    while ( 4 > end_i )
    {
        delim_order[i] = delim2[end_i];
        delim_order[i+1] = delim2[end_i+1];
        end_i += 2; i += 2;
    }

    stack = null; s = '';

    i = 0;
    while( i < l )
    {
        c = tpl[CHAR](i);
        if ( ESC === c )
        {
            s += i+1 < l ? tpl[CHAR](i+1) : '';
            i += 2;
            continue;
        }

        delim = null;
        if ( delim_order[0] === tpl.substr(i,delim_order[1]) )
            delim = delim_order[0];
        else if ( delim_order[2] === tpl.substr(i,delim_order[3]) )
            delim = delim_order[2];
        else if ( delim_order[4] === tpl.substr(i,delim_order[5]) )
            delim = delim_order[4];
        else if ( delim_order[6] === tpl.substr(i,delim_order[7]) )
            delim = delim_order[6];

        if ( IDL === delim )
        {
            i += lenIDL;

            if ( s.length )
            {
                if ( 0 === a.node.type ) a.node.val += s;
                else a = new TplEntry({type: 0, val: s, algn: ''}, a);
            }
            s = '';
        }
        else if ( IDR === delim )
        {
            i += lenIDR;

            // argument
            argument = s; s = '';
            if ( -1 < (p=argument.indexOf(DEF)) )
            {
                default_value = argument.slice( p+1 );
                argument = argument.slice( 0, p );
            }
            else
            {
                default_value = null;
            }
            if ( postop )
            {
                c = i < l ? tpl[CHAR](i) : '';
            }
            else
            {
                c = argument[CHAR](0);
            }
            if ( OPT === c || OPTR === c )
            {
                optional = 1;
                if ( OPTR === c )
                {
                    start_i = 1;
                    end_i = -1;
                }
                else
                {
                    start_i = 0;
                    end_i = 0;
                }
                if ( postop )
                {
                    i += 1;
                    if ( (i < l) && (NEG === tpl[CHAR](i)) )
                    {
                        negative = 1;
                        i += 1;
                    }
                    else
                    {
                        negative = 0;
                    }
                }
                else
                {
                    if ( NEG === argument[CHAR](1) )
                    {
                        negative = 1;
                        argument = argument.slice(2);
                    }
                    else
                    {
                        negative = 0;
                        argument = argument.slice(1);
                    }
                }
            }
            else if ( REPL === c )
            {
                if ( postop )
                {
                    s = ''; j = i+1; jl = l;
                    while ( (j < jl) && (REPR !== tpl[CHAR](j)) ) s += tpl[CHAR](j++);
                    i = j+1;
                }
                else
                {
                    s = ''; j = 1; jl = argument.length;
                    while ( (j < jl) && (REPR !== argument[CHAR](j)) ) s += argument[CHAR](j++);
                    argument = argument.slice( j+1 );
                }
                s = s.split(',');
                if ( s.length > 1 )
                {
                    start_i = trim(s[0]);
                    start_i = start_i.length ? (+start_i)|0 /*parseInt(start_i,10)||0*/ : 0;
                    end_i = trim(s[1]);
                    end_i = end_i.length ? (+end_i)|0 /*parseInt(end_i,10)||0*/ : -1;
                    optional = 1;
                }
                else
                {
                    start_i = trim(s[0]);
                    start_i = start_i.length ? (+start_i)|0 /*parseInt(start_i,10)||0*/ : 0;
                    end_i = start_i;
                    optional = 0;
                }
                s = '';
                negative = 0;
            }
            else
            {
                optional = 0;
                negative = 0;
                start_i = 0;
                end_i = 0;
            }
            if ( negative && (null === default_value) ) default_value = '';

            c = argument[CHAR](0);
            if ( ALGN === c )
            {
                aligned = 1;
                argument = argument.slice(1);
            }
            else
            {
                aligned = 0;
            }

            c = argument[CHAR](0);
            if ( DOT === c )
            {
                localised = 1;
                argument = argument.slice(1);
            }
            else
            {
                localised = 0;
            }

            template = -1 < argument.indexOf(REF) ? argument.split(REF) : [argument,null];
            argument = template[0]; template = template[1];
            nested = -1 < argument.indexOf(DOT) ? argument.split(DOT) : null;

            if ( cur_tpl && !HAS(arg_tpl,cur_tpl) ) arg_tpl[cur_tpl] = {};

            if ( TPL+OBL === tpl.substr(i,2+lenOBL) )
            {
                // template definition
                i += 2;
                template = template&&template.length ? template : 'grtpl--'+guid( );
                start_tpl = template;
                if ( cur_tpl && argument.length)
                    arg_tpl[cur_tpl][argument] = template;
            }

            if ( !argument.length ) continue; // template definition only

            if ( (null==template) && cur_tpl && HAS(arg_tpl,cur_tpl) && HAS(arg_tpl[cur_tpl],argument) )
                template = arg_tpl[cur_tpl][argument];

            if ( optional && !cur_arg.opt )
            {
                cur_arg.name = argument;
                cur_arg.key = nested;
                cur_arg.stpl = template;
                cur_arg.dval = default_value;
                cur_arg.opt = optional;
                cur_arg.neg = negative;
                cur_arg.algn = aligned;
                cur_arg.loc = localised;
                cur_arg.start = start_i;
                cur_arg.end = end_i;
                // handle multiple optional arguments for same optional block
                opt_args = new StackEntry(null, [argument,nested,negative,start_i,end_i,optional,localised]);
            }
            else if ( optional )
            {
                // handle multiple optional arguments for same optional block
                if ( (start_i !== end_i) && (cur_arg.start === cur_arg.end) )
                {
                    // set as main arg a loop arg, if exists
                    cur_arg.name = argument;
                    cur_arg.key = nested;
                    cur_arg.stpl = template;
                    cur_arg.dval = default_value;
                    cur_arg.opt = optional;
                    cur_arg.neg = negative;
                    cur_arg.algn = aligned;
                    cur_arg.loc = localised;
                    cur_arg.start = start_i;
                    cur_arg.end = end_i;
                }
                opt_args = new StackEntry(opt_args, [argument,nested,negative,start_i,end_i,optional,localised]);
            }
            else if ( !optional && (null === cur_arg.name) )
            {
                cur_arg.name = argument;
                cur_arg.key = nested;
                cur_arg.stpl = template;
                cur_arg.dval = default_value;
                cur_arg.opt = 0;
                cur_arg.neg = negative;
                cur_arg.algn = aligned;
                cur_arg.loc = localised;
                cur_arg.start = start_i;
                cur_arg.end = end_i;
                // handle multiple optional arguments for same optional block
                opt_args = new StackEntry(null, [argument,nested,negative,start_i,end_i,0,localised]);
            }
            if ( 0 === a.node.type ) a.node.algn = compute_alignment(a.node.val, 0, a.node.val.length);
            a = new TplEntry({
                type    : 1,
                name    : argument,
                key     : nested,
                stpl    : template,
                dval    : default_value,
                opt     : optional,
                algn    : aligned,
                loc     : localised,
                start   : start_i,
                end     : end_i
            }, a);
        }
        else if ( OBL === delim )
        {
            i += lenOBL;

            if ( s.length )
            {
                if ( 0 === a.node.type ) a.node.val += s;
                else a = new TplEntry({type: 0, val: s, algn: ''}, a);
            }
            s = '';

            // comment
            if ( COMMENT === tpl[CHAR](i) )
            {
                j = i+1; jl = l;
                while ( (j < jl) && (COMMENT_CLOSE !== tpl.substr(j,lenOBR+1)) ) s += tpl[CHAR](j++);
                i = j+lenOBR+1;
                if ( 0 === a.node.type ) a.node.algn = compute_alignment(a.node.val, 0, a.node.val.length);
                a = new TplEntry({type: -100, val: s}, a);
                s = '';
                continue;
            }

            // optional block
            stack = new StackEntry(stack, [a, block, cur_arg, opt_args, cur_tpl, start_tpl]);
            if ( start_tpl ) cur_tpl = start_tpl;
            start_tpl = null;
            cur_arg = {
                type    : 1,
                name    : null,
                key     : null,
                stpl    : null,
                dval    : null,
                opt     : 0,
                neg     : 0,
                algn    : 0,
                loc     : 0,
                start   : 0,
                end     : 0
            };
            opt_args = null;
            a = new TplEntry({type: 0, val: '', algn: ''});
            block = a;
        }
        else if ( OBR === delim )
        {
            i += lenOBR;

            b = a;
            cur_block = block;
            prev_arg = cur_arg;
            prev_opt_args = opt_args;
            if ( stack )
            {
                a = stack.value[0];
                block = stack.value[1];
                cur_arg = stack.value[2];
                opt_args = stack.value[3];
                cur_tpl = stack.value[4];
                start_tpl = stack.value[5];
                stack = stack.prev;
            }
            else
            {
                a = null;
            }
            if ( s.length )
            {
                if ( 0 === b.node.type ) b.node.val += s;
                else b = new TplEntry({type: 0, val: s, algn: ''}, b);
            }
            s = '';
            if ( start_tpl )
            {
                subtpl[start_tpl] = new TplEntry({
                    type    : 2,
                    name    : prev_arg.name,
                    key     : prev_arg.key,
                    loc     : prev_arg.loc,
                    algn    : prev_arg.algn,
                    start   : prev_arg.start,
                    end     : prev_arg.end,
                    opt_args: null/*opt_args*/,
                    tpl     : cur_block
                });
                start_tpl = null;
            }
            else
            {
                if ( 0 === a.node.type ) a.node.algn = compute_alignment(a.node.val, 0, a.node.val.length);
                a = new TplEntry({
                    type    : -1,
                    name    : prev_arg.name,
                    key     : prev_arg.key,
                    loc     : prev_arg.loc,
                    algn    : prev_arg.algn,
                    start   : prev_arg.start,
                    end     : prev_arg.end,
                    opt_args: prev_opt_args,
                    tpl     : cur_block
                }, a);
            }
        }
        else
        {
            c = tpl[CHAR](i++);
            if ( "\n" === c )
            {
                // note line changes to handle alignments
                if ( s.length )
                {
                    if ( 0 === a.node.type ) a.node.val += s;
                    else a = new TplEntry({type: 0, val: s, algn: ''}, a);
                }
                s = '';
                if ( 0 === a.node.type ) a.node.algn = compute_alignment(a.node.val, 0, a.node.val.length);
                a = new TplEntry({type: 100, val: "\n"}, a);
            }
            else
            {
                s += c;
            }
        }
    }
    if ( s.length )
    {
        if ( 0 === a.node.type ) a.node.val += s;
        else a = new TplEntry({type: 0, val: s, algn: ''}, a);
    }
    if ( 0 === a.node.type ) a.node.algn = compute_alignment(a.node.val, 0, a.node.val.length);
    return [roottpl, subtpl];
}

function optional_block( args, block, SUB, FN, index, alignment, orig_args )
{
    var opt_vars, opt_v, opt_arg, arr, rs, re, ri, len, block_arg = null, out = '';

    if ( -1 === block.type )
    {
        // optional block, check if optional variables can be rendered
        opt_vars = block.opt_args;
        // if no optional arguments, render block by default
        if ( opt_vars && opt_vars.value[5] )
        {
            while( opt_vars )
            {
                opt_v = opt_vars.value;
                opt_arg = walk( args, opt_v[1], [String(opt_v[0])], opt_v[6] ? null : orig_args );
                if ( (null === block_arg) && (block.name === opt_v[0]) ) block_arg = opt_arg;

                if ( (0 === opt_v[2] && null == opt_arg) ||
                    (1 === opt_v[2] && null != opt_arg)
                )
                    return '';
                opt_vars = opt_vars.prev;
            }
        }
    }
    else
    {
        block_arg = walk( args, block.key, [String(block.name)], block.loc ? null : orig_args );
    }

    arr = is_array( block_arg ); len = arr ? block_arg.length : -1;
    //if ( !block.algn ) alignment = '';
    if ( arr && (len > block.start) )
    {
        for(rs=block.start,re=(-1===block.end?len-1:Math.min(block.end,len-1)),ri=rs; ri<=re; ri++)
            out += main( args, block.tpl, SUB, FN, ri, alignment, orig_args );
    }
    else if ( !arr && (block.start === block.end) )
    {
        out = main( args, block.tpl, SUB, FN, null, alignment, orig_args );
    }
    return out;
}
function non_terminal( args, symbol, SUB, FN, index, alignment, orig_args )
{
    var opt_arg, tpl_args, tpl, out = '', fn;
    if ( symbol.stpl && (
        HAS(SUB,symbol.stpl) ||
        HAS(GrammarTemplate.subGlobal,symbol.stpl) ||
        HAS(FN,symbol.stpl) || HAS(FN,'*') ||
        HAS(GrammarTemplate.fnGlobal,symbol.stpl) ||
        HAS(GrammarTemplate.fnGlobal,'*')
    ) )
    {
        // using custom function or sub-template
        opt_arg = walk( args, symbol.key, [String(symbol.name)], symbol.loc ? null : orig_args );

        if ( HAS(SUB,symbol.stpl) || HAS(GrammarTemplate.subGlobal,symbol.stpl) )
        {
            // sub-template
            if ( (null != index) && ((0 !== index) || (symbol.start !== symbol.end) || !symbol.opt) && is_array(opt_arg) )
            {
                opt_arg = index < opt_arg.length ? opt_arg[ index ] : null;
            }

            if ( (null == opt_arg) && (null !== symbol.dval) )
            {
                // default value if missing
                out = symbol.dval;
            }
            else
            {
                // try to associate sub-template parameters to actual input arguments
                tpl = HAS(SUB,symbol.stpl) ? SUB[symbol.stpl].node : GrammarTemplate.subGlobal[symbol.stpl].node;
                tpl_args = {};
                if ( null != opt_arg )
                {
                    /*if ( HAS(opt_arg,tpl.name) && !HAS(opt_arg,symbol.name) ) tpl_args = opt_arg;
                    else tpl_args[tpl.name] = opt_arg;*/
                    if ( is_array(opt_arg) ) tpl_args[tpl.name] = opt_arg;
                    else tpl_args = opt_arg;
                }
                out = optional_block( tpl_args, tpl, SUB, FN, null, symbol.algn ? alignment : '', null == orig_args ? args : orig_args );
                //if ( symbol.algn ) out = align(out, alignment);
            }
        }
        else //if ( fn )
        {
            // custom function
            fn = null;
            if      ( HAS(FN,symbol.stpl) )                         fn = FN[symbol.stpl];
            else if ( HAS(FN,'*') )                                 fn = FN['*'];
            else if ( HAS(GrammarTemplate.fnGlobal,symbol.stpl) )   fn = GrammarTemplate.fnGlobal[symbol.stpl];
            else if ( GrammarTemplate.fnGlobal['*'] )               fn = GrammarTemplate.fnGlobal['*'];

            if ( is_array(opt_arg) )
            {
                index = null != index ? index : symbol.start;
                opt_arg = index < opt_arg.length ? opt_arg[ index ] : null;
            }

            if ( "function" === typeof fn )
            {
                var fn_arg = {
                    //value               : opt_arg,
                    symbol              : symbol,
                    index               : index,
                    currentArguments    : args,
                    originalArguments   : orig_args,
                    alignment           : alignment
                };
                opt_arg = fn( opt_arg, fn_arg );
            }
            else
            {
                opt_arg = String(fn);
            }

            out = (null == opt_arg) && (null !== symbol.dval) ? symbol.dval : String(opt_arg);
            if ( symbol.algn ) out = align(out, alignment);
        }
    }
    else if ( symbol.opt && (null !== symbol.dval) )
    {
        // boolean optional argument
        out = symbol.dval;
    }
    else
    {
        // plain symbol argument
        opt_arg = walk( args, symbol.key, [String(symbol.name)], symbol.loc ? null : orig_args );

        // default value if missing
        if ( is_array(opt_arg) )
        {
            index = null != index ? index : symbol.start;
            opt_arg = index < opt_arg.length ? opt_arg[ index ] : null;
        }
        out = (null == opt_arg) && (null !== symbol.dval) ? symbol.dval : String(opt_arg);
        if ( symbol.algn ) out = align(out, alignment);
    }
    return out;
}
function main( args, tpl, SUB, FN, index, alignment, orig_args )
{
    alignment = alignment || '';
    var tt, current_alignment = alignment, out = '';
    while ( tpl )
    {
        tt = tpl.node.type;
        if ( -1 === tt ) /* optional code-block */
        {
            out += optional_block( args, tpl.node, SUB, FN, index, tpl.node.algn ? current_alignment : alignment, orig_args );
        }
        else if ( 1 === tt ) /* non-terminal */
        {
            out += non_terminal( args, tpl.node, SUB, FN, index, tpl.node.algn ? current_alignment : alignment, orig_args );
        }
        else if ( 0 === tt ) /* terminal */
        {
            current_alignment += tpl.node.algn;
            out += tpl.node.val;
        }
        else if ( 100 === tt ) /* new line */
        {
            current_alignment = alignment;
            out += "\n" + alignment;
        }
        /*else if ( -100 === tt ) /* comment * /
        {
            /* pass * /
        }*/
        tpl = tpl.next;
    }
    return out;
}


function GrammarTemplate( tpl, delims, postop )
{
    var self = this;
    if ( !(self instanceof GrammarTemplate) ) return new GrammarTemplate(tpl, delims, postop);
    self.id = null;
    self.tpl = null;
    self.fn = {};
    // lazy init
    self._args = [tpl||'', delims||GrammarTemplate.defaultDelimiters, postop||false];
};
GrammarTemplate.VERSION = '3.0.0';
GrammarTemplate.defaultDelimiters = ['<','>','[',']'];
GrammarTemplate.fnGlobal = {};
GrammarTemplate.subGlobal = {};
GrammarTemplate.guid = guid;
GrammarTemplate.multisplit = multisplit;
GrammarTemplate.align = align;
GrammarTemplate.main = main;
GrammarTemplate[PROTO] = {
    constructor: GrammarTemplate

    ,id: null
    ,tpl: null
    ,fn: null
    ,_args: null

    ,dispose: function( ) {
        var self = this;
        self.id = null;
        self.tpl = null;
        self.fn = null;
        self._args = null;
        return self;
    }
    ,parse: function( ) {
        var self = this;
        if ( (null === self.tpl) && (null !== self._args) )
        {
            // lazy init
            self.tpl = GrammarTemplate.multisplit( self._args[0], self._args[1], self._args[2] );
            self._args = null;
        }
        return self;
    }
    ,render: function( args ) {
        var self = this;
        // lazy init
        if ( null === self.tpl ) self.parse( );
        return GrammarTemplate.main( null==args ? {} : args, self.tpl[0], self.tpl[1], self.fn );
    }
};

function F( a, f )
{
    return new Function( a, f );
}
function RE( r, f )
{
    return new RegExp( r, f||'' );
}
//function DATE( d, f ){ return new Date( d ); }

function is_string( v )
{
    return (v instanceof String) || ('[object String]' === toString.call(v));
}
function is_object( v )
{
    return /*(v instanceof Object) ||*/ ('[object Object]' === toString.call(v));
}
function starts_with( s, p, i )
{
    i = i || 0;
    return s.length-i >= p.length && p === s.substr(i, p.length);
}
function pad_( s, len, ch )
{
    var sp = s.toString( ), n = len-sp.length;
    return n > 0 ? new Array(n+1).join(ch||' ')+sp : sp;
}
function time( )
{
    return floor(new Date().getTime() / 1000);
}
function date( format, timestamp )
{
    if ( !arguments.length ) return '';
    var formatted_datetime, f, i, l, jsdate,
        locale = default_date_locale
    ;

    // JS Date
    if ( timestamp instanceof Date ) jsdate = new Date( timestamp );
    // UNIX timestamp (auto-convert to int)
    else if ( "number" === typeof timestamp ) jsdate =  new Date(timestamp * 1000);
    // undefined
    else/*if ( null === timestamp  || undef === timestamp )*/ jsdate = new Date( );

    var D = { }, tzo = jsdate.getTimezoneOffset( ), atzo = abs(tzo), m = jsdate.getMonth( ), jmod10;
    // 24-Hours; 0..23
    D.G = jsdate.getHours( );
    // Day of month; 1..31
    D.j = jsdate.getDate( ); jmod10 = D.j%10;
    // Month; 1...12
    D.n = m + 1;
    // Full year; e.g. 1980...2010
    D.Y = jsdate.getFullYear( );
    // Day of week; 0[Sun]..6[Sat]
    D.w = jsdate.getDay( );
    // ISO-8601 day of week; 1[Mon]..7[Sun]
    D.N = D.w || 7;
    // Day of month w/leading 0; 01..31
    D.d = pad_(D.j, 2, '0');
    // Shorthand day name; Mon...Sun
    D.D = locale.day_short[ D.w ];
    // Full day name; Monday...Sunday
    D.l = locale.day[ D.w ];
    // Ordinal suffix for day of month; st, nd, rd, th
    D.S = locale.ordinal.ord[ D.j ] ? locale.ordinal.ord[ D.j ] : (locale.ordinal.ord[ jmod10 ] ? locale.ordinal.ord[ jmod10 ] : locale.ordinal.nth);
    // Day of year; 0..365
    D.z = round((new Date(D.Y, m, D.j) - new Date(D.Y, 0, 1)) / 864e5);
    // ISO-8601 week number
    D.W = pad_(1 + round((new Date(D.Y, m, D.j - D.N + 3) - new Date(D.Y, 0, 4)) / 864e5 / 7), 2, '0');
    // Full month name; January...December
    D.F = locale.month[ m ];
    // Month w/leading 0; 01...12
    D.m = pad_(D.n, 2, '0');
    // Shorthand month name; Jan...Dec
    D.M = locale.month_short[ m ];
    // Days in month; 28...31
    D.t = (new Date(D.Y, m+1, 0)).getDate( );
    // Is leap year?; 0 or 1
    D.L = D.Y % 4 === 0 & D.Y % 100 !== 0 | D.Y % 400 === 0;
    // ISO-8601 year
    D.o = D.Y + (11 === m && D.W < 9 ? 1 : (0 === m && D.W > 9 ? -1 : 0));
    // Last two digits of year; 00...99
    D.y = D.Y.toString( ).slice(-2);
    // am or pm
    D.a = D.G > 11 ? locale.meridian.pm : locale.meridian.am;
    // AM or PM
    D.A = D.G > 11 ? locale.meridian.PM : locale.meridian.AM;
    // Swatch Internet time; 000..999
    D.B = pad_(floor((jsdate.getUTCHours( ) * 36e2 + jsdate.getUTCMinutes( ) * 60 + jsdate.getUTCSeconds( ) + 36e2) / 86.4) % 1e3, 3, '0');
    // 12-Hours; 1..12
    D.g = (D.G % 12) || 12;
    // 12-Hours w/leading 0; 01..12
    D.h = pad_(D.g, 2, '0');
    // 24-Hours w/leading 0; 00..23
    D.H = pad_(D.G, 2, '0');
    // Minutes w/leading 0; 00..59
    D.i = pad_(jsdate.getMinutes( ), 2, '0');
    // Seconds w/leading 0; 00..59
    D.s = pad_(jsdate.getSeconds( ), 2, '0');
    // Microseconds; 000000-999000
    D.u = pad_(jsdate.getMilliseconds( ) * 1000, 6, '0');
    // Timezone identifier; e.g. Atlantic/Azores, ...
    // The following works, but requires inclusion of the very large
    // timezone_abbreviations_list() function.
    /*              return that.date_default_timezone_get();
    */
    D.e = '';
    // DST observed?; 0 or 1
    D.I = ((new Date(D.Y, 0) - Date.UTC(D.Y, 0)) !== (new Date(D.Y, 6) - Date.UTC(D.Y, 6))) ? 1 : 0;
    // Difference to GMT in hour format; e.g. +0200
    D.O = (tzo > 0 ? "-" : "+") + pad_(floor(atzo / 60) * 100 + atzo % 60, 4, '0');
    // Difference to GMT w/colon; e.g. +02:00
    D.P = (D.O.substr(0, 3) + ":" + D.O.substr(3, 2));
    // Timezone abbreviation; e.g. EST, MDT, ...
    D.T = 'UTC';
    // Timezone offset in seconds (-43200...50400)
    D.Z = -tzo * 60;
    // Seconds since UNIX epoch
    D.U = jsdate / 1000 | 0;
    // ISO-8601 date. 'Y-m-d\\TH:i:sP'
    D.c = [ D.Y,'-',D.m,'-',D.d,'\\',D.T,D.H,':',D.i,':',D.s,D.P ].join('');
    // RFC 2822 'D, d M Y H:i:s O'
    D.r = [ D.D,', ',D.d,' ',D.M,' ',D.Y,' ',D.H,':',D.i,':',D.s,' ',D.O ].join('');

    formatted_datetime = '';
    for (i=0,l=format.length; i<l; i++)
    {
        f = format.charAt( i );
        formatted_datetime += hasOwnProperty.call(D,f) ? D[ f ] : f;
    }
    return formatted_datetime;
}
function dummy( /*Var, Fn, Cache*/ )
{
    return null;
}
function evaluator_factory(evaluator_str,Fn,Cache)
{
    var evaluator = F('Fn,Cache,Xpresion', [
     'return function evaluator(Var){'
    ,'    "use strict";'
    ,'    return ' + evaluator_str + ';'
    ,'};'
    ].join("\n"))(Fn,Cache,Xpresion);
    return evaluator;
}

function parse_re_flags(s,i,l)
{
    var flags = '',
        has_i = false,
        has_g = false,
        has_m = false,
        seq = 0,
        i2 = i+seq,
        not_done = true,
        ch
    ;
    while (i2 < l && not_done)
    {
        ch = s.charAt(i2++);
        seq += 1;
        if ('i' == ch && !has_i)
        {
            flags += 'i';
            has_i = true;
        }

        if ('m' == ch && !has_m)
        {
            flags += 'm';
            has_m = true;
        }

        if ('g' == ch && !has_g)
        {
            flags += 'g';
            has_g = true;
        }

        if (seq >= 3 || (!has_i && !has_g && !has_m))
        {
            not_done = false;
        }
    }
    return flags;
}

function Configuration( conf )
{
    var self = this;

    if ( !(self instanceof Configuration) )
        return new Configuration(conf);

    self.RE = {};
    self.BLOCKS = {};
    self.RESERVED = {};
    self.OPERATORS = {};
    self.FUNCTIONS = {};
    self.FN = {
         'INF'          : Infinity
        ,'NAN'          : NaN
    };

    if ( "object" === typeof conf )
    {
        if ( conf.re )
            self.defRE( conf.re );
        if ( conf.blocks )
            self.defBlock( conf.blocks );
        if ( conf.reserved )
            self.defReserved( conf.reserved );
        if ( conf.operators )
            self.defOp( conf.operators );
        if ( conf.functions )
            self.defFunc( conf.functions );
        if ( conf.runtime )
            self.defRuntimeFunc( conf.runtime );
    }
}
Configuration[PROTO] = {
    constructor: Configuration,

    RE: null,
    BLOCKS: null,
    RESERVED: null,
    OPERATORS: null,
    FUNCTIONS: null,
    FN: null,

    dispose: function( ) {
        var self = this;

        self.RE = null;
        self.BLOCKS = null;
        self.RESERVED = null;
        self.OPERATORS = null;
        self.FUNCTIONS = null;
        self.FN = null;

        return self;
    },

    defRE: function( obj ) {
        if ( 'object' === typeof obj )
        {
            for (var k in obj)
            {
                if ( hasOwnProperty.call(obj,k) )
                    this.RE[ k ] = obj[ k ];
            }
        }
        return this;
    },
    defBlock: function( obj ) {
        if ( 'object' === typeof obj )
        {
            for (var k in obj)
            {
                if ( hasOwnProperty.call(obj,k) )
                    this.BLOCKS[ k ] = obj[ k ];
            }
        }
        return this;
    },
    defReserved: function( obj ) {
        if ( 'object' === typeof obj )
        {
            for (var k in obj)
            {
                if ( hasOwnProperty.call(obj,k) )
                    this.RESERVED[ k ] = obj[ k ];
            }
        }
        return this;
    },
    defOp: function( obj ) {
        if ( 'object' === typeof obj )
        {
            var k, op;
            for (k in obj)
            {
                if ( !hasOwnProperty.call(obj,k) || !obj[ k ] ) continue;

                op = obj[ k ];

                if ( op instanceof Alias || op instanceof Op )
                {
                    this.OPERATORS[ k ] = op;
                    continue;
                }

                if ( op.polymorphic )
                {
                    this.OPERATORS[ k ] = Op().Polymorphic(op.polymorphic.map(function(entry){
                        var func, op;
                        if ( is_object(entry) )
                        {
                            func = entry['check'];
                            op = entry['op'];
                        }
                        else
                        {
                            func = entry[0];
                            op = entry[1];
                        }
                        op = op instanceof Op ? op : new Op(
                            op.input,
                            op.output,
                            op.otype,
                            op.fixity,
                            op.associativity,
                            op.priority,
                            op.ofixity
                        );
                        return [func, op];
                    }));
                }
                else
                {
                    this.OPERATORS[ k ] = new Op(
                        op.input,
                        op.output,
                        op.otype,
                        op.fixity,
                        op.associativity,
                        op.priority,
                        op.ofixity
                    );
                }
            }
        }
        return this;
    },
    defFunc: function( obj ) {
        if ( 'object' === typeof obj )
        {
            var k, op;
            for (k in obj)
            {
                if ( !hasOwnProperty.call(obj,k) || !obj[ k ] ) continue;

                op = obj[ k ];

                if ( op instanceof Alias || op instanceof Func )
                {
                    this.FUNCTIONS[ k ] = op;
                    continue;
                }

                this.FUNCTIONS[ k ] = new Func(
                    op.input,
                    op.output,
                    op.otype,
                    op.priority,
                    op.arity,
                    op.associativity,
                    op.ofixity
                );
            }
        }
        return this;
    },
    defRuntimeFunc: function( obj ) {
        if ( 'object' === typeof obj )
        {
            for (var k in obj)
            {
                if ( hasOwnProperty.call(obj,k) )
                    this.FN[ k ] = obj[ k ];
            }
        }
        return this;
    }
};

function Xpresion( expr, conf )
{
    var self = this;
    if ( !(self instanceof Xpresion) ) return new Xpresion( expr, conf );
    if ( (!conf) || !(conf instanceof Configuration) )
        conf = Xpresion.defaultConfiguration();
    self.source = String(null==expr ? '' : expr);
    self.dummy_evaluator = dummy;
    Xpresion.parse( self, conf );
}
Xpresion.VERSION = __version__;

Xpresion.Configuration = Configuration;

Xpresion.CONF = null;
Xpresion.defaultConfiguration = function(conf) {
    if ( arguments.length )
    {
        Xpresion.CONF = conf;
    }
    return Xpresion.CONF;
};

// STATIC
var
 COMMA       = Xpresion.COMMA      =   ','
,LPAREN      = Xpresion.LPAREN     =   '('
,RPAREN      = Xpresion.RPAREN     =   ')'

,NONE        = Xpresion.NONE       =   0
,DEFAULT     = Xpresion.DEFAULT    =   1
,LEFT        = Xpresion.LEFT       =  -2
,RIGHT       = Xpresion.RIGHT      =   2
,PREFIX      = Xpresion.PREFIX     =   2
,INFIX       = Xpresion.INFIX      =   4
,POSTFIX     = Xpresion.POSTFIX    =   8

,T_DUM       = Xpresion.T_DUM      =   0
,T_MIX       = Xpresion.T_MIX      =   1
,T_DFT       = Xpresion.T_DFT      =   T_MIX
,T_IDE       = Xpresion.T_IDE      =   16
,T_VAR       = Xpresion.T_VAR      =   17
,T_LIT       = Xpresion.T_LIT      =   32
,T_NUM       = Xpresion.T_NUM      =   33
,T_STR       = Xpresion.T_STR      =   34
,T_REX       = Xpresion.T_REX      =   35
,T_BOL       = Xpresion.T_BOL      =   36
,T_DTM       = Xpresion.T_DTM      =   37
,T_ARY       = Xpresion.T_ARY      =   38
,T_OP        = Xpresion.T_OP       =   128
,T_N_OP      = Xpresion.T_N_OP     =   129
,T_POLY_OP   = Xpresion.T_POLY_OP  =   130
,T_FUN       = Xpresion.T_FUN      =   131
,T_EMPTY     = Xpresion.T_EMPTY    =   1024
;

Xpresion.TYPES = {
    '0' : 'T_DUM',
    '1' : 'T_MIX',
    //'1' => 'T_DFT',
    '16' : 'T_IDE',
    '17' : 'T_VAR',
    '32' : 'T_LIT',
    '33' : 'T_NUM',
    '34' : 'T_STR',
    '35' : 'T_REX',
    '36' : 'T_BOL',
    '37' : 'T_DTM',
    '38' : 'T_ARY',
    '128' : 'T_OP',
    '129' : 'T_N_OP',
    '130' : 'T_POLY_OP',
    '131' : 'T_FUN',
    '1024' : 'T_EMPTY'
};

Xpresion.Tpl = GrammarTemplate;

function Alias( alias )
{
    if ( !(this instanceof Alias) ) return new Alias(alias);
    this.alias = alias;
}
Xpresion.Alias = Alias;
Alias.get_entry = function( entries, id ) {
    if ( id && entries && hasOwnProperty.call(entries,id) )
    {
        // walk/bypass aliases, if any
        var entry = entries[ id ];
        while ( (entry instanceof Alias) && hasOwnProperty.call(entries,entry.alias) )
        {
            id = entry.alias;
            // circular reference
            if (entry === entries[ id ]) return false;
            entry = entries[ id ];
        }
        return entry;
    }
    return false;
};

function Node(type, arity, node, children, pos)
{
    var self = this;
    if ( !(self instanceof Node) ) return new Node(type, arity, node, children, pos);
    self.type = type;
    self.arity = arity;
    self.node = node;
    self.children = children || null;
    self.pos = pos || 0;
}
Xpresion.Node = Node;
Node[PROTO] = {
    constructor: Node
    ,type: null
    ,arity: null
    ,node: null
    ,children: null
    ,pos: null
    ,op_parts: null
    ,op_def: null
    ,op_index: null
    ,op_next: function( op, pos, op_queue, token_queue ) {
        var self = this, num_args = 0,
            is_next = (0 === self.op_parts.indexOf( op.input ));
        if ( is_next )
        {
            if ( 0 === self.op_def[0][0] )
            {
                num_args = Op.match_args(self.op_def[0][2], pos-1, op_queue, token_queue );
                if ( false === num_args )
                {
                    is_next = false;
                }
                else
                {
                    self.arity = num_args;
                    self.op_def.shift( );
                }
            }
        }
        if ( is_next )
        {
            self.op_def.shift( );
            self.op_parts.shift( );
        }
        return is_next;
    }
    ,op_complete: function( ) {
        return !this.op_parts.length;
    }
    ,dispose: function( ) {
        var self = this,
            c = self.children, l, i;
        if (c && (l=c.length))
        {
            for (i=0; i<l; i++) c[i] && c[i].dispose();
        }
        self.type = null;
        self.arity = null;
        self.pos = null;
        self.node = null;
        self.op_parts = null;
        self.op_def = null;
        self.op_index = null;
        c = self.children = null;
        return self;
    }
    ,toString: function( ) {
        var out = [], n = this.node,
            ch = this.children ? this.children : [],
            i, l = ch.length,
            tab = /*arguments.length && arguments[0].substr ? arguments[0] :*/ "",
            tab_tab = tab+"  "
        ;
        for (i=0; i<l; i++) out.push(ch[i].toString(/*tab_tab*/));
        return tab + [
        "Node("+n.type+","+n.arity+"): " + (n.parts ? n.parts.join(' ') : n.input),
        "Childs: [",
        tab + out.join("\n" + tab),
        "]"
        ].join("\n"+tab) + "\n";
    }
};
// depth-first traversal
Node.DFT = function DFT( root, action, andDispose ) {
    /*
        one can also implement a symbolic solver here,
        also known as "unification" in symbolic computation and rewrite systems
        by manipulating the tree to produce 'x' on one side
        and the reverse operators/tokens on the other side
        i.e by transposing the top op on other side of the '=' op and using the 'associated inverse operator'
        in stack order (i.e most top op is transposed first etc.. until only the branch with 'x' stays on one side)
        (easy when only one unknown in one state, more difficult for many unknowns
        or one unknown in different states, e.g x and x^2 etc..)
    */
    andDispose = false !== andDispose;
    action = action || Xpresion.render;
    var node, op, arity, o, stack = [ root ], output = [ ];
    while ( stack.length )
    {
        node = stack[ 0 ];
        if ( node.children && node.children.length )
        {
            stack = node.children.concat( stack );
            node.children = null;
        }
        else
        {
            stack.shift( );
            op = node.node;
            arity = op.arity;
            if ( (T_OP & op.type) && 0 === arity ) arity = 1; // have already padded with empty token
            else if ( arity > output.length && op.arity_min <= op.arity ) arity = op.arity_min;
            o = action(op, arity ? output.splice(output.length-arity, arity) : [])
            output.push( o );
            if ( andDispose ) node.dispose( );
        }
    }
    stack = null;
    return output[ 0 ];
};

Xpresion.reduce = function( token_queue, op_queue, nop_queue, current_op, pos, err ) {
    var entry, op, n, opc, fixity, nop = null, nop_index = 0, validation, arity;
    /*
        n-ary operatots (eg ternary) or composite operators
        as operators with multi-parts
        which use their own stack or equivalently
        lock their place on the OP_STACK
        until all the parts of the operator are
        unified and collapsed

        Equivalently n-ary ops are like ops which relate NOT to
        args but to other ops

        In this way the BRA_KET special op handling
        can be made into an n-ary op with uniform handling
    */
    // TODO: maybe do some optimisation here when 2 operators can be combined into 1, etc..
    // e.g not is => isnot

    if ( current_op )
    {
        opc = current_op;

        // polymorphic operator
        // get the current operator morph, based on current context
        (T_POLY_OP === opc.type) && (opc = opc.morph([pos,token_queue,op_queue]));

        // n-ary/multi-part operator, initial part
        // push to nop_queue/op_queue
        if ( T_N_OP === opc.type )
        {
            validation = opc.validate(pos, op_queue, token_queue);
            if ( false === validation[0] )
            {
                // operator is not valid in current state
                err.err = true;
                err.msg = validation[1];
                return false;
            }
            n = opc.node(null, pos, op_queue, token_queue);
            n.arity = validation[0];
            nop_queue.unshift( n );
            op_queue.unshift( n );
        }
        else
        {
            if ( nop_queue.length )
            {
                nop = nop_queue[0];
                nop_index = nop.op_index;
            }

            // n-ary/multi-part operator, further parts
            // combine one-by-one, until n-ary operator is complete
            if ( nop && nop.op_next(opc, pos, op_queue, token_queue) )
            {
                while ( op_queue.length > nop_index )
                {
                    entry = op_queue.shift( ); op = entry.node;
                    arity = op.arity;
                    if ( (T_OP & op.type) && 0 === arity ) arity = 1; // have already padded with empty token
                    else if ( arity > token_queue.length && op.arity_min <= op.arity ) arity = op.arity_min;
                    n = op.node(arity ? token_queue.splice(token_queue.length-arity, arity) : [], entry.pos);
                    token_queue.push( n );
                }

                if ( nop.op_complete( ) )
                {
                    nop_queue.shift( );
                    op_queue.shift( );
                    opc = nop.node;
                    nop.dispose( );
                    nop_index = nop_queue.length ? nop_queue[0].op_index : 0;
                }
                else
                {
                    return;
                }
            }
            else
            {
                validation = opc.validate(pos, op_queue, token_queue);
                if ( false === validation[0] )
                {
                    // operator is not valid in current state
                    err.err = true;
                    err.msg = validation[1];
                    return false;
                }
            }

            fixity = opc.fixity;
            if ( POSTFIX === fixity )
            {
                // postfix assumed to be already in correct order,
                // no re-structuring needed
                arity = opc.arity;
                if ( arity > token_queue.length && opc.arity_min <= token_queue.length ) arity = opc.arity_min;
                n = opc.node(arity ? token_queue.splice(token_queue.length-arity, arity) : [], pos);
                token_queue.push( n );
            }
            else if ( PREFIX === fixity )
            {
                // prefix assumed to be already in reverse correct order,
                // just push to op queue for later re-ordering
                op_queue.unshift( Node(opc.otype, opc.arity, opc, null, pos) );
                if ( (/*T_FUN*/T_OP & opc.type) && (0 === opc.arity) )
                {
                    token_queue.push(EMPTY_TOKEN.node(null, pos+1));
                }
            }
            else/* if ( INFIX === fixity )*/
            {
                while ( op_queue.length > nop_index )
                {
                    entry = op_queue.shift( ); op = entry.node;

                    if ( (op.priority < opc.priority ||
                        (op.priority === opc.priority &&
                        (op.associativity < opc.associativity ||
                        (op.associativity === opc.associativity &&
                        op.associativity < 0))))
                    )
                    {
                        arity = op.arity;
                        if ( (T_OP & op.type) && 0 === arity ) arity = 1; // have already padded with empty token
                        else if ( arity > token_queue.length && op.arity_min <= op.arity ) arity = op.arity_min;
                        n = op.node(arity ? token_queue.splice(token_queue.length-arity, arity) : [], entry.pos);
                        token_queue.push( n );
                    }
                    else
                    {
                        op_queue.unshift( entry );
                        break;
                    }
                }
                op_queue.unshift( Node(opc.otype, opc.arity, opc, null, pos) );
            }
        }
    }
    else
    {
        while ( op_queue.length )
        {
            entry = op_queue.shift( ); op = entry.node;
            arity = op.arity;
            if ( (T_OP & op.type) && 0 === arity ) arity = 1; // have already padded with empty token
            else if ( arity > token_queue.length && op.arity_min <= op.arity ) arity = op.arity_min;
            n = op.node(arity ? token_queue.splice(token_queue.length-arity, arity) : [], entry.pos);
            token_queue.push( n );
        }
    }
};

Xpresion.parse_delimited_block = function parse_delimited_block(s, i, l, delim, is_escaped) {
    if ( 5 > arguments.length ) is_escaped = true;
    var p = delim, esc = false, ch = '';
    is_escaped = (false !== is_escaped);
    i += 1;
    while (i < l)
    {
        ch = s.charAt(i++);
        p += ch;
        if (delim === ch && !esc) break;
        esc = is_escaped ? (!esc && ('\\' === ch)) : false;
    }
    return p;
};
Xpresion.parse_re_flags = parse_re_flags;

Xpresion.parse = function( xpr, conf ) {
    var expr, l

        ,e, ch, v, i
        ,m, t, AST, OPS, NOPS, t_index

        ,reduce = Xpresion.reduce
        ,get_entry = Alias.get_entry

        ,block
        ,t_var_is_also_ident = !hasOwnProperty.call(conf.RE,'t_var')
        ,evaluator, block_rest
        ,err = 0, errpos, errmsg, errors = {err: false, msg: ''}
    ;

    expr = String(xpr.source);
    l = expr.length; i = 0;
    xpr._cnt = 0;
    xpr._symbol_table = { };
    xpr._cache = { };
    xpr.variables = { };
    AST = [ ]; OPS = [ ]; NOPS = [ ];
    t_index = 0;
    err = 0;
    while ( i < l )
    {
        ch = expr.charAt( i );

        // use customized (escaped) delimited blocks here
        // TODO: add a "date" block as well with #..#
        if ( block = get_entry(conf.BLOCKS, ch) ) // string or regex or date ('"`#)
        {
            v = block.parse(expr, i, l, ch);
            if ( false !== v )
            {
                i += v.length;
                if ('function' === typeof block.rest)
                {
                    block_rest = block.rest(expr, i, l);
                    if (!block_rest) block_rest = '';
                }
                else
                {
                    block_rest = '';
                }

                i += block_rest.length;

                t = xpr.t_block( conf, v, block.type, block_rest );
                if ( false !== t )
                {
                    t_index += 1;
                    AST.push( t.node(null, t_index) );
                    continue;
                }
            }
        }

        e = expr.slice( i );

        if ( m = e.match( conf.RE.t_spc ) ) // space
        {
            i += m[ 0 ].length;
            continue;
        }

        if ( m = e.match( conf.RE.t_num ) ) // number
        {
            t = xpr.t_liter( conf, m[ 1 ], T_NUM );
            if ( false !== t )
            {
                t_index+=1;
                AST.push( t.node(null, t_index) );
                i += m[ 0 ].length;
                continue;
            }
        }

        if ( m = e.match( conf.RE.t_ident ) ) // ident, reserved, function, operator, etc..
        {
            t = xpr.t_liter( conf, m[ 1 ], T_IDE ); // reserved keyword
            if ( false !== t )
            {
                t_index+=1;
                AST.push( t.node(null, t_index) );
                i += m[ 0 ].length;
                continue;
            }
            t = xpr.t_op( conf, m[ 1 ] ); // (literal) operator
            if ( false !== t )
            {
                t_index+=1;
                reduce( AST, OPS, NOPS, t, t_index, errors );
                if ( errors.err )
                {
                    err = 1;
                    errmsg = errors.msg;
                    break;
                }
                i += m[ 0 ].length;
                continue;
            }
            if ( t_var_is_also_ident )
            {
                t = xpr.t_var( conf, m[ 1 ] ); // variables are also same identifiers
                if ( false !== t )
                {
                    t_index+=1;
                    AST.push( t.node(null, t_index) );
                    i += m[ 0 ].length;
                    continue;
                }
            }
        }

        if ( m = e.match( conf.RE.t_special ) ) // special symbols..
        {
            v = m[ 1 ]; t = false;
            while ( v.length > 0 ) // try to match maximum length op/func
            {
                t = xpr.t_op( conf, v ); // function, (non-literal) operator
                if ( false !== t ) break;
                v = v.slice( 0, -1 );
            }
            if ( false !== t )
            {
                t_index+=1;
                reduce( AST, OPS, NOPS, t, t_index, errors );
                if ( errors.err )
                {
                    err = 1;
                    errmsg = errors.msg;
                    break;
                }
                i += v.length;
                continue;
            }
        }

        if ( !t_var_is_also_ident && (m = e.match( conf.RE.t_var )) ) // variables
        {
            t = xpr.t_var( conf, m[ 1 ] );
            if ( false !== t )
            {
                t_index+=1;
                AST.push( t.node(null, t_index) );
                i += m[ 0 ].length;
                continue;
            }
        }

        if ( m = e.match( conf.RE.t_nonspc ) ) // other non-space tokens/symbols..
        {
            t = xpr.t_liter( conf, m[ 1 ], T_LIT ); // reserved keyword
            if ( false !== t )
            {
                t_index+=1;
                AST.push( t.node(null, t_index) );
                i += m[ 0 ].length;
                continue;
            }
            t = xpr.t_op( conf, m[ 1 ] ); // function, other (non-literal) operator
            if ( false !== t )
            {
                t_index+=1;
                reduce( AST, OPS, NOPS, t, t_index, errors );
                if ( errors.err )
                {
                    err = 1;
                    errmsg = errors.msg;
                    break;
                }
                i += m[ 0 ].length;
                continue;
            }
            /*t = xpr.t_tok( conf, m[ 1 ] );
            t_index+=1;
            AST.push( t.node(null, t_index) ); // pass-through ..
            i += m[ 0 ].length;*/
            //continue;
            err = 1;
            errmsg = 'Unknown token "'+m[0]+'"'; // exit with error
            break;

        }
    }

    if ( !err )
    {
        reduce( AST, OPS, NOPS );

        if ( (1 !== AST.length) || (OPS.length > 0) )
        {
            err = 1;
            errmsg = 'Parse Error, Mismatched Parentheses or Operators';
        }
    }

    if ( !err )
    {
        try {

            evaluator = xpr.compile( AST[0], conf );

        } catch( e ) {

            err = 1;
            errmsg = 'Compilation Error, ' + e.toString() + '';
        }
    }

    NOPS = null;
    OPS = null;
    AST = null;
    xpr._symbol_table = null;

    if ( err )
    {
        evaluator = null;
        xpr.variables = [ ];
        xpr._cnt = 0;
        xpr._cache = { };
        xpr.evaluatorString = '';
        xpr.evaluator = xpr.dummy_evaluator;
        throw new Error( 'Xpresion Error: ' + errmsg + ' at "' + expr + '"');
    }
    else
    {
        // make array
        xpr.variables = Keys( xpr.variables );
        xpr.evaluatorString = evaluator[0];
        xpr.evaluator = evaluator[1];
    }

    return xpr;
};

Xpresion.render = function( tok, args ) {
    return tok.render( args );
    //return Tok.render(tok, args);
};

Xpresion.GET = function( obj, keys ) {
    if ( !keys || !keys.length ) return obj;
    var i = 0, l = keys.length, o = obj, k;
    while( i < l )
    {
        k = keys[i++];
        if ( !o )
        {
            break;
        }
        if ( null != o[k] )
        {
            o = o[k];
        }
        else
        {
            break;
        }
    }
    return i===l ? o : null;
};

function Tok( type, input, output, value )
{
    var self = this;
    if ( !(self instanceof Tok) ) return new Tok( type, input, output, value );
    self.type = type;
    self.input = input;
    self.output = output;
    self.value = value || null;
    self.priority = 1000;
    self.parity = 0;
    self.arity = 0;
    self.arity_min = 0;
    self.arity_max = 0;
    self.associativity = DEFAULT;
    self.fixity = INFIX;
    self.parenthesize = false;
    self.revert = false;
}
Xpresion.Tok = Tok;
Tok.render = function( t, args ) { return (t instanceof Tok) ? t.render(args||[]) : String(t); };
Tok[PROTO] = {
    constructor: Tok

    ,type: null
    ,input: null
    ,output: null
    ,value: null
    ,priority: 1000
    ,parity: 0
    ,arity: 0
    ,arity_min: 0
    ,arity_max: 0
    ,associativity: DEFAULT
    ,fixity: INFIX
    ,parenthesize: false
    ,revert: false

    ,dispose: function( ) {
        var self = this;
        self.type = null;
        self.input = null;
        self.output = null;
        self.value = null;
        self.priority = null;
        self.parity = null;
        self.arity = null;
        self.arity_min = null;
        self.arity_max = null;
        self.associativity = null;
        self.fixity = null;
        self.parenthesize = null;
        self.revert = null;
        return self;
    }
    ,setType: function( type ) {
        this.type = type;
        return this;
    }
    ,setParenthesize: function( bool ) {
        this.parenthesize = !!bool;
        return this;
    }
    ,setReverse: function( bool ) {
        this.revert = !!bool;
        return this;
    }
    ,render: function( args ) {
        var self = this,
            token = self.output,
            p = self.parenthesize,
            lparen = p ? Xpresion.LPAREN : '',
            rparen = p ? Xpresion.RPAREN : '',
            out
        ;
        if (!args) args = [];
        args.unshift(self.input);
        if ( token instanceof GrammarTemplate ) out = token.render( {'$':args} );
        else                                    out = String(token);
        return lparen + out + rparen;
    }
    ,node: function( args, pos ) {
        return Node(this.type, this.arity, this, !!args ? args : null, pos||0)
    }
    ,toString: function( ) {
        return String(this.output);
    }
};
EMPTY_TOKEN = Xpresion.EMPTY_TOKEN = Tok(T_EMPTY, '', '');

function Op( input, output, otype, fixity, associativity, priority, /*arity,*/ ofixity )
{
    var self = this;
    if ( !(self instanceof Op) )
        return new Op(input, output, otype, fixity, associativity, priority, /*arity,*/ ofixity);

    input = null==input ? '' : input;
    output = null==output ? '' : output;
    var opdef = Op.parse_definition( input );
    self.type = opdef[0];
    self.opdef = opdef[1];
    self.parts = opdef[2];

    if ( !(output instanceof GrammarTemplate) ) output = new GrammarTemplate(String(output));

    Tok.call(self, self.type, self.parts[0], output);

    self.fixity = null != fixity ? fixity : PREFIX;
    self.associativity = null != associativity ? associativity : DEFAULT;
    self.priority = null != priority ? priority : 1000;
    self.arity = opdef[3];
    self.arity_min = opdef[4];
    self.arity_max = opdef[5];
    //self.arity = arity || 0;
    self.otype = null != otype ? otype : T_MIX;
    self.ofixity = null != ofixity ? ofixity : self.fixity;
    self.parenthesize = false;
    self.revert = false;
    self.morphes = null;
}
Xpresion.Op = Op;
Op.Condition = function( f ) {
    if ( is_string(f[0]) )
    {
        try {
            f[0] = F('curr,Xpresion', 'return '+f[0]+';');
        } catch(ex) {
            f[0] = null;
        }
    }
    return [
        'function'===typeof f[0] ? f[0] : null,
        f[1]
    ];
};
Op.parse_definition = function( op_def ) {
    var parts = [], op = [], num_args,
        arity = 0, arity_min = 0, arity_max = 0, type, i, l;
    if ( is_string(op_def) )
    {
        // assume infix, arity = 2;
        op_def = [1,op_def,1];
    }
    else
    {
        op_def = [].concat(op_def);
    }
    for (i=0,l=op_def.length; i<l; i++)
    {
        if ( is_string( op_def[i] ) )
        {
            parts.push(op_def[i]);
            op.push([1, i, op_def[i]]);
        }
        else
        {
            op.push([0, i, op_def[i]]);
            num_args = abs(op_def[i]);
            arity += num_args;
            arity_max += num_args;
            arity_min += op_def[i];
        }
    }
    if ( 1 === parts.length && 1 === op.length )
    {
        op = [[0, 0, 1], [1, 1, parts[0]], [0, 2, 1]];
        arity_min = arity_max = arity = 2; type = T_OP;
    }
    else
    {
        type = parts.length > 1 ? T_N_OP : T_OP;
    }
    return [type, op, parts, arity, max(0, arity_min), arity_max];
};
Op.match_args = function( expected_args, args_pos, op_queue, token_queue ) {
    var tl = token_queue.length,
        t = tl-1, num_args = 0,
        num_expected_args = abs(expected_args),
        p2, INF = -10
    ;
    while (num_args < num_expected_args || t >= 0 /*|| o < ol*/ )
    {
        p2 = t >= 0 ? token_queue[t].pos : INF;
        if ( args_pos === p2 )
        {
            num_args++;
            args_pos--;
            t--;
        }
        else break;
    }
    return num_args >= num_expected_args ? num_expected_args : (expected_args <= 0 ? 0 : false);
};
Op[PROTO] = Extend( Tok[PROTO] );
Op[PROTO].otype = null;
Op[PROTO].ofixity = null;
Op[PROTO].opdef = null;
Op[PROTO].parts = null;
Op[PROTO].morphes = null;
Op[PROTO].dispose = function( ) {
    var self = this;
    self.otype = null;
    self.ofixity = null;
    self.opdef = null;
    self.parts = null;
    self.morphes = null;
    Tok[PROTO].dispose.call(self);
    return self;
};
Op[PROTO].Polymorphic = function(morphes) {
    var self = this;
    self.type = T_POLY_OP;
    self.morphes = (morphes || [ ]).map( Op.Condition );
    return self;
};
Op[PROTO].morph = function( args ) {
    var morphes = this.morphes, l = morphes.length, i = 0,
        op, minop = morphes[0][1], found = false, matched, nargs, deduced_type, indt, indo;

    // [pos,token_queue,op_queue]
    if (args.length < 7)
    {
        args.push(args[1].length ? args[1][args[1].length-1] : false);
        args.push(args[2].length ? args[2][0] : false);
        args.push(args[4] ? (args[4].pos+1===args[0]) : false);
        deduced_type = 0; // T_DUM
        indt = args[1].length-1; indo = 0;
        // try to inherit type from other tokens/ops if current type is T_DUM(0), eg for bracket operator
        while (!deduced_type)
        {
            if (indt>=0 && indo<args[2].length && indo+1<args[2].length && (args[2][indo+1].node instanceof Xpresion.Func))
                deduced_type = /*args[1][indt].pos>args[2][indo].pos ?*/ args[1][indt--].type /*: args[2][indo++].type*/;
            else if (indo<args[2].length)
                deduced_type = args[2][indo++].type;
            else if (indt>=0)
                deduced_type = args[1][indt--].type;
            else break;
        }
        args.push(deduced_type);
    }
    // array('${POS}'=>0,'${TOKS}'=>1,'${OPS}'=>2,'${TOK}'=>3,'${OP}'=>4,'${PREV_IS_OP}'=>5,'${DEDUCED_TYPE}'=>6)
    nargs = {
        POS: args[0],
        TOKS: args[1],
        OPS: args[2],
        TOK: args[3],
        OP: args[4],
        PREV_IS_OP: args[5],
        DEDUCED_TYPE: args[6]
        //DEDUCED_TYPE_STR: Xpresion.TYPES[deduced_type]
    };

    while ( i < l )
    {
        op = morphes[i++];
        matched = Boolean(op[0]( nargs, Xpresion ));
        if ( true === matched )
        {
            op = op[1];
            found = true;
            break;
        }
        if ( op[1].priority >= minop.priority ) minop = op[1];
    }
    // try to return minimum priority operator, if none matched
    if ( !found ) op = minop;
    // nested polymorphic op, if any
    while ( T_POLY_OP === op.type ) op = op.morph( args );
    return op;
};
Op[PROTO].render = function( args ) {
    if (!args || !args.length) args = ['',''];
    var self = this, i,
        output_type = self.otype,
        op = self.output,
        p = self.parenthesize,
        lparen = p ? Xpresion.LPAREN : '',
        rparen = p ? Xpresion.RPAREN : '',
        comma = Xpresion.COMMA,
        out_fixity = self.ofixity,
        numargs = args.length, out
    ;
    //if ( (T_DUM === output_type) && numargs )
    //    output_type = args[ 0 ].type;

    //args = args.map( Tok.render );

    if ( op instanceof GrammarTemplate )
        out = lparen + op.render( {'$':args} ) + rparen;
    else if ( INFIX === out_fixity )
        out = lparen + args.join(op) + rparen;
    else if ( POSTFIX === out_fixity )
        out = lparen + args.join(comma) + rparen + op;
    else/* if ( PREFIX === out_fixity )*/
        out = op + lparen + args.join(comma) + rparen;
    return Tok(output_type, out, out);
};
Op[PROTO].validate = function( pos, op_queue, token_queue ) {
    var self = this, opdef = self.opdef,
        msg = '', num_args = 0;
    if ( 0 === opdef[0][0] ) // expecting argument(s)
    {
        num_args = Op.match_args(opdef[0][2], pos-1, op_queue, token_queue );
        if ( false === num_args )
            msg = 'Operator "' + self.input + '" expecting ' + opdef[0][2] + ' prior argument(s)';
    }
    return [num_args, msg];
};
Op[PROTO].node = function( args, pos, op_queue, token_queue ) {
    args = args || [];
    pos = pos || 0;
    var self = this, otype = self.otype, n;
    if ( self.revert ) args.reverse( );
    if ( (T_DUM === otype) && args.length ) otype = args[ 0 ].type;
    else if ( args.length ) args[0].type = otype;
    n = new Node(otype, self.arity, self, args, pos);
    if ( (T_N_OP === self.type) && (arguments.length > 2) )
    {
        n.op_parts = self.parts.slice(1);
        n.op_def = self.opdef.slice(0 === self.opdef[0][0] ? 2 : 1);
        n.op_index = arguments[2].length+1;
    }
    return n;
};

function Func( input, output, otype, priority, arity, associativity, ofixity )
{
    var self = this;
    if ( !(self instanceof Func) ) return new Func(input, output, otype, priority, arity, associativity, ofixity);
    input = null==input ? '' : input;
    output = null==output ? '' : output;
    Op.call(self,
        is_string(input) ? [input, null!=arity ? arity : 1] : input,
        output,
        null!=otype ? otype : T_MIX,
        PREFIX,
        null!=associativity ? associativity : RIGHT,
        null!=priority ? priority : 1,
        null!=ofixity ? ofixity : PREFIX
    );
    self.type = T_FUN;
}
Xpresion.Func = Func;
Func[PROTO] = Extend( Op[PROTO] );

// Methods
Xpresion[PROTO] = {
    constructor: Xpresion

    ,source: null
    ,variables: null
    ,evaluatorString: null
    ,evaluator: null

    ,_cnt: 0
    ,_cache: null
    ,_symbol_table: null
    ,dummy_evaluator: null

    ,dispose: function( ) {
        var self = this;
        self.dummy_evaluator = null;

        self.source = null;
        self.variables = null;
        self.evaluatorString = null;
        self.evaluator = null;

        self._cnt = null;
        self._symbol_table = null;
        self._cache = null;

        return self;
    }

    ,compile: function( AST, conf ) {
        // depth-first traversal and rendering of Abstract Syntax Tree (AST)
        if ( !conf )
            conf = Xpresion.defaultConfiguration();
        var evaluator_str = Node.DFT( AST, Xpresion.render, true );
        return [evaluator_str, evaluator_factory(evaluator_str,conf.FN,this._cache)];
    }

    ,evaluate: function( data ) {
        if ( 1 > arguments.length ) data = {};
        return 'function' === typeof this.evaluator ? this.evaluator( data ) : null;
    }

    ,debug: function( data ) {
        var self = this;
        var out = [
            'Expression: ' + self.source,
            'Variables : [' + self.variables.join(',') + ']',
            'Evaluator : ' + self.evaluatorString
        ];
        if ( arguments.length )
        {
            out.push('Data      : ' + toJSON(data, null, 4));
            out.push('Result    : ' + toJSON(self.evaluate(data)));
        }
        return out.join("\n");
    }

    ,toString: function( ) {
        return '[Xpresion source]: ' + String(this.source) + '';
    }

    ,t_liter: function( conf, token, type ) {
        if ( T_NUM === type ) return Tok(T_NUM, token, token);
        return Alias.get_entry(conf.RESERVED, token.toLowerCase( ));
    }

    ,t_block: function( conf, token, type, rest ) {
        rest = rest || '';
        if ( T_STR === type )
        {
            return Tok(T_STR, token, token);
        }
        else if ( T_REX === type )
        {
            var sid = 're_'+token+rest, id, rs;
            if ( hasOwnProperty.call(this._symbol_table,sid) )
            {
                id = this._symbol_table[sid];
            }
            else
            {
                id = 're_' + (++this._cnt);
                rs = token.slice(1,-1);//.replace(/\\/g, '\\\\')
                this._cache[ id ] = RE(rs, rest);
                this._symbol_table[sid] = id;
            }
            return Tok(T_REX, token, 'Cache.'+id+'');
        }
        return false;
    }

    ,t_var: function( conf, token ) {
        var parts = token.split('.'), main = parts[0], keys;
        if ( !hasOwnProperty.call(this.variables, main ) ) this.variables[ main ] = main;
        if ( 1 < parts.length )
        {
            keys = '["' + parts.slice(1).join('","') + '"]';
            return Tok(T_VAR, main, 'Xpresion.GET(Var["' + main + '"],'+keys+')');
        }
        else
        {
            return Tok(T_VAR, main, 'Var["' + main + '"]');
        }
        //return Tok(T_VAR, token, 'Var["' + token.split('.').join('"]["') + '"]');
    }

    ,t_op: function( conf, token ) {
        var op = false;
        op = Alias.get_entry(conf.FUNCTIONS, token);
        if ( false === op ) op = Alias.get_entry(conf.OPERATORS, token);
        return op;
    }

    ,t_tok: function( conf, token ) { return Tok(T_MIX, token, token); }
};

Xpresion.init = function( ) {
    if ( __inited ) return;

    __inited = true;

    // e.g https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
    Xpresion.defaultConfiguration(Configuration({
    // regular expressions for tokens
    // ===============================
    're' : {
     't_spc'        :  /^(\s+)/
    ,'t_nonspc'     :  /^(\S+)/
    ,'t_special'    :  /^([*.\\\-+\/\^\$\(\)\[\]|?<:>&~%!#@=_,;{}]+)/
    ,'t_num'        :  /^(\d+(\.\d+)?)/
    ,'t_ident'      :  /^([a-zA-Z_][a-zA-Z0-9_]*)\b/
    ,'t_var'        :  /^\$([a-zA-Z0-9_][a-zA-Z0-9_.]*)\b/
    }

    // block-type tokens (eg strings and regexes)
    // ==========================================
    ,'blocks' : {
     '\'': {
        'type': T_STR,
        'parse': Xpresion.parse_delimited_block
     }
    ,'"': Alias('\'')
    ,'`': {
        'type': T_REX,
        'parse': Xpresion.parse_delimited_block,
        'rest': Xpresion.parse_re_flags
    }
    }

    // reserved keywords and literals
    // ===============================
    ,'reserved' : {
     'null'     : Tok(T_IDE, 'null', 'null')
    ,'false'    : Tok(T_BOL, 'false', 'false')
    ,'true'     : Tok(T_BOL, 'true', 'true')
    ,'infinity' : Tok(T_NUM, 'Infinity', 'Infinity')
    ,'nan'      : Tok(T_NUM, 'NaN', 'NaN')
    // aliases
    ,'none'     : Alias('null')
    ,'inf'      : Alias('infinity')
    }

    // operators
    // ==========
    ,'operators' : {
    // bra-kets as n-ary operators
    // negative number of arguments, indicate optional arguments (experimental)
     '('    :   {
                    'input'         : ['(',-1,')']
                    ,'output'       : '<$.0>'
                    ,'otype'        : T_DUM
                    ,'fixity'       : POSTFIX
                    ,'associativity': RIGHT
                    ,'priority'     : 0
                }
    ,')'    :   {'input':[-1,')']}
    ,'['    :   {
                    'input'         : ['[',-1,']']
                    ,'output'       : '\\[<$.0>\\]'
                    ,'otype'        : T_ARY
                    ,'fixity'       : POSTFIX
                    ,'associativity': RIGHT
                    ,'priority'     : 2
                }
    ,']'    :   {'input':[-1,']']}
    ,','    :   {
                    'input'         : [1,',',1]
                    ,'output'       : '<$.0>,<$.1>'
                    ,'otype'        : T_DFT
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 103 // comma operator needs to have very low priority because it can break other expressions which are between commas
                }
    // n-ary (ternary) if-then-else operator
    ,'?'    :   {
                    'input'         : [1,'?',1,':',1]
                    ,'output'       : '(<$.0>?<$.1>:<$.2>)'
                    ,'otype'        : T_MIX
                    ,'fixity'       : INFIX
                    ,'associativity': RIGHT
                    ,'priority'     : 100
                }
    ,':'    :   {'input':[1,':',1]}

    ,'!'    :   {
                    'input'         : ['!',1]
                    ,'output'       : '!<$.0>'
                    ,'otype'        : T_BOL
                    ,'fixity'       : PREFIX
                    ,'associativity': RIGHT
                    ,'priority'     : 10
                }
    ,'~'    :   {
                    'input'         : ['~',1]
                    ,'output'       : '~<$.0>'
                    ,'otype'        : T_NUM
                    ,'fixity'       : PREFIX
                    ,'associativity': RIGHT
                    ,'priority'     : 10
                }
    ,'^'    :   {
                    'input'         : [1,'^',1]
                    ,'output'       : 'Math.pow(<$.0>,<$.1>)'
                    ,'otype'        : T_NUM
                    ,'fixity'       : INFIX
                    ,'associativity': RIGHT
                    ,'priority'     : 11
                }
    ,'*'    :   {
                    'input'         : [1,'*',1]
                    ,'output'       : '(<$.0>*<$.1>)'
                    ,'otype'        : T_NUM
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 20
                }
    ,'/'    :   {
                    'input'         : [1,'/',1]
                    ,'output'       : '(<$.0>/<$.1>)'
                    ,'otype'        : T_NUM
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 20
                }
    ,'%'    :   {
                    'input'         : [1,'%',1]
                    ,'output'       : '(<$.0>%<$.1>)'
                    ,'otype'        : T_NUM
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 20
                }
    // addition/concatenation/unary plus as polymorphic operators
    ,'+'    :   {'polymorphic':[
                // array concatenation
                [
                function(curr,Xpresion){return curr.TOK && (!curr.PREV_IS_OP) && (curr.DEDUCED_TYPE===Xpresion.T_ARY);},
                {
                    'input'         : [1,'+',1]
                    ,'output'       : 'Fn.ary_merge(<$.0>,<$.1>)'
                    ,'otype'        : T_ARY
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 25
                }
                ]
                // string concatenation
                ,[
                function(curr,Xpresion){return curr.TOK && (!curr.PREV_IS_OP) && (curr.DEDUCED_TYPE===Xpresion.T_STR);},
                {
                    'input'         : [1,'+',1]
                    ,'output'       : '(<$.0>+String(<$.1>))'
                    ,'otype'        : T_STR
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 25
                }
                ]
                // numeric addition
                ,[
                function(curr,Xpresion){return curr.TOK && !curr.PREV_IS_OP;},
                {
                    'input'         : [1,'+',1]
                    ,'output'       : '(<$.0>+<$.1>)'
                    ,'otype'        : T_NUM
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 25
                }
                ]
                // unary plus
                ,[
                function(curr,Xpresion){return (!curr.TOK) || curr.PREV_IS_OP;},
                {
                    'input'         : ['+',1]
                    ,'output'       : '<$.0>'
                    ,'otype'        : T_NUM
                    ,'fixity'       : PREFIX
                    ,'associativity': RIGHT
                    ,'priority'     : 4
                }
                ]
                ]}
    ,'-'    :   {'polymorphic':[
                // numeric subtraction
                [
                function(curr,Xpresion){return curr.TOK && !curr.PREV_IS_OP;},
                {
                    'input'         : [1,'-',1]
                    ,'output'       : '(<$.0>-<$.1>)'
                    ,'otype'        : T_NUM
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 25
                }
                ]
                // unary negation
                ,[
                function(curr,Xpresion){return (!curr.TOK) || curr.PREV_IS_OP;},
                {
                    'input'         : ['-',1]
                    ,'output'       : '(-<$.0>)'
                    ,'otype'        : T_NUM
                    ,'fixity'       : PREFIX
                    ,'associativity': RIGHT
                    ,'priority'     : 4
                }
                ]
                ]}
    ,'>>'   :   {
                    'input'         : [1,'>>',1]
                    ,'output'       : '(<$.0>\\>\\><$.1>)'
                    ,'otype'        : T_NUM
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 30
                }
    ,'<<'   :   {
                    'input'         : [1,'<<',1]
                    ,'output'       : '(<$.0>\\<\\<<$.1>)'
                    ,'otype'        : T_NUM
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 30
                }
    ,'>'    :   {
                    'input'         : [1,'>',1]
                    ,'output'       : '(<$.0>\\><$.1>)'
                    ,'otype'        : T_BOL
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 35
                }
    ,'<'    :   {
                    'input'         : [1,'<',1]
                    ,'output'       : '(<$.0>\\<<$.1>)'
                    ,'otype'        : T_BOL
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 35
                }
    ,'>='   :   {
                    'input'         : [1,'>=',1]
                    ,'output'       : '(<$.0>\\>=<$.1>)'
                    ,'otype'        : T_BOL
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 35
                }
    ,'<='   :   {
                    'input'         : [1,'<=',1]
                    ,'output'       : '(<$.0>\\<=<$.1>)'
                    ,'otype'        : T_BOL
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 35
                }
    ,'=='   :   {'polymorphic':[
                // array equivalence
                [
                function(curr,Xpresion){return curr.DEDUCED_TYPE===Xpresion.T_ARY;},
                {
                    'input'         : [1,'==',1]
                    ,'output'       : 'Fn.ary_eq(<$.0>,<$.1>)'
                    ,'otype'        : T_BOL
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 40
                }
                ]
                // default equivalence
                ,[
                function(curr,Xpresion){return true;},
                {
                    'input'         : [1,'==',1]
                    ,'output'       : '(<$.0>==<$.1>)'
                    ,'otype'        : T_BOL
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 40
                }
                ]
                ]}
    ,'!='   :   {
                    'input'         : [1,'!=',1]
                    ,'output'       : '(<$.0>!=<$.1>)'
                    ,'otype'        : T_BOL
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 40
                }
    ,'is'   :   {
                    'input'         : [1,'is',1]
                    ,'output'       : '(<$.0>===<$.1>)'
                    ,'otype'        : T_BOL
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 40
                }
    ,'matches': {
                    'input'         : [1,'matches',1]
                    ,'output'       : 'Fn.match(<$.1>,<$.0>)'
                    ,'otype'        : T_BOL
                    ,'fixity'       : INFIX
                    ,'associativity': NONE
                    ,'priority'     : 40
                }
    ,'in'   :   {
                    'input'         : [1,'in',1]
                    ,'output'       : 'Fn.contains(<$.1>,<$.0>)'
                    ,'otype'        : T_BOL
                    ,'fixity'       : INFIX
                    ,'associativity': NONE
                    ,'priority'     : 40
                }
    ,'&'    :   {
                    'input'         : [1,'&',1]
                    ,'output'       : '(<$.0>&<$.1>)'
                    ,'otype'        : T_NUM
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 45
                }
    ,'|'    :   {
                    'input'         : [1,'|',1]
                    ,'output'       : '(<$.0>|<$.1>)'
                    ,'otype'        : T_NUM
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 46
                }
    ,'&&'   :   {
                    'input'         : [1,'&&',1]
                    ,'output'       : '(<$.0>&&<$.1>)'
                    ,'otype'        : T_BOL
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 47
                }
    ,'||'   :   {
                    'input'         : [1,'||',1]
                    ,'output'       : '(<$.0>||<$.1>)'
                    ,'otype'        : T_BOL
                    ,'fixity'       : INFIX
                    ,'associativity': LEFT
                    ,'priority'     : 48
                }
    //------------------------------------------
    //                aliases
    //-------------------------------------------
    ,'or'    :  Alias( '||' )
    ,'and'   :  Alias( '&&' )
    ,'not'   :  Alias( '!' )
    }

    // functional operators
    // ====================
    ,'functions' : {
     'min'      : {
                    'input'     : 'min'
                    ,'output'   : 'Math.min(<$.0>)'
                    ,'otype'    : T_NUM
                }
    ,'max'      : {
                    'input'     : 'max'
                    ,'output'   : 'Math.max(<$.0>)'
                    ,'otype'    : T_NUM
                }
    ,'pow'      : {
                    'input'     : 'pow'
                    ,'output'   : 'Math.pow(<$.0>)'
                    ,'otype'    : T_NUM
                }
    ,'sqrt'     : {
                    'input'     : 'sqrt'
                    ,'output'   : 'Math.sqrt(<$.0>)'
                    ,'otype'    : T_NUM
                }
    ,'len'      : {
                    'input'     : 'len'
                    ,'output'   : 'Fn.len(<$.0>)'
                    ,'otype'    : T_NUM
                }
    ,'int'      : {
                    'input'     : 'int'
                    ,'output'   : 'parseInt(<$.0>)'
                    ,'otype'    : T_NUM
                }
    ,'float'    : {
                    'input'     : 'float'
                    ,'output'   : 'parseFloat(<$.0>)'
                    ,'otype'    : T_NUM
                }
    ,'str'      : {
                    'input'     : 'str'
                    ,'output'   : 'String(<$.0>)'
                    ,'otype'    : T_STR
                }
    ,'array'    : {
                    'input'     : 'array'
                    ,'output'   : 'Fn.ary(<$.0>)'
                    ,'otype'    : T_ARY
                }
    ,'clamp'    : {
                    'input'     : 'clamp'
                    ,'output'   : 'Fn.clamp(<$.0>)'
                    ,'otype'    : T_NUM
                }
    ,'sum'      : {
                    'input'     : 'sum'
                    ,'output'   : 'Fn.sum(<$.0>)'
                    ,'otype'    : T_NUM
                }
    ,'avg'      : {
                    'input'     : 'avg'
                    ,'output'   : 'Fn.avg(<$.0>)'
                    ,'otype'    : T_NUM
                }
    ,'time'     : {
                    'input'     : 'avg'
                    ,'output'   : 'Fn.time()'
                    ,'otype'    : T_NUM
                    ,'arity'    : 0
                }
    ,'date'     : {
                    'input'     : 'date'
                    ,'output'   : 'Fn.date(<$.0>)'
                    ,'otype'    : T_STR
                }
    //---------------------------------------
    //                aliases
    //----------------------------------------
     // ...
    }

    // runtime (implementation) functions
    // ==================================
    ,'runtime' : {
    'clamp'        : function( v, m, M ) {
                        if ( m > M ) return v > m ? m : (v < M ? M : v);
                        else return v > M ? M : (v < m ? m : v);
                    }
    ,'len'          : function( v ) {
                        if ( v )
                        {
                            if ( is_array(v) || is_string(v) ) return v.length;
                            if ( is_object(v) ) return Keys(v).length;
                            return 1;
                        }
                        return 0;
                    }
    ,'sum'          : function( ) {
                        var args = arguments, i, l, s = 0;
                        if (args[0] && is_array(args[0]) ) args = args[0];
                        l = args.length;
                        if ( l > 0 ) { for(i=0; i<l; i++) s += args[i]; }
                        return s;
                    }
    ,'avg'          : function( ) {
                        var args = arguments, i, l, s = 0;
                        if (args[0] && is_array(args[0]) ) args = args[0];
                        l = args.length;
                        if ( l > 0 ) { for(i=0; i<l; i++) s += args[i]; s = s/l;}
                        return s;
                    }
    ,'ary'          : function( x ) {
                        return is_array(x) ? x : [x];
                    }
    ,'ary_eq'       : function( a1, a2 ) {
                        var l = a1.length, i;
                        if ( l===a2.length )
                        {
                            for (i=0; i<l; i++)
                                if ( a1[i]!=a2[i] ) return false;
                        }
                        else return false;
                        return true;
                    }
    ,'ary_merge'    : function(a1, a2) {
                        return [ ].concat( a1 ).concat( a2 );
                    }
    ,'match'        : function( str, regex ) {
                        return regex.test( str );
                    }
    ,'contains'     : function( o, i ) {
                        return is_array(o) || is_string(o) ? -1 < o.indexOf( i ) : hasOwnProperty.call(o, i);
                    }
    ,'time'         : time
    ,'date'         : date
    }
    }));
};

// init it
Xpresion.init( );

// export it
return Xpresion;
});
For more information send a message to info at phpclasses dot org.