forked from ldmud/ldmud
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathparse.c
1846 lines (1565 loc) · 54.7 KB
/
parse.c
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
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*---------------------------------------------------------------------------
* Pattern Parser v 3.1 (non-compat mode)
* (C) Copyright 1991 JnA ([email protected])
*
*---------------------------------------------------------------------------
* TODO: Improve this efun, make it more general.
* EFUN parse_command
*
* int parse_command (string cmd, object env, string fmt, mixed &var, ...)
* int parse_command (string cmd, object* arr, string fmt, mixed &var, ...)
*
* parse_command() is basically a spiffed up sscanf operating on word basis
* and targeted at recognizing object descriptions from command strings.
*
* The efun takes the command string <cmd> and the object(s) <env>/<arr>
* and tries to match it against the format string <fmt>. Successfully
* matched elements are assigned to the variables <var>....
* The result from the efun is 1 if the command could be fully matched,
* and 0 otherwise.
*
* If the objects are given as a single object <env>, the efun matches
* against the given object and all objects contained therein. Otherwise,
* if the objects are given as an array <arr> of objects, the efun
* matches only against the given objects.
*
* The format string <fmt> consists of words, syntactic markers, and
* %-directives for the values to parse and return in the variables.
* A typical example is " 'get' / 'take' %i " or
* " 'spray' / 'paint' [paint] %i ". The elements in detail are:
*
* 'word': obligatory text
* [word]: optional text
* / : Alternative marker
* %o : Single item, object
* %s : Any text
* %w : Any word
* %p : One of a list of prepositions.
* If the variable associated with %p is used to pass
* a list of words to the efun, the matching will take
* only against this list.
* %l : Living objects
* %i : Any objects
* %d : Number >= 0, or when given textual: 0-99.
*
* A <word> in this context is any sequence of characters not containing
* a space. 'living objects' are searched by calls to the (simul)efuns
* find_player() and find_living(): both functions have to accept a name
* as argument and return the object for this name, or 0 if there
* is none.
*
* The results assigned to the variables by the %-directives are:
*
* %o : returns an object
* %s : returns a string of words
* %w : returns a string of one word
* %p : if passed empty: a string
* if passed as array of words: var[0] is the matched word
* %i : returns an array with the following content:
* [0]: int: the count/number recognized in the object spec
* > 0: a count (e.g. 'three', '4')
* < 0: an ordinal (e.g. 'second', 'third')
* = 0: 'all' or a generic plural such as 'apples'
* [1..]: object: all(!) objects matching the item description.
* In the <env> form this may be the whole
* recursive inventory of the <env> object.
* It is up to the caller to interpret the recognized numeral
* and to apply it on the list of matched objects.
* %l : as %i, except that only living objects are returned.
*
* %i and %l match descriptions like 'three red roses','all nasty bugs'
* or 'second blue sword'.
*
* Note: Patterns of type: "%s %w %i" might not work as one would expect.
* %w will always succeed so the arg corresponding to %s will always be empty.
*
*
* To make the efun useful it must have a certain support from the mudlib:
* it calls a set of functions in objects to get the information it needs
* to parse a string.
*
* 1. string *parse_command_id_list()
* Normal singular names of the object.
*
* 2. string *parse_command_plural_id_list() - optional
* Plural forms of the names returned by 1.
* If this function doesn't exist, the parser tries to pluralize
* the names returned by 1.
*
* 3. string *parse_command_adjectiv_id_list() - optional
* All adjectives associated with this object.
*
* All names and adjectives may consist of several words separated
* by spaces.
*
* These functions should exist in all objects and are therefore best
* put into a mandatory inherit file (e.g. /std/object.c).
*
* In addition the master object may offer the same functions to provide
* reasonable defaults (like 'thing' as generic singular name):
*
* string *parse_command_id_list()
* - Would normally return: ({ "one", "thing" })
*
* string *parse_command_plural_id_list()
* - Would normally return: ({ "ones", "things", "them" })
*
* string *parse_command_adjectiv_id_list()
* - Would normally return ({ "iffish" })
*
* Two additional functions in the master object provide the default
* list of prepositions (needed for %p) and the single 'all' word:
*
* string *parse_command_prepos_list()
* - Would normally return: ({ "in", "on", "under", "behind", "beside" })
*
* string parse_command_all_word()
* - Would normally return: "all"
*---------------------------------------------------------------------------
* TODO: A proper localisation would at least put all the following into the
* TODO:: master object as well.
*
* If you want to use a different language than English, you need to write
* a small file 'parse_local.c' and include it into parse.c at the
* marked position.
*
* The 'parse_local.c' has to contain your localized pluralmaker and
* the textual number words and should look like this:
*
* ---------- SNIP ----------
* #define PARSE_FOREIGN
*
* char *parse_to_plural(str)
* char *str;
* {
*
* * Your own plural converter for your language *
*
* }
*
* * The numberwords below should be replaced for the new language *
*
* static char *ord1[] = {"", "first", "second", "third", "fourth", "fifth",
* "sixth", "seventh", "eighth", "nineth", "tenth",
* "eleventh", "twelfth", "thirteenth", "fourteenth",
* "fifteenth", "sixteenth", "seventeenth",
* "eighteenth","nineteenth"};
*
* static char *ord10[] = {"", "", "twenty","thirty","forty","fifty","sixty",
* "seventy", "eighty","ninety"};
*
* static char *sord10[] = {"", "", "twentieth", "thirtieth", "fortieth",
* "fiftieth", "sixtieth","seventieth", "eightieth",
* "ninetieth"};
*
* static char *num1[] = {"", "one","two","three","four","five","six",
* "seven","eight","nine","ten",
* "eleven","twelve","thirteen","fourteen","fifteen",
* "sixteen", "seventeen","eighteen","nineteen"};
*
* static char *num10[] = {"", "", "twenty","thirty","forty","fifty","sixty",
* "seventy", "eighty","ninety"};
* ---------- SNIP ----------
*---------------------------------------------------------------------------
*/
#include "driver.h"
#if defined(USE_PARSE_COMMAND)
#include "typedefs.h"
#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include "parse.h"
#include "actions.h"
#include "array.h"
#include "gcollect.h"
#include "interpret.h"
#include "lex.h"
#include "main.h"
#include "mstrings.h"
#include "object.h"
#include "simulate.h"
#include "stdstrings.h"
#include "svalue.h"
#include "wiz_list.h"
#include "xalloc.h"
/* For a localisation of parse_command(),
* #include "parse_local.c"
* here.
*/
/*-------------------------------------------------------------------------*/
/* Some useful string macros
*/
#define PREFIXED(x,y) (strncmp(x, y, strlen(x)) == 0)
/*-------------------------------------------------------------------------*/
/* To make parse_command() reentrant, the module maintains a list
* of previous contexts using this structure:
*/
typedef struct parse_context_s parse_context_t;
struct parse_context_s
{
parse_context_t *previous;
vector_t *id, *plid, *adjid;
vector_t *id_d, *plid_d, *adjid_d, *prepos;
string_t *allword;
/* This context: the lists of ids and such. */
vector_t *wvec, *patvec, *obvec;
/* Next context(!): word, pattern and object vector */
};
/*-------------------------------------------------------------------------*/
/* Arrays holding the constituent words of textual numbers from 0 to 99.
* The numbers are constructed by concatenation.
*/
#ifndef PARSE_FOREIGN
static char *ord1[] = {"", "first", "second", "third", "fourth", "fifth"
, "sixth", "seventh", "eighth", "nineth", "tenth"
, "eleventh", "twelfth", "thirteenth", "fourteenth"
, "fifteenth", "sixteenth", "seventeenth"
, "eighteenth","nineteenth"};
/* The ordinals from 1 to 19, also used to build the ordinals 20..99.
*/
static char *ord10[] = { "", "", "twenty","thirty","forty","fifty","sixty"
, "seventy", "eighty","ninety"};
/* The first word for the ordinals 20..99.
*/
static char *sord10[] = { "", "", "twentieth", "thirtieth", "fortieth"
, "fiftieth", "sixtieth","seventieth", "eightieth"
, "ninetieth"};
/* The ordinals 20, 30, ..., 90.
*/
static char *num1[] = { "", "one","two","three","four","five","six"
, "seven","eight","nine","ten"
, "eleven","twelve","thirteen","fourteen","fifteen"
, "sixteen", "seventeen","eighteen","nineteen"};
/* The numbers 1 to 19, also used to build the numbers 20..99.
*/
static char *num10[] = { "", "", "twenty","thirty","forty","fifty","sixty"
, "seventy", "eighty","ninety"};
/* The first word for the numbers 20..99.
*/
#endif
/*-------------------------------------------------------------------------*/
static svalue_t find_living_closures[2]
= { { T_INVALID }, { T_INVALID } };
/* The closures to the functions 'find_living' and 'find_player',
* which are generated at runtime to be able to find simul-efuns with
* these names.
* TODO: This too should go into a master function.
*/
static parse_context_t *gPrevious_context = NULL;
/* The list of previous contexts.
*/
/* The following variables 'cache' the various lists read from the
* matched objects and the master object. The original values are
* save when parse_command() is called, making the efun re-entrant.
*/
static vector_t *gId_list = NULL;
static vector_t *gPluid_list = NULL;
static vector_t *gAdjid_list = NULL;
/* Arrays of the lists from the objects matched against.
* For example gId_list[2] returns the singular name list for
* the third object.
* The arrays are filled on demand only. svalue-0s denote entries
* yet to fill, svalue-1s are entries where the object doesn't provide
* the particular information.
*/
static vector_t *gId_list_d = NULL;
static vector_t *gPluid_list_d = NULL;
static vector_t *gAdjid_list_d = NULL;
static vector_t *gPrepos_list = NULL;
static string_t *gAllword = NULL;
/* The lists and the 'all' word from the master object.
*/
/*-------------------------------------------------------------------------*/
static object_t *
find_living_object (string_t *name, Bool player)
/* Find the living (<player> is false) or player (<player> is true)
* with the name <name>.
* Return the found object, or NULL if not found.
*
* The functions calls the (simul)efuns 'find_living' resp.
* 'find_player' for this purpose.
*/
{
svalue_t *svp;
/* Get or create the closure for the function to call */
svp = &find_living_closures[player ? 1 : 0];
if (svp->type == T_INVALID)
{
/* We have to create the closure */
symbol_efun(player ? STR_PC_FIND_PLAYER : STR_PC_FIND_LIVING, svp);
}
/* Call the closure */
inter_sp++;
put_ref_string(inter_sp, name);
call_lambda(svp, 1);
pop_stack();
/* In theory this could lose the last ref to the result object.
* In praxis, those objects have more refs.
*/
return inter_sp[1].type != T_OBJECT ? NULL : inter_sp[1].u.ob;
} /* find_living_object() */
/*-------------------------------------------------------------------------*/
#ifdef GC_SUPPORT
void
clear_parse_refs (void)
/* GC support: Clear the references of all memory held by the parser.
*/
{
clear_ref_in_vector( find_living_closures
, sizeof find_living_closures / sizeof(svalue_t)
);
} /* clear_parse_refs() */
/*-------------------------------------------------------------------------*/
void
count_parse_refs (void)
/* GC support: Count the references of all memory held by the parser.
*/
{
count_ref_in_vector( find_living_closures
, sizeof find_living_closures / sizeof(svalue_t)
);
} /* count_parse_refs() */
#endif /* GC_SUPPORT */
#ifndef PARSE_FOREIGN
/*-------------------------------------------------------------------------*/
static string_t *
parse_one_plural (string_t *str)
/* Change the singular noun <str> to a plural and return it.
* The result is either a new string with one reference, or <str> itself
* with an added reference.
*/
{
static char pbuf[100]; /* Result buffer */
char ch, ch2; /* Last two characters in <str> */
size_t sl; /* Last index in <str> */
sl = mstrsize(str);
if (sl < 3 || sl > sizeof(pbuf) - 10)
return str;
sl--;
/* Copy <str> except for the last char into pbuf */
ch = get_txt(str)[sl];
ch2 = get_txt(str)[sl-1];
strcpy(pbuf, get_txt(str)); pbuf[sl] = '\0';
/* Try to make plural based on the last two chars */
switch (ch)
{
case 's':
case 'x':
case 'h':
return new_unicode_mstring(strcat(pbuf, "ses"));
case 'y':
return new_unicode_mstring(strcat(pbuf, "ies"));
case 'e':
if (ch2 == 'f')
{
pbuf[sl-1] = 0;
return new_unicode_mstring(strcat(pbuf, "ves"));
}
}
/* Some known special cases */
if (mstreq(str, STR_PC_CORPSE)) return ref_mstring(STR_PC_CORPSES);
if (mstreq(str, STR_PC_TOOTH)) return ref_mstring(STR_PC_TEETH);
if (mstreq(str, STR_PC_FOOT)) return ref_mstring(STR_PC_FEET);
if (mstreq(str, STR_PC_MAN)) return ref_mstring(STR_PC_MEN);
if (mstreq(str, STR_PC_WOMAN)) return ref_mstring(STR_PC_WOMEN);
if (mstreq(str, STR_PC_CHILD)) return ref_mstring(STR_PC_CHILDREN);
if (mstreq(str, STR_PC_SHEEP)) return ref_mstring(STR_PC_SHEEP);
/* Default: just append 's' */
pbuf[sl] = ch;
return new_unicode_mstring(strcat(pbuf, "s"));
} /* parse_one_plural() */
/*-------------------------------------------------------------------------*/
static string_t *
parse_to_plural (string_t *str)
/* Change the singular name <str> to a plural name. The result is a new
* string with one reference.
*
* The algorithm groups the <str> into runs delimited by 'of' (e.g. "the box
* of the king" and pluralizes the last word before each 'of' and the last
* word in the string (giving "the boxes of the kings").
* TODO: TubMud has a good plural maker.
*/
{
vector_t *words;
svalue_t stmp;
string_t *sp;
size_t il;
Bool changed;
/* If it's a single word, it's easy */
if (!(strchr(get_txt(str), ' ')))
return parse_one_plural(str);
/* Multiple words, possible grouped into runs delimited by 'of':
* pluralize the last word in the string, and the last word
* before each 'of'.
*/
words = explode_string(str, STR_SPACE);
for (changed = MY_FALSE, il = 1; (p_int)il < VEC_SIZE(words); il++)
{
if ((mstreq(words->item[il].u.str, STR_PC_OF))
|| (p_int)il+1 == VEC_SIZE(words))
{
/* Got one to pluralize */
sp = parse_one_plural(words->item[il-1].u.str);
if (sp != words->item[il-1].u.str)
{
put_string(&stmp, sp);
transfer_svalue(&words->item[il-1], &stmp);
changed = MY_TRUE;
}
else
free_mstring(sp); /* Reuse the old reference */
}
}
/* If nothing changed, just return a copy of the original */
if (!changed)
{
free_array(words);
return ref_mstring(str);
}
/* We changed it: return the new name */
sp = implode_string(words, STR_SPACE);
free_array(words);
return sp;
} /* parse_to_plural() */
#endif /* PARSE_FOREIGN */
/*-------------------------------------------------------------------------*/
static void
load_lpc_info (size_t ix, object_t *ob)
/* Load the relevant information (singular names, plural names and adjectives)
* for object <ob> into position <ix> of the cache lists, unless already
* loaded.
*
* If the object does not provide plural names, they are synthesized from
* the singular names.
*/
{
Bool make_plural = MY_FALSE; /* TRUE: synthesize plurals */
svalue_t * ret;
if (!ob || ob->flags & O_DESTRUCTED)
return;
/* Get the plural names, if any.
*/
if (gPluid_list
&& VEC_SIZE(gPluid_list) > (p_int)ix
&& gPluid_list->item[ix].type == T_NUMBER
&& gPluid_list->item[ix].u.number == 0
)
{
ret = apply(STR_PC_P_ID_LIST, ob, 0);
if (ret && ret->type == T_POINTER)
assign_svalue_no_free(&gPluid_list->item[ix], ret);
else
{
make_plural = MY_TRUE;
gPluid_list->item[ix].u.number = 1;
}
}
/* Get the singular names and, if desired, synthesize the
* plural names.
*/
if (gId_list
&& VEC_SIZE(gId_list) > (p_int)ix
&& gId_list->item[ix].type == T_NUMBER
&& gId_list->item[ix].u.number == 0
&& !(ob->flags & O_DESTRUCTED) )
{
ret = apply(STR_PC_ID_LIST, ob, 0);
if (ret && ret->type == T_POINTER)
{
assign_svalue_no_free(&gId_list->item[ix], ret);
if (make_plural)
{
/* Pluralize the singular names */
vector_t *tmp, *sing;
svalue_t sval;
string_t *str;
size_t il;
tmp = allocate_array((size_t)VEC_SIZE(ret->u.vec));
if (!tmp)
errorf("(parse_command) Out of memory: array[%lu] for "
"plural names.\n"
, (unsigned long)VEC_SIZE(ret->u.vec));
sing = ret->u.vec;
for (il = 0; (p_int)il < VEC_SIZE(tmp); il++)
{
if (sing->item[il].type == T_STRING)
{
str = parse_to_plural(sing->item[il].u.str);
put_string(&sval, str);
transfer_svalue_no_free(&tmp->item[il],&sval);
}
}
put_array(&sval, tmp);
transfer_svalue_no_free(&gPluid_list->item[ix], &sval);
}
}
else
{
gId_list->item[ix].u.number = 1;
}
}
/* Get the adjectives, if any.
*/
if (gAdjid_list
&& VEC_SIZE(gAdjid_list) > (p_int)ix
&& gAdjid_list->item[ix].type == T_NUMBER
&& gAdjid_list->item[ix].u.number == 0
&& !(ob->flags & O_DESTRUCTED) )
{
ret = apply(STR_PC_ADJ_LIST, ob, 0);
if (ret && ret->type == T_POINTER)
assign_svalue_no_free(&gAdjid_list->item[ix], ret);
else
gAdjid_list->item[ix].u.number = 1;
}
} /* load_lpc_info() */
/*-------------------------------------------------------------------------*/
static void
parse_error_handler (error_handler_t *arg UNUSED)
/* The current parse_command() processing was interrupted by an error.
* Clean up the current context and restore the previous context.
*/
{
#ifdef __MWERKS__
# pragma unused(arg)
#endif
parse_context_t *old;
old = gPrevious_context;
/* Delete and free the id arrays. */
if (gId_list)
free_array(gId_list);
if (gPluid_list)
free_array(gPluid_list);
if (gAdjid_list)
free_array(gAdjid_list);
if (gId_list_d)
free_array(gId_list_d);
if (gPluid_list_d)
free_array(gPluid_list_d);
if (gAdjid_list_d)
free_array(gAdjid_list_d);
if (gPrepos_list)
free_array(gPrepos_list);
if (gAllword)
free_mstring(gAllword);
/* Restore the previous lists */
gId_list_d = old->id_d;
gPluid_list_d = old->plid_d;
gAdjid_list_d = old->adjid_d;
gPrepos_list = old->prepos;
gId_list = old->id;
gPluid_list = old->plid;
gAdjid_list = old->adjid;
gAllword = old->allword;
/* Free the local arrays */
free_array(old->wvec);
free_array(old->patvec);
free_array(old->obvec);
gPrevious_context = old->previous;
xfree(old);
} /* parse_error_handler() */
/*-------------------------------------------------------------------------*/
static INLINE void
stack_put (svalue_t *pval, svalue_t *sp, size_t pos, int max)
/* Store the value <pval> into the lvalue <sp>[<pos>].
* If <pval> is NULL, <sp>[<pos>] not a lvalue or <pos> >= <max>,
* nothing happens - which is a good thing as this function stores
* the parsed results into the variables passed to the efun, and we
* never know what the wizards are going to pass there.
*/
{
if (pval && pos < (size_t)max && sp[pos].type == T_LVALUE)
transfer_svalue(sp[pos].u.lvalue, pval);
} /* stack_put() */
/*-------------------------------------------------------------------------*/
static svalue_t *
slice_words (vector_t *wvec, size_t from, size_t to)
/* Return an imploded string of words from <wvec>[<from>..<to>] as tabled
* string svalue.
* Return NULL if there is nothing to slice.
*/
{
vector_t *slice;
string_t *tx;
static svalue_t stmp;
if (from > to)
return NULL;
slice = slice_array(wvec, from, to);
if (VEC_SIZE(slice))
tx = implode_string(slice, STR_SPACE);
else
tx = NULL;
free_array(slice);
if (tx)
{
put_string(&stmp, make_tabled(tx));
return &stmp;
}
else
return NULL;
} /* slice_words() */
/*-------------------------------------------------------------------------*/
static int
find_string (string_t *str, vector_t *wvec, size_t *cix_in)
/* Test if the (multi-word) string <str> exists in the array of words <wvec>
* at or after position <cix_in>.
* If found, return the starting position in <wvec> and set <cix_in> to
* the position of the last word of the found string.
* If not round, return -1, <cix_in> will be set to the end of <wvec>.
*/
{
int fpos;
string_t *p1;
char *p2;
vector_t *split;
/* Step through wvec and look for a match */
for (; (p_int)*cix_in < VEC_SIZE(wvec); (*cix_in)++)
{
p1 = wvec->item[*cix_in].u.str;
/* Quick test: first character has to match */
if (get_txt(p1)[0] != get_txt(str)[0])
continue;
if (mstreq(p1, str)) /* str was one word and we found it */
return (int)*cix_in;
if (!(p2 = strchr(get_txt(str), ' ')))
continue;
/* If str is a multiword string and we need to make some special checks
*/
if ((p_int)*cix_in + 1 == VEC_SIZE(wvec))
continue;
split = explode_string(str, STR_SPACE);
/* Now: wvec->size - *cix_in = 2: One extra word
* = 3: Two extra words
*/
if (!split || VEC_SIZE(split) > (VEC_SIZE(wvec) - (p_int)*cix_in))
{
if (split)
free_array(split);
continue;
}
/* Test if the following words match the string */
fpos = (int)*cix_in;
for (; *cix_in < (size_t)(VEC_SIZE(split) + fpos); (*cix_in)++)
{
if (!mstreq(split->item[*cix_in-fpos].u.str,
wvec->item[*cix_in].u.str))
break;
}
/* If all of split matched, we found it */
if ((p_int)(*cix_in - fpos) == VEC_SIZE(split))
{
(*cix_in)--; /* point to the last matched word */
return fpos;
}
/* Not found: continue search */
*cix_in = fpos;
}
/* Not found */
return -1;
} /* find_string() */
/*-------------------------------------------------------------------------*/
static int
member_string (string_t *str, vector_t *svec)
/* Test if string <str> is member of the array <svec>.
* Return the position if found, and -1 otherwise.
*/
{
size_t il;
if (!svec)
return -1;
for (il = 0; (p_int)il < VEC_SIZE(svec); il++)
{
if (svec->item[il].type != T_STRING)
continue;
if (mstreq(svec->item[il].u.str, str))
return (int)il;
}
return -1;
} /* member_string() */
/*-------------------------------------------------------------------------*/
static Bool
check_adjectiv (size_t obix, vector_t *wvec, size_t from, size_t to)
/* Check if the command words <wvec>[<from>..<to>] match the adjectives
* for object <obix>.
* Return TRUE if yes.
*/
{
size_t il;
size_t sum; /* Total length of command words tested */
size_t back;
Bool fail; /* TRUE if not found */
string_t *adstr;
char *adstrp;
vector_t *ids; /* Adj list of the object */
/* Get the objects adj-list if existing */
if (gAdjid_list->item[obix].type == T_POINTER)
ids = gAdjid_list->item[obix].u.vec;
else
ids = NULL;
/* Scan the given command words, sum up their length and
* test if all of them match the adjectives given.
*/
for (sum = 0, fail = MY_FALSE, il = from; il <= to; il++)
{
sum += mstrsize(wvec->item[il].u.str) + 1;
if ((member_string(wvec->item[il].u.str, ids) < 0)
&& (member_string(wvec->item[il].u.str, gAdjid_list_d) < 0))
{
fail = MY_TRUE;
}
}
/* Simple case: all adjs were single words and matched.
*/
if (!fail)
return MY_TRUE;
if (from == to)
return MY_FALSE;
/* It could be that some of the adjectives provided by the object are
* multi-words; in that case the above loop would signal a mismatch.
*
* To find these, concatenate the command words with spaces and
* test them against the single adjective strings.
* TODO: This test could be implemented faster.
*/
adstr = NULL;
adstrp = xalloc(sum); /* Workspace */
/* Test the adjectives one after the other */
for (il = from; il < to;)
{
/* For every adjective, perform a greedy match first, ie
* try to match the longer concatenated strings before
* the shorter ones.
*/
for (back = to; back > il; back--)
{
/* Catenate the adjective from the command words */
adstrp[0] = '\0';
for (sum = il; sum <= back; sum++)
{
if (sum > il)
strcat(adstrp, " ");
strcat(adstrp, get_txt(wvec->item[sum].u.str));
}
adstr = new_unicode_mstring(adstrp);
if ((member_string(adstr, ids) >= 0)
|| (member_string(adstr, gAdjid_list_d) >= 0))
{
/* Found: continue search after this matched adjective */
il = back + 1;
break;
}
/* Not found: abort */
mstring_free(adstr);
xfree(adstrp);
return MY_FALSE;
}
}
/* Found: clean up and return */
mstring_free(adstr);
xfree(adstrp);
return MY_TRUE;
} /* check_adjectiv() */
/*-------------------------------------------------------------------------*/
static svalue_t *
number_parse( vector_t *obvec UNUSED /* in: array of objects to match against */
, vector_t *wvec /* in: array of words to match */
, size_t *cix_in /* in-out: position in wvec */
, Bool *fail /* out: TRUE on mismatch */
)
/* Interpret the words in wvec[cix_in] as numeric descriptor, parse it
* and return an int-svalue with the result:
* > 0: a number ('one', 'two', 'three', or the number given)
* < 0: an ordinal ('first', 'second')
* = 0: any ('zero', 0, the gAllword)
* On failure, return NULL.
*
* On return, <fail> is set to the success state of the match, and <cix_in>
* has been set past the parsed number.
*/
{
#ifdef __MWERKS__
# pragma unused(obvec)
#endif
static svalue_t stmp; /* Result buffer */
size_t cix;
int ten, ones, num;
cix = *cix_in; *fail = MY_FALSE;
ones = 0;
/* First try to parse the number in digit representation */
if (sscanf(get_txt(wvec->item[cix].u.str), "%d", &num))
{
if (num >= 0)
{
(*cix_in)++;
put_number(&stmp, num);
return &stmp;
}
*fail = MY_TRUE;
return NULL; /* Only nonnegative numbers allowed */
}
/* Is it the 'all' word? */
if (gAllword && mstreq(wvec->item[cix].u.str, gAllword))
{
(*cix_in)++;
put_number(&stmp, 0);
return &stmp;
}
/* Test the number against every known textual number.
*/
for (ten = 0; ten < 10; ten++)
{
char *second;
/* Test if the first part of the word matches */
if (!PREFIXED(num10[ten], get_txt(wvec->item[cix].u.str)))
continue;
/* Yup, now match the rest */
second = get_txt(wvec->item[cix].u.str) + strlen(num10[ten]);
for (ones = 0; ones < 10; ones++)
{
char *tmp;
tmp = (ten>1) ? num1[ones] : num1[ten*10+ones];
if (!strcmp(second, tmp))
{
(*cix_in)++;
put_number(&stmp, ten*10+ones);
return &stmp;
}
} /* for (ones) */
} /* for (ten) */
/* Test the number against every known textual ordinal.
*/
for (ten = 0; ten < 10; ten++)
{
char *second;
/* Multiples of 10 have their own words */
if (!strcmp(sord10[ten], get_txt(wvec->item[cix].u.str)))
{
(*cix_in)++;
put_number(&stmp, -(ten*10+ones));
return &stmp;
}
/* Test if the first part of the word matches */
if (!PREFIXED(ord10[ten], get_txt(wvec->item[cix].u.str)))
continue;
/* Yup, now match the rest */
second = get_txt(wvec->item[cix].u.str) + strlen(ord10[ten]);