|
| 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 warningsFATAL=>'all'; |
| 52 | +use Getopt::Long; |
| 53 | + |
| 54 | +my$outfile =''; |
| 55 | + |
| 56 | +GetOptions('outfile=s'=> \$outfile)ordie"$0: wrong arguments"; |
| 57 | + |
| 58 | +openmy$infh,'<',$ARGV[0] |
| 59 | +ordie"$0: could not open input file '$ARGV[0]':$!\n"; |
| 60 | + |
| 61 | +my$outfh; |
| 62 | +if ($outfile) |
| 63 | +{ |
| 64 | +open$outfh,'>',$outfile |
| 65 | +ordie"$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 | +lastifm|^\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 | +lastifm|^\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 | +lastifm|^\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 | +subprocess_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 | +subprocess_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 | +subusage |
| 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 | +} |