diff --git a/build.gradle.kts b/build.gradle.kts index fcea405f..3e32b8ef 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,10 +4,11 @@ import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension plugins { - java - kotlin("jvm") version "1.9.24" + kotlin("jvm") version "1.9.24" apply false id("org.jetbrains.intellij.platform") version "2.1.0" id("org.jetbrains.changelog") version "2.2.1" + id("org.jetbrains.grammarkit") version "2022.3.2.2" apply false + idea } val javaVersion = providers.gradleProperty("javaVersion").get().toInt() @@ -19,6 +20,11 @@ subprojects { apply(plugin = "java") apply(plugin = "org.jetbrains.kotlin.jvm") apply(plugin = "org.jetbrains.intellij.platform.module") + apply(plugin = "idea") + + extensions.configure("kotlin") { + jvmToolchain(javaVersion) + } } tasks { @@ -31,13 +37,17 @@ tasks { } allprojects { - kotlin { - jvmToolchain(javaVersion) + idea { + module { + isDownloadJavadoc = false + isDownloadSources = true + } } java { toolchain { languageVersion = JavaLanguageVersion.of(javaVersion) + @Suppress("UnstableApiUsage") vendor = JvmVendorSpec.JETBRAINS } sourceCompatibility = JavaVersion.toVersion(javaVersion) @@ -66,7 +76,8 @@ dependencies { zipSigner() } - implementation(project(":core")) + implementation(project(":zig")) + implementation(project(":zon")) } intellijPlatform { @@ -121,6 +132,7 @@ intellijPlatform { } } buildSearchableOptions = false + instrumentCode = false } changelog { @@ -132,4 +144,10 @@ tasks { publishPlugin { dependsOn(patchChangelog) } + compileJava { + enabled = false + } + classes { + enabled = false + } } \ No newline at end of file diff --git a/modules/core/build.gradle.kts b/modules/core/build.gradle.kts deleted file mode 100644 index 61d6d0e9..00000000 --- a/modules/core/build.gradle.kts +++ /dev/null @@ -1,7 +0,0 @@ -import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType - -dependencies { - intellijPlatform { - create(IntelliJPlatformType.IntellijIdeaCommunity, providers.gradleProperty("ideaCommunityVersion")) - } -} \ No newline at end of file diff --git a/modules/zig/build.gradle.kts b/modules/zig/build.gradle.kts new file mode 100644 index 00000000..c28abd6a --- /dev/null +++ b/modules/zig/build.gradle.kts @@ -0,0 +1,73 @@ +import org.jetbrains.grammarkit.tasks.GenerateLexerTask +import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType + +plugins { + kotlin("jvm") + id("org.jetbrains.grammarkit") +} + +dependencies { + intellijPlatform { + create(IntelliJPlatformType.IntellijIdeaCommunity, providers.gradleProperty("ideaCommunityVersion")) + } +} + +val grammarGenRoot = "generated/sources/grammarkit/zig" +val rootPackagePath = "com/falsepattern/zigbrains/zig" + +val parserDir = layout.buildDirectory.dir("$grammarGenRoot/parser") +val lexerDir = layout.buildDirectory.dir("$grammarGenRoot/lexer") + +sourceSets { + main { + java { + srcDir(parserDir) + srcDir(lexerDir) + } + } +} + +idea { + module { + sourceDirs.addAll(listOf(parserDir.get().asFile, lexerDir.get().asFile)) + generatedSourceDirs.addAll(listOf(parserDir.get().asFile, lexerDir.get().asFile)) + } +} + +tasks { + generateLexer { + purgeOldFiles = true + sourceFile = file("src/main/grammar/Zig.flex") + targetOutputDir = layout.buildDirectory.dir("$grammarGenRoot/lexer/$rootPackagePath/lexer") + } + + register("generateStringLexer") { + purgeOldFiles = true + sourceFile = file("src/main/grammar/ZigString.flex") + targetOutputDir = layout.buildDirectory.dir("$grammarGenRoot/stringlexer/$rootPackagePath/stringlexer") + + } + + generateParser { + purgeOldFiles = true + sourceFile = file("src/main/grammar/Zig.bnf") + targetRootOutputDir = layout.buildDirectory.dir("$grammarGenRoot/parser") + pathToParser = "$rootPackagePath/psi/ZigParser.java" + pathToPsiRoot = "$rootPackagePath/psi" + } + + register("generateGrammars") { + group = "grammarkit" + dependsOn("generateLexer") + dependsOn("generateStringLexer") + dependsOn("generateParser") + } + + compileJava { + dependsOn("generateGrammars") + } + + compileKotlin { + dependsOn("generateGrammars") + } +} \ No newline at end of file diff --git a/modules/zig/src/main/grammar/Zig.bnf b/modules/zig/src/main/grammar/Zig.bnf new file mode 100644 index 00000000..0989e572 --- /dev/null +++ b/modules/zig/src/main/grammar/Zig.bnf @@ -0,0 +1,568 @@ +/* + * Copyright 2023-2024 FalsePattern + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + parserClass="com.falsepattern.zigbrains.zig.parser.ZigParser" + + extends="com.intellij.extapi.psi.ASTWrapperPsiElement" + extends(".*Expr")=Expr + + psiClassPrefix="Zig" + psiImplClassSuffix="Impl" + psiPackage="com.falsepattern.zigbrains.zig.psi" + psiImplPackage="com.falsepattern.zigbrains.zig.psi.impl" + + elementTypeHolderClass="com.falsepattern.zigbrains.zig.psi.ZigTypes" + elementTypeClass="com.falsepattern.zigbrains.zig.parser.ZigElementType" + + tokenTypeClass="com.falsepattern.zigbrains.zig.parser.ZigTokenType" + generateTokenAccessors = true + tokens=[ + + //Symbols + AMPERSAND='&' + AMPERSANDEQUAL='&=' + ASTERISK='*' + ASTERISK2='**' + ASTERISKEQUAL='*=' + ASTERISKPERCENT='*%' + ASTERISKPERCENTEQUAL='*%=' + ASTERISKPIPE='*|' + ASTERISKPIPEEQUAL='*|=' + CARET='^' + CARETEQUAL='^=' + COLON=':' + COMMA=',' + DOT='.' + DOT2='..' + DOT3='...' + DOTASTERISK='.*' + DOTQUESTIONMARK='.?' + EQUAL='=' + EQUALEQUAL='==' + EQUALRARROW='=>' + EXCLAMATIONMARK='!' + EXCLAMATIONMARKEQUAL='!=' + LARROW='<' + LARROW2='<<' + LARROW2EQUAL='<<=' + LARROW2PIPE='<<|' + LARROW2PIPEEQUAL='<<|=' + LARROWEQUAL='<=' + LBRACE='{' + LBRACKET='[' + LPAREN='(' + MINUS='-' + MINUSEQUAL='-=' + MINUSPERCENT='-%' + MINUSPERCENTEQUAL='-%=' + MINUSPIPE='-|' + MINUSPIPEEQUAL='-|=' + MINUSRARROW='->' + PERCENT='%' + PERCENTEQUAL='%=' + PIPE='|' + PIPE2='||' + PIPEEQUAL='|=' + PLUS='+' + PLUS2='++' + PLUSEQUAL='+=' + PLUSPERCENT='+%' + PLUSPERCENTEQUAL='+%=' + PLUSPIPE='+|' + PLUSPIPEEQUAL='+|=' + QUESTIONMARK='?' + RARROW='>' + RARROW2='>>' + RARROW2EQUAL='>>=' + RARROWEQUAL='>=' + RBRACE='}' + RBRACKET=']' + RPAREN=')' + SEMICOLON=';' + SLASH='/' + SLASHEQUAL='/=' + TILDE='~' + + //Keywords + KEYWORD_ADDRSPACE='addrspace' + KEYWORD_ALIGN='align' + KEYWORD_ALLOWZERO='allowzero' + KEYWORD_AND='and' + KEYWORD_ANYFRAME='anyframe' + KEYWORD_ANYTYPE='anytype' + KEYWORD_ASM='asm' + KEYWORD_ASYNC='async' + KEYWORD_AWAIT='await' + KEYWORD_BREAK='break' + KEYWORD_CALLCONV='callconv' + KEYWORD_CATCH='catch' + KEYWORD_COMPTIME='comptime' + KEYWORD_CONST='const' + KEYWORD_CONTINUE='continue' + KEYWORD_DEFER='defer' + KEYWORD_ELSE='else' + KEYWORD_ENUM='enum' + KEYWORD_ERRDEFER='errdefer' + KEYWORD_ERROR='error' + KEYWORD_EXPORT='export' + KEYWORD_EXTERN='extern' + KEYWORD_FN='fn' + KEYWORD_FOR='for' + KEYWORD_IF='if' + KEYWORD_INLINE='inline' + KEYWORD_NOALIAS='noalias' + KEYWORD_NOSUSPEND='nosuspend' + KEYWORD_NOINLINE='noinline' + KEYWORD_OPAQUE='opaque' + KEYWORD_OR='or' + KEYWORD_ORELSE='orelse' + KEYWORD_PACKED='packed' + KEYWORD_PUB='pub' + KEYWORD_RESUME='resume' + KEYWORD_RETURN='return' + KEYWORD_LINKSECTION='linksection' + KEYWORD_STRUCT='struct' + KEYWORD_SUSPEND='suspend' + KEYWORD_SWITCH='switch' + KEYWORD_TEST='test' + KEYWORD_THREADLOCAL='threadlocal' + KEYWORD_TRY='try' + KEYWORD_UNION='union' + KEYWORD_UNREACHABLE='unreachable' + KEYWORD_USINGNAMESPACE='usingnamespace' + KEYWORD_VAR='var' + KEYWORD_VOLATILE='volatile' + KEYWORD_WHILE='while' + + CONTAINER_DOC_COMMENT='container doc comment' + DOC_COMMENT='doc comment' + LINE_COMMENT='comment' + + CHAR_LITERAL='character literal' + + FLOAT='float' + INTEGER='integer' + + STRING_LITERAL_SINGLE='quoted string literal' + STRING_LITERAL_MULTI='multiline string literal' + + IDENTIFIER='identifier' + BUILTINIDENTIFIER='builtin identifier' + ] + + //Mixins +// mixin("StringLiteral")="com.falsepattern.zigbrains.zig.psi.impl.mixins.ZigStringLiteralMixinImpl" +// implements("StringLiteral")="com.falsepattern.zigbrains.zig.psi.mixins.ZigStringLiteralMixin" +} + +Root ::= CONTAINER_DOC_COMMENT? ContainerMembers? + +// *** Top level *** +ContainerMembers ::= ContainerDeclarations? (ContainerField COMMA)* (ContainerField | ContainerDeclarations)? + +ContainerDeclarations ::= (TestDecl | ComptimeDecl | DOC_COMMENT? KEYWORD_PUB? Decl)+ + +TestDecl ::= DOC_COMMENT? KEYWORD_TEST (STRING_LITERAL_SINGLE | IDENTIFIER)? Block {pin=2} + +ComptimeDecl ::= KEYWORD_COMPTIME Block + +Decl +::= (KEYWORD_EXPORT | KEYWORD_EXTERN STRING_LITERAL_SINGLE? | KEYWORD_INLINE | KEYWORD_NOINLINE)? FnProto (SEMICOLON | Block) + | (KEYWORD_EXPORT | KEYWORD_EXTERN STRING_LITERAL_SINGLE?)? KEYWORD_THREADLOCAL? GlobalVarDecl + | KEYWORD_USINGNAMESPACE Expr SEMICOLON + +FnProto ::= KEYWORD_FN IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr {pin=1} + +VarDeclProto ::= (KEYWORD_CONST | KEYWORD_VAR) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection? {pin=1} + +GlobalVarDecl ::= VarDeclProto (EQUAL Expr)? SEMICOLON {pin=1} + +ContainerField ::= DOC_COMMENT? KEYWORD_COMPTIME? !KEYWORD_FN (IDENTIFIER COLON)? TypeExpr ByteAlign? (EQUAL Expr)? {pin=5} + +// *** Block Level *** +Statement +::= KEYWORD_COMPTIME ComptimeStatement + | KEYWORD_NOSUSPEND BlockExprStatement + | KEYWORD_DEFER BlockExprStatement + | KEYWORD_ERRDEFER Payload? BlockExprStatement + | IfStatement + | LabeledStatement + | VarDeclExprStatement + +ComptimeStatement +::= BlockExpr + | VarDeclExprStatement + +IfStatement +::= IfPrefix ZB_IfStatement_Body {pin(".*")=1} + +private ZB_IfStatement_Body +::= BlockExpr ( KEYWORD_ELSE Payload? Statement )? + | AssignExpr ( SEMICOLON | KEYWORD_ELSE Payload? Statement ) + +LabeledStatement ::= BlockLabel? (Block | LoopStatement | SwitchExpr) + +LoopStatement ::= KEYWORD_INLINE? (ForStatement | WhileStatement) + +ForStatement +::= ForPrefix ZB_ForStatement_Body {pin(".*")=1} + +private ZB_ForStatement_Body +::= BlockExpr ( KEYWORD_ELSE Statement )? + | AssignExpr ( SEMICOLON | KEYWORD_ELSE Statement ) + +WhileStatement +::= WhilePrefix ZB_WhileStatement_Body {pin(".*") =1} + +private ZB_WhileStatement_Body +::= BlockExpr ( KEYWORD_ELSE Payload? Statement )? + | AssignExpr ( SEMICOLON | KEYWORD_ELSE Payload? Statement) + +BlockExprStatement +::= BlockExpr + | ZB_BlockExprStatement_AssignExpr + +private ZB_BlockExprStatement_AssignExpr ::= AssignExpr SEMICOLON {pin=1} + +BlockExpr ::= BlockLabel? Block + +//An expression, assignment, or any destructure, as a statement. +VarDeclExprStatement +::= VarDeclProto (COMMA (VarDeclProto | Expr))* EQUAL Expr SEMICOLON + | Expr (AssignOp Expr | (COMMA (VarDeclProto | Expr))+ EQUAL Expr)? SEMICOLON {pin(".*")=1} + +// *** Expression Level *** + +// An assignment or a destructure whose LHS are all lvalue expressions. +AssignExpr ::= Expr (AssignOp Expr | (COMMA Expr)+ EQUAL Expr)? + +SingleAssignExpr ::= Expr (AssignOp Expr)? + +Expr ::= BoolOrExpr + +BoolOrExpr ::= BoolAndExpr (KEYWORD_OR BoolAndExpr)* + +BoolAndExpr ::= CompareExpr (KEYWORD_AND CompareExpr)* + +CompareExpr ::= BitwiseExpr (CompareOp BitwiseExpr)? + +BitwiseExpr ::= BitShiftExpr (BitwiseOp BitShiftExpr)* + +BitShiftExpr ::= AdditionExpr (BitShiftOp AdditionExpr)* + +AdditionExpr ::= MultiplyExpr (AdditionOp MultiplyExpr)* + +MultiplyExpr ::= PrefixExpr (MultiplyOp PrefixExpr)* + +PrefixExpr ::= PrefixOp* PrimaryExpr + +PrimaryExpr +::= AsmExpr + | IfExpr + | KEYWORD_BREAK BreakLabel? Expr? + | KEYWORD_COMPTIME Expr + | KEYWORD_NOSUSPEND Expr + | KEYWORD_CONTINUE BreakLabel? Expr? + | KEYWORD_RESUME Expr + | KEYWORD_RETURN Expr? + | BlockLabel? LoopExpr + | Block + | CurlySuffixExpr + +IfExpr ::= IfPrefix Expr (KEYWORD_ELSE Payload? Expr)? + +Block ::= LBRACE ZB_Block_Statement RBRACE {pin=1} + +private ZB_Block_Statement ::= Statement* {recoverWhile="ZB_Block_Statement_recover"} + +private ZB_Block_Statement_recover ::= !(RBRACE) + +LoopExpr ::= KEYWORD_INLINE? (ForExpr | WhileExpr) + +ForExpr ::= ForPrefix Expr (KEYWORD_ELSE Expr)? + +WhileExpr ::= WhilePrefix Expr (KEYWORD_ELSE Payload? Expr)? + +CurlySuffixExpr ::= TypeExpr InitList? + +InitList +::= LBRACE ZB_InitList_Body RBRACE {pin=1} + +private ZB_InitList_Body +::= FieldInit (COMMA ZB_InitList_FieldInit)* COMMA? + | Expr (COMMA ZB_InitList_Expr)* COMMA? + | () + +private ZB_InitList_FieldInit ::= FieldInit {recoverWhile="ZB_InitList_Recover"} +private ZB_InitList_Expr ::= Expr {recoverWhile="ZB_InitList_Recover"} + +private ZB_InitList_Recover ::= !(COMMA | RBRACE) + +TypeExpr ::= PrefixTypeOp* ErrorUnionExpr + +ErrorUnionExpr ::= SuffixExpr (EXCLAMATIONMARK TypeExpr)? + +SuffixExpr +::= KEYWORD_ASYNC PrimaryTypeExpr SuffixOp* FnCallArguments + | PrimaryTypeExpr (SuffixOp | FnCallArguments)* + +PrimaryTypeExpr +::= BUILTINIDENTIFIER FnCallArguments + | CHAR_LITERAL + | ContainerDecl + | DOT IDENTIFIER + | DOT InitList + | ErrorSetDecl + | FLOAT + | FnProto + | GroupedExpr + | LabeledTypeExpr + | IDENTIFIER + | IfTypeExpr + | INTEGER + | KEYWORD_COMPTIME TypeExpr + | KEYWORD_ERROR DOT IDENTIFIER + | KEYWORD_ANYFRAME + | KEYWORD_UNREACHABLE + | StringLiteral + +ContainerDecl ::= (KEYWORD_EXTERN | KEYWORD_PACKED)? ContainerDeclAuto + +ErrorSetDecl ::= KEYWORD_ERROR LBRACE IdentifierList RBRACE + +GroupedExpr ::= LPAREN Expr RPAREN + +IfTypeExpr ::= IfPrefix TypeExpr (KEYWORD_ELSE Payload? TypeExpr)? + +LabeledTypeExpr +::= BlockLabel Block + | BlockLabel? LoopTypeExpr + | BlockLabel? SwitchExpr + +LoopTypeExpr ::= KEYWORD_INLINE? (ForTypeExpr | WhileTypeExpr) + +ForTypeExpr ::= ForPrefix TypeExpr (KEYWORD_ELSE TypeExpr)? + +WhileTypeExpr ::= WhilePrefix TypeExpr (KEYWORD_ELSE Payload? TypeExpr)? + +SwitchExpr ::= KEYWORD_SWITCH LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE {pin=1} + +// *** Assembly *** +AsmExpr ::= KEYWORD_ASM KEYWORD_VOLATILE? LPAREN Expr AsmOutput? RPAREN + +AsmOutput ::= COLON AsmOutputList AsmInput? + +AsmOutputItem ::= LBRACKET IDENTIFIER RBRACKET StringLiteral LPAREN (MINUSRARROW TypeExpr | IDENTIFIER) RPAREN + +AsmInput ::= COLON AsmInputList AsmClobbers? + +AsmInputItem ::= LBRACKET IDENTIFIER RBRACKET StringLiteral LPAREN Expr RPAREN + +AsmClobbers ::= COLON StringList + +// *** Helper grammar *** +BreakLabel ::= COLON IDENTIFIER + +BlockLabel ::= IDENTIFIER COLON + +FieldInit ::= DOT IDENTIFIER EQUAL Expr + +WhileContinueExpr ::= COLON LPAREN AssignExpr RPAREN + +LinkSection ::= KEYWORD_LINKSECTION LPAREN Expr RPAREN + +AddrSpace ::= KEYWORD_ADDRSPACE LPAREN Expr RPAREN + +// Fn specific +CallConv ::= KEYWORD_CALLCONV LPAREN Expr RPAREN + +ParamDecl +::= DOC_COMMENT? (KEYWORD_NOALIAS | KEYWORD_COMPTIME)? (IDENTIFIER COLON)? ParamType + | DOT3 + +ParamType +::= KEYWORD_ANYTYPE + | TypeExpr + +// Control flow prefixes +IfPrefix ::= KEYWORD_IF ZB_IfPrefix_Operand PtrPayload? {pin=1} + +private ZB_IfPrefix_Operand ::= LPAREN Expr RPAREN {pin=1} + +WhilePrefix ::= KEYWORD_WHILE ZB_WhilePrefix_Operand PtrPayload? WhileContinueExpr? {pin=1} + +private ZB_WhilePrefix_Operand ::= LPAREN Expr RPAREN {pin=1} + +ForRange ::= Expr DOT2 Expr? +ForOperand ::= ForRange | Expr {recoverWhile="ZB_ForOperand_Recover"} + +private ZB_ForOperand_Recover ::= !(COMMA | RPAREN) + +ForPrefix ::= KEYWORD_FOR ZB_ForPrefix_Operands PtrIndexPayload {pin=1} + +private ZB_ForPrefix_Operands ::= LPAREN (ForOperand COMMA)* ForOperand RPAREN {pin=1} + +// Payloads +Payload ::= PIPE IDENTIFIER PIPE + +PtrPayload ::= PIPE ASTERISK? IDENTIFIER PIPE + +PtrIndexPayload ::= PIPE (ASTERISK? IDENTIFIER COMMA)* (ASTERISK? IDENTIFIER) PIPE + +// Switch specific +SwitchProng ::= KEYWORD_INLINE? SwitchCase EQUALRARROW PtrIndexPayload? SingleAssignExpr {pin=3} + +SwitchCase +::= SwitchItem (COMMA SwitchItem)* COMMA? + | KEYWORD_ELSE + +SwitchItem ::= Expr (DOT3 Expr)? + +// Operators +AssignOp +::= ASTERISKEQUAL + | ASTERISKPIPEEQUAL + | SLASHEQUAL + | PERCENTEQUAL + | PLUSEQUAL + | PLUSPIPEEQUAL + | MINUSEQUAL + | MINUSPIPEEQUAL + | LARROW2EQUAL + | LARROW2PIPEEQUAL + | RARROW2EQUAL + | AMPERSANDEQUAL + | CARETEQUAL + | PIPEEQUAL + | ASTERISKPERCENTEQUAL + | PLUSPERCENTEQUAL + | MINUSPERCENTEQUAL + | EQUAL + +CompareOp +::= EQUALEQUAL + | EXCLAMATIONMARKEQUAL + | LARROW + | RARROW + | LARROWEQUAL + | RARROWEQUAL + +BitwiseOp +::= AMPERSAND + | CARET + | PIPE + | KEYWORD_ORELSE + | KEYWORD_CATCH Payload? + +BitShiftOp +::= LARROW2 + | RARROW2 + | LARROW2PIPE + +AdditionOp +::= PLUS + | MINUS + | PLUS2 + | PLUSPERCENT + | MINUSPERCENT + | PLUSPIPE + | MINUSPIPE + +MultiplyOp +::= PIPE2 + | ASTERISK + | SLASH + | PERCENT + | ASTERISK2 + | ASTERISKPERCENT + | ASTERISKPIPE + +PrefixOp +::= EXCLAMATIONMARK + | MINUS + | TILDE + | MINUSPERCENT + | AMPERSAND + | KEYWORD_TRY + | KEYWORD_AWAIT + +PrefixTypeOp +::= QUESTIONMARK + | KEYWORD_ANYFRAME MINUSRARROW + | SliceTypeStart (ByteAlign | AddrSpace | KEYWORD_CONST | KEYWORD_VOLATILE | KEYWORD_ALLOWZERO)* + | PtrTypeStart (AddrSpace | KEYWORD_ALIGN LPAREN Expr (COLON Expr COLON Expr)? RPAREN | KEYWORD_CONST | KEYWORD_VOLATILE | KEYWORD_ALLOWZERO)* + | ArrayTypeStart + +SuffixOp +::= LBRACKET Expr (DOT2 (Expr? (COLON Expr)?)?)? RBRACKET + | DOT IDENTIFIER + | DOTASTERISK + | DOTQUESTIONMARK + +FnCallArguments ::= LPAREN ExprList RPAREN {pin=1} + +// Ptr specific +SliceTypeStart ::= LBRACKET (COLON Expr)? RBRACKET + +PtrTypeStart +::= ASTERISK + | ASTERISK2 + | LBRACKET ASTERISK ("c" | COLON Expr)? RBRACKET + +ArrayTypeStart ::= LBRACKET Expr (COLON Expr)? RBRACKET + +// ContainerDecl specific +ContainerDeclAuto ::= ContainerDeclType LBRACE CONTAINER_DOC_COMMENT? ZB_ContainerDeclAuto_ContainerMembers RBRACE {pin=2} + +private ZB_ContainerDeclAuto_ContainerMembers ::= ContainerMembers {recoverWhile="ZB_ContainerDeclAuto_ContainerMembers_recover"} +private ZB_ContainerDeclAuto_ContainerMembers_recover ::= !(RBRACE) + +ContainerDeclType +::= KEYWORD_STRUCT (LPAREN ZB_ContainerDeclType_Expr RPAREN)? + | KEYWORD_OPAQUE + | KEYWORD_ENUM (LPAREN ZB_ContainerDeclType_Expr RPAREN)? + | KEYWORD_UNION (LPAREN (KEYWORD_ENUM (LPAREN ZB_ContainerDeclType_Expr RPAREN)? | ZB_ContainerDeclType_Expr) RPAREN)? {pin(".*")=1} + +private ZB_ContainerDeclType_Expr ::= Expr {recoverWhile="ZB_ContainerDeclType_Expr_recover"} +private ZB_ContainerDeclType_Expr_recover ::= !(RPAREN) + +// Alignment +ByteAlign ::= KEYWORD_ALIGN LPAREN Expr RPAREN + +// Lists +IdentifierList ::= (DOC_COMMENT? IDENTIFIER COMMA)* (DOC_COMMENT? IDENTIFIER)? + +SwitchProngList ::= (ZB_SwitchProngList_SwitchProng COMMA)* ZB_SwitchProngList_SwitchProng? + +private ZB_SwitchProngList_SwitchProng ::= SwitchProng {recoverWhile="ZB_SwitchProngList_Recover"} + +private ZB_SwitchProngList_Recover ::= !(COMMA | RBRACE) + +AsmOutputList ::= (AsmOutputItem COMMA)* AsmOutputItem? + +AsmInputList ::= (AsmInputItem COMMA)* AsmInputItem? + +StringList ::= (StringLiteral COMMA)* StringLiteral? + +ParamDeclList ::= (ParamDecl COMMA)* ParamDecl? + +ExprList ::= (ZB_ExprList_Expr COMMA)* ZB_ExprList_Expr? + +private ZB_ExprList_Expr ::= Expr {recoverWhile="ZB_ExprList_recover"} +private ZB_ExprList_recover ::= !(RPAREN | COMMA) + +StringLiteral ::= STRING_LITERAL_SINGLE | STRING_LITERAL_MULTI \ No newline at end of file diff --git a/modules/zig/src/main/grammar/Zig.flex b/modules/zig/src/main/grammar/Zig.flex new file mode 100644 index 00000000..39de6ce8 --- /dev/null +++ b/modules/zig/src/main/grammar/Zig.flex @@ -0,0 +1,282 @@ +/* + * Copyright 2023-2024 FalsePattern + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.falsepattern.zigbrains.zig.lexer; + +import com.intellij.lexer.FlexLexer; +import com.intellij.psi.tree.IElementType; + +import static com.intellij.psi.TokenType.WHITE_SPACE; +import static com.intellij.psi.TokenType.BAD_CHARACTER; +import static com.falsepattern.zigbrains.zig.psi.ZigTypes.*; + +%% + +%class ZigFlexLexer +%implements FlexLexer +%function advance +%type IElementType + +CRLF=\R +WHITE_SPACE=[\s]+ + +bin=[01] +bin_="_"? {bin} +oct=[0-7] +oct_="_"? {oct} +hex=[0-9a-fA-F] +hex_="_"? {hex} +dec=[0-9] +dec_="_"? {dec} + +bin_int={bin} {bin_}* +oct_int={oct} {oct_}* +dec_int={dec} {dec_}* +hex_int={hex} {hex_}* + +ox80_oxBF=[\200-\277] +oxF4=\364 +ox80_ox8F=[\200-\217] +oxF1_oxF3=[\361-\363] +oxF0=\360 +ox90_0xBF=[\220-\277] +oxEE_oxEF=[\356-\357] +oxED=\355 +ox80_ox9F=[\200-\237] +oxE1_oxEC=[\341-\354] +oxE0=\340 +oxA0_oxBF=[\240-\277] +oxC2_oxDF=[\302-\337] + +// From https://lemire.me/blog/2018/05/09/how-quickly-can-you-check-that-a-string-is-valid-unicode-utf-8/ +// First Byte Second Byte Third Byte Fourth Byte +// [0x00,0x7F] +// [0xC2,0xDF] [0x80,0xBF] +// 0xE0 [0xA0,0xBF] [0x80,0xBF] +// [0xE1,0xEC] [0x80,0xBF] [0x80,0xBF] +// 0xED [0x80,0x9F] [0x80,0xBF] +// [0xEE,0xEF] [0x80,0xBF] [0x80,0xBF] +// 0xF0 [0x90,0xBF] [0x80,0xBF] [0x80,0xBF] +// [0xF1,0xF3] [0x80,0xBF] [0x80,0xBF] [0x80,0xBF] +// 0xF4 [0x80,0x8F] [0x80,0xBF] [0x80,0xBF] + +mb_utf8_literal= {oxF4} {ox80_ox8F} {ox80_oxBF} {ox80_oxBF} + | {oxF1_oxF3} {ox80_oxBF} {ox80_oxBF} {ox80_oxBF} + | {oxF0} {ox90_0xBF} {ox80_oxBF} {ox80_oxBF} + | {oxEE_oxEF} {ox80_oxBF} {ox80_oxBF} + | {oxED} {ox80_ox9F} {ox80_oxBF} + | {oxE1_oxEC} {ox80_oxBF} {ox80_oxBF} + | {oxE0} {oxA0_oxBF} {ox80_oxBF} + | {oxC2_oxDF} {ox80_oxBF} + +ascii_char_not_nl_slash_squote=[\000-\011\013-\046\050-\133\135-\177] + +char_escape= "\\x" {hex} {hex} + | "\\u{" {hex}+ "}" + | "\\" [nr\\t'\"] +char_char= {mb_utf8_literal} + | {char_escape} + | {ascii_char_not_nl_slash_squote} + +string_char= \\ . + | [^\"\n] + +all_nl_wrap=[^\n]* [ \n]* +all_nl_nowrap=[^\n]* \n + + +FLOAT= "0x" {hex_int} "." {hex_int} ([pP] [-+]? {dec_int})? + | {dec_int} "." {dec_int} ([eE] [-+]? {dec_int})? + | "0x" {hex_int} [pP] [-+]? {dec_int} + | {dec_int} [eE] [-+]? {dec_int} + +INTEGER= "0b" {bin_int} + | "0o" {oct_int} + | "0x" {hex_int} + | {dec_int} + +IDENTIFIER_PLAIN=[A-Za-z_][A-Za-z0-9_]* +BUILTINIDENTIFIER="@"[A-Za-z_][A-Za-z0-9_]* + +%state STR_LIT +%state STR_MULT_LINE +%state CHAR_LIT + +%state ID_QUOT +%state UNT_QUOT + +%state CDOC_CMT +%state DOC_CMT +%state LINE_CMT +%% + +//Comments + + "//!" { yybegin(CDOC_CMT); } + {all_nl_wrap} "//!" { } + {all_nl_nowrap} { yybegin(YYINITIAL); return CONTAINER_DOC_COMMENT; } + + "///" { yybegin(DOC_CMT); } + {all_nl_wrap} "///" { } + {all_nl_nowrap} { yybegin(YYINITIAL); return DOC_COMMENT; } + + "//" { yybegin(LINE_CMT); } + {all_nl_wrap} "//" { } + {all_nl_nowrap} { yybegin(YYINITIAL); return LINE_COMMENT; } + +//Symbols + "&" { return AMPERSAND; } + "&=" { return AMPERSANDEQUAL; } + "*" { return ASTERISK; } + "**" { return ASTERISK2; } + "*=" { return ASTERISKEQUAL; } + "*%" { return ASTERISKPERCENT; } + "*%=" { return ASTERISKPERCENTEQUAL; } + "*|" { return ASTERISKPIPE; } + "*|=" { return ASTERISKPIPEEQUAL; } + "^" { return CARET; } + "^=" { return CARETEQUAL; } + ":" { return COLON; } + "," { return COMMA; } + "." { return DOT; } + ".." { return DOT2; } + "..." { return DOT3; } + ".*" { return DOTASTERISK; } + ".?" { return DOTQUESTIONMARK; } + "=" { return EQUAL; } + "==" { return EQUALEQUAL; } + "=>" { return EQUALRARROW; } + "!" { return EXCLAMATIONMARK; } + "!=" { return EXCLAMATIONMARKEQUAL; } + "<" { return LARROW; } + "<<" { return LARROW2; } + "<<=" { return LARROW2EQUAL; } + "<<|" { return LARROW2PIPE; } + "<<|=" { return LARROW2PIPEEQUAL; } + "<=" { return LARROWEQUAL; } + "{" { return LBRACE; } + "[" { return LBRACKET; } + "(" { return LPAREN; } + "-" { return MINUS; } + "-=" { return MINUSEQUAL; } + "-%" { return MINUSPERCENT; } + "-%=" { return MINUSPERCENTEQUAL; } + "-|" { return MINUSPIPE; } + "-|=" { return MINUSPIPEEQUAL; } + "->" { return MINUSRARROW; } + "%" { return PERCENT; } + "%=" { return PERCENTEQUAL; } + "|" { return PIPE; } + "||" { return PIPE2; } + "|=" { return PIPEEQUAL; } + "+" { return PLUS; } + "++" { return PLUS2; } + "+=" { return PLUSEQUAL; } + "+%" { return PLUSPERCENT; } + "+%=" { return PLUSPERCENTEQUAL; } + "+|" { return PLUSPIPE; } + "+|=" { return PLUSPIPEEQUAL; } +//This one is directly in the tokenizer, because it conflicts with identifiers without context +// "c" { return LETTERC; } + "?" { return QUESTIONMARK; } + ">" { return RARROW; } + ">>" { return RARROW2; } + ">>=" { return RARROW2EQUAL; } + ">=" { return RARROWEQUAL; } + "}" { return RBRACE; } + "]" { return RBRACKET; } + ")" { return RPAREN; } + ";" { return SEMICOLON; } + "/" { return SLASH; } + "/=" { return SLASHEQUAL; } + "~" { return TILDE; } + +// keywords + "addrspace" { return KEYWORD_ADDRSPACE; } + "align" { return KEYWORD_ALIGN; } + "allowzero" { return KEYWORD_ALLOWZERO; } + "and" { return KEYWORD_AND; } + "anyframe" { return KEYWORD_ANYFRAME; } + "anytype" { return KEYWORD_ANYTYPE; } + "asm" { return KEYWORD_ASM; } + "async" { return KEYWORD_ASYNC; } + "await" { return KEYWORD_AWAIT; } + "break" { return KEYWORD_BREAK; } + "callconv" { return KEYWORD_CALLCONV; } + "catch" { return KEYWORD_CATCH; } + "comptime" { return KEYWORD_COMPTIME; } + "const" { return KEYWORD_CONST; } + "continue" { return KEYWORD_CONTINUE; } + "defer" { return KEYWORD_DEFER; } + "else" { return KEYWORD_ELSE; } + "enum" { return KEYWORD_ENUM; } + "errdefer" { return KEYWORD_ERRDEFER; } + "error" { return KEYWORD_ERROR; } + "export" { return KEYWORD_EXPORT; } + "extern" { return KEYWORD_EXTERN; } + "fn" { return KEYWORD_FN; } + "for" { return KEYWORD_FOR; } + "if" { return KEYWORD_IF; } + "inline" { return KEYWORD_INLINE; } + "noalias" { return KEYWORD_NOALIAS; } + "nosuspend" { return KEYWORD_NOSUSPEND; } + "noinline" { return KEYWORD_NOINLINE; } + "opaque" { return KEYWORD_OPAQUE; } + "or" { return KEYWORD_OR; } + "orelse" { return KEYWORD_ORELSE; } + "packed" { return KEYWORD_PACKED; } + "pub" { return KEYWORD_PUB; } + "resume" { return KEYWORD_RESUME; } + "return" { return KEYWORD_RETURN; } + "linksection" { return KEYWORD_LINKSECTION; } + "struct" { return KEYWORD_STRUCT; } + "suspend" { return KEYWORD_SUSPEND; } + "switch" { return KEYWORD_SWITCH; } + "test" { return KEYWORD_TEST; } + "threadlocal" { return KEYWORD_THREADLOCAL; } + "try" { return KEYWORD_TRY; } + "union" { return KEYWORD_UNION; } + "unreachable" { return KEYWORD_UNREACHABLE; } + "usingnamespace" { return KEYWORD_USINGNAMESPACE; } + "var" { return KEYWORD_VAR; } + "volatile" { return KEYWORD_VOLATILE; } + "while" { return KEYWORD_WHILE; } + + "'" { yybegin(CHAR_LIT); } + {char_char}"'" { yybegin(YYINITIAL); return CHAR_LITERAL; } + [^] { yypushback(1); yybegin(UNT_QUOT); } + + {FLOAT} { return FLOAT; } + {INTEGER} { return INTEGER; } + + "\"" { yybegin(STR_LIT); } + {string_char}*"\"" { yybegin(YYINITIAL); return STRING_LITERAL_SINGLE; } + [^] { yypushback(1); yybegin(UNT_QUOT); } + "\\\\" { yybegin(STR_MULT_LINE); } + {all_nl_wrap} "\\\\" { } + {all_nl_nowrap} { yybegin(YYINITIAL); return STRING_LITERAL_MULTI; } + + {IDENTIFIER_PLAIN} { return IDENTIFIER; } + "@\"" { yybegin(ID_QUOT); } + {string_char}*"\"" { yybegin(YYINITIAL); return IDENTIFIER; } + [^] { yypushback(1); yybegin(UNT_QUOT); } + + {BUILTINIDENTIFIER} { return BUILTINIDENTIFIER; } + + [^\n]*{CRLF} { yybegin(YYINITIAL); return BAD_CHARACTER; } + + {WHITE_SPACE} { return WHITE_SPACE; } + +[^] { return BAD_CHARACTER; } diff --git a/modules/zig/src/main/grammar/ZigString.flex b/modules/zig/src/main/grammar/ZigString.flex new file mode 100644 index 00000000..48afab8e --- /dev/null +++ b/modules/zig/src/main/grammar/ZigString.flex @@ -0,0 +1,62 @@ +/* + * Copyright 2023-2024 FalsePattern + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.falsepattern.zigbrains.zig.stringlexer; + +import com.intellij.lexer.FlexLexer; +import com.intellij.psi.tree.IElementType; + +import static com.intellij.psi.TokenType.WHITE_SPACE; +import static com.intellij.psi.TokenType.BAD_CHARACTER; +import static com.falsepattern.zigbrains.zig.psi.ZigTypes.*; +import static com.intellij.psi.StringEscapesTokenTypes.*; + +%% + +%public +%class ZigStringLexer +%implements FlexLexer +%function advance +%type IElementType +%{ + public ZigStringLexer() { + + } +%} + +hex=[0-9a-fA-F] + +char_escape_unicode= "\\x" {hex} {hex} | "\\u{" {hex}+ "}" +char_escape_unicode_invalid= "\\x" | "\\u" + +char_escape_single_valid= "\\" [nr\\t'\"] +char_escape_single_invalid= "\\" [^nr\\t'\"] + +%state STR +%% + + + { + "\"" { yybegin(STR); return STRING_LITERAL_SINGLE; } + [^] { return STRING_LITERAL_SINGLE; } +} + + { + {char_escape_unicode} { return VALID_STRING_ESCAPE_TOKEN; } + {char_escape_unicode_invalid} { return INVALID_UNICODE_ESCAPE_TOKEN; } + {char_escape_single_valid} { return VALID_STRING_ESCAPE_TOKEN; } + {char_escape_single_invalid} { return INVALID_CHARACTER_ESCAPE_TOKEN; } + [^] { return STRING_LITERAL_SINGLE; } +} diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/zig/Icons.kt b/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/Icons.kt similarity index 100% rename from modules/core/src/main/kotlin/com/falsepattern/zigbrains/zig/Icons.kt rename to modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/Icons.kt diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/zig/ZigFileType.kt b/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/ZigFileType.kt similarity index 93% rename from modules/core/src/main/kotlin/com/falsepattern/zigbrains/zig/ZigFileType.kt rename to modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/ZigFileType.kt index 27cc8f13..8fee2759 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/zig/ZigFileType.kt +++ b/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/ZigFileType.kt @@ -1,7 +1,6 @@ package com.falsepattern.zigbrains.zig import com.intellij.openapi.fileTypes.LanguageFileType -import javax.swing.Icon object ZigFileType : LanguageFileType(ZigLanguage) { override fun getName() = "Zig File" diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/zig/ZigLanguage.kt b/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/ZigLanguage.kt similarity index 100% rename from modules/core/src/main/kotlin/com/falsepattern/zigbrains/zig/ZigLanguage.kt rename to modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/ZigLanguage.kt diff --git a/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/lexer/ZigLexerAdapter.kt b/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/lexer/ZigLexerAdapter.kt new file mode 100644 index 00000000..8c4b5679 --- /dev/null +++ b/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/lexer/ZigLexerAdapter.kt @@ -0,0 +1,5 @@ +package com.falsepattern.zigbrains.zig.lexer + +import com.intellij.lexer.FlexAdapter + +class ZigLexerAdapter: FlexAdapter(ZigFlexLexer(null)) \ No newline at end of file diff --git a/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/parser/ZigElementType.kt b/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/parser/ZigElementType.kt new file mode 100644 index 00000000..8441168e --- /dev/null +++ b/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/parser/ZigElementType.kt @@ -0,0 +1,6 @@ +package com.falsepattern.zigbrains.zig.parser + +import com.falsepattern.zigbrains.zig.ZigLanguage +import com.intellij.psi.tree.IElementType + +class ZigElementType(debugName: String): IElementType(debugName, ZigLanguage) \ No newline at end of file diff --git a/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/parser/ZigParserDefinition.kt b/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/parser/ZigParserDefinition.kt new file mode 100644 index 00000000..78cdf508 --- /dev/null +++ b/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/parser/ZigParserDefinition.kt @@ -0,0 +1,30 @@ +package com.falsepattern.zigbrains.zig.parser + +import com.falsepattern.zigbrains.zig.ZigLanguage +import com.falsepattern.zigbrains.zig.lexer.ZigLexerAdapter +import com.falsepattern.zigbrains.zig.psi.ZigFile +import com.falsepattern.zigbrains.zig.psi.ZigTypes +import com.intellij.lang.ASTNode +import com.intellij.lang.ParserDefinition +import com.intellij.openapi.project.Project +import com.intellij.psi.FileViewProvider +import com.intellij.psi.tree.IFileElementType + +class ZigParserDefinition : ParserDefinition { + override fun createLexer(project: Project?) = ZigLexerAdapter() + + override fun createParser(project: Project?) = ZigParser() + + override fun getFileNodeType() = FILE + + override fun getCommentTokens() = ZigTokenSets.COMMENTS + + override fun getStringLiteralElements() = ZigTokenSets.STRINGS + + override fun createElement(node: ASTNode?) = ZigTypes.Factory.createElement(node)!! + + override fun createFile(viewProvider: FileViewProvider) = ZigFile(viewProvider) + +} + +val FILE = IFileElementType(ZigLanguage) \ No newline at end of file diff --git a/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/parser/ZigTokenSets.kt b/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/parser/ZigTokenSets.kt new file mode 100644 index 00000000..ca675aaf --- /dev/null +++ b/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/parser/ZigTokenSets.kt @@ -0,0 +1,9 @@ +package com.falsepattern.zigbrains.zig.parser + +import com.falsepattern.zigbrains.zig.psi.ZigTypes +import com.intellij.psi.tree.TokenSet + +object ZigTokenSets { + val COMMENTS = TokenSet.create(ZigTypes.LINE_COMMENT, ZigTypes.DOC_COMMENT, ZigTypes.CONTAINER_DOC_COMMENT) + val STRINGS = TokenSet.create(ZigTypes.STRING_LITERAL_SINGLE, ZigTypes.STRING_LITERAL_MULTI) +} \ No newline at end of file diff --git a/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/parser/ZigTokenType.kt b/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/parser/ZigTokenType.kt new file mode 100644 index 00000000..e84ec6e6 --- /dev/null +++ b/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/parser/ZigTokenType.kt @@ -0,0 +1,6 @@ +package com.falsepattern.zigbrains.zig.parser + +import com.falsepattern.zigbrains.zig.ZigLanguage +import com.intellij.psi.tree.IElementType + +class ZigTokenType(debugName: String): IElementType(debugName, ZigLanguage) \ No newline at end of file diff --git a/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/psi/ZigFile.kt b/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/psi/ZigFile.kt new file mode 100644 index 00000000..afda8808 --- /dev/null +++ b/modules/zig/src/main/kotlin/com/falsepattern/zigbrains/zig/psi/ZigFile.kt @@ -0,0 +1,12 @@ +package com.falsepattern.zigbrains.zig.psi + +import com.falsepattern.zigbrains.zig.ZigFileType +import com.falsepattern.zigbrains.zig.ZigLanguage +import com.intellij.extapi.psi.PsiFileBase +import com.intellij.psi.FileViewProvider + +class ZigFile(viewProvider: FileViewProvider): PsiFileBase(viewProvider, ZigLanguage) { + override fun getFileType() = ZigFileType + + override fun toString() = ZigFileType.name +} \ No newline at end of file diff --git a/modules/core/src/main/resources/icons/zig.svg b/modules/zig/src/main/resources/icons/zig.svg similarity index 100% rename from modules/core/src/main/resources/icons/zig.svg rename to modules/zig/src/main/resources/icons/zig.svg diff --git a/modules/zon/build.gradle.kts b/modules/zon/build.gradle.kts new file mode 100644 index 00000000..692847e0 --- /dev/null +++ b/modules/zon/build.gradle.kts @@ -0,0 +1,64 @@ +import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType + +plugins { + kotlin("jvm") + id("org.jetbrains.grammarkit") +} + +dependencies { + intellijPlatform { + create(IntelliJPlatformType.IntellijIdeaCommunity, providers.gradleProperty("ideaCommunityVersion")) + } +} + +val grammarGenRoot = "generated/sources/grammarkit/zon" +val rootPackagePath = "com/falsepattern/zigbrains/zon" + +val parserDir = layout.buildDirectory.dir("$grammarGenRoot/parser") +val lexerDir = layout.buildDirectory.dir("$grammarGenRoot/lexer") + +sourceSets { + main { + java { + srcDir(parserDir) + srcDir(lexerDir) + } + } +} + +idea { + module { + sourceDirs.addAll(listOf(parserDir.get().asFile, lexerDir.get().asFile)) + generatedSourceDirs.addAll(listOf(parserDir.get().asFile, lexerDir.get().asFile)) + } +} + +tasks { + generateLexer { + purgeOldFiles = true + sourceFile = file("src/main/grammar/Zon.flex") + targetOutputDir = layout.buildDirectory.dir("$grammarGenRoot/lexer/$rootPackagePath/lexer") + } + + generateParser { + purgeOldFiles = true + sourceFile = file("src/main/grammar/Zon.bnf") + targetRootOutputDir = layout.buildDirectory.dir("$grammarGenRoot/parser") + pathToParser = "$rootPackagePath/psi/ZonParser.java" + pathToPsiRoot = "$rootPackagePath/psi" + } + + register("generateGrammars") { + group = "grammarkit" + dependsOn("generateLexer") + dependsOn("generateParser") + } + + compileJava { + dependsOn("generateGrammars") + } + + compileKotlin { + dependsOn("generateGrammars") + } +} \ No newline at end of file diff --git a/modules/zon/src/main/grammar/Zon.bnf b/modules/zon/src/main/grammar/Zon.bnf new file mode 100644 index 00000000..a97d6423 --- /dev/null +++ b/modules/zon/src/main/grammar/Zon.bnf @@ -0,0 +1,73 @@ +/* + * Copyright 2023-2024 FalsePattern + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + parserClass="com.falsepattern.zigbrains.zon.parser.ZonParser" + + extends="com.intellij.extapi.psi.ASTWrapperPsiElement" + + psiClassPrefix="Zon" + psiImplClassSuffix="Impl" + psiPackage="com.falsepattern.zigbrains.zon.psi" + psiImplPackage="com.falsepattern.zigbrains.zon.psi.impl" + + elementTypeHolderClass="com.falsepattern.zigbrains.zon.psi.ZonTypes" + elementTypeClass="com.falsepattern.zigbrains.zon.parser.ZonElementType" + tokenTypeClass="com.falsepattern.zigbrains.zon.parser.ZonTokenType" + tokens=[ + DOT='.' + LBRACE='{' + RBRACE='}' + EQ='=' + COMMA=',' + COMMENT='comment' + ID='identifier' + STRING_LITERAL_SINGLE='string' + LINE_STRING='multiline string' + BAD_STRING='unterminated string' + BOOL_TRUE='true' + BOOL_FALSE='false' + ] + + //Mixins + mixin("entry")="com.falsepattern.zigbrains.zon.psi.impl.mixins.ZonEntryMixinImpl" + implements("entry")="com.falsepattern.zigbrains.zon.psi.mixins.ZonEntryMixin" + + mixin("identifier")="com.falsepattern.zigbrains.zon.psi.impl.mixins.ZonIdentifierMixinImpl" + implements("identifier")="com.falsepattern.zigbrains.zon.psi.mixins.ZonIdentifierMixin" +} + +zonFile ::= entry + +entry ::= DOT LBRACE (list | struct | ()) RBRACE + +struct ::= (property | property_placeholder) (COMMA (property_placeholder? property property_placeholder? | property_placeholder))* COMMA? + +list ::= value (COMMA value)* COMMA? + +property ::= DOT identifier EQ value + +identifier ::= ID + +property_placeholder ::= DOT? INTELLIJ_COMPLETION_DUMMY + +private value ::= entry | boolean | STRING_LITERAL | value_placeholder + +value_placeholder ::= INTELLIJ_COMPLETION_DUMMY + +boolean ::= BOOL_TRUE | BOOL_FALSE + +STRING_LITERAL ::= STRING_LITERAL_SINGLE | LINE_STRING+ diff --git a/modules/zon/src/main/grammar/Zon.flex b/modules/zon/src/main/grammar/Zon.flex new file mode 100644 index 00000000..c5bbddaf --- /dev/null +++ b/modules/zon/src/main/grammar/Zon.flex @@ -0,0 +1,83 @@ +/* + * Copyright 2023-2024 FalsePattern + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.falsepattern.zigbrains.zon.lexer; + +import com.intellij.lexer.FlexLexer; +import com.intellij.psi.tree.IElementType; + +import static com.intellij.psi.TokenType.WHITE_SPACE; +import static com.intellij.psi.TokenType.BAD_CHARACTER; +import static com.falsepattern.zigbrains.zon.psi.ZonTypes.*; + +%% + +%class ZonFlexLexer +%implements FlexLexer +%function advance +%type IElementType +%unicode + +CRLF=\R +WHITE_SPACE=[\s]+ +LINE_COMMENT="//" [^\n]* | "////" [^\n]* +COMMENT="///".* + +ID=[A-Za-z_][A-Za-z0-9_]* + +hex=[0-9a-fA-F] +char_escape + = "\\x" {hex} {hex} + | "\\u{" {hex}+ "}" + | "\\" [nr\\t'\"] + +string_char + = {char_escape} + | [^\\\"\n] + +LINE_STRING=("\\\\" [^\n]* [ \n]*)+ + +%state STRING_LITERAL +%state ID_STRING +%state UNCLOSED_STRING +%% + + + {WHITE_SPACE} { return WHITE_SPACE; } + "." { return DOT; } + "IntellijIdeaRulezzz" { return INTELLIJ_COMPLETION_DUMMY; } + "{" { return LBRACE; } + "}" { return RBRACE; } + "=" { return EQ; } + "," { return COMMA; } + "true" { return BOOL_TRUE; } + "false" { return BOOL_FALSE; } + {COMMENT} { return COMMENT; } + {LINE_COMMENT} { return COMMENT; } + + {ID} { return ID; } + "@\"" { yybegin(ID_STRING); } + {string_char}*"\"" { yybegin(YYINITIAL); return ID; } + [^] { yypushback(1); yybegin(UNCLOSED_STRING); } + + "\"" { yybegin(STRING_LITERAL); } + {string_char}*"\"" { yybegin(YYINITIAL); return STRING_LITERAL_SINGLE; } + [^] { yypushback(1); yybegin(UNCLOSED_STRING); } + +[^\n]*{CRLF} { yybegin(YYINITIAL); return BAD_STRING; } + + {LINE_STRING} { return LINE_STRING; } + +[^] { return BAD_CHARACTER; } diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/Icons.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/Icons.kt new file mode 100644 index 00000000..67eb4275 --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/Icons.kt @@ -0,0 +1,7 @@ +package com.falsepattern.zigbrains.zon + +import com.intellij.openapi.util.IconLoader + +object Icons { + val ZON = IconLoader.getIcon("/icons/zon.svg", Icons::class.java) +} \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/ZonFileType.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/ZonFileType.kt new file mode 100644 index 00000000..8dd3c9ef --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/ZonFileType.kt @@ -0,0 +1,13 @@ +package com.falsepattern.zigbrains.zon + +import com.intellij.openapi.fileTypes.LanguageFileType + +object ZonFileType: LanguageFileType(ZonLanguage) { + override fun getName() = "Zon File" + + override fun getDescription() = "Zig object notation file" + + override fun getDefaultExtension() = "zon" + + override fun getIcon() = Icons.ZON +} \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/ZonLanguage.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/ZonLanguage.kt new file mode 100644 index 00000000..de004d9e --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/ZonLanguage.kt @@ -0,0 +1,7 @@ +package com.falsepattern.zigbrains.zon + +import com.intellij.lang.Language + +object ZonLanguage: Language("Zon") { + private fun readResolve(): Any = ZonLanguage +} \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/comments/ZonCommenter.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/comments/ZonCommenter.kt new file mode 100644 index 00000000..2255980d --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/comments/ZonCommenter.kt @@ -0,0 +1,35 @@ +package com.falsepattern.zigbrains.zon.comments + +import com.falsepattern.zigbrains.zon.psi.ZonTypes +import com.intellij.codeInsight.generation.IndentedCommenter +import com.intellij.lang.CodeDocumentationAwareCommenter +import com.intellij.psi.PsiComment +import com.intellij.psi.tree.IElementType + +class ZonCommenter: CodeDocumentationAwareCommenter, IndentedCommenter { + override fun getLineCommentPrefix() = "// " + + override fun getBlockCommentPrefix() = null + + override fun getBlockCommentSuffix() = null + + override fun getCommentedBlockCommentPrefix() = null + + override fun getCommentedBlockCommentSuffix() = null + + override fun forceIndentedLineComment() = true + + override fun getLineCommentTokenType(): IElementType = ZonTypes.COMMENT + + override fun getBlockCommentTokenType() = null + + override fun getDocumentationCommentTokenType() = null + + override fun getDocumentationCommentPrefix() = null + + override fun getDocumentationCommentLinePrefix() = null + + override fun getDocumentationCommentSuffix() = null + + override fun isDocumentationComment(element: PsiComment?) = false +} \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/completion/ZonCompletionContributor.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/completion/ZonCompletionContributor.kt new file mode 100644 index 00000000..4969ccf5 --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/completion/ZonCompletionContributor.kt @@ -0,0 +1,111 @@ +package com.falsepattern.zigbrains.zon.completion + +import com.falsepattern.zigbrains.zon.psi.ZonEntry +import com.falsepattern.zigbrains.zon.psi.ZonFile +import com.falsepattern.zigbrains.zon.psi.ZonProperty +import com.falsepattern.zigbrains.zon.psi.ZonPropertyPlaceholder +import com.falsepattern.zigbrains.zon.psi.ZonTypes +import com.falsepattern.zigbrains.zon.psi.ZonValuePlaceholder +import com.intellij.codeInsight.completion.* +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.patterns.ElementPattern +import com.intellij.patterns.PlatformPatterns.psiElement +import com.intellij.patterns.PsiElementPattern +import com.intellij.psi.PsiElement +import com.intellij.psi.util.parentOfType +import com.intellij.util.ProcessingContext + +class ZonCompletionContributor : CompletionContributor() { + init { + extend( + CompletionType.BASIC, + psiElement() + .withParent(psiElement(ZonTypes.PROPERTY_PLACEHOLDER)) + .withSuperParent(4, psiOfType()) + ) { parameters, _, result -> + val placeholder = parameters.position.parentOfType() ?: return@extend + val zonEntry = placeholder.parentOfType() ?: return@extend + val keys = zonEntry.keys + doAddCompletions(placeholder.text.startsWith('.'), keys, ZON_ROOT_KEYS, result) + } + extend( + CompletionType.BASIC, + psiElement() + .withParent(psiElement(ZonTypes.VALUE_PLACEHOLDER)) + .withSuperParent(2, psiElement(ZonTypes.LIST)) + .withSuperParent(4, psiOfType()) + ) { _, _, result -> + doAddCompletions(false, emptySet(), ZON_ROOT_KEYS, result) + } + extend( + CompletionType.BASIC, + psiElement() + .withParent(psiElement(ZonTypes.PROPERTY_PLACEHOLDER)) + .withSuperParent(4, psiElement(ZonTypes.PROPERTY)) + .withSuperParent(7, psiElement(ZonTypes.PROPERTY)) + .withSuperParent(10, psiOfType()) + ) { parameters, _, result -> + val placeholder = parameters.position.parentOfType() ?: return@extend + val depEntry = placeholder.parentOfType() ?: return@extend + if (depEntry.isDependency) { + doAddCompletions(false, emptySet(), ZON_DEP_KEYS, result) + } + } + extend( + CompletionType.BASIC, + psiElement() + .withParent(psiElement(ZonTypes.VALUE_PLACEHOLDER)) + .withSuperParent(5, psiElement(ZonTypes.PROPERTY)) + .withSuperParent(8, psiElement(ZonTypes.PROPERTY)) + .withSuperParent(11, psiOfType()) + ) { parameters, _, result -> + val placeholder = parameters.position.parentOfType() ?: return@extend + val valueProperty = placeholder.parentOfType() ?: return@extend + if (valueProperty.isDependency && valueProperty.identifier.value == "lazy") { + result.addElement(LookupElementBuilder.create("true")) + result.addElement(LookupElementBuilder.create("false")) + } + } + } + + private inline fun extend(type: CompletionType, place: ElementPattern, crossinline action: Processor) { + extend(type, place, object : CompletionProvider() { + override fun addCompletions( + parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet + ) = action(parameters, context, result) + }) + } +} + +private typealias Processor = ( + parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet +) -> Unit + +private val ZON_ROOT_KEYS: List = + listOf("name", "version", "minimum_zig_version", "dependencies", "paths") +private val ZON_DEP_KEYS: List = listOf("url", "hash", "path", "lazy") + +private fun doAddCompletions( + hasDot: Boolean, current: Set, expected: List, result: CompletionResultSet +) { + for (key in expected) { + if (current.contains(key)) { + continue + } + result.addElement(LookupElementBuilder.create(if (hasDot) key else ".$key")) + } +} + +private val ZonProperty.isDependency: Boolean + get() { + return parentOfType()?.isDependency ?: false + } + +private val ZonEntry.isDependency: Boolean + get() { + val parentProperty = parentOfType()?.parentOfType() ?: return false + return parentProperty.identifier.value == "dependencies" + } + +private inline fun psiOfType(): PsiElementPattern.Capture = + psiElement(T::class.java) \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/folding/ZonFoldingBuilder.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/folding/ZonFoldingBuilder.kt new file mode 100644 index 00000000..0c8deae6 --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/folding/ZonFoldingBuilder.kt @@ -0,0 +1,36 @@ +package com.falsepattern.zigbrains.zon.folding + +import com.falsepattern.zigbrains.zon.psi.ZonStruct +import com.falsepattern.zigbrains.zon.psi.ZonVisitor +import com.intellij.lang.ASTNode +import com.intellij.lang.folding.CustomFoldingBuilder +import com.intellij.lang.folding.FoldingDescriptor +import com.intellij.openapi.editor.Document +import com.intellij.openapi.project.DumbAware +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement + +class ZonFoldingBuilder: CustomFoldingBuilder(), DumbAware { + override fun buildLanguageFoldRegions( + descriptors: MutableList, + root: PsiElement, + document: Document, + quick: Boolean + ) { + root.accept(object: ZonVisitor() { + override fun visitElement(element: PsiElement) { + super.visitElement(element) + element.acceptChildren(this) + } + + override fun visitStruct(o: ZonStruct) { + super.visitStruct(o) + descriptors.add(FoldingDescriptor(o, o.textRange)) + } + }) + } + + override fun getLanguagePlaceholderText(node: ASTNode, range: TextRange) = ".{...}" + + override fun isRegionCollapsedByDefault(node: ASTNode) = false +} \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/formatter/ZonBlock.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/formatter/ZonBlock.kt new file mode 100644 index 00000000..8ff66ea3 --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/formatter/ZonBlock.kt @@ -0,0 +1,54 @@ +package com.falsepattern.zigbrains.zon.formatter + +import com.falsepattern.zigbrains.zon.psi.ZonTypes +import com.intellij.formatting.* +import com.intellij.lang.ASTNode +import com.intellij.psi.TokenType +import com.intellij.psi.formatter.common.AbstractBlock + +class ZonBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + private val spacingBuilder: SpacingBuilder +) : AbstractBlock(node, wrap, alignment) { + + override fun buildChildren(): MutableList { + val blocks = ArrayList() + var child = myNode.firstChildNode + while (child != null) { + if (child.elementType != TokenType.WHITE_SPACE) { + blocks.add(ZonBlock(child, null, null, spacingBuilder)) + } + child = child.treeNext + } + return blocks + } + + override fun getIndent(): Indent { + val parent = myNode.treeParent ?: return Indent.getNoneIndent() + val elementType = myNode.elementType + return if (parent.elementType == ZonTypes.ENTRY && + elementType != ZonTypes.DOT && + elementType != ZonTypes.LBRACE && + elementType != ZonTypes.RBRACE + ) { + Indent.getNormalIndent() + } else { + Indent.getNoneIndent() + } + } + + override fun getSpacing(child1: Block?, child2: Block) = null + + override fun isLeaf(): Boolean = myNode.firstChildNode == null + + override fun getChildIndent(): Indent { + return if (myNode.elementType == ZonTypes.ENTRY) { + Indent.getNormalIndent() + } else { + Indent.getNoneIndent() + } + } + +} \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/formatter/ZonFormattingModelBuilder.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/formatter/ZonFormattingModelBuilder.kt new file mode 100644 index 00000000..93c04ade --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/formatter/ZonFormattingModelBuilder.kt @@ -0,0 +1,19 @@ +package com.falsepattern.zigbrains.zon.formatter + +import com.falsepattern.zigbrains.zon.ZonLanguage +import com.intellij.formatting.FormattingContext +import com.intellij.formatting.FormattingModel +import com.intellij.formatting.FormattingModelBuilder +import com.intellij.formatting.FormattingModelProvider +import com.intellij.formatting.SpacingBuilder + +class ZonFormattingModelBuilder : FormattingModelBuilder { + override fun createModel(context: FormattingContext): FormattingModel { + val settings = context.codeStyleSettings + return FormattingModelProvider.createFormattingModelForPsiFile( + context.containingFile, + ZonBlock(context.node, null, null, SpacingBuilder(settings, ZonLanguage)), + settings + ) + } +} diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/highlighting/ZonColorSettingsPage.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/highlighting/ZonColorSettingsPage.kt new file mode 100644 index 00000000..9ec31370 --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/highlighting/ZonColorSettingsPage.kt @@ -0,0 +1,57 @@ +package com.falsepattern.zigbrains.zon.highlighting + +import com.falsepattern.zigbrains.zon.Icons +import com.intellij.openapi.options.colors.AttributesDescriptor +import com.intellij.openapi.options.colors.ColorDescriptor +import com.intellij.openapi.options.colors.ColorSettingsPage + +class ZonColorSettingsPage: ColorSettingsPage { + override fun getAttributeDescriptors() = DESCRIPTORS + + override fun getColorDescriptors(): Array = ColorDescriptor.EMPTY_ARRAY + + override fun getDisplayName() = "Zon" + + override fun getIcon() = Icons.ZON + + override fun getHighlighter() = ZonSyntaxHighlighter() + + override fun getDemoText() = """ + .{ + //This is an example file with some random data + .name = "zls", + .version = "0.11.0", + + .dependencies = .{ + .known_folders = .{ + .url = "https://github.com/ziglibs/known-folders/archive/fa75e1bc672952efa0cf06160bbd942b47f6d59b.tar.gz", + .hash = "122048992ca58a78318b6eba4f65c692564be5af3b30fbef50cd4abeda981b2e7fa5", + .lazy = true, + }, + .diffz = .{ + .url = "https://github.com/ziglibs/diffz/archive/90353d401c59e2ca5ed0abe5444c29ad3d7489aa.tar.gz", + .hash = "122089a8247a693cad53beb161bde6c30f71376cd4298798d45b32740c3581405864", + }, + .binned_allocator = .{ + .url = "https://gist.github.com/antlilja/8372900fcc09e38d7b0b6bbaddad3904/archive/6c3321e0969ff2463f8335da5601986cf2108690.tar.gz", + .hash = "1220363c7e27b2d3f39de6ff6e90f9537a0634199860fea237a55ddb1e1717f5d6a5", + }, + }, + .paths = .{""}, + } + """.trimIndent() + + override fun getAdditionalHighlightingTagToDescriptorMap() = null +} + +val DESCRIPTORS = arrayOf( + AttributesDescriptor("Equals", ZonSyntaxHighlighter.EQ), + AttributesDescriptor("Identifier", ZonSyntaxHighlighter.ID), + AttributesDescriptor("Comment", ZonSyntaxHighlighter.COMMENT), + AttributesDescriptor("Bad value", ZonSyntaxHighlighter.BAD_CHAR), + AttributesDescriptor("String", ZonSyntaxHighlighter.STRING), + AttributesDescriptor("Comma", ZonSyntaxHighlighter.COMMA), + AttributesDescriptor("Dot", ZonSyntaxHighlighter.DOT), + AttributesDescriptor("Boolean", ZonSyntaxHighlighter.BOOLEAN), + AttributesDescriptor("Braces", ZonSyntaxHighlighter.BRACE) +) \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/highlighting/ZonSyntaxHighlighter.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/highlighting/ZonSyntaxHighlighter.kt new file mode 100644 index 00000000..79d16a68 --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/highlighting/ZonSyntaxHighlighter.kt @@ -0,0 +1,53 @@ +package com.falsepattern.zigbrains.zon.highlighting + +import com.falsepattern.zigbrains.zon.lexer.ZonLexerAdapter +import com.falsepattern.zigbrains.zon.psi.ZonTypes +import com.intellij.openapi.editor.HighlighterColors +import com.intellij.openapi.editor.colors.TextAttributesKey +import com.intellij.openapi.fileTypes.SyntaxHighlighterBase +import com.intellij.psi.TokenType +import com.intellij.psi.tree.IElementType +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors as DefaultColors + +class ZonSyntaxHighlighter : SyntaxHighlighterBase() { + override fun getHighlightingLexer() = ZonLexerAdapter() + + override fun getTokenHighlights(tokenType: IElementType?) = KEYMAP.getOrDefault(tokenType, EMPTY_KEYS) + + companion object { + // @formatter:off + val EQ = createKey("EQ" , DefaultColors.OPERATION_SIGN ) + val ID = createKey("ID" , DefaultColors.INSTANCE_FIELD ) + val COMMENT = createKey("COMMENT" , DefaultColors.LINE_COMMENT ) + val BAD_CHAR = createKey("BAD_CHARACTER", HighlighterColors.BAD_CHARACTER) + val STRING = createKey("STRING" , DefaultColors.STRING ) + val COMMA = createKey("COMMA" , DefaultColors.COMMA ) + val BOOLEAN = createKey("BOOLEAN" , DefaultColors.KEYWORD ) + val DOT = createKey("DOT" , DefaultColors.DOT ) + val BRACE = createKey("BRACE" , DefaultColors.BRACES ) + // @formatter:on + + private val EMPTY_KEYS = emptyArray() + private val KEYMAP = HashMap>() + + private fun createKey(name: String, fallback: TextAttributesKey) = + TextAttributesKey.createTextAttributesKey("ZON_$name", fallback) + + private fun addMapping(key: TextAttributesKey, vararg types: IElementType) = types.forEach { KEYMAP[it] = arrayOf(key) } + + init { + // @formatter:off + addMapping(DOT , ZonTypes.DOT) + addMapping(COMMA , ZonTypes.COMMA) + addMapping(BRACE , ZonTypes.LBRACE) + addMapping(BRACE , ZonTypes.RBRACE) + addMapping(STRING , ZonTypes.LINE_STRING, ZonTypes.STRING_LITERAL_SINGLE) + addMapping(BAD_CHAR, TokenType.BAD_CHARACTER) + addMapping(COMMENT , ZonTypes.COMMENT) + addMapping(ID , ZonTypes.ID) + addMapping(EQ , ZonTypes.EQ) + addMapping(BOOLEAN , ZonTypes.BOOL_FALSE, ZonTypes.BOOL_TRUE) + // @formatter:on + } + } +} \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/highlighting/ZonSyntaxHighlighterFactory.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/highlighting/ZonSyntaxHighlighterFactory.kt new file mode 100644 index 00000000..de0ea74e --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/highlighting/ZonSyntaxHighlighterFactory.kt @@ -0,0 +1,9 @@ +package com.falsepattern.zigbrains.zon.highlighting + +import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile + +class ZonSyntaxHighlighterFactory: SyntaxHighlighterFactory() { + override fun getSyntaxHighlighter(project: Project?, virtualFile: VirtualFile?) = ZonSyntaxHighlighter() +} \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/lexer/ZonLexerAdapter.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/lexer/ZonLexerAdapter.kt new file mode 100644 index 00000000..52f8cb1d --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/lexer/ZonLexerAdapter.kt @@ -0,0 +1,5 @@ +package com.falsepattern.zigbrains.zon.lexer + +import com.intellij.lexer.FlexAdapter + +class ZonLexerAdapter: FlexAdapter(ZonFlexLexer(null)) \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/pairing/ZonBraceMatcher.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/pairing/ZonBraceMatcher.kt new file mode 100644 index 00000000..92be7302 --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/pairing/ZonBraceMatcher.kt @@ -0,0 +1,21 @@ +package com.falsepattern.zigbrains.zon.pairing + +import com.falsepattern.zigbrains.zon.psi.ZonTypes +import com.intellij.lang.BracePair +import com.intellij.lang.PairedBraceMatcher +import com.intellij.psi.PsiFile +import com.intellij.psi.tree.IElementType + +class ZonBraceMatcher : PairedBraceMatcher { + override fun getPairs() = + PAIRS + + override fun isPairedBracesAllowedBeforeType(lbraceType: IElementType, contextType: IElementType?) = + true + + override fun getCodeConstructStart(file: PsiFile?, openingBraceOffset: Int) = + file?.findElementAt(openingBraceOffset)?.parent?.textOffset ?: openingBraceOffset +} + +private val PAIR = BracePair(ZonTypes.LBRACE, ZonTypes.RBRACE, true) +private val PAIRS = arrayOf(PAIR) \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/pairing/ZonQuoteHandler.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/pairing/ZonQuoteHandler.kt new file mode 100644 index 00000000..99e001e5 --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/pairing/ZonQuoteHandler.kt @@ -0,0 +1,11 @@ +package com.falsepattern.zigbrains.zon.pairing + +import com.falsepattern.zigbrains.zon.psi.ZonTypes +import com.intellij.codeInsight.editorActions.MultiCharQuoteHandler +import com.intellij.codeInsight.editorActions.SimpleTokenSetQuoteHandler +import com.intellij.openapi.editor.highlighter.HighlighterIterator + +class ZonQuoteHandler: SimpleTokenSetQuoteHandler(ZonTypes.STRING_LITERAL, ZonTypes.BAD_STRING), MultiCharQuoteHandler { + override fun getClosingQuote(iterator: HighlighterIterator, offset: Int) = + "\"" +} \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/parser/ZonElementType.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/parser/ZonElementType.kt new file mode 100644 index 00000000..32018e86 --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/parser/ZonElementType.kt @@ -0,0 +1,6 @@ +package com.falsepattern.zigbrains.zon.parser + +import com.falsepattern.zigbrains.zon.ZonLanguage +import com.intellij.psi.tree.IElementType + +class ZonElementType(debugName: String): IElementType(debugName, ZonLanguage) \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/parser/ZonParserDefinition.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/parser/ZonParserDefinition.kt new file mode 100644 index 00000000..759060d2 --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/parser/ZonParserDefinition.kt @@ -0,0 +1,29 @@ +package com.falsepattern.zigbrains.zon.parser + +import com.falsepattern.zigbrains.zon.ZonLanguage +import com.falsepattern.zigbrains.zon.lexer.ZonLexerAdapter +import com.falsepattern.zigbrains.zon.psi.ZonFile +import com.falsepattern.zigbrains.zon.psi.ZonTypes +import com.intellij.lang.ASTNode +import com.intellij.lang.ParserDefinition +import com.intellij.openapi.project.Project +import com.intellij.psi.FileViewProvider +import com.intellij.psi.tree.IFileElementType + +class ZonParserDefinition: ParserDefinition { + override fun createLexer(project: Project?) = ZonLexerAdapter() + + override fun createParser(project: Project?) = ZonParser() + + override fun getFileNodeType() = FILE + + override fun getCommentTokens() = ZonTokenSets.COMMENTS + + override fun getStringLiteralElements() = ZonTokenSets.STRINGS + + override fun createElement(node: ASTNode?) = ZonTypes.Factory.createElement(node)!! + + override fun createFile(viewProvider: FileViewProvider) = ZonFile(viewProvider) +} + +val FILE = IFileElementType(ZonLanguage) \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/parser/ZonTokenSets.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/parser/ZonTokenSets.kt new file mode 100644 index 00000000..2eea415a --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/parser/ZonTokenSets.kt @@ -0,0 +1,9 @@ +package com.falsepattern.zigbrains.zon.parser + +import com.falsepattern.zigbrains.zon.psi.ZonTypes +import com.intellij.psi.tree.TokenSet + +object ZonTokenSets { + val COMMENTS = TokenSet.create(ZonTypes.COMMENT) + val STRINGS = TokenSet.create(ZonTypes.LINE_STRING, ZonTypes.STRING_LITERAL_SINGLE) +} \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/parser/ZonTokenType.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/parser/ZonTokenType.kt new file mode 100644 index 00000000..7250201d --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/parser/ZonTokenType.kt @@ -0,0 +1,6 @@ +package com.falsepattern.zigbrains.zon.parser + +import com.falsepattern.zigbrains.zon.ZonLanguage +import com.intellij.psi.tree.IElementType + +class ZonTokenType(debugName: String): IElementType(debugName, ZonLanguage) \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/psi/ZonFile.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/psi/ZonFile.kt new file mode 100644 index 00000000..69d1e83b --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/psi/ZonFile.kt @@ -0,0 +1,12 @@ +package com.falsepattern.zigbrains.zon.psi + +import com.falsepattern.zigbrains.zon.ZonFileType +import com.falsepattern.zigbrains.zon.ZonLanguage +import com.intellij.extapi.psi.PsiFileBase +import com.intellij.psi.FileViewProvider + +class ZonFile(viewProvider: FileViewProvider): PsiFileBase(viewProvider, ZonLanguage) { + override fun getFileType() = ZonFileType + + override fun toString() = "Zon File" +} \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/psi/impl/mixins/ZonEntryMixinImpl.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/psi/impl/mixins/ZonEntryMixinImpl.kt new file mode 100644 index 00000000..d44780bc --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/psi/impl/mixins/ZonEntryMixinImpl.kt @@ -0,0 +1,12 @@ +package com.falsepattern.zigbrains.zon.psi.impl.mixins + +import com.falsepattern.zigbrains.zon.psi.ZonEntry +import com.intellij.extapi.psi.ASTWrapperPsiElement +import com.intellij.lang.ASTNode + +abstract class ZonEntryMixinImpl(node: ASTNode): ASTWrapperPsiElement(node), ZonEntry { + override val keys: Set get() { + val struct = this.struct ?: return emptySet() + return struct.propertyList.map { it.identifier.value }.toSet() + } +} \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/psi/impl/mixins/ZonIdentifierMixinImpl.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/psi/impl/mixins/ZonIdentifierMixinImpl.kt new file mode 100644 index 00000000..4d56e38f --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/psi/impl/mixins/ZonIdentifierMixinImpl.kt @@ -0,0 +1,15 @@ +package com.falsepattern.zigbrains.zon.psi.impl.mixins + +import com.falsepattern.zigbrains.zon.psi.ZonIdentifier +import com.intellij.extapi.psi.ASTWrapperPsiElement +import com.intellij.lang.ASTNode + +abstract class ZonIdentifierMixinImpl(node: ASTNode): ASTWrapperPsiElement(node), ZonIdentifier { + override val value: String get() { + val text = this.text!! + return if (text.startsWith('@')) + text.substring(2, text.length - 1) + else + text + } +} diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/psi/mixins/ZonEntryMixin.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/psi/mixins/ZonEntryMixin.kt new file mode 100644 index 00000000..bd6054d5 --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/psi/mixins/ZonEntryMixin.kt @@ -0,0 +1,7 @@ +package com.falsepattern.zigbrains.zon.psi.mixins + +import com.intellij.psi.PsiElement + +interface ZonEntryMixin: PsiElement { + val keys: Set +} \ No newline at end of file diff --git a/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/psi/mixins/ZonIdentifierMixin.kt b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/psi/mixins/ZonIdentifierMixin.kt new file mode 100644 index 00000000..dd96d8e4 --- /dev/null +++ b/modules/zon/src/main/kotlin/com/falsepattern/zigbrains/zon/psi/mixins/ZonIdentifierMixin.kt @@ -0,0 +1,7 @@ +package com.falsepattern.zigbrains.zon.psi.mixins + +import com.intellij.psi.PsiElement + +interface ZonIdentifierMixin: PsiElement { + val value: String +} \ No newline at end of file diff --git a/modules/zon/src/main/resources/icons/zon.svg b/modules/zon/src/main/resources/icons/zon.svg new file mode 100644 index 00000000..36a11876 --- /dev/null +++ b/modules/zon/src/main/resources/icons/zon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 509216e4..4a2998fb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,5 +3,7 @@ plugins { } rootProject.name = "ZigBrains" -include("core") -project(":core").projectDir = file("modules/core") \ No newline at end of file +include("zig") +project(":zig").projectDir = file("modules/zig") +include("zon") +project(":zon").projectDir = file("modules/zon") \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 8dda3328..56ac3ce1 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -5,11 +5,54 @@ com.intellij.modules.platform + - + + + + + + + + + + + + + + + + + + \ No newline at end of file