mirror of
https://github.com/peterdell/wudsn-ide.git
synced 2024-05-31 17:41:28 +00:00
1215 lines
39 KiB
Java
1215 lines
39 KiB
Java
/**
|
||
* Copyright (C) 2009 - 2019 <a href="https://www.wudsn.com" target="_top">Peter Dell</a>
|
||
*
|
||
* This file is part of WUDSN IDE.
|
||
*
|
||
* WUDSN IDE is free software: you can redistribute it and/or modify
|
||
* it under the terms of the GNU General Public License as published by
|
||
* the Free Software Foundation, either version 2 of the License, or
|
||
* (at your option) any later version.
|
||
*
|
||
* WUDSN IDE is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
* GNU General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License
|
||
* along with WUDSN IDE. If not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
package com.wudsn.ide.asm.compiler.parser;
|
||
|
||
import java.io.File;
|
||
import java.util.HashMap;
|
||
import java.util.Iterator;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
|
||
import org.eclipse.core.runtime.CoreException;
|
||
import org.eclipse.jface.text.BadLocationException;
|
||
import org.eclipse.jface.text.Document;
|
||
import org.eclipse.jface.text.IDocument;
|
||
import org.eclipse.jface.text.IRegion;
|
||
|
||
import com.wudsn.ide.asm.AssemblerPlugin;
|
||
import com.wudsn.ide.asm.AssemblerProperties;
|
||
import com.wudsn.ide.asm.compiler.Compiler;
|
||
import com.wudsn.ide.asm.compiler.syntax.CompilerSyntax;
|
||
import com.wudsn.ide.asm.compiler.syntax.Instruction;
|
||
import com.wudsn.ide.asm.compiler.syntax.InstructionSet;
|
||
import com.wudsn.ide.asm.compiler.syntax.InstructionType;
|
||
import com.wudsn.ide.base.BasePlugin;
|
||
import com.wudsn.ide.base.common.FileUtility;
|
||
import com.wudsn.ide.base.common.StringUtility;
|
||
|
||
/**
|
||
* Source parser for creating {@link CompilerSourceParserTreeObject} instances.
|
||
* The performance is so good, that no caching for the results of source include
|
||
* parsing is required.
|
||
*
|
||
* @author Peter Dell
|
||
*
|
||
*/
|
||
public abstract class CompilerSourceParser {
|
||
|
||
// The sections of a single line
|
||
private static final class LineSection {
|
||
public static final int NONE = 0;
|
||
public static final int SYMBOL = 1;
|
||
public static final int INSTRUCTION = 2;
|
||
public static final int OPERAND = 3;
|
||
}
|
||
|
||
// The compiler syntax and instruction set.
|
||
private CompilerSyntax compilerSyntax;
|
||
private InstructionSet instructionSet;
|
||
|
||
// Fields set once for parsing a file.
|
||
private CompilerSourceFile compilerSourceFile;
|
||
|
||
// Fields modified during parsing.
|
||
private CompilerSourceParserTreeObject section;
|
||
|
||
// Fields modified per line. Preserved to and restored from local variables
|
||
// during recursive parsing of SOURCE_INCLUDE_DIRECTIVE instructions.
|
||
private CompilerSourceParserTreeObject child;
|
||
private CompilerSourceParserTreeObject labelChild;
|
||
private boolean blockStarting;
|
||
private boolean blockEnding;
|
||
|
||
// For debugging.
|
||
private boolean logEnabled = false;
|
||
|
||
/**
|
||
* Extract all {@link AssemblerProperties} properties from a document.
|
||
*
|
||
* @param document
|
||
* The document, not <code>null</code>.
|
||
* @return The properties, may be empty, not <code>null</code>.
|
||
*/
|
||
public static AssemblerProperties getDocumentProperties(IDocument document) {
|
||
if (document == null) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'document' must not be null.");
|
||
}
|
||
String content = document.get();
|
||
AssemblerProperties properties = new AssemblerProperties();
|
||
|
||
int index1 = content.indexOf(AssemblerProperties.PREFIX);
|
||
while (index1 >= 0) {
|
||
|
||
int indexEqualSign = content.indexOf('=', index1);
|
||
int indexNewLine = content.indexOf('\n', index1);
|
||
if (indexNewLine < 0) {
|
||
indexNewLine = content.indexOf('\r', index1);
|
||
}
|
||
if (indexNewLine < 0) {
|
||
indexNewLine = content.length();
|
||
}
|
||
|
||
if (indexEqualSign >= 0 && indexEqualSign < indexNewLine) {
|
||
String key = content.substring(index1, indexEqualSign).trim();
|
||
String value = content.substring(indexEqualSign + 1,
|
||
indexNewLine).trim();
|
||
int lineNumber;
|
||
try {
|
||
lineNumber = document.getLineOfOffset(index1) + 1;
|
||
} catch (BadLocationException ex) {
|
||
lineNumber = 0;
|
||
}
|
||
properties.put(key, value, lineNumber);
|
||
}
|
||
index1 = content.indexOf(AssemblerProperties.PREFIX, indexNewLine);
|
||
}
|
||
return properties;
|
||
}
|
||
|
||
/**
|
||
* Creation is protected.
|
||
*/
|
||
protected CompilerSourceParser() {
|
||
|
||
}
|
||
|
||
/**
|
||
* Gets the compiler syntax for this parser.
|
||
*
|
||
* @return The compiler syntax, not <code>null</code>.
|
||
*/
|
||
public final CompilerSyntax getCompilerSyntax() {
|
||
if (compilerSyntax == null) {
|
||
throw new IllegalStateException(
|
||
"Field 'compilerSyntax' must not be null.");
|
||
}
|
||
return compilerSyntax;
|
||
}
|
||
|
||
/**
|
||
* Gets the instruction for the currently active CPU.
|
||
*
|
||
* @return The instruction set, not <code>null</code>.
|
||
*
|
||
* @since 1.6.1
|
||
*/
|
||
public final InstructionSet getInstructionSet() {
|
||
if (instructionSet == null) {
|
||
throw new IllegalStateException(
|
||
"Field 'instructionSet' must not be null.");
|
||
}
|
||
return instructionSet;
|
||
}
|
||
|
||
/**
|
||
* Logs a message to the error log in case logging is enabled.
|
||
*
|
||
* @param message
|
||
* The message with place holders, may be empty, not
|
||
* <code>null</code>.
|
||
* @param parameters
|
||
* The parameters for the place holders, may be empty or
|
||
* <code>null</code>.
|
||
*/
|
||
private void log(String message, Object... parameters) {
|
||
if (logEnabled) {
|
||
BasePlugin.getInstance().log(message, parameters);
|
||
}
|
||
|
||
}
|
||
|
||
/**
|
||
* Called by {@link Compiler} to link the parser to the compile syntax.
|
||
*
|
||
* @param instructionSet
|
||
* The instruction set, not <code>null</code>.
|
||
*/
|
||
public final void init(InstructionSet instructionSet) {
|
||
if (instructionSet == null) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'instructionSet' must not be null.");
|
||
}
|
||
this.instructionSet = instructionSet;
|
||
this.compilerSyntax = instructionSet.getCompilerSyntax();
|
||
|
||
}
|
||
|
||
/**
|
||
* Detects a file references in the given source line. This method is
|
||
* stateless.
|
||
*
|
||
* @param line
|
||
* The source line, may be empty, not <code>null</code>.
|
||
* @param include
|
||
* The modifiable include statement description, not
|
||
* <code>null</code>.
|
||
*/
|
||
public final void detectFileReference(String line,
|
||
CompilerSourceParserFileReference include) {
|
||
if (line == null) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'line' must not be null.");
|
||
}
|
||
if (include == null) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'include' must not be null.");
|
||
}
|
||
|
||
boolean caseSenstive = instructionSet.areInstructionsCaseSensitive();
|
||
if (!caseSenstive) {
|
||
line = line.toUpperCase();
|
||
}
|
||
|
||
// Find next (possible) quote.
|
||
int quoteOffset = -1;
|
||
Iterator<String> i = compilerSyntax.getStringDelimiters().iterator();
|
||
while (i.hasNext() && quoteOffset == -1) {
|
||
String quote = i.next();
|
||
quoteOffset = line.indexOf(quote);
|
||
}
|
||
|
||
for (Instruction instruction : instructionSet
|
||
.getFileReferenceInstructions()) {
|
||
int instructionOffset;
|
||
String instructionName;
|
||
|
||
if (caseSenstive) {
|
||
instructionName = instruction.getName();
|
||
} else {
|
||
instructionName = instruction.getUpperCaseName();
|
||
}
|
||
instructionOffset = line.indexOf(instructionName);
|
||
|
||
// Key word found before the quote?
|
||
if (quoteOffset > -1 && instructionOffset > -1
|
||
&& instructionOffset < quoteOffset) {
|
||
if (instruction.getType() == InstructionType.SOURCE_INCLUDE_DIRECTIVE) {
|
||
include.setType(CompilerSourceParserFileReferenceType.SOURCE);
|
||
} else if (instruction.getType() == InstructionType.BINARY_INCLUDE_DIRECTIVE
|
||
|| instruction.getType() == InstructionType.BINARY_OUTPUT_DIRECTIVE) {
|
||
include.setType(CompilerSourceParserFileReferenceType.BINARY);
|
||
} else {
|
||
throw new IllegalStateException("Include instruction '"
|
||
+ instructionName + "' has the unsupported type '"
|
||
+ instruction.getType() + "'");
|
||
}
|
||
|
||
include.setDirectiveEndOffset(instructionOffset
|
||
+ instructionName.length());
|
||
return;
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Enhances the file path of an include, for example adds a default extension
|
||
* for source includes.
|
||
*
|
||
* @param type
|
||
* The type of include, see
|
||
* {@link CompilerSourceParserFileReferenceType}.
|
||
*
|
||
* @param documentDirectory
|
||
* The current directory which act as the basis for relative
|
||
* paths or not <code>null</code> if it is not known.
|
||
*
|
||
* @param filePath
|
||
* The possibly relative file path of the include file in OS
|
||
* specific notation, not empty and not <code>null</code>.
|
||
* @return The enhanced absolute file path of the include file in OS
|
||
* specific notation, not empty and not <code>null</code>.
|
||
*/
|
||
public final String getIncludeAbsoluteFilePath(int type,
|
||
File documentDirectory, String filePath) {
|
||
|
||
if (filePath == null) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'filePath' must not be null.");
|
||
}
|
||
|
||
String relativeCurrentPrefix;
|
||
String relativeParentPrefix;
|
||
File absoluteFile;
|
||
relativeCurrentPrefix = ".";
|
||
relativeParentPrefix = "..";
|
||
|
||
// Ensure documentDirectory is a canonical file.
|
||
if (documentDirectory != null) {
|
||
documentDirectory = FileUtility.getCanonicalFile(documentDirectory);
|
||
}
|
||
|
||
// Check double or single dots followed by path delimiter first.
|
||
int relativeParentPrefixLength = relativeParentPrefix.length() + 1;
|
||
int relativeCurrentPrefixLength = relativeCurrentPrefix.length() + 1;
|
||
if (filePath.startsWith(relativeParentPrefix)
|
||
&& filePath.length() >= relativeParentPrefixLength) {
|
||
if (documentDirectory == null) {
|
||
return null;
|
||
}
|
||
absoluteFile = new File(documentDirectory.getParentFile(),
|
||
filePath.substring(relativeParentPrefixLength));
|
||
|
||
} else if (filePath.startsWith(relativeCurrentPrefix)
|
||
&& filePath.length() >= relativeCurrentPrefixLength) {
|
||
if (documentDirectory == null) {
|
||
return null;
|
||
}
|
||
absoluteFile = new File(documentDirectory,
|
||
filePath.substring(relativeCurrentPrefixLength));
|
||
} else {
|
||
|
||
// If there is no file separator in the file name, we can assume a
|
||
// relative path based on the current directory.
|
||
File file = new File(filePath);
|
||
if (file.exists()) {
|
||
absoluteFile = file;
|
||
} else {
|
||
absoluteFile = new File(documentDirectory, filePath);
|
||
}
|
||
}
|
||
|
||
// Ensure the complete file path is in OS notation.
|
||
absoluteFile = FileUtility.getCanonicalFile(absoluteFile);
|
||
String absoluteFilePath = absoluteFile.getPath();
|
||
if (type == CompilerSourceParserFileReferenceType.SOURCE) {
|
||
int index = absoluteFilePath.lastIndexOf(File.separator);
|
||
if (index < 1) {
|
||
index = 0;
|
||
}
|
||
String fileName = absoluteFilePath.substring(index);
|
||
index = fileName.lastIndexOf('.');
|
||
if (index == -1) {
|
||
String extension = compilerSyntax
|
||
.getSourceIncludeDefaultExtension();
|
||
if (extension.length() > 0) {
|
||
absoluteFilePath = absoluteFilePath + "." + extension;
|
||
}
|
||
}
|
||
}
|
||
return absoluteFilePath;
|
||
}
|
||
|
||
/**
|
||
* Creates a new, yet empty compiler source file.
|
||
*
|
||
* @param file
|
||
* The file, not <code>null</code>.
|
||
* @param document
|
||
* The document, not <code>null</code>.
|
||
* @return The compiler source file for use in
|
||
* {@link #parse(CompilerSourceFile, CompilerSourceParserLineCallback)}
|
||
* , not <code>null</code>.
|
||
* @since 1.6.1
|
||
*/
|
||
public final CompilerSourceFile createCompilerSourceFile(File file,
|
||
IDocument document) {
|
||
if (compilerSyntax == null) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'compilerSyntax' must not be null.");
|
||
}
|
||
if (document == null) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'document' must not be null.");
|
||
}
|
||
return new CompilerSourceFile(compilerSyntax, file, document);
|
||
}
|
||
|
||
/**
|
||
* Creates a new compiler source file for persistent file.
|
||
*
|
||
* @param filePath
|
||
* The absolute file path, not empty and not <code>null</code>.
|
||
* @return The compiler source file, not <code>null</code>.
|
||
*
|
||
* @since 1.6.3
|
||
*/
|
||
private CompilerSourceFile createCompilerSourceFile(String filePath) {
|
||
if (filePath == null) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'filePath' must not be null.");
|
||
}
|
||
if (StringUtility.isEmpty(filePath)) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'filePath' must not be empty.");
|
||
}
|
||
File newDocumentFile = new File(filePath);
|
||
String newDocumentContent;
|
||
try {
|
||
newDocumentContent = FileUtility.readString(newDocumentFile,
|
||
FileUtility.MAX_SIZE_UNLIMITED);
|
||
} catch (CoreException ex) {
|
||
newDocumentContent = compilerSyntax
|
||
.getSingleLineCommentDelimiters().get(0)
|
||
+ " "
|
||
+ ex.getMessage();
|
||
}
|
||
IDocument newDocument = new Document(newDocumentContent);
|
||
CompilerSourceFile newSourceFile = createCompilerSourceFile(
|
||
newDocumentFile, newDocument);
|
||
return newSourceFile;
|
||
}
|
||
|
||
/**
|
||
* Parse the new input and builds up the parse tree.
|
||
*
|
||
* @param compilerSourceFile
|
||
* The file to be parsed, not <code>null</code>.
|
||
* @param compilerSourceParserLineCallback
|
||
* The callback to be notified when a certain line is encountered
|
||
* or <code>null</code>.
|
||
*/
|
||
public final void parse(CompilerSourceFile compilerSourceFile,
|
||
CompilerSourceParserLineCallback compilerSourceParserLineCallback) {
|
||
if (compilerSourceFile == null) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'compilerSourceFile' must not be null.");
|
||
}
|
||
Map<String, CompilerSourceFile> parsedFiles;
|
||
parsedFiles = new HashMap<String, CompilerSourceFile>();
|
||
parseInternal(compilerSourceFile, parsedFiles,
|
||
compilerSourceParserLineCallback);
|
||
return;
|
||
}
|
||
|
||
/**
|
||
* Parse the new input and builds up the parse tree recursively with
|
||
* collecting already parsed includes.
|
||
*
|
||
* @param compilerSourceFile
|
||
* The file to be parsed, not <code>null</code>.
|
||
* @param parsedFiles
|
||
* The list of already parsed file names to prevent recursion,
|
||
* not <code>null</code>.
|
||
* @param compilerSourceParserLineCallback
|
||
* The callback to be notified when a certain line is encountered
|
||
* or <code>null</code>.
|
||
* @return <code>true</code> if the file was parsed now, <code>false</code>
|
||
* if the file is already in the list of parsed files.
|
||
*/
|
||
private boolean parseInternal(CompilerSourceFile compilerSourceFile,
|
||
Map<String, CompilerSourceFile> parsedFiles,
|
||
CompilerSourceParserLineCallback compilerSourceParserLineCallback) {
|
||
if (compilerSourceFile == null) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'compilerSourceFile' must not be null.");
|
||
}
|
||
if (parsedFiles == null) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'parsedFiles' must not be null.");
|
||
}
|
||
|
||
// Ensure every persistent file is parsed only once to prevent infinite
|
||
// recursions caused by circular includes. Non-persistent files cannot
|
||
// cause recursions since they cannot be included yet.
|
||
if (compilerSourceFile.getDocumentFile() != null) {
|
||
String key = compilerSourceFile.getDocumentFile().getPath();
|
||
if (parsedFiles.containsKey(key)) {
|
||
return false;
|
||
}
|
||
parsedFiles.put(key, compilerSourceFile);
|
||
}
|
||
|
||
this.compilerSourceFile = compilerSourceFile;
|
||
|
||
// To allow folding for introduction comment at the begin of the source,
|
||
// the definition section is always open already, even if it does not
|
||
// contain any definitions.
|
||
child = compilerSourceFile.getDefinitionSection();
|
||
beginSection(0, true);
|
||
|
||
IDocument document = compilerSourceFile.getDocument();
|
||
int lines = document.getNumberOfLines();
|
||
int lineOffset, lineLength, startOffset, endOffset;
|
||
String stringLine = "";
|
||
|
||
// Prepare line and document offsets.
|
||
lineOffset = 0;
|
||
lineLength = 0;
|
||
startOffset = 0;
|
||
endOffset = 0;
|
||
|
||
// Prepare line section buffers.
|
||
StringBuilder symbolBuffer;
|
||
StringBuilder instructionBuffer;
|
||
StringBuilder operandBuffer;
|
||
StringBuilder commentBuffer;
|
||
char blockDefinitonStartCharacter;
|
||
char blockDefinitonEndCharacter;
|
||
|
||
symbolBuffer = new StringBuilder(100);
|
||
instructionBuffer = new StringBuilder(100);
|
||
operandBuffer = new StringBuilder(100);
|
||
commentBuffer = new StringBuilder(100);
|
||
blockDefinitonStartCharacter = compilerSyntax
|
||
.getBlockDefinitionStartCharacter();
|
||
blockDefinitonEndCharacter = compilerSyntax
|
||
.getBlockDefinitionEndCharacter();
|
||
|
||
for (int lineNumber = 0; lineNumber < lines; lineNumber++) {
|
||
|
||
/**
|
||
* Part 1: Parse line segments from line string.
|
||
*/
|
||
|
||
int symbolOffset = 0;
|
||
boolean symbolOffsetFound = false;
|
||
symbolBuffer.setLength(0);
|
||
int instructionOffset = 0;
|
||
boolean instructionOffsetFound = false;
|
||
instructionBuffer.setLength(0);
|
||
int operandOffset = 0;
|
||
boolean operandOffsetFound = false;
|
||
operandBuffer.setLength(0);
|
||
int commentOffset = 0;
|
||
boolean commentOffsetFound = false;
|
||
commentBuffer.setLength(0);
|
||
|
||
try {
|
||
IRegion region = document.getLineInformation(lineNumber);
|
||
lineOffset = region.getOffset();
|
||
lineLength = region.getLength();
|
||
stringLine = document.get(lineOffset, lineLength);
|
||
|
||
int pos = 0;
|
||
char lastChar = 0;
|
||
int lineSection = LineSection.NONE;
|
||
while (pos < lineLength) {
|
||
char ch = stringLine.charAt(pos);
|
||
boolean whiteSpace = Character.isWhitespace(ch);
|
||
// Find the next word.
|
||
if (pos == 0
|
||
|| (!whiteSpace && Character.isWhitespace(lastChar))) {
|
||
|
||
// Does the current section allow instructions?
|
||
if (CompilerSourceParserTreeObjectType
|
||
.areInstructionsAllowed(section.getType())) {
|
||
if (lineSection == LineSection.NONE) {
|
||
lineSection = LineSection.SYMBOL;
|
||
} else if (lineSection == LineSection.SYMBOL) {
|
||
lineSection = LineSection.INSTRUCTION;
|
||
if (symbolBuffer.length() > 0) {
|
||
String possibleInstruction = symbolBuffer
|
||
.toString().toUpperCase();
|
||
if (isInstruction(possibleInstruction)) {
|
||
|
||
instructionOffset = symbolOffset;
|
||
instructionOffsetFound = true;
|
||
instructionBuffer.append(symbolBuffer);
|
||
symbolOffset = 0;
|
||
symbolBuffer.setLength(0);
|
||
lineSection = LineSection.OPERAND;
|
||
}
|
||
}
|
||
} else if (lineSection == LineSection.INSTRUCTION) {
|
||
lineSection = LineSection.OPERAND;
|
||
}
|
||
} else {
|
||
// No instructions allowed.
|
||
if (!symbolOffsetFound) {
|
||
if (!whiteSpace
|
||
&& lineSection == LineSection.NONE) {
|
||
lineSection = LineSection.SYMBOL;
|
||
}
|
||
} else {
|
||
lineSection = LineSection.OPERAND;
|
||
}
|
||
}
|
||
|
||
}
|
||
String type = document.getPartition(lineOffset + pos)
|
||
.getType();
|
||
if (type.equals(IDocument.DEFAULT_CONTENT_TYPE)) {
|
||
if (lineSection == LineSection.SYMBOL) {
|
||
|
||
// TODO: Does not work with kernel equates
|
||
// if (symbolBuffer.length() == 0 &&
|
||
// compilerSyntax.isIdentifierStartCharacter(ch)
|
||
// || symbolBuffer.length() > 0 &&
|
||
// compilerSyntax.isIdentifierPartCharacter(ch))
|
||
if (compilerSyntax.isIdentifierCharacter(ch)) {
|
||
if (!symbolOffsetFound) {
|
||
symbolOffsetFound = true;
|
||
symbolOffset = pos;
|
||
}
|
||
symbolBuffer.append(ch);
|
||
|
||
}
|
||
} else if (lineSection == LineSection.INSTRUCTION) {
|
||
if (!whiteSpace) {
|
||
if (!instructionOffsetFound) {
|
||
instructionOffsetFound = true;
|
||
instructionOffset = pos;
|
||
}
|
||
instructionBuffer.append(ch);
|
||
}
|
||
} else {
|
||
if (!operandOffsetFound) {
|
||
operandOffsetFound = true;
|
||
operandOffset = pos;
|
||
}
|
||
operandBuffer.append(ch);
|
||
}
|
||
} else if (type
|
||
.equals(CompilerSourcePartitionScanner.PARTITION_COMMENT_SINGLE)) {
|
||
if (!commentOffsetFound) {
|
||
commentOffsetFound = true;
|
||
commentOffset = pos;
|
||
}
|
||
// Keep spaces within comments and convert tabs to
|
||
// spaces.
|
||
if (ch == 0x9) {
|
||
ch = ' ';
|
||
}
|
||
if (ch != 0xa && ch != 0xd) {
|
||
commentBuffer.append(ch);
|
||
}
|
||
} else if (type
|
||
.equals(CompilerSourcePartitionScanner.PARTITION_STRING)) {
|
||
operandBuffer.append(ch);
|
||
}
|
||
|
||
lastChar = ch;
|
||
pos++;
|
||
}
|
||
|
||
} catch (BadLocationException ex) {
|
||
throw new RuntimeException(ex);
|
||
}
|
||
|
||
/**
|
||
* Part 2: Post processing of line segments
|
||
*/
|
||
startOffset = lineOffset;
|
||
try {
|
||
endOffset = startOffset + document.getLineLength(lineNumber);
|
||
} catch (BadLocationException ex) {
|
||
throw new RuntimeException(ex);
|
||
}
|
||
|
||
// Check if the single symbol in the line is actually an
|
||
// instruction.
|
||
String possibleInstruction = symbolBuffer.toString().toUpperCase();
|
||
if (isInstruction(possibleInstruction)) {
|
||
|
||
instructionOffset = symbolOffset;
|
||
instructionBuffer.append(symbolBuffer);
|
||
symbolOffset = 0;
|
||
symbolBuffer.setLength(0);
|
||
}
|
||
|
||
String symbol = symbolBuffer.toString();
|
||
String instruction = instructionBuffer.toString();
|
||
if (!instructionSet.areInstructionsCaseSensitive()) {
|
||
instruction = instruction.toUpperCase();
|
||
}
|
||
|
||
// Refine operand and detect block start and end.
|
||
String operand = operandBuffer.toString().trim();
|
||
blockStarting = false;
|
||
if (operandOffsetFound) {
|
||
int blockStartOffset = operand
|
||
.indexOf(blockDefinitonStartCharacter);
|
||
if (blockStartOffset > -1) {
|
||
operand = operand.substring(0, blockStartOffset);
|
||
blockStarting = true;
|
||
}
|
||
}
|
||
blockEnding = false;
|
||
int blockEndOffset = stringLine.indexOf(blockDefinitonEndCharacter);
|
||
if (blockEndOffset > -1) {
|
||
if (!commentOffsetFound || blockEndOffset < commentOffset) {
|
||
blockEnding = true;
|
||
}
|
||
}
|
||
|
||
// Refine comment. Strip leading single comment sign.
|
||
String comment = commentBuffer.toString();
|
||
if (comment.length() > 0) {
|
||
for (String singleLineCommentDelimiter : compilerSyntax
|
||
.getSingleLineCommentDelimiters()) {
|
||
if (comment.startsWith(singleLineCommentDelimiter)) {
|
||
comment = comment.substring(singleLineCommentDelimiter
|
||
.length());
|
||
}
|
||
}
|
||
comment = comment.trim();
|
||
}
|
||
|
||
/**
|
||
* Part 3: Parse labels or equates, either directly or via
|
||
* delegation.
|
||
*/
|
||
// This is an instance variable modified per line.
|
||
labelChild = null;
|
||
|
||
// Depending on the current context parsing is delegated or not.
|
||
switch (section.getType()) {
|
||
case CompilerSourceParserTreeObjectType.ENUM_DEFINITION_SECTION:
|
||
if (symbol.length() > 0) {
|
||
String value = operand.trim();
|
||
|
||
if (value.startsWith("=")) {
|
||
value = value.substring(1).trim();
|
||
}
|
||
if (value.length() == 0) {
|
||
value = "(auto)";
|
||
}
|
||
createEquateDefinitionChild(startOffset, startOffset
|
||
+ symbolOffset, symbol, value, comment);
|
||
}
|
||
break;
|
||
case CompilerSourceParserTreeObjectType.STRUCTURE_DEFINITION_SECTION:
|
||
if (symbol.length() > 0) {
|
||
createLabelDefinitionChild(startOffset, startOffset
|
||
+ symbolOffset, symbol, comment);
|
||
}
|
||
break;
|
||
default:
|
||
parseLine(startOffset, symbol, symbolOffset, instruction,
|
||
instructionOffset, operand, comment);
|
||
break;
|
||
}
|
||
|
||
/**
|
||
* Part 4: Instruction based parsing
|
||
*/
|
||
int positionStartOffset = startOffset + instructionOffset;
|
||
parseInstruction(startOffset, endOffset, positionStartOffset,
|
||
symbol, instruction, operand, comment, parsedFiles,
|
||
compilerSourceParserLineCallback);
|
||
// Label not yet consumed by instruction, so it is a single label.
|
||
if (labelChild != null) {
|
||
section.addChild(labelChild);
|
||
}
|
||
|
||
/**
|
||
* Part 6: Handle generic block starts and ends.
|
||
*/
|
||
if (blockStarting && child == null) {
|
||
// Block started but not yet converted into a child like a macro
|
||
// or procedure.
|
||
beginFolding(startOffset);
|
||
}
|
||
if (blockEnding) {
|
||
// Arbitrary block end, can be section or folding.
|
||
if (compilerSourceFile.isFoldingForSection()) {
|
||
endSection(startOffset + blockEndOffset);
|
||
} else {
|
||
endFolding(startOffset + blockEndOffset);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Part 7: Callback for dedicated source file lines.
|
||
*/
|
||
// Was the current file and line relevant for the callback?
|
||
if (compilerSourceParserLineCallback != null
|
||
&& compilerSourceParserLineCallback.getSourceFilePath()
|
||
.equals(compilerSourceFile.getDocumentFile()
|
||
.getPath())
|
||
&& lineNumber == compilerSourceParserLineCallback
|
||
.getLineNumber()) {
|
||
compilerSourceParserLineCallback.processLine(this,
|
||
compilerSourceFile, lineNumber, startOffset,
|
||
symbolOffset, isInstruction(instruction),
|
||
instructionOffset, instruction, operandOffset, section);
|
||
}
|
||
}
|
||
|
||
// End last section.
|
||
endSection(document.getLength() > 0 ? document.getLength() - 1 : 0);
|
||
|
||
// End incomplete sections.
|
||
compilerSourceFile.endAllSections();
|
||
|
||
// End incomplete sections.
|
||
compilerSourceFile.endAllFoldings();
|
||
|
||
return true;
|
||
}
|
||
|
||
private boolean isInstruction(String instructionName) {
|
||
if (instructionName == null) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'instructionName' must not be null.");
|
||
}
|
||
boolean result;
|
||
result = instructionSet.getInstruction(instructionName) != null;
|
||
return result;
|
||
}
|
||
|
||
protected void parseLine(int startOffset, String symbol, int symbolOffset,
|
||
String instruction, int instructionOffset, String operand,
|
||
String comment) {
|
||
return;
|
||
}
|
||
|
||
protected final void createEquateDefinitionChild(int startOffset,
|
||
int positionStartOffset, String symbol, String operand,
|
||
String comment) {
|
||
ensureDefinitionSection(startOffset, positionStartOffset);
|
||
createChild(positionStartOffset,
|
||
CompilerSourceParserTreeObjectType.EQUATE_DEFINITION, symbol,
|
||
symbol + " = " + operand, comment);
|
||
section.addChild(child);
|
||
|
||
}
|
||
|
||
protected final void createLabelDefinitionChild(int startOffset,
|
||
int positionStartOffset, String symbol, String comment) {
|
||
createChild(positionStartOffset,
|
||
CompilerSourceParserTreeObjectType.LABEL_DEFINITION, symbol,
|
||
symbol, comment);
|
||
// Remember the label child. It will be added only if the label is not
|
||
// consumed by some other instruction.
|
||
labelChild = child;
|
||
}
|
||
|
||
protected final void beginImplementationSection(int startOffset,
|
||
int positionStartOffset, String operand, String comment) {
|
||
|
||
if (section.getParent() != null) {
|
||
return;
|
||
}
|
||
int endOffset = startOffset - 1;
|
||
if (endOffset < 0) {
|
||
endOffset = 0;
|
||
}
|
||
endSection(endOffset);
|
||
|
||
// Folding for an implementation section should only bebe active if
|
||
// there is<69>a name section in the code.
|
||
boolean withFolding = (StringUtility.isSpecified(operand));
|
||
if (StringUtility.isEmpty(operand)) {
|
||
operand = "Implementation Section";
|
||
}
|
||
|
||
createChild(positionStartOffset,
|
||
CompilerSourceParserTreeObjectType.IMPLEMENTATION_SECTION,
|
||
operand, operand, comment);
|
||
|
||
// This will always be a top level section.
|
||
beginSection(startOffset, withFolding);
|
||
compilerSourceFile.getImplementationSections().add(section);
|
||
labelChild = null;
|
||
}
|
||
|
||
/**
|
||
* Parse the instruction in a single line.
|
||
*
|
||
* @param startOffset
|
||
* The start offset of the line.
|
||
* @param endOffset
|
||
* The end offset of the line, including its last character and
|
||
* the line delimiter is available.
|
||
* @param positionStartOffset
|
||
* The start offset for positioning the cursor.
|
||
* @param symbol
|
||
* The symbol name, may be empty, not <code>null</code>.
|
||
* @param instructionName
|
||
* The instruction name, may be empty, not <code>null</code>.
|
||
* @param operand
|
||
* The operand, may be empty, not <code>null</code>.
|
||
* @param comment
|
||
* The comment, may be empty, not <code>null</code>.
|
||
* @param parsedFiles
|
||
* The parsed files, not <code>null</code>.
|
||
* @param compilerSourceParserLineCallback
|
||
* The callback to be notified when a certain line is encountered
|
||
* or <code>null</code>.
|
||
*/
|
||
private void parseInstruction(int startOffset, int endOffset,
|
||
int positionStartOffset, String symbol, String instructionName,
|
||
String operand, String comment,
|
||
Map<String, CompilerSourceFile> parsedFiles,
|
||
CompilerSourceParserLineCallback compilerSourceParserLineCallback) {
|
||
|
||
if (StringUtility.isEmpty(instructionName)) {
|
||
return;
|
||
}
|
||
Instruction instruction = instructionSet
|
||
.getInstruction(instructionName);
|
||
|
||
if (instruction == null) {
|
||
return;
|
||
}
|
||
|
||
if (logEnabled) {
|
||
log("parseInstruction: startOffset={0} endOffset={1} positionStartOffset={2}, symbol={3} instructionName={4} symbol={5} instructionType={6} labelChild={7}",
|
||
Integer.toString(startOffset), Integer.toString(endOffset),
|
||
Integer.toString(positionStartOffset), symbol,
|
||
instructionName, operand,
|
||
Integer.toString(instruction.getType()), labelChild);
|
||
}
|
||
|
||
String symbolOrOperand;
|
||
String symbolOrOperandFirstWord;
|
||
symbolOrOperand = symbol;
|
||
if (StringUtility.isEmpty(symbolOrOperand)) {
|
||
symbolOrOperand = operand;
|
||
}
|
||
symbolOrOperandFirstWord = symbolOrOperand;
|
||
int i = 0;
|
||
for (; i < symbolOrOperandFirstWord.length(); i++) {
|
||
if (Character.isWhitespace(symbolOrOperandFirstWord.charAt(i))) {
|
||
break;
|
||
}
|
||
}
|
||
symbolOrOperandFirstWord = symbolOrOperandFirstWord.substring(0, i);
|
||
|
||
switch (instruction.getType()) {
|
||
case InstructionType.BEGIN_IMPLEMENTATION_SECTION_DIRECTIVE:
|
||
beginImplementationSection(startOffset, positionStartOffset,
|
||
operand, comment);
|
||
break;
|
||
case InstructionType.DIRECTIVE:
|
||
if (blockStarting) {
|
||
beginFolding(startOffset);
|
||
}
|
||
break;
|
||
case InstructionType.BEGIN_FOLDING_BLOCK_DIRECTIVE:
|
||
beginFolding(startOffset);
|
||
break;
|
||
case InstructionType.END_FOLDING_BLOCK_DIRECTIVE:
|
||
endFolding(endOffset);
|
||
break;
|
||
case InstructionType.END_SECTION_DIRECTIVE:
|
||
endSection(endOffset);
|
||
break;
|
||
|
||
case InstructionType.BEGIN_ENUM_DEFINITION_SECTION_DIRECTIVE:
|
||
ensureDefinitionSection(startOffset, positionStartOffset);
|
||
createChild(positionStartOffset,
|
||
CompilerSourceParserTreeObjectType.ENUM_DEFINITION_SECTION,
|
||
symbolOrOperandFirstWord, symbolOrOperand, comment);
|
||
beginSection(startOffset, true);
|
||
break;
|
||
|
||
case InstructionType.BEGIN_STRUCTURE_DEFINITION_SECTION_DIRECTIVE:
|
||
ensureDefinitionSection(startOffset, positionStartOffset);
|
||
createChild(
|
||
positionStartOffset,
|
||
CompilerSourceParserTreeObjectType.STRUCTURE_DEFINITION_SECTION,
|
||
symbolOrOperandFirstWord, symbolOrOperand, comment);
|
||
beginSection(startOffset, true);
|
||
break;
|
||
|
||
case InstructionType.BEGIN_LOCAL_SECTION_DIRECTIVE:
|
||
ensureImplementationSection(startOffset, positionStartOffset);
|
||
createChild(positionStartOffset,
|
||
CompilerSourceParserTreeObjectType.LOCAL_SECTION,
|
||
symbolOrOperandFirstWord, symbolOrOperand, comment);
|
||
beginSection(startOffset, true);
|
||
|
||
break;
|
||
case InstructionType.BEGIN_MACRO_DEFINITION_SECTION_DIRECTIVE:
|
||
ensureDefinitionSection(startOffset, positionStartOffset);
|
||
createChild(
|
||
positionStartOffset,
|
||
CompilerSourceParserTreeObjectType.MACRO_DEFINITION_SECTION,
|
||
symbolOrOperandFirstWord, symbolOrOperand, comment);
|
||
beginSection(startOffset, true);
|
||
break;
|
||
case InstructionType.BEGIN_PROCEDURE_DEFINITION_SECTION_DIRECTIVE:
|
||
ensureImplementationSection(startOffset, positionStartOffset);
|
||
createChild(
|
||
positionStartOffset,
|
||
CompilerSourceParserTreeObjectType.PROCEDURE_DEFINITION_SECTION,
|
||
symbolOrOperandFirstWord, symbolOrOperand, comment);
|
||
beginSection(startOffset, true);
|
||
break;
|
||
case InstructionType.BEGIN_PAGES_SECTION_DIRECTIVE:
|
||
ensureImplementationSection(startOffset, positionStartOffset);
|
||
createChild(positionStartOffset,
|
||
CompilerSourceParserTreeObjectType.PAGES_SECTION, "",
|
||
symbolOrOperand, comment);
|
||
beginSection(startOffset, true);
|
||
break;
|
||
case InstructionType.BEGIN_REPEAT_SECTION_DIRECTIVE:
|
||
createChild(positionStartOffset,
|
||
CompilerSourceParserTreeObjectType.REPEAT_SECTION, "",
|
||
symbolOrOperand, comment);
|
||
beginSection(startOffset, true);
|
||
break;
|
||
|
||
case InstructionType.SOURCE_INCLUDE_DIRECTIVE:
|
||
// Remove leading and trailing string delimiters.
|
||
String filePath = operand;
|
||
for (String stringDelimiter : compilerSyntax.getStringDelimiters()) {
|
||
if (filePath.startsWith(stringDelimiter)) {
|
||
filePath = filePath.substring(stringDelimiter.length());
|
||
break;
|
||
}
|
||
}
|
||
for (String stringDelimiter : compilerSyntax.getStringDelimiters()) {
|
||
if (filePath.endsWith(stringDelimiter)) {
|
||
filePath = filePath.substring(0, filePath.length()
|
||
- stringDelimiter.length());
|
||
break;
|
||
}
|
||
}
|
||
filePath = getIncludeAbsoluteFilePath(
|
||
CompilerSourceParserFileReferenceType.SOURCE,
|
||
compilerSourceFile.getDocumentDirectory(), filePath);
|
||
|
||
createChild(positionStartOffset,
|
||
CompilerSourceParserTreeObjectType.SOURCE_INCLUDE, "",
|
||
operand, comment);
|
||
|
||
// If there is no file, the include is a normal child.
|
||
if (filePath == null) {
|
||
if (labelChild == null) {
|
||
section.addChild(child);
|
||
} else {
|
||
labelChild.addChild(child);
|
||
}
|
||
} else {
|
||
// If there is a file, the include is a section.
|
||
beginSection(startOffset, true);
|
||
|
||
// Preserve current line specific state into local variables.
|
||
CompilerSourceFile oldSourceFile = compilerSourceFile;
|
||
CompilerSourceParserTreeObject oldSection = section;
|
||
CompilerSourceParserTreeObject oldChild = child;
|
||
CompilerSourceParserTreeObject oldLabelChild = labelChild;
|
||
boolean oldBlockStarting = blockStarting;
|
||
boolean oldBlockEnding = blockEnding;
|
||
|
||
CompilerSourceFile newSourceFile = createCompilerSourceFile(filePath);
|
||
|
||
// This might be moved to the createCompilerSourceFile() method.
|
||
CompilerSourcePartitionScanner partitionScanner = new CompilerSourcePartitionScanner(
|
||
compilerSyntax);
|
||
partitionScanner.createDocumentPartitioner(newSourceFile
|
||
.getDocument());
|
||
boolean parsed = parseInternal(newSourceFile, parsedFiles,
|
||
compilerSourceParserLineCallback);
|
||
|
||
if (parsed) {
|
||
// Restore old line specific state from local variables.
|
||
section = oldSection;
|
||
compilerSourceFile = oldSourceFile;
|
||
child = oldChild;
|
||
labelChild = oldLabelChild;
|
||
blockStarting = oldBlockStarting;
|
||
blockEnding = oldBlockEnding;
|
||
|
||
section.setIncludedCompilerSourceFile(newSourceFile);
|
||
List<CompilerSourceParserTreeObject> newSourceFileSections = newSourceFile
|
||
.getSections();
|
||
if (newSourceFileSections.size() == 1
|
||
&& newSourceFileSections.get(0).getType() == CompilerSourceParserTreeObjectType.SOURCE_INCLUDE) {
|
||
newSourceFileSections = newSourceFileSections.get(0)
|
||
.getChildren();
|
||
}
|
||
for (CompilerSourceParserTreeObject newChild : newSourceFileSections) {
|
||
section.addChild(newChild);
|
||
}
|
||
} else {
|
||
AssemblerPlugin
|
||
.getInstance()
|
||
.log("Include file '{0}' was already parsed. Stopping recursion.",
|
||
new Object[] { newSourceFile
|
||
.getDocumentFile().getPath() });
|
||
}
|
||
endSection(endOffset);
|
||
}
|
||
break;
|
||
case InstructionType.BINARY_INCLUDE_DIRECTIVE:
|
||
ensureImplementationSection(startOffset, positionStartOffset);
|
||
createChild(positionStartOffset,
|
||
CompilerSourceParserTreeObjectType.BINARY_INCLUDE, "",
|
||
operand, comment);
|
||
if (labelChild == null) {
|
||
section.addChild(child);
|
||
} else {
|
||
labelChild.addChild(child);
|
||
}
|
||
|
||
break;
|
||
case InstructionType.BINARY_OUTPUT_DIRECTIVE:
|
||
|
||
createChild(positionStartOffset,
|
||
CompilerSourceParserTreeObjectType.BINARY_OUTPUT, "",
|
||
operand, comment);
|
||
if (labelChild == null) {
|
||
section.addChild(child);
|
||
} else {
|
||
labelChild.addChild(child);
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
}
|
||
|
||
private void ensureDefinitionSection(int startOffset,
|
||
int positionStartOffset) {
|
||
// To allow folding for introduction comment at the begin of the source,
|
||
// the definition section is always open already.
|
||
}
|
||
|
||
private void ensureImplementationSection(int startOffset,
|
||
int positionStartOffset) {
|
||
if (section.getType() == CompilerSourceParserTreeObjectType.DEFINITION_SECTION) {
|
||
beginImplementationSection(startOffset, positionStartOffset, "", "");
|
||
}
|
||
}
|
||
|
||
private void createChild(int startOffset, int type, String name,
|
||
String displayName, String comment) {
|
||
if (startOffset < 0) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'startOffset' must not be negative. Specified value is "
|
||
+ startOffset + ".");
|
||
}
|
||
if (name == null) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'name' must not be null.");
|
||
}
|
||
if (displayName == null) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'displayName' must not be null.");
|
||
}
|
||
if (comment == null) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'comment' must not be null.");
|
||
}
|
||
|
||
child = new CompilerSourceParserTreeObject(compilerSourceFile,
|
||
startOffset, type, name, displayName, comment);
|
||
}
|
||
|
||
/**
|
||
* Starts a new active section.
|
||
*
|
||
* @param startOffset
|
||
* The start offset, a non-negative integer.
|
||
* @param withFolding
|
||
* <code>true</code> if the section is also a folding section,
|
||
* <code>false</code> otherwise.
|
||
*/
|
||
private void beginSection(int startOffset, boolean withFolding) {
|
||
if (startOffset < 0) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'startOffset' must not be negative. Specified value is "
|
||
+ startOffset + ".");
|
||
}
|
||
if (child == null) {
|
||
throw new IllegalStateException("Field 'child' must not be null.");
|
||
}
|
||
section = child;
|
||
compilerSourceFile.beginSection(startOffset, section, withFolding);
|
||
labelChild = null;
|
||
blockStarting = false;
|
||
}
|
||
|
||
/**
|
||
* Ends the currently active section.
|
||
*
|
||
* @param endOffset
|
||
* The end offset, a non-negative integer.
|
||
*/
|
||
private void endSection(int endOffset) {
|
||
if (section == null) {
|
||
throw new IllegalStateException(
|
||
"Variable 'section' must not be null.");
|
||
}
|
||
if (endOffset < 0) {
|
||
throw new IllegalArgumentException(
|
||
"Parameter 'endOffset' must not be negative. Specified value is "
|
||
+ endOffset + ".");
|
||
}
|
||
CompilerSourceParserTreeObject section;
|
||
section = compilerSourceFile.endSection(endOffset);
|
||
if (section != null) {
|
||
this.section = section;
|
||
}
|
||
labelChild = null;
|
||
blockEnding = false;
|
||
}
|
||
|
||
/**
|
||
* Starts a new active folding which does not belong to a section.
|
||
*
|
||
* @param startOffset
|
||
* The start offset, a non-negative integer.
|
||
*/
|
||
private void beginFolding(int startOffset) {
|
||
compilerSourceFile.beginFolding(startOffset, false);
|
||
blockStarting = false;
|
||
}
|
||
|
||
/**
|
||
* Ends the currently active folding which does not belong to a section.
|
||
*
|
||
* @param endOffset
|
||
* The end offset, a non-negative integer. This must be the
|
||
* actual offset of the last character in the line, including the
|
||
* line end delimiter, if present.
|
||
*/
|
||
private void endFolding(int endOffset) {
|
||
compilerSourceFile.endFolding(endOffset);
|
||
blockEnding = false;
|
||
}
|
||
}
|