Why is the parent of an anonymous script set at runtime?

Hi,
there’s always something else that surprises me in AppleScript :slight_smile: Consider this script:


on makeParent()
	script Base
		display dialog "Base"
	end script
end makeParent
end

script Child
	property parent : makeParent()
	display dialog "Child"
	continue run
end script

run Child

What it does is pretty straightforward: it calls makeParent() at compile-time to set the parent of Child to Base. You can verify that makeParent() is called at compile-time by removing its definition and trying to compile:


-- This does not compile. You'll get:
-- Syntax Error: «script» doesn't understand the makeParent message.
script Child
	property parent : makeParent()
	display dialog "Child"
	continue run
end script

run Child

Now the surprising fact: if the script is made anonymous, then it compiles! The following script produces an error only when run:


-- This compiles, but it gives the following runtime error:
-- AppleScript Error: «script» doesn't understand the makeParent message.
script
	property parent : makeParent()
	display dialog "Child"
	continue run
end script

Apparently, makeParent() is not called at compile-time in this case. Does anyone know why? Any idea how to make it behave like the named script?

PS: I have tried this on Lion (AppleScript 2.2.1), not on Mavericks.

I’m not sure but here’s a long shot from my experience in other OOP languages. We have classes and objects in programming languages. The difference in Supersets of C is that the class is in fact the object on the stack while an object is an copy of the object on the heap. Scripting languages seems to simulate this kind of behavior like PHP and JavaScript for example. This makes also the class the blueprint for every object. The difference is also that the class itself, read object on the stack, are less dynamic like it can’t be freed, only when the thread of process is killed. Another mayor difference as a side effect is that a class is created at compile time and an object is created at run time (stack vs heap).

With this knowledge about the differences between classes and objects the difference in AppleScript could be explained by anonymous vs named script objects. Here an example:

script x 
--do nothing
end script
set foo to result --> this results in an error, there is nothing returned???

now the same code with an anonymous script object

script
--do nothing
end script
set foo to result --> foo is now the script object

With named script objects we’ll see that script objects are taken out of our running code and put also in a “stack”. Like in C, code on the “stack” needs to be created at compile time. However there is no direct heap and stack control like in C but the virtual machine where the script is running in could simulate such behavior like many script languages do. Anonymous script objects remains part of the running code, it’s created on the “heap” and doesn’t need to be checked at compile time but only at run time. In AppleScript this means it remains part of the code and is not taken out of it and put in stack. That explains why the second example works.

To get inheritance by using anonymous scripts can be done like this:

on makeParent()
	script --another anonymous script
		on testHandler()
			return "testHandler"
		end testHandler
	end script
end makeParent

script 
	property parent : makeParent()
end script
set foo to result

foo's testHandler()

Note, this is just a theory of mine and I can’t prove the differences but it does make sense to me.

Hi.

Only top level script objects with labels are “instantiated” (ie. come into existence) when the main script’s compiled. Otherwise, they’re just definitions and the actual script objects are instantiated when the code’s encountered at run time.

script a
	script x
		--do nothing
	end script
	set foo to result
end script

run a

This also returns in an error, removing the label won’t just like in top level. So there is still a difference, even when they’re instantiated at run time. They’re completely ignored because when i put “set y to 1” as the first line in script object a foo will still be 1. So unlabeled or anonymous script objects will return itself. Are labeled script objects still not picked out and put on a stack then? Time for some reverse engineering again tonight, so I’ll come back to this later :slight_smile:

Yeah. OK. :slight_smile:

a is a labelled, top level script oject of the script. It’s instantiated during compilation.
x is a labelled, top level script oject of a. It too is instantiated during compilation.
When a’s run, nothing happens to produce a result because x is already compiled into the script, like a property.
If the label ‘x’ is removed, the inner script is only instantiated when a is run, producing a result.
If instead the label ‘a’ is removed, neither script object is instantiated at compilation time, whether the inner script’s labelled or not:

script
	script x
		--do nothing
	end script
	set foo to result
end script

set a to result
run a

Exactly… It’s not that the result is set to a void-like value. The result is never been set, to me that looks like the script object has been removed from the code and stored somewhere else, also like properties.

That’s very interesting! Now I have a puzzle. I am maintaining ASUnit (https://github.com/lifepillar/ASUnit), which is a unit testing framework. A minimal test file looks like this:


use AppleScript
use ASUnit : script "ASUnit"
property parent : ASUnit
property suite : makeTestSuite("My test suite")

script |A test set|
	property parent : TestSet(me)
	script |A simple test|
		property parent : UnitTest(me)
		assertEqual(2, 1 + 1)
	end script
end script

autorun(suite)

The relevant part is the two nested scripts. Unit tests and test sets inherit at compile-time from scripts returned by UnitTest() and TestSet(), respectively, which are handlers defined at the top level of the ASUnit script library. Also, ASUnit uses the names of the scripts in the output, which looks like this:


-- 
-- Thursday 6 February 2014 22:13:45
-- 
-- My test suite
-- 
-- A test set - A simple test ... ok
-- 
-- Finished in 0 seconds.
-- 
-- 1 tests, 1 passed (1 assertions), 0 failures, 0 errors, 0 skips.
-- 
-- OK

I started this thread because I was trying to use anonymous scripts instead. I thought the code would be equivalent, but I ran into two problems. First I “anonymised” the test set:


use AppleScript
use ASUnit : script "ASUnit"
property parent : ASUnit
property suite : makeTestSuite("My test suite")

script
	property name : "A test set"
	property parent : TestSet(me)
	script |A simple test|
		property parent : UnitTest(me)
		assertEqual(2, 1 + 1)
	end script
end script

autorun(suite)

This script still runs, but it seems to add a new script object at every execution, so that the first run produces the same output as before, but the second run (without recompiling, of course) prints:


-- My test suite
-- 
-- A test set - A test set ... ok
-- A test set - A test set ... ok
-- 
-- Finished in 0 seconds.
-- 
-- 2 tests, 2 passed (2 assertions), 0 failures, 0 errors, 0 skips.

and the third run has three tests, and so on. In other words, an anonymous script seems to be instantiated as a new, different, script object at every run, and such scripts are persistent between executions.

Anyway, I went on “anonymising” the unit test script, as follows:


use AppleScript
use ASUnit : script "ASUnit"
property parent : ASUnit
property suite : makeTestSuite("My test suite")

script
	property name : "A test set"
	property parent : TestSet(me)
	script
		property name : "A simple test"
		property parent : UnitTest(me)
		assertEqual(2, 1 + 1)
	end script
end script

autorun(suite)

Here the problem is that no tests are executed:


-- My test suite
-- 
-- 
-- Finished in 0 seconds.
-- 
-- 0 tests, 0 passed (0 assertions), 0 failures, 0 errors, 0 skips.

In other words, the UnitTest() handler is not executed, not even at runtime (in fact, it can be replaced by a non-existent handler without getting errors).

My impression is that the two problems I’ve mentioned cannot be fixed, so it is not possible to use scripts without names in ASUnit. Anyway, I’ve thrown the code here, because in the past I was shown AppleScript tricks that I thought impossible :slight_smile:

Hello.

I don’t know if this is helpful or not. But I’d consider inverting the solution.The run time lookup of something AppleScriptish works with an outwards scoping until it reaches the top-level. Like the responder chain. So, instead of going for anynomous, you could work upwards and try to call something.

(I hope it makes sense someway, somehow. :slight_smile: )