package org.jetbrains.plugins.clojure.parser;
import com.intellij.lang.ASTNode;
import com.intellij.lang.PsiBuilder;
import com.intellij.lang.PsiParser;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.clojure.ClojureBundle;
import org.jetbrains.plugins.clojure.lexer.ClojureTokenTypes;
import static org.jetbrains.plugins.clojure.parser.ClojureElementTypes.*;
import org.jetbrains.plugins.clojure.parser.util.ParserUtils;
import static org.jetbrains.plugins.clojure.parser.ClojureSpecialFormTokens.DEF_TOKENS;
import java.util.Arrays;
import java.util.Set;
/**
* User: peter
* Date: Nov 21, 2008
* Time: 9:45:41 AM
* Copyright 2007, 2008, 2009 Red Shark Technology
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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.
*/
public class ClojureParser implements PsiParser, ClojureTokenTypes {
private static final String CREATE_NS = "create-ns";
private static final String IN_NS = "in-ns";
private static final String NS = "ns";
public static final Set<String> NS_TOKENS = new HashSet<String>();
static {
NS_TOKENS.addAll(Arrays.asList(NS, IN_NS, CREATE_NS));
}
@NotNull
public ASTNode parse(IElementType root, PsiBuilder builder) {
//builder.setDebugMode(true);
PsiBuilder.Marker marker = builder.mark();
for (IElementType token = builder.getTokenType(); token != null; token = builder.getTokenType()) {
parseExpression(builder);
}
marker.done(FILE);
return builder.getTreeBuilt();
}
private void parseExpression(PsiBuilder builder) {
IElementType token = builder.getTokenType();
if (LEFT_PAREN == token) {
parseList(builder);
} else if (LEFT_SQUARE == token) {
parseVector(builder);
} else if (LEFT_CURLY == token) {
parseMap(builder);
} else if (QUOTE == token) {
parseQuotedForm(builder);
} else if (BACKQUOTE == token) {
parseBackQuote(builder);
} else if (ParserUtils.lookAhead(builder, SHARP, LEFT_CURLY)) {
parseSet(builder);
} else if (SHARP == token) {
parseSharp(builder);
} else if (UP == token) {
parseUp(builder);
} else if (SHARPUP == token) {
parseMetadata(builder);
} else if (TILDA == token) {
parseTilda(builder);
} else if (AT == token) {
parseAt(builder);
} else if (TILDAAT == token) {
parseTildaAt(builder);
} else if (symS.contains(token)) {
parseSymbol(builder);
} else if (COLON_SYMBOL == token) {
parseKeyword(builder);
} else if (LITERALS.contains(token)) {
parseLiteral(builder);
} else {
syntaxError(builder, ClojureBundle.message("expected.left.paren.symbol.or.literal"));
}
}
private void parseExpressions(IElementType endToken, PsiBuilder builder) {
for (IElementType token = builder.getTokenType(); token != endToken && token != null; token = builder.getTokenType()) {
parseExpression(builder);
}
if (builder.getTokenType() != endToken) {
builder.error(ClojureBundle.message("expected.token", endToken.toString()));
} else {
builder.advanceLexer();
}
}
private void syntaxError(PsiBuilder builder, String msg) {
String e = msg + ": " + builder.getTokenText();
builder.error(e);
advanceLexerOrEOF(builder);
}
private void advanceLexerOrEOF(PsiBuilder builder) {
if (builder.getTokenType() != null) builder.advanceLexer();
}
private PsiBuilder.Marker markAndAdvance(PsiBuilder builder) {
PsiBuilder.Marker marker = builder.mark();
builder.advanceLexer();
return marker;
}
private void markAndAdvance(PsiBuilder builder, IElementType type) {
markAndAdvance(builder).done(type);
}
private void internalError(String msg) {
throw new Error(msg);
}
/**
* Enter: Lexer is pointed at symbol
* Exit: Lexer is pointed immediately after symbol
*
* @param builder
*/
private void parseSymbol(PsiBuilder builder) {
final PsiBuilder.Marker marker = builder.mark();
//parse implicit
if (builder.getTokenType() == symIMPLICIT_ARG) {
builder.advanceLexer();
marker.done(IMPLICIT_ARG);
return;
}
builder.advanceLexer(); // eat atom
if (SEPARATORS.contains(builder.getTokenType())) {
final PsiBuilder.Marker pred = marker.precede();
marker.done(SYMBOL);
parseSymbol1(builder, pred);
} else {
marker.done(SYMBOL);
}
}
private void parseSymbol1(PsiBuilder builder, PsiBuilder.Marker marker) {
if (SEPARATORS.contains(builder.getTokenType())) {
builder.advanceLexer(); //eat separator
if (builder.getTokenType() == symATOM) {
builder.advanceLexer(); //eat atom
}
if (SEPARATORS.contains(builder.getTokenType())) {
final PsiBuilder.Marker pred = marker.precede();
marker.done(SYMBOL);
parseSymbol1(builder, pred);
} else {
marker.done(SYMBOL);
}
} else {
marker.drop();
}
}
/**
* Enter: Lexer is pointed at symbol
* Exit: Lexer is pointed immediately after symbol
*
* @param builder
*/
private void parseKeyword(PsiBuilder builder) {
markAndAdvance(builder, KEYWORD);
}
/**
* Enter: Lexer is pointed at literal
* Exit: Lexer is pointed immediately after literal
*
* @param builder
*/
private void parseLiteral(PsiBuilder builder) {
PsiBuilder.Marker marker = builder.mark();
final boolean isWrong = builder.getTokenType() == WRONG_STRING_LITERAL;
builder.advanceLexer();
if (isWrong) {
marker.error(ClojureBundle.message("uncompleted.string.literal"));
} else {
marker.done(LITERAL);
}
}
/**
* Enter: Lexer is pointed at '
* Exit: Lexer is pointed immediately after quoted value
*/
private void parseQuotedForm(PsiBuilder builder) {
if (builder.getTokenType() != QUOTE) internalError(ClojureBundle.message("expected.quote"));
final PsiBuilder.Marker mark = builder.mark();
builder.advanceLexer();
parseExpression(builder);
mark.done(QUOTED_FORM);
}
/**
* Enter: Lexer is pointed at `
* Exit: Lexer is pointed immediately after quoted value
*/
private void parseBackQuote(PsiBuilder builder) {
if (builder.getTokenType() != BACKQUOTE) internalError(ClojureBundle.message("expected.backquote"));
final PsiBuilder.Marker mark = builder.mark();
builder.advanceLexer();
parseExpression(builder);
mark.done(BACKQUOTED_EXPRESSION);
}
/**
* Enter: Lexer is pointed at #
* Exit: Lexer is pointed immediately after closing }
*/
private void parseSharp(PsiBuilder builder) {
if (builder.getTokenType() != SHARP) internalError(ClojureBundle.message("expected.sharp"));
PsiBuilder.Marker mark = builder.mark();
builder.advanceLexer();
parseExpression(builder);
mark.done(SHARP_EXPRESSION);
}
private void parseSet(PsiBuilder builder) {
if (!ParserUtils.lookAhead(builder, SHARP, LEFT_CURLY)) {
internalError(ClojureBundle.message("expected.sharp.lcurly"));
}
PsiBuilder.Marker marker = builder.mark();
builder.advanceLexer();
assert builder.getTokenType() != null;
builder.eof();
builder.advanceLexer();
for (IElementType token = builder.getTokenType(); token != RIGHT_CURLY && token != null; token = builder.getTokenType()) {
parseExpression(builder); //entry
}
advanceLexerOrEOF(builder);
marker.done(SET);
}
/**
* Enter: Lexer is pointed at ^
* Exit: Lexer is pointed immediately after closing }
*/
private void parseUp(PsiBuilder builder) {
if (builder.getTokenType() != UP) internalError(ClojureBundle.message("expected.cup"));
PsiBuilder.Marker mark = builder.mark();
builder.advanceLexer();
parseExpression(builder);
mark.done(META_FORM);
}
/**
* Enter: Lexer is pointed at ^
* Exit: Lexer is pointed immediately after closing }
*/
private void parseMetadata(PsiBuilder builder) {
//todo add expression with metadata
if (builder.getTokenType() != SHARPUP) internalError(ClojureBundle.message("expected.sharp.cup"));
PsiBuilder.Marker mark = builder.mark();
builder.advanceLexer();
parseExpression(builder);
mark.done(METADATA);
}
/**
* Enter: Lexer is pointed at ~
* Exit: Lexer is pointed immediately after closing }
*/
private void parseTilda(PsiBuilder builder) {
if (builder.getTokenType() != TILDA) internalError(ClojureBundle.message("expected.tilde"));
PsiBuilder.Marker mark = builder.mark();
builder.advanceLexer();
parseExpression(builder);
mark.done(TILDA_EXPRESSION);
}
/**
* Enter: Lexer is pointed at @
* Exit: Lexer is pointed immediately after closing }
*/
private void parseAt(PsiBuilder builder) {
if (builder.getTokenType() != AT) internalError(ClojureBundle.message("expected.at"));
PsiBuilder.Marker mark = builder.mark();
builder.advanceLexer();
parseExpression(builder);
mark.done(AT_EXPRESSION);
}
/**
* Enter: Lexer is pointed at ^
* Exit: Lexer is pointed immediately after closing }
*/
private void parseTildaAt(PsiBuilder builder) {
if (builder.getTokenType() != TILDAAT) internalError(ClojureBundle.message("expected.tilde.at"));
PsiBuilder.Marker mark = builder.mark();
builder.advanceLexer();
parseExpression(builder);
mark.done(TILDAAT_EXPRESSION);
}
/**
* Enter: Lexer is pointed at the opening left paren
* Exit: Lexer is pointed immediately after the closing right paren, or at the end-of-file
*/
private void parseList(PsiBuilder builder) {
if (builder.getTokenType() != LEFT_PAREN) internalError(ClojureBundle.message("expected.lparen"));
PsiBuilder.Marker marker = markAndAdvance(builder);
final String tokenText = builder.getTokenText();
if (builder.getTokenType() == symATOM && DEF_TOKENS.contains(tokenText)) {
parseDef(builder, marker);
} else if (builder.getTokenType() == symATOM && NS_TOKENS.contains(tokenText)) {
parseNs(builder, marker);
} else {
parseExpressions(RIGHT_PAREN, builder);
marker.done(LIST);
}
}
/**
* Enter: Lexer is pointed at the opening left square
* Exit: Lexer is pointed immediately after the closing right paren, or at the end-of-file
*/
private void parseVector(PsiBuilder builder) {
PsiBuilder.Marker marker = markAndAdvance(builder);
parseExpressions(RIGHT_SQUARE, builder);
marker.done(VECTOR);
}
/**
* Enter: Lexer is pointed at the opening left paren
* Exit: Lexer is pointed immediately after the closing right paren, or at the end-of-file
*/
private void parseMap(PsiBuilder builder) {
if (builder.getTokenType() != LEFT_CURLY) internalError(ClojureBundle.message("expected.lcurly"));
PsiBuilder.Marker marker = markAndAdvance(builder);
for (IElementType token = builder.getTokenType(); token != RIGHT_CURLY && token != null; token = builder.getTokenType()) {
PsiBuilder.Marker entry = builder.mark();
parseExpression(builder); // key
parseExpression(builder); // value
entry.done(MAP_ENTRY);
}
if (builder.getTokenType() != RIGHT_CURLY) {
builder.error(ClojureBundle.message("expected.token", RIGHT_CURLY.toString()));
} else {
advanceLexerOrEOF(builder);
}
marker.done(MAP);
}
/**
* Enter: Lexer is pointed at the def
* Exit: Lexer is pointed immediately after the closing right paren, or at the end-of-file
*/
private void parseDef(PsiBuilder builder, PsiBuilder.Marker marker) {
final String text = builder.getTokenText();
if (!DEF_TOKENS.contains(text) || builder.getTokenType() != symATOM) {
internalError(ClojureBundle.message("expected.element"));
}
parseSymbol(builder);
for (IElementType token = builder.getTokenType(); token != RIGHT_PAREN && token != null; token = builder.getTokenType()) {
parseExpression(builder);
}
if (builder.getTokenType() != RIGHT_PAREN) {
builder.error(ClojureBundle.message("expected.token", RIGHT_PAREN.toString()));
} else {
advanceLexerOrEOF(builder);
}
marker.done("defmethod".equals(text) ? ClojureElementTypes.DEFMETHOD : ClojureElementTypes.DEF);
}
private void parseNs(PsiBuilder builder, PsiBuilder.Marker marker) {
final String text = builder.getTokenText();
if (!NS_TOKENS.contains(text) || builder.getTokenType() != symATOM) {
internalError(ClojureBundle.message("expected.element"));
}
parseSymbol(builder);
for (IElementType token = builder.getTokenType(); token != RIGHT_PAREN && token != null; token = builder.getTokenType()) {
parseExpression(builder);
}
if (builder.getTokenType() != RIGHT_PAREN) {
builder.error(ClojureBundle.message("expected.token", RIGHT_PAREN.toString()));
} else {
advanceLexerOrEOF(builder);
}
if (CREATE_NS.equals(text)) marker.done(ClojureElementTypes.CREATE_NS);
else if (IN_NS.equals(text)) marker.done(ClojureElementTypes.IN_NS);
else marker.done(ClojureElementTypes.NS);
}
/**
* Enter: Lexer is pointed at the opening left square
* Exit: Lexer is pointed immediately after the closing right paren, or at the end-of-file
*/
private void parseBindings(PsiBuilder builder) {
if (builder.getTokenType() != LEFT_SQUARE) internalError(ClojureBundle.message("expected.lsquare"));
PsiBuilder.Marker marker = markAndAdvance(builder);
for (IElementType token = builder.getTokenType(); token != RIGHT_SQUARE && token != null; token = builder.getTokenType()) {
parseExpression(builder); // variables being defined, values or metadata
}
advanceLexerOrEOF(builder);
marker.done(BINDINGS);
}
/**
* Enter: Lexer is pointed after the 'let'
* Exit: Lexer is pointed immediately after the closing right paren, or at the end-of-file
*/
private void parseList(PsiBuilder builder, PsiBuilder.Marker marker) {
parseExpressions(RIGHT_PAREN, builder);
marker.done(LIST);
}
}