/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.flex.compiler.internal.parsing.as; import java.util.ArrayList; import org.apache.flex.compiler.parsing.IASToken; import org.apache.flex.compiler.parsing.IASToken.ASTokenKind; /** * Buffer used that supports streaming of tokens, instead of a pre-computed * list. This token buffer is used by the ActionScript compiler. */ public final class StreamingTokenBuffer extends BaseRepairingTokenBuffer implements IRepairingTokenBuffer { /** * The "rewind buffer size" is the limit of the number of tokens allowed in * a single syntactic predicate. */ private static final int REWIND_BUFFER_SIZE = 10; private final StreamingASTokenizer tokenizer; private final ArrayList<ASToken> buffer; private int bufferSize; private ASToken previousToken; public StreamingTokenBuffer(final StreamingASTokenizer tokens) { super(tokens.getSourcePath()); tokenizer = tokens; buffer = new ArrayList<ASToken>(); for (int i = 0; i < REWIND_BUFFER_SIZE; i++) { buffer.add(eofToken); } bufferSize = 0; previousToken = eofToken; } /** * @return Path of the token source. */ public String getSourcePath() { return tokenizer.getSourcePath(); } @Override public final boolean insertSemicolon(final boolean isNextToken) { if (!insertSemis) return false; if (isNextToken) onSemicolonInserted(); return true; } private final void fill(final int distance) { for (int pos = 0; pos < distance; pos++) { final ASToken next = tokenizer.next(); buffer.add(next); bufferSize++; } } @Override public void rewind(final int position) { final int backSteps = this.position - position; if (backSteps > REWIND_BUFFER_SIZE) { throw new IllegalStateException(String.format( "Token buffer can't rewind that far. Max rewind is %d, but got %d.", REWIND_BUFFER_SIZE, backSteps)); } for (int i = 0; i < backSteps; i++) { // Left-pad the buffer with EOF tokens to push the look-ahead tokens further. buffer.add(0, eofToken); bufferSize++; } this.position = position; } @Override public final void consume() { if (nextIsSemicolon) { nextIsSemicolon = false; previousToken = SEMICOLON; } else { position++; // "fBufferSize" is the number of tokens, not including // the last EOF in the buffer, or the rewind buffer tokens. if (bufferSize > 0) { assert previousToken != null; previousToken = buffer.get(REWIND_BUFFER_SIZE); buffer.remove(1); bufferSize--; assert bufferSize >= 0 : "fBufferSize can not be negative"; } } } @Override protected ASToken lookAheadSkipInsertedSemicolon(int i) { assert bufferSize + REWIND_BUFFER_SIZE == buffer.size() : "buffer size out-of-sync"; if (bufferSize < i) { fill(i - bufferSize); } final ASToken result = buffer.get(REWIND_BUFFER_SIZE - 1 + i); if (result != null) result.lock(); return result != null ? result : eofToken; } /** */ public IASToken[] getTokens(final boolean includeInserted) { throw new UnsupportedOperationException(); } @Override public ASToken previous() { return previousToken != null ? previousToken : eofToken; } /** * Match optional semicolon. * <p> * This function implements the first 2 optional semicolon insertion rules * in the ECMA specification. * * @see "ECMA 2.6.2 Chapter 7.9.1 Rules of Automatic Semicolon Insertion" */ @Override public boolean matchOptionalSemicolon() { final ASToken nextToken = LT(1); if (nextToken == null) { // Pass -- end of file } else if (nextToken.getType() == ASTokenTypes.EOF) { // Pass -- end of file } else if (nextToken.getType() == ASTokenTypes.TOKEN_SEMICOLON) { // Found the semicolon. consume(); } else if (nextToken.getTokenKind() == ASTokenKind.SCOPE_CLOSE) { // Pass - the "offending token" is a "}". } else if (nextToken.getType() == ASTokenTypes.TOKEN_KEYWORD_ELSE) { // Pass - the "offending token" is "else". } else if (nextToken.getLine() > previous().getLine()) { // Insert - the "offending token" is on another line. insertSemicolon(false); } else if (!nextToken.getSourcePath().equals(previous().getSourcePath())) { // Insert - the "offending token" is in another file. // The previous token is in an included file. insertSemicolon(false); } else { // Failed to insert a virtual semicolon. return false; } return true; } }