LoginSignup
15
11

More than 5 years have passed since last update.

CGoでYacc & Bison

Posted at

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

意外とおてがる

15
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
11