InfluxDBのQuery Parserの初期実装から切り出したものをまとめたものです。今ん所雑多なメモ
適当なところで
go get launchpad.net/gocheck
query.lex
- yytextには一致した文字列が入る
- Bisonで%unionを宣言して'-d'オプションを使うと'.tab.h'というファイルを作成してくれます。 lexの側で`.tab.h'ファイルをインクルードすれば基本はOK。
%{
#include <stdlib.h>
#include <string.h>
#include "query_types.h"
#include "y.tab.h"
%}
%option reentrant
%option bison-bridge
%option noyywrap
%%
; { return *yytext; }
from { return FROM; }
where { return WHERE; }
select { return SELECT; }
= { return *yytext; }
[a-zA-z][a-zA-Z0-9]* { yylval->string = strdup(yytext); return NAME;}
[0-9]+ {yylval->i = atoi(yytext); return INT_VALUE; }
\'.*\' {
yytext[yyleng-1] = '\0';
yylval->string = strdup(yytext+1);
return STRING_VALUE;
}
その他メモ
- reentrant 並行呼び出し可能にする
- bison-bridge
int yylex ( YYSTYPE * lvalp, yyscan_t scanner );
が
int yylex ( YYSTYPE * lvalp, YYLTYPE * llocp, yyscan_t scanner );
こうなる
- noyywrap 入力ファイルが一つであることを示す
query.yacc
- http://www.gnu.org/software/bison/manual/html_node/Pure-Decl.html
- Alternatively, you can generate a pure, reentrant parser. The Bison declaration ‘%define api.pure’ says that you want the parser to be reentrant. It looks like this:
- — Directive: %parse-param {argument-declaration} ...
- Bison declaration to specify additional arguments that yyparse should accept. See The Parser Function yyparse.
- — Directive: %lex-param {argument-declaration} ...
- Bison declaration to specifying additional arguments that yylex should accept. See Calling Conventions for Pure Parsers.
- 3.7.4 Nonterminal Symbols
- — Directive: %start
- Bison assumes by default that the start symbol for the grammar is the first nonterminal specified in the grammar specification section. The programmer may override this restriction with the %start declaration as follows:
%{
#include <stdio.h>
#include "query_types.h"
%}
%union {
char *string;
int i;
from *f;
where *w;
value *v;
}
%debug
%define api.pure
%parse-param {query *q}
%parse-param {void *scanner}
%lex-param {void *scanner}
%token SELECT FROM WHERE EQUAL
%token <string> NAME STRING_VALUE
%token <i> INT_VALUE
%type <f> FROM_CLAUSE
%type <string> TABLE_NAME
%type <string> FIELD_NAME
%type <w> WHERE_CLAUSE
%type <v> FIELD_VALUE
%start QUERY
%%
QUERY: SELECT FROM_CLAUSE WHERE_CLAUSE ';'
{
q->f = $2;
q->w = $3;
}
FROM_CLAUSE: FROM TABLE_NAME
{
$$ = malloc(sizeof(from));
$$->table = $2;
}
WHERE_CLAUSE: WHERE FIELD_NAME '=' FIELD_VALUE
{
$$ = malloc(sizeof(where));
$$->column_name = $2;
$$->op = OP_EQUAL;
$$->v = $4;
}
TABLE_NAME: NAME
FIELD_NAME: NAME
{
$$ = $1;
}
FIELD_VALUE:
STRING_VALUE
{
$$ = malloc(sizeof(value));
$$->svalue = $1;
}
|
INT_VALUE
{
$$ = malloc(sizeof(value));
$$->ivalue = $1;
}
%%
void *yy_scan_string(char *, void *);
void yy_delete_buffer(void *, void *);
void close_query (query *q) {
free(q->w->column_name);
free(q->w->v->svalue);
free(q->w->v);
free(q->w);
free(q->f->table);
free(q->f);
}
query parse_query(char *const query_s) {
/* yydebug = 1; */
query q;
q.error = NULL;
void *scanner;
yylex_init(&scanner);
void *buffer = yy_scan_string(query_s, scanner);
yyparse(&q, scanner);
yy_delete_buffer(buffer, scanner);
printf("table name: %s\n", q.f->table);
printf("where column: %s, value: %s\n", q.w->column_name, q.w->v->svalue);
yylex_destroy(scanner);
return q;
}
int main2() {
query q = parse_query("select from t where foo = '5' ;");
printf("table name: %s\n", q.f->table);
printf("where column: %s, value: %s\n", q.w->column_name, q.w->v->svalue);
close_query(&q);
return 0;
}
int yyerror(query *q, void *s, char *err) {
fprintf(stderr, "error: %s\n", err);
}
query_types.h
typedef struct {
char *table;
} from;
typedef enum {
OP_EQUAL
} operation_t;
typedef struct {
int ivalue;
char *svalue;
} value;
typedef struct {
char *column_name;
operation_t op;
value *v;
} where;
typedef struct {
from *f;
where *w;
char *error;
} query;
void close_query (query *q);
query parse_query(char *const query_s);
parser.go
package query
// #include "query_types.h"
// #include <stdlib.h>
import "C"
import (
"errors"
"unsafe"
)
type From struct {
TableName string
}
type Operation int
const (
EQUAL Operation = C.OP_EQUAL
)
type WhereClause struct {
ColumnName string
Op Operation
Value interface{}
}
type Query struct {
q C.query
}
func (self *Query) GetFromClause() *From {
return &From{C.GoString(self.q.f.table)}
}
func (self *Query) GetWhereClause() *WhereClause {
return &WhereClause{
ColumnName: C.GoString(self.q.w.column_name),
Op: Operation(self.q.w.op),
Value: C.GoString(self.q.w.v.svalue),
}
}
func (self *Query) Close() {
C.close_query(&self.q)
}
func ParseQuery(query string) (*Query, error) {
queryString := C.CString(query)
defer C.free(unsafe.Pointer(queryString))
q := C.parse_query(queryString)
var err error
if q.error != nil {
err = errors.New(C.GoString(q.error))
}
return &Query{q}, err
}
parser_test.go
package query
import (
. "launchpad.net/gocheck"
"testing"
)
// Hook up gocheck into the gotest runner.
func Test(t *testing.T) {
TestingT(t)
}
type QueryParserSuite struct{}
var _ = Suite(&QueryParserSuite{})
func (self *QueryParserSuite) TestParseBasicSelectQuery(c *C) {
q, err := ParseQuery("select from t where c = '5';")
c.Assert(err, IsNil)
w := q.GetWhereClause()
c.Assert(q.GetFromClause().TableName, Equals, "t")
c.Assert(w.ColumnName, Equals, "c")
c.Assert(w.Value, Equals, "5")
}
build.sh
yacc -t -d query.yacc && lex query.lex
go test -v query
意外とおてがる