/** * 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.io.IOException; import java.util.ArrayList; import java.util.List; import de.codesourcery.jasm16.Address; import de.codesourcery.jasm16.compiler.CompilationError; import de.codesourcery.jasm16.compiler.ICompilationContext; import de.codesourcery.jasm16.compiler.ICompilationUnit; import de.codesourcery.jasm16.compiler.ISymbolTable; import de.codesourcery.jasm16.compiler.io.IObjectCodeWriter; import de.codesourcery.jasm16.exceptions.ParseException; import de.codesourcery.jasm16.lexer.IToken; import de.codesourcery.jasm16.lexer.TokenType; import de.codesourcery.jasm16.parser.IParseContext; import de.codesourcery.jasm16.parser.Operator; /** * An AST node that represents values for initialized memory (.dat /.word/.byte). * * @author tobias.gierke@code-sourcery.de */ public class InitializedMemoryNode extends ObjectCodeOutputNode implements IPreprocessorDirective { public enum AllowedSize { BYTE { @Override public int getMaxSupportedValue() { return 255; } }, WORD, PACK { @Override public boolean use16BitCharacterLiterals() { return false; } }; public boolean use16BitCharacterLiterals() { return true; } public int getMaxSupportedValue() { return 65535; } } private byte[] parsedData; private AllowedSize allowedSize; public InitializedMemoryNode() { } @Override protected InitializedMemoryNode parseInternal(IParseContext context) throws ParseException { final ICompilationUnit unit = context.getCompilationUnit(); IToken tok = context.peek(); final TokenType acceptedType; if ( tok.hasType( TokenType.INITIALIZED_MEMORY_PACK ) ) { acceptedType = tok.getType(); allowedSize = AllowedSize.PACK; } else if ( tok.hasType( TokenType.INITIALIZED_MEMORY_BYTE ) ) { acceptedType = tok.getType(); allowedSize = AllowedSize.BYTE; } else if ( tok.hasType( TokenType.INITIALIZED_MEMORY_WORD ) ) { acceptedType = tok.getType(); allowedSize = AllowedSize.WORD; } else { throw new ParseException("Unexpected token type "+tok.getType(), tok ); } mergeWithAllTokensTextRegion( context.read(acceptedType) ); mergeWithAllTokensTextRegion( context.parseWhitespace() ); boolean expectingData = true; outer: while ( ! context.eof() ) { IToken token = context.peek(); if ( token.isWhitespace() ) { mergeWithAllTokensTextRegion( context.parseWhitespace() ); continue; } if ( expectingData ) { if ( (token.hasType(TokenType.OPERATOR ) && Operator.fromString( token.getContents() ) == Operator.MINUS) || token.hasType( TokenType.CHARACTERS ) || token.hasType( TokenType.NUMBER_LITERAL ) || token.hasType( TokenType.PARENS_OPEN ) ) { try { context.mark(); final ASTNode expr = new ExpressionNode().parseInternal( context ); if ( ASTUtils.getRegisterReferenceCount( expr ) > 0 ) { unit.addMarker( new CompilationError("Expression must not refer to a register", unit,expr ) ); } if ( ! isValueInRange( expr , context.getSymbolTable() ) ) { unit.addMarker( new CompilationError("Value "+getValue( expr , context.getSymbolTable() )+" is out-of-range", unit,expr ) ); } addChild( expr , context ); } catch(Exception e) { addCompilationErrorAndAdvanceParser( e , new TokenType[] { TokenType.COMMA , TokenType.EOL } , context ); } finally { context.clearMark(); } } else if ( token.hasType( TokenType.STRING_DELIMITER ) ) { addChild( new CharacterLiteralNode().parse( context ) , context ); } else { throw new ParseException("Expected a number or character literal, got "+token.getType(),token); } expectingData = false; } if ( context.eof() || context.peek().isEOL() ) { break outer; } while ( ! context.eof() ) { token = context.peek(); if ( token.isWhitespace() ) { mergeWithAllTokensTextRegion( context.parseWhitespace() ); } else if ( token.hasType( TokenType.COMMA ) ) { mergeWithAllTokensTextRegion( context.read( TokenType.COMMA ) ); expectingData = true; continue outer; } else { break outer; } } } if ( expectingData ) { throw new ParseException("Expected number or character literal",context.currentParseIndex() , 0 ); } return this; } private Long getValue(ASTNode node,ISymbolTable table) { if ( !(node instanceof TermNode) ) { return null; } return ((TermNode) node).calculate( table ); } private boolean isValueInRange(ASTNode node,ISymbolTable table) { final Long value = getValue( node , table ); if ( value != null && value.longValue() > allowedSize.getMaxSupportedValue() ) { return false; } return true; } private byte[] internalParseData(ICompilationContext context) throws ParseException { final ICompilationUnit unit = context.getCurrentCompilationUnit(); final ISymbolTable symbolTable = context.getSymbolTable(); final List<ASTNode> children = new ArrayList<ASTNode>(); for ( ASTNode node : getChildren() ) { if ( node instanceof TermNode || node instanceof CharacterLiteralNode ) { children.add( node ); } } final List<Integer> data = new ArrayList<Integer>(); for ( ASTNode node : children ) { if ( node instanceof CharacterLiteralNode ) { final List<Integer> bytes = ((CharacterLiteralNode) node).getBytes(); int index ; if ( allowedSize.use16BitCharacterLiterals() ) { index = 0; } else { index = 1; } for ( ; index < bytes.size() ; ) { data.add( bytes.get(index ) ); if ( allowedSize.use16BitCharacterLiterals() ) { index++; } else { index += 2; // skip over color information byte } } } else if ( node instanceof TermNode) { final TermNode termNode = (TermNode) node; final Long lValue = termNode.calculate( symbolTable ); if ( lValue == null ) { return null; } if ( ! isValueInRange( termNode , symbolTable ) ) { unit.addMarker( new CompilationError("Value "+getValue( termNode , symbolTable )+" is out-of-range", unit,termNode ) ); data.add( 0xff ); if ( allowedSize.getMaxSupportedValue() > 255 ) { data.add( 0xff ); } } else { final int value = lValue.intValue(); final boolean fromAddress = ( node instanceof SymbolReferenceNode); if ( ( value > 255 || fromAddress ) || allowedSize == AllowedSize.WORD || allowedSize == AllowedSize.PACK ) { data.add( (value & 0xff00) >> 8 ); data.add( value & 0x00ff ); } else { data.add( value ); } } } else { throw new RuntimeException("Unreachable code reached"); } } final int aligned = Address.alignTo16Bit( data.size() ); final byte[] result = new byte[ aligned ]; for ( int i = 0 ; i < data.size() ; i++ ) { result[i] = (byte) data.get(i).intValue(); } return result; } @Override protected InitializedMemoryNode copySingleNode() { final InitializedMemoryNode result = new InitializedMemoryNode(); result.allowedSize = allowedSize; result.setAddress( getAddress() ); if ( parsedData != null ) { result.parsedData = new byte[ this.parsedData.length ]; System.arraycopy( this.parsedData , 0 , result.parsedData , 0 , this.parsedData.length ); } return result; } @Override public void writeObjectCode(IObjectCodeWriter writer, ICompilationContext compContext) throws IOException { setAddress( writer.getCurrentWriteOffset() ); writer.writeObjectCode( this.parsedData ); } @Override public void symbolsResolved(ICompilationContext context) { byte[] bytes; try { bytes = internalParseData( context ); } catch (ParseException e) { throw new RuntimeException("Internal error, caught unexpected exception",e); } if ( bytes != null ) { final int bytesToWrite = Address.alignTo16Bit( bytes.length ); final byte[] data = new byte[ bytesToWrite ]; System.arraycopy( bytes , 0 , data , 0 , bytes.length ); this.parsedData = data; } else { this.parsedData = null; } } /** * UNIT-TESTING ONLY. * * @return */ public byte[] getBytes() { return parsedData; } @Override public int getSizeInBytes(long thisNodesObjectCodeOffsetInBytes) { if ( this.parsedData == null ) { return UNKNOWN_SIZE; } return parsedData.length; } @Override public boolean supportsChildNodes() { return true; } }