A function that evaluates a mathematical expression given as a string

Post your working scripts, libraries and tools for AHK v1.1 and older
lyubarsky
Posts: 9
Joined: 01 Mar 2023, 13:09

A function that evaluates a mathematical expression given as a string

12 May 2023, 10:37

The basis of any mathematical calculator is a function that calculates mathematical expressions, specified as a string.
This is due to the fact that the calculator user can only pass a mathematical expression in this form. This is true for
simple calculators as well as more complex ones such as like the "Monster" calculator owned by Laszlo, or the Calculator
"Blackboard" (https://blackboard.su) with which the user can use loop operators and branch constructions.

In many programming languages, in JavaScript, PHP, Autoit, etc., such functions are built-in. Unfortunately, AutoHotkey
does not have such a function except for custom development. I propose another variant of such a function, which is close
in algorithm to the one used by Laszlo in the "Monster" calculator, but not using recursion. The Calculate() function accepts
as a string any mathematical, in particular, logical expression valid in AutoHotkey and returns its value. (Bitwise and
increment-decrement operations that are not strictly speaking mathematical, are not processed, but can be easily connected
via general scheme, like any other unary and binary operators.)

Algorithm
The Calc() helper function evaluates only those mathematical expressions which do not contain parentheses. For this, regular
expressions are used. With their help, they find arithmetic and logical operators in order of priority and their corresponding
operands. After performing the found operations their results are returned to the original place of the mathematical expression.
As a result the number remains - the value of the expression.

The main Calculate() function "specializes" in parenthesis expansion. Using regular expressions, in each loop, the function looks
for pairs of brackets that do not include nested brackets. The Calc() function is applied to the contents of each such pair, and
the entire bracket pair, along with the contents, is replaced with the computed value. Since different kinds of parentheses can
occur: parentheses that change the order actions, brackets containing an argument of functions of one or more variables,
brackets enclosing the base of the degree, all of them are processed in each cycle according to separately. After a certain number
of iterations, the expression without brackets remains, to which the Calc() function is again applied.

Due to the simplicity and uniformity of the algorithm, it is easy to connect any operations and functions that make sense in a
mathematical expression. To speed up calculations, by about 2 - 2.5 times, if it is critical, before calling the Calculate() function,
you need to increase the priority of its process (Process Priority,,High) and keep the processor from sleeping (SetBatchLines -1).

In case of run-time errors (divide by zero, logarithm of negative numbers, etc.) the Calculate() function, like Autohotkey, returns
an empty string. For syntax errors, the Calculate() function usually returns a fragment expressions that she could not "parse".

If you find errors, please send the expressions in which they occur.
Good luck in creating calculators!

Code: Select all

global Decimal 								; decimal pattern
global SignedDecimal						; signed decimal pattern

/* The function calculates any mathematical and logical expression valid in AutoHotkey
 */
Calculate(MathEx) {
	Decimal = \d+\.?\d*														; Unsigned decimal pattern
	SignedDecimal = [\+-]?%Decimal%											; Signed decimal pattern (Float)
	Function = Abs|Ceil|Log|Ln|Exp|Floor|ASin|ACos|ATan|Sin|Cos|Tan|Sqrt 	; Pattern for Single Variable Functions
	Function2 = Round|Mod|Min|Max									; Pattern for functions of two or more variables
	NoNested = \(([^()]+)\)			; Pattern for parentheses not containing nested parentheses with capturing brackets

;~ We will manage with one set of logical operators, the one that comes from C/C ++
	MathEx := RegExReplace(MathEx, "i)\bNot\b", "!")
	MathEx := RegExReplace(MathEx, "i)\bAnd\b", "&&")
	MathEx := RegExReplace(MathEx, "i)\bOr\b", "||")

;~ Removing all spaces
	MathEx := RegExReplace(MathEx, "\s+")
	Loop {
; Replacing a parenthesis with a number if it does not refer to a function or power
		A := RegExMatch(MathEx, "i)(?<!" Function "|" Function2 ")" NoNested "(?!\*\*})", Match)
		MathEx := StrReplace(MathEx, Match, Calc(Match1),, 1)

; Calculation of functions of one variable
		B := RegExMatch(MathEx, "i)\b(" Function ")" NoNested, Match)
		MathEx := StrReplace(MathEx, Match, "(" %Match1%(Calc(Match2)) ")",, 1)

; Calculation of functions of two or more variables
		C := RegExMatch(MathEx, "i)\b(" Function2 ")" NoNested, Match)
		Arr := StrSplit(Match2, ",")
		Loop % Arr.Length()
			Arr[A_Index] := Calc(Arr[A_Index])
		MathEx := StrReplace(MathEx, Match, "(" %Match1%(Arr*) ")",, 1)

; Calculating powers with parentheses
		D := RegExMatch(MathEx, NoNested "\*\*(" SignedDecimal ")", Match)
		MathEx := StrReplace(MathEx, Match, Calc(Match1)**Match2,, 1)
	} until A + B + C + D = 0
	return Calc(MathEx)
}
/* The function calculates simple mathematical and logical expressions, that is, without brackets and functions.
 * For technical purposes, chains of + and - signs that occur when opening brackets and calculating functions,
 * are replaced by a single + or - sign, depending on the evenness of the number of minuses.
 */
Calc(SimpleEx) {
;~ We get rid of the scientific format "mantissa" E "order", translating it into "float".
	DecimalWithDot = \d+\.\d*										; Decimal pattern with mandatory dot
	MathOperation = \+|-|\*|/|<|>|=|&|\|$							; Математическая операция или конец строки
	SignedWhole = ([+-]?)(\d+)(?=%MathOperation%)					; Integer pattern with sign and number capture

	while RegExMatch(SimpleEx,  "(" DecimalWithDot ")[eE]" SignedWhole, Match) {
		if (Match2 = "-")
		Loop % Match3
			Match1 /= 10
		else
			Loop % Match3
			Match1 *= 10
		SimpleEx := StrReplace(SimpleEx, Match, Match1,, 1)
	}
;~ Exponentiation
	while RegExMatch(SimpleEx, "(" Decimal ")\*\*(" SignedDecimal ")", Match)
		SimpleEx := StrReplace(SimpleEx, Match, Match1 ** Match2,, 1)

;~ Multiplication, division and integer division
	while RegExMatch(SimpleEx, "(" Decimal ")(\*|/|//)(" SignedDecimal ")", Match) {
		switch Match2 {
			case "*":  Result := Match1 * Match3
			case "/":  Result := Match1 / Match3
			case "//": Result := Match1 // Match3
		}
		SimpleEx := StrReplace(SimpleEx, Match, Result,, 1)
	}
;~ Replacing strings like +---- that appear when opening brackets, with one sign depending on the evenness of the number
;~ of minuses
	while RegExMatch(SimpleEx, "[+-]{2,}", Chain) {
		Signs := RegExReplace(Chain, "\+")
		Signs := RegExReplace(Signs, "-{2}")
		SimpleEx := StrReplace(SimpleEx, Chain, Signs ? "-" : "+")
	}
;~ Addition and subtraction
	while RegExMatch(SimpleEx, "(" SignedDecimal ")([+-])(" Decimal ")", Match) {
		switch Match2 {
			case "+": Result := Match1 + Match3
			case "-": Result := Match1 - Match3
		}
		SimpleEx := StrReplace(SimpleEx, Match, Result,, 1)
	}
;~ Comparisons
	while RegExMatch(SimpleEx, "(" SignedDecimal ")(<|<=|=|!=|>=|>)(" SignedDecimal ")", Match) {
		switch Match2 {
			case "<": 	Result := Match1 < Match3
			case "<=":  Result := Match1 <= Match3
			case "=":   Result := Match1 = Match3
			case "!=":  Result := Match1 != Match3
			case ">=":  Result := Match1 >= Match3
			case ">": 	Result := Match1 > Match3
		}
		SimpleEx := StrReplace(SimpleEx, Match, Result,, 1)
	}
;~ Negation
	while RegExMatch(SimpleEx, "i)!(" SignedDecimal ")", Match)
		SimpleEx := StrReplace(SimpleEx, Match, ! Match1,, 1)

;~ Boolean multiplication
	while RegExMatch(SimpleEx, "(" Decimal ")&&(" SignedDecimal ")", Match)
		SimpleEx := StrReplace(SimpleEx, Match, Match1 && Match2,, 1)

;~ Logical addition
	while RegExMatch(SimpleEx, "(" SignedDecimal ")\|\|(" SignedDecimal ")", Match)
		SimpleEx := StrReplace(SimpleEx, Match, Match1 || Match2,, 1)

	return SimpleEx
}

[Mod edit: Removed redundant code tags.]
balkinishu
Posts: 18
Joined: 17 Nov 2017, 11:13

Re: A function that evaluates a mathematical expression given as a string

17 Apr 2024, 15:09

This is awesome. Thanks for sharing!

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 58 guests