user_guide:extend:rulefiles

Rulefiles Structure and Syntax

Rulefiles are used to describe, first of all, the mathematical vocabulary of the application. New object types and properties must be defined in the rulefiles, as well as all user functions written in perl. Besides this, the rulefiles can contain various auxiliary technical stuff like preference lists, custom variables, help topics, etc.

Rulefiles must reside in the rules subdirectory of the corresponding application. The names for the rulefiles can be chosen arbitrarily, but there are two names with predefined semantics:

main.rules
is always loaded when the application is used for the first time (for the user's favorite applications this happens automatically at the beginning of each session). This rulefile usually contains some general help topics and few definitions, including the whole rest from further rulefiles.
upgrade.rules
is loaded on demand, when the user tries to load a data file written in old plain text style (used in polymake releases until 2.9). It contains methods for conversion of obsolete properties to the current format.

All other rulefiles are usually included (directly or indirectly) from main.rules, so that they are loaded from the very beginning as well. Some optional rulefiles may be deliberately not included in main.rules, leaving it to the user to load them, when needed, with the interactive command include.

A rulefile consists of definition blocks, comment lines, and arbitrary perl code, e.g. introducing internal subroutines and lexical variables. A definition block always starts with an empty line, followed by an optional comment block, further empty lines, and finally a special header line deviating from the valid perl syntax. Long headers can be split into several lines, using the backslash as a continuation marker.

Depending on the kind of definition, the text in the comment block can be interpreted as a corresponding help topic, which is then made available to the user by means of the interactive help command and auto-generated HTML documentation pages. A help topic can also be created in a stand-alone comment block, by starting it with a word @topic in the first line. Elsewhere empty lines and comments can be freely used as far as allowed by the standard perl syntax.

There are three types of definition blocks, differing in syntax:

  1. single-line definitions consisting solely of a header, like file_suffix, label, or prefer.
  2. plain text blocks like USE, INCLUDE, or CREDIT. Their body extends up to the next empty line. Interspersed comment lines are allowed and ignored.
  3. scoped blocks like object or property_type. Their body is enclosed in curly braces { … } and can expand over arbitrarily many lines. Scope-like blocks can be nested, but must not intersect with plain text blocks.

Production rules constitute a special case in that they can be defined by a sequence of several blocks. The first block, usually a scoped one, comprising the main rule subroutine, may be followed by further single-line and scoped blocks defining its weight and preconditions. Empty lines and comments between the blocks in such a sequence are allowed and ignored.

The rulefile is automatically prepended with a package declaration

package Polymake::APPNAME;

The scoped blocks automatically introduce nested packages which must not be changed in the containing code. On the top level, however, you can introduce new packages and switch between them as often as you need. Definitions of objects and property types must reside in the application default package; to switch back into it, you should write

package application;

The entire rulefile contents is always interpreted in the namespace mode, which, among others, means that you may use abbreviated package names, and must explicitly introduce every variable used in your code either as lexical (my) or as global (declare).

The following definitions control the dependencies between applications, extensions, and single rulefiles. All of them are written as plain text blocks. At your discretion, they can be written on a single line, with contents immediately following the header keyword, or distributed over several lines. The items (application, extension, or file names) are separated by white spaces.

USE appname …

Tells that the current application depends on other applications, which have to be loaded immediately (if not happened yet) as this block is proceeded. Cyclic dependencies between applications are forbidden; when detected, they lead to a fatal error. The using relation between applications is expanded transitively, so that you don't need to list all applications, just the independent ones.

Having established the using relation allows you to refer to many symbols defined in other applications, like names of rulefiles, global variables, property types, object types, and user functions, by simply prefixing their names with appname:: .

IMPORT appname …

Like USE, but additionally injecting the symbols (names of object types, property types, functions, global variables, and preference labels) defined in the named applications into the namespace of the current one, so that they are found without explicit prefixing. Like using, the importing relation is expanded transitively. You should be thrifty with importing, as it may easily lead to name conflicts.

Importing a polymorphic function has a side effect you should be aware of as well. If you introduce further overloaded instances of a function in the current application, they will be also taken into account in the imported applications. This is harmless as far as the new instances are overloaded for data types introduced in the current application, otherwise it can lead to a surprising effect that the code in one application starts to behave differently when another application is loaded. Be careful.

INCLUDE filename …

Includes further rulefiles before proceeding with the rest of the current rulefile. Normally the additional rulefiles are residing in the same rules subdirectory, but they also may come from an extension. You can also include rulefiles from other applications, provided these applications are used or imported by the current one. To this end, the filename must be prefixed with appname:: .

Each rulefile is included only once, even if it occurs in multiple INCLUDE blocks. It is a fatal error if the given filename can't be found. Rulefiles excluded by configuration are not loaded but silently ignored. You can provide a fallback rulefile as an alternative to one or more configurable rulefiles:

rulefile | fallbackfile
if rulefile is successfully configured, it will be loaded and fallbackfile disabled; otherwise rulefile will be silently skipped and fallbackfile will be loaded.

rulefile1 + rulefile2 + ... | fallbackfile
loads all successfully configured rulefiles; fallbackfile will only be loaded if all prior ones are unconfigured.

REQUIRE filename …

Like INCLUDE, this causes the specified rulefiles to be loaded before the current rulefile is processed further. This form of inclusion makes the further loading process of the current rulefile depending on the configuration status of the requested files. If at least one of them is excluded by configuration, then the current rulefile is also marked as unconfigured and the rest of it not processed.

You can specify a list of alternatives rulefile1 | rulefile2 | ... if any single one of them is a sufficient prerequisite for the current rule file. The list is processed from left to right; as soon as one alternative is successfully loaded, the rest is skipped. However, this does not preclude the skipped rulefiles from being included elsewhere, if needed. As with INCLUDE, a rulefile may be safely requested several times throughout the complete ruleset.

REQUIRE_EXTENSION URI

States that the current rulefile can only be processed further if the specified extension(s) are successfully registered and configured. You can specify a list of alternatives:

URI1 | URI2 | ...
if any single extension of this list is sufficient.

REQUIRE_APPLICATION name …

States that the current rulefile can only be processed after the named applications are loaded. If the required applications are indeed loaded, the rulefile is processed normally, otherwise it is put on hold. When all required applications are loaded at a later moment of time, the processing of this rulefile is automatically resumed, no user interaction is needed.

This is a guard for so called cross-application functions which construct objects defined in one application out of other objects defined in other unrelated applications, that is, not being imported or used by the owning application. All cross-application functions should be defined in designated rulefiles, having such guard at the very beginning (maybe preceded by auto-configuration blocks). Such a rulefile may only introduce functions, methods, and auxiliary code, but no new object types, property types, or properties. This constraint naturally follows from the requirement of applications being self-consistent.

Note that applications the current one really depends on, that is, imported or used ones, must not be listed in such guard.

Rulefiles may contain configurable settings, which can be filled automatically and manipulated by the user. There are two kinds of items related to configuration: custom variables and auto-configuration blocks. Declarations of custom variables should be preceded with comments, which are copied into the user's settings files for the sake of better legibility.

custom $var = default_value;
Introduces a custom scalar variable.
custom @list = ( default values );
Introduces a custom array variable.
custom %table = (
key => value, # Type and comment

);

Introduces a custom hash table; the meaning of each key should be described in an own comment, which should start with the expected value type. Lengthy comments can be put on separate line(s) above the key-value pair.

All custom variables must have default values, unless they are to be filled in a subsequent CONFIGURE block.

CONFIGURE {

return 1;
}

Defines an auto-configuration subroutine. It can search (and ask the user) for some programs, read system files, or whatever else deemed appropriate, storing the result in custom variables defined beforehand. When the subroutine returns TRUE (any non-zero value), the rulefile is considered successfully configured, this fact is also recorded in the user's settings file, and the processing of the rule goes on. If this subroutine returns zero or dies with a message, the rulefile is marked as unconfigured and its loading stops. In the subsequent polymake sessions, all succesfully configured rulefiles are loaded without re-running the auto-configuration subroutines, and all unconfigured rulefiles are silently skipped. Please recall that the user may later manipulate the configuration status using the interactive commands reconfigure and unconfigure.

A rulefile can actually have several CONFIGURE blocks, in which case it is considered successfully configured if all of them returned TRUE.

CONFIGURE_OPT {

}

Defines an optional auto-configuration subroutine. Even if it returns FALSE (but does not raise an exception), the processing of the rulefile does not stop.

label name

Introduces a top-level name for labels, used in definitions of production rules and user methods.

prefer label expression

Establishes a default preference list for a certain group of competing rules or user methods. The syntax of the label expression is the same as in the interactive commands prefer and prefer_now, but here you should not enclose it in quotes nor put a trailing semicolon. These are the lists that are re-established upon execution of a reset_preference command.

CREDIT name
Some text …
URL

Briefly describes a third-party software package used in (or interfaced by) the production rules and/or user methods subsequently defined in the current rulefile. Depending on the user's settings, the credit notes are displayed at the first use of the software (or each time it is used), as well as stored in the datafiles produced with its help. The text should at least include a copyright notice, list the main authors, and refer to the appropriate website. The name is a short unique id allowing to refer to this credit note from other rulefiles.

CREDIT name

Signals that the subsequently following production rules and/or user methods are subject to the credit note defined elsewhere in the ruleset.

CREDIT off

Ends the sequence of rules and/or user methods subject to the credit note referred above. All subsequent code (up to the next CREDIT block) is considered genuine polymake intellectual property.

CREDIT default

Ends the sequence of rules and/or user methods subject to the credit note referred above, switching back to the credit note of the entire extension, as far as it is specified in the description file polymake.ext.

# @topic help/hierarchy/path/topic
# Some text …

Introduces a stand-alone help topic, to be displayed by the interactive help command and included into the auto-generated HTML documentation. In most cases, help topics are automatically associated with the subsequent definition block. This stand-alone form should be used for help topics not belonging to any definition item, like the top-level application description or when a built-in user command, defined in the polymake core modules, is to be documented. More details about formatting the help texts can be found here.

# @topic category help/hierarchy/path/Title with few words

Describes a category of help topics, e.g. a group of object properties closely related to each other.

HELP filename …

Specifies additional rulefiles consisting of help topics solely. Works like INCLUDE but only when polymake is running in interactive mode or is generating documentation XML files; otherwise the rulefiles are ignored. The only purpose of the special treatment of help files is to reduce start-up time when running in batch mode. There are no precautions in place which would hinder you from putting any substantial definitions in help files, so please be careful to avoid surprises when a function working perfectly in an interactive session suddenly fails in all scripts.

Property types, aka “small object types”, must be defined once before any use. The definition consists of a declaration header listing the essential type attributes and relations to other property types, and an optional definition scope block where type-specific methods (described in more details below) are introduced. The method definition scope can be reopened any times in the same rulefile where the type has been introduced or in any rulefile processed later. This allows to define methods depending on configuration or in extensions.

declare property_type name [ : super_class ] [ : upgrades( simpler_type, … ) ] [ : c++ ( attributes ) ] …

Introduces a new property type with an optional binding to a C++ class or built-in type. The optional super_class designates another property type to inherit methods from.

simpler_types named in the optional upgrades clause must be already defined property types that can be coerced to the currently defined type without any loss of information. In more formal words, the value domain of each simpler_type must be isomorphic to a subset of the value domain of currently defined type. For example, type Integer upgrades the built-in type Int, type Rational upgrades Integer, polynomial Term upgrades the corresponding Monomial and Coefficient types, etc. Type upgrade relations play an important role in flexible type deduction of polymorphic functions, but otherwise do not incur any implicit data conversions performed by polymake.

The name of each type must be unique in the enclosing application. It may be redefined in other applications using this one, but generally reusing names leads to more confusion and should be avoided. In some rare cases where reusing the same type name seems less harmful than introducing unnatural different names, you can disambiguate it in your code by prefixing it with the name of application where it is defined, or with a special prefix props::, to distinguish it from “big” object type names.

The attribute list keyed with c++ means that the data are to be kept in a native C++ object attached to the so called perl magic storage. The interplay between property types visible on the perl side and C++ classes is explained in more details here.

The declaration header must be followed by the method definition scope block or be concluded by a semicolon.

declare property_type name < paramname, … > [ [ typechecks ] ] …

Introduces a new parametrized property type, similar to a C++ class template. paramname are placeholders for type parameters, which can be used everywhere in the definition scope block, including the super type expression, signatures and bodies of the methods. Parameters may have default values specified like paramname = type or paramname = (complex expression). The latter form can dynamically choose the proper default value based on other (preceding) type parameters.

The super class, if any, can also depend on type parameters, including dynamic choice by a complex expression enclosed in parentheses.

An optional type checking clause (enclosed in square brackets) may be used to restrict the feasible set of type parameter values. It may contain one or more calls to type checking functions in void context, which are supposed to fire an exception if the supplied type parameters do not meet the expectations.

property_type name {

}

Reopens the method definition scope block for a property type introduced earlier in the rulebase.

property_type name < other_type, … > {

}

Opens a method definition scope block for a specialization of a parametrized type with the given set of type parameters. Unlike in C++, a specialization does not replace the generic definition of the type, but rather augments it with more methods. Polymorphic method instances defined in the specialization are merged into the methods of same names defined in the generic scope; they participate in the call resolution according to the usual rules. Non-polymorphic methods defined in the specialization, however, override the methods of same names defined in the generic scope.

Specialization blocks can also be reopened in any rulefiles using the identical syntax.

Similar to “small” object types, “big” object types are defined by a declaration header and a series of definition scope blocks which may be arbitrarily distributed over the rulebase. The definition scope blocks introduce properties, production rules, user methods, and other features; they are described below in more details.

declare object name [ : super_type, … ]

Introduces a new “big” object type, with an optional list of other types to be derived from. Multiple inheritance is supported here. The declaration header must either be followed by the first definition scope block or be concluded with a semicolon.

declare_object name < paramname, … > [ [ typecheck ] ] [ : super_type, … ]

Introduces a new parametrized “big” object type. Similar to property type definitions, the type parameters may have static or dynamic default values and be involved into additional checking routine. The super-types may also depend on the type parameters.

object name {

}

Reopens the definition scope block for the named “big” object type. This can happen in any rulefile processed after the one containing the declaration header. It is also allowed to extend object types defined in another application (which must be used or imported by the current one). In this case the object type name must be prefixed with the name of the application hosting the original definition, also in the imported case.

object name < other_type, … > {

}

Opens a definition scope block for a full specialization of a parametrized “big” object type with the given set of concrete type parameters. Unlike in C++, a specialization does not replace the generic definition of the type, but rather augments it with more properties, rules, methods, etc. A specialization can not override rules or properties defined for the generic type; this is only allowed for derived object types.

Specialization blocks can be reopened as often as needed using the identical syntax.

declare object_specialization [ spec_name ] < paramname, … > = name < type patterns > [ [ typecheck ] ] {

}

Introduces a partial specialization of a parametrized “big” object type. A partial specialization applies to instances of a parametrized type with type parameters matching the given set of patterns and/or satisfying the given type checking expressions. A concrete instance of a “big” object type may match several different partial specializations and thus inherit all their features.

Polymorphic methods of same name introduced in the generic definition scope and any specializations are merged together, and usual call resolution rules apply. Non-polymorphic methods defined in a specialization override those defined in the generic definition scope. A full specialization takes preference over any partial specializations, while the preference between partial specializations is implied by the order of their appearance in the rulebase.

A production rule defined in a partial specialization may refer to properties introduced in another partial specialization of the same object type. No explicit declaration of such cross-dependency is needed, but the author must assert by design of type patterns and/or type-checking routines that the referring specialization is equivalent to or is a special sub-case of the property-defining specialization. polymake can only verify the correctness of such a dependency on a case-by-case basis at the moment of instantiation of concrete object types; a subtle error could remain undiscovered for years! A full specialization may safely refer to properties introduced in the matching partial specializations; no special checks are applied, because the inheritance from the matching partial specializations is established immediately at the moment of declaration.

An optional spec_name can be assigned to the partial specialization in order to be able to reopen it later elsewhere in the rulebase. For the sake of well-formed documentation, it is required that any partial specialization that introduces any user-visible features (properties or user-methods) must be given an own name. Apart from that, the specialization name can't be used for anything; in particular, it can't be used as a type designator in property declarations, constructors, typeof expressions, etc. The specialization names are localized by the object type, so that it is possible to reuse the same name for similar specializations of distinct object types.

declare object_specialization spec_name = object_type {
precondition : SOURCE, … { … }

}

Introduces a restricted specialization for the given “big” object type. The rules, properties, and methods defined herein are only defined for objects fulfilling one or more preconditions. The preconditions are written in exactly the same way as for production rules. Note that a partial specialization can be restricted by preconditions as well.

object_specialization object_name::spec_name {

}

Reopens the definition scope of a named partial/restricted specialization of the given “big” object type.

function [label : ] name ( signature ) {

}

Defines a polymorphic (overloaded) function.

function [label : ] name ( signature ) : c++( options );

Defines a wrapper for a C++ function. function without user-visible comments can be defined as well.

function [label : ] name ( signature ) : c++( options ) {

}

Defines a hybrid function implemented partially in perl, partially in C++.

In all definition flavors you can change the leading keyword from function to user_function. Then the comment block preceding the definition will be converted into a help topic accessible via the interactive help function, F1 help, and the HTML documentation pages; besides this, the function name and keyword options (if any) will appear in the suggestion lists produced by TAB completion.

options %table_name = (
%list_to_inherit_from,
# Comment …
key => default value,

);

Defines a named list of keywords to be used in signatures of polymorphic functions and methods. The declaration syntax is similar to that of the custom hash tables.

Options lists can be easily built up hierarchically, extending each other much like a derived class extends a base class. The extension relation is expressed by mere inclusion. The names of lists to be extended upon are listed in the beginning of the definition body.

These are special tiny functions used for sanity checking and type manipulations when building parameterized types. A typecheck function is written much like a polymorphic function, thus it can be overloaded for different types as well, but as arguments the prototype descriptors are passed instead of real data entities. When called in a void context, a type checking function does nothing if the passed types match the imposed requirements, otherwise it dies with a (hopefully informative) message. Called in a scalar context, it should return a boolean value signaling whether it's content with the type passed to it.

The definition of a type checking function is identical to that of a polymorphic function:

function name ( signature ) {
...
}

Usually type checking functions are only referred in the check clauses (parts enclosed in square brackets) and default type parameter expressions in definitions of parametrized property and object types, like in this example:

declare object Cone<Scalar=Rational> [ is_ordered_field(Scalar) ];

This type checking function is defined in application common, where it accepts Rational, Float, and QuadraticExtension as valid types, but rejects anything else.

Besides definitions of parametrized types, type checking can also participate in overload resolution of polymorphic functions.

file_suffix suffix
Defines the default name suffix for datafiles containing objects defined in the current application.

A property type may have several constructors with arbitrary combinations of arguments. All constructor instances are defined as usual overloaded methods:

method construct( signature )
PropertyType, … ⇒ value

The only special case is the parsing constructor taking a single String argument, it should be defined as type_method parse described in the next paragraph.

The leading argument passed to the constructor is the property descriptor object. The return value should be a new value of this type. If the arguments are invalid, for whatever reason, an exception must be raised: it is not allowed to silently return an undef.

For property types with C++ binding, you can declare constructor functions with a c++ attribute, mapping them to corresponding C++ constructors. Please note that a default constructor (without arguments) and a universal single-argument constructor are automatically provided, unless inhibited in the property type declaration header.

Please note that, unlike in C++, all constructor instances defined in super classes are inherited and participate in overload resolution, as long as they are not shadowed by constructors with identical signatures defined in the derived class.

There are several basic operations applicable to any property type, like construction, conversion to a printable string, or comparison. For all of them a sensible default implementation is in place, doing “the right job” for a standard perl scalar value. Besides this, for types with C++ binding, all possible functions are automatically mapped to the corresponding C++ methods and operators. If you need a different behavior, you can put the implementations into the property type definition scope.

Derived property types inherit the implementations unless they overwrite them with their own ones. A concrete instantiation of a parametrized type inherit everything from its generic type. By re-opening the definition scope for a concrete instantiation, you can provide implementations tailored just for a specific case.

All functions described below are introduced with a special keyword type_method instead of usual function or method. The main difference to normal functions is that they get an additional leading argument, namely a prototype descriptor of the owning property type. This is a perl object of class Polymake::Core::PropertyType for simple types or Polymake::Core::PropertyParamedType for parametrized types. It carries the entire description of the property type, including the pointers to all specific and inherited methods, the super type, and type parameters. Please refer to PropertyType.pm for details how to retrieve this information; also look into rulefiles of application common for numerous examples.

Please note that methods inherited from a super class normally get the prototype descriptor of that super-type and not of the real (derived) type. If it does not fit the implementation needs, you can mark the corresponding methods by executing Struct::pass_original_object(&method_name); once during the rulefile loading phase.

equal
PropertyType, value1, value2 ⇒ boolean

Compares two values for equality.

isa
PropertyType, value ⇒ boolean

Tells whether the given value is of this property type or one of the types derived thereof

parse
PropertyType, “string” ⇒ value

Converts a printable representation of a value to a corresponding object of this property type. The string is assumed to contain just the value to be converted and nothing else, aparet from possible trailing blanks. If the string value is not valid, an exception must be raised.

canonical
PropertyType, value ⇒

Analyzes the given value and brings it to canonical form (in place). This function is applied to new values created by parse or construct. Values loaded from trustworthy XML data files are not canonicalized, as they are supposed to be already in canonical form.

toString
PropertyType, value ⇒ “string”

Produces a printable representation of the given value. Ideally this function should be the inverse of parse, that is, equal($x, parse(toString($x))) should always evaluate to TRUE.

toXML
PropertyType, value, XML::Writer, attributes ⇒

Writes the XML representation of the given value, compliant to the polymake RNG scheme (can be found in the top-level xml directory). The XML representation must contain all relevant data, allowing for unambiguous reconstruction of the value upon loading it from the file. Optional trailing arguments are key-value pairs encoding XML attributes for the enclosing tag, like “ext”. They should be passed as is to the appropriate XML::Writer method.

Note that there is no special XML parsing method for property types. Instead, the XML reader calls the parse or construct method, depending on the representation in the XML file. parse is called when the value consists of a single string stored in the value attribute of the <property> or <data> tag. construct is called with an anonymous array of elements enclosed in these tags, where the elements may be simple scalars or nested arrays. The dimension of sparse containers stored in additional attributes cols and dim is passed as a hidden attachment to the corresponding element array; use get_array_flags($array) to retrieve it.

coherent_type
PropertyType, value ⇒ OtherPropertyType

Analyze a value of a different type (which does not even satisfy the isa test) and try to find a type derived from this one, which would be best suitable for representation of a value. This method is primarily used when a non-conforming value is assigned to a big object property. For example, for a big object Polytope<Rational>, its property type Vector<Rational>, and assigned value SparseVector<Float>, the resulting coherent type will be SparseVector<Rational>, which on one hand conforms to the coordinate type of the owning big object, but on the other hand preserves the sparseness of data.

If no suitable type can be found, this method should return undef, which leads then to an attempt to convert the input data directly to the target property type.

init
PropertyType ⇒

For parametrized types, this method is called once for each concrete instantiation, that is, each combination of type parameters at its first occurrence. Its purpose is to change the associated methods or other attributes depending on the concrete parameters.

A property type can naturally define its own static functions and methods. Especially the types with C++ binding provide a lot of wrappers for most popular operations defined for their C++ counterparts. All these functions must be defined in the property definition scope block using pretty much the same syntax as for functions in the application scope. Be sure to introduce them as user_function or user_method and provide decent comments if they should appear in the documentation and in TAB completion. Unlike special methods described in the paragraph above, all other methods are treated in a traditional sense, in that the first argument they get is a reference to the object. Should you occasionally need to access something in the prototype descriptor, you can get it with $obj->type.

Constructors for big objects are defined much like constructors for property types:

method construct( signature )
ObjectType, … ⇒ new Object

Only special cases like conversion from unrelated object types must be handled in user-defined constructors. Every big object type automatically inherits all standard constructors.

property NAME : Type [ : attribute … ] ;

introduces a property of the current object type. The name must be unique for this object, and also not coincide with properties or methods inherited from ancestor object types. The reason is that for each property a method with exactly this name is automatically defined, allowing for quick retrieving and setting its value. Due to somewhat antiquated tradition, property names defined in polymake core applications are written in capital letters, but this is actually not a hard requirement - any valid identifier would do.

Type must be a valid type expression referring to property types or object types defined earlier in this application, or in an application used or imported into this one. If the enclosing object type scope is parametrized, you can use the names of the parameter placeholders in the type expression, thus yielding a context-dependent type. For example, in the scope of object Polytope<Scalar> you may define a property VERTICES : Matrix<Scalar>;. Then, the actual type of this property will depend on the type of the owning object, for Polytope<Rational> it will be Matrix<Rational> and so on.

Values assigned to the properties are always converted to the specified type, unless they are of a derived type. For example, you can assign a SparseMatrix value to the VERTICES property mentioned above and it will be kept in sparse form; but if you attempt to assign a SparseMatrix<Integer>, it will be first converted to SparseMatrix<Rational>. An attempt to assign a value which can't be converted to the declared property type raises an exception.

When the property is a subobject of the same type as the enclosing object, you can use an abbreviation self instead of repeating the full type name. It's especially handy for twin properties.

attribute may be one of the following keywords:

mutable

allows to add, change, or remove this property at any time. Note that the value of any property is made write-protected once assigned to it; the allowance to change a mutable property of a composite type means exchanging its value completely, not element by element.

multiple

applicable only to subobjects: this signals that more than one subobject instance may be attached to this property. There are special methods for adding and retrieving multiple subobjects.

Without any of these attributes, a property is considered immutable and single-valued; please refer to Object methods for detailed explanations.

twin

applicable only to subobjects of the same type as the owning object or one of its base types; this property describes a mutual relation between the parent object and the subobject, like duality. The subobject is automatically equipped with the same property pointing back to the parent object.

non_storable

the property should not be stored in data files, either because it does not have any easy-to-parse printable representation, or because the representation is too voluminous, or for whatever reason. Unlike properties created with temporary flag, the non-storable properties does not disappear immediately after completion of an execution cycle, but rather survive until the end of the session.

construct(PROPERTY_PATH)

specifies a prerequisite property of the owning object (or one of its subobjects) needed for proper construction of the property value. The value of the prerequisite property is passed to the construct method of the property type together with the value being assigned to this property. For example, values of type NodeMap or EdgeMap need a Graph object to be attached to, therefore such properties of “big” objects graph::Graph or graph::FaceLattice usually have an attribute construct(ADJACENCY).

property NAME = override INHERITED_NAME;

introduces an alias for an inherited property. The alias name will appear everywhere you ask for properties, e.g. in XML data files and in lists produced by list_properties method; but the value of the property stays accessible over both alias name and inherited original name. The inherited production rules for this property are in effect as well; if you want to disable them, see the override option for rules.

For obvious reasons, a property can't be overridden in the same object type where it has been defined, nor within a partial or conditional specialization of an object type.

property NAME = override [ INHERITED_NAME ] : Type;

overrides the subobject property inherited from a base object type, extending its type to a specified one which should be derived from the inherited property type. For a twin property, the type will usually be self, referring to the enclosing object type. If the INHERITED_NAME is omitted, then only the type is overridden but the original name NAME is preserved.

property PROPERTY_PATH {
...
}

opens the scope block for the subobject for local augmenting its type by further subproperties and methods. Augmenting can occur any times and in any context where the definition of the original property is visible, that is, in any specialization or descendant of the object type holding the property definition. Twin properties, however, cannot be augmented because this would distort the symmetry of the mutual relation between the parent and the subobject.

The scope block can be combined with the property definition on a single line, like this:

property NAME : Type {
...
}
property NAME = override INHERITED_NAME {
...
}

Permutations are subobject properties with special semantics. Syntactically, there is no difference between definitions of properties and permutations.

permutation NAME : BaseType;

Introduces a permutation of the enclosing object type. BaseType must be a “big” object type defined elsewhere before. Usually, the type PermBase defined in application common suffices.

permutation NAME : BaseType { ... }

Introduces a permutation and opens a property definition scope for augmentation.

permutation NAME { ... }

Reopens the definition scope for augmentation.

The syntax of rules is so manifold that it deserves a description on a separate page.

user_method [labels :] name ( signature ) {

}

defines a method to be called from scripts and interactive shell. It should be accompanied by a properly formatted comment block. The syntax rules for the signature and overload resolution rules are the same as for user_functions, apart from the fact that the leading argument, a reference to an object of the current type, may not appear in the signature. As the name method says, it is inherited by all derived object types. Methods defined in derived classes completely override inherited methods of the same name only if they possess compatible signatures (that is, accept the same or broader sets of arguments than the inherited instance).

labels can be used in preference settings controlling the favorites among several overloaded instances with equivalent signatures. The first limb of each label must be defined earlier in this application, or in a used or imported one.

If the routine is declared as method instead of user_method, it does not appear in the interactive help, TAB completion, or auto-generated documentation, but in all other aspects both flavors behave identically.

user_method [labels :] name ( signature ) : property, … {

}

defines a rule-like method. This is a hybrid creature between a polymorphic method and a rule. Like a normal production rule, it specifies a list of required input properties and, optionally, preconditions, which must follow the main code block.

When such a method is called, suitable candidates are first chosen depending on the argument list. Then they are handled much like production rules: the input property lists and preconditions are evaluated and the cheapest feasible candidate is executed. However, unlike for real production rules, if the chosen method instance throws an exception, no alternative candidates are tried any more.

Rule-like methods can also be defined without signatures. In this case the choice of a suitable instance is performed solely by analyzing input properties and preconditions.

file_suffix suffix

Overrides the application-wide default name suffix for datafiles containing objects of this type.

For atomic properties, the following special functions or methods may be defined. If declared as method, the corresponding routine will get the owning “big” object as an additional leading argument.

canonical
value ⇒

like the function/method of the same name for property types, this can be used to transform the value assigned to a property to some canonical representation. The value should be changed in place, the return value is ignored.

equal
value1, value2 ⇒

compare two values, return TRUE if they are deemed equivalent. This function is primarily used in the unit testing framework, where it compares the outcome of an operation with an expected correct value. Some properties may not have a unique canonical representation at all, or the canonicalization may be too expensive for the daily use.

For subobject properties, you may define here pretty much the same elements as in the object type definition scope, that is, properties, rules, and (user) methods. The probability to see here new production rules is, however, quite low, because in most cases you will have to access some properties from the parent object as well (otherwise you could simply introduce the new properties in the top-level object type scope). But for sanity-checking initial rules this is a good place anyway.

Note that the mere fact of opening a definition scope block for a subobject automatically introduces an anonymous, so called locally extended subobject type. It is derived from the normal object type stated in the property definition. Reopening the definition block in derived parent object types gives raise to a corresponding parallel hierarchy of locally extended subobject types. When you ask for the type name of a subobject, you will see something like:

print $p->GRAPH->type->full_name;
Graph<Undirected> as Polytope<Rational>::GRAPH

As mentioned in the preamble, a rulefile may contain arbitrary perl code, which also includes the possibility to define new packages (=classes). While feature-rich classes should be better defined in separate module files residing in apps/APPNAME/perllib/, small auxiliary classes can be more conveniently defined just in the rulefile using it. Moreover, some syntactical elements can only be recognized in rulefiles but not in separate module files. These are custom variables, auto-configuration blocks, polymorphic functions, and option lists. Therefore, in some situations you might even decide to divide the class definition in two parts, one in the rulefile, other one in a module file require'd from it.

There is also one sort of definition elements which is specific for packages not belonging to the “big” object hierarchy: so called global methods. They are defined exactly the same way as polymorphic user_methods, just using the keyword global_method. The main difference lies in the overload resolution: it usually takes place before the object of the class is created. The global methods from different classes having an identical signature are bundled together by preference lists and the current favorite is used to determine the class to create the object which is then passed to the chosen method. This is how all the visualization methods work. If, for example, you are going to draw a graph, all known global methods having signatures compatible to draw(Visual::Graph) are ordered according to the active preference settings, then the definition package of the method in the “pole position” determines which visualization back-end object is to be created, and finally the draw method is called with this object and the graph.

  • user_guide/extend/rulefiles.txt
  • Last modified: 2021/01/12 15:38
  • by 127.0.0.1