# $Id: Grammar.prd,v 1.17 2004/08/01 20:05:19 gaissmai Exp gaissmai $ # First, read the Config::Scoped manual to understand what's going on here! # # Second, read the Parse::RecDescent manual and learn it by heart to # understand what's going on here! # # Do you still wish to understand this grammar? # Be warned, dragons ahead, recursive brain damage possible! # # INHERITANCE! # The real action is done via $thisparser->_method() calls # to methods in the Config::Scoped package in order to keep the actions in # this grammar file simple and maintainable. # # The logic is heavily based on localization via in order to # handle scopes properly. # # Call by value are always deep copies via Storable::dclone. # # Blocks, declarations and hashes start new scopes for parameters, macros # and warnings. # # Include files are handled by a cloned Config::Scoped parser. # Include files import parameters and macros to the current scope # but not the warnings. Warnings are scoped within the include files and don't # leak to the parent file. If you don't wish the leakage of parameters and # macros to the parent file, put the %inlcude pragma inside a block {}. # # Declarations collect the parameters and store them # in the unscoped $config hashref. The declaration name(s) are the # keys in the $config hashref. Declarations are never scoped, # they always add to the global config. Declarations are just (named) # collectors of the parameters. # # The principle is easy, isn't it? # ######################################################################### # # START of GRAMMAR for Config::Scoped # ######################################################################### # # STARTRULE, get the localized state hashes per parser, call by ref # config : {local}{params}> config : {local}{macros}> config : {local}{warnings}> config : config_item(s) eofile | { # Error handling: # fetch only the first error, this is the most important one my $parse_error = shift @{ $thisparser->{errors} }; # keep P::RD silent, see the P::RD FAQ $thisparser->{errors} = undef; # throw an exception Config::Scoped::Error::Parse->throw( -text => $parse_error->[0], -line => $parse_error->[1], -file => $thisparser->{local}{cfg_file} ); } # hack, could be done without this intermediate rule, but # the error messages are more readable with this hack. # # commit hack: with a we get better error messages config_item : statement | ######################################################################### # STATEMENT'S ######################################################################### # # use $break to shortcut the alternate productions after a rejected commit # in a subrule. # # This is a hack since P::RD is missing a directive. # I do this programmatically with a localized and { ++$break } # statement : statement : parameter | block | declaration | pragma | comment ######################################################################### # BLOCK'S: { statement(s) } ######################################################################### # # Open a new scope, inherit (deep copy) the scoped hashes. # # call by value: # block : block : block : block : '{' { ++$break } statement(s) '}' stop_pattern | ######################################################################### # DECLARATIONS ######################################################################### # # Open a new scope, inherit (deep copy) the scoped hashes. # # call by value: # declaration : declaration : declaration : declaration : key(s) '{' { ++$break } decl_item(s?) '}' stop_pattern { local $thisparser->{local}{line} = $thisline; local $thisparser->{local}{params} = $params; local $thisparser->{local}{macros} = $macros; local $thisparser->{local}{warnings} = $warnings; $thisparser->_store_declaration( name => $item{'key(s)'}, value => $params, ); # rule success, errors in the method don't raise syntax errors 1; } | decl_item : ...!'}' parameter_or_macro_or_comment_or_warning | ######################################################################### # HASH ######################################################################### # # Reset the params hash! # Open a new scope, inherit (deep copy) the localized hashes # for macros and warnings. # # call by value: # hash : hash : hash : hash : '{' { ++$break } hash_item(s?) '}' { # returns just the filled parameter hash as value $return = $params } | hash_item : ...!'}' parameter_or_macro_or_comment_or_warning m/,?/ | ######################################################################### # LIST ######################################################################### # # lists start no scope, they are just a special kind of parameters # list : list : '[' { ++$break } list_item(s?) ']' { # returns just the filled list as value $return = \@list; } | list_item : ...!']' hash_or_list_or_value_or_comment m/,?/ | ######################################################################### # PARAMETER'S ######################################################################### # parameter : key /=>?/ { ++$break } hash_or_list_or_value stop_pattern { local $thisparser->{local}{line} = $thisline; local $thisparser->{local}{params} = $params; local $thisparser->{local}{macros} = $macros; local $thisparser->{local}{warnings} = $warnings; # store the parameter in the local scope $thisparser->_store_parameter( name => $item{key}, value => $item{hash_or_list_or_value}, ); # rule success, errors in the method don't raise syntax errors 1; } | ######################################################################### # intermediate compounds ######################################################################### # # use $break to shortcut the alternations after a rejected commit parameter_or_macro_or_comment_or_warning : parameter_or_macro_or_comment_or_warning : parameter | macro | warning | comment # use $break to shortcut the alternations after a rejected commit hash_or_list_or_value_or_comment : hash_or_list_or_value_or_comment : hash_or_list_or_value { # fill the list, but not with comments! push @list, $item{hash_or_list_or_value} } | comment # use $break to shortcut the alternations after a rejected commit hash_or_list_or_value : hash_or_list_or_value : hash | list | value ######################################################################### # PRAGMA's ######################################################################### # pragma : macro | include | warning macro : '%macro' { ++$break } key value stop_pattern { local $thisparser->{local}{line} = $thisline; local $thisparser->{local}{params} = $params; local $thisparser->{local}{macros} = $macros; local $thisparser->{local}{warnings} = $warnings; $thisparser->_store_macro( name => $item{key}, value => $item{value}, ); # rule success, errors in the method don't raise syntax errors 1; } | include : '%include' { ++$break } value stop_pattern { # call recursively a new P::RD parser for this include file # call by ref for the current $params and $macros # call by value for the current $warnings local $thisparser->{local}{line} = $thisline; local $thisparser->{local}{params} = $params; local $thisparser->{local}{macros} = $macros; local $thisparser->{local}{warnings} = Storable::dclone $warnings; $thisparser->_include( file => $item{value}, ); # rule success, errors in the method don't raise syntax errors 1; } | warning : warning_short | warning_long warning_short : /%warnings?/i on_off { ++$break } stop_pattern { local $thisparser->{local}{line} = $thisline; local $thisparser->{local}{params} = $params; local $thisparser->{local}{macros} = $macros; local $thisparser->{local}{warnings} = $warnings; $thisparser->_set_warnings( switch => $item{on_off} ); # rule success, errors in the method don't raise syntax errors 1; } | warning_long : /%warnings?/i ...!on_off key { ++$break } on_off stop_pattern { local $thisparser->{local}{line} = $thisline; local $thisparser->{local}{params} = $params; local $thisparser->{local}{macros} = $macros; local $thisparser->{local}{warnings} = $warnings; $thisparser->_set_warnings( name => $item{key}, switch => $item{on_off}, ); # rule success, errors in the method don't raise syntax errors 1; } | on_off : /on|off/i ######################################################################### # KEY and VALUE'S ######################################################################### # key : perl_code | token | perl_quote value : perl_code | token | perl_quote # everything unless separator characters, better than \w in unicode times token : /[^ \s >< }{ )( [\] ; , ' " = # % ]+/x perl_quote : .../"|'|< { local $thisparser->{local}{line} = $thisline; local $thisparser->{local}{params} = $params; local $thisparser->{local}{macros} = $macros; local $thisparser->{local}{warnings} = $warnings; $return = $thisparser->_quotelike( value => $item{__DIRECTIVE1__} ); } perl_code : /perl_code|eval/i { local $thisparser->{local}{line} = $thisline; local $thisparser->{local}{params} = $params; local $thisparser->{local}{macros} = $macros; local $thisparser->{local}{warnings} = $warnings; $return = $thisparser->_perl_code( expr => $item{__DIRECTIVE1__}, ); } ######################################################################### # helpers ######################################################################### # # The skip reset is necessary, since the default eats the newlines. # stop_pattern is: # a newline, a semicolon, a comma or a look-ahead for '}', ']', '\s' # stop_pattern : m/\s* (\n | ; | , | \z | (?=[ \} \] \s ]) )/x eofile : /\z/ comment : m/#.*\n/ ######################################################################### # # END of GRAMMAR, without headache? # ######################################################################### # vim: sw=4 sts=4 ft=perl