Skip to content

Xpresion: simple and flexible Expression Parser Engine with variables support for PHP, Python, JavaScript

Notifications You must be signed in to change notification settings

foo123/Xpresion

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Xpr3s10n

A simple, fast and flexible eXpression Parser Engine (with custom functions and variables support) for PHP, Python, Node.js and Browser

Xpresion

light-weight (~29kB minified, ~10kB zipped)

version 1.0.1 Xpresion.js, Xpresion.min.js

see also:

  • ModelView a simple, fast, powerful and flexible MVVM framework for JavaScript
  • tico a tiny, super-simple MVC framework for PHP
  • LoginManager a simple, barebones agnostic login manager for PHP, JavaScript, Python
  • SimpleCaptcha a simple, image-based, mathematical captcha with increasing levels of difficulty for PHP, JavaScript, Python
  • Dromeo a flexible, and powerful agnostic router for PHP, JavaScript, Python
  • PublishSubscribe a simple and flexible publish-subscribe pattern implementation for PHP, JavaScript, Python
  • Importer simple class & dependency manager and loader for PHP, JavaScript, Python
  • Contemplate a fast and versatile isomorphic template engine for PHP, JavaScript, Python
  • HtmlWidget html widgets, made as simple as possible, both client and server, both desktop and mobile, can be used as (template) plugins and/or standalone for PHP, JavaScript, Python (can be used as plugins for Contemplate)
  • Paginator simple and flexible pagination controls generator for PHP, JavaScript, Python
  • Formal a simple and versatile (Form) Data validation framework based on Rules for PHP, JavaScript, Python
  • Dialect a cross-vendor & cross-platform SQL Query Builder, based on GrammarTemplate, for PHP, JavaScript, Python
  • DialectORM an Object-Relational-Mapper (ORM) and Object-Document-Mapper (ODM), based on Dialect, for PHP, JavaScript, Python
  • Unicache a simple and flexible agnostic caching framework, supporting various platforms, for PHP, JavaScript, Python
  • Xpresion a simple and flexible eXpression parser engine (with custom functions and variables support), based on GrammarTemplate, for PHP, JavaScript, Python
  • Regex Analyzer/Composer Regular Expression Analyzer and Composer for PHP, JavaScript, Python

Contents

Xpresion Live

Features

(v. 1.0.0+)

Xpresion is not an expression parser, it does not parse expressions. It is a parser engine. It builds parsers, which parse (custom) expressions. Effectively it is a small rewrite system

This is accomplished by configuring the Xpresion engine to fit the needed expression syntax. Configuration is intutive, easy and flexible (see examples).

However, since Xpresion is a parser engine, adding a default configuration, it can itself be a parser as well (out-of-the-box).

NOTE: Xpresion (v.1.0.0+) uses Grammar Templates for operator output. This is more flexible than simply using String Templates as previously, since it supports blocks of optional code, default values, optional placeholders and many other things. Only make sure to escape (with \ character) all "<",">","[","]" literal characters inside the output you define for operators and functions (see default configuration example below)

Features:

  1. simple, intuitive, flexible, fast
  2. support an adequate expression syntax out of the box (i.e numbers, strings, regexes, reserved tokens, arithmetical/logical operators, ternary operators, some advanced functions, variables)
  3. highly flexible and configurable (regexes, outputs, functions, operators, aliases, runtime functions, etc..)
  4. support (user-defined) variables, (user-defined) functions
  5. define custom operators (prefix, infix, postfix, left/right association, etc..) seamlessly
  6. define polymorphic operators / operator overloading seamlessly (e.g subtraction and unary negation)
  7. define n-ary operators seamlessly (e.g ternary if-then-else, etc..)
  8. define aliases of operators/functions seamlessly
  9. define new custom expression syntaxes easily (e.g via another configuration or by extending defaultConfiguration )
  10. implementations for Node.js and Browser, PHP, Python (only one class per implementation)

If you use Xpresion in your application and you want to share it, feel free to submit an example link

Live Examples

Examples

(see test/test.js)

javascript

var echo = console.log, 
    Xpresion = require(__dirname+'/../src/js/Xpresion.js')
;

function test_expr(expr, evaluate)
{
    evaluate = evaluate || false;
    echo('==========================================');
    var xpr = null;
    try {
        // uses defaultConfiguration by default
        xpr = Xpresion(expr);
    } catch (err ) {
        xpr = null;
        echo(err.toString());
    }
    if ( xpr )
    {
        var debug = evaluate ? xpr.debug(evaluate instanceof Object ? evaluate : {}) : xpr.debug();
        echo(debug);
    }
}

echo( 'Xpresion.VERSION ' + Xpresion.VERSION + "\n" );

test_expr('13');
test_expr('1.32');
test_expr('-0.12');
test_expr('-3');
test_expr('("1,2,3")+3',true);
test_expr('"1,2,3"+3',true);
test_expr('"1,2,3"+3+4',true);
test_expr('[1,2,3]+3',true);
test_expr('-3+2',true);
test_expr('1-3+2',true);
test_expr('1+-3',true);
test_expr('+1+3',true);
test_expr('2*-1');
test_expr('2*(-1)');
test_expr('2^-1');
test_expr('2^(-1)');
test_expr('2^-1^3');
test_expr('-2^-1^3');
test_expr('2^(-1)^3');
test_expr('sqrt(2)', true);
test_expr('$v');
test_expr('$v.key.0.key', {'v':{'key':[{'key':'correct'},'foo']}});
test_expr('True');
test_expr('"string"');
test_expr('["a","rra","y"]');
test_expr('`^regex?`i');
test_expr('0 == 1');
test_expr('TRUE == False');
test_expr('TRUE is False');
test_expr('1+2');
test_expr('1+2+3');
test_expr('1+2*3');
test_expr('1*2+3');
test_expr('1*2*3');
test_expr('1+2/3');
test_expr('1*2/3');
test_expr('1^2');
test_expr('1^2^3');
test_expr('1^(2^3)');
test_expr('(1^2)^3');
test_expr('((1^2))^3');
test_expr('`^regex?`i matches "string"');
test_expr('`^regex?`i matches "string" and `^regex?`i matches "string2"');
test_expr('$v in ["a","b","c"]');
test_expr('1 ? : (1+2) (3+4)');
test_expr('1 ? sum(1,2) : (3+4)');
test_expr('1 ? 1+2 : (3+4)');
test_expr('1 ? (2+3) : 2 ? (3+4) : (4+5)');
test_expr('date("Y-m-d H:i:s")', true);
test_expr('time()', true);
test_expr('date("Y-m-d H:i:s", time())');
test_expr('pow(1,pow(2,3))');
test_expr('pow(pow(2,3),4)');
test_expr('pow(pow(1,2),pow(2,3))');

output

Xpresion.VERSION 1.0.0

==========================================
Expression: 13
Variables : []
Evaluator : 13
==========================================
Expression: 1.32
Variables : []
Evaluator : 1.32
==========================================
Expression: -0.12
Variables : []
Evaluator : (-0.12)
==========================================
Expression: -3
Variables : []
Evaluator : (-3)
==========================================
Expression: ("1,2,3")+3
Variables : []
Evaluator : ("1,2,3"+String(3))
Data      : {}
Result    : "1,2,33"
==========================================
Expression: "1,2,3"+3
Variables : []
Evaluator : ("1,2,3"+String(3))
Data      : {}
Result    : "1,2,33"
==========================================
Expression: "1,2,3"+3+4
Variables : []
Evaluator : (("1,2,3"+String(3))+String(4))
Data      : {}
Result    : "1,2,334"
==========================================
Expression: [1,2,3]+3
Variables : []
Evaluator : Fn.ary_merge([1,2,3],3)
Data      : {}
Result    : [1,2,3,3]
==========================================
Expression: -3+2
Variables : []
Evaluator : ((-3)+2)
Data      : {}
Result    : -1
==========================================
Expression: 1-3+2
Variables : []
Evaluator : ((1-3)+2)
Data      : {}
Result    : 0
==========================================
Expression: 1+-3
Variables : []
Evaluator : (1+(-3))
Data      : {}
Result    : -2
==========================================
Expression: +1+3
Variables : []
Evaluator : (1+3)
Data      : {}
Result    : 4
==========================================
Expression: 2*-1
Variables : []
Evaluator : (2*(-1))
==========================================
Expression: 2*(-1)
Variables : []
Evaluator : (2*(-1))
==========================================
Expression: 2^-1
Variables : []
Evaluator : Math.pow(2,(-1))
==========================================
Expression: 2^(-1)
Variables : []
Evaluator : Math.pow(2,(-1))
==========================================
Expression: 2^-1^3
Variables : []
Evaluator : Math.pow(2,Math.pow((-1),3))
==========================================
Expression: -2^-1^3
Variables : []
Evaluator : Math.pow((-2),Math.pow((-1),3))
==========================================
Expression: 2^(-1)^3
Variables : []
Evaluator : Math.pow(2,Math.pow((-1),3))
==========================================
Expression: sqrt(2)
Variables : []
Evaluator : Math.sqrt(2)
Data      : {}
Result    : 1.4142135623730951
==========================================
Expression: $v
Variables : [v]
Evaluator : Var["v"]
==========================================
Expression: $v.key.0.key
Variables : [v]
Evaluator : Xpresion.GET(Var["v"],["key","0","key"])
Data      : {
    "v": {
        "key": [
            {
                "key": "correct"
            },
            "foo"
        ]
    }
}
Result    : "correct"
==========================================
Expression: True
Variables : []
Evaluator : true
==========================================
Expression: "string"
Variables : []
Evaluator : "string"
==========================================
Expression: ["a","rra","y"]
Variables : []
Evaluator : ["a","rra","y"]
==========================================
Expression: `^regex?`i
Variables : []
Evaluator : Cache.re_1
==========================================
Expression: 0 == 1
Variables : []
Evaluator : (0==1)
==========================================
Expression: TRUE == False
Variables : []
Evaluator : (true==false)
==========================================
Expression: TRUE is False
Variables : []
Evaluator : (true===false)
==========================================
Expression: 1+2
Variables : []
Evaluator : (1+2)
==========================================
Expression: 1+2+3
Variables : []
Evaluator : ((1+2)+3)
==========================================
Expression: 1+2*3
Variables : []
Evaluator : (1+(2*3))
==========================================
Expression: 1*2+3
Variables : []
Evaluator : ((1*2)+3)
==========================================
Expression: 1*2*3
Variables : []
Evaluator : ((1*2)*3)
==========================================
Expression: 1+2/3
Variables : []
Evaluator : (1+(2/3))
==========================================
Expression: 1*2/3
Variables : []
Evaluator : ((1*2)/3)
==========================================
Expression: 1^2
Variables : []
Evaluator : Math.pow(1,2)
==========================================
Expression: 1^2^3
Variables : []
Evaluator : Math.pow(1,Math.pow(2,3))
==========================================
Expression: 1^(2^3)
Variables : []
Evaluator : Math.pow(1,Math.pow(2,3))
==========================================
Expression: (1^2)^3
Variables : []
Evaluator : Math.pow(Math.pow(1,2),3)
==========================================
Expression: ((1^2))^3
Variables : []
Evaluator : Math.pow(Math.pow(1,2),3)
==========================================
Expression: `^regex?`i matches "string"
Variables : []
Evaluator : Fn.match("string",Cache.re_1)
==========================================
Expression: `^regex?`i matches "string" and `^regex?`i matches "string2"
Variables : []
Evaluator : (Fn.match("string",Cache.re_1)&&Fn.match("string2",Cache.re_1))
==========================================
Expression: $v in ["a","b","c"]
Variables : [v]
Evaluator : Fn.contains(["a","b","c"],Var["v"])
==========================================
Error: Xpresion Error: Operator ":" expecting 1 prior argument(s) at "1 ? : (1+2) (3+4)"
==========================================
Expression: 1 ? sum(1,2) : (3+4)
Variables : []
Evaluator : (1?Fn.sum(1,2):(3+4))
==========================================
Expression: 1 ? 1+2 : (3+4)
Variables : []
Evaluator : (1?(1+2):(3+4))
==========================================
Expression: 1 ? (2+3) : 2 ? (3+4) : (4+5)
Variables : []
Evaluator : (1?(2+3):(2?(3+4):(4+5)))
==========================================
Expression: date("Y-m-d H:i:s")
Variables : []
Evaluator : Fn.date("Y-m-d H:i:s")
Data      : {}
Result    : "2019-03-19 13:00:31"
==========================================
Expression: time()
Variables : []
Evaluator : Fn.time()
Data      : {}
Result    : 1552993231
==========================================
Expression: date("Y-m-d H:i:s", time())
Variables : []
Evaluator : Fn.date("Y-m-d H:i:s",Fn.time())
==========================================
Expression: pow(1,pow(2,3))
Variables : []
Evaluator : Math.pow(1,Math.pow(2,3))
==========================================
Expression: pow(pow(2,3),4)
Variables : []
Evaluator : Math.pow(Math.pow(2,3),4)
==========================================
Expression: pow(pow(1,2),pow(2,3))
Variables : []
Evaluator : Math.pow(Math.pow(1,2),Math.pow(2,3))

API

// define a new configuration
var conf = Xpresion.Configuration();
// or extend default configuration
var conf = Xpresion.defaultConfiguration();

// define an operator
conf.defOp({
    '===' :{
             'input'        : [1,'===',1]           // input
            ,'output'       : '(<$.0>===<$.1>)'     // output (GrammarTemplate)
            ,'otype'        : Xpresion.T_BOL        // output type BOOLEAN, default = Xpresion.T_MIX = mixed
            ,'fixity'       : Xpresion.INFIX        // operator fixity`
            ,'associativity': Xpresion.LEFT         // operator associativity
            ,'priority'     : 40                    // operator priority
        }
});

// define an alias
conf.defOp({
    'is': Xpresion.Alias('===')
});

// define a function (functional operator)
conf.defFunc({
    'len': {
             'input'    : 'len',
            ,'output'   : 'Fn.len(<$.0>)' // <$.0> matches all arguments passed not only one, separated by comma
            ,'otype'    : Xpresion.T_NUM
        }
});

// define a runtime function (called during evaluation) which implements the 'len' function
conf.defRuntimeFunc({
    'len'    :   function( v ) { 
                    if ( null==v ) return 0;
                    if ( v instanceof String || v instanceof Array ) return v.length;
                    if ( v instanceof Object ) return Object.keys(v).length;
                    return 1;
                }
});

// new Xpresion(..) will also work
var expr = Xpresion( "1+2+$v" [, conf=Xpresion.defaultConfiguration()] );

// !note! will trow an error if parsing the expression fails, so better to catch it and handle it

try {
    expr = Xpresion( "1+2+$v", conf );
} catch(e) {
    console.log(e.toString());
}


console.log(expr.debug({'v': 3}));

// class methods==============================

// get or set the Xpresion.defaultConfiguration()
var defaultConf = Xpresion.defaultConfiguration();
Xpresion.defaultConfiguration(newConf);

// methods====================================

// returns debug information about the expresion, plus the evaluated result if data given
expr.debug([Object data]);

// evaluates the expresion based on given data (as variables) and returns the result
expr.evaluate(Object data );

// properties=================================

expr.variables; // array of variable names referenced in the expression
expr.evaluatorString; // evaluator function as string
expr.evaluator; // actual function that evaluates the expression

default Xpresion Configuration

// default out-of-the-box Xpresion parser configuration
// 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': 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
            }
// n-ary (ternary) if-then-else operator
,'?'    :   {
                'input'         : [1,'?',1,':',1]
                ,'output'       : '(<$.0>?<$.1>:<$.2>)'
                ,'otype'        : T_BOL
                ,'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
}
}));

Platform Support

  • PHP 5.3.0+
  • Python 2.x or 3.x
  • Node 0.8+
  • Browser all

Performance

expression parsing algorithm can be seen as a variation and generalisation of Shunting-Yard algorithm, running in linear-time ( O(n) ) in the input sequence

TODO

  • use Grammar Template for more powerful and flexible rewrite output [DONE]
  • add full support for optional arguments in operators/functions [ALMOST DONE]
  • add support for (xml-like) tags in expressions
  • performance/unit tests [DONE PARTIALLY]