 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
Laszlo
Joined: 14 Feb 2005 Posts: 4016 Location: Pittsburgh
|
Posted: Wed Feb 28, 2007 1:06 am Post subject: Monster: evaluate math expressions in strings |
|
|
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 coefficien. 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 used. They are kept in memory until the script is reloaded. They can have values assigned before the expression is evaluated, like "a:=1; b:=2; a+b". In the next expression "a" and "b" still have their last values.
User functions (even recursive ones) can also be defined in the expression itself: f(x) := x < 2 ? 1 : x*f(x-1). Here the formal parameter x is not defined as a variable. The function definition 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"
After pressing Win+Ctrl+- the original line above 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 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);
- Unary operators: =, -, ! (logical), ~ (bitwise)
- Functions Abs,Ceil,Exp,Floor,Log,Ln,Round,Sqrt,Sin,Cos,Tan,ASin,ACos,ATan,SGN,Fib,fac
(They are in the order of their precedence, from low to high. The ones separated by commas are of equal precedence.)
The output format can 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, = 0 integer) decimal digits are shown after the 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 more 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 "c". (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 between ' characters, operators are between « 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).
| Code: | ; MONSTER Version 1.1 (needs AHK 1.0.46.12+)
; EVALUATE ARITHMETIC EXPRESSIONS containing HEX, Binary ('1001), scientific numbers (1.2e+5)
; (..); variables, constants: e, pi, inch, foot, mile, ounce, pint, gallon, oz, lb;
; (? :); logicals ||; &&; relationals =,<>; <,>,<=,>=; user operators GCD,MIN,MAX,Choose;
; |; ^; &; <<, >>; +, -; *, /, \ (or % = mod); ** (or @ = power); !,~;
; Functions Abs|Ceil|Exp|Floor|Log|Ln|Round|Sqrt|Sin|Cos|Tan|ASin|ACos|ATan|SGN|Fib|fac
; User defined functions: f(x) := expr;
; Output: $x,$h: Hex; $b{W}: W-bit binary; ${k},${k}e,${k}g: k-digit decimal (FixP/Sci), Default $6g
; "Assignments;" can preceed an expression: a:=1; b:=2; a+b
#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("$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. ~380 ms
*/
^#-:: ; 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)
IfEqual y2,choose,Return Choose(Eval@(y1),Eval@(y3))
IfEqual y2,Gcd, Return GCD( Eval@(y1),Eval@(y3))
IfEqual y2,Min, Return (y1:=Eval@(y1)) < (y3:=Eval@(y3)) ? y1 : y3
IfEqual y2,Max, Return (y1:=Eval@(y1)) > (y3:=Eval@(y3)) ? y1 : y3
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 IsLabel(y3)
GoTo %y3% ; built-in functions are executed last: y4 is number
Return Eval@(y1 . Eval1(RegExReplace(f%y3%__F, f%y3%__X, y4))) ; user defined function
Abs:
Return Eval@(y1 . Abs(y4))
Ceil:
Return Eval@(y1 . Ceil(y4))
Exp:
Return Eval@(y1 . Exp(y4))
Floor:
Return Eval@(y1 . Floor(y4))
Log:
Return Eval@(y1 . Log(y4))
Ln:
Return Eval@(y1 . Ln(y4))
Round:
Return Eval@(y1 . Round(y4))
Sqrt:
Return Eval@(y1 . Sqrt(y4))
Sin:
Return Eval@(y1 . Sin(y4))
Cos:
Return Eval@(y1 . Cos(y4))
Tan:
Return Eval@(y1 . Tan(y4))
ASin:
Return Eval@(y1 . ASin(y4))
ACos:
Return Eval@(y1 . ACos(y4))
ATan:
Return Eval@(y1 . ATan(y4))
Sgn:
Return Eval@(y1 . (y4>0)) ; Sign of x = (x>0)-(x<0)
Fib:
Return Eval@(y1 . Fib(y4))
Fac:
Return Eval@(y1 . Fac(y4))
}
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))
}
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)
Last edited by Laszlo on Sat May 24, 2008 8:34 pm; edited 22 times in total |
|
| Back to top |
|
 |
jonny
Joined: 13 Nov 2004 Posts: 3004 Location: Minnesota
|
Posted: Wed Feb 28, 2007 5:08 am Post subject: |
|
|
| 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. |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4016 Location: Pittsburgh
|
Posted: Wed Feb 28, 2007 5:14 am Post subject: |
|
|
| It sounds appealing: the user enters a math expression and the limits, the step size, and he gets it plotted. |
|
| Back to top |
|
 |
ribbet.1
Joined: 20 Feb 2007 Posts: 36 Location: D.C.
|
Posted: Wed Feb 28, 2007 1:02 pm Post subject: |
|
|
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. |
|
| Back to top |
|
 |
ribbet.1
Joined: 20 Feb 2007 Posts: 36 Location: D.C.
|
Posted: Wed Feb 28, 2007 1:16 pm Post subject: |
|
|
| Quote: | | 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 |
|
| Back to top |
|
 |
majkinetor
Joined: 24 May 2006 Posts: 3626 Location: Belgrade
|
Posted: Wed Feb 28, 2007 1:22 pm Post subject: |
|
|
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. _________________
 |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4016 Location: Pittsburgh
|
Posted: Wed Feb 28, 2007 4:20 pm Post subject: |
|
|
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. |
|
| Back to top |
|
 |
majkinetor
Joined: 24 May 2006 Posts: 3626 Location: Belgrade
|
Posted: Wed Feb 28, 2007 4:31 pm Post subject: |
|
|
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.  _________________
 |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4016 Location: Pittsburgh
|
Posted: Wed Feb 28, 2007 4:48 pm Post subject: |
|
|
| 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. |
|
| Back to top |
|
 |
jonny
Joined: 13 Nov 2004 Posts: 3004 Location: Minnesota
|
Posted: Thu Mar 01, 2007 3:29 am Post subject: |
|
|
There seems to be a few issues with unary minus (-):
| Code: | 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. |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4016 Location: Pittsburgh
|
Posted: Thu Mar 01, 2007 4:52 am Post subject: |
|
|
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. |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4016 Location: Pittsburgh
|
Posted: Fri Mar 02, 2007 4:46 am Post subject: |
|
|
| I updated Monster to handle unary operators after others, like 2*-3 (thanks jonny). Please check if nothing is broken with the changes. |
|
| Back to top |
|
 |
jonny
Joined: 13 Nov 2004 Posts: 3004 Location: Minnesota
|
Posted: Fri Mar 02, 2007 5:30 am Post subject: |
|
|
| Great! It works now, but unfortunately it's a lot slower than the old method. I'll explain more in the grapher topic. |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4016 Location: Pittsburgh
|
Posted: Fri Mar 02, 2007 6:37 pm Post subject: |
|
|
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 ?
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). |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4016 Location: Pittsburgh
|
Posted: Sat Mar 03, 2007 5:17 am Post subject: |
|
|
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). |
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|