/*
* Copyright 2013-2015 Sergey Ignatov, Alexander Zolotov, Florin Patan
*
* 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 com.goide.util;
import com.goide.psi.GoStringLiteral;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.LiteralTextEscaper;
import org.jetbrains.annotations.NotNull;
import java.util.Locale;
public class GoStringLiteralEscaper extends LiteralTextEscaper<GoStringLiteral> {
private int[] outSourceOffsets;
public GoStringLiteralEscaper(@NotNull GoStringLiteral host) {
super(host);
}
@Override
public boolean decode(@NotNull TextRange rangeInsideHost, @NotNull StringBuilder outChars) {
TextRange.assertProperRange(rangeInsideHost);
String subText = rangeInsideHost.substring(myHost.getText());
if (myHost.getRawString() != null) {
outChars.append(subText);
return true;
}
return parseStringCharacters(subText, outChars);
}
@Override
public int getOffsetInHost(int offsetInDecoded, @NotNull TextRange rangeInsideHost) {
TextRange.assertProperRange(rangeInsideHost);
if (myHost.getRawString() != null) {
int offset = offsetInDecoded;
offset += rangeInsideHost.getStartOffset();
return offset > rangeInsideHost.getEndOffset() ? -1 : offset;
}
int result = offsetInDecoded < outSourceOffsets.length ? outSourceOffsets[offsetInDecoded] : -1;
if (result == -1) return -1;
return (result <= rangeInsideHost.getLength() ? result : rangeInsideHost.getLength()) + rangeInsideHost.getStartOffset();
}
/**
* Escapes the specified string in accordance with https://golang.org/ref/spec#Rune_literals
*
* @param chars
* @param outChars
*/
public static void escapeString(@NotNull String chars, @NotNull StringBuilder outChars) {
int index = 0;
while (index < chars.length()) {
int c = chars.codePointAt(index);
switch (c) {
case (char)7:
outChars.append("\\a");
break;
case '\b':
outChars.append("\\b");
break;
case '\f':
outChars.append("\\f");
break;
case '\n':
outChars.append("\\n");
break;
case '\r':
outChars.append("\\r");
break;
case '\t':
outChars.append("\\t");
break;
case (char)0x0b:
outChars.append("\\v");
break;
case '\\':
outChars.append("\\\\");
break;
case '\'':
outChars.append("\\'");
break;
case '"':
outChars.append("\\\"");
break;
default:
switch (Character.getType(c)) {
case Character.CONTROL:
case Character.PRIVATE_USE:
case Character.UNASSIGNED:
if (c <= 0xffff) {
outChars.append("\\u").append(String.format(Locale.US, "%04X", c));
} else {
outChars.append("\\U").append(String.format(Locale.US, "%08X", c));
}
break;
default:
outChars.appendCodePoint(c);
}
}
index += Character.charCount(c);
}
}
private boolean parseStringCharacters(String chars, StringBuilder outChars) {
outSourceOffsets = new int[chars.length() + 1];
outSourceOffsets[chars.length()] = -1;
if (chars.indexOf('\\') < 0) {
outChars.append(chars);
for (int i = 0; i < outSourceOffsets.length; i++) {
outSourceOffsets[i] = i;
}
return true;
}
int index = 0;
while (index < chars.length()) {
char c = chars.charAt(index++);
outSourceOffsets[outChars.length()] = index - 1;
outSourceOffsets[outChars.length() + 1] = index;
if (c != '\\') {
outChars.append(c);
continue;
}
if (index == chars.length()) return false;
c = chars.charAt(index++);
switch (c) {
case 'a':
outChars.append((char)7);
break;
case 'b':
outChars.append('\b');
break;
case 'f':
outChars.append('\f');
break;
case 'n':
outChars.append('\n');
break;
case 'r':
outChars.append('\r');
break;
case 't':
outChars.append('\t');
break;
case 'v':
outChars.append((char)0x0b);
break;
case '\\':
outChars.append('\\');
break;
case '\'':
outChars.append('\'');
break;
case '"':
outChars.append('"');
break;
case '\n':
outChars.append('\n');
break;
// octal
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;
// hex
case 'x':
if (index + 2 <= chars.length()) {
try {
int v = Integer.parseInt(chars.substring(index, index + 2), 16);
outChars.append((char)v);
index += 2;
}
catch (Exception e) {
return false;
}
}
else {
return false;
}
break;
// little unicode
case 'u':
if (index + 4 <= chars.length()) {
try {
int v = Integer.parseInt(chars.substring(index, index + 4), 16);
c = chars.charAt(index);
if (c == '+' || c == '-') return false;
outChars.append((char)v);
index += 4;
}
catch (Exception e) {
return false;
}
}
else {
return false;
}
break;
// big unicode
case 'U':
if (index + 8 <= chars.length()) {
try {
int v = Integer.parseInt(chars.substring(index, index + 8), 16);
c = chars.charAt(index);
if (c == '+' || c == '-') return false;
outChars.append((char)v);
index += 8;
}
catch (Exception e) {
return false;
}
}
else {
return false;
}
break;
default:
return false;
}
outSourceOffsets[outChars.length()] = index;
}
return true;
}
@Override
public boolean isOneLine() {
return true;
}
}