Home | History | Annotate | Download | only in i18n
      1 #!/usr/bin/perl
      2 # Copyright (C) 2016 and later: Unicode, Inc. and others.
      3 # License & terms of use: http://www.unicode.org/copyright.html
      4 #  ********************************************************************
      5 #  * COPYRIGHT:
      6 #  * Copyright (c) 2002-2016, International Business Machines Corporation and
      7 #  * others. All Rights Reserved.
      8 #  ********************************************************************
      9 #
     10 #  regexcst.pl
     11 #            Compile the regular expression paser state table data into initialized C data.
     12 #            Usage:
     13 #                   cd icu/source/i18n
     14 #                   perl regexcst.pl < regexcst.txt > regexcst.h
     15 #
     16 #             The output file, regexcst.h, is included by some of the .cpp regex
     17 #             implementation files.   This perl script is NOT run as part
     18 #             of a normal ICU build.  It is run by hand when needed, and the
     19 #             regexcst.h generated file is put back into cvs.
     20 #
     21 #             See regexcst.txt for a description of the input format for this script.
     22 #
     23 #             This script is derived from rbbicst.pl, which peforms the same function
     24 #             for the Rule Based Break Iterator Rule Parser.  Perhaps they could be
     25 #             merged?
     26 #
     27 
     28 
     29 $num_states = 1;         # Always the state number for the line being compiled.
     30 $line_num  = 0;          # The line number in the input file.
     31 
     32 $states{"pop"} = 255;    # Add the "pop"  to the list of defined state names.
     33                          # This prevents any state from being labelled with "pop",
     34                          #  and resolves references to "pop" in the next state field.
     35 
     36 line_loop: while (<>) {
     37     chomp();
     38     $line = $_;
     39     @fields = split();
     40     $line_num++;
     41 
     42     # Remove # comments, which are any fields beginning with a #, plus all
     43     #  that follow on the line.
     44     for ($i=0; $i<@fields; $i++) {
     45         if ($fields[$i] =~ /^#/) {
     46             @fields = @fields[0 .. $i-1];
     47             last;
     48         }
     49     }
     50     # ignore blank lines, and those with no fields left after stripping comments..
     51     if (@fields == 0) {
     52         next;
     53     }
     54 
     55     #
     56     # State Label:  handling.
     57     #    Does the first token end with a ":"?  If so, it's the name  of a state.
     58     #    Put in a hash, together with the current state number,
     59     #        so that we can later look up the number from the name.
     60     #
     61     if (@fields[0] =~ /.*:$/) {
     62         $state_name = @fields[0];
     63         $state_name =~ s/://;        # strip off the colon from the state name.
     64 
     65         if ($states{$state_name} != 0) {
     66             print "  rbbicst: at line $line-num duplicate definition of state $state_name\n";
     67         }
     68         $states{$state_name} = $num_states;
     69         $stateNames[$num_states] = $state_name;
     70 
     71         # if the label was the only thing on this line, go on to the next line,
     72         # otherwise assume that a state definition is on the same line and fall through.
     73         if (@fields == 1) {
     74             next line_loop;
     75         }
     76         shift @fields;                       # shift off label field in preparation
     77                                              #  for handling the rest of the line.
     78     }
     79 
     80     #
     81     # State Transition line.
     82     #   syntax is this,
     83     #       character   [n]  target-state  [^push-state]  [function-name]
     84     #   where
     85     #      [something]   is an optional something
     86     #      character     is either a single quoted character e.g. '['
     87     #                       or a name of a character class, e.g. white_space
     88     #
     89 
     90     $state_line_num[$num_states] = $line_num;   # remember line number with each state
     91                                                 #  so we can make better error messages later.
     92     #
     93     # First field, character class or literal character for this transition.
     94     #
     95     if ($fields[0] =~ /^'.'$/) {
     96         # We've got a quoted literal character.
     97         $state_literal_chars[$num_states] = $fields[0];
     98         $state_literal_chars[$num_states] =~ s/'//g;
     99     } else {
    100         # We've got the name of a character class.
    101         $state_char_class[$num_states] = $fields[0];
    102         if ($fields[0] =~ /[\W]/) {
    103             print "  rbbicsts:  at line $line_num, bad character literal or character class name.\n";
    104             print "     scanning $fields[0]\n";
    105             exit(-1);
    106         }
    107     }
    108     shift @fields;
    109 
    110     #
    111     # do the 'n' flag
    112     #
    113     $state_flag[$num_states] = "FALSE";
    114     if ($fields[0] eq "n") {
    115         $state_flag[$num_states] = "TRUE";
    116         shift @fields;
    117     }
    118 
    119     #
    120     # do the destination state.
    121     #
    122     $state_dest_state[$num_states] = $fields[0];
    123     if ($fields[0] eq "") {
    124         print "  rbbicsts:  at line $line_num, destination state missing.\n";
    125         exit(-1);
    126     }
    127     shift @fields;
    128 
    129     #
    130     # do the push state, if present.
    131     #
    132     if ($fields[0] =~ /^\^/) {
    133         $fields[0] =~ s/^\^//;
    134         $state_push_state[$num_states] = $fields[0];
    135         if ($fields[0] eq "" ) {
    136             print "  rbbicsts:  at line $line_num, expected state after ^ (no spaces).\n";
    137             exit(-1);
    138         }
    139         shift @fields;
    140     }
    141 
    142     #
    143     # Lastly, do the optional action name.
    144     #
    145     if ($fields[0] ne "") {
    146         $state_func_name[$num_states] = $fields[0];
    147         shift @fields;
    148     }
    149 
    150     #
    151     #  There should be no fields left on the line at this point.
    152     #
    153     if (@fields > 0) {
    154        print "  rbbicsts:  at line $line_num, unexpected extra stuff on input line.\n";
    155        print "     scanning $fields[0]\n";
    156    }
    157    $num_states++;
    158 }
    159 
    160 #
    161 # We've read in the whole file, now go back and output the
    162 #   C source code for the state transition table.
    163 #
    164 # We read all states first, before writing anything,  so that the state numbers
    165 # for the destination states are all available to be written.
    166 #
    167 
    168 #
    169 # Make hashes for the names of the character classes and
    170 #      for the names of the actions that appeared.
    171 #
    172 for ($state=1; $state < $num_states; $state++) {
    173     if ($state_char_class[$state] ne "") {
    174         if ($charClasses{$state_char_class[$state]} == 0) {
    175             $charClasses{$state_char_class[$state]} = 1;
    176         }
    177     }
    178     if ($state_func_name[$state] eq "") {
    179         $state_func_name[$state] = "doNOP";
    180     }
    181     if ($actions{$state_action_name[$state]} == 0) {
    182         $actions{$state_func_name[$state]} = 1;
    183     }
    184 }
    185 
    186 #
    187 # Check that all of the destination states have been defined
    188 #
    189 #
    190 $states{"exit"} = 0;              # Predefined state name, terminates state machine.
    191 for ($state=1; $state<$num_states; $state++) {
    192    if ($states{$state_dest_state[$state]} == 0 && $state_dest_state[$state] ne "exit") {
    193        print "Error at line $state_line_num[$state]: target state \"$state_dest_state[$state]\" is not defined.\n";
    194        $errors++;
    195    }
    196    if ($state_push_state[$state] ne "" && $states{$state_push_state[$state]} == 0) {
    197        print "Error at line $state_line_num[$state]: target state \"$state_push_state[$state]\" is not defined.\n";
    198        $errors++;
    199    }
    200 }
    201 
    202 die if ($errors>0);
    203 
    204 print "//---------------------------------------------------------------------------------\n";
    205 print "//\n";
    206 print "// Generated Header File.  Do not edit by hand.\n";
    207 print "//    This file contains the state table for the ICU Regular Expression Pattern Parser\n";
    208 print "//    It is generated by the Perl script \"regexcst.pl\" from\n";
    209 print "//    the rule parser state definitions file \"regexcst.txt\".\n";
    210 print "//\n";
    211 print "//   Copyright (C) 2002-2016 International Business Machines Corporation \n";
    212 print "//   and others. All rights reserved.  \n";
    213 print "//\n";
    214 print "//---------------------------------------------------------------------------------\n";
    215 print "#ifndef RBBIRPT_H\n";
    216 print "#define RBBIRPT_H\n";
    217 print "\n";
    218 print "#include \"unicode/utypes.h\"\n";
    219 print "\n";
    220 print "U_NAMESPACE_BEGIN\n";
    221 
    222 #
    223 # Emit the constants for indicies of Unicode Sets
    224 #   Define one constant for each of the character classes encountered.
    225 #   At the same time, store the index corresponding to the set name back into hash.
    226 #
    227 print "//\n";
    228 print "// Character classes for regex pattern scanning.\n";
    229 print "//\n";
    230 $i = 128;                   # State Table values for Unicode char sets range from 128-250.
    231                             # Sets "default", "quoted", etc. get special handling.
    232                             #  They have no corresponding UnicodeSet object in the state machine,
    233                             #    but are handled by special case code.  So we emit no reference
    234                             #    to a UnicodeSet object to them here.
    235 foreach $setName (keys %charClasses) {
    236     if ($setName eq "default") {
    237         $charClasses{$setName} = 255;}
    238     elsif ($setName eq "quoted") {
    239         $charClasses{$setName} = 254;}
    240     elsif ($setName eq "eof") {
    241         $charClasses{$setName} = 253;}
    242     else {
    243         # Normal character class.  Fill in array with a ptr to the corresponding UnicodeSet in the state machine.
    244        print "    static const uint8_t kRuleSet_$setName = $i;\n";
    245         $charClasses{$setName} = $i;
    246         $i++;
    247     }
    248 }
    249 print "\n\n";
    250 
    251 #
    252 # Emit the enum for the actions to be performed.
    253 #
    254 print "enum Regex_PatternParseAction {\n";
    255 foreach $act (keys %actions) {
    256     print "    $act,\n";
    257 }
    258 print "    rbbiLastAction};\n\n";
    259 
    260 #
    261 # Emit the struct definition for transtion table elements.
    262 #
    263 print "//-------------------------------------------------------------------------------\n";
    264 print "//\n";
    265 print "//  RegexTableEl       represents the structure of a row in the transition table\n";
    266 print "//                     for the pattern parser state machine.\n";
    267 print "//-------------------------------------------------------------------------------\n";
    268 print "struct RegexTableEl {\n";
    269 print "    Regex_PatternParseAction      fAction;\n";
    270 print "    uint8_t                       fCharClass;       // 0-127:    an individual ASCII character\n";
    271 print "                                                    // 128-255:  character class index\n";
    272 print "    uint8_t                       fNextState;       // 0-250:    normal next-state numbers\n";
    273 print "                                                    // 255:      pop next-state from stack.\n";
    274 print "    uint8_t                       fPushState;\n";
    275 print "    UBool                         fNextChar;\n";
    276 print "};\n\n";
    277 
    278 #
    279 # emit the state transition table
    280 #
    281 print "static const struct RegexTableEl gRuleParseStateTable[] = {\n";
    282 print "    {doNOP, 0, 0, 0, TRUE}\n";    # State 0 is a dummy.  Real states start with index = 1.
    283 for ($state=1; $state < $num_states; $state++) {
    284     print "    , {$state_func_name[$state],";
    285     if ($state_literal_chars[$state] ne "") {
    286         $c = $state_literal_chars[$state];
    287         printf(" %d /* $c */,", ord($c));   #  use numeric value, so EBCDIC machines are ok.
    288     }else {
    289         print " $charClasses{$state_char_class[$state]},";
    290     }
    291     print " $states{$state_dest_state[$state]},";
    292 
    293     # The push-state field is optional.  If omitted, fill field with a zero, which flags
    294     #   the state machine that there is no push state.
    295     if ($state_push_state[$state] eq "") {
    296         print "0, ";
    297     } else {
    298         print " $states{$state_push_state[$state]},";
    299     }
    300     print " $state_flag[$state]} ";
    301 
    302     # Put out a C++ comment showing the number (index) of this state row,
    303     #   and, if this is the first row of the table for this state, the state name.
    304     print "    //  $state ";
    305     if ($stateNames[$state] ne "") {
    306         print "     $stateNames[$state]";
    307     }
    308     print "\n";
    309 };
    310 print " };\n";
    311 
    312 
    313 #
    314 # emit a mapping array from state numbers to state names.
    315 #
    316 #    This array is used for producing debugging output from the pattern parser.
    317 #
    318 print "static const char * const RegexStateNames[] = {";
    319 for ($state=0; $state<$num_states; $state++) {
    320     if ($stateNames[$state] ne "") {
    321         print "     \"$stateNames[$state]\",\n";
    322     } else {
    323         print "    0,\n";
    324     }
    325 }
    326 print "    0};\n\n";
    327 
    328 print "U_NAMESPACE_END\n";
    329 print "#endif\n";
    330 
    331 
    332 
    333