########################
# IDL Parse::Yapp parser
# Copyright (C) Andrew Tridgell <tridge@samba.org>
# released under the GNU GPL version 3 or later



# the precedence actually doesn't matter at all for this grammar, but
# by providing a precedence we reduce the number of conflicts
# enormously
%left   '-' '+' '&' '|' '*' '>' '.' '/' '(' ')' '[' ',' ';'


################
# grammar
%%
idl: 
	#empty  { {} }
	| idl interface { push(@{$_[1]}, $_[2]); $_[1] }
	| idl coclass { push(@{$_[1]}, $_[2]); $_[1] }
	| idl import { push(@{$_[1]}, $_[2]); $_[1] }
	| idl include { push(@{$_[1]}, $_[2]); $_[1] }
	| idl importlib { push(@{$_[1]}, $_[2]); $_[1] }
	| idl cpp_quote { push(@{$_[1]}, $_[2]); $_[1] }
;

import: 'import' commalist ';' {{
			"TYPE" => "IMPORT", 
			"PATHS" => $_[2],
		   "FILE" => $_[0]->YYData->{FILE},
		   "LINE" => $_[0]->YYData->{LINE}
		}}
;
include: 'include' commalist ';' {{ 
			"TYPE" => "INCLUDE", 
			"PATHS" => $_[2],
		   "FILE" => $_[0]->YYData->{FILE},
		   "LINE" => $_[0]->YYData->{LINE}
		}}
;
importlib: 'importlib' commalist ';' {{ 
			"TYPE" => "IMPORTLIB", 
			"PATHS" => $_[2],
		   "FILE" => $_[0]->YYData->{FILE},
		   "LINE" => $_[0]->YYData->{LINE}
		}}
;

commalist: 
      text { [ $_[1] ] }
    | commalist ',' text { push(@{$_[1]}, $_[3]); $_[1] }
;

coclass: property_list 'coclass' identifier '{' interface_names '}' optional_semicolon
          {{
               "TYPE" => "COCLASS", 
	       "PROPERTIES" => $_[1],
	       "NAME" => $_[3],
	       "DATA" => $_[5],
		   "FILE" => $_[0]->YYData->{FILE},
		   "LINE" => $_[0]->YYData->{LINE},
          }}
;

interface_names:
	#empty { {} }
	| interface_names 'interface' identifier ';' { push(@{$_[1]}, $_[2]); $_[1] }
;

interface: property_list 'interface' identifier base_interface '{' definitions '}' optional_semicolon
          {{
               "TYPE" => "INTERFACE", 
	       "PROPERTIES" => $_[1],
	       "NAME" => $_[3],
		   "BASE" => $_[4],
	       "DATA" => $_[6],
		   "FILE" => $_[0]->YYData->{FILE},
		   "LINE" => $_[0]->YYData->{LINE},
          }}
;

base_interface:
    #empty
    | ':' identifier { $_[2] }
;


cpp_quote: 'cpp_quote' '(' text ')'
	{{
		 "TYPE" => "CPP_QUOTE",
		 "FILE" => $_[0]->YYData->{FILE},
		 "LINE" => $_[0]->YYData->{LINE},
		 "DATA" => $_[3]
	}}
;

definitions: 
      definition              { [ $_[1] ] }    
    | definitions definition  { push(@{$_[1]}, $_[2]); $_[1] }
;    


definition: function | const | typedef | typedecl
;

const: 'const' identifier pointers identifier '=' anytext ';' 
        {{
                     "TYPE"  => "CONST", 
		     "DTYPE"  => $_[2],
			 "POINTERS" => $_[3],
		     "NAME"  => $_[4],
		     "VALUE" => $_[6],
		     "FILE" => $_[0]->YYData->{FILE},
		     "LINE" => $_[0]->YYData->{LINE},
        }}
	| 'const' identifier pointers identifier array_len '=' anytext ';' 
        {{
                     "TYPE"  => "CONST", 
		     "DTYPE"  => $_[2],
			 "POINTERS" => $_[3],
		     "NAME"  => $_[4],
		     "ARRAY_LEN" => $_[5],
		     "VALUE" => $_[7],
		     "FILE" => $_[0]->YYData->{FILE},
		     "LINE" => $_[0]->YYData->{LINE},
        }}
;


function: property_list type identifier '(' element_list2 ')' ';' 
	 {{
		"TYPE" => "FUNCTION",
		"NAME" => $_[3],
		"RETURN_TYPE" => $_[2],
		"PROPERTIES" => $_[1],
		"ELEMENTS" => $_[5],
		"FILE" => $_[0]->YYData->{FILE},
		"LINE" => $_[0]->YYData->{LINE},
	  }}
;

typedef: property_list 'typedef' type identifier array_len ';' 
        {{
	             "TYPE" => "TYPEDEF", 
                     "PROPERTIES" => $_[1],
		     "NAME" => $_[4],
		     "DATA" => $_[3],
		     "ARRAY_LEN" => $_[5],
		     "FILE" => $_[0]->YYData->{FILE},
		     "LINE" => $_[0]->YYData->{LINE},
        }}
;

usertype: struct | union | enum | bitmap;

typedecl: usertype ';' { $_[1] };

sign: 'signed' | 'unsigned';

existingtype: 
	sign identifier { ($_[1]?$_[1]:"signed") ." $_[2]" }
	| identifier 
;

type: usertype | existingtype | void { "void" } ;

enum_body: '{' enum_elements '}' { $_[2] };
opt_enum_body: | enum_body;
enum: property_list 'enum' optional_identifier opt_enum_body
        {{
             "TYPE" => "ENUM", 
			 "PROPERTIES" => $_[1],
			 "NAME" => $_[3],
		     "ELEMENTS" => $_[4]
        }}
;

enum_elements: 
      enum_element                    { [ $_[1] ] }            
    | enum_elements ',' enum_element  { push(@{$_[1]}, $_[3]); $_[1] }
;

enum_element: identifier 
	      | identifier '=' anytext { "$_[1]$_[2]$_[3]" }
;

bitmap_body: '{' opt_bitmap_elements '}' { $_[2] };
opt_bitmap_body: | bitmap_body;
bitmap: property_list 'bitmap' optional_identifier opt_bitmap_body
        {{
             "TYPE" => "BITMAP", 
		     "PROPERTIES" => $_[1],
			 "NAME" => $_[3],
		     "ELEMENTS" => $_[4]
        }}
;

bitmap_elements: 
      bitmap_element                    { [ $_[1] ] }            
    | bitmap_elements ',' bitmap_element  { push(@{$_[1]}, $_[3]); $_[1] }
;

opt_bitmap_elements: | bitmap_elements;

bitmap_element: identifier '=' anytext { "$_[1] ( $_[3] )" }
;

struct_body: '{' element_list1 '}' { $_[2] };
opt_struct_body: | struct_body;

struct: property_list 'struct' optional_identifier opt_struct_body
        {{
             "TYPE" => "STRUCT", 
			 "PROPERTIES" => $_[1],
			 "NAME" => $_[3],
		     "ELEMENTS" => $_[4]
        }}
;

empty_element: property_list ';'
	{{
		 "NAME" => "",
		 "TYPE" => "EMPTY",
		 "PROPERTIES" => $_[1],
		 "POINTERS" => 0,
		 "ARRAY_LEN" => [],
		 "FILE" => $_[0]->YYData->{FILE},
		 "LINE" => $_[0]->YYData->{LINE},
	 }}
;

base_or_empty: base_element ';' | empty_element;

optional_base_element:
	property_list base_or_empty { $_[2]->{PROPERTIES} = FlattenHash([$_[1],$_[2]->{PROPERTIES}]); $_[2] }
;

union_elements: 
    #empty
    | union_elements optional_base_element { push(@{$_[1]}, $_[2]); $_[1] }
;

union_body: '{' union_elements '}' { $_[2] };
opt_union_body: | union_body;

union: property_list 'union' optional_identifier opt_union_body
        {{
             "TYPE" => "UNION", 
			 "PROPERTIES" => $_[1],
		     "NAME" => $_[3],
		     "ELEMENTS" => $_[4]
        }}
;

base_element: property_list type pointers identifier array_len
	      {{
			   "NAME" => $_[4],
			   "TYPE" => $_[2],
			   "PROPERTIES" => $_[1],
			   "POINTERS" => $_[3],
			   "ARRAY_LEN" => $_[5],
		       "FILE" => $_[0]->YYData->{FILE},
		       "LINE" => $_[0]->YYData->{LINE},
              }}
;


pointers: 
  #empty            
   { 0 }
    | pointers '*'  { $_[1]+1 }
;

element_list1: 
	{ [] }
    | element_list1 base_element ';' { push(@{$_[1]}, $_[2]); $_[1] }
;

optional_const: 
	#empty
	| 'const'
;

element_list2: 
    #empty
    | 'void' 
    | optional_const base_element { [ $_[2] ] }
    | element_list2 ',' optional_const base_element { push(@{$_[1]}, $_[4]); $_[1] }
;

array_len: 
    #empty                        { [] }
    | '[' ']' array_len           { push(@{$_[3]}, "*"); $_[3] }
    | '[' anytext ']' array_len   { push(@{$_[4]}, "$_[2]"); $_[4] }
;


property_list: 
    #empty
    | property_list '[' properties ']' { FlattenHash([$_[1],$_[3]]); }
;

properties: property          { $_[1] }
    | properties ',' property { FlattenHash([$_[1], $_[3]]); }
;

property: identifier                   {{ "$_[1]" => "1"     }}
          | identifier '(' commalisttext ')' {{ "$_[1]" => "$_[3]" }}
;

commalisttext:
    anytext 
    | commalisttext ',' anytext { "$_[1],$_[3]" }
;

anytext:  #empty
    { "" }
    | identifier | constant | text
    | anytext '-' anytext  { "$_[1]$_[2]$_[3]" }
    | anytext '.' anytext  { "$_[1]$_[2]$_[3]" }
    | anytext '*' anytext  { "$_[1]$_[2]$_[3]" }
    | anytext '>' anytext  { "$_[1]$_[2]$_[3]" }
    | anytext '<' anytext  { "$_[1]$_[2]$_[3]" }
    | anytext '|' anytext  { "$_[1]$_[2]$_[3]" }
    | anytext '&' anytext  { "$_[1]$_[2]$_[3]" }
    | anytext '/' anytext  { "$_[1]$_[2]$_[3]" }
    | anytext '?' anytext  { "$_[1]$_[2]$_[3]" }
    | anytext ':' anytext  { "$_[1]$_[2]$_[3]" }
    | anytext '=' anytext  { "$_[1]$_[2]$_[3]" }
    | anytext '+' anytext  { "$_[1]$_[2]$_[3]" }
    | anytext '~' anytext  { "$_[1]$_[2]$_[3]" }
    | anytext '(' commalisttext ')' anytext  { "$_[1]$_[2]$_[3]$_[4]$_[5]" }
    | anytext '{' commalisttext '}' anytext  { "$_[1]$_[2]$_[3]$_[4]$_[5]" }
;

identifier: IDENTIFIER
;

optional_identifier: 
	IDENTIFIER
   | #empty { undef }
;

constant: CONSTANT
;

text: TEXT { "\"$_[1]\"" }
;

optional_semicolon: 
	#empty
	| ';'
;


#####################################
# start code
%%

use Parse::Pidl qw(error);

#####################################################################
# flatten an array of hashes into a single hash
sub FlattenHash($) 
{ 
    my $a = shift;
    my %b;
    for my $d (@{$a}) {
	for my $k (keys %{$d}) {
	    $b{$k} = $d->{$k};
	}
    }
    return \%b;
}



#####################################################################
# traverse a perl data structure removing any empty arrays or
# hashes and any hash elements that map to undef
sub CleanData($)
{
    sub CleanData($);
    my($v) = shift;
	return undef if (not defined($v));
    if (ref($v) eq "ARRAY") {
	foreach my $i (0 .. $#{$v}) {
	    CleanData($v->[$i]);
	}
	# this removes any undefined elements from the array
	@{$v} = grep { defined $_ } @{$v};
    } elsif (ref($v) eq "HASH") {
	foreach my $x (keys %{$v}) {
	    CleanData($v->{$x});
	    if (!defined $v->{$x}) { delete($v->{$x}); next; }
	}
    }
	return $v;
}

sub _Error {
    if (exists $_[0]->YYData->{ERRMSG}) {
		error($_[0]->YYData, $_[0]->YYData->{ERRMSG});
		delete $_[0]->YYData->{ERRMSG};
		return;
	}
	my $last_token = $_[0]->YYData->{LAST_TOKEN};
	
	error($_[0]->YYData, "Syntax error near '$last_token'");
}

sub _Lexer($)
{
	my($parser)=shift;

    $parser->YYData->{INPUT} or return('',undef);

again:
	$parser->YYData->{INPUT} =~ s/^[ \t]*//;

	for ($parser->YYData->{INPUT}) {
		if (/^\#/) {
			if (s/^\# (\d+) \"(.*?)\"( \d+|)//) {
				$parser->YYData->{LINE} = $1-1;
				$parser->YYData->{FILE} = $2;
				goto again;
			}
			if (s/^\#line (\d+) \"(.*?)\"( \d+|)//) {
				$parser->YYData->{LINE} = $1-1;
				$parser->YYData->{FILE} = $2;
				goto again;
			}
			if (s/^(\#.*)$//m) {
				goto again;
			}
		}
		if (s/^(\n)//) {
			$parser->YYData->{LINE}++;
			goto again;
		}
		if (s/^\"(.*?)\"//) {
			$parser->YYData->{LAST_TOKEN} = $1;
			return('TEXT',$1); 
		}
		if (s/^(\d+)(\W|$)/$2/) {
			$parser->YYData->{LAST_TOKEN} = $1;
			return('CONSTANT',$1); 
		}
		if (s/^([\w_]+)//) {
			$parser->YYData->{LAST_TOKEN} = $1;
			if ($1 =~ 
			    /^(coclass|interface|const|typedef|union|cpp_quote
			      |struct|enum|bitmap|void|unsigned|signed|import|include
				  |importlib)$/x) {
				return $1;
			}
			return('IDENTIFIER',$1);
		}
		if (s/^(.)//s) {
			$parser->YYData->{LAST_TOKEN} = $1;
			return($1,$1);
		}
	}
}

sub parse_string
{
	my ($data,$filename) = @_;

	my $self = new Parse::Pidl::IDL;

    $self->YYData->{FILE} = $filename;
    $self->YYData->{INPUT} = $data;
    $self->YYData->{LINE} = 0;
    $self->YYData->{LAST_TOKEN} = "NONE";

	my $idl = $self->YYParse( yylex => \&_Lexer, yyerror => \&_Error );

	return CleanData($idl);
}

sub parse_file($$)
{
	my ($filename,$incdirs) = @_;

	my $saved_delim = $/;
	undef $/;
	my $cpp = $ENV{CPP};
	if (! defined $cpp) {
		$cpp = "cpp";
	}
	my $includes = join('',map { " -I$_" } @$incdirs);
	my $data = `$cpp -D__PIDL__$includes -xc $filename`;
	$/ = $saved_delim;

	return parse_string($data, $filename);
}