content: /*
   * MIPSTokenMarker.java - MIPS Assembly token marker Copyright (C) 1998, 1999
   * Slava Pestov, 2010 Pete Sanderson
   *
   * You may use and modify this package for any purpose. Redistribution is
   * permitted, in both source and binary form, provided that this notice remains
   * intact in all source distributions of this package.
   */
  
  package mars.venus.editors.jeditsyntax.tokenmarker;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.TreeSet;
  
  import javax.swing.text.Segment;
  
  import mars.assembler.Directives;
  import mars.mips.instructions.BasicInstruction;
  import mars.mips.instructions.Instruction;
  import mars.venus.editors.jeditsyntax.KeywordMap;
  import mars.venus.editors.jeditsyntax.PopupHelpItem;
  
  /**
   * MIPS token marker.
   *
   * @author Pete Sanderson (2010) and Slava Pestov (1999)
   */
  public class MIPSTokenMarker extends TokenMarker {
  
  	public MIPSTokenMarker() { this(getKeywords()); }
  
  	public MIPSTokenMarker(final KeywordMap keywords) { this.keywords = keywords; }
  
  	public static String[] getMIPSTokenLabels() {
  		if (tokenLabels == null) {
  			tokenLabels = new String[Token.ID_COUNT];
  			tokenLabels[Token.COMMENT1] = "Comment";
  			tokenLabels[Token.LITERAL1] = "String literal";
  			tokenLabels[Token.LITERAL2] = "Character literal";
  			tokenLabels[Token.LABEL] = "Label";
  			tokenLabels[Token.KEYWORD1] = "MIPS instruction";
  			tokenLabels[Token.KEYWORD2] = "Assembler directive";
  			tokenLabels[Token.KEYWORD3] = "Register";
  			tokenLabels[Token.INVALID] = "In-progress, invalid";
  			tokenLabels[Token.MACRO_ARG] = "Macro parameter";
  			tokenLabels[Token.EDITOR_BG] = "Background";
  			tokenLabels[Token.EDITOR_LINE] = "Line";
  			tokenLabels[Token.EDITOR_SELECTION] = "Selection";
  		}
  		return tokenLabels;
  	}
  
  	public static String[] getMIPSTokenExamples() {
  		if (tokenExamples == null) {
  			tokenExamples = new String[Token.ID_COUNT];
  			tokenExamples[Token.COMMENT1] = "# Load";
  			tokenExamples[Token.LITERAL1] = "\"First\"";
  			tokenExamples[Token.LITERAL2] = "'\\n'";
  			tokenExamples[Token.LABEL] = "main:";
  			tokenExamples[Token.KEYWORD1] = "lui";
  			tokenExamples[Token.KEYWORD2] = ".text";
  			tokenExamples[Token.KEYWORD3] = "$zero";
  			tokenExamples[Token.INVALID] = "\"Regi";
  			tokenExamples[Token.MACRO_ARG] = "%arg";
  			tokenExamples[Token.EDITOR_BG] = "";
  			tokenExamples[Token.EDITOR_LINE] = "";
  			tokenExamples[Token.EDITOR_SELECTION] = "";
  		}
  		return tokenExamples;
  	}
  
  	@Override
  	public byte markTokensImpl(byte token, final Segment line, final int lineIndex) {
  		final char[] array = line.array;
  		final int offset = line.offset;
  		lastOffset = offset;
  		lastKeyword = offset;
  		final int length = line.count + offset;
  		boolean backslash = false;
  
  		loop:
  		for (int i = offset; i < length; i++) {
  			final int i1 = i + 1;
  
  			final char c = array[i];
  			if (c == '\\') {
  				backslash = !backslash;
  				continue;
  			}
  
  			switch (token) {
  			case Token.NULL:
  				switch (c) {
  				case '"':
  					doKeyword(line, i, c);
  					if (backslash) {
  						backslash = false;
  					} else {
  						addToken(i - lastOffset, token);
  						token = Token.LITERAL1;
  						lastOffset = lastKeyword = i;
  					}
  					break;
  				case '\'':
  					doKeyword(line, i, c);
  					if (backslash) {
  						backslash = false;
  					} else {
  						addToken(i - lastOffset, token);
  						token = Token.LITERAL2;
  						lastOffset = lastKeyword = i;
  					}
  					break;
  				case ':':
  					/*  Original code for ':' case, replaced 3 Aug 2010. Details below.
  					  if(lastKeyword == offset)
  					  { // Commenting out this IF statement permits recognition of keywords
  					    // used as labels when terminated with ":".  The most common example
  					    // is "b:" (where b is mnemonic for branch instruction). DPS 6-July-2010.
  					    //
  					    // if(doKeyword(line,i,c))
  					    //   break;
  					     backslash = false;
  					     addToken(i1 - lastOffset,Token.LABEL);
  					     lastOffset = lastKeyword = i1;
  					  }
  					  else if(doKeyword(line,i,c))
  					     break;
  					break;
  					*/
  					// Replacement code 3 Aug 2010.  Will recognize label definitions when:
  					// (1) label is same as instruction name, (2) label begins after column 1,
  					// (3) there are spaces between label name and colon, (4) label is valid
  					// MIPS identifier (otherwise would catch, say, 0 (zero) in .word 0:10)
  					backslash = false;
  					//String lab = new String(array, lastOffset, i1-lastOffset-1).trim();
  					boolean validIdentifier = false;
  					try {
  						validIdentifier = mars.assembler.TokenTypes.isValidIdentifier(
  							new String(array, lastOffset, i1 - lastOffset - 1).trim());
  					} catch (final StringIndexOutOfBoundsException e) {
  						validIdentifier = false;
  					}
  					if (validIdentifier) {
  						addToken(i1 - lastOffset, Token.LABEL);
  						lastOffset = lastKeyword = i1;
  					}
  					break;
  				case '#':
  					backslash = false;
  					doKeyword(line, i, c);
  					if (length - i >= 1) {
  						addToken(i - lastOffset, token);
  						addToken(length - i, Token.COMMENT1);
  						lastOffset = lastKeyword = length;
  						break loop;
  					}
  					break;
  				default:
  					backslash = false;
  					// . and $ added 4/6/10 DPS; % added 12/12 M.Sekhavat
  					if (!Character.isLetterOrDigit(c) && c != '_' && c != '.' && c != '$' && c != '%') {
  						doKeyword(line, i, c);
  					}
  					break;
  				}
  				break;
  			case Token.LITERAL1:
  				if (backslash) {
  					backslash = false;
  				} else if (c == '"') {
  					addToken(i1 - lastOffset, token);
  					token = Token.NULL;
  					lastOffset = lastKeyword = i1;
  				}
  				break;
  			case Token.LITERAL2:
  				if (backslash) {
  					backslash = false;
  				} else if (c == '\'') {
  					addToken(i1 - lastOffset, Token.LITERAL1);
  					token = Token.NULL;
  					lastOffset = lastKeyword = i1;
  				}
  				break;
  			default:
  				throw new InternalError(
  						"Invalid state: "
  							+ token);
  			}
  		}
  
  		if (token == Token.NULL) {
  			doKeyword(line, length, '\0');
  		}
  
  		switch (token) {
  		case Token.LITERAL1:
  		case Token.LITERAL2:
  			addToken(length - lastOffset, Token.INVALID);
  			token = Token.NULL;
  			break;
  		case Token.KEYWORD2:
  			addToken(length - lastOffset, token);
  			if (!backslash) {
  				token = Token.NULL;
  			}
  		default:
  			addToken(length - lastOffset, token);
  			break;
  		}
  
  		return token;
  	}
  
  	/**
  	 * Construct and return any appropriate help information for the given token.
  	 *
  	 * @param token the pertinent Token object
  	 * @param tokenText the source String that matched to the token
  	 * @return ArrayList of PopupHelpItem objects, one per match.
  	 */
  	@Override
  	public ArrayList getTokenExactMatchHelp(final Token token, final String tokenText) {
  		ArrayList matches = null;
  		if (token != null && token.id == Token.KEYWORD1) {
  			final ArrayList instrMatches = mars.Globals.instructionSet.matchOperator(tokenText);
  			if (instrMatches.size() > 0) {
  				int realMatches = 0;
  				matches = new ArrayList();
  				for (int i = 0; i < instrMatches.size(); i++) {
  					final Instruction inst = (Instruction) instrMatches.get(i);
  					if (mars.Globals.getSettings().getExtendedAssemblerEnabled() || inst instanceof BasicInstruction) {
  						matches.add(new PopupHelpItem(tokenText, inst.getExampleFormat(), inst.getDescription()));
  						realMatches++;
  					}
  				}
  				if (realMatches == 0) {
  					matches.add(new PopupHelpItem(tokenText, tokenText, "(is not a basic instruction)"));
  				}
  			}
  		}
  		if (token != null && token.id == Token.KEYWORD2) {
  			final Directives dir = Directives.matchDirective(tokenText);
  			if (dir != null) {
  				matches = new ArrayList();
  				matches.add(new PopupHelpItem(tokenText, dir.getName(), dir.getDescription()));
  			}
  		}
  		return matches;
  	}
  
  	/**
  	 * Construct and return any appropriate help information for prefix match based
  	 * on current line's token list.
  	 *
  	 * @param line String containing current line
  	 * @param tokenList first Token on current line (head of linked list)
  	 * @param token the pertinent Token object
  	 * @param tokenText the source String that matched to the token in previous
  	 * parameter
  	 * @return ArrayList of PopupHelpItem objects, one per match.
  	 */
  
  	@Override
  	public ArrayList getTokenPrefixMatchHelp(final String line, final Token tokenList, final Token token, final String tokenText) {
  		// CASE:  Unlikely boundary case...
  		if (tokenList == null || tokenList.id == Token.END) { return null; }
  
  		// CASE:  if current token is a comment, turn off the text.
  		if (token != null && token.id == Token.COMMENT1) { return null; }
  
  		// Let's see if the line already contains an instruction or directive.  If so, we need its token
  		// text as well so we can do the match.  Also need to distinguish the case where current
  		// token is also an instruction/directive (moreThanOneKeyword variable).
  
  		Token tokens = tokenList;
  		String keywordTokenText = null;
  		byte keywordType = -1;
  		int offset = 0;
  		boolean moreThanOneKeyword = false;
  		while (tokens.id != Token.END) {
  			if (tokens.id == Token.KEYWORD1 || tokens.id == Token.KEYWORD2) {
  				if (keywordTokenText != null) {
  					moreThanOneKeyword = true;
  					break;
  				}
  				keywordTokenText = line.substring(offset, offset + tokens.length);
  				keywordType = tokens.id;
  			}
  			offset += tokens.length;
  			tokens = tokens.next;
  		}
  
  		// CASE:  Current token is valid KEYWORD1 (MIPS instruction).  If this line contains a previous KEYWORD1 or KEYWORD2
  		//        token, then we ignore this one and do exact match on the first one.  If it does not, there may be longer
  		//        instructions for which this is a prefix, so do a prefix match on current token.
  		if (token != null && token.id == Token.KEYWORD1) {
  			if (moreThanOneKeyword) {
  				return keywordType == Token.KEYWORD1 ? getTextFromInstructionMatch(
  					keywordTokenText,
  					true) : getTextFromDirectiveMatch(keywordTokenText, true);
  			} else {
  				return getTextFromInstructionMatch(tokenText, false);
  			}
  		}
  
  		// CASE:  Current token is valid KEYWORD2 (MIPS directive).  If this line contains a previous KEYWORD1 or KEYWORD2
  		//        token, then we ignore this one and do exact match on the first one.  If it does not, there may be longer
  		//        directives for which this is a prefix, so do a prefix match on current token.
  		if (token != null && token.id == Token.KEYWORD2) {
  			if (moreThanOneKeyword) {
  				return keywordType == Token.KEYWORD1 ? getTextFromInstructionMatch(
  					keywordTokenText,
  					true) : getTextFromDirectiveMatch(keywordTokenText, true);
  			} else {
  				return getTextFromDirectiveMatch(tokenText, false);
  			}
  		}
  
  		// CASE: line already contains KEYWORD1 or KEYWORD2 and current token is something other
  		//       than KEYWORD1 or KEYWORD2. Generate text based on exact match of that token.
  		if (keywordTokenText != null) {
  			if (keywordType == Token.KEYWORD1) { return getTextFromInstructionMatch(keywordTokenText, true); }
  			if (keywordType == Token.KEYWORD2) { return getTextFromDirectiveMatch(keywordTokenText, true); }
  		}
  
  		// CASE:  Current token is NULL, which can be any number of things.  Think of it as being either white space
  		//        or an in-progress token possibly preceded by white space.  We'll do a trim on the token.  Now there
  		//        are two subcases to consider:
  		//    SUBCASE: The line does not contain any KEYWORD1 or KEYWORD2 tokens but nothing remains after trimming the
  		//             current token's text.  This means it consists only of white space and there is nothing more to do
  		//             but return.
  		//    SUBCASE: The line does not contain any KEYWORD1 or KEYWORD2 tokens.  This means we do a prefix match of
  		//             of the current token to either instruction or directive names.  Easy to distinguish since
  		//             directives start with "."
  
  		if (token != null && token.id == Token.NULL) {
  
  			final String trimmedTokenText = tokenText.trim();
  
  			// Subcase: no KEYWORD1 or KEYWORD2 but current token contains nothing but white space.  We're done.
  			if (keywordTokenText == null && trimmedTokenText.length() == 0) { return null; }
  
  			// Subcase: no KEYWORD1 or KEYWORD2.  Generate text based on prefix match of trimmed current token.
  			if (keywordTokenText == null && trimmedTokenText.length() > 0) {
  				if (trimmedTokenText.charAt(0) == '.') {
  					return getTextFromDirectiveMatch(trimmedTokenText, false);
  				} else if (trimmedTokenText.length() >= mars.Globals.getSettings().getEditorPopupPrefixLength()) {
  					return getTextFromInstructionMatch(trimmedTokenText, false);
  				}
  			}
  		}
  		// should never get here...
  		return null;
  	}
  
  	///////////////////////////////////////////////////////////////////////////
  	// Return ArrayList of PopupHelpItem for match of directives.  If second argument
  	// true, will do exact match.  If false, will do prefix match.  Returns null
  	// if no matches.
  	private ArrayList getTextFromDirectiveMatch(final String tokenText, final boolean exact) {
  		ArrayList matches = null;
  		ArrayList directiveMatches = null;
  		if (exact) {
  			final Object dir = Directives.matchDirective(tokenText);
  			if (dir != null) {
  				directiveMatches = new ArrayList();
  				directiveMatches.add(dir);
  			}
  		} else {
  			directiveMatches = Directives.prefixMatchDirectives(tokenText);
  		}
  		if (directiveMatches != null) {
  			matches = new ArrayList();
  			for (int i = 0; i < directiveMatches.size(); i++) {
  				final Directives direct = (Directives) directiveMatches.get(i);
  				matches.add(new PopupHelpItem(tokenText, direct.getName(), direct.getDescription(), exact));
  			}
  		}
  		return matches;
  	}
  
  	// Return text for match of instruction mnemonic.  If second argument true, will
  	// do exact match.  If false, will do prefix match.   Text is returned as ArrayList
  	// of PopupHelpItem objects. If no matches, returns null.
  	private ArrayList getTextFromInstructionMatch(final String tokenText, final boolean exact) {
  		ArrayList matches = null;
  		final ArrayList results = new ArrayList();
  		if (exact) {
  			matches = mars.Globals.instructionSet.matchOperator(tokenText);
  		} else {
  			matches = mars.Globals.instructionSet.prefixMatchOperator(tokenText);
  		}
  		if (matches == null) { return null; }
  		int realMatches = 0;
  		final HashMap insts = new HashMap();
  		final TreeSet mnemonics = new TreeSet();
  		for (int i = 0; i < matches.size(); i++) {
  			final Instruction inst = (Instruction) matches.get(i);
  			if (mars.Globals.getSettings().getExtendedAssemblerEnabled() || inst instanceof BasicInstruction) {
  				if (exact) {
  					results.add(new PopupHelpItem(tokenText, inst.getExampleFormat(), inst.getDescription(), exact));
  				} else {
  					final String mnemonic = inst.getExampleFormat().split(" ")[0];
  					if (!insts.containsKey(mnemonic)) {
  						mnemonics.add(mnemonic);
  						insts.put(mnemonic, inst.getDescription());
  					}
  				}
  				realMatches++;
  			}
  		}
  		if (realMatches == 0) {
  			if (exact) {
  				results.add(new PopupHelpItem(tokenText, tokenText, "(not a basic instruction)", exact));
  			} else {
  				return null;
  			}
  		} else {
  			if (!exact) {
  				final Iterator mnemonicList = mnemonics.iterator();
  				while (mnemonicList.hasNext()) {
  					final String mnemonic = (String) mnemonicList.next();
  					final String info = (String) insts.get(mnemonic);
  					results.add(new PopupHelpItem(tokenText, mnemonic, info, exact));
  				}
  			}
  		}
  		return results;
  	}
  
  	/**
  	 * Get KeywordMap containing all MIPS key words. This includes all instruction
  	 * mnemonics, assembler directives, and register names.
  	 *
  	 * @return KeywordMap where key is the keyword and associated value is the token
  	 * type (e.g. Token.KEYWORD1).
  	 */
  
  	public static KeywordMap getKeywords() {
  		if (cKeywords == null) {
  			cKeywords = new KeywordMap(false);
  			// add Instruction mnemonics
  			final java.util.ArrayList instructionSet = mars.Globals.instructionSet.getInstructionList();
  			for (int i = 0; i < instructionSet.size(); i++) {
  				cKeywords.add(((mars.mips.instructions.Instruction) instructionSet.get(i)).getName(), Token.KEYWORD1);
  			}
  			// add assembler directives
  			final java.util.ArrayList directiveSet = mars.assembler.Directives.getDirectiveList();
  			for (int i = 0; i < directiveSet.size(); i++) {
  				cKeywords.add(((mars.assembler.Directives) directiveSet.get(i)).getName(), Token.KEYWORD2);
  			}
  			// add integer register file
  			final mars.mips.hardware.Register[] registerFile = mars.mips.hardware.RegisterFile.getRegisters();
  			for (int i = 0; i < registerFile.length; i++) {
  				cKeywords.add(registerFile[i].getName(), Token.KEYWORD3);
  				cKeywords.add(
  					"$"
  						+ i,
  					Token.KEYWORD3);  // also recognize $0, $1, $2, etc
  			}
  			// add Coprocessor 1 (floating point) register file
  			final mars.mips.hardware.Register[] coprocessor1RegisterFile = mars.mips.hardware.Coprocessor1
  				.getRegisters();
  			for (int i = 0; i < coprocessor1RegisterFile.length; i++) {
  				cKeywords.add(coprocessor1RegisterFile[i].getName(), Token.KEYWORD3);
  			}
  			// Note: Coprocessor 0 registers referenced only by number: $8, $12, $13, $14. These are already in the map
  
  		}
  		return cKeywords;
  	}
  
  	// private members
  	private static KeywordMap cKeywords;
  	private static String[] tokenLabels, tokenExamples;
  	private final KeywordMap keywords;
  	private int lastOffset;
  	private int lastKeyword;
  
  	private boolean doKeyword(final Segment line, final int i, final char c) {
  		final int i1 = i + 1;
  
  		final int len = i - lastKeyword;
  		final byte id = keywords.lookup(line, lastKeyword, len);
  		if (id != Token.NULL) {
  			// If this is a Token.KEYWORD1 and line already contains a keyword,
  			// then assume this one is a label reference and ignore it.
  			//   if (id == Token.KEYWORD1 && tokenListContainsKeyword()) {
  			//    }
  			//    else {
  			if (lastKeyword != lastOffset) {
  				addToken(lastKeyword - lastOffset, Token.NULL);
  			}
  			addToken(len, id);
  			lastOffset = i;
  			//  }
  		}
  		lastKeyword = i1;
  		return false;
  	}
  
  	private boolean tokenListContainsKeyword() {
  		Token token = firstToken;
  		boolean result = false;
  		String str = "";
  		while (token != null) {
  			str += ""
  				+ token.id
  				+ "("
  				+ token.length
  				+ ") ";
  			if (token.id == Token.KEYWORD1 || token.id == Token.KEYWORD2 || token.id == Token.KEYWORD3) {
  				result = true;
  			}
  			token = token.next;
  		}
  		System.out.println(
  			""
  				+ result
  				+ " "
  				+ str);
  		return result;
  	}
  }
  