/* * Copyright (c) 2006 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #ifndef _QEQUERY_H_ #define _QEQUERY_H_ #include typedef struct __QEQuery * QEQueryRef; /******************************************************************************* * Flexible Query Engine * * This engine builds complex logical expression evaluators with predicates you * define via callbacks. The engine directly supports AND, OR, NOT operators, * as well as grouping. The engine is currently designed to build queries by * serially adding elements, and figures out the grouping by itself. Operator * precedence is explicit groups, then NOT, then AND, then OR. * * Query elements are dictionaries with three basic attributes: * * - Predicate: the 'verb' to evaluate * - Arguments: data for use by the predicate * - Negated: whether the evaluated result must be negated (this is handled * by the engine, and your callbacks needn't worry about it) * * Your callbacks can stuff arbitrary key/value pairs into the dictionary in * lieu of using the arguments array. Note that the engine reserves keys of the * form '__QE...__', so don't use those. * ******************************************************************************** * Using a Query * * You start by calling QEQueryCreate(), which takes a user data pointer for * context info. Then you set parse and evaluation callbacks for the predicates * you want to handle, add elements to your query using one of the two methods * just below, and then use QEQueryEvaluate() on the objects you want to check. * * QEQueryEvaluate() returns true if the query matches the object you provide, * and false if it doesn't or if an evaluation error occurs. If it does return * false, you should call QEQueryLastError() and possibly cancel further queries * using that query engine instance if an error did occur. * ********** * Building a Query from Strings * * If you set parse callbacks, you need only call QEQueryAppendElementFromArgs() * repeatedly until it returns false. After doing that you should check whether * there were any errors using QEQueryLastError(). * * By default, the parse engine automatically handles the tokens '-and', '-or', * '-not', '(', and ')', but does not give them any priority over your parse * callbacks. That is, if your callback eats up an '-and' as an argument for a * predicate, the query engine will just keep rolling along (and you might see a * syntax error later). * * Your parse calllback will have as arguments: * * - The element being built, with its predicate already set to the token * that triggered the callback. * - A pointer to a list of strings following that token, which you can * proceed to parse. * - An in/out 'num used' parameter giving how many tokens have been parsed * by the query engine as a whole; you should update this by the number of * tokens you consume in parsing, if successful--if parsing fails, do not * adjust it. This number is *not* to be used as an index into the list * of argument strings; that starts at zero. * - A pointer to the query user data. * - A pointer to the query's error code, which you should set to * kQEQueryErrorInvalidOrMissingArgument or kQEQueryErrorParseCallbackFailed * if something goes wrong. * * Your callback should update the element by changing its predicate if necessary * (so you can have a single keyword routed to different evaluation calbacks, * or multiple keywords routed into a single evaluation callback), and reading * as many arguments as needed from the list of strings and adding them to the * elements arguments array. You can also set arbitrary properties on the element * as noted above. It should update the num_used parameter as it successfully * handles arguments. Upon successfully parsing all potential arguments, it * should return true; otherwise, it should set the error code as appopriate * and return false. * * Functions that you may find useful in a parse callback are: * * - QEQueryElementGetPredicate() * - QEQueryElementSetPredicate() * - QEQueryElementAppendArgument() * - QEQueryElementSetArgumentsArray() * - QEQueryElementSetArguments() * * NOTE: If you get a reference to query element's predicate, and then set the * predicate, your original predicate will change! Always 're-fetch' the * predicate if you need to check it after setting it. (I think this is a CF * bug, but I haven't investigated it thoroughly). * ********** * Building a Query by Hand * * You can forgo using parse callbacks if you want to build query elements * directly, using: * * - QEQueryCreateElement() - creates an element with a user-define predicate * - QEQueryAppendElement() * - QEQueryAppendAndOperator() and QEQueryAppendOrOperator() * - QEQueryStartGroup() and QEQueryEndGroup() * * Just call them in sequence and the engine will produce the correct evaluation * structure. These functions return true on success, and false if they would * result in an incorrectly-structures query (for example, with two OR operators * in a row). You can call QEQueryLastError() to find out the specific problem. * ******************************************************************************** * Evaluating a Query * * Your evaluation callback will have as arguments: * * - The query element being evaluated. * - A pointer to the object being checked. * - A pointer to the query use data. * - A pointer to the query's error code, which you should set to * kQEQueryErrorEvaluationCallbackFailed if something goes wrong. * * Your callback should call QEQueryElementGetPredicate(), * QEQueryElementGetArguments(), and QEQueryElementGetArgumentAtIndex() * as necessary to apply its logic to the object and return a true or false * value. It can also get arbitrary keys from the dictionary, as noted above. * Your callback does not need to handle whether the query element is negated; * the query engine does that. * * If your callback suffers a failure, it should set the error and return false. * * Note that evaluation callbacks can perform operations on or with the object * as well as just checking them against a query predicate. For example, you * could define a '-print' predicate that just prints data from the object * and returns true. ******************************************************************************** * TO DO: * XXX: Add functions that take CF strings? * XXX: Allow for inspection/traversal of query, and insertion? * XXX: Make QEQueryPrint() output friendlier (always record raw input tokens?) * XXX: Add internal parse item counter and formatted error messages listing * XXX: problem item and its number? *******************************************************************************/ typedef enum { kQEQueryErrorNone = 0, kQEQueryErrorUnspecified, kQEQueryErrorNoMemory, kQEQueryErrorGroupNesting, // doesn't distinguish lib error from user.... kQEQueryErrorEmptyGroup, kQEQueryErrorSyntax, kQEQueryErrorNoParseCallback, kQEQueryErrorNoEvaluationCallback, kQEQueryErrorInvalidOrMissingArgument, // set by user parse callback kQEQueryErrorParseCallbackFailed, // set by user parse callback kQEQueryErrorEvaluationCallbackFailed, // set by user eval callback } QEQueryError; /* The default set of tokens understood by the engine. * You may need to handle these specially while processing * options. */ #define kQEQueryTokenAnd "-and" #define kQEQueryTokenOr "-or" #define kQEQueryTokenNot "-not" #define kQEQueryTokenGroupStart "(" #define kQEQueryTokenGroupEnd ")" typedef Boolean (*QEQueryParseCallback)( CFMutableDictionaryRef element, int argc, char * const argv[], uint32_t * num_used, void * user_data, QEQueryError * error); typedef Boolean (*QEQueryEvaluationCallback)( CFDictionaryRef element, void * object, void * user_data, QEQueryError * error); /******************************************************************************* * Create and set up a query. *******************************************************************************/ QEQueryRef QEQueryCreate(void * userData); void QEQueryFree(QEQueryRef query); /* Replace the builtin logical operators with your own. If you pass NULL * for any one, the builtin is used (and for -not, ! is automatically registered * as a synonym). If you just want to add synonyms for logical operators, or * remove the default alias ! for -not, use QEQuerySetSynonymForPredicate(). */ void QEQuerySetOperators(QEQueryRef query, CFStringRef andPredicate, CFStringRef orPredicate, CFStringRef notPredicate, CFStringRef groupStartPredicate, CFStringRef groupEndPredicate); /* Empty all custom parse information: predicates, synonyms, and callbacks. */ void QEQueryEmptyParseDictionaries(QEQueryRef query); /* You can remove a single callback by passing NULL. */ void QEQuerySetParseCallbackForPredicate( QEQueryRef query, CFStringRef predicate, QEQueryParseCallback parseCallback); void QEQuerySetEvaluationCallbackForPredicate( QEQueryRef query, CFStringRef predicate, QEQueryEvaluationCallback evaluationCallback); /* Causes 'synonym' to be automatically replaced with 'predicate' during * parsing and upon creation of an element dictionary with * QEQueryCreateElement(). If 'predicate' is NULL, the synonym is unregistered. */ void QEQuerySetSynonymForPredicate( QEQueryRef query, CFStringRef synonym, CFStringRef predicate); Boolean QEQueryIsComplete(QEQueryRef query); QEQueryError QEQueryLastError(QEQueryRef query); /******************************************************************************* * Evaluate a query. *******************************************************************************/ void QEQuerySetShortCircuits(QEQueryRef query, Boolean flag); Boolean QEQueryGetShortCircuits(QEQueryRef query); Boolean QEQueryEvaluate(QEQueryRef query, void * object); /******************************************************************************* * Build a query from command-line arguments. See below for hand-building. *******************************************************************************/ Boolean QEQueryAppendElementFromArgs( QEQueryRef query, int argc, char * const argv[], uint32_t * num_used); /******************************************************************************* * Functions for manually building a query. *******************************************************************************/ CFMutableDictionaryRef QEQueryCreateElement( QEQueryRef query, CFStringRef predicate, // will be replaced if a synonym for another CFArrayRef arguments, // may be NULL Boolean negated); /* Do not manually build a dictionary element; use QEQueryCreateElement(), * which sets internal values needed by the query engine. */ Boolean QEQueryAppendElement( QEQueryRef query, CFMutableDictionaryRef element); Boolean QEQueryAppendAndOperator(QEQueryRef query); Boolean QEQueryAppendOrOperator(QEQueryRef query); Boolean QEQueryStartGroup(QEQueryRef query, Boolean negated); Boolean QEQueryEndGroup(QEQueryRef query); /******************************************************************************* * Functions for use by parse and evaluation callbacks. *******************************************************************************/ CFStringRef QEQueryElementGetPredicate(CFDictionaryRef element); CFMutableArrayRef QEQueryElementGetArguments(CFDictionaryRef element); CFTypeRef QEQueryElementGetArgumentAtIndex( CFDictionaryRef element, CFIndex i); /******************************************************************************* * Functions for use by parse callbacks. *******************************************************************************/ void QEQueryElementSetPredicate(CFMutableDictionaryRef element, CFStringRef predicate); void QEQueryElementAppendArgument(CFMutableDictionaryRef element, CFTypeRef argument); void QEQueryElementSetArgumentsArray(CFMutableDictionaryRef element, CFArrayRef arguments); Boolean QEQueryElementSetArguments(CFMutableDictionaryRef element, uint32_t numArgs, ...); /******************************************************************************* * Print the raw CFPropertyList contents of a query. *******************************************************************************/ void QEQueryPrint(QEQueryRef query); #endif /* _QEQUERY_H_ */