| | 1 | /* |
| | 2 | * Copyright (c) 2002 by Michael J. Roberts. All Rights Reserved. |
| | 3 | * |
| | 4 | * Please see the accompanying license file, LICENSE.TXT, for information |
| | 5 | * on using and copying this software. |
| | 6 | */ |
| | 7 | /* |
| | 8 | Name |
| | 9 | tccmdutl.cpp - TADS 3 Compiler command-line parsing utilities |
| | 10 | Function |
| | 11 | Defines some utility functions for command-line parsing. |
| | 12 | Notes |
| | 13 | |
| | 14 | Modified |
| | 15 | 04/03/02 MJRoberts - Creation |
| | 16 | */ |
| | 17 | |
| | 18 | #include <string.h> |
| | 19 | #include <stdlib.h> |
| | 20 | #include <ctype.h> |
| | 21 | |
| | 22 | #include "os.h" |
| | 23 | #include "t3std.h" |
| | 24 | #include "tccmdutl.h" |
| | 25 | |
| | 26 | /* ------------------------------------------------------------------------ */ |
| | 27 | /* |
| | 28 | * Get an option argument. We take a argument vector, the index of the |
| | 29 | * vector entry containing the option whose argument we're to fetch, and |
| | 30 | * the length of the option string. We'll return a pointer to the option |
| | 31 | * string, or null if no argument is present. |
| | 32 | * |
| | 33 | * We'll look for the argument's option first in the same vector entry |
| | 34 | * containing the option, appended directly to the option string; if |
| | 35 | * there's nothing there, we'll look for the argument in the next vector |
| | 36 | * entry. This lets us find option arguments that use syntax like |
| | 37 | * "-Itest" or "-I test". |
| | 38 | */ |
| | 39 | char *CTcCommandUtil::get_opt_arg(int argc, char **argv, |
| | 40 | int *curarg, int optlen) |
| | 41 | { |
| | 42 | /* |
| | 43 | * if it's jammed up against the option letter, get it from the |
| | 44 | * current array entry; otherwise, get it from the next array entry |
| | 45 | */ |
| | 46 | if (argv[*curarg][optlen + 1] != '\0') |
| | 47 | { |
| | 48 | /* it's attached to the same option entry */ |
| | 49 | return argv[*curarg] + optlen + 1; |
| | 50 | } |
| | 51 | else if (*curarg + 1 < argc) |
| | 52 | { |
| | 53 | /* it's by itself as the next option entry */ |
| | 54 | ++(*curarg); |
| | 55 | return argv[*curarg]; |
| | 56 | } |
| | 57 | else |
| | 58 | { |
| | 59 | /* it's not present at all */ |
| | 60 | return 0; |
| | 61 | } |
| | 62 | } |
| | 63 | |
| | 64 | /* ------------------------------------------------------------------------ */ |
| | 65 | /* |
| | 66 | * Simple option helper implementation for counting arguments. Some |
| | 67 | * callers will want to let us scan an option file to get an argument count |
| | 68 | * before they allocate space for the arguments. On these count-only |
| | 69 | * passes, the caller doesn't usually care about the contents of the file, |
| | 70 | * so they don't want to bother performing the full set of operations that |
| | 71 | * the helper normally defines. For these cases, callers can pass us a |
| | 72 | * null helper, in which case we'll use this simple helper by default. |
| | 73 | */ |
| | 74 | class CTcOptFileHelperDefault: public CTcOptFileHelper |
| | 75 | { |
| | 76 | public: |
| | 77 | /* allocate an option string */ |
| | 78 | virtual char *alloc_opt_file_str(size_t len) |
| | 79 | { |
| | 80 | return (char *)t3malloc(len + 1); |
| | 81 | } |
| | 82 | |
| | 83 | /* free a string allocated with alloc_opt_file_str() */ |
| | 84 | virtual void free_opt_file_str(char *str) |
| | 85 | { |
| | 86 | t3free(str); |
| | 87 | } |
| | 88 | |
| | 89 | /* process a comment line */ |
| | 90 | virtual void process_comment_line(const char *) { } |
| | 91 | |
| | 92 | /* process a non-comment line */ |
| | 93 | virtual void process_non_comment_line(const char *) { } |
| | 94 | |
| | 95 | /* process a configuration line */ |
| | 96 | virtual void process_config_line(const char *, const char *, int) { } |
| | 97 | }; |
| | 98 | |
| | 99 | |
| | 100 | /* ------------------------------------------------------------------------ */ |
| | 101 | /* |
| | 102 | * Parse an options file. If argv is null, we'll simply count the |
| | 103 | * arguments and return the count. If argv is non-null, it must be |
| | 104 | * allocated with the proper number of slots for the number of arguments |
| | 105 | * in the file (as obtained from a call to this function with argv = null). |
| | 106 | * |
| | 107 | * Each string returned in argv[] is separately allocated with a call to |
| | 108 | * helper->alloc_opt_file_str(). |
| | 109 | */ |
| | 110 | int CTcCommandUtil::parse_opt_file(osfildef *fp, char **argv, |
| | 111 | CTcOptFileHelper *helper) |
| | 112 | { |
| | 113 | char *buf; |
| | 114 | char config_id[128]; |
| | 115 | size_t buflen; |
| | 116 | int argc; |
| | 117 | CTcOptFileHelperDefault default_helper; |
| | 118 | |
| | 119 | /* if they didn't give us a helper object, use our default */ |
| | 120 | if (helper == 0) |
| | 121 | helper = &default_helper; |
| | 122 | |
| | 123 | /* allocate our initial buffer */ |
| | 124 | buflen = 512; |
| | 125 | buf = helper->alloc_opt_file_str(buflen); |
| | 126 | |
| | 127 | /* we're not in a configuration section yet */ |
| | 128 | config_id[0] = '\0'; |
| | 129 | |
| | 130 | /* keep going until we run out of text in the file */ |
| | 131 | for (argc = 0 ; ; ) |
| | 132 | { |
| | 133 | size_t len; |
| | 134 | char *p; |
| | 135 | |
| | 136 | /* read the next line - stop if we're done */ |
| | 137 | if (osfgets(buf, buflen, fp) == 0) |
| | 138 | break; |
| | 139 | |
| | 140 | /* check for proper termination */ |
| | 141 | for (;;) |
| | 142 | { |
| | 143 | /* get the buffer length */ |
| | 144 | len = strlen(buf); |
| | 145 | |
| | 146 | /* check for a newline of some kind at the end */ |
| | 147 | if (len != 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) |
| | 148 | { |
| | 149 | /* |
| | 150 | * remove consecutive newline/return characters - in case |
| | 151 | * we are using a file moved from another system with |
| | 152 | * incompatible newline conventions, simply remove all of |
| | 153 | * the newlines we find |
| | 154 | */ |
| | 155 | while (len != 0 |
| | 156 | && (buf[len-1] == '\n' || buf[len - 1] == '\r')) |
| | 157 | --len; |
| | 158 | |
| | 159 | /* null-terminate the line */ |
| | 160 | buf[len] = '\0'; |
| | 161 | |
| | 162 | /* we now have our line */ |
| | 163 | break; |
| | 164 | } |
| | 165 | else if (len == buflen - 1) |
| | 166 | { |
| | 167 | char *new_buf; |
| | 168 | size_t new_buf_len; |
| | 169 | |
| | 170 | /* |
| | 171 | * The buffer was completely filled, and wasn't |
| | 172 | * null-terminated - the line must be too long for the |
| | 173 | * buffer. Expand the buffer and go back for more. |
| | 174 | */ |
| | 175 | new_buf_len = buflen + 512; |
| | 176 | new_buf = helper->alloc_opt_file_str(new_buf_len); |
| | 177 | |
| | 178 | /* copy the old buffer into the new buffer */ |
| | 179 | memcpy(new_buf, buf, buflen); |
| | 180 | |
| | 181 | /* discard the old buffer */ |
| | 182 | helper->free_opt_file_str(buf); |
| | 183 | |
| | 184 | /* switch to the new buffer */ |
| | 185 | buf = new_buf; |
| | 186 | buflen = new_buf_len; |
| | 187 | |
| | 188 | /* append more data into the line buffer and try again */ |
| | 189 | if (osfgets(buf + len, buflen - len, fp) == 0) |
| | 190 | break; |
| | 191 | } |
| | 192 | else |
| | 193 | { |
| | 194 | /* |
| | 195 | * The buffer wasn't completely full, and we didn't find a |
| | 196 | * newline. This must mean that we've reached the end of |
| | 197 | * the file, so we've read as much as we can for this |
| | 198 | * line. |
| | 199 | */ |
| | 200 | break; |
| | 201 | } |
| | 202 | } |
| | 203 | |
| | 204 | /* skip leading spaces */ |
| | 205 | for (p = buf ; isspace(*p) ; ++p) ; |
| | 206 | |
| | 207 | /* |
| | 208 | * Check to see if we're entering a new configuration section. If |
| | 209 | * the line matches the pattern "[Config:xxx]", then we're starting |
| | 210 | * a new configuration section with identifier "xxx". |
| | 211 | */ |
| | 212 | if (strlen(p) > 8 && memicmp(p, "[config:", 8) == 0) |
| | 213 | { |
| | 214 | char *start; |
| | 215 | size_t copy_len; |
| | 216 | |
| | 217 | /* |
| | 218 | * note the part after the colon and up to the closing bracket |
| | 219 | * - it's the ID of this configuration section |
| | 220 | */ |
| | 221 | for (p += 8, start = p ; *p != ']' && *p != '\0' ; ++p) ; |
| | 222 | |
| | 223 | /* limit the ID size to our buffer maximum */ |
| | 224 | copy_len = p - start; |
| | 225 | if (copy_len > sizeof(config_id) - 1) |
| | 226 | copy_len = sizeof(config_id) - 1; |
| | 227 | |
| | 228 | /* copy and null-terminate the ID string */ |
| | 229 | memcpy(config_id, start, copy_len); |
| | 230 | config_id[copy_len] = '\0'; |
| | 231 | |
| | 232 | /* process the configuration ID line itself as a config line */ |
| | 233 | helper->process_config_line(config_id, buf, TRUE); |
| | 234 | |
| | 235 | /* we're done processing this line */ |
| | 236 | continue; |
| | 237 | } |
| | 238 | |
| | 239 | /* |
| | 240 | * if we're in a configuration section, simply process the line |
| | 241 | * through the helper as a configuration line - this doesn't |
| | 242 | * contain any options we can parse, since configuration sections |
| | 243 | * are private to their respective definers |
| | 244 | */ |
| | 245 | if (config_id[0] != '\0') |
| | 246 | { |
| | 247 | /* process the configuration line through the helper */ |
| | 248 | helper->process_config_line(config_id, buf, FALSE); |
| | 249 | |
| | 250 | /* we're done with this line */ |
| | 251 | continue; |
| | 252 | } |
| | 253 | |
| | 254 | /* |
| | 255 | * If we've reached the end of the line or a command character |
| | 256 | * ('#'), we're done with this line. Note that we check for |
| | 257 | * comments AFTER checking to see if we're in a config section, |
| | 258 | * because we want to send any comment lines within a config |
| | 259 | * section to the helper for processing as part of the config - |
| | 260 | * even comments are opaque to us within a config section. |
| | 261 | */ |
| | 262 | if (*p == '\0' || *p == '#') |
| | 263 | { |
| | 264 | /* tell the helper about the comment */ |
| | 265 | helper->process_comment_line(buf); |
| | 266 | |
| | 267 | /* we're done with the line - go back for the next one */ |
| | 268 | continue; |
| | 269 | } |
| | 270 | |
| | 271 | /* tell the helper about the non-comment line */ |
| | 272 | helper->process_non_comment_line(buf); |
| | 273 | |
| | 274 | /* parse the options on this line */ |
| | 275 | for (p = buf ; *p != '\0' ; ) |
| | 276 | { |
| | 277 | char *start; |
| | 278 | size_t arg_len; |
| | 279 | |
| | 280 | /* skip leading spaces */ |
| | 281 | for ( ; isspace(*p) ; ++p) ; |
| | 282 | |
| | 283 | /* check to see if we reached the end of the line */ |
| | 284 | if (*p == '\0') |
| | 285 | break; |
| | 286 | |
| | 287 | /* |
| | 288 | * if the argument is quoted, find the matching quote; |
| | 289 | * otherwise, look for the next space |
| | 290 | */ |
| | 291 | if (*p == '"') |
| | 292 | { |
| | 293 | char *dst; |
| | 294 | |
| | 295 | /* find the matching quote */ |
| | 296 | for (++p, start = dst = p ; *p != '\0' ; ++p) |
| | 297 | { |
| | 298 | /* |
| | 299 | * if this is a quote, check for stuttering - if it's |
| | 300 | * stuttered, treat it as a single quote (and convert |
| | 301 | * it to same), otherwise we've reached the end of the |
| | 302 | * argument |
| | 303 | */ |
| | 304 | if (*p == '"') |
| | 305 | { |
| | 306 | /* check for stuttering */ |
| | 307 | if (*(p+1) == '"') |
| | 308 | { |
| | 309 | /* |
| | 310 | * it's stuttered - skip the extra quote, so |
| | 311 | * that we copy only a single quote |
| | 312 | */ |
| | 313 | ++p; |
| | 314 | } |
| | 315 | else |
| | 316 | { |
| | 317 | /* this is our closing quote - skip it */ |
| | 318 | ++p; |
| | 319 | |
| | 320 | /* we're done with the argument */ |
| | 321 | break; |
| | 322 | } |
| | 323 | } |
| | 324 | |
| | 325 | /* copy this character to the output */ |
| | 326 | *dst++ = *p; |
| | 327 | } |
| | 328 | |
| | 329 | /* note the length of the argument */ |
| | 330 | arg_len = dst - start; |
| | 331 | } |
| | 332 | else |
| | 333 | { |
| | 334 | /* find the next space */ |
| | 335 | for (start = p ; *p != '\0' && !isspace(*p) ; ++p) ; |
| | 336 | |
| | 337 | /* note the length of the argument */ |
| | 338 | arg_len = p - start; |
| | 339 | } |
| | 340 | |
| | 341 | /* store the argument if we have an output vector */ |
| | 342 | if (argv != 0) |
| | 343 | { |
| | 344 | /* allocate the argument */ |
| | 345 | argv[argc] = helper->alloc_opt_file_str(arg_len + 1); |
| | 346 | |
| | 347 | /* copy it into the result */ |
| | 348 | memcpy(argv[argc], start, arg_len); |
| | 349 | argv[argc][arg_len] = '\0'; |
| | 350 | } |
| | 351 | |
| | 352 | /* count the argument */ |
| | 353 | ++argc; |
| | 354 | } |
| | 355 | } |
| | 356 | |
| | 357 | /* done with our line buffer - delete it */ |
| | 358 | helper->free_opt_file_str(buf); |
| | 359 | |
| | 360 | /* return the argument count */ |
| | 361 | return argc; |
| | 362 | } |
| | 363 | |