ClassAds Programming Tutorial (C++)
Edited by Alain Roy (HTCondor Team), January 2005Originally by Rajesh Raman (HTCondor Team), June 2000
This document is an informal tutorial. For an in-depth description of the ClassAd language, see the reference manual. If there is a discrepancy between this tutorial and the reference manual, then the reference manual is correct. If there is a discrepancy between the code and the reference manual, the reference manual is correct and there is a bug in the code.
For detailed documentation on the C++ ClassAd API, see the API documentation.
Table of Contents
1. Informal Language Description
A classad is a structure that contains a set of named expressions. Think of it as an attribute-value list that does not have a scheme constraining the list of attributes. The expressions are very similar to their counterparts in C, but are more general:- The classad language has a richer set of types: in addition to traditional scalar C types (integer, real, boolean), the classad language provides the following primitive types (undefined, error, string, absolute time, relative time).
- The semantics of expressions is defined so that evaluation is well-defined in all circumstances, such as multiplying an integer and a string, using an attribute which does not exist, etc. (The results in the above two cases are the error and undefined values respectively.)
- Expressions may contain classads and lists of expressions.
- A rich set of built-in functions is included for many tasks such as getting the current time of day, pattern matching, type conversions, etc.
1.1. Syntax and Evaluation Semantics
1.1.1. Literals
Literals are atomic expressions which represent single scalar values.- Integers
- 7, 0x1a, 0666, -33
- Reals
- 3.14, -2.718, 1e+5, 1M, 2.3G.
(Note: The suffixes B, K, M, G and T indicate bytes, kilobytes, megabytes, gigabytes and terabytes respectively. The numeric prefix is converted to a real and multiplied by the appropriate power of 2.) - Strings
- "aaa", "BBB", "a\t\"" (escape characters are supported; see K&R p.38)
- Booleans
- false, true
- Absolute Time
- absTime("2005-01-13T08:17:00-06:00")
- Relative Time
- relTime("-5:00")
- Undefined
- undefined (single value)
- Error
- error (single value)
1.1.2 ClassAds
ClassAds have the following syntax:[ id0 = expr0 ; id1 = expr1 ; ... ; idn = exprn ]where each id is a valid identifier and each expr is an expression. Identifiers begin with an alphabetic character or an underscore, and are followed with any number of alphabetic, numeric, or underscore characters. Identifiers are case-insensitive, but case-preserving. That is, if you refer to "Rank" or "rANK", it refers to the same attribute, but it will print with the same case that it was created with.
Since classads are themselves expressions, classads can be hierarchically nested, as illustrated in the following example.
[ a = 7 ; b = "foo" ; c = [ a = -1 ; b = "bar" ] ]The outermost classad is referred to the root scope. Every other classad is contained in its parent scope, which is the unique classad that immediately contains the classad in question.
Classads are self-evaluating structures: a classad "expression" evaluates to a classad "value."
1.1.3. Attribute References
Attribute references are analogous to variable references in C++ expressions, but are more sophisticated. Attribute references have three forms:- The first variant attr evaluates to the value of the bound expression in the attribute named attr. If no such attribute exists in the current scope, succeeding parent scopes are searched. If no attribute is found, the result of the evaluation is the undefined value.
- The second variant .attr is similar to the first variant, but looks up only the root scope. If the named attribute does not exist in the root scope, the result is the undefined value.
- The third variant expr.attr first evaluates the expression expr, which must evaluate to a classad value. The search for the expression begins in the scope of the evaluated classad value, and then proceeds as in the first variant. If the expression itself evaluated to undefined, the result is undefined. Otherwise, if the expression neither evaluated to a classad or undefined, the result is error.
- The attribute self evaluates to the closest classad that encloses the attribute reference.
- The reference parent evaluates to the parent scope of the classad containing the reference. (This evaluates to undefined for the root scope.)
- The reference root evaluates to the root scope of the evaluation.
1.1.4. Operators
The classad language provides almost all the operators of C: the only operators not supported are assignment and pointer operators. In general, operators are strict with respect to undefined and error, so if any operand is undefined (error), the result is also undefined (error).The following important points must be noted:
- The operators is and isnt are like == and !=, but they are not strict. I.e., they test the "is identical to" predicate, and always return either true or false.
- String comparison is case-sensitive only when comparing with the is and isnt operators, and case-insensitive in all other contexts.
- The ternary conditional operator ?: is strict only in the selector expression.
- The logical operators || (OR) and
&& (AND) are
non-strict. Their truth tables are supplied below.
& & (AND) F T U E F F F F F T F T U E U F U U E E E E E E || (OR) F T U E F F T U E T T T T T U U T U E E E E E E
1.1.5. Lists
The classad language allows lists of expressions to be constructed. The syntax for list specification is:{ e1, e2 ... , en }where each 'e' is an expression. Components of lists may be accessed via the subscript operator as in:
{ 10, "foo", -3.14 }[0]which evaluates to the integer value 10.
1.1.6. Function Calls
The classad language provides a rich set of built-in functions. User-defined functions may not be defined. However, additional functions may easily be added if access to the library source code is available.The syntax of a function call is
name( arg0, arg1, ... , argn )As with operators, most functions are strict with respect to undefined and error on all arguments. However, some functions are non-strict. The name of the function is case-insensitive. The supported set of functions are:
Type predicates (Non-Strict) | |
isUndefined(V) | True if and only if V is the undefined value. |
isError(V) | True if and only if V is the error value. |
isString(V) | True if and only if V is a string value. |
isList(V) | True if and only if V is a list value. |
isClassad(V) | True if and only if V is a classad value. |
isBoolean(V) | True if and only if V is a boolean value. |
isAbsTime(V) | True if and only if V is an absolute time value. |
isRelTime(V) | True if and only if V is a relative time value. |
List Membership | |
member(V,L) | True if and only if scalar value V is a member of the list L |
identicalMember(V,L) | Like Member, but uses is for comparison instead of ==. Not strict on first argument. |
Time | |
time() | Returns the current Coordinated Universal Time, in seconds since midnight January 1, 1970. |
currentTime() | Get current time as an absolute time |
timeZoneOffset() | Get time zone offset as a relative time |
dayTime() | Get current time as relative time since midnight. |
splitTime(T) | Creates a ClassAd with each component of the time (absolute or relative) as an element of the ClassAd |
formatTime(T, S) | Formats an absolute time according the the strftime-style format. See the reference manual for details. |
String Functions | |
strcat(V1, ... , Vn) | Concatenates string representations of values V1 through Vn |
toUpper(S) | Converts the string S to uppercase |
toLower(S) | Converts the string S to lowercase |
substr(S,offset [,len]) | Returns substring of S. Negative offsets and lengths count from the end of the string. |
regexp(P,S) | Checks if S matches pattern P (both args must be strings). For details on the options other than "i" for case-insensitive matching, see the reference manual. |
Type Conversion Functions | |
int(V) | Converts V to an integer. Time values are converted to number of seconds, strings are parsed, bools are mapped to 0 or 1. Other values result in error. |
real(V) | Similar to int(V), but to a real value. |
string(V) | Converts V to its string representation |
bool(V) | Converts V to a boolean value. Empty strings, and zero values converted to false; non-empty strings and non-zero values converted to true. |
absTime(V) | Converts V to an absolute time. Numeric values treated as seconds past UNIX epoch, strings parsed as necessary. See the reference manual for details. |
relTime(V) | Converts V to an relative time. Numeric values treated as number of seconds, strings parsed as necessary. See the reference manual for details. |
Mathematical Functions | |
floor(N) | Floor of numeric value N |
ceiling(N) | Ceiling of numeric value N |
round(N) | Rounded value of numeric value N |
random(N)> | If N is an integer, the result is an integer random number R in the range 0 <= R < N. If N is a real number, the result is a real random number in the same range.If N is anything else, the result is an error. |
1.2. Examples
Consider the following classad[ a = 17; b = "foo"; c = { "x", "y", 3*a }; d = [ a = 23; b = 15; c = [] d = .a; ] e = '00:15:00'; ]We now provide a few example expressions, and their resulting values when evaluated in the context of the above classad.
Expression | Result |
a | 17 |
strcat(b, "bar", a) | "foobar17" |
x | undefined |
x+10 | undefined |
x || true | true |
d.a | 23 |
d.b | 15 |
d.self | [ a = 23 ; b = 15 ; c = [] ; d = .a ] |
d.c | [] |
d.c.parent | [ a = 23 ; b = 15 ; c = [] ; d = .a ] |
d.c.a | 23 |
d.parent.c | { "x", "y", 3*a } |
d.parent.c[2] | 51 |
d.d | 17 |
e*4 | '01:00:00' |
2. The C++ ClassAd API
The primary activities associated with ``programming'' classads are constructing, evaluating and displaying expressions.2.1. Preliminary
- Your code must be compiled with g++.
- The classad package's include directory must be made accessible via the -I flag to the compiler.
- Code which uses the classad code must include the "classad_distribution.h" header file.
- The code must be linked in with the libclassad.a library.
- The convention is that methods return true on success and false on failure.
- On error, the integer variable HTCondorErrno contains an error code, and the STL string variable HTCondorErrMsg contains the cause of error. (For your information, the library uses STL extensively.)
- The ClassAd library is not thread-safe.
2.2 Creating ClassAds
ClassAds are really a special case of expressions, so the next section will explain how to create ClassAds. However, since ClassAds are often the primary concern of programmers that use the ClassAd library, we will explain them separately here.
2.2.1 Parsing ClassAds
The most common method to create ClassAds is with the ClassAd parser. The parser can take text input (a string, a file, or a stream) and convert it to a ClassAd. The parser is encapsulated in the ClassAdParser class, which can be found in source.h. Normally you won't need to include source.h directly, since it is included by classad_distribution.h.
The ClassAdParser has two prototypical ways of creating a ClassAd, though there are several variants with each method. For this tutorial, we'll create ClassAds from strings: the variants merely let you construct the ClassAds from different sources.
Method One: Return a string
string classad_string = "[a = 1; b = \"Cardini\"]"; ClassAd *classad; ClassAdParser parser; classad = parser.ParseClassAd(classad_string, true);In this example, true is passed to ParseClassAd to indicate that no extra text follows the ClassAd. If it does, it will return an error. If you legitimately may have extra text after a ClassAd, pass false instead. If the parsing fails, NULL will be returned. You own the ClassAd, so it is up to you to delete it when you are finished with it.
Method Two: Fill in a ClassAd
string classad_string = "[a = 1; b = \"Cardini\"]"; ClassAd classad; ClassAdParser parser; bool success. success = parser.ParseClassAd(classad_string, classad, true);In this example, the classad is filled in directly. If the ClassAd already contains attributes, they are cleared. You could also allocated the ClassAd with new and pass it as *classad, if you prefer.
2.2.1 Creating ClassAds manually
If you prefer, you can create and empty ClassAd, then fill it up with expressions that you insert on the fly.. Here is an example:
ClassAd *classad; classad = new ClassAd; // Insert a = 1 classad->InsertAttr("a", 1); // Insert b = "Cardini" classad->InsertAttr("b", "Cardini");
2.3 Creating expressions
All ClassAd expressions are sub-classed from the ExprTree abstract class, which defines a standard interface for all expressions. The specific kinds of expressions (e.g., Literals, FunctionCalls, etc.) are derived from ExprTree, and have protected constructors. With the exception of ClassAd, all other expressions can only be explicitly constructed via their static factory methods.However, users rarely directly invoke factory methods since expressions may usually be constructed through more convenient means. Expressions can be created in three ways.
- As previously mentioned, they can be explicitly created via their
factory methods. This is usually only convenient for creating
literals, especially time values. For example:
ExprTree *now = Literal::MakeAbsTime(); // current time value ExprTree *past = Literal::MakeAbsTime("1999-01-13T09:00:00-0600"); ExprTree *tenMinutes = Literal::MakeRelTime(600); // in seconds
The Literal::MakeRelTime() factory also has a variant which accepts two time_t values and constructs the interval between their values. If -1 is passed for either argument, the current time is used in that place. - The same ClassAdParser that can create ClassAds from strings
can also create any other kind of expression from a string. There
are fewer variations of ParseExpression than ParseClassAd. An example:
ClassAdParser parser; ExprTree *tree; ClassAd *ad; if( !( tree = parser.ParseExpression( "10 * 17 / foo.bar" ) ) || !( ad = parser.ParseClassAd( "[ a=3 ; b=5 ]" ) ) ) { // error ... } else { ad->Insert("c", tree); }
- Most classad expressions are simple literals which are used as
attributes in classads. These expressions may be implicitly created by
simply inserting values into a classad.
ClassAd ad; if( !ad.InsertAttr( "Memory", 128, Value::M_FACTOR )|| // Memory=128M !ad.InsertAttr( "Name", "foo.cs.wisc.edu" ) || // Name="foo.cs.wisc.edu" !ad.InsertAttr( "IsBlue", false ) ) { // IsBlue=false // error ... } }
2.4. Inserting Expressions into ClassAds
Expressions are inserted into a classad via the Insert method.ExprTree *now; ClassAd ad; if( !( now = Literal::MakeAbsTime() ) || !(ad.Insert( "CurrentTime", now ) ) ) { // error ... }If a similarly named expression (case-insensitive) already exists in the ad, the old expression is deleted, and the new expression takes its place. Once inserted, the storage associated with the expression is adopted by the classad---do not deallocate the storage of the expression yourself. If you will need direct access to the expression after you insert it, use the Copy() method first to make a private copy as follows:
ExprTree *original_expression, *private_copy; ... // Assume original_expression has been created private_copy = original_expression->Copy( ); // Now we insert the original_copy ad.Insert( "foo", original_copy); // At this point, we shouldn't delete the original_copy // because it is owned by the ClassAd, but we can manipulate // the private_copy as much as we like.
2.5 Removing expressions from ClassAds
Expressions may be removed from a Classad in two ways:- The Delete method removes an expression from the classad, and deallocates the storage associated with the expression.
- The Remove method removes an expression from the classad, and returns the expression to the user without deallocating its storage.
ClassAd ad; if( !ad.InsertAttr( "foo", 10 ) || !ad.InsertAttr( "bar", 20 ) ) { // error ... } if( !ad.Delete( "foo" ) ) { // attr not found in ad } ExprTree *tree = ad.Remove( "bar" ); // "tree" is now yours (NULL if ad had no "bar") ... delete tree;
2.6 Looking Up Expressions in ClassAds
Once expressions have been inserted into classads they may be looked up with the Lookup() method. The method returns the expression (or NULL if no such attribute exists in the classad), but the expression still belongs to the classad. Thus, the expression must not be deallocated, or its parent scope reset.
ClassAd ad; ExprTree *tree; ad.InsertAttr( "foo", 10 ); tree = ad.Lookup( "foo" );If the classad being used is nested in another classad, the LookupInScope() method may be used to test if the expression exists in the classad, or any of its parent scopes. Often you will prefer to evaluate expressions instead of looking them up. See below for more details.
Expressions in the classad may also be enumerated by means of an iterator as illustrated below.
ClassAd ad; ... // assume the ad is filled with attributes ClassAdUnParser unp; string buffer; ClassAdIterator itr; string attrName; const ExprTree *attrExpr; itr.Initialize( ad ); while( !itr.IsAfterLast( ) ) { itr.CurrentAttribute( attrName, attrExpr ); buffer += attrName + " = "; unp.Unparse( buffer, attrExpr ); buffer += " "; itr.NextAttribute( attrName, attrExpr ); }
You can also use STL-style iterators to iterate through a ClassAd.
ClassAd ad; ... // assume the ad is filled with attributes ClassAd::iterator iter; iter = ad->begin(); while (iter != ad->end()) { string name = iter->first; ExprTree *tree = iter->second; ... iter++; }
2.7 Evaluating Expressions
Expressions can only be evaluated in the context of a classad. The result of an evaluation is a Value object, which has several methods defined to obtain the type and result of the evaluation.Expressions already in the classad may be evaluated via the EvaluateAttr() method.
ClassAd ad; Value val; int fooVal; if( !ad.InsertAttr( "foo" ,10 ) || !ad.EvaluateAttr( "foo", val ) ) { // error ... } if( !val.IsIntegerValue( fooVal ) ) { // enh??? }In the above example, the result of the evaluation was already expected to be an integer. Thus, the following shortcut may be used.
if( !ad.EvaluateAttrInt( "foo", fooVal ) ) { // eval error, or not an int ... }Similar convenience methods exist for all the other types.
Expressions which have not been inserted into the classad may be evaluated in the context of the classad via the EvaluateExpr() method. However, the expression must be notified of the scope that it is being evaluated in, as illustrated in the following example. (Return values are not checked to keep the example brief.)
ClassAd ad; ClassAdParser parser; ExprTree *tree; Value val; ad.InsertAttr( "foo", 10 ); ad.InsertAttr( "bar", 20 ); tree = parser.ParseExpression( "foo - bar" ); tree->SetParentScope( &ad ); // to be evaluated in the context of ad ad.EvaluateExpr( tree, val ); // now evaluate the expression
2.8 Testing Matches
Testing for a match between two classads is performed by evaluating the "Requirements" expressions of the two classads in an evaluation environment that maps the "other" attribute to the match candidate. The classad language is powerful enough to represent this evaluation environment as a classad. In the API, the MatchClassAd object provides this functionality.The MatchClassAd object extends a classad by providing two additional functionalities:
- It defines two scopes (left and right), which may be filled with the ReplaceLeftAd() and ReplaceRightAd() calls. Since these calls will delete the classad previously occupying that position, you may wish to first RemoveLeftAd() and/or RemoveRightAd().
- The MatchClassAd object also defines the following convenience attributes: symmetricMatch, leftMatchesRight, rightMatchesLeft, leftRankValue and rightRankValue.
MatchClassAd mad; ClassAd *mach, *job; ... // assume mach and job are two valid classads // the following is not necessary if no ads were inserted mad.RemoveLeftAd( ); mad.RemoveRightAd( ); // insert the two ads into the context mad.ReplaceLeftAd( mach ); mad.ReplaceRightAd( job ); // test bool match if( !mad.EvaluateAttrBool( "symmetricMatch" match ) || !match ) { // they don't like each other ... } else { // they like each other ... }For your information, the MatchClassAd sets itself up as follows:
[ symmetricMatch = leftMatchesRight && rightMatchesLeft; leftMatchesRight = adcr.ad.requirements; rightMatchesLeft = adcl.ad.requirements; leftRankValue = adcl.ad.rank; rightRankValue = adcr.ad.rank; adcl = [ other = .adcr.ad; my = ad; // for HTCondor backwards compatibility target = other; // for HTCondor backwards compatibility ad = [ // the ``left'' match candidate goes here ] ]; adcr = [ other = .adcl.ad; my = ad; // for HTCondor backwards compatibility target = other; // for HTCondor backwards compatibility ad = [ // the ``right'' match candidate goes here ] ]; ]
2.9 Displaying and Transporting Expressions and Values
ClassAds, expressions and values are displayed by first "unparsing" them into STL strings and then printing them to the screen. The same mechanism is used to transport classads over the network: they are first unparsed into a string which is transported over the network, and then parsed back by the receiver.
Unparsing is performed by the ClassAdUnParser object. The interface is straightforward.
ClassAdUnParser unp; ExprTree *tree; ClassAd ad; Value val; ... // assume tree, ad, val need to be unparsed string buffer1, buffer2, buffer3; unp.Unparse( buffer1, tree ); unp.Unparse( buffer2, &ad ); unp.Unparse( buffer3, val );A more sophisticated unparser called PrettyPrint is also available. The pretty printer can indent classads and lists, and display parenthesized expressions with the minimal number of required parentheses.
PrettyPrint pp; ExprTree *tree; ClassAd ad; Value val; ... // assume tree, ad, val need to be unparsed string buffer1, buffer2, buffer3; pp.Unparse( buffer1, tree ); pp.Unparse( buffer2, &ad ); pp.Unparse( buffer3, val );
XML Classads
ClassAds can be displayed and parsed in XML as well as the native format used above. For example, a ClassAd in XML format might look like this:
<?xml version="1.0"?> <c> <a n=\"A\"><s>Alain Aslag Roy</s></a> <a n=\"B\"><i>3</i></a> </c>
They are parsed and printed similarly to the native format described above. To parse an XML ClassAd you use the ClassAdXMLParser class:
ClassAdXMLParser parser; ClassAd *classad; string xml = ... // The ClassAd classad = parser.ParseClassAd(xml);
To print out a ClassAd in XML format you use the ClassAdXMLUnParser class:
ClassAd *classad; ClassAdXMLUnParser unparser; string printed_classad; // Assume classad is already created unparser.SetCompactSpacing(false); unparser.Unparse(printed_classad, classad);