aboutsummaryrefslogtreecommitdiff
path: root/engines/director/lingo/lingo-preprocessor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/director/lingo/lingo-preprocessor.cpp')
-rw-r--r--engines/director/lingo/lingo-preprocessor.cpp333
1 files changed, 333 insertions, 0 deletions
diff --git a/engines/director/lingo/lingo-preprocessor.cpp b/engines/director/lingo/lingo-preprocessor.cpp
new file mode 100644
index 0000000000..c7cc491b3d
--- /dev/null
+++ b/engines/director/lingo/lingo-preprocessor.cpp
@@ -0,0 +1,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