| | 1 | /* |
| | 2 | * Copyright (c) 2002, 2002 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 | tclibprs.cpp - tads compiler: library parser |
| | 10 | Function |
| | 11 | Parses .tl files, which are text files that reference source files |
| | 12 | for inclusion in a compilation. A .tl file can be used in a compilation |
| | 13 | as though it were a source file, and stands for the set of source files |
| | 14 | it references. A .tl file can reference other .tl files. |
| | 15 | Notes |
| | 16 | |
| | 17 | Modified |
| | 18 | 01/08/02 MJRoberts - Creation |
| | 19 | */ |
| | 20 | |
| | 21 | #include <ctype.h> |
| | 22 | #include <string.h> |
| | 23 | #include <stdlib.h> |
| | 24 | #include <stdio.h> |
| | 25 | |
| | 26 | #include "os.h" |
| | 27 | #include "t3std.h" |
| | 28 | #include "tclibprs.h" |
| | 29 | #include "tcsrc.h" |
| | 30 | |
| | 31 | |
| | 32 | /* |
| | 33 | * Instantiate. We'll note the name of the library, but we don't attempt |
| | 34 | * to open it at this point - we won't open the file until parse_lib() is |
| | 35 | * called. |
| | 36 | */ |
| | 37 | CTcLibParser::CTcLibParser(const char *lib_name) |
| | 38 | { |
| | 39 | char lib_path[OSFNMAX]; |
| | 40 | |
| | 41 | /* |
| | 42 | * Extract the library's directory path. Since files within a library |
| | 43 | * are always relative to the directory containing the library itself, |
| | 44 | * we use the full path to the library as the starting point for each |
| | 45 | * file found within the library. |
| | 46 | */ |
| | 47 | os_get_path_name(lib_path, sizeof(lib_path), lib_name); |
| | 48 | |
| | 49 | /* remember the library path */ |
| | 50 | lib_path_ = lib_copy_str(lib_path); |
| | 51 | |
| | 52 | /* remember the name of the library */ |
| | 53 | lib_name_ = lib_copy_str(lib_name); |
| | 54 | |
| | 55 | /* no errors yet */ |
| | 56 | err_cnt_ = 0; |
| | 57 | |
| | 58 | /* no lines read yet */ |
| | 59 | linenum_ = 0; |
| | 60 | } |
| | 61 | |
| | 62 | CTcLibParser::~CTcLibParser() |
| | 63 | { |
| | 64 | /* delete our library filename and path */ |
| | 65 | lib_free_str(lib_name_); |
| | 66 | lib_free_str(lib_path_); |
| | 67 | } |
| | 68 | |
| | 69 | /* |
| | 70 | * Parse a library file. |
| | 71 | */ |
| | 72 | void CTcLibParser::parse_lib() |
| | 73 | { |
| | 74 | CTcSrcFile *fp; |
| | 75 | |
| | 76 | /* open our file - we only accept plain ASCII here */ |
| | 77 | fp = CTcSrcFile::open_ascii(lib_name_); |
| | 78 | |
| | 79 | /* if that failed, abort */ |
| | 80 | if (fp == 0) |
| | 81 | { |
| | 82 | err_open_file(); |
| | 83 | ++err_cnt_; |
| | 84 | return; |
| | 85 | } |
| | 86 | |
| | 87 | /* keep going until we run out of file */ |
| | 88 | for (;;) |
| | 89 | { |
| | 90 | char buf[1024]; |
| | 91 | char expbuf[1024]; |
| | 92 | size_t len; |
| | 93 | char *p; |
| | 94 | char *dst; |
| | 95 | char *sep; |
| | 96 | char *var_name; |
| | 97 | char *var_val; |
| | 98 | |
| | 99 | /* read the next line of text */ |
| | 100 | if ((len = fp->read_line(buf, sizeof(buf))) == 0) |
| | 101 | break; |
| | 102 | |
| | 103 | /* count the line */ |
| | 104 | ++linenum_; |
| | 105 | |
| | 106 | /* un-count the terminating null byte in the length */ |
| | 107 | --len; |
| | 108 | |
| | 109 | /* check for a terminating newline sequence */ |
| | 110 | while (len != 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) |
| | 111 | --len; |
| | 112 | |
| | 113 | /* if we didn't find a newline, the line is too long */ |
| | 114 | if (buf[len] == '\0') |
| | 115 | { |
| | 116 | char buf2[128]; |
| | 117 | size_t len2; |
| | 118 | |
| | 119 | /* |
| | 120 | * If we can read anything more from the file, this indicates |
| | 121 | * that the line was simply too long to read into our buffer; |
| | 122 | * skip the rest of the line and log an error in this case. |
| | 123 | * If we can't read any more text, it means we've reached the |
| | 124 | * end of the file. |
| | 125 | */ |
| | 126 | if ((len2 = fp->read_line(buf2, sizeof(buf2))) != 0) |
| | 127 | { |
| | 128 | /* there's more, so the line is too long - flag an error */ |
| | 129 | err_line_too_long(); |
| | 130 | ++err_cnt_; |
| | 131 | |
| | 132 | /* skip text until we find a newline */ |
| | 133 | for (;;) |
| | 134 | { |
| | 135 | /* un-count the null terminator */ |
| | 136 | --len2; |
| | 137 | |
| | 138 | /* if we've found our newline, we're done skipping */ |
| | 139 | if (len2 != 0 |
| | 140 | && (buf2[len2-1] == '\n' || buf2[len2-1] == '\r')) |
| | 141 | break; |
| | 142 | |
| | 143 | /* read another chunk */ |
| | 144 | if ((len2 = fp->read_line(buf2, sizeof(buf2))) == 0) |
| | 145 | break; |
| | 146 | } |
| | 147 | |
| | 148 | /* we've skipped the line, so go back for the next */ |
| | 149 | continue; |
| | 150 | } |
| | 151 | } |
| | 152 | |
| | 153 | /* remove the newline from the buffer */ |
| | 154 | buf[len] = '\0'; |
| | 155 | |
| | 156 | /* scan for and expand preprocessor symbols */ |
| | 157 | for (p = buf, dst = expbuf ; *p != '\0' ; ++p) |
| | 158 | { |
| | 159 | /* check for the start of a preprocessor symbol */ |
| | 160 | if (*p == '$') |
| | 161 | { |
| | 162 | /* |
| | 163 | * We have a dollar sign, so this *could* be a preprocessor |
| | 164 | * symbol. If the next character is '(', then find the |
| | 165 | * matching ')', and take the part between the parentheses |
| | 166 | * as a symbol to expand. If the next character is '$', |
| | 167 | * replace the pair with a single dollar sign. If the next |
| | 168 | * character is anything else, the '$' is not significant, |
| | 169 | * so leave it as it is. |
| | 170 | */ |
| | 171 | if (*(p+1) == '(') |
| | 172 | { |
| | 173 | char *closep; |
| | 174 | |
| | 175 | /* find the close paren */ |
| | 176 | for (closep = p + 2 ; *closep != '\0' && *closep != ')' ; |
| | 177 | ++closep) ; |
| | 178 | |
| | 179 | /* |
| | 180 | * if we found the close paren, process it; if not, |
| | 181 | * just ignore the whole thing and treat the '$(' as a |
| | 182 | * pair of ordinary characters |
| | 183 | */ |
| | 184 | if (*closep == ')') |
| | 185 | { |
| | 186 | int vallen; |
| | 187 | size_t symlen; |
| | 188 | size_t rem; |
| | 189 | |
| | 190 | /* skip to the start of the symbol name */ |
| | 191 | p += 2; |
| | 192 | |
| | 193 | /* get the length of the symbol */ |
| | 194 | symlen = closep - p; |
| | 195 | |
| | 196 | /* figure out how much space we have left */ |
| | 197 | rem = sizeof(expbuf) - (dst - expbuf); |
| | 198 | |
| | 199 | /* look up the symbol's value */ |
| | 200 | vallen = get_pp_symbol(dst, rem, p, symlen); |
| | 201 | |
| | 202 | /* note overflow or undefined symbol errors */ |
| | 203 | if (vallen < 0) |
| | 204 | { |
| | 205 | /* it's undefined - note it */ |
| | 206 | err_undef_sym(p, symlen); |
| | 207 | } |
| | 208 | else if ((size_t)vallen > rem) |
| | 209 | { |
| | 210 | /* it's too long - note it and give up now */ |
| | 211 | err_expanded_line_too_long(); |
| | 212 | break; |
| | 213 | } |
| | 214 | else |
| | 215 | { |
| | 216 | /* success - advance past the value */ |
| | 217 | dst += vallen; |
| | 218 | } |
| | 219 | |
| | 220 | /* |
| | 221 | * Skip to the close paren and continue with the |
| | 222 | * main loop. We don't need to copy any input for |
| | 223 | * the symbol, because we've already copied the |
| | 224 | * expansion instead; so just go back to the start |
| | 225 | * of the input loop. |
| | 226 | */ |
| | 227 | p = closep; |
| | 228 | continue; |
| | 229 | } |
| | 230 | else |
| | 231 | { |
| | 232 | /* ill-formed macro substitution expression */ |
| | 233 | err_invalid_dollar(); |
| | 234 | } |
| | 235 | } |
| | 236 | else if (*(p+1) == '$') |
| | 237 | { |
| | 238 | /* |
| | 239 | * we have a double dollar sign, so turn it into a |
| | 240 | * single dollar sign - simply skip the first of the |
| | 241 | * pair so we only keep one |
| | 242 | */ |
| | 243 | ++p; |
| | 244 | } |
| | 245 | else |
| | 246 | { |
| | 247 | /* invalid '$' sequence - warn about it */ |
| | 248 | err_invalid_dollar(); |
| | 249 | } |
| | 250 | } |
| | 251 | |
| | 252 | /* make sure we have room for this character plus a null byte */ |
| | 253 | if ((dst - expbuf) + 2 > sizeof(expbuf)) |
| | 254 | { |
| | 255 | err_expanded_line_too_long(); |
| | 256 | break; |
| | 257 | } |
| | 258 | |
| | 259 | /* copy this character to the destination string */ |
| | 260 | *dst++ = *p; |
| | 261 | } |
| | 262 | |
| | 263 | /* null-terminate the expanded buffer */ |
| | 264 | *dst = '\0'; |
| | 265 | |
| | 266 | /* skip leading spaces */ |
| | 267 | for (p = expbuf ; isspace(*p) ; ++p) ; |
| | 268 | |
| | 269 | /* if the line is empty or starts with '#', skip it */ |
| | 270 | if (*p == '\0' || *p == '#') |
| | 271 | continue; |
| | 272 | |
| | 273 | /* the variable name starts here */ |
| | 274 | var_name = p; |
| | 275 | |
| | 276 | /* the variable name ends at the next space or colon */ |
| | 277 | for ( ; *p != '\0' && *p != ':' && !isspace(*p) ; ++p) ; |
| | 278 | |
| | 279 | /* note where the variable name ends */ |
| | 280 | sep = p; |
| | 281 | |
| | 282 | /* skip spaces after the end of the variable name */ |
| | 283 | for ( ; isspace(*p) ; ++p) ; |
| | 284 | |
| | 285 | /* |
| | 286 | * if we didn't find a colon after the variable name, it's an |
| | 287 | * error; flag the error and skip the line |
| | 288 | */ |
| | 289 | if (*p != ':') |
| | 290 | { |
| | 291 | /* check for stand-alone flags */ |
| | 292 | if (stricmp(var_name, "nodef") == 0) |
| | 293 | { |
| | 294 | /* scan the "nodef" flag */ |
| | 295 | scan_nodef(); |
| | 296 | } |
| | 297 | else |
| | 298 | { |
| | 299 | /* |
| | 300 | * it's not a valid stand-alone flag; the definition must |
| | 301 | * be missing the value portion |
| | 302 | */ |
| | 303 | err_missing_colon(); |
| | 304 | ++err_cnt_; |
| | 305 | } |
| | 306 | |
| | 307 | /* we're done with this line now */ |
| | 308 | continue; |
| | 309 | } |
| | 310 | |
| | 311 | /* put a null terminator at the end of the variable name */ |
| | 312 | *sep = '\0'; |
| | 313 | |
| | 314 | /* skip any spaces after the colon */ |
| | 315 | for (++p ; isspace(*p) ; ++p) ; |
| | 316 | |
| | 317 | /* the value starts here */ |
| | 318 | var_val = p; |
| | 319 | |
| | 320 | /* scan the value */ |
| | 321 | scan_var(var_name, var_val); |
| | 322 | } |
| | 323 | |
| | 324 | /* we're done - close the file */ |
| | 325 | delete fp; |
| | 326 | } |
| | 327 | |
| | 328 | /* |
| | 329 | * Scan a variable |
| | 330 | */ |
| | 331 | void CTcLibParser::scan_var(const char *name, const char *val) |
| | 332 | { |
| | 333 | /* call the appropriate routine based on the variable name */ |
| | 334 | if (stricmp(name, "name") == 0) |
| | 335 | scan_name(val); |
| | 336 | else if (stricmp(name, "source") == 0) |
| | 337 | scan_source(val); |
| | 338 | else if (stricmp(name, "library") == 0) |
| | 339 | scan_library(val); |
| | 340 | else if (stricmp(name, "resource") == 0) |
| | 341 | scan_resource(val); |
| | 342 | else if (stricmp(name, "needmacro") == 0) |
| | 343 | scan_needmacro(val); |
| | 344 | else |
| | 345 | err_unknown_var(name, val); |
| | 346 | } |
| | 347 | |
| | 348 | /* |
| | 349 | * Scan a source filename. We build the full filename by combining the |
| | 350 | * library path and the given filename, then we call scan_full_source(). |
| | 351 | */ |
| | 352 | void CTcLibParser::scan_source(const char *val) |
| | 353 | { |
| | 354 | char rel_path[OSFNMAX]; |
| | 355 | char full_name[OSFNMAX]; |
| | 356 | |
| | 357 | /* convert the value from a URL-style path to a local path */ |
| | 358 | os_cvt_url_dir(rel_path, sizeof(rel_path), val, FALSE); |
| | 359 | |
| | 360 | /* build the full name */ |
| | 361 | os_build_full_path(full_name, sizeof(full_name), lib_path_, rel_path); |
| | 362 | |
| | 363 | /* call the full filename scanner */ |
| | 364 | scan_full_source(val, full_name); |
| | 365 | } |
| | 366 | |
| | 367 | /* |
| | 368 | * Scan a library filename. We build the full filename by combining the |
| | 369 | * enclosing library path and the given filename, then we call |
| | 370 | * scan_full_library(). |
| | 371 | */ |
| | 372 | void CTcLibParser::scan_library(const char *val) |
| | 373 | { |
| | 374 | char rel_path[OSFNMAX]; |
| | 375 | char full_name[OSFNMAX]; |
| | 376 | |
| | 377 | /* convert the value from a URL-style path to a local path */ |
| | 378 | os_cvt_url_dir(rel_path, sizeof(rel_path), val, FALSE); |
| | 379 | |
| | 380 | /* build the full name */ |
| | 381 | os_build_full_path(full_name, sizeof(full_name), lib_path_, rel_path); |
| | 382 | |
| | 383 | /* call the full library filename scanner */ |
| | 384 | scan_full_library(val, full_name); |
| | 385 | } |
| | 386 | |
| | 387 | /* |
| | 388 | * Scan a resource filename. We build the full filename by combining the |
| | 389 | * enclosing library path and the given filename, then we call |
| | 390 | * scan_full_resource(). |
| | 391 | */ |
| | 392 | void CTcLibParser::scan_resource(const char *val) |
| | 393 | { |
| | 394 | char rel_path[OSFNMAX]; |
| | 395 | char full_name[OSFNMAX]; |
| | 396 | |
| | 397 | /* convert the value from a URL-style path to a local path */ |
| | 398 | os_cvt_url_dir(rel_path, sizeof(rel_path), val, FALSE); |
| | 399 | |
| | 400 | /* build the full name */ |
| | 401 | os_build_full_path(full_name, sizeof(full_name), lib_path_, rel_path); |
| | 402 | |
| | 403 | /* call the full resource filename scanner */ |
| | 404 | scan_full_resource(val, full_name); |
| | 405 | } |
| | 406 | |
| | 407 | /* |
| | 408 | * scan a "needmacro" definition |
| | 409 | */ |
| | 410 | void CTcLibParser::scan_needmacro(const char *val) |
| | 411 | { |
| | 412 | const char *p; |
| | 413 | const char *macro_name; |
| | 414 | size_t macro_len; |
| | 415 | |
| | 416 | /* skip leading whitespace */ |
| | 417 | for (p = val ; isspace(*p) ; ++p) ; |
| | 418 | |
| | 419 | /* the macro starts here */ |
| | 420 | macro_name = p; |
| | 421 | |
| | 422 | /* find the next whitspace, which separates the token */ |
| | 423 | for ( ; *p != '\0' && !isspace(*p) ; ++p) ; |
| | 424 | |
| | 425 | /* note the length of the macro name */ |
| | 426 | macro_len = p - macro_name; |
| | 427 | |
| | 428 | /* skip the whitspace */ |
| | 429 | for ( ; isspace(*p) ; ++p) ; |
| | 430 | |
| | 431 | /* process the parsed needmacro definition */ |
| | 432 | scan_parsed_needmacro(macro_name, macro_len, p); |
| | 433 | } |
| | 434 | |
| | 435 | /* |
| | 436 | * Scan a parsed "needmacro" definition. This default implementation shows |
| | 437 | * an error and the library-defined warning text. |
| | 438 | */ |
| | 439 | void CTcLibParser::scan_parsed_needmacro(const char *macro_name, |
| | 440 | size_t macro_len, |
| | 441 | const char *warning_text) |
| | 442 | { |
| | 443 | char buf[100+128+128]; |
| | 444 | |
| | 445 | /* look up the symbol; if it's not defined, show a warning */ |
| | 446 | if (get_pp_symbol(buf, sizeof(buf), macro_name, macro_len) < 0) |
| | 447 | { |
| | 448 | /* limit the symbol length to avoid overflowing the error buffer */ |
| | 449 | if (macro_len > 128) |
| | 450 | macro_len = 128; |
| | 451 | |
| | 452 | /* show a suitable warning */ |
| | 453 | src_err_msg("library requires preprocessor symbol \"%.*s\" to be " |
| | 454 | "defined; use -D %.*s=value to define it (%s)", |
| | 455 | (int)macro_len, macro_name, (int)macro_len, macro_name, |
| | 456 | warning_text); |
| | 457 | } |
| | 458 | } |
| | 459 | |
| | 460 | /* |
| | 461 | * log an error in a source line |
| | 462 | */ |
| | 463 | void CTcLibParser::src_err_msg(const char *msg, ...) |
| | 464 | { |
| | 465 | char buf[1024]; |
| | 466 | va_list args; |
| | 467 | |
| | 468 | /* format the caller's message and its arguments */ |
| | 469 | va_start(args, msg); |
| | 470 | t3vsprintf(buf, sizeof(buf), msg, args); |
| | 471 | va_end(args); |
| | 472 | |
| | 473 | /* display the message with the source name and line number */ |
| | 474 | err_msg("%s (%lu): %s", lib_name_, linenum_, buf); |
| | 475 | } |