summaryrefslogtreecommitdiff
path: root/agen5/funcEval.c
blob: 5775a3420f185b22e5426f141be728ce861a8b34 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651

/**
 * @file funcEval.c
 *
 *  This module evaluates macro expressions.
 *
 *  Time-stamp:        "2012-04-07 09:41:46 bkorb"
 *
 *  This file is part of AutoGen.
 *  AutoGen Copyright (c) 1992-2012 by Bruce Korb - all rights reserved
 *
 * AutoGen is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * AutoGen is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* = = = START-STATIC-FORWARD = = = */
static inline char const *
tpl_text(templ_t * tpl, macro_t * mac);

static void
tpl_warning(templ_t * tpl, macro_t * mac, char const * msg);

static int
expr_type(char * pz);
/* = = = END-STATIC-FORWARD = = = */

/**
 * Convert SCM to displayable string.
 * @param s the input scm
 * @returns a string a human can read
 */
LOCAL char const *
scm2display(SCM s)
{
    static char  z[48];
    char const * pzRes = z;

    switch (ag_scm_type_e(s)) {
    case GH_TYPE_BOOLEAN:
        z[0] = AG_SCM_NFALSEP(s) ? '1' : '0'; z[1] = NUL;
        break;

    case GH_TYPE_STRING:
    case GH_TYPE_SYMBOL:
        pzRes = ag_scm2zchars(s, "SCM Result");
        break;

    case GH_TYPE_CHAR:
        z[0] = AG_SCM_CHAR(s); z[1] = NUL; break;

    case GH_TYPE_VECTOR:
        pzRes = RESOLVE_SCM_VECTOR; break;

    case GH_TYPE_PAIR:
        pzRes = RESOLVE_SCM_PAIR; break;

    case GH_TYPE_NUMBER:
        snprintf(z, sizeof(z), RESOLVE_SCM_NUMBER, AG_SCM_TO_ULONG(s)); break;

    case GH_TYPE_PROCEDURE:
#ifdef SCM_SUBR_ENTRY
    {
        void * x = &SCM_SUBR_ENTRY(s);

        snprintf(z, sizeof(z), RESOLVE_SCM_PROC,
                 (unsigned long)x);
        break;
    }
#else
        pzRes = "** PROCEDURE **";
        break;
#endif

    case GH_TYPE_LIST:
        pzRes = RESOLVE_SCM_LIST; break;

    case GH_TYPE_INEXACT:
        pzRes = RESOLVE_SCM_INEXACT; break;

    case GH_TYPE_EXACT:
        pzRes = RESOLVE_SCM_EXACT; break;

    case GH_TYPE_UNDEFINED:
        pzRes = (char*)zNil; break;

    default:
        pzRes = RESOLVE_SCM_UNKNOWN; break;
    }

    return pzRes;
}

/**
 * Return the text associated with a macro.
 */
static inline char const *
tpl_text(templ_t * tpl, macro_t * mac)
{
    if (mac->md_txt_off == 0)
        return zNil;

    return tpl->td_text + mac->md_txt_off;
}

static void
tpl_warning(templ_t * tpl, macro_t * mac, char const * msg)
{
    fprintf(trace_fp, TPL_WARN_FMT, tpl->td_file, mac->md_line, msg);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
 *  Evaluate an expression and return a string pointer.  Always.
 *  It may need to be deallocated, so a boolean pointer is used
 *  to tell the caller.  Also, the match existence and non-existence
 *  pay attention to the address of the empty string that gets
 *  returned.  If it is, specifically, "zNil", then this code is
 *  saying, "we could not compute a value, so we're returning this
 *  empty string".  This is used by the Select_Match_Existence and
 *  Select_Match_NonExistence code to distinguish non-existence from
 *  an empty string value.
 *
 * @param allocated tell caller if result has been allocated
 * @returns a string representing the result.
 */
LOCAL char const *
eval_mac_expr(bool * allocated)
{
    templ_t *     tpl   = current_tpl;
    macro_t *     mac   = cur_macro;
    int           code  = mac->md_res;
    char const *  text  = NULL; /* warning patrol */
    def_ent_t *   ent;

    *allocated = false;

    if ((code & EMIT_NO_DEFINE) != 0) {
        text  = tpl_text(tpl, mac);
        code &= EMIT_PRIMARY_TYPE;
        ent   = NULL; /* warning patrol */
    }

    else {
        /*
         *  Get the named definition entry, maybe
         */
        bool indexed;
        ent = find_def_ent(tpl->td_text + mac->md_name_off, &indexed);

        if (ent == NULL) {
            switch (code & (EMIT_IF_ABSENT | EMIT_ALWAYS)) {
            case EMIT_IF_ABSENT:
                /*
                 *  There is only one expression.  It applies because
                 *  we did not find a definition.
                 */
                text = tpl_text(tpl, mac);
                code &= EMIT_PRIMARY_TYPE;
                break;

            case EMIT_ALWAYS:
                /*
                 *  There are two expressions.  Take the second one.
                 */
                text = tpl->td_text + mac->md_end_idx;
                code = ((code & EMIT_SECONDARY_TYPE)
                          >> EMIT_SECONDARY_SHIFT);
                break;

            case 0:
                /*
                 *  Emit only if found
                 */
                return no_def_str;

            case (EMIT_IF_ABSENT | EMIT_ALWAYS):
                /*
                 *  Emit inconsistently :-}
                 */
                AG_ABEND_IN(tpl, mac, EVAL_EXPR_PROG_ERR);
                /* NOTREACHED */
            }
        }

        /*
         *  OTHERWISE, we found an entry.  Make sure we were supposed to.
         */
        else {
            if ((code & EMIT_IF_ABSENT) != 0)
                return (char*)zNil;

            if (  (ent->de_type != VALTYP_TEXT)
               && ((code & EMIT_PRIMARY_TYPE) == EMIT_VALUE)  ) {
                tpl_warning(tpl, mac, EVAL_EXPR_BLOCK_IN_EVAL);
                return (char*)zNil;
            }

            /*
             *  Compute the expression string.  There are three possibilities:
             *  1.  There is an expression string in the macro, but it must
             *      be formatted with the text value.
             *      Make sure we have a value.
             *  2.  There is an expression string in the macro, but it is *NOT*
             *      to be formatted.  Use it as is.  Do *NOT* verify that
             *      the define value is text.
             *  3.  There is no expression with the macro invocation.
             *      The define value *must* be text.
             */
            if ((code & EMIT_FORMATTED) != 0) {
                /*
                 *  And make sure what we found is a text value
                 */
                if (ent->de_type != VALTYP_TEXT) {
                    tpl_warning(tpl, mac, EVAL_EXPR_BLOCK_IN_EVAL);
                    return (char*)zNil;
                }

                *allocated = true;
                text = aprf(tpl_text(tpl, mac), ent->de_val.dvu_text);
            }

            else if (mac->md_txt_off != 0)
                text = tpl->td_text + mac->md_txt_off;

            else {
                /*
                 *  And make sure what we found is a text value
                 */
                if (ent->de_type != VALTYP_TEXT) {
                    tpl_warning(tpl, mac, EVAL_EXPR_BLOCK_IN_EVAL);
                    return (char*)zNil;
                }

                text = ent->de_val.dvu_text;
            }

            code &= EMIT_PRIMARY_TYPE;
        }
    }

    /*
     *  The "code" tells us how to handle the expression
     */
    switch (code) {
    case EMIT_VALUE:
        assert(ent != NULL);
        if (*allocated) {
            AGFREE((void*)text);
            *allocated = false;
        }

        text = ent->de_val.dvu_text;
        break;

    case EMIT_EXPRESSION:
    {
        SCM res = ag_eval(text);

        if (*allocated) {
            AGFREE((void*)text);
            *allocated = false;
        }

        text = scm2display(res);
        break;
    }

    case EMIT_SHELL:
    {
        char* pz = shell_cmd(text);

        if (*allocated)
            AGFREE((void*)text);

        if (pz != NULL) {
            *allocated = true;
            text = pz;
        }
        else {
            *allocated = false;
            text = (char*)zNil;
        }
        break;
    }

    case EMIT_STRING:
        break;
    }

    return text;
}

/*=gfunc error_source_line
 *
 * what: display of file & line
 * general_use:
 * doc:  This function is only invoked just before Guile displays
 *       an error message.  It displays the file name and line number
 *       that triggered the evaluation error.  You should not need to
 *       invoke this routine directly.  Guile will do it automatically.
=*/
SCM
ag_scm_error_source_line(void)
{
    fprintf(stderr, SCM_ERROR_FMT, current_tpl->td_name, cur_macro->md_line,
            current_tpl->td_text + cur_macro->md_txt_off);
    fflush(stderr);

    return SCM_UNDEFINED;
}


/*=gfunc emit
 *
 * what: emit the text for each argument
 *
 * exparg: alist, list of arguments to stringify and emit, , list
 *
 * doc:  Walk the tree of arguments, displaying the values of displayable
 *       SCM types.  EXCEPTION: if the first argument is a number, then
 *       that number is used to index the output stack.  "0" is the default,
 *       the current output.
=*/
SCM
ag_scm_emit(SCM val)
{
    static int depth = 0;
    static FILE * fp;

    switch (depth) {
    case 1:
    {
        out_stack_t* pSaveFp;
        unsigned long pnum;

        if (! AG_SCM_NUM_P(val))
            break;

        pSaveFp = cur_fpstack;
        pnum    = AG_SCM_TO_ULONG(val);

        for (; pnum > 0; pnum--) {
            pSaveFp = pSaveFp->stk_prev;
            if (pSaveFp == NULL)
                AG_ABEND(aprf(EMIT_INVAL_PORT, AG_SCM_TO_ULONG(val)));
        }

        fp = pSaveFp->stk_fp;
        return SCM_UNDEFINED;
    }

    case 0:
        fp = cur_fpstack->stk_fp; // initialize the first time through
        break;
    } 

    depth++;
    for (;;) {
        if (val == SCM_UNDEFINED)
            break;

        if (AG_SCM_NULLP(val))
            break;

        if (AG_SCM_STRING_P(val)) {
            fputs((char*)ag_scm2zchars(val, "emit val"), fp);
            fflush(fp);
            break;
        }

        switch (ag_scm_type_e(val)) {
        case GH_TYPE_LIST:
        case GH_TYPE_PAIR:
            ag_scm_emit(SCM_CAR(val));
            val = SCM_CDR(val);
            continue;

        default:
            fputs(scm2display(val), fp);
            fflush(fp);
            break;
        }

        break;
    }

    depth--;
    return SCM_UNDEFINED;
}

/**
 *  The global evaluation function.
 *
 *  The string to "evaluate" may be a literal string, or may need Scheme
 *  interpretation.  So, we do one of three things: if the string starts with
 *  a Scheme comment character or evaluation character (';' or '('), then run
 *  a Scheme eval.  If it starts with a quote character ('\'' or '"'), then
 *  digest the string and return that.  Otherwise, just return the string.
 *
 * @param expr input expression string
 * @returns an SCM value representing the result
 */
LOCAL SCM
eval(char const * expr)
{
    bool   allocated = false;
    char * pzTemp;
    SCM    res;

    switch (*expr) {
    case '(':
    case ';':
        res = ag_eval((char*)expr);
        break;

    case '`':
        AGDUPSTR(pzTemp, expr, "shell script");
        (void)span_quote(pzTemp);
        expr = shell_cmd(pzTemp);
        AGFREE((void*)pzTemp);
        res = AG_SCM_STR02SCM((char*)expr);
        AGFREE((void*)expr);
        break;

    case '"':
    case '\'':
        AGDUPSTR(pzTemp, expr, "quoted string");
        (void)span_quote(pzTemp);
        allocated = true;
        expr = pzTemp;
        /* FALLTHROUGH */

    default:
        res = AG_SCM_STR02SCM((char*)expr);
        if (allocated)
            AGFREE((void*)expr);
    }

    return res;
}


/*=macfunc EXPR
 *
 *  what:  Evaluate and emit an Expression
 *  alias: + - + ? + % + ; + ( + '`' + '"' + "'" + . +
 *
 *  handler_proc:
 *  load_proc:
 *
 *  desc:
 *   This macro does not have a name to cause it to be invoked
 *   explicitly, though if a macro starts with one of the apply codes
 *   or one of the simple expression markers, then an expression
 *   macro is inferred.  The result of the expression evaluation
 *   (@pxref{expression syntax}) is written to the current output.
=*/
macro_t*
mFunc_Expr(templ_t * tpl, macro_t * mac)
{
    bool allocated_str;
    char const * pz = eval_mac_expr(&allocated_str);

    (void)tpl;

    if (*pz != NUL) {
        fputs(pz, cur_fpstack->stk_fp);
        fflush(cur_fpstack->stk_fp);
    }

    if (allocated_str)
        AGFREE((void*)pz);

    return mac + 1;
}

/**
 * Determine the expression type.  It may be Scheme (starts with a semi-colon
 * or an opening parenthesis), a shell command (starts with a back tick),
 * a quoted string (either single or double), or it is some sort of plain
 * string.  In that case, just return the text.
 *
 * @param pz pointer to string to diagnose
 * @returns the EMIT_* compound value, though actually only
 * EXPRESSION, SHELL or STRING can really ever be returned.
 */
static int
expr_type(char * pz)
{
    switch (*pz) {
    case ';':
    case '(':
        return EMIT_EXPRESSION;

    case '`':
        span_quote(pz);
        return EMIT_SHELL;

    case '"':
    case '\'':
        span_quote(pz);
        /* FALLTHROUGH */

    default:
        return EMIT_STRING;
    }
}


/**
 *  mLoad_Expr
 */
macro_t *
mLoad_Expr(templ_t * tpl, macro_t * mac, char const ** ppzScan)
{
    char *        copy; /* next text dest   */
    char const *  src     = (char const*)mac->md_txt_off; /* macro text */
    size_t        src_len = (long)mac->md_res;           /* macro len  */
    char const *  end_src = src + src_len;

    if (src_len == 0) {
        if (mac->md_code == FTYP_INCLUDE)
            AG_ABEND_IN(tpl, mac, LD_INC_NO_FNAME);
        mac->md_res = EMIT_VALUE;
        mac->md_txt_off = 0;
        return mac + 1;
    }

    switch (*src) {
    case '-':
        mac->md_res = EMIT_IF_ABSENT;
        src++;
        break;

    case '?':
        mac->md_res = EMIT_ALWAYS;
        src++;
        if (*src == '%') {
            mac->md_res |= EMIT_FORMATTED;
            src++;
        }
        break;

    case '%':
        mac->md_res = EMIT_FORMATTED;
        src++;
        break;

    case '`':
        (void) mLoad_Unknown(tpl, mac, ppzScan);
        mac->md_res = EMIT_NO_DEFINE | EMIT_SHELL;
        span_quote(tpl->td_text + mac->md_txt_off);
        return mac + 1;

    case '"':
    case '\'':
        (void) mLoad_Unknown(tpl, mac, ppzScan);
        mac->md_res = EMIT_NO_DEFINE | EMIT_STRING;
        span_quote(tpl->td_text + mac->md_txt_off);
        return mac + 1;

    case '(':
    case ';':
        (void) mLoad_Unknown(tpl, mac, ppzScan);
        mac->md_res = EMIT_NO_DEFINE | EMIT_EXPRESSION;
        return mac + 1;

    default:
        mac->md_res = EMIT_VALUE; /* zero */
        break;
    }

    copy = tpl->td_scan;
    mac->md_name_off = (copy - tpl->td_text);
    {
        size_t remLen = canonical_name(copy, src, (int)src_len);
        if (remLen > src_len)
            AG_ABEND_IN(tpl, mac, LD_EXPR_BAD_NAME);
        src    += src_len - remLen;
        src_len = remLen;
        copy   += strlen(copy) + 1;
    }

    if (src >= end_src) {
        if (mac->md_res != EMIT_VALUE)
            AG_ABEND_IN(tpl, mac, LD_EXPR_NO_TEXT);

        mac->md_txt_off = 0;

    } else {
        char* pz = copy;
        src_len = (long)(end_src - src);

        mac->md_txt_off = (copy - tpl->td_text);
        /*
         *  Copy the expression
         */
        memcpy(copy, src, src_len);
        copy += src_len;
        *(copy++) = NUL; *(copy++) = NUL; /* double terminate */

        /*
         *  IF this expression has an "if-present" and "if-not-present"
         *  THEN find the ending expression...
         */
        if ((mac->md_res & EMIT_ALWAYS) != 0) {
            char* pzNextExpr = (char*)skip_expr(pz, src_len);

            /*
             *  The next expression must be within bounds and space separated
             */
            if (pzNextExpr >= pz + src_len)
                AG_ABEND_IN(tpl, mac, LD_EXPR_NEED_TWO);

            if (! IS_WHITESPACE_CHAR(*pzNextExpr))
                AG_ABEND_IN(tpl, mac, LD_EXPR_NO_SPACE);

            /*
             *  NUL terminate the first expression, skip intervening
             *  white space and put the secondary expression's type
             *  into the macro type code as the "secondary type".
             */
            *(pzNextExpr++) = NUL;
            pzNextExpr = SPN_WHITESPACE_CHARS(pzNextExpr);
            mac->md_res |= (expr_type(pzNextExpr) << EMIT_SECONDARY_SHIFT);
            mac->md_end_idx = pzNextExpr - tpl->td_text;
        }

        mac->md_res |= expr_type(pz);
    }

    tpl->td_scan = copy;
    return mac + 1;
}
/*
 * Local Variables:
 * mode: C
 * c-file-style: "stroustrup"
 * indent-tabs-mode: nil
 * End:
 * end of agen5/funcEval.c */