/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.fandev.lang.fan.parsing.expression.argument; import com.intellij.lang.PsiBuilder; import com.intellij.psi.tree.TokenSet; import org.fandev.lang.fan.FanBundle; import static org.fandev.lang.fan.FanBundle.message; import static org.fandev.lang.fan.FanElementTypes.*; import static org.fandev.lang.fan.FanTokenTypes.*; import org.fandev.lang.fan.parsing.expression.Expression; import org.fandev.lang.fan.parsing.types.TypeSpec; import org.fandev.lang.fan.parsing.types.TypeType; import static org.fandev.lang.fan.parsing.util.ParserUtils.*; /** * @author freds * @date Mar 2, 2009 */ public class LiteralExpression { private static final TokenSet LITERALS = TokenSet.orSet( FAN_LITERALS, TokenSet.create( NULL_KEYWORD, THIS_KEYWORD, SUPER_KEYWORD )); private static final TokenSet COLON_STOPPER = TokenSet.create(COLON); private static final TokenSet COLON_COMMA_RBRACKET_STOPPER = TokenSet.create(COLON, COMMA, RBRACKET); private static final TokenSet RBRACKET_COMMA = TokenSet.create(RBRACKET, COMMA); public static boolean parse(final PsiBuilder builder, final TokenSet stopper) { // TODO: Question: I removed the simple literal since it should be a callExpression PsiBuilder.Marker marker = builder.mark(); // Listed literals if (THIS_KEYWORD.equals(builder.getTokenType())) { builder.advanceLexer(); marker.done(THIS_REFERENCE_EXPRESSION); return true; } else if (SUPER_KEYWORD.equals(builder.getTokenType())) { builder.advanceLexer(); marker.done(SUPER_REFERENCE_EXPRESSION); return true; } if (LITERALS.contains(builder.getTokenType())) { builder.advanceLexer(); marker.done(LITERAL); return true; } // Slot literals may starts with # if (getToken(builder, SHARP)) { if (getToken(builder, IDENTIFIER_TOKENS_SET, FanBundle.message("identifier.expected"))) { marker.done(LITERAL); return true; } else { marker.drop(); return false; } } // If we have a type may be literal with # or list and map literal [xxx] final TypeType typeType = TypeSpec.parseType(builder, true); if (typeType != TypeType.NONE) { if (getToken(builder, SHARP)) { // May be a slot type literal getToken(builder, IDENTIFIER_TOKENS_SET); marker.done(LITERAL); return true; } if (getToken(builder, DSL_STRING)) { marker.done(LITERAL); return true; } if (LBRACKET == builder.getTokenType()) { if (typeType == TypeType.MAP) { return parseListOrMapLiteral(builder, LiteralType.MAP, marker); } if (typeType == TypeType.LIST) { return parseListOrMapLiteral(builder, LiteralType.LIST, marker); } } marker.rollbackTo(); marker = builder.mark(); } return parseListOrMapLiteral(builder, LiteralType.UNKNOW, marker); } enum LiteralType { UNKNOW, LIST, MAP, ERROR } private static boolean parseListOrMapLiteral(final PsiBuilder builder, LiteralType litType, final PsiBuilder.Marker marker) { if (!getToken(builder, LBRACKET)) { marker.rollbackTo(); return false; } removeNls(builder); final LiteralType emptyLiteralType = emptyMapOrList(builder, litType); switch (emptyLiteralType) { case LIST: removeNls(builder); getToken(builder, RBRACKET, FanBundle.message("rbrack.expected")); marker.done(LIST_LITERAL); return true; case MAP: removeNls(builder); getToken(builder, RBRACKET, FanBundle.message("rbrack.expected")); marker.done(MAP_LITERAL); return true; case ERROR: marker.rollbackTo(); return false; } // Literal with values litType = mapOrListLiteralWithValues(builder, litType); switch (litType) { case LIST: removeNls(builder); getToken(builder, RBRACKET, FanBundle.message("rbrack.expected")); marker.done(LIST_LITERAL); return true; case MAP: removeNls(builder); getToken(builder, RBRACKET, FanBundle.message("rbrack.expected")); marker.done(MAP_LITERAL); return true; } marker.rollbackTo(); return false; } private static LiteralType mapOrListLiteralWithValues(final PsiBuilder builder, LiteralType litType) { final PsiBuilder.Marker valMark = builder.mark(); // First find literal type by parsing the first expression and finding the separator boolean res = Expression.parseExpr(builder, COLON_COMMA_RBRACKET_STOPPER, EXPRESSION); if (res) { litType = findLiteralType(builder, litType); } if (!res || litType == LiteralType.ERROR) { valMark.drop(); return LiteralType.ERROR; } if (litType == LiteralType.LIST) { valMark.done(LIST_ITEM); if (COMMA.equals(builder.getTokenType())) { advanceNoNls(builder); } while (res && !builder.eof() && RBRACKET != builder.getTokenType()) { res = Expression.parseExpr(builder, RBRACKET_COMMA, LIST_ITEM); removeNls(builder); if (COMMA.equals(builder.getTokenType())) { advanceNoNls(builder); } } return LiteralType.LIST; } if (litType == LiteralType.MAP) { PsiBuilder.Marker mapEntryMark = valMark.precede(); valMark.done(MAP_ITEM_KEY); advanceNoNls(builder); if (!Expression.parseExpr(builder, RBRACKET_COMMA, MAP_ITEM_VALUE)) { mapEntryMark.drop(); return LiteralType.ERROR; } mapEntryMark.done(MAP_ITEM); removeNls(builder); if (COMMA.equals(builder.getTokenType())) { advanceNoNls(builder); } while (!builder.eof() && RBRACKET != builder.getTokenType()) { mapEntryMark = builder.mark(); if (Expression.parseExpr(builder, COLON_STOPPER, MAP_ITEM_KEY)) { advanceNoNls(builder); if (Expression.parseExpr(builder, RBRACKET_COMMA, MAP_ITEM_VALUE)) { mapEntryMark.done(MAP_ITEM); removeNls(builder); if (COMMA.equals(builder.getTokenType())) { advanceNoNls(builder); } } else { mapEntryMark.drop(); return LiteralType.MAP; } } else { mapEntryMark.drop(); return LiteralType.MAP; } } return LiteralType.MAP; } return LiteralType.ERROR; } private static LiteralType findLiteralType(final PsiBuilder builder, LiteralType litType) { switch (litType) { case LIST: if (!RBRACKET_COMMA.contains(builder.getTokenType())) { builder.error(message("comma.rbracket.expected")); litType = LiteralType.ERROR; } break; case MAP: if (!COLON.equals(builder.getTokenType())) { builder.error(message("colon.expected")); litType = LiteralType.ERROR; } break; case UNKNOW: case ERROR: // Is it good? default: if (RBRACKET_COMMA.contains(builder.getTokenType())) { litType = LiteralType.LIST; } else if (COLON.equals(builder.getTokenType())) { litType = LiteralType.MAP; } else { builder.error(message("literal.listOrMap.expected")); litType = LiteralType.ERROR; } } return litType; } private static LiteralType emptyMapOrList(final PsiBuilder builder, final LiteralType litType) { LiteralType res = LiteralType.UNKNOW; if (getToken(builder, COMMA)) { // Empty list literal should have ] just after if (litType == LiteralType.UNKNOW) { res = LiteralType.LIST; } else if (litType != LiteralType.LIST) { // TODO: An error for me ??? // builder.error(message("literal.list.unexpected")); // Forcing list literal anyway res = LiteralType.LIST; } else { res = litType; } } else if (getToken(builder, COLON)) { // Empty map literal should have ] just after if (litType == LiteralType.UNKNOW) { res = LiteralType.MAP; } else if (litType != LiteralType.MAP) { builder.error(message("literal.map.unexpected")); // Forcing map literal anyway res = LiteralType.MAP; } else { res = litType; } } return res; } }