/Users/petercappello/NetBeansProjects/56-2014/56-2014-5-Gala/src/Instruction.java |
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JTextArea;
A GVM instruction.
@author
final class Instruction
{
private static final int DEFINE = -1;
private static final int OPCODE_NOT_SET = Integer.MAX_VALUE;
private static final String numberRegex = "^-?\\d*$";
private static final String identifierRegex = "[a-zA-Z_][\\w_]*";
private static final String opcodeRegex = "set|load|store|add|zero|goto|drawline|drawRect|drawOval|fillRect|fillOval";
private static final Pattern identifierPattern = Pattern.compile( identifierRegex );
private static final Map<String, Integer> stringToOpcode = new HashMap<>();
private static int programMemoryIndex = 0;
private static Map<String, Integer> identifierToValue;
private static Map<String, List<Instruction>> undefinedIdentifierToInstructionList;
static boolean areAllIdentifiersDefined( JTextArea console )
{
if ( undefinedIdentifierToInstructionList.isEmpty() )
{
return true;
}
for ( String undefinedIdentifier : undefinedIdentifierToInstructionList.keySet() )
{
console.append( undefinedIdentifier );
console.append( " is undefined in statements with the following numbers:" );
for ( Instruction instruction : undefinedIdentifierToInstructionList.get( undefinedIdentifier ) )
{
console.append( String.valueOf( instruction.lineNum ) );
}
console.append( "\n" );
}
return false;
}
Initializes static variable that cannot be initialized in declaration.
static void setClass()
{
stringToOpcode.put( "define", DEFINE );
stringToOpcode.put( "stop", GVM.STOP );
stringToOpcode.put( "set", GVM.SET );
stringToOpcode.put( "load", GVM.LOAD );
stringToOpcode.put( "store", GVM.STORE );
stringToOpcode.put( "add", GVM.ADD );
stringToOpcode.put( "zero", GVM.ZERO );
stringToOpcode.put( "goto", GVM.GOTO );
stringToOpcode.put( "setcolor", GVM.SETCOLOR );
stringToOpcode.put( "drawline", GVM.DRAWLINE );
stringToOpcode.put( "drawrect", GVM.DRAWRECT );
stringToOpcode.put( "fillrect", GVM.FILLRECT );
stringToOpcode.put( "drawoval", GVM.DRAWOVAL );
stringToOpcode.put( "filloval", GVM.FILLOVAL );
Collections.unmodifiableMap( stringToOpcode );
initStringToInteger();
}
static void initStringToInteger()
{
identifierToValue = new HashMap<>();
identifierToValue.put( "ACC", GVM.ACC );
identifierToValue.put( "X", GVM.X );
identifierToValue.put( "Y", GVM.Y );
identifierToValue.put( "WIDTH", GVM.WIDTH );
identifierToValue.put( "HEIGHT", GVM.HEIGHT );
identifierToValue.put( "RED", GVM.RED );
identifierToValue.put( "GREEN", GVM.GREEN );
identifierToValue.put( "BLUE", GVM.BLUE );
}
Reinitializes static variables for a new assembly.
static void resetClass()
{
programMemoryIndex = 0;
undefinedIdentifierToInstructionList = new HashMap<>();
initStringToInteger();
}
private final String line;
private final int lineNum;
private final int location = programMemoryIndex;
private int opcode = OPCODE_NOT_SET;
private int operand1;
private int operand2;
private String message;
private int tokenIndex;
String[] tokens;
public Instruction( String line, int lineNum )
{
this.line = line;
this.lineNum = lineNum;
tokens = line.trim().split( "\\s+" );
assert tokens.length > 0;
processLabel( tokens[ tokenIndex ] );
if ( ! isValid() )
{
return;
}
processOpcode( tokens[ tokenIndex ] );
if ( ! isValid() || ! correctNumOperands() )
{
return;
}
switch ( opcode )
{
case DEFINE: case GVM.ADD: case GVM.GOTO: case GVM.LOAD: case GVM.SET: case GVM.STORE: case GVM.ZERO:
processOperand1( tokens[ tokenIndex ]);
}
if ( opcode != DEFINE )
{
programMemoryIndex++;
}
}
@Override
public String toString()
{
StringBuilder instructionString = new StringBuilder();
instructionString.append( "\t line: ").append( line ).append( "\n\t" );
instructionString.append( "\t statementNum: ").append( lineNum ).append( '\t' );
instructionString.append( "\t location: ").append( location ).append( '\t' );
instructionString.append( "\t opcode: ").append( opcode ).append( '\t' );
instructionString.append( "\t operand1: ").append( operand1 ).append( '\t' );
instructionString.append( "\t operand2: ").append( operand2 ).append( '\t' );
return instructionString.toString();
}
String getErrorMessage() { return message; }
int getOpcode() { return opcode; }
boolean isMachineInstruction() { return opcode != DEFINE; }
boolean isValid() { return message == null; }
int[] toArray()
{
switch( opcode )
{
case GVM.SET: case GVM.LOAD: case GVM.ADD: case GVM.GOTO: case GVM.STORE: case GVM.ZERO:
return new int[]{ opcode, operand1 };
default:
return new int[]{ opcode };
}
}
private void processLabel( String string )
{
Integer prospectiveOpcode = stringToOpcode.get( string );
if ( prospectiveOpcode != null )
{
opcode = prospectiveOpcode;
return;
}
Matcher matcher = identifierPattern.matcher( string );
if ( matcher.matches() )
{
if ( identifierToValue.get( string ) == null )
{
identifierToValue.put( string, location );
resolvePriorReferences( string, location );
if ( ++tokenIndex >= tokens.length )
{
message = "labeled statement is missing an opcode.";
}
return;
}
message = string + " has been defined previously. ";
return;
}
message= string + " is an invalid statement label.";
}
private void processOpcode( String string )
{
if ( opcode != OPCODE_NOT_SET )
{
tokenIndex++;
return;
}
Integer prospectiveOpcode = stringToOpcode.get( string );
if ( prospectiveOpcode != null )
{
opcode = prospectiveOpcode;
tokenIndex++;
return;
}
message = string + " is an invalid opcode.";
}
private boolean correctNumOperands()
{
switch ( opcode )
{
case GVM.DRAWLINE: case GVM.DRAWOVAL: case GVM.DRAWRECT: case GVM.FILLOVAL: case GVM.FILLRECT: case GVM.SETCOLOR: case GVM.STOP:
if ( tokenIndex == tokens.length )
{
return true;
}
break;
case GVM.ADD: case GVM.GOTO: case GVM.LOAD: case GVM.SET: case GVM.STORE: case GVM.ZERO:
if ( tokenIndex + 1 == tokens.length )
{
return true;
}
break;
case DEFINE:
if ( tokenIndex + 2 == tokens.length )
{
return true;
}
break;
}
message = "The number of operands for this opcode is incorrect.";
return false;
}
private void processOperand1( String string )
{
Matcher matcher = identifierPattern.matcher( string );
if ( matcher.matches() )
{
Integer value = identifierToValue.get( string );
if ( value == null )
{
processUndefinedIdentifier( string );
return;
}
processDefinedIdentifier( string, value );
return;
}
if ( opcode == DEFINE )
{
message = "define opcode's 1st operand must be an identifier.";
}
operand1 = processNumber( string );
}
private void processUndefinedIdentifier( String string )
{
switch ( opcode )
{
case DEFINE:
int value = processNumber( tokens[ ++tokenIndex ] );
identifierToValue.put( string, value );
return;
case GVM.GOTO: case GVM.ZERO:
addToUndefinedLabelList( string );
return;
case GVM.SET: case GVM.LOAD: case GVM.STORE: case GVM.ADD:
message = string + " is undefined.";
return;
default:
assert false;
}
}
private void processDefinedIdentifier( String string, int value )
{
switch ( opcode )
{
case DEFINE:
message = string + " already is defined.";
return;
case GVM.GOTO: case GVM.ZERO:
operand1 = value;
return;
case GVM.SET: case GVM.LOAD: case GVM.STORE: case GVM.ADD:
operand1 = value;
return;
default:
assert false;
}
}
private int processNumber( String string )
{
try
{
return Integer.parseInt( string );
}
catch ( NumberFormatException exception )
{
message = string + " is not interpretable as a number.";
}
return 0;
}
private void addToUndefinedLabelList( String string )
{
List<Instruction> patchInstructions = undefinedIdentifierToInstructionList.get( string );
if ( patchInstructions == null )
{
patchInstructions = new ArrayList<>();
undefinedIdentifierToInstructionList.put( string, patchInstructions );
}
patchInstructions.add( this );
}
Patch Instruction objects that referred to this label.
@param string
@param location
private void resolvePriorReferences( String string, int location )
{
List<Instruction> instructionList = undefinedIdentifierToInstructionList.remove( string );
if ( instructionList == null )
{
return;
}
for ( Instruction instruction : instructionList )
{
instruction.setOperand1( location );
}
}
private void setOperand1( int location ) { operand1 = location; }
public static void main( String[] args )
{
System.out.println("identifierRegex: define " + Pattern.matches( identifierRegex, "define" ) );
System.out.println("opcdoePattern: \" \"" + Pattern.matches( numberRegex, " " ) );
System.out.println("whiteSpacesPattern: SET -1 " + Pattern.matches( "\\s+", " SET -1" ) );
System.out.println("opcdoePattern: \"#\"" + Pattern.matches( numberRegex, "#" ) );
System.out.println("opcdoePattern: \"a\"" + Pattern.matches( numberRegex, "a" ) );
System.out.println("opcdoePattern: \"7\"" + Pattern.matches( numberRegex, "7" ) );
System.out.println("opcdoePattern: \"-7\"" + Pattern.matches( numberRegex, "-7" ) );
System.out.println("opcdoePattern: \"--7\"" + Pattern.matches( numberRegex, "--7" ) );
System.out.println("opcdoePattern: \"set\"" + Pattern.matches( opcodeRegex, "set" ) );
System.out.println("opcdoePattern: \"fillOval\"" + Pattern.matches( opcodeRegex, "fillOval" ) );
System.out.println("opcdoePattern: \"fillwhat\"" + Pattern.matches( opcodeRegex, "fillwhat" ) );
}
}