diff --git a/java/java-frontback-psi-impl/src/com/intellij/java/frontback/psi/impl/syntax/JShellSyntaxDefinitionExtension.kt b/java/java-frontback-psi-impl/src/com/intellij/java/frontback/psi/impl/syntax/JShellSyntaxDefinitionExtension.kt
new file mode 100644
index 000000000000..fb29d1581ca6
--- /dev/null
+++ b/java/java-frontback-psi-impl/src/com/intellij/java/frontback/psi/impl/syntax/JShellSyntaxDefinitionExtension.kt
@@ -0,0 +1,14 @@
+// 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.frontback.psi.impl.syntax
+
+import com.intellij.java.syntax.JavaSyntaxDefinition
+import com.intellij.platform.syntax.SyntaxElementTypeSet
+import com.intellij.platform.syntax.lexer.Lexer
+import com.intellij.platform.syntax.psi.LanguageSyntaxDefinition
+import com.intellij.pom.java.LanguageLevel
+
+internal class JShellSyntaxDefinitionExtension : LanguageSyntaxDefinition {
+ override fun getLexer(): Lexer = JavaSyntaxDefinition.createLexer(LanguageLevel.HIGHEST)
+
+ override fun getCommentTokens(): SyntaxElementTypeSet = JavaSyntaxDefinition.commentSet
+}
\ No newline at end of file
diff --git a/java/java-psi-impl/resources/META-INF/JavaPsiPlugin.xml b/java/java-psi-impl/resources/META-INF/JavaPsiPlugin.xml
index d7395449af53..c8ef17fb4466 100644
--- a/java/java-psi-impl/resources/META-INF/JavaPsiPlugin.xml
+++ b/java/java-psi-impl/resources/META-INF/JavaPsiPlugin.xml
@@ -106,6 +106,7 @@
+
@@ -136,6 +137,8 @@
+
+
IMPORT_PARSED_CONDITION = tokenType -> JavaElementType.IMPORT_STATEMENT == tokenType;
- private static final Predicate DECLARATION_PARSED_CONDITION = tokenType -> TOP_LEVEL_DECLARATIONS.contains(tokenType);
-
- private final FileParser myJShellFileParser = new FileParser(this) {
- private final TokenSet IMPORT_PARSING_STOP_LIST = TokenSet.orSet(
- IMPORT_LIST_STOPPER_SET,
- TokenSet.orSet(
- ElementType.MODIFIER_BIT_SET,
- ElementType.JAVA_COMMENT_BIT_SET,
- ElementType.EXPRESSION_BIT_SET,
- ElementType.JAVA_STATEMENT_BIT_SET,
- ElementType.PRIMITIVE_TYPE_BIT_SET,
- TokenSet.create(JShellElementType.ROOT_CLASS, JavaTokenType.IDENTIFIER)
- )
- );
-
- @Override
- public void parse(@NotNull PsiBuilder builder) {
- parseImportList(builder, b -> IMPORT_PARSING_STOP_LIST.contains(b.getTokenType()));
-
- final PsiBuilder.Marker rootClass = builder.mark();
- try {
- while (!builder.eof()) {
- PsiBuilder.Marker wrapper = builder.mark();
- IElementType wrapperType = null;
-
- PsiBuilder.Marker marker = parseImportStatement(builder);
- if (isParsed(marker, builder, IMPORT_PARSED_CONDITION)) {
- wrapperType = JShellElementType.IMPORT_HOLDER;
- }
- else {
- revert(marker);
- marker = getDeclarationParser().parse(builder, DeclarationParser.Context.JSHELL);
- if (isParsed(marker, builder, DECLARATION_PARSED_CONDITION) && !((PsiBuilderImpl)builder).hasErrorsAfter(marker)) {
- wrapper.drop(); // don't need wrapper for top-level declaration
- wrapper = null;
- }
- else {
- revert(marker);
- marker = getStatementParser().parseStatement(builder);
- if (marker != null && !((PsiBuilderImpl)builder).hasErrorsAfter(marker)) {
- wrapperType = JShellElementType.STATEMENTS_HOLDER;
- }
- else {
- revert(marker);
- marker = getExpressionParser().parse(builder);
- wrapperType = marker != null ? JShellElementType.STATEMENTS_HOLDER : null;
- }
- }
- }
-
- if (marker == null) {
- wrapper.drop();
- break;
- }
-
- if (wrapper != null) {
- wrapper.done(wrapperType);
- }
- }
-
- if (!builder.eof()) {
- builder.mark().error(JavaPsiBundle.message("unexpected.token"));
- while (!builder.eof()) {
- builder.advanceLexer();
- }
- }
- }
- finally {
- rootClass.done(JShellElementType.ROOT_CLASS);
- }
- }
- };
-
- private static boolean isParsed(@Nullable PsiBuilder.Marker parsedMarker, PsiBuilder builder, final Predicate super IElementType> cond) {
- if (parsedMarker == null) {
- return false;
- }
- final LighterASTNode lastDone = builder.getLatestDoneMarker();
- if (lastDone == null) {
- return false;
- }
- return cond.test(lastDone.getTokenType());
- }
-
- private static void revert(PsiBuilder.Marker parsedMarker) {
- if (parsedMarker != null) {
- parsedMarker.rollbackTo();
- }
- }
-
- @Override
- public @NotNull FileParser getFileParser() {
- return myJShellFileParser;
- }
-}
diff --git a/java/java-psi-impl/src/com/intellij/lang/java/syntax/JShellElementTypeConverter.kt b/java/java-psi-impl/src/com/intellij/lang/java/syntax/JShellElementTypeConverter.kt
new file mode 100644
index 000000000000..cb0be62b25c6
--- /dev/null
+++ b/java/java-psi-impl/src/com/intellij/lang/java/syntax/JShellElementTypeConverter.kt
@@ -0,0 +1,20 @@
+// 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.lang.java.syntax
+
+import com.intellij.java.syntax.element.JShellSyntaxElementType
+import com.intellij.lang.java.JShellParserDefinition
+import com.intellij.platform.syntax.psi.ElementTypeConverter
+import com.intellij.platform.syntax.psi.ElementTypeConverterFactory
+import com.intellij.platform.syntax.psi.elementTypeConverterOf
+import com.intellij.psi.impl.source.tree.JShellElementType
+
+internal class JShellElementTypeConverterExtension : ElementTypeConverterFactory {
+ override fun getElementTypeConverter(): ElementTypeConverter = JShellElementTypeConverter
+}
+
+internal val JShellElementTypeConverter: ElementTypeConverter = elementTypeConverterOf(
+ JShellSyntaxElementType.FILE to JShellParserDefinition.FILE_ELEMENT_TYPE,
+ JShellSyntaxElementType.ROOT_CLASS to JShellElementType.ROOT_CLASS,
+ JShellSyntaxElementType.IMPORT_HOLDER to JShellElementType.IMPORT_HOLDER,
+ JShellSyntaxElementType.STATEMENTS_HOLDER to JShellElementType.STATEMENTS_HOLDER,
+)
\ No newline at end of file
diff --git a/java/java-syntax/src/com/intellij/java/syntax/element/JShellSyntaxElementType.kt b/java/java-syntax/src/com/intellij/java/syntax/element/JShellSyntaxElementType.kt
new file mode 100644
index 000000000000..f82a20d285fc
--- /dev/null
+++ b/java/java-syntax/src/com/intellij/java/syntax/element/JShellSyntaxElementType.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.java.syntax.element
+
+import com.intellij.platform.syntax.SyntaxElementType
+
+object JShellSyntaxElementType {
+ val FILE: SyntaxElementType = SyntaxElementType("JSHELL_FILE")
+ val ROOT_CLASS: SyntaxElementType = SyntaxElementType("JSHELL_ROOT_CLASS")
+ val STATEMENTS_HOLDER: SyntaxElementType = SyntaxElementType("JSHELL_STATEMENTS_HOLDER")
+ val IMPORT_HOLDER: SyntaxElementType = SyntaxElementType("JSHELL_IMPORT_HOLDER")
+}
diff --git a/java/java-syntax/src/com/intellij/java/syntax/parser/FileParser.kt b/java/java-syntax/src/com/intellij/java/syntax/parser/FileParser.kt
index ca1f492a17e9..f87ca8458f2a 100644
--- a/java/java-syntax/src/com/intellij/java/syntax/parser/FileParser.kt
+++ b/java/java-syntax/src/com/intellij/java/syntax/parser/FileParser.kt
@@ -1,13 +1,11 @@
// 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.parser
-import com.intellij.java.syntax.JavaSyntaxBundle.message
-import com.intellij.java.syntax.JavaSyntaxBundle.resourceBundle
+import com.intellij.java.syntax.JavaSyntaxBundle
import com.intellij.java.syntax.element.JavaSyntaxElementType
import com.intellij.java.syntax.element.JavaSyntaxTokenType
import com.intellij.java.syntax.element.SyntaxElementTypes
import com.intellij.java.syntax.element.WhiteSpaceAndCommentSetHolder
-import com.intellij.openapi.util.NlsContexts
import com.intellij.platform.syntax.SyntaxElementType
import com.intellij.platform.syntax.SyntaxElementTypeSet
import com.intellij.platform.syntax.i18n.ResourceBundle
@@ -16,8 +14,12 @@ import com.intellij.platform.syntax.syntaxElementTypeSetOf
import com.intellij.platform.syntax.util.parser.SyntaxBuilderUtil.expect
open class FileParser(private val myParser: JavaParser) {
- fun parse(builder: SyntaxTreeBuilder) {
- parseFile(builder, { b -> this.stopImportListParsing(b) }, resourceBundle, "expected.class.or.interface")
+ open fun parse(builder: SyntaxTreeBuilder) {
+ parseFile(builder = builder,
+ importListStopper = { b -> this.stopImportListParsing(b) },
+ bundle = JavaSyntaxBundle.resourceBundle,
+ errorMessageKey = "expected.class.or.interface"
+ )
}
fun parseFile(
@@ -44,7 +46,7 @@ open class FileParser(private val myParser: JavaParser) {
if (declaration == null) declaration = parseInitial(builder)
if (declaration != null) {
if (invalidElements != null) {
- invalidElements.errorBefore(error(bundle, errorMessageKey), declaration)
+ invalidElements.errorBefore(bundle.message(errorMessageKey), declaration)
invalidElements = null
}
if (firstDeclarationOk == null) {
@@ -66,7 +68,7 @@ open class FileParser(private val myParser: JavaParser) {
if (firstDeclarationOk == null) firstDeclarationOk = false
}
- invalidElements?.error(error(bundle, errorMessageKey))
+ invalidElements?.error(bundle.message(errorMessageKey))
if (impListInfo.second && firstDeclarationOk == true) {
impListInfo.first.setCustomEdgeTokenBinders(myWhiteSpaceAndCommentSetHolder.getPrecedingCommentBinder(myParser.languageLevel),
@@ -108,7 +110,7 @@ open class FileParser(private val myParser: JavaParser) {
val ref = myParser.referenceParser.parseJavaCodeReference(builder, true, false, false, false)
if (ref == null) {
- statement.error(message("expected.class.or.interface"))
+ statement.error(JavaSyntaxBundle.message("expected.class.or.interface"))
return
}
@@ -117,7 +119,7 @@ open class FileParser(private val myParser: JavaParser) {
JavaParserUtil.done(statement, JavaSyntaxElementType.PACKAGE_STATEMENT, myParser.languageLevel, myWhiteSpaceAndCommentSetHolder)
}
- private fun parseImportList(
+ protected fun parseImportList(
builder: SyntaxTreeBuilder,
stopper: (SyntaxTreeBuilder) -> Boolean,
): Pair {
@@ -138,7 +140,7 @@ open class FileParser(private val myParser: JavaParser) {
if (statement != null) {
isEmpty = false
if (invalidElements != null) {
- invalidElements.errorBefore(message("unexpected.token"), statement)
+ invalidElements.errorBefore(JavaSyntaxBundle.message("unexpected.token"), statement)
invalidElements = null
}
continue
@@ -180,10 +182,11 @@ open class FileParser(private val myParser: JavaParser) {
}
//if it is `module` we should expect either `;` or `identifier`
- if (isOk && !isModule && !isStatic && builder.tokenType !== JavaSyntaxTokenType.SEMICOLON &&
+ if (isOk && !isModule &&
+ !isStatic && builder.tokenType !== JavaSyntaxTokenType.SEMICOLON &&
PsiKeyword.MODULE == identifierText
) {
- JavaParserUtil.error(builder, message("expected.identifier.or.semicolon"))
+ JavaParserUtil.error(builder, JavaSyntaxBundle.message("expected.identifier.or.semicolon"))
}
else if (isOk) {
JavaParserUtil.semicolon(builder)
@@ -209,9 +212,6 @@ open class FileParser(private val myParser: JavaParser) {
return JavaSyntaxElementType.IMPORT_STATEMENT
}
- private fun error(bundle: ResourceBundle, errorMessageKey: String): @NlsContexts.ParsingError String {
- return bundle.message(errorMessageKey)
- }
}
val IMPORT_LIST_STOPPER_SET: SyntaxElementTypeSet =
diff --git a/java/java-syntax/src/com/intellij/java/syntax/parser/JShellParser.kt b/java/java-syntax/src/com/intellij/java/syntax/parser/JShellParser.kt
new file mode 100644
index 000000000000..200c252c5185
--- /dev/null
+++ b/java/java-syntax/src/com/intellij/java/syntax/parser/JShellParser.kt
@@ -0,0 +1,112 @@
+// 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.parser
+
+import com.intellij.java.syntax.JavaSyntaxBundle
+import com.intellij.java.syntax.element.JShellSyntaxElementType
+import com.intellij.java.syntax.element.JavaSyntaxElementType
+import com.intellij.java.syntax.element.JavaSyntaxTokenType
+import com.intellij.java.syntax.element.SyntaxElementTypes
+import com.intellij.platform.syntax.SyntaxElementType
+import com.intellij.platform.syntax.SyntaxElementTypeSet
+import com.intellij.platform.syntax.parser.SyntaxTreeBuilder
+import com.intellij.platform.syntax.syntaxElementTypeSetOf
+import com.intellij.pom.java.LanguageLevel
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+
+class JShellParser(languageLevel: LanguageLevel) : JavaParser(languageLevel) {
+ override val fileParser: FileParser = object : FileParser(this) {
+ override fun parse(builder: SyntaxTreeBuilder) {
+ parseImportList(builder) { b -> b.tokenType in IMPORT_PARSING_STOP_LIST }
+
+ val rootClass: SyntaxTreeBuilder.Marker = builder.mark()
+ try {
+ while (!builder.eof()) {
+ var wrapper: SyntaxTreeBuilder.Marker? = builder.mark()
+ var wrapperType: SyntaxElementType? = null
+
+ var marker: SyntaxTreeBuilder.Marker? = parseImportStatement(builder)
+ if (isParsed(marker, builder) { tokenType -> tokenType === JavaSyntaxElementType.IMPORT_STATEMENT }) {
+ wrapperType = JShellSyntaxElementType.IMPORT_HOLDER
+ }
+ else {
+ marker?.rollbackTo()
+ marker = declarationParser.parse(builder, DeclarationParser.Context.JSHELL)
+ if (isParsed(marker, builder) { tokenType -> tokenType in TOP_LEVEL_DECLARATIONS } &&
+ !builder.hasErrorsAfter(marker)
+ ) {
+ wrapper!!.drop() // don't need wrapper for top-level declaration
+ wrapper = null
+ }
+ else {
+ marker?.rollbackTo()
+ marker = statementParser.parseStatement(builder)
+ if (marker != null && !builder.hasErrorsAfter(marker)) {
+ wrapperType = JShellSyntaxElementType.STATEMENTS_HOLDER
+ }
+ else {
+ marker?.rollbackTo()
+ marker = expressionParser.parse(builder)
+ wrapperType = if (marker != null) JShellSyntaxElementType.STATEMENTS_HOLDER else null
+ }
+ }
+ }
+
+ if (marker == null) {
+ wrapper!!.drop()
+ break
+ }
+
+ wrapper?.done(wrapperType!!)
+ }
+
+ if (!builder.eof()) {
+ builder.mark().error(JavaSyntaxBundle.message("unexpected.token"))
+ while (!builder.eof()) {
+ builder.advanceLexer()
+ }
+ }
+ }
+ finally {
+ rootClass.done(JShellSyntaxElementType.ROOT_CLASS)
+ }
+ }
+ }
+
+ fun parse(builder: SyntaxTreeBuilder) {
+ val root = builder.mark()
+ fileParser.parse(builder)
+ root.done(JShellSyntaxElementType.FILE)
+ }
+
+ @OptIn(ExperimentalContracts::class)
+ private fun isParsed(
+ parsedMarker: SyntaxTreeBuilder.Marker?,
+ builder: SyntaxTreeBuilder,
+ cond: (SyntaxElementType) -> Boolean,
+ ): Boolean {
+ contract {
+ returns(true) implies (parsedMarker != null)
+ }
+ if (parsedMarker == null) return false
+ val lastDone = builder.lastDoneMarker ?: return false
+ return cond(lastDone.getNodeType())
+ }
+}
+
+private val TOP_LEVEL_DECLARATIONS: SyntaxElementTypeSet = syntaxElementTypeSetOf(
+ JavaSyntaxElementType.FIELD,
+ JavaSyntaxElementType.METHOD,
+ JavaSyntaxElementType.CLASS
+)
+
+private val IMPORT_PARSING_STOP_LIST: SyntaxElementTypeSet =
+ IMPORT_LIST_STOPPER_SET +
+ SyntaxElementTypes.MODIFIER_BIT_SET +
+ SyntaxElementTypes.JAVA_COMMENT_BIT_SET +
+ SyntaxElementTypes.EXPRESSION_BIT_SET +
+ SyntaxElementTypes.JAVA_STATEMENT_BIT_SET +
+ SyntaxElementTypes.PRIMITIVE_TYPE_BIT_SET +
+ JShellSyntaxElementType.ROOT_CLASS +
+ JavaSyntaxTokenType.IDENTIFIER
+
diff --git a/java/java-tests/testSrc/com/intellij/java/parser/JavaParsingTestCase.java b/java/java-tests/testSrc/com/intellij/java/parser/JavaParsingTestCase.java
index a380fa0fa540..724e6718df4c 100644
--- a/java/java-tests/testSrc/com/intellij/java/parser/JavaParsingTestCase.java
+++ b/java/java-tests/testSrc/com/intellij/java/parser/JavaParsingTestCase.java
@@ -2,11 +2,16 @@
package com.intellij.java.parser;
import com.intellij.JavaTestUtil;
+import com.intellij.java.frontback.psi.impl.syntax.JavaSyntaxDefinitionExtension;
import com.intellij.lang.LanguageASTFactory;
import com.intellij.lang.ParserDefinition;
+import com.intellij.lang.java.JShellLanguage;
import com.intellij.lang.java.JavaLanguage;
+import com.intellij.lang.java.syntax.JavaElementTypeConverterExtension;
import com.intellij.openapi.roots.LanguageLevelProjectExtension;
import com.intellij.openapi.roots.impl.LanguageLevelProjectExtensionImpl;
+import com.intellij.platform.syntax.psi.ElementTypeConverters;
+import com.intellij.platform.syntax.psi.LanguageSyntaxDefinitions;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.source.tree.JavaASTFactory;
@@ -29,6 +34,8 @@ public abstract class JavaParsingTestCase extends ParsingTestCase {
getProject().registerService(WorkspaceModelTopics.class, new WorkspaceModelTopics());
getProject().registerService(LanguageLevelProjectExtension.class, new LanguageLevelProjectExtensionImpl(getProject()));
addExplicitExtension(LanguageASTFactory.INSTANCE, JavaLanguage.INSTANCE, new JavaASTFactory());
+ addExplicitExtension(LanguageSyntaxDefinitions.getINSTANCE(), JavaLanguage.INSTANCE, new JavaSyntaxDefinitionExtension());
+ addExplicitExtension(ElementTypeConverters.getInstance(), JavaLanguage.INSTANCE, new JavaElementTypeConverterExtension());
}
@Override
diff --git a/java/java-tests/testSrc/com/intellij/java/parser/jshell/JShellParsingTest.java b/java/java-tests/testSrc/com/intellij/java/parser/jshell/JShellParsingTest.java
index 8eefa5520d5a..84ccedc75bb1 100644
--- a/java/java-tests/testSrc/com/intellij/java/parser/jshell/JShellParsingTest.java
+++ b/java/java-tests/testSrc/com/intellij/java/parser/jshell/JShellParsingTest.java
@@ -2,9 +2,17 @@
package com.intellij.java.parser.jshell;
import com.intellij.ide.highlighter.JShellFileType;
+import com.intellij.java.frontback.psi.impl.syntax.JShellSyntaxDefinitionExtension;
+import com.intellij.java.frontback.psi.impl.syntax.JavaSyntaxDefinitionExtension;
import com.intellij.java.parser.JavaParsingTestCase;
+import com.intellij.lang.java.JShellLanguage;
import com.intellij.lang.java.JShellParserDefinition;
+import com.intellij.lang.java.JavaLanguage;
import com.intellij.lang.java.JavaParserDefinition;
+import com.intellij.lang.java.syntax.JShellElementTypeConverterExtension;
+import com.intellij.lang.java.syntax.JavaElementTypeConverterExtension;
+import com.intellij.platform.syntax.psi.ElementTypeConverters;
+import com.intellij.platform.syntax.psi.LanguageSyntaxDefinitions;
public class JShellParsingTest extends JavaParsingTestCase {
@@ -12,6 +20,14 @@ public class JShellParsingTest extends JavaParsingTestCase {
super("parser-full/jshell", JShellFileType.DEFAULT_EXTENSION, new JShellParserDefinition(), new JavaParserDefinition());
}
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ addExplicitExtension(ElementTypeConverters.getInstance(), JShellLanguage.INSTANCE, new JavaElementTypeConverterExtension());
+ addExplicitExtension(ElementTypeConverters.getInstance(), JShellLanguage.INSTANCE, new JShellElementTypeConverterExtension());
+ addExplicitExtension(LanguageSyntaxDefinitions.getINSTANCE(), JShellLanguage.INSTANCE, new JShellSyntaxDefinitionExtension());
+ }
+
public void testClass0() { doTest(); }
public void testStatement0() { doTest(); }
public void testStatement1() { doTest(); }