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 "common/textconsole.h"
24 
25 #include "parallaction/parallaction.h"
26 #include "parallaction/parser.h"
27 
28 
29 namespace Parallaction {
30 
31 #define MAX_TOKENS	50
32 
33 int				_numTokens;
34 char			_tokens[MAX_TOKENS][MAX_TOKEN_LEN];
35 
Script(Common::ReadStream * input,bool disposeSource)36 Script::Script(Common::ReadStream *input, bool disposeSource) : _input(input), _disposeSource(disposeSource), _line(0) {}
37 
~Script()38 Script::~Script() {
39 	if (_disposeSource)
40 		delete _input;
41 }
42 
43 /*
44  * readLineIntern read a text line and prepares it for
45  * parsing, by stripping the leading whitespace and
46  * changing tabs to spaces. It will stop on a CR, LF, or
47  * SUB (0x1A), which may all occur at the end of a script
48  * line.
49  * Returns an empty string (length = 0) when a line
50  * has no printable text in it.
51  */
readLineIntern(char * buf,size_t bufSize)52 char *Script::readLineIntern(char *buf, size_t bufSize) {
53 	uint i = 0;
54 	for ( ; i < bufSize; ) {
55 		char c = _input->readSByte();
56 		if (_input->eos())
57 			break;
58 		// break if EOL
59 		if (c == '\n' || c == '\r' || c == (char)0x1A)
60 			break;
61 		if (c == '\t')
62 			c = ' ';
63 
64 		if ((c != ' ') || (i > 0)) {
65 			buf[i] = c;
66 			i++;
67 		}
68 	}
69 	_line++;
70 	if (i == bufSize) {
71 		warning("overflow in readLineIntern (line %i)", _line);
72 	}
73 	if (i == 0 && _input->eos()) {
74 		return 0;
75 	}
76 	buf[i] = '\0';
77 	return buf;
78 }
79 
isCommentLine(char * text)80 bool isCommentLine(char *text) {
81 	return text[0] == '#';
82 }
83 
isStartOfCommentBlock(char * text)84 bool isStartOfCommentBlock(char *text) {
85 	return (text[0] == '[');
86 }
87 
isEndOfCommentBlock(char * text)88 bool isEndOfCommentBlock(char *text) {
89 	return (text[0] == ']');
90 }
91 
readLine(char * buf,size_t bufSize)92 char *Script::readLine(char *buf, size_t bufSize) {
93 	bool inBlockComment = false;
94 	bool ignoreLine = true;
95 
96 	char *line = 0;
97 	do {
98 		line = readLineIntern(buf, bufSize);
99 		if (line == 0) {
100 			return 0;
101 		}
102 
103 		if (line[0] == '\0')
104 			continue;
105 
106 		ignoreLine = false;
107 
108 		line = Common::ltrim(line);
109 		if (isCommentLine(line)) {
110 			// ignore this line
111 			ignoreLine = true;
112 		} else
113 		if (isStartOfCommentBlock(line)) {
114 			// mark this and the following lines as comment
115 			inBlockComment = true;
116 		} else
117 		if (isEndOfCommentBlock(line)) {
118 			// comment is finished, so stop ignoring
119 			inBlockComment = false;
120 			// the current line must be skipped, though,
121 			// as it contains the end-of-comment marker
122 			ignoreLine = true;
123 		}
124 
125 	} while (inBlockComment || ignoreLine);
126 
127 	return line;
128 }
129 
130 
131 
clearTokens()132 void Script::clearTokens() {
133 	memset(_tokens, 0, sizeof(_tokens));
134 	_numTokens = 0;
135 }
136 
skip(const char * endToken)137 void Script::skip(const char* endToken) {
138 
139 	while (scumm_stricmp(_tokens[0], endToken)) {
140 		readLineToken(true);
141 	}
142 
143 }
144 
145 //
146 //	Scans 's' until one of the stop-chars in 'brk' is found, building a token.
147 //	If the routine encounters quotes, it will extract the contained text and
148 //  make a proper token. When scanning inside quotes, 'brk' is ignored and
149 //  only newlines are considered stop-chars.
150 //
151 //	The routine returns the unparsed portion of the input string 's'.
152 //
parseNextToken(char * s,char * tok,uint16 count,const char * brk)153 char *Script::parseNextToken(char *s, char *tok, uint16 count, const char *brk) {
154 
155 	enum STATES { NORMAL, QUOTED };
156 
157 	STATES state = NORMAL;
158 
159 	while (count > 0) {
160 
161 		switch (state) {
162 		case NORMAL:
163 			if (*s == '\0') {
164 				*tok = '\0';
165 				return s;
166 			} else
167 			if (strchr(brk, *s)) {
168 				*tok = '\0';
169 				return ++s;
170 			} else
171 			if (*s == '"') {
172 				state = QUOTED;
173 				s++;
174 			} else {
175 				*tok++ = *s++;
176 				count--;
177 			}
178 			break;
179 
180 		case QUOTED:
181 			if (*s == '\0') {
182 				*tok = '\0';
183 				return s;
184 			} else
185 			if (*s == '"') {
186 				*tok = '\0';
187 				return ++s;
188 			} else {
189 				*tok++ = *s++;
190 				count--;
191 			}
192 			break;
193 		}
194 
195 	}
196 
197 	*tok = '\0';
198 	// TODO: if execution flows here, make *REALLY* sure everything has been parsed
199 	// out of the input string. This is what is supposed to happen, but never ever
200 	// allocated time to properly check.
201 
202 	return tok;
203 
204 }
205 
readLineToken(bool errorOnEOF)206 uint16 Script::readLineToken(bool errorOnEOF) {
207 	char buf[200];
208 	char *line = readLine(buf, 200);
209 	if (!line) {
210 		if (errorOnEOF)
211 			error("unexpected end of file while parsing");
212 		else
213 			return 0;
214 	}
215 
216 	clearTokens();
217 	while (*line && _numTokens < MAX_TOKENS) {
218 		line = parseNextToken(line, _tokens[_numTokens], MAX_TOKEN_LEN, " ");
219 		line = Common::ltrim(line);
220 		_numTokens++;
221 	}
222 	return _numTokens;
223 }
224 
225 
reset()226 void Parser::reset() {
227 	_currentOpcodes = 0;
228 	_currentStatements = 0;
229 	_lookup = 0;
230 
231 	_statements.clear();
232 	_opcodes.clear();
233 }
234 
pushTables(OpcodeSet * opcodes,Table * statements)235 void Parser::pushTables(OpcodeSet *opcodes, Table *statements) {
236 	_opcodes.push(_currentOpcodes);
237 	_statements.push(_currentStatements);
238 
239 	_currentOpcodes = opcodes;
240 	_currentStatements = statements;
241 }
242 
popTables()243 void Parser::popTables() {
244 	assert(_opcodes.size() > 0);
245 
246 	_currentOpcodes = _opcodes.pop();
247 	_currentStatements = _statements.pop();
248 }
249 
parseStatement()250 void Parser::parseStatement() {
251 	assert(_currentOpcodes != 0);
252 
253 	_lookup = _currentStatements->lookup(_tokens[0]);
254 
255 	debugC(9, kDebugParser, "parseStatement: %s (lookup = %i)", _tokens[0], _lookup);
256 
257 	(*(*_currentOpcodes)[_lookup])();
258 }
259 
260 } // End of namespace Parallaction
261