9. User Macros

This section describes in detail TrEd's support for user macros. It is devoted mainly to programmers.

As was already mentioned, TrEd is itself written in Perl and therefore it is natural that also user macros are written in Perl language. The details of the Perl language syntax are not described here. You may find them in the literature on programming in Perl as well as in manual pages of Perl language. A large volume of documentation on Perl and many specific Perl modules is collected for example by ActiveState. In this text, we concentrate on the TrEd's macro specifics.

9.1. What is a macro

A macro for TrEd is simply any Perl routine defined using the sub { ... } construct:

sub my_first_macro {
	# any Perl code may come here
}

Macros may be called from other macros in the same way as any other sub-routines in Perl, for example:

sub my_first_macro {
	my_second_macro();  # call another macro
}

In this case, macros may use the usual Perl mechanisms to pass the called macro any number of argumens.

To make the macro accessible from TrEd, two more things should be done. First, a special “pragmas” should be added, so that the macro appears in the User-defined menu and thus may be called interactively, second the macro must be stored in some file and TrEd must be told where to look for the file. This (and more) is discussed in the following sections.

9.2. Where does TrEd look for macros

On startup, TrEd looks for two files with macros.

  1. The default set of macros (described in more detail in Section 9.5.) tred.def

  2. The user set of macros tred.mac or any other file given after the -m parameter on the command line or a file specified in the TrEd's configuration option MacroFile.

Macro programmers are supposed to keep the tred.def file unchanged, but are encouraged to modify or append the tred.mac macro file according to their needs.

Other macro files may be included into any tred.mac file by using the following directive:

#include filename

If the filename provides only a relative path, then directories are searched in the following way and order:

  1. Current directory is searched.

  2. The directory of the file where the #include directive occured is searched.

  3. TrEd's library directory is searched.

The first found occurrence of filename is used as if the content of the file was actually included in place of the #include directive.

9.3. Adding macros to menu, assigning shortcuts

For convenience, two more directives are available for adding a macro to TrEd's User-defined menu and for assigning a keyboard shortcut to macro.

To connect the my_first_macro with a new menu item labeled My First Macro under the User-defined menu, the following directive should appear in some macro file read by TrEd:

#insert my_first_macro as menu My First Macro

Actually the words as and menu may be omitted, so it is possible to write the above directive also as:

#insert my_first_macro as My First Macro

or even

#insert my_first_macro My First Macro

To make the macro accessible via a keyboard shortcut, the #bind directive may be used. Thus, to “bind” the my_first_macro macro to say Ctrl+Alt+Del keyboard shortcut, the following line should appear somewhere in the macro file:

#bind my_first_macro to key Ctrl+Alt+Del

Again, the to and key words may be omitted.

Warning

Using the shortcut selected here as an example is probbably not a good idea, as it is a shortcut that restarts the system on many computers. Remember, that only shortcuts which are not interpreted by the operating system or desktop environment may be assigned to TrEd's macros and work correctly.

If both the menu item and keyboard shortcut are needed for a macro, the following abbreviated form can be used:

#bind my_first_macro to key Ctrl+Alt+Del menu My First Macro

If more than one keyboard shortcuts or menu items are connected with the same macro, any of them may be used to invoke the macro. If more than one macro was assigned the same keyboard shortcut, the latest binding appearing in the macro files is used. Also, if two or more macros with the same name appear in the loaded macro files, the last one is used.

When an interactively invoked macro returns, TrEd automatically redraws the tree and activates the node referenced by variable $this. Moreover, the file is also considered modified which may toggle the state of the button. This may be an unwanted behaviour for macros which do not change the structure of the trees nor attribute values. To prevent TrEd from considering the file modified by the macro, the macro must explicitly set the special variable named $FileNotSaved to zero.

Note

If the state of the file was considered modified and not saved when a macro was interactively invoked, any changes to the $FileNotSaved variable made by the macro are ignored.

9.4. Accessing tree nodes

In TrEd macros as well as in TrEd itself tree nodes are represented by FSNode objects. They are basically only references to hash tables where the attribute-name/attribute-value information is stored as a key/value pair. Moreover, the FSNode object provide a few methods to access the node's neighbors in the tree.

The macro invoked by user action (e.g. pressing the keyboard shortcut assigned to the macro) may use the following global variables (declared within the TredMacro namespace, but exported by default to other derived contexts) to refer to the active node and to the root of the active tree (how to access any tree in the file will be told later):

$this

Points to the active node in the current tree. Macros may use this variable to access the active node as well as to make other node active (by assigning a different node reference to it).

$root

Points to the root of the current tree. Macros may use this variable to access the root of the tree and (consequently) to access the whole tree. However, all assignments to this variable are ignored by TrEd.

9.4.1. Accessing node attributes

If $node is a FSNode object reference, the value of the attribute attr is returned by the $node->{attr} expression. To give the node's attr attribute new value, say val, the following simple assignment may be used:

$node->{attr}='val';

9.4.2. Accessing node neighbors

There is a few methods provided by every FSNode reference that can be used to access other nodes in the tree. Each of the methods returns either a FSNode reference pointing to the requested node, or zero if the requested node does not exist. For example, the following code may be used to make the active node's parent node active:

$this = $this->parent if ($this->parent);

The above code states: activate the parent node of the currently active node in case the currently active node has a parent.

The following table describes all available FSNode methods:

Table 2. FSNode methods

Method name Description
parent Return node's parent node.
firstson Return the first child node of the given node (the first node depending on the given node).
rbrother Return the right brother of the given node. However, hence the nodes may be displayed in the order given by the special FS-format numbering attribute @N, the returned node need not necessarily be displayed to the right of the given node. This is because the ordering according to the special numbering attribute does not have to correspond to the natural structure ordering of the tree.
lbrother Return the left brother of the given node. However, as for the right brother, hence the nodes may be displayed in the order given by the special FS-format numbering attribute @N, the returned node need not necessarily be displayed to the left of the given node. This is because the ordering according to the special numbering attribute does not have to correspond to the natural structure ordering of the tree.
following(top?) Return the next node of the given node in the natural ordering of the tree provided by the tree structure. E.g. if the given node has a child, this child is returned. If the node has no childs but does have a right brother, FSNode reference to the right brother is returned. If no child and no right brother exists for the given node, the method looks for a right brother of its nearest ancestor, etc. Starting from the root of the tree and calling this method repeatedly on the returned nodes ensures that all the nodes in the trees are accessed during the procedure. If the optional argument top contains a FSNode reference, the searching is limited to the subtree governed by the node of top.
previous(top?) Return the previous node of the given node in the natural ordering of the tree provided by the tree structure (see the above description of the following method for more detail on the ordering). If the optional argument top contains a FSNode reference, the searching is limited to the subtree governed by the node of top.

9.4.3. Accessing other trees

To be able to describe a method to access any tree in a file opened by TrEd from macros we must first take a more detailed look on the TrEd's internal structures and classes.

Macros have actually access to one of the most fundamental structure of TrEd: the so called `Grouping' structure. This structure may be accessed using the $grp variable. There is really a lot of things that can be achieved by using this variable in a proper way, but since actually not many members of the `Grouping' structure are guaranteed to be present or unchanged in the future versions of TrEd, here we pay our attention only to a few of the most important.

The current file is accessed via the $grp->{FSFile} reference. This reference is a pointer to an object of the FSFile class. The following methods are provided for the class:

Table 3. FSFile class methods

Method name Description
filename Returns current file's name.
changeFilename(name) Change current file's name.
FS Return a reference to an associated FSFormat object.
trees Return a list of all trees (i.e. a list of FSNode object references pointing to the roots of the trees).
hint Return the TrEd's hint pattern associated with the file.
changeHint(pattern) Change the TrEd's hint pattern associated with the file.
pattern(n) Return the n'th attribute pattern associated with the file.
patterns Return a list of all attribute patterns associated with the file.
pattern_count Return the number of display attribute patterns associated with the file.
changePatterns(patterns) Change the list of attribute patterns associated with the file.

Actually, some other FSFile methods exist, but as they are not intended to be used from the macros directly, there is no reason to describe them here.

The most important of the methods above is the FS method which may be used to access the FSFormat class object associated with the given file. Similarly as in the case of FSFile class, only the most importand methods of the FSFormat class are described here.

Table 4. FSFormat class methods

Method name Description
order Return the name of the special numerical FS attribute responsible for providing the tree order. This attribute is declared in the FS file as @N.
sentord Return the name of the special numerical FS attribute responsible for providing the order of the values to form a `sentence'. This attribute is declared in the FS file as @W.
value Return the name of the special FS attribute providing a descriptive value of the node used when forming a `sentence'. This attribute is declared in the FS file as @V.
hide Return the name of the special FS attribute which can be used to hide several subtrees. This attribute is declared in the FS file as @H; a subtree is hidden if its governing node's value for this attribute is 'hide'.
isHidden(node) Return true if the given FSNodenode belongs to a hidden subtree.
isList(attr) Return true if the given attribute is of list type with a strictly defined set of possible values. This type of attributes is declared by the @L header in the FS format.
listValues(attr) Return a list of all possible values for the given attribute attr. Empty list is retured in case the attribute is not of the list type.
color(attr) Return one of the Shadow, Hilite, XHilite and normal values, depending on the color specified in the FS file header.
attributes Return a list containing names of all the attributes declared in the FS file header.
atno(n) Return name of the n'th attribute.
indexOf(attr) Return index of the attribute named attr, according to the order in which attributes are defined in the FS file.
count Return the number of attributes defined in the FS file.
exists(attr) Return true if attribute named attr exists for the FS file. If it is not the case, false (i.e. zero) is returned.
make_sentence(root_node) Return a string forming a `sentence' for the given node's subtree. The sentence is formed in the following way:
  1. All the nodes of the root_node node's subtree and ordered according to their values for the special numerical FS sentence ordering attribute (see method sentord above). The special FS numbering attribute (see method order above) is used if no sentence ordering attribute is declared in the FS file.

  2. For every such node, its descriptive value (see method value above) is taken.

  3. The values obtained in this way and order are joined into a single string with fields separated by the value of separator.

The methods sentord, order, value, and hide described above are useful especially in macros of general purposes, where names of the corresponding attributes are not known in advance. However, one should keep in mind, that calling these functions too often may result in considerably worse performence. The following example which actually re-implements the make_sentence method shows usage of the FSFormat object member methods.

sub MakeSentence {
  my ($top,$separator)=@_;   # two parameters

  $separator = ' ' unless defined($separator);
  my @nodes  = ();           # array to store the nodes in
  my $ord    = $grp->{FSFile}->FS->sentord ||
	       $grp->{FSFile}->FS->order;
  my $value  = $grp->{FSFile}->FS->value;

  my $node = $top;
  while ($node) {
    push @nodes, $node;      # collect all nodes in the nodes array
    $node = $node->following($top);
  }

  # Translation of the following mighty Perl construct to English:
  # 1. sort the collected nodes comparing their values for attribute $ord
  # 2. get their values using a "map" which maps element $_ to $_->{$value}
  # 3. join the values separating the fields with $separator
  # 4. and finally return the string.

  return join( $separator,
	       map { $_->{$value} }
	       sort { $a->{$ord} <=> $b->{$ord} } @nodes );
}
	  

If the values of value, sentord or possibly order were evaluated each time the Perl sort or map function needs to compare or map nodes, the performance of the code would considerably decrease.

9.5. Pre-defined functions

The above mentioned tred.def file provides a set of pre-defined TrEd macros. Pre-defined macros may be freely used in all other user-defined macros and are guaranteed to be updated and shipped with every version of TrEd preserving their own API. Most of the pre-defined macros wrap the key TrEd's internal function calls. The following table briefly describes each of them.

Table 5. TrEd's pre-defined macros.

Macro name Description
Redraw Force TrEd to redraw the current tree immediately. Hence TrEd redraws the tree right after an interactively invoked macro finishes. The Redraw macro is needed only under certain circumstances.
SetDisplayAttrs(patterns) Use the given list of TrEd's patterns for displaying attributes. See also Section 5..
SetBalloonPattern(pattern) Use the given pattern as a new hint pattern. See also Section 5..
SwitchContext(context_name) Switch to a different macro context. See Section 9.7. for more details about contexts.
CurrentContext Return the name of the current macro context. See Section 9.7. for more details about contexts.
GotoTree(n) Display the n'th tree in the current file.
NextTree Display the next tree in the current file.
PrevTree Display the previous tree in the current file.
GetOrd(node) Return value of the special numbering FS attribute. This macro actually returns the same value as
$node->{ $grp->{FSFile}->FS->order }
CutNode(node) Cut the node's subtree off the tree and return it.
PasteNode(node,parent) Paste the subtree of the node given by the node argument under the node given in the parent argument. TrEd tries to place the top node of the pasted subtree between the present children of parent according to values of the special FS numbering attribute.
NewTree Create a new tree before the current tree. The new tree consists of exactly one node. This node is activated and a reference to its FSNode object is returned.
NewTreeAfter Create a new tree after the current tree. The new tree consists of exactly one node. This node is activated and a reference to its FSNode object is returned.
Save Save the current file.
SaveAndNextFile Save the current file and open the next file in the current file-list.
SaveAndPrevFile Save the current file and open the previous file in the current file-list.
Find Open the Find Node by Attributes GUI dialog. See Find... menu command for more detail.
FindNext Searches for the first node matching the criteria of the previous use of the Find... menu command or FindNode macro usage.
FindPrev Searches for the previous node matching the criteria of the previous use of the Find... menu command or FindNode macro usage.
PlainNewSon(parent) Add a new child node to the given parent.
PlainDeleteNode(node) Delete the given node. The node must be a leaf of the tree (may not have any children) and must have a parent (may not be the root of the tree).
NewRBrother(node) Create a new brother of the given node and recalculate the special FS numbering attribute values in the whole tree so that the new node is the first right sibling of the given node.
NewLBrother(node) Create a new brother of the given node and recalculate the special FS numbering attribute values in the whole tree so that the new node is the first left sibling of the given node.
NewSon(parent) Create a new child of the given parent node and recalculate the special FS numbering attribute values in the whole tree so that the new node is the first node left to the given parent.
DeleteThisNode Delete the current node and recalculate the special FS numbering attribute values in the whole tree so that there is no gap in the numbering. If the current node is not a leaf or if it is the root of the current tree, this macro does nothing.
CopyValues Copy the values of all the attributes except the special FS numbering attribute of the current node to a global hash variable named %ValuesClipboard.
PasteValues Replace the values of the current node's attributes by those stored in the global hash variable named %ValuesClipboard.
NextNode(node,top) Return the first displayed node following the given node in the subtree of top. This function behaves in the same manner as the
node->following(top)
method, except it works only on the nodes which are actually visible according to the state of the View->Show Hidden Nodes menu item.
PrevNode(node,top) Return the first displayed node preceding the given node in the subtree of top. This function behaves in the same manner as the
node->previous(top)
method, except it works only on the nodes which are actually visible according to the state of the View->Show Hidden Nodes menu item.
NextVisibleNode(node,top) Return the first visible node following the given node in the subtree of top. This function behaves in the same manner as the
node->following(top)
method, except that nodes of hidden subtrees are skipped.
PrevVisibleNode(node,top) Return the first visible node preceding the given node in the subtree of top. This function behaves in the same manner as the
node->previous(top)
method, except that nodes of hidden subtrees are skipped.
IsHidden(node) Return true if the given node is member of a hidden subtree. This macro is only an abbreviation for
$grp->{FSFile}->FS->isHidden(node)
GetNodes(top?) Return the list of all nodes in the subtree of the given top node (or the whole current tree if no top is given). The list returned is ordered in the natural structure ordering.
GetVisibleNodes(top?) Return the list of all visible nodes in the subtree of the given top node (or the whole current tree if no top is given). The list returned is ordered in the natural structure ordering and all members of hidden subtrees are skipped.
NormalizeOrds(listref) Adjusts the special FS numbering attribute of every node of the list referenced by the listref argument so that the value for the attribute corresponds to the order of the node in the list.
SortByOrd(listref) Sort the list of nodes referenced by the listref argumnt according to the values of the special FS numbering attribute.
RepasteNode(node) Cut the given node and paste it immediately on the same parent so that its structural position between its parent children is brought to correspondence with the values of the special FS numbering attribute.
ShiftNodeRightSkipHidden(node) Shift the current node in the tree to the right leaping over all hidden subtress by modifying the tree structure and value of the special FS numbering attribute appropriately.
ShiftNodeLeftSkipHidden Shift the current node in the tree to the left leaping over all hidden subtress by modifying the tree structure and value of the special FS numbering attribute appropriately.
ShiftNodeRight Shift the current node in the tree to the right by modifying the tree structure and value of the special FS numbering attribute appropriately.
ShiftNodeLeft Shift the current node in the tree to the right by modifying the tree structure and value of the special FS numbering attribute appropriately.
AtrNo(n) Retrun name of the n'th attribute defined in the FS file. This macro only abbreviates the following expression:
$grp->{FSFile}->FS->atno(n)
ValNo(n,string) Return the n'th field of the given string where individual fields are separated by “|”.
Union(string-a,string-b) Return a string consisting of |-separated fields which form a pairwise disjoint set of the |-separated fields of the given strings string-a and string-b.
Interjection Return a string consisting of |-separated fields which form the interjection of the sets of |-separated fields of the given strings string_a and string_b.
ListSplit(string) Split the given string of |-separated fields and return a list of the individual fields.
ListJoin(fields) Form a string of |-separated fields from the given list of fields.
ListEq(string_a,string_b) Compare the sets of |-separated fields of the two given strings. Return true if the sets contain the same elements; return false otherwise.
ListAssign(string,n,value) Return the given string of |-separated fields, except that the n'th field in the string is replaced by the given value.

9.6. Hooks: automatically executed macros

There is a special set of macros that was not mentioned yet. These macros, called hooks, are automatically executed by TrEd on certain occasions. By defining such macros a little more of the TrEd's implicit behaviour may be influenced, for example the execution of a planned action may be aborted.

Hooks differ from other macros in the following aspects:

  1. User cannot choose a name for a hook; on the contrary hook is recognized as a macro having a special name identifying it as being a certain hook.

  2. Sometimes parameters are passed to hooks.

  3. No modifications of the tree or current node are reflected after the hook returns, i.e. the tree is not redrawn, changes to $this variable are not reflected. If necessary, a hook must provide this functionality itself.

  4. Unlike macros, hooks are not expected to modify the tree unless they explicitly state that by setting the $FileNotSaved to 1.

There is a special class of hooks that may be used to prevent TrEd from finishing a planned action, e.g. enabling the user to modify certain attribute value etc. The value returned by a hook of this class is checked and the action is aborted in case the value equals to “stop”.

In the following table the hooks which may be used to prevent TrEd from executing a certain action are marked by yes in the column “Stop”.

Table 6. Hooks

Name Stop Hook event description
start_hook yes Executed on start-up, before the main event loop is entered.
exit_hook no Executed when the main window of TrEd is destroyed, before the user is prompted to save the modified files.
do_edit_attrs_hook(attr,node) yes Executed before a dialog window for editing an attribute value is displayed (usually when the user double-clicks the displayed attribute value).
enable_attr_hook(attr, type) yes Executed before any text field for an attribute value modification or list of possible values is created. If the hook returns “stop”, the field or list is made read-only. The type argument may have one of the following values: ambiguous (in case of multiple values or an attribute with a given list of possible values) or normal.
goto_file_hook yes Executed before the next or previous file in the current file-list is opened.
file_opened_hook no Executed after a file is opened but before the first tree is displayed.
file_resumed_hook no Executed after an already open (kept) file is resumed.
pre_switch_context_hook(previous,next) yes Executed before the macro context is changed (see Section 9.7.). The previous argument contains the name of the current context while next contains the name of the future mode.
about_file_hook(stringref) no This hook is executed before the About window is displayed. The hook takes one argument: a reference to a scalar variable. The hook may populate the referenced variable with any information which is then displayed as a part of the About window message.
customize_attrs_hook yes Executed before the dialog for customizing attribute patterns is displayed.
sort_attrs_hook(attibsref) no Executed when attribute names are pre-sorted (see SortAttributes configuration options). The attribsref parameter passed to the hook is a reference pointing to a list variable. The list is populated with all attribute names and the hook may modify the list by changing the order of its elements or by removing certain elements from the list. If then non-zero value is returned by the hook, the (possibly modified) list is used for displaying purposes without any further sorting.
after_edit_node_hook(node,result) no Executed after a node's attribute values were edited in the Edit Node Attributes dialog window. The result argument contains 1 if the user pressed Ok button to close the dialog, and it contains 0 if the user closed the dialog with the Close button.
after_edit_attr_hook no Executed after a node's attribute was edited in the Edit Attribute dialog window. The result argument contains 1 if the user pressed Ok button to close the dialog, and it contains 0 if the user closed the dialog with the Close button.

9.7. Using different sets of macros in different situations

Name-spaces, contexts and binding contexts

TrEd adopts mechanisms similar e.g. to Emacs major-modes which allow the user to safely create several sets of macros with possibly coliding key and menu bindings as well as macro names. These mechanisms are called contexts, or more specifically: name-spaces and binding contexts.

To prevent macros from colliding or modifying the internal TrEd variables or functions the Perl concept of packages or namespaces is used. Unless the Perl package command is used, each macro belongs to a default package (or name-space) called TredMacro. Beside the user-defined macros, the pre-defined macros are defined within this package.

New package named say MyPackage is created using the following command:

package MyPackage;
To be able to call macros defined in the TredMacro package (e.g. the pre-defined macros) without having to prefix their names with one of TredMacro-> or TredMacro:: prefixes the following command may be used to import the TredMacro name-space to the current package's name-space:
import TredMacro;
Now all the subsequent macro definitions (or global variable declarations) are created within the MyPackage name-space unless other package command occurs. Thus the following example defines a macro named my_macro in a new name-space of the MyPackage package:

package MyPackage;
import TredMacro;

sub my_macro {
  # macro code comes here 
}

To call the my_macro of the previous example from a different package than MyPackage, the full package name must be given together with the name of the macro:

MyPackage::my_macro();   # call to a macro defined in MyPackage

In the interactive work, name-space are combined with a concept of binding-contexts. Binding contexts usually correspond to the package names. Analogously to the package command, a special directive exists for creating or switching a binding context, however in this case, more then one binding-contexts may be used at once:

#binding-context context_name_1 context_name_2 ...
The default binding-context is again named TredMacro. To switch to a new context, say MyContext, the following directive should be used:
#binding-context MyContext
Now, all the subsequent directives #bind or #insert would apply to this context and all the bound macros, when invoked, will be first searched in the package named MyContext.

There is a separate submenu created for each binding-context in the User-defined menu. In TrEd there may be only one context active at one time. The active context may be choosen from the menu on the right end of the menu bar. If a keyboard shortcut is pressed, TrEd first searches for a macro bound to the shortcut within the active binding-context and if such macro exists, TrEd invokes it. In this case, the macro is supposed to be defined or imported in a package of the same name as is the name of the active context. If no macro is bound to the shortcut within the active binding-context, TrEd tries to find a binding in the default TredMacro context. The macro is then supposed to be defined within the TredMacro package. If the search fails, no action is taken. The same holds for hooks as well.

The following example shows how to create a set of macros which enable TrEd to automatically decide which context to use when a file is opened. It is quite similar to the Emacs concept of “auto-modes”, except that content of the file is considered here, not the file-name extension. In the example, existence of a certain attribute is used to decide which context to use. The example is extracted form the set of macros written for the annotation of the Prague Dependency Treebank. There are two contexts for annotation: for the “Analytic” and “Tectogrammatic” layers. The “tectogrammatic” trees may be easily distinguished from the “analytical” ones because (among others) a special attribute named TR is defined for them.

Example 1. Using contexts

package TredMacro; # Ensure we are in the default package
                   # (This may not be necessary.)

# we use a hook to set the context
sub file_opened_hook {
  if ($grp->{FSFile}->FS->exists('TR')) {
    SwitchContext('Tectogrammatic'); 
  } else {
    SwitchContext('Analytic');
  }
}

#binding-context Analytic
package Analytic;
import TredMacro;


#bind SubtreeAfunAssign to Ctrl+Shift+F1
sub SubtreeAfunAssign {
   # automatic assignment of some analytical attributes
}

sub ReorderTree {
   # ...
}


# the following binding of ReorderTree will apply to both binding contexts

#binding-context Analytic Tectogrammatic
#bind ReorderTree to Ctrl+R

#binding-context Tectogrammatic
package Tectogrammatic;
import Analytic;   # all macros of the Analytic package (eg. ReorderTree) 
	           # will now be available here directly

#bind SubtreeAfunAssign to Ctrl+Shift+F1
sub SubtreeFuncAssign {
   # automatic assignment of some tectogrammatical attributes
}