@tsomaiatech/moxite 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -0
- package/idea-plugin/build.gradle.kts +60 -0
- package/idea-plugin/gradle.properties +14 -0
- package/idea-plugin/settings.gradle.kts +1 -0
- package/idea-plugin/src/main/flex/Moxite.flex +101 -0
- package/idea-plugin/src/main/grammar/Moxite.bnf +79 -0
- package/idea-plugin/src/main/java/tech/tsomaia/moxite/idea/MoxiteFileType.java +39 -0
- package/idea-plugin/src/main/java/tech/tsomaia/moxite/idea/MoxiteIcons.java +9 -0
- package/idea-plugin/src/main/java/tech/tsomaia/moxite/idea/MoxiteLanguage.java +11 -0
- package/idea-plugin/src/main/java/tech/tsomaia/moxite/idea/MoxiteParserDefinition.java +77 -0
- package/idea-plugin/src/main/java/tech/tsomaia/moxite/idea/MoxiteSyntaxHighlighter.java +82 -0
- package/idea-plugin/src/main/java/tech/tsomaia/moxite/idea/MoxiteSyntaxHighlighterFactory.java +16 -0
- package/idea-plugin/src/main/java/tech/tsomaia/moxite/idea/lexer/MoxiteLexerAdapter.java +9 -0
- package/idea-plugin/src/main/java/tech/tsomaia/moxite/idea/parser/MoxiteParserUtil.java +6 -0
- package/idea-plugin/src/main/java/tech/tsomaia/moxite/idea/psi/MoxiteElementType.java +12 -0
- package/idea-plugin/src/main/java/tech/tsomaia/moxite/idea/psi/MoxiteFile.java +25 -0
- package/idea-plugin/src/main/java/tech/tsomaia/moxite/idea/psi/MoxiteTokenType.java +17 -0
- package/idea-plugin/src/main/resources/META-INF/plugin.xml +32 -0
- package/package.json +36 -0
- package/src/__tests__/template-engine.test.ts +437 -0
- package/src/template-engine.ts +480 -0
- package/src/template-manager.ts +75 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Moxite Template Engine
|
|
2
|
+
|
|
3
|
+
Moxite is an extremely fast, structural, and strictly evaluated template engine designed to be completely **indestructible**.
|
|
4
|
+
|
|
5
|
+
Drawing inspiration from modern control flows like Angular 19, Moxite enforces a separation of logic and presentation through a pristine, character-by-character parsed Abstract Syntax Tree (AST). It completely eliminates the use of fuzzy Regular Expressions for parsing, meaning it is impossible to crash the engine with malformed overlapping templates, escaped quotes, or structural injection attacks.
|
|
6
|
+
|
|
7
|
+
## Why Moxite?
|
|
8
|
+
|
|
9
|
+
Modern template engines (like Jinja, EJS, and Handlebars) rely heavily on Regular Expressions and have slowly mutated into sprawling, poorly-implemented programming languages that execute inline logic, define JSON objects inline, and succumb to context leaks.
|
|
10
|
+
|
|
11
|
+
Moxite takes a diametrically opposed approach:
|
|
12
|
+
1. **Zero Regex Parsing**: Every token is mapped through a strict, atomic Lexical State machine and evaluated via a recursive descent Parser.
|
|
13
|
+
2. **Zero Inline Objects**: The Expression Lexer intentionally forbids array closures `[1, 2]` or object structures `{k: v}` to prevent Turing-complete bloat and force developers to construct their data in the core application logic (e.g. TypeScript/Java), not in the view layer.
|
|
14
|
+
3. **Implicit Safe Navigation**: The engine automatically protects against `null` or `undefined` property access without throwing exceptions, turning `{{ user.profile.name }}` into an implicit `{{ user?.profile?.name }}`.
|
|
15
|
+
4. **Absolute Polyglot Isolation**: Moxite's domain syntax (`@` and `{{ }}`) will perfectly isolate itself even when overlapping natively with 10 other template engines or syntaxes in the exact same file (C# Razor, PHP, Bash, JSON-LD, etc.).
|
|
16
|
+
|
|
17
|
+
## Syntax Outline
|
|
18
|
+
|
|
19
|
+
### Interpolation & Implicit Chaining
|
|
20
|
+
```mx
|
|
21
|
+
Hello, {{ user.name }}!
|
|
22
|
+
Implicit Safe Output: {{ user.deep.missing.path.shouldNotCrash }}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Conditionals (`@if`)
|
|
26
|
+
```mx
|
|
27
|
+
@if (user.isAdmin)
|
|
28
|
+
Welcome Administrator.
|
|
29
|
+
@else if (user.isGuest === "yes")
|
|
30
|
+
Welcome Guest.
|
|
31
|
+
@else
|
|
32
|
+
Welcome User.
|
|
33
|
+
@endif
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Loops (`@for`)
|
|
37
|
+
```mx
|
|
38
|
+
@for (item of items)
|
|
39
|
+
Item: {{ item.name }}
|
|
40
|
+
@endfor
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Immutable Block Scoping (`@const`)
|
|
44
|
+
```mx
|
|
45
|
+
@const greeting = "Hello"
|
|
46
|
+
@const role = user.role
|
|
47
|
+
|
|
48
|
+
{{ greeting }}, you are a {{ role }}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Pipes (`|`)
|
|
52
|
+
Pipes allow you to pass values through pure formatting functions registered in the evaluation context.
|
|
53
|
+
```mx
|
|
54
|
+
{{ user.name | upper }}
|
|
55
|
+
{{ total | currency: "USD" }}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Structure
|
|
59
|
+
|
|
60
|
+
- `src/`: Contains the pure TypeScript reference implementation of the strict Lexer, AST Parser, and Evaluator (`template-engine.ts`).
|
|
61
|
+
- `idea-plugin/`: An independent Gradle project containing the IntelliJ IDEA plugin. Utilizing Grammar-Kit and JFlex, it tokenizes `.mx` files with the exact deterministic boundary rules as the TypeScript engine, providing native JetBrains syntax highlighting and completion.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
plugins {
|
|
2
|
+
id("java")
|
|
3
|
+
id("org.jetbrains.intellij") version "1.17.4"
|
|
4
|
+
id("org.jetbrains.grammarkit") version "2022.3.2.2"
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
group = project.properties["pluginGroup"] as String
|
|
8
|
+
version = project.properties["pluginVersion"] as String
|
|
9
|
+
|
|
10
|
+
repositories {
|
|
11
|
+
mavenCentral()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Configure Gradle IntelliJ Plugin
|
|
15
|
+
// Read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html
|
|
16
|
+
intellij {
|
|
17
|
+
pluginName.set(project.properties["pluginName"] as String)
|
|
18
|
+
version.set(project.properties["platformVersion"] as String)
|
|
19
|
+
type.set(project.properties["platformType"] as String)
|
|
20
|
+
|
|
21
|
+
// Plugin Dependencies. Uses `java` plugin.
|
|
22
|
+
plugins.set(listOf("com.intellij.java"))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
tasks {
|
|
26
|
+
// Set the JVM compatibility versions
|
|
27
|
+
withType<JavaCompile> {
|
|
28
|
+
sourceCompatibility = project.properties["javaVersion"] as String
|
|
29
|
+
targetCompatibility = project.properties["javaVersion"] as String
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
patchPluginXml {
|
|
33
|
+
sinceBuild.set(project.properties["pluginSinceBuild"] as String)
|
|
34
|
+
untilBuild.set(project.properties["pluginUntilBuild"] as String)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
signPlugin {
|
|
38
|
+
certificateChain.set(System.getenv("CERTIFICATE_CHAIN"))
|
|
39
|
+
privateKey.set(System.getenv("PRIVATE_KEY"))
|
|
40
|
+
password.set(System.getenv("PRIVATE_KEY_PASSWORD"))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
publishPlugin {
|
|
44
|
+
token.set(System.getenv("PUBLISH_TOKEN"))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
sourceSets {
|
|
49
|
+
main {
|
|
50
|
+
java.srcDirs("src/main/java", "src/main/gen")
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Ensure the generate sources directories exist
|
|
55
|
+
val generateLexer = tasks.named("generateLexer")
|
|
56
|
+
val generateParser = tasks.named("generateParser")
|
|
57
|
+
|
|
58
|
+
tasks.withType<JavaCompile> {
|
|
59
|
+
dependsOn(generateLexer, generateParser)
|
|
60
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
pluginGroup = "tech.tsomaia.moxite"
|
|
2
|
+
pluginName = "Moxite Template Support"
|
|
3
|
+
pluginVersion = "1.0.0"
|
|
4
|
+
|
|
5
|
+
# Supported IDE versions
|
|
6
|
+
pluginSinceBuild = "231"
|
|
7
|
+
pluginUntilBuild = "241.*"
|
|
8
|
+
|
|
9
|
+
# IntelliJ Platform Properties
|
|
10
|
+
platformType = "IC"
|
|
11
|
+
platformVersion = "2023.1.5"
|
|
12
|
+
|
|
13
|
+
# Java language level
|
|
14
|
+
javaVersion = "17"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rootProject.name = "moxite-idea"
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
package tech.tsomaia.moxite.idea.lexer;
|
|
2
|
+
|
|
3
|
+
import com.intellij.lexer.FlexLexer;
|
|
4
|
+
import com.intellij.psi.tree.IElementType;
|
|
5
|
+
import tech.tsomaia.moxite.idea.psi.MoxiteTypes;
|
|
6
|
+
import com.intellij.psi.TokenType;
|
|
7
|
+
|
|
8
|
+
%%
|
|
9
|
+
|
|
10
|
+
%class _MoxiteLexer
|
|
11
|
+
%implements FlexLexer
|
|
12
|
+
%unicode
|
|
13
|
+
%function advance
|
|
14
|
+
%type IElementType
|
|
15
|
+
%eof{ return;
|
|
16
|
+
%eof}
|
|
17
|
+
|
|
18
|
+
// Token definitions
|
|
19
|
+
WHITE_SPACE=[\ \n\r\t]+
|
|
20
|
+
IDENTIFIER=[a-zA-Z_$][a-zA-Z0-9_$]*
|
|
21
|
+
NUMBER=[0-9]+(\.[0-9]+)?
|
|
22
|
+
STRING=\"[^\"]*\"|'[^']*'
|
|
23
|
+
|
|
24
|
+
%state IN_TAG_ARGS
|
|
25
|
+
%state IN_INTERPOLATION
|
|
26
|
+
|
|
27
|
+
%%
|
|
28
|
+
|
|
29
|
+
// When in standard TEXT mode, we look for { { or @
|
|
30
|
+
<YYINITIAL> {
|
|
31
|
+
"{{" { yybegin(IN_INTERPOLATION); return MoxiteTypes.INTERP_START; }
|
|
32
|
+
|
|
33
|
+
"@if" / [^a-zA-Z0-9_$] { yybegin(IN_TAG_ARGS); return MoxiteTypes.TAG_IF; }
|
|
34
|
+
"@else if" / [^a-zA-Z0-9_$] { yybegin(IN_TAG_ARGS); return MoxiteTypes.TAG_ELSE_IF; }
|
|
35
|
+
"@else" / [^a-zA-Z0-9_$] { return MoxiteTypes.TAG_ELSE; }
|
|
36
|
+
"@endif" / [^a-zA-Z0-9_$] { return MoxiteTypes.TAG_ENDIF; }
|
|
37
|
+
|
|
38
|
+
"@for" / [^a-zA-Z0-9_$] { yybegin(IN_TAG_ARGS); return MoxiteTypes.TAG_FOR; }
|
|
39
|
+
"@endfor" / [^a-zA-Z0-9_$] { return MoxiteTypes.TAG_ENDFOR; }
|
|
40
|
+
|
|
41
|
+
"@const" / [^a-zA-Z0-9_$] { yybegin(IN_TAG_ARGS); return MoxiteTypes.TAG_CONST; }
|
|
42
|
+
|
|
43
|
+
// Anything else is just text
|
|
44
|
+
[^\{@]+ { return MoxiteTypes.TEXT; }
|
|
45
|
+
"{" { return MoxiteTypes.TEXT; }
|
|
46
|
+
"@" { return MoxiteTypes.TEXT; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// When inside parentheses of a loop or condition
|
|
50
|
+
<IN_TAG_ARGS> {
|
|
51
|
+
{WHITE_SPACE} { return TokenType.WHITE_SPACE; }
|
|
52
|
+
")" { yybegin(YYINITIAL); return MoxiteTypes.RPAREN; }
|
|
53
|
+
"(" { return MoxiteTypes.LPAREN; }
|
|
54
|
+
"[" { return MoxiteTypes.LBRACKET; }
|
|
55
|
+
"]" { return MoxiteTypes.RBRACKET; }
|
|
56
|
+
"." { return MoxiteTypes.DOT; }
|
|
57
|
+
"," { return MoxiteTypes.COMMA; }
|
|
58
|
+
":" { return MoxiteTypes.COLON; }
|
|
59
|
+
"|" { return MoxiteTypes.PIPE; }
|
|
60
|
+
"=" { return MoxiteTypes.EQ; }
|
|
61
|
+
|
|
62
|
+
"==="|!=="|=="|"!=" { return MoxiteTypes.EQUALITY_OP; }
|
|
63
|
+
"<="|">="|"<"|">" { return MoxiteTypes.COMPARE_OP; }
|
|
64
|
+
|
|
65
|
+
"of" { return MoxiteTypes.IDENTIFIER; } // Handled structurally in BNF
|
|
66
|
+
{IDENTIFIER} { return MoxiteTypes.IDENTIFIER; }
|
|
67
|
+
{NUMBER} { return MoxiteTypes.NUMBER; }
|
|
68
|
+
{STRING} { return MoxiteTypes.STRING; }
|
|
69
|
+
|
|
70
|
+
// Fallback for weird characters that are technically invalid but shouldn't crash the lexer
|
|
71
|
+
. { return TokenType.BAD_CHARACTER; }
|
|
72
|
+
|
|
73
|
+
// Handle unexpected newline to exit state if unclosed
|
|
74
|
+
\n { yybegin(YYINITIAL); return TokenType.WHITE_SPACE; }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
<IN_INTERPOLATION> {
|
|
78
|
+
{WHITE_SPACE} { return TokenType.WHITE_SPACE; }
|
|
79
|
+
"}}" { yybegin(YYINITIAL); return MoxiteTypes.INTERP_END; }
|
|
80
|
+
|
|
81
|
+
"(" { return MoxiteTypes.LPAREN; }
|
|
82
|
+
")" { return MoxiteTypes.RPAREN; }
|
|
83
|
+
"[" { return MoxiteTypes.LBRACKET; }
|
|
84
|
+
"]" { return MoxiteTypes.RBRACKET; }
|
|
85
|
+
"." { return MoxiteTypes.DOT; }
|
|
86
|
+
"," { return MoxiteTypes.COMMA; }
|
|
87
|
+
":" { return MoxiteTypes.COLON; }
|
|
88
|
+
"|" { return MoxiteTypes.PIPE; }
|
|
89
|
+
"=" { return MoxiteTypes.EQ; }
|
|
90
|
+
|
|
91
|
+
"==="|!=="|=="|"!=" { return MoxiteTypes.EQUALITY_OP; }
|
|
92
|
+
"<="|">="|"<"|">" { return MoxiteTypes.COMPARE_OP; }
|
|
93
|
+
|
|
94
|
+
{IDENTIFIER} { return MoxiteTypes.IDENTIFIER; }
|
|
95
|
+
{NUMBER} { return MoxiteTypes.NUMBER; }
|
|
96
|
+
{STRING} { return MoxiteTypes.STRING; }
|
|
97
|
+
|
|
98
|
+
. { return TokenType.BAD_CHARACTER; }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
[^] { return TokenType.BAD_CHARACTER; }
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
parserClass="tech.tsomaia.moxite.idea.parser.MoxiteParser"
|
|
3
|
+
parserUtilClass="tech.tsomaia.moxite.idea.parser.MoxiteParserUtil"
|
|
4
|
+
|
|
5
|
+
extends="com.intellij.extapi.psi.ASTWrapperPsiElement"
|
|
6
|
+
|
|
7
|
+
psiClassPrefix="Moxite"
|
|
8
|
+
psiImplClassSuffix="Impl"
|
|
9
|
+
psiPackage="tech.tsomaia.moxite.idea.psi"
|
|
10
|
+
psiImplPackage="tech.tsomaia.moxite.idea.psi.impl"
|
|
11
|
+
|
|
12
|
+
elementTypeHolderClass="tech.tsomaia.moxite.idea.psi.MoxiteTypes"
|
|
13
|
+
elementTypeClass="tech.tsomaia.moxite.idea.psi.MoxiteElementType"
|
|
14
|
+
tokenTypeClass="tech.tsomaia.moxite.idea.psi.MoxiteTokenType"
|
|
15
|
+
|
|
16
|
+
tokens = [
|
|
17
|
+
// These will be returned by the JFlex Lexer
|
|
18
|
+
TEXT="TEXT"
|
|
19
|
+
INTERP_START="{{"
|
|
20
|
+
INTERP_END="}}"
|
|
21
|
+
|
|
22
|
+
TAG_IF="@if"
|
|
23
|
+
TAG_ELSE_IF="@else if"
|
|
24
|
+
TAG_ELSE="@else"
|
|
25
|
+
TAG_ENDIF="@endif"
|
|
26
|
+
|
|
27
|
+
TAG_FOR="@for"
|
|
28
|
+
TAG_ENDFOR="@endfor"
|
|
29
|
+
|
|
30
|
+
TAG_CONST="@const"
|
|
31
|
+
|
|
32
|
+
LPAREN="("
|
|
33
|
+
RPAREN=")"
|
|
34
|
+
LBRACKET="["
|
|
35
|
+
RBRACKET="]"
|
|
36
|
+
DOT="."
|
|
37
|
+
COMMA=","
|
|
38
|
+
COLON=":"
|
|
39
|
+
PIPE="|"
|
|
40
|
+
EQ="="
|
|
41
|
+
|
|
42
|
+
EQUALITY_OP="regexp:(===|!==|==|!=)"
|
|
43
|
+
COMPARE_OP="regexp:(<|>|<=|>=)"
|
|
44
|
+
|
|
45
|
+
IDENTIFIER="regexp:[a-zA-Z_$][a-zA-Z0-9_$]*"
|
|
46
|
+
NUMBER="regexp:[0-9]+(\.[0-9]+)?"
|
|
47
|
+
STRING="regexp:\"[^\"]*\"|'[^']*'"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
moxiteFile ::= item_*
|
|
52
|
+
|
|
53
|
+
private item_ ::= ( textBlock | interpolation | ifBlock | forBlock | constBlock )
|
|
54
|
+
|
|
55
|
+
textBlock ::= TEXT
|
|
56
|
+
|
|
57
|
+
interpolation ::= INTERP_START expression INTERP_END
|
|
58
|
+
|
|
59
|
+
ifBlock ::= ifStatement elseIfStatement* elseStatement? TAG_ENDIF
|
|
60
|
+
ifStatement ::= TAG_IF LPAREN expression RPAREN item_*
|
|
61
|
+
elseIfStatement ::= TAG_ELSE_IF LPAREN expression RPAREN item_*
|
|
62
|
+
elseStatement ::= TAG_ELSE item_*
|
|
63
|
+
|
|
64
|
+
forBlock ::= TAG_FOR LPAREN IDENTIFIER IDENTIFIER /* 'of' */ expression RPAREN item_* TAG_ENDFOR
|
|
65
|
+
|
|
66
|
+
constBlock ::= TAG_CONST IDENTIFIER EQ expression
|
|
67
|
+
|
|
68
|
+
// Expression rules simplified for highlighting
|
|
69
|
+
expression ::= pipeExpression
|
|
70
|
+
|
|
71
|
+
pipeExpression ::= equalityExpression (PIPE IDENTIFIER (COLON equalityExpression (COMMA equalityExpression)* )? )*
|
|
72
|
+
|
|
73
|
+
equalityExpression ::= compareExpression (EQUALITY_OP compareExpression)*
|
|
74
|
+
|
|
75
|
+
compareExpression ::= primaryExpression (COMPARE_OP primaryExpression)*
|
|
76
|
+
|
|
77
|
+
primaryExpression ::= baseExpression ( DOT IDENTIFIER | LBRACKET pipeExpression RBRACKET )*
|
|
78
|
+
|
|
79
|
+
baseExpression ::= STRING | NUMBER | IDENTIFIER | LPAREN pipeExpression RPAREN
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
package tech.tsomaia.moxite.idea;
|
|
2
|
+
|
|
3
|
+
import com.intellij.openapi.fileTypes.LanguageFileType;
|
|
4
|
+
import org.jetbrains.annotations.NotNull;
|
|
5
|
+
import org.jetbrains.annotations.Nullable;
|
|
6
|
+
|
|
7
|
+
import javax.swing.*;
|
|
8
|
+
|
|
9
|
+
public class MoxiteFileType extends LanguageFileType {
|
|
10
|
+
public static final MoxiteFileType INSTANCE = new MoxiteFileType();
|
|
11
|
+
|
|
12
|
+
private MoxiteFileType() {
|
|
13
|
+
super(MoxiteLanguage.INSTANCE);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@NotNull
|
|
17
|
+
@Override
|
|
18
|
+
public String getName() {
|
|
19
|
+
return "Moxite Template";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@NotNull
|
|
23
|
+
@Override
|
|
24
|
+
public String getDescription() {
|
|
25
|
+
return "Moxite template logic format";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@NotNull
|
|
29
|
+
@Override
|
|
30
|
+
public String getDefaultExtension() {
|
|
31
|
+
return "mx";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@Nullable
|
|
35
|
+
@Override
|
|
36
|
+
public Icon getIcon() {
|
|
37
|
+
return MoxiteIcons.FILE;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
package tech.tsomaia.moxite.idea;
|
|
2
|
+
|
|
3
|
+
import com.intellij.lang.ASTNode;
|
|
4
|
+
import com.intellij.lang.ParserDefinition;
|
|
5
|
+
import com.intellij.lang.PsiParser;
|
|
6
|
+
import com.intellij.lexer.Lexer;
|
|
7
|
+
import com.intellij.openapi.project.Project;
|
|
8
|
+
import com.intellij.psi.FileViewProvider;
|
|
9
|
+
import com.intellij.psi.PsiElement;
|
|
10
|
+
import com.intellij.psi.PsiFile;
|
|
11
|
+
import com.intellij.psi.TokenType;
|
|
12
|
+
import com.intellij.psi.tree.IFileElementType;
|
|
13
|
+
import com.intellij.psi.tree.TokenSet;
|
|
14
|
+
import org.jetbrains.annotations.NotNull;
|
|
15
|
+
import tech.tsomaia.moxite.idea.lexer.MoxiteLexerAdapter;
|
|
16
|
+
import tech.tsomaia.moxite.idea.parser.MoxiteParser;
|
|
17
|
+
import tech.tsomaia.moxite.idea.psi.MoxiteFile;
|
|
18
|
+
import tech.tsomaia.moxite.idea.psi.MoxiteTypes;
|
|
19
|
+
|
|
20
|
+
public class MoxiteParserDefinition implements ParserDefinition {
|
|
21
|
+
|
|
22
|
+
public static final IFileElementType FILE = new IFileElementType(MoxiteLanguage.INSTANCE);
|
|
23
|
+
|
|
24
|
+
@NotNull
|
|
25
|
+
@Override
|
|
26
|
+
public Lexer createLexer(Project project) {
|
|
27
|
+
return new MoxiteLexerAdapter();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@NotNull
|
|
31
|
+
@Override
|
|
32
|
+
public TokenSet getWhitespaceTokens() {
|
|
33
|
+
return TokenSet.create(TokenType.WHITE_SPACE);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@NotNull
|
|
37
|
+
@Override
|
|
38
|
+
public TokenSet getCommentTokens() {
|
|
39
|
+
return TokenSet.EMPTY; // no comments in this language yet
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@NotNull
|
|
43
|
+
@Override
|
|
44
|
+
public TokenSet getStringLiteralElements() {
|
|
45
|
+
return TokenSet.create(MoxiteTypes.STRING);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@NotNull
|
|
49
|
+
@Override
|
|
50
|
+
public PsiParser createParser(Project project) {
|
|
51
|
+
return new MoxiteParser();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@NotNull
|
|
55
|
+
@Override
|
|
56
|
+
public IFileElementType getFileNodeType() {
|
|
57
|
+
return FILE;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@NotNull
|
|
61
|
+
@Override
|
|
62
|
+
public PsiFile createFile(@NotNull FileViewProvider viewProvider) {
|
|
63
|
+
return new MoxiteFile(viewProvider);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@NotNull
|
|
67
|
+
@Override
|
|
68
|
+
public SpaceRequirements spaceExistenceTypeBetweenTokens(ASTNode left, ASTNode right) {
|
|
69
|
+
return SpaceRequirements.MAY;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@NotNull
|
|
73
|
+
@Override
|
|
74
|
+
public PsiElement createElement(ASTNode node) {
|
|
75
|
+
return tech.tsomaia.moxite.idea.psi.MoxiteTypes.Factory.createElement(node);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
package tech.tsomaia.moxite.idea;
|
|
2
|
+
|
|
3
|
+
import com.intellij.lexer.Lexer;
|
|
4
|
+
import com.intellij.openapi.editor.DefaultLanguageHighlighterColors;
|
|
5
|
+
import com.intellij.openapi.editor.colors.TextAttributesKey;
|
|
6
|
+
import com.intellij.openapi.fileTypes.SyntaxHighlighterBase;
|
|
7
|
+
import com.intellij.psi.tree.IElementType;
|
|
8
|
+
import org.jetbrains.annotations.NotNull;
|
|
9
|
+
import tech.tsomaia.moxite.idea.lexer.MoxiteLexerAdapter;
|
|
10
|
+
import tech.tsomaia.moxite.idea.psi.MoxiteTypes;
|
|
11
|
+
|
|
12
|
+
import static com.intellij.openapi.editor.colors.TextAttributesKey.createTextAttributesKey;
|
|
13
|
+
|
|
14
|
+
public class MoxiteSyntaxHighlighter extends SyntaxHighlighterBase {
|
|
15
|
+
|
|
16
|
+
public static final TextAttributesKey KEYWORD =
|
|
17
|
+
createTextAttributesKey("RELAY_KEYWORD", DefaultLanguageHighlighterColors.KEYWORD);
|
|
18
|
+
public static final TextAttributesKey STRING =
|
|
19
|
+
createTextAttributesKey("RELAY_STRING", DefaultLanguageHighlighterColors.STRING);
|
|
20
|
+
public static final TextAttributesKey NUMBER =
|
|
21
|
+
createTextAttributesKey("RELAY_NUMBER", DefaultLanguageHighlighterColors.NUMBER);
|
|
22
|
+
public static final TextAttributesKey IDENTIFIER =
|
|
23
|
+
createTextAttributesKey("RELAY_IDENTIFIER", DefaultLanguageHighlighterColors.IDENTIFIER);
|
|
24
|
+
public static final TextAttributesKey BRACES =
|
|
25
|
+
createTextAttributesKey("RELAY_BRACES", DefaultLanguageHighlighterColors.BRACES);
|
|
26
|
+
public static final TextAttributesKey OPERATOR =
|
|
27
|
+
createTextAttributesKey("RELAY_OPERATOR", DefaultLanguageHighlighterColors.OPERATION_SIGN);
|
|
28
|
+
|
|
29
|
+
private static final TextAttributesKey[] KEYWORD_KEYS = new TextAttributesKey[]{KEYWORD};
|
|
30
|
+
private static final TextAttributesKey[] STRING_KEYS = new TextAttributesKey[]{STRING};
|
|
31
|
+
private static final TextAttributesKey[] NUMBER_KEYS = new TextAttributesKey[]{NUMBER};
|
|
32
|
+
private static final TextAttributesKey[] IDENTIFIER_KEYS = new TextAttributesKey[]{IDENTIFIER};
|
|
33
|
+
private static final TextAttributesKey[] BRACES_KEYS = new TextAttributesKey[]{BRACES};
|
|
34
|
+
private static final TextAttributesKey[] OPERATOR_KEYS = new TextAttributesKey[]{OPERATOR};
|
|
35
|
+
private static final TextAttributesKey[] EMPTY_KEYS = new TextAttributesKey[0];
|
|
36
|
+
|
|
37
|
+
@NotNull
|
|
38
|
+
@Override
|
|
39
|
+
public Lexer getHighlightingLexer() {
|
|
40
|
+
return new MoxiteLexerAdapter();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@NotNull
|
|
44
|
+
@Override
|
|
45
|
+
public TextAttributesKey[] getTokenHighlights(IElementType tokenType) {
|
|
46
|
+
if (tokenType.equals(MoxiteTypes.TAG_IF) ||
|
|
47
|
+
tokenType.equals(MoxiteTypes.TAG_ELSE_IF) ||
|
|
48
|
+
tokenType.equals(MoxiteTypes.TAG_ELSE) ||
|
|
49
|
+
tokenType.equals(MoxiteTypes.TAG_ENDIF) ||
|
|
50
|
+
tokenType.equals(MoxiteTypes.TAG_FOR) ||
|
|
51
|
+
tokenType.equals(MoxiteTypes.TAG_ENDFOR) ||
|
|
52
|
+
tokenType.equals(MoxiteTypes.TAG_CONST)) {
|
|
53
|
+
return KEYWORD_KEYS;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (tokenType.equals(MoxiteTypes.INTERP_START) ||
|
|
57
|
+
tokenType.equals(MoxiteTypes.INTERP_END)) {
|
|
58
|
+
return BRACES_KEYS;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (tokenType.equals(MoxiteTypes.STRING)) {
|
|
62
|
+
return STRING_KEYS;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (tokenType.equals(MoxiteTypes.NUMBER)) {
|
|
66
|
+
return NUMBER_KEYS;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (tokenType.equals(MoxiteTypes.IDENTIFIER)) {
|
|
70
|
+
return IDENTIFIER_KEYS;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (tokenType.equals(MoxiteTypes.EQUALITY_OP) ||
|
|
74
|
+
tokenType.equals(MoxiteTypes.COMPARE_OP) ||
|
|
75
|
+
tokenType.equals(MoxiteTypes.EQ) ||
|
|
76
|
+
tokenType.equals(MoxiteTypes.PIPE)) {
|
|
77
|
+
return OPERATOR_KEYS;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return EMPTY_KEYS;
|
|
81
|
+
}
|
|
82
|
+
}
|
package/idea-plugin/src/main/java/tech/tsomaia/moxite/idea/MoxiteSyntaxHighlighterFactory.java
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package tech.tsomaia.moxite.idea;
|
|
2
|
+
|
|
3
|
+
import com.intellij.openapi.fileTypes.SyntaxHighlighter;
|
|
4
|
+
import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
|
|
5
|
+
import com.intellij.openapi.project.Project;
|
|
6
|
+
import com.intellij.openapi.vfs.VirtualFile;
|
|
7
|
+
import org.jetbrains.annotations.NotNull;
|
|
8
|
+
import org.jetbrains.annotations.Nullable;
|
|
9
|
+
|
|
10
|
+
public class MoxiteSyntaxHighlighterFactory extends SyntaxHighlighterFactory {
|
|
11
|
+
@NotNull
|
|
12
|
+
@Override
|
|
13
|
+
public SyntaxHighlighter getSyntaxHighlighter(@Nullable Project project, @Nullable VirtualFile virtualFile) {
|
|
14
|
+
return new MoxiteSyntaxHighlighter();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
package tech.tsomaia.moxite.idea.psi;
|
|
2
|
+
|
|
3
|
+
import com.intellij.psi.tree.IElementType;
|
|
4
|
+
import org.jetbrains.annotations.NonNls;
|
|
5
|
+
import org.jetbrains.annotations.NotNull;
|
|
6
|
+
import tech.tsomaia.moxite.idea.MoxiteLanguage;
|
|
7
|
+
|
|
8
|
+
public class MoxiteElementType extends IElementType {
|
|
9
|
+
public MoxiteElementType(@NotNull @NonNls String debugName) {
|
|
10
|
+
super(debugName, MoxiteLanguage.INSTANCE);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package tech.tsomaia.moxite.idea.psi;
|
|
2
|
+
|
|
3
|
+
import com.intellij.extapi.psi.PsiFileBase;
|
|
4
|
+
import com.intellij.openapi.fileTypes.FileType;
|
|
5
|
+
import com.intellij.psi.FileViewProvider;
|
|
6
|
+
import org.jetbrains.annotations.NotNull;
|
|
7
|
+
import tech.tsomaia.moxite.idea.MoxiteFileType;
|
|
8
|
+
import tech.tsomaia.moxite.idea.MoxiteLanguage;
|
|
9
|
+
|
|
10
|
+
public class MoxiteFile extends PsiFileBase {
|
|
11
|
+
public MoxiteFile(@NotNull FileViewProvider viewProvider) {
|
|
12
|
+
super(viewProvider, MoxiteLanguage.INSTANCE);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@NotNull
|
|
16
|
+
@Override
|
|
17
|
+
public FileType getFileType() {
|
|
18
|
+
return MoxiteFileType.INSTANCE;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@Override
|
|
22
|
+
public String toString() {
|
|
23
|
+
return "Moxite File";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package tech.tsomaia.moxite.idea.psi;
|
|
2
|
+
|
|
3
|
+
import com.intellij.psi.tree.IElementType;
|
|
4
|
+
import org.jetbrains.annotations.NonNls;
|
|
5
|
+
import org.jetbrains.annotations.NotNull;
|
|
6
|
+
import tech.tsomaia.moxite.idea.MoxiteLanguage;
|
|
7
|
+
|
|
8
|
+
public class MoxiteTokenType extends IElementType {
|
|
9
|
+
public MoxiteTokenType(@NotNull @NonNls String debugName) {
|
|
10
|
+
super(debugName, MoxiteLanguage.INSTANCE);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@Override
|
|
14
|
+
public String toString() {
|
|
15
|
+
return "MoxiteTokenType." + super.toString();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<idea-plugin>
|
|
2
|
+
<id>tech.tsomaia.moxite.idea</id>
|
|
3
|
+
<name>Moxite Template Support</name>
|
|
4
|
+
<vendor email="admin@tsomaia.tech" url="https://tsomaia.tech">Tornike Tsomaia</vendor>
|
|
5
|
+
|
|
6
|
+
<description><![CDATA[
|
|
7
|
+
Provides language support for the Moxite Template Engine.
|
|
8
|
+
Includes full syntax highlighting, lexing, and semantic parsing for .mx files.
|
|
9
|
+
]]></description>
|
|
10
|
+
|
|
11
|
+
<!-- please see https://plugins.jetbrains.com/docs/intellij/plugin-compatibility.html
|
|
12
|
+
on how to target different products -->
|
|
13
|
+
<depends>com.intellij.modules.platform</depends>
|
|
14
|
+
|
|
15
|
+
<extensions defaultExtensionNs="com.intellij">
|
|
16
|
+
<fileType
|
|
17
|
+
name="Moxite Template"
|
|
18
|
+
implementationClass="tech.tsomaia.moxite.idea.MoxiteFileType"
|
|
19
|
+
fieldName="INSTANCE"
|
|
20
|
+
language="Moxite"
|
|
21
|
+
extensions="mx"
|
|
22
|
+
/>
|
|
23
|
+
<lang.parserDefinition
|
|
24
|
+
language="Moxite"
|
|
25
|
+
implementationClass="tech.tsomaia.moxite.idea.MoxiteParserDefinition"
|
|
26
|
+
/>
|
|
27
|
+
<lang.syntaxHighlighterFactory
|
|
28
|
+
language="Moxite"
|
|
29
|
+
implementationClass="tech.tsomaia.moxite.idea.MoxiteSyntaxHighlighterFactory"
|
|
30
|
+
/>
|
|
31
|
+
</extensions>
|
|
32
|
+
</idea-plugin>
|