blob: 0e4799a508fb6204b515b5272c74942afc7cbff7 [file] [log] [blame]
/* -----------------------------------------------------------------------------
* sql.yy
*
* A simple context-free grammar to parse SQL files and translating
* CREATE FUNCTION statements into C++ function declarations.
* This allows .sql files to be documented by documentation tools like Doxygen.
*
* Revision History:
* 0.3: Florian Schoppmann, 29 Jan 2011, CREATE AGGREGATE supported, return
* types inferred from final function,
* line numbers are preserved
* 0.2: " , 16 Jan 2011, Converted to C++
* 0.1: " , 10 Jan 2011, Initial version, support for CREATE
* FUNCTION.
* -----------------------------------------------------------------------------
*/
/* The %code directive needs bison >= 2.4 */
%require "2.4"
%code requires {
#include <map>
#include <fstream>
#include <cstring>
/*
* FIXME: We should not disable warnings. Without this option, we would
* get the following warnings:
* 1) deprecated conversion from string constant to 'char*'
* 2) ignoring return value of '...', declared with attribute
* warn_unused_result
*/
#if defined(__GNUC__)
#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2))
#pragma GCC diagnostic ignored "-Wwrite-strings"
#endif
#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))
#pragma GCC diagnostic ignored "-Wunused-result"
#endif
#pragma GCC diagnostic ignored "-Wconversion"
#endif
#ifdef COMPILING_SCANNER
/* Flex expects the signature of yylex to be defined in the macro
* YY_DECL. */
#define YY_DECL \
int \
bison::SQLScanner::lex( \
bison::SQLParser::semantic_type *yylval, \
bison::SQLParser::location_type *yylloc, \
bison::SQLDriver *driver \
)
#else
/* In the parser, we need to call the lexer and therefore need the
* lexer class declaration. */
#define yyFlexLexer SQLFlexLexer
#undef yylex
#include "FlexLexer.h"
#undef yyFlexLexer
#endif
namespace bison {
/* Forward declaration because referenced by generated class declaration
* SQLParser */
class SQLDriver;
}
}
%code provides {
namespace bison {
class SQLScanner;
class SQLDriver
{
public:
class CountingOStream {
public:
CountingOStream();
CountingOStream &advance(int line);
CountingOStream &operator<<(const char *str);
CountingOStream &operator<<(char c);
int currentLine;
};
SQLDriver(const std::string &inExeName, const std::string &inFilename);
virtual ~SQLDriver();
void error(const SQLParser::location_type &l, const std::string &m);
void error(const std::string &m);
CountingOStream cout;
std::map<std::string, char *> fnToReturnType;
SQLScanner *scanner;
std::string exeName;
std::string filename;
};
/* We need to subclass because SQLFlexLexer's yylex function does not have
* the proper signature */
class SQLScanner : public SQLFlexLexer
{
public:
SQLScanner(std::istream *arg_yyin = 0, std::ostream* arg_yyout = 0);
virtual ~SQLScanner();
static inline char *strlowerdup(const char *inString);
int lex(SQLParser::semantic_type *yylval,
SQLParser::location_type *yylloc, SQLDriver *driver);
void preScannerAction(SQLParser::semantic_type *yylval,
SQLParser::location_type *yylloc, SQLDriver *driver);
void more();
char *stringLiteralQuotation;
unsigned long oldLength;
};
class TaggedStr
{
public:
TaggedStr(int inTag, char *inStr) : tag(inTag), str(inStr) { };
virtual ~TaggedStr() { };
int tag;
char *str;
};
} // namespace bison
/* "Connect" the bison parser in the driver to the flex scanner class
* object. The C++ scanner generated by flex is a bit ugly, therefore
* this sort of hack here.
*/
#undef yylex
#define yylex driver->scanner->lex
}
/* write out a header file containing the token defines */
%defines
/* use C++ and its skeleton file */
%skeleton "lalr1.cc"
/* keep track of the current position within the input */
%locations
%initial-action {
// Initialize the initial location.
@$.begin.filename = @$.end.filename = &driver->filename;
};
/* The name of the parser class. */
%define parser_class_name "SQLParser"
/* Declare that an argument declared by the braced-code `argument-declaration'
* is an additional yyparse argument. The `argument-declaration' is used when
* declaring functions or prototypes. The last identifier in
* `argument-declaration' must be the argument name. */
%parse-param { SQLDriver *driver }
/* Declare that the braced-code argument-declaration is an additional yylex
* argument declaration. */
%lex-param { SQLDriver *driver }
/* namespace to enclose parser in */
%name-prefix="bison"
%union
{
char *str;
class TaggedStr *tStr;
int i;
}
%token END 0 "end of file"
%token <str> IDENTIFIER "identifier"
%token <str> COMMENT
%token CREATE_FUNCTION
%token CREATE_AGGREGATE
/* Function tokens */
%token IN
%token OUT
%token INOUT
%token RETURNS
%token SETOF
%token AS
%token LANGUAGE
%token IMMUTABLE
%token STABLE
%token VOLATILE
%token CALLED_ON_NULL_INPUT
%token RETURNS_NULL_ON_NULL_INPUT
%token SECURITY_INVOKER
%token SECURITY_DEFINER
%token DEFAULT
/* Aggregate tokens */
%token <i> SFUNC
%token <i> PREFUNC
%token <i> FINALFUNC
%token SORTOP
%token STYPE
%token INITCOND
/* types with more than 1 word */
%token BIT
%token CHARACTER
%token DOUBLE
%token PRECISION
%token TIME
%token VARYING
%token VOID
%token WITH
%token WITHOUT
%token ZONE
%token <str> INTEGER_LITERAL
%token <str> FLOAT_LITERAL
%token <str> STRING_LITERAL
%token <str> NULL_KEYWORD
/* Special tokens, for extending SQL syntax in C commands */
%token BEGIN_SPECIAL
%token END_SPECIAL
%type <str> expr
%type <str> prefixExpr
%type <str> qualifiedIdent
%type <str> optFnArgList fnArgList fnArgument
%type <str> optDefaultArgument defaultArgument
%type <str> optAggArgList aggArgList aggArgument
%type <str> argname type baseType optLength optArray array
%type <str> returnDecl retType
%type <tStr> aggOptionList aggOption
%type <i> aggFunc
%% /* Grammar rules and actions follow. */
input:
| input stmt
| input COMMENT { driver->cout.advance(@2.begin.line) << $2; }
| input '\n' { driver->cout << '\n'; }
;
stmt:
';'
| createFnStmt ';'
| createAggStmt ';'
;
createFnStmt:
CREATE_FUNCTION qualifiedIdent '(' optFnArgList ')' returnDecl fnOptions {
driver->cout.advance(@1.begin.line) << $6 << ' ' << $2 << '(' << $4 << ") { };";
driver->fnToReturnType.insert(std::pair<std::string,char *>($2, $6));
}
;
createAggStmt:
CREATE_AGGREGATE qualifiedIdent '(' optAggArgList ')' '(' aggOptionList ')' {
driver->cout.advance(@1.begin.line) << "@aggregate "
<< ($7 == NULL ? "" : $7->str) << ' ' << $2 << '(' << $4 << ") { };";
}
;
qualifiedIdent:
IDENTIFIER
| IDENTIFIER '.' IDENTIFIER {
$$ = $3;
}
;
optFnArgList: { $$ = ""; }
| fnArgList
;
optAggArgList:
'*' { $$ = ""; }
| aggArgList
;
fnArgList:
fnArgList ',' fnArgument {
/* Yes, we'll leak memory. And we'll fail if there is not enough.
* We ignore all that here and below. */
asprintf(&($$), "%s, %s", $1, $3);
}
| fnArgument
;
aggArgList:
aggArgList ',' aggArgument {
asprintf(&($$), "%s, %s", $1, $3);
}
| aggArgument
fnArgument:
type optDefaultArgument {
asprintf(&($$), "%s%s", $1, $2);
}
| argname type optDefaultArgument {
asprintf(&($$), "%s %s%s", $2, $1, $3);
}
| argmode argname type optDefaultArgument {
asprintf(&($$), "%s %s%s", $3, $2, $4);
}
;
optDefaultArgument: { $$ = ""; }
| defaultArgument
| BEGIN_SPECIAL defaultArgument END_SPECIAL { $$ = $2; }
;
defaultArgument:
DEFAULT expr {
asprintf(&($$), " = %s", $2);
}
| '=' expr {
asprintf(&($$), " = %s", $2);
}
;
aggArgument:
type optDefaultArgument
| argname type optDefaultArgument {
asprintf(&($$), "%s %s%s", $2, $1, $3);
}
;
argmode:
IN
| OUT
| INOUT
;
argname:
IDENTIFIER
| BEGIN_SPECIAL IDENTIFIER END_SPECIAL { $$ = $2; }
;
type:
baseType optArray {
asprintf(&($$), "%s%s", $1, $2);
}
;
baseType:
qualifiedIdent
| BIT VARYING optLength {
asprintf(&($$), "varbit%s", $3);
}
| CHARACTER VARYING optLength {
asprintf(&($$), "varchar%s", $3);
}
| DOUBLE PRECISION { $$ = "float8"; }
| VOID { $$ = "void"; }
;
optArray: { $$ = ""; }
| array;
optLength: { $$ = ""; }
| '(' INTEGER_LITERAL ')' {
asprintf(&($$), "(%s)", $2);
}
;
array:
'[' ']' { $$ = "[]"; }
| '[' INTEGER_LITERAL ']' {
asprintf(&($$), "[%s]", $2);
}
| array '[' ']' {
asprintf(&($$), "%s[]", $1);
}
| array '[' INTEGER_LITERAL ']' {
asprintf(&($$), "%s[%s]", $1, $3);
}
;
returnDecl: { $$ = "void"; }
| RETURNS retType { $$ = $2; }
;
retType:
type
| SETOF type {
asprintf(&($$), "set<%s>", $2);
}
;
fnOptions:
| fnOptions fnOption;
fnOption:
AS STRING_LITERAL
| AS STRING_LITERAL ',' STRING_LITERAL
| LANGUAGE STRING_LITERAL
| LANGUAGE IDENTIFIER
| IMMUTABLE
| STABLE
| VOLATILE
| CALLED_ON_NULL_INPUT
| RETURNS_NULL_ON_NULL_INPUT
| SECURITY_INVOKER
| SECURITY_DEFINER
;
aggOptionList:
aggOptionList ',' aggOption {
if ($1 == NULL)
$$ = $3;
else if ($3 == NULL)
$$ = $1;
else if ($1->tag == token::FINALFUNC)
$$ = $1;
else
$$ = $3;
}
| aggOption
;
aggOption:
aggFunc '=' qualifiedIdent {
$$ = new TaggedStr($1, driver->fnToReturnType[$3]);
}
| STYPE '=' type { $$ = new TaggedStr(token::STYPE, $3); }
| INITCOND '=' expr { $$ = NULL; }
/* FIXME: SORTOP not yet supported at this point */
;
aggFunc:
SFUNC
| PREFUNC
| FINALFUNC
;
expr:
INTEGER_LITERAL
| FLOAT_LITERAL
| STRING_LITERAL
| IDENTIFIER
| NULL_KEYWORD
| prefixExpr
/* FIXME: Support more or ignore completely */
;
prefixExpr:
'+' expr {
asprintf(&($$), "+%s", $2);
}
| '-' expr {
asprintf(&($$), "-%s", $2);
}
;
%%
namespace bison{
SQLDriver::SQLDriver(const std::string &inExeName,
const std::string &inFilename)
: exeName(inExeName), filename(inFilename) { }
SQLDriver::~SQLDriver() { }
void SQLDriver::error(const SQLParser::location_type &l, const std::string &m) {
std::cerr << exeName << ":" << l << ": " << m << std::endl;
}
void SQLDriver::error(const std::string &m) {
std::cerr << m << std::endl;
}
SQLDriver::CountingOStream::CountingOStream() : currentLine(1) {
}
SQLDriver::CountingOStream &SQLDriver::CountingOStream::advance(int line) {
while (currentLine < line) {
std::cout.put('\n');
currentLine++;
}
return *this;
}
SQLDriver::CountingOStream &SQLDriver::CountingOStream::operator<<(
const char *str) {
for (int i = 0; str[i] != 0; i++)
if (str[i] == '\n')
currentLine++;
std::cout << str;
return *this;
}
SQLDriver::CountingOStream &SQLDriver::CountingOStream::operator<<(char c) {
if (c == '\n') currentLine++;
std::cout.put(c);
return *this;
}
void SQLParser::error(const SQLParser::location_type &l,
const std::string &m) {
driver->error(l, m);
}
} // namespace bison
/* This implementation of SQLFlexLexer::yylex() is required because it is
* declared in FlexLexer.h. The scanner's "real" yylex function is generated by
* flex and "connected" via YY_DECL. */
#ifdef yylex
#undef yylex
#endif
int SQLFlexLexer::yylex()
{
std::cerr <<
"Error: SQLFlexLexer::yylex() was called. Use SQLScanner::lex() instead"
<< std::endl;
return 0;
}
int main(int argc, char **argv)
{
std::istream *inStream = NULL;
std::string filename = "<stdin>";
bool error = false;
bool customFileName = false;
for (int i = 1; i < argc; i++) {
if (std::strcmp(argv[1], "-f") == 0) {
if (i < argc - 1) {
filename = argv[++i];
customFileName = true;
} else {
error = true;
break;
}
} else if (inStream == NULL) {
inStream = new std::ifstream(argv[i]);
if (!customFileName)
filename = argv[i];
}
}
if (error) {
std::cerr << "Usage: " << argv[0] << " [-f customFileName] [inputFile]"
<< std::endl;
return 1;
}
bison::SQLDriver driver(argv[0], filename);
bison::SQLScanner scanner(inStream); driver.scanner = &scanner;
bison::SQLParser parser(&driver);
int result = parser.parse();
if (inStream != NULL)
delete inStream;
if (result != 0)
return result;
std::cout << '\n';
/*
std::cout << "// List of functions:\n";
for (std::map<std::string,char *>::iterator it = driver.fnToReturnType.begin();
it != driver.fnToReturnType.end(); it++)
std::cout << "// " << (*it).first << ": return type " << (*it).second << std::endl;
*/
return 0;
}