Trig and other math functions

how-to
asobjc
mojave

(Shane Stanley) #1

Some of you will have seen the discussion on the Apple mailing list about replacing Satimage’s trig functions. As pointed out there, Takaaki Naganoya’s calcLibAS script library is an excellent substitute: fast and simple.

Here’s an alternative that takes a different approach. Instead of defining handlers for each function, you pass the name of the function as one of the arguments. It also has a map-style method to process a list of numbers in one call, for greater efficiency, although that is limited to functions that take one (or fewer) arguments.

It’s not any better than Takaaki’s, although in theory it might in some cases be the tiniest bit more accurate because it doesn’t rely on AppleScript’s truncating coercion of reals to strings. (It’s probably also a little slower.) It’s more an example of how JavaScript can be invoked efficiently from AppleScript without relying on string concatenation to build source code.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "JavaScriptCore"
use scripting additions

property theContext : missing value

-- examples
doJsMath("cos", 1)
doJsMath("random", {})
doJsMath("hypot", {3, 4, 5})
doJsMath("max", {1, 2, 3, 4, 6, 2, 10})
-- map examples
doJsMapMath("cos", {1, 2, 3})
doJsMapMath("random", {1, 1, 1}) -- numbers are irrelevant here

-- methods supported are: abs(x), acos(x), acosh(x), asin(x), asinh(x), atan(x), atan2(y, x), atanh(x), cbrt(x), ceil(x), cos(x), cosh(x), exp(x), floor(x), log(x), max(x, y, z, ..., n), min(x, y, z, ..., n), pow(x, y), random(), round(x), sin(x), sinh(x), sqrt(x), tan(x), tanh(x), trunc(x)
on doJsMath(methodName, argsList)
	if class of argsList is not list then set argsList to {argsList}
	return ((myContext()'s objectForKeyedSubscript:"Math")'s invokeMethod:methodName withArguments:argsList)'s toDouble()
end doJsMath

-- This lets you retrieve a list of values in one call by passing in a list
-- Only supports methods that take one (or fewer) arguments
on doJsMapMath(methodName, argsList)
	set theJSArray to current application's JSValue's valueWithObject:argsList inContext:(myContext())
	set mathMethod to (myContext()'s objectForKeyedSubscript:"Math")'s objectForKeyedSubscript:methodName
	return (theJSArray's invokeMethod:"map" withArguments:{mathMethod})'s toArray() as list
end doJsMapMath

on myContext()
	if theContext = missing value then
		-- set up js engine once
		set theContext to current application's JSContext's new()
	end if
	return theContext
end myContext