Skip to content

Instantly share code, notes, and snippets.

@BGMcoder
Last active April 23, 2018 14:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BGMcoder/2d6e1ef7a88adc5c5c661498a369714b to your computer and use it in GitHub Desktop.
Save BGMcoder/2d6e1ef7a88adc5c5c661498a369714b to your computer and use it in GitHub Desktop.
my calculator script
ResetNormalFormats(){
global
if(usedecimalplaces){
normalfloatformat := "0." . decimalplaces
}else{
normalfloatformat := "0." . maxdecimalplaces
}
;W := W="" ? "0.6g" : "0." . W
;SetFormat Integer, %FormI%
SetFormat Integer, D ;decimal format
SetFormat FLOAT, %normalfloatformat%
}
;------adapted from monster 1.2------------(needs AHK 1.0.48+)--------------------------------------------------------------------------------------------------------------------
;http://www.autohotkey.com/board/topic/15675-monster-evaluate-math-expressions-in-strings/
; 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
;Predefined variables are only accepted if listed in the keywords variables in gui.ahk
; 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)
;I fixed this to return "" instead of 0 for incomplete expressions
;tobin() crashes the script
;$b is the inline flag for decimal -to- binary with answer as binary
;$h is the inline flag for decimal -to- hexadecimal with the answer as hex
;if x is discovered in the string, it is interepreted as hex with answer as dec
;preprocessed means that we have iterated over a certain part of the equation and already converted it to a solution
;this is necessary to fetch global variables into the Eval function
FetchGlobal(whatvar){
thisval := % %whatvar%
return thisval
}
SetGlobal(whatvar, whatvalue){
global
%whatvar% := whatvalue
}
;there is a limit to the math.
;ahk cannot process a number higher than 2**64-1, which is: 9223372036854775800.000000 on a 64bit system
;but on a 32bit system it is 2**32-1, which is: 4294967295.000000
;if you reach that value, it just stops computing
Eval(x, byref whatcommand="") { ; non-recursive PRE/POST PROCESSING: I/O forms, numbers, ops, ";"
Local FORM, i, W, y, y1, y2, y3, y4
Local preform, prex, pre, pre1, pre2
FormI := A_FormatInteger, FormF := A_FormatFloat ;save the current number formats
;usedecimalplaces := fetchglobal("usedecimalplaces")
;usedivisionsign := fetchglobal("usedivisionsign")
SetFormat Integer, D ; decimal intermediate results!
;---------------Process Special Calculor Math Functions First (Maestrith)--------------------
;this colourizes the letter combinations --> see gui.ahk
RegExReplace(x,"\(",,Count),replace:=[]
Loop,%count%{
pos:=InStr(x,"(",0,0,A_Index)-1
if((match:=hequation.2353(pos))>pos){
text:=hequation.textrange(pos+1,match)
function:=hequation.textrange(hequation.2266(pos-1),hequation.2267(pos-1))
if(keywordsSpecial2~="\b" function "\b"){
new:=%function%(StrSplit(text,",")*)
replace.push({find:function "(" text ")",replace:new})
;ToolTip,This was triggered %A_TickCount%
}
}
}
total:=""
for a,b in replace{
;alert(b.find,b.replace)
total.=b.find " , " b.replace
x:=RegExReplace(x,"\Q" b.find "\E",b.replace)
}
;--------------------------------------------------------------------------------------------------------------------------------------
;replace any fancy characters with their computable alternatives
;if(usedivisionsign){
; x := RegExReplace(x,"÷","/")
;}
;--------------------------------------------------------------------------------------------------------------------------------------
;Let's do some conversion modes before we begin calculating
RegExMatch(x, "\$(c|u|a|n|rfs|rf)(.*)", pre)
;x = input
;pre = regexmatch array
;pre1 = flag (that is, c or u)
;pre2 = value after flag
;prex = return value (we don't want to mess up x, just in case)
whatcommand := ""
preform := pre1
StringReplace prex, x, %pre% ; remove $.. flag
if(preform){
whatcommand := "$" . preform ;make sure to return the command so we can colour it with Enhance()
;CHARACTER TO ASCII CODE
;produce codepoints from characters
;type a character and get the ascii code
if(preform = "c"){
loop,parse,pre2,,%a_space%
prex .= ord(a_loopfield) . " "
}
;CHARACTER TO UNICODE CODEPOINT
;type a character and get the unicode code point
;produce a character from this unicode code by inserting 0x before the code point supplied by the user
;we have to do tricks though to get the unicode code point
if(preform = "n"){
loop,parse,pre2,,%a_space%
{
thisnum := Format("{1:#x}", ord(a_loopfield))
prex .= math_padnumber(thisnum,"00000","0") . " "
}
}
;UNICODE CODE TO CHARACTER
;produce a unicode character from its code
if(preform = "u"){
loop,parse,pre2,%a_space%
{
thiscode := a_loopfield
if(left(thiscode,2) != "0x")
thiscode := "0x" . thiscode
prex .= chr(thiscode) . " "
;provide some explanations for spaces which won't show in the solution
;https://www.cs.tut.fi/~jkorpela/chars/spaces.html
if(thiscode = "0x0020")
prex := "ascii space"
else if(thiscode = "0x2002")
prex := "en space"
else if(thiscode = "0x2003")
prex := "em space"
else if(thiscode = "0x2009")
prex := "thin space"
else if(thiscode = "0x00a0")
prex := "no-break space"
}
}
;ASCII CODE TO CHARACTER
;produce an ascii character from its code
if(preform = "a"){
loop,parse,pre2,%a_space%
{
if(a_loopfield = "32")
prex .= "[space]"
else
prex .= chr(a_loopfield) . " "
}
}
;ROUND FRACTION
;this is more of a convertor than a math function
if(preform = "rfs"){
stringsplit, xrf, pre2,`,,%a_space%
prex := roundfraction(xrf1,xrf2,false) ;don't simplify
}
if(preform = "rf"){
stringsplit, xrf, pre2,`,,%a_space%
prex := roundfraction(xrf1,xrf2,true) ;simplify
}
return prex
}
;--------------------------------------------------------------------------------------------------------------------------------------
;Process math flags
RegExMatch(x, "\$(b|h)(\d*[eEgG]?)", y)
FORM := y1, W := y2 ; HeX, Bin, .{digits} output format
SetFormat FLOAT, 0.16 ; Full intermediate float precision
;SetFormat FLOAT, 0.16e ; Full intermediate float precision
StringReplace x, x, %y% ; remove $.. flag
if(FORM)
whatcommand := "$" . FORM
; convert hex numbers to decimal by checking for numbers with 0x in front of them
Loop
If RegExMatch(x, "i)(.*)(0x[a-f\d]*)(.*)", y)
x := y1 . y2+0 . y3
Else Break
; convert binary numbers to decimal
Loop
If RegExMatch(x, "(.*)''([01]*)(.*)", y)
x := y1 . FromBin(y2) . y3
Else Break
; convert unsigned binary numbers to decimal
Loop
If RegExMatch(x, "(.*)'([01]*)(.*)", y)
x := y1 . baseConvert(y2, "base2", "base10") . y3
Else Break
; consider all numbers in equation are in hex and we will convert them to decimals
; if(FORM = "x"){
; Loop
; If RegExMatch(x, "i)(.*)(.*[a-f].*)(.*)", y)
; x := y1 . baseConvert(y2, "base16", "base10") . y3
; Else Break
; }
; add missing '.' before E (1e3 -> 1.e3)
x := RegExReplace(x,"(^|[^.\d])(\d+)(e|E)","$1$2.$3")
; 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 for exponents
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
; work on pre-processed sub expressions beginning with semicolon ;
Loop Parse, x, `;
y := Eval1(A_LoopField)
;y will be the result of the whole evaluation so far
;<<><><><><><>><><><>>
;after this point, we are operating on the solution
; return result of last sub-expression (numeric)
; convert output to binary
If (FORM = "b"){
y := W ? ToBinW(Round(y),W) : ToBin(Round(y))
; convert output to hex
}else if(FORM = "h") {
y := baseConvert(y, "base10", "base16")
;cleanup floating point numbers with too many 0's
}else{
ResetNormalFormats()
y := rtrim(y, 0)
}
Return y
}
Eval1(x) { ; recursive PREPROCESSING of :=, vars, (..) [decimal, no ";"]
Local i, y, y1, y2, y3
;This was originally in Monster calc, and allowed for some sort of user-defined function definition. There is a corresponding part at the end of Eval@(x)
; save function definition: f(x) := expr
; If RegExMatch(x, "(\S*?)\((.*?)\)\s*:=\s*(.*)", y) {
; f%y1%__X := y2, f%y1%__F := y3
; Return
; }
;SPECIAL VARIABLES
; execute leftmost ":=" operator of a := b := ...
;here we exchange the values of special vars like pi, e, gallon, inch, etc. these vars start with "x"
;the colours for these words are set in strings in gui.ahk
If RegExMatch(x, "(\S*?)\s*:=\s*(.*)", y) {
y := "x" . y1 ; user vars internally start with x to avoid name conflicts
if(y = "xe"){ ;don't allow e to become a user variable
;I WISH THIS ALERT COULD BE MADE MODAL *AND* ONTOP - I can only get one or the other.
alert("Sorry, Friend, you can't use e for a variable`rThat letter is reserved for the Euler's Number",error,apptitle . " Error")
return error
}else{
Return %y% := Eval1(y2)
}
}
; at this point there are no variables 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
; find last innermost (..)
Loop {
If( RegExMatch(x, "(.*)\(([^\(\)]*)\)(.*)", y) ){
;y1= whatever is before (
;y2=whatever is between ()
;y3=whatever is after ()
;y4=
;evaltest(y1,y2,y3,y4,"() loop")
;test for operators to the left and right of the parenthesis, that is, in y1 and y3; if there are none, process it as multiplication by supplying *
if(regexmatch(y1,".*(\d$)",thisop) ){
y1 := y1 . "*"
}
if(regexmatch(y3,"(^\d).*",thisop) ){
y3 := "*" . y3
}
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
; no more operators left
If x is number
Return x
; execute rightmost ?,: operator
RegExMatch(x, "(.*)(\?|:)(.*)", y)
IfEqual y2,?, Return Eval@(y1) ? Eval@(y3) : ""
IfEqual y2,:, Return ((y := Eval@(y1)) = "" ? Eval@(y3) : y)
; execute rightmost || operator
StringGetPos i, x, ||, R
IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) || Eval@(SubStr(x,3+i))
; execute rightmost && operator
StringGetPos i, x, &&, R
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)
;evaltest(y1,y2,y3,y4,"low precedence`r" . %y2%)
;evaltest(y1,y2,y3,y4,"func=" . %y2%)
If(IsFunc(y2) ){
;y1=everthing before ()
;y2=between ()
;y3=solved equation after()
;y4=
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) ){ ; no more function (y1 <> "" only at multiple unaries: --+-)
Return x
}
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 ~
;From here on we are detecting functions in the query
;don't return anything if the parenthesis isn't closed
;Wait till AFTER the close parenthesis is given whence the upper code removes the parenthesis
if(instr(y4,"(") && !instr(y4,")")2)
return
;evaltest(y1,y2,y3,y4,x)
;intercept ahk functions typed into the query box
if(y3 = "cos"){
y3 := "math_cos"
}else if(y3 = "sin"){
y3 := "math_sin"
}else if(y3 = "tan"){
y3 := "math_tan"
}else if(y3 = "acos"){
y3 := "math_acos"
}else if(y3 = "asin"){
y3 := "math_asin"
}else if(y3 = "atan"){
y3 := "math_atan"
}else if(y3 = "deg"){
y3 := "math_deg"
}else if(y3 = "rad"){
y3 := "math_rad"
}else if(y3 = "log"){
;if you feed Log() a 1 it will return a blank string, which makes Calculor think it should return ". . ."
;so override this and return the correct answer
if(y4 = 1){
Return Eval@(y1 . "0" )
}
}
; built-in and predefined functions(y4)
If(IsFunc(y3) ){
;Don't run any functions in the query box unless they are in the main list (see the list of keywords in gui.ahk)
if(!instr(AllQueryFunctions, y3))
return
;evaltest(y1,y2,y3,y4,x)
;Determine if the parameters in y4 are multiple and separate them
;yparam := strsplit(y4,",",a_space) <--why didn't this work?? I don't know
stringsplit, yparam, y4,`,
;keep the function from being called until the second parameter is ready
if(yparam0 > 1){ ;check for more than one parameter; yparam0 is the number of parameters
if(yparam1 != "" && yparam2 != "") ;prevent the function from running against blanks, but allow 0 - otherwise gcd will crash
return Eval@(y1 . %y3%(yparam1,yparam2) )
else
return
;else there are no commas so there is only one parameter
}else{
;evaltest(y1,y2,y3,y4,x)
Return Eval@(y1 . %y3%(y4) )
}
}
;I'm not sure if we need this last part... when you figure out what it does, re-enable it
;By disabling it, it allows us to not calculate when a user has a function call typo
;There is a corresponding part at the beginning of Eval1(x)
;evaltest(y1,y2,y3,y4,x)
return
; LAST: user defined functions
;Return Eval@(y1 . Eval1(RegExReplace(f%y3%__F, f%y3%__X, y4)))
}
evaltest(y1,y2,y3,y4,whattitle="EvalTest"){
;y1=
;y2='functionName'
;y3=functionName
;y4=function parameters calculated separately within parenthesis
thisstring := whattitle . "`r`ry1=" . y1 . "`ry2=" . y2 . "`ry3=" . y3 . "`ry4=" . y4, "info"
ToolTip, %thisstring%, %x%+10, %y%+50
}
tooly(what){
ToolTip, %what%, %x%+50, %y%+50
}
;--------------------------------------------------------------------------------------------------------------------------------------
;Special Functions that can be called - see the list of globals in gui.ahk
;if the functions are not in these lists then they cannot be run from Calculor
;we do this so that Calculor won't get confused to run other ahk functions folks might type in the box
;keywordsMath, keywordsCommands, keywordsSpecial1, keywordsSpecial2, keywordsPredefined
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){ ; Greatest Common Divisor (we include both functions here for the user's sake
Return math_gcd(a,b)
}
gcf(a,b){ ; Greatest Common Factor
Return math_gcd(a,b)
}
choose(n,k){ ; Binomial coefficient
return math_choose(n,k)
}
fib(n){ ; n-th Fibonacci number (n < 0 OK, iterative to avoid globals)
return math_fib(n)
}
fac(n){ ; n!
;Return n<2 ? 1 : n*fac(n-1)
return math_fac(n)
}
rf(a,b){
;alert("hitting rf")
Return roundfraction(a,b)
}
rad(n){
return math_rad(n)
}
deg(n){
return math_deg(n)
}
;--------------------------------------------------------------------------------------------------------------------------------------
roundfraction(whatval, roundto, simplify=false){
;original fraction
stringsplit, fracval, whatval,`,%a_space%
thisfrac := new fraction
thisfrac.fast(true) ;do not simplify
thisfrac.set(fracval1,fracval2)
;convert original fraction to number and round it off to the nearest roundto
thisval := thisfrac.tonumber()
if(!simplify){
roundedval := Round(thisval*roundto,4)/roundto
}else{
roundedval := Round(thisval*roundto,4)/roundto
}
;new, rounded fraction
roundedfrac := new fraction
roundedfrac.fast(true)
roundedfrac.set(roundedval)
rounded := roundedfrac.tostring()
;if the user wants the closest value, give it
if(!simplify){
return rounded
}
;to unsimplify it in case the user just wants the value closest to the roundto number
stringsplit, rvals, rounded,`/
div := roundto / rvals2
top := round(rvals1 * div,0)
bottom := roundto
newfrac := new fraction
newfrac.fast(true)
newfrac.set(top ,bottom)
;return unsimplified fraction
return newfrac.tostring()
}
;round to fraction - requires the fraction.ahk library
roundfractionx(whatval, roundto, simplify=true){
;convert whatval into a decimal
thisval := new fraction(whatval).tonumber()
;round it and divide
rounded := Round(thisval*roundto,0)/roundto
;return it to fraction form
retval := new fraction(rounded)
return retval.tostring() ; whatval " : " . roundto . " = " . rounded . " = " . retval.tostring()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment