package org.fandev.lang.fan.parsing.types; import com.intellij.lang.PsiBuilder; import org.fandev.lang.fan.FanBundle; import org.fandev.lang.fan.FanElementTypes; import static org.fandev.lang.fan.FanElementTypes.LIST_TYPE; import static org.fandev.lang.fan.FanElementTypes.MAP_TYPE; import static org.fandev.lang.fan.FanTokenTypes.*; import org.fandev.lang.fan.parsing.util.ParserUtils; /** * @author Dror Bereznitsky * @date Jan 10, 2009 4:40:10 PM */ public class TypeSpec { public static boolean parse(final PsiBuilder builder) { ParserUtils.removeNls(builder); final boolean res = parseType(builder, false) != TypeType.NONE; return res; } private static TypeType parseFunctionOrSimpleType(final PsiBuilder builder, final boolean forLiteral) { //func type if (OR == builder.getTokenType()) { return FuncTypeSpec.parseFuncType(builder, forLiteral); } return SimpleTypeSpec.parseSimpleType(builder, forLiteral); } /** * <mapType> := ["["] <type> ":" <type> ["]"] * * @param builder * @return */ public static TypeType parseType(final PsiBuilder builder, final boolean forLiteral) { boolean bracketFlag = false; TypeType result; // TODO: Use lookahead to determined different types // TODO: [Question] Can you use Function type as key without brackets for maps PsiBuilder.Marker typeMarker = builder.mark(); boolean forLiteralInnerType = forLiteral; if (LBRACKET == builder.getTokenType()) { bracketFlag = true; builder.advanceLexer(); // If bracket opened, the literal should be after bracket close forLiteralInnerType = false; } result = parseFunctionOrSimpleType(builder, forLiteralInnerType); if (result == TypeType.NONE) { typeMarker.rollbackTo(); return result; } // Check for : to know if map type if (COLON != builder.getTokenType()) { if (bracketFlag) { // Should have a : if (forLiteral) { // May be a list literal typeMarker.rollbackTo(); return TypeType.NONE; } builder.error(FanBundle.message("colon.expected")); // Eat ] if exists if (ParserUtils.firstAfter(builder, NLS) == RBRACKET) { ParserUtils.removeNls(builder); ParserUtils.advanceNoNls(builder); } } typeMarker.drop(); return result; } else { result = TypeType.MAP; builder.advanceLexer(); final TypeType valueType = parseFunctionOrSimpleType(builder, false); if (valueType != TypeType.NONE) { // TODO: Check [] for list of maps // if we have an opening with "[" we need a closing "]" if (!RBRACKET.equals(builder.getTokenType()) && bracketFlag) { typeMarker.error(FanBundle.message("rbrack.expected")); return result; } else if (RBRACKET.equals(builder.getTokenType()) && !bracketFlag) { typeMarker.error(FanBundle.message("rbrack.no.lbrack")); return result; } else if (RBRACKET.equals(builder.getTokenType()) && bracketFlag) { builder.advanceLexer(); } if (LBRACKET == builder.getTokenType() || QUEST == builder.getTokenType()) { PsiBuilder.Marker arrMarker = typeMarker; typeMarker = arrMarker.precede(); result = endOfTypeParse(builder, arrMarker, forLiteral, TypeType.MAP); } typeMarker.done(FanElementTypes.MAP_TYPE); return result; } else { if (bracketFlag) { // TODO: Eat ] } typeMarker.error(FanBundle.message("type.expected")); return result; } } } /** * Parse for ? nullable type and list declaration [] * * @param builder * @param marker * @param forLiteral * @param defaultType * @return */ static TypeType endOfTypeParse(final PsiBuilder builder, final PsiBuilder.Marker marker, final boolean forLiteral, final TypeType defaultType) { PsiBuilder.Marker rollTo = builder.mark(); if (QUEST == builder.getTokenType()) { // Should not have whitespaces before final int offset = builder.getCurrentOffset(); if (offset > 0) { char c = builder.getOriginalText().charAt(offset - 1); if (!Character.isWhitespace(c)) { builder.advanceLexer(); rollTo.done(FanElementTypes.NULLABLE_TYPE); rollTo = builder.mark(); } } } if (!ParserUtils.getToken(builder, LBRACKET)) { rollTo.rollbackTo(); marker.drop(); return defaultType; } ParserUtils.removeNls(builder); if (!ParserUtils.getToken(builder, RBRACKET)) { // In literal mode needs smart analysis to know the type if (forLiteral) { // First do the simple empty literal // If it's the empty list if (COMMA == builder.getTokenType()) { rollTo.rollbackTo(); marker.done(LIST_TYPE); return TypeType.LIST; } // Or an empty map if (COLON == builder.getTokenType()) { rollTo.rollbackTo(); marker.done(MAP_TYPE); return TypeType.MAP; } // If already declared map or list it will return the type if (defaultType != TypeType.MAP && defaultType != TypeType.LIST) { // TODO: Without knowing if it's a type or a variable it's impossible to know if // it's a literal list or indexed expression :( // So, for the moment I go for looking for a comma which is my problem boolean hasComma = false; while (!builder.eof() && builder.getTokenType() != RBRACKET) { if (builder.getTokenType() == COMMA) { hasComma = true; break; } builder.advanceLexer(); } if (hasComma) { rollTo.rollbackTo(); marker.done(LIST_TYPE); return TypeType.LIST; } } } rollTo.rollbackTo(); marker.drop(); return defaultType; } rollTo.drop(); ParserUtils.removeNls(builder); marker.done(LIST_TYPE); final PsiBuilder.Marker newMarker = builder.mark(); return endOfTypeParse(builder, newMarker, forLiteral, TypeType.LIST); } }