CGoでYacc & Bison


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
  * http://www.gnu.org/software/bison/manual/html_node/Type-Decl.html#Type-Decl
* — 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
```

意外とおてがる