2020-12-22 00:01:36 +00:00
// https://silentmatt.com/javascript-expression-evaluator/
2022-01-19 15:41:36 +00:00
. pragma library
2020-12-22 00:01:36 +00:00
var INUMBER = 'INUMBER' ;
var IOP1 = 'IOP1' ;
var IOP2 = 'IOP2' ;
var IOP3 = 'IOP3' ;
var IVAR = 'IVAR' ;
var IVARNAME = 'IVARNAME' ;
var IFUNCALL = 'IFUNCALL' ;
var IFUNDEF = 'IFUNDEF' ;
var IEXPR = 'IEXPR' ;
var IEXPREVAL = 'IEXPREVAL' ;
var IMEMBER = 'IMEMBER' ;
var IENDSTATEMENT = 'IENDSTATEMENT' ;
var IARRAY = 'IARRAY' ;
2022-10-18 00:55:15 +00:00
// Additional variable characters.
var ADDITIONAL _VARCHARS = [
"α " , "β" , "γ " , "δ" , "ε" , "ζ" , "η" ,
"π" , "θ" , "κ" , "λ" , "μ" , "ξ" , "ρ " ,
"ς" , "σ " , "τ" , "φ" , "χ" , "ψ" , "ω" ,
"Γ" , "Δ" , "Θ" , "Λ" , "Ξ" , "Π" , "Σ" ,
"Φ" , "Ψ" , "Ω" , "ₐ" , "ₑ" , "ₒ" , "ₓ" ,
"ₕ" , "ₖ" , "ₗ" , "ₘ" , "ₙ" , "ₚ" , "ₛ" ,
"ₜ" , "¹" , "²" , "³" , "⁴" , "⁵" , "⁶" ,
"⁷" , "⁸" , "⁹" , "⁰" , "₁" , "₂" , "₃" ,
"₄" , "₅" , "₆" , "₇" , "₈" , "₉" , "₀"
]
2020-12-22 00:01:36 +00:00
function Instruction ( type , value ) {
this . type = type ;
this . value = ( value !== undefined && value !== null ) ? value : 0 ;
}
Instruction . prototype . toString = function ( ) {
switch ( this . type ) {
case INUMBER :
case IOP1 :
case IOP2 :
case IOP3 :
case IVAR :
case IVARNAME :
case IENDSTATEMENT :
return this . value ;
case IFUNCALL :
return 'CALL ' + this . value ;
case IFUNDEF :
return 'DEF ' + this . value ;
case IARRAY :
return 'ARRAY ' + this . value ;
case IMEMBER :
return '.' + this . value ;
default :
return 'Invalid Instruction' ;
}
} ;
function unaryInstruction ( value ) {
return new Instruction ( IOP1 , value ) ;
}
function binaryInstruction ( value ) {
return new Instruction ( IOP2 , value ) ;
}
function ternaryInstruction ( value ) {
return new Instruction ( IOP3 , value ) ;
}
function simplify ( tokens , unaryOps , binaryOps , ternaryOps , values ) {
var nstack = [ ] ;
var newexpression = [ ] ;
var n1 , n2 , n3 ;
var f ;
for ( var i = 0 ; i < tokens . length ; i ++ ) {
var item = tokens [ i ] ;
var type = item . type ;
if ( type === INUMBER || type === IVARNAME ) {
if ( Array . isArray ( item . value ) ) {
nstack . push . apply ( nstack , simplify ( item . value . map ( function ( x ) {
return new Instruction ( INUMBER , x ) ;
} ) . concat ( new Instruction ( IARRAY , item . value . length ) ) , unaryOps , binaryOps , ternaryOps , values ) ) ;
} else {
nstack . push ( item ) ;
}
} else if ( type === IVAR && values . hasOwnProperty ( item . value ) ) {
item = new Instruction ( INUMBER , values [ item . value ] ) ;
nstack . push ( item ) ;
} else if ( type === IOP2 && nstack . length > 1 ) {
n2 = nstack . pop ( ) ;
n1 = nstack . pop ( ) ;
f = binaryOps [ item . value ] ;
item = new Instruction ( INUMBER , f ( n1 . value , n2 . value ) ) ;
nstack . push ( item ) ;
} else if ( type === IOP3 && nstack . length > 2 ) {
n3 = nstack . pop ( ) ;
n2 = nstack . pop ( ) ;
n1 = nstack . pop ( ) ;
if ( item . value === '?' ) {
nstack . push ( n1 . value ? n2 . value : n3 . value ) ;
} else {
f = ternaryOps [ item . value ] ;
item = new Instruction ( INUMBER , f ( n1 . value , n2 . value , n3 . value ) ) ;
nstack . push ( item ) ;
}
} else if ( type === IOP1 && nstack . length > 0 ) {
n1 = nstack . pop ( ) ;
f = unaryOps [ item . value ] ;
item = new Instruction ( INUMBER , f ( n1 . value ) ) ;
nstack . push ( item ) ;
} else if ( type === IEXPR ) {
while ( nstack . length > 0 ) {
newexpression . push ( nstack . shift ( ) ) ;
}
newexpression . push ( new Instruction ( IEXPR , simplify ( item . value , unaryOps , binaryOps , ternaryOps , values ) ) ) ;
} else if ( type === IMEMBER && nstack . length > 0 ) {
n1 = nstack . pop ( ) ;
nstack . push ( new Instruction ( INUMBER , n1 . value [ item . value ] ) ) ;
} / * else if ( type === IARRAY && nstack . length >= item . value ) {
var length = item . value ;
while ( length -- > 0 ) {
newexpression . push ( nstack . pop ( ) ) ;
}
newexpression . push ( new Instruction ( IARRAY , item . value ) ) ;
} * / e l s e {
while ( nstack . length > 0 ) {
newexpression . push ( nstack . shift ( ) ) ;
}
newexpression . push ( item ) ;
}
}
while ( nstack . length > 0 ) {
newexpression . push ( nstack . shift ( ) ) ;
}
return newexpression ;
}
function substitute ( tokens , variable , expr ) {
var newexpression = [ ] ;
for ( var i = 0 ; i < tokens . length ; i ++ ) {
var item = tokens [ i ] ;
var type = item . type ;
if ( type === IVAR && item . value === variable ) {
for ( var j = 0 ; j < expr . tokens . length ; j ++ ) {
var expritem = expr . tokens [ j ] ;
var replitem ;
if ( expritem . type === IOP1 ) {
replitem = unaryInstruction ( expritem . value ) ;
} else if ( expritem . type === IOP2 ) {
replitem = binaryInstruction ( expritem . value ) ;
} else if ( expritem . type === IOP3 ) {
replitem = ternaryInstruction ( expritem . value ) ;
} else {
replitem = new Instruction ( expritem . type , expritem . value ) ;
}
newexpression . push ( replitem ) ;
}
} else if ( type === IEXPR ) {
newexpression . push ( new Instruction ( IEXPR , substitute ( item . value , variable , expr ) ) ) ;
} else {
newexpression . push ( item ) ;
}
}
return newexpression ;
}
function evaluate ( tokens , expr , values ) {
var nstack = [ ] ;
var n1 , n2 , n3 ;
var f , args , argCount ;
if ( isExpressionEvaluator ( tokens ) ) {
return resolveExpression ( tokens , values ) ;
}
var numTokens = tokens . length ;
for ( var i = 0 ; i < numTokens ; i ++ ) {
var item = tokens [ i ] ;
var type = item . type ;
if ( type === INUMBER || type === IVARNAME ) {
nstack . push ( item . value ) ;
} else if ( type === IOP2 ) {
n2 = nstack . pop ( ) ;
n1 = nstack . pop ( ) ;
if ( item . value === 'and' ) {
nstack . push ( n1 ? ! ! evaluate ( n2 , expr , values ) : false ) ;
} else if ( item . value === 'or' ) {
nstack . push ( n1 ? true : ! ! evaluate ( n2 , expr , values ) ) ;
} else if ( item . value === '=' ) {
f = expr . binaryOps [ item . value ] ;
nstack . push ( f ( n1 , evaluate ( n2 , expr , values ) , values ) ) ;
} else {
f = expr . binaryOps [ item . value ] ;
nstack . push ( f ( resolveExpression ( n1 , values ) , resolveExpression ( n2 , values ) ) ) ;
}
} else if ( type === IOP3 ) {
n3 = nstack . pop ( ) ;
n2 = nstack . pop ( ) ;
n1 = nstack . pop ( ) ;
if ( item . value === '?' ) {
nstack . push ( evaluate ( n1 ? n2 : n3 , expr , values ) ) ;
} else {
f = expr . ternaryOps [ item . value ] ;
nstack . push ( f ( resolveExpression ( n1 , values ) , resolveExpression ( n2 , values ) , resolveExpression ( n3 , values ) ) ) ;
}
} else if ( type === IVAR ) {
2022-10-17 20:30:59 +00:00
// Check for variable value
2020-12-22 00:01:36 +00:00
if ( item . value in expr . functions ) {
nstack . push ( expr . functions [ item . value ] ) ;
} else if ( item . value in expr . unaryOps && expr . parser . isOperatorEnabled ( item . value ) ) {
nstack . push ( expr . unaryOps [ item . value ] ) ;
} else {
var v = values [ item . value ] ;
if ( v !== undefined ) {
nstack . push ( v ) ;
} else {
throw new Error ( 'undefined variable: ' + item . value ) ;
}
}
} else if ( type === IOP1 ) {
n1 = nstack . pop ( ) ;
f = expr . unaryOps [ item . value ] ;
nstack . push ( f ( resolveExpression ( n1 , values ) ) ) ;
} else if ( type === IFUNCALL ) {
argCount = item . value ;
args = [ ] ;
while ( argCount -- > 0 ) {
args . unshift ( resolveExpression ( nstack . pop ( ) , values ) ) ;
}
f = nstack . pop ( ) ;
if ( f . apply && f . call ) {
nstack . push ( f . apply ( undefined , args ) ) ;
2022-10-17 20:30:59 +00:00
} else if ( f . execute ) {
// Objects & expressions execution
2022-10-17 20:54:02 +00:00
nstack . push ( f . execute . apply ( f , args ) ) ;
2020-12-22 00:01:36 +00:00
} else {
2022-10-17 20:30:59 +00:00
throw new Error ( f + ' cannot be executed' ) ;
2020-12-22 00:01:36 +00:00
}
} else if ( type === IFUNDEF ) {
// Create closure to keep references to arguments and expression
nstack . push ( ( function ( ) {
var n2 = nstack . pop ( ) ;
var args = [ ] ;
var argCount = item . value ;
while ( argCount -- > 0 ) {
args . unshift ( nstack . pop ( ) ) ;
}
var n1 = nstack . pop ( ) ;
var f = function ( ) {
var scope = Object . assign ( { } , values ) ;
for ( var i = 0 , len = args . length ; i < len ; i ++ ) {
scope [ args [ i ] ] = arguments [ i ] ;
}
return evaluate ( n2 , expr , scope ) ;
} ;
// f.name = n1
Object . defineProperty ( f , 'name' , {
value : n1 ,
writable : false
} ) ;
values [ n1 ] = f ;
return f ;
} ) ( ) ) ;
} else if ( type === IEXPR ) {
nstack . push ( createExpressionEvaluator ( item , expr ) ) ;
} else if ( type === IEXPREVAL ) {
nstack . push ( item ) ;
} else if ( type === IMEMBER ) {
n1 = nstack . pop ( ) ;
nstack . push ( n1 [ item . value ] ) ;
} else if ( type === IENDSTATEMENT ) {
nstack . pop ( ) ;
} else if ( type === IARRAY ) {
argCount = item . value ;
args = [ ] ;
while ( argCount -- > 0 ) {
args . unshift ( nstack . pop ( ) ) ;
}
nstack . push ( args ) ;
} else {
throw new Error ( 'invalid Expression' ) ;
}
}
if ( nstack . length > 1 ) {
throw new Error ( 'invalid Expression (parity)' ) ;
}
// Explicitly return zero to avoid test issues caused by -0
return nstack [ 0 ] === 0 ? 0 : resolveExpression ( nstack [ 0 ] , values ) ;
}
function createExpressionEvaluator ( token , expr , values ) {
if ( isExpressionEvaluator ( token ) ) return token ;
return {
type : IEXPREVAL ,
value : function ( scope ) {
return evaluate ( token . value , expr , scope ) ;
}
} ;
}
function isExpressionEvaluator ( n ) {
return n && n . type === IEXPREVAL ;
}
function resolveExpression ( n , values ) {
return isExpressionEvaluator ( n ) ? n . value ( values ) : n ;
}
function expressionToString ( tokens , toJS ) {
var nstack = [ ] ;
var n1 , n2 , n3 ;
var f , args , argCount ;
for ( var i = 0 ; i < tokens . length ; i ++ ) {
var item = tokens [ i ] ;
var type = item . type ;
if ( type === INUMBER ) {
if ( typeof item . value === 'number' && item . value < 0 ) {
nstack . push ( '(' + item . value + ')' ) ;
} else if ( Array . isArray ( item . value ) ) {
nstack . push ( '[' + item . value . map ( escapeValue ) . join ( ', ' ) + ']' ) ;
} else {
nstack . push ( escapeValue ( item . value ) ) ;
}
} else if ( type === IOP2 ) {
n2 = nstack . pop ( ) ;
n1 = nstack . pop ( ) ;
f = item . value ;
if ( toJS ) {
if ( f === '^' ) {
nstack . push ( 'Math.pow(' + n1 + ', ' + n2 + ')' ) ;
} else if ( f === 'and' ) {
nstack . push ( '(!!' + n1 + ' && !!' + n2 + ')' ) ;
} else if ( f === 'or' ) {
nstack . push ( '(!!' + n1 + ' || !!' + n2 + ')' ) ;
} else if ( f === '||' ) {
nstack . push ( '(function(a,b){ return Array.isArray(a) && Array.isArray(b) ? a.concat(b) : String(a) + String(b); }((' + n1 + '),(' + n2 + ')))' ) ;
} else if ( f === '==' ) {
nstack . push ( '(' + n1 + ' === ' + n2 + ')' ) ;
} else if ( f === '!=' ) {
nstack . push ( '(' + n1 + ' !== ' + n2 + ')' ) ;
} else if ( f === '[' ) {
nstack . push ( n1 + '[(' + n2 + ') | 0]' ) ;
} else {
nstack . push ( '(' + n1 + ' ' + f + ' ' + n2 + ')' ) ;
}
} else {
if ( f === '[' ) {
nstack . push ( n1 + '[' + n2 + ']' ) ;
} else {
nstack . push ( '(' + n1 + ' ' + f + ' ' + n2 + ')' ) ;
}
}
} else if ( type === IOP3 ) {
n3 = nstack . pop ( ) ;
n2 = nstack . pop ( ) ;
n1 = nstack . pop ( ) ;
f = item . value ;
if ( f === '?' ) {
nstack . push ( '(' + n1 + ' ? ' + n2 + ' : ' + n3 + ')' ) ;
} else {
throw new Error ( 'invalid Expression' ) ;
}
} else if ( type === IVAR || type === IVARNAME ) {
nstack . push ( item . value ) ;
} else if ( type === IOP1 ) {
n1 = nstack . pop ( ) ;
f = item . value ;
if ( f === '-' || f === '+' ) {
nstack . push ( '(' + f + n1 + ')' ) ;
} else if ( toJS ) {
if ( f === 'not' ) {
nstack . push ( '(' + '!' + n1 + ')' ) ;
} else if ( f === '!' ) {
nstack . push ( 'fac(' + n1 + ')' ) ;
} else {
nstack . push ( f + '(' + n1 + ')' ) ;
}
} else if ( f === '!' ) {
nstack . push ( '(' + n1 + '!)' ) ;
} else {
nstack . push ( '(' + f + ' ' + n1 + ')' ) ;
}
} else if ( type === IFUNCALL ) {
argCount = item . value ;
args = [ ] ;
while ( argCount -- > 0 ) {
args . unshift ( nstack . pop ( ) ) ;
}
f = nstack . pop ( ) ;
nstack . push ( f + '(' + args . join ( ', ' ) + ')' ) ;
} else if ( type === IFUNDEF ) {
n2 = nstack . pop ( ) ;
argCount = item . value ;
args = [ ] ;
while ( argCount -- > 0 ) {
args . unshift ( nstack . pop ( ) ) ;
}
n1 = nstack . pop ( ) ;
if ( toJS ) {
nstack . push ( '(' + n1 + ' = function(' + args . join ( ', ' ) + ') { return ' + n2 + ' })' ) ;
} else {
nstack . push ( '(' + n1 + '(' + args . join ( ', ' ) + ') = ' + n2 + ')' ) ;
}
} else if ( type === IMEMBER ) {
n1 = nstack . pop ( ) ;
nstack . push ( n1 + '.' + item . value ) ;
} else if ( type === IARRAY ) {
argCount = item . value ;
args = [ ] ;
while ( argCount -- > 0 ) {
args . unshift ( nstack . pop ( ) ) ;
}
nstack . push ( '[' + args . join ( ', ' ) + ']' ) ;
} else if ( type === IEXPR ) {
nstack . push ( '(' + expressionToString ( item . value , toJS ) + ')' ) ;
} else if ( type === IENDSTATEMENT ) ; else {
throw new Error ( 'invalid Expression' ) ;
}
}
if ( nstack . length > 1 ) {
if ( toJS ) {
nstack = [ nstack . join ( ',' ) ] ;
} else {
nstack = [ nstack . join ( ';' ) ] ;
}
}
return String ( nstack [ 0 ] ) ;
}
function escapeValue ( v ) {
if ( typeof v === 'string' ) {
return JSON . stringify ( v ) . replace ( /\u2028/g , '\\u2028' ) . replace ( /\u2029/g , '\\u2029' ) ;
}
return v ;
}
function contains ( array , obj ) {
for ( var i = 0 ; i < array . length ; i ++ ) {
if ( array [ i ] === obj ) {
return true ;
}
}
return false ;
}
function getSymbols ( tokens , symbols , options ) {
options = options || { } ;
var withMembers = ! ! options . withMembers ;
var prevVar = null ;
for ( var i = 0 ; i < tokens . length ; i ++ ) {
var item = tokens [ i ] ;
if ( item . type === IVAR || item . type === IVARNAME ) {
if ( ! withMembers && ! contains ( symbols , item . value ) ) {
symbols . push ( item . value ) ;
} else if ( prevVar !== null ) {
if ( ! contains ( symbols , prevVar ) ) {
symbols . push ( prevVar ) ;
}
prevVar = item . value ;
} else {
prevVar = item . value ;
}
} else if ( item . type === IMEMBER && withMembers && prevVar !== null ) {
prevVar += '.' + item . value ;
} else if ( item . type === IEXPR ) {
getSymbols ( item . value , symbols , options ) ;
} else if ( prevVar !== null ) {
if ( ! contains ( symbols , prevVar ) ) {
symbols . push ( prevVar ) ;
}
prevVar = null ;
}
}
if ( prevVar !== null && ! contains ( symbols , prevVar ) ) {
symbols . push ( prevVar ) ;
}
}
function Expression ( tokens , parser ) {
this . tokens = tokens ;
this . parser = parser ;
this . unaryOps = parser . unaryOps ;
this . binaryOps = parser . binaryOps ;
this . ternaryOps = parser . ternaryOps ;
this . functions = parser . functions ;
}
Expression . prototype . simplify = function ( values ) {
values = values || { } ;
return new Expression ( simplify ( this . tokens , this . unaryOps , this . binaryOps , this . ternaryOps , values ) , this . parser ) ;
} ;
Expression . prototype . substitute = function ( variable , expr ) {
if ( ! ( expr instanceof Expression ) ) {
expr = this . parser . parse ( String ( expr ) ) ;
}
return new Expression ( substitute ( this . tokens , variable , expr ) , this . parser ) ;
} ;
Expression . prototype . evaluate = function ( values ) {
values = values || { } ;
return evaluate ( this . tokens , this , values ) ;
} ;
Expression . prototype . toString = function ( ) {
return expressionToString ( this . tokens , false ) ;
} ;
Expression . prototype . symbols = function ( options ) {
options = options || { } ;
var vars = [ ] ;
getSymbols ( this . tokens , vars , options ) ;
return vars ;
} ;
Expression . prototype . variables = function ( options ) {
options = options || { } ;
var vars = [ ] ;
getSymbols ( this . tokens , vars , options ) ;
var functions = this . functions ;
return vars . filter ( function ( name ) {
return ! ( name in functions ) ;
} ) ;
} ;
Expression . prototype . toJSFunction = function ( param , variables ) {
var expr = this ;
var f = new Function ( param , 'with(this.functions) with (this.ternaryOps) with (this.binaryOps) with (this.unaryOps) { return ' + expressionToString ( this . simplify ( variables ) . tokens , true ) + '; }' ) ; // eslint-disable-line no-new-func
return function ( ) {
return f . apply ( expr , arguments ) ;
} ;
} ;
var TEOF = 'TEOF' ;
var TOP = 'TOP' ;
var TNUMBER = 'TNUMBER' ;
var TSTRING = 'TSTRING' ;
var TPAREN = 'TPAREN' ;
var TBRACKET = 'TBRACKET' ;
var TCOMMA = 'TCOMMA' ;
var TNAME = 'TNAME' ;
var TSEMICOLON = 'TSEMICOLON' ;
function Token ( type , value , index ) {
this . type = type ;
this . value = value ;
this . index = index ;
}
Token . prototype . toString = function ( ) {
return this . type + ': ' + this . value ;
} ;
function TokenStream ( parser , expression ) {
this . pos = 0 ;
this . current = null ;
this . unaryOps = parser . unaryOps ;
this . binaryOps = parser . binaryOps ;
this . ternaryOps = parser . ternaryOps ;
this . consts = parser . consts ;
this . expression = expression ;
this . savedPosition = 0 ;
this . savedCurrent = null ;
this . options = parser . options ;
this . parser = parser ;
}
TokenStream . prototype . newToken = function ( type , value , pos ) {
return new Token ( type , value , pos != null ? pos : this . pos ) ;
} ;
TokenStream . prototype . save = function ( ) {
this . savedPosition = this . pos ;
this . savedCurrent = this . current ;
} ;
TokenStream . prototype . restore = function ( ) {
this . pos = this . savedPosition ;
this . current = this . savedCurrent ;
} ;
TokenStream . prototype . next = function ( ) {
if ( this . pos >= this . expression . length ) {
return this . newToken ( TEOF , 'EOF' ) ;
}
if ( this . isWhitespace ( ) || this . isComment ( ) ) {
return this . next ( ) ;
} else if ( this . isRadixInteger ( ) ||
this . isNumber ( ) ||
this . isOperator ( ) ||
this . isString ( ) ||
this . isParen ( ) ||
this . isBracket ( ) ||
this . isComma ( ) ||
this . isSemicolon ( ) ||
this . isNamedOp ( ) ||
this . isConst ( ) ||
this . isName ( ) ) {
return this . current ;
} else {
this . parseError ( 'Unknown character "' + this . expression . charAt ( this . pos ) + '"' ) ;
}
} ;
TokenStream . prototype . isString = function ( ) {
var r = false ;
var startPos = this . pos ;
var quote = this . expression . charAt ( startPos ) ;
if ( quote === '\'' || quote === '"' ) {
var index = this . expression . indexOf ( quote , startPos + 1 ) ;
while ( index >= 0 && this . pos < this . expression . length ) {
this . pos = index + 1 ;
if ( this . expression . charAt ( index - 1 ) !== '\\' ) {
var rawString = this . expression . substring ( startPos + 1 , index ) ;
this . current = this . newToken ( TSTRING , this . unescape ( rawString ) , startPos ) ;
r = true ;
break ;
}
index = this . expression . indexOf ( quote , index + 1 ) ;
}
}
return r ;
} ;
TokenStream . prototype . isParen = function ( ) {
var c = this . expression . charAt ( this . pos ) ;
if ( c === '(' || c === ')' ) {
this . current = this . newToken ( TPAREN , c ) ;
this . pos ++ ;
return true ;
}
return false ;
} ;
TokenStream . prototype . isBracket = function ( ) {
var c = this . expression . charAt ( this . pos ) ;
if ( ( c === '[' || c === ']' ) && this . isOperatorEnabled ( '[' ) ) {
this . current = this . newToken ( TBRACKET , c ) ;
this . pos ++ ;
return true ;
}
return false ;
} ;
TokenStream . prototype . isComma = function ( ) {
var c = this . expression . charAt ( this . pos ) ;
if ( c === ',' ) {
this . current = this . newToken ( TCOMMA , ',' ) ;
this . pos ++ ;
return true ;
}
return false ;
} ;
TokenStream . prototype . isSemicolon = function ( ) {
var c = this . expression . charAt ( this . pos ) ;
if ( c === ';' ) {
this . current = this . newToken ( TSEMICOLON , ';' ) ;
this . pos ++ ;
return true ;
}
return false ;
} ;
TokenStream . prototype . isConst = function ( ) {
var startPos = this . pos ;
var i = startPos ;
for ( ; i < this . expression . length ; i ++ ) {
var c = this . expression . charAt ( i ) ;
if ( c . toUpperCase ( ) === c . toLowerCase ( ) ) {
if ( i === this . pos || ( c !== '_' && c !== '.' && ( c < '0' || c > '9' ) ) ) {
break ;
}
}
}
if ( i > startPos ) {
var str = this . expression . substring ( startPos , i ) ;
if ( str in this . consts ) {
this . current = this . newToken ( TNUMBER , this . consts [ str ] ) ;
this . pos += str . length ;
return true ;
}
}
return false ;
} ;
TokenStream . prototype . isNamedOp = function ( ) {
var startPos = this . pos ;
var i = startPos ;
for ( ; i < this . expression . length ; i ++ ) {
var c = this . expression . charAt ( i ) ;
if ( c . toUpperCase ( ) === c . toLowerCase ( ) ) {
if ( i === this . pos || ( c !== '_' && ( c < '0' || c > '9' ) ) ) {
break ;
}
}
}
if ( i > startPos ) {
var str = this . expression . substring ( startPos , i ) ;
if ( this . isOperatorEnabled ( str ) && ( str in this . binaryOps || str in this . unaryOps || str in this . ternaryOps ) ) {
this . current = this . newToken ( TOP , str ) ;
this . pos += str . length ;
return true ;
}
}
return false ;
} ;
TokenStream . prototype . isName = function ( ) {
var startPos = this . pos ;
var i = startPos ;
var hasLetter = false ;
for ( ; i < this . expression . length ; i ++ ) {
var c = this . expression . charAt ( i ) ;
2022-10-18 00:55:15 +00:00
if ( c . toUpperCase ( ) === c . toLowerCase ( ) && ! ADDITIONAL _VARCHARS . includes ( c ) ) {
2020-12-22 00:01:36 +00:00
if ( i === this . pos && ( c === '$' || c === '_' ) ) {
if ( c === '_' ) {
hasLetter = true ;
}
continue ;
} else if ( i === this . pos || ! hasLetter || ( c !== '_' && ( c < '0' || c > '9' ) ) ) {
break ;
}
} else {
hasLetter = true ;
}
}
if ( hasLetter ) {
var str = this . expression . substring ( startPos , i ) ;
this . current = this . newToken ( TNAME , str ) ;
this . pos += str . length ;
return true ;
}
return false ;
} ;
TokenStream . prototype . isWhitespace = function ( ) {
var r = false ;
var c = this . expression . charAt ( this . pos ) ;
while ( c === ' ' || c === '\t' || c === '\n' || c === '\r' ) {
r = true ;
this . pos ++ ;
if ( this . pos >= this . expression . length ) {
break ;
}
c = this . expression . charAt ( this . pos ) ;
}
return r ;
} ;
var codePointPattern = /^[0-9a-f]{4}$/i ;
TokenStream . prototype . unescape = function ( v ) {
var index = v . indexOf ( '\\' ) ;
if ( index < 0 ) {
return v ;
}
var buffer = v . substring ( 0 , index ) ;
while ( index >= 0 ) {
var c = v . charAt ( ++ index ) ;
switch ( c ) {
case '\'' :
buffer += '\'' ;
break ;
case '"' :
buffer += '"' ;
break ;
case '\\' :
buffer += '\\' ;
break ;
case '/' :
buffer += '/' ;
break ;
case 'b' :
buffer += '\b' ;
break ;
case 'f' :
buffer += '\f' ;
break ;
case 'n' :
buffer += '\n' ;
break ;
case 'r' :
buffer += '\r' ;
break ;
case 't' :
buffer += '\t' ;
break ;
case 'u' :
// interpret the following 4 characters as the hex of the unicode code point
var codePoint = v . substring ( index + 1 , index + 5 ) ;
if ( ! codePointPattern . test ( codePoint ) ) {
this . parseError ( 'Illegal escape sequence: \\u' + codePoint ) ;
}
buffer += String . fromCharCode ( parseInt ( codePoint , 16 ) ) ;
index += 4 ;
break ;
default :
throw this . parseError ( 'Illegal escape sequence: "\\' + c + '"' ) ;
}
++ index ;
var backslash = v . indexOf ( '\\' , index ) ;
buffer += v . substring ( index , backslash < 0 ? v . length : backslash ) ;
index = backslash ;
}
return buffer ;
} ;
TokenStream . prototype . isComment = function ( ) {
var c = this . expression . charAt ( this . pos ) ;
if ( c === '/' && this . expression . charAt ( this . pos + 1 ) === '*' ) {
this . pos = this . expression . indexOf ( '*/' , this . pos ) + 2 ;
if ( this . pos === 1 ) {
this . pos = this . expression . length ;
}
return true ;
}
return false ;
} ;
TokenStream . prototype . isRadixInteger = function ( ) {
var pos = this . pos ;
if ( pos >= this . expression . length - 2 || this . expression . charAt ( pos ) !== '0' ) {
return false ;
}
++ pos ;
var radix ;
var validDigit ;
if ( this . expression . charAt ( pos ) === 'x' ) {
radix = 16 ;
validDigit = /^[0-9a-f]$/i ;
++ pos ;
} else if ( this . expression . charAt ( pos ) === 'b' ) {
radix = 2 ;
validDigit = /^[01]$/i ;
++ pos ;
} else {
return false ;
}
var valid = false ;
var startPos = pos ;
while ( pos < this . expression . length ) {
var c = this . expression . charAt ( pos ) ;
if ( validDigit . test ( c ) ) {
pos ++ ;
valid = true ;
} else {
break ;
}
}
if ( valid ) {
this . current = this . newToken ( TNUMBER , parseInt ( this . expression . substring ( startPos , pos ) , radix ) ) ;
this . pos = pos ;
}
return valid ;
} ;
TokenStream . prototype . isNumber = function ( ) {
var valid = false ;
var pos = this . pos ;
var startPos = pos ;
var resetPos = pos ;
var foundDot = false ;
var foundDigits = false ;
var c ;
while ( pos < this . expression . length ) {
c = this . expression . charAt ( pos ) ;
if ( ( c >= '0' && c <= '9' ) || ( ! foundDot && c === '.' ) ) {
if ( c === '.' ) {
foundDot = true ;
} else {
foundDigits = true ;
}
pos ++ ;
valid = foundDigits ;
} else {
break ;
}
}
if ( valid ) {
resetPos = pos ;
}
if ( c === 'e' || c === 'E' ) {
pos ++ ;
var acceptSign = true ;
var validExponent = false ;
while ( pos < this . expression . length ) {
c = this . expression . charAt ( pos ) ;
if ( acceptSign && ( c === '+' || c === '-' ) ) {
acceptSign = false ;
} else if ( c >= '0' && c <= '9' ) {
validExponent = true ;
acceptSign = false ;
} else {
break ;
}
pos ++ ;
}
if ( ! validExponent ) {
pos = resetPos ;
}
}
if ( valid ) {
this . current = this . newToken ( TNUMBER , parseFloat ( this . expression . substring ( startPos , pos ) ) ) ;
this . pos = pos ;
} else {
this . pos = resetPos ;
}
return valid ;
} ;
TokenStream . prototype . isOperator = function ( ) {
var startPos = this . pos ;
var c = this . expression . charAt ( this . pos ) ;
if ( c === '+' || c === '-' || c === '*' || c === '/' || c === '%' || c === '^' || c === '?' || c === ':' || c === '.' ) {
this . current = this . newToken ( TOP , c ) ;
} else if ( c === '∙' || c === '•' ) {
this . current = this . newToken ( TOP , '*' ) ;
} else if ( c === '>' ) {
if ( this . expression . charAt ( this . pos + 1 ) === '=' ) {
this . current = this . newToken ( TOP , '>=' ) ;
this . pos ++ ;
} else {
this . current = this . newToken ( TOP , '>' ) ;
}
} else if ( c === '<' ) {
if ( this . expression . charAt ( this . pos + 1 ) === '=' ) {
this . current = this . newToken ( TOP , '<=' ) ;
this . pos ++ ;
} else {
this . current = this . newToken ( TOP , '<' ) ;
}
} else if ( c === '|' ) {
if ( this . expression . charAt ( this . pos + 1 ) === '|' ) {
this . current = this . newToken ( TOP , '||' ) ;
this . pos ++ ;
} else {
return false ;
}
} else if ( c === '=' ) {
if ( this . expression . charAt ( this . pos + 1 ) === '=' ) {
this . current = this . newToken ( TOP , '==' ) ;
this . pos ++ ;
} else {
this . current = this . newToken ( TOP , c ) ;
}
} else if ( c === '!' ) {
if ( this . expression . charAt ( this . pos + 1 ) === '=' ) {
this . current = this . newToken ( TOP , '!=' ) ;
this . pos ++ ;
} else {
this . current = this . newToken ( TOP , c ) ;
}
} else {
return false ;
}
this . pos ++ ;
if ( this . isOperatorEnabled ( this . current . value ) ) {
return true ;
} else {
this . pos = startPos ;
return false ;
}
} ;
TokenStream . prototype . isOperatorEnabled = function ( op ) {
return this . parser . isOperatorEnabled ( op ) ;
} ;
TokenStream . prototype . getCoordinates = function ( ) {
var line = 0 ;
var column ;
var newline = - 1 ;
do {
line ++ ;
column = this . pos - newline ;
newline = this . expression . indexOf ( '\n' , newline + 1 ) ;
} while ( newline >= 0 && newline < this . pos ) ;
return {
line : line ,
column : column
} ;
} ;
TokenStream . prototype . parseError = function ( msg ) {
var coords = this . getCoordinates ( ) ;
throw new Error ( 'parse error [' + coords . line + ':' + coords . column + ']: ' + msg ) ;
} ;
function ParserState ( parser , tokenStream , options ) {
this . parser = parser ;
this . tokens = tokenStream ;
this . current = null ;
this . nextToken = null ;
this . next ( ) ;
this . savedCurrent = null ;
this . savedNextToken = null ;
this . allowMemberAccess = options . allowMemberAccess !== false ;
}
ParserState . prototype . next = function ( ) {
this . current = this . nextToken ;
return ( this . nextToken = this . tokens . next ( ) ) ;
} ;
ParserState . prototype . tokenMatches = function ( token , value ) {
if ( typeof value === 'undefined' ) {
return true ;
} else if ( Array . isArray ( value ) ) {
return contains ( value , token . value ) ;
} else if ( typeof value === 'function' ) {
return value ( token ) ;
} else {
return token . value === value ;
}
} ;
ParserState . prototype . save = function ( ) {
this . savedCurrent = this . current ;
this . savedNextToken = this . nextToken ;
this . tokens . save ( ) ;
} ;
ParserState . prototype . restore = function ( ) {
this . tokens . restore ( ) ;
this . current = this . savedCurrent ;
this . nextToken = this . savedNextToken ;
} ;
ParserState . prototype . accept = function ( type , value ) {
if ( this . nextToken . type === type && this . tokenMatches ( this . nextToken , value ) ) {
this . next ( ) ;
return true ;
}
return false ;
} ;
ParserState . prototype . expect = function ( type , value ) {
if ( ! this . accept ( type , value ) ) {
var coords = this . tokens . getCoordinates ( ) ;
throw new Error ( 'parse error [' + coords . line + ':' + coords . column + ']: Expected ' + ( value || type ) ) ;
}
} ;
ParserState . prototype . parseAtom = function ( instr ) {
var unaryOps = this . tokens . unaryOps ;
function isPrefixOperator ( token ) {
return token . value in unaryOps ;
}
if ( this . accept ( TNAME ) || this . accept ( TOP , isPrefixOperator ) ) {
instr . push ( new Instruction ( IVAR , this . current . value ) ) ;
} else if ( this . accept ( TNUMBER ) ) {
instr . push ( new Instruction ( INUMBER , this . current . value ) ) ;
} else if ( this . accept ( TSTRING ) ) {
instr . push ( new Instruction ( INUMBER , this . current . value ) ) ;
} else if ( this . accept ( TPAREN , '(' ) ) {
this . parseExpression ( instr ) ;
this . expect ( TPAREN , ')' ) ;
} else if ( this . accept ( TBRACKET , '[' ) ) {
if ( this . accept ( TBRACKET , ']' ) ) {
instr . push ( new Instruction ( IARRAY , 0 ) ) ;
} else {
var argCount = this . parseArrayList ( instr ) ;
instr . push ( new Instruction ( IARRAY , argCount ) ) ;
}
} else {
throw new Error ( 'unexpected ' + this . nextToken ) ;
}
} ;
ParserState . prototype . parseExpression = function ( instr ) {
var exprInstr = [ ] ;
if ( this . parseUntilEndStatement ( instr , exprInstr ) ) {
return ;
}
this . parseVariableAssignmentExpression ( exprInstr ) ;
if ( this . parseUntilEndStatement ( instr , exprInstr ) ) {
return ;
}
this . pushExpression ( instr , exprInstr ) ;
} ;
ParserState . prototype . pushExpression = function ( instr , exprInstr ) {
for ( var i = 0 , len = exprInstr . length ; i < len ; i ++ ) {
instr . push ( exprInstr [ i ] ) ;
}
} ;
ParserState . prototype . parseUntilEndStatement = function ( instr , exprInstr ) {
if ( ! this . accept ( TSEMICOLON ) ) return false ;
if ( this . nextToken && this . nextToken . type !== TEOF && ! ( this . nextToken . type === TPAREN && this . nextToken . value === ')' ) ) {
exprInstr . push ( new Instruction ( IENDSTATEMENT ) ) ;
}
if ( this . nextToken . type !== TEOF ) {
this . parseExpression ( exprInstr ) ;
}
instr . push ( new Instruction ( IEXPR , exprInstr ) ) ;
return true ;
} ;
ParserState . prototype . parseArrayList = function ( instr ) {
var argCount = 0 ;
while ( ! this . accept ( TBRACKET , ']' ) ) {
this . parseExpression ( instr ) ;
++ argCount ;
while ( this . accept ( TCOMMA ) ) {
this . parseExpression ( instr ) ;
++ argCount ;
}
}
return argCount ;
} ;
ParserState . prototype . parseVariableAssignmentExpression = function ( instr ) {
this . parseConditionalExpression ( instr ) ;
while ( this . accept ( TOP , '=' ) ) {
var varName = instr . pop ( ) ;
var varValue = [ ] ;
var lastInstrIndex = instr . length - 1 ;
if ( varName . type === IFUNCALL ) {
if ( ! this . tokens . isOperatorEnabled ( '()=' ) ) {
throw new Error ( 'function definition is not permitted' ) ;
}
for ( var i = 0 , len = varName . value + 1 ; i < len ; i ++ ) {
var index = lastInstrIndex - i ;
if ( instr [ index ] . type === IVAR ) {
instr [ index ] = new Instruction ( IVARNAME , instr [ index ] . value ) ;
}
}
this . parseVariableAssignmentExpression ( varValue ) ;
instr . push ( new Instruction ( IEXPR , varValue ) ) ;
instr . push ( new Instruction ( IFUNDEF , varName . value ) ) ;
continue ;
}
if ( varName . type !== IVAR && varName . type !== IMEMBER ) {
throw new Error ( 'expected variable for assignment' ) ;
}
this . parseVariableAssignmentExpression ( varValue ) ;
instr . push ( new Instruction ( IVARNAME , varName . value ) ) ;
instr . push ( new Instruction ( IEXPR , varValue ) ) ;
instr . push ( binaryInstruction ( '=' ) ) ;
}
} ;
ParserState . prototype . parseConditionalExpression = function ( instr ) {
this . parseOrExpression ( instr ) ;
while ( this . accept ( TOP , '?' ) ) {
var trueBranch = [ ] ;
var falseBranch = [ ] ;
this . parseConditionalExpression ( trueBranch ) ;
this . expect ( TOP , ':' ) ;
this . parseConditionalExpression ( falseBranch ) ;
instr . push ( new Instruction ( IEXPR , trueBranch ) ) ;
instr . push ( new Instruction ( IEXPR , falseBranch ) ) ;
instr . push ( ternaryInstruction ( '?' ) ) ;
}
} ;
ParserState . prototype . parseOrExpression = function ( instr ) {
this . parseAndExpression ( instr ) ;
while ( this . accept ( TOP , 'or' ) ) {
var falseBranch = [ ] ;
this . parseAndExpression ( falseBranch ) ;
instr . push ( new Instruction ( IEXPR , falseBranch ) ) ;
instr . push ( binaryInstruction ( 'or' ) ) ;
}
} ;
ParserState . prototype . parseAndExpression = function ( instr ) {
this . parseComparison ( instr ) ;
while ( this . accept ( TOP , 'and' ) ) {
var trueBranch = [ ] ;
this . parseComparison ( trueBranch ) ;
instr . push ( new Instruction ( IEXPR , trueBranch ) ) ;
instr . push ( binaryInstruction ( 'and' ) ) ;
}
} ;
var COMPARISON _OPERATORS = [ '==' , '!=' , '<' , '<=' , '>=' , '>' , 'in' ] ;
ParserState . prototype . parseComparison = function ( instr ) {
this . parseAddSub ( instr ) ;
while ( this . accept ( TOP , COMPARISON _OPERATORS ) ) {
var op = this . current ;
this . parseAddSub ( instr ) ;
instr . push ( binaryInstruction ( op . value ) ) ;
}
} ;
var ADD _SUB _OPERATORS = [ '+' , '-' , '||' ] ;
ParserState . prototype . parseAddSub = function ( instr ) {
this . parseTerm ( instr ) ;
while ( this . accept ( TOP , ADD _SUB _OPERATORS ) ) {
var op = this . current ;
this . parseTerm ( instr ) ;
instr . push ( binaryInstruction ( op . value ) ) ;
}
} ;
var TERM _OPERATORS = [ '*' , '/' , '%' ] ;
ParserState . prototype . parseTerm = function ( instr ) {
this . parseFactor ( instr ) ;
while ( this . accept ( TOP , TERM _OPERATORS ) ) {
var op = this . current ;
this . parseFactor ( instr ) ;
instr . push ( binaryInstruction ( op . value ) ) ;
}
} ;
ParserState . prototype . parseFactor = function ( instr ) {
var unaryOps = this . tokens . unaryOps ;
function isPrefixOperator ( token ) {
return token . value in unaryOps ;
}
this . save ( ) ;
if ( this . accept ( TOP , isPrefixOperator ) ) {
if ( this . current . value !== '-' && this . current . value !== '+' ) {
if ( this . nextToken . type === TPAREN && this . nextToken . value === '(' ) {
this . restore ( ) ;
this . parseExponential ( instr ) ;
return ;
} else if ( this . nextToken . type === TSEMICOLON || this . nextToken . type === TCOMMA || this . nextToken . type === TEOF || ( this . nextToken . type === TPAREN && this . nextToken . value === ')' ) ) {
this . restore ( ) ;
this . parseAtom ( instr ) ;
return ;
}
}
var op = this . current ;
this . parseFactor ( instr ) ;
instr . push ( unaryInstruction ( op . value ) ) ;
} else {
this . parseExponential ( instr ) ;
}
} ;
ParserState . prototype . parseExponential = function ( instr ) {
this . parsePostfixExpression ( instr ) ;
while ( this . accept ( TOP , '^' ) ) {
this . parseFactor ( instr ) ;
instr . push ( binaryInstruction ( '^' ) ) ;
}
} ;
ParserState . prototype . parsePostfixExpression = function ( instr ) {
this . parseFunctionCall ( instr ) ;
while ( this . accept ( TOP , '!' ) ) {
instr . push ( unaryInstruction ( '!' ) ) ;
}
} ;
ParserState . prototype . parseFunctionCall = function ( instr ) {
var unaryOps = this . tokens . unaryOps ;
function isPrefixOperator ( token ) {
return token . value in unaryOps ;
}
if ( this . accept ( TOP , isPrefixOperator ) ) {
var op = this . current ;
this . parseAtom ( instr ) ;
instr . push ( unaryInstruction ( op . value ) ) ;
} else {
this . parseMemberExpression ( instr ) ;
while ( this . accept ( TPAREN , '(' ) ) {
if ( this . accept ( TPAREN , ')' ) ) {
instr . push ( new Instruction ( IFUNCALL , 0 ) ) ;
} else {
var argCount = this . parseArgumentList ( instr ) ;
instr . push ( new Instruction ( IFUNCALL , argCount ) ) ;
}
}
}
} ;
ParserState . prototype . parseArgumentList = function ( instr ) {
var argCount = 0 ;
while ( ! this . accept ( TPAREN , ')' ) ) {
this . parseExpression ( instr ) ;
++ argCount ;
while ( this . accept ( TCOMMA ) ) {
this . parseExpression ( instr ) ;
++ argCount ;
}
}
return argCount ;
} ;
ParserState . prototype . parseMemberExpression = function ( instr ) {
this . parseAtom ( instr ) ;
while ( this . accept ( TOP , '.' ) || this . accept ( TBRACKET , '[' ) ) {
var op = this . current ;
if ( op . value === '.' ) {
if ( ! this . allowMemberAccess ) {
throw new Error ( 'unexpected ".", member access is not permitted' ) ;
}
this . expect ( TNAME ) ;
instr . push ( new Instruction ( IMEMBER , this . current . value ) ) ;
} else if ( op . value === '[' ) {
if ( ! this . tokens . isOperatorEnabled ( '[' ) ) {
throw new Error ( 'unexpected "[]", arrays are disabled' ) ;
}
this . parseExpression ( instr ) ;
this . expect ( TBRACKET , ']' ) ;
instr . push ( binaryInstruction ( '[' ) ) ;
} else {
throw new Error ( 'unexpected symbol: ' + op . value ) ;
}
}
} ;
function add ( a , b ) {
return Number ( a ) + Number ( b ) ;
}
function sub ( a , b ) {
return a - b ;
}
function mul ( a , b ) {
return a * b ;
}
function div ( a , b ) {
return a / b ;
}
function mod ( a , b ) {
return a % b ;
}
function concat ( a , b ) {
if ( Array . isArray ( a ) && Array . isArray ( b ) ) {
return a . concat ( b ) ;
}
return '' + a + b ;
}
function equal ( a , b ) {
return a === b ;
}
function notEqual ( a , b ) {
return a !== b ;
}
function greaterThan ( a , b ) {
return a > b ;
}
function lessThan ( a , b ) {
return a < b ;
}
function greaterThanEqual ( a , b ) {
return a >= b ;
}
function lessThanEqual ( a , b ) {
return a <= b ;
}
function andOperator ( a , b ) {
return Boolean ( a && b ) ;
}
function orOperator ( a , b ) {
return Boolean ( a || b ) ;
}
function inOperator ( a , b ) {
return contains ( b , a ) ;
}
function sinh ( a ) {
return ( ( Math . exp ( a ) - Math . exp ( - a ) ) / 2 ) ;
}
function cosh ( a ) {
return ( ( Math . exp ( a ) + Math . exp ( - a ) ) / 2 ) ;
}
function tanh ( a ) {
if ( a === Infinity ) return 1 ;
if ( a === - Infinity ) return - 1 ;
return ( Math . exp ( a ) - Math . exp ( - a ) ) / ( Math . exp ( a ) + Math . exp ( - a ) ) ;
}
function asinh ( a ) {
if ( a === - Infinity ) return a ;
return Math . log ( a + Math . sqrt ( ( a * a ) + 1 ) ) ;
}
function acosh ( a ) {
return Math . log ( a + Math . sqrt ( ( a * a ) - 1 ) ) ;
}
function atanh ( a ) {
return ( Math . log ( ( 1 + a ) / ( 1 - a ) ) / 2 ) ;
}
function log10 ( a ) {
return Math . log ( a ) * Math . LOG10E ;
}
function neg ( a ) {
return - a ;
}
function not ( a ) {
return ! a ;
}
function trunc ( a ) {
return a < 0 ? Math . ceil ( a ) : Math . floor ( a ) ;
}
function random ( a ) {
return Math . random ( ) * ( a || 1 ) ;
}
function factorial ( a ) { // a!
return gamma ( a + 1 ) ;
}
function isInteger ( value ) {
return isFinite ( value ) && ( value === Math . round ( value ) ) ;
}
var GAMMA _G = 4.7421875 ;
var GAMMA _P = [
0.99999999999999709182 ,
57.156235665862923517 , - 59.597960355475491248 ,
14.136097974741747174 , - 0.49191381609762019978 ,
0.33994649984811888699 e - 4 ,
0.46523628927048575665 e - 4 , - 0.98374475304879564677 e - 4 ,
0.15808870322491248884 e - 3 , - 0.21026444172410488319 e - 3 ,
0.21743961811521264320 e - 3 , - 0.16431810653676389022 e - 3 ,
0.84418223983852743293 e - 4 , - 0.26190838401581408670 e - 4 ,
0.36899182659531622704 e - 5
] ;
// Gamma function from math.js
function gamma ( n ) {
var t , x ;
if ( isInteger ( n ) ) {
if ( n <= 0 ) {
return isFinite ( n ) ? Infinity : NaN ;
}
if ( n > 171 ) {
return Infinity ; // Will overflow
}
var value = n - 2 ;
var res = n - 1 ;
while ( value > 1 ) {
res *= value ;
value -- ;
}
if ( res === 0 ) {
res = 1 ; // 0! is per definition 1
}
return res ;
}
if ( n < 0.5 ) {
return Math . PI / ( Math . sin ( Math . PI * n ) * gamma ( 1 - n ) ) ;
}
if ( n >= 171.35 ) {
return Infinity ; // will overflow
}
if ( n > 85.0 ) { // Extended Stirling Approx
var twoN = n * n ;
var threeN = twoN * n ;
var fourN = threeN * n ;
var fiveN = fourN * n ;
return Math . sqrt ( 2 * Math . PI / n ) * Math . pow ( ( n / Math . E ) , n ) *
( 1 + ( 1 / ( 12 * n ) ) + ( 1 / ( 288 * twoN ) ) - ( 139 / ( 51840 * threeN ) ) -
( 571 / ( 2488320 * fourN ) ) + ( 163879 / ( 209018880 * fiveN ) ) +
( 5246819 / ( 75246796800 * fiveN * n ) ) ) ;
}
-- n ;
x = GAMMA _P [ 0 ] ;
for ( var i = 1 ; i < GAMMA _P . length ; ++ i ) {
x += GAMMA _P [ i ] / ( n + i ) ;
}
t = n + GAMMA _G + 0.5 ;
return Math . sqrt ( 2 * Math . PI ) * Math . pow ( t , n + 0.5 ) * Math . exp ( - t ) * x ;
}
function stringOrArrayLength ( s ) {
if ( Array . isArray ( s ) ) {
return s . length ;
}
return String ( s ) . length ;
}
function hypot ( ) {
var sum = 0 ;
var larg = 0 ;
for ( var i = 0 ; i < arguments . length ; i ++ ) {
var arg = Math . abs ( arguments [ i ] ) ;
var div ;
if ( larg < arg ) {
div = larg / arg ;
sum = ( sum * div * div ) + 1 ;
larg = arg ;
} else if ( arg > 0 ) {
div = arg / larg ;
sum += div * div ;
} else {
sum += arg ;
}
}
return larg === Infinity ? Infinity : larg * Math . sqrt ( sum ) ;
}
function condition ( cond , yep , nope ) {
return cond ? yep : nope ;
}
/ * *
* Decimal adjustment of a number .
* From @ escopecz .
*
* @ param { Number } value The number .
* @ param { Integer } exp The exponent ( the 10 logarithm of the adjustment base ) .
* @ return { Number } The adjusted value .
* /
function roundTo ( value , exp ) {
// If the exp is undefined or zero...
if ( typeof exp === 'undefined' || + exp === 0 ) {
return Math . round ( value ) ;
}
value = + value ;
exp = - ( + exp ) ;
// If the value is not a number or the exp is not an integer...
if ( isNaN ( value ) || ! ( typeof exp === 'number' && exp % 1 === 0 ) ) {
return NaN ;
}
// Shift
value = value . toString ( ) . split ( 'e' ) ;
value = Math . round ( + ( value [ 0 ] + 'e' + ( value [ 1 ] ? ( + value [ 1 ] - exp ) : - exp ) ) ) ;
// Shift back
value = value . toString ( ) . split ( 'e' ) ;
return + ( value [ 0 ] + 'e' + ( value [ 1 ] ? ( + value [ 1 ] + exp ) : exp ) ) ;
}
function setVar ( name , value , variables ) {
if ( variables ) variables [ name ] = value ;
return value ;
}
function arrayIndex ( array , index ) {
return array [ index | 0 ] ;
}
function max ( array ) {
if ( arguments . length === 1 && Array . isArray ( array ) ) {
return Math . max . apply ( Math , array ) ;
} else {
return Math . max . apply ( Math , arguments ) ;
}
}
function min ( array ) {
if ( arguments . length === 1 && Array . isArray ( array ) ) {
return Math . min . apply ( Math , array ) ;
} else {
return Math . min . apply ( Math , arguments ) ;
}
}
function arrayMap ( f , a ) {
if ( typeof f !== 'function' ) {
throw new Error ( 'First argument to map is not a function' ) ;
}
if ( ! Array . isArray ( a ) ) {
throw new Error ( 'Second argument to map is not an array' ) ;
}
return a . map ( function ( x , i ) {
return f ( x , i ) ;
} ) ;
}
function arrayFold ( f , init , a ) {
if ( typeof f !== 'function' ) {
throw new Error ( 'First argument to fold is not a function' ) ;
}
if ( ! Array . isArray ( a ) ) {
throw new Error ( 'Second argument to fold is not an array' ) ;
}
return a . reduce ( function ( acc , x , i ) {
return f ( acc , x , i ) ;
} , init ) ;
}
function arrayFilter ( f , a ) {
if ( typeof f !== 'function' ) {
throw new Error ( 'First argument to filter is not a function' ) ;
}
if ( ! Array . isArray ( a ) ) {
throw new Error ( 'Second argument to filter is not an array' ) ;
}
return a . filter ( function ( x , i ) {
return f ( x , i ) ;
} ) ;
}
function stringOrArrayIndexOf ( target , s ) {
if ( ! ( Array . isArray ( s ) || typeof s === 'string' ) ) {
throw new Error ( 'Second argument to indexOf is not a string or array' ) ;
}
return s . indexOf ( target ) ;
}
function arrayJoin ( sep , a ) {
if ( ! Array . isArray ( a ) ) {
throw new Error ( 'Second argument to join is not an array' ) ;
}
return a . join ( sep ) ;
}
function sign ( x ) {
return ( ( x > 0 ) - ( x < 0 ) ) || + x ;
}
var ONE _THIRD = 1 / 3 ;
function cbrt ( x ) {
return x < 0 ? - Math . pow ( - x , ONE _THIRD ) : Math . pow ( x , ONE _THIRD ) ;
}
function expm1 ( x ) {
return Math . exp ( x ) - 1 ;
}
function log1p ( x ) {
return Math . log ( 1 + x ) ;
}
function log2 ( x ) {
return Math . log ( x ) / Math . LN2 ;
}
class Parser {
constructor ( options ) {
this . options = options || { } ;
this . unaryOps = {
sin : Math . sin ,
cos : Math . cos ,
tan : Math . tan ,
asin : Math . asin ,
acos : Math . acos ,
atan : Math . atan ,
sinh : Math . sinh || sinh ,
cosh : Math . cosh || cosh ,
tanh : Math . tanh || tanh ,
asinh : Math . asinh || asinh ,
acosh : Math . acosh || acosh ,
atanh : Math . atanh || atanh ,
sqrt : Math . sqrt ,
cbrt : Math . cbrt || cbrt ,
log : Math . log ,
log2 : Math . log2 || log2 ,
ln : Math . log ,
lg : Math . log10 || log10 ,
log10 : Math . log10 || log10 ,
expm1 : Math . expm1 || expm1 ,
log1p : Math . log1p || log1p ,
abs : Math . abs ,
ceil : Math . ceil ,
floor : Math . floor ,
round : Math . round ,
trunc : Math . trunc || trunc ,
'-' : neg ,
'+' : Number ,
exp : Math . exp ,
not : not ,
length : stringOrArrayLength ,
'!' : factorial ,
sign : Math . sign || sign
} ;
this . binaryOps = {
'+' : add ,
'-' : sub ,
'*' : mul ,
'/' : div ,
'%' : mod ,
'^' : Math . pow ,
'||' : concat ,
'==' : equal ,
'!=' : notEqual ,
'>' : greaterThan ,
'<' : lessThan ,
'>=' : greaterThanEqual ,
'<=' : lessThanEqual ,
and : andOperator ,
or : orOperator ,
'in' : inOperator ,
'=' : setVar ,
'[' : arrayIndex
} ;
this . ternaryOps = {
'?' : condition
} ;
this . functions = {
random : random ,
fac : factorial ,
min : min ,
max : max ,
hypot : Math . hypot || hypot ,
pyt : Math . hypot || hypot , // backward compat
pow : Math . pow ,
atan2 : Math . atan2 ,
'if' : condition ,
gamma : gamma ,
roundTo : roundTo ,
map : arrayMap ,
fold : arrayFold ,
filter : arrayFilter ,
indexOf : stringOrArrayIndexOf ,
join : arrayJoin
} ;
this . consts = {
E : Math . E ,
PI : Math . PI ,
'true' : true ,
'false' : false
} ;
}
parse ( expr ) {
var instr = [ ] ;
var parserState = new ParserState (
this ,
new TokenStream ( this , expr ) ,
{ allowMemberAccess : this . options . allowMemberAccess }
) ;
parserState . parseExpression ( instr ) ;
parserState . expect ( TEOF , 'EOF' ) ;
return new Expression ( instr , this ) ;
}
evaluate ( expr , variables ) {
return this . parse ( expr ) . evaluate ( variables ) ;
}
} ;
var sharedParser = new Parser ( ) ;
Parser . parse = function ( expr ) {
return sharedParser . parse ( expr ) ;
} ;
Parser . evaluate = function ( expr , variables ) {
return sharedParser . parse ( expr ) . evaluate ( variables ) ;
} ;
var optionNameMap = {
'+' : 'add' ,
'-' : 'subtract' ,
'*' : 'multiply' ,
'/' : 'divide' ,
'%' : 'remainder' ,
'^' : 'power' ,
'!' : 'factorial' ,
'<' : 'comparison' ,
'>' : 'comparison' ,
'<=' : 'comparison' ,
'>=' : 'comparison' ,
'==' : 'comparison' ,
'!=' : 'comparison' ,
'||' : 'concatenate' ,
'and' : 'logical' ,
'or' : 'logical' ,
'not' : 'logical' ,
'?' : 'conditional' ,
':' : 'conditional' ,
'=' : 'assignment' ,
'[' : 'array' ,
'()=' : 'fndef'
} ;
function getOptionName ( op ) {
return optionNameMap . hasOwnProperty ( op ) ? optionNameMap [ op ] : op ;
}
Parser . prototype . isOperatorEnabled = function ( op ) {
var optionName = getOptionName ( op ) ;
var operators = this . options . operators || { } ;
return ! ( optionName in operators ) || ! ! operators [ optionName ] ;
} ;
/ * !
2022-04-02 16:17:09 +00:00
Based on ndef . parser , by Raphael Graf < r @ undefined . ch >
2020-12-22 00:01:36 +00:00
http : //www.undefined.ch/mparser/index.html
2022-04-02 16:17:09 +00:00
Ported to JavaScript and modified by Matthew Crumley < email @ matthewcrumley . com > ( http : //silentmatt.com/)
Ported to QMLJS with modifications done accordingly done by Ad5001 < mail @ ad5001 . eu > ( https : //ad5001.eu)
2020-12-22 00:01:36 +00:00
You are free to use and modify this code in anyway you find useful . Please leave this comment in the code
to acknowledge its original source . If you feel like it , I enjoy hearing about projects that use my code ,
but don ' t feel like you have to let me know or ask permission .
* /