View Javadoc

1   /*
2    * $Id: VariableInstruction.java,v 1.24 2005/11/03 00:52:47 weiju Exp $
3    * 
4    * Created on 03.10.2005
5    * Copyright 2005 by Wei-ju Wu
6    *
7    * This file is part of The Z-machine Preservation Project (ZMPP).
8    *
9    * ZMPP is free software; you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as published by
11   * the Free Software Foundation; either version 2 of the License, or
12   * (at your option) any later version.
13   *
14   * ZMPP is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   *
19   * You should have received a copy of the GNU General Public License
20   * along with ZMPP; if not, write to the Free Software
21   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
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; // Versions 1-3
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; // Versions 1-4
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       // Sets the number of arguments
172       int numArgs = getNumOperands() - 1;
173       routineContext.setNumArguments(numArgs);
174     
175       // Save return parameters
176       routineContext.setReturnAddress(getMachine().getProgramCounter()
177                                       + getLength());
178       routineContext.setReturnVariable(getStoreVariable());
179       
180       
181       // Set call parameters into the local variables
182       // if there are more parameters than local variables,
183       // those are thrown away
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       // save invocation stack pointer
193       routineContext.setInvocationStackPointer(
194           getMachine().getStackPointer());
195       
196       // Pushes the routine context onto the routine stack
197       getMachine().pushRoutineContext(routineContext);
198 
199       // Jump to the address
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     // standard 1.1
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     // Stream number should be a signed byte
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     // Write the number of tokens in byte 1 of the parse buffer
367     int maxwords = memaccess.readUnsignedByte(parsebuffer);
368     //System.out.println("maxwords: " + maxwords);
369     
370     // Do not go beyond the limit of maxwords
371     int numParsedTokens = Math.min(maxwords, tokens.size());
372     
373     // Write the number of parsed tokens into byte 1 of the parse buffer
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; // because of the length byte
401       
402       // write out the entry to the parse buffer
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     // read input from text buffer
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     // Retrieve the defined separators
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     // The tokenizer will also return the delimiters
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 }