View Javadoc

1   /*
2    * $Id: Machine3.java,v 1.41 2005/11/03 19:06:29 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.List;
27  
28  import org.zmpp.base.MemoryAccess;
29  import org.zmpp.vmutil.PredictableRandomGenerator;
30  import org.zmpp.vmutil.RandomGenerator;
31  import org.zmpp.vmutil.UnpredictableRandomGenerator;
32  import org.zmpp.vmutil.ZsciiConverter;
33  import org.zmpp.vmutil.ZsciiString;
34  import org.zmpp.vmutil.ZsciiConverter.Alphabet;
35  
36  /***
37   * This class implements the state and some services of a Z-machine, version 3.
38   * 
39   * @author Wei-ju Wu
40   * @version 1.0
41   */
42  public class Machine3 implements Machine {
43  
44    /***
45     * The memory access object.
46     */
47    private MemoryAccess memaccess;
48    
49    /***
50     * This machine's current program counter.
51     */
52    private int programCounter;
53    
54    /***
55     * This machine's global stack.
56     */
57    private List<Short> stack;  
58    
59    /***
60     * The routine info.
61     */
62    private List<RoutineContext> routineContextStack;
63    
64    /***
65     * The start of global variables.
66     */
67    private int globalsAddress;
68    
69    /***
70     * The object tree.
71     */
72    private ObjectTree objectTree;
73    
74    /***
75     * The dictionary.
76     */
77    private Dictionary dictionary;
78    
79    /***
80     * This is the array of output streams.
81     */
82    private OutputStream[] outputStream;
83    
84    /***
85     * This is the array of input streams.
86     */
87    private InputStream[] inputStream;
88    
89    /***
90     * The selected input stream.
91     */
92    private int selectedInputStreamIndex;
93    
94    /***
95     * This flag indicates the run status.
96     */
97    private boolean running;
98    
99    /***
100    * The random generator.
101    */
102   private RandomGenerator random;
103   
104   /***
105    * The instruction decoder.
106    */
107   private InstructionDecoder decoder;
108   
109   /***
110    * The status line.
111    */
112   private StatusLine statusLine;
113   
114   /***
115    * Returns the checksum verification status of the story file.
116    */
117   private boolean hasValidChecksum;
118   
119   /***
120    * The file header.
121    */
122   private StoryFileHeader fileHeader;
123   
124   /***
125    * Constructor.
126    */
127   public Machine3() {
128 
129   }
130   
131   /***
132    * {@inheritDoc}
133    */
134   public void initialize(MemoryAccess memaccess, StoryFileHeader fileheader) {
135     
136     this.memaccess = memaccess;
137     this.fileHeader = fileheader;
138     this.stack = new ArrayList<Short>();
139     this.routineContextStack = new ArrayList<RoutineContext>();
140     this.programCounter = fileheader.getProgramStart();
141     this.globalsAddress = fileheader.getGlobalsAddress();
142     this.objectTree =
143       new Objects(memaccess, fileheader.getObjectTableAddress());
144     this.outputStream = new OutputStream[3];
145     this.inputStream = new InputStream[2];
146     this.selectedInputStreamIndex = 1;
147     this.random = new UnpredictableRandomGenerator();
148     this.dictionary = new DefaultDictionary(memaccess,
149         fileheader.getDictionaryAddress());
150     this.decoder = new InstructionDecoder(this, memaccess);
151     this.running = true;
152     int checksum = calculateChecksum(fileheader);
153     hasValidChecksum = fileheader.getChecksum() == checksum;
154   }
155   
156   /***
157    * {@inheritDoc}
158    */
159   public StoryFileHeader getStoryFileHeader() {
160     
161     return fileHeader;
162   }
163     
164   /***
165    * Calculates the checksum of the file.
166    * 
167    * @param fileheader the file header
168    * @return the check sum
169    */
170   private int calculateChecksum(StoryFileHeader fileheader) {
171     
172     int filelen = fileheader.getFileLength();
173     int sum = 0;
174     
175     for (int i = 0x40; i < filelen; i++) {
176     
177       sum += memaccess.readUnsignedByte(i);
178     }
179     return (sum & 0xffff);
180   }
181   
182   /***
183    * {@inheritDoc}
184    */
185   public boolean hasValidChecksum() {
186     
187     return this.hasValidChecksum;
188   }
189   
190   /***
191    * {@inheritDoc}
192    */
193   public MemoryAccess getMemoryAccess() {
194     
195     return memaccess;
196   }
197   
198   /***
199    * {@inheritDoc}
200    */
201   public Dictionary getDictionary() {
202     
203     return dictionary;
204   }
205   
206   public ObjectTree getObjectTree() {
207     
208     return objectTree;
209   }
210   
211   /***
212    * Returns the global stack pointer. This function is mainly exposed for
213    * testing purposes.
214    * 
215    * @return the stack pointer
216    */
217   public int getStackPointer() {
218     
219     return stack.size();
220   }
221   
222   /***
223    * {@inheritDoc}
224    */
225   public void setStackPointer(int stackpointer) {
226 
227     // remove the last diff elements
228     int diff = getStackPointer() - stackpointer;
229     for (int i = 0; i < diff; i++) {
230      
231       stack.remove(stack.size() - 1);
232     }
233   }
234   
235   /***
236    * {@inheritDoc}
237    */
238   public short getStackTopElement() {
239     
240     if (stack.size() > 0) {
241       
242       return stack.get(stack.size() - 1);
243     }
244     return -1;
245   }
246   
247   public void setStackTopElement(short value) {
248     
249     stack.set(stack.size() - 1, value);
250   }
251   
252   /***
253    * {@inheritDoc}
254    */
255   public int getProgramCounter() {
256     
257     return programCounter;
258   }
259 
260   /***
261    * {@inheritDoc}
262    */
263   public void setProgramCounter(int address) {
264 
265     this.programCounter = address;
266   }
267 
268   /***
269    * {@inheritDoc}
270    */
271   public short getVariable(int variableNumber) {
272 
273     VariableType varType = getVariableType(variableNumber);
274     if (varType == VariableType.STACK) {
275       
276       if (stack.size() == 0) {
277         
278         throw new IllegalStateException("stack is empty");
279         
280       } else {
281    
282         return stack.remove(stack.size() - 1);
283       }
284       
285     } else if (varType == VariableType.LOCAL) {
286       
287       int localVarNumber = getLocalVariableNumber(variableNumber);
288       checkLocalVariableAccess(localVarNumber);
289       return getCurrentRoutineContext().getLocalVariable(localVarNumber);
290       
291     } else { // GLOBAL
292       
293       return memaccess.readShort(globalsAddress
294           + (getGlobalVariableNumber(variableNumber) * 2));
295     }
296   }
297 
298   /***
299    * {@inheritDoc}
300    */
301   public void setVariable(int variableNumber, short value) {
302 
303     VariableType varType = getVariableType(variableNumber);
304     if (varType == VariableType.STACK) {
305       
306       stack.add(value);
307       
308     } else if (varType == VariableType.LOCAL) {
309       
310       int localVarNumber = getLocalVariableNumber(variableNumber);
311       checkLocalVariableAccess(localVarNumber);
312       getCurrentRoutineContext().setLocalVariable(localVarNumber, value);
313       
314     } else {
315       
316       memaccess.writeShort(globalsAddress
317           + (getGlobalVariableNumber(variableNumber) * 2), value);
318     }
319   }
320 
321   /***
322    * {@inheritDoc}
323    */
324   public void pushRoutineContext(RoutineContext routineContext) {
325 
326     routineContext.setInvocationStackPointer(getStackPointer());
327     routineContextStack.add(routineContext);
328   }
329   
330   /***
331    * {@inheritDoc}
332    */
333   public void popRoutineContext(short returnValue) {
334     
335     if (routineContextStack.size() > 0) {
336 
337       RoutineContext popped =
338         routineContextStack.remove(routineContextStack.size() - 1);
339     
340       // Restore stack pointer and pc
341       setStackPointer(popped.getInvocationStackPointer());
342       programCounter = popped.getReturnAddress();
343       setVariable(popped.getReturnVariable(), returnValue);
344     } else {
345       
346       throw new IllegalStateException("no routine context active");
347     }
348   }
349 
350   /***
351    * {@inheritDoc}
352    */
353   public RoutineContext getCurrentRoutineContext() {
354     
355     if (routineContextStack.size() == 0) return null;
356     return routineContextStack.get(routineContextStack.size() - 1);
357   }
358   
359   public int getRoutineStackPointer() {
360     
361     return routineContextStack.size();
362   }
363   
364   /***
365    * Returns the variable type for the given variable number.
366    * 
367    * @param variableNumber the variable number
368    * @return STACK if stack variable, LOCAL if local variable, GLOBAL if global
369    */
370   public static VariableType getVariableType(int variableNumber) {
371     
372     if (variableNumber == 0) return VariableType.STACK;
373     else if (variableNumber < 0x10) return VariableType.LOCAL;
374     else return VariableType.GLOBAL;
375   }
376 
377   /***
378    * {@inheritDoc}
379    */
380   public short random(short range) {
381     
382     if (range < 0) {
383       
384       random = new PredictableRandomGenerator(-range);
385       return 0;
386       
387     } else if (range == 0) {
388       
389       random = new UnpredictableRandomGenerator();
390       return 0;
391     }
392     return (short) ((random.next() % range) + 1);
393   }
394 
395   // **********************************************************************
396   // ****** Streams
397   // ****************************
398   
399   /***
400    * {@inheritDoc}
401    */
402   public void setOutputStream(int streamnumber, OutputStream stream) {
403     
404     outputStream[streamnumber - 1] = stream;
405   }
406   
407   /***
408    * {@inheritDoc}
409    */
410   public void printZsciiString(int address) {
411     
412     print(new ZsciiString(memaccess, address).toString());
413   }
414   
415   /***
416    * {@inheritDoc}
417    */
418   public void print(String str) {
419     
420     for (int i = 0; i < outputStream.length; i++) {
421       
422       if (outputStream[i] != null && outputStream[i].isEnabled()) {
423       
424         outputStream[i].print(str);
425       }
426     }
427   }
428   
429   /***
430    * {@inheritDoc}
431    */
432   public void newline() {
433     
434     for (int i = 0; i < outputStream.length; i++) {
435       
436       if (outputStream[i] != null && outputStream[i].isEnabled()) {
437       
438         outputStream[i].newline();
439       }
440     }
441   }
442   
443   /***
444    * {@inheritDoc}
445    */
446   public void printZchar(short zchar) {
447     
448     String str = String.valueOf(ZsciiConverter.decode(Alphabet.A0, zchar));
449     print(str);
450   }
451   
452   /***
453    * {@inheritDoc}
454    */
455   public void printNumber(short number) {
456     
457     print(String.valueOf(number));
458   }
459   
460   /***
461    * {@inheritDoc}
462    */
463   public void enableOutputStream(int streamnumber, boolean flag) {
464     
465     outputStream[streamnumber - 1].setEnabled(flag);
466   }
467   
468   /***
469    * {@inheritDoc}
470    */
471   public void setInputStream(int streamnumber, InputStream stream) {
472     
473     inputStream[streamnumber] = stream;
474   }
475   
476   /***
477    * {@inheritDoc}
478    */
479   public void selectInputStream(int streamnumber) {
480     
481     selectedInputStreamIndex = streamnumber;
482   }
483   
484   /***
485    * {@inheritDoc}
486    */
487   public void readLine(int address, int bufferlen) {
488     
489     inputStream[selectedInputStreamIndex].readLine(memaccess, address,
490         bufferlen);
491   }
492   
493   /***
494    * {@inheritDoc} 
495    */
496   public int translatePackedAddress(int packedAddress) {
497     
498     return packedAddress * 2;
499   }
500 
501   // ************************************************************************
502   // ****** Control functions
503   // ************************************************
504 
505   public void updateStatusLine() {
506   
507     if (statusLine != null) {
508       
509       int objNum = getVariable(0x10);    
510       ZObject obj = getObjectTree().getObject(objNum);      
511       String objectName = (new ZsciiString(getMemoryAccess(),
512             obj.getPropertiesDescriptionAddress())).toString();
513       
514       int score = getVariable(0x11);
515       int steps = getVariable(0x12);
516       statusLine.updateStatus(objectName, score, steps);
517     }
518   }
519   
520   /***
521    * {@inheritDoc}
522    */
523   public void setStatusLine(StatusLine statusLine) {
524     
525     this.statusLine = statusLine;
526   }
527   
528   /***
529    * {@inheritDoc}
530    */
531   public void halt(String errormsg) {
532   
533     print(errormsg);
534     running = false;
535   }
536   
537   /***
538    * {@inheritDoc} 
539    */
540   public boolean save() {
541     
542     print("the save function is not implemented yet.");
543     return false;
544   }
545     
546   /***
547    * {@inheritDoc} 
548    */
549   public boolean restore() {
550     
551     print("the restore function is not implemented yet.");
552     return false;
553   }
554   
555   /***
556    * {@inheritDoc} 
557    */
558   public void restart() {
559     
560     print("the restart function is not implemented yet.");    
561   }
562   
563   /***
564    * {@inheritDoc} 
565    */
566   public void quit() {
567     
568     running = false;
569   }
570   
571   /***
572    * {@inheritDoc}
573    */
574   public boolean isRunning() {
575     
576     return running;
577   }
578   
579   /***
580    * {@inheritDoc}
581    */
582   public void start() {
583     
584     running = true;
585   }
586   
587   public Instruction nextStep() {
588     
589     Instruction instruction = decoder.decodeInstruction(getProgramCounter());
590     return instruction;
591   }
592   
593   // ************************************************************************
594   // ****** Private functions
595   // ************************************************
596   /***
597    * Returns the local variable number for a specified variable number.
598    * 
599    * @param variableNumber the variable number in an operand (0x01-0x0f)
600    * @return the local variable number
601    */
602   private int getLocalVariableNumber(int variableNumber) {
603     
604     return variableNumber - 1;
605   }
606   
607   /***
608    * Returns the global variable for the specified variable number.
609    * 
610    * @param variableNumber a variable number (0x10-0xff)
611    * @return the global variable number
612    */
613   private int getGlobalVariableNumber(int variableNumber) {
614     
615     return variableNumber - 0x10;
616   }
617   
618   /***
619    * This function throws an exception if a non-existing local variable
620    * is accessed on the current routine context or no current routine context
621    * is set.
622    * 
623    * @param localVariableNumber the local variable number
624    */
625   private void checkLocalVariableAccess(int localVariableNumber) {
626     
627     if (routineContextStack.size() == 0) {
628       
629       throw new IllegalStateException("no routine context set");
630     }
631     
632     if (localVariableNumber >= getCurrentRoutineContext().getNumLocalVariables()) {
633       
634       throw new IllegalStateException("access to non-existent local variable: "
635                                       + localVariableNumber);
636     }
637   }
638 }