1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 //include <cctype> //isalnum()
24 //include <cstdio>
25 #include "ags/shared/ac/common.h"
26 #include "ags/shared/ac/game_setup_struct.h"
27 #include "ags/engine/ac/game_state.h"
28 #include "ags/engine/ac/parser.h"
29 #include "ags/engine/ac/string.h"
30 #include "ags/shared/ac/words_dictionary.h"
31 #include "ags/engine/debugging/debug_log.h"
32 #include "ags/shared/util/string.h"
33 #include "ags/shared/util/string_compat.h"
34 #include "ags/shared/debugging/out.h"
35 #include "ags/engine/script/script_api.h"
36 #include "ags/engine/script/script_runtime.h"
37 #include "ags/engine/ac/dynobj/script_string.h"
38 #include "ags/globals.h"
39
40 namespace AGS3 {
41
42 using namespace AGS::Shared;
43
44
45
46
Parser_FindWordID(const char * wordToFind)47 int Parser_FindWordID(const char *wordToFind) {
48 return find_word_in_dictionary(wordToFind);
49 }
50
Parser_SaidUnknownWord()51 const char *Parser_SaidUnknownWord() {
52 if (_GP(play).bad_parsed_word[0] == 0)
53 return nullptr;
54 return CreateNewScriptString(_GP(play).bad_parsed_word);
55 }
56
ParseText(const char * text)57 void ParseText(const char *text) {
58 parse_sentence(text, &_GP(play).num_parsed_words, _GP(play).parsed_words, nullptr, 0);
59 }
60
61 // Said: call with argument for example "get apple"; we then check
62 // word by word if it matches (using dictonary ID equivalence to match
63 // synonyms). Returns 1 if it does, 0 if not.
Said(const char * checkwords)64 int Said(const char *checkwords) {
65 int numword = 0;
66 short words[MAX_PARSED_WORDS];
67 return parse_sentence(checkwords, &numword, &words[0], _GP(play).parsed_words, _GP(play).num_parsed_words);
68 }
69
70 //=============================================================================
71
find_word_in_dictionary(const char * lookfor)72 int find_word_in_dictionary(const char *lookfor) {
73 int j;
74 if (_GP(game).dict == nullptr)
75 return -1;
76
77 for (j = 0; j < _GP(game).dict->num_words; j++) {
78 if (ags_stricmp(lookfor, _GP(game).dict->word[j]) == 0) {
79 return _GP(game).dict->wordnum[j];
80 }
81 }
82 if (lookfor[0] != 0) {
83 // If the word wasn't found, but it ends in 'S', see if there's
84 // a non-plural version
85 const char *ptat = &lookfor[strlen(lookfor) - 1];
86 char lastletter = *ptat;
87 if ((lastletter == 's') || (lastletter == 'S') || (lastletter == '\'')) {
88 String singular = lookfor;
89 singular.ClipRight(1);
90 return find_word_in_dictionary(singular.GetCStr());
91 }
92 }
93 return -1;
94 }
95
is_valid_word_char(char theChar)96 int is_valid_word_char(char theChar) {
97 if ((Common::isAlnum((unsigned char)theChar)) || (theChar == '\'') || (theChar == '-')) {
98 return 1;
99 }
100 return 0;
101 }
102
FindMatchingMultiWordWord(char * thisword,const char ** text)103 int FindMatchingMultiWordWord(char *thisword, const char **text) {
104 // see if there are any multi-word words
105 // that match -- if so, use them
106 const char *tempptr = *text;
107 char tempword[150] = "";
108 if (thisword != nullptr)
109 strcpy(tempword, thisword);
110
111 int bestMatchFound = -1, word;
112 const char *tempptrAtBestMatch = tempptr;
113
114 do {
115 // extract and concat the next word
116 strcat(tempword, " ");
117 while (tempptr[0] == ' ') tempptr++;
118 char chbuffer[2];
119 while (is_valid_word_char(tempptr[0])) {
120 sprintf(chbuffer, "%c", tempptr[0]);
121 strcat(tempword, chbuffer);
122 tempptr++;
123 }
124 // is this it?
125 word = find_word_in_dictionary(tempword);
126 // take the longest match we find
127 if (word >= 0) {
128 bestMatchFound = word;
129 tempptrAtBestMatch = tempptr;
130 }
131
132 } while (tempptr[0] == ' ');
133
134 word = bestMatchFound;
135
136 if (word >= 0) {
137 // yes, a word like "pick up" was found
138 *text = tempptrAtBestMatch;
139 if (thisword != nullptr)
140 strcpy(thisword, tempword);
141 }
142
143 return word;
144 }
145
146 // parse_sentence: pass compareto as NULL to parse the sentence, or
147 // compareto as non-null to check if it matches the passed sentence
parse_sentence(const char * src_text,int * numwords,short * wordarray,short * compareto,int comparetonum)148 int parse_sentence(const char *src_text, int *numwords, short *wordarray, short *compareto, int comparetonum) {
149 char thisword[150] = "\0";
150 int i = 0, comparing = 0;
151 int8 in_optional = 0, do_word_now = 0;
152 int optional_start = 0;
153
154 numwords[0] = 0;
155 if (compareto == nullptr)
156 _GP(play).bad_parsed_word[0] = 0;
157
158 String uniform_text = src_text;
159 uniform_text.MakeLower();
160 const char *text = uniform_text.GetCStr();
161 while (1) {
162 if ((compareto != nullptr) && (compareto[comparing] == RESTOFLINE))
163 return 1;
164
165 if ((text[0] == ']') && (compareto != nullptr)) {
166 if (!in_optional)
167 quitprintf("!Said: unexpected ']'\nText: %s", src_text);
168 do_word_now = 1;
169 }
170
171 if (is_valid_word_char(text[0])) {
172 // Part of a word, add it on
173 thisword[i] = text[0];
174 i++;
175 } else if ((text[0] == '[') && (compareto != nullptr)) {
176 if (in_optional)
177 quitprintf("!Said: nested optional words\nText: %s", src_text);
178
179 in_optional = 1;
180 optional_start = comparing;
181 } else if ((thisword[0] != 0) || ((text[0] == 0) && (i > 0)) || (do_word_now == 1)) {
182 // End of word, so process it
183 thisword[i] = 0;
184 i = 0;
185 int word = -1;
186
187 if (text[0] == ' ') {
188 word = FindMatchingMultiWordWord(thisword, &text);
189 }
190
191 if (word < 0) {
192 // just a normal word
193 word = find_word_in_dictionary(thisword);
194 }
195
196 // "look rol"
197 if (word == RESTOFLINE)
198 return 1;
199 if (compareto) {
200 // check string is longer than user input
201 if (comparing >= comparetonum) {
202 if (in_optional) {
203 // eg. "exit [door]" - there's no more user input
204 // but the optional word is still there
205 if (do_word_now) {
206 in_optional = 0;
207 do_word_now = 0;
208 }
209 thisword[0] = 0;
210 text++;
211 continue;
212 }
213 return 0;
214 }
215 if (word <= 0)
216 quitprintf("!Said: supplied word '%s' is not in dictionary or is an ignored word\nText: %s", thisword, src_text);
217 if (word == ANYWORD) {
218 } else if (word != compareto[comparing]) {
219 // words don't match - if a comma then a list of possibles,
220 // so allow retry
221 if (text[0] == ',')
222 comparing--;
223 else {
224 // words don't match
225 if (in_optional) {
226 // inside an optional clause, so skip it
227 while (text[0] != ']') {
228 if (text[0] == 0)
229 quitprintf("!Said: unterminated [optional]\nText: %s", src_text);
230 text++;
231 }
232 // -1 because it's about to be ++'d again
233 comparing = optional_start - 1;
234 }
235 // words don't match outside an optional clause, abort
236 else
237 return 0;
238 }
239 } else if (text[0] == ',') {
240 // this alternative matched, but there are more
241 // so skip the other alternatives
242 int continueSearching = 1;
243 while (continueSearching) {
244
245 const char *textStart = &text[1];
246
247 while ((text[0] == ',') || (Common::isAlnum((unsigned char)text[0]) != 0))
248 text++;
249
250 continueSearching = 0;
251
252 if (text[0] == ' ') {
253 strcpy(thisword, textStart);
254 thisword[text - textStart] = 0;
255 // forward past any multi-word alternatives
256 if (FindMatchingMultiWordWord(thisword, &text) >= 0)
257 continueSearching = 1;
258 }
259 }
260
261 if ((text[0] == ']') && (in_optional)) {
262 // [go,move] we just matched "go", so skip over "move"
263 in_optional = 0;
264 text++;
265 }
266
267 // go back cos it'll be ++'d in a minute
268 text--;
269 }
270 comparing++;
271 } else if (word != 0) {
272 // it's not an ignore word (it's a known word, or an unknown
273 // word, so save its index)
274 wordarray[numwords[0]] = word;
275 numwords[0]++;
276 if (numwords[0] >= MAX_PARSED_WORDS)
277 return 0;
278 // if it's an unknown word, store it for use in messages like
279 // "you can't use the word 'xxx' in this game"
280 if ((word < 0) && (_GP(play).bad_parsed_word[0] == 0))
281 strcpy(_GP(play).bad_parsed_word, thisword);
282 }
283
284 if (do_word_now) {
285 in_optional = 0;
286 do_word_now = 0;
287 }
288
289 thisword[0] = 0;
290 }
291 if (text[0] == 0)
292 break;
293 text++;
294 }
295 // If the user input is longer than the Said string, it's wrong
296 // eg Said("look door") and they type "look door jibble"
297 // rol should be used instead to enable this
298 if (comparing < comparetonum)
299 return 0;
300 return 1;
301 }
302
303 //=============================================================================
304 //
305 // Script API Functions
306 //
307 //=============================================================================
308
309 // int (const char *wordToFind)
Sc_Parser_FindWordID(const RuntimeScriptValue * params,int32_t param_count)310 RuntimeScriptValue Sc_Parser_FindWordID(const RuntimeScriptValue *params, int32_t param_count) {
311 API_SCALL_INT_POBJ(Parser_FindWordID, const char);
312 }
313
314 // void (char*text)
Sc_ParseText(const RuntimeScriptValue * params,int32_t param_count)315 RuntimeScriptValue Sc_ParseText(const RuntimeScriptValue *params, int32_t param_count) {
316 API_SCALL_VOID_POBJ(ParseText, /*const*/ char);
317 }
318
319 // const char* ()
Sc_Parser_SaidUnknownWord(const RuntimeScriptValue * params,int32_t param_count)320 RuntimeScriptValue Sc_Parser_SaidUnknownWord(const RuntimeScriptValue *params, int32_t param_count) {
321 API_CONST_SCALL_OBJ(const char, _GP(myScriptStringImpl), Parser_SaidUnknownWord);
322 }
323
324 // int (char*checkwords)
Sc_Said(const RuntimeScriptValue * params,int32_t param_count)325 RuntimeScriptValue Sc_Said(const RuntimeScriptValue *params, int32_t param_count) {
326 API_SCALL_INT_POBJ(Said, /*const*/ char);
327 }
328
329
RegisterParserAPI()330 void RegisterParserAPI() {
331 ccAddExternalStaticFunction("Parser::FindWordID^1", Sc_Parser_FindWordID);
332 ccAddExternalStaticFunction("Parser::ParseText^1", Sc_ParseText);
333 ccAddExternalStaticFunction("Parser::SaidUnknownWord^0", Sc_Parser_SaidUnknownWord);
334 ccAddExternalStaticFunction("Parser::Said^1", Sc_Said);
335 }
336
337 } // namespace AGS3
338