/** * Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.codesourcery.jasm16.parser; import java.io.IOException; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.apache.log4j.Logger; import de.codesourcery.jasm16.ast.ASTNode; import de.codesourcery.jasm16.ast.StartMacroNode; import de.codesourcery.jasm16.compiler.CompilationError; import de.codesourcery.jasm16.compiler.ICompilationUnit; import de.codesourcery.jasm16.compiler.ICompilationUnitResolver; import de.codesourcery.jasm16.compiler.IMarker; import de.codesourcery.jasm16.compiler.ISymbol; import de.codesourcery.jasm16.compiler.ISymbolTable; import de.codesourcery.jasm16.compiler.io.IResource; import de.codesourcery.jasm16.compiler.io.IResourceResolver; import de.codesourcery.jasm16.exceptions.CircularSourceIncludeException; import de.codesourcery.jasm16.exceptions.EOFException; import de.codesourcery.jasm16.exceptions.ParseException; import de.codesourcery.jasm16.exceptions.ResourceNotFoundException; import de.codesourcery.jasm16.lexer.ILexer; import de.codesourcery.jasm16.lexer.IToken; import de.codesourcery.jasm16.lexer.Lexer; import de.codesourcery.jasm16.lexer.TokenType; import de.codesourcery.jasm16.parser.IParser.ParserOption; import de.codesourcery.jasm16.scanner.Scanner; import de.codesourcery.jasm16.utils.ITextRegion; import de.codesourcery.jasm16.utils.Misc; /** * Default {@link IParseContext} implementation. * * @author tobias.gierke@code-sourcery.de */ public class ParseContext implements IParseContext { private static final Logger LOG = Logger.getLogger( ParseContext.class ); private final ICompilationUnit unit; private final ISymbolTable symbolTable; private final ILexer lexer; private final IResourceResolver resourceResolver; private final ICompilationUnitResolver compilationUnitResolver; private final Set<ParserOption> options = new HashSet<ParserOption>(); private final StartMacroNode currentlyExpandingMacro; // values are IResource#getIdentifier() values private final LinkedHashSet<String> includedSourceFiles; private ISymbol lastGlobalSymbol; private boolean recoveringFromParseError; private StartMacroNode macroBeingParsed; public ParseContext(ICompilationUnit unit , ISymbolTable symbolTable, ILexer lexer, IResourceResolver resourceResolver, ICompilationUnitResolver compilationUnitResolver, Set<ParserOption> options, StartMacroNode currentlyExpandingMacro) { this( unit , symbolTable , lexer ,resourceResolver , compilationUnitResolver , options , new LinkedHashSet<String>() , currentlyExpandingMacro ); } protected ParseContext(ICompilationUnit unit , ISymbolTable symbolTable, ILexer lexer, IResourceResolver resourceResolver, ICompilationUnitResolver compilationUnitResolver, Set<ParserOption> options, LinkedHashSet<String> includedSourceFiles, StartMacroNode currentlyExpandingMacro) { if (lexer == null) { throw new IllegalArgumentException("lexer must not be NULL"); } if ( unit == null ) { throw new IllegalArgumentException("unit must not be NULL"); } if ( symbolTable == null ) { throw new IllegalArgumentException("symbolTable must not be NULL."); } if ( compilationUnitResolver == null ) { throw new IllegalArgumentException("compilationUnitResolver must not be NULL"); } if ( resourceResolver == null ) { throw new IllegalArgumentException("resourceResolver must not be NULL."); } if ( options == null ) { throw new IllegalArgumentException("options must not be NULL."); } this.includedSourceFiles = includedSourceFiles; this.options.addAll( options ); this.resourceResolver = resourceResolver; this.symbolTable = symbolTable; this.unit = unit; this.lexer = lexer; this.compilationUnitResolver = compilationUnitResolver; this.currentlyExpandingMacro = currentlyExpandingMacro; } @Override public List<IToken> advanceTo(TokenType[] expectedTypes, boolean advancePastMatchedToken) { return lexer.advanceTo(expectedTypes, advancePastMatchedToken); } @Override public void clearMark() { lexer.clearMark(); } @Override public int currentParseIndex() { return lexer.currentParseIndex(); } @Override public boolean eof() { return lexer.eof(); } @Override public void mark() { lexer.mark(); } @Override public IToken peek() throws EOFException { return lexer.peek(); } @Override public boolean peek(TokenType t) throws EOFException { return lexer.peek(t); } @Override public IToken read() throws EOFException { return lexer.read(); } @Override public IToken read(TokenType expectedType) throws ParseException, EOFException { return lexer.read(expectedType); } @Override public void reset() { lexer.reset(); } @Override public ICompilationUnit getCompilationUnit() { return unit; } @Override public ISymbolTable getSymbolTable() { return symbolTable; } @Override public int getCurrentLineNumber() { return lexer.getCurrentLineNumber(); } @Override public int getCurrentLineStartOffset() { return lexer.getCurrentLineStartOffset(); } @Override public String toString() { return eof() ? "Parser is at EOF" : peek().toString()+" ( offset "+currentParseIndex()+" )"; } public Identifier parseIdentifier(ITextRegion range,boolean localLabelsAllowed) throws EOFException, ParseException { if ( eof() || ! peek().hasType( TokenType.CHARACTERS ) ) { if ( ! eof() && isKeyword( peek().getContents() ) ) { throw new ParseException("Not a valid identifier" , peek() ); } if ( ! eof() && peek().hasType(TokenType.DOT ) ) { if ( ! localLabelsAllowed ) { throw new ParseException("Support for local labels disabled by configuration" , currentParseIndex() ,0 ); } // fall-through } else { throw new ParseException("Expected an identifier" , currentParseIndex() ,0 ); } } int startOffset = currentParseIndex(); String chars = ""; IToken token = peek(); if ( token.hasType( TokenType.DOT ) ) { chars += read().getContents(); if ( range != null ) { range.merge( token ); } } token = read( TokenType.CHARACTERS ); if ( range != null ) { range.merge( token ); } chars += token.getContents(); int i = 0; for ( char c : chars.toCharArray() ) { if ( i == 0 && c == '.' ) { // special case: local label continue; } if ( ! Identifier.isValidIdentifierChar( c ) ) { throw new ParseException("Character '"+c+"' is not allowed within an identifier", startOffset+i , 1 ); } i++; } if ( isKeyword( chars ) ) { throw new ParseException("Keywords are not allowed as identifier" , startOffset , chars.length() ); } Identifier.assertValidIdentifier( chars , token ); return new Identifier( chars ); } @Override public ITextRegion parseWhitespace() throws EOFException, ParseException { return read( TokenType.WHITESPACE ); } @Override public boolean isRecoveringFromParseError() { return recoveringFromParseError; } @Override public void setRecoveringFromParseError(boolean recoveringFromParseError) { this.recoveringFromParseError = recoveringFromParseError; } @Override public IResource resolve(String identifier) throws ResourceNotFoundException { return this.resourceResolver.resolve( identifier ); } @Override public IResource resolveRelative(String identifier, IResource parent) throws ResourceNotFoundException { return this.resourceResolver.resolveRelative( identifier , parent ); } @Override public void addMarker(IMarker marker) { getCompilationUnit().addMarker( marker ); } @Override public void addCompilationError(String message, ASTNode node) { final CompilationError error = new CompilationError( message , getCompilationUnit() , node ); error.setLineNumber( getCurrentLineNumber() ); error.setLineStartOffset( getCurrentLineStartOffset() ); addMarker( error ); } @Override public IToken read(String errorMessage, TokenType expectedType) throws ParseException, EOFException { return lexer.read(errorMessage, expectedType); } public void setParserOption(ParserOption option, boolean onOff) { if ( option == null ) { throw new IllegalArgumentException("option must not be NULL."); } if ( onOff ) { options.add( option ); } else { options.remove(option); } } @Override public boolean hasParserOption(ParserOption option) { if (option == null) { throw new IllegalArgumentException("option must not be NULL."); } return options.contains( option ); } @Override public void setLexerOption(LexerOption option, boolean onOff) { lexer.setLexerOption( option, onOff); } @Override public boolean hasLexerOption(LexerOption option) { return lexer.hasLexerOption( option ); } @Override public ICompilationUnit getCompilationUnitFor(IResource resource) throws IOException { return compilationUnitResolver.getCompilationUnit( resource ); } @Override public IParseContext createParseContextForInclude(IResource resource) throws IOException { final String source = Misc.readSource( resource ); final ICompilationUnit unit = compilationUnitResolver.getOrCreateCompilationUnit( resource ); if ( includedSourceFiles.contains( resource.getIdentifier() ) ) { final StringBuilder cycle = new StringBuilder(); for (Iterator<String> it = includedSourceFiles.iterator(); it.hasNext();) { String id = it.next(); cycle.append( id ); if ( it.hasNext() ) { cycle.append(" <-> "); } } final String errorMsg ="Circular includes detected while parsing: "+getCompilationUnit().getResource()+" <-> "+ cycle; LOG.error("createParseContextForInclude(): "+errorMsg); throw new CircularSourceIncludeException( errorMsg , getCompilationUnit() ); } includedSourceFiles.add( resource.getIdentifier() ); getCompilationUnit().addDependency( unit ); final ILexer lexer = new Lexer( new Scanner( source ) ); IParseContext result = new ParseContext(unit, symbolTable, lexer, resourceResolver,compilationUnitResolver , options , this.includedSourceFiles , this.currentlyExpandingMacro); return result; } @Override public String parseString(ITextRegion region) throws EOFException, ParseException { final IToken delimiter= read( TokenType.STRING_DELIMITER ); if ( region != null ) { region.merge( delimiter ); } final StringBuilder contents = new StringBuilder(); final String delimiterString = delimiter.getContents(); boolean characterQuoted = false; do { if ( eof() ) { throw new ParseException("Unexpected EOF while looking for closing string delimiter",currentParseIndex(),0); } IToken tok = peek(); if ( tok.isEOL() ) { throw new ParseException("Unexpected EOL while looking for closing string delimiter",currentParseIndex(),0); } if ( tok.hasType( TokenType.STRING_ESCAPE ) && ! characterQuoted ) { read(); characterQuoted = true; if ( region != null ) { region.merge( tok ); } continue; } if ( ! characterQuoted && tok.hasType( TokenType.STRING_DELIMITER ) && tok.getContents().equals( delimiterString ) ) { break; } read(); if ( region != null ) { region.merge( tok ); } contents.append( tok.getContents() ); characterQuoted = false; } while ( true ); if ( region != null ) { region.merge( read(TokenType.STRING_DELIMITER) ); } return contents.toString(); } @Override public void storePreviousGlobalSymbol(ISymbol globalSymbol) { if ( globalSymbol != null && globalSymbol.isLocalSymbol() ) { throw new IllegalArgumentException("You need to pass a GLOBAL symbol, not "+globalSymbol); } this.lastGlobalSymbol = globalSymbol; } @Override public ISymbol getPreviousGlobalSymbol() { return lastGlobalSymbol; } @Override public List<IToken> skipWhitespace(boolean skipEOL) { return lexer.skipWhitespace(skipEOL); } @Override public void setCurrentMacroDefinition(StartMacroNode node) { macroBeingParsed=node; } @Override public StartMacroNode getCurrentMacroDefinition() { return macroBeingParsed; } @Override public StartMacroNode getCurrentlyExpandingMacro() { return this.currentlyExpandingMacro; } @Override public boolean isParsingMacroDefinition() { return macroBeingParsed != null; } @Override public boolean isKeyword(String s) { return lexer.isKeyword(s); } }