2 # src/interfaces/ecpg/preproc/parse.pl
3 # parser generater for ecpg version 2
4 # call with backend parser as stdin
6 # Copyright (c) 2007-2018, PostgreSQL Global Development Group
8 # Written by Mike Aubury <mike.aubury@aubit.com>
9 # Michael Meskes <meskes@postgresql.org>
10 # Andy Colson <andy@squeakycode.net>
12 # Placed under the same license as PostgreSQL.
17 no warnings 'uninitialized';
19 my $path = shift @ARGV;
20 $path = "." unless $path;
25 my $header_included = 0;
26 my $feature_not_supported = 0;
29 my (%buff, $infield, $comment, %tokens, %addons);
30 my ($stmt_mode, @fields);
31 my ($line, $non_term_id);
34 # some token have to be replaced by other symbols
37 'BCONST' => 'ecpg_bconst',
38 'FCONST' => 'ecpg_fconst',
39 'Sconst' => 'ecpg_sconst',
40 'IDENT' => 'ecpg_ident',
41 'PARAM' => 'ecpg_param',);
44 my %replace_string = (
46 'NULLS_LA' => 'nulls',
50 'COLON_EQUALS' => ':=',
51 'EQUALS_GREATER' => '=>',
52 'LESS_EQUALS' => '<=',
53 'GREATER_EQUALS' => '>=',
54 'NOT_EQUALS' => '<>',);
56 # specific replace_types for specific non-terminals - never include the ':'
57 # ECPG-only replace_types are defined in ecpg-replace_types
59 'PrepareStmt' => '<prep>',
60 'opt_array_bounds' => '<index>',
62 # "ignore" means: do not create type and rules for this non-term-id
63 'stmtblock' => 'ignore',
64 'stmtmulti' => 'ignore',
65 'CreateAsStmt' => 'ignore',
66 'DeallocateStmt' => 'ignore',
68 'type_function_name' => 'ignore',
69 'ColLabel' => 'ignore',
70 'Sconst' => 'ignore',);
72 # these replace_line commands excise certain keywords from the core keyword
73 # lists. Be sure to account for these in ColLabel and related productions.
75 'unreserved_keywordCONNECTION' => 'ignore',
76 'unreserved_keywordCURRENT_P' => 'ignore',
77 'unreserved_keywordDAY_P' => 'ignore',
78 'unreserved_keywordHOUR_P' => 'ignore',
79 'unreserved_keywordINPUT_P' => 'ignore',
80 'unreserved_keywordMINUTE_P' => 'ignore',
81 'unreserved_keywordMONTH_P' => 'ignore',
82 'unreserved_keywordSECOND_P' => 'ignore',
83 'unreserved_keywordYEAR_P' => 'ignore',
84 'col_name_keywordCHAR_P' => 'ignore',
85 'col_name_keywordINT_P' => 'ignore',
86 'col_name_keywordVALUES' => 'ignore',
87 'reserved_keywordTO' => 'ignore',
88 'reserved_keywordUNION' => 'ignore',
90 # some other production rules have to be ignored or replaced
91 'fetch_argsFORWARDopt_from_incursor_name' => 'ignore',
92 'fetch_argsBACKWARDopt_from_incursor_name' => 'ignore',
93 "opt_array_boundsopt_array_bounds'['Iconst']'" => 'ignore',
94 'VariableShowStmtSHOWvar_name' => 'SHOW var_name ecpg_into',
95 'VariableShowStmtSHOWTIMEZONE' => 'SHOW TIME ZONE ecpg_into',
96 'VariableShowStmtSHOWTRANSACTIONISOLATIONLEVEL' =>
97 'SHOW TRANSACTION ISOLATION LEVEL ecpg_into',
98 'VariableShowStmtSHOWSESSIONAUTHORIZATION' =>
99 'SHOW SESSION AUTHORIZATION ecpg_into',
100 'returning_clauseRETURNINGtarget_list' =>
101 'RETURNING target_list opt_ecpg_into',
102 'ExecuteStmtEXECUTEnameexecute_param_clause' =>
103 'EXECUTE prepared_name execute_param_clause execute_rest',
104 'ExecuteStmtCREATEOptTempTABLEcreate_as_targetASEXECUTEnameexecute_param_clause'
105 => 'CREATE OptTemp TABLE create_as_target AS EXECUTE prepared_name execute_param_clause',
106 'PrepareStmtPREPAREnameprep_type_clauseASPreparableStmt' =>
107 'PREPARE prepared_name prep_type_clause AS PreparableStmt',
108 'var_nameColId' => 'ECPGColId',);
114 dump_buffer('header');
115 dump_buffer('tokens');
116 dump_buffer('types');
117 dump_buffer('ecpgtype');
118 dump_buffer('orig_tokens');
120 print 'prog: statements;', "\n";
121 dump_buffer('rules');
122 include_file('trailer', 'ecpg.trailer');
123 dump_buffer('trailer');
129 if (/ERRCODE_FEATURE_NOT_SUPPORTED/)
131 $feature_not_supported = 1;
137 # comment out the line below to make the result file match (blank line wise)
141 # Dump the action for a rule -
142 # stmt_mode indicates if we are processing the 'stmt:'
143 # rule (mode==0 means normal, mode==1 means stmt:)
144 # flds are the fields to use. These may start with a '$' - in
145 # which case they are the result of a previous non-terminal
147 # if they dont start with a '$' then they are token name
149 # len is the number of fields in flds...
150 # leadin is the padding to apply at the beginning (just use for formatting)
162 # Make sure any braces are split
166 # Any comments are split
170 # Now split the line into individual fields
171 my @arr = split(' ');
173 if ($arr[0] eq '%token' && $tokenmode == 0)
176 include_file('tokens', 'ecpg.tokens');
178 elsif ($arr[0] eq '%type' && $header_included == 0)
180 include_file('header', 'ecpg.header');
181 include_file('ecpgtype', 'ecpg.type');
182 $header_included = 1;
205 if (substr($a, 0, 1) eq '<')
213 $str = $str . ' ' . $a;
214 if ($a eq 'IDENT' && $prior eq '%nonassoc')
217 # add two more tokens to the list
218 $str = $str . "\n%nonassoc CSTRING\n%nonassoc UIDENT";
222 add_to_buffer('orig_tokens', $str);
226 # Dont worry about anything if we're not in the right section of gram.y
233 # Go through each field in turn
235 my $fieldIndexer = 0;
236 $fieldIndexer < scalar(@arr);
239 if ($arr[$fieldIndexer] eq '*/' && $comment)
248 elsif ($arr[$fieldIndexer] eq '/*')
251 # start of a multiline comment
255 elsif ($arr[$fieldIndexer] eq '//')
259 elsif ($arr[$fieldIndexer] eq '}')
264 elsif ($arr[$fieldIndexer] eq '{')
270 if ($brace_indent > 0)
274 if ($arr[$fieldIndexer] eq ';')
280 dump_line($stmt_mode, \@fields);
282 add_to_buffer('rules', ";\n\n");
294 if ($arr[$fieldIndexer] eq '|')
300 $infield = $infield + dump_line($stmt_mode, \@fields);
311 if (exists $replace_token{ $arr[$fieldIndexer] })
313 $arr[$fieldIndexer] = $replace_token{ $arr[$fieldIndexer] };
316 # Are we looking at a declaration of a non-terminal ?
317 if (($arr[$fieldIndexer] =~ /[A-Za-z0-9]+:/)
318 || $arr[ $fieldIndexer + 1 ] eq ':')
320 $non_term_id = $arr[$fieldIndexer];
321 $non_term_id =~ tr/://d;
323 if (not defined $replace_types{$non_term_id})
325 $replace_types{$non_term_id} = '<str>';
328 elsif ($replace_types{$non_term_id} eq 'ignore')
334 $line = $line . ' ' . $arr[$fieldIndexer];
336 # Do we have the : attached already ?
337 # If yes, we'll have already printed the ':'
338 if (!($arr[$fieldIndexer] =~ '[A-Za-z0-9]+:'))
341 # Consume the ':' which is next...
347 if ($non_term_id eq 'stmt')
357 . $replace_types{$non_term_id} . ' '
359 add_to_buffer('types', $tstr);
363 add_to_buffer('rules', $line);
372 $line = $line . ' ' . $arr[$fieldIndexer];
374 if ($arr[$fieldIndexer] eq '%prec')
383 && length($arr[$fieldIndexer])
386 if ($arr[$fieldIndexer] ne 'Op'
387 && ( $tokens{ $arr[$fieldIndexer] } > 0
388 || $arr[$fieldIndexer] =~ /'.+'/)
392 if (exists $replace_string{ $arr[$fieldIndexer] })
394 $S = $replace_string{ $arr[$fieldIndexer] };
398 $S = $arr[$fieldIndexer];
408 push(@fields, lc($S));
413 push(@fields, '$' . (scalar(@fields) + 1));
421 # append a file onto a buffer.
422 # Arguments: buffer_name, filename (without path)
425 my ($buffer, $filename) = @_;
426 my $full = "$path/$filename";
427 open(my $fh, '<', $full) or die;
431 add_to_buffer($buffer, $_);
438 my ($buffer, $block, $fields, $stmt_mode) = @_;
439 my $rec = $addons{$block};
440 return 0 unless $rec;
442 if ($rec->{type} eq 'rule')
444 dump_fields($stmt_mode, $fields, ' { ');
446 elsif ($rec->{type} eq 'addon')
448 add_to_buffer('rules', ' { ');
451 #add_to_buffer( $stream, $_ );
452 #We have an array to add to the buffer, we'll add it ourself instead of
453 #calling add_to_buffer, which does not know about arrays
455 push(@{ $buff{$buffer} }, @{ $rec->{lines} });
457 if ($rec->{type} eq 'addon')
459 dump_fields($stmt_mode, $fields, '');
463 # if we added something (ie there are lines in our array), return 1
464 return 1 if (scalar(@{ $rec->{lines} }) > 0);
469 # include_addon does this same thing, but does not call this
470 # sub... so if you change this, you need to fix include_addon too
471 # Pass: buffer_name, string_to_append
474 push(@{ $buff{ $_[0] } }, "$_[1]\n");
480 print '/* ', $buffer, ' */', "\n";
481 my $ref = $buff{$buffer};
487 my ($mode, $flds, $ln) = @_;
488 my $len = scalar(@$flds);
494 add_to_buffer('rules', $ln);
495 if ($feature_not_supported == 1)
498 # we found an unsupported feature, but we have to
499 # filter out ExecuteStmt: CREATE OptTemp TABLE ...
500 # because the warning there is only valid in some situations
501 if ($flds->[0] ne 'create' || $flds->[2] ne 'table')
503 add_to_buffer('rules',
504 'mmerror(PARSE_ERROR, ET_WARNING, "unsupported feature will be passed to server");'
507 $feature_not_supported = 0;
513 # We have no fields ?
514 add_to_buffer('rules', ' $$=EMPTY; }');
519 # Go through each field and try to 'aggregate' the tokens
520 # into a single 'mm_strdup' where possible
523 for (my $z = 0; $z < $len; $z++)
525 if (substr($flds->[$z], 0, 1) eq '$')
527 push(@flds_new, $flds->[$z]);
536 || substr($flds->[ $z + 1 ], 0, 1) eq '$')
539 # We're at the end...
540 push(@flds_new, "mm_strdup(\"$str\")");
544 $str = $str . ' ' . $flds->[$z];
548 # So - how many fields did we end up with ?
549 $len = scalar(@flds_new);
553 # Straight assignment
554 $str = ' $$ = ' . $flds_new[0] . ';';
555 add_to_buffer('rules', $str);
560 # Need to concatenate the results to form
563 ' $$ = cat_str(' . $len . ',' . join(',', @flds_new) . ');';
564 add_to_buffer('rules', $str);
566 add_to_buffer('rules', '}');
572 # we're in the stmt: rule
576 # or just the statement ...
577 add_to_buffer('rules',
578 ' { output_statement($1, 0, ECPGst_normal); }');
582 add_to_buffer('rules', ' { $$ = NULL; }');
590 my ($stmt_mode, $fields) = @_;
591 my $block = $non_term_id . $line;
593 my $rep = $replace_line{$block};
596 if ($rep eq 'ignore')
601 if (index($line, '|') != -1)
609 $block = $non_term_id . $line;
612 add_to_buffer('rules', $line);
613 my $i = include_addon('rules', $block, $fields, $stmt_mode);
616 dump_fields($stmt_mode, $fields, ' { ');
622 load addons into cache
624 stmtClosePortalStmt => { 'type' => 'block', 'lines' => [ "{", "if (INFORMIX_MODE)" ..., "}" ] },
625 stmtViewStmt => { 'type' => 'rule', 'lines' => [ "| ECPGAllocateDescr", ... ] }
632 my $filename = $path . "/ecpg.addons";
633 open(my $fh, '<', $filename) or die;
635 # there may be multiple lines starting ECPG: and then multiple lines of code.
636 # the code need to be add to all prior ECPG records.
637 my (@needsRules, @code, $record);
639 # there may be comments before the first ECPG line, skip them
643 if (/^ECPG:\s(\S+)\s?(\w+)?/)
648 for my $x (@needsRules)
650 push(@{ $x->{lines} }, @code);
656 $record->{type} = $2;
657 $record->{lines} = [];
658 if (exists $addons{$1}) { die "Ga! there are dups!\n"; }
659 $addons{$1} = $record;
660 push(@needsRules, $record);
671 for my $x (@needsRules)
673 push(@{ $x->{lines} }, @code);