########################
# ASN.1 Parse::Yapp parser
# Copyright (C) Stefan (metze) Metzmacher <metze@samba.org>
# released under the GNU GPL version 3 or later



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


################
# grammer
%%

asn1: 
	identifier asn1_definitions asn1_delimitter asn1_begin asn1_decls asn1_end
		{{
			"OBJECT"	=>	"ASN1_DEFINITION",
			"IDENTIFIER"	=>	$_[1],
			"DATA"		=>	$_[5]
		}}
;

asn1_delimitter: 
	delimitter
;

asn1_definitions:
	'DEFINITIONS'
;

asn1_begin:
	'BEGIN'
;

asn1_end:
	'END'
;

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



asn1_def: 
	asn1_target asn1_delimitter asn1_application asn1_type
		{{ 
		    "OBJECT"		=> "ASN1_DEF",
		    "IDENTIFIER"	=> $_[1],
		    "APPLICATION"	=> $_[3],
		    "STRUCTURE"		=> $_[4]
		}}
;

asn1_target:
	identifier
;

asn1_application:
	#empty
	| '[' 'APPLICATION' constant ']'
		{ $_[3] }
;

asn1_type: 
	asn1_boolean
	| asn1_integer
	| asn1_bit_string
	| asn1_octet_string
	| asn1_null
	| asn1_object_identifier
	| asn1_real
	| asn1_enumerated
	| asn1_sequence
	| identifier
;

asn1_boolean:
	'BOOLEAN'
		{{
			"TYPE"		=> "BOOLEAN",
			"TAG"		=> 1
		}}
;

asn1_integer:
	'INTEGER'
		{{
			"TYPE"		=> "INTEGER",
			"TAG"		=> 2
		}}
	| 'INTEGER' '(' constant '.' '.' constant ')'
		{{
			"TYPE"		=> "INTEGER",
			"TAG"		=> 2,
			"RANGE_LOW"	=> $_[3],
			"RENAGE_HIGH"	=> $_[6]
		}}
;

asn1_bit_string:
	'BIT' 'STRING'
		{{
			"TYPE"		=> "BIT STRING",
			"TAG"		=> 3
		}}
;

asn1_octet_string:
	'OCTET' 'STRING'
		{{
			"TYPE"		=> "OCTET STRING",
			"TAG"		=> 4
		}}
;

asn1_null:
	'NULL'
		{{
			"TYPE"		=> "NULL",
			"TAG"		=> 5
		}}
;

asn1_object_identifier:
	'OBJECT' 'IDENTIFIER'
		{{
			"TYPE"		=> "OBJECT IDENTIFIER",
			"TAG"		=> 6
		}}
;

asn1_real:
	'REAL'
		{{
			"TYPE"		=> "REAL",
			"TAG"		=> 9
		}}
;

asn1_enumerated:
	'ENUMERATED'
		{{
			"TYPE"		=> "ENUMERATED",
			"TAG"		=> 10
		}}
;

asn1_sequence:
	'SEQUENCE' '{' asn1_var_dec_list '}'
		{{
			"TYPE"		=> "SEQUENCE",
			"TAG"		=> 16,
			"STRUCTURE"	=> $_[3]
		}}
;

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

asn1_var_dec: 
	identifier asn1_type
	{{
		"NAME" => $_[1],
		"TYPE" => $_[2]
	}}
;

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 ')' anytext  { "$_[1]$_[2]$_[3]$_[4]$_[5]" }
;

delimitter: DELIMITTER
;

identifier: IDENTIFIER
;

constant: CONSTANT
;

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

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

use util;

sub _ASN1_Error {
        if (exists $_[0]->YYData->{ERRMSG}) {
		print $_[0]->YYData->{ERRMSG};
		delete $_[0]->YYData->{ERRMSG};
		return;
	};
	my $line = $_[0]->YYData->{LINE};
	my $last_token = $_[0]->YYData->{LAST_TOKEN};
	my $file = $_[0]->YYData->{INPUT_FILENAME};
	
	print "$file:$line: Syntax error near '$last_token'\n";
}

sub _ASN1_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->{INPUT_FILENAME} = $2;
				goto again;
			}
			if (s/^\#line (\d+) \"(.*?)\"( \d+|)//) {
				$parser->YYData->{LINE} = $1-1;
				$parser->YYData->{INPUT_FILENAME} = $2;
				goto again;
			}
			if (s/^(\#.*)$//m) {
				goto again;
			}
		}
		if (s/^(\n)//) {
			$parser->YYData->{LINE}++;
			goto again;
		}
		if (s/^(--.*\n)//) {
			$parser->YYData->{LINE}++;
			goto again;
		}
		if (s/^(::=)//) {
			$parser->YYData->{LAST_TOKEN} = $1;
			return('DELIMITTER',$1); 
		}
		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 =~ 
			    /^(SEQUENCE|INTEGER|OCTET|STRING|
			       APPLICATION|OPTIONAL|NULL|COMPONENTS|OF|
			       BOOLEAN|ENUMERATED|CHOISE|REAL|BIT|OBJECT|IDENTIFIER|
			       DEFAULT|FALSE|TRUE|SET|DEFINITIONS|BEGIN|END)$/x) {
				return $1;
			}
			return('IDENTIFIER',$1);
		}
		if (s/^(.)//s) {
			$parser->YYData->{LAST_TOKEN} = $1;
			return($1,$1);
		}
	}
}

sub parse_asn1($$)
{
	my $self = shift;
	my $filename = shift;

	my $saved_delim = $/;
	undef $/;
	my $cpp = $ENV{CPP};
	if (! defined $cpp) {
		$cpp = "cpp"
	}
	my $data = `$cpp -xc $filename`;
	$/ = $saved_delim;

        $self->YYData->{INPUT} = $data;
        $self->YYData->{LINE} = 0;
        $self->YYData->{LAST_TOKEN} = "NONE";
	return $self->YYParse( yylex => \&_ASN1_Lexer, yyerror => \&_ASN1_Error );
}