package com.haskforce.language; import com.intellij.lang.refactoring.NamesValidator; import com.intellij.openapi.project.Project; import com.intellij.util.containers.HashSet; import org.jetbrains.annotations.NotNull; import java.util.Arrays; /** * Ensures names are legal Haskell. Used by refactoring. */ public class HaskellNamesValidator implements NamesValidator { /** * Slight overapproximation of keywords, but they are keyword in some * Haskell variant. */ public static final HashSet<String> HASKELL_KEYWORDS = new HashSet<>( Arrays.asList("as", "case", "class", "data", "default" , "deriving", "do", "else", "export", "forall", "foreign" , "hiding", "if", "import", "in", "infix", "infixl", "infixr" , "instance", "let", "module", "newtype", "of", "qualified" , "safe", "then", "type", "where")); /** * Checks if the specified string is a keyword in the language. */ @Override public boolean isKeyword(@NotNull String name, Project project) { return HASKELL_KEYWORDS.contains(name); } /** * Checks if the specified string is a valid identifier in the language. */ @Override public boolean isIdentifier(@NotNull String name, Project project) { String[] parts = name.split("\\."); for (int i = 0; i < parts.length - 1; i++) { String word = parts[i]; if (!isModid(word, project)) return false; } return isOneid(parts[parts.length - 1], project, true); } /** * Extended grammar for varid/conid from Haskell 2010 specification. Also allows * underscore as start on varids. * * @param var True for varid, false for conid. */ private boolean isOneid(@NotNull String name, Project project, boolean var) { if (name.isEmpty()) return false; // Guards against "Data..Maybe". return !isKeyword(name, project); /* TODO: Make the name validator context sensitive. A constructor and a variable have different rules. if (var) { if (!(Character.isLowerCase(name.charAt(0)) || name.startsWith("_"))) return false; } else { if (!Character.isUpperCase(name.charAt(0))) return false; } for (char c : name.substring(1).toCharArray()) { if (!(Character.isDigit(c) || Character.isUpperCase(c) || Character.isLowerCase(c) || '\'' == c || '_' == c )) { return false; } } // Don't "simplify" this by applying the hint. This logic is readable. if (var) { return !isKeyword(name, project); } return true; */ } /** * Checks whether name is a valid module id. */ private boolean isModid(@NotNull String name, Project project) { return isOneid(name, project, false); } }