aboutsummaryrefslogtreecommitdiff
path: root/engines/director/lingo/lingo-preprocessor.cpp
blob: c7cc491b3d35f2f2daa8a85b136fd4d01ab321e2 (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
/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "director/lingo/lingo.h"

namespace Director {

bool isspecial(char c) {
	return strchr("-+*/%%^:,()><&[]", c) != NULL;
}

static Common::String nexttok(const char *s, const char **newP = nullptr) {
	Common::String res;

	// Scan first non-whitespace
	while (*s && (*s == ' ' || *s == '\t')) // If we see a whitespace
		s++;

	if (Common::isAlnum(*s)) {
		// Now copy everything till whitespace
		while (*s && (Common::isAlnum(*s) || *s == '.'))
			res += *s++;
	} else {
		while (*s && isspecial(*s))
			res += *s++;
	}

	if (newP)
		*newP = s;

	return res;
}

static Common::String prevtok(const char *s, const char *lineStart, const char **newP = nullptr) {
	Common::String res;

	// Scan first non-whitespace
	while (s >= lineStart && (*s == ' ' || *s == '\t')) // If we see a whitespace
		s--;

	// Now copy everything till whitespace
	while (s >= lineStart && *s != ' ' && *s != '\t')
		res = *s-- + res;

	if (newP)
		*newP = s;

	return res;
}

Common::String Lingo::codePreprocessor(const char *s, bool simple) {
	Common::String res;

	// Strip comments
	while (*s) {
		if (*s == '-' && *(s + 1) == '-') { // At the end of the line we will have \0
			while (*s && *s != '\n')
				s++;
		}

		if (*s == '\r')
			res += '\n';
		else if (*s)
			res += *s;

		s++;
	}

	Common::String tmp(res);
	res.clear();

	// Strip trailing whitespaces
	s = tmp.c_str();
	while (*s) {
		if (*s == ' ' || *s == '\t') { // If we see a whitespace
			const char *ps = s; // Remember where we saw it

			while (*ps == ' ' || *ps == '\t')	// Scan until end of whitespaces
				ps++;

			if (*ps) {	// Not end of the string
				if (*ps == '\n') {	// If it is newline, then we continue from it
					s = ps;
				} else {	// It is not a newline
					while (s != ps) {	// Add all whitespaces
						res += *s;
						s++;
					}
				}
			}
		}

		if (*s)
			res += *s;

		s++;
	}

	if (simple)
		return res;

	tmp = res;
	s = tmp.c_str();
	res.clear();

	// Preprocess if statements
	// Here we add ' end if' at end of each statement, which lets us
	// make the grammar very straightforward
	Common::String line, tok, res1;
	const char *lineStart, *prevEnd;
	int iflevel = 0;

	while (*s) {
		line.clear();
		res1.clear();

		// Get next line
		while (*s && *s != '\n') { // If we see a whitespace
			if (*s == '\xc2') {
				res1 += *s++;
				if (*s == '\n') {
					line += ' ';
					res1 += *s++;
				}
			} else {
				res1 += *s;
				line += tolower(*s++);
			}
		}
		debugC(2, kDebugLingoParse, "line: %d                         '%s'", iflevel, line.c_str());

		res1 = preprocessReturn(res1);

		res += res1;

		if (line.size() < 4) { // If line is too small, then skip it
			if (*s)	// copy newline symbol
				res += *s++;

			debugC(2, kDebugLingoParse, "too small");

			continue;
		}

		tok = nexttok(line.c_str(), &lineStart);
		if (tok.equals("if")) {
			tok = prevtok(&line.c_str()[line.size() - 1], lineStart, &prevEnd);
			debugC(2, kDebugLingoParse, "start-if <%s>", tok.c_str());

			if (tok.equals("if")) {
				debugC(2, kDebugLingoParse, "end-if");
				tok = prevtok(prevEnd, lineStart);

				if (tok.equals("end")) {
					// do nothing, we open and close same line
					debugC(2, kDebugLingoParse, "end-end");
				} else {
					iflevel++;
				}
			} else if (tok.equals("then")) {
				debugC(2, kDebugLingoParse, "last-then");
				iflevel++;
			} else if (tok.equals("else")) {
				debugC(2, kDebugLingoParse, "last-else");
				iflevel++;
			} else { // other token
				// Now check if we have tNLELSE
				if (!*s) {
					iflevel++;	// end, we have to add 'end if'
					break;
				}
				const char *s1 = s + 1;

				while (*s1 && *s1 == '\n')
					s1++;
				tok = nexttok(s1);

				if (tok.equalsIgnoreCase("else")) { // ignore case because it is look-ahead
					debugC(2, kDebugLingoParse, "tNLELSE");
					iflevel++;
				} else {
					debugC(2, kDebugLingoParse, "++++ end if (no nlelse after single liner)");
					res += " end if";
				}
			}
		} else if (tok.equals("else")) {
			debugC(2, kDebugLingoParse, "start-else");
			bool elseif = false;

			tok = nexttok(lineStart);
			if (tok.equals("if")) {
				debugC(2, kDebugLingoParse, "second-if");
				elseif = true;
			} else if (tok.empty()) {
				debugC(2, kDebugLingoParse, "lonely-else");
				continue;
			}

			tok = prevtok(&line.c_str()[line.size() - 1], lineStart, &prevEnd);
			debugC(2, kDebugLingoParse, "last: '%s'", tok.c_str());

			if (tok.equals("if")) {
				debugC(2, kDebugLingoParse, "end-if");
				tok = prevtok(prevEnd, lineStart);

				if (tok.equals("end")) {
					debugC(2, kDebugLingoParse, "end-end");
					iflevel--;
				}
			} else if (tok.equals("then")) {
				debugC(2, kDebugLingoParse, "last-then");

				if (elseif == false) {
					warning("Badly nested then");
				}
			} else if (tok.equals("else")) {
				debugC(2, kDebugLingoParse, "last-else");
				if (elseif == false) {
					warning("Badly nested else");
				}
			} else { // check if we have tNLELSE
				if (!*s) {
					break;
				}
				const char *s1 = s + 1;

				while (*s1 && *s1 == '\n')
					s1++;
				tok = nexttok(s1);

				if (tok.equalsIgnoreCase("else") && elseif) {
					// Nothing to do here, same level
					debugC(2, kDebugLingoParse, "tNLELSE");
				} else {
					debugC(2, kDebugLingoParse, "++++ end if (no tNLELSE)");
					res += " end if";
					iflevel--;
				}
			}
		} else if (tok.equals("end")) {
			debugC(2, kDebugLingoParse, "start-end");

			tok = nexttok(lineStart);
			if (tok.equals("if")) {
				debugC(2, kDebugLingoParse, "second-if");
				iflevel--;
			}
		}
	}

	for (int i = 0; i < iflevel; i++) {
		debugC(2, kDebugLingoParse, "++++ end if (unclosed)");
		res += "\nend if";
	}


	debugC(2, kDebugLingoParse, "#############\n%s\n#############", res.c_str());

	return res;
}

#ifndef strcasestr
const char *strcasestr(const char *s, const char *find) {
	char c, sc;
	size_t len;

	if ((c = *find++) != 0) {
		c = (char)tolower((unsigned char)c);
		len = strlen(find);
		do {
			do {
				if ((sc = *s++) == 0)
					return (NULL);
			} while ((char)tolower((unsigned char)sc) != c);
		} while (scumm_strnicmp(s, find, len) != 0);
		s--;
	}
	return s;
}
#endif

Common::String Lingo::preprocessReturn(Common::String in) {
	Common::String res, prev, next;
	const char *ptr = in.c_str();
	const char *beg = ptr;

	while ((ptr = strcasestr(beg, "return")) != NULL) {
		res += Common::String(beg, ptr);

		if (ptr == beg)
			prev = "";
		else
			prev = prevtok(ptr - 1, beg);

		next = nexttok(ptr + 6); // end of 'return'

		if (prev.equals("&") || prev.equals("&&") || prev.equals("=") ||
				next.equals("&") || next.equals("&&")) {
			res += "scummvm_"; // Turn it into scummvm_return
		}

		res += *ptr++; // We advance one character, so 'eturn' is left
		beg = ptr;
	}

	res += Common::String(beg);

	if (in.size() != res.size())
		debugC(2, kDebugLingoParse, "RETURN: in: %s\nout: %s", in.c_str(), res.c_str());

	return res;
}

} // End of namespace Director