|
| 1 | +#---------------------------------------------------------------------- |
| 2 | +# |
| 3 | +# gen_tabcomplete.pl |
| 4 | +# Perl script that transforms tab-complete.in.c to tab-complete.c. |
| 5 | +# |
| 6 | +# This script converts a C else-if chain into a switch statement. |
| 7 | +# The else-if statements to be processed must appear at single-tab-stop |
| 8 | +# indentation between lines reading |
| 9 | +# /* BEGIN GEN_TABCOMPLETE */ |
| 10 | +# /* END GEN_TABCOMPLETE */ |
| 11 | +# The first clause in each if-condition must be a call of one of the |
| 12 | +# functions Matches, HeadMatches, TailMatches, MatchesCS, HeadMatchesCS, |
| 13 | +# or TailMatchesCS. Its argument(s) must be string literals or macros |
| 14 | +# that expand to string literals or NULL. These clauses are removed from |
| 15 | +# the code and replaced by "break; case N:", where N is a unique number |
| 16 | +# for each such case label. |
| 17 | +# The BEGIN GEN_TABCOMPLETE and END GEN_TABCOMPLETE lines are replaced |
| 18 | +# by "switch (pattern_id) {" and "}" wrapping to make a valid switch. |
| 19 | +# The remainder of the code is copied verbatim. |
| 20 | +# |
| 21 | +# An if-condition can also be an OR ("||") of several *Matches function |
| 22 | +# calls, or it can be an AND ("&&") of a *Matches call with some other |
| 23 | +# condition. For example, |
| 24 | +# |
| 25 | +# else if (HeadMatches("DROP", "DATABASE") && ends_with(prev_wd, '(')) |
| 26 | +# |
| 27 | +# will be transformed to |
| 28 | +# |
| 29 | +# break; |
| 30 | +# case N: |
| 31 | +# if (ends_with(prev_wd, '(')) |
| 32 | +# |
| 33 | +# In addition, there must be one input line that reads |
| 34 | +# /* Insert tab-completion pattern data here. */ |
| 35 | +# This line is replaced in the output file by macro calls, one for each |
| 36 | +# replaced match condition. The output for the above example would be |
| 37 | +# TCPAT(N, HeadMatch, "DROP", "DATABASE"), |
| 38 | +# where N is the replacement case label, "HeadMatch" is the original |
| 39 | +# function name minus "es", and the rest are the function arguments. |
| 40 | +# The tab-completion data line must appear before BEGIN GEN_TABCOMPLETE. |
| 41 | +# |
| 42 | +# |
| 43 | +# Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group |
| 44 | +# Portions Copyright (c) 1994, Regents of the University of California |
| 45 | +# |
| 46 | +# src/bin/psql/gen_tabcomplete.pl |
| 47 | +# |
| 48 | +#---------------------------------------------------------------------- |
| 49 | + |
| 50 | +use strict; |
| 51 | +use warnings FATAL => 'all'; |
| 52 | +use Getopt::Long; |
| 53 | + |
| 54 | +my $outfile = ''; |
| 55 | + |
| 56 | +GetOptions('outfile=s' => \$outfile) or die "$0: wrong arguments"; |
| 57 | + |
| 58 | +open my $infh, '<', $ARGV[0] |
| 59 | + or die "$0: could not open input file '$ARGV[0]': $!\n"; |
| 60 | + |
| 61 | +my $outfh; |
| 62 | +if ($outfile) |
| 63 | +{ |
| 64 | + open $outfh, '>', $outfile |
| 65 | + or die "$0: could not open output file '$outfile': $!\n"; |
| 66 | +} |
| 67 | +else |
| 68 | +{ |
| 69 | + $outfh = *STDOUT; |
| 70 | +} |
| 71 | + |
| 72 | +# Opening boilerplate for output file. |
| 73 | +printf $outfh <<EOM; |
| 74 | +/*------------------------------------------------------------------------- |
| 75 | + * |
| 76 | + * tab-complete.c |
| 77 | + * Preprocessed tab-completion code. |
| 78 | + * |
| 79 | + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group |
| 80 | + * Portions Copyright (c) 1994, Regents of the University of California |
| 81 | + * |
| 82 | + * NOTES |
| 83 | + * ****************************** |
| 84 | + * *** DO NOT EDIT THIS FILE! *** |
| 85 | + * ****************************** |
| 86 | + * |
| 87 | + * It has been GENERATED by src/bin/psql/gen_tabcomplete.pl |
| 88 | + * |
| 89 | + *------------------------------------------------------------------------- |
| 90 | + */ |
| 91 | +
|
| 92 | +#define SWITCH_CONVERSION_APPLIED |
| 93 | +
|
| 94 | +#line 1 "tab-complete.in.c" |
| 95 | +EOM |
| 96 | + |
| 97 | +# Scan input file until we find the data-replacement label line. |
| 98 | +# Dump what we scan directly into the output file. |
| 99 | +while (<$infh>) |
| 100 | +{ |
| 101 | + chomp; |
| 102 | + last if m|^\s*/\* Insert tab-completion pattern data here\. \*/\s*$|; |
| 103 | + print $outfh "$_\n"; |
| 104 | +} |
| 105 | + |
| 106 | +# $table_data collects what we will substitute for the "pattern data" line. |
| 107 | +my $table_data = ''; |
| 108 | +# $output_code collects code that we can't emit till after $table_data. |
| 109 | +my $output_code = ''; |
| 110 | +# last case label assigned |
| 111 | +my $last_case_label = 0; |
| 112 | + |
| 113 | +# We emit #line directives to keep the output file's line numbering in sync |
| 114 | +# with the line numbering of the original, to simplify compiler error message |
| 115 | +# reading and debugging. |
| 116 | +my $next_line_no = $. + 1; |
| 117 | +$output_code .= "#line ${next_line_no} \"tab-complete.in.c\"\n"; |
| 118 | + |
| 119 | +# Scan until we find the BEGIN GEN_TABCOMPLETE line. |
| 120 | +# Add the scanned code to $output_code verbatim. |
| 121 | +while (<$infh>) |
| 122 | +{ |
| 123 | + chomp; |
| 124 | + last if m|^\s*/\* BEGIN GEN_TABCOMPLETE \*/\s*$|; |
| 125 | + $output_code .= $_ . "\n"; |
| 126 | +} |
| 127 | + |
| 128 | +# Emit the switch-starting lines. |
| 129 | +$output_code .= "\tswitch (pattern_id)\n"; |
| 130 | +$output_code .= "\t{\n"; |
| 131 | + |
| 132 | +# Keep line numbering in sync. |
| 133 | +$next_line_no = $. + 1; |
| 134 | +$output_code .= "#line ${next_line_no} \"tab-complete.in.c\"\n"; |
| 135 | + |
| 136 | +# Scan input file, collecting outer-level else-if conditions |
| 137 | +# to pass to process_else_if. |
| 138 | +# Lines that aren't else-if conditions go to $output_code verbatim. |
| 139 | +# True if we're handling a multiline else-if condition |
| 140 | +my $in_else_if = 0; |
| 141 | +# The accumulated line |
| 142 | +my $else_if_line; |
| 143 | +my $else_if_lineno; |
| 144 | + |
| 145 | +while (<$infh>) |
| 146 | +{ |
| 147 | + chomp; |
| 148 | + last if m|^\s*/\* END GEN_TABCOMPLETE \*/\s*$|; |
| 149 | + if ($in_else_if) |
| 150 | + { |
| 151 | + my $rest = $_; |
| 152 | + # collapse leading whitespace |
| 153 | + $rest =~ s/^\s+//; |
| 154 | + $else_if_line .= ' ' . $rest; |
| 155 | + # Double right paren is currently sufficient to detect completion |
| 156 | + if ($else_if_line =~ m/\)\)$/) |
| 157 | + { |
| 158 | + process_else_if($else_if_line, $else_if_lineno, $.); |
| 159 | + $in_else_if = 0; |
| 160 | + } |
| 161 | + } |
| 162 | + elsif (m/^\telse if \(/) |
| 163 | + { |
| 164 | + $else_if_line = $_; |
| 165 | + $else_if_lineno = $.; |
| 166 | + # Double right paren is currently sufficient to detect completion |
| 167 | + if ($else_if_line =~ m/\)\)$/) |
| 168 | + { |
| 169 | + process_else_if($else_if_line, $else_if_lineno, $.); |
| 170 | + } |
| 171 | + else |
| 172 | + { |
| 173 | + $in_else_if = 1; |
| 174 | + } |
| 175 | + } |
| 176 | + else |
| 177 | + { |
| 178 | + $output_code .= $_ . "\n"; |
| 179 | + } |
| 180 | +} |
| 181 | + |
| 182 | +die "unfinished else-if" if $in_else_if; |
| 183 | + |
| 184 | +# Emit the switch-ending lines. |
| 185 | +$output_code .= "\tbreak;\n"; |
| 186 | +$output_code .= "\tdefault:\n"; |
| 187 | +$output_code .= "\t\tAssert(false);\n"; |
| 188 | +$output_code .= "\t\tbreak;\n"; |
| 189 | +$output_code .= "\t}\n"; |
| 190 | + |
| 191 | +# Keep line numbering in sync. |
| 192 | +$next_line_no = $. + 1; |
| 193 | +$output_code .= "#line ${next_line_no} \"tab-complete.in.c\"\n"; |
| 194 | + |
| 195 | +# Scan the rest, adding it to $output_code verbatim. |
| 196 | +while (<$infh>) |
| 197 | +{ |
| 198 | + chomp; |
| 199 | + $output_code .= $_ . "\n"; |
| 200 | +} |
| 201 | + |
| 202 | +# Dump out the table data. |
| 203 | +print $outfh $table_data; |
| 204 | + |
| 205 | +# Dump out the modified code, and we're done! |
| 206 | +print $outfh $output_code; |
| 207 | + |
| 208 | +close($infh); |
| 209 | +close($outfh); |
| 210 | + |
| 211 | +# Disassemble an else-if condition. |
| 212 | +# Add the generated table-contents macro(s) to $table_data, |
| 213 | +# and add the replacement case label(s) to $output_code. |
| 214 | +sub process_else_if |
| 215 | +{ |
| 216 | + my ($else_if_line, $else_if_lineno, $end_lineno) = @_; |
| 217 | + |
| 218 | + # Strip the initial "else if (", which we know is there |
| 219 | + $else_if_line =~ s/^\telse if \(//; |
| 220 | + |
| 221 | + # Handle OR'd conditions |
| 222 | + my $isfirst = 1; |
| 223 | + while ($else_if_line =~ |
| 224 | + s/^(Head|Tail|)Matches(CS|)\((("[^"]*"|MatchAnyExcept\("[^"]*"\)|[A-Za-z,\s])+)\)\s*\|\|\s*// |
| 225 | + ) |
| 226 | + { |
| 227 | + my $typ = $1; |
| 228 | + my $cs = $2; |
| 229 | + my $args = $3; |
| 230 | + process_match($typ, $cs, $args, $else_if_lineno, $isfirst); |
| 231 | + $isfirst = 0; |
| 232 | + } |
| 233 | + |
| 234 | + # Check for AND'd condition |
| 235 | + if ($else_if_line =~ |
| 236 | + s/^(Head|Tail|)Matches(CS|)\((("[^"]*"|MatchAnyExcept\("[^"]*"\)|[A-Za-z,\s])+)\)\s*&&\s*// |
| 237 | + ) |
| 238 | + { |
| 239 | + my $typ = $1; |
| 240 | + my $cs = $2; |
| 241 | + my $args = $3; |
| 242 | + warn |
| 243 | + "could not process OR/ANDed if condition at line $else_if_lineno\n" |
| 244 | + if !$isfirst; |
| 245 | + process_match($typ, $cs, $args, $else_if_lineno, $isfirst); |
| 246 | + $isfirst = 0; |
| 247 | + # approximate line positioning of AND'd condition |
| 248 | + $output_code .= "#line ${end_lineno} \"tab-complete.in.c\"\n"; |
| 249 | + $output_code .= "\tif ($else_if_line\n"; |
| 250 | + } |
| 251 | + elsif ($else_if_line =~ |
| 252 | + s/^(Head|Tail|)Matches(CS|)\((("[^"]*"|MatchAnyExcept\("[^"]*"\)|[A-Za-z,\s])+)\)\)$// |
| 253 | + ) |
| 254 | + { |
| 255 | + my $typ = $1; |
| 256 | + my $cs = $2; |
| 257 | + my $args = $3; |
| 258 | + process_match($typ, $cs, $args, $else_if_lineno, $isfirst); |
| 259 | + $isfirst = 0; |
| 260 | + } |
| 261 | + else |
| 262 | + { |
| 263 | + warn |
| 264 | + "could not process if condition at line $else_if_lineno: the rest looks like $else_if_line\n"; |
| 265 | + $output_code .= "\telse if ($else_if_line\n"; |
| 266 | + } |
| 267 | + |
| 268 | + # Keep line numbering in sync. |
| 269 | + if ($end_lineno != $else_if_lineno) |
| 270 | + { |
| 271 | + my $next_lineno = $end_lineno + 1; |
| 272 | + $output_code .= "#line ${next_lineno} \"tab-complete.in.c\"\n"; |
| 273 | + } |
| 274 | +} |
| 275 | + |
| 276 | +sub process_match |
| 277 | +{ |
| 278 | + my ($typ, $cs, $args, $lineno, $isfirst) = @_; |
| 279 | + |
| 280 | + # Assign a new case label only for the first pattern in an OR group. |
| 281 | + if ($isfirst) |
| 282 | + { |
| 283 | + $last_case_label++; |
| 284 | + |
| 285 | + # We intentionally keep the "break;" and the "case" on one line, so |
| 286 | + # that they have the same line number as the original "else if"'s |
| 287 | + # first line. This avoids misleading displays in, e.g., lcov. |
| 288 | + $output_code .= "\t"; |
| 289 | + $output_code .= "break; " if $last_case_label > 1; |
| 290 | + $output_code .= "case $last_case_label:\n"; |
| 291 | + } |
| 292 | + |
| 293 | + $table_data .= |
| 294 | + "\tTCPAT(${last_case_label}, ${typ}Match${cs}, ${args}),\n"; |
| 295 | +} |
| 296 | + |
| 297 | + |
| 298 | +sub usage |
| 299 | +{ |
| 300 | + die <<EOM; |
| 301 | +Usage: gen_tabcomplete.pl [--outfile/-o <path>] input_file |
| 302 | + --outfile Output file (default is stdout) |
| 303 | +
|
| 304 | +gen_tabcomplete.pl transforms tab-complete.in.c to tab-complete.c. |
| 305 | +EOM |
| 306 | +} |
0 commit comments