/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.parsing;
import com.intellij.lang.
PsiBuilder;
import com.intellij.psi.tree.
IElementType;
import com.intellij.psi.tree.
TokenSet;
import com.intellij.util.containers.
Stack;
import org.jetbrains.annotations.
NotNull;
import org.jetbrains.annotations.
TestOnly;
import org.jetbrains.kotlin.lexer.
KtKeywordToken;
import org.jetbrains.kotlin.lexer.
KtToken;
import org.jetbrains.kotlin.lexer.
KtTokens;
import org.jetbrains.kotlin.utils.strings.
StringsKt;
import java.util.
HashMap;
import java.util.
Map;
import static org.jetbrains.kotlin.lexer.
KtTokens.*;
/*package*/ abstract class
AbstractKotlinParsing {
private static final
Map<
String,
KtKeywordToken>
SOFT_KEYWORD_TEXTS = new
HashMap<>();
static {
for (
IElementType type :
KtTokens.
SOFT_KEYWORDS.
getTypes()) {
KtKeywordToken keywordToken = (
KtKeywordToken)
type;
assert
keywordToken.
isSoft();
SOFT_KEYWORD_TEXTS.
put(
keywordToken.
getValue(),
keywordToken);
}
}
static {
for (
IElementType token :
KtTokens.
KEYWORDS.
getTypes()) {
assert
token instanceof
KtKeywordToken : "Must be KtKeywordToken: " +
token;
assert !((
KtKeywordToken)
token).
isSoft() : "Must not be soft: " +
token;
}
}
protected final
SemanticWhitespaceAwarePsiBuilder myBuilder;
public
AbstractKotlinParsing(
SemanticWhitespaceAwarePsiBuilder builder) {
this.
myBuilder =
builder;
}
protected
IElementType getLastToken() {
int
i = 1;
int
currentOffset =
myBuilder.
getCurrentOffset();
while (
i <=
currentOffset &&
WHITE_SPACE_OR_COMMENT_BIT_SET.
contains(
myBuilder.
rawLookup(-
i))) {
i++;
}
return
myBuilder.
rawLookup(-
i);
}
protected boolean
expect(
KtToken expectation,
String message) {
return
expect(
expectation,
message, null);
}
protected
PsiBuilder.
Marker mark() {
return
myBuilder.
mark();
}
protected void
error(
String message) {
myBuilder.
error(
message);
}
protected boolean
expect(
KtToken expectation,
String message,
TokenSet recoverySet) {
if (
at(
expectation)) {
advance(); // expectation
return true;
}
if (
expectation ==
KtTokens.
IDENTIFIER && "`".
equals(
myBuilder.
getTokenText())) {
advance();
}
errorWithRecovery(
message,
recoverySet);
return false;
}
protected void
expectNoAdvance(
KtToken expectation,
String message) {
if (
at(
expectation)) {
advance(); // expectation
return;
}
error(
message);
}
protected void
errorWithRecovery(
String message,
TokenSet recoverySet) {
IElementType tt =
tt();
if (
recoverySet == null ||
recoverySet.
contains(
tt) ||
tt ==
LBRACE ||
tt ==
RBRACE ||
(
recoverySet.
contains(
EOL_OR_SEMICOLON) && (
eof() ||
tt ==
SEMICOLON ||
myBuilder.
newlineBeforeCurrentToken()))) {
error(
message);
}
else {
errorAndAdvance(
message);
}
}
protected void
errorAndAdvance(
String message) {
errorAndAdvance(
message, 1);
}
protected void
errorAndAdvance(
String message, int
advanceTokenCount) {
PsiBuilder.
Marker err =
mark();
advance(
advanceTokenCount);
err.
error(
message);
}
protected boolean
eof() {
return
myBuilder.
eof();
}
protected void
advance() {
// TODO: how to report errors on bad characters? (Other than highlighting)
myBuilder.
advanceLexer();
}
protected void
advance(int
advanceTokenCount) {
for (int
i = 0;
i <
advanceTokenCount;
i++) {
advance(); // erroneous token
}
}
protected void
advanceAt(
IElementType current) {
assert
_at(
current);
myBuilder.
advanceLexer();
}
protected
IElementType tt() {
return
myBuilder.
getTokenType();
}
/**
* Side-effect-free version of at()
*/
protected boolean
_at(
IElementType expectation) {
IElementType token =
tt();
return
tokenMatches(
token,
expectation);
}
private boolean
tokenMatches(
IElementType token,
IElementType expectation) {
if (
token ==
expectation) return true;
if (
expectation ==
EOL_OR_SEMICOLON) {
if (
eof()) return true;
if (
token ==
SEMICOLON) return true;
if (
myBuilder.
newlineBeforeCurrentToken()) return true;
}
return false;
}
protected boolean
at(
IElementType expectation) {
if (
_at(
expectation)) return true;
IElementType token =
tt();
if (
token ==
IDENTIFIER &&
expectation instanceof
KtKeywordToken) {
KtKeywordToken expectedKeyword = (
KtKeywordToken)
expectation;
if (
expectedKeyword.
isSoft() &&
expectedKeyword.
getValue().
equals(
myBuilder.
getTokenText())) {
myBuilder.
remapCurrentToken(
expectation);
return true;
}
}
if (
expectation ==
IDENTIFIER &&
token instanceof
KtKeywordToken) {
KtKeywordToken keywordToken = (
KtKeywordToken)
token;
if (
keywordToken.
isSoft()) {
myBuilder.
remapCurrentToken(
IDENTIFIER);
return true;
}
}
return false;
}
/**
* Side-effect-free version of atSet()
*/
protected boolean
_atSet(
IElementType...
tokens) {
return
_atSet(
TokenSet.
create(
tokens));
}
/**
* Side-effect-free version of atSet()
*/
private boolean
_atSet(
TokenSet set) {
IElementType token =
tt();
if (
set.
contains(
token)) return true;
if (
set.
contains(
EOL_OR_SEMICOLON)) {
if (
eof()) return true;
if (
token ==
SEMICOLON) return true;
if (
myBuilder.
newlineBeforeCurrentToken()) return true;
}
return false;
}
protected boolean
atSet(
IElementType...
tokens) {
return
atSet(
TokenSet.
create(
tokens));
}
protected boolean
atSet(
TokenSet set) {
if (
_atSet(
set)) return true;
IElementType token =
tt();
if (
token ==
IDENTIFIER) {
KtKeywordToken keywordToken =
SOFT_KEYWORD_TEXTS.
get(
myBuilder.
getTokenText());
if (
keywordToken != null &&
set.
contains(
keywordToken)) {
myBuilder.
remapCurrentToken(
keywordToken);
return true;
}
}
else {
// We know at this point that <code>set</code> does not contain <code>token</code>
if (
set.
contains(
IDENTIFIER) &&
token instanceof
KtKeywordToken) {
if (((
KtKeywordToken)
token).
isSoft()) {
myBuilder.
remapCurrentToken(
IDENTIFIER);
return true;
}
}
}
return false;
}
protected
IElementType lookahead(int
k) {
return
myBuilder.
lookAhead(
k);
}
protected boolean
consumeIf(
KtToken token) {
if (
at(
token)) {
advance(); // token
return true;
}
return false;
}
// TODO: Migrate to predicates
protected void
skipUntil(
TokenSet tokenSet) {
boolean
stopAtEolOrSemi =
tokenSet.
contains(
EOL_OR_SEMICOLON);
while (!
eof() && !
tokenSet.
contains(
tt()) && !(
stopAtEolOrSemi &&
at(
EOL_OR_SEMICOLON))) {
advance();
}
}
protected void
errorUntil(
String message,
TokenSet tokenSet) {
assert
tokenSet.
contains(
LBRACE) : "Cannot include LBRACE into error element!";
assert
tokenSet.
contains(
RBRACE) : "Cannot include RBRACE into error element!";
PsiBuilder.
Marker error =
mark();
skipUntil(
tokenSet);
error.
error(
message);
}
protected static void
errorIf(
PsiBuilder.
Marker marker, boolean
condition,
String message) {
if (
condition) {
marker.
error(
message);
}
else {
marker.
drop();
}
}
protected class
OptionalMarker {
private final
PsiBuilder.
Marker marker;
private final int
offset;
public
OptionalMarker(boolean
actuallyMark) {
marker =
actuallyMark ?
mark() : null;
offset =
myBuilder.
getCurrentOffset();
}
public void
done(
IElementType elementType) {
if (
marker == null) return;
marker.
done(
elementType);
}
public void
error(
String message) {
if (
marker == null) return;
if (
offset ==
myBuilder.
getCurrentOffset()) {
marker.
drop(); // no empty errors
}
else {
marker.
error(
message);
}
}
public void
drop() {
if (
marker == null) return;
marker.
drop();
}
}
protected int
matchTokenStreamPredicate(
TokenStreamPattern pattern) {
PsiBuilder.
Marker currentPosition =
mark();
Stack<
IElementType>
opens = new
Stack<>();
int
openAngleBrackets = 0;
int
openBraces = 0;
int
openParentheses = 0;
int
openBrackets = 0;
while (!
eof()) {
if (
pattern.
processToken(
myBuilder.
getCurrentOffset(),
pattern.
isTopLevel(
openAngleBrackets,
openBrackets,
openBraces,
openParentheses))) {
break;
}
if (
at(
LPAR)) {
openParentheses++;
opens.
push(
LPAR);
}
else if (
at(
LT)) {
openAngleBrackets++;
opens.
push(
LT);
}
else if (
at(
LBRACE)) {
openBraces++;
opens.
push(
LBRACE);
}
else if (
at(
LBRACKET)) {
openBrackets++;
opens.
push(
LBRACKET);
}
else if (
at(
RPAR)) {
openParentheses--;
if (
opens.
isEmpty() ||
opens.
pop() !=
LPAR) {
if (
pattern.
handleUnmatchedClosing(
RPAR)) {
break;
}
}
}
else if (
at(
GT)) {
openAngleBrackets--;
}
else if (
at(
RBRACE)) {
openBraces--;
}
else if (
at(
RBRACKET)) {
openBrackets--;
}
advance(); // skip token
}
currentPosition.
rollbackTo();
return
pattern.
result();
}
protected boolean
eol() {
return
myBuilder.
newlineBeforeCurrentToken() ||
eof();
}
protected static void
closeDeclarationWithCommentBinders(@
NotNull PsiBuilder.
Marker marker, @
NotNull IElementType elementType, boolean
precedingNonDocComments) {
marker.
done(
elementType);
marker.
setCustomEdgeTokenBinders(
precedingNonDocComments ?
PrecedingCommentsBinder.
INSTANCE :
PrecedingDocCommentsBinder.
INSTANCE,
TrailingCommentsBinder.
INSTANCE);
}
protected abstract
KotlinParsing create(
SemanticWhitespaceAwarePsiBuilder builder);
protected
KotlinParsing createTruncatedBuilder(int
eofPosition) {
return
create(new
TruncatedSemanticWhitespaceAwarePsiBuilder(
myBuilder,
eofPosition));
}
protected class
At extends
AbstractTokenStreamPredicate {
private final
IElementType lookFor;
private final boolean
topLevelOnly;
public
At(
IElementType lookFor, boolean
topLevelOnly) {
this.
lookFor =
lookFor;
this.
topLevelOnly =
topLevelOnly;
}
public
At(
IElementType lookFor) {
this(
lookFor, true);
}
@
Override
public boolean
matching(boolean
topLevel) {
return (
topLevel || !
topLevelOnly) &&
at(
lookFor);
}
}
protected class
AtSet extends
AbstractTokenStreamPredicate {
private final
TokenSet lookFor;
private final
TokenSet topLevelOnly;
public
AtSet(
TokenSet lookFor,
TokenSet topLevelOnly) {
this.
lookFor =
lookFor;
this.
topLevelOnly =
topLevelOnly;
}
public
AtSet(
TokenSet lookFor) {
this(
lookFor,
lookFor);
}
@
Override
public boolean
matching(boolean
topLevel) {
return (
topLevel || !
atSet(
topLevelOnly)) &&
atSet(
lookFor);
}
}
@
SuppressWarnings("UnusedDeclaration")
@
TestOnly
public
String currentContext() {
return
StringsKt.
substringWithContext(
myBuilder.
getOriginalText(),
myBuilder.
getCurrentOffset(),
myBuilder.
getCurrentOffset(), 20);
}
}