diff --git a/src/com/bytezone/diskbrowser/disk/AppleDisk.java b/src/com/bytezone/diskbrowser/disk/AppleDisk.java index a3015fd..6dc7667 100755 --- a/src/com/bytezone/diskbrowser/disk/AppleDisk.java +++ b/src/com/bytezone/diskbrowser/disk/AppleDisk.java @@ -623,6 +623,7 @@ public class AppleDisk implements Disk private int getBufferOffset (DiskAddress da) { assert sectorSize == SECTOR_SIZE; + return da.getTrack () * trackSize + interleaveSector[interleave][da.getSector ()] * SECTOR_SIZE; } @@ -630,6 +631,7 @@ public class AppleDisk implements Disk private int getBufferOffset (DiskAddress da, int seq) { assert sectorSize == BLOCK_SIZE; + assert seq == 0 || seq == 1; return da.getTrack () * trackSize diff --git a/src/com/bytezone/diskbrowser/infocom/CodeManager.java b/src/com/bytezone/diskbrowser/infocom/CodeManager.java index 1b75877..454bacb 100644 --- a/src/com/bytezone/diskbrowser/infocom/CodeManager.java +++ b/src/com/bytezone/diskbrowser/infocom/CodeManager.java @@ -60,10 +60,33 @@ class CodeManager extends AbstractFile return blocks; } - void addMissingRoutines () + void addRoutines (int programCounter) + { + addRoutine (programCounter - 1, 0); // + addActionRoutines (); // obtained from Grammar + addCodeRoutines (); // obtained from Object properties + addMissingRoutines (); // requires stringPtr to be set + + if (false) + { + int ptr = header.highMemory; + for (int key : routines.keySet ()) + { + if (ptr % 2 == 1) + ++ptr; + Routine routine = routines.get (key); + if (routine.startPtr > ptr) + System.out.printf ("skipped %d bytes%n", routine.startPtr - ptr); + System.out.println (routine); + ptr = routine.startPtr + routine.length; + } + } + } + + private void addMissingRoutines () { System.out.printf ("%nWalking the code block%n%n"); - int total = 0; + int total = routines.size (); int ptr = header.highMemory; while (ptr < header.stringPointer) @@ -71,7 +94,7 @@ class CodeManager extends AbstractFile if (ptr >= 0 && ptr % 2 == 1) // routine must start on a word boundary ptr++; - if (containsRoutineAt (ptr)) + if (routines.containsKey (ptr)) { ptr += getRoutine (ptr).length; continue; @@ -81,18 +104,20 @@ class CodeManager extends AbstractFile if (routine == null) { System.out.printf ("Invalid routine found : %05X%n", ptr); - ptr = findNextRoutine (ptr + 1); + int nextRoutinePtr = findNextRoutine (ptr + 1); + // System.out.println (Utility.getHex (buffer, ptr, nextRoutinePtr - ptr)); + ptr = nextRoutinePtr; System.out.printf ("skipping to %05X%n", ptr); if (ptr == 0) break; } else { - total++; ptr += routine.length; } } - System.out.printf ("%n%d new routines found by walking the code block%n%n", total); + System.out.printf ("%n%d new routines found by walking the code block%n%n", + routines.size () - total); } private int findNextRoutine (int address) @@ -125,12 +150,7 @@ class CodeManager extends AbstractFile return text.toString (); } - boolean containsRoutineAt (int address) - { - return (routines.containsKey (address)); - } - - void addCodeRoutines () + private void addCodeRoutines () { List routines = header.objectManager.getCodeRoutines (); System.out.println ("Adding " + routines.size () + " code routines"); @@ -138,8 +158,9 @@ class CodeManager extends AbstractFile addRoutine (address, 0); } - void addActionRoutines () + private void addActionRoutines () { + // process actionRoutines and preActionRoutines List routines = header.grammar.getActionRoutines (); System.out.println ("Adding " + routines.size () + " action routines"); for (Integer address : routines) @@ -150,6 +171,7 @@ class CodeManager extends AbstractFile { if (address == 0) // stack-based call return null; + if (address > header.fileLength) return null; @@ -163,7 +185,7 @@ class CodeManager extends AbstractFile // try to create a new Routine Routine r = new Routine (address, header, caller); - if (r.length == 0) // invalid routine + if (!r.isValid ()) return null; // recursively add all routines called by this one diff --git a/src/com/bytezone/diskbrowser/infocom/Grammar.java b/src/com/bytezone/diskbrowser/infocom/Grammar.java index ebb229e..c083a09 100644 --- a/src/com/bytezone/diskbrowser/infocom/Grammar.java +++ b/src/com/bytezone/diskbrowser/infocom/Grammar.java @@ -1,6 +1,11 @@ package com.bytezone.diskbrowser.infocom; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; import com.bytezone.diskbrowser.utilities.HexFormatter; @@ -70,6 +75,7 @@ class Grammar extends InfocomAbstractFile hexBlocks.add (new HexBlock (actionPtr, actionSize, "Action routines:")); hexBlocks.add (new HexBlock (preActionPtr, preActionSize, "Pre-action routines:")); hexBlocks.add (new HexBlock (prepositionPtr, prepositionSize, "Preposition table:")); + // System.out.println (getHexDump ()); // create SentenceGroup and Sentence objects and action lists int count = 255; diff --git a/src/com/bytezone/diskbrowser/infocom/Header.java b/src/com/bytezone/diskbrowser/infocom/Header.java index 4576054..4241c2e 100755 --- a/src/com/bytezone/diskbrowser/infocom/Header.java +++ b/src/com/bytezone/diskbrowser/infocom/Header.java @@ -73,10 +73,7 @@ class Header extends InfocomAbstractFile // add all the ZStrings stringManager = new StringManager ("Strings", buffer, this); - codeManager.addRoutine (programCounter - 1, 0); - codeManager.addActionRoutines (); // obtained from Grammar - codeManager.addCodeRoutines (); // obtained from Object properties - codeManager.addMissingRoutines (); // requires stringPtr to be set + codeManager.addRoutines (programCounter); // add entries for AbstractFile.getHexDump () hexBlocks.add (new HexBlock (0, 64, "Header data:")); @@ -139,6 +136,41 @@ class Header extends InfocomAbstractFile text.append (String.format ("Total objects %d%n", objectManager.getObjects ().size ())); + text.append (getAlternate ()); + + return text.toString (); + } + + private String getAlternate () + { + StringBuilder text = new StringBuilder ("\n\n"); + + text.append (getLine (0, 1, "version")); + text.append (getLine (1, 3, "flags 1")); + text.append (getLine (4, 2, "high memory")); + text.append (getLine (6, 2, "program counter")); + text.append (getLine (8, 2, "dictionary")); + text.append (getLine (10, 2, "object table")); + text.append (getLine (12, 2, "global variables")); + text.append (getLine (14, 2, "static memory")); + text.append (getLine (16, 2, "flags 2")); + text.append (getLine (24, 2, "abbreviations table")); + text.append (getLine (26, 2, "length of file (x2 = " + fileLength + ")")); + text.append (getLine (28, 2, "checksum")); + text.append (getLine (50, 1, "revision number")); + return text.toString (); + } + + private String getLine (int offset, int size, String description) + { + StringBuilder text = new StringBuilder (); + text.append (String.format ("%04X - %04X ", offset, offset + size - 1)); + for (int i = 0; i < size; i++) + text.append (String.format ("%02X ", buffer[offset + i])); + while (text.length () < 24) + text.append (" "); + text.append (description); + text.append ("\n"); return text.toString (); } diff --git a/src/com/bytezone/diskbrowser/infocom/InfocomDisk.java b/src/com/bytezone/diskbrowser/infocom/InfocomDisk.java index a39881d..31c854b 100755 --- a/src/com/bytezone/diskbrowser/infocom/InfocomDisk.java +++ b/src/com/bytezone/diskbrowser/infocom/InfocomDisk.java @@ -11,7 +11,12 @@ import javax.swing.JOptionPane; import javax.swing.tree.DefaultMutableTreeNode; import com.bytezone.diskbrowser.applefile.AppleFileSource; -import com.bytezone.diskbrowser.disk.*; +import com.bytezone.diskbrowser.disk.AbstractFormattedDisk; +import com.bytezone.diskbrowser.disk.AppleDisk; +import com.bytezone.diskbrowser.disk.DefaultAppleFileSource; +import com.bytezone.diskbrowser.disk.Disk; +import com.bytezone.diskbrowser.disk.DiskAddress; +import com.bytezone.diskbrowser.disk.SectorType; import com.bytezone.diskbrowser.gui.DataSource; import com.bytezone.diskbrowser.utilities.HexFormatter; @@ -25,12 +30,11 @@ public class InfocomDisk extends AbstractFormattedDisk private static final boolean TYPE_NODE = true; private static final boolean TYPE_LEAF = false; private byte[] data; - // private int version; private final Header header; Color green = new Color (0, 200, 0); - SectorType bootSector = new SectorType ("Boot code", Color.lightGray); + SectorType bootSector = new SectorType ("ZIP code", Color.lightGray); SectorType stringsSector = new SectorType ("Strings", Color.magenta); SectorType objectsSector = new SectorType ("Objects", green); SectorType dictionarySector = new SectorType ("Dictionary", Color.blue); diff --git a/src/com/bytezone/diskbrowser/infocom/Instruction.java b/src/com/bytezone/diskbrowser/infocom/Instruction.java index cf385ef..64e3514 100755 --- a/src/com/bytezone/diskbrowser/infocom/Instruction.java +++ b/src/com/bytezone/diskbrowser/infocom/Instruction.java @@ -7,15 +7,15 @@ import com.bytezone.diskbrowser.utilities.HexFormatter; class Instruction { - Opcode opcode; - int startPtr; - byte[] buffer; + final Opcode opcode; + private int startPtr; + private byte[] buffer; // List abbreviations; - Header header; + private Header header; enum OperandType { - VAR_STACK, VAR_LOCAL, VAR_GLOBAL, BYTE, WORD, ARG_BRANCH, ARG_STRING + VAR_SP, VAR_LOCAL, VAR_GLOBAL, BYTE, WORD, ARG_BRANCH, ARG_STRING } static final String[] name2OP = @@ -38,26 +38,23 @@ class Instruction "scan_table", "not", "call_vn", "call_vn2", "tokenise", "encode_text", "copy_table", "print_table", "check_arg" }; - public Instruction (byte[] buffer, int ptr, Header header) + Instruction (byte[] buffer, int ptr, Header header) { this.buffer = buffer; this.startPtr = ptr; this.header = header; byte b1 = buffer[ptr]; - // long - if ((b1 & 0x80) == 0) + if ((b1 & 0x80) == 0) // long opcode = new Opcode2OPLong (buffer, ptr); - // short - else if ((b1 & 0x40) == 0) + else if ((b1 & 0x40) == 0) // short { if ((b1 & 0x30) == 0x30) opcode = new Opcode0OP (buffer, ptr); else opcode = new Opcode1OP (buffer, ptr); } - // variable - else + else // variable { if ((b1 & 0x20) == 0) opcode = new Opcode2OPVar (buffer, ptr); @@ -66,43 +63,43 @@ class Instruction } } - public int length () + int length () { return opcode.length (); } - public boolean isReturn () + boolean isReturn () { return opcode.isReturn; } - public boolean isPrint () + boolean isPrint () { return opcode.string != null; } - public boolean isCall () + boolean isCall () { return opcode.isCall; } - public boolean isJump () + boolean isJump () { // could use jumpTarget != 0 return (opcode instanceof Opcode1OP && opcode.opcodeNumber == 12); } - public boolean isBranch () + boolean isBranch () { return opcode.branch != null; } - public boolean isStore () + boolean isStore () { return opcode.store != null; } - public int target () + int target () { return isBranch () ? opcode.branch.target : 0; } @@ -117,8 +114,10 @@ class Instruction max = 9; extra = ".."; } + String hex = HexFormatter.getHexString (buffer, startPtr, max); - return String.format ("%-26s%2s %s", hex, extra, opcode.toString ()); + + return String.format ("%05X : %-26s%2s %s", startPtr, hex, extra, opcode.toString ()); } abstract class Opcode @@ -134,7 +133,7 @@ class Instruction int jumpTarget; int callTarget; - public Opcode () + Opcode () { operands = new ArrayList (); } @@ -143,9 +142,9 @@ class Instruction public String toString () { StringBuilder text = new StringBuilder (); - if (false) - text.append (HexFormatter.formatNoHeader (buffer, startPtr, length ()) + "\n"); - text.append (String.format ("%05X : %-12s", startPtr, opcodeName ())); + + text.append (String.format ("%-12s", opcodeName ())); + if (jumpTarget != 0) text.append (String.format (" L:%05X", jumpTarget)); else if (isCall) @@ -173,7 +172,7 @@ class Instruction return text.toString (); } - public int length () + int length () { int length = totalOperandLength + opcodeLength; if (branch != null) @@ -185,7 +184,7 @@ class Instruction return length; } - public abstract String opcodeName (); + abstract String opcodeName (); private void addOperand (Operand operand) { @@ -193,7 +192,7 @@ class Instruction totalOperandLength += operand.length; } - protected void addOperand (byte[] buffer, int ptr, boolean bit1, boolean bit2) + void addOperand (byte[] buffer, int ptr, boolean bit1, boolean bit2) { int offset = ptr + totalOperandLength; if (bit1) @@ -207,7 +206,7 @@ class Instruction addOperand (new OperandWord (header.getWord (offset))); // %00 } - protected void addOperand (byte[] buffer, int ptr, boolean bit) + void addOperand (byte[] buffer, int ptr, boolean bit) { int address = ptr + totalOperandLength; if (address >= buffer.length) @@ -221,7 +220,7 @@ class Instruction addOperand (new OperandByte (buffer[address])); } - protected void setVariableOperands (int ptr) + void setVariableOperands (int ptr) { int value = buffer[ptr + 1] & 0xFF; for (int i = 0; i < 4; i++) @@ -235,21 +234,21 @@ class Instruction } } - protected void setStore (byte[] buffer) + void setStore (byte[] buffer) { store = new OperandVariable (buffer[startPtr + totalOperandLength + opcodeLength]); } - protected void setBranch (byte[] buffer) + void setBranch (byte[] buffer) { int offset = startPtr + totalOperandLength + (store == null ? 0 : 1) + opcodeLength; - if ((buffer[offset] & 0x40) == 0x40) + if ((buffer[offset] & 0x40) != 0) branch = new ArgumentBranch (buffer[offset], offset); else branch = new ArgumentBranch (header.getWord (offset), offset); } - protected void setZString (byte[] buffer) + void setZString (byte[] buffer) { int offset = startPtr + totalOperandLength + opcodeLength; string = new ArgumentString (buffer, offset); @@ -258,7 +257,7 @@ class Instruction class Opcode0OP extends Opcode { - public Opcode0OP (byte[] buffer, int ptr) + Opcode0OP (byte[] buffer, int ptr) { opcodeNumber = buffer[ptr] & 0x0F; opcodeLength = 1; @@ -286,7 +285,7 @@ class Instruction class Opcode1OP extends Opcode { - public Opcode1OP (byte[] buffer, int ptr) + Opcode1OP (byte[] buffer, int ptr) { opcodeNumber = buffer[ptr] & 0x0F; opcodeLength = 1; @@ -315,12 +314,12 @@ class Instruction abstract class Opcode2OP extends Opcode { - public Opcode2OP () + Opcode2OP () { opcodeLength = 1; } - public void setArguments (byte[] buffer) + void setArguments (byte[] buffer) { if ((opcodeNumber >= 1 && opcodeNumber <= 7) || opcodeNumber == 10) setBranch (buffer); @@ -338,7 +337,7 @@ class Instruction class Opcode2OPLong extends Opcode2OP { - public Opcode2OPLong (byte[] buffer, int ptr) + Opcode2OPLong (byte[] buffer, int ptr) { opcodeNumber = buffer[ptr] & 0x1F; boolean bit1 = ((buffer[ptr] & 0x40) == 0x40); @@ -352,7 +351,7 @@ class Instruction class Opcode2OPVar extends Opcode2OP { - public Opcode2OPVar (byte[] buffer, int ptr) + Opcode2OPVar (byte[] buffer, int ptr) { opcodeNumber = buffer[ptr] & 0x1F; opcodeLength = 2; @@ -363,7 +362,7 @@ class Instruction class OpcodeVar extends Opcode { - public OpcodeVar (byte[] buffer, int ptr) + OpcodeVar (byte[] buffer, int ptr) { opcodeNumber = buffer[ptr] & 0x1F; opcodeLength = 2; @@ -394,7 +393,7 @@ class Instruction class OperandWord extends Operand { - public OperandWord (int value) + OperandWord (int value) { this.value = value; length = 2; @@ -410,7 +409,7 @@ class Instruction class OperandByte extends Operand { - public OperandByte (byte value) + OperandByte (byte value) { this.value = value & 0xFF; length = 1; @@ -426,13 +425,13 @@ class Instruction class OperandVariable extends Operand { - public OperandVariable (byte value) + OperandVariable (byte value) { this.value = value & 0xFF; length = 1; if (value == 0) - operandType = OperandType.VAR_STACK; + operandType = OperandType.VAR_SP; else if (value <= 15) operandType = OperandType.VAR_LOCAL; else @@ -442,8 +441,8 @@ class Instruction @Override public String toString () { - if (operandType == OperandType.VAR_STACK) - return ("ToS"); + if (operandType == OperandType.VAR_SP) + return ("SP"); if (operandType == OperandType.VAR_LOCAL) return (String.format ("L%02d", value)); return String.format ("G%03d", (value - 15)); @@ -452,13 +451,13 @@ class Instruction class ArgumentBranch extends Operand { - int target; - boolean branchOnTrue; + private int target; + private boolean branchOnTrue; - public ArgumentBranch (byte value, int offset) + ArgumentBranch (byte value, int offset) { - branchOnTrue = (value & 0x80) == 0x80; - int val = value & 0x3F; // unsigned + branchOnTrue = (value & 0x80) != 0; + int val = value & 0x3F; // 0 - 63 if (val <= 1) target = val; else @@ -467,12 +466,19 @@ class Instruction operandType = OperandType.ARG_BRANCH; } - public ArgumentBranch (int value, int offset) + ArgumentBranch (int value, int offset) { - branchOnTrue = (value & 0x8000) == 0x8000; - int val = value & 0x3FFF; // signed - if (val > 8191) - val -= 16384; + branchOnTrue = (value & 0x8000) != 0; + int val = ((value & 0x3FFF) << 18) >> 18; // signed 14-bit number + // int val = value & 0x3FFF; // signed + // if (val >= 0x2000) + // { + // System.out.printf ("%04X -> %d%n", val, (val - 0x4000)); + // val -= 0x4000; + // } + // else + // System.out.printf ("%04X%n", val); + target = val + offset; length = 2; operandType = OperandType.ARG_BRANCH; @@ -493,11 +499,11 @@ class Instruction class ArgumentString extends Operand { - ZString text; - int startPtr; - byte[] buffer; + private ZString text; + private int startPtr; + private byte[] buffer; - public ArgumentString (byte[] buffer, int offset) + ArgumentString (byte[] buffer, int offset) { this.buffer = buffer; text = new ZString (header, offset); diff --git a/src/com/bytezone/diskbrowser/infocom/ObjectAnalyser.java b/src/com/bytezone/diskbrowser/infocom/ObjectAnalyser.java index ba1ad2d..b417f89 100644 --- a/src/com/bytezone/diskbrowser/infocom/ObjectAnalyser.java +++ b/src/com/bytezone/diskbrowser/infocom/ObjectAnalyser.java @@ -39,10 +39,7 @@ class ObjectAnalyser pt.addTest (hmc); pt.doTests (); - // System.out.println ("\nSetting the string pointer\n"); - - for (Integer propertyNo : pt) - // list of all properties that passed all tests + for (Integer propertyNo : pt) // list of all properties that passed all tests list.add (hmc.statistics[propertyNo]); Collections.sort (list); @@ -88,22 +85,22 @@ class ObjectAnalyser System.out.println ("Routines found : " + totRoutines); } - public void checkThreeByteProperties () - { - for (ZObject object : parent.getObjects ()) - { - for (Property property : object.properties) - { - if (header.getPropertyName (property.propertyNumber).charAt (0) < 'a' - && property.length == 3) - { - int address = header.getWord (property.ptr + 1) * 2; - System.out.println ("checking " + address); - header.codeManager.addRoutine (address, 0); - } - } - } - } + // private void checkThreeByteProperties () + // { + // for (ZObject object : parent.getObjects ()) + // { + // for (Property property : object.properties) + // { + // if (header.getPropertyName (property.propertyNumber).charAt (0) < 'a' + // && property.length == 3) + // { + // int address = header.getWord (property.ptr + 1) * 2; + // System.out.println ("checking " + address); + // header.codeManager.addRoutine (address, 0); + // } + // } + // } + // } // find the property with only dictionary entries public void setDictionary () diff --git a/src/com/bytezone/diskbrowser/infocom/Routine.java b/src/com/bytezone/diskbrowser/infocom/Routine.java index 7e731d1..0dbf8d4 100644 --- a/src/com/bytezone/diskbrowser/infocom/Routine.java +++ b/src/com/bytezone/diskbrowser/infocom/Routine.java @@ -10,8 +10,6 @@ import com.bytezone.diskbrowser.infocom.Instruction.OperandType; class Routine extends InfocomAbstractFile implements Iterable, Comparable { - private static final String padding = " "; - int startPtr, length, strings, locals; List parameters = new ArrayList (); @@ -28,7 +26,7 @@ class Routine extends InfocomAbstractFile if (locals > 15) { System.out.println ("Too many locals: " + locals); - return; + return; // startPtr will be zero } startPtr = ptr++; // also used to flag a valid routine @@ -61,16 +59,19 @@ class Routine extends InfocomAbstractFile if (operand.operandType == OperandType.VAR_GLOBAL) header.globals.addRoutine (this, operand); - ptr += instruction.length (); + ptr += instruction.length (); // point to next instruction + if (isTarget (ptr)) + continue; // is it a backwards jump? - if (instruction.isJump () && instruction.target () < ptr && !moreCode (ptr)) + if (instruction.isJump () && instruction.target () < ptr) break; // is it an unconditional return? - if (instruction.isReturn () && !moreCode (ptr)) + if (instruction.isReturn ()) break; } + length = ptr - startPtr; hexBlocks.add (new HexBlock (startPtr, length, null)); @@ -79,22 +80,27 @@ class Routine extends InfocomAbstractFile if (true) { int endPtr = startPtr + length; - for (Instruction ins : instructions) + for (Instruction instruction : instructions) { - int target = ins.target () > 256 ? ins.target () - : ins.opcode.jumpTarget > 256 ? ins.opcode.jumpTarget : 0; + int target = instruction.target () > 256 ? instruction.target () + : instruction.opcode.jumpTarget > 256 ? instruction.opcode.jumpTarget : 0; if (target == 0) continue; - if (ins.isBranch () && (target > endPtr || target < startPtr)) - System.out.println (ins); - if (ins.isJump () && (target > endPtr || target < startPtr)) - System.out.println (ins); + if (instruction.isBranch () && (target > endPtr || target < startPtr)) + System.out.println (instruction); + if (instruction.isJump () && (target > endPtr || target < startPtr)) + System.out.println (instruction); } } } + boolean isValid () + { + return startPtr > 0; + } + // test whether the routine contains any instructions pointing to this address - private boolean moreCode (int ptr) + private boolean isTarget (int ptr) { for (Instruction ins : instructions) { @@ -116,17 +122,45 @@ class Routine extends InfocomAbstractFile public String getText () { StringBuilder text = new StringBuilder (); + text.append (String.format ("Called by : %3d%n", calledBy.size ())); - text.append (String.format ("Calls : %3d%n%n", calls.size ())); - text.append (String.format ("%s%05X : %d%n", padding, startPtr, locals)); + text.append (String.format ("Calls : %3d%n", calls.size ())); + text.append (String.format ("Length : %3d%n%n", length)); + + text.append (String.format ("%05X : %d%n", startPtr, locals)); + for (Parameter parameter : parameters) - text.append (padding + parameter.toString () + "\n"); + text.append (parameter.toString () + "\n"); + text.append ("\n"); + for (Instruction instruction : instructions) text.append (instruction + "\n"); + + if (calledBy.size () > 0) + { + text.append ("\n\nCalled by\n\n"); + for (int i : calledBy) + text.append (String.format ("%05X%n", i)); + } + + if (calls.size () > 0) + { + text.append ("\n\nCalls\n\n"); + for (int i : calls) + text.append (String.format ("%05X%n", i)); + } + return text.toString (); } + @Override + public String toString () + { + return String.format ("[Start: %05X, Len: %4d, Strings: %2d, Locals: %2d]", startPtr, + length, strings, locals); + } + class Parameter { int value;