Skip to content

Commit

Permalink
Added Expression Language support
Browse files Browse the repository at this point in the history
  • Loading branch information
adamwojs committed Jun 3, 2021
1 parent 5d6fd5d commit 2e248f5
Show file tree
Hide file tree
Showing 16 changed files with 555 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ out/
/.idea/misc.xml
/.gradle
/build
/src/main/gen
24 changes: 24 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.jetbrains.grammarkit.tasks.*

plugins {
id "org.jetbrains.intellij" version "0.4.11"
id 'com.palantir.git-version' version "0.11.0"
id "org.jetbrains.grammarkit" version "2021.1.3"
}

def htmlFixer = { htmlFile -> file(htmlFile).text.replace('<html>', '').replace('</html>', '') }
Expand All @@ -14,6 +16,7 @@ sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11

apply plugin: 'org.jetbrains.intellij'
apply plugin: 'org.jetbrains.grammarkit'

intellij {
version ideaVersion
Expand Down Expand Up @@ -53,6 +56,24 @@ publishPlugin {
token System.getenv('IJ_TOKEN')
}

task generateExpressionLanguageLexer(type: GenerateLexer) {
source = 'src/main/java/fr/adrienbrault/idea/symfony2plugin/expressionLanguage/ExpressionLanguage.flex'
targetDir = 'src/main/gen/fr/adrienbrault/idea/symfony2plugin/expressionLanguage/'
targetClass = 'ExpressionLanguageLexer'
}

task generateExpressionLanguageParser(type: GenerateParser) {
source = 'src/main/java/fr/adrienbrault/idea/symfony2plugin/expressionLanguage/ExpressionLanguage.bnf'
targetRoot = 'src/main/gen'
pathToParser = 'src/main/gen/fr/adrienbrault/idea/symfony2plugin/expressionLanguage/ExpressionLanguageParser.java'
pathToPsiRoot = 'src/main/gen/fr/adrienbrault/idea/symfony2plugin/expressionLanguage/psi'
}

compileJava {
dependsOn generateExpressionLanguageLexer
dependsOn generateExpressionLanguageParser
}

group 'fr.adrienbrault.idea.symfony2plugin'

def details = versionDetails()
Expand All @@ -67,3 +88,6 @@ wrapper {
}

test.testLogging.exceptionFormat = TestExceptionFormat.FULL

// Include the generated files in the source set
sourceSets.main.java.srcDirs 'src/main/gen'
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
{
parserClass="fr.adrienbrault.idea.symfony2plugin.expressionLanguage.ExpressionLanguageParser"

extends="com.intellij.extapi.psi.ASTWrapperPsiElement"

psiClassPrefix="ExpressionLanguage"
psiImplClassSuffix="Impl"
psiPackage="fr.adrienbrault.idea.symfony2plugin.expressionLanguage.psi"
psiImplPackage="fr.adrienbrault.idea.symfony2plugin.expressionLanguage.psi.impl"

elementTypeHolderClass="fr.adrienbrault.idea.symfony2plugin.expressionLanguage.psi.ExpressionLanguageTypes"
elementTypeClass="fr.adrienbrault.idea.symfony2plugin.expressionLanguage.psi.ExpressionLanguageElementType"
tokenTypeClass="fr.adrienbrault.idea.symfony2plugin.expressionLanguage.psi.ExpressionLanguageTokenType"

extends(".*expr")=expr
tokens=[
space='regexp:\s+'
number='regexp:\d+(\.\d*)?'
null="regexp:NULL|null"
true="regexp:TRUE|true"
false="regexp:FALSE|false"
id='regexp:\p{Alpha}\w*'
string="regexp:('([^'\\]|\\.)*'|\"([^\"\\]|\\.)*\")"
OP_OR="regexp:or|\|\|"
OP_AND="regexp:and|&&"
OP_BIT_OR="|"
OP_BIT_XOR="^"
OP_BIT_AND="&"
OP_IDENTICAL="==="
OP_EQ="=="
OP_NOT_IDENTICAL="!=="
OP_NEQ="!="
OP_LT="<"
OP_GT=">"
OP_GTE=">="
OP_LTE="<="
OP_NOT_IN="not in"
OP_IN="in"
OP_MATCHES="matches"
OP_RANGE=".."
OP_PLUS="+"
OP_MINUS="-"
OP_CONCAT="~"
OP_MUL="*"
OP_DIV="/"
OP_MOD="%"
OP_POW="**"
OP_NOT='regexp:not|\!'

L_ROUND_BRACKET="("
R_ROUND_BRACKET=")"
L_CURLY_BRACKET="{"
R_CURLY_BRACKET="}"
L_SQUARE_BRACKET="["
R_SQUARE_BRACKET="]"

syntax='regexp:[?:.,]'
]
}

root ::= expr

expr ::= ternary_group
| or_expr
| and_expr
| bit_or_expr
| bit_xor_expr
| bit_and_expr
| rel_group
| range_expr
| add_group
| concat_expr
| mul_group
| not_expr
| exp_expr
| sign_group
| qualification_expr
| array_access_expr
| primary_group

private sign_group ::= unary_plus_expr | unary_min_expr
private mul_group ::= mul_expr | div_expr | mod_expr
private add_group ::= plus_expr | minus_expr
private ternary_group ::= ternary_expr | elvis_expr

private rel_group ::= eq_strict_expr
| eq_expr
| neq_strict_expr
| neq_expr
| gte_expr
| gt_expr
| lt_expr
| lte_expr
| not_in_expr
| in_expr
| matches_expr

private primary_group ::= simple_ref_expr | literal_expr | array_expr | hash_expr | call_expr | paren_expr

not_expr ::= OP_NOT expr
unary_min_expr ::= OP_MINUS expr
unary_plus_expr ::= OP_PLUS expr
div_expr ::= expr OP_DIV expr
mul_expr ::= expr OP_MUL expr
mod_expr ::= expr OP_MOD expr
concat_expr ::= expr OP_CONCAT expr
minus_expr ::= expr OP_MINUS expr
plus_expr ::= expr OP_PLUS expr
range_expr ::= expr OP_RANGE expr
eq_strict_expr ::= expr OP_IDENTICAL expr
neq_strict_expr ::= expr OP_NOT_IDENTICAL expr
eq_expr ::= expr OP_EQ expr
neq_expr ::= expr OP_NEQ expr
gt_expr ::= expr OP_GT expr
gte_expr ::= expr OP_GTE expr
lt_expr ::= expr OP_LT expr
lte_expr ::= expr OP_LTE expr
not_in_expr ::= expr OP_NOT_IN expr
in_expr ::= expr OP_IN expr
matches_expr ::= expr OP_MATCHES expr
or_expr ::= expr OP_OR expr
and_expr ::= expr OP_AND expr
bit_and_expr ::= expr OP_BIT_AND expr
bit_or_expr ::= expr OP_BIT_OR expr
bit_xor_expr ::= expr OP_BIT_XOR expr
exp_expr ::= expr (OP_POW expr)+
paren_expr ::= L_ROUND_BRACKET expr R_ROUND_BRACKET
ternary_expr ::= expr '?' expr (':' expr)?
elvis_expr ::= expr '?:' expr

fake ref_expr ::= expr? '.' identifier
simple_ref_expr ::= identifier {extends=ref_expr elementType=ref_expr}
qualification_expr ::= expr '.' identifier {extends=ref_expr elementType=ref_expr}
array_access_expr ::= expr L_SQUARE_BRACKET expr R_SQUARE_BRACKET {extends=ref_expr elementType=ref_expr}

literal_expr ::= number | string | true | false | null
array_expr ::= L_SQUARE_BRACKET expr_list? R_SQUARE_BRACKET
hash_expr ::= L_CURLY_BRACKET hash_entries? R_CURLY_BRACKET

call_expr ::= expr L_ROUND_BRACKET expr_list? R_ROUND_BRACKET

private hash_entries ::= hash_entry (',' hash_entry)*
private hash_entry ::= identifier ':' expr
private expr_list ::= expr (',' expr_list)*

identifier ::= id
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package fr.adrienbrault.idea.symfony2plugin.expressionLanguage;

import com.intellij.lexer.FlexLexer;
import com.intellij.psi.tree.IElementType;

import static com.intellij.psi.TokenType.BAD_CHARACTER;
import static com.intellij.psi.TokenType.WHITE_SPACE;
import static fr.adrienbrault.idea.symfony2plugin.expressionLanguage.psi.ExpressionLanguageTypes.*;

%%

%{
public ExpressionLanguageLexer() {
this((java.io.Reader)null);
}
%}

%public
%class ExpressionLanguageLexer
%implements FlexLexer
%function advance
%type IElementType
%unicode

EOL=\R
WHITE_SPACE=\s+

SPACE=[ \t\n\x0B\f\r]+
NUMBER=[0-9]+(\.[0-9]*)?
NULL=NULL|null
TRUE=TRUE|true
FALSE=FALSE|false
ID=[:letter:][a-zA-Z_0-9]*
STRING=('([^'\\]|\\.)*'|\"([^\"\\]|\\.)*\")
OP_OR=or|\|\|
OP_AND=and|&&
OP_NOT=not|\!
SYNTAX=[?:.,]

%%
<YYINITIAL> {
{WHITE_SPACE} { return WHITE_SPACE; }

"|" { return OP_BIT_OR; }
"^" { return OP_BIT_XOR; }
"&" { return OP_BIT_AND; }
"===" { return OP_IDENTICAL; }
"==" { return OP_EQ; }
"!==" { return OP_NOT_IDENTICAL; }
"!=" { return OP_NEQ; }
"<" { return OP_LT; }
">" { return OP_GT; }
">=" { return OP_GTE; }
"<=" { return OP_LTE; }
"not in" { return OP_NOT_IN; }
"in" { return OP_IN; }
"matches" { return OP_MATCHES; }
".." { return OP_RANGE; }
"+" { return OP_PLUS; }
"-" { return OP_MINUS; }
"~" { return OP_CONCAT; }
"*" { return OP_MUL; }
"/" { return OP_DIV; }
"%" { return OP_MOD; }
"**" { return OP_POW; }
"(" { return L_ROUND_BRACKET; }
")" { return R_ROUND_BRACKET; }
"{" { return L_CURLY_BRACKET; }
"}" { return R_CURLY_BRACKET; }
"[" { return L_SQUARE_BRACKET; }
"]" { return R_SQUARE_BRACKET; }

{SPACE} { return SPACE; }
{NUMBER} { return NUMBER; }
{NULL} { return NULL; }
{TRUE} { return TRUE; }
{FALSE} { return FALSE; }
{ID} { return ID; }
{STRING} { return STRING; }
{OP_OR} { return OP_OR; }
{OP_AND} { return OP_AND; }
{OP_NOT} { return OP_NOT; }
{SYNTAX} { return SYNTAX; }

}

[^] { return BAD_CHARACTER; }
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package fr.adrienbrault.idea.symfony2plugin.expressionLanguage;

import com.intellij.lang.Language;

public class ExpressionLanguage extends Language {

public static final ExpressionLanguage INSTANCE = new ExpressionLanguage();

private ExpressionLanguage() {
super("Symfony Expression Language");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package fr.adrienbrault.idea.symfony2plugin.expressionLanguage;

import com.intellij.lang.BracePair;
import com.intellij.lang.PairedBraceMatcher;
import com.intellij.psi.PsiFile;
import com.intellij.psi.tree.IElementType;
import fr.adrienbrault.idea.symfony2plugin.expressionLanguage.psi.ExpressionLanguageTypes;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class ExpressionLanguageBraceMatcher implements PairedBraceMatcher {
private static BracePair[] PAIRS = {
new BracePair(
ExpressionLanguageTypes.L_ROUND_BRACKET,
ExpressionLanguageTypes.R_ROUND_BRACKET,
true
),
new BracePair(
ExpressionLanguageTypes.L_SQUARE_BRACKET,
ExpressionLanguageTypes.R_SQUARE_BRACKET,
true
),
new BracePair(
ExpressionLanguageTypes.L_CURLY_BRACKET,
ExpressionLanguageTypes.R_CURLY_BRACKET,
true
),
};

@Override
public BracePair[] getPairs() {
return PAIRS;
}

@Override
public boolean isPairedBracesAllowedBeforeType(@NotNull IElementType lbraceType, @Nullable IElementType contextType) {
return false;
}

@Override
public int getCodeConstructStart(PsiFile file, int openingBraceOffset) {
return openingBraceOffset;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package fr.adrienbrault.idea.symfony2plugin.expressionLanguage;

import com.intellij.openapi.fileTypes.LanguageFileType;
import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;

public class ExpressionLanguageFileType extends LanguageFileType {

public static final ExpressionLanguageFileType INSTANCE = new ExpressionLanguageFileType();

private ExpressionLanguageFileType() {
super(ExpressionLanguage.INSTANCE);
}

@NotNull
@Override
public String getName() {
return "Expression Language File";
}

@NotNull
@Override
public String getDescription() {
return "Expression Language file";
}

@NotNull
@Override
public String getDefaultExtension() {
return "sfel";
}

@Override
@Nullable
public Icon getIcon() {
return Symfony2Icons.SYMFONY;
}
}
Loading

0 comments on commit 2e248f5

Please sign in to comment.