[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

View File

@ -19,11 +19,11 @@ libraries:
```bash ```bash
sudo apt-get install \ sudo apt-get install \
build-essential git autotools-dev autoconf automake libtool gettext gawk \ build-essential git autotools-dev autoconf automake libtool gettext gawk \
gperf antlr3 libantlr3c-dev libconfuse-dev libunistring-dev libsqlite3-dev \ gperf libconfuse-dev libunistring-dev libsqlite3-dev libprotobuf-c-dev \
libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev \ libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev \
libasound2-dev libmxml-dev libgcrypt20-dev libavahi-client-dev zlib1g-dev \ libasound2-dev libmxml-dev libgcrypt20-dev libavahi-client-dev zlib1g-dev \
libevent-dev libplist-dev libsodium-dev libjson-c-dev libwebsockets-dev \ libevent-dev libplist-dev libsodium-dev libjson-c-dev libwebsockets-dev \
libcurl4-openssl-dev libprotobuf-c-dev libcurl4-openssl-dev
``` ```
Note that OwnTone will also work with other versions and flavours of Note that OwnTone will also work with other versions and flavours of
@ -83,7 +83,7 @@ sudo yum install \
sqlite-devel libconfuse-devel libunistring-devel mxml-devel libevent-devel \ sqlite-devel libconfuse-devel libunistring-devel mxml-devel libevent-devel \
avahi-devel libgcrypt-devel zlib-devel alsa-lib-devel ffmpeg-devel \ avahi-devel libgcrypt-devel zlib-devel alsa-lib-devel ffmpeg-devel \
libplist-devel libsodium-devel json-c-devel libwebsockets-devel \ libplist-devel libsodium-devel json-c-devel libwebsockets-devel \
libcurl-devel protobuf-c-devel antlr3 antlr3-C-devel libcurl-devel protobuf-c-devel
``` ```
Clone the OwnTone repo: Clone the OwnTone repo:
@ -184,12 +184,6 @@ git clone https://github.com/owntone/owntone-server.git
cd owntone-server cd owntone-server
``` ```
Install antlr3 and library using the included script:
```bash
scripts/antlr35_install.sh -p /usr/local
```
Finally, configure, build and install, adding configure arguments for Finally, configure, build and install, adding configure arguments for
optional features: optional features:
@ -229,11 +223,6 @@ dns-sd -B _daap._tcp
Required tools: Required tools:
- ANTLR v3 is required to build OwnTone, along with its C runtime
(libantlr3c). Use a version between 3.1.3 and 3.5 of ANTLR v3 and the
matching C runtime version. Get it from <http://www.antlr3.org/>
- Java runtime: ANTLR is written in Java and as such a JRE is required to
run the tool. The JRE is enough, you don't need a full JDK.
- autotools: autoconf 2.63+, automake 1.10+, libtool 2.2. Run `autoreconf -i` - autotools: autoconf 2.63+, automake 1.10+, libtool 2.2. Run `autoreconf -i`
at the top of the source tree to generate the build system. at the top of the source tree to generate the build system.
- gettext: libunistring requires iconv and gettext provides the autotools - gettext: libunistring requires iconv and gettext provides the autotools
@ -242,8 +231,6 @@ Required tools:
Libraries: Libraries:
- libantlr3c (ANTLR3 C runtime, use the same version as antlr3)
from <https://github.com/antlr/website-antlr3/tree/gh-pages/download/C>
- Avahi client libraries (avahi-client), 0.6.24 minimum - Avahi client libraries (avahi-client), 0.6.24 minimum
from <http://avahi.org/> from <http://avahi.org/>
- sqlite3 3.5.0+ with unlock notify API enabled (read below) - sqlite3 3.5.0+ with unlock notify API enabled (read below)
@ -297,22 +284,6 @@ documentation, look for `SQLITE_ENABLE_UNLOCK_NOTIFY`.
Start by generating the build system by running `autoreconf -i`. This will Start by generating the build system by running `autoreconf -i`. This will
generate the configure script and `Makefile.in`. generate the configure script and `Makefile.in`.
The configure script will look for a wrapper called antlr3 in the PATH to
invoke ANTLR3. If your installation of ANTLR3 does not come with such a
wrapper, create one as follows:
```bash
#!/bin/sh
CLASSPATH=...
exec /path/to/java -cp $CLASSPATH org.antlr.Tool "$@"
```
Adjust the `CLASSPATH` as needed so that Java will find all the jars needed
by ANTLR3.
The parsers will be generated during the build, no manual intervention is
needed.
To display the configure options `run ./configure --help`. To display the configure options `run ./configure --help`.
Support for Spotify is optional. Use `--disable-spotify` to disable this feature. Support for Spotify is optional. Use `--disable-spotify` to disable this feature.
@ -371,25 +342,6 @@ Use `--disable-install-systemd` if you don't want that.
Using `--enable-install-user` means that `make install` will also add a system Using `--enable-install-user` means that `make install` will also add a system
user and group for owntone. user and group for owntone.
You may see two kinds of warnings during make.
First, `/usr/bin/antlr3` may generate a long series of warnings that
begin like this:
```log
warning(24): template error: context ...
```
Second, you may see compiler warnings that look like this:
```log
RSPLexer.c: In function `mESCAPED':
RSPLexer.c:2674:16: warning: unused variable `_type' [-Wunused-variable]
ANTLR3_UINT32 _type;
^~~~~
```
You can safely ignore all of these warnings.
After installation: After installation:
- edit the configuration file, `/etc/owntone.conf` - edit the configuration file, `/etc/owntone.conf`

View File

@ -27,22 +27,10 @@ AS_IF([[test -z "$GPERF"]],
AC_MSG_NOTICE([[ AC_MSG_NOTICE([[
GNU gperf not found, but it's output appears to be present. GNU gperf not found, but it's output appears to be present.
If you modify any gperf or ANTLR grammar files (.g), you will need If you modify any .gperf files, you will need to install it.]])],
to install it.]])],
[AC_MSG_ERROR([[GNU gperf required, please install it.]])]) [AC_MSG_ERROR([[GNU gperf required, please install it.]])])
]) ])
AC_PATH_PROG([ANTLR], [[antlr3]])
AS_IF([[test -z "$ANTLR"]],
[AS_IF([[test -f "$srcdir/src/SMARTPLLexer.h"]],
[AM_MISSING_PROG([ANTLR], [[antlr3]])
AC_MSG_NOTICE([[
antlr3 not found, but it's output appears to be present.
If you modify any ANTLR grammar files (.g), you will need to install it.]])],
[AC_MSG_ERROR([[antlr3 wrapper required, please install it.]])])
])
dnl Enable all warnings by default. dnl Enable all warnings by default.
AM_CPPFLAGS="-Wall" AM_CPPFLAGS="-Wall"
AC_SUBST([AM_CPPFLAGS]) AC_SUBST([AM_CPPFLAGS])
@ -174,16 +162,6 @@ PKG_CHECK_EXISTS([libplist],
[OWNTONE_MODULES_CHECK([OWNTONE], [LIBPLIST], [libplist-2.0], [OWNTONE_MODULES_CHECK([OWNTONE], [LIBPLIST], [libplist-2.0],
[plist_dict_get_item], [plist/plist.h])]) [plist_dict_get_item], [plist/plist.h])])
OWNTONE_FUNC_REQUIRE([OWNTONE], [ANTLR3 C runtime], [ANTLR3C], [antlr3c],
[antlr3BaseRecognizerNew], [antlr3.h],
[AC_CHECK_FUNC([[antlr3NewAsciiStringInPlaceStream]],
[AC_DEFINE([ANTLR3C_NEW_INPUT], 0,
[define if antlr3 C runtime uses new input routines])],
[AC_DEFINE([ANTLR3C_NEW_INPUT], 1,
[define if antlr3 C runtime uses new input routines])])
])
AM_PATH_LIBGCRYPT([1:1.2.0]) AM_PATH_LIBGCRYPT([1:1.2.0])
OWNTONE_FUNC_REQUIRE([OWNTONE], [GNU Crypt Library], [LIBGCRYPT], [gcrypt], OWNTONE_FUNC_REQUIRE([OWNTONE], [GNU Crypt Library], [LIBGCRYPT], [gcrypt],
[gcry_control], [gcrypt.h]) [gcry_control], [gcrypt.h])

View File

@ -21,7 +21,7 @@ Source0: https://github.com/owntone/%{name}/archive/%{version}/%{name}-%{version
BuildRequires: gcc, make, systemd, pkgconfig, libunistring-devel BuildRequires: gcc, make, systemd, pkgconfig, libunistring-devel
BuildRequires: pkgconfig(zlib), pkgconfig(libconfuse), pkgconfig(mxml) BuildRequires: pkgconfig(zlib), pkgconfig(libconfuse), pkgconfig(mxml)
BuildRequires: pkgconfig(sqlite3) >= 3.5.0, pkgconfig(libevent) >= 2.0.0 BuildRequires: pkgconfig(sqlite3) >= 3.5.0, pkgconfig(libevent) >= 2.0.0
BuildRequires: pkgconfig(json-c), antlr3-C-devel, libgcrypt-devel >= 1.2.0 BuildRequires: pkgconfig(json-c), libgcrypt-devel >= 1.2.0
BuildRequires: libgpg-error-devel >= 1.6 BuildRequires: libgpg-error-devel >= 1.6
BuildRequires: pkgconfig(libavformat), pkgconfig(libavcodec) BuildRequires: pkgconfig(libavformat), pkgconfig(libavcodec)
BuildRequires: pkgconfig(libswscale), pkgconfig(libavutil) BuildRequires: pkgconfig(libswscale), pkgconfig(libavutil)
@ -109,6 +109,9 @@ exit 0
%{_mandir}/man?/* %{_mandir}/man?/*
%changelog %changelog
* Mon Jan 10 2022 Espen Jürgensen <espen.jurgensen@gmail.com> - 28.3-1
- Remove antlr dependency
* Mon Nov 22 2021 Derek Atkins <derek@ihtfp.com> - 28.2-1 * Mon Nov 22 2021 Derek Atkins <derek@ihtfp.com> - 28.2-1
- Release tarball is a XZ not GZ file - Release tarball is a XZ not GZ file
- Configure always needs protobuf-c, not just for chromecast - Configure always needs protobuf-c, not just for chromecast

View File

@ -1,255 +0,0 @@
#!/bin/sh
WORKDIR=~/antlr35.tmp
# programs
MAKE=${MAKE-make}
DOWNLOAD="wget --no-check-certificate"
ALTDOWNLOAD="curl -LO"
SUDO=sudo
# source
ANTLR_VERSION=3.5
ANTLR3_SOURCE="https://github.com/antlr/website-antlr3/raw/gh-pages/download"
ANTLR3_JAR="antlr-3.5.2-complete.jar"
ANTLR3_URL="$ANTLR3_SOURCE/$ANTLR3_JAR"
LIBANTLR3C="libantlr3c-3.4"
LIBANTLR3C_SOURCE="https://github.com/antlr/website-antlr3/raw/gh-pages/download/C"
LIBANTLR3C_TAR="${LIBANTLR3C}.tar.gz"
LIBANTLR3C_URL="$LIBANTLR3C_SOURCE/$LIBANTLR3C_TAR"
usage() {
echo
echo "This script will download, build and install antlr $ANTLR_VERSION"
echo " (and matching libantlrc) on your computer."
echo
echo "Usage: ${0##*/} -h | [ -p <prefix> ] [ -y ] [ <build-dir> ]"
echo
echo "Parameters:"
echo " -h Show this help"
echo " -p <prefix> Install to prefix (default: choose /usr or /usr/local)"
echo " -y Automatic yes to prompts (run non-interactively with -p)"
echo " <build-dir> Build directory (default: $WORKDIR)"
exit 0
}
GIVEN_PREFIX=
ALWAYS_YES=
while [ "$1" != "" ]; do
case $1 in
-p | --prefix )
shift
GIVEN_PREFIX=$1
;;
-y | --yes )
ALWAYS_YES=1
;;
-h | --help )
usage
exit
;;
* )
echo "Unrecognized option $1 (try -h for usage)"
exit 1
;;
esac
shift
done
# override build directory? (support ~ expansion)
[ -n "$1" ] && WORKDIR=$1
ORIG_DIR=`pwd`
err() {
echo "$*"
if [ -n "$FILES_EXIST" ]; then
echo "Files remain in $WORKDIR..."
else
cd "$ORIG_DIR"
rmdir "$WORKDIR"
fi
exit 1
}
is_yes() {
case "$1" in
[N]*|[n]*) return 1;;
*) ;;
esac
return 0
}
ask_yn() {
if [ "$ALWAYS_YES" = "1" ]; then
yn="y"
else
read -p "$1" yn
fi
}
prog_install() {
ask_yn "Would you like to install into $PREFIX now? [Y/n] "
if ! is_yes "$yn"; then
echo "Build left ready to install from $WORKDIR"
echo "You can re-run the script (eg. as root) to install into"
echo " $PREFIX later."
exit
fi
if [ `id -u` -ne 0 ]; then
ask_yn "Would you like to install with sudo? NOTE: You WILL be asked for your password! [Y/n] "
if ! is_yes "$yn"; then
SUDO=
ask_yn "Continue to install as non-root user? [Y/n] "
is_yes "$yn" || err "Install cancelled"
fi
else
SUDO=
fi
cd $LIBANTLR3C || err "Unable to cd to build libantlr3c build directory!"
echo "Installing libantlr3c to $PREFIX"
$SUDO $MAKE install || err "Install of libantlr3c to $PREFIX failed!"
cd "$ORIG_DIR"
cd $WORKDIR
echo "Installing antlr3 to $PREFIX"
$SUDO mkdir -p "$PREFIX_JAVA" || err "Unable to create $PREFIX_JAVA"
$SUDO install "$ANTLR3_JAR" "$PREFIX_JAVA" || \
err "Failed to install antlr3 jar to $PREFIX_JAVA"
$SUDO mkdir -p "$PREFIX/bin" || err "Unable to create $PREFIX/bin"
$SUDO install -m 755 antlr3 "$PREFIX/bin" || \
err "Failed to install antlr3 to $PREFIX/bin"
echo "Install complete (build remains in $WORKDIR)"
}
echo "This script will download, build and install antlr $ANTLR_VERSION"
echo " (and matching libantlrc) on your computer."
echo
# check if make works
ISGNU=`$MAKE --version 2>/dev/null | grep "GNU Make"`
if [ -z "$ISGNU" ]; then
MAKE=gmake
ISGNU=`$MAKE --version 2>/dev/null | grep "GNU Make"`
fi
[ -z "$ISGNU" ] && err "Unable to locate GNU Make, set \$MAKE to it's location and re-run"
if [ -f "$WORKDIR/install_env" ]; then
echo "Existing build found in $WORKDIR"
FILES_EXIST=1
cd $WORKDIR || err "Unable to cd to '$WORKDIR'"
. install_env
[ -n "$PREFIX" ] || err "PREFIX is missing in file 'install_env'"
if [ -n "$GIVEN_PREFIX" ] && [ "$GIVEN_PREFIX" != "$PREFIX" ]; then
echo "You must rebuild to install into $GIVEN_PREFIX (current build for $PREFIX)"
ask_yn "Would you like to rebuild for ${GIVEN_PREFIX}? [Y/n] "
if is_yes "$yn"; then
rm -f install_env
PREFIX=
else
ask_yn "Would you like to install to ${PREFIX}? [Y/n] "
! is_yes "$yn" && err "Install cancelled"
fi
fi
if [ -n "$PREFIX" ]; then
PREFIX_JAVA=$PREFIX/share/java
prog_install
exit 0
fi
fi
if [ ! -d "$WORKDIR" ]; then
ask_yn "Should the script create $WORKDIR and use it for building? [Y/n] "
is_yes "$yn" || exit
fi
if [ -n "$GIVEN_PREFIX" ]; then
PREFIX=$GIVEN_PREFIX
else
read -p "Should the script install with prefix /usr or /usr/local? [U/l] " yn
if [ "$yn" = "l" ]; then
PREFIX=/usr/local
else
PREFIX=/usr
fi
fi
PREFIX_JAVA=$PREFIX/share/java
MACHBITS=`getconf LONG_BIT 2>/dev/null`
[ "$MACHBITS" = "64" ] && DEF_AN="[Y/n]" || DEF_AN="[y/N]"
ask_yn "Should the script build libantlr3c for 64 bit? $DEF_AN "
[ -z "$yn" -a "$MACHBITS" != "64" ] && yn=n
is_yes "$yn" && ENABLE64BIT="--enable-64bit"
mkdir -p "$WORKDIR" || err "Error creating $WORKDIR"
# don't quote WORKDIR to catch a WORKDIR that will break the build (eg spaces)
cd $WORKDIR || err "Unable to cd to '$WORKDIR' (does it include spaces?)"
REMOVE_ON_CANCEL=
cancel_download() {
echo "removing $REMOVE_ON_CANCEL"
[ -n "$REMOVE_ON_CANCEL" ] && rm -f "$REMOVE_ON_CANCEL"
err "Cancelling download..."
}
antlr_download() {
trap cancel_download SIGINT
$DOWNLOAD --help >/dev/null 2>&1 || DOWNLOAD=$ALTDOWNLOAD
$DOWNLOAD --help >/dev/null 2>&1 || {
echo "Unable to find wget or curl commands to download source,"
echo " please install either one and re-try."
exit 1
}
[ "x$1" = "xreset" ] && rm "$ANTLR3_JAR" "$LIBANTLR3C_TAR"
if [ ! -f "$ANTLR3_JAR" ]; then
echo
echo "Downloading antlr from $ANTLR3_URL"
echo "Ctrl-C to abort..."
REMOVE_ON_CANCEL=$ANTLR3_JAR
$DOWNLOAD "$ANTLR3_URL" || err "Download of $ANTLR3_JAR failed!"
FILES_EXIST=1
fi
if [ ! -f "$LIBANTLR3C_TAR" ]; then
echo
echo "Downloading libantlr3c from $LIBANTLR3C_URL"
echo "Ctrl-C to abort..."
REMOVE_ON_CANCEL=$LIBANTLR3C_TAR
$DOWNLOAD "$LIBANTLR3C_URL" || err "Download of $LIBANTLR3C_TAR failed!"
FILES_EXIST=1
fi
trap - SIGINT
}
# retrieve the source
if [ -f "$ANTLR3_JAR" -a -f "$LIBANTLR3C_TAR" ]; then
FILES_EXIST=1
ask_yn "Files appear to already be downloaded, use them? [Y/n] "
! is_yes "$yn" && antlr_download reset
else
ask_yn "Should the script download and build antlr and libantlr3c? [Y/n] "
is_yes "$yn" || exit
antlr_download
fi
# build/install libantlr3c
[ -d "$LIBANTLR3C" ] && rm -rf "$LIBANTLR3C"
tar xzf "$LIBANTLR3C_TAR" || err "Uncompress of $LIBANTLR3C_TAR failed!"
cd $LIBANTLR3C || err "Unable to cd to build $LIBANTLR3C build directory!"
./configure $ENABLE64BIT --prefix=$PREFIX && $MAKE
[ $? -ne 0 ] && err "Build of libantlr3c failed!"
# install antlr3 jar and wrapper
cd "$ORIG_DIR"
cd $WORKDIR
printf "#!/bin/sh
export CLASSPATH
CLASSPATH=\$CLASSPATH:$PREFIX_JAVA/${ANTLR3_JAR}:$PREFIX_JAVA
/usr/bin/java org.antlr.Tool \$*
" > antlr3
# save for later install attempts
echo "PREFIX=$PREFIX" > install_env
echo
prog_install

View File

@ -46,30 +46,6 @@ if [ ! -d $WORKDIR ]; then
fi fi
cd $WORKDIR cd $WORKDIR
read -p "Should the script install antlr and libantlr3c? [y/N] " yn
if [ "$yn" = "y" ]; then
read -p "Should the script build libantlr3c for 64 bit? [Y/n] " yn
if [ "$yn" != "n" ]; then
ENABLE64BIT="--enable-64bit"
fi
wget --no-check-certificate https://github.com/antlr/website-antlr3/raw/gh-pages/download/antlr-3.4-complete.jar
wget --no-check-certificate https://github.com/antlr/website-antlr3/raw/gh-pages/download/C/libantlr3c-3.4.tar.gz
sudo install antlr-3.4-complete.jar /usr/local/share/java
printf "#!/bin/sh
export CLASSPATH
CLASSPATH=\$CLASSPATH:/usr/local/share/java/antlr-3.4-complete.jar:/usr/local/share/java
/usr/local/bin/java org.antlr.Tool \$*
" > antlr3
sudo install -m 755 antlr3 /usr/local/bin
tar xzf libantlr3c-3.4.tar.gz
cd libantlr3c-3.4
./configure $ENABLE64BIT && gmake && sudo gmake install
cd $WORKDIR
fi
read -p "Should the script build owntone? [y/N] " yn read -p "Should the script build owntone? [y/N] " yn
if [ "$yn" = "y" ]; then if [ "$yn" = "y" ]; then
git clone https://github.com/owntone/owntone-server.git git clone https://github.com/owntone/owntone-server.git

6
src/.gitignore vendored
View File

@ -1,11 +1,5 @@
owntone owntone
*.tokens
*Lexer.[ch]
*Parser.[ch]
*2SQL.[ch]
*.u
daap_query_hash.h daap_query_hash.h
rsp_query_hash.h rsp_query_hash.h
dacp_prop_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) 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 += \ AM_CPPFLAGS += \
$(OWNTONE_CPPFLAGS) \ $(OWNTONE_CPPFLAGS) \
$(OWNTONE_OPTS_CPPFLAGS) \ $(OWNTONE_OPTS_CPPFLAGS) \
@ -152,42 +130,16 @@ owntone_SOURCES = main.c \
mxml-compat.h \ mxml-compat.h \
outputs/plist_wrap.h \ outputs/plist_wrap.h \
$(LIBWEBSOCKETS_SRC) \ $(LIBWEBSOCKETS_SRC) \
$(GPERF_SRC) \ $(GPERF_SRC)
$(ANTLR_SRC)
# built by maintainers, and distributed. Clean with maintainer-clean # built by maintainers, and distributed. Clean with maintainer-clean
BUILT_SOURCES = \ BUILT_SOURCES = \
$(GPERF_SRC) \ $(GPERF_SRC)
$(ANTLR_SRC) \
$(ANTLR_TOKENS) \
$(ANTLR_DEPS)
EXTRA_DIST = \ EXTRA_DIST = \
$(GPERF_FILES) \ $(GPERF_FILES)
$(ANTLR_GRAMMARS) \
$(ANTLR_TOKENS) \
$(ANTLR_DEPS)
# silence unused warnings from antlr generated files
$(ANTLR_OBJECTS): AM_CPPFLAGS += -Wno-unused
# gperf construction rules # gperf construction rules
%_hash.h: %.gperf %_hash.h: %.gperf
$(AM_V_GEN)$(GPERF) --output-file=$@ $< $(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 "misc.h"
#include "daap_query.h" #include "daap_query.h"
#include "DAAPLexer.h"
#include "DAAPParser.h"
#include "DAAP2SQL.h"
char * char *
daap_query_parse_sql(const char *daap_query) 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; 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;
}

View File

@ -29,122 +29,8 @@
#include "misc.h" #include "misc.h"
#include "rsp_query.h" #include "rsp_query.h"
#include "RSPLexer.h"
#include "RSPParser.h"
#include "RSP2SQL.h"
char * char *
rsp_query_parse_sql(const char *rsp_query) 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; 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;
}

View File

@ -35,185 +35,19 @@
#include "logger.h" #include "logger.h"
#include "misc.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 int
smartpl_query_parse_file(struct smartpl *smartpl, const char *file) 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; return -1;
} }
ret = parse_input(smartpl, input);
input->close(input);
return ret;
}
int int
smartpl_query_parse_string(struct smartpl *smartpl, const char *expression) 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; return -1;
} }
ret = parse_input(smartpl, input);
input->close(input);
return ret;
}
void void
free_smartpl(struct smartpl *smartpl, int content_only) free_smartpl(struct smartpl *smartpl, int content_only)