diff --git a/CHANGELOG.md b/CHANGELOG.md index e23bd55..7125aa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,35 @@ -# TON Development Changelog +# TON Plugin for the IntelliJ IDEs Changelog + +## [2.1.0] + +### Added + +- Language selection in project template ([#148](https://github.com/ton-blockchain/intellij-ton/issues/148)) +- Empty and example project templates ([#147](https://github.com/ton-blockchain/intellij-ton/issues/147)) +- `#include` assist ([#108](https://github.com/ton-blockchain/intellij-ton/issues/108)) + +### Fixed + +- Negative method IDs are considered a syntax + error ([#157](https://github.com/ton-blockchain/intellij-ton/issues/157)) +- Non-ASCII characters in FunC identifiers ([#156](https://github.com/ton-blockchain/intellij-ton/issues/156)) +- Invalid trailing comma in return tuple type ([#155](https://github.com/ton-blockchain/intellij-ton/issues/155)) +- Unresolved reference to uninitialized + variable ([#151](https://github.com/ton-blockchain/intellij-ton/issues/151)) +- Invalid indent for multiline tuples in function signature return + type ([#150](https://github.com/ton-blockchain/intellij-ton/issues/150)) +- Auto-complete not work on `slice~` ([#149](https://github.com/ton-blockchain/intellij-ton/issues/149)) +- `method_id` completion ([#126](https://github.com/ton-blockchain/intellij-ton/issues/126)) +- Reference resolving with identifiers containing non-letter + characters ([#107](https://github.com/ton-blockchain/intellij-ton/issues/107)) +- Uppercase HEX in TL-B ([#100](https://github.com/ton-blockchain/intellij-ton/issues/100)) +- Indent in chain calls ([#38](https://github.com/ton-blockchain/intellij-ton/issues/38)) ## [2.0.5] - fixed builtin functions resolving for `load_int`, `load_uint` -## [2.0] +## [2.0.0] ### Complete plugin rework diff --git a/build.gradle.kts b/build.gradle.kts index 4b5f95f..a14876c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,6 +24,8 @@ plugins { kotlin("jvm") version "1.9.0" id("org.jetbrains.intellij") version "1.16.0" id("org.jetbrains.grammarkit") version "2022.3.2" + id("org.jetbrains.changelog") version "1.3.1" + idea } allprojects { @@ -32,6 +34,7 @@ allprojects { repositories { maven("https://cache-redirector.jetbrains.com/intellij-dependencies") mavenCentral() + maven(url = "https://jitpack.io") } tasks.withType { @@ -41,6 +44,8 @@ allprojects { dependencies { implementation(kotlin("stdlib-jdk8")) + implementation("me.alllex.parsus:parsus-jvm:0.6.1") + implementation("com.github.andreypfau.tlb:tlb-jvm:54070d9405") } } @@ -52,6 +57,7 @@ sourceSets { idea { module { + isDownloadSources = true generatedSourceDirs.add(file("src/gen")) } } @@ -91,12 +97,27 @@ val compileKotlin = tasks.named("compileKotlin") { val compileJava = tasks.named("compileJava") +changelog { + version.set(version) + path.set("${project.projectDir}/CHANGELOG.md") + header.set(provider { "[${version.get()}]" }) + itemPrefix.set("-") + keepUnreleasedSection.set(true) + unreleasedTerm.set("[Unreleased]") + groups.set(listOf("Added", "Changed", "Deprecated", "Removed", "Fixed", "Security")) +} + tasks { runIde { enabled = true } prepareSandbox { enabled = true } patchPluginXml { sinceBuild.set("231") untilBuild.set("") + changeNotes.set(provider { + changelog.run { + getLatest() + }.toHTML() + }) } buildSearchableOptions { enabled = prop("enableBuildSearchableOptions").toBoolean() @@ -108,13 +129,13 @@ fun prop(name: String, default: (() -> String?)? = null) = extra.properties[name fun generateParser(language: String, suffix: String = "", config: GenerateParserTask.() -> Unit = {}) = task("generate${language.capitalized()}Parser${suffix.capitalized()}") { - sourceFile.set(file("src/main/grammar/${language}Parser.bnf")) - targetRoot.set("src/gen") + sourceFile.set(file("src/main/grammar/${language}Parser.bnf")) + targetRoot.set("src/gen") pathToParser.set("/org/ton/intellij/${language.lowercase()}/parser/${language}Parser.java") pathToPsiRoot.set("/org/ton/intellij/${language.lowercase()}/psi") - purgeOldFiles.set(true) + purgeOldFiles.set(true) config() -} + } fun generateLexer(language: String) = task("generate${language}Lexer") { sourceFile.set(file("src/main/grammar/${language}Lexer.flex")) diff --git a/gradle.properties b/gradle.properties index 42215c8..71dc48d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.jvmargs=-Xmx4096m pluginGroup=org.ton -pluginVersion=2.0.5 +pluginVersion=2.1.0 publishChannel=release publishToken=token enableBuildSearchableOptions=false @@ -11,4 +11,4 @@ ideaVersion=231-EAP-SNAPSHOT # https://plugins.jetbrains.com/plugin/227-psiviewer/versions psiViewerPluginVersion=231-SNAPSHOT kotlin.stdlib.default.dependency=false -buildNumber=0 +buildNumber=8 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 84a0b92..a595206 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/grammar/FcParser.bnf b/src/main/grammar/FcParser.bnf new file mode 100644 index 0000000..f998024 --- /dev/null +++ b/src/main/grammar/FcParser.bnf @@ -0,0 +1,279 @@ +{ + tokens = [ + WHITESPACE = 'regexp:[ \t\f\n]' + + PLUS = '+' + MINUS = '-' + TIMES = '*' + DIV = '/' + MOD = '%' + QUEST = '?' + COLON = ':' + DOT = '.' + COMMA = ',' + SEMICOLON = ';' + LBRACE = '{' + RBRACE = '}' + LBRACK = '[' + RBRACK = ']' + LPAREN = '(' + RPAREN = ')' + EQ = '=' + UNDERSCORE = '_' + LT = '<' + GT = '>' + AND = '&' + OR = '|' + XOR = '^' + TILDE = '~' + + EQEQ = '==' + NEQ = '!=' + LEQ = '<=' + GEQ = '>=' + SPACESHIP = '<=>' + LSHIFT = '<<' + RSHIFT = '>>' + RSHIFTR = '~>>' + RSHIFTC = '^>>' + DIVR = '~/' + DIVC = '^/' + MODR = '~%' + MODC = '^%' + DIVMOD = '/%' + PLUSLET = '+=' + MINUSLET = '-=' + TIMESLET = '*=' + DIVLET = '/=' + DIVRLET = '~/=' + DIVCLET = '^/=' + MODLET = '%=' + MODRLET = '~%=' + MODCLET = '^%=' + LSHIFTLET = '<<=' + RSHIFTLET = '>>=' + RSHIFTRLET = '~>>=' + RSHIFTCLET = '^>>=' + ANDLET = '&=' + ORLET = '|=' + XORLET = '^=' + MAPSTO = '->' + + RETURN_KEYWORD = 'return' + VAR_KEYWORD = 'var' + REPEAT_KEYWORD = 'repeat' + DO_KEYWORD = 'do' + WHILE_KEYWORD = 'while' + UNTIL_KEYWORD = 'until' + TRY_KEYWORD = 'try' + CATCH_KEYWORD = 'catch' + IF_KEYWORD = 'if' + IFNOT_KEYWORD = 'ifnot' + THEN_KEYWORD = 'then' + ELSE_KEYWORD = 'else' + ELSEIF_KEYWORD = 'elseif' + ELSEIFNOT_KEYWORD = 'elseifnot' + + INT_KEYWORD = 'int' + CELL_KEYWORD = 'cell' + SLICE_KEYWORD = 'slice' + BUILDER_KEYWORD = 'builder' + CONT_KEYWORD = 'cont' + TUPLE_KEYWORD = 'tuple' + TYPE_KEYWORD = 'type' + FORALL_KEYWORD = 'forall' + TRUE_KEYWORD = 'true' + FALSE_KEYWORD = 'false' + NULL_KEYWORD = 'nil' + NIL_KEYWORD = 'Nil' + + EXTERN_KEYWORD = 'extern' + GLOBAL_KEYWORD = 'global' + ASM_KEYWORD = 'asm' + IMPURE_KEYWORD = 'impure' + INLINE_KEYWORD = 'inline' + INLINE_REF_KEYWORD = 'inline_ref' + AUTO_APPLY_KEYWORD = 'auto_apply' + METHOD_ID_KEYWORD = 'method_id' + OPERATOR_KEYWORD = 'operator' + INFIX_KEYWORD = 'infix' + INFIXL_KEYWORD = 'infixl' + INFIXR_KEYWORD = 'infixr' + CONST_KEYWORD = 'const' + + INCLUDE_MACRO = "#include" + PRAGMA_MACRO = "#pragma" + + + ESCAPE_SEQUENCE = "ESCAPE_SEQUENCE" + DANGLING_NEWLINE = "DANGLING_NEWLINE" + IDENTIFIER = "regexp:[a-zA-Z_$?:][0-9a-zA-Z_$?:]*" +// SPECIAL_IDENTIFIER = "regexp:[~.][a-zA-Z_$?:][0-9a-zA-Z_$?:]*" + INTEGER_LITERAL = 'regexp:(0|([1-9]([_0-9])*))' + ] + + extends("(.+Expr)") = Expr + extends("(.+Stmt)") = Stmt + elementType(".+BinExpr") = BinaryExpr + elementType(".+BinOp") = BinaryOp + + generateTokenAccessors=true + + parserClass='org.ton.intellij.func2.parser.FuncParser' + parserUtilClass='org.ton.intellij.func2.parser.FuncParserUtil' + + psiClassPrefix='Func' + psiImplClassSuffix='Impl' + psiPackage='org.ton.intellij.func2.psi' + psiImplPackage='org.ton.intellij.func2.psi.impl' + + elementTypeHolderClass='org.ton.intellij.func2.psi.FuncElementTypes' + tokenTypeClass='org.ton.intellij.func2.FuncTokenType' +} + +root ::= Stmt + +Stmt ::= ReturnStmt +| BlockStmt +| EmptyStmt +| RepeatStmt +| (&('if' | 'ifnot') IfStmt) +| DoStmt +| WhileStmt +| TryStmt +| ExprStmt { + name = "statement" +} +ExprStmt ::= Expr ';' +EmptyStmt ::= ';' +ReturnStmt ::= 'return' Expr ';' { + pin = 1 +} +BlockStmt ::= &'{' Block +RepeatStmt ::= 'repeat' Condition Block { + pin = 1 +} + +IfStmt ::= ('if' | 'ifnot' | 'elseif' | 'elseifnot') Condition Block (ElseBranch | (&('elseif' | 'elseifnot') IfStmt))? { + pin = 1 +} +ElseBranch ::= 'else' Block { + pin = 1 +} + +DoStmt ::= 'do' Block 'until' Condition { + pin = 1 +} +WhileStmt ::= 'while' Condition Block { + pin = 1 +} +TryStmt ::= 'try' Block CatchClause { + pin = 1 +} +CatchClause ::= 'catch' TensorOrParenExpr Block { + pin = 1 +} + +Condition ::= Expr + +Block ::= '{' BlockElement* '}' { + pin = 1 +} +private BlockElement ::= !'}' Stmt { + pin = 1 + recoverWhile = BlockElement_recover +} +private BlockElement_recover ::= !('}' | Expr_first | Stmt_first | ';') +private Stmt_first ::= ';' | 'return' | 'repeat' | 'do' | 'while' | 'try' | 'if' | 'ifnot' | '{' +private Expr_first ::= '(' | '-' | 'builder' | 'cell' | 'cont' | 'int' | 'slice' | 'tuple' | 'type' | 'var' | '~' | IDENTIFIER | SPECIAL_IDENTIFIER + +Expr ::= AssignBinExpr + | CompBinExpr + | RelCompBinExpr + | ShiftBinExpr + | NegExpr + | AddBinExpr + | OrBinExpr + | XorBinExpr + | MulBinExpr + | AndBinExpr + | InvExpr + | ModifyExpr + | ApplyExpr + | AtomExpr + +fake BinaryExpr ::= Expr BinaryOp Expr { + methods=[ + left="/Expr[0]" + right="/Expr[1]" + ] +} + +//noinspection BnfUnusedRule +fake BinaryOp ::= '=' | '+=' | '-=' | '*=' | '/=' | '~/=' | '^/=' | '%=' | '~%=' | '^%=' | '<<=' | '>>=' | '^>>=' | '~>>=' | '&=' | '|=' | '^=' +| '==' | '!=' +| '<' | '>' | '<=' | '>=' | '<=>' +| '<<' | '>>' | '~>>' | '^>>' +| '-' | '+' +| '|' +| '^' +| '*' | '/' | '%' | '/%' | '^/' | '~/' | '^%' | '~%' +| '&' + +AssignBinExpr ::= Expr AssignBinOp Expr { + rightAssociative = true +} + +CompBinExpr ::= Expr CompBinOp Expr +RelCompBinExpr ::= Expr RelCompBinOp Expr +ShiftBinExpr ::= Expr ShiftBinOp Expr +NegExpr ::= '-' Expr +AddBinExpr ::= Expr AddBinOp Expr +OrBinExpr ::= Expr OrBinOp Expr +XorBinExpr ::= Expr XorBinOp Expr +MulBinExpr ::= Expr MulBinOp Expr +AndBinExpr ::= Expr AndBinOp Expr +InvExpr ::= '~' Expr + +AssignBinOp ::= '=' | '+=' | '-=' | '*=' | '/=' | '~/=' | '^/=' | '%=' | '~%=' | '^%=' | '<<=' | '>>=' | '^>>=' | '~>>=' | '&=' | '|=' | '^=' { + name = "operator" +} + +CompBinOp ::= '==' | '!=' { name = "operator" } +RelCompBinOp ::= '<' | '>' | '<=' | '>=' | '<=>' { name = "operator" } +ShiftBinOp ::= '<<' | '>>' | '~>>' | '^>>' { name = "operator" } +AddBinOp ::= '-' | '+' { name = "operator" } +OrBinOp ::= '|' { name = "operator" } +XorBinOp ::= '^' { name = "operator" } +MulBinOp ::= '*' | '/' | '%' | '/%' | '^/' | '~/' | '^%' | '~%' { name = "operator" } +AndBinOp ::= '&' { name = "operator" } + +ModifyExpr ::= Expr &SPECIAL_IDENTIFIER Expr +ApplyExpr ::= Expr &('(' | '[' | IDENTIFIER) Expr + +private AtomExpr ::= UnitExpr | TensorOrParenExpr | LitExpr | TypeExpr | RefExpr + +UnitExpr ::= '(' ')' + +fake TensorExpr ::= '(' Expr+ ')' { + pin = 1 +} +fake ParenExpr ::= '(' Expr ')' { + pin = 1 +} + +TensorOrParenExpr ::= '(' Expr (TensorExprUpper | ')') { + pin=1 + elementType = ParenExpr +} +upper TensorExprUpper ::= ',' [Expr (',' Expr)*] ')' { + pin=1 + elementType = TensorExpr +} + +LitExpr ::= INTEGER_LITERAL | TRUE_KEYWORD | FALSE_KEYWORD | NULL_KEYWORD | NIL_KEYWORD | StringLiteral +StringLiteral ::= OPEN_QUOTE RAW_STRING_ELEMENT* CLOSING_QUOTE + +TypeExpr ::= 'var' | 'int' | 'cell' | 'slice' | 'builder' | 'cont' | 'type' | 'tuple' + +RefExpr ::= IDENTIFIER | SPECIAL_IDENTIFIER diff --git a/src/main/grammar/FuncLexer.flex b/src/main/grammar/FuncLexer.flex index e652101..9507a2b 100644 --- a/src/main/grammar/FuncLexer.flex +++ b/src/main/grammar/FuncLexer.flex @@ -3,7 +3,8 @@ package org.ton.intellij.func.lexer; import com.intellij.lexer.FlexLexer; import com.intellij.psi.tree.IElementType; import com.intellij.util.containers.Stack; -import org.ton.intellij.func.parser.FuncParserDefinition;import org.ton.intellij.func.psi.FuncElementTypes; +import org.ton.intellij.func.parser.FuncParserDefinition; +import org.ton.intellij.func.psi.FuncElementTypes; import static com.intellij.psi.TokenType.*; import static org.ton.intellij.func.psi.FuncElementTypes.*; @@ -136,17 +137,18 @@ IDENTIFIER_PART=[:digit:]|[:letter:]|IDENTIFIER_SYMBOLS LINE_DOC_COMMENT=;;;[^\n]* LINE_COMMENT=;;[^\n]* -INTEGER_LITERAL={DECIMAL_INTEGER_LITERAL}|{HEX_INTEGER_LITERAL}|{BIN_INTEGER_LITERAL} +INTEGER_LITERAL=-?({DECIMAL_INTEGER_LITERAL}|{HEX_INTEGER_LITERAL}|{BIN_INTEGER_LITERAL}) DECIMAL_INTEGER_LITERAL=(0|([1-9]({DIGIT_OR_UNDERSCORE})*)) HEX_INTEGER_LITERAL=0[Xx]({HEX_DIGIT_OR_UNDERSCORE})* BIN_INTEGER_LITERAL=0[Bb]({DIGIT_OR_UNDERSCORE})* - -PLAIN_IDENTIFIER=([a-zA-Z_$?:'~][0-9a-zA-Z_$?:'+\-\=\^><&|/%]*) -QUOTE_ESCAPED_IDENTIFIER = [.~]?`[^`\n]+` -UNDERSCORE_ESCAPED_IDENTIFIER = [.~\^]?_[^\s]+_ -OPERATOR_IDENTIFIER = _\+_|_-_|-_|_\*_|_\/_|_\~\/_|_\^\/_|_%_|_\~%_|_\^%_|_\/%_|_<<_|_>>_|_\~>>_|_\^>>_|_&_|_\|_|_\^_|\~_|\^_\+=_|\^_-=_|\^_\*=_|_==_|_\!=_ -IDENTIFIER = {OPERATOR_IDENTIFIER}|{PLAIN_IDENTIFIER}|{QUOTE_ESCAPED_IDENTIFIER} +//PLAIN_IDENTIFIER=([a-zA-Z_$?:'~][0-9a-zA-Z_$?:'+\-\=\^><&|/%]*) +//PLAIN_IDENTIFIER=[.~]?[^\s;,\(\)\[\]\{\},.~\"\+\-\*\/%]+ +//PLAIN_IDENTIFIER=^(?!{\-|;;)[.~]?[^\s;,.~\(\)\[\]\"\{\}]+ +PLAIN_IDENTIFIER=[^\s()\[\],.;~\"\{\}#]+ +QUOTE_ESCAPED_IDENTIFIER = (`[^`\n]+`)|(_[^_\n\w,]+_) +IDENTIFIER = [.~]?({QUOTE_ESCAPED_IDENTIFIER}|{PLAIN_IDENTIFIER}) +VERSION_VALUE = (=|>|>=|<|<=|\^)?\d+(\.\d+)?(\.\d+)? // ANY_ESCAPE_SEQUENCE = \\[^] THREE_QUO = (\"\"\") @@ -167,6 +169,12 @@ EOL_DOC_LINE = {LINE_WS}*!(!(";;;".*)|(";;;;".*)) \" { pushState(STRING); return OPEN_QUOTE; } + "{-" { yybegin(IN_BLOCK_COMMENT); yypushback(2); } + ";;;;" .* { return EOL_COMMENT; } + {EOL_DOC_LINE} { yybegin(IN_EOL_DOC_COMMENT); + zzPostponedMarkedPos = zzStartRead; } + ";;" .* { return EOL_COMMENT; } + "+" { return PLUS; } "-" { return MINUS; } "*" { return TIMES; } @@ -174,7 +182,6 @@ EOL_DOC_LINE = {LINE_WS}*!(!(";;;".*)|(";;;;".*)) "%" { return MOD; } "?" { return QUEST; } ":" { return COLON; } - "." { return DOT; } "," { return COMMA; } ";" { return SEMICOLON; } "{" { return LBRACE; } @@ -191,6 +198,7 @@ EOL_DOC_LINE = {LINE_WS}*!(!(";;;".*)|(";;;;".*)) "|" { return OR; } "^" { return XOR; } "~" { return TILDE; } + "#" { return SHA; } "==" { return EQEQ; } "!=" { return NEQ; } @@ -264,22 +272,10 @@ EOL_DOC_LINE = {LINE_WS}*!(!(";;;".*)|(";;;;".*)) "nil" { return NULL_KEYWORD; } "Nil" { return NIL_KEYWORD; } - "#include" { return INCLUDE_MACRO; } - "#pragma" { return PRAGMA_MACRO; } - - "{-" { yybegin(IN_BLOCK_COMMENT); yypushback(2); } - ";;;;" .* { return EOL_COMMENT; } - {EOL_DOC_LINE} { yybegin(IN_EOL_DOC_COMMENT); - zzPostponedMarkedPos = zzStartRead; } - ";;" .* { return EOL_COMMENT; } - - - {INTEGER_LITERAL} { return INTEGER_LITERAL; } {THREE_QUO} { pushState(RAW_STRING); return OPEN_QUOTE; } - {IDENTIFIER} { - return IDENTIFIER; - } + {VERSION_VALUE} { return VERSION_VALUE; } + {IDENTIFIER} { return IDENTIFIER; } } \n { return FuncElementTypes.RAW_STRING_ELEMENT; } @@ -303,31 +299,8 @@ EOL_DOC_LINE = {LINE_WS}*!(!(";;;".*)|(";;;;".*)) {REGULAR_STRING_PART} { return FuncElementTypes.RAW_STRING_ELEMENT; } -//"{--}" { -// return BLOCK_COMMENT; -//} -// -//"{--" { -// pushState(DOC_COMMENT); -// commentDepth = 0; -// commentStart = getTokenStart(); -//} -// -//"{-" { -// pushState(IN_BLOCK_COMMENT); -// commentDepth = 0; -// commentStart = getTokenStart(); -//} -// -//";;;;" .* { return EOL_COMMENT; } -//{EOL_DOC_LINE} { -// pushState(IN_EOL_DOC_COMMENT); -// zzPostponedMarkedPos = zzStartRead; -//} -//";;".* { return EOL_COMMENT; } - { - "{-" { if (zzNestedCommentLevel++ == 0) + "{-" { if (zzNestedCommentLevel++ == 0) zzPostponedMarkedPos = zzStartRead; } @@ -352,13 +325,3 @@ EOL_DOC_LINE = {LINE_WS}*!(!(";;;".*)|(";;;;".*)) } [^] { return BAD_CHARACTER; } - -//({WHITE_SPACE_CHAR})+ { return WHITE_SPACE; } -// -// -//{IDENTIFIER} { return IDENTIFIER; } -// -//[\s\S] { return BAD_CHARACTER; } -// -// . -// { return BAD_CHARACTER; } diff --git a/src/main/grammar/FuncParser.bnf b/src/main/grammar/FuncParser.bnf index 456b7ca..b78647d 100644 --- a/src/main/grammar/FuncParser.bnf +++ b/src/main/grammar/FuncParser.bnf @@ -14,6 +14,9 @@ extends(".*Expression")=Expression extends(".*Statement")=Statement + extends(".*Type")=TypeReference + elementType(".+BinExpression")=BinExpression + elementType(".+BinOp")=BinaryOp generateTokenAccessors=true @@ -25,7 +28,6 @@ MOD = '%' QUEST = '?' COLON = ':' - DOT = '.' COMMA = ',' SEMICOLON = ';' LBRACE = '{' @@ -42,6 +44,7 @@ OR = '|' XOR = '^' TILDE = '~' + SHA = '#' EQEQ = '==' NEQ = '!=' @@ -117,11 +120,9 @@ INFIXR_KEYWORD = 'infixr' CONST_KEYWORD = 'const' - INCLUDE_MACRO = "#include" - PRAGMA_MACRO = "#pragma" - ESCAPE_SEQUENCE = "ESCAPE_SEQUENCE" DANGLING_NEWLINE = "DANGLING_NEWLINE" + VERSION_VALUE = "regexp:((=|>|>=|<|<=|\^)?\d+(\.\d+)?(\.\d+)?)" IDENTIFIER = "regexp:[a-zA-Z_$?:][0-9a-zA-Z_$?:]*" // WHITESPACE = 'regexp:[\ \n\t\f]' INTEGER_LITERAL = 'regexp:(0|([1-9]([_0-9])*))' @@ -145,7 +146,7 @@ StringLiteral ::= OPEN_QUOTE RawString CLOSING_QUOTE GlobalVarList ::= 'global' << comma_separated_list GlobalVar >> ';' { pin=1 } -GlobalVar ::= Type IDENTIFIER { +GlobalVar ::= TypeReference IDENTIFIER { pin=1 mixin = "org.ton.intellij.func.psi.impl.FuncGlobalVarMixin" implements=["org.ton.intellij.func.psi.FuncNamedElement"] @@ -176,8 +177,8 @@ private ConstVar_recovery ::= !';' private UntilSemicolonRecover ::= !(';') -IncludeDefinition ::= '#include' StringLiteral ';' { - pin=2 +IncludeDefinition ::= "#include" StringLiteral ';' { + pin=1 mixin="org.ton.intellij.func.psi.impl.FuncIncludeDefinitionMixin" stubClass="org.ton.intellij.func.stub.FuncIncludeDefinitionStub" elementTypeFactory="org.ton.intellij.func.psi.FuncElementTypeFactory.stubFactory" @@ -186,27 +187,40 @@ IncludeDefinition ::= '#include' StringLiteral ';' { PragmaDefinition ::= PragmaDefinitionPart ';' PragmaKey ::= IDENTIFIER PragmaValue ::= StringLiteral | IntegerExpression -private PragmaDefinitionPart ::= '#pragma' (PragmaVersion | PragmaKeyValue) { +private PragmaDefinitionPart ::= "#pragma" (PragmaVersion | PragmaKeyValue) { pin = 1 recoverWhile = UntilSemicolonRecover } PragmaKeyValue ::= PragmaKey PragmaValue? { pin=1 } -PragmaVersion ::= ("version" | "not-version") ('^'|'>'|'<'|'='|'<='|'>=')? PragmaSemiVersion { +PragmaVersion ::= ("version" | "not-version") VERSION_VALUE { pin=1 } -PragmaSemiVersion ::= INTEGER_LITERAL ('.' INTEGER_LITERAL ('.' INTEGER_LITERAL)?)? +//PragmaSemiVersion ::= INTEGER_LITERAL ('.' INTEGER_LITERAL ('.' INTEGER_LITERAL)?)? -Function ::= TypeParameterList? Type FunctionIdentifier FunctionParameterList 'impure'? ('inline' | 'inline_ref')? MethodIdDefinition? (';' | AsmDefinition | BlockStatement) { - pin=3 - mixin="org.ton.intellij.func.psi.impl.FuncFunctionMixin" - implements=["org.ton.intellij.func.psi.FuncNamedElement"] - stubClass="org.ton.intellij.func.stub.FuncFunctionStub" - elementTypeFactory="org.ton.intellij.func.psi.FuncElementTypeFactory.stubFactory" - hooks = [ leftBinder = "ADJACENT_LINE_COMMENTS" ] +Function ::= TypeParameterList? TypeReference FunctionIdentifier FunctionParameterList FunctionAttributes FunctionBody { + pin = 3 + implements = [ + "org.ton.intellij.func.psi.FuncNamedElement" + "org.ton.intellij.func.psi.FuncInferenceContextOwner" + ] + mixin = "org.ton.intellij.func.psi.impl.FuncFunctionMixin" + stubClass = "org.ton.intellij.func.stub.FuncFunctionStub" + elementTypeFactory = "org.ton.intellij.func.psi.FuncElementTypeFactory.stubFactory" + hooks = [ + leftBinder = "ADJACENT_LINE_COMMENTS" + ] + recoverWhile = TopLevelDefinition_recover } +private FunctionAttributes ::= 'impure'? ('inline' | 'inline_ref')? MethodIdDefinition? { + recoverWhile = FunctionBody_recovery +} + +private FunctionBody ::= ';' | AsmDefinition | BlockStatement +private FunctionBody_recovery ::= !(';' | 'asm' | '{') + private TypeParameterList ::= 'forall' << comma_separated_list TypeParameter >> '->' { pin=1 } @@ -255,118 +269,160 @@ private BlockStatement_recovery ::= !('}' | IDENTIFIER | 'var' | 'return' | '{' RepeatStatement ::= 'repeat' Expression BlockStatement {pin=1} -IfStatement ::= ('if' | 'ifnot') Condition BlockStatement (Else | ElseIf)? { +IfStatement ::= ('if' | 'ifnot' | 'elseif' | 'elseifnot') Expression BlockStatement ElseBranch? { pin=1 + methods=[ + condition="/Expression" + ] } -Condition ::= Expression +ElseBranch ::= SimpleElseBranch | ConditionalElseBranch +private SimpleElseBranch ::= 'else' BlockStatement { + pin = 1 +} +private ConditionalElseBranch ::= &('elseif' | 'elseifnot') IfStatement { + pin = 1 +} -Else ::= 'else' BlockStatement { +DoStatement ::= 'do' BlockStatement 'until' Expression ';' { pin=1 + methods=[ + condition="/Expression" + ] } -ElseIf ::= ('elseif' | 'elseifnot') Condition BlockStatement (Else | ElseIf)? { +WhileStatement ::= 'while' Expression BlockStatement { pin=1 + methods=[ + condition="/Expression" + ] } - -DoStatement ::= 'do' BlockStatement 'until' Condition ';' {pin=1} -WhileStatement ::= 'while' Condition BlockStatement {pin=1} TryStatement ::= 'try' BlockStatement Catch {pin=1} Catch ::= 'catch' Expression BlockStatement { pin=1 } -Expression ::= AssignExpression +Expression ::= AssignBinExpression | TernaryExpression - | CompExpression_group - | BinaryShiftExpression_group - | UnaryExpression_group - | AddExpression_group - | MulExpression_group - | DotExpression - | ModifyExpression + | CompBinExpression + | RelCompBinExpression + | ShiftBinExpression + | UnaryMinusExpression + | AddBinExpression + | OrBinExpression + | XorBinExpression + | MulBinExpression + | AndBinExpression | InvExpression - | CallExpression - | VarExpression - | Expr100_group + | SpecialApplyExpression + | ApplyExpression + | AtomicExpression + +fake BinExpression ::= Expression BinaryOp Expression { + methods=[ + left="/Expression[0]" + right="/Expression[1]" + ] +} + +//noinspection BnfUnusedRule +fake BinaryOp ::= '=' | '+=' | '-=' | '*=' | '/=' | '~/=' | '^/=' | '%=' | '~%=' | '^%=' | '<<=' | '>>=' | '^>>=' | '~>>=' | '&=' | '|=' | '^=' +| '==' | '!=' +| '<' | '>' | '<=' | '>=' | '<=>' +| '<<' | '>>' | '~>>' | '^>>' +| '-' | '+' +| '|' +| '^' +| '*' | '/' | '%' | '/%' | '^/' | '~/' | '^%' | '~%' +| '&' -AssignExpression ::= Expression ('=' | '+=' | '-=' | '*=' | '/=' | '~/=' | '^/=' | '%=' | '~%=' | '^%=' | '<<=' | '>>=' | '^>>=' | '~>>=' | '&=' | '|=' | '^=') Expression { +TernaryExpression ::= Expression '?' Expression ':' Expression { + pin=2 + methods=[ + condition="/Expression[0]" + thenBranch="/Expression[1]" + elseBranch="/Expression[2]" + ] +} +AssignBinExpression ::= Expression AssignBinOp Expression { rightAssociative=true } - -private CompExpression_group ::= EqExpression | LtExpression | GtExpression | LeqExpression | GeqExpression | NeqExpression | SpaceshipExpression -private BinaryShiftExpression_group ::= LShiftExpression | RShiftExpression | RShiftCExpression | RShiftRExpression -private UnaryExpression_group ::= UnaryMinusExpression -private AddExpression_group ::= PlusExpression | MinusExpression | OrExpression | XorExpression -private MulExpression_group ::= MulExpression | DivExpression | ModExpression | DivModExpression | DivCExpression | DivRExpression | ModCExpression | ModRExpression | AndExpression - -VarExpression ::= (PrimitiveTypeExpression | HoleTypeExpression | TensorExpression | TupleExpression) (TensorExpression|TupleExpression|ReferenceExpression) -private Expr100_group ::= TensorExpression - | TupleExpression - | IntegerExpression - | StringExpression - | HoleTypeExpression - | PrimitiveTypeExpression - | BoolExpression - | NilExpression - | ReferenceExpression - -//private CompositeExpression ::= Expression [ TensorExpression | TupleExpression | ReferenceExpression ] - -TernaryExpression ::= Expression '?' Expression ':' Expression -EqExpression ::= Expression '==' Expression -LtExpression ::= Expression '<' Expression -GtExpression ::= Expression '>' Expression -LeqExpression ::= Expression '<=' Expression -GeqExpression ::= Expression '>=' Expression -NeqExpression ::= Expression '!=' Expression -SpaceshipExpression ::= Expression '<=>' Expression -LShiftExpression ::= Expression '<<' Expression -RShiftExpression ::= Expression '>>' Expression -RShiftCExpression ::= Expression '^>>' Expression -RShiftRExpression ::= Expression '~>>' Expression +CompBinExpression ::= Expression CompBinOp Expression +RelCompBinExpression ::= Expression RelCompBinOp Expression +ShiftBinExpression ::= Expression ShiftBinOp Expression UnaryMinusExpression ::= '-' Expression -PlusExpression ::= Expression '+' Expression -MinusExpression ::= Expression '-' Expression -OrExpression ::= Expression '|' Expression -XorExpression ::= Expression '^' Expression -MulExpression ::= Expression '*' Expression -DivExpression ::= Expression '/' Expression -ModExpression ::= Expression '%' Expression -DivModExpression ::= Expression '/%' Expression -DivCExpression ::= Expression '^/' Expression -DivRExpression ::= Expression '~/' Expression -ModCExpression ::= Expression '^%' Expression -ModRExpression ::= Expression '~%' Expression -AndExpression ::= Expression '&' Expression +AddBinExpression ::= Expression AddBinOp Expression +OrBinExpression ::= Expression OrBinOp Expression +XorBinExpression ::= Expression XorBinOp Expression +MulBinExpression ::= Expression MulBinOp Expression +AndBinExpression ::= Expression AndBinOp Expression InvExpression ::= '~' Expression -CallExpression ::= ReferenceExpression &('(' | IDENTIFIER) CallArgument -DotExpression ::= Expression '.' MethodCall -ModifyExpression ::= Expression MethodCall -MethodCall ::= ReferenceExpression CallArgument { - pin=1 +AssignBinOp ::= '=' | '+=' | '-=' | '*=' | '/=' | '~/=' | '^/=' | '%=' | '~%=' | '^%=' | '<<=' | '>>=' | '^>>=' | '~>>=' | '&=' | '|=' | '^=' { + name = "operator" } -CallArgument ::= Expr100_group +CompBinOp ::= '==' | '!=' { name = "operator" } +RelCompBinOp ::= '<' | '>' | '<=' | '>=' | '<=>' { name = "operator" } +ShiftBinOp ::= '<<' | '>>' | '~>>' | '^>>' { name = "operator" } +AddBinOp ::= '-' | '+' { name = "operator" } +OrBinOp ::= '|' { name = "operator" } +XorBinOp ::= '^' { name = "operator" } +MulBinOp ::= '*' | '/' | '%' | '/%' | '^/' | '~/' | '^%' | '~%' { name = "operator" } +AndBinOp ::= '&' { name = "operator" } -TensorExpression ::= '(' [ TensorElements ] ')' { - pin=1 - extends=Expression +SpecialApplyExpression ::= Expression &<> Expression { + methods=[ + left="/Expression[0]" + right="/Expression[1]" + ] +} +ApplyExpression ::= Expression &('(' | '[' | <>) Expression { + rightAssociative=true + methods=[ + left="/Expression[0]" + right="/Expression[1]" + ] +} + +private AtomicExpression ::= UnitExpression + | TensorOrParenExpression + | TupleExpression + | HoleTypeExpression + | PrimitiveTypeExpression + | LiteralExpression + | ReferenceExpression + +UnitExpression ::= '(' ')' + +fake TensorExpression ::= '(' <> ')' { + pin = 2 } -private TensorElements ::= Expression (!'(' ',' Expression)* { - recoverWhile=TensorElements_recovery +fake ParenExpression ::= '(' Expression ')' { + pin = 1 +} + +TensorOrParenExpression ::= '(' Expression (TensorExpression_upper | ')') { + pin = 1 + elementType = ParenExpression +} +upper TensorExpression_upper ::= TensorExpressionValue* ')' { + elementType = TensorExpression +} +private TensorExpressionValue ::= ',' Expression { + pin=1 } -private TensorElements_recovery ::= !(')'|';'|'{'|'}') TupleExpression ::= '[' [ <> ] ']' { pin=1 extends=Expression } -IntegerExpression ::= INTEGER_LITERAL -StringExpression ::= StringLiteral -BoolExpression ::= 'true' | 'false' -NilExpression ::= 'nil' | 'Nil' +private IntegerExpression ::= INTEGER_LITERAL +private BoolExpression ::= 'true' | 'false' +private NilExpression ::= 'nil' | 'Nil' + +LiteralExpression ::= INTEGER_LITERAL | BoolExpression | NilExpression | StringLiteral + ReferenceExpression ::= IDENTIFIER { mixin="org.ton.intellij.func.psi.impl.FuncReferenceExpressionMixin" implements="org.ton.intellij.func.psi.FuncNamedElement" @@ -390,40 +446,59 @@ FunctionParameter ::= TypeNamedFunctionParameter | TypeFunctionParameter | Named stubClass="org.ton.intellij.func.stub.FuncFunctionParameterStub" elementTypeFactory="org.ton.intellij.func.psi.FuncElementTypeFactory.stubFactory" } -private TypeNamedFunctionParameter ::= AtomicType IDENTIFIER +private TypeNamedFunctionParameter ::= TypeReference IDENTIFIER private TypeFunctionParameter ::= PrimitiveType private NamedFunctionParameter ::= IDENTIFIER -Type ::= MapType | AtomicType -AtomicType ::= PrimitiveType | HoleType | TypeIdentifier | TensorType | TupleType { - extends=Type +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Types +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +TypeReference ::= MapType | TypeReferenceWithoutMap +private TypeReferenceWithoutMap ::= TupleType + | UnitType + | TensorOrParenType + | HoleType + | PrimitiveType + | TypeIdentifier + +//private TypeReference_first ::= '[' | '(' | 'var' | '_' | 'int' | 'cell' | 'slice' | 'builder' | 'cont' | 'tuple' | 'type' | IDENTIFIER + +TupleType ::= '[' TypeReference TupleTypeValue* ']' { + pin = 1 } -MapType ::= AtomicType '->' Type { - pin=2 - extends=Type +private TupleTypeValue ::= ',' TypeReference { + recoverWhile=TupleTypeValueRecovery } +private TupleTypeValueRecovery ::= !(']'|IDENTIFIER|',') -HoleType ::= 'var' | '_' { - extends=AtomicType -} -PrimitiveType ::= 'int' | 'cell' | 'slice' | 'builder' | 'cont' | 'tuple' | 'type' { - extends=AtomicType +MapType ::= TypeReferenceWithoutMap '->' TypeReference { + pin = 2 + methods=[ + from="/TypeReference[0]" + to="/TypeReference[1]" + ] } + +HoleType ::= 'var' | '_' +PrimitiveType ::= 'int' | 'cell' | 'slice' | 'builder' | 'cont' | 'tuple' | 'type' TypeIdentifier ::= IDENTIFIER { - extends=AtomicType mixin="org.ton.intellij.func.psi.impl.FuncTypeIdentifierMixin" } +UnitType ::= '(' ')' -TensorType ::= '(' [ <> ] ')' { - extends=AtomicType - pin=1 +fake TensorType ::= '(' [ <> ] ')' +fake ParenType ::= '(' TypeReference ')' { + pin = 1 } -TupleType ::= TupleTypeStart ']' { - extends=AtomicType +TensorOrParenType ::= '(' !')' TypeReference (TensorType_upper | ')') { + pin = 2 + elementType = ParenType +} +upper TensorType_upper ::= ',' [ TypeReference (',' TypeReference)* ] ')' { + pin = 1 + elementType = TensorType } -TupleTypeItem ::= Type ( ','| &']' ) {pin=1} -private TupleTypeItemRecovery ::= !']' -private TupleTypeStart ::= '[' TupleTypeItem* {pin=1 recoverWhile=TupleTypeItemRecovery} private meta comma_separated_list ::= <> (',' <>)* diff --git a/src/main/grammar/TactParser.bnf b/src/main/grammar/TactParser.bnf index 75e23e9..7933f5c 100644 --- a/src/main/grammar/TactParser.bnf +++ b/src/main/grammar/TactParser.bnf @@ -2,21 +2,24 @@ parserClass='org.ton.intellij.tact.parser.TactParser' parserUtilClass='org.ton.intellij.tact.parser.TactParserUtil' + implements='org.ton.intellij.tact.psi.TactElement' + + elementTypeHolderClass='org.ton.intellij.tact.psi.TactElementTypes' + + elementTypeClass="org.ton.intellij.tact.psi.TactElementType" + tokenTypeClass="org.ton.intellij.tact.psi.TactTokenType" + psiClassPrefix='Tact' psiImplClassSuffix='Impl' psiPackage='org.ton.intellij.tact.psi' psiImplPackage='org.ton.intellij.tact.psi.impl' - implements='org.ton.intellij.tact.psi.TactElement' - - elementTypeHolderClass="org.ton.intellij.tact.psi.TactElementTypes" - tokenTypeClass="org.ton.intellij.tact.psi.TactTokenType" - elementTypeClass="org.ton.intellij.tact.psi.TactElementType" extends(".*Type")=Type extends(".*Expression")=Expression extends(".*Statement")=Statement generateTokenAccessors=true + tokens = [ LBRACE = '{' RBRACE = '}' @@ -91,7 +94,7 @@ NAME_MACRO = '@name' WHITE_SPACE = 'regexp:\s+' - INTEGER_LITERAL = 'regexp:(0[xX][0-9a-fA-F][0-9a-fA-F_]*\b)|(\b[0-9]+\b)' + INTEGER_LITERAL = 'regexp:(0[xX][0-9a-fA-F][0-9a-fA-F_]*\b)|(\b[0-9_]+\b)' STRING_LITERAL = 'regexp:(\"([^\"\r\n\\]|\\.)*\")' BOOLEAN_LITERAL = 'regexp:(true|false)' NULL_LITERAL = 'null' @@ -102,7 +105,7 @@ } File ::= RootItem_with_recover* -private RootItem ::= Import | Struct | Message | Contract | Trait | StaticFunction | Constant +private RootItem ::= Import | Struct | Message | Contract | Trait | Function | Constant private RootItem_with_recover ::= !<> RootItem { pin=1 recoverWhile=RootItem_recover @@ -111,13 +114,6 @@ private RootItem_recover ::= !Item_first private Item_first ::= 'import' | 'struct' | 'message' | '@interface' | 'contract' | 'trait' | 'get' | 'mutates' | 'extends' | 'virtual' | 'override' | 'inline' | 'abstract' | 'fun' | '@name' | 'native' | 'const' | 'init' | 'receive' | 'bounced' | 'external' -private StaticFunction ::= Function | NativeFunction -NativeFunction ::= NameAttribute FunctionAttribute* 'native' 'fun' IDENTIFIER FunctionParameters FunctionType? FunctionBody { - pin=1 -} -NameAttribute ::= '@name' FunctionId {pin=1} -FunctionId ::= '(' IDENTIFIER ')' {pin=1} - Import ::= 'import' STRING_LITERAL ';' {pin=1} // Type @@ -135,66 +131,95 @@ As ::= 'as' IDENTIFIER { } // Field -Field ::= IDENTIFIER ':' Type As? Assigment? ';' {pin=1} -Assigment ::= '=' Expression { +Field ::= IDENTIFIER ':' Type As? Assigment? ';' { + pin=1 +} +private Assigment ::= '=' Expression { pin = 1 } // Constant ConstantAttribute ::= 'virtual' | 'override' | 'abstract' Constant ::= ConstantAttribute* !'fun' 'const' IDENTIFIER ':' Type Assigment? ';' { - pin=3 + pin = 3 } // Struct -Struct ::= 'struct' IDENTIFIER StructBody {pin=1} -Message ::= 'message' MessageId? IDENTIFIER StructBody {pin=1} +Struct ::= 'struct' IDENTIFIER BlockFields {pin=1} +Message ::= 'message' MessageId? IDENTIFIER BlockFields {pin=1} MessageId ::= '(' INTEGER_LITERAL ')' { pin=1 } -StructBody ::= '{' Field* '}' {pin=1} +BlockFields ::= '{' Field* '}' {pin=1} // Contract WithClause ::= 'with' <> {pin=1} -Contract ::= ContractAttribute* 'contract' IDENTIFIER WithClause? ContractBody +Contract ::= ContractAttribute* 'contract' IDENTIFIER WithClause? ContractBody { + pin = 2 +} ContractBody ::= '{' ContractItem_with_recover* '}' {pin=1} private ContractItem_with_recover ::= !('}' | <>) ContractItem { pin=1 recoverWhile=ContractItem_recover } private ContractItem_recover ::= !('}' | Item_first | IDENTIFIER) -ContractItem ::= Field +private ContractItem ::= Field + | Constant | ContractInit | ReceiveFunction | BouncedFunction | ExternalFunction | Function - | Constant ContractAttribute ::= '@interface' StringId {pin=1} -ContractInit ::= 'init' FunctionParameters Block {pin=1} +ContractInit ::= 'init' FunctionParameters Block { + pin=1 +} // Trait Trait ::= ContractAttribute* 'trait' IDENTIFIER WithClause? TraitBody -TraitBody ::= '{' TraitItem* '}' -TraitItem ::= Field +TraitBody ::= '{' TraitItem* '}' { + pin=1 +} +private TraitItem ::= Field + | Constant | ReceiveFunction | BouncedFunction | ExternalFunction | Function - | Constant // Function FunctionAttribute ::= 'get' | 'mutates' | 'extends' | 'virtual' | 'override' | 'inline' | 'abstract' -Function ::= FunctionAttribute* 'fun' IDENTIFIER FunctionParameters FunctionType? FunctionBody +Function ::= (NativeFunctionHead | FunctionAttribute*) 'fun' IDENTIFIER FunctionParameters FunctionType? FunctionBody { + pin = 3 + implements = [ + "org.ton.intellij.tact.psi.TactNameIdentifierOwner" + ] +// extends = "org.ton.intellij.tact.psi.TactStubbedElementImpl" + mixin = "org.ton.intellij.tact.psi.TactFunctionImplMixin" + stubClass = "org.ton.intellij.tact.stub.TactFunctionStub" + elementTypeFactory = "org.ton.intellij.tact.stub.TactStubKt.factory" +} +private NativeFunctionHead ::= NameAttribute FunctionAttribute* 'native' { + pin=1 +} +NameAttribute ::= '@name' FunctionId +FunctionId ::= '(' IDENTIFIER ')' {pin=2} + FunctionParameters ::= '(' [ <> ] ')' {pin=1} FunctionParameter ::= IDENTIFIER ':' Type {pin=1} private FunctionType ::= ':' Type { pin = 1 } -FunctionBody ::= ';' | Block +private FunctionBody ::= ';' | Block -ReceiveFunction ::= 'receive' (StringId|FunctionParameters) Block {pin=1} -BouncedFunction ::= 'bounced' FunctionParameters Block {pin=1} -ExternalFunction ::= 'external' (StringId|FunctionParameters) Block {pin=1} +ReceiveFunction ::= 'receive' (StringId|FunctionParameters) Block { + pin=1 +} +BouncedFunction ::= 'bounced' FunctionParameters Block { + pin=1 +} +ExternalFunction ::= 'external' (StringId|FunctionParameters) Block { + pin=1 +} StringId ::= '(' STRING_LITERAL ')' {pin=2} // Statements @@ -206,7 +231,10 @@ Statement ::= LetStatement | ConditionStatement | WhileStatement | RepeatStatement - | UntilStatement + | UntilStatement { +// extends = 'org.ton.intellij.tact.psi.TactStubbedElementImpl' +// stubClass = "com.intellij.psi.stubs.StubBase" +} private Statement_first ::= 'let' | '{' | 'return' | Expression_first | 'if' | 'while' | 'repeat' | 'do' Block ::= '{' BlockItem* '}' {pin=1} @@ -223,7 +251,7 @@ LetStatement ::= 'let' IDENTIFIER ':' Type '=' Expression ';' { BlockStatement ::= Block ReturnStatement ::= 'return' Expression ';' { pin = 1 } ExpressionStatement ::= Expression ';' -AssignStatement ::= LValue '=' Expression ';' +AssignStatement ::= LValue ('='|'+='|'-='|'*='|'/='|'%=') Expression ';' ConditionStatement ::= 'if' Condition Block ConditionElse? {pin=1} ConditionElse ::= 'else' (Block | ConditionStatement) {pin=1} WhileStatement ::= 'while' Condition Block {pin=1} @@ -238,7 +266,8 @@ private LValue ::= IDENTIFIER '.' LValue | IDENTIFIER // Expressions -Expression ::= OrExpression +Expression ::= TernaryExpression + | OrExpression | AndExpression | CompareExpression | BinaryExpression @@ -256,9 +285,20 @@ Expression ::= OrExpression | ReferenceExpression | NullExpression | IntOfExpression - | StringExpression + | StringExpression { +// extends = 'org.ton.intellij.tact.psi.TactStubbedElementImpl' +// stubClass = "com.intellij.psi.stubs.StubBase" +} private Expression_first ::= '-' | '+' | '!' | IDENTIFIER | '(' | INTEGER_LITERAL | BOOLEAN_LITERAL | 'null' | 'intOf' | STRING_LITERAL +TernaryExpression ::= Expression '?' Expression ':' Expression { + pin=2 + methods=[ + condition="/Expression[0]" + thenBranch="/Expression[1]" + elseBranch="/Expression[2]" + ] +} OrExpression ::= Expression '||' Expression AndExpression ::= Expression '&&' Expression CompareExpression ::= Expression ('!='|'=='|'>'|'>='|'<'|'<=') Expression @@ -292,5 +332,4 @@ NullExpression ::= 'null' IntOfExpression ::= 'intOf' IDENTIFIER '(' [<>] ')' {pin=1} StringExpression ::= STRING_LITERAL - private meta comma_separated_list ::= <> ( ',' <> )* diff --git a/src/main/grammar/TlbLexer.flex b/src/main/grammar/TlbLexer.flex index eb78236..a92448d 100644 --- a/src/main/grammar/TlbLexer.flex +++ b/src/main/grammar/TlbLexer.flex @@ -75,7 +75,7 @@ import static org.ton.intellij.tlb.psi.TlbTypes.*; EOL=\R WHITE_SPACE_CHAR = [\ \n\t\f\r] -HEX_TAG=#([0-9a-f]+_?|_) +HEX_TAG=#([0-9a-fA-F]+_?|_) BINARY_TAG=\$([01]*|_) LINE_DOCUMENTATION = "/""/""/"[^\n]* @@ -83,6 +83,7 @@ LINE_COMMENT = "/""/"[^\n]* NUMBER=[0-9]+ IDENTIFIER=[a-zA-Z_][0-9a-zA-Z0-9_]* +PREDIFINED_TYPE=u?int[0-9]+|Cell %xstate BLOCK_COMMENT_STATE, DOC_COMMENT_STATE @@ -132,6 +133,7 @@ IDENTIFIER=[a-zA-Z_][0-9a-zA-Z0-9_]* {LINE_COMMENT} { return LINE_COMMENT; } {NUMBER} { return NUMBER; } +{PREDIFINED_TYPE} { return PREDIFINED_TYPE; } {IDENTIFIER} { return IDENTIFIER; } "+" { return PLUS; } diff --git a/src/main/grammar/TlbParser.bnf b/src/main/grammar/TlbParser.bnf index b8b0cc6..bdaa33e 100644 --- a/src/main/grammar/TlbParser.bnf +++ b/src/main/grammar/TlbParser.bnf @@ -13,7 +13,7 @@ tokenTypeClass="org.ton.intellij.tlb.psi.TlbTokenType" tokens = [ - HEX_TAG='regexp:#([0-9a-f]+_?|_)' + HEX_TAG='regexp:#([0-9a-fA-F]+_?|_)' BINARY_TAG='regexp:\$[01]*_?' PLUS='+' @@ -47,6 +47,7 @@ WHITE_SPACE='regexp:\s+' COMMENT='regexp:(//.*)' NUMBER='regexp:[0-9]+' + PREDIFINED_TYPE='PREDIFINED_TYPE' IDENTIFIER='regexp:[a-zA-Z_][0-9a-zA-Z0-9_]*' ] } @@ -99,6 +100,6 @@ named_ref ::= IDENTIFIER { mixin = "org.ton.intellij.tlb.psi.TlbNamedRefMixin" } private paren_expression ::= "(" expression ')' {pin=1} -builtin_type ::= '#<' | '#<=' | '##' | '#' +builtin_type ::= '#<' | '#<=' | '##' | '#' | PREDIFINED_TYPE //noinspection BnfUnusedRule private unused_in_bnf ::= LINE_COMMENT | BLOCK_COMMENT | LINE_DOCUMENTATION | BLOCK_DOCUMENTATION diff --git a/src/main/kotlin/org/ton/intellij/blueprint/BlueprintIcons.kt b/src/main/kotlin/org/ton/intellij/blueprint/BlueprintIcons.kt index 89da61b..9d522fe 100644 --- a/src/main/kotlin/org/ton/intellij/blueprint/BlueprintIcons.kt +++ b/src/main/kotlin/org/ton/intellij/blueprint/BlueprintIcons.kt @@ -4,4 +4,5 @@ import com.intellij.openapi.util.IconLoader object BlueprintIcons { val BLUEPRINT = IconLoader.getIcon("/icons/blueprint.svg", BlueprintIcons::class.java) + val TON_SYMBOL = IconLoader.getIcon("/icons/ton_symbol.svg", BlueprintIcons::class.java) } diff --git a/src/main/kotlin/org/ton/intellij/blueprint/cli/TonBlueprintProjectGenerator.kt b/src/main/kotlin/org/ton/intellij/blueprint/cli/TonBlueprintProjectGenerator.kt index fbc0b59..82901de 100644 --- a/src/main/kotlin/org/ton/intellij/blueprint/cli/TonBlueprintProjectGenerator.kt +++ b/src/main/kotlin/org/ton/intellij/blueprint/cli/TonBlueprintProjectGenerator.kt @@ -1,25 +1,26 @@ package org.ton.intellij.blueprint.cli import com.intellij.execution.filters.Filter -import com.intellij.execution.process.ProcessHandler +import com.intellij.ide.util.projectWizard.MultiWebTemplateNewProjectWizard import com.intellij.ide.util.projectWizard.SettingsStep -import com.intellij.ide.util.projectWizard.WebTemplateNewProjectWizard -import com.intellij.ide.util.projectWizard.WizardContext +import com.intellij.ide.util.projectWizard.WebTemplateProjectWizardStep +import com.intellij.ide.wizard.AbstractNewProjectWizardMultiStepBase import com.intellij.ide.wizard.GeneratorNewProjectWizardBuilderAdapter +import com.intellij.ide.wizard.NewProjectWizardBaseStep +import com.intellij.ide.wizard.NewProjectWizardStep import com.intellij.javascript.nodejs.packages.NodePackageUtil -import com.intellij.lang.javascript.boilerplate.JavaScriptNewTemplatesFactoryBase import com.intellij.lang.javascript.boilerplate.NpmPackageProjectGenerator import com.intellij.lang.javascript.boilerplate.NpxPackageDescriptor import com.intellij.lang.javascript.buildTools.npm.PackageJsonUtil import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.DataContext -import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ContentEntry +import com.intellij.openapi.util.Key import com.intellij.openapi.vfs.VirtualFile import com.intellij.platform.ProjectGeneratorPeer -import com.intellij.platform.ProjectTemplate -import com.intellij.ui.components.JBList +import com.intellij.ui.UIBundle +import com.intellij.ui.components.JBCheckBox import com.intellij.util.PathUtil import org.ton.intellij.blueprint.BlueprintIcons import org.ton.intellij.blueprint.action.InstallBlueprintAction @@ -27,12 +28,14 @@ import java.io.File import javax.swing.Icon import javax.swing.JPanel -class TonBlueprintProjectGenerator : NpmPackageProjectGenerator() { +class TonBlueprintProjectGenerator( + val projectType: ProjectType +) : NpmPackageProjectGenerator() { - override fun getId(): String = "ton-blueprint" + override fun getId(): String = "ton-blueprint-${projectType.id}" @Suppress("DialogTitleCapitalization") - override fun getName(): String = "TON" + override fun getName(): String = projectType.displayName override fun getDescription(): String = "Create a new TON Blueprint project using CLI" @@ -62,10 +65,18 @@ class TonBlueprintProjectGenerator : NpmPackageProjectGenerator() { override fun generatorArgs(project: Project?, dir: VirtualFile, settings: Settings): Array { val workingDir = if (generateInTemp()) dir.name else "." val packageName = settings.myPackage.name + val addSampleCode = settings.getUserData(ADD_SAMPLE_CODE) ?: false if (packageName.contains(CREATE_TON_PACKAGE_NAME)) { - return arrayOf(workingDir, "--type", "func-empty", "--contractName", "Main") + return arrayOf(workingDir, "--type", projectType.argument(addSampleCode), "--contractName", "Main") } - return arrayOf(CREATE_COMMAND, "--type", "func-empty", "--contractName", "Main", workingDir) + return arrayOf( + CREATE_COMMAND, + "--type", + projectType.argument(addSampleCode), + "--contractName", + "Main", + workingDir + ) } override fun postInstall(project: Project, baseDir: VirtualFile, workingDir: File?): Runnable = Runnable { @@ -80,51 +91,91 @@ class TonBlueprintProjectGenerator : NpmPackageProjectGenerator() { } override fun createPeer(): ProjectGeneratorPeer { - val default = JBList("tact", "func") - return object : NpmPackageGeneratorPeer() { + private lateinit var sampleCode: JBCheckBox + private var newCamelCaseCodeStyle: JBCheckBox? = null + override fun createPanel(): JPanel { - return super.createPanel() + return super.createPanel().apply { + sampleCode = JBCheckBox("Add sample code").also { + add(it) + } + if (projectType == ProjectType.FUNC) { + newCamelCaseCodeStyle = JBCheckBox("Use new camelCase code style").also { + add(it) + } + } + } } override fun buildUI(settingsStep: SettingsStep) { super.buildUI(settingsStep) + settingsStep.addSettingsComponent(sampleCode) + newCamelCaseCodeStyle?.let { settingsStep.addSettingsComponent(it) } } - } - } - override fun onProcessHandlerCreated(processHandler: ProcessHandler) { -// processHandler.addProcessListener(object : ProcessAdapter() { -// override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { -// if (event.text.contains("Project name")) { -// event.processHandler.removeProcessListener(this) -// val processInput = event.processHandler.processInput -// if (processInput != null) { -// try { -// processInput.write() -// } catch (e: IOException) { -// LOG.warn("Failed to write project name to the console.", e) -// } -// } -// } -// } -// }) + override fun getSettings(): Settings { + return super.getSettings().apply { + putUserData(ADD_SAMPLE_CODE, sampleCode.isSelected) + putUserData(ADD_NEW_CAMEL_CASE_CODE_STYLE, newCamelCaseCodeStyle?.isSelected ?: false) + } + } + } } - override fun generateInTemp(): Boolean = true + override fun generateInTemp(): Boolean = false companion object { - private val LOG = Logger.getInstance(TonBlueprintProjectGenerator::class.java) + private val ADD_SAMPLE_CODE = Key.create("create.ton.blueprint.add_sample_code") + private val ADD_NEW_CAMEL_CASE_CODE_STYLE = + Key.create("create.ton.blueprint.func.new_camel_case_code_style") const val CREATE_TON_PACKAGE_NAME = "create-ton" const val CREATE_COMMAND = "create" } + + enum class ProjectType( + val id: String, + val displayName: String, + ) { + FUNC("func", "FunC"), + TACT("tact", "Tact"); + + fun argument(addSampleCode: Boolean): String { + return if (addSampleCode) { + "$id-counter" + } else { + "$id-empty" + } + } + } } -class TonBlueprintProjectModuleBuilder : - GeneratorNewProjectWizardBuilderAdapter(WebTemplateNewProjectWizard(TonBlueprintProjectGenerator())) +class TonBlueprintProjectWizard : MultiWebTemplateNewProjectWizard( + listOf( + TonBlueprintProjectGenerator(TonBlueprintProjectGenerator.ProjectType.FUNC), + TonBlueprintProjectGenerator(TonBlueprintProjectGenerator.ProjectType.TACT) + ) +) { + override val icon: Icon + get() = BlueprintIcons.TON_SYMBOL + override val id: String + get() = "ton-blueprint" + override val name: String + get() = "TON" + + override fun createTemplateStep(parent: NewProjectWizardBaseStep): NewProjectWizardStep { + return object : AbstractNewProjectWizardMultiStepBase(parent) { + override val label: String + get() = UIBundle.message("label.project.wizard.new.project.language") + + override fun initSteps() = templates.associateBy({ it.name }, { WebTemplateProjectWizardStep(parent, it) }) + } + } +} -class TonBlueprintProjectTemplateFactory : JavaScriptNewTemplatesFactoryBase() { - override fun createTemplates(p0: WizardContext?): Array = - arrayOf(TonBlueprintProjectGenerator()) +class TonBlueprintProjectModuleBuilder : GeneratorNewProjectWizardBuilderAdapter(TonBlueprintProjectWizard()) { + override fun getWeight(): Int { + return 10 + } } diff --git a/src/main/kotlin/org/ton/intellij/func/FuncFileElementType.kt b/src/main/kotlin/org/ton/intellij/func/FuncFileElementType.kt index ba5de00..973f7a7 100644 --- a/src/main/kotlin/org/ton/intellij/func/FuncFileElementType.kt +++ b/src/main/kotlin/org/ton/intellij/func/FuncFileElementType.kt @@ -7,7 +7,7 @@ import com.intellij.psi.tree.IStubFileElementType import org.ton.intellij.func.psi.FuncFile import org.ton.intellij.func.stub.FuncFileStub -private const val STUB_VERSION = 3 and 0xFFFFFFF +private const val STUB_VERSION = 5 and 0xFFFFFFF object FuncFileElementType : IStubFileElementType("FUNC_FILE", FuncLanguage) { override fun getStubVersion(): Int = STUB_VERSION diff --git a/src/main/kotlin/org/ton/intellij/func/doc/FuncDocCommentElementType.kt b/src/main/kotlin/org/ton/intellij/func/doc/FuncDocCommentElementType.kt index b3c39b8..eca9252 100644 --- a/src/main/kotlin/org/ton/intellij/func/doc/FuncDocCommentElementType.kt +++ b/src/main/kotlin/org/ton/intellij/func/doc/FuncDocCommentElementType.kt @@ -11,6 +11,10 @@ import org.intellij.markdown.IElementType import org.intellij.markdown.MarkdownElementTypes import org.intellij.markdown.MarkdownTokenTypes import org.ton.intellij.func.FuncLanguage +import org.ton.intellij.func.doc.psi.FuncDocElementTypes.DOC_CODE_BLOCK +import org.ton.intellij.func.doc.psi.FuncDocElementTypes.DOC_CODE_FENCE +import org.ton.intellij.func.doc.psi.FuncDocElementTypes.DOC_CODE_FENCE_LANG +import org.ton.intellij.func.doc.psi.FuncDocElementTypes.DOC_CODE_FENCE_START_END import org.ton.intellij.func.doc.psi.FuncDocElementTypes.DOC_CODE_SPAN import org.ton.intellij.func.doc.psi.FuncDocElementTypes.DOC_FULL_REFERENCE_LINK import org.ton.intellij.func.doc.psi.FuncDocElementTypes.DOC_GAP @@ -32,6 +36,11 @@ class FuncDocCommentElementType(debugName: String) : ILazyParseableElementType(d val factory = object : MarkdownPsiFactory { override fun buildComposite(markdownElementType: IElementType): TreeElement? = when (markdownElementType) { MarkdownElementTypes.CODE_SPAN -> DOC_CODE_SPAN.createCompositeNode() + MarkdownElementTypes.CODE_FENCE -> DOC_CODE_FENCE.createCompositeNode() + MarkdownElementTypes.CODE_BLOCK -> DOC_CODE_BLOCK.createCompositeNode() + MarkdownTokenTypes.FENCE_LANG -> DOC_CODE_FENCE_LANG.createCompositeNode() + MarkdownTokenTypes.CODE_FENCE_START, + MarkdownTokenTypes.CODE_FENCE_END -> DOC_CODE_FENCE_START_END.createCompositeNode() MarkdownElementTypes.INLINE_LINK -> DOC_INLINE_LINK.createCompositeNode() MarkdownElementTypes.SHORT_REFERENCE_LINK -> DOC_SHORT_REFERENCE_LINK.createCompositeNode() @@ -47,7 +56,6 @@ class FuncDocCommentElementType(debugName: String) : ILazyParseableElementType(d MarkdownTokenTypes.LPAREN -> LeafPsiElement(FuncElementTypes.LPAREN, "(") MarkdownTokenTypes.RPAREN -> LeafPsiElement(FuncElementTypes.LPAREN, ")") else -> { -// println("build markdown: $markdownElementType") null } } diff --git a/src/main/kotlin/org/ton/intellij/func/doc/FuncDocRender.kt b/src/main/kotlin/org/ton/intellij/func/doc/FuncDocRender.kt new file mode 100644 index 0000000..4564e98 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/doc/FuncDocRender.kt @@ -0,0 +1,23 @@ +package org.ton.intellij.func.doc + +import org.intellij.markdown.parser.MarkdownParser +import org.ton.intellij.func.doc.psi.FuncDocComment +import org.ton.intellij.func.ide.quickdoc.MarkdownNode +import org.ton.intellij.util.DocLine +import org.ton.intellij.util.content +import org.ton.intellij.util.removeEolDecoration + +/** + * Render the markdown content of a [FuncDocComment] to HTML. + * @sample removeEolDecoration + */ +fun FuncDocComment.renderHtml(): String { + val rawText = DocLine.splitLines(text).removeEolDecoration(";;;").content().joinToString("\n") + + val markdownRoot = + MarkdownParser(FuncDocMarkdownFlavourDescriptor()).buildMarkdownTreeFromString( + rawText + ) + val markdownNode = MarkdownNode(markdownRoot, null, rawText, this) + return markdownNode.toHtml() +} diff --git a/src/main/kotlin/org/ton/intellij/func/doc/FuncDocumentationProvider.kt b/src/main/kotlin/org/ton/intellij/func/doc/FuncDocumentationProvider.kt index 4374b73..0a0022c 100644 --- a/src/main/kotlin/org/ton/intellij/func/doc/FuncDocumentationProvider.kt +++ b/src/main/kotlin/org/ton/intellij/func/doc/FuncDocumentationProvider.kt @@ -60,8 +60,7 @@ class FuncDocumentationProvider : AbstractDocumentationProvider() { } override fun generateRenderedDoc(comment: PsiDocCommentBase): String? { - if (comment !is FuncDocComment) return null - return MarkdownDocAstBuilder.renderHtml(comment.node.chars, ";;;", FuncDocMarkdownFlavourDescriptor()) + return (comment as? FuncDocComment)?.renderHtml() } private fun getComments(element: PsiElement?): PsiComment? { @@ -69,7 +68,8 @@ class FuncDocumentationProvider : AbstractDocumentationProvider() { } private fun getCommentText(comment: PsiComment): String { - return MarkdownDocAstBuilder.renderHtml(comment.node.chars, ";;;", FuncDocMarkdownFlavourDescriptor()) + return (comment as? FuncDocComment)?.renderHtml() + ?: MarkdownDocAstBuilder.renderHtml(comment.node.chars, ";;;", FuncDocMarkdownFlavourDescriptor()) } fun renderElement(element: PsiElement?, context: PsiElement?): String? { @@ -99,7 +99,7 @@ class FuncDocumentationProvider : AbstractDocumentationProvider() { append("->") append(NBSP) } - renderType(function.type) + renderType(function.typeReference) append(NBSP) if (function.isMutable) { append("~") @@ -121,7 +121,7 @@ class FuncDocumentationProvider : AbstractDocumentationProvider() { private fun StringBuilder.renderFunctionParameter( param: FuncFunctionParameter, ) { - val type = param.atomicType + val type = param.typeReference if (type != null) { renderType(type) } @@ -141,7 +141,7 @@ class FuncDocumentationProvider : AbstractDocumentationProvider() { } private fun StringBuilder.renderType( - type: FuncType, + type: FuncTypeReference, ) { when (type) { is FuncTypeIdentifier -> @@ -152,9 +152,9 @@ class FuncDocumentationProvider : AbstractDocumentationProvider() { is FuncTupleType -> { appendStyledSpan(FuncColor.BRACKETS.attributes, "[") - type.tupleTypeItemList.joinTo(this) { + type.typeReferenceList.joinTo(this) { buildString { - renderType(it.type) + renderType(it) } } appendStyledSpan(FuncColor.BRACKETS.attributes, "]") @@ -163,7 +163,7 @@ class FuncDocumentationProvider : AbstractDocumentationProvider() { is FuncTensorType -> { appendStyledSpan(FuncColor.PARENTHESES.attributes, "(") - type.typeList.joinTo(this) { + type.typeReferenceList.joinTo(this) { buildString { renderType(it) } @@ -179,7 +179,36 @@ class FuncDocumentationProvider : AbstractDocumentationProvider() { } } - else -> append(type) + is FuncParenType -> { + appendStyledSpan(FuncColor.PARENTHESES.attributes, "(") + type.typeReference?.let { + renderType(it) + } + appendStyledSpan(FuncColor.PARENTHESES.attributes, ")") + } + + is FuncMapType -> { + type.from?.let { + renderType(it) + } + append(" ") + appendStyledSpan(FuncColor.OPERATION_SIGN.attributes, "->") + append(" ") + type.to?.let { + renderType(it) + } + } + + is FuncUnitType -> appendStyledSpan(FuncColor.PARENTHESES.attributes, "()") + + else -> { + val typeIdentifier = type.typeIdentifier?.text + if (typeIdentifier != null) { + appendStyledSpan(FuncColor.TYPE_PARAMETER.attributes, typeIdentifier) + } else { + append(type) + } + } } } diff --git a/src/main/kotlin/org/ton/intellij/func/doc/psi/FuncDocElementTypes.kt b/src/main/kotlin/org/ton/intellij/func/doc/psi/FuncDocElementTypes.kt index 0804feb..065fa61 100644 --- a/src/main/kotlin/org/ton/intellij/func/doc/psi/FuncDocElementTypes.kt +++ b/src/main/kotlin/org/ton/intellij/func/doc/psi/FuncDocElementTypes.kt @@ -7,6 +7,8 @@ object FuncDocElementTypes { val DOC_TEXT = FuncDocTokenType("") val DOC_CODE_SPAN = FuncDocCompositeTokenType("", ::FuncDocCodeSpanImpl) + val DOC_CODE_FENCE = FuncDocCompositeTokenType("", ::FuncDocCodeFenceImpl) + val DOC_CODE_BLOCK = FuncDocCompositeTokenType("", ::FuncDocCodeBlockImpl) val DOC_INLINE_LINK = FuncDocCompositeTokenType("", ::FuncDocInlineLinkImpl) val DOC_SHORT_REFERENCE_LINK = @@ -18,4 +20,8 @@ object FuncDocElementTypes { val DOC_LINK_LABEL = FuncDocCompositeTokenType("", ::FuncDocLinkLabelImpl) val DOC_LINK_TITLE = FuncDocCompositeTokenType("", ::FuncDocLinkTitleImpl) val DOC_LINK_DESTINATION = FuncDocCompositeTokenType("", ::FuncDocLinkDestinationImpl) + + val DOC_CODE_FENCE_START_END = + FuncDocCompositeTokenType("", ::FuncDocCodeFenceStartEndImpl) + val DOC_CODE_FENCE_LANG = FuncDocCompositeTokenType("", ::FuncDocCodeFenceLangImpl) } diff --git a/src/main/kotlin/org/ton/intellij/func/doc/psi/impl/FuncDocCommentImpl.kt b/src/main/kotlin/org/ton/intellij/func/doc/psi/impl/FuncDocCommentImpl.kt index 5b8cd02..a93ffba 100644 --- a/src/main/kotlin/org/ton/intellij/func/doc/psi/impl/FuncDocCommentImpl.kt +++ b/src/main/kotlin/org/ton/intellij/func/doc/psi/impl/FuncDocCommentImpl.kt @@ -18,7 +18,6 @@ class FuncDocCommentImpl( override fun getOwner(): FuncDocOwner? = PsiTreeUtil.getParentOfType(this, FuncDocOwner::class.java, true) - override fun getReferences(): Array = ReferenceProvidersRegistry.getReferencesFromProviders(this) override fun accept(visitor: PsiElementVisitor) { diff --git a/src/main/kotlin/org/ton/intellij/func/doc/psi/impl/Psi.kt b/src/main/kotlin/org/ton/intellij/func/doc/psi/impl/Psi.kt index 4a4c32b..1f9651f 100644 --- a/src/main/kotlin/org/ton/intellij/func/doc/psi/impl/Psi.kt +++ b/src/main/kotlin/org/ton/intellij/func/doc/psi/impl/Psi.kt @@ -1,11 +1,15 @@ package org.ton.intellij.func.doc.psi.impl +import com.intellij.psi.LiteralTextEscaper +import com.intellij.psi.PsiLanguageInjectionHost import com.intellij.psi.impl.source.tree.AstBufferUtil import com.intellij.psi.impl.source.tree.CompositePsiElement import com.intellij.psi.impl.source.tree.LeafPsiElement import com.intellij.psi.tree.IElementType import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.childrenOfType import org.ton.intellij.func.doc.psi.* +import org.ton.intellij.util.SimpleMultiLineTextEscaper import org.ton.intellij.util.childOfType abstract class FuncDocElementImpl(type: IElementType) : CompositePsiElement(type), FuncDocElement { @@ -64,3 +68,26 @@ class FuncDocLinkDestinationImpl(type: IElementType) : FuncDocElementImpl(type), class FuncDocCodeSpanImpl(type: IElementType) : FuncDocElementImpl(type), FuncDocCodeSpan +class FuncDocCodeBlockImpl(type: IElementType) : FuncDocElementImpl(type), FuncDocCodeBlock +class FuncDocCodeFenceStartEndImpl(type: IElementType) : FuncDocElementImpl(type), FuncDocCodeFenceStartEnd +class FuncDocCodeFenceLangImpl(type: IElementType) : FuncDocElementImpl(type), FuncDocCodeFenceLang + +class FuncDocCodeFenceImpl(type: IElementType) : FuncDocElementImpl(type), FuncDocCodeFence { + + override val start: FuncDocCodeFenceStartEnd + get() = notNullChild(childOfType()) + override val lang: FuncDocCodeFenceLang? + get() = childOfType() + override val end: FuncDocCodeFenceStartEnd? + get() = childrenOfType().getOrNull(1) + + override fun isValidHost(): Boolean = true + + override fun createLiteralTextEscaper(): LiteralTextEscaper { + return SimpleMultiLineTextEscaper(this) + } + + override fun updateText(text: String): PsiLanguageInjectionHost { + return this + } +} diff --git a/src/main/kotlin/org/ton/intellij/func/highlighting/FuncAnnotator.kt b/src/main/kotlin/org/ton/intellij/func/highlighting/FuncAnnotator.kt index 0f2c148..94f0047 100644 --- a/src/main/kotlin/org/ton/intellij/func/highlighting/FuncAnnotator.kt +++ b/src/main/kotlin/org/ton/intellij/func/highlighting/FuncAnnotator.kt @@ -4,8 +4,8 @@ import com.intellij.lang.annotation.AnnotationHolder import com.intellij.lang.annotation.Annotator import com.intellij.lang.annotation.HighlightSeverity import com.intellij.openapi.editor.colors.TextAttributesKey +import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiElement -import com.intellij.psi.util.PsiTreeUtil import org.ton.intellij.func.psi.* class FuncAnnotator : Annotator { @@ -15,11 +15,18 @@ class FuncAnnotator : Annotator { highlight(element.identifier, holder, FuncColor.TYPE_PARAMETER.textAttributesKey) return } - is FuncTypeIdentifier -> { highlight(element.identifier, holder, FuncColor.TYPE_PARAMETER.textAttributesKey) return } + is FuncIncludeDefinition, is FuncPragmaDefinition -> { + val sha = element.node.firstChildNode + val macroName = sha.treeNext + + + highlight(macroName.textRange, holder, FuncColor.MACRO.textAttributesKey) + return + } is FuncGlobalVar -> { highlight( @@ -49,44 +56,28 @@ class FuncAnnotator : Annotator { } is FuncReferenceExpression -> { - when (element.node.treeParent.elementType) { - FuncElementTypes.CALL_EXPRESSION -> { - highlight( - element.identifier, - holder, - FuncColor.FUNCTION_STATIC.textAttributesKey - ) - } - - FuncElementTypes.METHOD_CALL -> { - highlight( - element.identifier, - holder, - FuncColor.FUNCTION_CALL.textAttributesKey - ) - } - - else -> { - val resolved = element.reference?.resolve() ?: element - var color: TextAttributesKey? = null - PsiTreeUtil.treeWalkUp(resolved, null) { scope, _ -> - color = when (scope) { - is FuncBlockStatement -> FuncColor.LOCAL_VARIABLE.textAttributesKey - is FuncConstVar -> FuncColor.CONSTANT.textAttributesKey - is FuncGlobalVar -> FuncColor.GLOBAL_VARIABLE.textAttributesKey - is FuncFunctionParameter -> FuncColor.PARAMETER.textAttributesKey - else -> null - } - color == null - } - color?.let { - highlight( - element.identifier, - holder, - it - ) + val reference = element.reference + if (reference == null) { + highlight( + element.identifier, + holder, + FuncColor.LOCAL_VARIABLE.textAttributesKey + ) + } else { + val resolved = reference.resolve() ?: return + val color = when (resolved) { + is FuncFunction -> FuncColor.FUNCTION_CALL + is FuncGlobalVar -> FuncColor.GLOBAL_VARIABLE + is FuncConstVar -> FuncColor.CONSTANT + is FuncFunctionParameter -> FuncColor.PARAMETER + is FuncReferenceExpression -> { + if (resolved.reference != null) return + FuncColor.LOCAL_VARIABLE } + + else -> return } + highlight(element.identifier, holder, color.textAttributesKey) } return } @@ -101,9 +92,12 @@ class FuncAnnotator : Annotator { } } - private fun highlight(element: PsiElement, holder: AnnotationHolder, key: TextAttributesKey) { + private fun highlight(element: PsiElement, holder: AnnotationHolder, key: TextAttributesKey) = + highlight(element.textRange, holder, key) + + private fun highlight(textRange: TextRange, holder: AnnotationHolder, key: TextAttributesKey) { holder.newSilentAnnotation(HighlightSeverity.INFORMATION) - .range(element.textRange) + .range(textRange) .textAttributes(key) .create() } diff --git a/src/main/kotlin/org/ton/intellij/func/highlighting/FuncSyntaxHighlighter.kt b/src/main/kotlin/org/ton/intellij/func/highlighting/FuncSyntaxHighlighter.kt index 3336f1f..8c2adb5 100644 --- a/src/main/kotlin/org/ton/intellij/func/highlighting/FuncSyntaxHighlighter.kt +++ b/src/main/kotlin/org/ton/intellij/func/highlighting/FuncSyntaxHighlighter.kt @@ -22,7 +22,7 @@ class FuncSyntaxHighlighter : SyntaxHighlighterBase() { FuncElementTypes.INTEGER_LITERAL -> FuncColor.NUMBER FuncElementTypes.SEMICOLON -> FuncColor.SEMICOLON FuncElementTypes.COMMA -> FuncColor.COMMA - FuncElementTypes.DOT -> FuncColor.DOT +// FuncElementTypes.DOT -> FuncColor.DOT FuncParserDefinition.EOL_COMMENT -> FuncColor.LINE_COMMENT FuncParserDefinition.BLOCK_COMMENT -> FuncColor.BLOCK_COMMENT in FUNC_DOC_COMMENTS -> FuncColor.DOC_COMMENT @@ -32,11 +32,10 @@ class FuncSyntaxHighlighter : SyntaxHighlighterBase() { in FuncParserDefinition.PRIMITIVE_TYPES -> FuncColor.PRIMITIVE_TYPES in FUNC_KEYWORDS -> FuncColor.KEYWORD in FuncParserDefinition.STRING_LITERALS -> FuncColor.STRING - in FuncParserDefinition.MACRO -> FuncColor.MACRO + FuncElementTypes.SHA -> FuncColor.MACRO in FuncParserDefinition.OPERATORS -> FuncColor.OPERATION_SIGN else -> null }.let { -// println(" = $it") pack(it?.textAttributesKey) } } diff --git a/src/main/kotlin/org/ton/intellij/func/ide/FuncLanguageInjector.kt b/src/main/kotlin/org/ton/intellij/func/ide/FuncLanguageInjector.kt new file mode 100644 index 0000000..bdac2e8 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/ide/FuncLanguageInjector.kt @@ -0,0 +1,22 @@ +package org.ton.intellij.func.ide + +import com.intellij.lang.injection.general.Injection +import com.intellij.lang.injection.general.LanguageInjectionContributor +import com.intellij.lang.injection.general.SimpleInjection +import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.elementType +import org.ton.intellij.fift.FiftLanguage +import org.ton.intellij.func.psi.FuncAsmBody +import org.ton.intellij.func.psi.FuncElementTypes + +class FuncLanguageInjector : LanguageInjectionContributor { + override fun getInjection(p0: PsiElement): Injection? { + if (p0.elementType != FuncElementTypes.RAW_STRING) return null + PsiTreeUtil.findFirstParent(p0) { it is FuncAsmBody } ?: return null + + return SimpleInjection( + FiftLanguage, "", "", null + ) + } +} diff --git a/src/main/kotlin/org/ton/intellij/func/ide/FuncParameterHintsProvider.kt b/src/main/kotlin/org/ton/intellij/func/ide/FuncParameterHintsProvider.kt deleted file mode 100644 index 99f1e1e..0000000 --- a/src/main/kotlin/org/ton/intellij/func/ide/FuncParameterHintsProvider.kt +++ /dev/null @@ -1,50 +0,0 @@ -package org.ton.intellij.func.ide - -import com.intellij.codeInsight.hints.InlayInfo -import com.intellij.codeInsight.hints.InlayParameterHintsProvider -import com.intellij.psi.PsiElement -import org.ton.intellij.func.psi.* - -@Suppress("UnstableApiUsage") -class FuncParameterHintsProvider : InlayParameterHintsProvider { - override fun getDefaultBlackList() = emptySet() - - override fun getParameterHints(element: PsiElement): List { - val function: FuncFunction - val argumentOffset: Int - val arguments: List - when (element) { - is FuncCallExpression -> { - function = element.referenceExpression.resolveFunction() ?: return emptyList() - argumentOffset = 0 - arguments = element.callArgument.toArgList() - } - - is FuncMethodCall -> { - function = element.referenceExpression.resolveFunction() ?: return emptyList() - argumentOffset = 1 - arguments = element.callArgument?.toArgList() ?: return emptyList() - } - - else -> return emptyList() - } - val parameters = function.functionParameterList - val result = ArrayList() - arguments.forEachIndexed { index, funcExpression -> - val parameter = parameters.getOrNull(index + argumentOffset) ?: return result - val parameterName = parameter.name - if (parameterName != null) { - result.add(InlayInfo(parameterName, funcExpression.textOffset)) - } - } - return result - } - - private fun FuncReferenceExpression.resolveFunction() = reference?.resolve() as? FuncFunction - private fun FuncCallArgument.toArgList() = expression.toArgList() - private fun FuncExpression.toArgList() = if (this is FuncTensorExpression) { - this.expressionList - } else { - listOf(this) - } -} diff --git a/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncCharFilter.kt b/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncCharFilter.kt new file mode 100644 index 0000000..c3c6335 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncCharFilter.kt @@ -0,0 +1,21 @@ +package org.ton.intellij.func.ide.completion + +import com.intellij.codeInsight.lookup.CharFilter +import com.intellij.codeInsight.lookup.Lookup +import org.ton.intellij.func.FuncLanguage + +class FuncCharFilter : CharFilter() { + override fun acceptChar(c: Char, prefixLength: Int, lookup: Lookup): Result? { + val file = lookup.psiFile ?: return null + if (!file.language.isKindOf(FuncLanguage)) return null + + val item = lookup.currentItem + if (item == null || !item.isValid) return null + + if (c == ';' || c == ',' || c == '(' || c == ')' || c == ' ' || c == '~' || c == '.' || c == '"') { + return Result.HIDE_LOOKUP + } + + return Result.ADD_TO_PREFIX + } +} diff --git a/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncCommonCompletionProvider.kt b/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncCommonCompletionProvider.kt new file mode 100644 index 0000000..bbe04a7 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncCommonCompletionProvider.kt @@ -0,0 +1,297 @@ +package org.ton.intellij.func.ide.completion + +import com.intellij.codeInsight.completion.CompletionParameters +import com.intellij.codeInsight.completion.CompletionResultSet +import com.intellij.codeInsight.completion.PrioritizedLookupElement +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.openapi.vfs.VfsUtilCore +import com.intellij.patterns.ElementPattern +import com.intellij.patterns.PlatformPatterns.psiElement +import com.intellij.psi.PsiElement +import com.intellij.psi.search.PsiElementProcessor +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.util.ProcessingContext +import org.ton.intellij.func.FuncIcons +import org.ton.intellij.func.psi.* +import org.ton.intellij.func.psi.impl.isVariableDefinition +import org.ton.intellij.func.psi.impl.rawParamType +import org.ton.intellij.func.psi.impl.rawReturnType +import org.ton.intellij.func.stub.index.FuncNamedElementIndex +import org.ton.intellij.func.type.ty.FuncTyAtomic +import org.ton.intellij.func.type.ty.FuncTyTensor +import org.ton.intellij.func.type.ty.FuncTyUnit +import org.ton.intellij.util.processAllKeys +import org.ton.intellij.util.psiElement +import java.util.* + +object FuncCommonCompletionProvider : FuncCompletionProvider() { + override val elementPattern: ElementPattern = + psiElement().withParent(psiElement()) + + override fun addCompletions( + parameters: CompletionParameters, + context: ProcessingContext, + result: CompletionResultSet + ) { + val position = parameters.position + val element = position.parent as FuncReferenceExpression + if (element.isVariableDefinition()) { + return + } + val elementName = element.name ?: return + + val ctx = FuncCompletionContext( + element + ) + + val processed = HashMap() + + val file = element.containingFile.originalFile as? FuncFile ?: return + + fun collectVariant(resolvedElement: FuncNamedElement): Boolean { + val resolvedName = resolvedElement.name ?: return false + if (processed.put(resolvedName, resolvedElement) != null) return false + when (resolvedElement) { + is FuncFunction -> { + if (!elementName.startsWith("~") && !resolvedName.startsWith("~")) { + return true + } + if (elementName.startsWith("~")) { + return (if (resolvedName.startsWith("~")) { + true + } else { + val returnType = resolvedElement.rawReturnType + + if (returnType is FuncTyTensor && returnType.types.size == 2) { + val retModifyType = returnType.types.first() + val argType = resolvedElement.rawParamType + argType == retModifyType || + (argType is FuncTyTensor && argType.types.first() == retModifyType) + } else { + false + } + }) + } + return false + } + + else -> return true + } + } + + collectLocalVariants(element) { + if (collectVariant(it)) { + result.addElement(it.toLookupElementBuilder(ctx, true)) + } + } + + val files = setOf(FuncPsiFactory[file.project].builtinFile) + file.collectIncludedFiles() + files.forEach { f -> + collectFileVariants(f) { + if (collectVariant(it)) { + result.addElement(it.toLookupElementBuilder(ctx, true)) + } + true + } + } + val globalNamedElements = sequence { + val keys = LinkedList() + processAllKeys(FuncNamedElementIndex.KEY, element.project) { key -> + keys.add(key) + true + } + keys.forEach { key -> + yieldAll(FuncNamedElementIndex.findElementsByName(element.project, key)) + } + }.toList() + + globalNamedElements.sortedBy { + VfsUtilCore.findRelativePath(file.virtualFile, it.containingFile.virtualFile, '/')?.count { c -> c == '/' } + }.forEach { + if (collectVariant(it)) { + result.addElement(it.toLookupElementBuilder(ctx, false)) + } + } + } + + private fun collectFileVariants(file: FuncFile, processor: PsiElementProcessor) { + file.constVars.forEach { + processor.execute(it) + } + file.globalVars.forEach { + processor.execute(it) + } + file.functions.forEach { + processor.execute(it) + } + } + + private fun collectLocalVariants(element: FuncReferenceExpression, processor: (FuncNamedElement) -> Unit) { + fun processExpression(expression: FuncExpression) { + when { + expression is FuncReferenceExpression && expression.isVariableDefinition() -> { + processor(expression) + } + + expression is FuncBinExpression -> { + val left = expression.left + processExpression(left) + } + + expression is FuncApplyExpression -> { + expression.right?.let { processExpression(it) } + } + + expression is FuncTensorExpression -> { + expression.expressionList.forEach { processExpression(it) } + } + + expression is FuncTupleExpression -> { + expression.expressionList.forEach { processExpression(it) } + } + } + } + + fun processStatement(statement: FuncStatement) { + when (statement) { + is FuncExpressionStatement -> { + val expression = statement.expression + processExpression(expression) + } + } + } + + PsiTreeUtil.treeWalkUp(element, null) { scope, prevParent -> + when (scope) { + is FuncFunction -> { + scope.functionParameterList.forEach { + processor(it) + } + return@treeWalkUp false + } + + is FuncBlockStatement -> { + for (funcStatement in scope.statementList) { + if (funcStatement == prevParent) break + processStatement(funcStatement) + } + } + } + true + } + } +} + +data class FuncCompletionContext( + val context: FuncElement? +) + +fun FuncNamedElement.toLookupElementBuilder( + context: FuncCompletionContext, + isImported: Boolean +): LookupElement { + val contextText = context.context?.text ?: "" + var name = this.name ?: "" + if (this is FuncFunction) { + name = when { + contextText.startsWith('.') -> ".${this.name}" + contextText.startsWith('~') && !name.startsWith("~") -> "~${this.name}" + else -> name + } + } + val file = this.containingFile.originalFile + val contextFile = context.context?.containingFile?.originalFile + val includePath = if (file == contextFile || contextFile == null) "" + else { + val contextVirtualFile = contextFile.virtualFile + val elementVirtualFile = file.virtualFile + if (contextVirtualFile != null && elementVirtualFile != null) { + VfsUtilCore.findRelativePath(contextVirtualFile, elementVirtualFile, '/') ?: "" + } else { + this.containingFile.name + } + } + val base = LookupElementBuilder.create(name) + .withIcon(this.getIcon(0)) + .withTailText(if (includePath.isEmpty()) "" else " ($includePath)") + + return when (this) { + is FuncFunction -> { + PrioritizedLookupElement.withPriority( + base + .withTypeText(this.rawReturnType.toString()) + .withInsertHandler { context, item -> + val paramType = this.rawParamType + val isExtensionFunction = name.startsWith("~") || name.startsWith(".") + val offset = if ( + (isExtensionFunction && paramType is FuncTyAtomic) || paramType == FuncTyUnit + ) 2 else 1 + context.editor.document.insertString(context.editor.caretModel.offset, "()") + context.editor.caretModel.moveToOffset(context.editor.caretModel.offset + offset) + context.commitDocument() + + val insertFile = context.file as? FuncFile ?: return@withInsertHandler + val includeCandidateFile = file as? FuncFile ?: return@withInsertHandler + insertFile.import(includeCandidateFile) + }, + if (isImported) FuncCompletionContributor.FUNCTION_PRIORITY + else FuncCompletionContributor.NOT_IMPORTED_FUNCTION_PRIORITY + ) + } + + is FuncConstVar -> { + PrioritizedLookupElement.withPriority( + base + .withTypeText(intKeyword?.text ?: sliceKeyword?.text ?: "") + .withTailText(if (includePath.isEmpty()) "" else " ($includePath)") + .withInsertHandler { context, item -> + context.commitDocument() + + val insertFile = context.file as? FuncFile ?: return@withInsertHandler + val includeCandidateFile = file as? FuncFile ?: return@withInsertHandler + insertFile.import(includeCandidateFile) + }, + if (isImported) FuncCompletionContributor.VAR_PRIORITY + else FuncCompletionContributor.NOT_IMPORTED_VAR_PRIORITY + ) + } + + is FuncGlobalVar -> { + PrioritizedLookupElement.withPriority( + base + .withTypeText(typeReference.text) + .withTailText(if (includePath.isEmpty()) "" else " ($includePath)") + .withInsertHandler { context, item -> + context.commitDocument() + + val insertFile = context.file as? FuncFile ?: return@withInsertHandler + val includeCandidateFile = file as? FuncFile ?: return@withInsertHandler + insertFile.import(includeCandidateFile) + }, + if (isImported) FuncCompletionContributor.VAR_PRIORITY + else FuncCompletionContributor.NOT_IMPORTED_VAR_PRIORITY + ) + } + + is FuncReferenceExpression -> { + PrioritizedLookupElement.withPriority( + base + .withIcon(FuncIcons.VARIABLE) + .withTypeText((parent as? FuncApplyExpression)?.left?.text ?: ""), + FuncCompletionContributor.VAR_PRIORITY + ) + } + + is FuncFunctionParameter -> { + PrioritizedLookupElement.withPriority( + base + .withIcon(FuncIcons.PARAMETER) + .withTypeText(typeReference?.text ?: ""), + FuncCompletionContributor.VAR_PRIORITY + ) + } + + else -> LookupElementBuilder.create(this.name.toString()).withIcon(this.getIcon(0)) + } +} diff --git a/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncCompletionConfidence.kt b/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncCompletionConfidence.kt index e9249be..ff0a592 100644 --- a/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncCompletionConfidence.kt +++ b/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncCompletionConfidence.kt @@ -8,6 +8,8 @@ import org.ton.intellij.func.psi.FuncNamedElement class FuncCompletionConfidence : CompletionConfidence() { override fun shouldSkipAutopopup(contextElement: PsiElement, psiFile: PsiFile, offset: Int): ThreeState { - return if (contextElement is FuncNamedElement && contextElement.name == "_") ThreeState.YES else ThreeState.UNSURE + return if (contextElement is FuncNamedElement && contextElement.name == "_") { + ThreeState.YES + } else ThreeState.UNSURE } } diff --git a/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncCompletionContributor.kt b/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncCompletionContributor.kt index b1b9e0a..99a8b8d 100644 --- a/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncCompletionContributor.kt +++ b/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncCompletionContributor.kt @@ -2,14 +2,15 @@ package org.ton.intellij.func.ide.completion import com.intellij.codeInsight.completion.CompletionContributor import com.intellij.codeInsight.completion.CompletionType +import com.intellij.patterns.ElementPattern +import com.intellij.patterns.PlatformPatterns.or import com.intellij.patterns.PlatformPatterns.psiElement -import org.ton.intellij.func.psi.FuncBlockStatement -import org.ton.intellij.func.psi.FuncExpressionStatement -import org.ton.intellij.func.psi.FuncReferenceExpression +import com.intellij.psi.PsiElement +import org.ton.intellij.func.psi.* class FuncCompletionContributor : CompletionContributor() { init { - extend(CompletionType.BASIC, referenceExpression(), FuncReferenceCompletionProvider()) + extend(FuncMacroCompletionProvider) extend( CompletionType.BASIC, inBlock(), @@ -25,15 +26,83 @@ class FuncCompletionContributor : CompletionContributor() { "try" ) ) + extend( + CompletionType.BASIC, + psiElement().withParent(FuncFunctionParameter::class.java), + FuncKeywordCompletionProvider( + CONTEXT_KEYWORD_PRIORITY, + keywords = funcPrimitiveTypes + ) + ) + extend( + CompletionType.BASIC, + or( + psiElement().inside(FuncTupleType::class.java), + psiElement().inside(FuncTensorType::class.java), + psiElement().inside(FuncParenType::class.java), + ), FuncKeywordCompletionProvider( + CONTEXT_KEYWORD_PRIORITY, + keywords = funcPrimitiveTypes, + insertSpace = false + ) + ) + extend( + CompletionType.BASIC, + baseFunctionAttributePattern( + psiElement(FuncElementTypes.RPAREN) + ), + FuncKeywordCompletionProvider( + CONTEXT_KEYWORD_PRIORITY, + "impure", + ) + ) + extend( + CompletionType.BASIC, + baseFunctionAttributePattern( + psiElement(FuncElementTypes.RPAREN), + psiElement(FuncElementTypes.IMPURE_KEYWORD), + ), + FuncKeywordCompletionProvider( + CONTEXT_KEYWORD_PRIORITY, + "inline", + "inline_ref", + ) + ) + extend( + CompletionType.BASIC, + baseFunctionAttributePattern( + psiElement(FuncElementTypes.RPAREN), + psiElement(FuncElementTypes.IMPURE_KEYWORD), + psiElement(FuncElementTypes.INLINE_KEYWORD), + psiElement(FuncElementTypes.INLINE_REF_KEYWORD), + ), + FuncKeywordCompletionProvider( + CONTEXT_KEYWORD_PRIORITY, + "method_id", + ) + ) + extend( + CompletionType.BASIC, + psiElement().afterLeaf( + psiElement(FuncElementTypes.RBRACE).withAncestor( + 2, + psiElement(FuncElementTypes.IF_STATEMENT) + ) + ), + FuncKeywordCompletionProvider( + CONTEXT_KEYWORD_PRIORITY, + "else", + "elseif", + "elseifnot" + ) + ) + extend(FuncCommonCompletionProvider) } fun extend(provider: FuncCompletionProvider) { extend(CompletionType.BASIC, provider.elementPattern, provider) } - private fun referenceExpression() = - psiElement().withParent(FuncReferenceExpression::class.java) - private fun inBlock() = psiElement().withParent( psiElement(FuncReferenceExpression::class.java).withParent( @@ -43,6 +112,18 @@ class FuncCompletionContributor : CompletionContributor() { ) ) + private fun baseFunctionAttributePattern( + vararg afterLeafs: ElementPattern, + ) = psiElement() + .withAncestor(2, psiElement(FuncFunction::class.java)) + .afterLeaf( + or( + *afterLeafs + ) + ).andNot( + psiElement().beforeLeaf(psiElement(FuncElementTypes.IDENTIFIER)) + ) + companion object { const val KEYWORD_PRIORITY = 20.0 const val CONTEXT_KEYWORD_PRIORITY = 25.0 @@ -50,5 +131,15 @@ class FuncCompletionContributor : CompletionContributor() { const val FUNCTION_PRIORITY = NOT_IMPORTED_FUNCTION_PRIORITY + 10.0 const val NOT_IMPORTED_VAR_PRIORITY = 5.0 const val VAR_PRIORITY = NOT_IMPORTED_VAR_PRIORITY + 10.0 + + private val funcPrimitiveTypes + get() = listOf( + "cell", + "builder", + "slice", + "int", + "tuple", + "cont" + ) } } diff --git a/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncImportCompletionProvider.kt b/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncImportCompletionProvider.kt deleted file mode 100644 index bad1c95..0000000 --- a/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncImportCompletionProvider.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.ton.intellij.func.ide.completion - -import com.intellij.codeInsight.completion.CompletionParameters -import com.intellij.codeInsight.completion.CompletionResultSet -import com.intellij.patterns.ElementPattern -import com.intellij.patterns.PlatformPatterns.psiElement -import com.intellij.psi.PsiElement -import com.intellij.util.ProcessingContext -import org.ton.intellij.func.psi.FuncFile - -class FuncImportCompletionProvider : FuncCompletionProvider() { - override val elementPattern: ElementPattern - get() = psiElement().withParent(FuncFile::class.java) - - override fun addCompletions( - parameters: CompletionParameters, - context: ProcessingContext, - result: CompletionResultSet, - ) { - - } -} diff --git a/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncKeywordCompletionProvider.kt b/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncKeywordCompletionProvider.kt index 81e1eda..1edc8a9 100644 --- a/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncKeywordCompletionProvider.kt +++ b/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncKeywordCompletionProvider.kt @@ -12,6 +12,7 @@ class FuncKeywordCompletionProvider( val priority: Double, val keywords: List = emptyList(), val insertHandler: InsertHandler? = null, + val insertSpace: Boolean = true ) : CompletionProvider() { constructor(priority: Double, vararg keywords: String) : this(priority, keywords.toList(), null) @@ -44,10 +45,9 @@ class FuncKeywordCompletionProvider( } else { val currentOffset = editor.caretModel.offset val documentText = editor.document.immutableCharSequence - if (documentText.length <= currentOffset || documentText[currentOffset] != ' ') { + if (insertSpace && (documentText.length <= currentOffset || documentText[currentOffset] != ' ')) { EditorModificationUtil.insertStringAtCaret(editor, " ") } - } } } diff --git a/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncMacroCompletionProvider.kt b/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncMacroCompletionProvider.kt new file mode 100644 index 0000000..97d947e --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncMacroCompletionProvider.kt @@ -0,0 +1,33 @@ +package org.ton.intellij.func.ide.completion + +import com.intellij.codeInsight.AutoPopupController +import com.intellij.codeInsight.completion.CompletionParameters +import com.intellij.codeInsight.completion.CompletionResultSet +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.openapi.project.DumbAware +import com.intellij.patterns.ElementPattern +import com.intellij.psi.PsiElement +import com.intellij.util.ProcessingContext +import org.ton.intellij.func.psi.FuncPsiPattern + +object FuncMacroCompletionProvider : FuncCompletionProvider(), DumbAware { + override val elementPattern: ElementPattern = + FuncPsiPattern.macroPattern() + + override fun addCompletions( + parameters: CompletionParameters, + context: ProcessingContext, + result: CompletionResultSet + ) { + result.addElement( + LookupElementBuilder.create("include").bold().withInsertHandler { context, item -> + context.document.insertString(context.selectionEndOffset, " \"\";") + context.editor.caretModel.moveToOffset(context.selectionEndOffset - 2) + AutoPopupController.getInstance(context.project).scheduleAutoPopup(context.editor) + } + ) + result.addElement( + LookupElementBuilder.create("pragma").bold() + ) + } +} diff --git a/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncReferenceCompletionProvider.kt b/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncReferenceCompletionProvider.kt deleted file mode 100644 index 2200942..0000000 --- a/src/main/kotlin/org/ton/intellij/func/ide/completion/FuncReferenceCompletionProvider.kt +++ /dev/null @@ -1,325 +0,0 @@ -package org.ton.intellij.func.ide.completion - -import com.intellij.codeInsight.TailType -import com.intellij.codeInsight.completion.CompletionParameters -import com.intellij.codeInsight.completion.CompletionProvider -import com.intellij.codeInsight.completion.CompletionResultSet -import com.intellij.codeInsight.completion.PrioritizedLookupElement -import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler -import com.intellij.codeInsight.lookup.* -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile -import com.intellij.psi.PsiReference -import com.intellij.psi.ResolveState -import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference -import com.intellij.psi.scope.PsiScopeProcessor -import com.intellij.psi.util.PsiTreeUtil -import com.intellij.util.ProcessingContext -import org.ton.intellij.func.FuncIcons -import org.ton.intellij.func.ide.completion.FuncCompletionContributor.Companion.FUNCTION_PRIORITY -import org.ton.intellij.func.ide.completion.FuncCompletionContributor.Companion.VAR_PRIORITY -import org.ton.intellij.func.psi.* -import org.ton.intellij.func.psi.impl.FuncReference - -class FuncReferenceCompletionProvider : CompletionProvider() { - override fun addCompletions( - parameters: CompletionParameters, - context: ProcessingContext, - result: CompletionResultSet, - ) { - val expression = PsiTreeUtil.getParentOfType(parameters.position, FuncReferenceExpression::class.java) - val originalFile = parameters.originalFile - if (expression != null) { -// println("parent: '${expression.identifier.text}'") -// println("add completion expression: $expression | ${expression.text}") - fillVariantsByReference(expression, expression.reference, originalFile, result) - } - } - - private fun fillVariantsByReference( - originalElement: FuncElement, - reference: PsiReference?, - file: PsiFile, - result: CompletionResultSet, - ) { - if (reference == null) return - when (reference) { - is PsiMultiReference -> { - reference.references.let { - it.sortWith(PsiMultiReference.COMPARATOR) - return fillVariantsByReference(originalElement, it.firstOrNull(), file, result) - } - } - - is FuncReference -> { - reference.processResolveVariants(MyProcessor(originalElement, result), implicitStdlib = false) - } - } - } - - private class MyProcessor( - val originalElement: FuncElement, - val result: CompletionResultSet, - ) : PsiScopeProcessor { - private val processedNames = HashSet() - - override fun execute(element: PsiElement, state: ResolveState): Boolean { -// println("add to elements: ${element.elementType} | ${element.text}") - addElement(element, originalElement, processedNames, result) - return true - } - } - - private object FunctionParameterRenderer : LookupElementRenderer() { - override fun renderElement(element: LookupElement, presentation: LookupElementPresentation) { - val psiElement = element.psiElement as? FuncFunctionParameter ?: return - presentation.apply { - icon = FuncIcons.PARAMETER - itemText = element.lookupString - isTypeGrayed = true - typeText = psiElement.atomicType?.text - } - } - } - - private class CatchVariableRenderer( - val catchStatement: FuncCatch, - ) : LookupElementRenderer() { - override fun renderElement(element: LookupElement, presentation: LookupElementPresentation) { - presentation.apply { - icon = FuncIcons.VARIABLE - isTypeGrayed = true - itemText = element.lookupString - } - } - } - - private class VariableRenderer( - val variable: FuncVarExpression, - ) : LookupElementRenderer() { - override fun renderElement(element: LookupElement, presentation: LookupElementPresentation) { - presentation.apply { - icon = FuncIcons.VARIABLE - isTypeGrayed = true - itemText = element.lookupString - } - } - } - - private class ConstRenderer( - val variable: FuncConstVar, - ) : LookupElementRenderer() { - override fun renderElement(element: LookupElement, presentation: LookupElementPresentation) { - presentation.apply { - icon = FuncIcons.CONSTANT - isTypeGrayed = true - itemText = element.lookupString - presentation.isTypeGrayed = true - presentation.typeText = variable.intKeyword?.text ?: variable.sliceKeyword?.text - } - } - } - - private class GlobalVarRenderer( - val variable: FuncGlobalVar, - ) : LookupElementRenderer() { - override fun renderElement(element: LookupElement, presentation: LookupElementPresentation) { - presentation.apply { - icon = FuncIcons.GLOBAL_VARIABLE - isTypeGrayed = true - itemText = element.lookupString - presentation.isTypeGrayed = true - presentation.typeText = variable.type.text - } - } - } - - private object FunctionRenderer : LookupElementRenderer() { - override fun renderElement(element: LookupElement, presentation: LookupElementPresentation) { - val psiElement = element.psiElement - if (psiElement !is FuncFunction) return - presentation.icon = FuncIcons.FUNCTION - presentation.itemText = element.lookupString - presentation.tailText = - psiElement.functionParameterList.joinToString(", ", prefix = "(", postfix = ")") { it.text } - presentation.isTypeGrayed = true - presentation.typeText = psiElement.type.text - } - } - - companion object { - - fun addElement( - element: PsiElement, - context: FuncElement, - processedNames: MutableSet, - set: CompletionResultSet, - ) { - val lookup = createLookupElement(element, context) ?: return - if (processedNames.add(lookup.lookupString)) { - set.addElement(lookup) - } - } - - fun createLookupElement( - element: PsiElement, - originalElement: FuncElement, - ): LookupElement? { - return when (element) { - is FuncFunction -> { - val functionName = element.name ?: return null - val parent = originalElement.parent - if (parent is FuncMethodCall) { - if (parent.parent is FuncDotExpression && functionName.firstOrNull() == '~') { - return null - } - val parameters = element.functionParameterList - if (parameters.isEmpty()) { - return null - } - } - - PrioritizedLookupElement.withPriority( - LookupElementBuilder - .createWithSmartPointer(functionName, element) - .withRenderer(FunctionRenderer) - .withInsertHandler { context, item -> - - val parameters = element.functionParameterList - - if (parameters.isEmpty()) { - ParenthesesInsertHandler.NO_PARAMETERS.handleInsert(context, item) - } else if (parameters.size == 1 && originalElement.parent is FuncMethodCall) { - ParenthesesInsertHandler.NO_PARAMETERS.handleInsert(context, item) - } else { -// println("grand grand parent: ${originalElement.parent.parent.parent.elementType} | ${originalElement.parent.parent.parent.text}") -// println("grand parent: ${originalElement.parent.parent.elementType} | ${originalElement.parent.parent.text}") -// println("parent: ${originalElement.parent.elementType} | ${originalElement.parent.text}") -// println("child: ${originalElement.elementType} | ${originalElement.text}") - // TODO: handle as NO_PARAMETERS at `a.foo/**caret**/` when `foo` - function with 1 parameter - ParenthesesInsertHandler.WITH_PARAMETERS.handleInsert(context, item) - } - }, - FUNCTION_PRIORITY - ).let { - var result = it - // TODO: insert semicolon if void return - PsiTreeUtil.treeWalkUp(originalElement, null) { scope, _ -> -// println("walking up: ${scope.elementType} | ${scope.text}") - when (scope) { - is FuncBlockStatement -> { - result = TailTypeDecorator.withTail(result, TailType.createSimpleTailType(';')) - return@treeWalkUp false - } - - is FuncTensorExpression -> { - return@treeWalkUp false - } - } - true - } - result - } - } - - is FuncFunctionParameter -> { - val name = element.name ?: return null - if (originalElement.parent is FuncMethodCall) return null - PrioritizedLookupElement.withPriority( - LookupElementBuilder - .createWithSmartPointer(name, element) - .withRenderer(FunctionParameterRenderer), - VAR_PRIORITY - ) - } - - is FuncConstVar -> { - val name = element.name ?: return null - if (originalElement.parent is FuncMethodCall) return null - PrioritizedLookupElement.withPriority( - LookupElementBuilder - .createWithSmartPointer(name, element) - .withRenderer(ConstRenderer(element)), - VAR_PRIORITY - ) - } - - is FuncGlobalVar -> { - val name = element.name ?: return null - if (originalElement.parent is FuncMethodCall) return null - PrioritizedLookupElement.withPriority( - LookupElementBuilder - .createWithSmartPointer(name, element) - .withRenderer(GlobalVarRenderer(element)), - VAR_PRIORITY - ) - } - - is FuncReferenceExpression -> { - val name = element.name ?: return null - val parent = originalElement.parent - if (parent is FuncCallExpression || parent is FuncMethodCall) return null - var lookupElement: LookupElement? = null - PsiTreeUtil.treeWalkUp(element, null) { scope, prevParent -> - when (scope) { - is FuncCatch -> if (scope.expression == prevParent) { - lookupElement = PrioritizedLookupElement.withPriority( - LookupElementBuilder - .createWithSmartPointer(name, element) - .withRenderer(CatchVariableRenderer(scope)), - VAR_PRIORITY - ) - return@treeWalkUp false - } - - is FuncAssignExpression -> { - when (val parentScope = scope.parent) { - is FuncVarExpression -> if (scope.expressionList.firstOrNull() == prevParent) { - lookupElement = PrioritizedLookupElement.withPriority( - LookupElementBuilder - .createWithSmartPointer(name, element) - .withRenderer(VariableRenderer(parentScope)), - VAR_PRIORITY - ) - return@treeWalkUp false - } - -// is FuncConstVariable -> if (scope.expressionList.firstOrNull() == prevParent) { -// lookupElement = PrioritizedLookupElement.withPriority( -// LookupElementBuilder -// .createWithSmartPointer(name, element) -// .withRenderer(ConstRenderer(parentScope)), -// VAR_PRIORITY -// ) -// return@treeWalkUp false -// } - } - } - - is FuncVarExpression -> if (scope.expressionList.getOrNull(1) == prevParent) { - lookupElement = PrioritizedLookupElement.withPriority( - LookupElementBuilder - .createWithSmartPointer(name, element) - .withRenderer(VariableRenderer(scope)), - VAR_PRIORITY - ) - return@treeWalkUp false - } - } - return@treeWalkUp true - } - lookupElement - } - - else -> null - } - } -// - -// - -// -// else -> null -// } - } -} diff --git a/src/main/kotlin/org/ton/intellij/func/ide/fixes/RemoveElementFix.kt b/src/main/kotlin/org/ton/intellij/func/ide/fixes/RemoveElementFix.kt new file mode 100644 index 0000000..e4a9c41 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/ide/fixes/RemoveElementFix.kt @@ -0,0 +1,27 @@ +package org.ton.intellij.func.ide.fixes + +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile + +class RemoveElementFix( + element: PsiElement, + private val removingElementName: String = "`${element.text}`" +) : LocalQuickFixAndIntentionActionOnPsiElement(element), LocalQuickFix { + override fun getFamilyName(): String = "Remove" + + override fun getText(): String = "Remove $removingElementName" + + override fun invoke( + project: Project, + file: PsiFile, + editor: Editor?, + startElement: PsiElement, + endElement: PsiElement + ) { + startElement.delete() + } +} diff --git a/src/main/kotlin/org/ton/intellij/func/ide/fixes/RenameUnserscoreFix.kt b/src/main/kotlin/org/ton/intellij/func/ide/fixes/RenameUnserscoreFix.kt new file mode 100644 index 0000000..0b0e85e --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/ide/fixes/RenameUnserscoreFix.kt @@ -0,0 +1,31 @@ +package org.ton.intellij.func.ide.fixes + +import com.intellij.codeInsight.CodeInsightBundle +import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import org.ton.intellij.func.psi.* + +class RenameUnderscoreFix( + val element: FuncReferenceExpression +) : LocalQuickFixAndIntentionActionOnPsiElement(element) { + override fun getFamilyName(): String = + CodeInsightBundle.message("rename.element.family") + + override fun getText(): String = + CodeInsightBundle.message("rename.named.element.text", element.name, "_") + + override fun invoke( + project: Project, + file: PsiFile, + editor: Editor?, + startElement: PsiElement, + endElement: PsiElement + ) { + val expression = FuncPsiFactory[project].createExpression("var (_) = 1") as FuncBinExpression + val newElement = ((expression.left as FuncApplyExpression).right as FuncTensorExpression).expressionList.first() + startElement.replace(newElement) + } +} diff --git a/src/main/kotlin/org/ton/intellij/func/ide/formatter/FuncFormatter.kt b/src/main/kotlin/org/ton/intellij/func/ide/formatter/FuncFormatter.kt index 59d4016..ef280be 100644 --- a/src/main/kotlin/org/ton/intellij/func/ide/formatter/FuncFormatter.kt +++ b/src/main/kotlin/org/ton/intellij/func/ide/formatter/FuncFormatter.kt @@ -5,6 +5,7 @@ import com.intellij.psi.codeStyle.CodeStyleSettings import com.intellij.psi.tree.TokenSet import org.ton.intellij.func.FuncLanguage import org.ton.intellij.func.psi.FuncElementTypes.* +import org.ton.intellij.util.tokenSetOf class FuncFormatter : FormattingModelBuilder { override fun createModel(formattingContext: FormattingContext): FormattingModel { @@ -51,30 +52,10 @@ class FuncFormatter : FormattingModelBuilder { .after(RBRACE).spaces(1) .around(UNTIL_KEYWORD).spaces(1) .before(TokenSet.create(BLOCK_STATEMENT, ASM_BODY)).spaces(1) - .aroundInside( - TokenSet.create( - EQ, - PLUSLET, - MINUSLET, - TIMESLET, - DIVLET, - DIVRLET, - DIVCLET, - MODLET, - MODRLET, - MODCLET, - LSHIFTLET, - RSHIFTLET, - RSHIFTCLET, - RSHIFTRLET, - ANDLET, - ORLET, - XORLET - ), - ASSIGN_EXPRESSION - ).spaces(1) + .around(BINARY_OP).spaces(1) .after(FORALL_KEYWORD).spaces(1) .around(MAPSTO).spaces(1) + .afterInside(tokenSetOf(PRIMITIVE_TYPE_EXPRESSION, HOLE_TYPE_EXPRESSION), APPLY_EXPRESSION).spaces(1) .betweenInside(LPAREN, MAPSTO, ASM_PARAMETERS).spaces(1) .betweenInside( TokenSet.create( @@ -83,49 +64,30 @@ class FuncFormatter : FormattingModelBuilder { TUPLE_TYPE, TYPE_IDENTIFIER, HOLE_TYPE - ), TokenSet.create(IDENTIFIER, TILDE, DOT), FUNCTION + ), TokenSet.create(IDENTIFIER, TILDE), FUNCTION ).spaces(1) - .afterInside(TYPE, FUNCTION).spaces(1) - .afterInside(TokenSet.create(DOT, TILDE), FUNCTION).none() + .afterInside(TYPE_REFERENCE, FUNCTION).spaces(1) + .afterInside(TokenSet.create(TILDE), FUNCTION).none() .afterInside(IDENTIFIER, FUNCTION).none() .beforeInside(IDENTIFIER, FUNCTION_PARAMETER).spaces(1) .after(FUNCTION_PARAMETER).none() - .beforeInside(TENSOR_EXPRESSION, CALL_EXPRESSION).none() + .beforeInside(TENSOR_EXPRESSION, APPLY_EXPRESSION).none() + .beforeInside(UNIT_EXPRESSION, APPLY_EXPRESSION).none() + .beforeInside(TENSOR_EXPRESSION, SPECIAL_APPLY_EXPRESSION).none() + .beforeInside(UNIT_EXPRESSION, SPECIAL_APPLY_EXPRESSION).none() .beforeInside(RPAREN, TokenSet.create(TENSOR_EXPRESSION, TENSOR_TYPE)).none() .beforeInside(RBRACK, TokenSet.create(TUPLE_EXPRESSION, TUPLE_TYPE)).none() .aroundInside(TokenSet.create(QUEST, COLON), TERNARY_EXPRESSION).spaces(1) - .aroundInside(EQEQ, EQ_EXPRESSION).spaces(1) - .aroundInside(LT, LT_EXPRESSION).spaces(1) - .aroundInside(GT, GT_EXPRESSION).spaces(1) - .aroundInside(LEQ, LEQ_EXPRESSION).spaces(1) - .aroundInside(GEQ, GEQ_EXPRESSION).spaces(1) - .aroundInside(NEQ, NEQ_EXPRESSION).spaces(1) - .aroundInside(SPACESHIP, SPACESHIP_EXPRESSION).spaces(1) - .aroundInside(LSHIFT, L_SHIFT_EXPRESSION).spaces(1) - .aroundInside(RSHIFT, R_SHIFT_EXPRESSION).spaces(1) - .aroundInside(RSHIFTC, R_SHIFT_C_EXPRESSION).spaces(1) - .aroundInside(RSHIFTR, R_SHIFT_R_EXPRESSION).spaces(1) - .aroundInside(PLUS, PLUS_EXPRESSION).spaces(1) - .aroundInside(MINUS, MINUS_EXPRESSION).spaces(1) - .aroundInside(OR, OR_EXPRESSION).spaces(1) - .aroundInside(XOR, XOR_EXPRESSION).spaces(1) - .aroundInside(TIMES, MUL_EXPRESSION).spaces(1) - .aroundInside(DIV, DIV_EXPRESSION).spaces(1) - .aroundInside(MOD, MOD_EXPRESSION).spaces(1) - .aroundInside(DIVMOD, DIV_MOD_EXPRESSION).spaces(1) - .aroundInside(DIVC, DIV_C_EXPRESSION).spaces(1) - .aroundInside(DIVR, DIV_R_EXPRESSION).spaces(1) - .aroundInside(MODC, MOD_C_EXPRESSION).spaces(1) - .aroundInside(MODR, MOD_R_EXPRESSION).spaces(1) - .aroundInside(AND, AND_EXPRESSION).spaces(1) + .aroundInside( + tokenSetOf( + EQ, PLUSLET, MINUSLET, TIMESLET, DIVLET, DIVCLET, DIVRLET, MODLET, MODCLET, MODRLET, + LSHIFTLET, RSHIFTLET, RSHIFTCLET, RSHIFTRLET, ANDLET, ORLET, XORLET, EQEQ, NEQ, LEQ, + GEQ, GT, LT, SPACESHIP, LSHIFT, RSHIFTR, RSHIFTC, MINUS, PLUS, OR, XOR, TIMES, DIV, MOD, + DIVMOD, DIVC, DIVR, MODR, MODC, AND + ), BIN_EXPRESSION + ).spaces(1) .afterInside(MINUS, UNARY_MINUS_EXPRESSION).none() -// .aroundInside(TokenSet.create(TILDE, DOT), QUALIFIED_EXPRESSION).none() .afterInside(TILDE, INV_EXPRESSION).spaces(1) - .aroundInside(TokenSet.create(REFERENCE_EXPRESSION), VAR_EXPRESSION).spaces(1) - .afterInside( - TokenSet.create(PRIMITIVE_TYPE_EXPRESSION, HOLE_TYPE_EXPRESSION, TENSOR_EXPRESSION), - VAR_EXPRESSION - ).spaces(1) .around(TokenSet.create(IMPURE_KEYWORD, INLINE_KEYWORD, INLINE_REF_KEYWORD, METHOD_ID_KEYWORD)).spaces(1) } } diff --git a/src/main/kotlin/org/ton/intellij/func/ide/formatter/FuncFormattingBlock.kt b/src/main/kotlin/org/ton/intellij/func/ide/formatter/FuncFormattingBlock.kt index 77b2294..3fc08fb 100644 --- a/src/main/kotlin/org/ton/intellij/func/ide/formatter/FuncFormattingBlock.kt +++ b/src/main/kotlin/org/ton/intellij/func/ide/formatter/FuncFormattingBlock.kt @@ -13,12 +13,12 @@ abstract class AbstractFuncBlock( val spacingBuilder: SpacingBuilder, wrap: Wrap? = null, alignment: Alignment? = null, - private val indent: Indent? = null, + private val indent: Indent? = Indent.getNoneIndent(), private val childIndent: Indent? = null, ) : AbstractBlock(node, wrap, alignment) { override fun getSpacing(child1: Block?, child2: Block): Spacing? = spacingBuilder.getSpacing(this, child1, child2) - override fun isLeaf(): Boolean = myNode.firstChildNode != null + override fun isLeaf(): Boolean = myNode.firstChildNode == null override fun getIndent(): Indent? = indent @@ -64,7 +64,11 @@ class FuncFormattingBlock( private fun createBlock(node: ASTNode): ASTBlock? { if (node.elementType == TokenType.WHITE_SPACE) return null val indent = calcIndent(node) ?: return null - val childIndent = if (node.elementType == BLOCK_STATEMENT) Indent.getNormalIndent() else null + val childIndent = + when (node.elementType) { + BLOCK_STATEMENT -> Indent.getNormalIndent() + else -> Indent.getNormalIndent() + } val wrap = calcWrap(node) return FuncFormattingBlock( @@ -77,65 +81,17 @@ class FuncFormattingBlock( ) } - private fun Int.example() = this - - private fun a() { - val a = 1 - a.example().example().example().example() - - a.example() - .example() - .example() - .example() - - } - -// private fun createChainingCallBlock(node: FuncQualifiedExpression): ASTBlock { -// return object : AbstractFuncBlock(node.node, spacingBuilder) { -// override fun buildChildren(): List = buildList { -// val expressions = node.expressionList -// -// val first = expressions.firstOrNull() -// if (first is FuncQualifiedExpression) { -// add(createChainingCallBlock(first)) -// } else if (first != null) { -// createBlock(first.node)?.let { -// add(it) -// } -// } -// -// add( -// block(node.node, spacingBuilder, indent = Indent.getNormalIndent(), wrap = Wrap.createChildWrap(Wrap.createWrap(WrapType.NONE, false), WrapType.NORMAL, false)) { -// buildList { -// node.dot?.node?.let { -// add(block(it, spacingBuilder, indent= Indent.getNoneIndent())) -// } -// expressions.getOrNull(1)?.node?.let { -// createBlock(it) -// }?.let { -// add(it) -// } -// } -// } -// ) -// } -// } -// } - private fun calcIndent(child: ASTNode): Indent? { val type = child.elementType val parent = child.treeParent val parentType = parent.elementType - if (parentType == BLOCK_STATEMENT) return indentIfNotBrace(child) - if (parentType == TENSOR_EXPRESSION && type != LPAREN && type != RPAREN) return Indent.getNormalIndent() + when (parentType) { + BLOCK_STATEMENT -> return indentIfNotBrace(child) + SPECIAL_APPLY_EXPRESSION -> if (parent.lastChildNode == child) return Indent.getNormalIndent() + TENSOR_EXPRESSION, PAREN_EXPRESSION, TENSOR_TYPE, PAREN_TYPE -> if (type != LPAREN && type != RPAREN) return Indent.getNormalIndent() + TUPLE_TYPE, TUPLE_EXPRESSION -> if (type != LBRACK && type != RBRACK) return Indent.getNormalIndent() + } if (type == PRIMITIVE_TYPE_EXPRESSION || type == HOLE_TYPE_EXPRESSION) return Indent.getNoneIndent() -// if (parentType == QUALIFIED_EXPRESSION && (type == DOT || type == TILDE)) return Indent.getContinuationWithoutFirstIndent() -// if (parentType == ASSIGN_EXPRESSION) return Indent.getContinuationWithoutFirstIndent(true) -// if (parentType == QUALIFIED_EXPRESSION && type != QUALIFIED_EXPRESSION) { -// return Indent.getContinuationWithoutFirstIndent() -// } -// if (parentType == QUALIFIED_EXPRESSION && type == QUALIFIED_EXPRESSION) return Indent.getNormalIndent() -// if (child.psi is FuncExpression) return Indent.getContinuationWithoutFirstIndent() return Indent.getNoneIndent() } diff --git a/src/main/kotlin/org/ton/intellij/func/ide/hints/FuncParameterHintsProvider.kt b/src/main/kotlin/org/ton/intellij/func/ide/hints/FuncParameterHintsProvider.kt new file mode 100644 index 0000000..a8be038 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/ide/hints/FuncParameterHintsProvider.kt @@ -0,0 +1,39 @@ +package org.ton.intellij.func.ide.hints + +import com.intellij.codeInsight.hints.InlayInfo +import com.intellij.codeInsight.hints.InlayParameterHintsProvider +import com.intellij.psi.PsiElement +import org.ton.intellij.func.psi.* + +@Suppress("UnstableApiUsage") +class FuncParameterHintsProvider : InlayParameterHintsProvider { + override fun getDefaultBlackList() = emptySet() + + override fun getParameterHints(element: PsiElement): List { + val applyExpression = element as? FuncApplyExpression ?: return emptyList() + val referenceExpression = applyExpression.left as? FuncReferenceExpression ?: return emptyList() + val name = referenceExpression.name ?: return emptyList() + val arguments = when (val right = applyExpression.right) { + is FuncTensorExpression -> right.expressionList + is FuncParenExpression -> listOf(right.expression) + else -> return emptyList() + } + if (arguments.isEmpty()) return emptyList() + + val function = referenceExpression.reference?.resolve() as? FuncFunction ?: return emptyList() + val isSpecialCall = name.startsWith('.') || name.startsWith('~') + val offset = if (isSpecialCall) 1 else 0 + + val params = function.functionParameterList + val result = ArrayList() + arguments.forEachIndexed { index, arg -> + val param = params.getOrNull(index + offset) ?: return result + val paramName = param.name + if (paramName != null) { + result.add(InlayInfo(paramName, arg.textOffset)) + } + } + + return result + } +} diff --git a/src/main/kotlin/org/ton/intellij/func/ide/hints/FuncParameterInfoHandler.kt b/src/main/kotlin/org/ton/intellij/func/ide/hints/FuncParameterInfoHandler.kt new file mode 100644 index 0000000..5992bcf --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/ide/hints/FuncParameterInfoHandler.kt @@ -0,0 +1,84 @@ +package org.ton.intellij.func.ide.hints + +import com.intellij.lang.parameterInfo.* +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiFile +import com.intellij.refactoring.suggested.startOffset +import org.ton.intellij.func.psi.FuncApplyExpression +import org.ton.intellij.func.psi.FuncElementTypes +import org.ton.intellij.func.psi.FuncFunction +import org.ton.intellij.func.psi.FuncReferenceExpression +import org.ton.intellij.util.ancestorStrict + +class FuncParameterInfoHandler : ParameterInfoHandler> { + override fun findElementForParameterInfo(context: CreateParameterInfoContext): FuncApplyExpression? { + val element = findFuncApplyExpression(context.file, context.offset) ?: return null + val left = element.left as? FuncReferenceExpression ?: return null + val leftName = left.name ?: return null + val resolved = left.reference?.resolve() as? FuncFunction ?: return null + val specialCall = leftName.startsWith('.') || leftName.startsWith('~') + val params = resolved.functionParameterList.map { it.text }.let { + if (specialCall) it.drop(1) else it + } + context.itemsToShow = arrayOf(params) + return element + } + + override fun findElementForUpdatingParameterInfo(context: UpdateParameterInfoContext): FuncApplyExpression? { + return findFuncApplyExpression(context.file, context.offset) + } + + override fun updateUI(p: List, context: ParameterInfoUIContext) { + val range = getArgumentRange(p, context.currentParameterIndex) + updateUI(presentText(p), range, context) + } + + override fun updateParameterInfo(parameterOwner: FuncApplyExpression, context: UpdateParameterInfoContext) { + if (context.parameterOwner != parameterOwner) { + context.removeHint() + return + } + val currentParameterIndex = if (parameterOwner.startOffset == context.offset) { + -1 + } else { + val right = parameterOwner.right + if (right != null) { + ParameterInfoUtils.getCurrentParameterIndex(right.node, context.offset, FuncElementTypes.COMMA) + } else { + -1 + } + } + context.setCurrentParameter(currentParameterIndex) + } + + override fun showParameterInfo(element: FuncApplyExpression, context: CreateParameterInfoContext) { + context.showHint(element, element.textRange.startOffset, this) + } + + private fun findFuncApplyExpression(file: PsiFile, offset: Int): FuncApplyExpression? { + return file.findElementAt(offset)?.ancestorStrict() + } + + private fun updateUI(text: String, range: TextRange, context: ParameterInfoUIContext) { + context.setupUIComponentPresentation( + text, + range.startOffset, + range.endOffset, + !context.isUIComponentEnabled, + false, + false, + context.defaultParameterColor + ) + } + + private fun getArgumentRange(arguments: List, index: Int): TextRange { + if (index < 0 || index >= arguments.size) return TextRange.EMPTY_RANGE + val start = arguments.take(index).sumOf { it.length + 2 } + val end = start + arguments[index].length + return TextRange(start, end) + } + + private fun presentText(params: List): String { + return if (params.isEmpty()) "" else params.joinToString(", ") + } +} diff --git a/src/main/kotlin/org/ton/intellij/func/ide/linemarker/FuncRecursiveCallLineMarkerProvider.kt b/src/main/kotlin/org/ton/intellij/func/ide/linemarker/FuncRecursiveCallLineMarkerProvider.kt index 4c8e39d..ddbbb02 100644 --- a/src/main/kotlin/org/ton/intellij/func/ide/linemarker/FuncRecursiveCallLineMarkerProvider.kt +++ b/src/main/kotlin/org/ton/intellij/func/ide/linemarker/FuncRecursiveCallLineMarkerProvider.kt @@ -5,10 +5,10 @@ import com.intellij.codeInsight.daemon.LineMarkerProviderDescriptor import com.intellij.openapi.editor.markup.GutterIconRenderer import com.intellij.psi.PsiElement import org.ton.intellij.func.FuncIcons -import org.ton.intellij.func.psi.FuncCallExpression +import org.ton.intellij.func.psi.FuncApplyExpression import org.ton.intellij.func.psi.FuncFunction -import org.ton.intellij.func.psi.FuncMethodCall import org.ton.intellij.func.psi.FuncReferenceExpression +import org.ton.intellij.func.psi.FuncSpecialApplyExpression import org.ton.intellij.util.ancestorStrict import org.ton.intellij.util.document import javax.swing.Icon @@ -30,8 +30,8 @@ class FuncRecursiveCallLineMarkerProvider : LineMarkerProviderDescriptor() { if (element !is FuncReferenceExpression) continue val parent = element.parent val isRecursive = when { - parent is FuncMethodCall && element == parent.referenceExpression && parent.referenceExpression.isRecursive -> true - parent is FuncCallExpression && element == parent.referenceExpression && parent.referenceExpression.isRecursive -> true + parent is FuncApplyExpression && (parent.left as? FuncReferenceExpression).isRecursive -> true + parent is FuncSpecialApplyExpression && (parent.left as? FuncReferenceExpression).isRecursive -> true else -> false } if (!isRecursive) continue @@ -54,9 +54,9 @@ class FuncRecursiveCallLineMarkerProvider : LineMarkerProviderDescriptor() { } } - private val FuncReferenceExpression.isRecursive: Boolean + private val FuncReferenceExpression?.isRecursive: Boolean get() { - val reference = reference ?: return false + val reference = this?.reference ?: return false val def = reference.resolve() return def != null && reference.element.ancestorStrict() == def } diff --git a/src/main/kotlin/org/ton/intellij/func/ide/quickdoc/FuncPsiDocumentationTargetProvider.kt b/src/main/kotlin/org/ton/intellij/func/ide/quickdoc/FuncPsiDocumentationTargetProvider.kt index 00f831d..c622cd1 100644 --- a/src/main/kotlin/org/ton/intellij/func/ide/quickdoc/FuncPsiDocumentationTargetProvider.kt +++ b/src/main/kotlin/org/ton/intellij/func/ide/quickdoc/FuncPsiDocumentationTargetProvider.kt @@ -14,7 +14,6 @@ import com.intellij.psi.PsiElement import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.SmartPointerManager import com.intellij.psi.SmartPsiElementPointer -import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.elementType import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor import org.intellij.markdown.parser.MarkdownParser @@ -106,21 +105,21 @@ class FuncDocumentationTarget(val element: PsiElement, val originalElement: PsiE ): Pair? = element to buildString { when (element) { is FuncReferenceExpression -> { - val resolved = element.reference?.resolve() - if (resolved != null) { - return renderElement(resolved, originalElement) - } else { - val varExpr = PsiTreeUtil.getParentOfType(element, FuncVarExpression::class.java) ?: return null - val left = varExpr.expressionList[0] - val right = varExpr.expressionList[1] - when (left) { - is FuncPrimitiveTypeExpression -> renderType(left.primitiveType) - is FuncHoleTypeExpression -> renderType(left.holeType) - else -> append(left.text) - } - append(NBSP) - append(right.text) - } +// val resolved = element.reference?.resolve() +// if (resolved != null) { +// return renderElement(resolved, originalElement) +// } else { +// val varExpr = PsiTreeUtil.getParentOfType(element, FuncVarExpression::class.java) ?: return null +// val left = varExpr.expressionList[0] +// val right = varExpr.expressionList[1] +// when (left) { +// is FuncPrimitiveTypeExpression -> renderType(left.primitiveType) +// is FuncHoleTypeExpression -> renderType(left.holeType) +// else -> append(left.text) +// } +// append(NBSP) +// append(right.text) +// } } is FuncFunction -> { @@ -161,7 +160,7 @@ class FuncDocumentationTarget(val element: PsiElement, val originalElement: PsiE append("->") append(NBSP) } - renderType(function.type) + renderType(function.typeReference) append(NBSP) if (function.isMutable) { append("~") @@ -183,7 +182,7 @@ class FuncDocumentationTarget(val element: PsiElement, val originalElement: PsiE fun StringBuilder.renderFunctionParameter( param: FuncFunctionParameter, ) { - val type = param.atomicType + val type = param.typeReference if (type != null) { renderType(type) } @@ -203,7 +202,7 @@ class FuncDocumentationTarget(val element: PsiElement, val originalElement: PsiE } fun StringBuilder.renderType( - type: FuncType, + type: FuncTypeReference, ) { when (type) { is FuncTypeIdentifier -> @@ -214,9 +213,9 @@ class FuncDocumentationTarget(val element: PsiElement, val originalElement: PsiE is FuncTupleType -> { appendStyledSpan(FuncColor.BRACKETS.attributes, "[") - type.tupleTypeItemList.joinTo(this) { + type.typeReferenceList.joinTo(this) { buildString { - renderType(it.type) + renderType(it) } } appendStyledSpan(FuncColor.BRACKETS.attributes, "]") @@ -225,7 +224,7 @@ class FuncDocumentationTarget(val element: PsiElement, val originalElement: PsiE is FuncTensorType -> { appendStyledSpan(FuncColor.PARENTHESES.attributes, "(") - type.typeList.joinTo(this) { + type.typeReferenceList.joinTo(this) { buildString { renderType(it) } diff --git a/src/main/kotlin/org/ton/intellij/func/ide/quickdoc/MarkdownNode.kt b/src/main/kotlin/org/ton/intellij/func/ide/quickdoc/MarkdownNode.kt index 1130775..be1701a 100644 --- a/src/main/kotlin/org/ton/intellij/func/ide/quickdoc/MarkdownNode.kt +++ b/src/main/kotlin/org/ton/intellij/func/ide/quickdoc/MarkdownNode.kt @@ -11,9 +11,12 @@ import com.intellij.openapi.editor.colors.EditorColorsManager import com.intellij.openapi.editor.colors.TextAttributesKey import com.intellij.openapi.editor.markup.TextAttributes import com.intellij.openapi.editor.richcopy.HtmlSyntaxInfoUtil +import com.intellij.openapi.fileTypes.FileTypeManager import com.intellij.openapi.project.Project import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.PsiElement +import com.intellij.util.applyIf +import com.intellij.xml.util.XmlStringUtil import org.intellij.markdown.IElementType import org.intellij.markdown.MarkdownElementTypes import org.intellij.markdown.MarkdownTokenTypes @@ -101,8 +104,28 @@ class MarkdownNode( MarkdownElementTypes.CODE_FENCE, -> { sb.trimEnd() - sb.append("
")
-                    processChildren()
+                    var language: Language = FuncLanguage
+                    val contents = StringBuilder()
+                    node.children.forEach { child ->
+                        when (child.type) {
+                            MarkdownTokenTypes.CODE_LINE, MarkdownTokenTypes.CODE_FENCE_CONTENT, MarkdownTokenTypes.EOL ->
+                                contents.append(child.text)
+
+                            MarkdownTokenTypes.FENCE_LANG -> {
+                                language = guessLanguage(child.text.trim().split(' ')[0]) ?: language
+                            }
+                        }
+                    }
+
+                    sb.append("
")
+                    sb.appendHighlightedCode(
+                        owner.project,
+                        language,
+                        DocumentationSettings.isHighlightingOfCodeBlocksEnabled(),
+                        contents,
+                        isForRenderedDoc = true,
+                        trim = true
+                    )
                     sb.append("
") } @@ -123,7 +146,6 @@ class MarkdownNode( if (owner is FuncFunction) { val resolved = FuncDocumentationProvider.resolve(label, owner) if (resolved != null) { - println("resolved = $resolved (${resolved.text})") val hyperlink = buildString { DocumentationManagerUtil.createHyperlink( this, @@ -326,11 +348,6 @@ private fun StringBuilder.appendStyledSpan( return this } -private fun guessLanguage(name: String): Language? { - return Language.findLanguageByID(name.lowercase()) - ?: Language.getRegisteredLanguages().firstOrNull { it.id.equals(name, ignoreCase = true) } -} - private fun getTargetLinkElementAttributes(key: TextAttributesKey): TextAttributes { return tuneAttributesForLink(EditorColorsManager.getInstance().globalScheme.getAttributes(key)) } @@ -357,3 +374,39 @@ private fun tuneAttributesForLink(attributes: TextAttributes): TextAttributes { } return attributes } + +private fun guessLanguage(language: String?): Language? = + if (language == null) + null + else + Language + .findInstancesByMimeType(language) + .asSequence() + .plus(Language.findInstancesByMimeType("text/$language")) + .plus( + Language.getRegisteredLanguages() + .asSequence() + .filter { languageMatches(language, it) } + ) + .firstOrNull() + +private fun languageMatches(langType: String, language: Language): Boolean = + langType.equals(language.id, ignoreCase = true) + || FileTypeManager.getInstance().getFileTypeByExtension(langType) === language.associatedFileType + +private fun StringBuilder.appendHighlightedCode( + project: Project, language: Language?, doHighlighting: Boolean, + code: CharSequence, isForRenderedDoc: Boolean, trim: Boolean +): StringBuilder { + val processedCode = code.toString().trim('\n', '\r').replace(' ', ' ') + .applyIf(trim) { trimEnd() } + if (language != null && doHighlighting) { + HtmlSyntaxInfoUtil.appendHighlightedByLexerAndEncodedAsHtmlCodeSnippet( + this, project, language, processedCode, + trim, DocumentationSettings.getHighlightingSaturation(isForRenderedDoc) + ) + } else { + append(XmlStringUtil.escapeString(processedCode.applyIf(trim) { trimIndent() })) + } + return this +} diff --git a/src/main/kotlin/org/ton/intellij/func/inspection/FuncFunctionCallInspection.kt b/src/main/kotlin/org/ton/intellij/func/inspection/FuncFunctionCallInspection.kt index 39d6763..1a43579 100644 --- a/src/main/kotlin/org/ton/intellij/func/inspection/FuncFunctionCallInspection.kt +++ b/src/main/kotlin/org/ton/intellij/func/inspection/FuncFunctionCallInspection.kt @@ -10,23 +10,26 @@ class FuncFunctionCallInspection : FuncInspectionBase() { holder: ProblemsHolder, session: LocalInspectionToolSession, ) = object : FuncVisitor() { - override fun visitCallExpression(o: FuncCallExpression) { - super.visitCallExpression(o) - val function = o.referenceExpression.reference?.resolve() as? FuncFunction ?: return - holder.check(function, o.callArgument, false) - } - - override fun visitMethodCall(o: FuncMethodCall) { - super.visitMethodCall(o) - val function = o.referenceExpression.reference?.resolve() as? FuncFunction ?: return - holder.check(function, o.callArgument ?: return, true) - } +// override fun visitSpecialApplyExpression(o: FuncSpecialApplyExpression) { +// super.visitSpecialApplyExpression(o) +// val function = o.left.reference?.resolve() as? FuncFunction ?: return +// holder.check(function, o.right ?: return, true) +// } +// +// override fun visitApplyExpression(o: FuncApplyExpression) { +// super.visitApplyExpression(o) +// val function = o.left.reference?.resolve() as? FuncFunction ?: return +// holder.check(function, o.right ?: return, false) +// } } - private fun ProblemsHolder.check(function: FuncFunction, argument: FuncCallArgument, isMethodCall: Boolean) { - val arguments = argument.expression.let { - if (it is FuncTensorExpression) it.expressionList - else listOf(it) + private fun ProblemsHolder.check(function: FuncFunction, argument: FuncExpression, isMethodCall: Boolean) { + val arguments = argument.let { + when (it) { + is FuncTensorExpression -> it.expressionList + is FuncUnitExpression -> emptyList() + else -> listOf(it) + } } val parameters = function.functionParameterList diff --git a/src/main/kotlin/org/ton/intellij/func/inspection/FuncImpureFunctionInspection.kt b/src/main/kotlin/org/ton/intellij/func/inspection/FuncImpureFunctionInspection.kt new file mode 100644 index 0000000..7c43841 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/inspection/FuncImpureFunctionInspection.kt @@ -0,0 +1,25 @@ +package org.ton.intellij.func.inspection + +import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.codeInspection.ProblemsHolder +import org.ton.intellij.func.psi.FuncApplyExpression +import org.ton.intellij.func.psi.FuncFunction +import org.ton.intellij.func.psi.FuncVisitor + +class FuncImpureFunctionInspection : FuncInspectionBase() { + override fun buildFuncVisitor(holder: ProblemsHolder, session: LocalInspectionToolSession): FuncVisitor { + return object : FuncVisitor() { + var currentFunction: FuncFunction? = null + + override fun visitFunction(o: FuncFunction) { + currentFunction = o + super.visitFunction(o) + } + + override fun visitApplyExpression(o: FuncApplyExpression) { + println("Apply: ${o.text} in ${currentFunction?.name}") + super.visitApplyExpression(o) + } + } + } +} diff --git a/src/main/kotlin/org/ton/intellij/func/inspection/FuncMissingReturnInspection.kt b/src/main/kotlin/org/ton/intellij/func/inspection/FuncMissingReturnInspection.kt index c84b685..7ace3ff 100644 --- a/src/main/kotlin/org/ton/intellij/func/inspection/FuncMissingReturnInspection.kt +++ b/src/main/kotlin/org/ton/intellij/func/inspection/FuncMissingReturnInspection.kt @@ -15,28 +15,32 @@ class FuncMissingReturnInspection : FuncInspectionBase() { } private fun check(function: FuncFunction, holder: ProblemsHolder) { - val block = function.blockStatement ?: return - val atomicType = function.type as? FuncAtomicType ?: return - val isVoid = atomicType is FuncHoleType || (atomicType as? FuncTensorType)?.typeList?.isEmpty() == true - if (isVoid || isTerminating(block)) return - val brace = block.rBrace - holder.registerProblem(brace ?: block, "Missing return at end of function") + // TODO: fix after type system is implemented +// val block = function.blockStatement ?: return +// val atomicType = function.typeReference as? FuncAtomicType ?: return +// val isVoid = atomicType is FuncHoleType || (atomicType as? FuncTensorType)?.typeReferenceList?.isEmpty() == true +// if (isVoid) return +// holder.isTerminating(block) } - private fun isTerminating(element: FuncElement?): Boolean { + private fun ProblemsHolder.isTerminating(element: FuncElement?): Boolean { ProgressIndicatorProvider.checkCanceled() return when (element) { null -> false is FuncReturnStatement -> true - is FuncBlockStatement -> isTerminating(element.statementList.lastOrNull()) - is FuncIfStatement -> isTerminating(element.blockStatement) && isTerminating( - element.`else` ?: element.elseIf - ) - - is FuncElse -> isTerminating(element.blockStatement) - is FuncElseIf -> - isTerminating(element.blockStatement) && isTerminating(element.`else` ?: element.elseIf) + is FuncBlockStatement -> { + val result = isTerminating(element.statementList.lastOrNull()) + if (!result) { + val brace = element.rBrace + registerProblem(brace ?: element, "Missing `return`") + } + result + } + is FuncIfStatement -> isTerminating(element.blockStatement) && isTerminating(element.elseBranch) + is FuncElseBranch -> isTerminating(element.statement) + is FuncTryStatement -> isTerminating(element.blockStatement) && isTerminating(element.catch) + is FuncCatch -> isTerminating(element.blockStatement) else -> false } } diff --git a/src/main/kotlin/org/ton/intellij/func/inspection/FuncReplaceGuardClauseWithFunctionCallInspection.kt b/src/main/kotlin/org/ton/intellij/func/inspection/FuncReplaceGuardClauseWithFunctionCallInspection.kt index 9286f25..6d9a15e 100644 --- a/src/main/kotlin/org/ton/intellij/func/inspection/FuncReplaceGuardClauseWithFunctionCallInspection.kt +++ b/src/main/kotlin/org/ton/intellij/func/inspection/FuncReplaceGuardClauseWithFunctionCallInspection.kt @@ -2,9 +2,7 @@ package org.ton.intellij.func.inspection import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project -import com.intellij.refactoring.suggested.startOffset -import org.ton.intellij.func.psi.* -import org.ton.intellij.func.psi.impl.collectArguments +import org.ton.intellij.func.psi.FuncIfStatement class FuncReplaceGuardClauseWithFunctionCallInspection : FuncAbstractApplicabilityBasedInspection( FuncIfStatement::class.java @@ -62,77 +60,82 @@ class FuncReplaceGuardClauseWithFunctionCallInspection : FuncAbstractApplicabili else -> super.fixText(element) } + // override fun inspectionText(element: FuncIfStatement): String = "Replace guard clause with function call" override fun isApplicable(element: FuncIfStatement): Boolean { - element.condition ?: return false - if (element.`else` != null || element.elseIf != null) { - return false // TODO: support else/elseif block's statements insert into current scope - } - val call = element.getCallExpression() ?: return false - val calleeText = call.referenceExpression.text - if (calleeText != THROW_FUNCTION) { - return false - } - val arguments = call.callArgument.collectArguments() - return arguments.size <= 1 + return false } + // element.condition ?: return false +// if (element.elseBranch != null || element.elseIfBranch != null) { +// return false // TODO: support else/elseIfBranch block's statements insert into current scope +// } +// val call = element.getCallExpression() ?: return false +// val calleeText = call.referenceExpression.text +// if (calleeText != THROW_FUNCTION) { +// return false +// } +// val arguments = call.callArgument.collectArguments() +// return arguments.size <= 1 +// } +// override fun applyTo(element: FuncIfStatement, project: Project, editor: Editor?) { - if (element.`else` != null || element.elseIf != null) { - return - } - - val condition = element.condition ?: return - val call = element.getCallExpression() ?: return - val argument = call.callArgument.collectArguments().firstOrNull()?.text ?: return - - val psiFactory = FuncPsiFactory[project] - - val newExpression = if (element.ifnotKeyword != null) { - psiFactory.createStatement( - "throw_unless(${ - argument.removeSurrounding( - "(", - ")" - ) - }, ${condition.text.removeSurrounding("(", ")")});" - ) - } else { - psiFactory.createStatement( - "throw_if(${ - argument.removeSurrounding( - "(", - ")" - ) - }, ${condition.text.removeSurrounding("(", ")")});" - ) - } - - val replaced = element.replaceWith(newExpression, psiFactory) - editor?.caretModel?.moveToOffset(replaced.startOffset) - } - - private fun FuncIfStatement.replaceWith(newExpression: FuncStatement, psiFactory: FuncPsiFactory): FuncStatement { - val parent = parent - val elseBranch = `else` - val elseIfBranch = elseIf - - if (elseBranch != null || elseIfBranch != null) { - TODO() - } else { - return replace(newExpression) as FuncStatement - } - } - - private fun FuncIfStatement.getCallExpression(): FuncCallExpression? { - val expression = blockStatement?.let { - it.statementList.firstOrNull() as? FuncExpressionStatement - } ?: return null - return expression.expression.let { - it as? FuncCallExpression - } } +// if (element.elseBranch != null || element.elseIfBranch != null) { +// return +// } +// +// val condition = element.condition ?: return +// val call = element.getCallExpression() ?: return +// val argument = call.callArgument.collectArguments().firstOrNull()?.text ?: return +// +// val psiFactory = FuncPsiFactory[project] +// +// val newExpression = if (element.ifnotKeyword != null) { +// psiFactory.createStatement( +// "throw_unless(${ +// argument.removeSurrounding( +// "(", +// ")" +// ) +// }, ${condition.text.removeSurrounding("(", ")")});" +// ) +// } else { +// psiFactory.createStatement( +// "throw_if(${ +// argument.removeSurrounding( +// "(", +// ")" +// ) +// }, ${condition.text.removeSurrounding("(", ")")});" +// ) +// } +// +// val replaced = element.replaceWith(newExpression, psiFactory) +// editor?.caretModel?.moveToOffset(replaced.startOffset) +// } +// +// private fun FuncIfStatement.replaceWith(newExpression: FuncStatement, psiFactory: FuncPsiFactory): FuncStatement { +// val parent = parent +// val elseBranch = elseBranch +// val elseIfBranchBranch = elseIfBranch +// +// if (elseBranch != null || elseIfBranchBranch != null) { +// TODO() +// } else { +// return replace(newExpression) as FuncStatement +// } +// } +// +// private fun FuncIfStatement.getCallExpression(): FuncCallExpression? { +// val expression = blockStatement?.let { +// it.statementList.firstOrNull() as? FuncExpressionStatement +// } ?: return null +// return expression.expression.let { +// it as? FuncCallExpression +// } +// } companion object { private const val THROW_FUNCTION = "throw" diff --git a/src/main/kotlin/org/ton/intellij/func/inspection/FuncUnresolvedReferenceInspection.kt b/src/main/kotlin/org/ton/intellij/func/inspection/FuncUnresolvedReferenceInspection.kt index 729f70a..c79bddf 100644 --- a/src/main/kotlin/org/ton/intellij/func/inspection/FuncUnresolvedReferenceInspection.kt +++ b/src/main/kotlin/org/ton/intellij/func/inspection/FuncUnresolvedReferenceInspection.kt @@ -6,7 +6,6 @@ import com.intellij.codeInspection.ProblemsHolder import com.intellij.openapi.util.TextRange import org.ton.intellij.func.psi.FuncReferenceExpression import org.ton.intellij.func.psi.FuncVisitor -import org.ton.intellij.func.psi.impl.FuncReferenceExpressionImpl class FuncUnresolvedReferenceInspection : FuncInspectionBase() { override fun buildFuncVisitor( @@ -15,19 +14,18 @@ class FuncUnresolvedReferenceInspection : FuncInspectionBase() { ): FuncVisitor = object : FuncVisitor() { override fun visitReferenceExpression(o: FuncReferenceExpression) { super.visitReferenceExpression(o) - if (o !is FuncReferenceExpressionImpl) return val reference = o.reference ?: return + val resolved = reference.resolve() + if (resolved != null) return - if (reference.resolve() == null) { - val id = o.identifier - val range = TextRange.from(id.startOffsetInParent, id.textLength) - holder.registerProblem( - o, - "Unresolved reference #ref", - ProblemHighlightType.LIKE_UNKNOWN_SYMBOL, - range - ) - } + val id = o.identifier + val range = TextRange.from(id.startOffsetInParent, id.textLength) + holder.registerProblem( + o, + "Unresolved reference #ref", + ProblemHighlightType.LIKE_UNKNOWN_SYMBOL, + range + ) } } } diff --git a/src/main/kotlin/org/ton/intellij/func/inspection/FuncUnusedFunctionInspection.kt b/src/main/kotlin/org/ton/intellij/func/inspection/FuncUnusedFunctionInspection.kt index 9d85786..8f1a9e2 100644 --- a/src/main/kotlin/org/ton/intellij/func/inspection/FuncUnusedFunctionInspection.kt +++ b/src/main/kotlin/org/ton/intellij/func/inspection/FuncUnusedFunctionInspection.kt @@ -21,7 +21,8 @@ class FuncUnusedFunctionInspection : FuncInspectionBase() { if (name == "recv_internal") return if (name == "recv_external") return if (name == "run_ticktock") return - if (o.containingFile.name == "stdlib.fc") return + val fileName = o.containingFile.name + if (fileName == "stdlib.fc" || fileName == "stdlib.func") return if (ReferencesSearch.search(o, o.useScope).findFirst() == null) { val id = o.identifier val range = TextRange.from(id.startOffsetInParent, id.textLength) diff --git a/src/main/kotlin/org/ton/intellij/func/inspection/FuncUnusedVariableInspection.kt b/src/main/kotlin/org/ton/intellij/func/inspection/FuncUnusedVariableInspection.kt index 1d397d5..2677c48 100644 --- a/src/main/kotlin/org/ton/intellij/func/inspection/FuncUnusedVariableInspection.kt +++ b/src/main/kotlin/org/ton/intellij/func/inspection/FuncUnusedVariableInspection.kt @@ -1,49 +1,53 @@ package org.ton.intellij.func.inspection import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.codeInspection.LocalQuickFix import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder -import com.intellij.openapi.progress.ProgressIndicatorProvider import com.intellij.openapi.util.TextRange import com.intellij.psi.search.searches.ReferencesSearch +import org.ton.intellij.func.ide.fixes.RemoveElementFix +import org.ton.intellij.func.ide.fixes.RenameUnderscoreFix import org.ton.intellij.func.psi.* -import org.ton.intellij.func.psi.impl.right +import org.ton.intellij.func.psi.impl.isVariableDefinition +import org.ton.intellij.util.ancestorStrict class FuncUnusedVariableInspection : FuncInspectionBase() { override fun buildFuncVisitor( holder: ProblemsHolder, session: LocalInspectionToolSession, ): FuncVisitor = object : FuncVisitor() { - override fun visitVarExpression(o: FuncVarExpression) { - val right = o.right ?: return - if (right is FuncReferenceExpression) { - processVarExpression(right, holder) - } - if (right is FuncTensorExpression) { - for (tensorElement in right.expressionList) { - ProgressIndicatorProvider.checkCanceled() - when (tensorElement) { - is FuncVarExpression -> visitVarExpression(tensorElement) - is FuncReferenceExpression -> processVarExpression(tensorElement, holder) + override fun visitReferenceExpression(o: FuncReferenceExpression) { + super.visitReferenceExpression(o) + if (!o.isVariableDefinition()) return + val id = o.identifier + if (id.text == "_") return + if (ReferencesSearch.search(o, o.useScope).findFirst() == null) { + val range = TextRange.from(id.startOffsetInParent, id.textLength) + val parent = o.parent + val fixes = mutableListOf() + + if (parent is FuncTensorExpression || parent is FuncTupleExpression) { + fixes.add(RenameUnderscoreFix(o)) + } + if (parent is FuncApplyExpression) { + val grandParent = parent.parent + if (grandParent is FuncBinExpression && grandParent.left == parent) { + val stmt = grandParent.ancestorStrict() + if (stmt != null) { + fixes.add(RemoveElementFix(stmt)) + } } } - } - } - } - private fun processVarExpression( - element: FuncNamedElement, - holder: ProblemsHolder, - ) { - val id = element.identifier ?: return - if (ReferencesSearch.search(element, element.useScope).findFirst() == null) { - val range = TextRange.from(id.startOffsetInParent, id.textLength) - holder.registerProblem( - element, - "Unused variable #ref #loc", - ProblemHighlightType.LIKE_UNUSED_SYMBOL, - range - ) + holder.registerProblem( + o, + "Unused variable #ref #loc", + ProblemHighlightType.LIKE_UNUSED_SYMBOL, + range, + *fixes.toTypedArray() + ) + } } } } diff --git a/src/main/kotlin/org/ton/intellij/func/parser/FuncParserDefinition.kt b/src/main/kotlin/org/ton/intellij/func/parser/FuncParserDefinition.kt index 44274ac..3804c24 100644 --- a/src/main/kotlin/org/ton/intellij/func/parser/FuncParserDefinition.kt +++ b/src/main/kotlin/org/ton/intellij/func/parser/FuncParserDefinition.kt @@ -45,7 +45,6 @@ class FuncParserDefinition : ParserDefinition { @JvmField val EOL_DOC_COMMENT = FuncDocCommentElementType("") - val MACRO = TokenSet.create(FuncElementTypes.INCLUDE_MACRO, FuncElementTypes.PRAGMA_MACRO) val PRIMITIVE_TYPES = TokenSet.create( FuncElementTypes.INT_KEYWORD, FuncElementTypes.CELL_KEYWORD, diff --git a/src/main/kotlin/org/ton/intellij/func/parser/FuncParserUtil.kt b/src/main/kotlin/org/ton/intellij/func/parser/FuncParserUtil.kt index bc5f2d1..a301b87 100644 --- a/src/main/kotlin/org/ton/intellij/func/parser/FuncParserUtil.kt +++ b/src/main/kotlin/org/ton/intellij/func/parser/FuncParserUtil.kt @@ -1,11 +1,13 @@ package org.ton.intellij.func.parser +import com.intellij.lang.PsiBuilder import com.intellij.lang.WhitespacesAndCommentsBinder import com.intellij.lang.parser.GeneratedParserUtilBase import com.intellij.psi.TokenType import org.ton.intellij.func.parser.FuncParserDefinition.Companion.BLOCK_DOC_COMMENT import org.ton.intellij.func.parser.FuncParserDefinition.Companion.EOL_COMMENT import org.ton.intellij.func.parser.FuncParserDefinition.Companion.EOL_DOC_COMMENT +import org.ton.intellij.func.psi.FuncElementTypes object FuncParserUtil : GeneratedParserUtilBase() { @JvmField @@ -26,4 +28,20 @@ object FuncParserUtil : GeneratedParserUtilBase() { } candidate } + + @JvmStatic + fun isSpecialIdentifier(b: PsiBuilder, level: Int): Boolean { + return b.tokenType == FuncElementTypes.IDENTIFIER && when (b.tokenText?.firstOrNull()) { + '~', '.' -> true + else -> false + } + } + + @JvmStatic + fun isRegularIdentifier(b: PsiBuilder, level: Int): Boolean { + return b.tokenType == FuncElementTypes.IDENTIFIER && when (b.tokenText?.firstOrNull()) { + '~', '.' -> false + else -> true + } + } } diff --git a/src/main/kotlin/org/ton/intellij/func/psi/FuncElement.kt b/src/main/kotlin/org/ton/intellij/func/psi/FuncElement.kt index 8df85e4..29513e7 100644 --- a/src/main/kotlin/org/ton/intellij/func/psi/FuncElement.kt +++ b/src/main/kotlin/org/ton/intellij/func/psi/FuncElement.kt @@ -1,7 +1,18 @@ package org.ton.intellij.func.psi import com.intellij.psi.PsiElement +import org.ton.intellij.func.type.infer.FuncInferenceResult +import org.ton.intellij.util.contexts +import org.ton.intellij.util.withNext -interface FuncElement : PsiElement { +interface FuncElement : PsiElement -} +val FuncElement.inferenceContextOwner: FuncInferenceContextOwner? + get() = contexts + .withNext() + .find { (it, next) -> + next != null && it is FuncInferenceContextOwner + }?.first as? FuncInferenceContextOwner + +val FuncElement.inference: FuncInferenceResult? + get() = inferenceContextOwner?.selfInferenceResult diff --git a/src/main/kotlin/org/ton/intellij/func/psi/FuncFile.kt b/src/main/kotlin/org/ton/intellij/func/psi/FuncFile.kt index 3157eec..9bae0ca 100644 --- a/src/main/kotlin/org/ton/intellij/func/psi/FuncFile.kt +++ b/src/main/kotlin/org/ton/intellij/func/psi/FuncFile.kt @@ -2,6 +2,7 @@ package org.ton.intellij.func.psi import com.intellij.extapi.psi.PsiFileBase import com.intellij.openapi.fileTypes.FileType +import com.intellij.openapi.vfs.VfsUtil import com.intellij.psi.FileViewProvider import com.intellij.psi.PsiElement import com.intellij.psi.stubs.StubElement @@ -16,12 +17,39 @@ import org.ton.intellij.func.stub.type.FuncConstVarStubElementType import org.ton.intellij.func.stub.type.FuncFunctionStubElementType import org.ton.intellij.func.stub.type.FuncGlobalVarStubElementType import org.ton.intellij.func.stub.type.FuncIncludeDefinitionStubElementType +import org.ton.intellij.util.recursionGuard -class FuncFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, FuncLanguage) { +//private fun processFile(context: FuncElement, file: FuncFile) { +// recursionGuard(file, { +// for (includeDefinition in file.includeDefinitions) { +// val nextFile = includeDefinition.reference?.resolve() +// if (nextFile !is FuncFile) continue +// processFile(context, nextFile) +// } + +class FuncFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, FuncLanguage), FuncElement { override fun getFileType(): FileType = FuncFileType override fun getStub(): FuncFileStub? = super.getStub() as? FuncFileStub + fun collectIncludedFiles(includeSelf: Boolean = true): Set { + return collectIncludedFiles(LinkedHashSet(), includeSelf) + } + + private fun collectIncludedFiles(collection: MutableSet, includeSelf: Boolean): MutableSet { + recursionGuard(this, { + for (includeDefinition in includeDefinitions) { + val nextFile = includeDefinition.reference?.resolve() + if (nextFile !is FuncFile) continue + nextFile.collectIncludedFiles(collection, true) + } + if (includeSelf) { + collection.add(this) + } + }, false) + return collection + } + val includeDefinitions: List get() = CachedValuesManager.getCachedValue(this) { val stub = stub @@ -72,9 +100,58 @@ class FuncFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, FuncL CachedValueProvider.Result.create(constVars, this) } + fun import(file: FuncFile) { + if (file == this) return + val path = VfsUtil.findRelativePath(virtualFile ?: return, file.virtualFile ?: return, '/') ?: return + val needImport = includeDefinitions.none { it.reference?.resolve() == file } + if (!needImport) return + + val factory = FuncPsiFactory[project] + val newInclude = factory.createIncludeDefinition(path) + + tryIncludeAtCorrectLocation(newInclude) + } + + private fun tryIncludeAtCorrectLocation(includeDefinition: FuncIncludeDefinition) { + val newline = FuncPsiFactory[project].createNewline() + val includes = includeDefinitions + if (includes.isEmpty()) { + addBefore(includeDefinition, firstChild) + addAfter(newline, firstChild) + return + } + + val (less, greater) = includes.partition { INCLUDE_COMPARE.compare(it, includeDefinition) < 0 } + val anchorBefore = less.lastOrNull() + val anchorAfter = greater.firstOrNull() + when { + anchorBefore != null -> { + val addedItem = addAfter(includeDefinition, anchorBefore) + addBefore(newline, addedItem) + } + + anchorAfter != null -> { + val addedItem = addBefore(includeDefinition, anchorAfter) + addAfter(newline, addedItem) + } + + else -> error("unreachable") + } + } + override fun toString(): String = "FuncFile($name)" } +private val INCLUDE_COMPARE: Comparator = + compareBy( + { + it.stringLiteral?.rawString?.text?.count { c -> c == '/' } + }, + { + it.stringLiteral?.rawString?.text?.lowercase() + } + ) + private fun getChildrenByType( stub: StubElement, elementType: IElementType, diff --git a/src/main/kotlin/org/ton/intellij/func/psi/FuncInferenceContextOwner.kt b/src/main/kotlin/org/ton/intellij/func/psi/FuncInferenceContextOwner.kt new file mode 100644 index 0000000..8696c03 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/psi/FuncInferenceContextOwner.kt @@ -0,0 +1,21 @@ +package org.ton.intellij.func.psi + +import com.intellij.openapi.util.Key +import com.intellij.psi.util.CachedValue +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValuesManager +import com.intellij.psi.util.PsiModificationTracker +import org.ton.intellij.func.type.infer.FuncInferenceResult +import org.ton.intellij.func.type.infer.inferTypesIn + +interface FuncInferenceContextOwner : FuncElement + +private val FUNC_INFERENCE_KEY: Key> = Key.create("FUNC_INFERENCE_KEY") + +val FuncInferenceContextOwner.selfInferenceResult: FuncInferenceResult + get() { + return CachedValuesManager.getCachedValue(this, FUNC_INFERENCE_KEY) { + val inferred = inferTypesIn(this) + CachedValueProvider.Result.create(inferred, PsiModificationTracker.MODIFICATION_COUNT) + } + } diff --git a/src/main/kotlin/org/ton/intellij/func/psi/FuncPsiFactory.kt b/src/main/kotlin/org/ton/intellij/func/psi/FuncPsiFactory.kt index be94a73..7e8f714 100644 --- a/src/main/kotlin/org/ton/intellij/func/psi/FuncPsiFactory.kt +++ b/src/main/kotlin/org/ton/intellij/func/psi/FuncPsiFactory.kt @@ -4,13 +4,14 @@ import com.intellij.openapi.components.Service import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.PsiFileFactory -import org.intellij.lang.annotations.Language +import com.intellij.psi.PsiParserFacade import org.ton.intellij.func.FuncLanguage +import org.ton.intellij.util.descendantOfTypeStrict @Service(Service.Level.PROJECT) class FuncPsiFactory private constructor(val project: Project) { - val builtinStdlibFile by lazy { - createFileFromText( + val builtinFile by lazy { + createFile( "builtin.fc", """ int _+_(int x, int y) asm "ADD"; @@ -162,14 +163,19 @@ class FuncPsiFactory private constructor(val project: Project) { } } - fun createFileFromText(@Language("FunC") text: String) = - createFileFromText(null, text) + fun createFile(text: CharSequence) = + createFile(null, text) - fun createFileFromText(name: String?, @Language("FunC") text: String) = + fun createFile(name: String?, text: CharSequence) = PsiFileFactory.getInstance(project).createFileFromText(name ?: "dummy.fc", FuncLanguage, text) as FuncFile + fun createNewline(): PsiElement = createWhitespace("\n") + + fun createWhitespace(ws: String): PsiElement = + PsiParserFacade.getInstance(project).createWhiteSpaceFromText(ws) + fun createStatement(text: String): FuncStatement { - val file = createFileFromText("() foo() { $text }") + val file = createFile("() foo() { $text }") return file.functions.first().blockStatement!!.statementList.first() } @@ -177,12 +183,20 @@ class FuncPsiFactory private constructor(val project: Project) { return (createStatement("$text;") as FuncExpressionStatement).expression } - fun createIdentifierFromText(text: String): PsiElement { - val funcFile = createFileFromText("() $text();") + fun createIdentifier(text: String): PsiElement { + val funcFile = createFile("() $text() {}") val function = funcFile.functions.first() return function.identifier } + fun createIncludeDefinition(text: String): FuncIncludeDefinition = + createFromText("#include \"$text\";") + ?: error("Failed to create include definition from text: `$text`") + + private inline fun createFromText( + code: CharSequence + ): T? = createFile(code).descendantOfTypeStrict() + companion object { operator fun get(project: Project) = requireNotNull(project.getService(FuncPsiFactory::class.java)) diff --git a/src/main/kotlin/org/ton/intellij/func/psi/FuncPsiManager.kt b/src/main/kotlin/org/ton/intellij/func/psi/FuncPsiManager.kt new file mode 100644 index 0000000..f4bdaa9 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/psi/FuncPsiManager.kt @@ -0,0 +1,104 @@ +package org.ton.intellij.func.psi + +import com.intellij.openapi.util.ModificationTracker +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.util.messages.MessageBusConnection +import com.intellij.util.messages.Topic + +/** Don't subscribe directly or via plugin.xml lazy listeners. Use [FuncPsiManager.subscribeFuncStructureChange] */ +private val FUNC_STRUCTURE_CHANGE_TOPIC: Topic = Topic.create( + "FUNC_STRUCTURE_CHANGE_TOPIC", + FuncStructureChangeListener::class.java, + Topic.BroadcastDirection.TO_PARENT +) + +interface FuncPsiManager { + /** + * A project-global modification tracker that increments on each PSI change that can affect + * name resolution or type inference. It will be incremented with a change of most types of + * PSI element excluding function bodies (expressions and statements) + */ + val funcStructureModificationTracker: ModificationTracker + + /** This is an instance method because [FuncPsiManager] should be created prior to event subscription */ + fun subscribeFuncStructureChange(connection: MessageBusConnection, listener: FuncStructureChangeListener) { + connection.subscribe(FUNC_STRUCTURE_CHANGE_TOPIC, listener) + } +} + +interface FuncStructureChangeListener { + fun funcStructureChanged(file: PsiFile?, changedElement: PsiElement?) +} + +//class FuncPsiManagerImpl(val project: Project) : FuncPsiManager, Disposable { +// override val funcStructureModificationTracker = SimpleModificationTracker() +// +// init { +// PsiManager.getInstance(project).addPsiTreeChangeListener() +// } +// +// override fun dispose() { +// } +// +// private fun incFuncStructureModificationCount(file: PsiFile? = null, psiElement: PsiElement? = null) { +// funcStructureModificationTracker.incModificationCount() +// project.messageBus.syncPublisher(FUNC_STRUCTURE_CHANGE_TOPIC).funcStructureChanged(file, psiElement) +// } +// +// inner class CacheInvalidator : PsiTreeChangeListener { +// override fun beforeChildAddition(event: PsiTreeChangeEvent) { +// +// } +// +// override fun beforeChildRemoval(event: PsiTreeChangeEvent) { +// } +// +// override fun beforeChildReplacement(event: PsiTreeChangeEvent) { +// } +// +// override fun beforeChildMovement(event: PsiTreeChangeEvent) { +// } +// +// override fun beforeChildrenChange(event: PsiTreeChangeEvent) { +// } +// +// override fun beforePropertyChange(event: PsiTreeChangeEvent) { +// } +// +// override fun childAdded(event: PsiTreeChangeEvent) { +// } +// +// override fun childRemoved(event: PsiTreeChangeEvent) { +// } +// +// override fun childReplaced(event: PsiTreeChangeEvent) { +// } +// +// override fun childrenChanged(event: PsiTreeChangeEvent) { +// } +// +// override fun childMoved(event: PsiTreeChangeEvent) { +// } +// +// override fun propertyChanged(event: PsiTreeChangeEvent) { +// } +// +// fun filterEvent(file: PsiFile?, element: PsiElement, isChildrenChange: Boolean) { +// // if file is null, this is an event about VFS changes +// if (file == null) { +// if (element is FuncFile) { +// val funcFile = element as? FuncFile +// incFuncStructureModificationCount(funcFile, funcFile) +// } +// } else { +// if (file.fileType != FuncFileType) return +// val isWhitespaceOrComment = element is PsiComment || element is PsiWhiteSpace +// if (isWhitespaceOrComment) return +// +// val owner = if (DumbService.isDumb(project)) null else element.findModificationTrackerOwner(!isChildrenChange) +// +// } +// } +// } +//} diff --git a/src/main/kotlin/org/ton/intellij/func/psi/FuncPsiPattern.kt b/src/main/kotlin/org/ton/intellij/func/psi/FuncPsiPattern.kt new file mode 100644 index 0000000..ed49990 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/psi/FuncPsiPattern.kt @@ -0,0 +1,44 @@ +package org.ton.intellij.func.psi + +import com.intellij.patterns.PatternCondition +import com.intellij.patterns.PlatformPatterns +import com.intellij.patterns.PsiElementPattern +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiWhiteSpace +import com.intellij.util.ProcessingContext +import org.ton.intellij.util.prevVisibleOrNewLine +import org.ton.intellij.util.psiElement + +object FuncPsiPattern { + fun baseDeclarationPattern(): PsiElementPattern.Capture = + psiElement() + .withParent(psiElement()) + + fun onStatementBeginning(vararg startWords: String): PsiElementPattern.Capture = + PlatformPatterns.psiElement().with(OnStatementBeginning(*startWords)) + + private fun identifierStatementBeginningPattern(vararg startWords: String) = + PlatformPatterns.psiElement(FuncElementTypes.IDENTIFIER).and(onStatementBeginning(*startWords)) + + fun macroPattern(): PsiElementPattern.Capture = + baseDeclarationPattern().and(identifierStatementBeginningPattern("#")) + + private class OnStatementBeginning(vararg startWords: String) : + PatternCondition("onStatementBeginning") { + private val _startWords = startWords + + override fun accepts(t: PsiElement, context: ProcessingContext?): Boolean { + val prev = t.prevVisibleOrNewLine + return if (_startWords.isEmpty()) + prev == null || prev is PsiWhiteSpace + else + prev != null && prev.node.text in _startWords + } + } + + private class TestPattern() : PatternCondition("testPattern") { + override fun accepts(t: PsiElement, context: ProcessingContext?): Boolean { + return true + } + } +} diff --git a/src/main/kotlin/org/ton/intellij/func/psi/FuncPsiUtil.kt b/src/main/kotlin/org/ton/intellij/func/psi/FuncPsiUtil.kt index 6c266dc..1828303 100644 --- a/src/main/kotlin/org/ton/intellij/func/psi/FuncPsiUtil.kt +++ b/src/main/kotlin/org/ton/intellij/func/psi/FuncPsiUtil.kt @@ -17,13 +17,3 @@ object FuncPsiUtil { return referenceFile == null || referenceFile.parent == declarationFile.parent } } - -val FuncCallExpression.isQualified: Boolean - get() { -// if (tilde != null) return true - val parent = parent -// if (parent !is FuncQualifiedExpression) return false -// val expressionList = parent.expressionList -// return expressionList.size == 2 && expressionList.last() == this - return false - } diff --git a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncAsmArgumentMixin.kt b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncAsmArgumentMixin.kt index f55f093..420d91a 100644 --- a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncAsmArgumentMixin.kt +++ b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncAsmArgumentMixin.kt @@ -37,7 +37,7 @@ class FuncAsmArgumentReference(element: FuncAsmArgument) : } override fun handleElementRename(newElementName: String): PsiElement { - myElement.identifier.replace(FuncPsiFactory[myElement.project].createIdentifierFromText(newElementName)) + myElement.identifier.replace(FuncPsiFactory[myElement.project].createIdentifier(newElementName)) return myElement } } diff --git a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncCachedReference.kt b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncCachedReference.kt index 9736569..d82b25d 100644 --- a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncCachedReference.kt +++ b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncCachedReference.kt @@ -23,7 +23,7 @@ abstract class FuncCachedReference( protected abstract fun resolveInner(): PsiElement? override fun handleElementRename(newElementName: String): PsiElement { - myElement.replace(FuncPsiFactory[myElement.project].createIdentifierFromText(newElementName)) + myElement.replace(FuncPsiFactory[myElement.project].createIdentifier(newElementName)) return myElement } diff --git a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncCallArgument.kt b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncCallArgument.kt index f2d2aba..1d760a5 100644 --- a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncCallArgument.kt +++ b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncCallArgument.kt @@ -1,14 +1 @@ package org.ton.intellij.func.psi.impl - -import org.ton.intellij.func.psi.FuncCallArgument -import org.ton.intellij.func.psi.FuncExpression -import org.ton.intellij.func.psi.FuncTupleExpression - -fun FuncCallArgument.collectArguments(): List { - val expression = expression - return if (expression is FuncTupleExpression) { - expression.expressionList - } else { - listOf(expression) - } -} diff --git a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncConstVarMixin.kt b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncConstVarMixin.kt index b813209..85857c9 100644 --- a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncConstVarMixin.kt +++ b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncConstVarMixin.kt @@ -2,11 +2,17 @@ package org.ton.intellij.func.psi.impl import com.intellij.lang.ASTNode import com.intellij.psi.stubs.IStubElementType +import org.ton.intellij.func.FuncIcons import org.ton.intellij.func.psi.FuncConstVar import org.ton.intellij.func.stub.FuncConstVarStub +import javax.swing.Icon abstract class FuncConstVarMixin : FuncNamedElementImpl, FuncConstVar { constructor(node: ASTNode) : super(node) constructor(stub: FuncConstVarStub, stubType: IStubElementType<*, *>) : super(stub, stubType) + + override fun getIcon(flags: Int): Icon { + return FuncIcons.CONSTANT + } } diff --git a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncFunctionImpl.kt b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncFunctionImpl.kt index 4892d9f..2d89e3b 100644 --- a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncFunctionImpl.kt +++ b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncFunctionImpl.kt @@ -2,14 +2,26 @@ package org.ton.intellij.func.psi.impl import com.intellij.lang.ASTNode import com.intellij.psi.stubs.IStubElementType +import org.ton.intellij.func.FuncIcons import org.ton.intellij.func.psi.FuncElementTypes import org.ton.intellij.func.psi.FuncFunction import org.ton.intellij.func.stub.FuncFunctionStub +import org.ton.intellij.func.type.ty.FuncTy +import org.ton.intellij.func.type.ty.FuncTyMap +import org.ton.intellij.func.type.ty.FuncTyUnknown +import org.ton.intellij.func.type.ty.rawType +import javax.swing.Icon abstract class FuncFunctionMixin : FuncNamedElementImpl, FuncFunction { constructor(node: ASTNode) : super(node) constructor(stub: FuncFunctionStub, stubType: IStubElementType<*, *>) : super(stub, stubType) + + override fun getIcon(flags: Int): Icon? { + return FuncIcons.FUNCTION + } + + override fun toString(): String = "FuncFunction($containingFile - $name)" } val FuncFunction.isImpure: Boolean @@ -23,3 +35,17 @@ val FuncFunction.hasMethodId: Boolean val FuncFunction.hasAsm: Boolean get() = stub?.hasAsm ?: (asmDefinition != null) + +val FuncFunction.rawReturnType: FuncTy + get() = typeReference.rawType + +val FuncFunction.rawParamType: FuncTy + get() = FuncTy(functionParameterList.map { + it.typeReference?.rawType ?: FuncTyUnknown + }) + +val FuncFunction.rawType: FuncTyMap + get() = FuncTyMap( + rawParamType, + rawReturnType + ) diff --git a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncGlobalVarMixin.kt b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncGlobalVarMixin.kt index dc9522a..1697f37 100644 --- a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncGlobalVarMixin.kt +++ b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncGlobalVarMixin.kt @@ -2,11 +2,17 @@ package org.ton.intellij.func.psi.impl import com.intellij.lang.ASTNode import com.intellij.psi.stubs.IStubElementType +import org.ton.intellij.func.FuncIcons import org.ton.intellij.func.psi.FuncGlobalVar import org.ton.intellij.func.stub.FuncGlobalVarStub +import javax.swing.Icon abstract class FuncGlobalVarMixin : FuncNamedElementImpl, FuncGlobalVar { constructor(node: ASTNode) : super(node) constructor(stub: FuncGlobalVarStub, stubType: IStubElementType<*, *>) : super(stub, stubType) + + override fun getIcon(flags: Int): Icon? { + return FuncIcons.GLOBAL_VARIABLE + } } diff --git a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncIncludeDefinitionImpl.kt b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncIncludeDefinitionImpl.kt index 6053f48..5d4cd80 100644 --- a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncIncludeDefinitionImpl.kt +++ b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncIncludeDefinitionImpl.kt @@ -2,6 +2,7 @@ package org.ton.intellij.func.psi.impl import com.intellij.extapi.psi.StubBasedPsiElementBase import com.intellij.lang.ASTNode +import com.intellij.psi.PsiReference import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference import com.intellij.psi.stubs.IStubElementType import com.intellij.psi.tree.IElementType @@ -17,8 +18,17 @@ abstract class FuncIncludeDefinitionMixin : StubBasedPsiElementBase> : FuncStubbedElementIm constructor(node: ASTNode) : super(node) override fun setName(name: String): PsiElement { - identifier?.replace(FuncPsiFactory[project].createIdentifierFromText(name)) + identifier?.replace(FuncPsiFactory[project].createIdentifier(name)) return this } diff --git a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncReference.kt b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncReference.kt index 54f60b4..4d6bfa9 100644 --- a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncReference.kt +++ b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncReference.kt @@ -1,273 +1,75 @@ package org.ton.intellij.func.psi.impl import com.intellij.openapi.util.TextRange -import com.intellij.psi.* +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiElementResolveResult +import com.intellij.psi.PsiReferenceBase +import com.intellij.psi.ResolveResult import com.intellij.psi.impl.source.resolve.ResolveCache -import com.intellij.psi.scope.PsiScopeProcessor -import com.intellij.psi.util.PsiTreeUtil -import com.intellij.util.PairProcessor -import com.intellij.util.containers.OrderedSet import org.ton.intellij.func.psi.* -import org.ton.intellij.func.psi.FuncPsiUtil.allowed +import org.ton.intellij.util.parentOfType class FuncReference( element: FuncReferenceExpression, rangeInElement: TextRange, ) : PsiReferenceBase.Poly(element, rangeInElement, false) { - private val resolver = ResolveCache.PolyVariantResolver { t, incompleteCode -> - if (!myElement.isValid) return@PolyVariantResolver ResolveResult.EMPTY_ARRAY - val result = OrderedSet() - val resolveProcessor = createResolveProcessor(result) - processResolveVariants(resolveProcessor) - result.toTypedArray() - } - val identifier: PsiElement get() = element.identifier - override fun multiResolve(incompleteCode: Boolean): Array { - if (!myElement.isValid) return ResolveResult.EMPTY_ARRAY - return ResolveCache.getInstance(myElement.project).resolveWithCaching(this, resolver, false, incompleteCode) - } - - override fun handleElementRename(newElementName: String): PsiElement { - identifier.replace(FuncPsiFactory[element.project].createIdentifierFromText(newElementName)) - return element - } - - private fun createResolveProcessor(result: MutableCollection): PsiScopeProcessor { - return PsiScopeProcessor { element, state -> - if (element == myElement) return@PsiScopeProcessor !result.add(PsiElementResolveResult(element)) - - val name = (element as? FuncNamedElement)?.name ?: return@PsiScopeProcessor true - val elementName = myElement.identifier - - when { - elementName.textMatches(name) -> { - result.add(PsiElementResolveResult(element)) - false - } - - element is FuncFunction && name.firstOrNull() != '~' && elementName.textMatches("~$name") -> { - result.add(PsiElementResolveResult(element)) - false - } + private val resolver = ResolveCache.PolyVariantResolver { t, incompleteCode -> + if (!myElement.isValid) return@PolyVariantResolver ResolveResult.EMPTY_ARRAY - else -> true - } + val inference = element.inference + val inferenceResolved = inference?.getResolvedRefs(element) + if (!inferenceResolved.isNullOrEmpty()) { + return@PolyVariantResolver inferenceResolved.toTypedArray() } - } - - fun processResolveVariants( - processor: PsiScopeProcessor, - implicitStdlib: Boolean = true, - ): Boolean { - val file = myElement.containingFile - if (file !is FuncFile) return false - val state = ResolveState.initial() - val processedFiles = HashSet() - - if (!PsiTreeUtil.treeWalkUp(element, null, FuncFunctionProcessor(processor, state))) return false - if (!processIncludeDefinitions(file, processor, state, processedFiles)) return false - if (implicitStdlib && processedFiles.none { it.endsWith("stdlib.fc") }) { - val stdlibFile = file.originalFile.containingDirectory?.findFile("stdlib.fc") - if (stdlibFile is FuncFile) { - if (!processFile(stdlibFile, processor, state, processedFiles)) return false - } + val name = identifier.text.let { + if (it.startsWith('.')) it.substring(1) else it } - if (!processFile(FuncPsiFactory[file.project].builtinStdlibFile, processor, state, HashSet())) return false - return true - } - - private class FuncFunctionProcessor(val delegate: PsiScopeProcessor, val state: ResolveState) : - PairProcessor { - var currentFunction: FuncFunction? = null - var currentStatement: FuncStatement? = null - - override fun process(scope: PsiElement?, prevParent: PsiElement?): Boolean { - if (scope is FuncFunction) { - currentFunction = scope - val parameterList = scope.functionParameterList - for (functionParameter in parameterList) { - if (!delegate.execute(functionParameter, state)) return false - } - } - if (scope is FuncStatement && currentStatement == null) { - currentStatement = scope - } - if (scope is FuncFile) { - for (child in scope.children) { - if (child is FuncFunction) { - if (!delegate.execute(child, state)) return false - } - if (child is FuncConstVarList) { - for (constVar in child.constVarList) { - if (!delegate.execute(constVar, state)) return false - } - } - if (child is FuncGlobalVarList) { - for (globalVar in child.globalVarList) { - if (!delegate.execute(globalVar, state)) return false - } + val file = element.containingFile as? FuncFile + if (file != null) { + val contextFunction = element.parentOfType() + val includes = file.collectIncludedFiles(true) + + includes.forEach { includedFile -> + includedFile.constVars.forEach { constVar -> + if (constVar.name == name) { + return@PolyVariantResolver arrayOf(PsiElementResolveResult(constVar)) } - if (child == prevParent) return true } - } - if (scope is FuncCatch) { - val expression = scope.expression ?: return true - val processResult = PsiTreeUtil.processElements(expression) { - delegate.execute(it, state) - } - if (!processResult) return false - } - if (scope is FuncVarExpression) return true - if (scope is FuncBlockStatement) { - for (statement in scope.statementList) { - if (statement == currentStatement) return true - if (!processStatement(statement, delegate, state)) return false - } - } - if (scope is FuncDoStatement) { - val block = scope.blockStatement ?: return true - if (prevParent == block) return true - for (statement in block.statementList) { - if (!processStatement(statement, delegate, state)) return false - } - } - return true - } - } - - companion object { - private fun processNamedElements( - processor: PsiScopeProcessor, - state: ResolveState, - elements: Collection, - condition: (T) -> Boolean = { true }, - ): Boolean { - for (element in elements) { - if (!condition(element)) continue - if (!element.isValid || !allowed(element.containingFile, null)) { - continue + includedFile.globalVars.forEach { globalVar -> + if (globalVar.name == name) { + return@PolyVariantResolver arrayOf(PsiElementResolveResult(globalVar)) + } } - if (!processor.execute(element, state)) return false - } - return true - } - - fun processStatement( - statement: FuncStatement, - processor: PsiScopeProcessor, - state: ResolveState, - ): Boolean { - val expression = (statement as? FuncExpressionStatement)?.expression - if (!processExpression(expression, processor, state)) return false - return true - } - - fun processExpression( - expression: FuncExpression?, - processor: PsiScopeProcessor, - state: ResolveState, - ): Boolean { - when (expression) { - null -> return true - is FuncAssignExpression -> { - val left = expression.expressionList.firstOrNull() ?: return true - when (left) { - is FuncVarExpression -> { - if (!processVarExpression(left, processor, state)) return false - } - is FuncTensorExpression -> { - for (tensorElement in left.expressionList) { - if (tensorElement is FuncVarExpression) { - if (!processVarExpression(tensorElement, processor, state)) return false - } - } - } - is FuncTupleExpression -> { - for (tupleElement in left.expressionList) { - if (tupleElement is FuncVarExpression) { - if (!processVarExpression(tupleElement, processor, state)) return false - } - } - } - is FuncReferenceExpression -> { - if (expression.parent is FuncConstVar) { - if (!processor.execute(left, state)) return false - } + includedFile.functions.forEach { function -> + val functionName = function.name + if (functionName == name) { + return@PolyVariantResolver arrayOf(PsiElementResolveResult(function)) + } + if (name.startsWith("~") && functionName != null && !functionName.startsWith("~")) { + val tensorList = (function.typeReference as? FuncTensorType)?.typeReferenceList + if (tensorList?.size == 2 && functionName == name.substring(1)) { + return@PolyVariantResolver arrayOf(PsiElementResolveResult(function)) } } - val right = expression.expressionList.getOrNull(1) ?: return true - if (!processExpression(right, processor, state)) return false - } - else -> { - for (funcVarExpression in PsiTreeUtil.findChildrenOfType( - expression, - FuncVarExpression::class.java - )) { - if (!processVarExpression(funcVarExpression, processor, state)) return false + if (function == contextFunction) { + return@PolyVariantResolver ResolveResult.EMPTY_ARRAY } } } - - return true - } - - private fun processVarExpression( - varExpression: FuncVarExpression, - processor: PsiScopeProcessor, - state: ResolveState, - ): Boolean { - val right = varExpression.right ?: return true - return PsiTreeUtil.processElements(right) { - if (!processor.execute(it, state)) return@processElements false - true - } } - } - private fun processFile( - file: FuncFile, - processor: PsiScopeProcessor, - state: ResolveState, - processedFiles: MutableSet, - ): Boolean { -// println("processing file: ${file.name}") - if (file == element.containingFile) return true - if (!processNamedElements(processor, state, file.functions)) return false - if (!processNamedElements(processor, state, file.constVars)) return false - if (!processNamedElements(processor, state, file.globalVars)) return false - if (!processIncludeDefinitions(file, processor, state, processedFiles)) return false - return true + ResolveResult.EMPTY_ARRAY } - private fun processIncludeDefinitions( - file: FuncFile, - processor: PsiScopeProcessor, - state: ResolveState, - processedFiles: MutableSet, - ): Boolean { -// println("processing include defs: ${file.includeDefinitions.size}") - for (includeDefinition in file.includeDefinitions) { -// println("include references: ${includeDefinition.references.size}") - val fileReference = includeDefinition.references.lastOrNull() - val resolvedFile = fileReference?.resolve() - if (resolvedFile !is FuncFile) continue -// println("resolved: ${resolvedFile.virtualFile.path}") - if (processedFiles.add(resolvedFile.virtualFile.path)) { - processFile(resolvedFile, processor, state, processedFiles) - } - } - return true + override fun multiResolve(incompleteCode: Boolean): Array { + if (!myElement.isValid) return ResolveResult.EMPTY_ARRAY + return ResolveCache.getInstance(myElement.project).resolveWithCaching(this, resolver, false, incompleteCode) } - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is FuncReference) return false - if (element != other.element) return false - return true + override fun handleElementRename(newElementName: String): PsiElement { + return element.identifier.replace(FuncPsiFactory[element.project].createIdentifier(newElementName)) } - - override fun hashCode(): Int = element.hashCode() } diff --git a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncReferenceExpressionMixin.kt b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncReferenceExpressionMixin.kt index 8f4576f..d2da957 100644 --- a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncReferenceExpressionMixin.kt +++ b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncReferenceExpressionMixin.kt @@ -4,55 +4,23 @@ import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiElement +import com.intellij.psi.util.CachedValueProvider.Result +import com.intellij.psi.util.CachedValuesManager import com.intellij.psi.util.PsiTreeUtil import org.ton.intellij.func.psi.* + abstract class FuncReferenceExpressionMixin(node: ASTNode) : ASTWrapperPsiElement(node), FuncReferenceExpression { override fun getReferences(): Array { -// val parent = parent -// when (parent) { -// is FuncVarExpression -> return emptyArray() -// } -// val grandParent = parent?.parent -// when (grandParent) { -// is FuncVarExpression, -// is FuncConstVariable, -// -> { -// if (parent !is FuncAssignExpression || parent.expressionList.firstOrNull() == this) { -// return emptyArray() -// } -// } -// } - return PsiTreeUtil.treeWalkUp(this, null) { scope, prevParent -> - when (scope) { - is FuncCatch -> { - if (scope.expression == prevParent) return@treeWalkUp false - } - - is FuncAssignExpression -> { - when (scope.parent) { - is FuncVarExpression -> { - if (scope.expressionList.firstOrNull() == prevParent) return@treeWalkUp false - } - } - } - - is FuncVarExpression -> { - if (scope.expressionList.getOrNull(1) == prevParent) return@treeWalkUp false - } - } - true - }.let { result -> - if (result) arrayOf(FuncReference(this, TextRange(0, textLength))) - else emptyArray() - } + if (isVariableDefinition()) return EMPTY_ARRAY + return arrayOf(FuncReference(this, TextRange(0, textLength))) } override fun getReference(): FuncReference? = references.firstOrNull() override fun setName(name: String): PsiElement { - identifier.replace(FuncPsiFactory[project].createIdentifierFromText(name)) + identifier.replace(FuncPsiFactory[project].createIdentifier(name)) return this } @@ -61,4 +29,31 @@ abstract class FuncReferenceExpressionMixin(node: ASTNode) : ASTWrapperPsiElemen override fun getName(): String? = identifier.text override fun getNameIdentifier(): PsiElement? = identifier + + companion object { + private val EMPTY_ARRAY = emptyArray() + } +} + +private fun FuncExpression.isTypeExpression(): Boolean = + when (this) { + is FuncTensorExpression -> this.expressionList.all { it.isTypeExpression() } + is FuncTupleExpression -> this.expressionList.all { it.isTypeExpression() } + is FuncHoleTypeExpression, + is FuncPrimitiveTypeExpression -> true + + else -> false + } + +fun FuncReferenceExpression.isVariableDefinition(): Boolean = CachedValuesManager.getCachedValue(this) { + val result = !PsiTreeUtil.treeWalkUp(this, null) { scope, lastParent -> + if (scope is FuncApplyExpression && scope.right == lastParent) { // `var |foo|` <-- last parent + val left = scope.left // type definition -> `|var| foo` + if (left.isTypeExpression()) { + return@treeWalkUp false + } + } + true + } + Result(result, this) } diff --git a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncVarExpressionImpl.kt b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncVarExpressionImpl.kt deleted file mode 100644 index 05f1249..0000000 --- a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncVarExpressionImpl.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.ton.intellij.func.psi.impl - -import org.ton.intellij.func.psi.FuncVarExpression - -val FuncVarExpression.right get() = expressionList.getOrNull(1) diff --git a/src/main/kotlin/org/ton/intellij/func/resolve/FuncLookup.kt b/src/main/kotlin/org/ton/intellij/func/resolve/FuncLookup.kt new file mode 100644 index 0000000..51008c2 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/resolve/FuncLookup.kt @@ -0,0 +1,52 @@ +package org.ton.intellij.func.resolve + +import com.intellij.openapi.project.Project +import org.ton.intellij.func.psi.* +import org.ton.intellij.func.type.infer.FuncInferenceContext + +class FuncLookup( + private val project: Project, + context: FuncElement? = null +) { + val ctx by lazy(LazyThreadSafetyMode.NONE) { + FuncInferenceContext(project, this) + } + + private val definitions = HashMap() + + init { + FuncPsiFactory[project].builtinFile.functions.forEach { function -> + val name = function.name ?: return@forEach + definitions[name] = function + } + if (context is FuncFunction) { + context.functionParameterList.forEach { + define(it) + } + } + } + + fun define(element: FuncNamedElement) { + val name = element.name ?: return + definitions[name] = element + } + + fun resolve(element: FuncNamedElement): Collection? { + val name = element.identifier?.text ?: return null + val parent = element.parent + if (parent is FuncApplyExpression && parent.left == element) { + val grandParent = parent.parent + if (grandParent is FuncSpecialApplyExpression && grandParent.right == parent) { + if (name.startsWith('.')) { + return resolve(name.substring(1)) + } + if (name.startsWith('~')) { + return resolve(name) ?: resolve(name.substring(1)) + } + } + } + return resolve(name) + } + + fun resolve(name: String): Collection? = definitions[name]?.let { listOf(it) } +} diff --git a/src/main/kotlin/org/ton/intellij/func/stub/index/FuncAllPublicNamesIndex.kt b/src/main/kotlin/org/ton/intellij/func/stub/index/FuncAllPublicNamesIndex.kt deleted file mode 100644 index 742c11d..0000000 --- a/src/main/kotlin/org/ton/intellij/func/stub/index/FuncAllPublicNamesIndex.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.ton.intellij.func.stub.index - -import com.intellij.psi.stubs.StringStubIndexExtension -import com.intellij.psi.stubs.StubIndexKey -import org.ton.intellij.func.FuncFileElementType -import org.ton.intellij.func.psi.FuncNamedElement - -class FuncAllPublicNamesIndex : StringStubIndexExtension() { - override fun getKey(): StubIndexKey = ALL_PUBLIC_NAMES - - override fun getVersion(): Int = FuncFileElementType.stubVersion - - companion object { - val ALL_PUBLIC_NAMES: StubIndexKey = - StubIndexKey.createIndexKey("func.all.name") - } -} diff --git a/src/main/kotlin/org/ton/intellij/func/stub/index/FuncNamedElementIndex.kt b/src/main/kotlin/org/ton/intellij/func/stub/index/FuncNamedElementIndex.kt new file mode 100644 index 0000000..d67e615 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/stub/index/FuncNamedElementIndex.kt @@ -0,0 +1,32 @@ +package org.ton.intellij.func.stub.index + +import com.intellij.openapi.project.Project +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.stubs.StringStubIndexExtension +import com.intellij.psi.stubs.StubIndexKey +import org.ton.intellij.func.FuncFileElementType +import org.ton.intellij.func.psi.FuncNamedElement +import org.ton.intellij.util.checkCommitIsNotInProgress +import org.ton.intellij.util.getElements + +class FuncNamedElementIndex : StringStubIndexExtension() { + override fun getVersion(): Int = FuncFileElementType.stubVersion + + override fun getKey(): StubIndexKey = KEY + + companion object { + val KEY: StubIndexKey = + StubIndexKey.createIndexKey("org.ton.intellij.func.stub.index.FuncNamedElementIndex") + + fun findElementsByName( + project: Project, + target: String, + scope: GlobalSearchScope = GlobalSearchScope.allScope(project) + ): Collection { + checkCommitIsNotInProgress(project) + return getElements(KEY, target, project, scope).also { +// println("try find $target = ${it.joinToString { it.text }}") + } + } + } +} diff --git a/src/main/kotlin/org/ton/intellij/func/stub/type/FuncConstVarStubElementType.kt b/src/main/kotlin/org/ton/intellij/func/stub/type/FuncConstVarStubElementType.kt index 374165c..014306d 100644 --- a/src/main/kotlin/org/ton/intellij/func/stub/type/FuncConstVarStubElementType.kt +++ b/src/main/kotlin/org/ton/intellij/func/stub/type/FuncConstVarStubElementType.kt @@ -1,6 +1,7 @@ package org.ton.intellij.func.stub.type import com.intellij.psi.PsiElement +import com.intellij.psi.stubs.IndexSink import com.intellij.psi.stubs.StubElement import com.intellij.psi.stubs.StubInputStream import com.intellij.psi.stubs.StubOutputStream @@ -8,6 +9,7 @@ import com.intellij.util.ArrayFactory import org.ton.intellij.func.psi.FuncConstVar import org.ton.intellij.func.psi.impl.FuncConstVarImpl import org.ton.intellij.func.stub.FuncConstVarStub +import org.ton.intellij.func.stub.index.FuncNamedElementIndex class FuncConstVarStubElementType( debugName: String, @@ -32,6 +34,11 @@ class FuncConstVarStubElementType( return FuncConstVarImpl(stub, this) } + override fun indexStub(stub: FuncConstVarStub, sink: IndexSink) { + val name = stub.name ?: return + sink.occurrence(FuncNamedElementIndex.KEY, name) + } + companion object { val EMPTY_ARRAY = emptyArray() val ARRAY_FACTORY: ArrayFactory = ArrayFactory { diff --git a/src/main/kotlin/org/ton/intellij/func/stub/type/FuncFunctionStubElementType.kt b/src/main/kotlin/org/ton/intellij/func/stub/type/FuncFunctionStubElementType.kt index f2fc94a..377e00b 100644 --- a/src/main/kotlin/org/ton/intellij/func/stub/type/FuncFunctionStubElementType.kt +++ b/src/main/kotlin/org/ton/intellij/func/stub/type/FuncFunctionStubElementType.kt @@ -1,6 +1,7 @@ package org.ton.intellij.func.stub.type import com.intellij.psi.PsiElement +import com.intellij.psi.stubs.IndexSink import com.intellij.psi.stubs.StubElement import com.intellij.psi.stubs.StubInputStream import com.intellij.psi.stubs.StubOutputStream @@ -8,6 +9,7 @@ import com.intellij.util.ArrayFactory import org.ton.intellij.func.psi.FuncFunction import org.ton.intellij.func.psi.impl.* import org.ton.intellij.func.stub.FuncFunctionStub +import org.ton.intellij.func.stub.index.FuncNamedElementIndex class FuncFunctionStubElementType( debugName: String, @@ -40,6 +42,11 @@ class FuncFunctionStubElementType( return FuncFunctionImpl(stub, this) } + override fun indexStub(stub: FuncFunctionStub, sink: IndexSink) { + val name = stub.name ?: return + sink.occurrence(FuncNamedElementIndex.KEY, name) + } + companion object { private const val IS_MUTABLE_FLAG = 0x1 private const val IS_IMPURE_FLAG = 0x2 diff --git a/src/main/kotlin/org/ton/intellij/func/stub/type/FuncGlobalVarStubElementType.kt b/src/main/kotlin/org/ton/intellij/func/stub/type/FuncGlobalVarStubElementType.kt index b6b727c..57dfe03 100644 --- a/src/main/kotlin/org/ton/intellij/func/stub/type/FuncGlobalVarStubElementType.kt +++ b/src/main/kotlin/org/ton/intellij/func/stub/type/FuncGlobalVarStubElementType.kt @@ -1,6 +1,7 @@ package org.ton.intellij.func.stub.type import com.intellij.psi.PsiElement +import com.intellij.psi.stubs.IndexSink import com.intellij.psi.stubs.StubElement import com.intellij.psi.stubs.StubInputStream import com.intellij.psi.stubs.StubOutputStream @@ -8,6 +9,7 @@ import com.intellij.util.ArrayFactory import org.ton.intellij.func.psi.FuncGlobalVar import org.ton.intellij.func.psi.impl.FuncGlobalVarImpl import org.ton.intellij.func.stub.FuncGlobalVarStub +import org.ton.intellij.func.stub.index.FuncNamedElementIndex class FuncGlobalVarStubElementType( debugName: String, @@ -32,6 +34,11 @@ class FuncGlobalVarStubElementType( return FuncGlobalVarImpl(stub, this) } + override fun indexStub(stub: FuncGlobalVarStub, sink: IndexSink) { + val name = stub.name ?: return + sink.occurrence(FuncNamedElementIndex.KEY, name) + } + companion object { val EMPTY_ARRAY = emptyArray() val ARRAY_FACTORY: ArrayFactory = ArrayFactory { diff --git a/src/main/kotlin/org/ton/intellij/func/stub/type/FuncNamedStubElementType.kt b/src/main/kotlin/org/ton/intellij/func/stub/type/FuncNamedStubElementType.kt index c3154e7..cd43ac9 100644 --- a/src/main/kotlin/org/ton/intellij/func/stub/type/FuncNamedStubElementType.kt +++ b/src/main/kotlin/org/ton/intellij/func/stub/type/FuncNamedStubElementType.kt @@ -21,8 +21,6 @@ abstract class FuncNamedStubElementType, T : FuncNamedEleme val name = stub.name if (name.isNullOrEmpty() || !shouldIndex()) return -// sink.occurrence(FuncAllPublicNamesIndex.ALL_PUBLIC_NAMES, name) - extraIndexKeys.forEach { key -> sink.occurrence(key, name) } diff --git a/src/main/kotlin/org/ton/intellij/func/type/FuncType.kt b/src/main/kotlin/org/ton/intellij/func/type/FuncType.kt deleted file mode 100644 index 1af84ad..0000000 --- a/src/main/kotlin/org/ton/intellij/func/type/FuncType.kt +++ /dev/null @@ -1,405 +0,0 @@ -package org.ton.intellij.func.type - -import com.intellij.openapi.util.Key -import org.ton.intellij.func.type.FuncType.Type.* -import java.util.concurrent.atomic.AtomicInteger -import kotlin.math.max -import kotlin.math.min - -class FuncType { - var type: Type - private set - var value: Int = 0 - private set - var width: Width = Width() - private set - var args: Array = emptyArray() - private set - - private constructor( - type: Type, - value: Int, - ) { - this.type = type - this.value = value - } - - private constructor( - type: Type, - value: Int, - width: Int, - ) { - this.type = type - this.value = value - this.width = Width(width) - } - - private constructor( - type: Type, - args: List, - ) { - this.type = type - this.args = args.toTypedArray() - this.value = args.size - computeWidth() - } - - fun isAtomic() = type == ATOMIC - - fun isAtomic(type: Atomic) = isAtomic() && value == type.ordinal - - fun isInt() = isAtomic(Atomic.INT) - - fun isVar() = type == VAR - - override fun toString(): String = buildString { - toString(this) - } - - private fun toString(sb: StringBuilder, lexLevel: Int = 0) { - sb.apply { - when (type) { - UNKNOWN -> append("??").append(value) - VAR -> when (value) { - in -26 until 0 -> append('_').append((91 + value).toChar()) - in 0..25 -> append((65 + value).toChar()) - else -> append("TVAR").append(value) - } - - INDIRECT -> args[0].toString(this) - ATOMIC -> append(Atomic[value]?.name?.lowercase() ?: "atomic-type-$value") - TENSOR -> { - if (lexLevel > -127) append('(') - var size = args.size - if (size != 0) { - args.forEach { - it.toString(this) - if (--size != 0) append(", ") - } - } - if (lexLevel > -127) append(')') - } - - TUPLE -> { - append('[') - var size = args.size - if (size == 1 && args[0].type == TENSOR) { - args[0].toString(this, -127) - } else if (size != 0) { - args.forEach { - it.toString(this) - if (--size != 0) append(", ") - } - } - append(']') - } - - MAP -> { - check(args.size == 2) - if (lexLevel > 0) append('(') - args[0].toString(this, 1) - append(" -> ") - args[1].toString(this) - if (lexLevel > 0) append(')') - } - - FORALL -> { - check(args.isNotEmpty()) - if (lexLevel > 0) append('(') - append("Forall") - for (i in 1 until args.size) { - append(if (i > 1) ' ' else '(') - args[i].toString(this) - } - append(") ") - args[0].toString(this) - if (lexLevel > 0) append(')') - } - } - } - } - - private fun computeWidth() { - width = when (type) { - TENSOR -> { - var min = 0 - var max = 0 - args.forEach { - min += it.width.min - max += it.width.max - } - if (min > Width.INF) { - min = Width.INF - } - if (max > Width.INF) { - max = Width.INF - } - Width(min, max) - } - - TUPLE -> { - args.forEach { - it.computeWidth() - } - Width(1) - } - - ATOMIC, MAP -> Width(1) - INDIRECT -> args[0].width - else -> Width() - } - } - - private fun recomputeWidth(): Boolean { - when (type) { - TENSOR, INDIRECT -> { - var min = 0 - var max = 0 - args.forEach { - min += it.width.min - max += it.width.max - } - if (min > width.max || max < width.min) { - return false - } - if (min > Width.INF) { - min = Width.INF - } - if (max > Width.INF) { - max = Width.INF - } - if (width.min < min) { - width = width.copy(min = min) - } - if (width.max > max) { - width = width.copy(max = max) - } - return true - } - - TUPLE -> { - args.forEach { - if (it.width.min > 1 || it.width.max < 1 || it.width.min > it.width.max) { - return false - } - } - return true - } - - else -> return false - } - } - - private fun replaceWidth(te2: FuncType) { - if (te2 == this) return - type = INDIRECT - value = 0 - width = te2.width - args = arrayOf(te2) - } - - enum class Type { - UNKNOWN, VAR, INDIRECT, ATOMIC, TENSOR, TUPLE, MAP, FORALL - } - - enum class Atomic { - INT, CELL, SLICE, BUILDER, CONT, TUPLE, TYPE; - - override fun toString(): String = name.lowercase() - - fun funcType(): FuncType = atomic(this) - - companion object { - private val values = values() - - operator fun get(ordinal: Int): Atomic? = values.getOrNull(ordinal) - } - } - - data class Width( - val min: Int, - val max: Int, - ) { - constructor() : this(0, INF) - constructor(width: Int) : this(width, width) - - override fun toString(): String = buildString { - append(min) - if (min != max) { - append("..") - if (max < INF) { - append(max) - } - } - } - - companion object { - const val INF = 1023 - } - } - - companion object { - val KEY = Key.create("FUNC_TYPE") - - private val holes = AtomicInteger() - private val typeVars = AtomicInteger() - - fun hole() = FuncType(UNKNOWN, holes.incrementAndGet()) - - fun unit() = FuncType(TENSOR, 0, 0) - - fun atomic(value: Atomic) = FuncType(ATOMIC, value.ordinal, 1) - - fun tensor(list: List, red: Boolean = true) = - if (red && list.size == 1) list.first() else FuncType(TENSOR, list) - - fun typeVar() = FuncType(VAR, typeVars.decrementAndGet(), 1) - - fun map(from: FuncType, to: FuncType) = FuncType(MAP, listOf(from, to)) - - fun forall(list: List, body: FuncType) = FuncType(FORALL, listOf(body) + list) - - fun removeIndirect( - teGetter: () -> FuncType, - teSetter: (FuncType) -> Unit, - forbidden: FuncType? = null, - ): Boolean { - while (teGetter().type == INDIRECT) { - teSetter(teGetter().args[0]) - } - val type = teGetter() - if (type.type == UNKNOWN) { - return type != forbidden - } - var res = true - - for (i in type.args.indices) { - res = res && removeIndirect( - teGetter = { type.args[i] }, - teSetter = { type.args[i] = it }, - forbidden - ) - } - return res - } - - private fun checkWidthCompat(te1: FuncType, te2: FuncType) { - if (te1.width.min > te2.width.max || te2.width.min > te1.width.max) { - throw FuncUnifyTypeException(te1, te2, "Can't unify types of width ${te1.width} and ${te2.width}") - } - } - - private fun checkUpdateWidths(te1: FuncType, te2: FuncType) { - checkWidthCompat(te1, te2) - val width = Width( - min = max(te1.width.min, te2.width.min), - max = min(te1.width.max, te2.width.max) - ) - te1.width = width - te2.width = width - check(width.min <= width.max) - } - - fun unify( - getTe1: () -> FuncType, - setTe1: (FuncType) -> Unit, - getTe2: () -> FuncType, - setTe2: (FuncType) -> Unit, - ) { - var te1 = getTe1() - var te2 = getTe2() - println("Start unifying: $te1 and $te2") - try { - while (te1.type == INDIRECT) { - te1 = te1.args[0] - } - while (te2.type == INDIRECT) { - te2 = te2.args[0] - } - if (te1 == te2) return - if (te1.type == UNKNOWN) { - if (te2.type == UNKNOWN) { - check(te1.value != te2.value) - } - if (!removeIndirect( - teGetter = { te2 }, - teSetter = { te2 = it }, - forbidden = te1 - ) - ) { - throw FuncUnifyTypeException(te1, te2, "type unification results in an infinite cyclic type") - } - checkUpdateWidths(te1, te2) - te1.replaceWidth(te2) - te1 = te2 - return - } - if (te2.type == UNKNOWN) { - if (!removeIndirect( - teGetter = { te1 }, - teSetter = { te1 = it }, - forbidden = te2 - ) - ) { - throw FuncUnifyTypeException(te2, te1, "type unification results in an infinite cyclic type") - } - checkUpdateWidths(te2, te1) - te2.replaceWidth(te1) - te2 = te1 - return - } - if (te1.type != te2.type || te1.value != te2.value || te1.args.size != te2.args.size) { - throw FuncUnifyTypeException(te1, te2) - } - - repeat(te1.args.size) { i -> - unify( - getTe1 = { te1.args[i] }, - setTe1 = { te1.args[i] = it }, - getTe2 = { te2.args[i] }, - setTe2 = { te2.args[i] = it } - ) - } - - if (te1.type == TENSOR) { - if (!te1.recomputeWidth()) { - throw FuncUnifyTypeException( - te1, - te2, - "type unification incompatible with known width of first type" - ) - } - if (!te2.recomputeWidth()) { - throw FuncUnifyTypeException( - te2, - te1, - "type unification incompatible with known width of first type" - ) - } - checkUpdateWidths(te1, te2) - } - - te1.replaceWidth(te2) - te1 = te2 - } finally { - setTe1(te1) - setTe2(te2) - } - } - } -} - -class FuncUnifyTypeException( - val te1: FuncType, - val te2: FuncType, - message: String = "", -) : RuntimeException(buildString { - append("Can't unify type ") - append(te1) - append(" with ") - append(te2) - if (message.isNotEmpty()) { - append(": ") - append(message) - } -}) diff --git a/src/main/kotlin/org/ton/intellij/func/type/FuncTypeUtils.kt b/src/main/kotlin/org/ton/intellij/func/type/FuncTypeUtils.kt deleted file mode 100644 index ef7c729..0000000 --- a/src/main/kotlin/org/ton/intellij/func/type/FuncTypeUtils.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.ton.intellij.func.type - -import com.intellij.psi.PsiElement - - -open class FuncUnresolvedTypeException : RuntimeException { - constructor(message: String, cause: Throwable) : super(message, cause) - constructor(message: String) : super(message) -} - -class FuncInvalidStringTypeException( - val psiElement: PsiElement, -) : FuncUnresolvedTypeException("Invalid string type '${psiElement.text}'") diff --git a/src/main/kotlin/org/ton/intellij/func/type/infer/Expectation.kt b/src/main/kotlin/org/ton/intellij/func/type/infer/Expectation.kt new file mode 100644 index 0000000..ad54e87 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/type/infer/Expectation.kt @@ -0,0 +1,32 @@ +package org.ton.intellij.func.type.infer + +import org.ton.intellij.func.type.ty.FuncTy +import org.ton.intellij.func.type.ty.FuncTyUnknown + +/** + * When type-checking an expression, we propagate downward + * whatever type hint we are able in the form of an [Expectation] + */ +sealed class Expectation { + + + /** We know nothing about what type this expression should have */ + object NoExpectation : Expectation() + + /** This expression should have the type given (or some subtype) */ + data class ExpectHasTy(val ty: FuncTy) : Expectation() + + fun onlyHasTy(ctx: FuncInferenceContext): FuncTy? { + return when (this) { + is ExpectHasTy -> ctx.resolveTypeVarsIfPossible(ty) + else -> return null + } + } +} + +fun FuncTy?.maybeHasType(): Expectation = + if (this == null || this is FuncTyUnknown) { + Expectation.NoExpectation + } else { + Expectation.ExpectHasTy(this) + } diff --git a/src/main/kotlin/org/ton/intellij/func/type/infer/Fold.kt b/src/main/kotlin/org/ton/intellij/func/type/infer/Fold.kt new file mode 100644 index 0000000..ebe0856 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/type/infer/Fold.kt @@ -0,0 +1,13 @@ +package org.ton.intellij.func.type.infer + +import org.ton.intellij.func.type.ty.FuncTy + +interface FuncTyFolder { + fun foldTy(ty: FuncTy): FuncTy = ty +} + +interface FuncTyFoldable { + fun foldWith(folder: FuncTyFolder): Self + + fun superFoldWith(folder: FuncTyFolder): Self +} diff --git a/src/main/kotlin/org/ton/intellij/func/type/infer/TypeInference.kt b/src/main/kotlin/org/ton/intellij/func/type/infer/TypeInference.kt new file mode 100644 index 0000000..c292e1f --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/type/infer/TypeInference.kt @@ -0,0 +1,90 @@ +package org.ton.intellij.func.type.infer + +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElementResolveResult +import com.intellij.util.containers.OrderedSet +import org.ton.intellij.func.psi.FuncExpression +import org.ton.intellij.func.psi.FuncFunction +import org.ton.intellij.func.psi.FuncInferenceContextOwner +import org.ton.intellij.func.psi.FuncReferenceExpression +import org.ton.intellij.func.resolve.FuncLookup +import org.ton.intellij.func.type.ty.FuncTy +import org.ton.intellij.func.type.ty.FuncTyUnknown +import org.ton.intellij.util.recursionGuard + +fun inferTypesIn(element: FuncInferenceContextOwner): FuncInferenceResult { + val lookup = FuncLookup(element.project, element) + return recursionGuard(element, { lookup.ctx.infer(element) }, memoize = false) + ?: error("Can not run nested type inference") +} + +interface FuncInferenceData { + fun getExprTy(expr: FuncExpression): FuncTy + + fun getResolvedRefs(element: FuncReferenceExpression): OrderedSet +} + +private val EMPTY_RESOLVED_SET = OrderedSet() + +data class FuncInferenceResult( + val exprTypes: Map, + val resolvedRefs: Map> +) : FuncInferenceData { + val timestamp = System.nanoTime() + + override fun getExprTy(expr: FuncExpression): FuncTy = + exprTypes[expr] ?: FuncTyUnknown + + override fun getResolvedRefs(element: FuncReferenceExpression): OrderedSet { + return resolvedRefs[element] ?: EMPTY_RESOLVED_SET + } + + companion object { + } +} + +class FuncInferenceContext( + val project: Project, + val lookup: FuncLookup +) : FuncInferenceData { + private val exprTypes = HashMap() + private val resolvedRefs = HashMap>() + + override fun getExprTy(expr: FuncExpression): FuncTy = + exprTypes[expr] ?: FuncTyUnknown + + fun setExprTy(expr: FuncExpression, ty: FuncTy) { + exprTypes[expr] = ty + } + + override fun getResolvedRefs(element: FuncReferenceExpression): OrderedSet { + return resolvedRefs[element] ?: EMPTY_RESOLVED_SET + } + + fun setResolvedRefs(element: FuncReferenceExpression, refs: Collection) { + resolvedRefs[element] = OrderedSet(refs) + } + + fun isTypeInferred(expression: FuncExpression): Boolean { + return exprTypes.containsKey(expression) + } + + fun > resolveTypeVarsIfPossible(ty: T): T { + TODO("Not yet implemented") + } + + fun infer(element: FuncInferenceContextOwner): FuncInferenceResult { + when (element) { + is FuncFunction -> { + val walker = FuncTypeInferenceWalker(this, FuncTyUnknown) + element.blockStatement?.let { + walker.inferFunctionBody(it) + } + } + } + + return FuncInferenceResult(exprTypes, resolvedRefs).also { +// println("Inferred types: $it") + } + } +} diff --git a/src/main/kotlin/org/ton/intellij/func/type/infer/TypeInferenceWaler.kt b/src/main/kotlin/org/ton/intellij/func/type/infer/TypeInferenceWaler.kt new file mode 100644 index 0000000..5e8d256 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/type/infer/TypeInferenceWaler.kt @@ -0,0 +1,247 @@ +package org.ton.intellij.func.type.infer + +import com.intellij.openapi.progress.ProgressManager +import com.intellij.psi.PsiElementResolveResult +import org.ton.intellij.func.psi.* +import org.ton.intellij.func.type.ty.* +import org.ton.intellij.util.infiniteWith + +class FuncTypeInferenceWalker( + val ctx: FuncInferenceContext, + private val returnTy: FuncTy +) { + private val definitions = HashMap() + private var variableDeclarationState = false + + fun inferFunctionBody( + block: FuncBlockStatement + ): FuncTy = block.inferTypeCoercibleTo(returnTy) + + private fun FuncBlockStatement.inferTypeCoercibleTo( + expected: FuncTy + ): FuncTy = inferType(Expectation.ExpectHasTy(expected), coerce = true) + + private fun FuncBlockStatement.inferType( + expected: Expectation = Expectation.NoExpectation, + coerce: Boolean = false + ): FuncTy { + for (statement in statementList) { + statement.inferType() + } + + return if (expected is Expectation.ExpectHasTy) { + expected.ty + } else { + FuncTyUnknown + } + } + + private fun FuncStatement.inferType() { + when (this) { + is FuncReturnStatement -> inferType() + is FuncBlockStatement -> inferType() + is FuncRepeatStatement -> inferType() + is FuncIfStatement -> inferType() + is FuncDoStatement -> inferType() + is FuncWhileStatement -> inferType() + is FuncTryStatement -> inferType() + is FuncExpressionStatement -> expression.inferType() + else -> { + } + } + } + + private fun FuncExpression.inferTypeCoercibleTo( + expected: FuncTy + ): FuncTy { + val inferred = inferType(expected.maybeHasType()) + // TODO: coerce to expected + return inferred + } + + private fun FuncExpression.inferType(expected: FuncTy?) = + inferType(expected.maybeHasType()) + + private fun FuncExpression.inferType( + expected: Expectation = Expectation.NoExpectation, + ): FuncTy { + ProgressManager.checkCanceled() + if (ctx.isTypeInferred(this)) { + error("Type already inferred for $this") + } + val ty = when (this) { + is FuncLiteralExpression -> inferLiteral(this) + is FuncParenExpression -> inferType(expected) + is FuncTensorExpression -> inferType(expected) + is FuncTupleExpression -> inferType(expected) + is FuncBinExpression -> inferType(expected) + is FuncApplyExpression -> inferType(expected) + is FuncSpecialApplyExpression -> inferType(expected) + is FuncReferenceExpression -> inferType(expected) + is FuncInvExpression -> inferType(expected) + is FuncTernaryExpression -> inferType(expected) + is FuncUnaryMinusExpression -> inferType(expected) + else -> FuncTyUnknown + } + + ctx.setExprTy(this, ty) + + return ty + } + + private fun inferLiteral(expr: FuncLiteralExpression): FuncTy { + // TODO: optimize via stubs + if (expr.integerLiteral != null || expr.trueKeyword != null || expr.falseKeyword != null) { + return FuncTyInt + } else if (expr.stringLiteral != null) { + return FuncTySlice + } + return FuncTyUnknown + } + + private fun FuncReturnStatement.inferType(): FuncTy { + return expression?.inferTypeCoercibleTo(returnTy) ?: FuncTyUnknown + } + + private fun FuncRepeatStatement.inferType(): FuncTy { + expression?.inferType(FuncTyInt) + blockStatement?.inferType() + return FuncTyUnit + } + + private fun FuncIfStatement.inferType(): FuncTy { + condition?.inferType(FuncTyInt) + blockStatement?.inferType() + elseBranch?.statement?.inferType() + return FuncTyUnit + } + + private fun FuncDoStatement.inferType(): FuncTy { + blockStatement?.inferType() + condition?.inferType(FuncTyInt) + return FuncTyUnit + } + + private fun FuncWhileStatement.inferType(): FuncTy { + condition?.inferType(FuncTyInt) + blockStatement?.inferType() + return FuncTyUnit + } + + private fun FuncTryStatement.inferType(): FuncTy { + blockStatement?.inferType() + catch?.expression?.inferType( + FuncTyTensor( + FuncTyVar(), + FuncTyInt + ) + ) + catch?.blockStatement?.inferType() + return FuncTyUnit + } + + private fun FuncTensorExpression.inferType( + expected: Expectation + ): FuncTy { +// val fields = expected.onlyHasTy(ctx)?.let { +// (it as? FuncTyTensor)?.types +//// (resolveTypeVarsWithObligations(it) as? FuncTyTensor)?.types TODO implement resolveTypeVarsWithObligations +// } + return FuncTyTensor(expressionList.inferType(null)) + } + + private fun FuncTupleExpression.inferType( + expected: Expectation + ): FuncTy { + return FuncTy(expressionList.inferType(null)) + } + + private fun FuncParenExpression.inferType( + expected: Expectation + ): FuncTy { + return expression?.inferType(expected) ?: FuncTyUnknown + } + + private fun FuncBinExpression.inferType( + expected: Expectation + ): FuncTy { + val leftTy = left.inferType() + val rightTy = right?.inferTypeCoercibleTo(leftTy) + return rightTy ?: leftTy + } + + private fun FuncApplyExpression.inferType( + expected: Expectation + ): FuncTy { + val expressions = expressionList + val lhs = expressions[0] + val rhs = expressions.getOrNull(1) + + if (lhs is FuncPrimitiveTypeExpression || lhs is FuncHoleTypeExpression) { + variableDeclarationState = true + } + val lhsTy = lhs?.inferType() + val rhsTy = rhs?.inferType() + variableDeclarationState = false + + return FuncTyUnit + } + + private fun FuncSpecialApplyExpression.inferType( + expected: Expectation + ): FuncTy { + val expressions = expressionList + val lhs = expressions.getOrNull(0) + val rhs = expressions.getOrNull(1) + val lhsTy = lhs?.inferType() + val rhsTy = rhs?.inferType() + return FuncTyUnit + } + + private fun FuncReferenceExpression.inferType( + expected: Expectation + ): FuncTy { + if (variableDeclarationState) { + ctx.lookup.define(this) + } else { + ctx.lookup.resolve(this)?.let { resolved -> + ctx.setResolvedRefs(this, resolved.map { PsiElementResolveResult(it) }) + } + } + return FuncTyUnknown + } + + private fun FuncInvExpression.inferType( + expected: Expectation + ): FuncTy { + val expression = expression + val ty = expression?.inferType(expected) + return ty ?: FuncTyUnknown + } + + private fun FuncTernaryExpression.inferType( + expected: Expectation + ): FuncTy { + val conditionTy = condition.inferType(FuncTyInt) + val thenTy = thenBranch?.inferType(expected) + val elseTy = elseBranch?.inferType(expected) + return thenTy ?: elseTy ?: FuncTyUnknown + } + + private fun FuncUnaryMinusExpression.inferType( + expected: Expectation + ): FuncTy { + val expression = expression + val ty = expression?.inferType(expected) + return ty ?: FuncTyUnknown + } + + private fun List.inferType( + expected: List? + ): List { + val extended = expected.orEmpty().asSequence().infiniteWith(FuncTyUnknown) + return asSequence().zip(extended).map { (expr, ty) -> + expr.inferTypeCoercibleTo(ty) + }.toList() + } +} diff --git a/src/main/kotlin/org/ton/intellij/func/type/ty/Ty.kt b/src/main/kotlin/org/ton/intellij/func/type/ty/Ty.kt new file mode 100644 index 0000000..1f84851 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/type/ty/Ty.kt @@ -0,0 +1,189 @@ +package org.ton.intellij.func.type.ty + +import com.intellij.psi.util.elementType +import org.ton.intellij.func.psi.* +import org.ton.intellij.func.type.infer.FuncTyFoldable +import org.ton.intellij.func.type.infer.FuncTyFolder + +sealed class FuncTy : FuncTyFoldable { + override fun foldWith(folder: FuncTyFolder): FuncTy = folder.foldTy(this) + + override fun superFoldWith(folder: FuncTyFolder): FuncTy = this + + fun isEquivalentTo(other: FuncTy?): Boolean = other != null && isEquivalentToInner(other) + + protected open fun isEquivalentToInner(other: FuncTy): Boolean = equals(other) +} + +data class FuncTyTensor( + val types: List +) : FuncTy() { + constructor(vararg types: FuncTy) : this(types.toList()) + + init { + assert(types.isNotEmpty()) { "TyTuple should not be empty" } + } + + override fun superFoldWith(folder: FuncTyFolder): FuncTy = + FuncTy(types.map { it.foldWith(folder) }) + + override fun isEquivalentToInner(other: FuncTy): Boolean { + if (this === other) return true + if (other is FuncTyUnit) { + return types.size == 1 && types.single().isEquivalentTo(other) + } + if (other !is FuncTyTensor) return false + if (types.size != other.types.size) return false + for (i in types.indices) { + if (!types[i].isEquivalentTo(other.types[i])) return false + } + + return true + } + + override fun toString(): String = types.joinToString(", ", "(", ")") +} + +data class FuncTyTuple( + val types: List +) : FuncTy() { + init { + assert(types.isNotEmpty()) { "TyTuple should not be empty" } + } + + override fun superFoldWith(folder: FuncTyFolder): FuncTy = + FuncTyTuple(types.map { it.foldWith(folder) }) + + override fun isEquivalentToInner(other: FuncTy): Boolean { + if (this === other) return true + if (other !is FuncTyTensor) return false + + if (types.size != other.types.size) return false + for (i in types.indices) { + if (!types[i].isEquivalentTo(other.types[i])) return false + } + + return true + } + + override fun toString(): String = types.joinToString(", ", "[", "]") +} + +fun FuncTy(types: List): FuncTy { + return when (types.size) { + 0 -> FuncTyUnit + 1 -> types.single() + else -> FuncTyTensor(types) + } +} + +data object FuncTyUnknown : FuncTy() { + override fun toString(): String { + return "???" + } +} + +data class FuncTyMap( + val from: FuncTy, + val to: FuncTy +) : FuncTy() { + override fun superFoldWith(folder: FuncTyFolder): FuncTy = + FuncTyMap(from.foldWith(folder), to.foldWith(folder)) + + override fun isEquivalentToInner(other: FuncTy): Boolean { + if (this === other) return true + if (other !is FuncTyMap) return false + + return from.isEquivalentTo(other.from) && to.isEquivalentTo(other.to) + } + + override fun toString(): String = "($from) -> $to" +} + +sealed class FuncTyAtomic : FuncTy() { + abstract val name: String +} + +data object FuncTyUnit : FuncTyAtomic() { + override val name: String = "()" + + override fun isEquivalentToInner(other: FuncTy): Boolean { + if (this === other) return true + return other is FuncTyTensor && other.types.isEmpty() + } + + override fun toString(): String = "()" +} + +data object FuncTyInt : FuncTyAtomic() { + override val name: String = "int" + + override fun toString(): String = "int" +} + +data object FuncTyCell : FuncTyAtomic() { + override val name: String = "cell" + + override fun toString(): String = "cell" +} + +data object FuncTySlice : FuncTyAtomic() { + override val name: String = "slice" + + override fun toString(): String = "slice" +} + +data object FuncTyBuilder : FuncTyAtomic() { + override val name: String = "builder" + + override fun toString(): String = "builder" +} + +data object FuncTyCont : FuncTyAtomic() { + override val name: String = "cont" + + override fun toString(): String = "cont" +} + +data object FuncTyAtomicTuple : FuncTyAtomic() { + override val name: String = "tuple" + + override fun toString(): String = "tuple" +} + +class FuncTyVar : FuncTy() + +val FuncTypeReference.rawType: FuncTy + get() { + return when (this) { + is FuncPrimitiveType -> when (firstChild.elementType) { + FuncElementTypes.INT_KEYWORD -> FuncTyInt + FuncElementTypes.CELL_KEYWORD -> FuncTyCell + FuncElementTypes.SLICE_KEYWORD -> FuncTySlice + FuncElementTypes.BUILDER_KEYWORD -> FuncTyBuilder + FuncElementTypes.CONT_KEYWORD -> FuncTyCont + FuncElementTypes.TUPLE_KEYWORD -> FuncTyAtomicTuple + else -> FuncTyUnknown + } + + is FuncUnitType -> FuncTyUnit + is FuncTensorType -> FuncTy( + typeReferenceList.map { + it.rawType + } + ) + + is FuncTupleType -> FuncTyTuple( + typeReferenceList.map { + it.rawType + } + ) + + is FuncMapType -> FuncTyMap( + from?.rawType ?: FuncTyUnknown, + to?.rawType ?: return FuncTyUnknown + ) + + else -> FuncTyUnknown + } + } diff --git a/src/main/kotlin/org/ton/intellij/tact/TactBraceMatcher.kt b/src/main/kotlin/org/ton/intellij/tact/TactBraceMatcher.kt new file mode 100644 index 0000000..f760556 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/TactBraceMatcher.kt @@ -0,0 +1,23 @@ +package org.ton.intellij.tact + +import com.intellij.lang.BracePair +import com.intellij.lang.PairedBraceMatcher +import com.intellij.psi.PsiFile +import com.intellij.psi.tree.IElementType +import org.ton.intellij.tact.psi.TactElementTypes + +class TactBraceMatcher : PairedBraceMatcher { + override fun getPairs(): Array = arrayOf( + BracePair(TactElementTypes.LBRACE, TactElementTypes.RBRACE, true), + BracePair(TactElementTypes.LPAREN, TactElementTypes.RPAREN, false), + BracePair(TactElementTypes.LBRACK, TactElementTypes.RBRACK, false), + ) + + override fun isPairedBracesAllowedBeforeType(lbraceType: IElementType, contextType: IElementType?): Boolean { + return true + } + + override fun getCodeConstructStart(file: PsiFile?, openingBraceOffset: Int): Int { + return openingBraceOffset + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/TactCommenter.kt b/src/main/kotlin/org/ton/intellij/tact/TactCommenter.kt new file mode 100644 index 0000000..2396dd4 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/TactCommenter.kt @@ -0,0 +1,15 @@ +package org.ton.intellij.tact + +import com.intellij.lang.Commenter + +class TactCommenter : Commenter { + override fun getLineCommentPrefix(): String = "//" + + override fun getBlockCommentPrefix(): String = "/*" + + override fun getBlockCommentSuffix(): String = "*/" + + override fun getCommentedBlockCommentPrefix(): String? = null + + override fun getCommentedBlockCommentSuffix(): String? = null +} diff --git a/src/main/kotlin/org/ton/intellij/tact/TactFoldingBuilder.kt b/src/main/kotlin/org/ton/intellij/tact/TactFoldingBuilder.kt new file mode 100644 index 0000000..fef2573 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/TactFoldingBuilder.kt @@ -0,0 +1,42 @@ +package org.ton.intellij.tact + +import com.intellij.lang.ASTNode +import com.intellij.lang.folding.FoldingBuilderEx +import com.intellij.lang.folding.FoldingDescriptor +import com.intellij.openapi.editor.Document +import com.intellij.openapi.project.DumbAware +import com.intellij.psi.PsiElement +import org.ton.intellij.tact.psi.TactBlock +import org.ton.intellij.tact.psi.TactContractBody +import org.ton.intellij.tact.psi.TactElementTypes +import org.ton.intellij.tact.psi.TactRecursiveElementWalkingVisitor + +class TactFoldingBuilder : FoldingBuilderEx(), DumbAware { + override fun buildFoldRegions(root: PsiElement, document: Document, quick: Boolean): Array { + val descriptors = mutableListOf() + root.accept(object : TactRecursiveElementWalkingVisitor() { + override fun visitBlock(o: TactBlock) { + super.visitBlock(o) + descriptors.add(FoldingDescriptor(o.node, o.textRange)) + } + + override fun visitContractBody(o: TactContractBody) { + super.visitContractBody(o) + descriptors.add(FoldingDescriptor(o.node, o.textRange)) + } + }) + return descriptors.toTypedArray() + } + + override fun getPlaceholderText(p0: ASTNode): String? { + return when (p0.elementType) { + TactElementTypes.BLOCK, + TactElementTypes.CONTRACT_BODY -> "{...}" + + else -> null + } + } + + override fun isCollapsedByDefault(p0: ASTNode): Boolean = false + +} diff --git a/src/main/kotlin/org/ton/intellij/tact/TactIcons.kt b/src/main/kotlin/org/ton/intellij/tact/TactIcons.kt index bcc4eb1..c76e318 100644 --- a/src/main/kotlin/org/ton/intellij/tact/TactIcons.kt +++ b/src/main/kotlin/org/ton/intellij/tact/TactIcons.kt @@ -4,7 +4,7 @@ import com.intellij.icons.AllIcons import com.intellij.openapi.util.IconLoader object TactIcons { - val FILE = IconLoader.getIcon("/icons/tact.svg", TactIcons::class.java) + val FILE = IconLoader.getIcon("icons/tact.svg", TactIcons::class.java) val FUNCTION = AllIcons.Nodes.Function val PARAMETER = AllIcons.Nodes.Parameter val CONSTANT = AllIcons.Nodes.Constant diff --git a/src/main/kotlin/org/ton/intellij/tact/TactQuoteHandler.kt b/src/main/kotlin/org/ton/intellij/tact/TactQuoteHandler.kt new file mode 100644 index 0000000..894708b --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/TactQuoteHandler.kt @@ -0,0 +1,6 @@ +package org.ton.intellij.tact + +import com.intellij.codeInsight.editorActions.SimpleTokenSetQuoteHandler +import org.ton.intellij.tact.psi.TactElementTypes + +class TactQuoteHandler : SimpleTokenSetQuoteHandler(TactElementTypes.STRING_LITERAL) diff --git a/src/main/kotlin/org/ton/intellij/tact/annotator/TactHighlightingAnnotator.kt b/src/main/kotlin/org/ton/intellij/tact/annotator/TactHighlightingAnnotator.kt new file mode 100644 index 0000000..d8529b3 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/annotator/TactHighlightingAnnotator.kt @@ -0,0 +1,50 @@ +package org.ton.intellij.tact.annotator + +import com.intellij.lang.annotation.AnnotationHolder +import com.intellij.lang.annotation.Annotator +import com.intellij.lang.annotation.HighlightSeverity +import com.intellij.psi.PsiElement +import com.intellij.psi.impl.source.tree.LeafPsiElement +import com.intellij.psi.tree.IElementType +import org.ton.intellij.tact.highlighting.TactColor +import org.ton.intellij.tact.psi.TactConstant +import org.ton.intellij.tact.psi.TactElement +import org.ton.intellij.tact.psi.TactElementTypes.IDENTIFIER +import org.ton.intellij.tact.psi.TactField +import org.ton.intellij.tact.psi.TactFunction + +class TactHighlightingAnnotator : Annotator { + override fun annotate(element: PsiElement, holder: AnnotationHolder) { + if (element !is LeafPsiElement) return + val elementType = element.elementType + + val color = highlightLeaf(element, elementType, holder) ?: return + + holder.newSilentAnnotation(HighlightSeverity.INFORMATION).textAttributes(color.textAttributesKey).create() + } + + private fun highlightLeaf(element: PsiElement, elementType: IElementType, holder: AnnotationHolder): TactColor? { + val parent = element.parent as? TactElement ?: return null + + return when (elementType) { + IDENTIFIER -> highlightIdentifier(element, parent, holder) + else -> null + } + } + + private fun highlightIdentifier(element: PsiElement, parent: TactElement, holder: AnnotationHolder): TactColor? { + if (element.text == "self") { + return TactColor.SELF_PARAMETER + } + return colorFor(parent) + } + + fun colorFor(element: TactElement) = when (element) { +// is TactNativeFunction -> TactColor.FUNCTION_STATIC + is TactFunction -> TactColor.FUNCTION_DECLARATION + is TactField -> TactColor.FIELD + is TactConstant -> TactColor.CONSTANT + else -> null + } + +} diff --git a/src/main/kotlin/org/ton/intellij/tact/formatter/TactFormatterBlock.kt b/src/main/kotlin/org/ton/intellij/tact/formatter/TactFormatterBlock.kt new file mode 100644 index 0000000..39b4d73 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/formatter/TactFormatterBlock.kt @@ -0,0 +1,63 @@ +package org.ton.intellij.tact.formatter + +import com.intellij.formatting.* +import com.intellij.lang.ASTNode +import com.intellij.psi.TokenType +import com.intellij.psi.formatter.common.AbstractBlock +import org.ton.intellij.tact.psi.TactElementTypes + +class TactFormatterBlock( + node: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + private val indent: Indent? = null, + private val spacingBuilder: SpacingBuilder +) : AbstractBlock(node, null, null) { + private val childIndent = when (node.elementType) { + TactElementTypes.CONTRACT_BODY, + TactElementTypes.TRAIT_BODY, + TactElementTypes.BLOCK_FIELDS, + TactElementTypes.BLOCK -> Indent.getNormalIndent() + + else -> Indent.getNoneIndent() + } + + override fun getSpacing(child1: Block?, child2: Block): Spacing? = spacingBuilder.getSpacing(this, child1, child2) + + override fun getIndent(): Indent? = indent + + override fun buildChildren(): List { + val blocks = ArrayList() + var child = myNode.firstChildNode + while (child != null) { + if (child.elementType != TokenType.WHITE_SPACE) { + blocks.add(TactFormatterBlock(child, null, null, computeIndent(child), spacingBuilder)) + } + child = child.treeNext + } + return blocks + } + + override fun getChildIndent(): Indent? = childIndent + + override fun isLeaf(): Boolean = myNode.firstChildNode == null + + fun computeIndent(child: ASTNode): Indent? { + val parentType = node.elementType + val childType = child.elementType + + return when (parentType) { + TactElementTypes.CONTRACT_BODY, + TactElementTypes.TRAIT_BODY, + TactElementTypes.BLOCK_FIELDS, + TactElementTypes.BLOCK -> { + when (childType) { + TactElementTypes.LBRACE, TactElementTypes.RBRACE -> Indent.getNoneIndent() + else -> Indent.getNormalIndent() + } + } + + else -> Indent.getNoneIndent() + } + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/formatter/TactFormattingModelBuilder.kt b/src/main/kotlin/org/ton/intellij/tact/formatter/TactFormattingModelBuilder.kt new file mode 100644 index 0000000..f78bb10 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/formatter/TactFormattingModelBuilder.kt @@ -0,0 +1,31 @@ +package org.ton.intellij.tact.formatter + +import com.intellij.formatting.* +import com.intellij.psi.codeStyle.CodeStyleSettings +import org.ton.intellij.tact.TactLanguage +import org.ton.intellij.tact.psi.TactElementTypes.* + +class TactFormattingModelBuilder : FormattingModelBuilder { + override fun createModel(formattingContext: FormattingContext): FormattingModel { + return FormattingModelProvider.createFormattingModelForPsiFile( + formattingContext.containingFile, + TactFormatterBlock( + formattingContext.node, + null, + null, + null, + createSpacing(formattingContext.codeStyleSettings) + ), + formattingContext.codeStyleSettings + ) + } + + fun createSpacing(codeStyleSettings: CodeStyleSettings): SpacingBuilder { + return SpacingBuilder(codeStyleSettings, TactLanguage) + .between(LPAREN, RPAREN).spacing(0, 0, 0, false, 0) + .between(LBRACE, RBRACE).spacing(0, 0, 0, false, 0) + .between(LBRACK, RBRACK).spacing(0, 0, 0, false, 0) + .after(LBRACE).lineBreakInCode() + .after(RBRACE).spaces(1) + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/highlighting/TactColor.kt b/src/main/kotlin/org/ton/intellij/tact/highlighting/TactColor.kt index b2a2c3b..f5a458c 100644 --- a/src/main/kotlin/org/ton/intellij/tact/highlighting/TactColor.kt +++ b/src/main/kotlin/org/ton/intellij/tact/highlighting/TactColor.kt @@ -30,7 +30,9 @@ enum class TactColor( FUNCTION_DECLARATION("Function declaration", Default.FUNCTION_DECLARATION), FUNCTION_CALL("Function call", Default.FUNCTION_CALL), FUNCTION_STATIC("Function static", Default.STATIC_METHOD), - CONSTANT("Constant", Default.CONSTANT), + FIELD("Variables//Field", Default.INSTANCE_FIELD), + CONSTANT("Variables//Constant", Default.CONSTANT), + SELF_PARAMETER("Parameters//Self parameter", Default.KEYWORD), GLOBAL_VARIABLE("Global variable", Default.GLOBAL_VARIABLE), LOCAL_VARIABLE("Local variable", Default.LOCAL_VARIABLE), MACRO("Macro", Default.METADATA), @@ -40,7 +42,7 @@ enum class TactColor( ; val textAttributesKey = - TextAttributesKey.createTextAttributesKey("org.ton.intellij.func.$name", default) + TextAttributesKey.createTextAttributesKey("org.ton.intellij.tact.$name", default) val attributesDescriptor = AttributesDescriptor(displayName, textAttributesKey) val attributes diff --git a/src/main/kotlin/org/ton/intellij/tact/parser/TactParserDefinition.kt b/src/main/kotlin/org/ton/intellij/tact/parser/TactParserDefinition.kt index 1562ed5..eaa96c1 100644 --- a/src/main/kotlin/org/ton/intellij/tact/parser/TactParserDefinition.kt +++ b/src/main/kotlin/org/ton/intellij/tact/parser/TactParserDefinition.kt @@ -7,14 +7,18 @@ import com.intellij.openapi.project.Project import com.intellij.psi.FileViewProvider import com.intellij.psi.PsiElement import com.intellij.psi.tree.TokenSet -import org.ton.intellij.tact.psi.* +import org.ton.intellij.tact.psi.TACT_COMMENTS +import org.ton.intellij.tact.psi.TACT_STRING_LITERALS +import org.ton.intellij.tact.psi.TactElementTypes +import org.ton.intellij.tact.psi.TactFile +import org.ton.intellij.tact.stub.TactFileStub class TactParserDefinition : ParserDefinition { override fun createLexer(project: Project) = FlexAdapter(org.ton.intellij.tact.parser._TactLexer()) override fun createParser(project: Project?) = TactParser() - override fun getFileNodeType() = TactFileElementType + override fun getFileNodeType() = TactFileStub.Type override fun getCommentTokens(): TokenSet = TACT_COMMENTS diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/TactElement.kt b/src/main/kotlin/org/ton/intellij/tact/psi/TactElement.kt index ae813f7..9452693 100644 --- a/src/main/kotlin/org/ton/intellij/tact/psi/TactElement.kt +++ b/src/main/kotlin/org/ton/intellij/tact/psi/TactElement.kt @@ -1,5 +1,24 @@ package org.ton.intellij.tact.psi +import com.intellij.extapi.psi.StubBasedPsiElementBase +import com.intellij.lang.ASTNode +import com.intellij.openapi.util.UserDataHolderEx import com.intellij.psi.PsiElement +import com.intellij.psi.impl.source.tree.CompositePsiElement +import com.intellij.psi.stubs.IStubElementType +import com.intellij.psi.stubs.StubBase +import com.intellij.psi.tree.IElementType -interface TactElement : PsiElement +interface TactElement : PsiElement, UserDataHolderEx + +abstract class TactElementImpl(type: IElementType) : CompositePsiElement(type), TactElement { + override fun toString(): String = "${javaClass.simpleName}($elementType)" +} + +abstract class TactStubbedElementImpl> : StubBasedPsiElementBase, TactElement { + constructor(node: ASTNode) : super(node) + + constructor(stub: StubT, nodeType: IStubElementType<*, *>) : super(stub, nodeType) + + override fun toString(): String = "${javaClass.simpleName}($elementType)" +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/TactElementType.kt b/src/main/kotlin/org/ton/intellij/tact/psi/TactElementType.kt index 913329e..2bf3f7b 100644 --- a/src/main/kotlin/org/ton/intellij/tact/psi/TactElementType.kt +++ b/src/main/kotlin/org/ton/intellij/tact/psi/TactElementType.kt @@ -5,4 +5,6 @@ import org.ton.intellij.tact.TactLanguage class TactElementType( debug: String, -) : IElementType(debug, TactLanguage) +) : IElementType(debug, TactLanguage) { + +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/TactFile.kt b/src/main/kotlin/org/ton/intellij/tact/psi/TactFile.kt index 1965f05..2f1b2e3 100644 --- a/src/main/kotlin/org/ton/intellij/tact/psi/TactFile.kt +++ b/src/main/kotlin/org/ton/intellij/tact/psi/TactFile.kt @@ -6,7 +6,7 @@ import org.ton.intellij.tact.TactFileType import org.ton.intellij.tact.TactLanguage import org.ton.intellij.tact.stub.TactFileStub -class TactFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, TactLanguage) { +class TactFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, TactLanguage), TactElement { override fun getFileType() = TactFileType override fun getStub() = super.getStub() as? TactFileStub diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/TactFileElementType.kt b/src/main/kotlin/org/ton/intellij/tact/psi/TactFileElementType.kt index 2810367..a65da3e 100644 --- a/src/main/kotlin/org/ton/intellij/tact/psi/TactFileElementType.kt +++ b/src/main/kotlin/org/ton/intellij/tact/psi/TactFileElementType.kt @@ -1,35 +1 @@ package org.ton.intellij.tact.psi - -import com.intellij.psi.PsiFile -import com.intellij.psi.StubBuilder -import com.intellij.psi.stubs.* -import com.intellij.psi.tree.IStubFileElementType -import org.ton.intellij.func.psi.FuncFile -import org.ton.intellij.func.stub.FuncFileStub -import org.ton.intellij.tact.TactLanguage - -private const val STUB_VERSION = 1 - -object TactFileElementType : IStubFileElementType("FUNC_FILE", TactLanguage) { - override fun getStubVersion(): Int = STUB_VERSION - - override fun getBuilder(): StubBuilder = object : DefaultStubBuilder() { - override fun createStubForFile(file: PsiFile): StubElement<*> = - if (file is FuncFile) FuncFileStub(file) - else super.createStubForFile(file) - } - - override fun indexStub(stub: PsiFileStub<*>, sink: IndexSink) { - super.indexStub(stub, sink) - } - - override fun serialize(stub: FuncFileStub, dataStream: StubOutputStream) { - - } - - override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>?): FuncFileStub { - return FuncFileStub(null) - } - - override fun getExternalId(): String = "tact.FILE" -} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/TactFunction.kt b/src/main/kotlin/org/ton/intellij/tact/psi/TactFunction.kt new file mode 100644 index 0000000..692ddfb --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/TactFunction.kt @@ -0,0 +1,21 @@ +package org.ton.intellij.tact.psi + +import com.intellij.lang.ASTNode +import com.intellij.psi.stubs.IStubElementType +import org.ton.intellij.tact.stub.TactFunctionStub +import org.ton.intellij.util.greenStub + +abstract class TactFunctionImplMixin : TactNamedElementImpl, TactFunction { + constructor(node: ASTNode) : super(node) + + constructor(stub: TactFunctionStub, type: IStubElementType<*, *>) : super(stub, type) +} + +val TactFunction.isNative get() = greenStub?.isNative ?: (nativeKeyword != null) +val TactFunction.isGet get() = greenStub?.isGet ?: functionAttributeList.any { it.getKeyword != null } +val TactFunction.isMutates get() = greenStub?.isMutates ?: functionAttributeList.any { it.mutatesKeyword != null } +val TactFunction.isExtends get() = greenStub?.isExtends ?: functionAttributeList.any { it.extendsKeyword != null } +val TactFunction.isVirtual get() = greenStub?.isVirtual ?: functionAttributeList.any { it.virtualKeyword != null } +val TactFunction.isOverride get() = greenStub?.isOverride ?: functionAttributeList.any { it.overrideKeyword != null } +val TactFunction.isInline get() = greenStub?.isInline ?: functionAttributeList.any { it.inlineKeyword != null } +val TactFunction.isAbstract get() = greenStub?.isAbstract ?: functionAttributeList.any { it.abstractKeyword != null } diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/TactItemElement.kt b/src/main/kotlin/org/ton/intellij/tact/psi/TactItemElement.kt new file mode 100644 index 0000000..6c8f544 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/TactItemElement.kt @@ -0,0 +1,3 @@ +package org.ton.intellij.tact.psi + +interface TactItemElement : TactElement diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/TactNamedElement.kt b/src/main/kotlin/org/ton/intellij/tact/psi/TactNamedElement.kt new file mode 100644 index 0000000..ea0498c --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/TactNamedElement.kt @@ -0,0 +1,30 @@ +package org.ton.intellij.tact.psi + +import com.intellij.lang.ASTNode +import com.intellij.psi.NavigatablePsiElement +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiNameIdentifierOwner +import com.intellij.psi.PsiNamedElement +import com.intellij.psi.stubs.IStubElementType +import org.ton.intellij.tact.psi.TactElementTypes.IDENTIFIER +import org.ton.intellij.tact.stub.TactNamedStub + +interface TactNamedElement : TactElement, PsiNamedElement, NavigatablePsiElement + +interface TactNameIdentifierOwner : TactNamedElement, PsiNameIdentifierOwner + +abstract class TactNamedElementImpl> : TactStubbedElementImpl, TactNameIdentifierOwner { + constructor(node: ASTNode) : super(node) + constructor(stub: T, nodeType: IStubElementType<*, *>) : super(stub, nodeType) + + override fun getNameIdentifier(): PsiElement? = findChildByType(IDENTIFIER) + + override fun getName(): String? = greenStub?.name ?: nameIdentifier?.text + + override fun setName(name: String): PsiElement { + nameIdentifier?.replace(TactPsiFactory(project).createIdentifier(name)) + return this + } + + override fun getTextOffset(): Int = nameIdentifier?.textOffset ?: super.getTextOffset() +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/TactPsiFactory.kt b/src/main/kotlin/org/ton/intellij/tact/psi/TactPsiFactory.kt new file mode 100644 index 0000000..42ff4f1 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/TactPsiFactory.kt @@ -0,0 +1,34 @@ +package org.ton.intellij.tact.psi + +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiFileFactory +import com.intellij.util.LocalTimeCounter +import org.ton.intellij.tact.TactFileType +import org.ton.intellij.util.descendantOfTypeStrict + +class TactPsiFactory( + private val project: Project, + private val markGenerated: Boolean = true, + private val eventSystemEnabled: Boolean = false +) { + fun createFile(text: CharSequence): TactFile = createPsiFile(text) as TactFile + + fun createPsiFile(text: CharSequence): PsiFile = PsiFileFactory.getInstance(project) + .createFileFromText( + "DUMMY.tact", + TactFileType, + text, + LocalTimeCounter.currentTime(), + eventSystemEnabled, + markGenerated + ) + + fun createIdentifier(name: String): PsiElement = + createFromText("struct $name {}")?.identifier ?: error("Failed to create identifier") + + private inline fun createFromText( + code: CharSequence + ): T? = createFile(code).descendantOfTypeStrict() +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/TactRecursiveElementWalkingVisitor.kt b/src/main/kotlin/org/ton/intellij/tact/psi/TactRecursiveElementWalkingVisitor.kt new file mode 100644 index 0000000..bec7055 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/TactRecursiveElementWalkingVisitor.kt @@ -0,0 +1,28 @@ +package org.ton.intellij.tact.psi + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiRecursiveVisitor +import com.intellij.psi.PsiWalkingState + +abstract class TactRecursiveElementWalkingVisitor : TactVisitor(), PsiRecursiveVisitor { + private val walkingState = object : PsiWalkingState(this) { + override fun elementFinished(element: PsiElement) { + this@TactRecursiveElementWalkingVisitor.elementFinished(element) + } + } + + override fun visitElement(element: PsiElement) { + walkingState.elementStarted(element) + } + + override fun visitElement(o: TactElement) { + walkingState.elementStarted(o) + } + + protected fun elementFinished(element: PsiElement) { + } + + fun stopWalking() { + walkingState.stopWalking() + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/TactTokenType.kt b/src/main/kotlin/org/ton/intellij/tact/psi/TactTokenType.kt index 6cbf9a2..2b56fea 100644 --- a/src/main/kotlin/org/ton/intellij/tact/psi/TactTokenType.kt +++ b/src/main/kotlin/org/ton/intellij/tact/psi/TactTokenType.kt @@ -2,17 +2,17 @@ package org.ton.intellij.tact.psi import com.intellij.psi.tree.IElementType import com.intellij.psi.tree.TokenSet -import org.ton.intellij.func.FuncLanguage +import org.ton.intellij.tact.TactLanguage import org.ton.intellij.tact.psi.TactElementTypes.* import org.ton.intellij.util.tokenSetOf -open class TactTokenType(debugName: String) : IElementType(debugName, FuncLanguage) +open class TactTokenType(debugName: String) : IElementType(debugName, TactLanguage) val TACT_REGULAR_COMMENTS = tokenSetOf(BLOCK_COMMENT, LINE_COMMENT) val TACT_DOC_COMMENTS = tokenSetOf() val TACT_COMMENTS = TokenSet.orSet(TACT_REGULAR_COMMENTS, TACT_DOC_COMMENTS) -val TACT_STRING_LITERALS = TokenSet.create(STRING_LITERAL) +val TACT_STRING_LITERALS = tokenSetOf(STRING_LITERAL) val TACT_MACROS = tokenSetOf(NAME_MACRO, INTERFACE_MACRO) val TACT_KEYWORDS = tokenSetOf( diff --git a/src/main/kotlin/org/ton/intellij/tact/resolve/TactReference.kt b/src/main/kotlin/org/ton/intellij/tact/resolve/TactReference.kt new file mode 100644 index 0000000..e3f42a7 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/resolve/TactReference.kt @@ -0,0 +1,27 @@ +package org.ton.intellij.tact.resolve + +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.psi.* +import org.ton.intellij.tact.psi.TactElement + +interface TactReference : PsiPolyVariantReference { + override fun getElement(): TactElement + + override fun resolve(): TactElement? + + fun multiResolve(): List +} + +abstract class TactReferenceBase(element: T) : PsiPolyVariantReferenceBase(element), TactReference { + override fun resolve(): TactElement? = super.resolve() as? TactElement + + override fun multiResolve(incompleteCode: Boolean): Array { + return multiResolve().map(::PsiElementResolveResult).toTypedArray() + } + + override fun getVariants(): Array = LookupElement.EMPTY_ARRAY + + override fun equals(other: Any?): Boolean = other is TactReferenceBase<*> && element === other.element + + override fun hashCode(): Int = element.hashCode() +} diff --git a/src/main/kotlin/org/ton/intellij/tact/stub/TactFileStub.kt b/src/main/kotlin/org/ton/intellij/tact/stub/TactFileStub.kt deleted file mode 100644 index b9642bc..0000000 --- a/src/main/kotlin/org/ton/intellij/tact/stub/TactFileStub.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.ton.intellij.tact.stub - -import com.intellij.psi.stubs.PsiFileStubImpl -import org.ton.intellij.func.FuncFileElementType -import org.ton.intellij.tact.psi.TactFile - -class TactFileStub( - file: TactFile?, -) : PsiFileStubImpl(file) { - override fun getType() = FuncFileElementType -} diff --git a/src/main/kotlin/org/ton/intellij/tact/stub/TactStub.kt b/src/main/kotlin/org/ton/intellij/tact/stub/TactStub.kt new file mode 100644 index 0000000..a2fb8a4 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/stub/TactStub.kt @@ -0,0 +1,150 @@ +package org.ton.intellij.tact.stub + +import com.intellij.psi.PsiFile +import com.intellij.psi.StubBuilder +import com.intellij.psi.stubs.* +import com.intellij.psi.tree.IStubFileElementType +import com.intellij.util.BitUtil +import com.intellij.util.io.StringRef +import org.ton.intellij.tact.TactLanguage +import org.ton.intellij.tact.psi.* +import org.ton.intellij.tact.psi.impl.TactFunctionImpl +import org.ton.intellij.tact.stub.index.indexFunction +import org.ton.intellij.util.BitFlagsBuilder + +abstract class TactNamedStub( + parent: StubElement<*>?, + elementType: IStubElementType<*, *>, + name: StringRef?, +) : NamedStubBase(parent, elementType, name) { + constructor(parent: StubElement<*>, elementType: IStubElementType<*, *>, name: String?) : this( + parent, + elementType, + StringRef.fromString(name) + ) + + override fun toString(): String { + return "${javaClass.simpleName}($name)" + } +} + +abstract class TactStubElementType, PsiT : TactElement>( + debugName: String +) : IStubElementType(debugName, TactLanguage) { + + final override fun getExternalId(): String = "tact.${super.toString()}" + + override fun indexStub(stub: StubT, sink: IndexSink) {} +} + +class TactFileStub( + file: TactFile?, +) : PsiFileStubImpl(file) { + override fun getType() = Type + + object Type : IStubFileElementType(TactLanguage) { + private const val STUB_VERSION = 2 + + override fun getStubVersion(): Int = STUB_VERSION + + override fun getBuilder(): StubBuilder = object : DefaultStubBuilder() { + override fun createStubForFile(file: PsiFile): StubElement<*> = + if (file is TactFile) TactFileStub(file) + else super.createStubForFile(file) + } + + override fun indexStub(stub: PsiFileStub<*>, sink: IndexSink) { + super.indexStub(stub, sink) + } + + override fun serialize(stub: TactFileStub, dataStream: StubOutputStream) { + + } + + override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>?): TactFileStub { + return TactFileStub(null) + } + + override fun getExternalId(): String = "tact.FILE" + } +} + +fun factory(name: String): TactStubElementType<*, *> { + return when (name) { + "FUNCTION" -> TactFunctionStub.Type + else -> error("Unknown element type: $name") + } +} + +class TactFunctionStub( + parent: StubElement<*>, + elementType: IStubElementType<*, *>, + name: StringRef?, + val flags: Int, +) : TactNamedStub(parent, elementType, name) { + constructor( + parent: StubElement<*>, elementType: IStubElementType<*, *>, + name: String?, + flags: Int, + ) : this( + parent, + elementType, + StringRef.fromString(name), + flags + ) + + val isNative get() = BitUtil.isSet(flags, Flags.NATIVE) + val isGet get() = BitUtil.isSet(flags, Flags.GET) + val isMutates get() = BitUtil.isSet(flags, Flags.MUTATES) + val isExtends get() = BitUtil.isSet(flags, Flags.EXTENDS) + val isVirtual get() = BitUtil.isSet(flags, Flags.VIRTUAL) + val isOverride get() = BitUtil.isSet(flags, Flags.OVERRIDE) + val isInline get() = BitUtil.isSet(flags, Flags.INLINE) + val isAbstract get() = BitUtil.isSet(flags, Flags.ABSTRACT) + + + object Type : TactStubElementType("FUNCTION") { + override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>): TactFunctionStub { + return TactFunctionStub(parentStub, this, dataStream.readName(), dataStream.readInt()) + } + + override fun serialize(stub: TactFunctionStub, dataStream: StubOutputStream) { + dataStream.writeName(stub.name) + dataStream.writeInt(stub.flags) + } + + override fun createPsi(stub: TactFunctionStub): TactFunction { + return TactFunctionImpl(stub, this) + } + + override fun createStub(psi: TactFunction, parentStub: StubElement<*>): TactFunctionStub { + var flags = 0 + flags = BitUtil.set(flags, Flags.NATIVE, psi.isNative) + flags = BitUtil.set(flags, Flags.GET, psi.isGet) + flags = BitUtil.set(flags, Flags.MUTATES, psi.isMutates) + flags = BitUtil.set(flags, Flags.EXTENDS, psi.isExtends) + flags = BitUtil.set(flags, Flags.VIRTUAL, psi.isVirtual) + flags = BitUtil.set(flags, Flags.OVERRIDE, psi.isOverride) + flags = BitUtil.set(flags, Flags.INLINE, psi.isInline) + flags = BitUtil.set(flags, Flags.ABSTRACT, psi.isAbstract) + return TactFunctionStub(parentStub, this, psi.name, flags) + } + + override fun indexStub(stub: TactFunctionStub, sink: IndexSink) = sink.indexFunction(stub) + } + + private object Flags : BitFlagsBuilder(Limit.INT) { + val NATIVE = nextBitMask() + val GET = nextBitMask() + val MUTATES = nextBitMask() + val EXTENDS = nextBitMask() + val VIRTUAL = nextBitMask() + val OVERRIDE = nextBitMask() + val INLINE = nextBitMask() + val ABSTRACT = nextBitMask() + } + + override fun toString(): String { + return "TactFunctionStub(name=${name}, flags=$flags, isNative=$isNative, isGet=$isGet, isMutates=$isMutates, isExtends=$isExtends, isVirtual=$isVirtual, isOverride=$isOverride, isInline=$isInline, isAbstract=$isAbstract)" + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/stub/index/StubIndexing.kt b/src/main/kotlin/org/ton/intellij/tact/stub/index/StubIndexing.kt new file mode 100644 index 0000000..22bca1a --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/stub/index/StubIndexing.kt @@ -0,0 +1,15 @@ +package org.ton.intellij.tact.stub.index + +import com.intellij.psi.stubs.IndexSink +import org.ton.intellij.tact.stub.TactFunctionStub +import org.ton.intellij.tact.stub.TactNamedStub + +fun IndexSink.indexFunction(stub: TactFunctionStub) { + indexNamedStub(stub) +} + +private fun IndexSink.indexNamedStub(stub: TactNamedStub<*>) { + stub.name?.let { + occurrence(TactNamedElementIndex.KEY, it) + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/stub/index/TactNamedElementIndex.kt b/src/main/kotlin/org/ton/intellij/tact/stub/index/TactNamedElementIndex.kt new file mode 100644 index 0000000..8d62a13 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/stub/index/TactNamedElementIndex.kt @@ -0,0 +1,30 @@ +package org.ton.intellij.tact.stub.index + +import com.intellij.openapi.project.Project +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.stubs.StringStubIndexExtension +import com.intellij.psi.stubs.StubIndexKey +import org.ton.intellij.tact.psi.TactNamedElement +import org.ton.intellij.tact.stub.TactFileStub +import org.ton.intellij.util.checkCommitIsNotInProgress +import org.ton.intellij.util.getElements + +class TactNamedElementIndex : StringStubIndexExtension() { + override fun getVersion(): Int = TactFileStub.Type.stubVersion + + override fun getKey(): StubIndexKey = KEY + + companion object { + val KEY = + StubIndexKey.createIndexKey("org.ton.intellij.tact.stub.index.TactNamedElementIndex") + + fun findElementsByName( + project: Project, + target: String, + scope: GlobalSearchScope = GlobalSearchScope.allScope(project) + ): Collection { + checkCommitIsNotInProgress(project) + return getElements(KEY, target, project, scope) + } + } +} diff --git a/src/main/kotlin/org/ton/intellij/tlb/TlbFileType.kt b/src/main/kotlin/org/ton/intellij/tlb/TlbFileType.kt index 38172d8..e5cee17 100644 --- a/src/main/kotlin/org/ton/intellij/tlb/TlbFileType.kt +++ b/src/main/kotlin/org/ton/intellij/tlb/TlbFileType.kt @@ -5,7 +5,7 @@ import com.intellij.openapi.vfs.VirtualFile import org.jetbrains.annotations.NonNls object TlbFileType : LanguageFileType(TlbLanguage) { - override fun getName() = "TL-B" + override fun getName() = "TLB" override fun getDescription() = "TL-B Schema file" override fun getDefaultExtension() = "tlb" override fun getIcon() = TlbIcons.FILE diff --git a/src/main/kotlin/org/ton/intellij/tlb/TlbLanguage.kt b/src/main/kotlin/org/ton/intellij/tlb/TlbLanguage.kt index 6ec71d6..2b5d5b6 100644 --- a/src/main/kotlin/org/ton/intellij/tlb/TlbLanguage.kt +++ b/src/main/kotlin/org/ton/intellij/tlb/TlbLanguage.kt @@ -3,4 +3,4 @@ package org.ton.intellij.tlb import com.intellij.lang.InjectableLanguage import com.intellij.lang.Language -object TlbLanguage : Language("TL-B", "text/tlb"), InjectableLanguage +object TlbLanguage : Language("tlb", "text/tlb", "text/tl-b"), InjectableLanguage diff --git a/src/main/kotlin/org/ton/intellij/tlb/ide/actions/GenerateFuncTlbAction.kt b/src/main/kotlin/org/ton/intellij/tlb/ide/actions/GenerateFuncTlbAction.kt new file mode 100644 index 0000000..b24d6e9 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tlb/ide/actions/GenerateFuncTlbAction.kt @@ -0,0 +1,105 @@ +package org.ton.intellij.tlb.ide.actions + +import com.intellij.notification.Notification +import com.intellij.notification.NotificationType +import com.intellij.notification.Notifications +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.fileChooser.FileChooserFactory +import com.intellij.openapi.fileChooser.FileSaverDescriptor +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.psi.search.FilenameIndex +import com.intellij.psi.search.ProjectScope +import com.intellij.util.IncorrectOperationException +import io.ktor.utils.io.errors.* +import org.ton.intellij.tlb.psi.TlbFile +import org.ton.tlb.compiler.TlbCompiler +import org.ton.tlb.generator.FuncCodeGen +import org.ton.tlb.parser.TlbGrammar + +class GenerateFuncTlbAction : AnAction() { + override fun getActionUpdateThread(): ActionUpdateThread { + return ActionUpdateThread.BGT + } + + override fun update(e: AnActionEvent) { + val file = e.getData(CommonDataKeys.PSI_FILE) + e.presentation.isEnabledAndVisible = file is TlbFile + } + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val file = e.getData(CommonDataKeys.PSI_FILE) as? TlbFile ?: return + val funcTlbParserFileName = getFuncTlbParserFileName(file) + val files = + FilenameIndex.getVirtualFilesByName(funcTlbParserFileName, ProjectScope.getAllScope(project)).firstOrNull() + val baseDir = files?.parent + ?: FilenameIndex.getVirtualFilesByName("stdlib.fc", ProjectScope.getAllScope(project)).firstOrNull()?.parent + ?: file.virtualFile.parent + + val descriptor = FileSaverDescriptor("Save TL-B Parser", "", "fc") + val virtualFile = FileChooserFactory.getInstance() + .createSaveFileDialog(descriptor, project) + .save(baseDir, funcTlbParserFileName) + ?.getVirtualFile(true) ?: return + + WriteCommandAction.runWriteCommandAction(project, e.presentation.text, null, Runnable { + try { + val text = generateFuncTlbParser(file) + VfsUtil.saveText(virtualFile, text) + Notifications.Bus.notify( + Notification( + "TL-B Generator", + "${virtualFile.name} generated", + "to ${virtualFile.parent.path}", + NotificationType.INFORMATION + ), + project + ) + FileEditorManager.getInstance(project).openFile(virtualFile, false, true) + } catch (e: Throwable) { + if (e is IOException || e is IncorrectOperationException) { + ApplicationManager.getApplication().invokeLater { + Messages.showErrorDialog( + project, + "Failed to create file $funcTlbParserFileName\n${e.localizedMessage}", + "Create FunC TL-B Parser" + ) + } + } else { + throw e + } + } + }) + } + + companion object { + fun generateFuncTlbParser(file: TlbFile): String { + val input = file.text + val ast = TlbGrammar().parseOrThrow(input) + val compiler = TlbCompiler() + ast.forEach { + compiler.compileConstructor(it) + } + val output = buildString { + appendLine(FuncCodeGen.TLB_LIB) + compiler.types.values.forEach { type -> + val funcCodeGen = FuncCodeGen(type) + funcCodeGen.generate(this) + } + } + return output + } + + fun getFuncTlbParserFileName(tlbFile: TlbFile): String { + val name = tlbFile.name + return "$name.fc" + } + } +} diff --git a/src/main/kotlin/org/ton/intellij/tlb/ide/annotation.kt b/src/main/kotlin/org/ton/intellij/tlb/ide/annotation.kt index 440ff9c..2dba710 100644 --- a/src/main/kotlin/org/ton/intellij/tlb/ide/annotation.kt +++ b/src/main/kotlin/org/ton/intellij/tlb/ide/annotation.kt @@ -8,6 +8,7 @@ import com.intellij.psi.PsiElement import org.ton.intellij.tlb.psi.* class TlbAnnotator : Annotator { + override fun annotate(element: PsiElement, holder: AnnotationHolder) { when (element) { is TlbConstructorName -> holder.annotateInfo(element, TlbColor.CONSTRUCTOR_NAME) diff --git a/src/main/kotlin/org/ton/intellij/tlb/ide/colors.kt b/src/main/kotlin/org/ton/intellij/tlb/ide/colors.kt index 25bf87b..e654cbb 100644 --- a/src/main/kotlin/org/ton/intellij/tlb/ide/colors.kt +++ b/src/main/kotlin/org/ton/intellij/tlb/ide/colors.kt @@ -23,6 +23,7 @@ enum class TlbColor( BRACKETS("Brackets", Defaults.BRACKETS), PARENTHESES("Parentheses", Defaults.PARENTHESES), SEMICOLON("Semicolon", Defaults.SEMICOLON), + OPERATION_SIGN("Operation Sign", Defaults.OPERATION_SIGN), NUMBER("Number", Defaults.NUMBER), CONSTRUCTOR_NAME("Constructor name", Defaults.STATIC_METHOD), diff --git a/src/main/kotlin/org/ton/intellij/tlb/ide/highlighter.kt b/src/main/kotlin/org/ton/intellij/tlb/ide/highlighter.kt index 719d147..3e91851 100644 --- a/src/main/kotlin/org/ton/intellij/tlb/ide/highlighter.kt +++ b/src/main/kotlin/org/ton/intellij/tlb/ide/highlighter.kt @@ -14,18 +14,23 @@ class TlbSyntaxHighlighterFactory : SyntaxHighlighterFactory() { object TlbSyntaxHighlighter : SyntaxHighlighterBase() { override fun getHighlightingLexer() = TlbLexerAdapter() + override fun getTokenHighlights(tokenType: IElementType) = when (tokenType) { in TlbParserDefinition.DOCUMENTATION -> TlbColor.DOCUMENTATION in TlbParserDefinition.COMMENTS -> TlbColor.COMMENT in TlbParserDefinition.BRACES -> TlbColor.BRACES in TlbParserDefinition.BRACKETS -> TlbColor.BRACKETS in TlbParserDefinition.PARENTHESES -> TlbColor.PARENTHESES + SEMICOLUMN -> TlbColor.SEMICOLON NUMBER -> TlbColor.NUMBER HEX_TAG -> TlbColor.HEX_TAG BINARY_TAG -> TlbColor.BINARY_TAG + PREDIFINED_TYPE -> TlbColor.TYPE + IDENTIFIER -> TlbColor.FIELD_NAME + CIRCUMFLEX, COLUMN, EQ -> TlbColor.OPERATION_SIGN in TlbParserDefinition.BUILTIN_TYPES -> TlbColor.TYPE else -> null }.let { pack(it?.textAttributesKey) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/org/ton/intellij/tlb/psi/TlbFile.kt b/src/main/kotlin/org/ton/intellij/tlb/psi/TlbFile.kt index 9696cef..e996888 100644 --- a/src/main/kotlin/org/ton/intellij/tlb/psi/TlbFile.kt +++ b/src/main/kotlin/org/ton/intellij/tlb/psi/TlbFile.kt @@ -8,5 +8,5 @@ import org.ton.intellij.tlb.TlbLanguage class TlbFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, TlbLanguage), TlbElement { override fun getFileType(): FileType = TlbFileType - override fun toString(): String = "TL-B" -} \ No newline at end of file + override fun toString(): String = "TLB" +} diff --git a/src/main/kotlin/org/ton/intellij/tlb/psi/elements.kt b/src/main/kotlin/org/ton/intellij/tlb/psi/elements.kt index d77c947..9e6f613 100644 --- a/src/main/kotlin/org/ton/intellij/tlb/psi/elements.kt +++ b/src/main/kotlin/org/ton/intellij/tlb/psi/elements.kt @@ -15,8 +15,7 @@ abstract class TlbNamedElementImpl(node: ASTNode) : TlbElementImpl(node), TlbNam override fun getNameIdentifier(): PsiElement? = findChildByType(TlbTypes.IDENTIFIER) override fun getName(): String? = nameIdentifier?.text override fun setName(name: String): PsiElement = apply { - TODO() -// nameIdentifier?.replace(project.TlbPsiFactory.createIdentifier(name)) + nameIdentifier?.replace(project.tlbPsiFactory.createIdentifier(name)) } override fun getTextOffset(): Int = nameIdentifier?.textOffset ?: super.getTextOffset() @@ -24,4 +23,4 @@ abstract class TlbNamedElementImpl(node: ASTNode) : TlbElementImpl(node), TlbNam abstract class TlbNamedRefMixin(node: ASTNode) : TlbNamedElementImpl(node), TlbNamedRef { override fun getReference() = TlbNamedRefReference(this) -} \ No newline at end of file +} diff --git a/src/main/kotlin/org/ton/intellij/tlb/psi/factory.kt b/src/main/kotlin/org/ton/intellij/tlb/psi/factory.kt index 9504250..4ebe6fe 100644 --- a/src/main/kotlin/org/ton/intellij/tlb/psi/factory.kt +++ b/src/main/kotlin/org/ton/intellij/tlb/psi/factory.kt @@ -1,6 +1,7 @@ package org.ton.intellij.tlb.psi import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement import com.intellij.psi.PsiFileFactory import org.ton.intellij.tlb.TlbFileType import org.ton.intellij.util.childOfType @@ -8,8 +9,10 @@ import org.ton.intellij.util.childOfType val Project.tlbPsiFactory get() = TlbPsiFactory(this) class TlbPsiFactory(val project: Project) { - inline fun createFromText(code: CharSequence): T? = + inline fun createFromText(code: CharSequence): T? = PsiFileFactory.getInstance(project) .createFileFromText("DUMMY.tlb", TlbFileType, code) .childOfType() + + fun createIdentifier(text: String) = createFromText("$text#_ = $text;")!!.identifier } diff --git a/src/main/kotlin/org/ton/intellij/util/BitFlagsBuilder.kt b/src/main/kotlin/org/ton/intellij/util/BitFlagsBuilder.kt new file mode 100644 index 0000000..3f2fff7 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/util/BitFlagsBuilder.kt @@ -0,0 +1,18 @@ +package org.ton.intellij.util + +abstract class BitFlagsBuilder private constructor(private val limit: Limit, startFromBit: Int) { + protected constructor(limit: Limit) : this(limit, 0) + protected constructor(prevBuilder: BitFlagsBuilder, limit: Limit) : this(limit, prevBuilder.counter) + + private var counter: Int = startFromBit + + protected fun nextBitMask(): Int { + val nextBit = counter++ + if (nextBit == limit.bits) error("Bitmask index out of $limit limit!") + return 1 shl nextBit + } + + protected enum class Limit(val bits: Int) { + BYTE(8), INT(32) + } +} diff --git a/src/main/kotlin/org/ton/intellij/util/Collections.kt b/src/main/kotlin/org/ton/intellij/util/Collections.kt new file mode 100644 index 0000000..c370d11 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/util/Collections.kt @@ -0,0 +1,33 @@ +package org.ton.intellij.util + +fun Sequence.infiniteWith(with: T): Sequence = + this + generateSequence { with } + +fun Iterator.nextOrNull(): T? = + if (hasNext()) next() else null + +typealias WithNextValue = Pair + +fun Sequence.withNext(): Sequence> = WithNextSequence(this) + +private class WithNextSequence(private val sequence: Sequence) : Sequence> { + + override fun iterator(): Iterator> = WithNextIterator(sequence.iterator()) +} + +private class WithNextIterator(private val iterator: Iterator) : Iterator> { + + private var next: T? = null + + override fun hasNext() = next != null || iterator.hasNext() + + override fun next(): WithNextValue { + if (next == null) { // The first invocation (or illegal after-the-last invocation) + next = iterator.next() + } + val next = next ?: throw NoSuchElementException() + val nextNext = iterator.nextOrNull() + this.next = nextNext + return WithNextValue(next, nextNext) + } +} diff --git a/src/main/kotlin/org/ton/intellij/util/DocLine.kt b/src/main/kotlin/org/ton/intellij/util/DocLine.kt new file mode 100644 index 0000000..d6657a0 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/util/DocLine.kt @@ -0,0 +1,107 @@ +package org.ton.intellij.util + +import com.intellij.util.text.CharArrayUtil +import com.intellij.util.text.CharSequenceSubSequence +import kotlin.math.min + +data class DocLine( + private val text: CharSequence, + private val startOffset: Int, + private val endOffset: Int, + val contentStartOffset: Int = startOffset, + private val contentEndOffset: Int = endOffset, + val isLastLine: Boolean, + val isRemoved: Boolean = false, +) { + init { + require(contentEndOffset >= contentStartOffset) { "`$text`, $contentStartOffset, $contentEndOffset" } + } + + val prefix: CharSequence get() = CharSequenceSubSequence(text, startOffset, contentStartOffset) + val content: CharSequence get() = CharSequenceSubSequence(text, contentStartOffset, contentEndOffset) + val suffix: CharSequence get() = CharSequenceSubSequence(text, contentEndOffset, endOffset) + + val hasPrefix: Boolean get() = startOffset != contentStartOffset + val hasContent: Boolean get() = contentStartOffset != contentEndOffset + val hasSuffix: Boolean get() = contentEndOffset != endOffset + + val contentLength: Int get() = contentEndOffset - contentStartOffset + + fun removePrefix(prefix: String): DocLine { + return if (CharArrayUtil.regionMatches(text, contentStartOffset, contentEndOffset, prefix)) { + copy(contentStartOffset = contentStartOffset + prefix.length, contentEndOffset = contentEndOffset) + } else { + this + } + } + + fun removeSuffix(suffix: String): DocLine { + if (contentLength < suffix.length) return this + return if (CharArrayUtil.regionMatches(text, contentEndOffset - suffix.length, contentEndOffset, suffix)) { + copy(contentStartOffset = contentStartOffset, contentEndOffset = contentEndOffset - suffix.length) + } else { + this + } + } + + fun markRemoved(): DocLine { + return copy(contentEndOffset = contentStartOffset, isRemoved = true) + } + + fun trimStart(): DocLine { + val newOffset = shiftForwardWhitespace() + return copy(contentStartOffset = newOffset, contentEndOffset = contentEndOffset) + } + + fun countStartWhitespace(): Int { + return shiftForwardWhitespace() - contentStartOffset + } + + private fun shiftForwardWhitespace(): Int = + CharArrayUtil.shiftForward(text, contentStartOffset, contentEndOffset, " \t") + + fun substring(startIndex: Int): DocLine { + require(startIndex <= contentLength) + return copy(contentStartOffset = contentStartOffset + startIndex) + } + + companion object { + fun splitLines(text: CharSequence): Sequence { + var prev = 0 + return generateSequence { + if (prev == -1) return@generateSequence null + val index = text.indexOf(char = '\n', startIndex = prev) + if (index >= 0) { + val line = DocLine(text, startOffset = prev, endOffset = index, isLastLine = false) + prev = index + 1 + line + } else { + val line = DocLine(text, startOffset = prev, endOffset = text.length, isLastLine = true) + prev = -1 + line + } + } + } + } +} + +public fun Sequence.removeEolDecoration(infix: String): Sequence = + map { it.trimStart().removePrefix(infix) }.removeCommonIndent() + +public fun Sequence.removeCommonIndent(): Sequence { + val lines = toList() + + val minIndent = lines.fold(Int.MAX_VALUE) { minIndent, line -> + if (line.isRemoved || line.content.isBlank()) { + minIndent + } else { + min(minIndent, line.countStartWhitespace()) + } + } + + return lines.asSequence().map { line -> + line.substring(min(minIndent, line.contentLength)) + } +} + +public fun Sequence.content() = mapNotNull { if (it.isRemoved) null else it.content } diff --git a/src/main/kotlin/org/ton/intellij/util/IntellijUtils.kt b/src/main/kotlin/org/ton/intellij/util/IntellijUtils.kt index b35bd0e..6312835 100644 --- a/src/main/kotlin/org/ton/intellij/util/IntellijUtils.kt +++ b/src/main/kotlin/org/ton/intellij/util/IntellijUtils.kt @@ -1,13 +1,24 @@ package org.ton.intellij.util +import com.intellij.extapi.psi.StubBasedPsiElementBase +import com.intellij.lang.ASTNode import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.EditorFactory import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Computable +import com.intellij.openapi.util.RecursionManager import com.intellij.openapi.util.TextRange -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile +import com.intellij.patterns.PlatformPatterns +import com.intellij.patterns.PsiElementPattern +import com.intellij.psi.* +import com.intellij.psi.impl.PsiDocumentManagerBase +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.stubs.StubElement +import com.intellij.psi.stubs.StubIndex +import com.intellij.psi.stubs.StubIndexKey import com.intellij.psi.tree.IElementType import com.intellij.psi.tree.TokenSet import com.intellij.psi.util.PsiTreeUtil @@ -42,3 +53,66 @@ fun PsiElement.findExistingEditor(): Editor? { val editors = editorFactory.getEditors(document) return editors.firstOrNull() } + +val PsiElement.contexts: Sequence + get() = generateSequence(this) { + if (it is PsiFile) null else it.context + } + +fun ASTNode?.isWhitespaceOrEmpty(): Boolean { + return this == null || textLength == 0 || elementType == TokenType.WHITE_SPACE +} + +@Suppress("UNCHECKED_CAST") +inline val > StubBasedPsiElement.greenStub: T? + get() = (this as? StubBasedPsiElementBase)?.greenStub + +fun checkCommitIsNotInProgress(project: Project) { + val app = ApplicationManager.getApplication() + if ((app.isUnitTestMode || app.isInternal) && app.isDispatchThread) { + if ((PsiDocumentManager.getInstance(project) as PsiDocumentManagerBase).isCommitInProgress) { + error("Accessing indices during PSI event processing can lead to typing performance issues") + } + } +} + +inline fun getElements( + indexKey: StubIndexKey, + key: Key, project: Project, + scope: GlobalSearchScope? = null +): Collection = + StubIndex.getElements(indexKey, key, project, scope, Psi::class.java) + +inline fun processAllKeys( + indexKey: StubIndexKey, + project: Project, + scope: GlobalSearchScope = GlobalSearchScope.allScope(project), + noinline processor: (Key) -> Boolean +) { + StubIndex.getInstance().processAllKeys(indexKey, processor, scope) +} + +fun recursionGuard(key: Any, block: Computable, memoize: Boolean = true): T? = + RecursionManager.doPreventingRecursion(key, memoize, block) + +inline fun psiElement(): PsiElementPattern.Capture { + return PlatformPatterns.psiElement(I::class.java) +} + +val PsiElement.leftLeaves: Sequence + get() = generateSequence(this, PsiTreeUtil::prevLeaf).drop(1) + +val PsiElement.rightSiblings: Sequence + get() = generateSequence(this.nextSibling) { it.nextSibling } + +val PsiElement.leftSiblings: Sequence + get() = generateSequence(this.prevSibling) { it.prevSibling } + +val PsiElement.childrenWithLeaves: Sequence + get() = generateSequence(this.firstChild) { it.nextSibling } + +val PsiElement.prevVisibleOrNewLine: PsiElement? + get() = leftLeaves + .filterNot { it is PsiComment || it is PsiErrorElement } + .filter { it !is PsiWhiteSpace || it.textContains('\n') } + .firstOrNull() diff --git a/src/main/kotlin/org/ton/intellij/util/SimpleMultiLineTextEscaper.kt b/src/main/kotlin/org/ton/intellij/util/SimpleMultiLineTextEscaper.kt new file mode 100644 index 0000000..6da6f14 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/util/SimpleMultiLineTextEscaper.kt @@ -0,0 +1,19 @@ +package org.ton.intellij.util + +import com.intellij.openapi.util.TextRange +import com.intellij.psi.LiteralTextEscaper +import com.intellij.psi.PsiLanguageInjectionHost + +/** Same as [com.intellij.psi.LiteralTextEscaper.createSimple], but multi line */ +class SimpleMultiLineTextEscaper(host: T) : LiteralTextEscaper(host) { + override fun decode(rangeInsideHost: TextRange, outChars: java.lang.StringBuilder): Boolean { + outChars.append(rangeInsideHost.substring(myHost.text)) + return true + } + + override fun getOffsetInHost(offsetInDecoded: Int, rangeInsideHost: TextRange): Int { + return rangeInsideHost.startOffset + offsetInDecoded + } + + override fun isOneLine(): Boolean = false +} diff --git a/src/main/kotlin/org/ton/intellij/util/snapshot/Snapshot.kt b/src/main/kotlin/org/ton/intellij/util/snapshot/Snapshot.kt new file mode 100644 index 0000000..4187609 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/util/snapshot/Snapshot.kt @@ -0,0 +1,85 @@ +package org.ton.intellij.util.snapshot + +interface Snapshot { + fun commit() + fun rollback() +} + +interface Undoable { + fun undo() +} + +/** An entity that allows you to take a snapshot ([startSnapshot]) and then roll back to snapshot state. */ +abstract class Snapshotable { + protected val undoLog: UndoLog = UndoLog() + + fun startSnapshot(): Snapshot = undoLog.startSnapshot() +} + +class UndoLog { + private val undoLog: MutableList = mutableListOf() + + fun logChange(undoable: Undoable) { + if (inSnapshot()) undoLog.add(undoable) + } + + fun startSnapshot(): Snapshot = LogBasedSnapshot.start(undoLog) + + private fun inSnapshot(): Boolean = undoLog.isNotEmpty() +} + +private class LogBasedSnapshot private constructor( + private val undoLog: MutableList, + val position: Int +) : Snapshot { + override fun commit() { + assertOpenSnapshot() + if (position == 0) { + undoLog.clear() + } else { + undoLog[position] = CommittedSnapshot + } + } + + override fun rollback() { + assertOpenSnapshot() + if (position + 1 != undoLog.size) { + val toRollback = undoLog.subList(position + 1, undoLog.size) + toRollback.asReversed().forEach(Undoable::undo) + toRollback.clear() + } + + val last = undoLog.removeLast() + check(last == OpenSnapshot) + check(undoLog.size == position) + } + + private fun assertOpenSnapshot() { + check(undoLog.getOrNull(position) == OpenSnapshot) + } + + companion object { + fun start(undoLog: MutableList): Snapshot { + undoLog.add(OpenSnapshot) + return LogBasedSnapshot(undoLog, undoLog.size - 1) + } + } + + private object OpenSnapshot : Undoable { + override fun undo() { + error("Cannot rollback an uncommitted snapshot") + } + } + + private object CommittedSnapshot : Undoable { + override fun undo() { + // This occurs when there are nested snapshots and + // the inner is committed but outer is rolled back. + } + } +} + +class CombinedSnapshot(private vararg val snapshots: Snapshot) : Snapshot { + override fun rollback() = snapshots.forEach { it.rollback() } + override fun commit() = snapshots.forEach { it.commit() } +} diff --git a/src/main/kotlin/org/ton/intellij/util/unify/Unify.kt b/src/main/kotlin/org/ton/intellij/util/unify/Unify.kt new file mode 100644 index 0000000..a553f15 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/util/unify/Unify.kt @@ -0,0 +1,122 @@ +package org.ton.intellij.util.unify + +import org.ton.intellij.util.snapshot.Snapshot +import org.ton.intellij.util.snapshot.UndoLog +import org.ton.intellij.util.snapshot.Undoable + +interface NodeOrValue +interface Node : NodeOrValue { + var parent: NodeOrValue +} + +data class VarValue(val value: V?, val rank: Int) : NodeOrValue + +/** + * [UnificationTable] is map from [K] to [V] with additional ability + * to redirect certain K's to a single V en-masse with the help of + * disjoint set union. + * + * We implement Tarjan's union-find + * algorithm: when two keys are unified, one of them is converted + * into a "redirect" pointing at the other. These redirects form a + * DAG: the roots of the DAG (nodes that are not redirected) are each + * associated with a value of type `V` and a rank. The rank is used + * to keep the DAG relatively balanced, which helps keep the running + * time of the algorithm under control. For more information, see + * [Disjoint-set data structure](https://en.wikipedia.org/wiki/Disjoint-set_data_structure). + */ +@Suppress("UNCHECKED_CAST") +class UnificationTable { + private val undoLog: UndoLog = UndoLog() + + @Suppress("UNCHECKED_CAST") + private data class Root(val key: K) { + private val varValue: VarValue = key.parent as VarValue + val rank: Int get() = varValue.rank + val value: V? get() = varValue.value + } + + private fun get(key: Node): Root { + val parent = key.parent + return if (parent is Node) { + val root = get(parent) + if (key.parent != root.key) { + logNodeState(key) + key.parent = root.key // Path compression + } + root + } else { + Root(key as K) + } + } + + private fun setValue(root: Root, value: V) { + logNodeState(root.key) + root.key.parent = VarValue(value, root.rank) + } + + private fun unify(rootA: Root, rootB: Root, newValue: V?): K { + return when { + // a has greater rank, so a should become b's parent, + // i.e., b should redirect to a. + rootA.rank > rootB.rank -> redirectRoot(rootA.rank, rootB, rootA, newValue) + // b has greater rank, so a should redirect to b. + rootA.rank < rootB.rank -> redirectRoot(rootB.rank, rootA, rootB, newValue) + // If equal, redirect one to the other and increment the + // other's rank. + else -> redirectRoot(rootA.rank + 1, rootA, rootB, newValue) + } + } + + private fun redirectRoot(newRank: Int, oldRoot: Root, newRoot: Root, newValue: V?): K { + val oldRootKey = oldRoot.key + val newRootKey = newRoot.key + logNodeState(newRootKey) + logNodeState(oldRootKey) + oldRootKey.parent = newRootKey + newRootKey.parent = VarValue(newValue, newRank) + return newRootKey + } + + fun findRoot(key: K): K = get(key).key + + fun findValue(key: K): V? = get(key).value + + fun unifyVarVar(key1: K, key2: K): K { + val node1 = get(key1) + val node2 = get(key2) + + if (node1.key == node2.key) return node1.key // already unified + + val val1 = node1.value + val val2 = node2.value + + val newVal = if (val1 != null && val2 != null) { + if (val1 != val2) error("unification error") // must be solved on the upper level + val1 + } else { + val1 ?: val2 + } + + return unify(node1, node2, newVal) + } + + fun unifyVarValue(key: K, value: V) { + val node = get(key) + if (node.value != null && node.value != value) error("unification error") // must be solved on the upper level + + setValue(node, value) + } + + private fun logNodeState(node: Node) { + undoLog.logChange(SetParent(node, node.parent)) + } + + fun startSnapshot(): Snapshot = undoLog.startSnapshot() + + private data class SetParent(private val node: Node, private val oldParent: NodeOrValue) : Undoable { + override fun undo() { + node.parent = oldParent + } + } +} diff --git a/src/main/resources/META-INF/func.xml b/src/main/resources/META-INF/func.xml index 2e18cd2..43bc86d 100644 --- a/src/main/resources/META-INF/func.xml +++ b/src/main/resources/META-INF/func.xml @@ -33,8 +33,9 @@ implementationClass="org.ton.intellij.func.ide.completion.FuncCompletionContributor"/> + - + @@ -46,8 +47,12 @@ implementation="org.ton.intellij.func.template.FuncEverywhereContextType"/>