From 6b4589098cf4dc37a07881c57ff152152888e929 Mon Sep 17 00:00:00 2001 From: Daniel Chao Date: Fri, 26 Apr 2024 11:15:14 -0400 Subject: [PATCH] Switch to adapter pattern (#453) --- .../src/main/kotlin/org/pkl/lsp/Builder.kt | 57 +- .../lsp/{cst/Ident.kt => ErrorMessages.kt} | 20 +- .../src/main/kotlin/org/pkl/lsp/LSPUtil.kt | 12 +- .../main/kotlin/org/pkl/lsp/PklLSPServer.kt | 6 +- .../kotlin/org/pkl/lsp/analyzers/Analyzer.kt | 43 + .../org/pkl/lsp/analyzers/ModifierAnalyzer.kt | 121 ++ .../PklDiagnostic.kt} | 23 +- .../StringLiteralAnalyzer.kt} | 16 +- .../SyntaxAnalyzer.kt} | 11 +- .../src/main/kotlin/org/pkl/lsp/ast/Basic.kt | 34 + .../src/main/kotlin/org/pkl/lsp/ast/Expr.kt | 155 +++ .../src/main/kotlin/org/pkl/lsp/ast/Member.kt | 96 ++ .../kotlin/org/pkl/lsp/ast/ModuleOrClass.kt | 132 ++ .../src/main/kotlin/org/pkl/lsp/ast/Node.kt | 421 +++++++ .../kotlin/org/pkl/lsp/{cst => ast}/Span.kt | 2 +- .../main/kotlin/org/pkl/lsp/ast/Terminal.kt | 145 +++ .../main/kotlin/org/pkl/lsp/ast/TokenType.kt | 135 ++ .../src/main/kotlin/org/pkl/lsp/ast/Type.kt | 54 + .../main/kotlin/org/pkl/lsp/cst/CstBuilder.kt | 1088 ----------------- .../main/kotlin/org/pkl/lsp/cst/DocComment.kt | 18 - .../src/main/kotlin/org/pkl/lsp/cst/Expr.kt | 239 ---- .../src/main/kotlin/org/pkl/lsp/cst/Import.kt | 19 - .../src/main/kotlin/org/pkl/lsp/cst/Module.kt | 174 --- .../kotlin/org/pkl/lsp/cst/ObjectMember.kt | 145 --- .../src/main/kotlin/org/pkl/lsp/cst/Type.kt | 81 -- .../org/pkl/lsp/features/HoverFeature.kt | 70 +- ...rs.properties => errorMessages.properties} | 24 +- .../test/kotlin/org/pkl/lsp/ast/AstTest.kt | 88 ++ 28 files changed, 1571 insertions(+), 1858 deletions(-) rename pkl-lsp/src/main/kotlin/org/pkl/lsp/{cst/Ident.kt => ErrorMessages.kt} (56%) create mode 100644 pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/Analyzer.kt create mode 100644 pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/ModifierAnalyzer.kt rename pkl-lsp/src/main/kotlin/org/pkl/lsp/{cst/Parameter.kt => analyzers/PklDiagnostic.kt} (58%) rename pkl-lsp/src/main/kotlin/org/pkl/lsp/{cst/Modifier.kt => analyzers/StringLiteralAnalyzer.kt} (73%) rename pkl-lsp/src/main/kotlin/org/pkl/lsp/{cst/PklNode.kt => analyzers/SyntaxAnalyzer.kt} (68%) create mode 100644 pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Basic.kt create mode 100644 pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Expr.kt create mode 100644 pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Member.kt create mode 100644 pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/ModuleOrClass.kt create mode 100644 pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Node.kt rename pkl-lsp/src/main/kotlin/org/pkl/lsp/{cst => ast}/Span.kt (97%) create mode 100644 pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Terminal.kt create mode 100644 pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/TokenType.kt create mode 100644 pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Type.kt delete mode 100644 pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/CstBuilder.kt delete mode 100644 pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/DocComment.kt delete mode 100644 pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Expr.kt delete mode 100644 pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Import.kt delete mode 100644 pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Module.kt delete mode 100644 pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/ObjectMember.kt delete mode 100644 pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Type.kt rename pkl-lsp/src/main/resources/org/pkl/lsp/{errors.properties => errorMessages.properties} (83%) create mode 100644 pkl-lsp/src/test/kotlin/org/pkl/lsp/ast/AstTest.kt diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/Builder.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/Builder.kt index 325b06551..ac209ff28 100644 --- a/pkl-lsp/src/main/kotlin/org/pkl/lsp/Builder.kt +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/Builder.kt @@ -25,17 +25,23 @@ import org.eclipse.lsp4j.PublishDiagnosticsParams import org.pkl.core.parser.LexParseException import org.pkl.core.parser.Parser import org.pkl.core.util.IoUtils -import org.pkl.lsp.cst.CstBuilder -import org.pkl.lsp.cst.ParseError -import org.pkl.lsp.cst.PklModule -import org.pkl.lsp.cst.Span +import org.pkl.lsp.LSPUtil.toRange +import org.pkl.lsp.analyzers.Analyzer +import org.pkl.lsp.analyzers.ModifierAnalyzer +import org.pkl.lsp.analyzers.PklDiagnostic +import org.pkl.lsp.ast.Module +import org.pkl.lsp.ast.ModuleImpl +import org.pkl.lsp.ast.Node +import org.pkl.lsp.ast.Span class Builder(private val server: PklLSPServer) { - private var runningBuild: CompletableFuture = CompletableFuture.supplyAsync(::noop) + private var runningBuild: CompletableFuture = CompletableFuture.supplyAsync(::noop) private val parser = Parser() - fun runningBuild(): CompletableFuture = runningBuild + private val analyzers: List = listOf(ModifierAnalyzer(server)) + + fun runningBuild(): CompletableFuture = runningBuild fun requestBuild(file: URI) { val change = IoUtils.readString(file.toURL()) @@ -46,34 +52,47 @@ class Builder(private val server: PklLSPServer) { runningBuild = CompletableFuture.supplyAsync { build(file, change) } } - private fun build(file: URI, change: String): PklModule? { - val cstBuilder = CstBuilder() - try { + private fun build(file: URI, change: String): Module? { + // val cstBuilder = CstBuilder() + return try { server.logger().log("building $file") val moduleCtx = parser.parseModule(change) - val module = cstBuilder.visitModule(moduleCtx) - makeDiagnostics(file, cstBuilder.errors()) + val module = ModuleImpl(moduleCtx) + val diagnostics = annotate(module) + makeDiagnostics(file, diagnostics) return module } catch (e: LexParseException) { server.logger().error("Error building $file: ${e.message}") - makeDiagnostics(file, cstBuilder.errors() + listOf(toParserError(e))) - return null + makeParserDiagnostics(file, listOf(toParserError(e))) + null } catch (e: Exception) { - server.logger().error("Error building $file: ${e.message}") - return null + server.logger().error("Error building $file: ${e.message} ${e.stackTraceToString()}") + null } } - private fun makeDiagnostics(file: URI, errors: List) { + private fun annotate(node: Node): List { + return buildList { + for (annotator in analyzers) { + annotator.annotate(node, this) + } + } + } + + private fun makeParserDiagnostics(file: URI, errors: List) { val diags = errors.map { err -> val msg = resolveErrorMessage(err.errorType, *err.args) - val diag = Diagnostic(LSPUtil.spanToRange(err.span), "$msg\n\n") + val diag = Diagnostic(err.span.toRange(), "$msg\n\n") diag.severity = DiagnosticSeverity.Error diag.source = "Pkl Language Server" server.logger().log("diagnostic: $msg at ${err.span}") diag } + makeDiagnostics(file, diags) + } + + private fun makeDiagnostics(file: URI, diags: List) { server.logger().log("Found ${diags.size} diagnostic errors for $file") val params = PublishDiagnosticsParams(file.toString(), diags) // Have to publish diagnostics even if there are no errors, so we clear previous problems @@ -81,7 +100,7 @@ class Builder(private val server: PklLSPServer) { } companion object { - private fun noop(): PklModule? { + private fun noop(): Module? { return null } @@ -103,3 +122,5 @@ class Builder(private val server: PklLSPServer) { } } } + +class ParseError(val errorType: String, val span: Span, vararg val args: Any) diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Ident.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ErrorMessages.kt similarity index 56% rename from pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Ident.kt rename to pkl-lsp/src/main/kotlin/org/pkl/lsp/ErrorMessages.kt index f70b54d4a..3caef425a 100644 --- a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Ident.kt +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ErrorMessages.kt @@ -13,6 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.pkl.lsp.cst +package org.pkl.lsp -data class Ident(val value: String, val span: Span) +import java.text.MessageFormat +import java.util.* + +object ErrorMessages { + fun create(messageName: String, vararg args: Any): String { + + val locale = Locale.getDefault() + val errorMessage = + ResourceBundle.getBundle("org.pkl.lsp.errorMessages", locale).getString(messageName) + + // only format if `errorMessage` is a format string + if (args.isEmpty()) return errorMessage + + val formatter = MessageFormat(errorMessage, locale) + return formatter.format(args) + } +} diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/LSPUtil.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/LSPUtil.kt index bbaa5b822..8ace96638 100644 --- a/pkl-lsp/src/main/kotlin/org/pkl/lsp/LSPUtil.kt +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/LSPUtil.kt @@ -17,12 +17,16 @@ package org.pkl.lsp import org.eclipse.lsp4j.Position import org.eclipse.lsp4j.Range -import org.pkl.lsp.cst.Span +import org.pkl.lsp.ast.Span object LSPUtil { - fun spanToRange(s: Span): Range { - val start = Position(s.beginLine - 1, s.beginCol - 1) - val end = Position(s.endLine - 1, s.endCol - 1) + fun Span.toRange(): Range { + val start = Position(beginLine - 1, beginCol - 1) + val end = Position(endLine - 1, endCol - 1) return Range(start, end) } + + inline fun List<*>.firstInstanceOf(): T? { + return firstOrNull { it is T } as T? + } } diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/PklLSPServer.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/PklLSPServer.kt index 4270b7cb6..6e59d81b2 100644 --- a/pkl-lsp/src/main/kotlin/org/pkl/lsp/PklLSPServer.kt +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/PklLSPServer.kt @@ -26,8 +26,8 @@ import org.eclipse.lsp4j.services.* class PklLSPServer(private val verbose: Boolean) : LanguageServer, LanguageClientAware { - val workspaceService: PklWorkspaceService = PklWorkspaceService() - val textService: PklTextDocumentService = PklTextDocumentService(this) + private val workspaceService: PklWorkspaceService = PklWorkspaceService() + private val textService: PklTextDocumentService = PklTextDocumentService(this) private lateinit var client: LanguageClient private lateinit var logger: ClientLogger @@ -38,7 +38,7 @@ class PklLSPServer(private val verbose: Boolean) : LanguageServer, LanguageClien res.capabilities.textDocumentSync = Either.forLeft(TextDocumentSyncKind.Full) // Hover capability - res.capabilities.setHoverProvider(true) + // res.capabilities.setHoverProvider(true) return CompletableFuture.supplyAsync { res } } diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/Analyzer.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/Analyzer.kt new file mode 100644 index 000000000..028d2d325 --- /dev/null +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/Analyzer.kt @@ -0,0 +1,43 @@ +/** + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * 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 + * + * https://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 org.pkl.lsp.analyzers + +import org.pkl.lsp.ast.Node + +/** + * Scans the source tree, and builds [PklDiagnostic]s. + * + * Diagnostics then get reported back to the user. + */ +abstract class Analyzer { + fun annotate(node: Node, diagnosticsHolder: MutableList) { + if (doAnnotate(node, diagnosticsHolder)) { + return + } + node.children.forEach { annotate(it, diagnosticsHolder) } + } + + /** + * Collect diagnostics, pushing them into [diagnosticsHolder] as they are captured. + * + * Return `false` if the annotator does not need to analyze any further. This skips calling + * [doAnnotate] on its children. + */ + protected abstract fun doAnnotate( + node: Node, + diagnosticsHolder: MutableList + ): Boolean +} diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/ModifierAnalyzer.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/ModifierAnalyzer.kt new file mode 100644 index 000000000..291c1257e --- /dev/null +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/ModifierAnalyzer.kt @@ -0,0 +1,121 @@ +/** + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * 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 + * + * https://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 org.pkl.lsp.analyzers + +import org.eclipse.lsp4j.DiagnosticSeverity +import org.pkl.lsp.ErrorMessages +import org.pkl.lsp.PklLSPServer +import org.pkl.lsp.ast.* +import org.pkl.lsp.ast.TokenType.* + +class ModifierAnalyzer(private val server: PklLSPServer) : Analyzer() { + companion object { + private val MODULE_MODIFIERS = setOf(ABSTRACT, OPEN) + private val AMENDING_MODULE_MODIFIERS = emptySet() + private val CLASS_MODIFIERS = setOf(ABSTRACT, OPEN, EXTERNAL, LOCAL) + private val TYPE_ALIAS_MODIFIERS = setOf(EXTERNAL, LOCAL) + private val CLASS_METHOD_MODIFIERS = setOf(ABSTRACT, EXTERNAL, LOCAL, CONST) + private val CLASS_PROPERTY_MODIFIERS = setOf(ABSTRACT, EXTERNAL, HIDDEN, LOCAL, FIXED, CONST) + private val OBJECT_METHOD_MODIFIERS = setOf(LOCAL) + private val OBJECT_PROPERTY_MODIFIERS = setOf(LOCAL) + } + + override fun doAnnotate(node: Node, diagnosticsHolder: MutableList): Boolean { + if (node !is ModifierListOwner || node.modifiers == null) { + return false + } + + var localModifier: Node? = null + var abstractModifier: Node? = null + var openModifier: Node? = null + var hiddenModifier: Node? = null + var fixedModifier: Node? = null + + for (modifier in node.modifiers!!) { + when (modifier.type) { + LOCAL -> localModifier = modifier + ABSTRACT -> abstractModifier = modifier + OPEN -> openModifier = modifier + HIDDEN -> hiddenModifier = modifier + FIXED -> fixedModifier = modifier + else -> {} + } + } + if (localModifier == null) { + when (node) { + is ClassProperty -> { + if ( + node.parent is Module && + (node.parent as Module).isAmend && + (hiddenModifier != null || node.typeAnnotation != null) + ) { + if (node.identifier != null) { + diagnosticsHolder.add( + PklDiagnostic( + node.identifier!!, + ErrorMessages.create("missingModifierLocal"), + DiagnosticSeverity.Error + ) + ) + return true + } + } + } + } + } + + if (abstractModifier != null && openModifier != null) { + diagnosticsHolder.add( + PklDiagnostic( + abstractModifier, + ErrorMessages.create("modifierAbstractConflictsWithOpen"), + DiagnosticSeverity.Error + ) + ) + diagnosticsHolder.add( + PklDiagnostic( + openModifier, + ErrorMessages.create("modifierOpenConflictsWithAbstract"), + DiagnosticSeverity.Error + ) + ) + } + + val (description, applicableModifiers) = + when (node) { + is ModuleDeclaration -> + if (node.isAmend) "amending modules" to AMENDING_MODULE_MODIFIERS + else "modules" to MODULE_MODIFIERS + is Class -> "classes" to CLASS_MODIFIERS + is TypeAlias -> "typealiases" to TYPE_ALIAS_MODIFIERS + is ClassMethod -> "class methods" to CLASS_METHOD_MODIFIERS + is ClassProperty -> "class properties" to CLASS_PROPERTY_MODIFIERS + else -> return false + } + for (modifier in node.modifiers!!) { + if (modifier.type !in applicableModifiers) { + diagnosticsHolder.add( + PklDiagnostic( + modifier, + ErrorMessages.create("modifierIsNotApplicable", modifier.text, description), + DiagnosticSeverity.Error + ) + ) + } + } + return false + } +} diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Parameter.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/PklDiagnostic.kt similarity index 58% rename from pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Parameter.kt rename to pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/PklDiagnostic.kt index 5f782cd36..5402e6eae 100644 --- a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Parameter.kt +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/PklDiagnostic.kt @@ -13,16 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.pkl.lsp.cst +package org.pkl.lsp.analyzers -sealed class Parameter(override val span: Span) : PklNode(span) { +import org.eclipse.lsp4j.Diagnostic +import org.eclipse.lsp4j.DiagnosticSeverity +import org.pkl.lsp.LSPUtil.toRange +import org.pkl.lsp.ast.Node +import org.pkl.lsp.ast.Span - data class Underscore(override val span: Span) : Parameter(span) - - data class TypedIdent(val ident: Ident, val type: Type?, override val span: Span) : - Parameter(span) { - init { - type?.parent = this - } - } +class PklDiagnostic(span: Span, message: String, severity: DiagnosticSeverity) : + Diagnostic(span.toRange(), message, severity, "pkl") { + constructor( + node: Node, + message: String, + severity: DiagnosticSeverity + ) : this(node.span, message, severity) } diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Modifier.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/StringLiteralAnalyzer.kt similarity index 73% rename from pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Modifier.kt rename to pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/StringLiteralAnalyzer.kt index 5d0fe544a..0d53016da 100644 --- a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Modifier.kt +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/StringLiteralAnalyzer.kt @@ -13,16 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.pkl.lsp.cst +package org.pkl.lsp.analyzers -data class Modifier(val mod: ModifierValue, val span: Span) +import org.pkl.lsp.ast.Node -enum class ModifierValue { - EXTERNAL, - ABSTRACT, - OPEN, - LOCAL, - HIDDEN, - FIXED, - CONST +class StringLiteralAnalyzer : Analyzer() { + override fun doAnnotate(node: Node, diagnosticsHolder: MutableList): Boolean { + TODO("Not yet implemented") + } } diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/PklNode.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/SyntaxAnalyzer.kt similarity index 68% rename from pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/PklNode.kt rename to pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/SyntaxAnalyzer.kt index 2da8079f8..81a54e426 100644 --- a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/PklNode.kt +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/analyzers/SyntaxAnalyzer.kt @@ -13,8 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.pkl.lsp.cst +package org.pkl.lsp.analyzers -abstract class PklNode(open val span: Span) { - var parent: PklNode? = null +import org.pkl.lsp.PklLSPServer +import org.pkl.lsp.ast.Node + +class SyntaxAnalyzer(private val server: PklLSPServer) : Analyzer() { + override fun doAnnotate(node: Node, diagnosticsHolder: MutableList): Boolean { + TODO("Not yet implemented") + } } diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Basic.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Basic.kt new file mode 100644 index 000000000..d91755547 --- /dev/null +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Basic.kt @@ -0,0 +1,34 @@ +/** + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * 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 + * + * https://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 org.pkl.lsp.ast + +import org.pkl.core.parser.antlr.PklParser.QualifiedIdentifierContext +import org.pkl.core.parser.antlr.PklParser.StringConstantContext + +class QualifiedIdentifierImpl( + override val parent: Node, + override val ctx: QualifiedIdentifierContext +) : AbstractNode(parent, ctx), QualifiedIdentifier { + override val identifiers: List by lazy { getChildren(Terminal::class)!! } +} + +class StringConstantImpl( + override val parent: Node, + override val ctx: StringConstantContext +) : AbstractNode(parent, ctx), StringConstant { + override val value: String + get() = TODO("Not yet implemented") +} diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Expr.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Expr.kt new file mode 100644 index 000000000..9b60f33de --- /dev/null +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Expr.kt @@ -0,0 +1,155 @@ +/** + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * 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 + * + * https://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 org.pkl.lsp.ast + +import org.pkl.core.parser.antlr.PklParser.* + +class ThisExprImpl(override val parent: Node, override val ctx: ThisExprContext) : + AbstractNode(parent, ctx), ThisExpr + +class OuterExprImpl(override val parent: Node, override val ctx: OuterExprContext) : + AbstractNode(parent, ctx), OuterExpr + +class ModuleExprImpl(override val parent: Node, override val ctx: ModuleExprContext) : + AbstractNode(parent, ctx), ModuleExpr + +class NullLiteralExprImpl(override val parent: Node, override val ctx: NullLiteralContext) : + AbstractNode(parent, ctx), NullLiteralExpr + +class TrueLiteralExprImpl(override val parent: Node, override val ctx: TrueLiteralContext) : + AbstractNode(parent, ctx), TrueLiteralExpr + +class FalseLiteralExprImpl(override val parent: Node, override val ctx: FalseLiteralContext) : + AbstractNode(parent, ctx), FalseLiteralExpr + +class IntLiteralExprImpl(override val parent: Node, override val ctx: IntLiteralContext) : + AbstractNode(parent, ctx), IntLiteralExpr + +class FloatLiteralExprImpl(override val parent: Node, override val ctx: FloatLiteralContext) : + AbstractNode(parent, ctx), FloatLiteralExpr + +class ThrowExprImpl(override val parent: Node, override val ctx: ThrowExprContext) : + AbstractNode(parent, ctx), ThrowExpr + +class TraceExprImpl(override val parent: Node, override val ctx: TraceExprContext) : + AbstractNode(parent, ctx), TraceExpr + +class ImportExprImpl(override val parent: Node, override val ctx: ImportExprContext) : + AbstractNode(parent, ctx), ImportExpr { + override val isGlob: Boolean by lazy { ctx.IMPORT_GLOB() != null } + + override val moduleUri: String + get() = TODO("Not yet implemented") +} + +class ReadExprImpl(override val parent: Node, override val ctx: ReadExprContext) : + AbstractNode(parent, ctx), ReadExpr + +class UnqualifiedAccessExprImpl( + override val parent: Node, + override val ctx: UnqualifiedAccessExprContext +) : AbstractNode(parent, ctx), UnqualifiedAccessExpr { + override val identifier: Terminal? by lazy { terminals.find { it.type == TokenType.Identifier } } +} + +class SingleLineStringLiteralImpl( + override val parent: Node, + override val ctx: SingleLineStringLiteralContext +) : AbstractNode(parent, ctx), SingleLineStringLiteral + +class MultilineStringLiteralImpl( + override val parent: Node, + override val ctx: MultiLineStringLiteralContext +) : AbstractNode(parent, ctx), MultilineStringLiteral + +class NewExprImpl(override val parent: Node, override val ctx: NewExprContext) : + AbstractNode(parent, ctx), NewExpr + +class AmendExprImpl(override val parent: Node, override val ctx: AmendExprContext) : + AbstractNode(parent, ctx), AmendExpr + +class SuperAccessExprImpl(override val parent: Node, override val ctx: SuperAccessExprContext) : + AbstractNode(parent, ctx), SuperAccessExpr + +class SuperSubscriptExprImpl( + override val parent: Node, + override val ctx: SuperSubscriptExprContext +) : AbstractNode(parent, ctx), SuperSubscriptExpr + +class QualifiedAccessExprImpl( + override val parent: Node, + override val ctx: QualifiedAccessExprContext +) : AbstractNode(parent, ctx), QualifiedAccessExpr + +class SubscriptExprImpl(override val parent: Node, override val ctx: SubscriptExprContext) : + AbstractNode(parent, ctx), SubscriptExpr + +class NonNullExprImpl(override val parent: Node, override val ctx: NonNullExprContext) : + AbstractNode(parent, ctx), NonNullExpr + +class UnaryMinusExprImpl(override val parent: Node, override val ctx: UnaryMinusExprContext) : + AbstractNode(parent, ctx), UnaryMinusExpr + +class LogicalNotExprImpl(override val parent: Node, override val ctx: LogicalNotExprContext) : + AbstractNode(parent, ctx), LogicalNotExpr + +class ExponentiationExprImpl( + override val parent: Node, + override val ctx: ExponentiationExprContext +) : AbstractNode(parent, ctx), ExponentiationExpr + +class MultiplicativeExprImpl( + override val parent: Node, + override val ctx: MultiplicativeExprContext +) : AbstractNode(parent, ctx), MultiplicativeExpr + +class AdditiveExprImpl(override val parent: Node, override val ctx: AdditiveExprContext) : + AbstractNode(parent, ctx), AdditiveExpr + +class ComparisonExprImpl(override val parent: Node, override val ctx: ComparisonExprContext) : + AbstractNode(parent, ctx), ComparisonExpr + +class TypeTestExprImpl(override val parent: Node, override val ctx: TypeTestExprContext) : + AbstractNode(parent, ctx), TypeTestExpr + +class EqualityExprImpl(override val parent: Node, override val ctx: EqualityExprContext) : + AbstractNode(parent, ctx), EqualityExpr + +class LogicalAndExprImpl(override val parent: Node, override val ctx: LogicalAndExprContext) : + AbstractNode(parent, ctx), LogicalAndExpr + +class LogicalOrExprImpl(override val parent: Node, override val ctx: LogicalOrExprContext) : + AbstractNode(parent, ctx), LogicalOrExpr + +class PipeExprImpl(override val parent: Node, override val ctx: PipeExprContext) : + AbstractNode(parent, ctx), PipeExpr + +class NullCoalesceExprImpl(override val parent: Node, override val ctx: NullCoalesceExprContext) : + AbstractNode(parent, ctx), NullCoalesceExpr + +class IfExprImpl(override val parent: Node, override val ctx: IfExprContext) : + AbstractNode(parent, ctx), IfExpr + +class LetExprImpl(override val parent: Node, override val ctx: LetExprContext) : + AbstractNode(parent, ctx), LetExpr { + override val identifier: Terminal? by lazy { terminals.find { it.type == TokenType.Identifier } } +} + +class FunctionLiteralImpl(override val parent: Node, override val ctx: FunctionLiteralContext) : + AbstractNode(parent, ctx), FunctionLiteral + +class ParenthesizedExprImpl(override val parent: Node, override val ctx: ParenthesizedExprContext) : + AbstractNode(parent, ctx), ParenthesizedExpr diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Member.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Member.kt new file mode 100644 index 000000000..2453dcd73 --- /dev/null +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Member.kt @@ -0,0 +1,96 @@ +/** + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * 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 + * + * https://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 org.pkl.lsp.ast + +import org.pkl.core.parser.antlr.PklParser.* +import org.pkl.lsp.LSPUtil.firstInstanceOf + +class ClassPropertyImpl(override val parent: Node, override val ctx: ClassPropertyContext) : + AbstractNode(parent, ctx), ClassProperty { + override val identifier: Terminal? by lazy { terminals.find { it.type == TokenType.Identifier } } + + override val modifiers: List by lazy { + buildList { + children.forEach { node -> + if (node is Terminal && modifierTypes.contains(node.type)) { + add(node) + } + } + } + } + + override val typeAnnotation: TypeAnnotation? by lazy { + children.firstInstanceOf() + } + + override val expr: Expr? by lazy { children.firstInstanceOf() } + + override val objectBody: ObjectBody? by lazy { getChild(ObjectBodyImpl::class) } +} + +class ClassMethodImpl(override val parent: Node, override val ctx: ClassMethodContext) : + AbstractNode(parent, ctx), ClassMethod { + override val methodHeader: MethodHeader by lazy { getChild(MethodHeaderImpl::class)!! } +} + +class MethodHeaderImpl(override val parent: Node, override val ctx: MethodHeaderContext) : + AbstractNode(parent, ctx), MethodHeader { + override val parameterList: ParameterList? by lazy { getChild(ParameterListImpl::class) } + + override val typeParameterList: TypeParameterList? by lazy { getChild(TypeParameterList::class) } + + override val modifiers: List by lazy { terminals.takeWhile { it.isModifier } } + + override val identifier: Terminal? by lazy { terminals.find { it.type == TokenType.Identifier } } +} + +class ParameterListImpl(override val parent: Node, override val ctx: ParameterListContext) : + AbstractNode(parent, ctx), ParameterList + +class ObjectBodyImpl(override val parent: Node, override val ctx: ObjectBodyContext) : + AbstractNode(parent, ctx), ObjectBody { + override val parameterList: ParameterList? + get() = TODO("Not yet implemented") + + override val members: List? + get() = TODO("Not yet implemented") +} + +class ObjectPropertyImpl(override val parent: Node, override val ctx: ObjectPropertyContext) : + AbstractNode(parent, ctx), ObjectProperty { + override val identifier: Terminal? by lazy { terminals.find { it.type == TokenType.Identifier } } +} + +class ObjectMethodImpl(override val parent: Node, override val ctx: ObjectMethodContext) : + AbstractNode(parent, ctx), ObjectMethod + +class ObjectEntryImpl(override val parent: Node, override val ctx: ObjectEntryContext) : + AbstractNode(parent, ctx), ObjectEntry + +class MemberPredicateImpl(override val parent: Node, override val ctx: MemberPredicateContext) : + AbstractNode(parent, ctx), MemberPredicate + +class ForGeneratorImpl(override val parent: Node, override val ctx: ForGeneratorContext) : + AbstractNode(parent, ctx), ForGenerator + +class WhenGeneratorImpl(override val parent: Node, override val ctx: WhenGeneratorContext) : + AbstractNode(parent, ctx), WhenGenerator + +class ObjectElementImpl(override val parent: Node, override val ctx: ObjectElementContext) : + AbstractNode(parent, ctx), ObjectElement + +class ObjectSpreadImpl(override val parent: Node, override val ctx: ObjectSpreadContext) : + AbstractNode(parent, ctx), ObjectSpread diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/ModuleOrClass.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/ModuleOrClass.kt new file mode 100644 index 000000000..30382561a --- /dev/null +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/ModuleOrClass.kt @@ -0,0 +1,132 @@ +/** + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * 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 + * + * https://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 org.pkl.lsp.ast + +import org.pkl.core.parser.antlr.PklParser +import org.pkl.core.parser.antlr.PklParser.ModuleHeaderContext +import org.pkl.lsp.LSPUtil.firstInstanceOf + +class ModuleImpl(override val ctx: PklParser.ModuleContext) : AbstractNode(null, ctx), Module { + override val isAmend: Boolean by lazy { + declaration?.moduleExtendsAmendsClause?.isAmend + ?: declaration?.moduleHeader?.moduleExtendsAmendsClause?.isAmend ?: false + } + + override val declaration: ModuleDeclaration? by lazy { getChild(ModuleDeclarationImpl::class) } + + override val members: List by lazy { children.filterIsInstance() } + + override val imports: List? by lazy { getChildren(ImportClauseImpl::class) } +} + +class AnnotationImpl(override val parent: Node, override val ctx: PklParser.AnnotationContext) : + AbstractNode(parent, ctx), Annotation { + override val typeName: TypeName + get() = TODO("Not yet implemented") + + override val objectBody: ObjectBody + get() = TODO("Not yet implemented") +} + +class ModuleHeaderImpl(override val parent: Node, override val ctx: ModuleHeaderContext) : + AbstractNode(parent, ctx), ModuleHeader { + override val qualifiedIdentifier: QualifiedIdentifier? by lazy { + getChild(QualifiedIdentifierImpl::class) + } + + override val moduleExtendsAmendsClause: ModuleExtendsAmendsClause? by lazy { + getChild(ModuleExtendsAmendsClauseImpl::class) + } + + override val modifiers: List? by lazy { terminals.takeWhile { it.isModifier } } +} + +class ModuleDeclarationImpl( + override val parent: Node, + override val ctx: PklParser.ModuleDeclContext +) : AbstractNode(parent, ctx), ModuleDeclaration { + + override val annotations: List by lazy { + ctx.annotation().map { AnnotationImpl(this, it) } + } + + override val moduleHeader: ModuleHeader? by lazy { + ctx.moduleHeader()?.let { ModuleHeaderImpl(this, it) } + } + + override val moduleExtendsAmendsClause: ModuleExtendsAmendsClause? by lazy { + children.firstInstanceOf() + } + + override val modifiers: List by lazy { moduleHeader?.modifiers ?: emptyList() } +} + +class ImportClauseImpl(override val parent: Node, override val ctx: PklParser.ImportClauseContext) : + AbstractNode(parent, ctx), ImportClause { + override val identifier: Terminal? by lazy { ctx.Identifier()?.toTerminal(this) } + + override val isGlob: Boolean by lazy { ctx.IMPORT_GLOB() != null } + + override val moduleUri: String + get() = TODO("Not yet implemented") +} + +class ModuleExtendsAmendsClauseImpl( + override val parent: Node, + override val ctx: PklParser.ModuleExtendsOrAmendsClauseContext +) : AbstractNode(parent, ctx), ModuleExtendsAmendsClause { + override val isAmend: Boolean + get() = ctx.AMENDS() != null + + override val isExtend: Boolean + get() = ctx.EXTENDS() != null + + override val moduleUri: String? by lazy { + getChild(StringConstantImpl::class)?.value + } +} + +class ClassImpl(override val parent: Node, override val ctx: PklParser.ClazzContext) : + AbstractNode(parent, ctx), Class { + override val classHeader: ClassHeader by lazy { getChild(ClassHeaderImpl::class)!! } + + override val annotations: List? by lazy { getChildren(AnnotationImpl::class) } + + override val classBody: ClassBody? by lazy { getChild(ClassBodyImpl::class) } +} + +class ClassHeaderImpl(override val parent: Node, override val ctx: PklParser.ClassHeaderContext) : + AbstractNode(parent, ctx), ClassHeader { + override val identifier: Terminal? by lazy { terminals.find { it.type == TokenType.Identifier } } + + override val modifiers: List by lazy { terminals.takeWhile { it.isModifier } } + + override val typeParameterList: TypeParameterList? by lazy { + getChild(TypeParameterListImpl::class) + } + + override val extends: Type? by lazy { children.last() as? Type } +} + +class ClassBodyImpl(override val parent: Node, override val ctx: PklParser.ClassBodyContext) : + AbstractNode(parent, ctx), ClassBody { + override val members: List by lazy { children.filterIsInstance() } +} + +class TypeParameterListImpl( + override val parent: Node, + override val ctx: PklParser.TypeParameterListContext +) : AbstractNode(parent, ctx), TypeParameterList diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Node.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Node.kt new file mode 100644 index 000000000..9885fdb7b --- /dev/null +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Node.kt @@ -0,0 +1,421 @@ +/** + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * 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 + * + * https://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 org.pkl.lsp.ast + +import kotlin.reflect.KClass +import org.antlr.v4.runtime.ParserRuleContext +import org.antlr.v4.runtime.tree.ParseTree +import org.antlr.v4.runtime.tree.TerminalNode +import org.pkl.core.parser.antlr.PklParser.* + +interface Node { + val span: Span + val parent: Node? + val children: List +} + +interface QualifiedIdentifier : Node { + val identifiers: List +} + +interface StringConstant : Node { + val value: String +} + +interface IdentifierOwner { + val identifier: Terminal? +} + +interface ModifierListOwner : Node { + val modifiers: List? +} + +interface DocCommentOwner : Node { + // assertion: DocComment is always the first node + val docComment: Terminal? + get() = (children.firstOrNull() as? Terminal)?.also { assert(it.type == TokenType.DocComment) } +} + +interface Module : Node { + val isAmend: Boolean + val declaration: ModuleDeclaration? + val imports: List? + val members: List +} + +/** Either [moduleHeader] is set, or [moduleExtendsAmendsClause] is set. */ +interface ModuleDeclaration : Node, ModifierListOwner, DocCommentOwner { + val annotations: List + + val isAmend: Boolean + get() = effectiveExtendsOrAmendsCluse?.isAmend ?: false + + val moduleHeader: ModuleHeader? + + val moduleExtendsAmendsClause: ModuleExtendsAmendsClause? + + val effectiveExtendsOrAmendsCluse: ModuleExtendsAmendsClause? get() = + moduleHeader?.moduleExtendsAmendsClause + ?: moduleExtendsAmendsClause +} + +interface ModuleHeader : Node, ModifierListOwner { + val qualifiedIdentifier: QualifiedIdentifier? + val moduleExtendsAmendsClause: ModuleExtendsAmendsClause? +} + +interface ModuleExtendsAmendsClause : Node { + val isAmend: Boolean + + val isExtend: Boolean + + val moduleUri: String? +} + +sealed interface ModuleMember : Node, DocCommentOwner + +interface Class : ModuleMember { + val classHeader: ClassHeader + val annotations: List? + val classBody: ClassBody? +} + +interface ClassBody : Node { + val members: List +} + +interface Annotation : Node { + val typeName: TypeName + val objectBody: ObjectBody +} + +interface TypeName : Node { + val module: Terminal? + val simpleTypeName: SimpleTypeName +} + +interface SimpleTypeName : Node, IdentifierOwner + +interface ClassHeader : Node, IdentifierOwner, ModifierListOwner { + val typeParameterList: TypeParameterList? + val extends: Type? +} + +sealed interface ClassMember : Node, DocCommentOwner + +interface ClassProperty : ModuleMember, ClassMember, IdentifierOwner, ModifierListOwner { + val typeAnnotation: TypeAnnotation? + + val expr: Expr? + + val objectBody: ObjectBody? +} + +interface ClassMethod : ModuleMember, ClassMember { + val methodHeader: MethodHeader +} + +sealed interface ObjectMember : Node + +interface ObjectProperty : ObjectMember, IdentifierOwner + +interface ObjectMethod : ObjectMember + +interface ObjectEntry : ObjectMember + +interface MemberPredicate : ObjectMember + +interface ForGenerator : ObjectMember + +interface WhenGenerator : ObjectMember + +interface ObjectElement : ObjectMember + +interface ObjectSpread : ObjectMember + +interface MethodHeader : Node, ModifierListOwner, IdentifierOwner { + val parameterList: ParameterList? + + val typeParameterList: TypeParameterList? +} + +interface ObjectBody : Node { + val parameterList: ParameterList? + + val members: List? +} + +interface TypeParameterList : Node + +interface ParameterList : Node + +interface TypeAlias : ModuleMember, IdentifierOwner, ModifierListOwner + +interface Terminal : Node { + val type: TokenType + + /** The verbatim text of this node. */ + val text: String +} + +interface ImportBase : Node { + val isGlob: Boolean + + val moduleUri: String +} + +sealed interface ImportClause : ImportBase, IdentifierOwner + +sealed interface Expr + +interface ThisExpr : Expr + +interface OuterExpr : Expr + +interface ModuleExpr : Expr + +interface NullLiteralExpr : Expr + +interface TrueLiteralExpr : Expr + +interface FalseLiteralExpr : Expr + +interface IntLiteralExpr : Expr + +interface FloatLiteralExpr : Expr + +interface ThrowExpr : Expr + +interface TraceExpr : Expr + +interface ImportExpr : ImportBase, Expr + +interface ReadExpr : Expr + +interface UnqualifiedAccessExpr : Expr, IdentifierOwner + +interface SingleLineStringLiteral : Expr + +interface MultilineStringLiteral : Expr + +interface NewExpr : Expr + +interface AmendExpr : Expr + +interface SuperAccessExpr : Expr + +interface SuperSubscriptExpr : Expr + +interface QualifiedAccessExpr : Expr + +interface SubscriptExpr : Expr + +interface NonNullExpr : Expr + +interface UnaryMinusExpr : Expr + +interface LogicalNotExpr : Expr + +interface ExponentiationExpr : Expr + +interface MultiplicativeExpr : Expr + +interface AdditiveExpr : Expr + +interface ComparisonExpr : Expr + +interface TypeTestExpr : Expr + +interface EqualityExpr : Expr + +interface LogicalAndExpr : Expr + +interface LogicalOrExpr : Expr + +interface PipeExpr : Expr + +interface NullCoalesceExpr : Expr + +interface IfExpr : Expr + +interface LetExpr : Expr, IdentifierOwner + +interface FunctionLiteral : Expr + +interface ParenthesizedExpr : Expr + +interface TypeAnnotation : Node { + val type: Type? +} + +sealed interface Type : Node + +interface UnknownType : Type + +interface NothingType : Type + +interface ModuleType : Type + +interface StringLiteralType : Type + +interface DeclaredType : Type + +interface ParenthesizedType : Type + +interface NullableType : Type + +interface ConstrainedType : Type + +interface UnionType : Type + +interface FunctionType : Type + +abstract class AbstractNode(override val parent: Node?, protected open val ctx: ParseTree) : Node { + private val childrenByType: Map, List> by lazy { + if (ctx !is ParserRuleContext) { + return@lazy emptyMap() + } + val parserCtx = ctx as ParserRuleContext + val self = this + // use LinkedHashMap to preserve order + LinkedHashMap, MutableList>().also { map -> + for (idx in parserCtx.children.indices) { + val node = parserCtx.children.toNode(self, idx) ?: continue + when (val nodes = map[node::class]) { + null -> map[node::class] = mutableListOf(node) + else -> nodes.add(node) + } + } + } + } + + override val span: Span by lazy { + when (ctx) { + is ParserRuleContext -> { + val c = ctx as ParserRuleContext + val begin = c.start + val end = c.stop + val endCol = end.charPositionInLine + 1 + end.text.length + Span(begin.line, begin.charPositionInLine + 1, end.line, endCol) + } + else -> { + ctx as TerminalNode + val token = (ctx as TerminalNode).symbol + val endCol = token.charPositionInLine + 1 + token.text.length + Span(token.line, token.charPositionInLine + 1, token.line, endCol) + } + } + } + + protected fun getChild(clazz: KClass): T? { + @Suppress("UNCHECKED_CAST") return childrenByType[clazz]?.firstOrNull() as T? + } + + protected fun getChildren(clazz: KClass): List? { + @Suppress("UNCHECKED_CAST") return childrenByType[clazz] as List? + } + + protected val terminals: List by lazy { + getChildren(TerminalImpl::class) ?: emptyList() + } + + override val children: List by lazy { childrenByType.values.flatten() } +} + +fun List.toNode(parent: Node?, idx: Int): Node? { + return when (val parseTree = get(idx)) { + is ModuleContext -> ModuleImpl(parseTree) + is ModuleDeclContext -> ModuleDeclarationImpl(parent!!, parseTree) + is ImportClauseContext -> ImportClauseImpl(parent!!, parseTree) + is ModuleExtendsOrAmendsClauseContext -> ModuleExtendsAmendsClauseImpl(parent!!, parseTree) + is ClazzContext -> ClassImpl(parent!!, parseTree) + is ClassHeaderContext -> ClassHeaderImpl(parent!!, parseTree) + is ClassBodyContext -> ClassBodyImpl(parent!!, parseTree) + is ClassPropertyContext -> ClassPropertyImpl(parent!!, parseTree) + is MethodHeaderContext -> MethodHeaderImpl(parent!!, parseTree) + is ClassMethodContext -> ClassMethodImpl(parent!!, parseTree) + is ParameterListContext -> ParameterListImpl(parent!!, parseTree) + is TypeAnnotationContext -> TypeAnnotationImpl(parent!!, parseTree) + is UnknownTypeContext -> UnknownTypeImpl(parent!!, parseTree) + is NothingTypeContext -> NothingTypeImpl(parent!!, parseTree) + is ModuleTypeContext -> ModuleTypeImpl(parent!!, parseTree) + is StringLiteralTypeContext -> StringLiteralTypeImpl(parent!!, parseTree) + is DeclaredTypeContext -> DeclaredTypeImpl(parent!!, parseTree) + is ParenthesizedTypeContext -> ParenthesizedTypeImpl(parent!!, parseTree) + is NullableTypeContext -> NullableTypeImpl(parent!!, parseTree) + is ConstrainedTypeContext -> ConstrainedTypeImpl(parent!!, parseTree) + is UnionTypeContext -> UnionTypeImpl(parent!!, parseTree) + is FunctionTypeContext -> FunctionTypeImpl(parent!!, parseTree) + is ThisExprContext -> ThisExprImpl(parent!!, parseTree) + is OuterExprContext -> OuterExprImpl(parent!!, parseTree) + is ModuleExprContext -> ModuleExprImpl(parent!!, parseTree) + is NullLiteralContext -> NullLiteralExprImpl(parent!!, parseTree) + is TrueLiteralContext -> TrueLiteralExprImpl(parent!!, parseTree) + is FalseLiteralContext -> FalseLiteralExprImpl(parent!!, parseTree) + is IntLiteralContext -> IntLiteralExprImpl(parent!!, parseTree) + is FloatLiteralContext -> FloatLiteralExprImpl(parent!!, parseTree) + is ThrowExprContext -> ThrowExprImpl(parent!!, parseTree) + is TraceExprContext -> TraceExprImpl(parent!!, parseTree) + is ImportExprContext -> ImportExprImpl(parent!!, parseTree) + is ReadExprContext -> ReadExprImpl(parent!!, parseTree) + is UnqualifiedAccessExprContext -> UnqualifiedAccessExprImpl(parent!!, parseTree) + is SingleLineStringLiteralContext -> SingleLineStringLiteralImpl(parent!!, parseTree) + is MultiLineStringLiteralContext -> MultilineStringLiteralImpl(parent!!, parseTree) + is NewExprContext -> NewExprImpl(parent!!, parseTree) + is AmendExprContext -> AmendExprImpl(parent!!, parseTree) + is SuperAccessExprContext -> SuperAccessExprImpl(parent!!, parseTree) + is SuperSubscriptExprContext -> SuperSubscriptExprImpl(parent!!, parseTree) + is QualifiedAccessExprContext -> QualifiedAccessExprImpl(parent!!, parseTree) + is SubscriptExprContext -> SubscriptExprImpl(parent!!, parseTree) + is NonNullExprContext -> NonNullExprImpl(parent!!, parseTree) + is UnaryMinusExprContext -> UnaryMinusExprImpl(parent!!, parseTree) + is LogicalNotExprContext -> LogicalNotExprImpl(parent!!, parseTree) + is ExponentiationExprContext -> ExponentiationExprImpl(parent!!, parseTree) + is MultiplicativeExprContext -> MultiplicativeExprImpl(parent!!, parseTree) + is AdditiveExprContext -> AdditiveExprImpl(parent!!, parseTree) + is ComparisonExprContext -> ComparisonExprImpl(parent!!, parseTree) + is TypeTestExprContext -> TypeTestExprImpl(parent!!, parseTree) + is EqualityExprContext -> EqualityExprImpl(parent!!, parseTree) + is LogicalAndExprContext -> LogicalAndExprImpl(parent!!, parseTree) + is LogicalOrExprContext -> LogicalOrExprImpl(parent!!, parseTree) + is PipeExprContext -> PipeExprImpl(parent!!, parseTree) + is NullCoalesceExprContext -> NullCoalesceExprImpl(parent!!, parseTree) + is IfExprContext -> IfExprImpl(parent!!, parseTree) + is LetExprContext -> LetExprImpl(parent!!, parseTree) + is FunctionLiteralContext -> FunctionLiteralImpl(parent!!, parseTree) + is ParenthesizedExprContext -> ParenthesizedExprImpl(parent!!, parseTree) + is QualifiedIdentifierContext -> QualifiedIdentifierImpl(parent!!, parseTree) + is ObjectBodyContext -> ObjectBodyImpl(parent!!, parseTree) + is ObjectPropertyContext -> ObjectPropertyImpl(parent!!, parseTree) + is ObjectMethodContext -> ObjectMethodImpl(parent!!, parseTree) + is ObjectEntryContext -> ObjectEntryImpl(parent!!, parseTree) + is MemberPredicateContext -> MemberPredicateImpl(parent!!, parseTree) + is ForGeneratorContext -> ForGeneratorImpl(parent!!, parseTree) + is WhenGeneratorContext -> WhenGeneratorImpl(parent!!, parseTree) + is ObjectElementContext -> ObjectElementImpl(parent!!, parseTree) + is ObjectSpreadContext -> ObjectSpreadImpl(parent!!, parseTree) + // treat modifiers as terminals; matches how we do it in pkl-intellij + is ModifierContext -> { + val terminalNode = + parseTree.CONST() + ?: parseTree.ABSTRACT() ?: parseTree.HIDDEN_() ?: parseTree.FIXED() + ?: parseTree.EXTERNAL() ?: parseTree.LOCAL() ?: parseTree.OPEN() + terminalNode.toTerminal(parent!!) + } + is TerminalNode -> parseTree.toTerminal(parent!!) + else -> null + } +} diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Span.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Span.kt similarity index 97% rename from pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Span.kt rename to pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Span.kt index 58fb7c172..36e6d41c0 100644 --- a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Span.kt +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Span.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.pkl.lsp.cst +package org.pkl.lsp.ast data class Span(val beginLine: Int, val beginCol: Int, val endLine: Int, val endCol: Int) { override fun toString(): String { diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Terminal.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Terminal.kt new file mode 100644 index 000000000..d9d873119 --- /dev/null +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Terminal.kt @@ -0,0 +1,145 @@ +/** + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * 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 + * + * https://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 org.pkl.lsp.ast + +import org.antlr.v4.runtime.tree.TerminalNode +import org.pkl.core.parser.antlr.PklParser + +class TerminalImpl( + override val parent: Node, + override val ctx: TerminalNode, + override val type: TokenType +) : AbstractNode(parent, ctx), Terminal { + override val text: String = ctx.text +} + +val Terminal.isModifier: Boolean + get() { + return modifierTypes.contains(type) + } + +fun TerminalNode.toTerminal(parent: Node): Terminal? { + val tokenType = + when (symbol.type) { + PklParser.ABSTRACT -> TokenType.ABSTRACT + PklParser.AMENDS -> TokenType.AMENDS + PklParser.AS -> TokenType.AS + PklParser.CLASS -> TokenType.CLASS + PklParser.CONST -> TokenType.CONST + PklParser.ELSE -> TokenType.ELSE + PklParser.EXTENDS -> TokenType.EXTENDS + PklParser.EXTERNAL -> TokenType.EXTERNAL + PklParser.FALSE -> TokenType.FALSE + PklParser.FIXED -> TokenType.FIXED + PklParser.FOR -> TokenType.FOR + PklParser.FUNCTION -> TokenType.FUNCTION + PklParser.HIDDEN_ -> TokenType.HIDDEN + PklParser.IF -> TokenType.IF + PklParser.IMPORT -> TokenType.IMPORT + PklParser.IMPORT_GLOB -> TokenType.IMPORT_GLOB + PklParser.IN -> TokenType.IN + PklParser.IS -> TokenType.IS + PklParser.LET -> TokenType.LET + PklParser.LOCAL -> TokenType.LOCAL + PklParser.MODULE -> TokenType.MODULE + PklParser.NEW -> TokenType.NEW + PklParser.NOTHING -> TokenType.NOTHING + PklParser.NULL -> TokenType.NULL + PklParser.OPEN -> TokenType.OPEN + PklParser.OUT -> TokenType.OUT + PklParser.OUTER -> TokenType.OUTER + PklParser.READ -> TokenType.READ + PklParser.READ_GLOB -> TokenType.READ_GLOB + PklParser.READ_OR_NULL -> TokenType.READ_OR_NULL + PklParser.SUPER -> TokenType.SUPER + PklParser.THIS -> TokenType.THIS + PklParser.THROW -> TokenType.THROW + PklParser.TRACE -> TokenType.TRACE + PklParser.TRUE -> TokenType.TRUE + PklParser.TYPE_ALIAS -> TokenType.TYPE_ALIAS + PklParser.UNKNOWN -> TokenType.UNKNOWN + PklParser.WHEN -> TokenType.WHEN + PklParser.PROTECTED -> TokenType.PROTECTED + PklParser.OVERRIDE -> TokenType.OVERRIDE + PklParser.RECORD -> TokenType.RECORD + PklParser.DELETE -> TokenType.DELETE + PklParser.CASE -> TokenType.CASE + PklParser.SWITCH -> TokenType.SWITCH + PklParser.VARARG -> TokenType.VARARG + PklParser.LPAREN -> TokenType.LPAREN + PklParser.RPAREN -> TokenType.RPAREN + PklParser.LBRACE -> TokenType.LBRACE + PklParser.RBRACE -> TokenType.RBRACE + PklParser.LBRACK -> TokenType.LBRACK + PklParser.RBRACK -> TokenType.RBRACK + PklParser.LPRED -> TokenType.LPRED + PklParser.COMMA -> TokenType.COMMA + PklParser.DOT -> TokenType.DOT + PklParser.QDOT -> TokenType.QDOT + PklParser.COALESCE -> TokenType.COALESCE + PklParser.NON_NULL -> TokenType.NON_NULL + PklParser.AT -> TokenType.AT + PklParser.ASSIGN -> TokenType.ASSIGN + PklParser.GT -> TokenType.GT + PklParser.LT -> TokenType.LT + PklParser.NOT -> TokenType.NOT + PklParser.QUESTION -> TokenType.QUESTION + PklParser.COLON -> TokenType.COLON + PklParser.ARROW -> TokenType.ARROW + PklParser.EQUAL -> TokenType.EQUAL + PklParser.NOT_EQUAL -> TokenType.NOT_EQUAL + PklParser.LTE -> TokenType.LTE + PklParser.GTE -> TokenType.GTE + PklParser.AND -> TokenType.AND + PklParser.OR -> TokenType.OR + PklParser.PLUS -> TokenType.PLUS + PklParser.MINUS -> TokenType.MINUS + PklParser.POW -> TokenType.POW + PklParser.STAR -> TokenType.STAR + PklParser.DIV -> TokenType.DIV + PklParser.INT_DIV -> TokenType.INT_DIV + PklParser.MOD -> TokenType.MOD + PklParser.UNION -> TokenType.UNION + PklParser.PIPE -> TokenType.PIPE + PklParser.SPREAD -> TokenType.SPREAD + PklParser.QSPREAD -> TokenType.QSPREAD + PklParser.UNDERSCORE -> TokenType.UNDERSCORE + PklParser.SLQuote -> TokenType.SLQuote + PklParser.MLQuote -> TokenType.MLQuote + PklParser.IntLiteral -> TokenType.IntLiteral + PklParser.FloatLiteral -> TokenType.FloatLiteral + PklParser.Identifier -> TokenType.Identifier + PklParser.NewlineSemicolon -> TokenType.NewlineSemicolon + PklParser.Whitespace -> TokenType.Whitespace + PklParser.DocComment -> TokenType.DocComment + PklParser.BlockComment -> TokenType.BlockComment + PklParser.LineComment -> TokenType.LineComment + PklParser.ShebangComment -> TokenType.ShebangComment + PklParser.SLEndQuote -> TokenType.SLEndQuote + PklParser.SLInterpolation -> TokenType.SLInterpolation + PklParser.SLUnicodeEscape -> TokenType.SLUnicodeEscape + PklParser.SLCharacterEscape -> TokenType.SLCharacterEscape + PklParser.SLCharacters -> TokenType.SLCharacters + PklParser.MLEndQuote -> TokenType.MLEndQuote + PklParser.MLInterpolation -> TokenType.MLInterpolation + PklParser.MLUnicodeEscape -> TokenType.MLUnicodeEscape + PklParser.MLCharacterEscape -> TokenType.MLCharacterEscape + PklParser.MLNewline -> TokenType.MLNewline + PklParser.MLCharacters -> TokenType.MLCharacters + else -> return null + } + return TerminalImpl(parent, this, tokenType) +} diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/TokenType.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/TokenType.kt new file mode 100644 index 000000000..0d4fe26e2 --- /dev/null +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/TokenType.kt @@ -0,0 +1,135 @@ +/** + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * 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 + * + * https://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 org.pkl.lsp.ast + +enum class TokenType { + ABSTRACT, + AMENDS, + AS, + CLASS, + CONST, + ELSE, + EXTENDS, + EXTERNAL, + FALSE, + FIXED, + FOR, + FUNCTION, + HIDDEN, + IF, + IMPORT, + IMPORT_GLOB, + IN, + IS, + LET, + LOCAL, + MODULE, + NEW, + NOTHING, + NULL, + OPEN, + OUT, + OUTER, + READ, + READ_GLOB, + READ_OR_NULL, + SUPER, + THIS, + THROW, + TRACE, + TRUE, + TYPE_ALIAS, + UNKNOWN, + WHEN, + PROTECTED, + OVERRIDE, + RECORD, + DELETE, + CASE, + SWITCH, + VARARG, + LPAREN, + RPAREN, + LBRACE, + RBRACE, + LBRACK, + RBRACK, + LPRED, + COMMA, + DOT, + QDOT, + COALESCE, + NON_NULL, + AT, + ASSIGN, + GT, + LT, + NOT, + QUESTION, + COLON, + ARROW, + EQUAL, + NOT_EQUAL, + LTE, + GTE, + AND, + OR, + PLUS, + MINUS, + POW, + STAR, + DIV, + INT_DIV, + MOD, + UNION, + PIPE, + SPREAD, + QSPREAD, + UNDERSCORE, + SLQuote, + MLQuote, + IntLiteral, + FloatLiteral, + Identifier, + NewlineSemicolon, + Whitespace, + DocComment, + BlockComment, + LineComment, + ShebangComment, + SLEndQuote, + SLInterpolation, + SLUnicodeEscape, + SLCharacterEscape, + SLCharacters, + MLEndQuote, + MLInterpolation, + MLUnicodeEscape, + MLCharacterEscape, + MLNewline, + MLCharacters, +} + +val modifierTypes = + setOf( + TokenType.EXTERNAL, + TokenType.ABSTRACT, + TokenType.OPEN, + TokenType.LOCAL, + TokenType.HIDDEN, + TokenType.FIXED, + TokenType.CONST + ) diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Type.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Type.kt new file mode 100644 index 000000000..1bc414a7c --- /dev/null +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Type.kt @@ -0,0 +1,54 @@ +/** + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * 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 + * + * https://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 org.pkl.lsp.ast + +import org.pkl.core.parser.antlr.PklParser.* +import org.pkl.lsp.LSPUtil.firstInstanceOf + +class TypeAnnotationImpl(override val parent: Node, ctx: TypeAnnotationContext) : + AbstractNode(parent, ctx), TypeAnnotation { + override val type: Type? by lazy { children.firstInstanceOf() } +} + +class UnknownTypeImpl(override val parent: Node, ctx: UnknownTypeContext) : + AbstractNode(parent, ctx), UnknownType + +class NothingTypeImpl(override val parent: Node, ctx: NothingTypeContext) : + AbstractNode(parent, ctx), NothingType + +class ModuleTypeImpl(override val parent: Node, ctx: ModuleTypeContext) : + AbstractNode(parent, ctx), ModuleType + +class StringLiteralTypeImpl(override val parent: Node, ctx: StringLiteralTypeContext) : + AbstractNode(parent, ctx), StringLiteralType + +class DeclaredTypeImpl(override val parent: Node, ctx: DeclaredTypeContext) : + AbstractNode(parent, ctx), DeclaredType + +class ParenthesizedTypeImpl(override val parent: Node, ctx: ParenthesizedTypeContext) : + AbstractNode(parent, ctx), ParenthesizedType + +class NullableTypeImpl(override val parent: Node, ctx: NullableTypeContext) : + AbstractNode(parent, ctx), NullableType + +class ConstrainedTypeImpl(override val parent: Node, ctx: ConstrainedTypeContext) : + AbstractNode(parent, ctx), ConstrainedType + +class UnionTypeImpl(override val parent: Node, ctx: UnionTypeContext) : + AbstractNode(parent, ctx), UnionType + +class FunctionTypeImpl(override val parent: Node, ctx: FunctionTypeContext) : + AbstractNode(parent, ctx), FunctionType diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/CstBuilder.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/CstBuilder.kt deleted file mode 100644 index bc89ef29e..000000000 --- a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/CstBuilder.kt +++ /dev/null @@ -1,1088 +0,0 @@ -/** - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. - * - * 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 - * - * https://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 org.pkl.lsp.cst - -import java.util.* -import org.antlr.v4.runtime.ParserRuleContext -import org.antlr.v4.runtime.Token -import org.antlr.v4.runtime.tree.ParseTree -import org.antlr.v4.runtime.tree.TerminalNode -import org.pkl.core.ast.builder.AstBuilder -import org.pkl.core.parser.antlr.PklLexer -import org.pkl.core.parser.antlr.PklParser -import org.pkl.core.parser.antlr.PklParserBaseVisitor - -class CstBuilder : PklParserBaseVisitor() { - - private val errors = mutableListOf() - - override fun visitUnknownType(ctx: PklParser.UnknownTypeContext): Type { - return Type.Unknown(toSpan(ctx)) - } - - override fun visitNothingType(ctx: PklParser.NothingTypeContext): Type { - return Type.Nothing(toSpan(ctx)) - } - - override fun visitModuleType(ctx: PklParser.ModuleTypeContext): Type { - return Type.Module(toSpan(ctx)) - } - - override fun visitStringConstant(ctx: PklParser.StringConstantContext): Ident { - checkSingleLineStringDelimiters(ctx.t, ctx.t2) - val str = doVisitSingleLineConstantStringPart(ctx.ts) - return Ident(str, toSpan(ctx)) - } - - override fun visitStringLiteralType(ctx: PklParser.StringLiteralTypeContext): Any { - val ident = visitStringConstant(ctx.stringConstant()) - return Type.StringConstant(ident.value, toSpan(ctx)) - } - - override fun visitParenthesizedType(ctx: PklParser.ParenthesizedTypeContext): Type { - val type = visitType(ctx.type()) - return Type.Parenthesised(type, toSpan(ctx)) - } - - override fun visitDeclaredType(ctx: PklParser.DeclaredTypeContext): Type { - val ids = visitQualifiedIdentifier(ctx.qualifiedIdentifier()) - val args = visitTypeArgumentList(ctx.typeArgumentList()) - return Type.Declared(ids, args, toSpan(ctx)) - } - - override fun visitTypeArgumentList(ctx: PklParser.TypeArgumentListContext?): List { - if (ctx == null) return listOf() - checkCommaSeparatedElements(ctx, ctx.ts, ctx.errs) - return ctx.ts.map(::visitType) - } - - override fun visitNullableType(ctx: PklParser.NullableTypeContext): Type { - return Type.Nullable(visitType(ctx.type()), toSpan(ctx)) - } - - override fun visitConstrainedType(ctx: PklParser.ConstrainedTypeContext): Type { - checkClosingDelimiter(ctx.err, ")", ctx.stop) - checkCommaSeparatedElements(ctx, ctx.es, ctx.errs) - val type = ctx.type().accept(this) as Type - val exprs = ctx.es.map(::visitExpr) - return Type.Constrained(type, exprs, toSpan(ctx)) - } - - override fun visitDefaultUnionType(ctx: PklParser.DefaultUnionTypeContext): Type { - val span = toSpan(ctx) - if (ctx.parent !is PklParser.UnionTypeContext) { - errors += ParseError("notAUnion", span) - } - return Type.DefaultUnion(visitType(ctx.type()), span) - } - - override fun visitUnionType(ctx: PklParser.UnionTypeContext): Type { - checkUnionType(ctx) - return Type.Union(visitType(ctx.l), visitType(ctx.r), toSpan(ctx)) - } - - private fun checkUnionType(ctx: PklParser.UnionTypeContext) { - var hasDefault = false - val list = ArrayDeque() - list.addLast(ctx.l) - list.addLast(ctx.r) - - while (list.isNotEmpty()) { - val current = list.removeFirst() - if (current is PklParser.UnionTypeContext) { - list.addFirst(current.r) - list.addFirst(current.l) - continue - } - if (current is PklParser.DefaultUnionTypeContext) { - if (hasDefault) errors += ParseError("multipleUnionDefaults", toSpan(current)) - hasDefault = true - } - } - } - - override fun visitFunctionType(ctx: PklParser.FunctionTypeContext): Any { - checkCommaSeparatedElements(ctx, ctx.ps, ctx.errs) - return Type.Function(ctx.ps.map(::visitType), visitType(ctx.r), toSpan(ctx)) - } - - override fun visitType(ctx: PklParser.TypeContext): Type { - return ctx.accept(this) as Type - } - - override fun visitTypeAnnotation(ctx: PklParser.TypeAnnotationContext?): Type? { - if (ctx == null) return null - return visitType(ctx.type()) - } - - override fun visitQualifiedIdentifier(ctx: PklParser.QualifiedIdentifierContext): List { - return ctx.ts.map(::toIdent) - } - - override fun visitThisExpr(ctx: PklParser.ThisExprContext): Expr { - return Expr.This(toSpan(ctx)) - } - - override fun visitOuterExpr(ctx: PklParser.OuterExprContext): Expr { - return Expr.Outer(toSpan(ctx)) - } - - override fun visitModuleExpr(ctx: PklParser.ModuleExprContext): Expr { - return Expr.Module(toSpan(ctx)) - } - - override fun visitNullLiteral(ctx: PklParser.NullLiteralContext): Expr { - return Expr.Null(toSpan(ctx)) - } - - override fun visitTrueLiteral(ctx: PklParser.TrueLiteralContext): Expr { - return Expr.BooleanLiteral(true, toSpan(ctx)) - } - - override fun visitFalseLiteral(ctx: PklParser.FalseLiteralContext): Expr { - return Expr.BooleanLiteral(false, toSpan(ctx)) - } - - override fun visitIntLiteral(ctx: PklParser.IntLiteralContext): Expr { - var text = ctx.IntLiteral().text - val span = toSpan(ctx) - - var radix = 10 - if (text.startsWith("0x") || text.startsWith("0b") || text.startsWith("0o")) { - val type = text[1] - radix = - when (type) { - 'x' -> 16 - 'b' -> 2 - else -> 8 - } - - text = text.substring(2) - if (text.startsWith("_")) { - errors += ParseError("invalidEscapeInNumber", span) - } - } - - if (ctx.getParent() is PklParser.UnaryMinusExprContext) { - text = "-$text" - } - text = text.replace("_", "") - val num = - try { - text.toLong(radix) - } catch (_: NumberFormatException) { - // keep going to find more errors - errors += ParseError("intTooLarge", span) - 1 - } - return Expr.IntLiteral(num, span) - } - - override fun visitFloatLiteral(ctx: PklParser.FloatLiteralContext): Expr { - var text = ctx.FloatLiteral().text - val span = toSpan(ctx) - - if (ctx.getParent() is PklParser.UnaryMinusExprContext) { - text = "-$text" - } - - val dotIdx = text.indexOf('.') - if (dotIdx != -1 && text[dotIdx + 1] == '_') { - errors += ParseError("invalidEscapeInNumber", span) - } - var exponentIdx = text.indexOf('e') - if (exponentIdx == -1) { - exponentIdx = text.indexOf('E') - } - if (exponentIdx != -1 && text[exponentIdx + 1] == '_') { - errors += ParseError("invalidEscapeInNumber", span) - } - - text = text.replace("_", "") - - val num = - try { - text.toDouble() - } catch (_: NumberFormatException) { - // keep going to find more errors - errors += ParseError("floatTooLarge", span) - 1.0 - } - return Expr.FloatLiteral(num, span) - } - - override fun visitSingleLineStringLiteral(ctx: PklParser.SingleLineStringLiteralContext): Expr { - checkSingleLineStringDelimiters(ctx.t, ctx.t2) - val singlePart = ctx.singleLineStringPart() - return when (singlePart.size) { - 0 -> Expr.ConstantString("", toSpan(ctx)) - 1 -> visitSingleLineStringPart(singlePart.first()) - else -> Expr.InterpolatedString(singlePart.map(::visitSingleLineStringPart), toSpan(ctx)) - } - } - - override fun visitSingleLineStringPart(ctx: PklParser.SingleLineStringPartContext): Expr { - if (ctx.e != null) { - return Expr.InterpolatedString(listOf(visitExpr(ctx.e)), toSpan(ctx)) - } - return Expr.ConstantString(doVisitSingleLineConstantStringPart(ctx.ts), toSpan(ctx)) - } - - override fun visitMultiLineStringPart(ctx: PklParser.MultiLineStringPartContext?): Any { - throw RuntimeException("unreacheable code: visitMultiLineStringPart") - } - - override fun visitMultiLineStringLiteral(ctx: PklParser.MultiLineStringLiteralContext): Expr { - val multiPart = ctx.multiLineStringPart() - - if (multiPart.isEmpty()) { - errors += ParseError("stringContentMustBeginOnNewLine", toSpan(ctx)) - } - - val firstPart = multiPart[0] - if (firstPart.e != null || firstPart.ts[0].type != PklLexer.MLNewline) { - errors += ParseError("stringContentMustBeginOnNewLine", toSpan(ctx)) - } - - val lastPart = multiPart[multiPart.size - 1] - val commonIndent: String = getCommonIndent(lastPart, ctx.t2) - - if (multiPart.size == 1) { - return Expr.ConstantString( - doVisitMultiLineConstantStringPart( - firstPart.ts, - commonIndent, - isStringStart = true, - isStringEnd = true - ), - toSpan(ctx) - ) - } - - val multiPartExprs = ArrayList(multiPart.size) - val lastIndex = multiPart.size - 1 - - for (i in 0..lastIndex) { - val part = multiPart[i] - multiPartExprs += - if (part.e != null) { - Expr.InterpolatedString(listOf(visitExpr(part.e)), toSpan(part)) - } else { - Expr.ConstantString( - doVisitMultiLineConstantStringPart(part.ts, commonIndent, i == 0, i == lastIndex), - toSpan(part) - ) - } - } - - return Expr.InterpolatedString(multiPartExprs, toSpan(ctx)) - } - - override fun visitThrowExpr(ctx: PklParser.ThrowExprContext): Expr { - checkClosingDelimiter(ctx.err, ")", ctx.stop) - return Expr.Throw(visitExpr(ctx.expr()), toSpan(ctx)) - } - - override fun visitTraceExpr(ctx: PklParser.TraceExprContext): Expr { - checkClosingDelimiter(ctx.err, ")", ctx.stop) - return Expr.Trace(visitExpr(ctx.expr()), toSpan(ctx)) - } - - override fun visitExpr(ctx: PklParser.ExprContext): Expr { - return ctx.accept(this) as Expr - } - - override fun visitImportExpr(ctx: PklParser.ImportExprContext): Expr { - checkClosingDelimiter(ctx.err, ")", ctx.stop) - val isGlob = ctx.t.type == PklLexer.IMPORT_GLOB - val uri = visitStringConstant(ctx.stringConstant()) - if (isGlob && uri.value.startsWith("...")) { - errors += ParseError("cannotGlobTripleDots", uri.span) - } - return if (isGlob) { - Expr.ImportGlob(uri, toSpan(ctx)) - } else { - Expr.Import(uri, toSpan(ctx)) - } - } - - override fun visitReadExpr(ctx: PklParser.ReadExprContext): Expr { - val exprCtx = ctx.expr() - checkClosingDelimiter(ctx.err, ")", exprCtx.stop) - val span = toSpan(ctx) - - val token = ctx.t.type - return when (token) { - PklLexer.READ -> Expr.Read(visitExpr(exprCtx), span) - PklLexer.READ_OR_NULL -> Expr.ReadNull(visitExpr(exprCtx), span) - else -> Expr.ReadGlob(visitExpr(exprCtx), span) - } - } - - override fun visitUnqualifiedAccessExpr(ctx: PklParser.UnqualifiedAccessExprContext): Expr { - val ident = toIdent(ctx.Identifier()) - - if (ctx.argumentList() == null) { - return Expr.UnqualifiedAccess(ident, toSpan(ctx)) - } - - return Expr.UnqualifiedMethodAccess(ident, visitArgumentList(ctx.argumentList()), toSpan(ctx)) - } - - override fun visitArgumentList(ctx: PklParser.ArgumentListContext): List { - checkClosingDelimiter(ctx.err, ")", ctx.stop) - checkCommaSeparatedElements(ctx, ctx.es, ctx.errs) - return ctx.es.map(::visitExpr) - } - - override fun visitQualifiedAccessExpr(ctx: PklParser.QualifiedAccessExprContext): Expr { - val expr = visitExpr(ctx.expr()) - val ident = toIdent(ctx.Identifier()) - val isNullable = ctx.t.type == PklLexer.QDOT - return if (ctx.argumentList() != null) { - Expr.QualifiedMethodAccess( - expr, - ident, - isNullable, - visitArgumentList(ctx.argumentList()), - toSpan(ctx) - ) - } else { - Expr.QualifiedAccess(expr, ident, isNullable, toSpan(ctx)) - } - } - - override fun visitSuperAccessExpr(ctx: PklParser.SuperAccessExprContext): Expr { - val member = toIdent(ctx.Identifier()) - val argCtx = ctx.argumentList() - - return if (argCtx != null) { - Expr.SuperMethodAccess(member, visitArgumentList(argCtx), toSpan(ctx)) - } else { - Expr.SuperAccess(member, toSpan(ctx)) - } - } - - override fun visitSuperSubscriptExpr(ctx: PklParser.SuperSubscriptExprContext): Expr { - checkClosingDelimiter(ctx.err, "]", ctx.e.stop) - return Expr.SuperSubscript(visitExpr(ctx.e), toSpan(ctx)) - } - - override fun visitSubscriptExpr(ctx: PklParser.SubscriptExprContext): Expr { - checkClosingDelimiter(ctx.err, "]", ctx.stop) - return Expr.Subscript(visitExpr(ctx.l), visitExpr(ctx.r), toSpan(ctx)) - } - - override fun visitIfExpr(ctx: PklParser.IfExprContext): Expr { - checkClosingDelimiter(ctx.err, ")", ctx.c.stop) - return Expr.If(visitExpr(ctx.c), visitExpr(ctx.l), visitExpr(ctx.r), toSpan(ctx)) - } - - override fun visitLetExpr(ctx: PklParser.LetExprContext): Expr { - checkClosingDelimiter(ctx.err, ")", ctx.l.stop) - val param = visitParameter(ctx.parameter()) - val binding = visitExpr(ctx.l) - val expr = visitExpr(ctx.r) - return Expr.Let(param, binding, expr, toSpan(ctx)) - } - - override fun visitFunctionLiteral(ctx: PklParser.FunctionLiteralContext): Expr { - val span = toSpan(ctx) - val params = visitParameterList(ctx.parameterList()) - if (params.size > 5) { - errors += ParseError("tooManyFunctionParameters", span) - } - return Expr.FunctionLiteral(params, visitExpr(ctx.expr()), span) - } - - override fun visitParenthesizedExpr(ctx: PklParser.ParenthesizedExprContext): Expr { - checkClosingDelimiter(ctx.err, ")", ctx.stop) - return Expr.Parenthesised(visitExpr(ctx.expr()), toSpan(ctx)) - } - - override fun visitNewExpr(ctx: PklParser.NewExprContext): Expr { - val type = ctx.type()?.let(::visitType) - val body = visitObjectBody(ctx.objectBody()) - return Expr.New(type, body, toSpan(ctx)) - } - - override fun visitAmendExpr(ctx: PklParser.AmendExprContext): Expr { - val parentExpr = ctx.expr() - if ( - !(parentExpr is PklParser.NewExprContext || - parentExpr is PklParser.AmendExprContext || - parentExpr is PklParser.ParenthesizedExprContext) - ) { - errors += - ParseError("unexpectedCurlyProbablyAmendsExpression", toSpan(ctx.objectBody().start)) - } - val expr = visitExpr(parentExpr) - val body = visitObjectBody(ctx.objectBody()) - return Expr.Amends(expr, body, toSpan(ctx)) - } - - override fun visitNonNullExpr(ctx: PklParser.NonNullExprContext): Expr { - return Expr.NonNull(visitExpr(ctx.expr()), toSpan(ctx)) - } - - override fun visitUnaryMinusExpr(ctx: PklParser.UnaryMinusExprContext): Expr { - val expr = visitExpr(ctx.expr()) - if (expr is Expr.IntLiteral || expr is Expr.FloatLiteral) { - // already handled - return expr - } - return Expr.UnaryMinus(expr, toSpan(ctx)) - } - - override fun visitLogicalNotExpr(ctx: PklParser.LogicalNotExprContext): Expr { - return Expr.LogicalNot(visitExpr(ctx.expr()), toSpan(ctx)) - } - - override fun visitLogicalAndExpr(ctx: PklParser.LogicalAndExprContext): Expr { - return Expr.BinaryOp(visitExpr(ctx.l), visitExpr(ctx.r), Operation.AND, toSpan(ctx)) - } - - override fun visitLogicalOrExpr(ctx: PklParser.LogicalOrExprContext): Expr { - return Expr.BinaryOp(visitExpr(ctx.l), visitExpr(ctx.r), Operation.OR, toSpan(ctx)) - } - - override fun visitAdditiveExpr(ctx: PklParser.AdditiveExprContext): Expr { - val type = if (ctx.t.type == PklLexer.PLUS) Operation.PLUS else Operation.MINUS - return Expr.BinaryOp(visitExpr(ctx.l), visitExpr(ctx.r), type, toSpan(ctx)) - } - - override fun visitMultiplicativeExpr(ctx: PklParser.MultiplicativeExprContext): Expr { - val type = - when (ctx.t.type) { - PklLexer.STAR -> Operation.MULT - PklLexer.DIV -> Operation.DIV - PklLexer.INT_DIV -> Operation.INT_DIV - PklLexer.MOD -> Operation.MOD - else -> throw RuntimeException("unreacheable code") - } - return Expr.BinaryOp(visitExpr(ctx.l), visitExpr(ctx.r), type, toSpan(ctx)) - } - - override fun visitComparisonExpr(ctx: PklParser.ComparisonExprContext): Expr { - val type = - when (ctx.t.type) { - PklLexer.GT -> Operation.GT - PklLexer.GTE -> Operation.GTE - PklLexer.LT -> Operation.LT - PklLexer.LTE -> Operation.LTE - else -> throw RuntimeException("unreacheable code") - } - return Expr.BinaryOp(visitExpr(ctx.l), visitExpr(ctx.r), type, toSpan(ctx)) - } - - override fun visitEqualityExpr(ctx: PklParser.EqualityExprContext): Expr { - val type = - when (ctx.t.type) { - PklLexer.EQUAL -> Operation.EQ_EQ - PklLexer.NOT_EQUAL -> Operation.NOT_EQ - else -> throw RuntimeException("unreacheable code") - } - return Expr.BinaryOp(visitExpr(ctx.l), visitExpr(ctx.r), type, toSpan(ctx)) - } - - override fun visitPipeExpr(ctx: PklParser.PipeExprContext): Expr { - return Expr.BinaryOp(visitExpr(ctx.l), visitExpr(ctx.r), Operation.PIPE, toSpan(ctx)) - } - - override fun visitNullCoalesceExpr(ctx: PklParser.NullCoalesceExprContext): Expr { - return Expr.BinaryOp(visitExpr(ctx.l), visitExpr(ctx.r), Operation.NULL_COALESCE, toSpan(ctx)) - } - - override fun visitExponentiationExpr(ctx: PklParser.ExponentiationExprContext): Expr { - return Expr.BinaryOp(visitExpr(ctx.l), visitExpr(ctx.r), Operation.POW, toSpan(ctx)) - } - - override fun visitTypeAlias(ctx: PklParser.TypeAliasContext): TypeAlias { - // TODO: add doc comment - val header = ctx.typeAliasHeader() - val modifiers = - checkModifiers( - header.modifier(), - "invalidModifier", - ModifierValue.LOCAL, - ModifierValue.EXTERNAL - ) - val name = toIdent(header.Identifier()) - val typePars = visitTypeParameterList(header.typeParameterList()) - val type = visitType(ctx.type()) - val annotations = ctx.annotation()?.map(this::visitAnnotation) ?: listOf() - return TypeAlias(null, annotations, modifiers, name, typePars, type, toSpan(ctx)) - } - - override fun visitAnnotation(ctx: PklParser.AnnotationContext): Annotation { - val type = visitType(ctx.type()) - val body = ctx.objectBody()?.let(::visitObjectBody) - return Annotation(type, body, toSpan(ctx)) - } - - override fun visitImportClause(ctx: PklParser.ImportClauseContext): Import { - val isGlobImport = ctx.t.type == PklLexer.IMPORT_GLOB - val importUri = visitStringConstant(ctx.stringConstant()) - if (isGlobImport && importUri.value.startsWith("...")) { - errors += ParseError("cannotGlobTripleDots", toSpan(ctx.stringConstant())) - } - val alias = ctx.Identifier()?.let(::toIdent) - return Import(importUri, isGlobImport, alias, toSpan(ctx)) - } - - override fun visitClazz(ctx: PklParser.ClazzContext): Clazz { - // TODO: add doc comment - val header = ctx.classHeader() - val modifiers = - checkModifiers( - header.modifier(), - "invalidModifier", - ModifierValue.LOCAL, - ModifierValue.OPEN, - ModifierValue.ABSTRACT, - ModifierValue.EXTERNAL - ) - val openAbstract = - modifiers.filter { it.mod == ModifierValue.OPEN || it.mod == ModifierValue.ABSTRACT } - if (openAbstract.size == 2) { - errors += ParseError("openAbstractClassOrModule", openAbstract[1].span) - } - val name = toIdent(header.Identifier()) - val typePars = visitTypeParameterList(header.typeParameterList()) - val superCLass = header.type()?.let(::visitType) - val body = visitClassBody(ctx.classBody()) - val annotations = ctx.annotation()?.map(::visitAnnotation) ?: listOf() - return Clazz(null, annotations, modifiers, name, typePars, superCLass, body, toSpan(ctx)) - } - - override fun visitClassBody(ctx: PklParser.ClassBodyContext): List { - checkClosingDelimiter(ctx.err, "}", ctx.stop) - val properties = ctx.classProperty().map(::visitClassProperty) - val methods = ctx.classMethod().map(::visitClassMethod) - return properties + methods - } - - override fun visitClassProperty(ctx: PklParser.ClassPropertyContext): ClassEntry { - // TODO: add doc comment - val modifiers = - checkModifiers( - ctx.modifier(), - "invalidModifier", - ModifierValue.ABSTRACT, - ModifierValue.LOCAL, - ModifierValue.HIDDEN, - ModifierValue.EXTERNAL, - ModifierValue.FIXED, - ModifierValue.CONST - ) - val name = toIdent(ctx.Identifier()) - val typeAnnotation = ctx.typeAnnotation()?.let(::visitTypeAnnotation) - val expr = ctx.expr()?.let(::visitExpr) - val body = ctx.objectBody()?.map(::visitObjectBody) - val span = toSpan(ctx) - val annotations = ctx.annotation()?.map(::visitAnnotation) ?: listOf() - return when { - expr != null -> ClassPropertyExpr(null, listOf(), modifiers, name, typeAnnotation, expr, span) - body != null -> ClassPropertyBody(null, listOf(), modifiers, name, typeAnnotation, body, span) - else -> { - assert(typeAnnotation != null) // parser makes sure that holds - ClassProperty(null, annotations, modifiers, name, typeAnnotation!!, span) - } - } - } - - override fun visitClassMethod(ctx: PklParser.ClassMethodContext): ClassMethod { - // TODO: add doc comment - val header = ctx.methodHeader() - val modifiers = - checkModifiers( - header.modifier(), - "invalidModifier", - ModifierValue.ABSTRACT, - ModifierValue.LOCAL, - ModifierValue.EXTERNAL, - ModifierValue.CONST - ) - val name = toIdent(header.Identifier()) - val typeParams = visitTypeParameterList(header.typeParameterList()) - val params = visitParameterList(header.parameterList()) - val typeAnnotation = header.typeAnnotation()?.let(::visitTypeAnnotation) - val expr = ctx.expr()?.let(::visitExpr) - val annotations = ctx.annotation()?.map(::visitAnnotation) ?: listOf() - return ClassMethod( - null, - annotations, - modifiers, - name, - typeParams, - params, - typeAnnotation, - expr, - toSpan(ctx) - ) - } - - override fun visitModuleDecl(ctx: PklParser.ModuleDeclContext?): ModuleDecl? { - // TODO: add doc comment - if (ctx == null) return null - - val annotations = ctx.annotation()?.map(this::visitAnnotation) ?: listOf() - val header = ctx.moduleHeader() ?: return null - val modifiers = - header.modifier()?.let { - val mods = checkModifiers(it, "invalidModifier", ModifierValue.ABSTRACT, ModifierValue.OPEN) - val openAbstract = - mods.filter { m -> m.mod == ModifierValue.OPEN || m.mod == ModifierValue.ABSTRACT } - if (openAbstract.size == 2) { - errors += ParseError("openAbstractClassOrModule", openAbstract[1].span) - } - mods - } - ?: listOf() - val name = - header.qualifiedIdentifier()?.let { ModuleNameDecl(visitQualifiedIdentifier(it), toSpan(it)) } - var extends: ExtendsDecl? = null - var amends: AmendsDecl? = null - val extendsOrAmends = header.moduleExtendsOrAmendsClause() - if (extendsOrAmends != null) { - val uri = extendsOrAmends.stringConstant() - if (extendsOrAmends.t.type == PklLexer.AMENDS) { - amends = AmendsDecl(visitStringConstant(uri), toSpan(uri)) - } else { - extends = ExtendsDecl(visitStringConstant(uri), toSpan(uri)) - } - } - return ModuleDecl(null, annotations, modifiers, name, extends, amends, toSpan(ctx)) - } - - override fun visitModule(ctx: PklParser.ModuleContext): PklModule { - val decl = visitModuleDecl(ctx.moduleDecl()) - val imports = ctx.importClause()?.map(::visitImportClause) ?: listOf() - val classes = ctx.cs.map(::visitClazz) - val typeAliases = ctx.ts.map(::visitTypeAlias) - val properties = ctx.ps.map(::visitClassProperty) - val methods = ctx.ms.map(::visitClassMethod) - return PklModule(decl, imports, classes + typeAliases + properties + methods, toSpan(ctx)) - } - - override fun visitTypeTestExpr(ctx: PklParser.TypeTestExprContext): Expr { - val expr = visitExpr(ctx.l) - val type = visitType(ctx.r) - val span = toSpan(ctx) - return if (ctx.t.type == PklLexer.IS) { - Expr.TypeTest(expr, type, span) - } else { - Expr.TypeCast(expr, type, span) - } - } - - override fun visitObjectBody(ctx: PklParser.ObjectBodyContext): ObjectBody { - checkClosingDelimiter(ctx.err, "}", ctx.stop) - checkCommaSeparatedElements(ctx, ctx.ps, ctx.errs) - val params = ctx.ps.map(::visitParameter) - val members = doVisitObjectMemberList(ctx.objectMember()) - return ObjectBody(params, members, toSpan(ctx)) - } - - override fun visitObjectMember(ctx: PklParser.ObjectMemberContext): ObjectMember { - return ctx.accept(this) as ObjectMember - } - - override fun visitObjectProperty(ctx: PklParser.ObjectPropertyContext): ObjectMember { - val span = toSpan(ctx) - val modifiers = - checkModifiers( - ctx.modifier(), - "invalidModifierForObjectPropertyOrMethod", - ModifierValue.LOCAL - ) - val ident = toIdent(ctx.Identifier()) - val typeAnnotation = visitTypeAnnotation(ctx.typeAnnotation()) - return if (ctx.expr() != null) { - ObjectMember.Property(modifiers, ident, typeAnnotation, visitExpr(ctx.expr()), span) - } else { - val bodies = ctx.objectBody().map(::visitObjectBody) - ObjectMember.PropertyBody(modifiers, ident, bodies, span) - } - } - - override fun visitObjectMethod(ctx: PklParser.ObjectMethodContext): ObjectMember { - val span = toSpan(ctx) - val method = ctx.methodHeader() - val modifiers = - checkModifiers( - method.modifier(), - "invalidModifierForObjectPropertyOrMethod", - ModifierValue.LOCAL - ) - val ident = toIdent(method.Identifier()) - val typeParams = visitTypeParameterList(method.typeParameterList()) - val params = visitParameterList(method.parameterList()) - val typeAnnotation = visitTypeAnnotation(method.typeAnnotation()) - val expr = visitExpr(ctx.expr()) - return ObjectMember.Method(modifiers, ident, typeParams, params, typeAnnotation, expr, span) - } - - override fun visitMemberPredicate(ctx: PklParser.MemberPredicateContext): ObjectMember { - if (ctx.err1 == null && ctx.err2 == null) { - errors += ParseError("missingDelimiter", toSpan(ctx.k.stop), ")") - } else if ( - ctx.err1 != null && (ctx.err2 == null || ctx.err1.startIndex != ctx.err2.startIndex - 1) - ) { - // There shouldn't be any whitespace between the first and second ']'. - errors += ParseError("wrongDelimiter", toSpan(ctx.err1), "]]", "]") - } - val pred = visitExpr(ctx.k) - val span = toSpan(ctx) - return if (ctx.v != null) { - ObjectMember.MemberPredicate(pred, visitExpr(ctx.v), span) - } else { - val bodies = ctx.objectBody().map(::visitObjectBody) - ObjectMember.MemberPredicateBody(pred, bodies, span) - } - } - - override fun visitObjectEntry(ctx: PklParser.ObjectEntryContext): ObjectMember { - checkClosingDelimiter(ctx.err1, "]", ctx.k.stop) - val pred = visitExpr(ctx.k) - val span = toSpan(ctx) - return if (ctx.v != null) { - ObjectMember.Entry(pred, visitExpr(ctx.v), span) - } else { - val bodies = ctx.objectBody().map(::visitObjectBody) - ObjectMember.EntryBody(pred, bodies, span) - } - } - - override fun visitObjectElement(ctx: PklParser.ObjectElementContext): ObjectMember { - return ObjectMember.Element(visitExpr(ctx.expr()), toSpan(ctx)) - } - - override fun visitObjectSpread(ctx: PklParser.ObjectSpreadContext): ObjectMember { - val isNullable = ctx.QSPREAD() != null - return ObjectMember.Spread(visitExpr(ctx.expr()), isNullable, toSpan(ctx)) - } - - override fun visitWhenGenerator(ctx: PklParser.WhenGeneratorContext): ObjectMember { - checkClosingDelimiter(ctx.err, ")", ctx.e.stop) - val pred = visitExpr(ctx.e) - val body = visitObjectBody(ctx.b1) - val elseBody = ctx.b2?.let(::visitObjectBody) - return ObjectMember.WhenGenerator(pred, body, elseBody, toSpan(ctx)) - } - - override fun visitForGenerator(ctx: PklParser.ForGeneratorContext): ObjectMember { - checkClosingDelimiter(ctx.err, ")", ctx.e.stop) - val p1 = visitParameter(ctx.t1) - val p2 = ctx.t2?.let(::visitParameter) - val expr = visitExpr(ctx.e) - val body = visitObjectBody(ctx.objectBody()) - return ObjectMember.ForGenerator(p1, p2, expr, body, toSpan(ctx)) - } - - override fun visitTypeParameter(ctx: PklParser.TypeParameterContext): TypeParameter { - val ident = toIdent(ctx.Identifier()) - val span = toSpan(ctx) - return when { - ctx.IN() != null -> TypeParameter(Variance.IN, ident, span) - ctx.OUT() != null -> TypeParameter(Variance.OUT, ident, span) - else -> TypeParameter(null, ident, span) - } - } - - override fun visitTypeParameterList( - ctx: PklParser.TypeParameterListContext? - ): List { - if (ctx == null) return listOf() - checkCommaSeparatedElements(ctx, ctx.ts, ctx.errs) - checkClosingDelimiter(ctx.err, ">", ctx.stop) - return ctx.ts.map(::visitTypeParameter) - } - - override fun visitModifier(ctx: PklParser.ModifierContext): Modifier { - val span = toSpan(ctx) - val value = - when (ctx.t.type) { - PklLexer.EXTERNAL -> ModifierValue.EXTERNAL - PklLexer.ABSTRACT -> ModifierValue.ABSTRACT - PklLexer.LOCAL -> ModifierValue.LOCAL - PklLexer.HIDDEN_ -> ModifierValue.HIDDEN - PklLexer.FIXED -> ModifierValue.FIXED - PklLexer.CONST -> ModifierValue.CONST - PklLexer.OPEN -> ModifierValue.OPEN - else -> { - errors += ParseError("invalidModifier", span) - ModifierValue.HIDDEN - } - } - return Modifier(value, span) - } - - override fun visitParameter(ctx: PklParser.ParameterContext): Parameter { - if (ctx.UNDERSCORE() != null) return Parameter.Underscore(toSpan(ctx)) - val ident = toIdent(ctx.typedIdentifier().Identifier()) - val typeAnnotation = visitTypeAnnotation(ctx.typedIdentifier().typeAnnotation()) - return Parameter.TypedIdent(ident, typeAnnotation, toSpan(ctx)) - } - - override fun visitParameterList(ctx: PklParser.ParameterListContext?): List { - if (ctx == null) return listOf() - checkCommaSeparatedElements(ctx, ctx.ts, ctx.errs) - checkClosingDelimiter(ctx.err, ")", ctx.stop) - return ctx.ts.map(::visitParameter) - } - - // Helpers - - fun errors(): List = errors - - private fun doVisitObjectMemberList( - members: List? - ): List { - if (members == null) return listOf() - return members.map(::visitObjectMember) - } - - private fun checkModifiers( - modifiers: List, - error: String, - vararg valids: ModifierValue - ): List { - return modifiers.map { - val mod = visitModifier(it) - if (mod.mod !in valids) { - errors += ParseError(error, toSpan(it)) - } - mod - } - } - - private fun doVisitSingleLineConstantStringPart(ts: List): String { - if (ts.isEmpty()) return "" - - val builder = StringBuilder() - for (token in ts) { - when (token.type) { - PklLexer.SLCharacters -> builder.append(token.text) - PklLexer.SLCharacterEscape -> builder.append(doParseCharacterEscapeSequence(token)) - PklLexer.SLUnicodeEscape -> builder.appendCodePoint(doParseUnicodeEscapeSequence(token)) - else -> throw RuntimeException("unreacheableCode") - } - } - - return builder.toString() - } - - private fun doVisitMultiLineConstantStringPart( - tokens: List, - commonIndent: String, - isStringStart: Boolean, - isStringEnd: Boolean - ): String { - var startIndex = 0 - if (isStringStart) { - // skip leading newline token - startIndex = 1 - } - - var endIndex = tokens.size - 1 - if (isStringEnd) { - endIndex -= - if (tokens[endIndex].type == PklLexer.MLNewline) { - // skip trailing newline token - 1 - } else { - // skip trailing newline and whitespace (common indent) tokens - 2 - } - } - - val builder = StringBuilder() - var isLineStart = isStringStart - - for (i in startIndex..endIndex) { - val token = tokens[i] - - when (token.type) { - PklLexer.MLNewline -> { - builder.append('\n') - isLineStart = true - } - PklLexer.MLCharacters -> { - val text = token.text - if (isLineStart) { - if (text.startsWith(commonIndent)) { - builder.append(text, commonIndent.length, text.length) - } else { - errors += ParseError("stringIndentationMustMatchLastLine", toSpan(token)) - } - } else { - builder.append(text) - } - isLineStart = false - } - PklLexer.MLCharacterEscape -> { - if (isLineStart && commonIndent.isNotEmpty()) { - errors += ParseError("stringIndentationMustMatchLastLine", toSpan(token)) - } - builder.append(doParseCharacterEscapeSequence(token)) - isLineStart = false - } - PklLexer.MLUnicodeEscape -> { - if (isLineStart && commonIndent.isNotEmpty()) { - errors += ParseError("stringIndentationMustMatchLastLine", toSpan(token)) - } - builder.appendCodePoint(doParseUnicodeEscapeSequence(token)) - isLineStart = false - } - else -> throw RuntimeException("unreacheable code: doVisitMultiLineConstantStringPart") - } - } - - return builder.toString() - } - - private fun doParseUnicodeEscapeSequence(token: Token): Int { - val text = token.text - val lastIndex = text.length - 1 - - if (text[lastIndex] != '}') { - errors.add(ParseError("unterminatedUnicodeEscapeSequence", toSpan(token))) - } - - val startIndex = text.indexOf('{', 2) - assert(startIndex != -1) // guaranteed by lexer - try { - return text.substring(startIndex + 1, lastIndex).toInt(16) - } catch (e: NumberFormatException) { - errors.add(ParseError("invalidUnicodeEscapeSequence", toSpan(token))) - return -1 - } - } - - private fun doParseCharacterEscapeSequence(token: Token): String { - val text = token.text - val lastChar = text[text.length - 1] - - when (lastChar) { - 'n' -> return "\n" - 'r' -> return "\r" - 't' -> return "\t" - '"' -> return "\"" - '\\' -> return "\\" - else -> { - errors.add(ParseError("invalidCharacterEscapeSequence", toSpan(token))) - return "" - } - } - } - - private fun getCommonIndent( - lastPart: PklParser.MultiLineStringPartContext, - endQuoteToken: Token - ): String { - if (lastPart.e != null) { - errors += ParseError("closingStringDelimiterMustBeginOnNewLine", toSpan(lastPart)) - } - - val tokens = lastPart.ts - assert(tokens.size >= 1) - val lastToken = tokens[tokens.size - 1] - - if (lastToken.type == PklLexer.MLNewline) { - return "" - } - - if (tokens.size > 1) { - val lastButOneToken = tokens[tokens.size - 2] - if (lastButOneToken.type == PklLexer.MLNewline && AstBuilder.isIndentChars(lastToken)) { - return lastToken.text - } - } - - errors += ParseError("closingStringDelimiterMustBeginOnNewLine", toSpan(endQuoteToken)) - return "" - } - - private fun checkCommaSeparatedElements( - ctx: ParserRuleContext, - elements: List, - separators: List - ) { - if (elements.isEmpty() || separators.size == elements.size - 1) return - - // determine location of missing separator - // O(n^2) but only runs once a syntax error has been detected - var prevChild: ParseTree? = null - for (child in ctx.children) { - val index = elements.indexOf(child) - if (index > 0) { // 0 rather than -1 because no separator is expected before first element - assert(prevChild != null) - if (prevChild !is TerminalNode || !separators.contains(prevChild.symbol)) { - val prevToken = - if (prevChild is TerminalNode) prevChild.symbol - else (prevChild as ParserRuleContext?)!!.getStop() - errors += ParseError("missingCommaSeparator", toSpan(prevToken)) - } - } - prevChild = child - } - } - - private fun checkClosingDelimiter( - delimiter: Token?, - delimiterSymbol: String, - tokenBeforeDelimiter: Token - ) { - if (delimiter == null) { - errors += ParseError("missingDelimiter", toSpan(tokenBeforeDelimiter), delimiterSymbol) - } - } - - private fun checkSingleLineStringDelimiters(openingDelimiter: Token, closingDelimiter: Token) { - val closingText = closingDelimiter.text - val lastChar = closingText[closingText.length - 1] - if (lastChar == '"' || lastChar == '#') return - - val openingText = openingDelimiter.text - errors += - ParseError( - "missingDelimiter", - toSpan(closingDelimiter), - "\"" + openingText.substring(0, openingText.length - 1) - ) - } - - companion object { - private fun toIdent(token: Token): Ident = Ident(token.text, toSpan(token)) - - private fun toIdent(node: TerminalNode): Ident = Ident(node.text, toSpan(node.symbol)) - - private fun toSpan(ctx: ParserRuleContext): Span { - val begin = ctx.start - val end = ctx.stop - val endCol = end.charPositionInLine + 1 + end.text.length - return Span(begin.line, begin.charPositionInLine + 1, end.line, endCol) - } - - private fun toSpan(token: Token): Span { - val endCol = token.charPositionInLine + 1 + token.text.length - return Span(token.line, token.charPositionInLine + 1, token.line, endCol) - } - } -} - -class ParseError(val errorType: String, val span: Span, vararg val args: Any) diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/DocComment.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/DocComment.kt deleted file mode 100644 index ea254157b..000000000 --- a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/DocComment.kt +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. - * - * 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 - * - * https://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 org.pkl.lsp.cst - -data class DocComment(val contents: String) diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Expr.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Expr.kt deleted file mode 100644 index 0b36b425c..000000000 --- a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Expr.kt +++ /dev/null @@ -1,239 +0,0 @@ -/** - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. - * - * 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 - * - * https://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 org.pkl.lsp.cst - -sealed class Expr(override val span: Span) : PklNode(span) { - data class This(override val span: Span) : Expr(span) - - data class Outer(override val span: Span) : Expr(span) - - data class Module(override val span: Span) : Expr(span) - - data class Null(override val span: Span) : Expr(span) - - data class BooleanLiteral(val b: Boolean, override val span: Span) : Expr(span) - - data class IntLiteral(val i: Long, override val span: Span) : Expr(span) - - data class FloatLiteral(val f: Double, override val span: Span) : Expr(span) - - data class ConstantString(val s: String, override val span: Span) : Expr(span) - - data class InterpolatedString(val exprs: List, override val span: Span) : Expr(span) { - init { - for (it in exprs) it.parent = this - } - } - - data class Throw(val expr: Expr, override val span: Span) : Expr(span) { - init { - expr.parent = this - } - } - - data class Trace(val expr: Expr, override val span: Span) : Expr(span) { - init { - expr.parent = this - } - } - - data class Import(val uri: Ident, override val span: Span) : Expr(span) - - data class ImportGlob(val uri: Ident, override val span: Span) : Expr(span) - - data class Read(val expr: Expr, override val span: Span) : Expr(span) { - init { - expr.parent = this - } - } - - data class ReadGlob(val expr: Expr, override val span: Span) : Expr(span) { - init { - expr.parent = this - } - } - - data class ReadNull(val expr: Expr, override val span: Span) : Expr(span) { - init { - expr.parent = this - } - } - - data class UnqualifiedAccess(val ident: Ident, override val span: Span) : Expr(span) - - data class UnqualifiedMethodAccess( - val ident: Ident, - val args: List, - override val span: Span - ) : Expr(span) { - init { - for (it in args) it.parent = this - } - } - - data class QualifiedAccess( - val expr: Expr, - val ident: Ident, - val isNullable: Boolean, - override val span: Span - ) : Expr(span) { - init { - expr.parent = this - } - } - - data class QualifiedMethodAccess( - val expr: Expr, - val ident: Ident, - val isNullable: Boolean, - val args: List, - override val span: Span - ) : Expr(span) { - init { - expr.parent = this - for (it in args) it.parent = this - } - } - - data class SuperAccess(val ident: Ident, override val span: Span) : Expr(span) - - data class SuperMethodAccess(val ident: Ident, val args: List, override val span: Span) : - Expr(span) { - init { - for (it in args) it.parent = this - } - } - - data class SuperSubscript(val arg: Expr, override val span: Span) : Expr(span) { - init { - arg.parent = this - } - } - - data class Subscript(val expr: Expr, val arg: Expr, override val span: Span) : Expr(span) { - init { - expr.parent = this - arg.parent = this - } - } - - data class If(val cond: Expr, val then: Expr, val els: Expr, override val span: Span) : - Expr(span) { - init { - cond.parent = this - then.parent = this - els.parent = this - } - } - - data class Let(val param: Parameter, val binding: Expr, val expr: Expr, override val span: Span) : - Expr(span) { - init { - param.parent = this - binding.parent = this - expr.parent = this - } - } - - data class FunctionLiteral(val args: List, val expr: Expr, override val span: Span) : - Expr(span) { - init { - expr.parent = this - for (it in args) it.parent = this - } - } - - data class Parenthesised(val expr: Expr, override val span: Span) : Expr(span) { - init { - expr.parent = this - } - } - - data class New(val type: Type?, val body: ObjectBody, override val span: Span) : Expr(span) { - init { - type?.parent = this - body.parent = this - } - } - - data class Amends(val expr: Expr, val body: ObjectBody, override val span: Span) : Expr(span) { - init { - expr.parent = this - body.parent = this - } - } - - data class NonNull(val expr: Expr, override val span: Span) : Expr(span) { - init { - expr.parent = this - } - } - - data class UnaryMinus(val expr: Expr, override val span: Span) : Expr(span) { - init { - expr.parent = this - } - } - - data class LogicalNot(val expr: Expr, override val span: Span) : Expr(span) { - init { - expr.parent = this - } - } - - data class BinaryOp(val left: Expr, val right: Expr, val op: Operation, override val span: Span) : - Expr(span) { - init { - left.parent = this - right.parent = this - } - } - - data class TypeTest(val expr: Expr, val type: Type, override val span: Span) : Expr(span) { - init { - expr.parent = this - } - } - - data class TypeCast(val expr: Expr, val type: Type, override val span: Span) : Expr(span) { - init { - expr.parent = this - type.parent = this - } - } -} - -enum class Operation { - POW, - MULT, - DIV, - INT_DIV, - MOD, - PLUS, - MINUS, - LT, - GT, - LTE, - GTE, - IS, - AS, - EQ_EQ, - NOT_EQ, - AND, - OR, - PIPE, - NULL_COALESCE -} diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Import.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Import.kt deleted file mode 100644 index 7d42ce5e4..000000000 --- a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Import.kt +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. - * - * 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 - * - * https://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 org.pkl.lsp.cst - -data class Import(val url: Ident, val isGlob: Boolean, val alias: Ident?, override val span: Span) : - PklNode(span) diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Module.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Module.kt deleted file mode 100644 index 920e6cdb4..000000000 --- a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Module.kt +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. - * - * 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 - * - * https://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 org.pkl.lsp.cst - -data class PklModule( - val decl: ModuleDecl?, - val imports: List, - val entries: List, - override val span: Span -) : PklNode(span) { - init { - decl?.parent = this - for (it in imports) it.parent = this - for (it in entries) it.parent = this - } -} - -data class ModuleDecl( - val docComment: DocComment?, - val annotations: List, - val modifiers: List, - val name: ModuleNameDecl?, - val extendsDecl: ExtendsDecl?, - val amendsDecl: AmendsDecl?, - override val span: Span, - val nameSpan: Span? = - if (modifiers.isNotEmpty() && name != null) Span.from(modifiers[0].span, name.span) - else name?.span -) : PklNode(span) { - init { - extendsDecl?.parent = this - amendsDecl?.parent = this - name?.parent = this - for (it in annotations) it.parent = this - } -} - -data class ExtendsDecl(val url: Ident, override val span: Span) : PklNode(span) - -data class AmendsDecl(val url: Ident, override val span: Span) : PklNode(span) - -data class ModuleNameDecl(val name: List, override val span: Span) : PklNode(span) { - val nameString = name.joinToString(".") { it.value } -} - -data class Annotation(val type: Type, val body: ObjectBody?, override val span: Span) : - PklNode(span) { - init { - type.parent = this - } -} - -sealed class ModuleEntry( - open val docComment: DocComment?, - open val annotations: List, - open val modifiers: List, - open val name: Ident, - override val span: Span -) : PklNode(span) - -data class TypeAlias( - override val docComment: DocComment?, - override val annotations: List, - override val modifiers: List, - override val name: Ident, - val typePars: List, - val type: Type, - override val span: Span -) : ModuleEntry(docComment, annotations, modifiers, name, span) { - init { - type.parent = this - for (it in annotations) it.parent = this - } -} - -data class Clazz( - override val docComment: DocComment?, - override val annotations: List, - override val modifiers: List, - override val name: Ident, - val typePars: List, - val superClass: Type?, - val body: List, - override val span: Span -) : ModuleEntry(docComment, annotations, modifiers, name, span) { - init { - superClass?.parent = this - for (it in annotations) it.parent = this - } -} - -sealed class ClassEntry( - override val docComment: DocComment?, - override val annotations: List, - override val modifiers: List, - override val name: Ident, - override val span: Span -) : ModuleEntry(docComment, annotations, modifiers, name, span) - -data class ClassProperty( - override val docComment: DocComment?, - override val annotations: List, - override val modifiers: List, - override val name: Ident, - val type: Type, - override val span: Span -) : ClassEntry(docComment, annotations, modifiers, name, span) { - init { - type.parent = this - for (it in annotations) it.parent = this - } -} - -data class ClassPropertyExpr( - override val docComment: DocComment?, - override val annotations: List, - override val modifiers: List, - override val name: Ident, - val type: Type?, - val expr: Expr, - override val span: Span -) : ClassEntry(docComment, annotations, modifiers, name, span) { - init { - expr.parent = this - type?.parent = this - for (it in annotations) it.parent = this - } -} - -data class ClassPropertyBody( - override val docComment: DocComment?, - override val annotations: List, - override val modifiers: List, - override val name: Ident, - val type: Type?, - val bodyList: List, - override val span: Span -) : ClassEntry(docComment, annotations, modifiers, name, span) { - init { - type?.parent = this - for (it in annotations) it.parent = this - } -} - -data class ClassMethod( - override val docComment: DocComment?, - override val annotations: List, - override val modifiers: List, - override val name: Ident, - val typePars: List, - val args: List, - val returnType: Type?, - val expr: Expr?, - override val span: Span -) : ClassEntry(docComment, annotations, modifiers, name, span) { - init { - expr?.parent = this - returnType?.parent = this - for (it in annotations) it.parent = this - } -} diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/ObjectMember.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/ObjectMember.kt deleted file mode 100644 index 347e90803..000000000 --- a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/ObjectMember.kt +++ /dev/null @@ -1,145 +0,0 @@ -/** - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. - * - * 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 - * - * https://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 org.pkl.lsp.cst - -sealed class ObjectMember(override val span: Span) : PklNode(span) { - data class Element(val expr: Expr, override val span: Span) : ObjectMember(span) { - init { - expr.parent = this - } - } - - data class Property( - val modifiers: List, - val ident: Ident, - val type: Type?, - val expr: Expr, - override val span: Span - ) : ObjectMember(span) { - init { - type?.parent = this - expr.parent = this - } - } - - data class PropertyBody( - val modifiers: List, - val ident: Ident, - val bodyList: List, - override val span: Span - ) : ObjectMember(span) { - init { - for (it in bodyList) it.parent = this - } - } - - data class Method( - val modifiers: List, - val ident: Ident, - val typePars: List, - val args: List, - val returnType: Type?, - val expr: Expr, - override val span: Span - ) : ObjectMember(span) { - init { - for (it in typePars) it.parent = this - for (it in args) it.parent = this - expr.parent = this - returnType?.parent = this - } - } - - data class MemberPredicate(val pred: Expr, val expr: Expr, override val span: Span) : - ObjectMember(span) { - init { - pred.parent = this - expr.parent = this - } - } - - data class MemberPredicateBody( - val key: Expr, - val bodyList: List, - override val span: Span - ) : ObjectMember(span) { - init { - key.parent = this - for (it in bodyList) it.parent = this - } - } - - data class Entry(val key: Expr, val value: Expr, override val span: Span) : ObjectMember(span) { - init { - key.parent = this - value.parent = this - } - } - - data class EntryBody(val key: Expr, val bodyList: List, override val span: Span) : - ObjectMember(span) { - init { - key.parent = this - for (it in bodyList) it.parent = this - } - } - - data class Spread(val expr: Expr, val isNullable: Boolean, override val span: Span) : - ObjectMember(span) { - init { - expr.parent = this - } - } - - data class WhenGenerator( - val cond: Expr, - val body: ObjectBody, - val elseBody: ObjectBody?, - override val span: Span - ) : ObjectMember(span) { - init { - cond.parent = this - body.parent = this - elseBody?.parent = this - } - } - - data class ForGenerator( - val p1: Parameter, - val p2: Parameter?, - val expr: Expr, - val body: ObjectBody, - override val span: Span - ) : ObjectMember(span) { - init { - p1.parent = this - p2?.parent = this - expr.parent = this - body.parent = this - } - } -} - -data class ObjectBody( - val pars: List, - val members: List, - override val span: Span -) : PklNode(span) { - init { - for (it in pars) it.parent = this - for (it in members) it.parent = this - } -} diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Type.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Type.kt deleted file mode 100644 index f8e293b06..000000000 --- a/pkl-lsp/src/main/kotlin/org/pkl/lsp/cst/Type.kt +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. - * - * 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 - * - * https://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 org.pkl.lsp.cst - -sealed class Type(override val span: Span) : PklNode(span) { - data class Unknown(override val span: Span) : Type(span) - - data class Nothing(override val span: Span) : Type(span) - - data class Module(override val span: Span) : Type(span) - - data class StringConstant(val string: String, override val span: Span) : Type(span) - - data class Declared(val idents: List, val args: List, override val span: Span) : - Type(span) { - init { - for (it in args) it.parent = this - } - } - - data class Parenthesised(val type: Type, override val span: Span) : Type(span) { - init { - type.parent = this - } - } - - data class Nullable(val type: Type, override val span: Span) : Type(span) { - init { - type.parent = this - } - } - - data class Constrained(val type: Type, val exprs: List, override val span: Span) : - Type(span) { - init { - type.parent = this - for (it in exprs) it.parent = this - } - } - - data class DefaultUnion(val type: Type, override val span: Span) : Type(span) { - init { - type.parent = this - } - } - - data class Union(val left: Type, val right: Type, override val span: Span) : Type(span) { - init { - left.parent = this - right.parent = this - } - } - - data class Function(val args: List, val ret: Type, override val span: Span) : Type(span) { - init { - ret.parent = this - for (it in args) it.parent = this - } - } -} - -enum class Variance { - IN, - OUT -} - -data class TypeParameter(val variance: Variance?, val ident: Ident, override val span: Span) : - PklNode(span) diff --git a/pkl-lsp/src/main/kotlin/org/pkl/lsp/features/HoverFeature.kt b/pkl-lsp/src/main/kotlin/org/pkl/lsp/features/HoverFeature.kt index 231bf6d33..fd78190dd 100644 --- a/pkl-lsp/src/main/kotlin/org/pkl/lsp/features/HoverFeature.kt +++ b/pkl-lsp/src/main/kotlin/org/pkl/lsp/features/HoverFeature.kt @@ -15,30 +15,26 @@ */ package org.pkl.lsp.features -import java.net.URI import java.util.concurrent.CompletableFuture import org.eclipse.lsp4j.Hover import org.eclipse.lsp4j.HoverParams -import org.eclipse.lsp4j.MarkupContent import org.pkl.lsp.PklLSPServer -import org.pkl.lsp.cst.ClassEntry -import org.pkl.lsp.cst.Clazz -import org.pkl.lsp.cst.PklModule class HoverFeature(private val server: PklLSPServer) { fun onHover(params: HoverParams): CompletableFuture { - fun run(mod: PklModule?): Hover? { - if (mod == null) return null - val uri = URI(params.textDocument.uri) - val line = params.position.line + 1 - val col = params.position.character + 1 - server.logger().log("received hover request at ($line - $col)") - val hoverText = findContext(mod, line, col)?.resolve() ?: return null - server.logger().log("hover text: $hoverText") - return Hover(MarkupContent("markdown", hoverText)) - } - return server.builder().runningBuild().thenApply(::run) + return server.builder().runningBuild().thenApply { Hover() } + // fun run(mod: Module?): Hover? { + // if (mod == null) return null + // val uri = URI(params.textDocument.uri) + // val line = params.position.line + 1 + // val col = params.position.character + 1 + // server.logger().log("received hover request at ($line - $col)") + //// val hoverText = findContext(mod, line, col)?.resolve() ?: return null + //// server.logger().log("hover text: $hoverText") + //// return Hover(MarkupContent("markdown", hoverText)) + // } + // return server.builder().runningBuild().thenApply(::run) } sealed class HoverCtx { @@ -55,25 +51,25 @@ class HoverFeature(private val server: PklLSPServer) { is Prop -> "**$name**" } } - - private fun findContext(mod: PklModule, line: Int, col: Int): HoverCtx? { - // search module declaration - val decl = mod.decl - if (decl != null && decl.span.matches(line, col)) { - if (decl.nameSpan != null && decl.nameSpan.matches(line, col)) { - return HoverCtx.Module(decl.name!!.nameString) - } - } - - for (entry in mod.entries) { - if (entry.name.span.matches(line, col)) { - return when (entry) { - is Clazz -> HoverCtx.Clazz(entry.name.value, null) - is ClassEntry -> HoverCtx.Prop(entry.name.value, null) - else -> null - } - } - } - return null - } + // + // private fun findContext(mod: PklModule, line: Int, col: Int): HoverCtx? { + // // search module declaration + // val decl = mod.decl + // if (decl != null && decl.span.matches(line, col)) { + // if (decl.nameSpan != null && decl.nameSpan.matches(line, col)) { + // return HoverCtx.Module(decl.name!!.nameString) + // } + // } + // + // for (entry in mod.entries) { + // if (entry.name.span.matches(line, col)) { + // return when (entry) { + // is Clazz -> HoverCtx.Clazz(entry.name.value, null) + // is ClassEntry -> HoverCtx.Prop(entry.name.value, null) + // else -> null + // } + // } + // } + // return null + // } } diff --git a/pkl-lsp/src/main/resources/org/pkl/lsp/errors.properties b/pkl-lsp/src/main/resources/org/pkl/lsp/errorMessages.properties similarity index 83% rename from pkl-lsp/src/main/resources/org/pkl/lsp/errors.properties rename to pkl-lsp/src/main/resources/org/pkl/lsp/errorMessages.properties index 4fc1fc890..d32cf2585 100644 --- a/pkl-lsp/src/main/resources/org/pkl/lsp/errors.properties +++ b/pkl-lsp/src/main/resources/org/pkl/lsp/errorMessages.properties @@ -1,3 +1,21 @@ +modifierIsNotApplicable=\ +Modifier ''{0}'' is not applicable to {1} + +modifierAbstractConflictsWithOpen=\ +Modifier 'abstract' conflicts with 'open' + +modifierOpenConflictsWithAbstract=\ +Modifier 'open' conflicts with 'abstract' + +missingModifierLocal=\ +Missing modifier 'local' + +invalidModifier=\ +Invalid modifier + +invalidModifierForObjectPropertyOrMethod=\ +Invalid modifier for object property or method + invalidCharacterEscapeSequence=\ Invalid character escape sequence @@ -31,12 +49,6 @@ Cannot combine glob imports with triple-dot module URIs tooManyFunctionParameters=\ Function literals can have at most five parameters -invalidModifier=\ -Invalid modifier - -invalidModifierForObjectPropertyOrMethod=\ -Invalid modifier for object property or method - unexpectedCurlyProbablyAmendsExpression=\ Unexpected token: `'{'` diff --git a/pkl-lsp/src/test/kotlin/org/pkl/lsp/ast/AstTest.kt b/pkl-lsp/src/test/kotlin/org/pkl/lsp/ast/AstTest.kt new file mode 100644 index 000000000..6abc2e7c8 --- /dev/null +++ b/pkl-lsp/src/test/kotlin/org/pkl/lsp/ast/AstTest.kt @@ -0,0 +1,88 @@ +/** + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * 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 + * + * https://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 org.pkl.lsp.ast + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.pkl.core.parser.Parser + +class AstTest { + @Test + fun testAst() { + val mod = + """ + /// This is a module. + /// + /// This is another line of doc comment. + abstract module Foo + + amends "bar.pkl" + + import "foo.pkl" + + hidden foo: String + + function bar() = "bar" + + open class Person { + /// Some comment + name: String + + /// Some comment + local function toUpperCase(foo: String): String + } + """ + .trimIndent() + val parser = Parser() + val module = ModuleImpl(parser.parseModule(mod)) + assertThat(module.declaration).isNotNull + assertThat(module.declaration!!.docComment!!.text) + .isEqualTo( + """ + /// This is a module. + /// + /// This is another line of doc comment. + + """ + .trimIndent() + ) + assertThat(module.declaration?.modifiers?.map { it.type }).isEqualTo(listOf(TokenType.ABSTRACT)) + assertThat(module.declaration?.effectiveExtendsOrAmendsCluse) + assertThat(module.members).hasSize(3) + assertThat(module.members[0]).isInstanceOf(ClassProperty::class.java) + val property = module.members[0] as ClassProperty + assertThat(property.identifier?.text).isEqualTo("foo") + assertThat(property.typeAnnotation?.type).isInstanceOf(DeclaredType::class.java) + assertThat(module.members[1]).isInstanceOf(ClassMethod::class.java) + val method = module.members[1] as ClassMethod + assertThat(method.methodHeader.identifier?.text).isEqualTo("bar") + assertThat(module.members[2]).isInstanceOf(Class::class.java) + val clazz = module.members[2] as Class + val classMembers = clazz.classBody?.members + assertThat(classMembers).hasSize(2) + val classProperty = classMembers?.get(0) + assertThat(classProperty).isInstanceOf(ClassProperty::class.java) + classProperty as ClassProperty + assertThat(classProperty.identifier?.text).isEqualTo("name") + assertThat(classProperty.docComment?.text).isEqualTo(" /// Some comment\n") + val classMethod = classMembers[1] + assertThat(classMethod).isInstanceOf(ClassMethod::class.java) + classMethod as ClassMethod + assertThat(classMethod.methodHeader.identifier?.text).isEqualTo("toUpperCase") + assertThat(classMethod.methodHeader.modifiers?.map { it.type }) + .isEqualTo(listOf(TokenType.LOCAL)) + } +}