[daap/rsp/smartpl] Drop ANTLR parsers

Replacing the antlr parsers solves multiple issues:

- Build warnings (issue #307)
- Build hacks: "-Xconversiontimeout 30000" and other Makefile magic
- Incorrect parsing of daap queries with sql wildcards (like 'tag:*tes%t_ng*')
- Infinite recursion/memory/CPU (issue #570 and #1248)
- systemd service file workarounds due to memory/CPU issue
- ANTLR3 being replaced with ANTLR4 (that doesn't support C file generation)
- Runtime dependency on antlr library
- Difficult installs of ANTLR3 on some systems (special install script)
This commit is contained in:
ejurgensen
2022-01-10 20:00:29 +01:00
parent a95b226fdb
commit 3a93dc5da8
16 changed files with 15 additions and 2550 deletions

6
src/.gitignore vendored
View File

@@ -1,11 +1,5 @@
owntone
*.tokens
*Lexer.[ch]
*Parser.[ch]
*2SQL.[ch]
*.u
daap_query_hash.h
rsp_query_hash.h
dacp_prop_hash.h

View File

@@ -1,63 +0,0 @@
/*
* Copyright (C) 2009-2010 Julien BLACHE <jb@jblache.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
grammar DAAP;
options {
output = AST;
ASTLabelType = pANTLR3_BASE_TREE;
language = C;
}
query : expr NEWLINE? EOF -> expr
;
expr : aexpr (OPOR^ aexpr)*
;
aexpr : crit (OPAND^ crit)*
;
crit : LPAR expr RPAR -> expr
| STR
;
QUOTE : '\'';
LPAR : '(';
RPAR : ')';
OPAND : '+' | ' ';
OPOR : ',';
NEWLINE : '\r'? '\n';
/*
Unescaping adapted from (ported to the C runtime)
<http://stackoverflow.com/questions/504402/how-to-handle-escape-sequences-in-string-literals-in-antlr-3>
*/
STR
@init{ pANTLR3_STRING unesc = GETTEXT()->factory->newRaw(GETTEXT()->factory); }
: QUOTE ( reg = ~('\\' | '\'') { unesc->addc(unesc, reg); }
| esc = ESCAPED { unesc->appendS(unesc, GETTEXT()); } )+ QUOTE { SETTEXT(unesc); };
fragment
ESCAPED : '\\'
( '\\' { SETTEXT(GETTEXT()->factory->newStr8(GETTEXT()->factory, (pANTLR3_UINT8)"\\")); }
| '\'' { SETTEXT(GETTEXT()->factory->newStr8(GETTEXT()->factory, (pANTLR3_UINT8)"\'")); }
)
;

View File

@@ -1,383 +0,0 @@
/*
* Copyright (C) 2009-2011 Julien BLACHE <jb@jblache.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
tree grammar DAAP2SQL;
options {
tokenVocab = DAAP;
ASTLabelType = pANTLR3_BASE_TREE;
language = C;
}
@header {
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include "logger.h"
#include "db.h"
#include "daap_query.h"
}
@members {
struct dmap_query_field_map {
char *dmap_field;
char *db_col;
int as_int;
};
/* gperf static hash, daap_query.gperf */
#include "daap_query_hash.h"
}
query returns [ pANTLR3_STRING result ]
@init { $result = NULL; }
: e = expr
{
if (!$e.valid)
{
$result = NULL;
}
else
{
$result = $e.result->factory->newRaw($e.result->factory);
$result->append8($result, "(");
$result->appendS($result, $e.result);
$result->append8($result, ")");
}
}
;
expr returns [ pANTLR3_STRING result, int valid ]
@init { $result = NULL; $valid = 1; }
: ^(OPAND a = expr b = expr)
{
if ($a.valid && $b.valid)
{
$result = $a.result->factory->newRaw($a.result->factory);
$result->append8($result, "(");
$result->appendS($result, $a.result);
$result->append8($result, " AND ");
$result->appendS($result, $b.result);
$result->append8($result, ")");
}
else if ($a.valid)
{
$result = $a.result->factory->newRaw($a.result->factory);
$result->appendS($result, $a.result);
}
else if ($b.valid)
{
$result = $b.result->factory->newRaw($b.result->factory);
$result->appendS($result, $b.result);
}
else
{
$valid = 0;
}
}
| ^(OPOR a = expr b = expr)
{
if ($a.valid && $b.valid)
{
$result = $a.result->factory->newRaw($a.result->factory);
$result->append8($result, "(");
$result->appendS($result, $a.result);
$result->append8($result, " OR ");
$result->appendS($result, $b.result);
$result->append8($result, ")");
}
else if ($a.valid)
{
$result = $a.result->factory->newRaw($a.result->factory);
$result->appendS($result, $a.result);
}
else if ($b.valid)
{
$result = $b.result->factory->newRaw($b.result->factory);
$result->appendS($result, $b.result);
}
else
{
$valid = 0;
}
}
| STR
{
pANTLR3_STRING str;
pANTLR3_UINT8 field;
pANTLR3_UINT8 val;
pANTLR3_UINT8 escaped;
ANTLR3_UINT8 op;
int neg_op;
const struct dmap_query_field_map *dqfm;
char *end;
long long llval;
escaped = NULL;
$result = $STR.text->factory->newRaw($STR.text->factory);
str = $STR.text->toUTF8($STR.text);
/* NOTE: the lexer delivers the string without quotes
which may not be obvious from the grammar due to embedded code
*/
/* Make daap.songalbumid:0 a no-op */
if (strcmp((char *)str->chars, "daap.songalbumid:0") == 0)
{
$result->append8($result, "1 = 1");
goto STR_out;
}
field = str->chars;
val = field;
while ((*val != '\0') && ((*val == '.')
|| (*val == '-')
|| ((*val >= 'a') && (*val <= 'z'))
|| ((*val >= 'A') && (*val <= 'Z'))
|| ((*val >= '0') && (*val <= '9'))))
{
val++;
}
if (*field == '\0')
{
DPRINTF(E_LOG, L_DAAP, "No field name found in clause '\%s'\n", field);
$valid = 0;
goto STR_result_valid_0; /* ABORT */
}
if (*val == '\0')
{
DPRINTF(E_LOG, L_DAAP, "No operator found in clause '\%s'\n", field);
$valid = 0;
goto STR_result_valid_0; /* ABORT */
}
op = *val;
*val = '\0';
val++;
if (op == '!')
{
if (*val == '\0')
{
DPRINTF(E_LOG, L_DAAP, "Negation found but operator missing in clause '\%s\%c'\n", field, op);
$valid = 0;
goto STR_result_valid_0; /* ABORT */
}
neg_op = 1;
op = *val;
val++;
}
else
neg_op = 0;
/* Lookup DMAP field in the query field map */
dqfm = daap_query_field_lookup((char *)field, strlen((char *)field));
if (!dqfm)
{
DPRINTF(E_LOG, L_DAAP, "DMAP field '\%s' is not a valid field in queries\n", field);
$valid = 0;
goto STR_result_valid_0; /* ABORT */
}
/* Empty values OK for string fields, NOK for integer */
if (*val == '\0')
{
if (dqfm->as_int)
{
DPRINTF(E_LOG, L_DAAP, "No value given in clause '\%s\%s\%c'\n", field, (neg_op) ? "!" : "", op);
$valid = 0;
goto STR_result_valid_0; /* ABORT */
}
/* No need to exclude empty artist and album, as the server makes sure there always exists an artist/album. */
if (neg_op && (op == ':' || op == '@')
&& (strcmp((char *)field, "daap.songalbumartist") == 0
|| strcmp((char *)field, "daap.songartist") == 0
|| strcmp((char *)field, "daap.songalbum") == 0))
{
DPRINTF(E_DBG, L_DAAP, "Ignoring clause '\%s\%s\%c'\n", field, (neg_op) ? "!" : "", op);
$valid = 0;
goto STR_result_valid_0;
}
/* Need to check against NULL too */
if (op == ':' || op == '@')
$result->append8($result, "(");
}
/* Int field: check integer conversion */
if (dqfm->as_int)
{
errno = 0;
llval = strtoll((const char *)val, &end, 10);
if (((errno == ERANGE) && ((llval == LLONG_MAX) || (llval == LLONG_MIN)))
|| ((errno != 0) && (llval == 0)))
{
DPRINTF(E_LOG, L_DAAP, "Value '\%s' in clause '\%s\%s\%c\%s' does not convert to an integer type\n",
val, field, (neg_op) ? "!" : "", op, val);
$valid = 0;
goto STR_result_valid_0; /* ABORT */
}
if (end == (char *)val)
{
DPRINTF(E_LOG, L_DAAP, "Value '\%s' in clause '\%s\%s\%c\%s' does not represent an integer value\n",
val, field, (neg_op) ? "!" : "", op, val);
$valid = 0;
goto STR_result_valid_0; /* ABORT */
}
*end = '\0'; /* Cut out potential garbage - we're being kind */
/* The server only has media_kind = 1 for music - so remove media_kind = 32 to imporve select query performance. */
if (llval == 32
&& (strcmp((char *)field, "com.apple.itunes.mediakind") == 0
|| strcmp((char *)field, "com.apple.itunes.extended-media-kind") == 0))
{
DPRINTF(E_DBG, L_DAAP, "Ignoring clause '\%s\%s\%c\%s'\n", field, (neg_op) ? "!" : "", op, val);
if (neg_op)
$result->append8($result, "1 = 1");
else
$result->append8($result, "1 = 0");
goto STR_out;
}
}
/* String field: escape string, check for '*' */
else
{
/* With Apple Music 1.0.1.37, we observed some ':' queries using '@' instead, resulting in 'Unknown operator' errors */
/* Ex: '/databases/1/containers?query=('com.apple.itunes.extended-media-kind@1', ...) */
if (op != ':' && op != '@')
{
DPRINTF(E_LOG, L_DAAP, "Operation '\%c' not valid for string values\n", op);
$valid = 0;
goto STR_result_valid_0; /* ABORT */
}
escaped = (pANTLR3_UINT8)db_escape_string((char *)val);
if (!escaped)
{
DPRINTF(E_LOG, L_DAAP, "Could not escape value\n");
$valid = 0;
goto STR_result_valid_0; /* ABORT */
}
val = escaped;
if (val[0] == '*')
{
op = '\%';
val[0] = '\%';
}
if (val[0] && val[1] && val[strlen((char *)val) - 1] == '*')
{
op = '\%';
val[strlen((char *)val) - 1] = '\%';
}
}
$result->append8($result, dqfm->db_col);
switch(op)
{
case '@':
case ':':
if (neg_op)
$result->append8($result, " <> ");
else
$result->append8($result, " = ");
break;
case '+':
if (neg_op)
$result->append8($result, " <= ");
else
$result->append8($result, " > ");
break;
case '-':
if (neg_op)
$result->append8($result, " >= ");
else
$result->append8($result, " < ");
break;
case '\%':
$result->append8($result, " LIKE ");
break;
default:
if (neg_op)
DPRINTF(E_LOG, L_DAAP, "Missing or unknown operator '\%c' in clause '\%s!\%c\%s'\n", op, field, op, val);
else
DPRINTF(E_LOG, L_DAAP, "Unknown operator '\%c' in clause '\%s\%c\%s'\n", op, field, op, val);
$valid = 0;
goto STR_result_valid_0; /* ABORT */
break;
}
if (!dqfm->as_int)
$result->append8($result, "'");
$result->append8($result, (const char *)val);
if (!dqfm->as_int)
$result->append8($result, "'");
/* For empty string value, we need to check against NULL too */
if ((*val == '\0') && (op == ':' || op == '@'))
{
if (neg_op)
$result->append8($result, " AND ");
else
$result->append8($result, " OR ");
$result->append8($result, dqfm->db_col);
if (neg_op)
$result->append8($result, " IS NOT NULL");
else
$result->append8($result, " IS NULL");
$result->append8($result, ")");
}
STR_result_valid_0: /* bail out label */
;
if (escaped)
free(escaped);
STR_out: /* get out of here */
;
}
;

View File

@@ -62,28 +62,6 @@ GPERF_FILES = \
GPERF_SRC = $(GPERF_FILES:.gperf=_hash.h)
ANTLR_GRAMMARS = \
RSP.g RSP2SQL.g \
DAAP.g DAAP2SQL.g \
SMARTPL.g SMARTPL2SQL.g
ANTLR_TOKENS = $(ANTLR_GRAMMARS:.g=.tokens)
ANTLR_DEPS = $(ANTLR_GRAMMARS:%.g=$(srcdir)/%.u)
ANTLR_SRC = \
RSPLexer.c RSPLexer.h RSPParser.c RSPParser.h \
RSP2SQL.c RSP2SQL.h \
DAAPLexer.c DAAPLexer.h DAAPParser.c DAAPParser.h \
DAAP2SQL.c DAAP2SQL.h \
SMARTPLLexer.c SMARTPLLexer.h SMARTPLParser.c SMARTPLParser.h \
SMARTPL2SQL.c SMARTPL2SQL.h
ANTLR_OBJECTS = \
RSPLexer.$(OBJEXT) RSPParser.$(OBJEXT) RSP2SQL.$(OBJEXT) \
DAAPLexer.$(OBJEXT) DAAPParser.$(OBJEXT) DAAP2SQL.$(OBJEXT) \
SMARTPLLexer.$(OBJEXT) SMARTPLParser.$(OBJEXT) SMARTPL2SQL.$(OBJEXT)
AM_CPPFLAGS += \
$(OWNTONE_CPPFLAGS) \
$(OWNTONE_OPTS_CPPFLAGS) \
@@ -152,42 +130,16 @@ owntone_SOURCES = main.c \
mxml-compat.h \
outputs/plist_wrap.h \
$(LIBWEBSOCKETS_SRC) \
$(GPERF_SRC) \
$(ANTLR_SRC)
$(GPERF_SRC)
# built by maintainers, and distributed. Clean with maintainer-clean
BUILT_SOURCES = \
$(GPERF_SRC) \
$(ANTLR_SRC) \
$(ANTLR_TOKENS) \
$(ANTLR_DEPS)
$(GPERF_SRC)
EXTRA_DIST = \
$(GPERF_FILES) \
$(ANTLR_GRAMMARS) \
$(ANTLR_TOKENS) \
$(ANTLR_DEPS)
# silence unused warnings from antlr generated files
$(ANTLR_OBJECTS): AM_CPPFLAGS += -Wno-unused
$(GPERF_FILES)
# gperf construction rules
%_hash.h: %.gperf
$(AM_V_GEN)$(GPERF) --output-file=$@ $<
# silent rules for antlr
antlr_verbose = $(antlr_verbose_@AM_V@)
antlr_verbose_ = $(antlr_verbose_@AM_DEFAULT_V@)
antlr_verbose_0 = @echo " GEN " $< "products";
# ANTLR grammar products
%.tokens %.c %Lexer.c %Parser.c %Lexer.h %Parser.h %.h: %.g
$(antlr_verbose)$(ANTLR) -Xconversiontimeout 30000 $(ANTLR_OPTIONS) -fo . $<
# ANTLR dependency files (bypass circular dependency of .g on .tokens)
%.u: %.g
$(AM_V_GEN)$(ANTLR) -depend -fo . $< > $@
$(AM_V_at)$(SED) -n -e '/^.*\.g[ ]*:\(.*\)/ { s//\1/;h;d; }' -e '/\.tokens.*:/ { p;d; }' -e '/:/ { G;s/\n/ /;p; }' $@ > $@-t
$(AM_V_at)mv $@-t $@
-include $(ANTLR_DEPS)

148
src/RSP.g
View File

@@ -1,148 +0,0 @@
/*
* Copyright (C) 2009-2010 Julien BLACHE <jb@jblache.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
grammar RSP;
options {
output = AST;
ASTLabelType = pANTLR3_BASE_TREE;
language = C;
}
query : expr NEWLINE? EOF -> expr
;
expr : aexpr (OR^ aexpr)*
;
aexpr : crit (AND^ crit)*
;
crit : LPAR expr RPAR -> expr
| strcrit
| intcrit
| datecrit
;
strcrit : FIELD strop STR -> ^(strop FIELD STR)
| FIELD NOT strop STR -> ^(NOT ^(strop FIELD STR))
;
strop : equal=EQUAL
| includes=INCLUDES
| startsw=STARTSW
| endsw=ENDSW
;
intcrit : FIELD intop INT -> ^(intop FIELD INT)
| FIELD NOT intop INT -> ^(NOT ^(intop FIELD INT))
;
intop : equal=EQUAL
| less=LESS
| greater=GREATER
| lte=LTE
| gte=GTE
;
datecrit: FIELD dateop datespec -> ^(dateop FIELD datespec)
;
dateop : before=BEFORE
| after=AFTER
;
datespec: dateref
| INT dateintval dateop dateref -> ^(dateop dateref INT dateintval)
;
dateref : date=DATE
| today=TODAY
;
dateintval
: day=DAY
| week=WEEK
| month=MONTH
| year=YEAR
;
QUOTE : '"';
LPAR : '(';
RPAR : ')';
AND : 'and';
OR : 'or';
NOT : '!';
/* Both string & int */
EQUAL : '=';
/* String */
INCLUDES: 'includes';
STARTSW : 'startswith';
ENDSW : 'endswith';
/* Int */
GREATER : '>';
LESS : '<';
GTE : '>=';
LTE : '<=';
/* Date */
BEFORE : 'before';
AFTER : 'after';
DAY : 'day' | 'days';
WEEK : 'week' | 'weeks';
MONTH : 'month' | 'months';
YEAR : 'year' | 'years';
TODAY : 'today';
NEWLINE : '\r'? '\n';
WS : (' ' | '\t') { $channel = HIDDEN; };
FIELD : 'a'..'z' ('a'..'z' | '_')* 'a'..'z';
INT : DIGIT19 DIGIT09*;
/* YYYY-MM-DD */
DATE : DIGIT19 DIGIT09 DIGIT09 DIGIT09 '-' ('0' DIGIT19 | '1' '0'..'2') '-' ('0' DIGIT19 | '1'..'2' DIGIT09 | '3' '0'..'1');
/*
Unescaping adapted from (ported to the C runtime)
<http://stackoverflow.com/questions/504402/how-to-handle-escape-sequences-in-string-literals-in-antlr-3>
*/
STR
@init{ pANTLR3_STRING unesc = GETTEXT()->factory->newRaw(GETTEXT()->factory); }
: QUOTE ( reg = ~('\\' | '"') { unesc->addc(unesc, reg); }
| esc = ESCAPED { unesc->appendS(unesc, GETTEXT()); } )+ QUOTE { SETTEXT(unesc); }
;
fragment
ESCAPED : '\\'
( '\\' { SETTEXT(GETTEXT()->factory->newStr8(GETTEXT()->factory, (pANTLR3_UINT8)"\\")); }
| '"' { SETTEXT(GETTEXT()->factory->newStr8(GETTEXT()->factory, (pANTLR3_UINT8)"\"")); }
)
;
fragment
DIGIT09 : '0'..'9';
fragment
DIGIT19 : '1'..'9';

View File

@@ -1,474 +0,0 @@
/*
* Copyright (C) 2009-2011 Julien BLACHE <jb@jblache.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
tree grammar RSP2SQL;
options {
tokenVocab = RSP;
ASTLabelType = pANTLR3_BASE_TREE;
language = C;
}
@header {
/* Needs #define _GNU_SOURCE for strptime() */
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdint.h>
#include "logger.h"
#include "db.h"
#include "misc.h"
#include "rsp_query.h"
}
@members {
#define RSP_TYPE_STRING 0
#define RSP_TYPE_INT 1
#define RSP_TYPE_DATE 2
struct rsp_query_field_map {
char *rsp_field;
int field_type;
/* RSP fields are named after the DB columns - or vice versa */
};
/* gperf static hash, rsp_query.gperf */
#include "rsp_query_hash.h"
}
query returns [ pANTLR3_STRING result ]
@init { $result = NULL; }
: e = expr
{
if (!$e.valid)
{
$result = NULL;
}
else
{
$result = $e.result->factory->newRaw($e.result->factory);
$result->append8($result, "(");
$result->appendS($result, $e.result);
$result->append8($result, ")");
}
}
;
expr returns [ pANTLR3_STRING result, int valid ]
@init { $result = NULL; $valid = 1; }
: ^(AND a = expr b = expr)
{
if (!$a.valid || !$b.valid)
{
$valid = 0;
}
else
{
$result = $a.result->factory->newRaw($a.result->factory);
$result->append8($result, "(");
$result->appendS($result, $a.result);
$result->append8($result, " AND ");
$result->appendS($result, $b.result);
$result->append8($result, ")");
}
}
| ^(OR a = expr b = expr)
{
if (!$a.valid || !$b.valid)
{
$valid = 0;
}
else
{
$result = $a.result->factory->newRaw($a.result->factory);
$result->append8($result, "(");
$result->appendS($result, $a.result);
$result->append8($result, " OR ");
$result->appendS($result, $b.result);
$result->append8($result, ")");
}
}
| c = strcrit
{
$valid = $c.valid;
$result = $c.result;
}
| ^(NOT c = strcrit)
{
if (!$c.valid || !$c.result)
{
$valid = 0;
}
else
{
$result = $c.result->factory->newRaw($c.result->factory);
$result->append8($result, "(NOT ");
$result->appendS($result, $c.result);
$result->append8($result, ")");
}
}
| i = intcrit
{
$valid = $i.valid;
$result = $i.result;
}
| ^(NOT i = intcrit)
{
if (!$i.valid || !$i.result)
{
$valid = 0;
}
else
{
$result = $i.result->factory->newRaw($i.result->factory);
$result->append8($result, "(NOT ");
$result->appendS($result, $i.result);
$result->append8($result, ")");
}
}
| d = datecrit
{
$valid = $d.valid;
$result = $d.result;
}
;
strcrit returns [ pANTLR3_STRING result, int valid ]
@init { $result = NULL; $valid = 1; }
: ^(o = strop f = FIELD s = STR)
{
char *op;
const struct rsp_query_field_map *rqfp;
pANTLR3_STRING field;
char *escaped;
ANTLR3_UINT32 optok;
escaped = NULL;
op = NULL;
optok = $o.op->getType($o.op);
switch (optok)
{
case EQUAL:
op = " = ";
break;
case INCLUDES:
case STARTSW:
case ENDSW:
op = " LIKE ";
break;
}
field = $f->getText($f);
/* Field lookup */
rqfp = rsp_query_field_lookup((char *)field->chars, strlen((char *)field->chars));
if (!rqfp)
{
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a valid field in queries\n", field->chars);
$valid = 0;
goto strcrit_valid_0; /* ABORT */
}
/* Check field type */
if (rqfp->field_type != RSP_TYPE_STRING)
{
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a string field\n", field->chars);
$valid = 0;
goto strcrit_valid_0; /* ABORT */
}
escaped = db_escape_string((char *)$s->getText($s)->chars);
if (!escaped)
{
DPRINTF(E_LOG, L_RSP, "Could not escape value\n");
$valid = 0;
goto strcrit_valid_0; /* ABORT */
}
$result = field->factory->newRaw(field->factory);
$result->append8($result, "f.");
$result->appendS($result, field);
$result->append8($result, op);
$result->append8($result, "'");
if ((optok == INCLUDES) || (optok == STARTSW))
$result->append8($result, "\%");
$result->append8($result, escaped);
if ((optok == INCLUDES) || (optok == ENDSW))
$result->append8($result, "\%");
$result->append8($result, "'");
strcrit_valid_0:
;
if (escaped)
free(escaped);
}
;
strop returns [ pANTLR3_COMMON_TOKEN op ]
@init { $op = NULL; }
: n = EQUAL
{ $op = $n->getToken($n); }
| n = INCLUDES
{ $op = $n->getToken($n); }
| n = STARTSW
{ $op = $n->getToken($n); }
| n = ENDSW
{ $op = $n->getToken($n); }
;
intcrit returns [ pANTLR3_STRING result, int valid ]
@init { $result = NULL; $valid = 1; }
: ^(o = intop f = FIELD i = INT)
{
char *op;
const struct rsp_query_field_map *rqfp;
pANTLR3_STRING field;
op = NULL;
switch ($o.op->getType($o.op))
{
case EQUAL:
op = " = ";
break;
case LESS:
op = " < ";
break;
case GREATER:
op = " > ";
break;
case LTE:
op = " <= ";
break;
case GTE:
op = " >= ";
break;
}
field = $f->getText($f);
/* Field lookup */
rqfp = rsp_query_field_lookup((char *)field->chars, strlen((char *)field->chars));
if (!rqfp)
{
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a valid field in queries\n", field->chars);
$valid = 0;
goto intcrit_valid_0; /* ABORT */
}
/* Check field type */
if (rqfp->field_type != RSP_TYPE_INT)
{
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not an integer field\n", field->chars);
$valid = 0;
goto intcrit_valid_0; /* ABORT */
}
$result = field->factory->newRaw(field->factory);
$result->append8($result, "f.");
$result->appendS($result, field);
$result->append8($result, op);
$result->appendS($result, $i->getText($i));
intcrit_valid_0:
;
}
;
intop returns [ pANTLR3_COMMON_TOKEN op ]
@init { $op = NULL; }
: n = EQUAL
{ $op = $n->getToken($n); }
| n = LESS
{ $op = $n->getToken($n); }
| n = GREATER
{ $op = $n->getToken($n); }
| n = LTE
{ $op = $n->getToken($n); }
| n = GTE
{ $op = $n->getToken($n); }
;
datecrit returns [ pANTLR3_STRING result, int valid ]
@init { $result = NULL; $valid = 1; }
: ^(o = dateop f = FIELD d = datespec)
{
char *op;
const struct rsp_query_field_map *rqfp;
pANTLR3_STRING field;
char buf[32];
int ret;
op = NULL;
switch ($o.op->getType($o.op))
{
case BEFORE:
op = " < ";
break;
case AFTER:
op = " > ";
break;
}
field = $f->getText($f);
/* Field lookup */
rqfp = rsp_query_field_lookup((char *)field->chars, strlen((char *)field->chars));
if (!rqfp)
{
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a valid field in queries\n", field->chars);
$valid = 0;
goto datecrit_valid_0; /* ABORT */
}
/* Check field type */
if (rqfp->field_type != RSP_TYPE_DATE)
{
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a date field\n", field->chars);
$valid = 0;
goto datecrit_valid_0; /* ABORT */
}
ret = snprintf(buf, sizeof(buf), "\%ld", $d.date);
if ((ret < 0) || (ret >= sizeof(buf)))
{
DPRINTF(E_LOG, L_RSP, "Date \%ld too large for buffer, oops!\n", $d.date);
$valid = 0;
goto datecrit_valid_0; /* ABORT */
}
$result = field->factory->newRaw(field->factory);
$result->append8($result, "f.");
$result->appendS($result, field);
$result->append8($result, op);
$result->append8($result, buf);
datecrit_valid_0:
;
}
;
dateop returns [ pANTLR3_COMMON_TOKEN op ]
@init { $op = NULL; }
: n = BEFORE
{ $op = $n->getToken($n); }
| n = AFTER
{ $op = $n->getToken($n); }
;
datespec returns [ time_t date, int valid ]
@init { $date = 0; $valid = 1; }
: r = dateref
{
if (!$r.valid)
$valid = 0;
else
$date = $r.date;
}
| ^(o = dateop r = dateref m = INT i = dateintval)
{
int32_t val;
int ret;
if (!$r.valid || !$i.valid)
{
$valid = 0;
goto datespec_valid_0; /* ABORT */
}
ret = safe_atoi32((char *)$m->getText($m)->chars, &val);
if (ret < 0)
{
DPRINTF(E_LOG, L_RSP, "Could not convert '\%s' to integer\n", (char *)$m->getText($m));
$valid = 0;
goto datespec_valid_0; /* ABORT */
}
switch ($o.op->getType($o.op))
{
case BEFORE:
$date = $r.date - (val * $i.period);
break;
case AFTER:
$date = $r.date + (val * $i.period);
break;
}
datespec_valid_0:
;
}
;
dateref returns [ time_t date, int valid ]
@init { $date = 0; $valid = 1; }
: n = DATE
{
struct tm tm;
char *ret;
ret = strptime((char *)$n->getText($n), "\%Y-\%m-\%d", &tm);
if (!ret)
{
DPRINTF(E_LOG, L_RSP, "Date '\%s' could not be interpreted\n", (char *)$n->getText($n));
$valid = 0;
goto dateref_valid_0; /* ABORT */
}
else
{
if (*ret != '\0')
DPRINTF(E_LOG, L_RSP, "Garbage at end of date '\%s' ?!\n", (char *)$n->getText($n));
$date = mktime(&tm);
if ($date == (time_t) -1)
{
DPRINTF(E_LOG, L_RSP, "Date '\%s' could not be converted to an epoch\n", (char *)$n->getText($n));
$valid = 0;
goto dateref_valid_0; /* ABORT */
}
}
dateref_valid_0:
;
}
| TODAY
{ $date = time(NULL); }
;
dateintval returns [ time_t period, int valid ]
@init { $period = 0; $valid = 1; }
: DAY
{ $period = 24 * 60 * 60; }
| WEEK
{ $period = 7 * 24 * 60 * 60; }
| MONTH
{ $period = 30 * 24 * 60 * 60; }
| YEAR
{ $period = 365 * 24 * 60 * 60; }
;

View File

@@ -1,240 +0,0 @@
/*
* Copyright (C) 2015 Christian Meffert <christian.meffert@googlemail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
grammar SMARTPL;
options {
output = AST;
ASTLabelType = pANTLR3_BASE_TREE;
language = C;
}
playlist : STR '{' expression '}' EOF
;
expression : aexpr (OR^ aexpr)* (HAVING^ aexpr)? (ORDERBY^ orderexpr)? (LIMIT^ limitexpr)?
;
orderexpr : ordertag SORTDIR
;
ordertag : STRTAG
| INTTAG
| DATETAG
| ENUMTAG
| RANDOMTAG
| (XXX)? NeverUsedRule
;
NeverUsedRule: /* antlr3 seems to have a problem with ordertag, introducing the NeverUsedRule fixes it. See: https://stackoverflow.com/questions/20057063/follow-set-in-is-undefined-in-generated-parser */
;
XXX : 'XXX' /**/
;
limitexpr : INT
;
aexpr : nexpr (AND^ nexpr)*
;
nexpr : NOT^ crit
| crit
;
crit : LPAR expression RPAR -> expression
| STRTAG (INCLUDES|IS|STARTSWITH) STR
| INTTAG INTBOOL INT
| DATETAG (AFTER|BEFORE) dateval
| ENUMTAG IS ENUMVAL
| GROUPTAG INTBOOL INT
;
dateval : DATE
| interval BEFORE DATE
| interval AFTER DATE
| interval AGO
;
interval : INT DATINTERVAL
;
STRTAG : 'artist'
| 'album_artist'
| 'album'
| 'title'
| 'genre'
| 'composer'
| 'path'
| 'type'
| 'grouping'
| 'artist_id'
| 'album_id'
| 'songartistid'
| 'songalbumid'
| 'codectype'
| 'comment'
;
INTTAG : 'play_count'
| 'skip_count'
| 'rating'
| 'year'
| 'compilation'
| 'track'
| 'disc'
| 'bitrate'
| 'bits_per_sample'
| 'samplerate'
| 'song_length'
| 'usermark'
;
DATETAG : 'time_added'
| 'time_modified'
| 'time_played'
| 'time_skipped'
| 'date_released'
;
ENUMTAG : 'data_kind'
| 'media_kind'
| 'scan_kind'
;
GROUPTAG : 'track_count'
| 'album_count'
;
RANDOMTAG : 'random'
;
INCLUDES : 'includes'
;
IS : 'is'
;
STARTSWITH : 'starts with'
;
INTBOOL : (GREATER|GREATEREQUAL|LESS|LESSEQUAL|EQUAL)
;
fragment
GREATER : '>'
;
fragment
GREATEREQUAL: '>='
;
fragment
LESS : '<'
;
fragment
LESSEQUAL : '<='
;
fragment
EQUAL : '='
;
AFTER : 'after'
;
BEFORE : 'before'
;
AGO : 'ago'
;
AND : 'AND'
| 'and'
;
OR : 'OR'
| 'or'
;
NOT : 'NOT'
| 'not'
;
LPAR : '('
;
RPAR : ')'
;
DATE : ('0'..'9')('0'..'9')('0'..'9')('0'..'9')'-'('0'..'1')('0'..'9')'-'('0'..'3')('0'..'9')
| 'today'
| 'yesterday'
| 'last week'
| 'last month'
| 'last year'
;
DATINTERVAL : 'days'
| 'weeks'
| 'months'
| 'years'
;
ENUMVAL : 'music'
| 'movie'
| 'podcast'
| 'audiobook'
| 'tvshow'
| 'file'
| 'url'
| 'spotify'
| 'pipe'
| 'files'
| 'rss'
;
ORDERBY : 'order by'
| 'ORDER BY'
;
SORTDIR : 'asc'
| 'ASC'
| 'desc'
| 'DESC'
;
LIMIT : 'limit'
| 'LIMIT'
;
HAVING : 'having'
| 'HAVING'
;
STR : '"' ~('"')+ '"'
;
INT : ('0'..'9')+
;
WHITESPACE : ('\t'|' '|'\r'|'\n'|'\u000C') { $channel = HIDDEN; }
;

View File

@@ -1,427 +0,0 @@
/*
* Copyright (C) 2015 Christian Meffert <christian.meffert@googlemail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
tree grammar SMARTPL2SQL;
options {
tokenVocab = SMARTPL;
ASTLabelType = pANTLR3_BASE_TREE;
language = C;
}
@header {
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <time.h>
#include <sqlite3.h>
#include "logger.h"
#include "db.h"
}
@members {
static void append_date(pANTLR3_STRING result, const char *datval, char beforeorafter, const char *interval)
{
if (strcmp((char *)datval, "today") == 0)
{
result->append8(result, "strftime('\%s', datetime('now', 'start of day'");
}
else if (strcmp((char *)datval, "yesterday") == 0)
{
result->append8(result, "strftime('\%s', datetime('now', 'start of day', '-1 day'");
}
else if (strcmp((char *)datval, "last week") == 0)
{
result->append8(result, "strftime('\%s', datetime('now', 'start of day', 'weekday 0', '-13 days'");
}
else if (strcmp((char *)datval, "last month") == 0)
{
result->append8(result, "strftime('\%s', datetime('now', 'start of month', '-1 month'");
}
else if (strcmp((char *)datval, "last year") == 0)
{
result->append8(result, "strftime('\%s', datetime('now', 'start of year', '-1 year'");
}
else
{
result->append8(result, "strftime('\%s', datetime(\'");
result->append8(result, datval);
result->append8(result, "\'");
}
if (beforeorafter)
{
result->append8(result, ", '");
result->addc(result, beforeorafter);
result->append8(result, interval);
result->addc(result, '\'');
}
result->append8(result, ", 'utc'))");
}
}
playlist returns [ pANTLR3_STRING title, pANTLR3_STRING query, pANTLR3_STRING orderby, pANTLR3_STRING having, int limit ]
@init { $title = NULL; $query = NULL; $orderby = NULL; $having = NULL; $limit = -1; }
: STR '{' e = expression '}'
{
pANTLR3_UINT8 val;
val = $STR.text->toUTF8($STR.text)->chars;
val++;
val[strlen((const char *)val) - 1] = '\0';
$title = $STR.text->factory->newRaw($STR.text->factory);
$title->append8($title, (const char *)val);
$query = $e.result->factory->newRaw($e.result->factory);
$query->append8($query, "(");
$query->appendS($query, $e.result);
$query->append8($query, ")");
$limit = $e.limit;
$orderby = $e.result->factory->newRaw($e.result->factory);
$orderby->appendS($orderby, $e.orderby);
$having = $e.result->factory->newRaw($e.result->factory);
$having->appendS($having, $e.having);
}
;
expression returns [ pANTLR3_STRING result, pANTLR3_STRING orderby, pANTLR3_STRING having, int limit ]
@init { $result = NULL; $orderby = NULL; $having = NULL; $limit = -1; }
: ^(LIMIT a = expression INT)
{
$result = $a.result->factory->newRaw($a.result->factory);
$result->appendS($result, $a.result);
$having = $a.result->factory->newRaw($a.result->factory);
$having->appendS($having, $a.having);
$orderby = $a.result->factory->newRaw($a.result->factory);
$orderby->appendS($orderby, $a.orderby);
$limit = atoi((const char *)$INT.text->chars);
}
| ^(ORDERBY a = expression o = ordertag SORTDIR)
{
$result = $a.result->factory->newRaw($a.result->factory);
$result->appendS($result, $a.result);
$having = $a.result->factory->newRaw($a.result->factory);
$having->appendS($having, $a.having);
$orderby = $o.result->factory->newRaw($o.result->factory);
$orderby->appendS($orderby, $o.result);
$orderby->append8($orderby, " ");
$orderby->appendS($orderby, $SORTDIR.text->toUTF8($SORTDIR.text));
}
| ^(HAVING a = expression b = expression)
{
$result = $a.result->factory->newRaw($a.result->factory);
$result->appendS($result, $a.result);
$having = $b.result->factory->newRaw($b.result->factory);
$having->appendS($having, $b.result);
}
| ^(NOT a = expression)
{
$result = $a.result->factory->newRaw($a.result->factory);
$result->append8($result, "NOT(");
$result->appendS($result, $a.result);
$result->append8($result, ")");
}
| ^(AND a = expression b = expression)
{
$result = $a.result->factory->newRaw($a.result->factory);
$result->append8($result, "(");
$result->appendS($result, $a.result);
$result->append8($result, " AND ");
$result->appendS($result, $b.result);
$result->append8($result, ")");
}
| ^(OR a = expression b = expression)
{
$result = $a.result->factory->newRaw($a.result->factory);
$result->append8($result, "(");
$result->appendS($result, $a.result);
$result->append8($result, " OR ");
$result->appendS($result, $b.result);
$result->append8($result, ")");
}
| STRTAG INCLUDES STR
{
pANTLR3_UINT8 val;
char *tmp;
val = $STR.text->toUTF8($STR.text)->chars;
val++;
val[strlen((const char *)val) - 1] = '\0';
tmp = sqlite3_mprintf("\%q", (const char *)val);
$result = $STR.text->factory->newRaw($STR.text->factory);
$result->append8($result, "f.");
$result->appendS($result, $STRTAG.text->toUTF8($STRTAG.text));
$result->append8($result, " LIKE '\%");
$result->append8($result, tmp);
$result->append8($result, "\%'");
sqlite3_free(tmp);
}
| STRTAG IS STR
{
pANTLR3_UINT8 val;
char *tmp;
val = $STR.text->toUTF8($STR.text)->chars;
val++;
val[strlen((const char *)val) - 1] = '\0';
tmp = sqlite3_mprintf("\%q", (const char *)val);
$result = $STR.text->factory->newRaw($STR.text->factory);
$result->append8($result, "f.");
$result->appendS($result, $STRTAG.text->toUTF8($STRTAG.text));
$result->append8($result, " LIKE '");
$result->append8($result, tmp);
$result->append8($result, "'");
sqlite3_free(tmp);
}
| STRTAG STARTSWITH STR
{
pANTLR3_UINT8 val;
char *tmp;
val = $STR.text->toUTF8($STR.text)->chars;
val++;
val[strlen((const char *)val) - 1] = '\0';
tmp = sqlite3_mprintf("\%q", (const char *)val);
$result = $STR.text->factory->newRaw($STR.text->factory);
$result->append8($result, "f.");
$result->appendS($result, $STRTAG.text->toUTF8($STRTAG.text));
$result->append8($result, " LIKE '");
$result->append8($result, tmp);
$result->append8($result, "\%'");
sqlite3_free(tmp);
}
| INTTAG INTBOOL INT
{
$result = $INTTAG.text->factory->newRaw($INTTAG.text->factory);
$result->append8($result, "f.");
$result->appendS($result, $INTTAG.text->toUTF8($INTTAG.text));
$result->append8($result, " ");
$result->appendS($result, $INTBOOL.text->toUTF8($INTBOOL.text));
$result->append8($result, " ");
$result->appendS($result, $INT.text->toUTF8($INT.text));
}
| DATETAG AFTER dateval
{
$result = $DATETAG.text->factory->newRaw($DATETAG.text->factory);
$result->append8($result, "f.");
$result->appendS($result, $DATETAG.text->toUTF8($DATETAG.text));
$result->append8($result, " > ");
$result->append8($result, (const char*)$dateval.result->chars);
}
| DATETAG BEFORE dateval
{
$result = $DATETAG.text->factory->newRaw($DATETAG.text->factory);
$result->append8($result, "f.");
$result->appendS($result, $DATETAG.text->toUTF8($DATETAG.text));
$result->append8($result, " < ");
$result->append8($result, (const char*)$dateval.result->chars);
}
| ENUMTAG IS ENUMVAL
{
pANTLR3_UINT8 tag;
pANTLR3_UINT8 val;
char str[20];
sprintf(str, "1=1");
tag = $ENUMTAG.text->chars;
val = $ENUMVAL.text->chars;
if (strcmp((char *)tag, "media_kind") == 0)
{
if (strcmp((char *)val, "music") == 0)
{
sprintf(str, "f.media_kind = \%d", MEDIA_KIND_MUSIC);
}
else if (strcmp((char *)val, "movie") == 0)
{
sprintf(str, "f.media_kind = \%d", MEDIA_KIND_MOVIE);
}
else if (strcmp((char *)val, "podcast") == 0)
{
sprintf(str, "f.media_kind = \%d", MEDIA_KIND_PODCAST);
}
else if (strcmp((char *)val, "audiobook") == 0)
{
sprintf(str, "f.media_kind = \%d", MEDIA_KIND_AUDIOBOOK);
}
else if (strcmp((char *)val, "tvshow") == 0)
{
sprintf(str, "f.media_kind = \%d", MEDIA_KIND_TVSHOW);
}
}
else if (strcmp((char *)tag, "data_kind") == 0)
{
if (strcmp((char *)val, "file") == 0)
{
sprintf(str, "f.data_kind = \%d", DATA_KIND_FILE);
}
else if (strcmp((char *)val, "url") == 0)
{
sprintf(str, "f.data_kind = \%d", DATA_KIND_HTTP);
}
else if (strcmp((char *)val, "spotify") == 0)
{
sprintf(str, "f.data_kind = \%d", DATA_KIND_SPOTIFY);
}
else if (strcmp((char *)val, "pipe") == 0)
{
sprintf(str, "f.data_kind = \%d", DATA_KIND_PIPE);
}
}
else if (strcmp((char *)tag, "scan_kind") == 0)
{
if (strcmp((char *)val, "files") == 0)
{
sprintf(str, "f.scan_kind = \%d", SCAN_KIND_FILES);
}
else if (strcmp((char *)val, "spotify") == 0)
{
sprintf(str, "f.scan_kind = \%d", SCAN_KIND_SPOTIFY);
}
else if (strcmp((char *)val, "rss") == 0)
{
sprintf(str, "f.scan_kind = \%d", SCAN_KIND_RSS);
}
}
$result = $ENUMTAG.text->factory->newRaw($ENUMTAG.text->factory);
$result->append8($result, str);
}
| GROUPTAG INTBOOL INT
{
$result = $GROUPTAG.text->factory->newRaw($GROUPTAG.text->factory);
$result->appendS($result, $GROUPTAG.text->toUTF8($GROUPTAG.text));
$result->append8($result, " ");
$result->appendS($result, $INTBOOL.text->toUTF8($INTBOOL.text));
$result->append8($result, " ");
$result->appendS($result, $INT.text->toUTF8($INT.text));
}
;
ordertag returns [ pANTLR3_STRING result ]
@init { $result = NULL; }
: STRTAG
{
$result = $STRTAG.text->factory->newRaw($STRTAG.text->factory);
$result->append8($result, "f.");
$result->appendS($result, $STRTAG.text->toUTF8($STRTAG.text));
}
| INTTAG
{
$result = $INTTAG.text->factory->newRaw($INTTAG.text->factory);
$result->append8($result, "f.");
$result->appendS($result, $INTTAG.text->toUTF8($INTTAG.text));
}
| DATETAG
{
$result = $DATETAG.text->factory->newRaw($DATETAG.text->factory);
$result->append8($result, "f.");
$result->appendS($result, $DATETAG.text->toUTF8($DATETAG.text));
}
| ENUMTAG
{
$result = $ENUMTAG.text->factory->newRaw($ENUMTAG.text->factory);
$result->append8($result, "f.");
$result->appendS($result, $ENUMTAG.text->toUTF8($ENUMTAG.text));
}
| RANDOMTAG
{
$result = $RANDOMTAG.text->factory->newRaw($RANDOMTAG.text->factory);
$result->append8($result, "random()");
}
;
dateval returns [ pANTLR3_STRING result ]
@init { $result = NULL; }
: DATE
{
pANTLR3_UINT8 datval;
datval = $DATE.text->chars;
$result = $DATE.text->factory->newRaw($DATE.text->factory);
append_date($result, (const char *)datval, 0, NULL);
}
| interval BEFORE DATE
{
$result = $DATE.text->factory->newRaw($DATE.text->factory);
append_date($result, (const char *)$DATE.text->chars, '-', (const char *)$interval.result->chars);
}
| interval AFTER DATE
{
$result = $DATE.text->factory->newRaw($DATE.text->factory);
append_date($result, (const char *)$DATE.text->chars, '+', (const char *)$interval.result->chars);
}
| interval AGO
{
$result = $AGO.text->factory->newRaw($AGO.text->factory);
append_date($result, "today", '-', (const char *)$interval.result->chars);
}
;
interval returns [ pANTLR3_STRING result ]
@init { $result = NULL; }
: INT DATINTERVAL
{
pANTLR3_UINT8 interval;
int intval;
char buf[25];
$result = $DATINTERVAL.text->factory->newRaw($DATINTERVAL.text->factory);
// SQL doesnt have a modifer for 'week' but for day/hr/min/sec/month/yr
interval = $DATINTERVAL.text->chars;
if (strcmp((char *)interval, "weeks") == 0)
{
intval = atoi((const char *)$INT.text->chars) * 7;
snprintf(buf, sizeof(buf), "\%d days", intval);
$result->append8($result, buf);
}
else
{
$result->append8($result, (const char *)$INT.text->chars);
$result->append8($result, " ");
$result->append8($result, (const char *)$DATINTERVAL.text->chars);
}
return $result;
}
;

View File

@@ -29,129 +29,9 @@
#include "misc.h"
#include "daap_query.h"
#include "DAAPLexer.h"
#include "DAAPParser.h"
#include "DAAP2SQL.h"
char *
daap_query_parse_sql(const char *daap_query)
{
/* Input DAAP query, fed to the lexer */
pANTLR3_INPUT_STREAM query;
/* Lexer and the resulting token stream, fed to the parser */
pDAAPLexer lxr;
pANTLR3_COMMON_TOKEN_STREAM tkstream;
/* Parser and the resulting AST, fed to the tree parser */
pDAAPParser psr;
DAAPParser_query_return qtree;
pANTLR3_COMMON_TREE_NODE_STREAM nodes;
/* Tree parser and the resulting SQL query string */
pDAAP2SQL sqlconv;
pANTLR3_STRING sql;
char *ret = NULL;
if (!daap_query)
{
DPRINTF(E_LOG, L_DAAP, "DAAP query is null\n");
return NULL;
}
DPRINTF(E_DBG, L_DAAP, "Trying DAAP query -%s-\n", daap_query);
#if ANTLR3C_NEW_INPUT
query = antlr3StringStreamNew ((pANTLR3_UINT8)daap_query, ANTLR3_ENC_8BIT, (ANTLR3_UINT64)strlen(daap_query), (pANTLR3_UINT8)"DAAP query");
#else
query = antlr3NewAsciiStringInPlaceStream ((pANTLR3_UINT8)daap_query, (ANTLR3_UINT64)strlen(daap_query), (pANTLR3_UINT8)"DAAP query");
#endif
if (!query)
{
DPRINTF(E_DBG, L_DAAP, "Could not create input stream\n");
return NULL;
}
lxr = DAAPLexerNew(query);
if (!lxr)
{
DPRINTF(E_DBG, L_DAAP, "Could not create DAAP lexer\n");
goto lxr_fail;
}
tkstream = antlr3CommonTokenStreamSourceNew(ANTLR3_SIZE_HINT, TOKENSOURCE(lxr));
if (!tkstream)
{
DPRINTF(E_DBG, L_DAAP, "Could not create DAAP token stream\n");
goto tkstream_fail;
}
psr = DAAPParserNew(tkstream);
if (!psr)
{
DPRINTF(E_DBG, L_DAAP, "Could not create DAAP parser\n");
goto psr_fail;
}
qtree = psr->query(psr);
/* Check for parser errors */
if (psr->pParser->rec->state->errorCount > 0)
{
DPRINTF(E_LOG, L_DAAP, "DAAP query parser terminated with %d errors\n", psr->pParser->rec->state->errorCount);
goto psr_error;
}
DPRINTF(E_SPAM, L_DAAP, "DAAP query AST:\n\t%s\n", qtree.tree->toStringTree(qtree.tree)->chars);
nodes = antlr3CommonTreeNodeStreamNewTree(qtree.tree, ANTLR3_SIZE_HINT);
if (!nodes)
{
DPRINTF(E_DBG, L_DAAP, "Could not create node stream\n");
goto psr_error;
}
sqlconv = DAAP2SQLNew(nodes);
if (!sqlconv)
{
DPRINTF(E_DBG, L_DAAP, "Could not create SQL converter\n");
goto sql_fail;
}
sql = sqlconv->query(sqlconv);
/* Check for tree parser errors */
if (sqlconv->pTreeParser->rec->state->errorCount > 0)
{
DPRINTF(E_LOG, L_DAAP, "DAAP query tree parser terminated with %d errors\n", sqlconv->pTreeParser->rec->state->errorCount);
goto sql_error;
}
if (sql)
{
DPRINTF(E_DBG, L_DAAP, "DAAP SQL query: -%s-\n", sql->chars);
ret = strdup((char *)sql->chars);
}
else
{
DPRINTF(E_LOG, L_DAAP, "Invalid DAAP query -%s-\n", daap_query);
ret = NULL;
}
sql_error:
sqlconv->free(sqlconv);
sql_fail:
nodes->free(nodes);
psr_error:
psr->free(psr);
psr_fail:
tkstream->free(tkstream);
tkstream_fail:
lxr->free(lxr);
lxr_fail:
query->close(query);
return ret;
return NULL;
}

View File

@@ -29,122 +29,8 @@
#include "misc.h"
#include "rsp_query.h"
#include "RSPLexer.h"
#include "RSPParser.h"
#include "RSP2SQL.h"
char *
rsp_query_parse_sql(const char *rsp_query)
{
/* Input RSP query, fed to the lexer */
pANTLR3_INPUT_STREAM query;
/* Lexer and the resulting token stream, fed to the parser */
pRSPLexer lxr;
pANTLR3_COMMON_TOKEN_STREAM tkstream;
/* Parser and the resulting AST, fed to the tree parser */
pRSPParser psr;
RSPParser_query_return qtree;
pANTLR3_COMMON_TREE_NODE_STREAM nodes;
/* Tree parser and the resulting SQL query string */
pRSP2SQL sqlconv;
pANTLR3_STRING sql;
char *ret = NULL;
DPRINTF(E_DBG, L_RSP, "Trying RSP query -%s-\n", rsp_query);
#if ANTLR3C_NEW_INPUT
query = antlr3StringStreamNew ((pANTLR3_UINT8)rsp_query, ANTLR3_ENC_8BIT, (ANTLR3_UINT64)strlen(rsp_query), (pANTLR3_UINT8)"RSP query");
#else
query = antlr3NewAsciiStringInPlaceStream ((pANTLR3_UINT8)rsp_query, (ANTLR3_UINT64)strlen(rsp_query), (pANTLR3_UINT8)"RSP query");
#endif
if (!query)
{
DPRINTF(E_DBG, L_RSP, "Could not create input stream\n");
return NULL;
}
lxr = RSPLexerNew(query);
if (!lxr)
{
DPRINTF(E_DBG, L_RSP, "Could not create RSP lexer\n");
goto lxr_fail;
}
tkstream = antlr3CommonTokenStreamSourceNew(ANTLR3_SIZE_HINT, TOKENSOURCE(lxr));
if (!tkstream)
{
DPRINTF(E_DBG, L_RSP, "Could not create RSP token stream\n");
goto tkstream_fail;
}
psr = RSPParserNew(tkstream);
if (!psr)
{
DPRINTF(E_DBG, L_RSP, "Could not create RSP parser\n");
goto psr_fail;
}
qtree = psr->query(psr);
/* Check for parser errors */
if (psr->pParser->rec->state->errorCount > 0)
{
DPRINTF(E_LOG, L_RSP, "RSP query parser terminated with %d errors\n", psr->pParser->rec->state->errorCount);
goto psr_error;
}
DPRINTF(E_SPAM, L_RSP, "RSP query AST:\n\t%s\n", qtree.tree->toStringTree(qtree.tree)->chars);
nodes = antlr3CommonTreeNodeStreamNewTree(qtree.tree, ANTLR3_SIZE_HINT);
if (!nodes)
{
DPRINTF(E_DBG, L_RSP, "Could not create node stream\n");
goto psr_error;
}
sqlconv = RSP2SQLNew(nodes);
if (!sqlconv)
{
DPRINTF(E_DBG, L_RSP, "Could not create SQL converter\n");
goto sql_fail;
}
sql = sqlconv->query(sqlconv);
/* Check for tree parser errors */
if (sqlconv->pTreeParser->rec->state->errorCount > 0)
{
DPRINTF(E_LOG, L_RSP, "RSP query tree parser terminated with %d errors\n", sqlconv->pTreeParser->rec->state->errorCount);
goto sql_error;
}
if (sql)
{
ret = strdup((char *)sql->chars);
}
else
{
DPRINTF(E_LOG, L_RSP, "Invalid RSP query\n");
ret = NULL;
}
sql_error:
sqlconv->free(sqlconv);
sql_fail:
nodes->free(nodes);
psr_error:
psr->free(psr);
psr_fail:
tkstream->free(tkstream);
tkstream_fail:
lxr->free(lxr);
lxr_fail:
query->close(query);
return ret;
return NULL;
}

View File

@@ -35,183 +35,17 @@
#include "logger.h"
#include "misc.h"
#include "SMARTPLLexer.h"
#include "SMARTPLParser.h"
#include "SMARTPL2SQL.h"
static int
parse_input(struct smartpl *smartpl, pANTLR3_INPUT_STREAM input)
{
pSMARTPLLexer lxr;
pANTLR3_COMMON_TOKEN_STREAM tstream;
pSMARTPLParser psr;
SMARTPLParser_playlist_return qtree;
pANTLR3_COMMON_TREE_NODE_STREAM nodes;
pSMARTPL2SQL sqlconv;
SMARTPL2SQL_playlist_return plreturn;
int ret;
lxr = SMARTPLLexerNew(input);
// Need to check for errors
if (lxr == NULL)
{
DPRINTF(E_LOG, L_SCAN, "Could not create SMARTPL lexer\n");
ret = -1;
goto lxr_fail;
}
tstream = antlr3CommonTokenStreamSourceNew(ANTLR3_SIZE_HINT, TOKENSOURCE(lxr));
if (tstream == NULL)
{
DPRINTF(E_LOG, L_SCAN, "Could not create SMARTPL token stream\n");
ret = -1;
goto tkstream_fail;
}
// Finally, now that we have our lexer constructed, we can create the parser
psr = SMARTPLParserNew(tstream); // CParserNew is generated by ANTLR3
if (psr == NULL)
{
DPRINTF(E_LOG, L_SCAN, "Could not create SMARTPL parser\n");
ret = -1;
goto psr_fail;
}
qtree = psr->playlist(psr);
/* Check for parser errors */
if (psr->pParser->rec->state->errorCount > 0)
{
DPRINTF(E_LOG, L_SCAN, "SMARTPL query parser terminated with %d errors\n", psr->pParser->rec->state->errorCount);
ret = -1;
goto psr_error;
}
DPRINTF(E_DBG, L_SCAN, "SMARTPL query AST:\n\t%s\n", qtree.tree->toStringTree(qtree.tree)->chars);
nodes = antlr3CommonTreeNodeStreamNewTree(qtree.tree, ANTLR3_SIZE_HINT);
if (!nodes)
{
DPRINTF(E_LOG, L_SCAN, "Could not create node stream\n");
ret = -1;
goto psr_error;
}
sqlconv = SMARTPL2SQLNew(nodes);
if (!sqlconv)
{
DPRINTF(E_LOG, L_SCAN, "Could not create SQL converter\n");
ret = -1;
goto sql_fail;
}
plreturn = sqlconv->playlist(sqlconv);
/* Check for tree parser errors */
if (sqlconv->pTreeParser->rec->state->errorCount > 0)
{
DPRINTF(E_LOG, L_SCAN, "SMARTPL query tree parser terminated with %d errors\n", sqlconv->pTreeParser->rec->state->errorCount);
ret = -1;
goto sql_error;
}
if (plreturn.title && plreturn.query)
{
DPRINTF(E_DBG, L_SCAN, "SMARTPL SQL title '%s', query: '%s', having: '%s', order by: '%s', limit: %d \n", plreturn.title->chars, plreturn.query->chars, plreturn.having->chars, plreturn.orderby->chars, plreturn.limit);
if (smartpl->title)
free(smartpl->title);
smartpl->title = strdup((char *)plreturn.title->chars);
if (smartpl->query_where)
free(smartpl->query_where);
smartpl->query_where = strdup((char *)plreturn.query->chars);
if (smartpl->having)
free(smartpl->having);
smartpl->having = safe_strdup((char *)plreturn.having->chars);
if (smartpl->order)
free(smartpl->order);
smartpl->order = safe_strdup((char *)plreturn.orderby->chars);
smartpl->limit = plreturn.limit;
ret = 0;
}
else
{
DPRINTF(E_LOG, L_SCAN, "Invalid SMARTPL query\n");
ret = -1;
}
sql_error:
sqlconv->free(sqlconv);
sql_fail:
nodes->free(nodes);
psr_error:
psr->free(psr);
psr_fail:
tstream->free(tstream);
tkstream_fail:
lxr->free(lxr);
lxr_fail:
return ret;
}
int
smartpl_query_parse_file(struct smartpl *smartpl, const char *file)
{
pANTLR3_INPUT_STREAM input;
int ret;
#if ANTLR3C_NEW_INPUT
input = antlr3FileStreamNew((pANTLR3_UINT8) file, ANTLR3_ENC_8BIT);
#else
input = antlr3AsciiFileStreamNew((pANTLR3_UINT8) file);
#endif
// The input will be created successfully, providing that there is enough memory and the file exists etc
if (input == NULL)
{
DPRINTF(E_LOG, L_SCAN, "Unable to open smart playlist file %s\n", file);
return -1;
}
ret = parse_input(smartpl, input);
input->close(input);
return ret;
return -1;
}
int
smartpl_query_parse_string(struct smartpl *smartpl, const char *expression)
{
pANTLR3_INPUT_STREAM input;
int ret;
#if ANTLR3C_NEW_INPUT
input = antlr3StringStreamNew ((pANTLR3_UINT8)expression, ANTLR3_ENC_8BIT, (ANTLR3_UINT64)strlen(expression), (pANTLR3_UINT8)"SMARTPL expression");
#else
input = antlr3NewAsciiStringInPlaceStream ((pANTLR3_UINT8)expression, (ANTLR3_UINT64)strlen(expression), (pANTLR3_UINT8)"SMARTPL expression");
#endif
// The input will be created successfully, providing that there is enough memory and the file exists etc
if (input == NULL)
{
DPRINTF(E_LOG, L_SCAN, "Unable to pars smart pl expression %s\n", expression);
return -1;
}
ret = parse_input(smartpl, input);
input->close(input);
return ret;
return -1;
}