owntone-server/src/smart-parser.c

1339 lines
32 KiB
C
Raw Normal View History

/*
* $Id$
*
* This is really two parts -- the lexer and the parser. Converting
* a parse tree back to a format that works with the database backend
* is left to the db backend.
*
* Oh, and this is called "smart-parser" because it parses terms for
* specifying smart playlists, not because it is particularly smart. :)
*
*/
2006-02-26 03:46:24 -05:00
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
2005-10-29 17:23:43 -04:00
#include <time.h>
2006-02-26 03:46:24 -05:00
#include "strptime.h"
#include "err.h"
2005-10-13 03:38:22 -04:00
typedef struct tag_token {
2005-08-14 23:16:36 -04:00
int token_id;
union {
2005-10-13 03:38:22 -04:00
char *cvalue;
int ivalue;
2005-10-29 17:23:43 -04:00
time_t tvalue;
2005-08-14 23:16:36 -04:00
} data;
2005-10-13 03:38:22 -04:00
} SP_TOKEN;
2005-08-14 23:16:36 -04:00
2005-10-18 18:35:10 -04:00
typedef struct tag_sp_node {
union {
struct tag_sp_node *node;
char *field;
} left;
int op;
int op_type;
int not_flag;
2005-10-18 18:35:10 -04:00
union {
struct tag_sp_node *node;
int ivalue;
char *cvalue;
2005-10-29 17:23:43 -04:00
time_t tvalue;
2005-10-18 18:35:10 -04:00
} right;
} SP_NODE;
#define SP_OPTYPE_ANDOR 0
#define SP_OPTYPE_STRING 1
#define SP_OPTYPE_INT 2
#define SP_OPTYPE_DATE 3
2005-10-13 03:38:22 -04:00
/*
2005-10-02 18:48:07 -04:00
#define T_ID 0x00
#define T_PATH 0x01
#define T_TITLE 0x02
#define T_ARTIST 0x03
#define T_ALBUM 0x04
#define T_GENRE 0x05
#define T_COMMENT 0x06
#define T_TYPE 0x07
#define T_COMPOSER 0x08
#define T_ORCHESTRA 0x09
#define T_GROUPING 0x0a
#define T_URL 0x0b
#define T_BITRATE 0x0c
#define T_SAMPLERATE 0x0d
#define T_SONG_LENGTH 0x0e
#define T_FILE_SIZE 0x0f
#define T_YEAR 0x10
#define T_TRACK 0x11
#define T_TOTAL_TRACKS 0x12
#define T_DISC 0x13
#define T_TOTAL_DISCS 0x14
#define T_BPM 0x15
#define T_COMPILATION 0x16
#define T_RATING 0x17
#define T_PLAYCOUNT 0x18
#define T_DATA_KIND 0x19
#define T_ITEM_KIND 0x1a
#define T_DESCRIPTION 0x1b
#define T_TIME_ADDED 0x1c
#define T_TIME_MODIFIED 0x0d
#define T_TIME_PLAYED 0x1d
#define T_TIME_STAMP 0x1e
#define T_DISABLED 0x1f
#define T_SAMPLE_COUNT 0x1e
#define T_FORCE_UPDATE 0x1f
#define T_CODECTYPE 0x20
#define T_IDX 0x21
2005-10-13 03:38:22 -04:00
*/
2005-10-18 18:35:10 -04:00
/**
2005-10-13 03:38:22 -04:00
* high 4 bits:
*
2005-10-18 18:35:10 -04:00
* 0x8000 -
2005-10-13 03:38:22 -04:00
* 0x4000 -
* 0x2000 - data is string
* 0x1000 - data is int
2005-10-17 00:57:06 -04:00
*
2005-10-18 18:35:10 -04:00
* 0x0800 -
2005-10-17 00:57:06 -04:00
* 0x0400 -
* 0x0200 -
* 0x0100 -
2005-10-13 03:38:22 -04:00
*/
#define T_STRING 0x2001
#define T_INT_FIELD 0x2002
2005-10-13 03:38:22 -04:00
#define T_STRING_FIELD 0x2003
#define T_DATE_FIELD 0x2004
2005-10-02 18:48:07 -04:00
2005-10-13 03:38:22 -04:00
#define T_OPENPAREN 0x0005
#define T_CLOSEPAREN 0x0006
2005-10-17 00:57:06 -04:00
#define T_LESS 0x0007
#define T_LESSEQUAL 0x0008
#define T_GREATER 0x0009
#define T_GREATEREQUAL 0x000a
#define T_EQUAL 0x000b
#define T_OR 0x000c
#define T_AND 0x000d
#define T_QUOTE 0x000e
#define T_NUMBER 0x000f
2005-10-23 20:18:08 -04:00
#define T_INCLUDES 0x0010
2005-10-29 17:23:43 -04:00
#define T_BEFORE 0x0011
#define T_AFTER 0x0012
#define T_AGO 0x0013
#define T_TODAY 0x0014
#define T_THE 0x0015
#define T_DAY 0x0016
#define T_WEEK 0x0017
#define T_MONTH 0x0018
#define T_YEAR 0x0019
#define T_DATE 0x001a
#define T_NOT 0x001b
#define T_STARTSWITH 0x001c
#define T_ENDSWITH 0x001d
#define T_LAST 0x001e
2005-10-17 00:57:06 -04:00
#define T_EOF 0x00fd
#define T_BOF 0x00fe
#define T_ERROR 0x00ff
char *sp_token_descr[] = {
"unknown",
"literal string",
"integer field",
"string field",
"date field",
"(",
")",
"<",
"<=",
">",
">=",
"=",
"or",
"and",
"quote",
2005-10-23 20:18:08 -04:00
"number",
2005-10-29 17:23:43 -04:00
"like",
"before",
"after",
"ago",
"today",
"the",
"day(s)",
"week(s)",
"month(s)",
"year(s)",
"date",
"not",
"like",
"like"
2005-10-17 00:57:06 -04:00
};
2005-10-13 03:38:22 -04:00
typedef struct tag_fieldlookup {
int type;
char *name;
} FIELDLOOKUP;
FIELDLOOKUP sp_fields[] = {
{ T_INT_FIELD, "id" },
{ T_STRING_FIELD, "path" },
2005-10-29 17:23:43 -04:00
{ T_STRING_FIELD, "fname" },
2005-10-13 03:38:22 -04:00
{ T_STRING_FIELD, "title" },
{ T_STRING_FIELD, "artist" },
{ T_STRING_FIELD, "album" },
{ T_STRING_FIELD, "genre" },
{ T_STRING_FIELD, "comment" },
{ T_STRING_FIELD, "type" },
{ T_STRING_FIELD, "composer" },
{ T_STRING_FIELD, "orchestra" },
{ T_STRING_FIELD, "grouping" },
{ T_STRING_FIELD, "url" },
{ T_INT_FIELD, "bitrate" },
{ T_INT_FIELD, "samplerate" },
2005-10-29 17:23:43 -04:00
{ T_INT_FIELD, "song_length" },
{ T_INT_FIELD, "file_size" },
2005-10-13 03:38:22 -04:00
{ T_INT_FIELD, "year" },
{ T_INT_FIELD, "track" },
2005-10-29 17:23:43 -04:00
{ T_INT_FIELD, "total_tracks" },
2005-10-13 03:38:22 -04:00
{ T_INT_FIELD, "disc" },
2005-10-29 17:23:43 -04:00
{ T_INT_FIELD, "total_discs" },
2005-10-13 03:38:22 -04:00
{ T_INT_FIELD, "bpm" },
{ T_INT_FIELD, "compilation" },
{ T_INT_FIELD, "rating" },
2005-10-29 17:23:43 -04:00
{ T_INT_FIELD, "play_count" },
{ T_INT_FIELD, "data_kind" },
{ T_INT_FIELD, "item_kind" },
2005-10-13 03:38:22 -04:00
{ T_STRING_FIELD, "description" },
2005-10-29 17:23:43 -04:00
{ T_DATE_FIELD, "time_added" },
{ T_DATE_FIELD, "time_modified" },
{ T_DATE_FIELD, "time_played" },
{ T_DATE_FIELD, "db_timestamp" },
{ T_INT_FIELD, "sample_count" },
{ T_INT_FIELD, "force_update" },
{ T_STRING_FIELD, "codectype" },
{ T_INT_FIELD, "idx" },
2005-10-17 00:57:06 -04:00
/* end of db fields */
{ T_OR, "or" },
{ T_AND, "and" },
2005-10-23 20:18:08 -04:00
{ T_INCLUDES, "includes" },
2005-10-29 17:23:43 -04:00
{ T_BEFORE, "before" },
{ T_AFTER, "after" },
{ T_AGO, "ago" },
{ T_TODAY, "today" },
{ T_THE, "the" },
{ T_DAY, "days" },
{ T_DAY, "day" },
{ T_WEEK, "weeks" },
{ T_WEEK, "week" },
{ T_MONTH, "months" },
{ T_MONTH, "month" },
{ T_YEAR, "years" },
{ T_YEAR, "year" },
{ T_NOT, "not" },
{ T_STARTSWITH, "startswith" },
{ T_ENDSWITH, "endswith" },
2005-10-17 00:57:06 -04:00
/* end */
2005-10-13 03:38:22 -04:00
{ 0, NULL },
2005-08-14 23:16:36 -04:00
};
2005-10-18 18:35:10 -04:00
typedef struct tag_parsetree {
int in_string;
char *term;
char *current;
2005-10-13 03:38:22 -04:00
SP_TOKEN token;
int token_pos;
SP_NODE *tree;
char *error;
char level;
} PARSESTRUCT, *PARSETREE;
2005-10-29 17:23:43 -04:00
#define SP_E_SUCCESS 0x00
#define SP_E_CLOSE 0x01
#define SP_E_FIELD 0x02
#define SP_E_STRCMP 0x03
#define SP_E_CLOSEQUOTE 0x04
#define SP_E_STRING 0x05
#define SP_E_OPENQUOTE 0x06
#define SP_E_INTCMP 0x07
#define SP_E_NUMBER 0x08
#define SP_E_DATECMP 0x09
#define SP_E_BEFOREAFTER 0x0a
#define SP_E_TIMEINTERVAL 0x0b
#define SP_E_DATE 0x0c
char *sp_errorstrings[] = {
"Success",
"Expecting ')'",
"Expecting field name",
"Expecting string comparison operator (=, includes)",
"Expecting '\"' (closing quote)",
"Expecting literal string",
"Expecting '\"' (opening quote)",
"Expecting integer comparison operator (=,<,>, etc)",
2005-10-29 17:23:43 -04:00
"Expecting integer",
"Expecting date comparison operator (<,<=,>,>=)",
"Expecting interval comparison (before, after)",
"Expecting time interval (days, weeks, months, years)",
"Expecting date"
};
2005-10-17 00:57:06 -04:00
/* Forwards */
SP_NODE *sp_parse_phrase(PARSETREE tree);
SP_NODE *sp_parse_oexpr(PARSETREE tree);
SP_NODE *sp_parse_aexpr(PARSETREE tree);
SP_NODE *sp_parse_expr(PARSETREE tree);
SP_NODE *sp_parse_criterion(PARSETREE tree);
SP_NODE *sp_parse_string_criterion(PARSETREE tree);
SP_NODE *sp_parse_int_criterion(PARSETREE tree);
SP_NODE *sp_parse_date_criterion(PARSETREE tree);
2005-10-29 17:23:43 -04:00
time_t sp_parse_date(PARSETREE tree);
time_t sp_parse_date_interval(PARSETREE tree);
void sp_free_node(SP_NODE *node);
int sp_node_size(SP_NODE *node);
void sp_set_error(PARSETREE tree,int error);
/**
* simple logging funcitons
*
* @param tree tree ew are parsing
* @param function funtion entering/exiting
* @param result result of param (if exiting)
*/
void sp_enter_exit(PARSETREE tree, char *function, int enter, void *result) {
char *str_result = result ? "success" : "failure";
if(enter) {
tree->level++;
DPRINTF(E_DBG,L_PARSE,"%*s Entering %s\n",tree->level," ",function);
} else {
DPRINTF(E_DBG,L_PARSE,"%*s Exiting %s (%s)\n",tree->level," ",
function, str_result);
tree->level--;
}
}
/**
* see if a string is actually a number
*
* @param string string to check
* @returns 1 if the string is numeric, 0 otherwise
*/
int sp_isnumber(char *string) {
char *current=string;
while(*current && (*current >= '0') && (*current <= '9')) {
current++;
}
return *current ? 0 : 1;
}
/**
* see if a string is actually a date in the date format
* YYYY-MM-DD
*
* @param string string to check
* @returns time_t of date, or 0 if not a date
*/
time_t sp_isdate(char *string) {
struct tm date_time;
time_t seconds=0;
memset((void*)&date_time,0,sizeof(date_time));
if(strptime(string,"%Y-%m-%d",&date_time)) {
seconds=timegm(&date_time);
}
return seconds;
}
2005-10-17 00:57:06 -04:00
2005-10-13 03:38:22 -04:00
/**
* scan the input, returning the next available token.
*
* @param tree current working parse tree.
* @returns next token (token, not the value)
*/
2005-08-14 23:16:36 -04:00
int sp_scan(PARSETREE tree) {
2005-10-17 00:57:06 -04:00
char *terminator=NULL;
char *tail;
2005-10-13 03:38:22 -04:00
int advance=0;
FIELDLOOKUP *pfield=sp_fields;
int len;
2005-10-17 00:57:06 -04:00
int found;
int numval;
time_t tval;
2005-10-13 03:38:22 -04:00
if(tree->token.token_id & 0x2000) {
2005-10-17 00:57:06 -04:00
if(tree->token.data.cvalue)
free(tree->token.data.cvalue);
2005-10-13 03:38:22 -04:00
}
2005-10-17 00:57:06 -04:00
if(tree->token.token_id == T_EOF) {
DPRINTF(E_DBG,L_PARSE,"%*s Returning token T_EOF\n",tree->level," ");
2005-10-13 03:38:22 -04:00
return T_EOF;
2005-10-17 00:57:06 -04:00
}
/* keep advancing until we have a token */
while(*(tree->current) && strchr(" \t\n\r",*(tree->current)))
2005-10-13 03:38:22 -04:00
tree->current++;
2005-10-18 18:35:10 -04:00
2006-02-26 03:46:24 -05:00
tree->token_pos = (int) (tree->current - tree->term);
2005-10-13 03:38:22 -04:00
if(!*(tree->current)) {
tree->token.token_id = T_EOF;
DPRINTF(E_DBG,L_PARSE,"%*s Returning token %04x\n",tree->level," ",
tree->token.token_id);
2005-10-13 03:38:22 -04:00
return tree->token.token_id;
}
2005-10-18 18:35:10 -04:00
DPRINTF(E_SPAM,L_PARSE,"Current offset: %d, char: %c\n",
tree->token_pos, *(tree->current));
/* check singletons */
if(!tree->in_string) {
switch(*(tree->current)) {
case '|':
if((*(tree->current + 1) == '|')) {
advance = 2;
tree->token.token_id = T_OR;
}
break;
2005-10-17 00:57:06 -04:00
case '&':
if((*(tree->current + 1) == '&')) {
advance = 2;
tree->token.token_id = T_AND;
}
break;
2005-10-17 00:57:06 -04:00
case '=':
advance=1;
tree->token.token_id = T_EQUAL;
break;
2005-10-18 18:35:10 -04:00
case '<':
if((*(tree->current + 1)) == '=') {
advance = 2;
tree->token.token_id = T_LESSEQUAL;
} else {
advance = 1;
tree->token.token_id = T_LESS;
}
break;
2005-10-18 18:35:10 -04:00
case '>':
if((*(tree->current + 1)) == '=') {
advance = 2;
tree->token.token_id = T_GREATEREQUAL;
} else {
advance = 1;
tree->token.token_id = T_GREATER;
}
break;
case '(':
advance=1;
tree->token.token_id = T_OPENPAREN;
break;
2005-10-13 03:38:22 -04:00
case ')':
advance=1;
tree->token.token_id = T_CLOSEPAREN;
break;
case '!':
advance=1;
tree->token.token_id = T_NOT;
break;
}
}
2005-10-13 03:38:22 -04:00
if(*tree->current == '"') {
advance = 1;
tree->in_string = !tree->in_string;
tree->token.token_id = T_QUOTE;
2005-10-13 03:38:22 -04:00
}
2005-10-17 00:57:06 -04:00
if(advance) { /* singleton */
2005-10-13 03:38:22 -04:00
tree->current += advance;
} else { /* either a keyword token or a quoted string */
DPRINTF(E_SPAM,L_PARSE,"keyword or string!\n");
2005-10-18 18:35:10 -04:00
2005-10-13 03:38:22 -04:00
/* walk to a terminator */
tail = tree->current;
2005-10-18 18:35:10 -04:00
terminator = " \t\n\r\"<>=()|&!";
if(tree->in_string) {
2005-10-17 00:57:06 -04:00
terminator="\"";
}
2005-10-18 18:35:10 -04:00
2005-10-17 00:57:06 -04:00
while((*tail) && (!strchr(terminator,*tail))) {
2005-10-13 03:38:22 -04:00
tail++;
}
2005-10-17 00:57:06 -04:00
found=0;
2006-02-26 03:46:24 -05:00
len = (int) (tail - tree->current);
2005-10-17 00:57:06 -04:00
if(!tree->in_string) {
2005-10-17 00:57:06 -04:00
/* find it in the token list */
pfield=sp_fields;
DPRINTF(E_SPAM,L_PARSE,"Len is %d\n",len);
while(pfield->name) {
if(strlen(pfield->name) == len) {
if(strncasecmp(pfield->name,tree->current,len) == 0) {
found=1;
break;
}
}
pfield++;
2005-10-13 03:38:22 -04:00
}
}
2005-10-18 18:35:10 -04:00
2005-10-17 00:57:06 -04:00
if(found) {
tree->token.token_id = pfield->type;
} else {
tree->token.token_id = T_STRING;
}
2005-10-13 03:38:22 -04:00
if(tree->token.token_id & 0x2000) {
tree->token.data.cvalue = malloc(len + 1);
if(!tree->token.data.cvalue) {
2005-10-17 00:57:06 -04:00
/* fail on malloc error */
DPRINTF(E_FATAL,L_PARSE,"Malloc error.\n");
}
strncpy(tree->token.data.cvalue,tree->current,len);
tree->token.data.cvalue[len] = '\x0';
2005-10-17 00:57:06 -04:00
}
2005-10-18 18:35:10 -04:00
2005-10-17 00:57:06 -04:00
/* check for numberic? */
if(tree->token.token_id == T_STRING &&
(!tree->in_string) &&
sp_isnumber(tree->token.data.cvalue)) {
/* woops! */
numval = atoi(tree->token.data.cvalue);
free(tree->token.data.cvalue);
tree->token.data.ivalue = numval;
tree->token.token_id = T_NUMBER;
}
if(tree->token.token_id == T_STRING &&
(!tree->in_string) &&
(tval=sp_isdate(tree->token.data.cvalue))) {
free(tree->token.data.cvalue);
tree->token.data.tvalue = tval;
tree->token.token_id = T_DATE;
}
tree->current=tail;
2005-10-13 03:38:22 -04:00
}
2005-10-18 18:35:10 -04:00
DPRINTF(E_DBG,L_PARSE,"%*s Returning token %04x\n",tree->level," ",
tree->token.token_id);
2005-10-17 00:57:06 -04:00
if(tree->token.token_id & 0x2000)
DPRINTF(E_SPAM,L_PARSE,"String val: %s\n",tree->token.data.cvalue);
if(tree->token.token_id & 0x1000)
DPRINTF(E_SPAM,L_PARSE,"Int val: %d\n",tree->token.data.ivalue);
2005-10-18 18:35:10 -04:00
2005-10-13 03:38:22 -04:00
return tree->token.token_id;
2005-08-14 23:16:36 -04:00
}
/**
* set up the initial parse tree
2005-10-18 18:35:10 -04:00
*
* @returns opaque parsetree struct
*/
PARSETREE sp_init(void) {
PARSETREE ptree;
ptree = (PARSETREE)malloc(sizeof(PARSESTRUCT));
2005-10-18 18:35:10 -04:00
if(!ptree)
2005-10-13 03:38:22 -04:00
DPRINTF(E_FATAL,L_PARSE,"Alloc error\n");
memset(ptree,0,sizeof(PARSESTRUCT));
return ptree;
}
/**
* parse a term or phrase into a tree.
*
2005-10-17 00:57:06 -04:00
* I'm not a language expert, so I'd welcome suggestions on the
2005-10-16 02:55:42 -04:00
* following production rules:
*
* phrase -> oexpr T_EOF
* oexpr -> aexpr { T_AND aexpr }
* aexpr -> expr { T_OR expr }
* expr -> T_OPENPAREN oexpr T_CLOSEPAREN | criterion
2005-10-17 00:57:06 -04:00
* criterion -> field op value
2005-10-16 02:55:42 -04:00
*
* field -> T_STRINGFIELD, T_INTFIELD, T_DATEFIELD
* op -> T_EQUAL, T_GREATEREQUAL, etc
* value -> T_NUMBER, T_STRING, or T_DATE, as appropriate
*
* @param tree parsetree previously created with sp_init
* @param term term or phrase to parse
* @returns 1 if successful, 0 if not
*/
int sp_parse(PARSETREE tree, char *term) {
tree->term = strdup(term); /* will be destroyed by parsing */
tree->current=tree->term;
2005-10-13 03:38:22 -04:00
tree->token.token_id=T_BOF;
2005-10-18 18:35:10 -04:00
if(tree->tree)
sp_free_node(tree->tree);
sp_scan(tree);
tree->tree = sp_parse_phrase(tree);
if(tree->tree) {
2005-10-17 00:57:06 -04:00
DPRINTF(E_SPAM,L_PARSE,"Parsed successfully\n");
} else {
DPRINTF(E_SPAM,L_PARSE,"Parsing error\n");
}
2005-08-14 23:16:36 -04:00
return tree->tree ? 1 : 0;
}
2005-10-16 02:55:42 -04:00
/**
* parse for a phrase
*
* phrase -> aexpr T_EOF
*
* @param tree tree we are parsing (and building)
* @returns new SP_NODE * if successful, NULL otherwise
2005-10-16 02:55:42 -04:00
*/
2005-10-18 18:35:10 -04:00
SP_NODE *sp_parse_phrase(PARSETREE tree) {
SP_NODE *expr;
2005-10-18 18:35:10 -04:00
sp_enter_exit(tree,"sp_parse_phrase",1,NULL);
DPRINTF(E_SPAM,L_PARSE,"%*s Entering sp_parse_phrase\n",tree->level," ");
tree->level++;
expr = sp_parse_oexpr(tree);
if((!expr) || (tree->token.token_id != T_EOF)) {
sp_free_node(expr);
expr = NULL;
}
2005-10-18 18:35:10 -04:00
sp_enter_exit(tree,"sp_parse_phrase",0,expr);
return expr;
2005-10-16 02:55:42 -04:00
}
2005-10-18 18:35:10 -04:00
2005-10-16 02:55:42 -04:00
/**
* parse for an ANDed expression
*
* aexpr -> expr { T_AND expr }
2005-10-16 02:55:42 -04:00
*
* @param tree tree we are building
* @returns new SP_NODE pointer if successful, NULL otherwise
2005-10-16 02:55:42 -04:00
*/
SP_NODE *sp_parse_aexpr(PARSETREE tree) {
SP_NODE *expr;
SP_NODE *pnew;
sp_enter_exit(tree,"sp_parse_aexpr",1,NULL);
2005-10-18 18:35:10 -04:00
expr = sp_parse_expr(tree);
while(expr && (tree->token.token_id == T_AND)) {
pnew = (SP_NODE*)malloc(sizeof(SP_NODE));
if(!pnew) {
DPRINTF(E_FATAL,L_PARSE,"Malloc error\n");
}
memset(pnew,0x00,sizeof(SP_NODE));
pnew->op=T_AND;
pnew->op_type = SP_OPTYPE_ANDOR;
pnew->left.node = expr;
sp_scan(tree);
pnew->right.node = sp_parse_expr(tree);
if(!pnew->right.node) {
sp_free_node(pnew);
pnew=NULL;
}
expr=pnew;
2005-10-16 02:55:42 -04:00
}
2005-10-29 17:23:43 -04:00
sp_enter_exit(tree,"sp_parse_aexpr",0,expr);
return expr;
2005-10-16 02:55:42 -04:00
}
/**
* parse for an ORed expression
*
* oexpr -> aexpr { T_OR aexpr }
2005-10-16 02:55:42 -04:00
*
* @param tree tree we are building
* @returns new SP_NODE pointer if successful, NULL otherwise
2005-10-16 02:55:42 -04:00
*/
SP_NODE *sp_parse_oexpr(PARSETREE tree) {
SP_NODE *expr;
SP_NODE *pnew;
sp_enter_exit(tree,"sp_parse_oexpr",1,NULL);
expr = sp_parse_aexpr(tree);
while(expr && (tree->token.token_id == T_OR)) {
pnew = (SP_NODE*)malloc(sizeof(SP_NODE));
if(!pnew) {
DPRINTF(E_FATAL,L_PARSE,"Malloc error\n");
}
memset(pnew,0x00,sizeof(SP_NODE));
pnew->op=T_OR;
pnew->op_type = SP_OPTYPE_ANDOR;
pnew->left.node = expr;
sp_scan(tree);
pnew->right.node = sp_parse_aexpr(tree);
if(!pnew->right.node) {
sp_free_node(pnew);
pnew=NULL;
}
expr=pnew;
2005-10-16 02:55:42 -04:00
}
sp_enter_exit(tree,"sp_parse_oexpr",0,expr);
return expr;
2005-10-16 02:55:42 -04:00
}
/**
* parse for an expression
*
* expr -> T_OPENPAREN oexpr T_CLOSEPAREN | criteria
2005-10-18 18:35:10 -04:00
*
2005-10-16 02:55:42 -04:00
* @param tree tree we are building
* @returns pointer to new SP_NODE if successful, NULL otherwise
2005-10-16 02:55:42 -04:00
*/
SP_NODE *sp_parse_expr(PARSETREE tree) {
SP_NODE *expr;
2005-10-18 18:35:10 -04:00
sp_enter_exit(tree,"sp_parse_expr",1,NULL);
2005-10-17 00:57:06 -04:00
if(tree->token.token_id == T_OPENPAREN) {
sp_scan(tree);
expr = sp_parse_oexpr(tree);
if((expr) && (tree->token.token_id == T_CLOSEPAREN)) {
2005-10-17 00:57:06 -04:00
sp_scan(tree);
} else {
/* Error: expecting close paren */
sp_set_error(tree,SP_E_CLOSE);
sp_free_node(expr);
expr=NULL;
2005-10-17 00:57:06 -04:00
}
} else {
expr = sp_parse_criterion(tree);
2005-10-17 00:57:06 -04:00
}
2005-10-18 18:35:10 -04:00
sp_enter_exit(tree,"sp_parse_expr",0,expr);
return expr;
2005-10-17 00:57:06 -04:00
}
2005-10-18 18:35:10 -04:00
2005-10-17 00:57:06 -04:00
/**
* parse for a criterion
*
* criterion -> field op value
*
* @param tree tree we are building
* @returns pointer to new SP_NODE if successful, NULL otherwise.
2005-10-17 00:57:06 -04:00
*/
SP_NODE *sp_parse_criterion(PARSETREE tree) {
SP_NODE *expr=NULL;
sp_enter_exit(tree,"sp_parse_criterion",1,expr);
2005-10-17 00:57:06 -04:00
switch(tree->token.token_id) {
case T_STRING_FIELD:
expr = sp_parse_string_criterion(tree);
2005-10-17 00:57:06 -04:00
break;
2005-10-18 18:35:10 -04:00
2005-10-17 00:57:06 -04:00
case T_INT_FIELD:
expr = sp_parse_int_criterion(tree);
2005-10-17 00:57:06 -04:00
break;
2005-10-18 18:35:10 -04:00
2005-10-17 00:57:06 -04:00
case T_DATE_FIELD:
expr = sp_parse_date_criterion(tree);
2005-10-17 00:57:06 -04:00
break;
2005-10-18 18:35:10 -04:00
2005-10-17 00:57:06 -04:00
default:
/* Error: expecting field */
sp_set_error(tree,SP_E_FIELD);
expr = NULL;
2005-10-17 00:57:06 -04:00
break;
}
sp_enter_exit(tree,"sp_parse_criterion",0,expr);
return expr;
2005-10-16 02:55:42 -04:00
}
2005-10-17 00:57:06 -04:00
/**
* parse for a string criterion
*
* @param tree tree we are building
* @returns pointer to new SP_NODE if successful, NULL otherwise
2005-10-17 00:57:06 -04:00
*/
SP_NODE *sp_parse_string_criterion(PARSETREE tree) {
2005-10-17 00:57:06 -04:00
int result=0;
SP_NODE *pnew = NULL;
2005-10-17 00:57:06 -04:00
sp_enter_exit(tree,"sp_parse_string_criterion",1,NULL);
pnew = malloc(sizeof(SP_NODE));
if(!pnew) {
DPRINTF(E_FATAL,L_PARSE,"Malloc Error\n");
}
memset(pnew,0x00,sizeof(SP_NODE));
pnew->left.field = strdup(tree->token.data.cvalue);
2005-10-17 00:57:06 -04:00
sp_scan(tree); /* scan past the string field we know is there */
if(tree->token.token_id == T_NOT) {
pnew->not_flag=1;
sp_scan(tree);
}
2005-10-17 00:57:06 -04:00
switch(tree->token.token_id) {
case T_EQUAL:
2005-10-23 20:18:08 -04:00
case T_INCLUDES:
case T_STARTSWITH:
case T_ENDSWITH:
pnew->op=tree->token.token_id;
pnew->op_type = SP_OPTYPE_STRING;
2005-10-17 00:57:06 -04:00
result = 1;
break;
default:
/* Error: expecting legal string comparison operator */
sp_set_error(tree,SP_E_STRCMP);
2005-10-17 00:57:06 -04:00
break;
}
2005-10-18 18:35:10 -04:00
2005-10-17 00:57:06 -04:00
if(result) {
sp_scan(tree);
/* should be sitting on quote literal string quote */
if(tree->token.token_id == T_QUOTE) {
sp_scan(tree);
if(tree->token.token_id == T_STRING) {
pnew->right.cvalue=strdup(tree->token.data.cvalue);
2005-10-17 00:57:06 -04:00
sp_scan(tree);
if(tree->token.token_id == T_QUOTE) {
result=1;
sp_scan(tree);
} else {
sp_set_error(tree,SP_E_CLOSEQUOTE);
2005-10-17 00:57:06 -04:00
DPRINTF(E_SPAM,L_PARSE,"Expecting closign quote\n");
}
} else {
sp_set_error(tree,SP_E_STRING);
2005-10-17 00:57:06 -04:00
DPRINTF(E_SPAM,L_PARSE,"Expecting literal string\n");
}
} else {
sp_set_error(tree,SP_E_OPENQUOTE);
2005-10-17 00:57:06 -04:00
DPRINTF(E_SPAM,L_PARSE,"Expecting opening quote\n");
}
}
if(!result) {
sp_free_node(pnew);
pnew=NULL;
}
sp_enter_exit(tree,"sp_parse_string_criterion",0,pnew);
return pnew;
2005-10-17 00:57:06 -04:00
}
2005-10-18 18:35:10 -04:00
2005-10-17 00:57:06 -04:00
/**
* parse for an int criterion
*
* @param tree tree we are building
* @returns address of new SP_NODE if successful, NULL otherwise
2005-10-17 00:57:06 -04:00
*/
SP_NODE *sp_parse_int_criterion(PARSETREE tree) {
2005-10-17 00:57:06 -04:00
int result=0;
SP_NODE *pnew = NULL;
sp_enter_exit(tree,"sp_parse_int_criterion",1,pnew);
pnew = malloc(sizeof(SP_NODE));
if(!pnew) {
DPRINTF(E_FATAL,L_PARSE,"Malloc Error\n");
}
memset(pnew,0x00,sizeof(SP_NODE));
pnew->left.field = strdup(tree->token.data.cvalue);
2005-10-17 00:57:06 -04:00
sp_scan(tree); /* scan past the int field we know is there */
if(tree->token.token_id == T_NOT) {
pnew->not_flag=1;
sp_scan(tree);
}
2005-10-17 00:57:06 -04:00
switch(tree->token.token_id) {
case T_LESSEQUAL:
case T_LESS:
case T_GREATEREQUAL:
case T_GREATER:
case T_EQUAL:
result = 1;
pnew->op=tree->token.token_id;
pnew->op_type = SP_OPTYPE_INT;
2005-10-17 00:57:06 -04:00
break;
default:
/* Error: expecting legal int comparison operator */
sp_set_error(tree,SP_E_INTCMP);
DPRINTF(E_LOG,L_PARSE,"Expecting int comparison op, got %04X\n",
2005-10-17 00:57:06 -04:00
tree->token.token_id);
break;
}
2005-10-18 18:35:10 -04:00
2005-10-17 00:57:06 -04:00
if(result) {
sp_scan(tree);
/* should be sitting on a literal string */
if(tree->token.token_id == T_NUMBER) {
result = 1;
pnew->right.ivalue=tree->token.data.ivalue;
2005-10-17 00:57:06 -04:00
sp_scan(tree);
} else {
/* Error: Expecting number */
sp_set_error(tree,SP_E_NUMBER);
DPRINTF(E_LOG,L_PARSE,"Expecting number, got %04X\n",
2005-10-17 00:57:06 -04:00
tree->token.token_id);
result = 0;
}
}
if(!result) {
sp_free_node(pnew);
pnew=NULL;
}
sp_enter_exit(tree,"sp_parse_int_criterion",0,pnew);
2005-10-18 18:35:10 -04:00
return pnew;
2005-10-17 00:57:06 -04:00
}
/**
* parse for a date criterion
*
* @param tree tree we are building
* @returns pointer to new SP_NODE if successful, NULL otherwise
2005-10-17 00:57:06 -04:00
*/
2005-10-29 17:23:43 -04:00
SP_NODE *sp_parse_date_criterion(PARSETREE tree) {
SP_NODE *pnew=NULL;
2005-10-29 17:23:43 -04:00
int result=0;
sp_enter_exit(tree,"sp_parse_date_criterion",1,pnew);
2005-10-29 17:23:43 -04:00
pnew = malloc(sizeof(SP_NODE));
if(!pnew) {
DPRINTF(E_FATAL,L_PARSE,"Malloc Error\n");
}
memset(pnew,0x00,sizeof(SP_NODE));
pnew->left.field = strdup(tree->token.data.cvalue);
sp_scan(tree); /* scan past the date field we know is there */
if(tree->token.token_id == T_NOT) {
pnew->not_flag=1;
sp_scan(tree);
}
2005-10-29 17:23:43 -04:00
switch(tree->token.token_id) {
case T_LESSEQUAL:
case T_LESS:
case T_GREATEREQUAL:
case T_GREATER:
result = 1;
pnew->op=tree->token.token_id;
pnew->op_type = SP_OPTYPE_DATE;
break;
case T_BEFORE:
result = 1;
pnew->op_type = SP_OPTYPE_DATE;
pnew->op=T_LESS;
break;
case T_AFTER:
result = 1;
pnew->op_type = SP_OPTYPE_DATE;
pnew->op=T_GREATER;
break;
default:
/* Error: expecting legal int comparison operator */
sp_set_error(tree,SP_E_DATECMP);
DPRINTF(E_LOG,L_PARSE,"Expecting int comparison op, got %04X\n",
tree->token.token_id);
break;
}
if(result) {
sp_scan(tree);
/* should be sitting on a date */
if((pnew->right.tvalue = sp_parse_date(tree))) {
result=1;
} else {
sp_set_error(tree,SP_E_DATE);
result=0;
}
}
if(!result) {
sp_free_node(pnew);
pnew=NULL;
}
2005-10-17 00:57:06 -04:00
sp_enter_exit(tree,"sp_parse_date_criterion",0,pnew);
2005-10-18 18:35:10 -04:00
return pnew;
2005-10-17 00:57:06 -04:00
}
2005-10-18 18:35:10 -04:00
2005-10-29 17:23:43 -04:00
/**
* parse a date value for a date field comparison
*
* date -> T_TODAY | T_DATE |
2005-10-29 17:23:43 -04:00
* date_interval T_BEFORE date | date_interval T_AFTER date
* date_interval -> T_NUMBER ( T_DAY | T_WEEK | T_MONTH | T_YEAR)
* (T_BEFORE | T_AFTER) date
*
* @param tree tree we are building/parsing
* @returns time_t of date, or 0 if failure
*/
time_t sp_parse_date(PARSETREE tree) {
time_t result=0;
time_t interval;
int before;
2005-10-29 17:23:43 -04:00
sp_enter_exit(tree,"sp_parse_date",1,(void*)result);
switch(tree->token.token_id) {
case T_TODAY:
result = time(NULL);
sp_scan(tree);
break;
case T_DATE:
result = tree->token.data.tvalue;
sp_scan(tree);
break;
}
2005-10-29 17:23:43 -04:00
if(!result) {
/* must be an interval */
interval = sp_parse_date_interval(tree);
if(!interval) {
result = 0;
} else if((tree->token.token_id != T_BEFORE) &&
(tree->token.token_id != T_AFTER)) {
sp_set_error(tree,SP_E_BEFOREAFTER);
result=0;
} else {
before = 1;
if(tree->token.token_id == T_AFTER)
before = 0;
2005-10-29 17:23:43 -04:00
sp_scan(tree);
result = sp_parse_date(tree);
if(result) {
if(before) {
result -= interval;
} else {
result += interval;
}
}
}
}
2005-10-29 17:23:43 -04:00
sp_enter_exit(tree,"sp_parse_date_criterion",0,(void*)result);
return result;
}
/**
* parse a date inteval
*
* date_interval -> T_NUMBER (T_DAY | T_WEEK | T_MONTH | T_YEAR)
* @param tree tree we are parsing/building
* @returns time_t seconds in interval, or 0 if error
*/
time_t sp_parse_date_interval(PARSETREE tree) {
time_t result=0;
int count;
2005-10-29 17:23:43 -04:00
sp_enter_exit(tree,"sp_parse_date_interval",1,(void*)result);
if(tree->token.token_id != T_NUMBER) {
result=0;
sp_set_error(tree,SP_E_NUMBER);
2005-10-29 17:23:43 -04:00
} else {
count = tree->token.data.ivalue;
sp_scan(tree);
switch(tree->token.token_id) {
case T_DAY:
result = count * 3600 * 24;
sp_scan(tree);
break;
case T_WEEK:
result = count * 3600 * 24 * 7;
sp_scan(tree);
break;
case T_MONTH:
result = count * 3600 * 24 * 30;
sp_scan(tree);
break;
case T_YEAR:
result = count * 3600 * 24 * 365;
sp_scan(tree);
break;
default:
result=0;
sp_set_error(tree, SP_E_TIMEINTERVAL);
break;
}
}
2005-10-29 17:23:43 -04:00
sp_enter_exit(tree,"sp_parse_date_interval",0,(void*)result);
return result;
}
2005-10-29 17:23:43 -04:00
/**
* free a node, and all left/right subnodes
*
* @param node node to free
*/
void sp_free_node(SP_NODE *node) {
if(!node)
return;
if(node->op_type == SP_OPTYPE_ANDOR) {
if(node->left.node) {
sp_free_node(node->left.node);
node->left.node = NULL;
}
if(node->right.node) {
sp_free_node(node->right.node);
node->right.node = NULL;
}
} else {
if(node->left.field) {
free(node->left.field);
node->left.field = NULL;
}
if(node->op_type == SP_OPTYPE_STRING) {
if(node->right.cvalue) {
free(node->right.cvalue);
node->right.cvalue = NULL;
}
}
}
free(node);
}
2005-10-18 18:35:10 -04:00
/**
* dispose of an initialized tree
*
* @param tree tree to dispose
* @returns 1
*/
int sp_dispose(PARSETREE tree) {
if(tree->term)
2005-10-13 03:38:22 -04:00
free(tree->term);
if(tree->token.token_id & 0x2000)
free(tree->token.data.cvalue);
if(tree->error)
free(tree->error);
free(tree);
return 1;
}
/**
* calculate the size required to render the tree as a
* sql query.
*
* @parameter node node/tree to calculate
* @returns size in bytes of sql "where" clause
*/
int sp_node_size(SP_NODE *node) {
int size;
if(node->op_type == SP_OPTYPE_ANDOR) {
size = sp_node_size(node->left.node);
size += sp_node_size(node->right.node);
size += 7; /* (xxx AND xxx) */
} else {
size = 4; /* parens, plus spaces around op */
2006-02-26 03:46:24 -05:00
size += (int) strlen(node->left.field);
if((node->op & 0x0FFF) > T_LAST) {
DPRINTF(E_FATAL,L_PARSE,"Can't determine node size: op %04x\n",
node->op);
} else {
2006-02-26 03:46:24 -05:00
size += (int) strlen(sp_token_descr[node->op & 0x0FFF]);
}
2005-10-23 20:18:08 -04:00
if(node->op_type == SP_OPTYPE_STRING) {
2006-02-26 03:46:24 -05:00
size += (2 + (int) strlen(node->right.cvalue));
2005-10-23 20:18:08 -04:00
if(node->op == T_INCLUDES) {
size += 2; /* extra %'s */
}
if((node->op == T_STARTSWITH)||(node->op == T_ENDSWITH)) {
size += 1;
}
2005-10-23 20:18:08 -04:00
}
2005-10-29 17:23:43 -04:00
if((node->op_type == SP_OPTYPE_INT) || (node->op_type == SP_OPTYPE_DATE)) {
size += 40; /* what *is* the max size of int64? */
}
if(node->not_flag) {
size += 5; /* " not " */
}
}
return size;
}
/**
* serialize node into pre-allocated string
*
* @param node node to serialize
* @param string string to generate
*/
void sp_serialize_sql(SP_NODE *node, char *string) {
char buffer[40];
if(node->op_type == SP_OPTYPE_ANDOR) {
strcat(string,"(");
sp_serialize_sql(node->left.node,string);
if(node->op == T_AND) strcat(string," and ");
if(node->op == T_OR) strcat(string," or ");
sp_serialize_sql(node->right.node,string);
strcat(string,")");
} else {
strcat(string,"(");
if(node->not_flag) {
strcat(string,"not ");
}
strcat(string,node->left.field);
strcat(string," ");
strcat(string,sp_token_descr[node->op & 0x0FFF]);
strcat(string," ");
if(node->op_type == SP_OPTYPE_STRING) {
strcat(string,"'");
if((node->op == T_INCLUDES) || (node->op == T_ENDSWITH))
2005-10-23 20:18:08 -04:00
strcat(string,"%");
strcat(string,node->right.cvalue);
if((node->op == T_INCLUDES) || (node->op == T_STARTSWITH))
2005-10-23 20:18:08 -04:00
strcat(string,"%");
strcat(string,"'");
}
if(node->op_type == SP_OPTYPE_INT) {
sprintf(buffer,"%d",node->right.ivalue);
strcat(string,buffer);
}
2005-10-29 17:23:43 -04:00
if(node->op_type == SP_OPTYPE_DATE) {
sprintf(buffer,"%d",(int)node->right.tvalue);
strcat(string,buffer);
}
strcat(string,")");
}
}
/**
* generate sql "where" clause
*
* @param node node/tree to calculate
* @returns sql string. Must be freed by caller
*/
char *sp_sql_clause(PARSETREE tree) {
int size;
char *sql;
size = sp_node_size(tree->tree);
sql = (char*)malloc(size+1);
memset(sql,0x00,size+1);
sp_serialize_sql(tree->tree,sql);
return sql;
}
/**
* if there was an error in a previous action (parsing?)
2005-10-18 18:35:10 -04:00
* then return that error to the client. This does not
* clear the error condition -- multiple calls to sp_geterror
* will return the same value. Also, if you want to keep an error,
* you must strdup it before it disposing the parse tree...
*
* memory handling is done on the smart-parser side.
*
* @param tree tree that generated the last error
* @returns text of the last error
*/
char *sp_get_error(PARSETREE tree) {
return tree->error;
}
/**
* set the parse tree error for retrieval above
*
* @param tree tree we are setting error for
* @param error error code
*/
void sp_set_error(PARSETREE tree, int error) {
int len;
if(tree->error)
free(tree->error);
2006-02-26 03:46:24 -05:00
len = 10 + (tree->token_pos / 10) + 1 + (int) strlen(sp_errorstrings[error]) + 1;
tree->error = (char*)malloc(len);
if(!tree->error) {
DPRINTF(E_FATAL,L_PARSE,"Malloc error");
return;
}
sprintf(tree->error,"Offset %d: %s",tree->token_pos + 1,sp_errorstrings[error]);
return;
}