Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Monster: evaluate math expressions in strings


  • Please log in to reply
87 replies to this topic
Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Monster: evaluate math expressions in strings w/o external programs

This script gradually grew from twenty lines to over 250 AHK code lines. It might not be the best approach, but each time only a few new lines were added, so it was never feasible to start all over for a more general expression parser.

The main point is not providing a standalone calculator, because there are hundreds of free ones available, but to allow evaluating a math expression typed in an editor or word processor. The compiled script can run in a PC, where AHK is not installed. It is easy to add new functions of one variable. Follow the examples of SGN (the sign of its argument), Fib (the n-th Fibonacci number) and FAC (the factorial). Two argument functions are implemented as operators: "9 gcd 6" is the same as it would be in functional form gcd(9,6), which is 3. "4choose2" = 6, the binomial coefficient. Min and Max can be chained: "-1 min 2 min -5" gives -5; "3 max 5 max 2" = max(3,5,2) = 5. Further operators can be easily added to the script.

User variables can be defined and used. They are kept in memory until the script is reloaded, with values assigned before they are used in expressions, like "a:=1; b:=2; a+b". In later evaluated expressions "a" and "b" still have their last assigned values.

User functions (even recursive ones) can also be defined in expressions:
f(x) := x < 2 ? 1 : x*f(x-1)
Here the formal parameter x is not defined as a variable, it is a placeholder for the value given to the function at a call. The function definition also remains in memory until the script is terminated.

As usage examples, two hotkeys obtain the math text from the current document. Ctrl+Win+- replaces the expression with the result, Ctrl+Win+= appends an "=" and the result. If a (multi-line) expression is selected, it gets evaluated. If there is nothing selected, the script searches for the last back-quote in the current line, and uses the text after it, until the insertion point (caret). For example, after pressing Win+Ctrl+= the text

"The area is `125*32"
becomes
"The area is 125*32 = 4000"

If instead you press Win+Ctrl+- at the the original line above, it becomes
"The area is 4000"

There are many features supported in the expressions:

- They can contain HEX (0x1ff), Decimal (123, 1.23e2) and Binary numbers ('1001, first bit = sign),
- arbitrary number of nested parentheses (..)
- variables (a, b), constants (e, pi, inch, foot, mile, ounce, pint, gallon, oz, lb)
- user defined functions
- ternary- (_? _ : _) and logical operators !; ||; &&
- relational operators =,<>; <,>,<=,>=
- special operators: GCD, MIN, MAX, Choose;
- bitwise operators ~; |; ^; &; <<, >>
- arithmetic operators +, - (or #); *, /, \ (or % = mod); ** (or @ = power);
(They are listed in the order of their precedence, from low to high, except the unary operators !,-, + and ~ are of highest precedence. The operators separated by commas are of equal precedence.)
- Functions Abs,Ceil,Exp,Floor,Log,Ln,Round,Sqrt,Sin,Cos,Tan,ASin,ACos,ATan,SGN,Fib,fac

The output format can also be specified.
- If the expression does not contain the format specifier "$", 6 decimal digits are shown in the general form (%0.6g in C), and integers are in decimal
- With ${k}: k (optional integer) decimal digits are shown after the decimal point, in case of floating point result
- With ${k}e or ${k}g: k decimal digits are shown of floating point results, in exponential or general scientific form, respectively
- With $x or $h: Rounded results are shown Hex
- With $b{W}: Rounded results are shown in binary form (LS W-bits; W="": first bit is sign: "1000" = -8, "0111" = 7)

There are no precision limitations in version 1.1 or later. All intermediate results are computed and stored in full 64-bit accuracy.

The ternary operator "condition ? exp1 : exp2" is implemented as two low precedence operators: "a ? b" (which returns "b" if "a" is true, that is nonempty and nonzero. It evaluates to the empty string if "a" is false, that is empty or 0. The other operator "b : c" returns "c" if "b" is the empty string, otherwise it returns "b". (It can also be used to set default values to uninitialized variables.) This saves time, because only the one of "b" or "c" is computed, which is returned. It also helps avoiding arithmetic errors, like in "x=0 ? 0 : 1/x". Here 1/x is not computed if x=0.

There is, however, a little difference between this implementation and the true ternary operator "a ? b : c". In Monster if "a" is true, "b" is returned by the "?" operator. If it happens to be empty (an error in an arithmetic expression), the following ":" operator does not know if "a" was false or this error occurred, and returns "c", instead of the empty "b". It was easy to change this behavior (e.g. "?" could return a special, non-numeric character when "a" is false and ":" checks for this character), but then we'd loose the "default value" function of the ":" operator "x:0".

Function names are internally enclosed by ' characters, operators are enclosed by « and » characters, to prevent problems when their names happen to be the prefix or postfix of another name (like tan/atan/at).

Please report bugs! Regular expressions are heavily used, and they are notoriously difficult to read (and to get them right).
; MONSTER Version 1.2 to EVALUATE ARITHMETIC EXPRESSIONS in strings (needs AHK 1.0.48+)
; Containing HEX, Signed Binary ('11 = -1, '011 = 3), scientific numbers (1.2e+5)
; Assignments :=, preceding an expression. E.g: a:=1; b:=2; a+b
; User defined functions: f(x) := expr;
; AHK Functions Abs|Ceil|Exp|Floor|Log|Ln|Round|Sqrt|Sin|Cos|Tan|ASin|ACos|ATan
; Predefined functions: SGN|Fib|Fac (sign, Fibonacci numbers, Factorials)
; '(',')'; Variables; Predefined operators GCD,MIN,MAX,Choose (2-parameter functions)
; Predefined constants: e, pi, inch, foot, mile, ounce, pint, gallon, oz, lb;
; Logic operators: !, ||, &&; ternary operator: (_?_:_);
; Relations: =,<>; <,>,<=,>=
; Binary operators: ~; |, ^, &, <<, >>
; Arithmetic operators: +, -; *, /, \ (or % = mod); ** (or @ = power)
; Output FORMAT: $x,$h: Hex; $b{W}: W-bit binary;
;    ${k}: k-digit fixpoint,  ${k}e,${k}g: k-digit scientific (Default $6g)

#SingleInstance Force
#NoEnv
SetBatchLines -1
Process Priority,,High

xe := 2.718281828459045, xpi := 3.141592653589793      ; referenced as "e", "pi"
xinch := 2.54, xfoot := 30.48, xmile := 1.609344       ; [cm], [cm], [Km]
xounce := 0.02841, xpint := 0.5682, xgallon := 4.54609 ; liters
xoz := 28.35, xlb := 453.59237                         ; gramms

/* -test cases
MsgBox % Eval("1e1")                                               ; 10
MsgBox % Eval("0x1E")                                              ; 30
MsgBox % Eval("ToBin(35)")                                         ; 100011
MsgBox % Eval("$b 35")                                             ; 0100011
MsgBox % Eval("'10010")                                            ; -14
MsgBox % Eval("2>3 ? 9 : 7")                                       ; 7
MsgBox % Eval("$2E 1e3 -50.0e+0 + 100.e-1")                        ; 9.60E+002
MsgBox % Eval("fact(x) := x < 2 ? 1 : x*fact(x-1); fact(5)")       ; 120
MsgBox % Eval("f(ab):=sqrt(ab)/ab; y:=f(2); ff(y):=y*(y-1)/2/x; x := 2; y+ff(3)/f(16)") ; 6.70711
MsgBox % Eval("x := qq:1; x := 5*x; y := x+1")                     ; 6 [if y empty, x := 1...]
MsgBox % Eval("x:=-!0; x<0 ? 2*x : sqrt(x)")                       ; -2
MsgBox % Eval("tan(atan(atan(tan(1))))-exp(sqrt(1))")              ; -1.71828
MsgBox % Eval("---2+++9 + ~-2 --1 -2*-3")                          ; 15
MsgBox % Eval("x1:=1; f1:=sin(x1)/x1; y:=2; f2:=sin(y)/y; f1/f2")  ; 1.85082
MsgBox % Eval("Round(fac(10)/fac(5)**2) - (10 choose 5) + Fib(8)") ; 21
MsgBox % Eval("1 min-1 min-2 min 2")                               ; -2
MsgBox % Eval("(-1>>1<=9 && 3>2)<<2>>1")                           ; 2
MsgBox % Eval("(1 = 1) + (2<>3 || 2 < 1) + (9>=-1 && 3>2)")        ; 3
MsgBox % Eval("$b6 -21/3")                                         ; 111001
MsgBox % Eval("$b ('1001 << 5) | '01000")                          ; 100101000
MsgBox % Eval("$0 194*lb/1000")                                    ; 88 [Kg]
MsgBox % Eval("$x ~0xfffffff0 & 7 | 0x100 << 2")                   ; 0x407
MsgBox % Eval("- 1 * (+pi -((3%5))) +pi+ 1-2 + e-ROUND(abs(sqrt(floor(2)))**2)-e+pi $9") ; 3.141592654
MsgBox % Eval("(20+4 GCD abs(2**4)) + (9 GCD (6 CHOOSE 2))")       ; 11
t := A_TickCount
Loop 1000
   r := Eval("x:=" A_Index/1000 ";atan(x)-exp(sqrt(x))")           ; simulated plot
t := A_TickCount - t
MsgBox Result = %r%`nTime = %t%                                    ; -1.93288: ~400 ms [on Inspiron 9300]
*/

^#-::                                  ; Replace selection or `expression with result
^#=::                                  ; Append result to selection or `expression
   ClipBoard =
   SendInput ^c                        ; copy selection
   ClipWait 0.5
   If (ErrorLevel) {
      SendInput +{HOME}^c              ; copy, keep selection to overwrite (^x for some apps)
      ClipWait 1
      IfEqual ErrorLevel,1, Return
      If RegExMatch(ClipBoard, "(.*)(``)(.*)", y)
         SendInput %  "{RAW}" y1 . (A_ThisHotKey="^#=" ? y3 . " = "  : "") . Eval(y3)
   } Else
      SendInput % "{RAW}" . (A_ThisHotKey="^#=" ? ClipBoard . " = "  : "") . Eval(ClipBoard)
Return

Eval(x) {                              ; non-recursive PRE/POST PROCESSING: I/O forms, numbers, ops, ";"
   Local FORM, FormF, FormI, i, W, y, y1, y2, y3, y4
   FormI := A_FormatInteger, FormF := A_FormatFloat

   SetFormat Integer, D                ; decimal intermediate results!
   RegExMatch(x, "\$(b|h|x|)(\d*[eEgG]?)", y)
   FORM := y1, W := y2                 ; HeX, Bin, .{digits} output format
   SetFormat FLOAT, 0.16e              ; Full intermediate float precision
   StringReplace x, x, %y%             ; remove $..
   Loop
      If RegExMatch(x, "i)(.*)(0x[a-f\d]*)(.*)", y)
         x := y1 . y2+0 . y3           ; convert hex numbers to decimal
      Else Break
   Loop
      If RegExMatch(x, "(.*)'([01]*)(.*)", y)
         x := y1 . FromBin(y2) . y3    ; convert binary numbers to decimal: sign = first bit
      Else Break
   x := RegExReplace(x,"(^|[^.\d])(\d+)(e|E)","$1$2.$3") ; add missing '.' before E (1e3 -> 1.e3)
                                       ; literal scientific numbers between ‘ and ’ chars
   x := RegExReplace(x,"(\d*\.\d*|\d)([eE][+-]?\d+)","‘$1$2’")

   StringReplace x, x,`%, \, All       ; %  -> \ (= MOD)
   StringReplace x, x, **,@, All       ; ** -> @ for easier process
   StringReplace x, x, +, ±, All       ; ± is addition
   x := RegExReplace(x,"(‘[^’]*)±","$1+") ; ...not inside literal numbers
   StringReplace x, x, -, ¬, All       ; ¬ is subtraction
   x := RegExReplace(x,"(‘[^’]*)¬","$1-") ; ...not inside literal numbers

   Loop Parse, x, `;
      y := Eval1(A_LoopField)          ; work on pre-processed sub expressions
                                       ; return result of last sub-expression (numeric)
   If FORM = b                         ; convert output to binary
      y := W ? ToBinW(Round(y),W) : ToBin(Round(y))
   Else If (FORM="h" or FORM="x") {
      SetFormat Integer, Hex           ; convert output to hex
      y := Round(y) + 0
   }
   Else {
      W := W="" ? "0.6g" : "0." . W    ; Set output form, Default = 6 decimal places
      SetFormat FLOAT, %W%
      y += 0.0
   }
   SetFormat Integer, %FormI%          ; restore original formats
   SetFormat FLOAT,   %FormF%
   Return y
}

Eval1(x) {                             ; recursive PREPROCESSING of :=, vars, (..) [decimal, no ";"]
   Local i, y, y1, y2, y3
                                       ; save function definition: f(x) := expr
   If RegExMatch(x, "(\S*?)\((.*?)\)\s*:=\s*(.*)", y) {
      f%y1%__X := y2, f%y1%__F := y3
      Return
   }
                                       ; execute leftmost ":=" operator of a := b := ...
   If RegExMatch(x, "(\S*?)\s*:=\s*(.*)", y) {
      y := "x" . y1                    ; user vars internally start with x to avoid name conflicts
      Return %y% := Eval1(y2)
   }
                                       ; here: no variable to the left of last ":="
   x := RegExReplace(x,"([\)’.\w]\s+|[\)’])([a-z_A-Z]+)","$1«$2»")  ; op -> «op»

   x := RegExReplace(x,"\s+")          ; remove spaces, tabs, newlines

   x := RegExReplace(x,"([a-z_A-Z]\w*)\(","'$1'(") ; func( -> 'func'( to avoid atan|tan conflicts

   x := RegExReplace(x,"([a-z_A-Z]\w*)([^\w'»’]|$)","%x$1%$2") ; VAR -> %xVAR%
   x := RegExReplace(x,"(‘[^’]*)%x[eE]%","$1e") ; in numbers %xe% -> e
   x := RegExReplace(x,"‘|’")          ; no more need for number markers
   Transform x, Deref, %x%             ; dereference all right-hand-side %var%-s

   Loop {                              ; find last innermost (..)
      If RegExMatch(x, "(.*)\(([^\(\)]*)\)(.*)", y)
         x := y1 . Eval@(y2) . y3      ; replace (x) with value of x
      Else Break
   }
   Return Eval@(x)
}

Eval@(x) {                             ; EVALUATE PRE-PROCESSED EXPRESSIONS [decimal, NO space, vars, (..), ";", ":="]
   Local i, y, y1, y2, y3, y4

   If x is number                      ; no more operators left
      Return x
                                       ; execute rightmost ?,: operator
   RegExMatch(x, "(.*)(\?|:)(.*)", y)
   IfEqual y2,?,  Return Eval@(y1) ? Eval@(y3) : ""
   IfEqual y2,:,  Return ((y := Eval@(y1)) = "" ? Eval@(y3) : y)

   StringGetPos i, x, ||, R            ; execute rightmost || operator
   IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) || Eval@(SubStr(x,3+i))
   StringGetPos i, x, &&, R            ; execute rightmost && operator
   IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) && Eval@(SubStr(x,3+i))
                                       ; execute rightmost =, <> operator
   RegExMatch(x, "(.*)(?<![\<\>])(\<\>|=)(.*)", y)
   IfEqual y2,=,  Return Eval@(y1) =  Eval@(y3)
   IfEqual y2,<>, Return Eval@(y1) <> Eval@(y3)
                                       ; execute rightmost <,>,<=,>= operator
   RegExMatch(x, "(.*)(?<![\<\>])(\<=?|\>=?)(?![\<\>])(.*)", y)
   IfEqual y2,<,  Return Eval@(y1) <  Eval@(y3)
   IfEqual y2,>,  Return Eval@(y1) >  Eval@(y3)
   IfEqual y2,<=, Return Eval@(y1) <= Eval@(y3)
   IfEqual y2,>=, Return Eval@(y1) >= Eval@(y3)
                                       ; execute rightmost user operator (low precedence)
   RegExMatch(x, "i)(.*)«(.*?)»(.*)", y)
   If IsFunc(y2)
      Return %y2%(Eval@(y1),Eval@(y3)) ; predefined relational ops

   StringGetPos i, x, |, R             ; execute rightmost | operator
   IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) | Eval@(SubStr(x,2+i))
   StringGetPos i, x, ^, R             ; execute rightmost ^ operator
   IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) ^ Eval@(SubStr(x,2+i))
   StringGetPos i, x, &, R             ; execute rightmost & operator
   IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) & Eval@(SubStr(x,2+i))
                                       ; execute rightmost <<, >> operator
   RegExMatch(x, "(.*)(\<\<|\>\>)(.*)", y)
   IfEqual y2,<<, Return Eval@(y1) << Eval@(y3)
   IfEqual y2,>>, Return Eval@(y1) >> Eval@(y3)
                                       ; execute rightmost +- (not unary) operator
   RegExMatch(x, "(.*[^!\~±¬\@\*/\\])(±|¬)(.*)", y) ; lower precedence ops already handled
   IfEqual y2,±,  Return Eval@(y1) + Eval@(y3)
   IfEqual y2,¬,  Return Eval@(y1) - Eval@(y3)
                                       ; execute rightmost */% operator
   RegExMatch(x, "(.*)(\*|/|\\)(.*)", y)
   IfEqual y2,*,  Return Eval@(y1) * Eval@(y3)
   IfEqual y2,/,  Return Eval@(y1) / Eval@(y3)
   IfEqual y2,\,  Return Mod(Eval@(y1),Eval@(y3))
                                       ; execute rightmost power
   StringGetPos i, x, @, R
   IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) ** Eval@(SubStr(x,2+i))
                                       ; execute rightmost function, unary operator
   If !RegExMatch(x,"(.*)(!|±|¬|~|'(.*)')(.*)", y)
      Return x                         ; no more function (y1 <> "" only at multiple unaries: --+-)
   IfEqual y2,!,Return Eval@(y1 . !y4) ; unary !
   IfEqual y2,±,Return Eval@(y1 .  y4) ; unary +
   IfEqual y2,¬,Return Eval@(y1 . -y4) ; unary - (they behave like functions)
   IfEqual y2,~,Return Eval@(y1 . ~y4) ; unary ~
   If IsFunc(y3)
      Return Eval@(y1 . %y3%(y4))      ; built-in and predefined functions(y4)
   Return Eval@(y1 . Eval1(RegExReplace(f%y3%__F, f%y3%__X, y4))) ; LAST: user defined functions
}

ToBin(n) {      ; Binary representation of n. 1st bit is SIGN: -8 -> 1000, -1 -> 1, 0 -> 0, 8 -> 01000
   Return n=0||n=-1 ? -n : ToBin(n>>1) . n&1
}
ToBinW(n,W=8) { ; LS W-bits of Binary representation of n
   Loop %W%     ; Recursive (slower): Return W=1 ? n&1 : ToBinW(n>>1,W-1) . n&1
      b := n&1 . b, n >>= 1
   Return b
}
FromBin(bits) { ; Number converted from the binary "bits" string, 1st bit is SIGN
   n = 0
   Loop Parse, bits
      n += n + A_LoopField
   Return n - (SubStr(bits,1,1)<<StrLen(bits))
}

Sgn(x) {
   Return (x>0)-(x<0)
}

MIN(a,b) {
   Return a<b ? a : b
}
MAX(a,b) {
   Return a<b ? b : a
}
GCD(a,b) {      ; Euclidean GCD
   Return b=0 ? Abs(a) : GCD(b, mod(a,b))
}
Choose(n,k) {   ; Binomial coefficient
   p := 1, i := 0, k := k < n-k ? k : n-k
   Loop %k%                   ; Recursive (slower): Return k = 0 ? 1 : Choose(n-1,k-1)*n//k
      p *= (n-i)/(k-i), i+=1  ; FOR INTEGERS: p *= n-i, p //= ++i
   Return Round(p)
}

Fib(n) {        ; n-th Fibonacci number (n < 0 OK, iterative to avoid globals)
   a := 0, b := 1
   Loop % abs(n)-1
      c := b, b += a, a := c
   Return n=0 ? 0 : n>0 || n&1 ? b : -b
}
fac(n) {        ; n!
   Return n<2 ? 1 : n*fac(n-1)
}
Edit 20070228: Removed restrictions on user variable names, documented forbidden consecutive operators
Edit 20070301: Version 0.3 handles unary operators after others, like 2*-3.
Edit 20070302: Version 0.4 - minor speedup and fixed ATan, ASin, ACos.
Edit 20070302: Version 0.5 - further speedup, unary "!" and the ternary "? :" implemented, internal function renaming.
Edit 20070304: Version 0.6 - restores original number formats, to help using Monster in other scripts; removed work around fixed AHK bugs
Edit 20070309: Version 0.7 - scientific constants (1.2e+5), minor simplifications, negative Fibonacci, • -> '
Edit 20070309: Version 0.8 - $b{W}: Width of binary output is supported; Rounded results at binary and hex output
Edit 20070314: Version 1.0 - User defined functions; removed [..] around operators
Edit 20070425: Version 1.1 - Full 64-bit internal precision, scientific output format with $6E, $9g type directives
Edit 20080524: Bugfix. Order of converting hex to decimal and adding missing decimal point to scientific numbers is swapped (Thanks Rajat)
Edit 20100511: gallon is now US, not imperial (thanks flyingDman)
Edit 20100818: Version 1.2 - use dynamic function calls to shorten code, make adding predefined functions easier

jonny
  • Members
  • 2951 posts
  • Last active: Feb 24 2008 04:22 AM
  • Joined: 13 Nov 2004
Obviously very well thought-out. I don't have much time now, but as soon as I can I'll see if I can use it in my function grapher.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
It sounds appealing: the user enters a math expression and the limits, the step size, and he gets it plotted.

ribbet.1
  • Members
  • 198 posts
  • Last active: Feb 07 2012 01:21 AM
  • Joined: 20 Feb 2007
Now I will REALLY get girls! Thanks much! I see this one does use a clipboard wait, in order to not get the potential error discussed in your other thread, and that it also gives optional replacement of the equation with its solution, or the placement of a "=" and then the solution as discussed there also. I am sure this calculator will be far more than adequate for my everyday needs. But as Rev. Dobbs says, too much is better than not enough! Assuming all goes well with it I sincerely hope you compile this calculator and post it to Simtel and others, Laszlo!


That plotter idea sounds really nice too. Not something I need ATM but an item every math student could use. Super idea to be able to write a paper and have this type of thing generated for you in your own editor.

ribbet.1
  • Members
  • 198 posts
  • Last active: Feb 07 2012 01:21 AM
  • Joined: 20 Feb 2007

if there is nothing selected, the script searches for the last back-quote in the current line, and uses the text after it, to the caret


Not really a bug report but I note that you have to press either hotkey twice if text is not selected. Once selects the text, second time evaluates it. I suppose that allows you to review if that is what you wanted it to evaluate. Cheers, ribbet.1

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
RTFM ribbet.zip

The one thing is its hell slow, but again, its just anoying, you don't need this scritp to be superb fast. RE's are probably guilty.

Other then that, its superb. I miss ability to add custom functions though. Like

`d: f(x) = sin(x)/x; f(3)

then later only ` f(3) as f is already defined

You noticed I also suggest ; as expression separator.
Posted Image

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
For speed: the script wastes half a second waiting for the ClipBoard, if nothing is selected. You can avoid this delay by defining a separate hotkey for the case you want to evaluate the selection, another one if you want to search for the beginning of the expression.

Allowing dynamic functions is a good idea. Internally we have to use subroutines, because AHK has no means to call functions dynamically, but it can be done.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Why do you wait 0.5 s for the clipboard ?
You should set this as an option in the top of the script and default to be 0. Then if you get nothing from clipboard you could inform the user about this param eventualy + make him never see this message again if he knows it works but accidently pressed the the hotkey.

OFC, its not worth the trouble as it will work without dealy in 99% of cases. ITs bad choice in programming to slow down everybody just cuz some might have problems. Always leave an option for that and run at maximum speed. Everybody else should RTFM.

About dynamic function calls:
This is very needed. We can even later connect this script with jonny's grapher and get function display on single hotkey, within the active editor. :D
Posted Image

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Unfortunately, Windows sets up the ClipBoard asynchronously to AHK scripts. In every complex application (MS Word, MultiEdit, PowerPoint, Acrobat...) sending ^c and using the ClipBoard variable immediately afterwards sometimes fails. The ClipWait instruction is a must. Even the half sec delay is too little once in a while. If you are after speed, you cannot avoid separating the hotkeys.

jonny
  • Members
  • 2951 posts
  • Last active: Feb 24 2008 04:22 AM
  • Joined: 13 Nov 2004
There seems to be a few issues with unary minus (-):
Eval("3*3")  ;works fine: 9
Eval("-3*3")  ;works fine: -9
Eval("3*-3")  ;huh? -3?
Eval("-3*-3")  ;also -3

Also, you should use longer variable names internal to the function, to account for user variables. For instance, I have to use 'x' as a user variable name for this to be useful to me, but you're already using it inside Eval, so it screws the whole thing up. Also 'y' and 'z', which, like 'x', are very common variable names in math expressions. 'u' and 'i' are less common, but are also invalid. If you can't fix this, you should at least document it.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Thanks, Jonny. I prepended an x internally to the names of user variables. This way there are no restrictions, any normal name is valid (letters, digits). Even reserved AHK names, like ErrorLevel, A_Now.

The case "*- " is not easy. In general, operators after each other are not allowed, not like in programming languages. Until I find a simple solution, I documented this restriction.

User defined functions, as majkinetor suggested, are even harder. They can be done like C macros, but they blow up the expressions, and slow down the evaluation significantly. Monster was not meant for really large expressions, but for simple, general calculations. In these cases there is a workaround: "x:=2; f2:=sin(x)/x; x:=3; f3:=sin(x)/x; f2/f3". It is not much longer.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
I updated Monster to handle unary operators after others, like 2*-3 (thanks jonny). Please check if nothing is broken with the changes.

jonny
  • Members
  • 2951 posts
  • Last active: Feb 24 2008 04:22 AM
  • Joined: 13 Nov 2004
Great! It works now, but unfortunately it's a lot slower than the old method. I'll explain more in the grapher topic.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
If you don't intend to use logic-, bitwise-, user- or relational operators, you can comment out those parts, saving more than half of the RegExMatch calls, but the resulting code will not deserve to be called Monster. Maybe Speedy Gonzales :lol: ?

Some speedup can be achieved by checking in the beginning of the script if the expression is already reduced to a number, before searching for the operators. I updated the original post, accordingly (and fixed atan, asin and acos).

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Version 0.5 is posted. Changes: the top level Eval function is split into a non-recursive part (which handles the I/O formats and processes sub-expressions separated by ";"), and a recursive function Eval1 (which handles chained assignments, like a:=b:=sqrt(x)). This way a lot of duplicate work can be saved, and the process speeds up further.

There are two missing operators implemented: "!", the logical not, and the ternary "condition ? exp1 : exp2". The later is implemented as two low precedence operators: "a ? b" (which returns "b" if "a" is true or nonzero, and the empty string if "a" is false or 0) and "b : c" (which returns "c" if "b" is the empty string, otherwise it returns "c"). This can save time, because only the one of "b" or "c" is computed, which is returned. It also helps avoiding arithmetic errors, like in "x=0 ? 0 : 1/x". Here 1/x is not computed if x=0.

Function names are internally enclosed between ANSI 0183 characters (∙), which more robustly prevents problems when a user function happens to be the prefix or postfix of another function name (like tan/atan/at).