@@ -3,12 +3,14 @@ export default function evalNumExpr(amount: string): number {
33 const operators = [
44 new Operator ( "-" , 2 ) ,
55 new Operator ( "+" , 2 ) ,
6+ new Operator ( "add_percentage" , 2 ) ,
7+ new Operator ( "sub_percentage" , 2 ) ,
68 new Operator ( "/" , 3 ) ,
79 new Operator ( "*" , 3 ) ,
810 new Operator ( "^" , 4 ) ,
911 ] ;
1012 const tokens = tokenize ( amount ) ;
11- const outputQueue = new Queue < string > ( ) ;
13+ const outputQueue = new Queue < string | number > ( ) ;
1214 const operatorsStack = new Stack < OperatorFns | "(" | ")" > ( ) ;
1315 /**
1416 * https://en.wikipedia.org/wiki/Shunting-yard_algorithm
@@ -100,7 +102,14 @@ export default function evalNumExpr(amount: string): number {
100102 return values . pop ( ) ;
101103}
102104
103- type OperatorFns = "-" | "+" | "/" | "*" | "^" ;
105+ type OperatorFns =
106+ | "-"
107+ | "+"
108+ | "/"
109+ | "*"
110+ | "^"
111+ | "add_percentage"
112+ | "sub_percentage" ;
104113
105114class Operator {
106115 static readonly LEFT_ASSOCIATIVE = 0 ;
@@ -118,6 +127,8 @@ class Operator {
118127 case "+" :
119128 case "*" :
120129 case "/" :
130+ case "add_percentage" :
131+ case "sub_percentage" :
121132 this . associativity = Operator . LEFT_ASSOCIATIVE ;
122133 break ;
123134 case "^" :
@@ -146,6 +157,10 @@ class Operator {
146157 return ( operandA || 0 ) + ( operandB || 0 ) ;
147158 case "-" :
148159 return ( operandA || 0 ) - ( operandB || 0 ) ;
160+ case "add_percentage" :
161+ return ( operandA || 0 ) + ( ( operandA || 0 ) * ( operandB || 0 ) ) / 100 ;
162+ case "sub_percentage" :
163+ return ( operandA || 0 ) - ( ( operandA || 0 ) * ( operandB || 0 ) ) / 100 ;
149164 case "*" :
150165 return (
151166 ( typeof operandA !== "undefined" ? operandA : 1 ) *
@@ -170,33 +185,51 @@ class Operator {
170185 }
171186}
172187
173- function tokenize ( amount : string ) : Array < OperatorFns | "(" | ")" | string > {
188+ function tokenize (
189+ amount : string
190+ ) : Array < OperatorFns | "(" | ")" | string | number > {
174191 // sanitize the amount
175192 amount = amount
176193 . replace ( / \s / gi, "" ) // replace all spaces
177- . replace ( / % / gi, "/100" ) // replace percentage; TODO: Add modulus support as well
178194 . replace ( / [ x X ] / gi, "*" ) // replace the x with multiplier *
179- . replace ( / [ ^ + - / * ^ \d ( ) . ] / g, "" )
195+ . replace ( / [ ^ + - / * ^ \d ( ) . % ] / g, "" )
196+ . replace ( / \+ ( \d + ) % (? ! [ * / ( \d ] ) / gi, "add_percentage$1" )
197+ . replace ( / - ( \d + ) % (? ! [ * / ( \d ] ) / gi, "sub_percentage$1" )
198+ . replace ( / % ( [ \d ( ] ) / gi, "/100*$1" )
199+ . replace ( / % / gi, "/100" )
180200 // i have no idea why commas were not replaced :(
181201 // it works in Test Env (NodeJs)
182202 . replace ( / , / g, "" ) ;
183- const keywords = [ "-" , "+" , "/" , "*" , "^" , "(" , ")" ] ;
203+ const keywords = [ "-" , "+" , "/" , "*" , "^" , "%" , " (", ")" ] ;
184204 const tokens = [ ] ;
185- let numberValue : string = "" ;
205+ let token : string = "" ;
186206 for ( let i = 0 ; i < amount . length ; i ++ ) {
187207 const char = amount [ i ] ;
188- if ( keywords . indexOf ( char ) !== - 1 ) {
189- if ( numberValue ) {
190- tokens . push ( numberValue ) ;
191- numberValue = "" ;
192- }
193- tokens . push ( char ) ;
194- } else {
195- numberValue += char ;
208+ const nextChar = amount [ i + 1 ] || "" ;
209+ token += char ;
210+ switch ( true ) {
211+ case ! isNaN ( Number ( token ) ) &&
212+ Number . isFinite ( Number ( token ) ) &&
213+ isNaN ( Number ( nextChar ) ) &&
214+ nextChar !== "." :
215+ tokens . push ( Number ( token ) ) ;
216+ token = "" ;
217+ break ;
218+ case keywords . indexOf ( token ) !== - 1 :
219+ tokens . push ( token ) ;
220+ token = "" ;
221+ break ;
222+ case token === "add_percentage" :
223+ case token === "sub_percentage" :
224+ tokens . push ( token ) ;
225+ token = "" ;
226+ break ;
227+ default :
228+ break ;
196229 }
197230 }
198- if ( numberValue ) {
199- tokens . push ( numberValue ) ;
231+ if ( token ) {
232+ tokens . push ( token ) ;
200233 }
201234 return tokens ;
202235}
0 commit comments