idea: brace matching and function resolution

This commit is contained in:
2023-09-21 21:08:20 -07:00
parent c4f65a66ca
commit d12aadf18c
22 changed files with 229 additions and 49 deletions

View File

@ -130,6 +130,8 @@ types:
type: Boolean type: Boolean
FunctionCall: FunctionCall:
parent: Expression parent: Expression
referencedElementValue: symbol
referencedElementType: FunctionDefinition
values: values:
- name: symbol - name: symbol
type: Symbol type: Symbol
@ -256,7 +258,8 @@ types:
type: String type: String
SymbolReference: SymbolReference:
parent: Expression parent: Expression
namedElementValue: symbol referencedElementValue: symbol
referencedElementType: Node
values: values:
- name: symbol - name: symbol
type: Symbol type: Symbol

View File

@ -76,15 +76,14 @@ class AstPorkIdeaCodegen(pkg: String, outputDirectory: Path, world: AstWorld) :
if (baseType == "PorkNamedElement") { if (baseType == "PorkNamedElement") {
kotlinClass.imports.add(0, "com.intellij.psi.PsiElement") kotlinClass.imports.add(0, "com.intellij.psi.PsiElement")
kotlinClass.imports.add("gay.pizza.pork.ast.NodeType") kotlinClass.imports.add("gay.pizza.pork.idea.psi.PorkElementHelpers")
kotlinClass.imports.add("gay.pizza.pork.idea.PorkElementTypes")
val getNameFunction = KotlinFunction( val getNameFunction = KotlinFunction(
"getName", "getName",
overridden = true, overridden = true,
returnType = "String?", returnType = "String?",
isImmediateExpression = true isImmediateExpression = true
) )
getNameFunction.body.add("node.findChildByType(PorkElementTypes.elementTypeFor(NodeType.${type.name}))?.text") getNameFunction.body.add("PorkElementHelpers.nameOfNamedElement(this)")
kotlinClass.functions.add(getNameFunction) kotlinClass.functions.add(getNameFunction)
val setNameFunction = KotlinFunction( val setNameFunction = KotlinFunction(
@ -96,8 +95,32 @@ class AstPorkIdeaCodegen(pkg: String, outputDirectory: Path, world: AstWorld) :
KotlinParameter("name", "String") KotlinParameter("name", "String")
) )
) )
setNameFunction.body.add("this") setNameFunction.body.add("PorkElementHelpers.setNameOfNamedElement(this, name)")
kotlinClass.functions.add(setNameFunction) kotlinClass.functions.add(setNameFunction)
val getNameIdentifierFunction = KotlinFunction(
"getNameIdentifier",
overridden = true,
returnType = "PsiElement?",
isImmediateExpression = true
)
getNameIdentifierFunction.body.add("PorkElementHelpers.nameIdentifierOfNamedElement(this)")
kotlinClass.functions.add(getNameIdentifierFunction)
}
if (type.referencedElementValue != null && type.referencedElementType != null) {
kotlinClass.imports.add(0, "com.intellij.psi.PsiReference")
kotlinClass.imports.add("gay.pizza.pork.ast.NodeType")
kotlinClass.imports.add("gay.pizza.pork.idea.psi.PorkElementHelpers")
val getReferenceFunction = KotlinFunction(
"getReference",
overridden = true,
returnType = "PsiReference?",
isImmediateExpression = true
)
getReferenceFunction.body.add("PorkElementHelpers.referenceOfElement(this, NodeType.${type.referencedElementType})")
kotlinClass.functions.add(getReferenceFunction)
} }
write("${type.name}Element.kt", KotlinWriter(kotlinClass)) write("${type.name}Element.kt", KotlinWriter(kotlinClass))
@ -153,10 +176,12 @@ class AstPorkIdeaCodegen(pkg: String, outputDirectory: Path, world: AstWorld) :
), ),
inherits = mutableListOf( inherits = mutableListOf(
"PorkElement(node)", "PorkElement(node)",
"PsiNamedElement" "PsiNamedElement",
"PsiNameIdentifierOwner"
), ),
imports = mutableListOf( imports = mutableListOf(
"com.intellij.lang.ASTNode", "com.intellij.lang.ASTNode",
"com.intellij.psi.PsiNameIdentifierOwner",
"com.intellij.psi.PsiNamedElement" "com.intellij.psi.PsiNamedElement"
) )
) )

View File

@ -1,6 +1,11 @@
package gay.pizza.pork.buildext.ast package gay.pizza.pork.buildext.ast
class AstType(val name: String, var parent: AstType? = null, var namedElementValue: String? = null) { class AstType(
val name: String, var parent: AstType? = null,
var namedElementValue: String? = null,
var referencedElementValue: String? = null,
var referencedElementType: String? = null
) {
private var internalValues: MutableList<AstValue>? = null private var internalValues: MutableList<AstValue>? = null
private val internalEnums = mutableListOf<AstEnum>() private val internalEnums = mutableListOf<AstEnum>()

View File

@ -7,5 +7,7 @@ data class AstTypeDescription(
val parent: String? = null, val parent: String? = null,
val values: List<AstValueDescription>? = null, val values: List<AstValueDescription>? = null,
val enums: List<AstEnumDescription> = emptyList(), val enums: List<AstEnumDescription> = emptyList(),
val namedElementValue: String? = null val namedElementValue: String? = null,
val referencedElementValue: String? = null,
val referencedElementType: String? = null
) )

View File

@ -57,6 +57,14 @@ class AstWorld {
type.namedElementValue = typeDescription.namedElementValue type.namedElementValue = typeDescription.namedElementValue
} }
if (typeDescription.referencedElementValue != null) {
type.referencedElementValue = typeDescription.referencedElementValue
}
if (typeDescription.referencedElementType != null) {
type.referencedElementType = typeDescription.referencedElementType
}
if (typeDescription.values != null) { if (typeDescription.values != null) {
type.markHasValues() type.markHasValues()
for (value in typeDescription.values) { for (value in typeDescription.values) {

View File

@ -39,9 +39,9 @@ enum class TokenType(vararg properties: TokenTypeProperty) {
RightBracket(SingleChar(']')), RightBracket(SingleChar(']')),
LeftParentheses(SingleChar('(')), LeftParentheses(SingleChar('(')),
RightParentheses(SingleChar(')')), RightParentheses(SingleChar(')')),
Not(ManyChars("not"), OperatorFamily), Not(ManyChars("not"), KeywordFamily),
Mod(ManyChars("mod"), OperatorFamily), Mod(ManyChars("mod"), KeywordFamily),
Rem(ManyChars("rem"), OperatorFamily), Rem(ManyChars("rem"), KeywordFamily),
Comma(SingleChar(',')), Comma(SingleChar(',')),
DotDotDot(ManyChars("...")), DotDotDot(ManyChars("...")),
DotDot(ManyChars(".."), Promotion('.', DotDotDot)), DotDot(ManyChars(".."), Promotion('.', DotDotDot)),

View File

@ -0,0 +1,35 @@
package gay.pizza.pork.idea
import com.intellij.lang.BracePair
import com.intellij.lang.PairedBraceMatcher
import com.intellij.psi.PsiFile
import com.intellij.psi.tree.IElementType
import gay.pizza.pork.parser.TokenType
class PorkBraceMatcher : PairedBraceMatcher {
override fun getPairs(): Array<BracePair> = arrayOf(
BracePair(
PorkElementTypes.elementTypeFor(TokenType.LeftCurly),
PorkElementTypes.elementTypeFor(TokenType.RightCurly),
true
),
BracePair(
PorkElementTypes.elementTypeFor(TokenType.LeftParentheses),
PorkElementTypes.elementTypeFor(TokenType.RightParentheses),
false
),
BracePair(
PorkElementTypes.elementTypeFor(TokenType.LeftBracket),
PorkElementTypes.elementTypeFor(TokenType.RightBracket),
false
)
)
override fun isPairedBracesAllowedBeforeType(lbraceType: IElementType, contextType: IElementType?): Boolean {
return true
}
override fun getCodeConstructStart(file: PsiFile?, openingBraceOffset: Int): Int {
return openingBraceOffset
}
}

View File

@ -2,4 +2,5 @@ package gay.pizza.pork.idea
import com.intellij.lang.Language import com.intellij.lang.Language
@Suppress("JavaIoSerializableObjectMustHaveReadResolve")
object PorkLanguage : Language("Pork") object PorkLanguage : Language("Pork")

View File

@ -61,10 +61,12 @@ class PorkLexer : LexerBase() {
internalTokenEnd = currentToken.sourceIndex.index + currentToken.text.length internalTokenEnd = currentToken.sourceIndex.index + currentToken.text.length
} catch (e: ProcessCanceledException) { } catch (e: ProcessCanceledException) {
throw e throw e
} catch (e: Throwable) { } catch (e: BadCharacterError) {
currentTokenType = PsiTokenType.BAD_CHARACTER
internalTokenEnd = bufferEnd
} catch (e: UnterminatedTokenError) {
currentTokenType = PsiTokenType.BAD_CHARACTER currentTokenType = PsiTokenType.BAD_CHARACTER
internalTokenEnd = bufferEnd internalTokenEnd = bufferEnd
log.warn(Tokenizer::class.java.name, e)
} }
} }

View File

@ -3,17 +3,20 @@ package gay.pizza.pork.idea
import com.intellij.lang.ASTNode import com.intellij.lang.ASTNode
import com.intellij.lang.PsiBuilder import com.intellij.lang.PsiBuilder
import com.intellij.lang.PsiParser import com.intellij.lang.PsiParser
import com.intellij.psi.impl.PsiElementBase
import com.intellij.psi.tree.IElementType import com.intellij.psi.tree.IElementType
import gay.pizza.pork.parser.Parser import gay.pizza.pork.parser.Parser
class PorkParser : PsiParser { class PorkParser : PsiParser {
override fun parse(root: IElementType, builder: PsiBuilder): ASTNode { override fun parse(root: IElementType, builder: PsiBuilder): ASTNode {
val marker = builder.mark()
val psiBuilderMarkAttribution = PsiBuilderMarkAttribution(builder) val psiBuilderMarkAttribution = PsiBuilderMarkAttribution(builder)
val source = PsiBuilderTokenSource(builder) val source = PsiBuilderTokenSource(builder)
val parser = Parser(source, psiBuilderMarkAttribution) val parser = Parser(source, psiBuilderMarkAttribution)
try { try {
parser.parseCompilationUnit() parser.parseCompilationUnit()
} catch (_: ExitParser) {} } catch (_: ExitParser) {}
marker.done(root)
return builder.treeBuilt return builder.treeBuilt
} }

View File

@ -1,11 +1,34 @@
package gay.pizza.pork.idea.psi package gay.pizza.pork.idea.psi
import gay.pizza.pork.ast.Node import com.intellij.psi.PsiElement
import gay.pizza.pork.idea.PorkNodeKey import com.intellij.psi.PsiReference
import com.intellij.psi.util.childrenOfType
import gay.pizza.pork.ast.NodeType
import gay.pizza.pork.idea.PorkElementTypes
import gay.pizza.pork.idea.psi.gen.PorkElement import gay.pizza.pork.idea.psi.gen.PorkElement
import gay.pizza.pork.idea.psi.gen.PorkNamedElement
import gay.pizza.pork.idea.psi.gen.SymbolElement
val PorkElement.porkNode: Node? object PorkElementHelpers {
get() = node.getUserData(PorkNodeKey) private val symbolElementType = PorkElementTypes.elementTypeFor(NodeType.Symbol)
val PorkElement.porkNodeChecked: Node fun nameOfNamedElement(element: PorkNamedElement): String? {
get() = porkNode!! val child = element.node.findChildByType(symbolElementType)
return child?.text
}
fun setNameOfNamedElement(element: PorkNamedElement, name: String): PsiElement = element
fun nameIdentifierOfNamedElement(element: PorkNamedElement): PsiElement? {
val child = element.node.findChildByType(symbolElementType)
return child?.psi
}
fun referenceOfElement(element: PorkElement, type: NodeType): PsiReference? {
val textRangeOfSymbolInElement = element.childrenOfType<SymbolElement>().firstOrNull()?.textRangeInParent ?: return null
if (type == NodeType.FunctionDefinition) {
return PorkFunctionReference(element, textRangeOfSymbolInElement)
}
return null
}
}

View File

@ -0,0 +1,22 @@
package gay.pizza.pork.idea.psi
import com.intellij.openapi.util.TextRange
import com.intellij.psi.util.PsiTreeUtil
import gay.pizza.pork.idea.psi.gen.FunctionDefinitionElement
import gay.pizza.pork.idea.psi.gen.PorkElement
class PorkFunctionReference(element: PorkElement, textRange: TextRange) : PorkReference(element, textRange) {
override fun resolve(): PorkElement? {
val functionName = canonicalText
for (file in getRelevantFiles()) {
val thisFileFunctionDefinitions = PsiTreeUtil.collectElementsOfType(file, FunctionDefinitionElement::class.java)
val thisFileFoundFunctionDefinition = thisFileFunctionDefinitions.firstOrNull {
it.name == functionName
}
if (thisFileFoundFunctionDefinition != null) {
return thisFileFoundFunctionDefinition
}
}
return null
}
}

View File

@ -0,0 +1,34 @@
package gay.pizza.pork.idea.psi
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.psi.PsiReferenceBase
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.childrenOfType
import gay.pizza.pork.idea.psi.gen.ImportDeclarationElement
import gay.pizza.pork.idea.psi.gen.PorkElement
import gay.pizza.pork.idea.psi.gen.SymbolElement
abstract class PorkReference(element: PorkElement, textRange: TextRange) : PsiReferenceBase<PsiElement>(element, textRange) {
fun getRelevantFiles(): List<PsiFile> {
val containingFile = element.containingFile
val importDeclarationElements = PsiTreeUtil.collectElementsOfType(containingFile, ImportDeclarationElement::class.java)
val files = mutableListOf<PsiFile>(containingFile)
for (importDeclaration in importDeclarationElements) {
val symbolElements = importDeclaration.childrenOfType<SymbolElement>()
val importType = importDeclaration.childrenOfType<SymbolElement>().first().text
if (importType != "local") {
continue
}
val basicImportPath = symbolElements.drop(1).joinToString("/") { it.text.trim() }
val actualImportPath = "../${basicImportPath}.pork"
val virtualFile = containingFile.virtualFile.findFileByRelativePath(actualImportPath) ?: continue
val psiFile = PsiManager.getInstance(element.project).findFile(virtualFile) ?: continue
files.add(psiFile)
}
return files
}
}

View File

@ -1,6 +1,12 @@
// GENERATED CODE FROM PORK AST CODEGEN // GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.idea.psi.gen package gay.pizza.pork.idea.psi.gen
import com.intellij.psi.PsiReference
import com.intellij.lang.ASTNode import com.intellij.lang.ASTNode
import gay.pizza.pork.ast.NodeType
import gay.pizza.pork.idea.psi.PorkElementHelpers
class FunctionCallElement(node: ASTNode) : PorkElement(node) class FunctionCallElement(node: ASTNode) : PorkElement(node) {
override fun getReference(): PsiReference? =
PorkElementHelpers.referenceOfElement(this, NodeType.FunctionDefinition)
}

View File

@ -3,13 +3,15 @@ package gay.pizza.pork.idea.psi.gen
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.lang.ASTNode import com.intellij.lang.ASTNode
import gay.pizza.pork.ast.NodeType import gay.pizza.pork.idea.psi.PorkElementHelpers
import gay.pizza.pork.idea.PorkElementTypes
class FunctionDefinitionElement(node: ASTNode) : PorkNamedElement(node) { class FunctionDefinitionElement(node: ASTNode) : PorkNamedElement(node) {
override fun getName(): String? = override fun getName(): String? =
node.findChildByType(PorkElementTypes.elementTypeFor(NodeType.FunctionDefinition))?.text PorkElementHelpers.nameOfNamedElement(this)
override fun setName(name: String): PsiElement = override fun setName(name: String): PsiElement =
this PorkElementHelpers.setNameOfNamedElement(this, name)
override fun getNameIdentifier(): PsiElement? =
PorkElementHelpers.nameIdentifierOfNamedElement(this)
} }

View File

@ -3,13 +3,15 @@ package gay.pizza.pork.idea.psi.gen
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.lang.ASTNode import com.intellij.lang.ASTNode
import gay.pizza.pork.ast.NodeType import gay.pizza.pork.idea.psi.PorkElementHelpers
import gay.pizza.pork.idea.PorkElementTypes
class LetAssignmentElement(node: ASTNode) : PorkNamedElement(node) { class LetAssignmentElement(node: ASTNode) : PorkNamedElement(node) {
override fun getName(): String? = override fun getName(): String? =
node.findChildByType(PorkElementTypes.elementTypeFor(NodeType.LetAssignment))?.text PorkElementHelpers.nameOfNamedElement(this)
override fun setName(name: String): PsiElement = override fun setName(name: String): PsiElement =
this PorkElementHelpers.setNameOfNamedElement(this, name)
override fun getNameIdentifier(): PsiElement? =
PorkElementHelpers.nameIdentifierOfNamedElement(this)
} }

View File

@ -3,13 +3,15 @@ package gay.pizza.pork.idea.psi.gen
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.lang.ASTNode import com.intellij.lang.ASTNode
import gay.pizza.pork.ast.NodeType import gay.pizza.pork.idea.psi.PorkElementHelpers
import gay.pizza.pork.idea.PorkElementTypes
class LetDefinitionElement(node: ASTNode) : PorkNamedElement(node) { class LetDefinitionElement(node: ASTNode) : PorkNamedElement(node) {
override fun getName(): String? = override fun getName(): String? =
node.findChildByType(PorkElementTypes.elementTypeFor(NodeType.LetDefinition))?.text PorkElementHelpers.nameOfNamedElement(this)
override fun setName(name: String): PsiElement = override fun setName(name: String): PsiElement =
this PorkElementHelpers.setNameOfNamedElement(this, name)
override fun getNameIdentifier(): PsiElement? =
PorkElementHelpers.nameIdentifierOfNamedElement(this)
} }

View File

@ -2,6 +2,7 @@
package gay.pizza.pork.idea.psi.gen package gay.pizza.pork.idea.psi.gen
import com.intellij.lang.ASTNode import com.intellij.lang.ASTNode
import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.psi.PsiNamedElement import com.intellij.psi.PsiNamedElement
abstract class PorkNamedElement(node: ASTNode) : PorkElement(node), PsiNamedElement abstract class PorkNamedElement(node: ASTNode) : PorkElement(node), PsiNamedElement, PsiNameIdentifierOwner

View File

@ -3,13 +3,15 @@ package gay.pizza.pork.idea.psi.gen
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.lang.ASTNode import com.intellij.lang.ASTNode
import gay.pizza.pork.ast.NodeType import gay.pizza.pork.idea.psi.PorkElementHelpers
import gay.pizza.pork.idea.PorkElementTypes
class SetAssignmentElement(node: ASTNode) : PorkNamedElement(node) { class SetAssignmentElement(node: ASTNode) : PorkNamedElement(node) {
override fun getName(): String? = override fun getName(): String? =
node.findChildByType(PorkElementTypes.elementTypeFor(NodeType.SetAssignment))?.text PorkElementHelpers.nameOfNamedElement(this)
override fun setName(name: String): PsiElement = override fun setName(name: String): PsiElement =
this PorkElementHelpers.setNameOfNamedElement(this, name)
override fun getNameIdentifier(): PsiElement? =
PorkElementHelpers.nameIdentifierOfNamedElement(this)
} }

View File

@ -1,15 +1,12 @@
// GENERATED CODE FROM PORK AST CODEGEN // GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.idea.psi.gen package gay.pizza.pork.idea.psi.gen
import com.intellij.psi.PsiElement import com.intellij.psi.PsiReference
import com.intellij.lang.ASTNode import com.intellij.lang.ASTNode
import gay.pizza.pork.ast.NodeType import gay.pizza.pork.ast.NodeType
import gay.pizza.pork.idea.PorkElementTypes import gay.pizza.pork.idea.psi.PorkElementHelpers
class SymbolReferenceElement(node: ASTNode) : PorkNamedElement(node) { class SymbolReferenceElement(node: ASTNode) : PorkElement(node) {
override fun getName(): String? = override fun getReference(): PsiReference? =
node.findChildByType(PorkElementTypes.elementTypeFor(NodeType.SymbolReference))?.text PorkElementHelpers.referenceOfElement(this, NodeType.Node)
override fun setName(name: String): PsiElement =
this
} }

View File

@ -3,13 +3,15 @@ package gay.pizza.pork.idea.psi.gen
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.lang.ASTNode import com.intellij.lang.ASTNode
import gay.pizza.pork.ast.NodeType import gay.pizza.pork.idea.psi.PorkElementHelpers
import gay.pizza.pork.idea.PorkElementTypes
class VarAssignmentElement(node: ASTNode) : PorkNamedElement(node) { class VarAssignmentElement(node: ASTNode) : PorkNamedElement(node) {
override fun getName(): String? = override fun getName(): String? =
node.findChildByType(PorkElementTypes.elementTypeFor(NodeType.VarAssignment))?.text PorkElementHelpers.nameOfNamedElement(this)
override fun setName(name: String): PsiElement = override fun setName(name: String): PsiElement =
this PorkElementHelpers.setNameOfNamedElement(this, name)
override fun getNameIdentifier(): PsiElement? =
PorkElementHelpers.nameIdentifierOfNamedElement(this)
} }

View File

@ -17,6 +17,9 @@
<lang.commenter <lang.commenter
language="Pork" language="Pork"
implementationClass="gay.pizza.pork.idea.PorkCommenter"/> implementationClass="gay.pizza.pork.idea.PorkCommenter"/>
<lang.braceMatcher
language="Pork"
implementationClass="gay.pizza.pork.idea.PorkBraceMatcher"/>
<psi.declarationProvider implementation="gay.pizza.pork.idea.PorkSymbolDeclarationProvider"/> <psi.declarationProvider implementation="gay.pizza.pork.idea.PorkSymbolDeclarationProvider"/>
</extensions> </extensions>