wudsn-ide/com.wudsn.ide.lng/src/com/wudsn/ide/lng/compiler/parser/CompilerSourceParser.java
2023-02-27 01:19:09 +01:00

1067 lines
38 KiB
Java
Raw Blame History

/**
* Copyright (C) 2009 - 2021 <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.lng.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.base.BasePlugin;
import com.wudsn.ide.base.common.FileUtility;
import com.wudsn.ide.base.common.StringUtility;
import com.wudsn.ide.lng.LanguageAnnotationValues;
import com.wudsn.ide.lng.LanguagePlugin;
import com.wudsn.ide.lng.compiler.Compiler;
import com.wudsn.ide.lng.compiler.syntax.CompilerSyntax;
import com.wudsn.ide.lng.compiler.syntax.Instruction;
import com.wudsn.ide.lng.compiler.syntax.InstructionSet;
import com.wudsn.ide.lng.compiler.syntax.InstructionType;
/**
* 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 LanguageAnnotationValues} properties from a document.
*
* @param document The document, not <code>null</code>.
* @return The properties, may be empty, not <code>null</code>.
*/
public static LanguageAnnotationValues getAnnotationValues(IDocument document) {
if (document == null) {
throw new IllegalArgumentException("Parameter 'document' must not be null.");
}
var result = LanguageAnnotationValues.parseDocument(document);
return result;
}
/**
* 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 Target.
*
* @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 {
LanguagePlugin.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;
}
}