summaryrefslogtreecommitdiffstats
path: root/stand/ficl/ficl.c
blob: 219cf84b22257d7d248aa5a0725adbdad7b9a330 (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
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
/*******************************************************************
** f i c l . c
** Forth Inspired Command Language - external interface
** Author: John Sadler (john_sadler@alum.mit.edu)
** Created: 19 July 1997
** $Id: ficl.c,v 1.16 2001/12/05 07:21:34 jsadler Exp $
*******************************************************************/
/*
** This is an ANS Forth interpreter written in C.
** Ficl uses Forth syntax for its commands, but turns the Forth 
** model on its head in other respects.
** Ficl provides facilities for interoperating
** with programs written in C: C functions can be exported to Ficl,
** and Ficl commands can be executed via a C calling interface. The
** interpreter is re-entrant, so it can be used in multiple instances
** in a multitasking system. Unlike Forth, Ficl's outer interpreter
** expects a text block as input, and returns to the caller after each
** text block, so the data pump is somewhere in external code in the 
** style of TCL.
**
** Code is written in ANSI C for portability. 
*/
/*
** Copyright (c) 1997-2001 John Sadler (john_sadler@alum.mit.edu)
** All rights reserved.
**
** Get the latest Ficl release at http://ficl.sourceforge.net
**
** I am interested in hearing from anyone who uses ficl. If you have
** a problem, a success story, a defect, an enhancement request, or
** if you would like to contribute to the ficl release, please
** contact me by email at the address above.
**
** L I C E N S E  and  D I S C L A I M E R
** 
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.
*/

/* $FreeBSD$ */

#ifdef TESTMAIN
#include <stdlib.h>
#else
#include <stand.h>
#endif
#include <string.h>
#include "ficl.h"


/*
** System statics
** Each FICL_SYSTEM builds a global dictionary during its start
** sequence. This is shared by all virtual machines of that system.
** Therefore only one VM can update the dictionary
** at a time. The system imports a locking function that
** you can override in order to control update access to
** the dictionary. The function is stubbed out by default,
** but you can insert one: #define FICL_MULTITHREAD 1
** and supply your own version of ficlLockDictionary.
*/
static int defaultStack = FICL_DEFAULT_STACK;


static void ficlSetVersionEnv(FICL_SYSTEM *pSys);


/**************************************************************************
                        f i c l I n i t S y s t e m
** Binds a global dictionary to the interpreter system. 
** You specify the address and size of the allocated area.
** After that, ficl manages it.
** First step is to set up the static pointers to the area.
** Then write the "precompiled" portion of the dictionary in.
** The dictionary needs to be at least large enough to hold the
** precompiled part. Try 1K cells minimum. Use "words" to find
** out how much of the dictionary is used at any time.
**************************************************************************/
FICL_SYSTEM *ficlInitSystemEx(FICL_SYSTEM_INFO *fsi)
{
    int nDictCells;
    int nEnvCells;
    FICL_SYSTEM *pSys = ficlMalloc(sizeof (FICL_SYSTEM));

    assert(pSys);
    assert(fsi->size == sizeof (FICL_SYSTEM_INFO));

    memset(pSys, 0, sizeof (FICL_SYSTEM));

    nDictCells = fsi->nDictCells;
    if (nDictCells <= 0)
        nDictCells = FICL_DEFAULT_DICT;

    nEnvCells = fsi->nEnvCells;
    if (nEnvCells <= 0)
        nEnvCells = FICL_DEFAULT_DICT;

    pSys->dp = dictCreateHashed((unsigned)nDictCells, HASHSIZE);
    pSys->dp->pForthWords->name = "forth-wordlist";

    pSys->envp = dictCreate((unsigned)nEnvCells);
    pSys->envp->pForthWords->name = "environment";

    pSys->textOut = fsi->textOut;
    pSys->pExtend = fsi->pExtend;

#if FICL_WANT_LOCALS
    /*
    ** The locals dictionary is only searched while compiling,
    ** but this is where speed is most important. On the other
    ** hand, the dictionary gets emptied after each use of locals
    ** The need to balance search speed with the cost of the 'empty'
    ** operation led me to select a single-threaded list...
    */
    pSys->localp = dictCreate((unsigned)FICL_MAX_LOCALS * CELLS_PER_WORD);
#endif

    /*
    ** Build the precompiled dictionary and load softwords. We need a temporary
    ** VM to do this - ficlNewVM links one to the head of the system VM list.
    ** ficlCompilePlatform (defined in win32.c, for example) adds platform specific words.
    */
    ficlCompileCore(pSys);
    ficlCompilePrefix(pSys);
#if FICL_WANT_FLOAT
    ficlCompileFloat(pSys);
#endif
#if FICL_PLATFORM_EXTEND
    ficlCompilePlatform(pSys);
#endif
    ficlSetVersionEnv(pSys);

    /*
    ** Establish the parse order. Note that prefixes precede numbers -
    ** this allows constructs like "0b101010" which might parse as a
    ** hex value otherwise.
    */
    ficlAddPrecompiledParseStep(pSys, "?prefix", ficlParsePrefix);
    ficlAddPrecompiledParseStep(pSys, "?number", ficlParseNumber);
#if FICL_WANT_FLOAT
    ficlAddPrecompiledParseStep(pSys, ">float", ficlParseFloatNumber);
#endif

    /*
    ** Now create a temporary VM to compile the softwords. Since all VMs are
    ** linked into the vmList of FICL_SYSTEM, we don't have to pass the VM
    ** to ficlCompileSoftCore -- it just hijacks whatever it finds in the VM list.
    ** ficl 2.05: vmCreate no longer depends on the presence of INTERPRET in the
    ** dictionary, so a VM can be created before the dictionary is built. It just
    ** can't do much...
    */
    ficlNewVM(pSys);
    ficlCompileSoftCore(pSys);
    ficlFreeVM(pSys->vmList);


    return pSys;
}


FICL_SYSTEM *ficlInitSystem(int nDictCells)
{
    FICL_SYSTEM_INFO fsi;
    ficlInitInfo(&fsi);
    fsi.nDictCells = nDictCells;
    return ficlInitSystemEx(&fsi);
}


/**************************************************************************
                        f i c l A d d P a r s e S t e p
** Appends a parse step function to the end of the parse list (see 
** FICL_PARSE_STEP notes in ficl.h for details). Returns 0 if successful,
** nonzero if there's no more room in the list.
**************************************************************************/
int ficlAddParseStep(FICL_SYSTEM *pSys, FICL_WORD *pFW)
{
    int i;
    for (i = 0; i < FICL_MAX_PARSE_STEPS; i++)
    {
        if (pSys->parseList[i] == NULL)
        {
            pSys->parseList[i] = pFW;
            return 0;
        }
    }

    return 1;
}


/*
** Compile a word into the dictionary that invokes the specified FICL_PARSE_STEP
** function. It is up to the user (as usual in Forth) to make sure the stack 
** preconditions are valid (there needs to be a counted string on top of the stack)
** before using the resulting word.
*/
void ficlAddPrecompiledParseStep(FICL_SYSTEM *pSys, char *name, FICL_PARSE_STEP pStep)
{
    FICL_DICT *dp = pSys->dp;
    FICL_WORD *pFW = dictAppendWord(dp, name, parseStepParen, FW_DEFAULT);
    dictAppendCell(dp, LVALUEtoCELL(pStep));
    ficlAddParseStep(pSys, pFW);
}


/*
** This word lists the parse steps in order
*/
void ficlListParseSteps(FICL_VM *pVM)
{
    int i;
    FICL_SYSTEM *pSys = pVM->pSys;
    assert(pSys);

    vmTextOut(pVM, "Parse steps:", 1);
    vmTextOut(pVM, "lookup", 1);

    for (i = 0; i < FICL_MAX_PARSE_STEPS; i++)
    {
        if (pSys->parseList[i] != NULL)
        {
            vmTextOut(pVM, pSys->parseList[i]->name, 1);
        }
        else break;
    }
    return;
}


/**************************************************************************
                        f i c l N e w V M
** Create a new virtual machine and link it into the system list
** of VMs for later cleanup by ficlTermSystem.
**************************************************************************/
FICL_VM *ficlNewVM(FICL_SYSTEM *pSys)
{
    FICL_VM *pVM = vmCreate(NULL, defaultStack, defaultStack);
    pVM->link = pSys->vmList;
    pVM->pSys = pSys;
    pVM->pExtend = pSys->pExtend;
    vmSetTextOut(pVM, pSys->textOut);

    pSys->vmList = pVM;
    return pVM;
}


/**************************************************************************
                        f i c l F r e e V M
** Removes the VM in question from the system VM list and deletes the
** memory allocated to it. This is an optional call, since ficlTermSystem
** will do this cleanup for you. This function is handy if you're going to
** do a lot of dynamic creation of VMs.
**************************************************************************/
void ficlFreeVM(FICL_VM *pVM)
{
    FICL_SYSTEM *pSys = pVM->pSys;
    FICL_VM *pList = pSys->vmList;

    assert(pVM != NULL);

    if (pSys->vmList == pVM)
    {
        pSys->vmList = pSys->vmList->link;
    }
    else for (; pList != NULL; pList = pList->link)
    {
        if (pList->link == pVM)
        {
            pList->link = pVM->link;
            break;
        }
    }

    if (pList)
        vmDelete(pVM);
    return;
}


/**************************************************************************
                        f i c l B u i l d
** Builds a word into the dictionary.
** Preconditions: system must be initialized, and there must
** be enough space for the new word's header! Operation is
** controlled by ficlLockDictionary, so any initialization
** required by your version of the function (if you overrode
** it) must be complete at this point.
** Parameters:
** name  -- duh, the name of the word
** code  -- code to execute when the word is invoked - must take a single param
**          pointer to a FICL_VM
** flags -- 0 or more of F_IMMEDIATE, F_COMPILE, use bitwise OR!
** 
**************************************************************************/
int ficlBuild(FICL_SYSTEM *pSys, char *name, FICL_CODE code, char flags)
{
#if FICL_MULTITHREAD
    int err = ficlLockDictionary(TRUE);
    if (err) return err;
#endif /* FICL_MULTITHREAD */

    assert(dictCellsAvail(pSys->dp) > sizeof (FICL_WORD) / sizeof (CELL));
    dictAppendWord(pSys->dp, name, code, flags);

    ficlLockDictionary(FALSE);
    return 0;
}


/**************************************************************************
                    f i c l E v a l u a t e
** Wrapper for ficlExec() which sets SOURCE-ID to -1.
**************************************************************************/
int ficlEvaluate(FICL_VM *pVM, char *pText)
{
    int returnValue;
    CELL id = pVM->sourceID;
    pVM->sourceID.i = -1;
    returnValue = ficlExecC(pVM, pText, -1);
    pVM->sourceID = id;
    return returnValue;
}


/**************************************************************************
                        f i c l E x e c
** Evaluates a block of input text in the context of the
** specified interpreter. Emits any requested output to the
** interpreter's output function.
**
** Contains the "inner interpreter" code in a tight loop
**
** Returns one of the VM_XXXX codes defined in ficl.h:
** VM_OUTOFTEXT is the normal exit condition
** VM_ERREXIT means that the interp encountered a syntax error
**      and the vm has been reset to recover (some or all
**      of the text block got ignored
** VM_USEREXIT means that the user executed the "bye" command
**      to shut down the interpreter. This would be a good
**      time to delete the vm, etc -- or you can ignore this
**      signal.
**************************************************************************/
int ficlExec(FICL_VM *pVM, char *pText)
{
    return ficlExecC(pVM, pText, -1);
}

int ficlExecC(FICL_VM *pVM, char *pText, FICL_INT size)
{
    FICL_SYSTEM *pSys = pVM->pSys;
    FICL_DICT   *dp   = pSys->dp;

    int        except;
    jmp_buf    vmState;
    jmp_buf   *oldState;
    TIB        saveTib;

    assert(pVM);
    assert(pSys->pInterp[0]);

    if (size < 0)
        size = strlen(pText);

    vmPushTib(pVM, pText, size, &saveTib);

    /*
    ** Save and restore VM's jmp_buf to enable nested calls to ficlExec 
    */
    oldState = pVM->pState;
    pVM->pState = &vmState; /* This has to come before the setjmp! */
    except = setjmp(vmState);

    switch (except)
    {
    case 0:
        if (pVM->fRestart)
        {
            pVM->runningWord->code(pVM);
            pVM->fRestart = 0;
        }
        else
        {   /* set VM up to interpret text */
            vmPushIP(pVM, &(pSys->pInterp[0]));
        }

        vmInnerLoop(pVM);
        break;

    case VM_RESTART:
        pVM->fRestart = 1;
        except = VM_OUTOFTEXT;
        break;

    case VM_OUTOFTEXT:
        vmPopIP(pVM);
#ifdef TESTMAIN
        if ((pVM->state != COMPILE) && (pVM->sourceID.i == 0))
            ficlTextOut(pVM, FICL_PROMPT, 0);
#endif
        break;

    case VM_USEREXIT:
    case VM_INNEREXIT:
    case VM_BREAK:
        break;

    case VM_QUIT:
        if (pVM->state == COMPILE)
        {
            dictAbortDefinition(dp);
#if FICL_WANT_LOCALS
            dictEmpty(pSys->localp, pSys->localp->pForthWords->size);
#endif
        }
        vmQuit(pVM);
        break;

    case VM_ERREXIT:
    case VM_ABORT:
    case VM_ABORTQ:
    default:    /* user defined exit code?? */
        if (pVM->state == COMPILE)
        {
            dictAbortDefinition(dp);
#if FICL_WANT_LOCALS
            dictEmpty(pSys->localp, pSys->localp->pForthWords->size);
#endif
        }
        dictResetSearchOrder(dp);
        vmReset(pVM);
        break;
   }

    pVM->pState    = oldState;
    vmPopTib(pVM, &saveTib);
    return (except);
}


/**************************************************************************
                        f i c l E x e c X T
** Given a pointer to a FICL_WORD, push an inner interpreter and
** execute the word to completion. This is in contrast with vmExecute,
** which does not guarantee that the word will have completed when
** the function returns (ie in the case of colon definitions, which
** need an inner interpreter to finish)
**
** Returns one of the VM_XXXX exception codes listed in ficl.h. Normal
** exit condition is VM_INNEREXIT, ficl's private signal to exit the
** inner loop under normal circumstances. If another code is thrown to
** exit the loop, this function will re-throw it if it's nested under
** itself or ficlExec.
**
** NOTE: this function is intended so that C code can execute ficlWords
** given their address in the dictionary (xt).
**************************************************************************/
int ficlExecXT(FICL_VM *pVM, FICL_WORD *pWord)
{
    int        except;
    jmp_buf    vmState;
    jmp_buf   *oldState;
    FICL_WORD *oldRunningWord;

    assert(pVM);
    assert(pVM->pSys->pExitInner);
    
    /* 
    ** Save the runningword so that RESTART behaves correctly
    ** over nested calls.
    */
    oldRunningWord = pVM->runningWord;
    /*
    ** Save and restore VM's jmp_buf to enable nested calls
    */
    oldState = pVM->pState;
    pVM->pState = &vmState; /* This has to come before the setjmp! */
    except = setjmp(vmState);

    if (except)
        vmPopIP(pVM);
    else
        vmPushIP(pVM, &(pVM->pSys->pExitInner));

    switch (except)
    {
    case 0:
        vmExecute(pVM, pWord);
        vmInnerLoop(pVM);
        break;

    case VM_INNEREXIT:
    case VM_BREAK:
        break;

    case VM_RESTART:
    case VM_OUTOFTEXT:
    case VM_USEREXIT:
    case VM_QUIT:
    case VM_ERREXIT:
    case VM_ABORT:
    case VM_ABORTQ:
    default:    /* user defined exit code?? */
        if (oldState)
        {
            pVM->pState = oldState;
            vmThrow(pVM, except);
        }
        break;
    }

    pVM->pState    = oldState;
    pVM->runningWord = oldRunningWord;
    return (except);
}


/**************************************************************************
                        f i c l L o o k u p
** Look in the system dictionary for a match to the given name. If
** found, return the address of the corresponding FICL_WORD. Otherwise
** return NULL.
**************************************************************************/
FICL_WORD *ficlLookup(FICL_SYSTEM *pSys, char *name)
{
    STRINGINFO si;
    SI_PSZ(si, name);
    return dictLookup(pSys->dp, si);
}


/**************************************************************************
                        f i c l G e t D i c t
** Returns the address of the system dictionary
**************************************************************************/
FICL_DICT *ficlGetDict(FICL_SYSTEM *pSys)
{
    return pSys->dp;
}


/**************************************************************************
                        f i c l G e t E n v
** Returns the address of the system environment space
**************************************************************************/
FICL_DICT *ficlGetEnv(FICL_SYSTEM *pSys)
{
    return pSys->envp;
}


/**************************************************************************
                        f i c l S e t E n v
** Create an environment variable with a one-CELL payload. ficlSetEnvD
** makes one with a two-CELL payload.
**************************************************************************/
void ficlSetEnv(FICL_SYSTEM *pSys, char *name, FICL_UNS value)
{
    STRINGINFO si;
    FICL_WORD *pFW;
    FICL_DICT *envp = pSys->envp;

    SI_PSZ(si, name);
    pFW = dictLookup(envp, si);

    if (pFW == NULL)
    {
        dictAppendWord(envp, name, constantParen, FW_DEFAULT);
        dictAppendCell(envp, LVALUEtoCELL(value));
    }
    else
    {
        pFW->param[0] = LVALUEtoCELL(value);
    }

    return;
}

void ficlSetEnvD(FICL_SYSTEM *pSys, char *name, FICL_UNS hi, FICL_UNS lo)
{
    FICL_WORD *pFW;
    STRINGINFO si;
    FICL_DICT *envp = pSys->envp;
    SI_PSZ(si, name);
    pFW = dictLookup(envp, si);

    if (pFW == NULL)
    {
        dictAppendWord(envp, name, twoConstParen, FW_DEFAULT);
        dictAppendCell(envp, LVALUEtoCELL(lo));
        dictAppendCell(envp, LVALUEtoCELL(hi));
    }
    else
    {
        pFW->param[0] = LVALUEtoCELL(lo);
        pFW->param[1] = LVALUEtoCELL(hi);
    }

    return;
}


/**************************************************************************
                        f i c l G e t L o c
** Returns the address of the system locals dictionary. This dict is
** only used during compilation, and is shared by all VMs.
**************************************************************************/
#if FICL_WANT_LOCALS
FICL_DICT *ficlGetLoc(FICL_SYSTEM *pSys)
{
    return pSys->localp;
}
#endif



/**************************************************************************
                        f i c l S e t S t a c k S i z e
** Set the stack sizes (return and parameter) to be used for all
** subsequently created VMs. Returns actual stack size to be used.
**************************************************************************/
int ficlSetStackSize(int nStackCells)
{
    if (nStackCells >= FICL_DEFAULT_STACK)
        defaultStack = nStackCells;
    else
        defaultStack = FICL_DEFAULT_STACK;

    return defaultStack;
}


/**************************************************************************
                        f i c l T e r m S y s t e m
** Tear the system down by deleting the dictionaries and all VMs.
** This saves you from having to keep track of all that stuff.
**************************************************************************/
void ficlTermSystem(FICL_SYSTEM *pSys)
{
    if (pSys->dp)
        dictDelete(pSys->dp);
    pSys->dp = NULL;

    if (pSys->envp)
        dictDelete(pSys->envp);
    pSys->envp = NULL;

#if FICL_WANT_LOCALS
    if (pSys->localp)
        dictDelete(pSys->localp);
    pSys->localp = NULL;
#endif

    while (pSys->vmList != NULL)
    {
        FICL_VM *pVM = pSys->vmList;
        pSys->vmList = pSys->vmList->link;
        vmDelete(pVM);
    }

    ficlFree(pSys);
    pSys = NULL;
    return;
}


/**************************************************************************
                        f i c l S e t V e r s i o n E n v
** Create a double cell environment constant for the version ID
**************************************************************************/
static void ficlSetVersionEnv(FICL_SYSTEM *pSys)
{
    ficlSetEnvD(pSys, "ficl-version", FICL_VER_MAJOR, FICL_VER_MINOR);
    ficlSetEnv (pSys, "ficl-robust",  FICL_ROBUST);
    return;
}

OpenPOWER on IntegriCloud