BeScript
Version 1.0a3
©2000 Dario Accornero
BeDev #10183
http://bexoft.com
mailto:bexoft@bexoft.com

Introduction to BeOS Scripting

The BeOS low-level scripting interface is described in the BeBook, the online documentation available with the system development tools. This introduction is meant for non-programmers who don't want to browse the C++ classes and documentation in order to learn what their system can do.

Native scripting is supported in the form of scripting messages targeted at specified objects. The action of scripting an object corresponds to sending a particular message to the desired object, which in turn may respond to the message in the appropriate way. This includes not responding at all as well as providing a given piece of data. An object capable of understanding scripting messages is known as a scriptable object. Most applications and GUI objects (windows and "views", such as buttons, scrollbars, and text fields) are automatically scriptable: the BeOS provides a number of system suites, collections of standard messages understood by all objects of a given class or type. This implies that most programs running on the system at any given time can be controlled remotely by users, one of the reasons why the scripting system was developed.

An example of a scripting action might be asking a window for its title. Although at first this might seem a useless action, consider that the window may live in a workspace different from the current one, hence its title would not be visible to the user. In this case, the object (window) responds to the message (retrieve title) with the requested information (a text string containing the title).
Messages of this kind are known as system messages: most applications and GUI objects support this form of scripting, which -- although apparently limited -- is actually very useful for savvy users wishing to automate their daily work. System messages currently fall in these categories:

GET: retrieve specific information, e.g. get Title of Window 1
SET: assign given information, e.g. set Title of Window 1 to "OurTitle"
CREATE: instantiate an object, e.g. create Canvas with name=MyCanvas and size=BRect(100.0,100.0,300.0,300.0)
DELETE: remove instances of an object, e.g. delete MenuItem "About" of MenuBar of Window 1
COUNT: retrieve number of instances of an object, e.g. count View of Window 1
EXECUTE: invoke an action on an object, e.g. do Item 1 of View "listview"
GETSUITES: retrieve scripting capabilities (system and custom) of an object, e.g. getsuites of Window 0
Here, Title, Canvas, MenuItem, Item, and View are called properties: each scriptable object contains a number of properties and associated messages. A collection of properties, methods to specifiy them, and messages they support is known as a suite . The last message shown is the means of inquirying an object of the suites it advertises to the outside world. Note, however, that unfortunately some applications do not publish their suites completely -- in that case, it is up to the developer of the applications to provide information about their scripting capabilities.
Another example of a scripting message might be asking a window to move itself at a specified screen location or by a specified offset in the plane. That is known as a command and this particular example is defined by window objects only. You cannot ask applications or views to move themselves directly: the scripting system is designed in such a way to allow specific objects to define their own scripting messages (and here windows take advantage of this option), support or not the system ones, and provide alternative scripting means. We are concerned only with the first two possibilities here: the third one is meant for C++ programmers. The system suites of scripting messages understood by most objects are described later: we must now address the question of targeting objects for our scripting actions.

Specifiers

A scriptable object is identified in native BeOS scripting through a specifier, a description of the object in terms of just itself (known as a direct specifier, such as the MenuBar of a window), its name (known as a name specifier, such as the "Screen" window in the corresponding preferences panel), or its position within the hierarchy of an application or window (known as an index specifier, such as the first window currently opened in NetPositive). Scriptable objects, such as views in a window, are often organized in a hierarchy: specifiers are hierachical as well, regardless of their type (i.e. direct, name, index). Case-sensitivity is enforced throughout the whole scripting system: under BeOS, "Window" (part of a correct specifier) is different from "window" (invalid specifier for all system purposes). This takes a little to get used to. Some third-party applications use case-insensitive comparisons, but all system specifiers are case-sensitive.

To address a given specifier, users must inform the system about whom they wish to talk to. This involves identifying an application by putting it at the top of the scriptable objects hierarchy: this way, an application will contain a number of windows, each of which will contain a number of views, and so on. Any given scriptable object will be accessible through a specifier in a hierarchical manner, such as the view called "OkayButton" in the second view of the "Untitled 1" window of the selected application. Recursion is potentially limitless, although it is somewhat rare (because it is usually a hint of bad design) to see hierarchies more than 10+ levels deep. BeScript supports the concept of targeting a given application with the TELL command, described later. The language also allows users to launch arbitrary applications prior to scripting them: this is sometimes needed because the BeOS scripting system allows to send messages only to running applications (and this of course makes sense). Accessing a specifier expressed with any method is known as resolving a specifier.

The problem with specifiers is that identifying views and windows by their position in the hierarchy (i.e. through an index specifier) is not always easy and very often it is also cumbersome. Unfortunately the native system is quite picky about name specifiers: the "screen name" of an object, what users see at a GUI level such as the button "Save Changes" in some application, is almost never directly addressable. A typical example is the "Host name:" text field of the "Identity" settings in the Network preferences panel: this text field is only addressable through its hierarchy position using bare-bones native BeOS scripting. A complete specifier for this object, assuming that we're addressing the Network preferences panel, is: View 0 of View 0 of View "Identity" of View "view container" of View 0 of View 0 of Window "Network". Not exactly a friendly specifier, but that is what the system provides us with. The reason for this is that the authors of the Network preferences panel did not assign an internal name to the text field: most programmers (including myself) often forget to identify their views properly, probably because besides scripting (and debugging) there is no real need to use internal names. Unfortunately the native scripting relies on internal names only when using name specifiers, and this is what prevents us from specifying that field as View "Host name:" -- the screen name is simply not taken into account. On the other hand, the scripting system is smart enough to navigate hierarchies looking for a name specifier when it is unique: in the example above, we could have addressed the View "Identity" of the Network preferences panel with just View "Identity" of Window "Network".

BeScript solves the problem of complex specifiers by introducing the concept of dynamic specifiers. All scriptable objects with screen names (technically, all objects that respond to get Label messages) can be specified by name: BeScript X-rays the target hierarchy and locates the given object transparently. An example is the specifier View "Host name:" in Window "Network": the language will analyze the target window and automatically locate the desired text field. The price to pay for using a dynamic specifier is a slight performance penaly due to the necessity of analyzing the target before resolving the specifier. In most (if not all) cases this is much preferable to expressing long and cumbersome specifiers.
However, for certain applications not even dynamic specifiers can be used successfully. Some objects just do not have any meaningful screen name. For these cases, BeScript comes with an application called BeScriptPeek that lets users analyze the entire hierarchy of scriptable objects of their running applications. The peek application is useful because it also displays the scripting support (that is, the supported suites) of all scriptable objects currently running. Please browse the documentation for BeScriptPeek before using the program: in the current BeOS version (R5.x) there are severe system bugs, acknowledged by Be, Inc., that can potentially crash some applications. Note that these bugs often apply to BeScript as well, because both programs use the same scripting engine, so you should be aware of them.

Language Structure

BeScript has a syntax similar to the great "hey" utility by Attila Mezei. I have put as much effort as possible into BeScript to make it resemble plain English, much like AppleScript on MacOS. BeScript was not designed just for programmers but rather for "power users" as well, therefore it does not look like a traditional programming language.
Note that in the language description reserved keywords are sometimes displayed in UPPERCASE: this is for highlighting purposes only, please remember that all BeScript keywords must appear exactly as described in the Commands section. Once again, the language is case-sensitive.
A script is composed of a sequence of TELL blocks, LAUNCH statements, and possibly high-level commands, such as PAUSE and PRINT. A first example might be:
	print "Starting/activating the BeIDE...\n"
	launch "application/x-mw-BeIDE"
	tell BeIDE
	    print "BeIDE scripting capabilities:\n"
	    getsuites
	    about
	    pause 1000                              # In milliseconds.
	    let Window 2 do                         # Either a text or project window.
	        get Title
	        do MoveTo BPoint(0.0,0.0)           /* Custom command. */
	        set Active to true                  # Make it front-most.
	        choose "Preferences..."             /* This is actually an ellipsis. */
	    end
	endtell

This simple script will first make sure that the BeIDE application (the Be Integrated Development Environment, available with the system development tools) is currently running on the system. The script will then ask the BeIDE for its scripting capabilities, display the program about box, pause one second, start talking to and retrieve the title of the third window (all indices start at zero), move this window to the upper-left corner of the screen and activate it, display the BeIDE preferences, and finally terminate.

A script can consist of any number of TELL blocks. A TELL block can be addressed only to an already running application. LAUNCH statements are needed when we don't know whether the application whom we want to talk to is currently running. Any number of commands, all addressed to the same remote application, can be given in a TELL block: this represents the desired action(s) to be performed by the target application. At the moment, a program in BeScript is mainly a way to control the behaviour of remote applications at a command-issue level. Support is planned for standard programming language features including control flow, symbol tables (variables, possibly functions) and expressions. BeScript, unlike "hey", is a real language in the sense that it has an actual syntax and a formal grammar: the language is "compiled" into scripting messages. By contrast, "hey" is basically a parser and interpreter. BeScript is not based on "hey", although it uses some of Attila's code for message display and properties management. This is not to discount "hey" in any way: it is a great application and I've learnt a lot from Attila's work. BeScript is simply a larger project than "hey", but they share the same purpose. Note that "hey" is still the fastest method to send a single scripting message to a remote object. Moreover, since BeScript currently lacks standard programming language features, operations such as iterating over a collection of objects are not available yet: currently they must be expressed using "hey" from a standard shell script. Hopefully this situation won't last too long.

I have included some sample scripts to help users get started with the language. All of them are reasonably easy to understand because BeScript is essentially English. BeScript has been tested somewhat extensively but certainly not enough. Users are encouraged to explore the possibilities offered by easy BeOS scripting and to contact me for help and further discussion. Below please find a description of the general grammar and also of all currently available commands. This is an alpha release so all standard caveats apply.

Grammar

BeScript programs are normal text files. UTF8 characters are supported in strings and elsewhere. Program formatting is not enforced but commands and blocks must be separated by whitespace.
The BeScript syntax is very similar to "hey" because it closely resembles the system. Most BeScript commands are in the form
	command [specifier [value [with data]]]
where specifier is sometimes optional, value is often optional, and data is almost always optional. All commands are in lowercase only; specifiers are either in MixedCase (all system ones), or in lowercase/UPPERCASE (some application-defined ones). Commands need only be separated by whitespace; there is no "end-of-statement" character (such as semicolon in C/C++). System commands include do (alias execute or exec), get, set, create, delete, count, getsuites, about, quit, save, and load. BeScript commands currently include click, type, choose, pause, print, and assign. Custom commands defined by scriptable objects (including both Be and third-party objects) are also available but must be addressed directly to the object that supports them. A common example is MoveBy defined by Window objects: you cannot ask an application or a View to do a MoveBy on a Window, you have to talk to the Window itself. This is a design choice of the native BeOS scripting system and, once again, it makes sense, so you should get used to thinking of talking to the right object.

Constructing a specifier that refers to the desired object is easy: just follow the system rules.
Each basic specifier can be: direct e.g. Shelf; name e.g. View "listview"; index e.g. Window 3 or -3 for reverse index; and range e.g. Text [0 to 6] or Text [-3 to 4] for reverse range. The latter form of specifier considers the first value as the starting index (counted from the end if negative) and the second value as the number of objects specified. Indices and ranges are always integers.
Specifiers can be regular, partial, complete, or dynamic.
Regular specifiers are standard Be specifiers, such as:
	Window 4
	Window "Network"
	View 0 of Window 3
	MenuBar of Window "Deskbar"
	View "someview" of Window "Untitled"
	View "listview" of View 0 of Window 3
	View "scroller" of View "superview" of View "baseview" of View 4
	Text [0 to 100] of View "TextField:" of Window "Text Settings"
	Selection [-1 to 4] of View "listview"
Regular specifiers can also have additional with blocks; see below for more information.
Partial specifiers are similar to regular specifiers, but they lack the initial property. Partial specifiers can also be dynamic. Examples are:
	"Screen size" of View "Preferences"
	"Host name:" in Window "Network"
	"Square size" of View "Settings" of Window "Render"
Complete specifiers are like regular specifiers but they always contain the entire path to an object. Usually they are given with indices and used mainly when examining the system with BeScriptPeek.
Dynamic specifiers are indentified by using in rather than of for the last item. Usually there are two items in a dynamic specifier: the object to be resolved and the container object. Dynamic specifiers can also be partial (e.g. not start with View/Window). Currently, a Window object must follow the in keyword: that is, currently only Window objects can be containers. Support is planned for View-based dynamic specifiers as well. Dynamic specifiers can also be partial. Some examples of currently available dynamic specifiers:
	"Host name:" in Window "Network"
	View "Manual" in Window "About Box"
	button "Use an external editor" in Window "Environment Preferences"
Here, button is a reserved keyword in BeScript -- not a Be-defined property.

Values can be either simple (like integers, floating point numbers, strings, and boolean values) or Be-defined (like int16, BRect, and so on). Currently BeScript supports the following values:
	VALUE                       EXAMPLE
	---------------------------------------------------------------------------------
	integer (32-bit)            27480
	floating point              -457.3
	string                      "text" or string("text")
	boolean                     true/false or bool(true/false) [Mixed/UPPER case too]
	integer 8-bit               int8(127)
	integer 16-bit              int16(49152)
	integer 32-bit              int32(-67045231)
	single precision float      float(5678.364)
	double precision float      double(829.9098947)
	point                       BPoint(45.0,120.0) [floating point only]
	rectangle                   BRect(100.0,150.0,300.0,350.0) [floating point only]
	RGB color                   rgb_color(240,200,160,255) [integer only]
	filename                    file("/path/to/file")
WITH blocks

Data can be added to some commands with the construct
	with [name=]value [and [name=]value ...]
If you do not specify the name, it is taken to be "data". An example of a with block comes from Becasso:
	create Canvas with name=MyCanvas and size=BRect(100.0,100.0,300.0,300.0)
The best way to get acquainted with the commands is to look at the sample sources, currently be_apps.bs and becasso.bs. Below please find a quick list of all available commands: I apologize for the lack of a more satisfying discussion but this is an alpha release and the language is not finalized yet.

Commands

Optional items are shown between square brackets ("[" and "]"). Alternate items are separated by a pipe character ("|"). The examples given do not always correspond to something meaningful: they are merely samples of correct grammar constructs in BeScript.

TELL blocks

BeScript commands are grouped within tell blocks:
	tell application | MIME signature
	    command
	    [command ...]
	endtell
This constructs informs BeScript that we will talk to the given application, possibly identified via its MIME signature, which must be running at the time when the tell block is executed. Once again, proper formatting is not enforced but strongly suggested. The endtell keyword may be abbreviated as endt.
Command:

	let specifier do
	    command
	    [command ...]
	end
	tell specifier to command

Examples:

	let View "Server:" in Window "Connection" do
	    get Value
	    set Value to "http://www.be.com"
	end
	tell Window "Untitled" to choose "Save..."

Description
Select the current object to whom BeScript will talk to when running the commands contained in the block. The specifier can be a regular, complete, or dynamic system specifier (not a partial one), or an application-defined specifier with custom syntax. The second form, with the tell keyword, is meant for a single command only and does not require an end keyword -- but it does require the to keyword just like let requires the do keyword. If the let or tell block refers to (i.e. specifier is) a Window, it will be used implicitly while resolving dynamic specifiers for commands contained in the block (if necessary).
Command:

	pause milliseconds

Examples:

	pause 2500   # Two and a half seconds.

Description
Pause execution for the given number of milliseconds (an integer value).
Command:

	print "string"

Examples:

	print "Hello, world!\n"

Description
Print the given string on the console (stdout).
Command:

	getsuites [specifier]

Examples:

	tell "Gobe Productive" getsuites endtell
	getsuites of Shelf of View "Status" of Window "Deskbar"
	getsuites of View "Okay"
	
Description
Return the scripting capabilities of the given specifier and display them on the console. The specifier can be a regular, complete, or dynamic system specifier (not a partial one), or an application-defined specifier with custom syntax. The result is printed in a human-readable form: each available property, specifier, and/or command is displayed for all suites supported by the object.
Command:

	do specifier | 'what' [with data]

Aliases:
	execute
	exec

Examples:

	do Item 1 of View "Programs List" in Window "List"
	do '_ABR' of Window 3
	exec MenuItem "Settings..." of Window 0
	
Description
Execute the given specifier or 'what' verb. The specifier can be a regular, complete, or dynamic system specifier (not a partial one), or an application-defined specifier with custom syntax. An optional with block may be appended to the specifier. If the specifier is a custom property defined by the currently selected object, an alternate form is supported:
	do property be_value
	
Examples:

	tell Window "Database" to do MoveBy BPoint(-50.0,50.0)

where be_value is any kind of value explicity qualified (i.e. "string" is not allowed: string("thestring") must be used instead; 244567 is wrong, int32(244567) is correct; ...)
Command:

	get specifier

Examples:

	get Title of Window 2
	get Shelf of View "Status" of Window "Deskbar"
	get Value of View "Rotation:"
	
Description
Return the contents of the given specifier and display them on the console. The specifier can be a regular, complete, or dynamic system specifier (not a partial one), or an application-defined specifier with custom syntax. The result is printed according to its type; messengers are displayed as sequences of hexadecimal values.
Command:

	set specifier to value

Examples:

	set Hidden to true
	set Foreground to rgb_color(255,0,128,255)
	set Value of View "Frame rate:" to 60
	set Label of View "unnamed_button" to "OurButton"
	set Title of Window 3 to "OurWindow"

Description
Set the contents of the given specifier to the given value. The latter can be any kind of value, even implicity qualified. The specifier can be a regular, complete, or dynamic system specifier (not a partial one), or an application-defined specifier with custom syntax.
Command:

	count specifier

Examples:

	count Window
	count View of View "listview" in Window "Document"
	count MenuItem of Menu "File"
	tell Shelf of View "Status" of Window "Deskbar" to count Replicant

Description
Count the number of instances of the given specifier and display it on the console. The specifier can be a regular, complete, or dynamic system specifier (not a partial one), or an application-defined specifier with custom syntax.
Command:

	delete specifier

Alias:

	del

Examples:

	delete MenuItem "Settings"
	delete Replicant "PPPDeskBar" of View "Status" of Window "Deskbar"

Description
Remove the given specifier. The specifier can be a regular, complete, or dynamic system specifier (not a partial one), or an application-defined specifier with custom syntax.
Command:

	create specifier [with data]

Examples:

	create Palette
	create Document with name=MyDocument
	create Canvas with name = MyCanvas and size = BRect(100.0,100.0,200.0,200.0)

Description
Create the given specifier. The specifier can be a regular, complete, or dynamic system specifier (not a partial one), or an application-defined specifier with custom syntax. An optional with block may be appended to the specifier.
Command:

	quit [specifier]

Examples:

	quit
	quit Window 3
	quit Canvas "MyCanvas"
	
Description
Quit the current target or the given specifier. The specifier can be a regular, complete, or dynamic system specifier (not a partial one), or an application-defined specifier with custom syntax.
Command:

	about [specifier]

Examples:

	about
	about Window "Untitled"

Description
Ask the current target or the given specifier for its about box. The specifier can be a regular, complete, or dynamic system specifier (not a partial one), or an application-defined specifier with custom syntax.
Command:

	save [specifier]

Examples:

	save
	save Window "ProjectFile"

Description
Save on disk the current target or the given specifier, usually a Window object. The specifier can be a regular, complete, or dynamic system specifier (not a partial one), or an application-defined specifier with custom syntax. Currently the message sent to the target is not always understood: I am aware of the problem and will fix it soon.
Command:

	load "file"
	load file("file")

Examples:

	tell Eddie load file("/boot/home/.profile") endt
	load "MyImage.jpg"

Description
Ask the current target to load the given file, which is a path name as appropriate for the context. This command will always work when addressed to applications that support it; some Windows, however, do not accept it correctly. Currently I don't know how to fix this but am working on it.
Command:

	click [button] specifier

Examples:

	click button "Manual" in Window "About"
	click "Save Changes" in Window "Preferences"

Description
Simulate a button-click on the given specifier. Note that this is not a mouse-click. The specifier can be a regular, complete, partial, or dynamic system specifier. button is an optional keyword.
Command:

	type "string" into [field] specifier

Examples:

	type "128.197.13.20" into field "URL:" of Window "Browser"
	type "OurHostName" into "Host name:"

Description
Transfer the text string into the field specifier. The specifier can be a system regular, complete, partial, or dynamic specifier. field is an optional keyword.
Command:

	choose "item" [specifier]
	choose item_number specifier

Examples:

	choose "Settings"
	choose "Preferences..." of Window 0
	choose "Save" of Menu "File"
	choose 3 of Menu "Insert Frame" of Menu "Frame"

Description
Invoke the given menu item or item_number. The specifier can be a system regular, complete, partial, or dynamic specifier, and it must be present in the second form of the command. It is usually a Window, or a particular Menu of a given Window. The MenuBar object must not be expressed explicitly: BeScript does it automatically. This implies that a choose command cannot appear in a let block that addresses a MenuBar; however, the command is meant exactly to avoid the need for such a block.
Command:

	assign value to specifier

Examples:

	assign 100 to "Size:"
	assign "http://www.benews.com" to "URL:"
	assign 0.5 to View "Panning" of Window "Mixer"

Description
Write the given value to the given specifier. The specifier can be a system regular, complete, partial, or dynamic specifier. The advantage over set is that there is no need of prepending "Value of" to the specifier, and that the latter can be partial. As you can see from the examples above, using assign with simple strings is equivalent to the type command.



Support for other commands is planned, but it is too early now to commit to any specific command. Users are once again encouraged to try out the language and submit bugs and features requests to Dario Accornero.
Please read the Version History in the README file before using BeScript.