cfad47cfa3/t3compiler/tads3/tccmdutl.cpp

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
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