syntax: introduce lazy parseables

GitOrigin-RevId: dfb8f5593c78ecae135b1d014707ac48d3ffbf74
This commit is contained in:
Max Medvedev
2025-05-26 21:44:45 +02:00
committed by intellij-monorepo-bot
parent a58b3405e5
commit 16ea12d6ae
15 changed files with 414 additions and 14 deletions

View File

@@ -31,6 +31,7 @@ jvm_library(
"//platform/util/base/multiplatform",
"//platform/util/multiplatform",
"//platform/syntax/syntax-util:util",
"//platform/syntax/syntax-extensions:extensions",
],
exports = [
"//platform/syntax/syntax-api:syntax",

View File

@@ -32,5 +32,6 @@
<orderEntry type="module" module-name="intellij.platform.util.base.multiplatform" />
<orderEntry type="module" module-name="intellij.platform.util.multiplatform" />
<orderEntry type="module" module-name="intellij.platform.syntax.util" exported="" />
<orderEntry type="module" module-name="intellij.platform.syntax.extensions" />
</component>
</module>

View File

@@ -7,12 +7,16 @@ import com.intellij.java.syntax.lexer.JavaDocLexer
import com.intellij.java.syntax.lexer.JavaLexer
import com.intellij.java.syntax.lexer.JavaTypeEscapeLexer
import com.intellij.platform.syntax.SyntaxElementTypeSet
import com.intellij.platform.syntax.element.SyntaxTokenTypes
import com.intellij.platform.syntax.extensions.SyntaxLanguage
import com.intellij.platform.syntax.lexer.Lexer
import com.intellij.platform.syntax.syntaxElementTypeSetOf
import com.intellij.pom.java.LanguageLevel
import kotlin.jvm.JvmStatic
object JavaSyntaxDefinition {
val language: SyntaxLanguage = SyntaxLanguage("com.intellij.java")
@JvmStatic
fun createLexer(languageLevel: LanguageLevel): Lexer = JavaLexer(languageLevel)
@@ -27,4 +31,6 @@ object JavaSyntaxDefinition {
JavaSyntaxTokenType.C_STYLE_COMMENT,
JavaDocSyntaxElementType.DOC_COMMENT
)
val whitespaces: SyntaxElementTypeSet = syntaxElementTypeSetOf(SyntaxTokenTypes.WHITE_SPACE)
}

View File

@@ -0,0 +1,9 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.syntax
import com.intellij.platform.syntax.Logger
import com.intellij.platform.syntax.util.log.logger
internal object JavaSyntaxLog {
val log: Logger = logger<JavaSyntaxLog>()
}

View File

@@ -0,0 +1,30 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.syntax.element
import com.intellij.platform.syntax.SyntaxElementType
import com.intellij.platform.syntax.element.SyntaxTokenTypes
import com.intellij.platform.syntax.parser.WhitespaceOrCommentBindingPolicy
import com.intellij.platform.syntax.syntaxElementTypeSetOf
internal object JavaBindingPolicy : WhitespaceOrCommentBindingPolicy {
private val leftBoundTokens = syntaxElementTypeSetOf(
SyntaxTokenTypes.ERROR_ELEMENT, // todo move somewhere?
JavaSyntaxElementType.TYPE_PARAMETER_LIST,
JavaSyntaxElementType.NAME_VALUE_PAIR,
JavaSyntaxElementType.ANNOTATION_PARAMETER_LIST,
JavaSyntaxElementType.EXTENDS_LIST,
JavaSyntaxElementType.IMPLEMENTS_LIST,
JavaSyntaxElementType.EXTENDS_BOUND_LIST,
JavaSyntaxElementType.THROWS_LIST,
JavaSyntaxElementType.PROVIDES_WITH_LIST,
JavaSyntaxElementType.PERMITS_LIST,
JavaSyntaxElementType.REFERENCE_PARAMETER_LIST,
JavaSyntaxElementType.EMPTY_EXPRESSION,
JavaSyntaxElementType.EXPRESSION_LIST,
JavaSyntaxElementType.ANNOTATION_PARAMETER_LIST,
)
override fun isLeftBound(elementType: SyntaxElementType): Boolean =
elementType in leftBoundTokens
}

View File

@@ -0,0 +1,66 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.syntax.element
import com.intellij.java.syntax.parser.JavaParser
import com.intellij.platform.syntax.CancellationProvider
import com.intellij.platform.syntax.LazyParser
import com.intellij.platform.syntax.LazyParsingContext
import com.intellij.platform.syntax.lexer.TokenList
import com.intellij.platform.syntax.parser.ProductionResult
import com.intellij.platform.syntax.parser.prepareProduction
import com.intellij.platform.syntax.tree.SyntaxNode
import com.intellij.platform.syntax.util.cancellation.cancellationProvider
import com.intellij.platform.syntax.util.parser.SyntaxBuilderUtil
import com.intellij.pom.java.LanguageLevel
internal class JavaCodeBlockParser : LazyParser {
override fun parse(parsingContext: LazyParsingContext): ProductionResult {
return doParse(
node = parsingContext.node,
cachedLexemes = parsingContext.tokenList,
text = parsingContext.text,
level = getLanguageLevel(parsingContext),
cancellationProvider = cancellationProvider()
)
}
override fun tryReparse(parsingContext: LazyParsingContext): ProductionResult? {
val cancellationProvider = cancellationProvider()
val level = getLanguageLevel(parsingContext)
val tokens = cachedOrLex(
cachedLexemes = parsingContext.tokenList,
text = parsingContext.text,
languageLevel = level,
cancellationProvider = cancellationProvider
)
val hasProperBraceBalance = SyntaxBuilderUtil.hasProperBraceBalance(
tokenList = tokens,
leftBrace = JavaSyntaxTokenType.LBRACE,
rightBrace = JavaSyntaxTokenType.RBRACE,
cancellationProvider = cancellationProvider
)
if (!hasProperBraceBalance) return null
return doParse(
node = parsingContext.node,
cachedLexemes = tokens,
text = parsingContext.text,
level = level,
cancellationProvider = cancellationProvider
)
}
private fun doParse(
node: SyntaxNode,
cachedLexemes: TokenList? = null,
text: CharSequence,
level: LanguageLevel,
cancellationProvider: CancellationProvider?,
): ProductionResult {
val builder = createSyntaxBuilder(node, text, level, cachedLexemes, cancellationProvider)
JavaParser(level).statementParser.parseCodeBlockDeep(builder, true)
return prepareProduction(builder)
}
}

View File

@@ -0,0 +1,25 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.syntax.element
import com.intellij.platform.syntax.LazyParsingContext
import com.intellij.platform.syntax.extensions.ExtensionPointKey
import com.intellij.platform.syntax.extensions.ExtensionSupport
import com.intellij.platform.syntax.tree.SyntaxTree
import com.intellij.pom.java.LanguageLevel
import org.jetbrains.annotations.ApiStatus
/**
* Implement this extension point to provide the custom language level for Java lazy parsers.
*/
@ApiStatus.OverrideOnly
interface JavaLanguageLevelProvider {
fun getLanguageLevel(syntaxTree: SyntaxTree): LanguageLevel
}
internal fun getLanguageLevel(parsingContext: LazyParsingContext): LanguageLevel {
val languageLevelProvider = ExtensionSupport().getExtensions(languageLevelExtensionPoint).firstOrNull()
val languageLevel = languageLevelProvider?.getLanguageLevel(parsingContext.tree) ?: LanguageLevel.HIGHEST
return languageLevel
}
private val languageLevelExtensionPoint: ExtensionPointKey<JavaLanguageLevelProvider> = ExtensionPointKey("com.intellij.java.syntax.languageLevelProvider")

View File

@@ -119,7 +119,7 @@ object JavaSyntaxElementType {
@JvmField val DEFAULT_CASE_LABEL_ELEMENT: SyntaxElementType = SyntaxElementType("DEFAULT_CASE_LABEL_ELEMENT")
@JvmField val CASE_LABEL_ELEMENT_LIST: SyntaxElementType = SyntaxElementType("CASE_LABEL_ELEMENT_LIST")
@JvmField val CODE_BLOCK: SyntaxElementType = SyntaxElementType("CODE_BLOCK")
@JvmField val CODE_BLOCK: SyntaxElementType = SyntaxElementType("CODE_BLOCK", lazyParser = JavaCodeBlockParser())
@JvmField val MEMBERS: SyntaxElementType = SyntaxElementType("MEMBERS")

View File

@@ -0,0 +1,50 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:JvmName("JavaSyntaxUtil")
package com.intellij.java.syntax.element
import com.intellij.java.syntax.JavaSyntaxDefinition
import com.intellij.java.syntax.JavaSyntaxLog
import com.intellij.platform.syntax.CancellationProvider
import com.intellij.platform.syntax.lexer.TokenList
import com.intellij.platform.syntax.lexer.performLexing
import com.intellij.platform.syntax.parser.SyntaxTreeBuilder
import com.intellij.platform.syntax.parser.SyntaxTreeBuilderFactory
import com.intellij.platform.syntax.tree.SyntaxNode
import com.intellij.pom.java.LanguageLevel
import kotlin.jvm.JvmName
internal fun createSyntaxBuilder(
chameleon: SyntaxNode,
text: CharSequence,
languageLevel: LanguageLevel,
cachedLexemes: TokenList? = null,
cancellationProvider: CancellationProvider?,
): SyntaxTreeBuilder {
val lexemes = cachedOrLex(cachedLexemes, text, languageLevel, cancellationProvider)
val builder = SyntaxTreeBuilderFactory.builder(
text = text,
whitespaces = JavaSyntaxDefinition.whitespaces,
comments = JavaSyntaxDefinition.commentSet,
tokenList = lexemes,
)
.withStartOffset(chameleon.startOffset)
.withLanguage(JavaSyntaxDefinition.language.id)
.withWhitespaceOrCommentBindingPolicy(JavaBindingPolicy)
.withCancellationProvider(cancellationProvider)
.withLogger(JavaSyntaxLog.log)
return builder.build()
}
internal fun cachedOrLex(
cachedLexemes: TokenList?,
text: CharSequence,
languageLevel: LanguageLevel,
cancellationProvider: CancellationProvider?,
): TokenList {
return cachedLexemes ?: run {
val lexer = JavaSyntaxDefinition.createLexer(languageLevel)
performLexing(text, lexer, cancellationProvider, JavaSyntaxLog.log)
}
}

View File

@@ -1,5 +1,17 @@
*:com.intellij.platform.syntax.CancellationProvider
- a:checkCancelled():V
*:com.intellij.platform.syntax.LazyParser
- a:parse(com.intellij.platform.syntax.LazyParsingContext):com.intellij.platform.syntax.parser.ProductionResult
- tryReparse(com.intellij.platform.syntax.LazyParsingContext):com.intellij.platform.syntax.parser.ProductionResult
*f:com.intellij.platform.syntax.LazyParserKt
- *sf:parseLazyNode(com.intellij.platform.syntax.LazyParsingContext):com.intellij.platform.syntax.parser.ProductionResult
- *sf:tryReparseLazyNode(com.intellij.platform.syntax.LazyParsingContext):com.intellij.platform.syntax.parser.ProductionResult
*f:com.intellij.platform.syntax.LazyParsingContext
- <init>(com.intellij.platform.syntax.tree.SyntaxTree,com.intellij.platform.syntax.tree.SyntaxNode,java.lang.CharSequence,com.intellij.platform.syntax.lexer.TokenList):V
- f:getNode():com.intellij.platform.syntax.tree.SyntaxNode
- f:getText():java.lang.CharSequence
- f:getTokenList():com.intellij.platform.syntax.lexer.TokenList
- f:getTree():com.intellij.platform.syntax.tree.SyntaxTree
*:com.intellij.platform.syntax.Logger
- a:debug(java.lang.String,java.lang.Throwable):V
- bs:debug$default(com.intellij.platform.syntax.Logger,java.lang.String,java.lang.Throwable,I,java.lang.Object):V
@@ -20,8 +32,11 @@
- equals(java.lang.Object):Z
- f:getIndex():I
- hashCode():I
- f:isLazyParseable():Z
*f:com.intellij.platform.syntax.SyntaxElementTypeKt
- *sf:SyntaxElementType(java.lang.String):com.intellij.platform.syntax.SyntaxElementType
- *sf:SyntaxElementType(java.lang.String,com.intellij.platform.syntax.LazyParser):com.intellij.platform.syntax.SyntaxElementType
- *bs:SyntaxElementType$default(java.lang.String,com.intellij.platform.syntax.LazyParser,I,java.lang.Object):com.intellij.platform.syntax.SyntaxElementType
*f:com.intellij.platform.syntax.SyntaxElementTypeSet
- java.util.Set
- kotlin.jvm.internal.markers.KMappedMarker
@@ -189,3 +204,17 @@
- f:defaultRightBinder():com.intellij.platform.syntax.parser.WhitespacesAndCommentsBinder
- f:greedyLeftBinder():com.intellij.platform.syntax.parser.WhitespacesAndCommentsBinder
- f:greedyRightBinder():com.intellij.platform.syntax.parser.WhitespacesAndCommentsBinder
*:com.intellij.platform.syntax.tree.SyntaxNode
- a:getEndOffset():I
- a:getErrorMessage():java.lang.String
- a:getFirstChild():com.intellij.platform.syntax.tree.SyntaxNode
- a:getLastChild():com.intellij.platform.syntax.tree.SyntaxNode
- a:getNextSibling():com.intellij.platform.syntax.tree.SyntaxNode
- a:getParent():com.intellij.platform.syntax.tree.SyntaxNode
- a:getPrevSibling():com.intellij.platform.syntax.tree.SyntaxNode
- a:getStartOffset():I
- a:getText():java.lang.CharSequence
- a:getType():com.intellij.platform.syntax.SyntaxElementType
*:com.intellij.platform.syntax.tree.SyntaxTree
- a:getRoot():com.intellij.platform.syntax.tree.SyntaxNode
- a:getText():java.lang.CharSequence

View File

@@ -0,0 +1,79 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:ApiStatus.Experimental
package com.intellij.platform.syntax
import com.intellij.platform.syntax.lexer.TokenList
import com.intellij.platform.syntax.parser.ProductionResult
import com.intellij.platform.syntax.tree.SyntaxNode
import com.intellij.platform.syntax.tree.SyntaxTree
import org.jetbrains.annotations.ApiStatus
/**
* A parser that is attached to so-called chameleon nodes which allows to parse them lazily on demand.
*
* ### Implementation note:
*
* Provided [SyntaxTree] and [SyntaxNode] might be backed by different tree implementations,
* depending on the syntax-lib client. So please don't make any assumptions on the actual types of the passed instances.
*
* If you want to add platform-specific code, introduce an extension point, see [com.intellij.platform.syntax.extensions.ExtensionSupport].
*
* @see parseLazyNode
* @see tryReparseLazyNode
*/
@ApiStatus.Experimental
@ApiStatus.OverrideOnly
interface LazyParser {
/**
* Called when the node is requested to be parsed.
*
* @return the result of the parsing operation
*/
fun parse(parsingContext: LazyParsingContext): ProductionResult
/**
* Called when the node is requested to be reparsed.
*
* @return the result of the parsing operation or `null` if reparsing is not possible
* (e.g., when braces got unbalanced in the next)
*/
fun tryReparse(parsingContext: LazyParsingContext): ProductionResult? = null
}
/**
* Parses the given node and returns [ProductionResult].
*
* @see LazyParser.parse
*/
@ApiStatus.Experimental
fun parseLazyNode(parsingContext: LazyParsingContext): ProductionResult {
return parsingContext.lazyParser.parse(parsingContext)
}
/**
* Tries to reparse the given node and returns [ProductionResult] if possible.
*
* @see LazyParser.tryReparse
*/
@ApiStatus.Experimental
fun tryReparseLazyNode(parsingContext: LazyParsingContext): ProductionResult? {
return parsingContext.lazyParser.tryReparse(parsingContext)
}
/**
* @param tree the tree being parsed
* @param node the node being parsed
* @param text the text of the node being parsed
* @param tokenList the token list being parsed. Might be missing if the parsing engine does not store this information.
*/
@ApiStatus.Experimental
class LazyParsingContext(
val tree: SyntaxTree,
val node: SyntaxNode,
val text: CharSequence,
val tokenList: TokenList?,
) {
internal val lazyParser: LazyParser
get() = node.type.lazyParser ?: error("Node ${node} has non-lazy element type ${node.type}")
}

View File

@@ -8,6 +8,7 @@ import org.jetbrains.annotations.ApiStatus
import kotlin.concurrent.atomics.AtomicInt
import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlin.concurrent.atomics.fetchAndIncrement
import kotlin.jvm.JvmOverloads
/**
* A class defining a token or node type.
@@ -17,10 +18,19 @@ import kotlin.concurrent.atomics.fetchAndIncrement
@ApiStatus.Experimental
class SyntaxElementType internal constructor(
private val debugName: String,
internal val lazyParser: LazyParser?,
@Suppress("unused") unusedParam: Any?, // this parameter is necessary for disambiguation with the factory function
) {
val index: Int = counter.fetchAndIncrement()
/**
* Checks if this element type is lazy-parseable.
* For performing reparse, use [parseLazyNode] and [tryReparseLazyNode] functions.
*
* @return `true` if this element type is lazy-parseable.
*/
fun isLazyParseable(): Boolean = lazyParser != null
override fun toString(): String = debugName
override fun equals(other: Any?): Boolean = this === other
@@ -29,9 +39,11 @@ class SyntaxElementType internal constructor(
}
@ApiStatus.Experimental
@JvmOverloads
fun SyntaxElementType(
debugName: String,
lazyParser: LazyParser? = null,
): SyntaxElementType =
SyntaxElementType(debugName, null as Any?)
SyntaxElementType(debugName, lazyParser, null as Any?)
private val counter = AtomicInt(0)

View File

@@ -0,0 +1,40 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.syntax.tree
import com.intellij.platform.syntax.SyntaxElementType
import org.jetbrains.annotations.ApiStatus
/**
* API for a Syntax Tree.
*
* The tree is passed to [com.intellij.platform.syntax.LazyParser] as the context for parsing.
*/
@ApiStatus.Experimental
interface SyntaxTree {
val text: CharSequence
val root: SyntaxNode
}
/**
* API for a Syntax Node.
*
*/
@ApiStatus.Experimental
interface SyntaxNode {
val text: CharSequence
val type: SyntaxElementType
val startOffset: Int
val endOffset: Int
val parent: SyntaxNode?
val prevSibling: SyntaxNode?
val nextSibling: SyntaxNode?
val firstChild: SyntaxNode?
val lastChild: SyntaxNode?
val errorMessage: String?
}

View File

@@ -109,13 +109,14 @@
- *sf:logger(java.lang.String):com.intellij.platform.syntax.Logger
*f:com.intellij.platform.syntax.util.parser.SyntaxBuilderUtil
- sf:INSTANCE:com.intellij.platform.syntax.util.parser.SyntaxBuilderUtil
- f:advance(com.intellij.platform.syntax.parser.SyntaxTreeBuilder,I):V
- f:drop(com.intellij.platform.syntax.parser.SyntaxTreeBuilder$Marker[]):V
- f:expect(com.intellij.platform.syntax.parser.SyntaxTreeBuilder,com.intellij.platform.syntax.SyntaxElementType):Z
- f:expect(com.intellij.platform.syntax.parser.SyntaxTreeBuilder,com.intellij.platform.syntax.SyntaxElementTypeSet):Z
- sf:advance(com.intellij.platform.syntax.parser.SyntaxTreeBuilder,I):V
- sf:drop(com.intellij.platform.syntax.parser.SyntaxTreeBuilder$Marker[]):V
- sf:expect(com.intellij.platform.syntax.parser.SyntaxTreeBuilder,com.intellij.platform.syntax.SyntaxElementType):Z
- sf:expect(com.intellij.platform.syntax.parser.SyntaxTreeBuilder,com.intellij.platform.syntax.SyntaxElementTypeSet):Z
- sf:hasProperBraceBalance(com.intellij.platform.syntax.lexer.TokenList,com.intellij.platform.syntax.SyntaxElementType,com.intellij.platform.syntax.SyntaxElementType,com.intellij.platform.syntax.CancellationProvider):Z
- sf:hasProperBraceBalance(java.lang.CharSequence,com.intellij.platform.syntax.lexer.Lexer,com.intellij.platform.syntax.SyntaxElementType,com.intellij.platform.syntax.SyntaxElementType,com.intellij.platform.syntax.CancellationProvider):Z
- f:parseBlockLazy(com.intellij.platform.syntax.parser.SyntaxTreeBuilder,com.intellij.platform.syntax.SyntaxElementType,com.intellij.platform.syntax.SyntaxElementType,com.intellij.platform.syntax.SyntaxElementType):com.intellij.platform.syntax.parser.SyntaxTreeBuilder$Marker
- f:rawTokenText(com.intellij.platform.syntax.parser.SyntaxTreeBuilder,I):java.lang.CharSequence
- sf:parseBlockLazy(com.intellij.platform.syntax.parser.SyntaxTreeBuilder,com.intellij.platform.syntax.SyntaxElementType,com.intellij.platform.syntax.SyntaxElementType,com.intellij.platform.syntax.SyntaxElementType):com.intellij.platform.syntax.parser.SyntaxTreeBuilder$Marker
- sf:rawTokenText(com.intellij.platform.syntax.parser.SyntaxTreeBuilder,I):java.lang.CharSequence
*c:com.intellij.platform.syntax.util.parser.SyntaxTreeBuilderAdapter
- com.intellij.platform.syntax.parser.SyntaxTreeBuilder
- <init>(com.intellij.platform.syntax.parser.SyntaxTreeBuilder):V

View File

@@ -5,6 +5,7 @@ import com.intellij.platform.syntax.CancellationProvider
import com.intellij.platform.syntax.SyntaxElementType
import com.intellij.platform.syntax.SyntaxElementTypeSet
import com.intellij.platform.syntax.lexer.Lexer
import com.intellij.platform.syntax.lexer.TokenList
import com.intellij.platform.syntax.parser.SyntaxTreeBuilder
import com.intellij.platform.syntax.parser.WhitespacesBinders
import com.intellij.util.text.CharSequenceSubSequence
@@ -19,6 +20,7 @@ object SyntaxBuilderUtil {
* @param this PSI builder to operate on.
* @param count number of tokens to skip.
*/
@JvmStatic
fun SyntaxTreeBuilder.advance(count: Int) {
repeat(count) {
if (eof()) return
@@ -34,6 +36,7 @@ object SyntaxBuilderUtil {
* @param expectedType expected token.
* @return true if token matches, false otherwise.
*/
@JvmStatic
fun SyntaxTreeBuilder.expect(expectedType: SyntaxElementType?): Boolean {
if (tokenType === expectedType) {
advanceLexer()
@@ -49,6 +52,7 @@ object SyntaxBuilderUtil {
* @param expectedTypes expected token types.
* @return true if token matches, false otherwise.
*/
@JvmStatic
fun SyntaxTreeBuilder.expect(expectedTypes: SyntaxElementTypeSet): Boolean {
if (tokenType in expectedTypes) {
advanceLexer()
@@ -62,12 +66,14 @@ object SyntaxBuilderUtil {
*
* @param markers markers to drop.
*/
@JvmStatic
fun drop(vararg markers: SyntaxTreeBuilder.Marker?) {
for (marker in markers) {
marker?.drop()
}
}
@JvmStatic
fun SyntaxTreeBuilder.rawTokenText(index: Int): CharSequence {
return CharSequenceSubSequence(
baseSequence = text,
@@ -80,6 +86,7 @@ object SyntaxBuilderUtil {
* tries to parse a code block with corresponding left and right braces.
* @return collapsed marker of the block or `null` if there is no code block at all.
*/
@JvmStatic
fun SyntaxTreeBuilder.parseBlockLazy(
leftBrace: SyntaxElementType,
rightBrace: SyntaxElementType,
@@ -114,8 +121,8 @@ object SyntaxBuilderUtil {
}
/**
* Checks if `text` looks like a proper block.
* In particular it
* Checks if [text] looks like a proper block.
* In particular, it
* (1) checks brace balance
* (2) verifies that the block's closing brace is the last token
*
@@ -135,15 +142,59 @@ object SyntaxBuilderUtil {
cancellationProvider: CancellationProvider?,
): Boolean {
lexer.start(text)
return checkBraceBalance(
leftBrace = leftBrace,
rightBrace = rightBrace,
cancellationProvider = cancellationProvider,
advance = lexer::advance,
curType = lexer::getTokenType
)
}
if (lexer.getTokenType() !== leftBrace) return false
/**
* Checks if [tokenList] looks like a proper block.
* In particular, it
* (1) checks brace balance
* (2) verifies that the block's closing brace is the last token
*
* @param tokenList - tokens to check
* @param leftBrace - left brace element type
* @param rightBrace - right brace element type
* @param cancellationProvider - a hook to stop operation if it's not necessary anymore
* @return true if `text` passes the checks
*/
@JvmStatic
fun hasProperBraceBalance(
tokenList: TokenList,
leftBrace: SyntaxElementType,
rightBrace: SyntaxElementType,
cancellationProvider: CancellationProvider?,
): Boolean {
var i = 0
return checkBraceBalance(
leftBrace = leftBrace,
rightBrace = rightBrace,
cancellationProvider = cancellationProvider,
advance = { i++ },
curType = { tokenList.getTokenType(i) }
)
}
private inline fun checkBraceBalance(
leftBrace: SyntaxElementType,
rightBrace: SyntaxElementType,
cancellationProvider: CancellationProvider?,
advance: () -> Unit,
curType: () -> SyntaxElementType?,
): Boolean {
if (curType() !== leftBrace) return false
advance()
lexer.advance()
var balance = 1
while (true) {
cancellationProvider?.checkCancelled()
val type = lexer.getTokenType()
val type = curType()
if (type == null) {
//eof: checking balance
@@ -162,7 +213,7 @@ object SyntaxBuilderUtil {
balance--
}
lexer.advance()
advance()
}
}
}