%{
#include "aidl_language.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int yyerror(char* errstr);
int yylex(void);
extern int yylineno;

static int count_brackets(const char*);

%}

%token IMPORT
%token PACKAGE
%token IDENTIFIER
%token GENERIC
%token ARRAY
%token PARCELABLE
%token INTERFACE
%token IN
%token OUT
%token INOUT
%token ONEWAY

%%
document:
        document_items                          { g_callbacks->document($1.document_item); }
    |   headers document_items                  { g_callbacks->document($2.document_item); }
    ;

headers:
        package                                 { }
    |   imports                                 { }
    |   package imports                         { }
    ;

package:
        PACKAGE                                 { }
    ;

imports:
        IMPORT                                  { g_callbacks->import(&($1.buffer)); }
    |   IMPORT imports                          { g_callbacks->import(&($1.buffer)); }
    ;

document_items:
                                                { $$.document_item = NULL; }
    |   document_items declaration              {
                                                    if ($2.document_item == NULL) {
                                                        // error cases only
                                                        $$ = $1;
                                                    } else {
                                                        document_item_type* p = $1.document_item;
                                                        while (p && p->next) {
                                                            p=p->next;
                                                        }
                                                        if (p) {
                                                            p->next = (document_item_type*)$2.document_item;
                                                            $$ = $1;
                                                        } else {
                                                            $$.document_item = (document_item_type*)$2.document_item;
                                                        }
                                                    }
                                                }
    | document_items error                      {
                                                    fprintf(stderr, "%s:%d: syntax error don't know what to do with \"%s\"\n", g_currentFilename,
                                                            $2.buffer.lineno, $2.buffer.data);
                                                    $$ = $1;
                                                }
    ;

declaration:
        parcelable_decl                            { $$.document_item = (document_item_type*)$1.parcelable; }
    |   interface_decl                             { $$.document_item = (document_item_type*)$1.interface_item; }
    ;

parcelable_decl:
        PARCELABLE IDENTIFIER ';'                  { 
                                                        parcelable_type* b = (parcelable_type*)malloc(sizeof(parcelable_type));
                                                        b->document_item.item_type = PARCELABLE_TYPE;
                                                        b->document_item.next = NULL;
                                                        b->parcelable_token = $1.buffer;
                                                        b->name = $2.buffer;
                                                        b->package = g_currentPackage ? strdup(g_currentPackage) : NULL;
                                                        b->semicolon_token = $3.buffer;
                                                        $$.parcelable = b;
                                                    }
    |   PARCELABLE ';'                              {
                                                        fprintf(stderr, "%s:%d syntax error in parcelable declaration. Expected type name.\n",
                                                                     g_currentFilename, $1.buffer.lineno);
                                                        $$.parcelable = NULL;
                                                    }
    |   PARCELABLE error ';'                        {
                                                        fprintf(stderr, "%s:%d syntax error in parcelable declaration. Expected type name, saw \"%s\".\n",
                                                                     g_currentFilename, $2.buffer.lineno, $2.buffer.data);
                                                        $$.parcelable = NULL;
                                                    }
    ;

interface_header:
        INTERFACE                                  {
                                                        interface_type* c = (interface_type*)malloc(sizeof(interface_type));
                                                        c->interface_token = $1.buffer;
                                                        c->oneway = false;
                                                        memset(&c->oneway_token, 0, sizeof(buffer_type));
                                                        c->comments_token = &c->interface_token;
                                                        $$.interface_obj = c;
                                                   }
    |   ONEWAY INTERFACE                           {
                                                        interface_type* c = (interface_type*)malloc(sizeof(interface_type));
                                                        c->interface_token = $2.buffer;
                                                        c->oneway = true;
                                                        c->oneway_token = $1.buffer;
                                                        c->comments_token = &c->oneway_token;
                                                        $$.interface_obj = c;
                                                   }
    ;

interface_decl:
        interface_header IDENTIFIER '{' interface_items '}' { 
                                                        interface_type* c = $1.interface_obj;
                                                        c->document_item.item_type = INTERFACE_TYPE;
                                                        c->document_item.next = NULL;
                                                        c->name = $2.buffer;
                                                        c->package = g_currentPackage ? strdup(g_currentPackage) : NULL;
                                                        c->open_brace_token = $3.buffer;
                                                        c->interface_items = $4.interface_item;
                                                        c->close_brace_token = $5.buffer;
                                                        $$.interface_obj = c;
                                                    }
    |   INTERFACE error '{' interface_items '}'     {
                                                        fprintf(stderr, "%s:%d: syntax error in interface declaration.  Expected type name, saw \"%s\"\n",
                                                                    g_currentFilename, $2.buffer.lineno, $2.buffer.data);
                                                        $$.document_item = NULL;
                                                    }
    |   INTERFACE error '}'                             {
                                                        fprintf(stderr, "%s:%d: syntax error in interface declaration.  Expected type name, saw \"%s\"\n",
                                                                    g_currentFilename, $2.buffer.lineno, $2.buffer.data);
                                                        $$.document_item = NULL;
                                                    }

    ;

interface_items:
                                                    { $$.interface_item = NULL; }
    |   interface_items method_decl                 {
                                                        interface_item_type* p=$1.interface_item;
                                                        while (p && p->next) {
                                                            p=p->next;
                                                        }
                                                        if (p) {
                                                            p->next = (interface_item_type*)$2.method;
                                                            $$ = $1;
                                                        } else {
                                                            $$.interface_item = (interface_item_type*)$2.method;
                                                        }
                                                    }
    |   interface_items error ';'                   {
                                                        fprintf(stderr, "%s:%d: syntax error before ';' (expected method declaration)\n",
                                                                    g_currentFilename, $3.buffer.lineno);
                                                        $$ = $1;
                                                    }
    ;

method_decl:
        type IDENTIFIER '(' arg_list ')' ';'  {
                                                        method_type *method = (method_type*)malloc(sizeof(method_type));
                                                        method->interface_item.item_type = METHOD_TYPE;
                                                        method->interface_item.next = NULL;
                                                        method->type = $1.type;
                                                        method->oneway = false;
                                                        memset(&method->oneway_token, 0, sizeof(buffer_type));
                                                        method->name = $2.buffer;
                                                        method->open_paren_token = $3.buffer;
                                                        method->args = $4.arg;
                                                        method->close_paren_token = $5.buffer;
                                                        method->semicolon_token = $6.buffer;
                                                        method->comments_token = &method->type.type;
                                                        $$.method = method;
                                                    }
    |   ONEWAY type IDENTIFIER '(' arg_list ')' ';'  {
                                                        method_type *method = (method_type*)malloc(sizeof(method_type));
                                                        method->interface_item.item_type = METHOD_TYPE;
                                                        method->interface_item.next = NULL;
                                                        method->oneway = true;
                                                        method->oneway_token = $1.buffer;
                                                        method->type = $2.type;
                                                        method->name = $3.buffer;
                                                        method->open_paren_token = $4.buffer;
                                                        method->args = $5.arg;
                                                        method->close_paren_token = $6.buffer;
                                                        method->semicolon_token = $7.buffer;
                                                        method->comments_token = &method->oneway_token;
                                                        $$.method = method;
                                                    }
    ;

arg_list:
                                { $$.arg = NULL; }
    |   arg                     { $$ = $1; }
    |   arg_list ',' arg        {
                                    if ($$.arg != NULL) {
                                        // only NULL on error
                                        $$ = $1;
                                        arg_type *p = $1.arg;
                                        while (p && p->next) {
                                            p=p->next;
                                        }
                                        $3.arg->comma_token = $2.buffer;
                                        p->next = $3.arg;
                                    }
                                }
    |   error                   {
                                    fprintf(stderr, "%s:%d: syntax error in parameter list\n", g_currentFilename, $1.buffer.lineno);
                                    $$.arg = NULL;
                                }
    ;

arg:
        direction type IDENTIFIER     {
                                                arg_type* arg = (arg_type*)malloc(sizeof(arg_type));
                                                memset(&arg->comma_token, 0, sizeof(buffer_type));
                                                arg->direction = $1.buffer;
                                                arg->type = $2.type;
                                                arg->name = $3.buffer;
                                                arg->next = NULL;
                                                $$.arg = arg;
                                      }
    ;

type:
        IDENTIFIER              {
                                    $$.type.type = $1.buffer;
                                    init_buffer_type(&$$.type.array_token, yylineno);
                                    $$.type.dimension = 0;
                                }
    |   IDENTIFIER ARRAY        {
                                    $$.type.type = $1.buffer;
                                    $$.type.array_token = $2.buffer;
                                    $$.type.dimension = count_brackets($2.buffer.data);
                                }
    |   GENERIC                 {
                                    $$.type.type = $1.buffer;
                                    init_buffer_type(&$$.type.array_token, yylineno);
                                    $$.type.dimension = 0;
                                }
    ;

direction:
                    { init_buffer_type(&$$.buffer, yylineno); }
    |   IN          { $$.buffer = $1.buffer; }
    |   OUT         { $$.buffer = $1.buffer; }
    |   INOUT       { $$.buffer = $1.buffer; }
    ;

%%

#include <ctype.h>
#include <stdio.h>

int g_error = 0;

int yyerror(char* errstr)
{
    fprintf(stderr, "%s:%d: %s\n", g_currentFilename, yylineno, errstr);
    g_error = 1;
    return 1;
}

void init_buffer_type(buffer_type* buf, int lineno)
{
    buf->lineno = lineno;
    buf->token = 0;
    buf->data = NULL;
    buf->extra = NULL;
}

static int count_brackets(const char* s)
{
    int n=0;
    while (*s) {
        if (*s == '[') n++;
        s++;
    }
    return n;
}
