1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package org.zmpp.vm;
24
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.StringTokenizer;
30
31 import org.zmpp.base.MemoryAccess;
32 import org.zmpp.base.MemoryReadAccess;
33 import org.zmpp.vmutil.ZsciiConverter;
34 import org.zmpp.vmutil.ZsciiConverter.Alphabet;
35
36
37 /***
38 * This class represents instructions of type VARIABLE.
39 *
40 * @author Wei-ju Wu
41 * @version 1.0
42 */
43 public class VariableInstruction extends AbstractInstruction {
44
45 /***
46 * List of opcodes. See Z-Machine Standards document 1.0 for
47 * explanations.
48 */
49 public static final int OP_CALL = 0x00;
50 public static final int OP_STOREW = 0x01;
51 public static final int OP_STOREB = 0x02;
52 public static final int OP_PUT_PROP = 0x03;
53 public static final int OP_SREAD = 0x04;
54 public static final int OP_PRINT_CHAR = 0x05;
55 public static final int OP_PRINT_NUM = 0x06;
56 public static final int OP_RANDOM = 0x07;
57 public static final int OP_PUSH = 0x08;
58 public static final int OP_PULL = 0x09;
59 public static final int OP_SPLIT_WINDOW = 0x0a;
60 public static final int OP_SET_WINDOW = 0x0b;
61 public static final int OP_OUTPUTSTREAM = 0x13;
62 public static final int OP_INPUTSTREAM = 0x14;
63 public static final int OP_SOUND_EFFECT = 0x15;
64
65 /***
66 * The operand count.
67 */
68 private OperandCount operandCount;
69
70 /***
71 * Constructor.
72 *
73 * @param machineState a reference to a MachineState object
74 * @param operandCount the operand count
75 * @param opcode the instruction's opcode
76 */
77 public VariableInstruction(Machine machineState,
78 OperandCount operandCount, int opcode) {
79
80 super(machineState, opcode);
81 this.operandCount = operandCount;
82 }
83
84 /***
85 * {@inheritDoc}
86 */
87 public InstructionForm getInstructionForm() {
88
89 return InstructionForm.VARIABLE;
90 }
91
92 /***
93 * {@inheritDoc}
94 */
95 public OperandCount getOperandCount() {
96
97 return operandCount;
98 }
99
100 /***
101 * {@inheritDoc}
102 */
103 public boolean storesResult() {
104
105 switch (getOpcode()) {
106
107 case OP_CALL:
108 case OP_RANDOM:
109 return true;
110 default:
111 return false;
112 }
113 }
114
115 /***
116 * {@inheritDoc}
117 */
118 public void execute() {
119
120 switch (getOpcode()) {
121
122 case OP_CALL:
123 call();
124 break;
125 case OP_STOREW:
126 storew();
127 break;
128 case OP_STOREB:
129 storeb();
130 break;
131 case OP_PUT_PROP:
132 putProp();
133 break;
134 case OP_SREAD:
135 sread();
136 break;
137 case OP_PRINT_CHAR:
138 printChar();
139 break;
140 case OP_PRINT_NUM:
141 printNum();
142 break;
143 case OP_RANDOM:
144 random();
145 break;
146 case OP_PUSH:
147 push();
148 break;
149 case OP_PULL:
150 pull();
151 break;
152 case OP_OUTPUTSTREAM:
153 outputstream();
154 break;
155 case OP_INPUTSTREAM:
156 inputstream();
157 break;
158 default:
159 throwInvalidOpcode();
160 }
161 }
162
163 private void call() {
164
165 int packedAddress = getUnsignedValue(0);
166 int routineAddress = getMachine().translatePackedAddress(packedAddress);
167
168 if (routineAddress != 0) {
169 RoutineContext routineContext = decodeRoutine(routineAddress);
170
171
172 int numArgs = getNumOperands() - 1;
173 routineContext.setNumArguments(numArgs);
174
175
176 routineContext.setReturnAddress(getMachine().getProgramCounter()
177 + getLength());
178 routineContext.setReturnVariable(getStoreVariable());
179
180
181
182
183
184 int numToCopy = Math.min(routineContext.getNumLocalVariables(),
185 numArgs);
186
187 for (int i = 0; i < numToCopy; i++) {
188
189 routineContext.setLocalVariable(i, getValue(i + 1));
190 }
191
192
193 routineContext.setInvocationStackPointer(
194 getMachine().getStackPointer());
195
196
197 getMachine().pushRoutineContext(routineContext);
198
199
200 getMachine().setProgramCounter(routineContext.getStartAddress());
201
202 } else {
203
204 storeResult(FALSE);
205 nextInstruction();
206 }
207 }
208
209 /***
210 * Decodes the routine at the specified address.
211 *
212 * @param routineAddress the routine address
213 * @return a RoutineContext object
214 */
215 private RoutineContext decodeRoutine(int routineAddress) {
216
217 MemoryReadAccess memaccess = getMachine().getMemoryAccess();
218 int numLocals = memaccess.readUnsignedByte(routineAddress);
219 short[] locals = new short[numLocals];
220 int currentAddress = routineAddress + 1;
221 for (int i = 0; i < numLocals; i++) {
222
223 locals[i] = memaccess.readShort(currentAddress);
224 currentAddress += 2;
225 }
226
227 RoutineContext info = new RoutineContext(currentAddress, numLocals);
228
229 for (int i = 0; i < numLocals; i++) {
230
231 info.setLocalVariable(i, locals[i]);
232 }
233 return info;
234 }
235
236 private void storew() {
237
238 MemoryAccess memaccess = getMachine().getMemoryAccess();
239 int array = getUnsignedValue(0);
240 int wordIndex = getUnsignedValue(1);
241 short value = getValue(2);
242
243 memaccess.writeShort(array + wordIndex * 2, value);
244 nextInstruction();
245 }
246
247 private void storeb() {
248
249 MemoryAccess memaccess = getMachine().getMemoryAccess();
250 int array = getUnsignedValue(0);
251 int byteIndex = getUnsignedValue(1);
252 byte value = (byte) getValue(2);
253
254 memaccess.writeByte(array + byteIndex, value);
255 nextInstruction();
256 }
257
258 private void putProp() {
259
260 int obj = getUnsignedValue(0);
261 int property = getUnsignedValue(1);
262 short value = getValue(2);
263 ZObject object = getMachine().getObjectTree().getObject(obj);
264
265 if (object.isPropertyAvailable(property)) {
266
267 if (object.getPropertySize(property) == 1) {
268
269 object.setPropertyByte(property, 0, (byte) (value & 0xff));
270
271 } else {
272
273 object.setPropertyByte(property, 0, (byte) ((value >> 8) & 0xff));
274 object.setPropertyByte(property, 1, (byte) (value & 0xff));
275 }
276 nextInstruction();
277
278 } else {
279
280 getMachine().halt("put_prop: the property [" + property
281 + "] of object [" + obj + "] does not exist");
282 }
283 }
284
285 private void printChar() {
286
287 short zchar = getValue(0);
288 getMachine().printZchar(zchar);
289 nextInstruction();
290 }
291
292 private void printNum() {
293
294 short number = getValue(0);
295 getMachine().printNumber(number);
296 nextInstruction();
297 }
298
299 private void push() {
300
301 short value = getValue(0);
302 getMachine().setVariable(0, value);
303 nextInstruction();
304 }
305
306 private void pull() {
307
308 int varnum = getUnsignedValue(0);
309 short value = getMachine().getVariable(0);
310
311
312 if (varnum == 0) {
313
314 getMachine().setStackTopElement(value);
315
316 } else {
317
318 getMachine().setVariable(varnum, value);
319 }
320 nextInstruction();
321 }
322
323 private void outputstream() {
324
325
326 short streamnumber = getValue(0);
327
328 if (streamnumber < 0) {
329
330 getMachine().enableOutputStream(-streamnumber, false);
331
332 } else if (streamnumber > 0) {
333
334 getMachine().enableOutputStream(streamnumber, true);
335 }
336 nextInstruction();
337 }
338
339 private void inputstream() {
340
341 getMachine().selectInputStream(getUnsignedValue(0));
342 nextInstruction();
343 }
344
345 private void random() {
346
347 short range = getValue(0);
348 storeResult(getMachine().random(range));
349 nextInstruction();
350 }
351
352 private void sread() {
353
354 getMachine().updateStatusLine();
355 MemoryAccess memaccess = getMachine().getMemoryAccess();
356 int textbuffer = getUnsignedValue(0);
357 int parsebuffer = getUnsignedValue(1);
358 int bufferlen = memaccess.readUnsignedByte(textbuffer);
359 getMachine().readLine(textbuffer + 1, bufferlen);
360
361 String input = bufferToString(textbuffer + 1, bufferlen);
362 List<String> tokens = tokenize(input);
363
364 Map<String, Integer> parsedTokens = new HashMap<String, Integer>();
365
366
367 int maxwords = memaccess.readUnsignedByte(parsebuffer);
368
369
370
371 int numParsedTokens = Math.min(maxwords, tokens.size());
372
373
374 memaccess.writeUnsignedByte(parsebuffer + 1, (short) numParsedTokens);
375
376 int parseaddr = parsebuffer + 2;
377
378 for (int i = 0; i < numParsedTokens; i++) {
379
380 String token = tokens.get(i);
381 int entryAddress = getMachine().getDictionary().lookup(token);
382
383 int startIndex = 0;
384 if (parsedTokens.containsKey(token)) {
385
386 int timesContained = parsedTokens.get(token);
387 parsedTokens.put(token, timesContained + 1);
388
389 for (int j = 0; j < timesContained; j++) {
390
391 int found = input.indexOf(token, startIndex);
392 startIndex = found + token.length();
393 }
394
395 } else {
396
397 parsedTokens.put(token, 1);
398 }
399 int tokenIndex = input.indexOf(token, startIndex);
400 tokenIndex = tokenIndex + 1;
401
402
403 memaccess.writeUnsignedShort(parseaddr, entryAddress);
404 memaccess.writeUnsignedByte(parseaddr + 2, (short) token.length());
405 memaccess.writeUnsignedByte(parseaddr + 3, (short) tokenIndex);
406 parseaddr += 4;
407 }
408
409 nextInstruction();
410 }
411
412 /***
413 * Turns the buffer into a Java string. This function reads at most
414 * |bufferlen| bytes and treats each byte as an ASCII character.
415 * The characters will be concatenated to the result string.
416 *
417 * @param address the buffer address
418 * @param bufferlen the buffer length
419 * @return the string contained in the buffer
420 */
421 private String bufferToString(int address, int bufferlen) {
422
423 MemoryAccess memaccess = getMachine().getMemoryAccess();
424
425
426 StringBuilder buffer = new StringBuilder();
427 for (int i = 0; i < bufferlen; i++) {
428
429 short charByte = memaccess.readUnsignedByte(address + i);
430 if (charByte == 0) break;
431 buffer.append((char) charByte);
432 }
433
434 return buffer.toString().toLowerCase();
435 }
436
437 /***
438 * Turns the specified input string into tokens. It will take whitespace
439 * implicitly and dictionary separators explicitly to tokenize the
440 * stream, dictionary specified separators are included in the result list.
441 *
442 * @param input the input string
443 * @return the tokens
444 */
445 private List<String> tokenize(String input) {
446
447 List<String> result = new ArrayList<String>();
448 String whitespace = " \n\t\r\f";
449
450
451 StringBuilder separators = new StringBuilder();
452 Dictionary dictionary = getMachine().getDictionary();
453 for (int i = 0; i < dictionary.getNumberOfSeparators(); i++) {
454
455 byte delim = dictionary.getSeparator(i);
456 separators.append(ZsciiConverter.decode(Alphabet.A0, delim));
457 }
458
459
460 String delim = whitespace + separators.toString();
461 StringTokenizer tok = new StringTokenizer(input, delim, true);
462
463 while (tok.hasMoreTokens()) {
464
465 String token = tok.nextToken();
466 if (!Character.isWhitespace(token.charAt(0))) {
467
468 result.add(token);
469 }
470 }
471 return result;
472 }
473 }