Detecting the name of a given handler at run-time?


#1

Again in the context of automating tests - is there a sensible way of getting a name-string for a given handler at run-time ?

(Parallel question to

Detecting at run-time how many arguments a handler expects? )

Error-trapping (always the worst way to structure code, but does have some hacky marginal uses) provides an almost-solution, as long as we are testing handlers that don’t have missile-launching side-effects …

Is there a cleaner and more sensible alternative to this kind of thing ? :

(JS functions, for example, have properties like .name and .length (arity), as well as methods like .toString() (source code), which I find I am rather missing in AS, in an attempt to set up a dual-language testing framework for generic functions )

Interim sub-optimal approach – reading error messages:

-- handlerName :: Handler -> String
on handlerName(h)
    tell mReturn(h)
        try
            |λ|()
            |λ|(missing value)
        on error e
            set {dlm, my text item delimiters} to {my text item delimiters, space}
            set xs to text items of e
            set my text item delimiters to dlm
            text 1 thru -2 of (last item of xs)
        end try
    end tell
end handlerName


-- TEST:  What is the name string of a given handler ?
on run
    
    map(handlerName, {hello, succ, min, zipWith})
    
    --> {"hello", "succ", "min", "zipWith"}
end run


-- SAMPLE HANDLERS FOR TESTING -----------------------------------------------

-- hello :: () -> String
on hello()
    "hello"
end hello

-- succ :: Int -> Int
on succ(x)
    x + 1
end succ

-- min :: Ord a => a -> a -> a
on min(x, y)
    if y < x then
        y
    else
        x
    end if
end min

-- zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
on zipWith(f, xs, ys)
    set lng to min(length of xs, length of ys)
    if lng < 1 then return {}
    set lst to {}
    tell mReturn(f)
        repeat with i from 1 to lng
            set end of lst to |λ|(item i of xs, item i of ys)
        end repeat
        return lst
    end tell
end zipWith

-- GENERIC FUNCTIONS ---------------------------------------------------------

-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
    tell mReturn(f)
        set lng to length of xs
        set lst to {}
        repeat with i from 1 to lng
            set end of lst to |λ|(item i of xs, i, xs)
        end repeat
        return lst
    end tell
end map

-- Lift 2nd class handler function into 1st class script wrapper
-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
    if class of f is script then
        f
    else
        script
            property |λ| : f
        end script
    end if
end mReturn

(Stan Cleveland) #2

Script Debugger contains a few commands in its dictionary that relate to handlers, some of which may be useful for automated testing. In particular, getting the name of any or all handers is easily doable.

tell application "Script Debugger"
	tell document "choose_from_list_width.scpt"
		name of script handlers
		--> {"open", "chooseFrom", "getTextWidth", "tid", "run"}
		name of script handler 3
		--> "getTextWidth"
		set hasIdleHandler to has idle handler
		--> false
		set hasOpenHandler to has open handler
		--> true
		set hasQuitHandler to has quit handler
		--> false
		set tidHandlerText to source text of script handler "tid"
		--> "set AppleScript's text item delimiters to d"
	end tell
end tell

Additionally, if you don’t mind using Satimage Smile as an external tool, you can call its “validate” command to find undefined and unused AS variables, both potentially useful tests. See my [very infrequent] blog post of 16 January 2012 at codemunki.com. The latest version of Smile (v3.8.0 build 970) works fine here running El Capitan 10.11.6 on a MacBook Pro, but I’ve seen posts about problems on High Sierra. BTW, the Satimage website shows build 959 as the latest, but the download is actually build 970.

HTH,
Stan C.


Detecting at run-time how many arguments a handler expects?
#3

That’s very helpful. Thank you !


(Ed Stockly) #4

I have a script that has numerous handler calls in its run handler. The calls are all in an error trap.

The script has a property named currentHandler.
The first line of each handler sets that property to the handler’s name.

If there is an error in any handler, the error handler can report that to the user or save it in a log.

You can also use that for some handlers to indicate how it was called. The first line of the handler would be something like:

Set currentHandler to {currentHandler ,handlerName}

(just be careful with recursive calls)


#5

Good thought - I could generate test versions which tagged themselves - thanks !


(Mark Alldritt) #6

AppleScript provides no facilities to introspect a script’s handlers, other than getting and setting the bodies of handlers (because handlers are actually just variables that contain code):

on myHandler(p1, p2)
    return p1 & " " & p2
end

set anotherHandler to myHandler

anotherHandler("Hello", "World") --> "Hello World"

This gives another clue: the code within a handler does not know its own name - that is only known to the script object that holds the variable.


#7

Very helpful to have that clarity – thank you !


(Nigel Garvey) #8

My goodness! Is that Mike Mercury? :smiley: