MacOS – AppleScript Calculator: Take selected text equation & calculate the result

applescriptmacosservices

I want to create a Service that is able to take the selected text, automatically perform the desired mathematical operation, and then paste the result directly after the selected text. This Service is similar to Google Search's built-in calculator function, but it is more handy.

Here are some examples:

if this is selected text : then this is new text

43+957 : = 1000

763-9482 : = -8719

8*26 : = 208

83/23 : = 3.60869565217

The above operations include addition, subtraction, multiplication, and division. So far, this script is not difficult to write.

But, I also would like the ability to make use of parentheses in the calculations. This is where the road gets rocky.

Here are some examples of equations that involve parentheses:

(4+55)/2 : = 29.5

352+((76.031*57/100)+(93.6*87/100)) : = 476.76967

(45+36+(64*0.04)+152+33+90)*(1/(1.98-425-(0.25*629)+431)) : = -2.40209017217

Yikes.

Okay. Baby steps…

Here is the code that I have written. It can only handle the first set of examples:

set inputString to "43 + 555 /4 *122"

-- Remove any and all spaces from this string
set inputString to replace_chars(inputString, " ", "")

-- Convert every instance of "x" to "*"
if (inputString contains "x") then
    set inputString to replace_chars(inputString, "x", "*")
end if

-- Ensure that the string contains no foreign characters:
set supportedCharacters to {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "+", "-", "*", "/"}

set x to 1
set everyCharacterInInputStringIsValid to true

repeat until (x > (length of inputString))
    if supportedCharacters contains (character x of inputString) then
        set x to (x + 1)
    else
        set everyCharacterInInputStringIsValid to false
        display dialog "Input string contains invalid character: " & (character x of inputString)
        error number -128 (* user cancelled *)
    end if
end repeat

-- String is all good. Now for the "fun" part.

set AppleScript's text item delimiters to {"+", "-", "*", "/"}
set onlyTheNumbers to text items of inputString
set AppleScript's text item delimiters to {""}
-- return onlyTheNumbers -- {"43", "555", "4", "122"}

set AppleScript's text item delimiters to onlyTheNumbers
set onlyTheSymbols to text items of inputString
set AppleScript's text item delimiters to {""}
-- return onlyTheSymbols -- {"", "+", "/", "*", ""}

-- Remove the first and last items in the onlyTheSymbols list
-- post #3 from http://macscripter.net/viewtopic.php?id=43371/
set removeSpecificItemsFromList to {1, (count of onlyTheSymbols)}
repeat with i in removeSpecificItemsFromList
    set item i of onlyTheSymbols to null
end repeat
set onlyTheSymbols to every text of onlyTheSymbols

set calculatorBalance to ((0) as number)
set x to 1 as integer
set y to 2 as integer
set z to 1 as integer
set num1 to (((item x) of onlyTheNumbers) as number)
repeat until ((z) is greater than (count of onlyTheSymbols))

    set num2 to (((item (y)) of onlyTheNumbers) as number)
    set symbol to ((item z) of onlyTheSymbols)
    if (symbol is "+") then
        set calculatorBalance to (num1 + num2)
    else if (symbol is "-") then
        set calculatorBalance to (num1 - num2)
    else if (symbol is "*") then
        set calculatorBalance to (num1 * num2)
    else if (symbol is "/") then
        set calculatorBalance to (num1 / num2)
    end if

    set num1 to (calculatorBalance as number)

    set y to (y + 1)
    set z to (z + 1)
end repeat

display dialog " = " & calculatorBalance


on replace_chars(this_text, search_string, replacement_string)
    set AppleScript's text item delimiters to the search_string
    set the item_list to every text item of this_text
    set AppleScript's text item delimiters to the replacement_string
    set this_text to the item_list as string
    set AppleScript's text item delimiters to ""
    return this_text
end replace_chars

I am confused as to how to approach the whole concept of parentheses in this script. Can someone lend a hand?

Here's my rough idea:

  1. Determine how many parentheses are in the string.

  2. Split the input string into segments. The number of segments is based on the total number of parentheses in the string, minus one. The number of segments should be an odd number.

  3. Check to see if any of the segments contain embedded parentheses.

  4. Repeat steps 1 through 4 until no parentheses exist in any segment.

  5. Now that you have your segments, act as if each segment is its own string. That is, calculate the result of each segment independently, instead of simply using the previous number in the list to interact with the following number in the list.

  6. Add all of the segment results together.

I'm not sure if I have the right idea.

I am aware that I have wholly ignored the concept of order of operations. I don't know how to implement this exactly, either. My defense is that this is basically a part of the parentheses code. E.g., 1+4*2 would first have to be converted to 1+(4*2) before the result could be calculated.


Note:

I stated that I want the input for the AppleScript to be the currently selected text. But, you will clearly see that I have not written the code in this way.

For the purposes of writing, debugging, and troubleshooting this script, I am ignoring that element of the script, because this is very easy part to write and makes testing the code more complicated.

Once the script has been perfected, I will simply set the Service to receive selected text in any application. For the output, I will tell the script to key code and then paste the final result.


By the way, if these elaborate AppleScript questions are getting to be a bit overboard for Ask Different, then please let me know and I shall take them to a more AppleScript-centric site (e.g., MacScripter.net). I don't know if I am pushing my limit here.

Best Answer

Over on StackOverflow you will find these related questions:

For a discussion about parsing expressions, see:

The Burden of AppleScript

AppleScript is not a language to relish writing a parser in. The language is great for many things but not text manipulation. The language is Turing Complete, so your project is possible, but difficult.

AppleScript is an Open Scripting Architecture (OSA) dialect and maybe you would be willing to consider another OSA dialect for this project, such as JavaScript. While JavaScript is not ideal, you will find more resources and guides to help you. You can also mix and match Script Editor compiled AppleScript and JavaScript scripts.

Use bc

Consider piping the selected text to bc and returning the result:

set theInput to "(4.0+55.0)/2.0"
set myCalculation to "echo 'scale=1;" & (theInput) & "' | bc"
set myResult to do shell script myCalculation

This approach avoids reinventing the wheel and uses the built-in command line calculator included with macOS, bc.