StringLiteral.ceylon

import ceylon.ast.core {
    Node,
    StringLiteral
}
import com.redhat.ceylon.compiler.typechecker.tree {
    JNode=Node,
    Tree {
        JStringLiteral=StringLiteral
    }
}
import com.redhat.ceylon.compiler.typechecker.parser {
    CeylonLexer {
        astring_literal=\iASTRING_LITERAL,
        averbatim_string=\iAVERBATIM_STRING,
        string_literal=\iSTRING_LITERAL,
        string_end=\iSTRING_END,
        string_mid=\iSTRING_MID,
        string_start=\iSTRING_START,
        verbatim_string_literal=\iVERBATIM_STRING
    }
}
import org.antlr.runtime {
    CommonToken
}

"""Strips quotes and leading indentation from a string literal.
   
   For example,
   
           "Hello,
            World"
   
   is turned into
   
       Hello,
       World
   
   This function also detecs if the literal was already visited by
   the RedHat compiler’s `LiteralVisitor`, and doesn’t do anything
   in that case.
   
   [[column]] can be used to provide a different amount of space to strip;
   this is necessary for string templates, where all parts are
   aligned to the firts part’s column."""
String stripStringLiteral(JStringLiteral literal, Integer? column = null) {
    if (literal.text != literal.mainToken.text) {
        // this already went through LiteralVisitor, nothing to do
        return literal.text;
    }
    Integer startQuoteLength;
    Integer stopQuoteLength;
    value type = literal.mainToken.type;
    if (type == string_literal || type == astring_literal) {
        startQuoteLength = 1;
        stopQuoteLength = 1;
    } else if (type == verbatim_string_literal || type == averbatim_string) {
        startQuoteLength = 3;
        stopQuoteLength = 3;
    } else if (type == string_start) {
        startQuoteLength = 1;
        stopQuoteLength = 2;
    } else if (type == string_mid) {
        startQuoteLength = 2;
        stopQuoteLength = 2;
    } else if (type == string_end) {
        startQuoteLength = 2;
        stopQuoteLength = 1;
    } else {
        throw AssertionError("Unknown token type ``type``");
    }
    value toStrip = column else literal.mainToken.charPositionInLine + startQuoteLength;
    StringBuilder ret = StringBuilder();
    value text = literal.mainToken.text[startQuoteLength : literal.mainToken.text.size - startQuoteLength - stopQuoteLength];
    value lines = text.lines;
    if (exists firstLine = lines.first) {
        ret.append(firstLine);
        for (line in lines.rest) {
            ret.appendNewline();
            value parts = line.slice(toStrip);
            "Multiline string content should align with start of string"
            assert (parts[0].every(Character.whitespace));
            ret.append(parts[1]);
        }
    }
    return ret.string;
}

"Converts a RedHat AST [[StringLiteral|JStringLiteral]] to a `ceylon.ast` [[StringLiteral]]."
throws (`class AssertionError`, "If the token type is neither `STRING_LITERAL` nor `VERBATIM_STRING`
                                 nor `ASTRING_LITERAL` nor `AVERBATIM_STRING`.")
shared StringLiteral stringLiteralToCeylon(JStringLiteral stringLiteral, Anything(JNode,Node) update = noop) {
    StringLiteral result;
    assert (is CommonToken token = stringLiteral.mainToken);
    if (token.type == verbatim_string_literal || token.type == averbatim_string) {
        // verbatim
        result = StringLiteral(stripStringLiteral(stringLiteral), true);
    } else if (token.type == string_literal || token.type == astring_literal) {
        // regular
        result = StringLiteral(stripStringLiteral(stringLiteral), false);
    } else {
        throw AssertionError("Unknown token type ``stringLiteral.mainToken.type``");
    }
    update(stringLiteral, result);
    return result;
}

"Converts a RedHat AST [[StringLiteral|JStringLiteral]] with annotation token type
 (`ASTRING_LITERAL` or `AVERBATIM_STRING`) to a `ceylon.ast` [[StringLiteral]]."
throws (`class AssertionError`, "If the token type is neither `ASTRING_LITERAL` nor `AVERBATIM_STRING`.")
shared StringLiteral aStringLiteralToCeylon(JStringLiteral stringLiteral, Anything(JNode,Node) update = noop) {
    assert (is CommonToken token = stringLiteral.mainToken);
    StringLiteral result;
    if (token.type == averbatim_string) {
        // verbatim
        result = StringLiteral(stripStringLiteral(stringLiteral), true);
    } else if (token.type == astring_literal) {
        // regular
        result = StringLiteral(stripStringLiteral(stringLiteral), false);
    } else {
        throw AssertionError("Unknown token type ``stringLiteral.mainToken.type``");
    }
    update(stringLiteral, result);
    return result;
}

"Compiles the given [[code]] for a String Literal
 into a [[StringLiteral]] using the Ceylon compiler
 (more specifically, the rule for a `stringLiteral`)."
shared StringLiteral? compileStringLiteral(String code, Anything(JNode,Node) update = noop) {
    if (exists jStringLiteral = createParser(code).stringLiteral()) {
        return stringLiteralToCeylon(jStringLiteral, update);
    } else {
        return null;
    }
}