Groovy: parsing of single quote string literals with slash before line feed

This commit is contained in:
Maxim.Medvedev
2011-11-15 21:30:23 +04:00
parent ce461ab8ec
commit 2ff73b9d77
5 changed files with 1131 additions and 933 deletions
File diff suppressed because it is too large Load Diff
@@ -133,12 +133,12 @@ mIDENT_NOBUCKS = {mLETTER} ({mLETTER} | {mDIGIT})*
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
mSTRING_NL = {mONE_NL}
mSTRING_ESC = \\ [^]
mSTRING_ESC = \\ [^] | \\ ({WHITE_SPACE})+ (\n|\r)
mREGEX_ESC = \\ n | \\ r | \\ t | \\ b | \\ f | "\\" "\\" | \\ "$" | \\ \" | \\ \'
| "\\""u"{mHEX_DIGIT}{4}
| "\\" [0..3] ([0..7] ([0..7])?)?
| "\\" [4..7] ([0..7])?
| "\\" {mONE_NL}
| "\\" ({WHITE_SPACE})* {mONE_NL}
/// Regexes ////////////////////////////////////////////////////////////////
@@ -160,7 +160,7 @@ mDOLLAR_SLASHY_LITERAL = {mDOLLAR_SLASHY_LITERAL_3}
mSINGLE_QUOTED_STRING_BEGIN = "\'" ( {mSTRING_ESC}
| "\""
| [^\\\'\r\n]
| "$" )*
| "$")*
mSINGLE_QUOTED_STRING = {mSINGLE_QUOTED_STRING_BEGIN} \'
mTRIPLE_QUOTED_STRING = "\'\'\'" ({mSTRING_ESC}
| \"
@@ -24,6 +24,7 @@ import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference;
import com.intellij.psi.impl.source.tree.LeafElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.Function;
import com.intellij.util.NullableFunction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.lexer.TokenSets;
@@ -32,6 +33,7 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals
import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.GrExpressionImpl;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.GrStringUtil;
import java.math.BigDecimal;
import java.math.BigInteger;
@@ -43,10 +45,10 @@ import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.*;
*/
public class GrLiteralImpl extends GrExpressionImpl implements GrLiteral, PsiLanguageInjectionHost {
private static final Function<GrLiteralImpl,PsiType> TYPE_CALCULATOR = new Function<GrLiteralImpl, PsiType>() {
private static final Function<GrLiteralImpl,PsiType> TYPE_CALCULATOR = new NullableFunction<GrLiteralImpl, PsiType>() {
@Override
public PsiType fun(GrLiteralImpl grLiteral) {
IElementType elemType = grLiteral.getFirstChild().getNode().getElementType();
IElementType elemType = getLiteralType(grLiteral);
return TypesUtil.getPsiType(grLiteral, elemType);
}
};
@@ -60,7 +62,7 @@ public class GrLiteralImpl extends GrExpressionImpl implements GrLiteral, PsiLan
}
public PsiType getType() {
if (getFirstChild().getNode().getElementType() == kNULL) return PsiType.NULL;
if (getLiteralType(this) == kNULL) return PsiType.NULL;
return GroovyPsiManager.getInstance(getProject()).getType(this, TYPE_CALCULATOR);
}
@@ -114,7 +116,9 @@ public class GrLiteralImpl extends GrExpressionImpl implements GrLiteral, PsiLan
if (text.endsWith("'")) {
text = text.substring(0, text.length() - 1);
}
return StringUtil.unescapeStringCharacters(text);
StringBuilder chars = new StringBuilder(text.length());
boolean result = GrStringUtil.parseStringCharacters(text, chars, null, true);
return result ? chars.toString() : null;
}
else if (elemType == mGSTRING_LITERAL) {
if (!text.startsWith("\"")) return null;
@@ -124,12 +128,23 @@ public class GrLiteralImpl extends GrExpressionImpl implements GrLiteral, PsiLan
else {
text = StringUtil.trimEnd(text.substring(1), "\"");
}
return StringUtil.unescapeStringCharacters(text);
StringBuilder chars = new StringBuilder(text.length());
boolean result = GrStringUtil.parseStringCharacters(text, chars, null, true);
return result ? chars.toString() : null;
}
else if (elemType == mREGEX_LITERAL) {
return StringUtil.trimStart(StringUtil.trimEnd(text, "/"), "/");
text = StringUtil.trimStart(StringUtil.trimEnd(text, "/"), "/");
StringBuilder chars = new StringBuilder(text.length());
boolean result = GrStringUtil.parseStringCharacters(text, chars, null, false);
return result ? chars.toString() : null;
}
return null; //todo
return null;
}
private static IElementType getLiteralType(GrLiteral literal) {
PsiElement firstChild = literal.getFirstChild();
assert firstChild != null;
return firstChild.getNode().getElementType();
}
public boolean isStringLiteral() {
@@ -349,4 +349,145 @@ public class GrStringUtil {
if (text.startsWith(DOUBLE_QUOTES)) return DOUBLE_QUOTES;
return "";
}
/**
* @see com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl#parseStringCharacters(String, StringBuilder, int[])
*/
public static boolean parseStringCharacters(@NotNull String chars,
@NotNull StringBuilder outChars,
@Nullable int[] sourceOffsets,
final boolean strictBackSlash) {
assert sourceOffsets == null || sourceOffsets.length == chars.length()+1;
if (chars.indexOf('\\') < 0) {
outChars.append(chars);
if (sourceOffsets != null) {
for (int i = 0; i < sourceOffsets.length; i++) {
sourceOffsets[i] = i;
}
}
return true;
}
int index = 0;
final int outOffset = outChars.length();
while (index < chars.length()) {
char c = chars.charAt(index++);
if (sourceOffsets != null) {
sourceOffsets[outChars.length()-outOffset] = index - 1;
sourceOffsets[outChars.length() + 1 -outOffset] = index;
}
if (c != '\\') {
outChars.append(c);
continue;
}
if (index == chars.length()) return false;
c = chars.charAt(index++);
switch (c) {
case'b':
outChars.append('\b');
break;
case't':
outChars.append('\t');
break;
case'n':
outChars.append('\n');
break;
case'f':
outChars.append('\f');
break;
case'r':
outChars.append('\r');
break;
case'"':
outChars.append('"');
break;
case'\'':
outChars.append('\'');
break;
case'\\':
outChars.append('\\');
break;
case'0':
case'1':
case'2':
case'3':
case'4':
case'5':
case'6':
case'7':
char startC = c;
int v = (int)c - '0';
if (index < chars.length()) {
c = chars.charAt(index++);
if ('0' <= c && c <= '7') {
v <<= 3;
v += c - '0';
if (startC <= '3' && index < chars.length()) {
c = chars.charAt(index++);
if ('0' <= c && c <= '7') {
v <<= 3;
v += c - '0';
}
else {
index--;
}
}
}
else {
index--;
}
}
outChars.append((char)v);
break;
case'u':
// uuuuu1234 is valid too
while (index != chars.length() && chars.charAt(index) == 'u') {
index++;
}
if (index + 4 <= chars.length()) {
try {
int code = Integer.parseInt(chars.substring(index, index + 4), 16);
//line separators are invalid here
if (code == 0x000a || code == 0x000d) return false;
c = chars.charAt(index);
if (c == '+' || c == '-') return false;
outChars.append((char)code);
index += 4;
}
catch (Exception e) {
return false;
}
}
else {
return false;
}
break;
default:
int newIndex = index;
while (chars.charAt(newIndex) == ' ') newIndex++;
if (chars.charAt(newIndex) == '\n') {
index = newIndex;
break;
}
if (!strictBackSlash) {
break;
}
return false;
}
if (sourceOffsets != null) {
sourceOffsets[outChars.length()-outOffset] = index;
}
}
return true;
}
}
@@ -286,4 +286,19 @@ public class ExpressionsParsingTest extends GroovyParsingTestCase {
void testpath$stringMethodCall1() {doTest()}
void testpath$stringMethodCall2() {doTest()}
void testpath$stringMethodCall3() {doTest()}
void testSpacesInStringAfterSlash() {
checkParsing '''
print 'abc \\ \ncde' ''', '''
Groovy script
PsiElement(new line)(\'\\n\')
Call expression
Reference expression
PsiElement(identifier)(\'print\')
PsiWhiteSpace(\' \')
Command arguments
Literal
PsiElement(string)(\'\'abc \\ \\ncde\'\')
PsiWhiteSpace(\' \')'''
}
}