/** * 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.ast; import java.util.ArrayList; import java.util.List; import de.codesourcery.jasm16.compiler.MacroNameSymbol; import de.codesourcery.jasm16.exceptions.ParseException; import de.codesourcery.jasm16.lexer.Lexer.ParseOffset; import de.codesourcery.jasm16.lexer.TokenType; import de.codesourcery.jasm16.parser.IParseContext; import de.codesourcery.jasm16.parser.Identifier; import de.codesourcery.jasm16.utils.ITextRegion; import de.codesourcery.jasm16.utils.Line; import de.codesourcery.jasm16.utils.TextRegion; public class StartMacroNode extends ASTNode { private Identifier name; private String macroBody; private ParseOffset bodyParseOffset; public StartMacroNode() { } private StartMacroNode(StartMacroNode macroNode) { this.name = macroNode.name; this.macroBody = macroNode.macroBody; this.bodyParseOffset = macroNode.bodyParseOffset != null ? new ParseOffset( macroNode.bodyParseOffset ) : null; } public Identifier getMacroName() { return name; } @Override protected ASTNode copySingleNode() { return new StartMacroNode(this ); } public List<Identifier> getArgumentNames() { if ( hasArgumentList() ) { return getArgumentsNode().getArgumentNames(); } return new ArrayList<>(); } public boolean hasArgumentList() { return hasChildren() && child(0) instanceof MacroParametersListNode; } private MacroParametersListNode getArgumentsNode() { return (MacroParametersListNode) child(0); } public int getArgumentCount() { if ( hasArgumentList() ) { return getArgumentsNode().getArgumentCount(); } return 0; } @Override public boolean supportsChildNodes() { return true; } @Override protected ASTNode parseInternal(IParseContext context) throws ParseException { final ITextRegion region = new TextRegion( context.read(TokenType.START_MACRO ) ); try { context.mark(); region.merge( context.skipWhitespace( false ) ); ITextRegion idRegion = new TextRegion( context.currentParseIndex() , 0 ); this.name = context.parseIdentifier( idRegion , false); region.merge( idRegion ); if ( context.isParsingMacroDefinition() ) { context.addCompilationError( "Already within macro definition of '"+context.getCurrentMacroDefinition().getMacroName()+"() , nested definitions are not allowed", this ); } // update IParseContext AFTER we assigned the macro name (just in case somebody queries for the macro name ) context.setCurrentMacroDefinition( this ); if ( context.getSymbolTable().containsSymbol( this.name , null ) ) { context.addCompilationError( "Macro name clashes with already defined symbol", this ); } else { // create symbol in global scope if ( ! context.isKeyword( this.name.getRawValue() ) ) { context.getSymbolTable().defineSymbol( new MacroNameSymbol( this , context.getCompilationUnit() , idRegion , this.name ) ); } else { context.addCompilationError( "Invalid macro name, clashes with keyword", this ); } } region.merge( context.skipWhitespace( false) ); if ( ! context.eof() && context.peek( TokenType.PARENS_OPEN ) ) { addChild( new MacroParametersListNode().parse(context) , context ); } region.merge( context.skipWhitespace(false) ); this.macroBody = ""; if ( ! context.eof() && context.peek().isEOL() && ! isNextLineEndOfMacro( context ) ) { // consume newline region.merge( context.read() ); } // lexer is now at start of next line after .macro this.bodyParseOffset = new ParseOffset( context.currentParseIndex() , context.getCurrentLineNumber() , context.getCurrentLineStartOffset() ); final StringBuilder buffer = new StringBuilder(); while ( ! context.eof() && ! isNextLineEndOfMacro( context ) ) { final int lineNumber = context.getCurrentLineNumber(); final int lineOffset = context.getCurrentLineStartOffset(); RawLineNode line = (RawLineNode) new RawLineNode().parse( context ); if ( line.getContents().length() > 0 ) { context.getCompilationUnit().setLine( new Line(lineNumber,lineOffset ) ); addChild( line , context ); buffer.append( line.getContents() ); } if ( ! context.eof() && context.peek().isEOL() && ! isNextLineEndOfMacro( context ) ) { buffer.append( context.read().getContents() ); // consume newline } } this.macroBody = buffer.toString(); } catch(Exception e) { addCompilationErrorAndAdvanceParser( e , context ); return this; } finally { context.clearMark(); } mergeWithAllTokensTextRegion( region ); return this; } private boolean isNextLineEndOfMacro(IParseContext context) { if ( context.eof() ) { return true; } if ( ! context.eof() && context.peek().isEOL() ) { context.mark(); context.read(); // advance so we can peek at next token if ( context.eof() || context.peek(TokenType.END_MACRO ) ) { context.reset(); return true; } context.reset(); context.clearMark(); } return false; } public String getMacroBody() { return macroBody; } public ParseOffset getBodyParseOffset() { return this.bodyParseOffset; } }