diff --git a/LanguageServer/src/main/java/org/nittalab/dtram/languageserver/utils/SemanticAnalyzer.java b/LanguageServer/src/main/java/org/nittalab/dtram/languageserver/utils/SemanticAnalyzer.java new file mode 100644 index 0000000..bc9107b --- /dev/null +++ b/LanguageServer/src/main/java/org/nittalab/dtram/languageserver/utils/SemanticAnalyzer.java @@ -0,0 +1,154 @@ +package org.nittalab.dtram.languageserver.utils; + +import org.eclipse.lsp4j.SemanticTokenTypes; +import org.nittalab.dtram.languageserver.model.*; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link SemanticAnalyzer} class analyzes the tokens and makes them semantics. + * + * @author Shohei Yamagiwa + * @since 0.1 + */ +public class SemanticAnalyzer { + /** + * Analyzes tokens by semantics. + * + * @param tokens The tokens to be analyzed + * @return Analyzed semantic tokens + * @author Shohei Yamagiwa + * @since 0.1 + */ + public static List analyze(List tokens) { + tokens = removeSpaces(tokens); // We don't need spaces anymore to analyze codes. + tokens = analyzeAtomicTokens(tokens); + tokens = analyzeStringTokens(tokens); + tokens = analyzeTypeTokens(tokens); + // TODO: Analyze functions and constants at least. + return tokens; + } + + /** + * Removes all spaces from given tokens to analyze. + * + * @param tokens All tokens to be analyzed. + * @return All tokens without unnecessary spaces. + * @author Shohei Yamagiwa + * @since 0.1 + */ + protected static List removeSpaces(List tokens) { + List newTokens = new ArrayList<>(); + for (Token token : tokens) { + if (token.getText().equals(Tokens.SPACE)) { + continue; + } + newTokens.add(token); + } + return newTokens; + } + + /** + * Analyzes atomic tokens + * + * @param tokens The tokens to be analyzed + * @return Tokens with some analyzed atomic tokens. + * @author Shohei Yamagiwa + * @since 0.1 + */ + protected static List analyzeAtomicTokens(List tokens) { + List newTokens = new ArrayList<>(); + for (Token token : tokens) { + if (!token.isAtomic()) { + newTokens.add(token); + continue; + } + switch (token.getText()) { + case Operators.ADD, Operators.SUB, Operators.MUL, Operators.DIV, Operators.MOD, + Operators.EQ, Operators.NEQ, + Operators.GT, Operators.LT, Operators.GE, Operators.LE, + Operators.AND, Operators.OR, Operators.NEG, + Operators.ASSIGNMENT -> newTokens.add(new SemanticToken(SemanticTokenTypes.Operator, token)); + case Tokens.DOUBLE_QUOT -> newTokens.add(new SemanticToken(SemanticTokenTypes.String, token)); + default -> { + if (token.getText().startsWith(Comments.COMMENT)) { // Single-line comment + newTokens.add(new SemanticToken(SemanticTokenTypes.Comment, token)); + } else if (token.getText().startsWith(Comments.MULTILINE_COMMENT_START)) { // Multiline comment + newTokens.add(new SemanticToken(SemanticTokenTypes.Comment, token)); + } else { + newTokens.add(token); // Brackets, comma, colon and dot + } + } + } + } + return newTokens; + } + + /** + * Analyzes string tokens. + * + * @param tokens The tokens to be analyzed + * @return The tokens with analyzed string tokens. + * @author Shohei Yamagiwa + * @since 0.1 + */ + protected static List analyzeStringTokens(List tokens) { + List newTokens = new ArrayList<>(); + boolean isString = false; + for (Token token : tokens) { + if (token instanceof SemanticToken semanticToken) { + if (semanticToken.getSemanticType().equals(SemanticTokenTypes.String)) { + isString = !isString; + } + newTokens.add(token); + } else { + if (isString) { + SemanticToken newToken = new SemanticToken(SemanticTokenTypes.String, token); + newTokens.add(newToken); + } else { + newTokens.add(token); + } + } + } + return newTokens; + } + + /** + * Analyzes type tokens. + * + * @param tokens The tokens to be analyzed + * @return The tokens with analyzed type tokens. + * @author Shohei Yamagiwa + * @since 0.1 + */ + protected static List analyzeTypeTokens(List tokens) { + List newTokens = new ArrayList<>(); + boolean wasColon = false; // A token that positions before type-token must be a colon. + for (Token token : tokens) { + if (token instanceof SemanticToken) { + newTokens.add(token); + continue; + } + if (token.getText().equals(Tokens.COLON)) { + newTokens.add(token); + wasColon = true; + continue; + } + if (wasColon) { + switch (token.getText()) { + case Types.INTEGER, Types.LONG, Types.FLOAT, Types.DOUBLE, Types.BOOLEAN, Types.STRING, + Types.LIST, Types.PAIR, Types.TUPLE, Types.MAP, Types.JSON -> { + SemanticToken newToken = new SemanticToken(SemanticTokenTypes.Type, token); + newTokens.add(newToken); + } + default -> newTokens.add(token); + } + wasColon = false; + } else { + newTokens.add(token); + } + } + return newTokens; + } +}