diff --git a/java/java-syntax/BUILD.bazel b/java/java-syntax/BUILD.bazel
index dfee08536ce9..3a4722949250 100644
--- a/java/java-syntax/BUILD.bazel
+++ b/java/java-syntax/BUILD.bazel
@@ -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",
diff --git a/java/java-syntax/intellij.java.syntax.iml b/java/java-syntax/intellij.java.syntax.iml
index 875490376c17..338ea00b50f4 100644
--- a/java/java-syntax/intellij.java.syntax.iml
+++ b/java/java-syntax/intellij.java.syntax.iml
@@ -32,5 +32,6 @@
+
\ No newline at end of file
diff --git a/java/java-syntax/src/com/intellij/java/syntax/JavaSyntaxDefinition.kt b/java/java-syntax/src/com/intellij/java/syntax/JavaSyntaxDefinition.kt
index ae5004cb2db9..2b40c4a24e0b 100644
--- a/java/java-syntax/src/com/intellij/java/syntax/JavaSyntaxDefinition.kt
+++ b/java/java-syntax/src/com/intellij/java/syntax/JavaSyntaxDefinition.kt
@@ -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)
}
diff --git a/java/java-syntax/src/com/intellij/java/syntax/JavaSyntaxLog.kt b/java/java-syntax/src/com/intellij/java/syntax/JavaSyntaxLog.kt
new file mode 100644
index 000000000000..06942f0b97b6
--- /dev/null
+++ b/java/java-syntax/src/com/intellij/java/syntax/JavaSyntaxLog.kt
@@ -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()
+}
\ No newline at end of file
diff --git a/java/java-syntax/src/com/intellij/java/syntax/element/JavaBindingPolicy.kt b/java/java-syntax/src/com/intellij/java/syntax/element/JavaBindingPolicy.kt
new file mode 100644
index 000000000000..478938b4996e
--- /dev/null
+++ b/java/java-syntax/src/com/intellij/java/syntax/element/JavaBindingPolicy.kt
@@ -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
+}
\ No newline at end of file
diff --git a/java/java-syntax/src/com/intellij/java/syntax/element/JavaCodeBlockParser.kt b/java/java-syntax/src/com/intellij/java/syntax/element/JavaCodeBlockParser.kt
new file mode 100644
index 000000000000..0b573a8586fb
--- /dev/null
+++ b/java/java-syntax/src/com/intellij/java/syntax/element/JavaCodeBlockParser.kt
@@ -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)
+ }
+}
\ No newline at end of file
diff --git a/java/java-syntax/src/com/intellij/java/syntax/element/JavaLanguageLevelProvider.kt b/java/java-syntax/src/com/intellij/java/syntax/element/JavaLanguageLevelProvider.kt
new file mode 100644
index 000000000000..f39f8f441650
--- /dev/null
+++ b/java/java-syntax/src/com/intellij/java/syntax/element/JavaLanguageLevelProvider.kt
@@ -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 = ExtensionPointKey("com.intellij.java.syntax.languageLevelProvider")
diff --git a/java/java-syntax/src/com/intellij/java/syntax/element/JavaSyntaxElementType.kt b/java/java-syntax/src/com/intellij/java/syntax/element/JavaSyntaxElementType.kt
index 8cf6e2efc99b..f6b1112259c0 100644
--- a/java/java-syntax/src/com/intellij/java/syntax/element/JavaSyntaxElementType.kt
+++ b/java/java-syntax/src/com/intellij/java/syntax/element/JavaSyntaxElementType.kt
@@ -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")
diff --git a/java/java-syntax/src/com/intellij/java/syntax/element/JavaSyntaxUtil.kt b/java/java-syntax/src/com/intellij/java/syntax/element/JavaSyntaxUtil.kt
new file mode 100644
index 000000000000..d8b7d6cef63a
--- /dev/null
+++ b/java/java-syntax/src/com/intellij/java/syntax/element/JavaSyntaxUtil.kt
@@ -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)
+ }
+}
diff --git a/platform/syntax/syntax-api/api-dump-experimental.txt b/platform/syntax/syntax-api/api-dump-experimental.txt
index 49e9d6b443db..7eabfcc34f6f 100644
--- a/platform/syntax/syntax-api/api-dump-experimental.txt
+++ b/platform/syntax/syntax-api/api-dump-experimental.txt
@@ -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
+- (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
diff --git a/platform/syntax/syntax-api/src/com/intellij/platform/syntax/LazyParser.kt b/platform/syntax/syntax-api/src/com/intellij/platform/syntax/LazyParser.kt
new file mode 100644
index 000000000000..3c96370fd359
--- /dev/null
+++ b/platform/syntax/syntax-api/src/com/intellij/platform/syntax/LazyParser.kt
@@ -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}")
+}
\ No newline at end of file
diff --git a/platform/syntax/syntax-api/src/com/intellij/platform/syntax/SyntaxElementType.kt b/platform/syntax/syntax-api/src/com/intellij/platform/syntax/SyntaxElementType.kt
index 0fd853f94d5d..0cf7e21b6f7a 100644
--- a/platform/syntax/syntax-api/src/com/intellij/platform/syntax/SyntaxElementType.kt
+++ b/platform/syntax/syntax-api/src/com/intellij/platform/syntax/SyntaxElementType.kt
@@ -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)
diff --git a/platform/syntax/syntax-api/src/com/intellij/platform/syntax/tree/SyntaxTree.kt b/platform/syntax/syntax-api/src/com/intellij/platform/syntax/tree/SyntaxTree.kt
new file mode 100644
index 000000000000..eeeed8e3876f
--- /dev/null
+++ b/platform/syntax/syntax-api/src/com/intellij/platform/syntax/tree/SyntaxTree.kt
@@ -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?
+}
diff --git a/platform/syntax/syntax-util/api-dump-experimental.txt b/platform/syntax/syntax-util/api-dump-experimental.txt
index 50e2434498cf..1e4bab237301 100644
--- a/platform/syntax/syntax-util/api-dump-experimental.txt
+++ b/platform/syntax/syntax-util/api-dump-experimental.txt
@@ -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
- (com.intellij.platform.syntax.parser.SyntaxTreeBuilder):V
diff --git a/platform/syntax/syntax-util/src/com/intellij/platform/syntax/util/parser/SyntaxBuilderUtil.kt b/platform/syntax/syntax-util/src/com/intellij/platform/syntax/util/parser/SyntaxBuilderUtil.kt
index bbb8204c63ee..e82ba94ce5b1 100644
--- a/platform/syntax/syntax-util/src/com/intellij/platform/syntax/util/parser/SyntaxBuilderUtil.kt
+++ b/platform/syntax/syntax-util/src/com/intellij/platform/syntax/util/parser/SyntaxBuilderUtil.kt
@@ -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()
}
}
}
\ No newline at end of file