Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/stand/ficl/ficl.c
34681 views
1
/*******************************************************************
2
** f i c l . c
3
** Forth Inspired Command Language - external interface
4
** Author: John Sadler ([email protected])
5
** Created: 19 July 1997
6
** $Id: ficl.c,v 1.16 2001/12/05 07:21:34 jsadler Exp $
7
*******************************************************************/
8
/*
9
** This is an ANS Forth interpreter written in C.
10
** Ficl uses Forth syntax for its commands, but turns the Forth
11
** model on its head in other respects.
12
** Ficl provides facilities for interoperating
13
** with programs written in C: C functions can be exported to Ficl,
14
** and Ficl commands can be executed via a C calling interface. The
15
** interpreter is re-entrant, so it can be used in multiple instances
16
** in a multitasking system. Unlike Forth, Ficl's outer interpreter
17
** expects a text block as input, and returns to the caller after each
18
** text block, so the data pump is somewhere in external code in the
19
** style of TCL.
20
**
21
** Code is written in ANSI C for portability.
22
*/
23
/*
24
** Copyright (c) 1997-2001 John Sadler ([email protected])
25
** All rights reserved.
26
**
27
** Get the latest Ficl release at http://ficl.sourceforge.net
28
**
29
** I am interested in hearing from anyone who uses ficl. If you have
30
** a problem, a success story, a defect, an enhancement request, or
31
** if you would like to contribute to the ficl release, please
32
** contact me by email at the address above.
33
**
34
** L I C E N S E and D I S C L A I M E R
35
**
36
** Redistribution and use in source and binary forms, with or without
37
** modification, are permitted provided that the following conditions
38
** are met:
39
** 1. Redistributions of source code must retain the above copyright
40
** notice, this list of conditions and the following disclaimer.
41
** 2. Redistributions in binary form must reproduce the above copyright
42
** notice, this list of conditions and the following disclaimer in the
43
** documentation and/or other materials provided with the distribution.
44
**
45
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
46
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
47
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
48
** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
49
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
50
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
51
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
52
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
53
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
54
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
55
** SUCH DAMAGE.
56
*/
57
58
59
#ifdef TESTMAIN
60
#include <stdlib.h>
61
#else
62
#include <stand.h>
63
#endif
64
#include <string.h>
65
#include "ficl.h"
66
67
68
/*
69
** System statics
70
** Each FICL_SYSTEM builds a global dictionary during its start
71
** sequence. This is shared by all virtual machines of that system.
72
** Therefore only one VM can update the dictionary
73
** at a time. The system imports a locking function that
74
** you can override in order to control update access to
75
** the dictionary. The function is stubbed out by default,
76
** but you can insert one: #define FICL_MULTITHREAD 1
77
** and supply your own version of ficlLockDictionary.
78
*/
79
static int defaultStack = FICL_DEFAULT_STACK;
80
81
82
static void ficlSetVersionEnv(FICL_SYSTEM *pSys);
83
84
85
/**************************************************************************
86
f i c l I n i t S y s t e m
87
** Binds a global dictionary to the interpreter system.
88
** You specify the address and size of the allocated area.
89
** After that, ficl manages it.
90
** First step is to set up the static pointers to the area.
91
** Then write the "precompiled" portion of the dictionary in.
92
** The dictionary needs to be at least large enough to hold the
93
** precompiled part. Try 1K cells minimum. Use "words" to find
94
** out how much of the dictionary is used at any time.
95
**************************************************************************/
96
FICL_SYSTEM *ficlInitSystemEx(FICL_SYSTEM_INFO *fsi)
97
{
98
int nDictCells;
99
int nEnvCells;
100
FICL_SYSTEM *pSys = ficlMalloc(sizeof (FICL_SYSTEM));
101
102
assert(pSys);
103
assert(fsi->size == sizeof (FICL_SYSTEM_INFO));
104
105
memset(pSys, 0, sizeof (FICL_SYSTEM));
106
107
nDictCells = fsi->nDictCells;
108
if (nDictCells <= 0)
109
nDictCells = FICL_DEFAULT_DICT;
110
111
nEnvCells = fsi->nEnvCells;
112
if (nEnvCells <= 0)
113
nEnvCells = FICL_DEFAULT_DICT;
114
115
pSys->dp = dictCreateHashed((unsigned)nDictCells, HASHSIZE);
116
pSys->dp->pForthWords->name = "forth-wordlist";
117
118
pSys->envp = dictCreate((unsigned)nEnvCells);
119
pSys->envp->pForthWords->name = "environment";
120
121
pSys->textOut = fsi->textOut;
122
pSys->pExtend = fsi->pExtend;
123
124
#if FICL_WANT_LOCALS
125
/*
126
** The locals dictionary is only searched while compiling,
127
** but this is where speed is most important. On the other
128
** hand, the dictionary gets emptied after each use of locals
129
** The need to balance search speed with the cost of the 'empty'
130
** operation led me to select a single-threaded list...
131
*/
132
pSys->localp = dictCreate((unsigned)FICL_MAX_LOCALS * CELLS_PER_WORD);
133
#endif
134
135
/*
136
** Build the precompiled dictionary and load softwords. We need a temporary
137
** VM to do this - ficlNewVM links one to the head of the system VM list.
138
** ficlCompilePlatform (defined in win32.c, for example) adds platform specific words.
139
*/
140
ficlCompileCore(pSys);
141
ficlCompilePrefix(pSys);
142
#if FICL_WANT_FLOAT
143
ficlCompileFloat(pSys);
144
#endif
145
#if FICL_PLATFORM_EXTEND
146
ficlCompilePlatform(pSys);
147
#endif
148
ficlSetVersionEnv(pSys);
149
150
/*
151
** Establish the parse order. Note that prefixes precede numbers -
152
** this allows constructs like "0b101010" which might parse as a
153
** hex value otherwise.
154
*/
155
ficlAddPrecompiledParseStep(pSys, "?prefix", ficlParsePrefix);
156
ficlAddPrecompiledParseStep(pSys, "?number", ficlParseNumber);
157
#if FICL_WANT_FLOAT
158
ficlAddPrecompiledParseStep(pSys, ">float", ficlParseFloatNumber);
159
#endif
160
161
/*
162
** Now create a temporary VM to compile the softwords. Since all VMs are
163
** linked into the vmList of FICL_SYSTEM, we don't have to pass the VM
164
** to ficlCompileSoftCore -- it just hijacks whatever it finds in the VM list.
165
** ficl 2.05: vmCreate no longer depends on the presence of INTERPRET in the
166
** dictionary, so a VM can be created before the dictionary is built. It just
167
** can't do much...
168
*/
169
ficlNewVM(pSys);
170
ficlCompileSoftCore(pSys);
171
ficlFreeVM(pSys->vmList);
172
173
174
return pSys;
175
}
176
177
178
FICL_SYSTEM *ficlInitSystem(int nDictCells)
179
{
180
FICL_SYSTEM_INFO fsi;
181
ficlInitInfo(&fsi);
182
fsi.nDictCells = nDictCells;
183
return ficlInitSystemEx(&fsi);
184
}
185
186
187
/**************************************************************************
188
f i c l A d d P a r s e S t e p
189
** Appends a parse step function to the end of the parse list (see
190
** FICL_PARSE_STEP notes in ficl.h for details). Returns 0 if successful,
191
** nonzero if there's no more room in the list.
192
**************************************************************************/
193
int ficlAddParseStep(FICL_SYSTEM *pSys, FICL_WORD *pFW)
194
{
195
int i;
196
for (i = 0; i < FICL_MAX_PARSE_STEPS; i++)
197
{
198
if (pSys->parseList[i] == NULL)
199
{
200
pSys->parseList[i] = pFW;
201
return 0;
202
}
203
}
204
205
return 1;
206
}
207
208
209
/*
210
** Compile a word into the dictionary that invokes the specified FICL_PARSE_STEP
211
** function. It is up to the user (as usual in Forth) to make sure the stack
212
** preconditions are valid (there needs to be a counted string on top of the stack)
213
** before using the resulting word.
214
*/
215
void ficlAddPrecompiledParseStep(FICL_SYSTEM *pSys, char *name, FICL_PARSE_STEP pStep)
216
{
217
FICL_DICT *dp = pSys->dp;
218
FICL_WORD *pFW = dictAppendWord(dp, name, parseStepParen, FW_DEFAULT);
219
dictAppendCell(dp, LVALUEtoCELL(pStep));
220
ficlAddParseStep(pSys, pFW);
221
}
222
223
224
/*
225
** This word lists the parse steps in order
226
*/
227
void ficlListParseSteps(FICL_VM *pVM)
228
{
229
int i;
230
FICL_SYSTEM *pSys = pVM->pSys;
231
assert(pSys);
232
233
vmTextOut(pVM, "Parse steps:", 1);
234
vmTextOut(pVM, "lookup", 1);
235
236
for (i = 0; i < FICL_MAX_PARSE_STEPS; i++)
237
{
238
if (pSys->parseList[i] != NULL)
239
{
240
vmTextOut(pVM, pSys->parseList[i]->name, 1);
241
}
242
else break;
243
}
244
return;
245
}
246
247
248
/**************************************************************************
249
f i c l N e w V M
250
** Create a new virtual machine and link it into the system list
251
** of VMs for later cleanup by ficlTermSystem.
252
**************************************************************************/
253
FICL_VM *ficlNewVM(FICL_SYSTEM *pSys)
254
{
255
FICL_VM *pVM = vmCreate(NULL, defaultStack, defaultStack);
256
pVM->link = pSys->vmList;
257
pVM->pSys = pSys;
258
pVM->pExtend = pSys->pExtend;
259
vmSetTextOut(pVM, pSys->textOut);
260
261
pSys->vmList = pVM;
262
return pVM;
263
}
264
265
266
/**************************************************************************
267
f i c l F r e e V M
268
** Removes the VM in question from the system VM list and deletes the
269
** memory allocated to it. This is an optional call, since ficlTermSystem
270
** will do this cleanup for you. This function is handy if you're going to
271
** do a lot of dynamic creation of VMs.
272
**************************************************************************/
273
void ficlFreeVM(FICL_VM *pVM)
274
{
275
FICL_SYSTEM *pSys = pVM->pSys;
276
FICL_VM *pList = pSys->vmList;
277
278
assert(pVM != NULL);
279
280
if (pSys->vmList == pVM)
281
{
282
pSys->vmList = pSys->vmList->link;
283
}
284
else for (; pList != NULL; pList = pList->link)
285
{
286
if (pList->link == pVM)
287
{
288
pList->link = pVM->link;
289
break;
290
}
291
}
292
293
if (pList)
294
vmDelete(pVM);
295
return;
296
}
297
298
299
/**************************************************************************
300
f i c l B u i l d
301
** Builds a word into the dictionary.
302
** Preconditions: system must be initialized, and there must
303
** be enough space for the new word's header! Operation is
304
** controlled by ficlLockDictionary, so any initialization
305
** required by your version of the function (if you overrode
306
** it) must be complete at this point.
307
** Parameters:
308
** name -- duh, the name of the word
309
** code -- code to execute when the word is invoked - must take a single param
310
** pointer to a FICL_VM
311
** flags -- 0 or more of F_IMMEDIATE, F_COMPILE, use bitwise OR!
312
**
313
**************************************************************************/
314
int ficlBuild(FICL_SYSTEM *pSys, char *name, FICL_CODE code, char flags)
315
{
316
#if FICL_MULTITHREAD
317
int err = ficlLockDictionary(TRUE);
318
if (err) return err;
319
#endif /* FICL_MULTITHREAD */
320
321
assert(dictCellsAvail(pSys->dp) > sizeof (FICL_WORD) / sizeof (CELL));
322
dictAppendWord(pSys->dp, name, code, flags);
323
324
ficlLockDictionary(FALSE);
325
return 0;
326
}
327
328
329
/**************************************************************************
330
f i c l E v a l u a t e
331
** Wrapper for ficlExec() which sets SOURCE-ID to -1.
332
**************************************************************************/
333
int ficlEvaluate(FICL_VM *pVM, char *pText)
334
{
335
int returnValue;
336
CELL id = pVM->sourceID;
337
pVM->sourceID.i = -1;
338
returnValue = ficlExecC(pVM, pText, -1);
339
pVM->sourceID = id;
340
return returnValue;
341
}
342
343
344
/**************************************************************************
345
f i c l E x e c
346
** Evaluates a block of input text in the context of the
347
** specified interpreter. Emits any requested output to the
348
** interpreter's output function.
349
**
350
** Contains the "inner interpreter" code in a tight loop
351
**
352
** Returns one of the VM_XXXX codes defined in ficl.h:
353
** VM_OUTOFTEXT is the normal exit condition
354
** VM_ERREXIT means that the interp encountered a syntax error
355
** and the vm has been reset to recover (some or all
356
** of the text block got ignored
357
** VM_USEREXIT means that the user executed the "bye" command
358
** to shut down the interpreter. This would be a good
359
** time to delete the vm, etc -- or you can ignore this
360
** signal.
361
**************************************************************************/
362
int ficlExec(FICL_VM *pVM, char *pText)
363
{
364
return ficlExecC(pVM, pText, -1);
365
}
366
367
int ficlExecC(FICL_VM *pVM, char *pText, FICL_INT size)
368
{
369
FICL_SYSTEM *pSys = pVM->pSys;
370
FICL_DICT *dp = pSys->dp;
371
372
int except;
373
jmp_buf vmState;
374
jmp_buf *oldState;
375
TIB saveTib;
376
377
assert(pVM);
378
assert(pSys->pInterp[0]);
379
380
if (size < 0)
381
size = strlen(pText);
382
383
vmPushTib(pVM, pText, size, &saveTib);
384
385
/*
386
** Save and restore VM's jmp_buf to enable nested calls to ficlExec
387
*/
388
oldState = pVM->pState;
389
pVM->pState = &vmState; /* This has to come before the setjmp! */
390
except = setjmp(vmState);
391
392
switch (except)
393
{
394
case 0:
395
if (pVM->fRestart)
396
{
397
pVM->runningWord->code(pVM);
398
pVM->fRestart = 0;
399
}
400
else
401
{ /* set VM up to interpret text */
402
vmPushIP(pVM, &(pSys->pInterp[0]));
403
}
404
405
vmInnerLoop(pVM);
406
break;
407
408
case VM_RESTART:
409
pVM->fRestart = 1;
410
except = VM_OUTOFTEXT;
411
break;
412
413
case VM_OUTOFTEXT:
414
vmPopIP(pVM);
415
#ifdef TESTMAIN
416
if ((pVM->state != COMPILE) && (pVM->sourceID.i == 0))
417
ficlTextOut(pVM, FICL_PROMPT, 0);
418
#endif
419
break;
420
421
case VM_USEREXIT:
422
case VM_INNEREXIT:
423
case VM_BREAK:
424
break;
425
426
case VM_QUIT:
427
if (pVM->state == COMPILE)
428
{
429
dictAbortDefinition(dp);
430
#if FICL_WANT_LOCALS
431
dictEmpty(pSys->localp, pSys->localp->pForthWords->size);
432
#endif
433
}
434
vmQuit(pVM);
435
break;
436
437
case VM_ERREXIT:
438
case VM_ABORT:
439
case VM_ABORTQ:
440
default: /* user defined exit code?? */
441
if (pVM->state == COMPILE)
442
{
443
dictAbortDefinition(dp);
444
#if FICL_WANT_LOCALS
445
dictEmpty(pSys->localp, pSys->localp->pForthWords->size);
446
#endif
447
}
448
dictResetSearchOrder(dp);
449
vmReset(pVM);
450
break;
451
}
452
453
pVM->pState = oldState;
454
vmPopTib(pVM, &saveTib);
455
return (except);
456
}
457
458
459
/**************************************************************************
460
f i c l E x e c X T
461
** Given a pointer to a FICL_WORD, push an inner interpreter and
462
** execute the word to completion. This is in contrast with vmExecute,
463
** which does not guarantee that the word will have completed when
464
** the function returns (ie in the case of colon definitions, which
465
** need an inner interpreter to finish)
466
**
467
** Returns one of the VM_XXXX exception codes listed in ficl.h. Normal
468
** exit condition is VM_INNEREXIT, ficl's private signal to exit the
469
** inner loop under normal circumstances. If another code is thrown to
470
** exit the loop, this function will re-throw it if it's nested under
471
** itself or ficlExec.
472
**
473
** NOTE: this function is intended so that C code can execute ficlWords
474
** given their address in the dictionary (xt).
475
**************************************************************************/
476
int ficlExecXT(FICL_VM *pVM, FICL_WORD *pWord)
477
{
478
int except;
479
jmp_buf vmState;
480
jmp_buf *oldState;
481
FICL_WORD *oldRunningWord;
482
483
assert(pVM);
484
assert(pVM->pSys->pExitInner);
485
486
/*
487
** Save the runningword so that RESTART behaves correctly
488
** over nested calls.
489
*/
490
oldRunningWord = pVM->runningWord;
491
/*
492
** Save and restore VM's jmp_buf to enable nested calls
493
*/
494
oldState = pVM->pState;
495
pVM->pState = &vmState; /* This has to come before the setjmp! */
496
except = setjmp(vmState);
497
498
if (except)
499
vmPopIP(pVM);
500
else
501
vmPushIP(pVM, &(pVM->pSys->pExitInner));
502
503
switch (except)
504
{
505
case 0:
506
vmExecute(pVM, pWord);
507
vmInnerLoop(pVM);
508
break;
509
510
case VM_INNEREXIT:
511
case VM_BREAK:
512
break;
513
514
case VM_RESTART:
515
case VM_OUTOFTEXT:
516
case VM_USEREXIT:
517
case VM_QUIT:
518
case VM_ERREXIT:
519
case VM_ABORT:
520
case VM_ABORTQ:
521
default: /* user defined exit code?? */
522
if (oldState)
523
{
524
pVM->pState = oldState;
525
vmThrow(pVM, except);
526
}
527
break;
528
}
529
530
pVM->pState = oldState;
531
pVM->runningWord = oldRunningWord;
532
return (except);
533
}
534
535
536
/**************************************************************************
537
f i c l L o o k u p
538
** Look in the system dictionary for a match to the given name. If
539
** found, return the address of the corresponding FICL_WORD. Otherwise
540
** return NULL.
541
**************************************************************************/
542
FICL_WORD *ficlLookup(FICL_SYSTEM *pSys, char *name)
543
{
544
STRINGINFO si;
545
SI_PSZ(si, name);
546
return dictLookup(pSys->dp, si);
547
}
548
549
550
/**************************************************************************
551
f i c l G e t D i c t
552
** Returns the address of the system dictionary
553
**************************************************************************/
554
FICL_DICT *ficlGetDict(FICL_SYSTEM *pSys)
555
{
556
return pSys->dp;
557
}
558
559
560
/**************************************************************************
561
f i c l G e t E n v
562
** Returns the address of the system environment space
563
**************************************************************************/
564
FICL_DICT *ficlGetEnv(FICL_SYSTEM *pSys)
565
{
566
return pSys->envp;
567
}
568
569
570
/**************************************************************************
571
f i c l S e t E n v
572
** Create an environment variable with a one-CELL payload. ficlSetEnvD
573
** makes one with a two-CELL payload.
574
**************************************************************************/
575
void ficlSetEnv(FICL_SYSTEM *pSys, char *name, FICL_UNS value)
576
{
577
STRINGINFO si;
578
FICL_WORD *pFW;
579
FICL_DICT *envp = pSys->envp;
580
581
SI_PSZ(si, name);
582
pFW = dictLookup(envp, si);
583
584
if (pFW == NULL)
585
{
586
dictAppendWord(envp, name, constantParen, FW_DEFAULT);
587
dictAppendCell(envp, LVALUEtoCELL(value));
588
}
589
else
590
{
591
pFW->param[0] = LVALUEtoCELL(value);
592
}
593
594
return;
595
}
596
597
void ficlSetEnvD(FICL_SYSTEM *pSys, char *name, FICL_UNS hi, FICL_UNS lo)
598
{
599
FICL_WORD *pFW;
600
STRINGINFO si;
601
FICL_DICT *envp = pSys->envp;
602
SI_PSZ(si, name);
603
pFW = dictLookup(envp, si);
604
605
if (pFW == NULL)
606
{
607
dictAppendWord(envp, name, twoConstParen, FW_DEFAULT);
608
dictAppendCell(envp, LVALUEtoCELL(lo));
609
dictAppendCell(envp, LVALUEtoCELL(hi));
610
}
611
else
612
{
613
pFW->param[0] = LVALUEtoCELL(lo);
614
pFW->param[1] = LVALUEtoCELL(hi);
615
}
616
617
return;
618
}
619
620
621
/**************************************************************************
622
f i c l G e t L o c
623
** Returns the address of the system locals dictionary. This dict is
624
** only used during compilation, and is shared by all VMs.
625
**************************************************************************/
626
#if FICL_WANT_LOCALS
627
FICL_DICT *ficlGetLoc(FICL_SYSTEM *pSys)
628
{
629
return pSys->localp;
630
}
631
#endif
632
633
634
635
/**************************************************************************
636
f i c l S e t S t a c k S i z e
637
** Set the stack sizes (return and parameter) to be used for all
638
** subsequently created VMs. Returns actual stack size to be used.
639
**************************************************************************/
640
int ficlSetStackSize(int nStackCells)
641
{
642
if (nStackCells >= FICL_DEFAULT_STACK)
643
defaultStack = nStackCells;
644
else
645
defaultStack = FICL_DEFAULT_STACK;
646
647
return defaultStack;
648
}
649
650
651
/**************************************************************************
652
f i c l T e r m S y s t e m
653
** Tear the system down by deleting the dictionaries and all VMs.
654
** This saves you from having to keep track of all that stuff.
655
**************************************************************************/
656
void ficlTermSystem(FICL_SYSTEM *pSys)
657
{
658
if (pSys->dp)
659
dictDelete(pSys->dp);
660
pSys->dp = NULL;
661
662
if (pSys->envp)
663
dictDelete(pSys->envp);
664
pSys->envp = NULL;
665
666
#if FICL_WANT_LOCALS
667
if (pSys->localp)
668
dictDelete(pSys->localp);
669
pSys->localp = NULL;
670
#endif
671
672
while (pSys->vmList != NULL)
673
{
674
FICL_VM *pVM = pSys->vmList;
675
pSys->vmList = pSys->vmList->link;
676
vmDelete(pVM);
677
}
678
679
ficlFree(pSys);
680
pSys = NULL;
681
return;
682
}
683
684
685
/**************************************************************************
686
f i c l S e t V e r s i o n E n v
687
** Create a double cell environment constant for the version ID
688
**************************************************************************/
689
static void ficlSetVersionEnv(FICL_SYSTEM *pSys)
690
{
691
ficlSetEnvD(pSys, "ficl-version", FICL_VER_MAJOR, FICL_VER_MINOR);
692
ficlSetEnv (pSys, "ficl-robust", FICL_ROBUST);
693
return;
694
}
695
696
697