diff --git a/.gitignore b/.gitignore index 8289f45e..9957dac9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ .gradle/ build/ generated/ +testgenerated/ # IntelliJ .idea/ diff --git a/DEVELOPMENT.adoc b/DEVELOPMENT.adoc index 61543f7e..685eccc9 100644 --- a/DEVELOPMENT.adoc +++ b/DEVELOPMENT.adoc @@ -83,8 +83,8 @@ based on version information from https://search.maven.org, https://plugins.grad * Truffle code generation is performed by Truffle's annotation processor, which runs as part of task `:pkl-core:compileJava` ** Output dir is `generated/truffle/` -* ANTLR code generation is performed by task `:pkl-core:generateGrammarSource` -** Output dir is `generated/antlr/` +* ANTLR code generation is performed by task `:pkl-core:generateTestGrammarSource` +** Output dir is `testgenerated/antlr/` == Remote JVM Debugging @@ -96,12 +96,6 @@ Example: `./gradlew test -Djvmdebug=true` == Resources For automated build setup examples see our https://github.com/apple/pkl/blob/main/.circleci/[CircleCI] jobs like our https://github.com/apple/pkl/blob/main/.circleci/jobs/BuildNativeJob.pkl[BuildNativeJob.pkl], where we build Pkl automatically. -=== ANTLR - -* https://github.com/antlr/antlr4/blob/master/doc/index.md[Documentation] -* https://groups.google.com/forum/#!forum/antlr-discussion[Forums] -* https://github.com/mobileink/lab.clj.antlr/tree/main/doc[Some third-party docs] - === Truffle * http://ssw.jku.at/Research/Projects/JVM/Truffle.html[Homepage] diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt index c79c5a8f..784f63f4 100644 --- a/THIRD-PARTY-NOTICES.txt +++ b/THIRD-PARTY-NOTICES.txt @@ -26,18 +26,7 @@ Copyright © 2017 Square, Inc. Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License.You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -4) ANTLR 4 Runtime (Optimized) (http://tunnelvisionlabs.com) -POM License: The BSD License - http://www.antlr.org/license.html - -Copyright (c) 2012 Terence Parr and Sam Harwell -All rights reserved. -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -5) GeantyRef (https://github.com/leangen/geantyref) +4) GeantyRef (https://github.com/leangen/geantyref) Manifest license URL: https://www.apache.org/licenses/LICENSE-2.0 POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 @@ -47,7 +36,7 @@ Copyright © 2017 Kaqqao Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License.You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -6) commonmark-java core +5) commonmark-java core POM License: The 2-Clause BSD License - https://opensource.org/licenses/BSD-2-Clause Embedded license: @@ -78,7 +67,7 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -7) commonmark-java extension for tables +6) commonmark-java extension for tables POM License: The 2-Clause BSD License - https://opensource.org/licenses/BSD-2-Clause Embedded license: @@ -109,7 +98,7 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -8) jansi (http://fusesource.com/) +7) jansi (http://fusesource.com/) Manifest license URL: https://www.apache.org/licenses/LICENSE-2.0 POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 @@ -119,13 +108,13 @@ Copyright © Fusesource 2023 Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License.You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -9) Graal Sdk (https://github.com/oracle/graal) +8) Graal Sdk (https://github.com/oracle/graal) POM License: Universal Permissive License, Version 1.0 - http://opensource.org/licenses/UPL -10) Truffle API (http://openjdk.java.net/projects/graal) +9) Truffle API (http://openjdk.java.net/projects/graal) POM License: Universal Permissive License, Version 1.0 - http://opensource.org/licenses/UPL -11) IntelliJ IDEA Annotations (http://www.jetbrains.org) +10) IntelliJ IDEA Annotations (http://www.jetbrains.org) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License @@ -133,7 +122,7 @@ Copyright © Jetbrains 2023 Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License.You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -12) kotlin-reflect (https://kotlinlang.org/) +11) kotlin-reflect (https://kotlinlang.org/) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License @@ -141,7 +130,7 @@ Copyright © Wuseal 2018 Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License.You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -13) kotlin-stdlib (https://kotlinlang.org/) +12) kotlin-stdlib (https://kotlinlang.org/) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License @@ -149,7 +138,7 @@ Copyright © 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contribu Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License.You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -14) kotlin-stdlib-common (https://kotlinlang.org/) +13) kotlin-stdlib-common (https://kotlinlang.org/) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License @@ -157,7 +146,7 @@ Copyright © 2023 Kotlin Team Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License.You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -15) kotlin-stdlib-jdk7 (https://kotlinlang.org/) +14) kotlin-stdlib-jdk7 (https://kotlinlang.org/) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License @@ -165,7 +154,7 @@ Copyright © 2023 Kotlin Team Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License.You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -16) kotlin-stdlib-jdk8 (https://kotlinlang.org/) +15) kotlin-stdlib-jdk8 (https://kotlinlang.org/) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License @@ -173,7 +162,7 @@ Copyright © 2023 Kotlin Team Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License.You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -17) kotlinx.html (https://github.com/Kotlin/kotlinx.html) +16) kotlinx.html (https://github.com/Kotlin/kotlinx.html) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License @@ -181,7 +170,7 @@ Copyright © 2017 Yole Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License.You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -18) kotlinx-serialization-core (https://github.com/Kotlin/kotlinx.serialization) +17) kotlinx-serialization-core (https://github.com/Kotlin/kotlinx.serialization) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License @@ -189,7 +178,7 @@ The Apache License Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License.You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -19) kotlinx-serialization-json (https://github.com/Kotlin/kotlinx.serialization) +18) kotlinx-serialization-json (https://github.com/Kotlin/kotlinx.serialization) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License @@ -197,7 +186,7 @@ The Apache License Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License.You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -20) JLine Reader +19) JLine Reader Manifest license URL: https://opensource.org/licenses/BSD-3-Clause POM License: The 3-Clause BSD License - https://opensource.org/licenses/BSD-3-Clause @@ -216,7 +205,7 @@ software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -21) JLine Terminal +20) JLine Terminal Manifest license URL: https://opensource.org/licenses/BSD-3-Clause POM License: The 3-Clause BSD License - https://opensource.org/licenses/BSD-3-Clause @@ -235,7 +224,7 @@ software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -22) JLine JANSI Terminal +21) JLine JANSI Terminal Manifest license URL: https://opensource.org/licenses/BSD-3-Clause POM License: The 3-Clause BSD License - https://opensource.org/licenses/BSD-3-Clause @@ -254,7 +243,7 @@ software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -23) msgpack-core (https://msgpack.org/) +22) msgpack-core (https://msgpack.org/) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License @@ -262,7 +251,7 @@ Copyright © 2016 Sadayuki Furuhashi, Muga Nishizawa, Taro L. Saito, Mitsunori K Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License.You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -24) Paguro (https://github.com/GlenKPeterson/Paguro) +23) Paguro (https://github.com/GlenKPeterson/Paguro) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License @@ -364,7 +353,7 @@ Everyone is permitted to copy and distribute copies of this Agreement, but in or This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. -25) SnakeYAML Engine (http://www.snakeyaml.org) +24) SnakeYAML Engine (http://www.snakeyaml.org) Manifest license URL: https://www.apache.org/licenses/LICENSE-2.0 POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 diff --git a/bench/bench.gradle.kts b/bench/bench.gradle.kts index 50f0ae0d..b5fbfa29 100644 --- a/bench/bench.gradle.kts +++ b/bench/bench.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -25,8 +25,6 @@ val graal: Configuration by configurations.creating @Suppress("UnstableApiUsage") dependencies { jmh(projects.pklCore) - // necessary because antlr4-runtime is declared as implementation dependency in pkl-core.gradle - jmh(libs.antlrRuntime) truffle(libs.truffleApi) graal(libs.graalCompiler) } diff --git a/bench/gradle.lockfile b/bench/gradle.lockfile index dcfd6f7a..f5bf7dc5 100644 --- a/bench/gradle.lockfile +++ b/bench/gradle.lockfile @@ -1,7 +1,6 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -com.tunnelvisionlabs:antlr4-runtime:4.9.0=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath net.bytebuddy:byte-buddy:1.15.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.sf.jopt-simple:jopt-simple:5.0.4=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath org.apache.commons:commons-math3:3.6.1=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath diff --git a/bench/src/jmh/java/org/pkl/core/parser/ParserBenchmark.java b/bench/src/jmh/java/org/pkl/core/parser/ParserBenchmark.java index f1a6a3af..d7ee2212 100644 --- a/bench/src/jmh/java/org/pkl/core/parser/ParserBenchmark.java +++ b/bench/src/jmh/java/org/pkl/core/parser/ParserBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. diff --git a/buildSrc/src/main/kotlin/pklFatJar.gradle.kts b/buildSrc/src/main/kotlin/pklFatJar.gradle.kts index 15345809..923c2700 100644 --- a/buildSrc/src/main/kotlin/pklFatJar.gradle.kts +++ b/buildSrc/src/main/kotlin/pklFatJar.gradle.kts @@ -39,7 +39,6 @@ val firstPartySourcesJarsConfiguration: Configuration = val relocations = mapOf( // pkl-core dependencies - "org.antlr.v4." to "org.pkl.thirdparty.antlr.v4.", "org.organicdesign.fp." to "org.pkl.thirdparty.paguro.", "org.snakeyaml.engine." to "org.pkl.thirdparty.snakeyaml.engine.", "org.msgpack." to "org.pkl.thirdparty.msgpack.", diff --git a/docs/modules/language-reference/pages/index.adoc b/docs/modules/language-reference/pages/index.adoc index 0e13c7fa..453434dd 100644 --- a/docs/modules/language-reference/pages/index.adoc +++ b/docs/modules/language-reference/pages/index.adoc @@ -3196,7 +3196,6 @@ This section discusses language features that are generally more relevant to tem <> + <> + <> + -<> + <> + <> + <> + @@ -5274,11 +5273,6 @@ For example, the prototype chain of value `42` contains, now listed from top to A prototype chain never contains a non-object value, such as `42`. -[[grammar-definition]] -=== Grammar Definition - -Pkl's link:{uri-antlr4}[ANTLR 4] grammar is defined in link:{uri-github-PklLexer}[PklLexer.g4] and link:{uri-github-PklParser}[PklParser.g4]. - [[reserved-keywords]] === Reserved keywords diff --git a/docs/src/test/kotlin/DocSnippetTests.kt b/docs/src/test/kotlin/DocSnippetTests.kt index 14b9a2fb..98e70706 100644 --- a/docs/src/test/kotlin/DocSnippetTests.kt +++ b/docs/src/test/kotlin/DocSnippetTests.kt @@ -16,15 +16,14 @@ import org.pkl.core.Loggers import org.pkl.core.SecurityManagers import org.pkl.core.StackFrameTransformers import org.pkl.core.module.ModuleKeyFactories -import org.pkl.core.parser.LexParseException -import org.pkl.core.parser.Parser -import org.pkl.core.parser.antlr.PklParser import org.pkl.core.repl.ReplRequest import org.pkl.core.repl.ReplResponse import org.pkl.core.repl.ReplServer import org.pkl.core.util.IoUtils -import org.antlr.v4.runtime.ParserRuleContext import org.pkl.core.http.HttpClient +import org.pkl.core.parser.Parser +import org.pkl.core.parser.ParserError +import org.pkl.core.parser.ast.ClassProperty import org.pkl.core.resource.ResourceReaders import java.nio.file.Files import kotlin.io.path.isDirectory @@ -303,7 +302,7 @@ class DocSnippetTestsEngine : HierarchicalTestEngine Parser().parseModule(code) "pkl-expr" -> Parser().parseExpressionInput(code) @@ -318,7 +317,7 @@ class DocSnippetTestsEngine : HierarchicalTestEngine() + val properties = parsed.children()?.filterIsInstance() ?: emptyList() val responses = mutableListOf() @@ -344,7 +343,7 @@ class DocSnippetTestsEngine : HierarchicalTestEngineA cleaner solution would be to have a separate {@link org.pkl.core.ast.builder.AstBuilder} for - * stdlib modules that produces a fully initialized module object without executing any Truffle - * nodes. + *

A cleaner solution would be to have a separate {@link AstBuilder} for stdlib modules that + * produces a fully initialized module object without executing any Truffle nodes. * *

This class is automatically discovered by native-image; no registration is required. */ diff --git a/pkl-codegen-java/gradle.lockfile b/pkl-codegen-java/gradle.lockfile index 43bb8834..6bc72096 100644 --- a/pkl-codegen-java/gradle.lockfile +++ b/pkl-codegen-java/gradle.lockfile @@ -4,7 +4,6 @@ com.github.ajalt.clikt:clikt-jvm:3.5.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt:3.5.4=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.palantir.javapoet:javapoet:0.6.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -com.tunnelvisionlabs:antlr4-runtime:4.9.0=runtimeClasspath,testRuntimeClasspath io.leangen.geantyref:geantyref:1.3.16=testRuntimeClasspath net.bytebuddy:byte-buddy:1.15.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata diff --git a/pkl-codegen-kotlin/gradle.lockfile b/pkl-codegen-kotlin/gradle.lockfile index a424ae81..24187ccb 100644 --- a/pkl-codegen-kotlin/gradle.lockfile +++ b/pkl-codegen-kotlin/gradle.lockfile @@ -4,7 +4,6 @@ com.github.ajalt.clikt:clikt-jvm:3.5.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt:3.5.4=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.squareup:kotlinpoet:1.6.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -com.tunnelvisionlabs:antlr4-runtime:4.9.0=runtimeClasspath,testRuntimeClasspath io.leangen.geantyref:geantyref:1.3.16=testRuntimeClasspath net.bytebuddy:byte-buddy:1.15.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata diff --git a/pkl-commons-cli/gradle.lockfile b/pkl-commons-cli/gradle.lockfile index 8f766027..44149732 100644 --- a/pkl-commons-cli/gradle.lockfile +++ b/pkl-commons-cli/gradle.lockfile @@ -3,7 +3,6 @@ # This file is expected to be part of source control. com.github.ajalt.clikt:clikt-jvm:3.5.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt:3.5.4=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -com.tunnelvisionlabs:antlr4-runtime:4.9.0=runtimeClasspath,testRuntimeClasspath net.bytebuddy:byte-buddy:1.15.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata org.assertj:assertj-core:3.27.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath diff --git a/pkl-config-java/gradle.lockfile b/pkl-config-java/gradle.lockfile index 590e18cb..f0a2185e 100644 --- a/pkl-config-java/gradle.lockfile +++ b/pkl-config-java/gradle.lockfile @@ -4,7 +4,6 @@ com.github.ajalt.clikt:clikt-jvm:3.5.4=pklCodegenJava com.github.ajalt.clikt:clikt:3.5.4=pklCodegenJava com.palantir.javapoet:javapoet:0.6.0=pklCodegenJava -com.tunnelvisionlabs:antlr4-runtime:4.9.0=pklCodegenJava,runtimeClasspath,testRuntimeClasspath io.leangen.geantyref:geantyref:1.3.16=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath javax.inject:javax.inject:1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.bytebuddy:byte-buddy:1.15.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath diff --git a/pkl-config-kotlin/gradle.lockfile b/pkl-config-kotlin/gradle.lockfile index beba88d9..ccb22274 100644 --- a/pkl-config-kotlin/gradle.lockfile +++ b/pkl-config-kotlin/gradle.lockfile @@ -4,7 +4,6 @@ com.github.ajalt.clikt:clikt-jvm:3.5.4=pklCodegenKotlin com.github.ajalt.clikt:clikt:3.5.4=pklCodegenKotlin com.squareup:kotlinpoet:1.6.0=pklCodegenKotlin -com.tunnelvisionlabs:antlr4-runtime:4.9.0=pklCodegenKotlin,pklConfigJava,runtimeClasspath,testRuntimeClasspath io.leangen.geantyref:geantyref:1.3.16=pklConfigJava,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.bytebuddy:byte-buddy:1.15.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata diff --git a/pkl-core/gradle.lockfile b/pkl-core/gradle.lockfile index 7849b393..e9808b1a 100644 --- a/pkl-core/gradle.lockfile +++ b/pkl-core/gradle.lockfile @@ -4,7 +4,7 @@ com.google.code.findbugs:jsr305:3.0.2=compileClasspath,compileOnlyDependenciesMetadata com.palantir.javapoet:javapoet:0.6.0=generatorCompileClasspath,generatorImplementationDependenciesMetadata,generatorRuntimeClasspath com.tunnelvisionlabs:antlr4-annotations:4.9.0=antlr -com.tunnelvisionlabs:antlr4-runtime:4.9.0=antlr,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.tunnelvisionlabs:antlr4-runtime:4.9.0=antlr,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.tunnelvisionlabs:antlr4:4.9.0=antlr net.bytebuddy:byte-buddy:1.15.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.abego.treelayout:org.abego.treelayout.core:1.0.1=antlr diff --git a/pkl-core/pkl-core.gradle.kts b/pkl-core/pkl-core.gradle.kts index 86969020..a65b786b 100644 --- a/pkl-core/pkl-core.gradle.kts +++ b/pkl-core/pkl-core.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -27,15 +27,16 @@ plugins { val generatorSourceSet = sourceSets.register("generator") -sourceSets { main { java { srcDir(file("generated/antlr")) } } } +sourceSets { test { java { srcDir(file("testgenerated/antlr")) } } } idea { module { - // mark src/main/antlr as source dir + // mark src/test/antlr as source dir // mark generated/antlr as generated source dir // mark generated/truffle as generated source dir - sourceDirs = sourceDirs + files("src/main/antlr", "generated/antlr", "generated/truffle") - generatedSourceDirs = generatedSourceDirs + files("generated/antlr", "generated/truffle") + sourceDirs = sourceDirs + files("generated/truffle") + generatedSourceDirs = generatedSourceDirs + files("testgenerated/antlr", "generated/truffle") + testSources.from(files("src/test/antlr", "testgenerated/antlr")) } } @@ -56,7 +57,6 @@ dependencies { // pkl-core implements pkl-executor's ExecutorSpi, but the SPI doesn't ship with pkl-core compileOnly(projects.pklExecutor) - implementation(libs.antlrRuntime) implementation(libs.msgpack) implementation(libs.truffleApi) implementation(libs.graalSdk) @@ -65,6 +65,7 @@ dependencies { implementation(libs.snakeYaml) + testImplementation(libs.antlrRuntime) testImplementation(projects.pklCommonsTest) add("generatorImplementation", libs.javaPoet) @@ -92,35 +93,33 @@ publishing { } } -tasks.generateGrammarSource { +tasks.generateTestGrammarSource { maxHeapSize = "64m" // generate only visitor arguments = arguments + listOf("-visitor", "-no-listener") // Due to https://github.com/antlr/antlr4/issues/2260, - // we can't put .g4 files into src/main/antlr/org/pkl/core/parser/antlr. - // Instead, we put .g4 files into src/main/antlr, adapt output dir below, + // we can't put .g4 files into src/test/antlr/org/pkl/core/parser/antlr. + // Instead, we put .g4 files into src/test/antlr, adapt output dir below, // and use @header directives in .g4 files (instead of setting `-package` argument here) // and task makeIntelliJAntlrPluginHappy to fix up the IDE story. - outputDirectory = file("generated/antlr/org/pkl/core/parser/antlr") + outputDirectory = file("testgenerated/antlr/org/pkl/core/parser/antlr") } -tasks.compileJava { dependsOn(tasks.generateGrammarSource) } - -tasks.sourcesJar { dependsOn(tasks.generateGrammarSource) } - -tasks.generateTestGrammarSource { enabled = false } +tasks.generateGrammarSource { enabled = false } tasks.named("generateGeneratorGrammarSource") { enabled = false } +tasks.compileTestKotlin { dependsOn(tasks.generateTestGrammarSource) } + // Satisfy expectations of IntelliJ ANTLR plugin, // which can't otherwise cope with our ANTLR setup. val makeIntelliJAntlrPluginHappy by tasks.registering(Copy::class) { dependsOn(tasks.generateGrammarSource) - into("src/main/antlr") - from("generated/antlr/org/pkl/core/parser/antlr") { include("PklLexer.tokens") } + into("test/antlr") + from("testgenerated/antlr/org/pkl/core/parser/antlr") { include("PklLexer.tokens") } } tasks.processResources { @@ -141,7 +140,7 @@ tasks.processResources { mapOf( "version" to buildInfo.pklVersion, "commitId" to buildInfo.commitId, - "stdlibModules" to stdlibModules.joinToString(",") + "stdlibModules" to stdlibModules.joinToString(","), ) ) } @@ -249,7 +248,7 @@ tasks.clean { spotless { antlr4 { licenseHeaderFile(rootProject.file("buildSrc/src/main/resources/license-header.star-block.txt")) - target(files("src/main/antlr/PklParser.g4", "src/main/antlr/PklLexer.g4")) + target(files("src/test/antlr/PklParser.g4", "src/test/antlr/PklLexer.g4")) } } diff --git a/pkl-core/src/main/java/org/pkl/core/PcfRenderer.java b/pkl-core/src/main/java/org/pkl/core/PcfRenderer.java index 9a5c32ac..edbbbd7e 100644 --- a/pkl-core/src/main/java/org/pkl/core/PcfRenderer.java +++ b/pkl-core/src/main/java/org/pkl/core/PcfRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. diff --git a/pkl-core/src/main/java/org/pkl/core/ast/builder/AbstractAstBuilder.java b/pkl-core/src/main/java/org/pkl/core/ast/builder/AbstractAstBuilder.java index 5e25bc8a..79e5963c 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/builder/AbstractAstBuilder.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/builder/AbstractAstBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -18,122 +18,131 @@ package org.pkl.core.ast.builder; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.source.SourceSection; import java.util.List; -import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.tree.TerminalNode; -import org.pkl.core.parser.antlr.PklLexer; -import org.pkl.core.parser.antlr.PklParser.ModifierContext; -import org.pkl.core.parser.antlr.PklParserBaseVisitor; +import org.pkl.core.PklBugException; +import org.pkl.core.parser.BaseParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.parser.ast.DocComment; +import org.pkl.core.parser.ast.Modifier; +import org.pkl.core.parser.ast.Modifier.ModifierValue; +import org.pkl.core.parser.ast.Node; +import org.pkl.core.parser.ast.StringConstant; +import org.pkl.core.parser.ast.StringConstantPart; +import org.pkl.core.parser.ast.StringConstantPart.ConstantPart; +import org.pkl.core.parser.ast.StringConstantPart.StringEscape; +import org.pkl.core.parser.ast.StringConstantPart.StringUnicodeEscape; import org.pkl.core.runtime.VmExceptionBuilder; import org.pkl.core.util.Nullable; -public abstract class AbstractAstBuilder extends PklParserBaseVisitor { +public abstract class AbstractAstBuilder extends BaseParserVisitor { protected final Source source; + protected abstract VmExceptionBuilder exceptionBuilder(); + protected AbstractAstBuilder(Source source) { this.source = source; } - protected abstract VmExceptionBuilder exceptionBuilder(); - - protected String doVisitSingleLineConstantStringPart(List ts) { - if (ts.isEmpty()) return ""; + protected String doVisitStringConstant(StringConstant expr) { + return doVisitStringConstant(expr.getStrParts().getParts()); + } + protected String doVisitStringConstant(List strs) { var builder = new StringBuilder(); - for (var token : ts) { - switch (token.getType()) { - case PklLexer.SLCharacters -> builder.append(token.getText()); - case PklLexer.SLCharacterEscape -> builder.append(parseCharacterEscapeSequence(token)); - case PklLexer.SLUnicodeEscape -> builder.appendCodePoint(parseUnicodeEscapeSequence(token)); - default -> throw exceptionBuilder().unreachableCode().build(); - } + for (var part : strs) { + builder.append(doVisitStringConstantPart(part)); } - return builder.toString(); } - protected int parseUnicodeEscapeSequence(Token token) { - var text = token.getText(); - var lastIndex = text.length() - 1; - - if (text.charAt(lastIndex) != '}') { - throw exceptionBuilder() - .evalError("unterminatedUnicodeEscapeSequence", token.getText()) - .withSourceSection(createSourceSection(token)) - .build(); + protected String doVisitStringConstantPart(StringConstantPart part) { + if (part instanceof ConstantPart cp) { + return cp.getStr(); } + if (part instanceof StringUnicodeEscape ue) { + var codePoint = parseUnicodeEscapeSequence(ue); + return Character.toString(codePoint); + } + if (part instanceof StringEscape se) { + return switch (se.getType()) { + case NEWLINE -> "\n"; + case QUOTE -> "\""; + case BACKSLASH -> "\\"; + case TAB -> "\t"; + case RETURN -> "\r"; + }; + } + throw PklBugException.unreachableCode(); + } + protected int parseUnicodeEscapeSequence(StringUnicodeEscape escape) { + var text = escape.getEscape(); + var lastIndex = text.length() - 1; var startIndex = text.indexOf('{', 2); assert startIndex != -1; // guaranteed by lexer - try { return Integer.parseInt(text.substring(startIndex + 1, lastIndex), 16); } catch (NumberFormatException e) { throw exceptionBuilder() - .evalError("invalidUnicodeEscapeSequence", token.getText(), text.substring(0, startIndex)) - .withSourceSection(createSourceSection(token)) + .evalError("invalidUnicodeEscapeSequence", text, text.substring(0, startIndex)) + .withSourceSection(createSourceSection(escape)) .build(); } } - protected String parseCharacterEscapeSequence(Token token) { - var text = token.getText(); - var lastChar = text.charAt(text.length() - 1); - - return switch (lastChar) { - case 'n' -> "\n"; - case 'r' -> "\r"; - case 't' -> "\t"; - case '"' -> "\""; - case '\\' -> "\\"; - default -> - throw exceptionBuilder() - .evalError( - "invalidCharacterEscapeSequence", text, text.substring(0, text.length() - 1)) - .withSourceSection(createSourceSection(token)) - .build(); - }; + protected final @Nullable SourceSection createSourceSection(@Nullable Node node) { + return node == null + ? null + : source.createSection(node.span().charIndex(), node.span().length()); } - protected final SourceSection createSourceSection(ParserRuleContext ctx) { - return createSourceSection(ctx.getStart(), ctx.getStop()); + protected SourceSection @Nullable [] createDocSourceSection(@Nullable DocComment node) { + return createDocSourceSection(source, node); } - protected final SourceSection createSourceSection(TerminalNode node) { - return createSourceSection(node.getSymbol()); - } - - protected final @Nullable SourceSection createSourceSection(@Nullable Token token) { - return token != null ? createSourceSection(token, token) : null; - } - - protected final SourceSection createSourceSection(Token start, Token stop) { - return source.createSection( - start.getStartIndex(), stop.getStopIndex() - start.getStartIndex() + 1); + protected SourceSection createSourceSection(Span span) { + return source.createSection(span.charIndex(), span.length()); } protected final SourceSection createSourceSection( - List modifierCtxs, int symbol) { + List modifiers, ModifierValue symbol) { var modifierCtx = - modifierCtxs.stream().filter(ctx -> ctx.t.getType() == symbol).findFirst().orElseThrow(); + modifiers.stream().filter(mod -> mod.getValue() == symbol).findFirst().orElseThrow(); return createSourceSection(modifierCtx); } - protected static SourceSection createSourceSection(Source source, ParserRuleContext ctx) { - var start = ctx.start.getStartIndex(); - var stop = ctx.stop.getStopIndex(); - return source.createSection(start, stop - start + 1); + protected static @Nullable SourceSection createSourceSection(Source source, @Nullable Node node) { + if (node == null) return null; + return createSourceSection(source, node.span()); } - protected static @Nullable SourceSection createSourceSection( - Source source, @Nullable Token token) { - if (token == null) return null; + protected static SourceSection @Nullable [] createDocSourceSection( + Source source, @Nullable DocComment node) { + if (node == null) return null; + var spans = node.getSpans(); + var sections = new SourceSection[spans.size()]; + for (var i = 0; i < sections.length; i++) { + var span = spans.get(i); + sections[i] = source.createSection(span.charIndex(), span.length()); + } + return sections; + } - var start = token.getStartIndex(); - var stop = token.getStopIndex(); - return source.createSection(start, stop - start + 1); + protected static SourceSection createSourceSection(Source source, Span span) { + return source.createSection(span.charIndex(), span.length()); + } + + protected SourceSection startOf(Node node) { + return startOf(node.span()); + } + + protected SourceSection startOf(Span span) { + return source.createSection(span.charIndex(), 1); + } + + protected SourceSection shrinkLeft(SourceSection section, int length) { + return source.createSection(section.getCharIndex() + length, section.getCharLength() - length); } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java index 0013a833..cd871708 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java @@ -24,44 +24,235 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; -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.graalvm.collections.EconomicMap; import org.pkl.core.PClassInfo; +import org.pkl.core.PklBugException; import org.pkl.core.SecurityManagerException; import org.pkl.core.TypeParameter; import org.pkl.core.TypeParameter.Variance; -import org.pkl.core.ast.*; +import org.pkl.core.ast.ConstantNode; +import org.pkl.core.ast.ConstantValueNode; +import org.pkl.core.ast.ExpressionNode; +import org.pkl.core.ast.MemberLookupMode; +import org.pkl.core.ast.PklRootNode; +import org.pkl.core.ast.VmModifier; import org.pkl.core.ast.builder.SymbolTable.AnnotationScope; import org.pkl.core.ast.builder.SymbolTable.ClassScope; -import org.pkl.core.ast.builder.SymbolTable.Scope; -import org.pkl.core.ast.expression.binary.*; -import org.pkl.core.ast.expression.generator.*; -import org.pkl.core.ast.expression.literal.*; -import org.pkl.core.ast.expression.member.*; -import org.pkl.core.ast.expression.primary.*; +import org.pkl.core.ast.expression.binary.AdditionNodeGen; +import org.pkl.core.ast.expression.binary.DivisionNodeGen; +import org.pkl.core.ast.expression.binary.EqualNodeGen; +import org.pkl.core.ast.expression.binary.ExponentiationNodeGen; +import org.pkl.core.ast.expression.binary.GreaterThanNodeGen; +import org.pkl.core.ast.expression.binary.GreaterThanOrEqualNodeGen; +import org.pkl.core.ast.expression.binary.LessThanNodeGen; +import org.pkl.core.ast.expression.binary.LessThanOrEqualNodeGen; +import org.pkl.core.ast.expression.binary.LetExprNode; +import org.pkl.core.ast.expression.binary.LogicalAndNodeGen; +import org.pkl.core.ast.expression.binary.LogicalOrNodeGen; +import org.pkl.core.ast.expression.binary.MultiplicationNodeGen; +import org.pkl.core.ast.expression.binary.NotEqualNodeGen; +import org.pkl.core.ast.expression.binary.NullCoalescingNodeGen; +import org.pkl.core.ast.expression.binary.PipeNodeGen; +import org.pkl.core.ast.expression.binary.RemainderNodeGen; +import org.pkl.core.ast.expression.binary.SubscriptNodeGen; +import org.pkl.core.ast.expression.binary.SubtractionNodeGen; +import org.pkl.core.ast.expression.binary.TruncatingDivisionNodeGen; +import org.pkl.core.ast.expression.generator.GeneratorElementNodeGen; +import org.pkl.core.ast.expression.generator.GeneratorEntryNodeGen; +import org.pkl.core.ast.expression.generator.GeneratorForNodeGen; +import org.pkl.core.ast.expression.generator.GeneratorMemberNode; +import org.pkl.core.ast.expression.generator.GeneratorObjectLiteralNode; +import org.pkl.core.ast.expression.generator.GeneratorObjectLiteralNodeGen; +import org.pkl.core.ast.expression.generator.GeneratorPredicateMemberNodeGen; +import org.pkl.core.ast.expression.generator.GeneratorPropertyNode; +import org.pkl.core.ast.expression.generator.GeneratorPropertyNodeGen; +import org.pkl.core.ast.expression.generator.GeneratorSpreadNodeGen; +import org.pkl.core.ast.expression.generator.GeneratorWhenNode; +import org.pkl.core.ast.expression.generator.RestoreForBindingsNode; +import org.pkl.core.ast.expression.literal.AmendModuleNodeGen; +import org.pkl.core.ast.expression.literal.CheckIsAnnotationClassNode; +import org.pkl.core.ast.expression.literal.ConstantEntriesLiteralNodeGen; +import org.pkl.core.ast.expression.literal.ElementsEntriesLiteralNodeGen; +import org.pkl.core.ast.expression.literal.ElementsLiteralNodeGen; +import org.pkl.core.ast.expression.literal.EmptyObjectLiteralNodeGen; +import org.pkl.core.ast.expression.literal.EntriesLiteralNodeGen; +import org.pkl.core.ast.expression.literal.FalseLiteralNode; +import org.pkl.core.ast.expression.literal.FloatLiteralNode; +import org.pkl.core.ast.expression.literal.FunctionLiteralNode; +import org.pkl.core.ast.expression.literal.IntLiteralNode; +import org.pkl.core.ast.expression.literal.InterpolatedStringLiteralNode; +import org.pkl.core.ast.expression.literal.ListLiteralNode; +import org.pkl.core.ast.expression.literal.MapLiteralNode; +import org.pkl.core.ast.expression.literal.PropertiesLiteralNodeGen; +import org.pkl.core.ast.expression.literal.SetLiteralNode; +import org.pkl.core.ast.expression.literal.TrueLiteralNode; +import org.pkl.core.ast.expression.member.InferParentWithinMethodNode; +import org.pkl.core.ast.expression.member.InferParentWithinObjectMethodNode; +import org.pkl.core.ast.expression.member.InferParentWithinPropertyNodeGen; +import org.pkl.core.ast.expression.member.InvokeMethodVirtualNodeGen; +import org.pkl.core.ast.expression.member.InvokeSuperMethodNodeGen; +import org.pkl.core.ast.expression.member.ReadPropertyNodeGen; +import org.pkl.core.ast.expression.member.ReadSuperEntryNode; +import org.pkl.core.ast.expression.member.ReadSuperPropertyNode; +import org.pkl.core.ast.expression.member.ResolveMethodNode; +import org.pkl.core.ast.expression.primary.GetEnclosingOwnerNode; +import org.pkl.core.ast.expression.primary.GetEnclosingReceiverNode; +import org.pkl.core.ast.expression.primary.GetMemberKeyNode; +import org.pkl.core.ast.expression.primary.GetModuleNode; +import org.pkl.core.ast.expression.primary.GetOwnerNode; +import org.pkl.core.ast.expression.primary.GetReceiverNode; +import org.pkl.core.ast.expression.primary.OuterNode; +import org.pkl.core.ast.expression.primary.ResolveVariableNode; +import org.pkl.core.ast.expression.primary.ThisNode; import org.pkl.core.ast.expression.ternary.IfElseNode; -import org.pkl.core.ast.expression.unary.*; +import org.pkl.core.ast.expression.unary.AbstractImportNode; +import org.pkl.core.ast.expression.unary.AbstractReadNode; +import org.pkl.core.ast.expression.unary.ImportGlobNode; +import org.pkl.core.ast.expression.unary.ImportNode; +import org.pkl.core.ast.expression.unary.LogicalNotNodeGen; +import org.pkl.core.ast.expression.unary.NonNullNode; +import org.pkl.core.ast.expression.unary.NullPropagatingOperationNode; +import org.pkl.core.ast.expression.unary.PropagateNullReceiverNodeGen; +import org.pkl.core.ast.expression.unary.ReadGlobNodeGen; +import org.pkl.core.ast.expression.unary.ReadNodeGen; +import org.pkl.core.ast.expression.unary.ReadOrNullNodeGen; +import org.pkl.core.ast.expression.unary.ThrowNodeGen; +import org.pkl.core.ast.expression.unary.TraceNode; +import org.pkl.core.ast.expression.unary.UnaryMinusNodeGen; import org.pkl.core.ast.internal.GetBaseModuleClassNode; import org.pkl.core.ast.internal.GetClassNodeGen; import org.pkl.core.ast.internal.ToStringNodeGen; import org.pkl.core.ast.lambda.ApplyVmFunction1NodeGen; -import org.pkl.core.ast.member.*; -import org.pkl.core.ast.type.*; +import org.pkl.core.ast.member.ClassNode; +import org.pkl.core.ast.member.ElementOrEntryNodeGen; +import org.pkl.core.ast.member.Lambda; +import org.pkl.core.ast.member.ModuleNode; +import org.pkl.core.ast.member.ObjectMember; +import org.pkl.core.ast.member.ObjectMethodNode; +import org.pkl.core.ast.member.TypeAliasNode; +import org.pkl.core.ast.member.UnresolvedFunctionNode; +import org.pkl.core.ast.member.UnresolvedMethodNode; +import org.pkl.core.ast.member.UnresolvedPropertyNode; +import org.pkl.core.ast.member.UntypedObjectMemberNode; +import org.pkl.core.ast.type.GetParentForTypeNode; +import org.pkl.core.ast.type.ResolveDeclaredTypeNode; +import org.pkl.core.ast.type.ResolveQualifiedDeclaredTypeNode; +import org.pkl.core.ast.type.ResolveSimpleDeclaredTypeNode; +import org.pkl.core.ast.type.TypeCastNode; +import org.pkl.core.ast.type.TypeConstraintNode; +import org.pkl.core.ast.type.TypeConstraintNodeGen; +import org.pkl.core.ast.type.TypeNode; +import org.pkl.core.ast.type.TypeTestNode; +import org.pkl.core.ast.type.UnresolvedTypeNode; +import org.pkl.core.ast.type.UnresolvedTypeNode.Constrained; import org.pkl.core.externalreader.ExternalReaderProcessException; import org.pkl.core.module.ModuleKey; import org.pkl.core.module.ModuleKeys; import org.pkl.core.module.ResolvedModuleKey; import org.pkl.core.packages.PackageLoadError; -import org.pkl.core.parser.antlr.PklLexer; -import org.pkl.core.parser.antlr.PklParser.*; -import org.pkl.core.runtime.*; +import org.pkl.core.parser.Span; +import org.pkl.core.parser.ast.Annotation; +import org.pkl.core.parser.ast.ArgumentList; +import org.pkl.core.parser.ast.Class; +import org.pkl.core.parser.ast.ClassMethod; +import org.pkl.core.parser.ast.ClassProperty; +import org.pkl.core.parser.ast.Expr; +import org.pkl.core.parser.ast.Expr.AmendsExpr; +import org.pkl.core.parser.ast.Expr.BinaryOperatorExpr; +import org.pkl.core.parser.ast.Expr.BoolLiteralExpr; +import org.pkl.core.parser.ast.Expr.FloatLiteralExpr; +import org.pkl.core.parser.ast.Expr.FunctionLiteralExpr; +import org.pkl.core.parser.ast.Expr.IfExpr; +import org.pkl.core.parser.ast.Expr.ImportExpr; +import org.pkl.core.parser.ast.Expr.IntLiteralExpr; +import org.pkl.core.parser.ast.Expr.LetExpr; +import org.pkl.core.parser.ast.Expr.LogicalNotExpr; +import org.pkl.core.parser.ast.Expr.ModuleExpr; +import org.pkl.core.parser.ast.Expr.MultiLineStringLiteralExpr; +import org.pkl.core.parser.ast.Expr.NewExpr; +import org.pkl.core.parser.ast.Expr.NonNullExpr; +import org.pkl.core.parser.ast.Expr.NullLiteralExpr; +import org.pkl.core.parser.ast.Expr.OuterExpr; +import org.pkl.core.parser.ast.Expr.ParenthesizedExpr; +import org.pkl.core.parser.ast.Expr.QualifiedAccessExpr; +import org.pkl.core.parser.ast.Expr.ReadExpr; +import org.pkl.core.parser.ast.Expr.SingleLineStringLiteralExpr; +import org.pkl.core.parser.ast.Expr.SubscriptExpr; +import org.pkl.core.parser.ast.Expr.SuperAccessExpr; +import org.pkl.core.parser.ast.Expr.SuperSubscriptExpr; +import org.pkl.core.parser.ast.Expr.ThisExpr; +import org.pkl.core.parser.ast.Expr.ThrowExpr; +import org.pkl.core.parser.ast.Expr.TraceExpr; +import org.pkl.core.parser.ast.Expr.TypeCastExpr; +import org.pkl.core.parser.ast.Expr.TypeCheckExpr; +import org.pkl.core.parser.ast.Expr.UnaryMinusExpr; +import org.pkl.core.parser.ast.Expr.UnqualifiedAccessExpr; +import org.pkl.core.parser.ast.ExtendsOrAmendsClause; +import org.pkl.core.parser.ast.Identifier; +import org.pkl.core.parser.ast.ImportClause; +import org.pkl.core.parser.ast.Modifier; +import org.pkl.core.parser.ast.Modifier.ModifierValue; +import org.pkl.core.parser.ast.Module; +import org.pkl.core.parser.ast.Node; +import org.pkl.core.parser.ast.ObjectBody; +import org.pkl.core.parser.ast.ObjectMember.ForGenerator; +import org.pkl.core.parser.ast.ObjectMember.MemberPredicate; +import org.pkl.core.parser.ast.ObjectMember.ObjectElement; +import org.pkl.core.parser.ast.ObjectMember.ObjectEntry; +import org.pkl.core.parser.ast.ObjectMember.ObjectMethod; +import org.pkl.core.parser.ast.ObjectMember.ObjectProperty; +import org.pkl.core.parser.ast.ObjectMember.ObjectSpread; +import org.pkl.core.parser.ast.ObjectMember.WhenGenerator; +import org.pkl.core.parser.ast.Parameter; +import org.pkl.core.parser.ast.Parameter.TypedIdentifier; +import org.pkl.core.parser.ast.ParameterList; +import org.pkl.core.parser.ast.QualifiedIdentifier; +import org.pkl.core.parser.ast.StringConstant; +import org.pkl.core.parser.ast.StringConstantPart; +import org.pkl.core.parser.ast.StringConstantPart.ConstantPart; +import org.pkl.core.parser.ast.StringConstantPart.StringEscape; +import org.pkl.core.parser.ast.StringConstantPart.StringNewline; +import org.pkl.core.parser.ast.StringConstantPart.StringUnicodeEscape; +import org.pkl.core.parser.ast.StringPart; +import org.pkl.core.parser.ast.StringPart.StringConstantParts; +import org.pkl.core.parser.ast.StringPart.StringInterpolation; +import org.pkl.core.parser.ast.Type; +import org.pkl.core.parser.ast.Type.ConstrainedType; +import org.pkl.core.parser.ast.Type.DeclaredType; +import org.pkl.core.parser.ast.Type.FunctionType; +import org.pkl.core.parser.ast.Type.ModuleType; +import org.pkl.core.parser.ast.Type.NothingType; +import org.pkl.core.parser.ast.Type.NullableType; +import org.pkl.core.parser.ast.Type.ParenthesizedType; +import org.pkl.core.parser.ast.Type.StringConstantType; +import org.pkl.core.parser.ast.Type.UnionType; +import org.pkl.core.parser.ast.Type.UnknownType; +import org.pkl.core.parser.ast.TypeAlias; +import org.pkl.core.parser.ast.TypeAnnotation; +import org.pkl.core.parser.ast.TypeParameterList; +import org.pkl.core.runtime.BaseModule; +import org.pkl.core.runtime.ModuleInfo; +import org.pkl.core.runtime.ModuleResolver; +import org.pkl.core.runtime.VmClass; +import org.pkl.core.runtime.VmContext; +import org.pkl.core.runtime.VmDataSize; +import org.pkl.core.runtime.VmDuration; +import org.pkl.core.runtime.VmException; import org.pkl.core.runtime.VmException.ProgramValue; +import org.pkl.core.runtime.VmExceptionBuilder; +import org.pkl.core.runtime.VmLanguage; +import org.pkl.core.runtime.VmList; +import org.pkl.core.runtime.VmMap; +import org.pkl.core.runtime.VmNull; +import org.pkl.core.runtime.VmSet; +import org.pkl.core.runtime.VmUtils; import org.pkl.core.stdlib.LanguageAwareNode; import org.pkl.core.stdlib.registry.ExternalMemberRegistry; import org.pkl.core.stdlib.registry.MemberRegistryFactory; @@ -71,7 +262,7 @@ import org.pkl.core.util.IoUtils; import org.pkl.core.util.Nullable; import org.pkl.core.util.Pair; -public final class AstBuilder extends AbstractAstBuilder { +public class AstBuilder extends AbstractAstBuilder { private final VmLanguage language; private final ModuleInfo moduleInfo; @@ -101,20 +292,20 @@ public final class AstBuilder extends AbstractAstBuilder { public static AstBuilder create( Source source, VmLanguage language, - ModuleContext ctx, + Module ctx, ModuleKey moduleKey, ResolvedModuleKey resolvedModuleKey, ModuleResolver moduleResolver) { - var moduleDecl = ctx.moduleDecl(); - var moduleHeader = moduleDecl != null ? moduleDecl.moduleHeader() : null; + var moduleDecl = ctx.getDecl(); var sourceSection = createSourceSection(source, ctx); var headerSection = - moduleHeader != null - ? createSourceSection(source, moduleHeader) + moduleDecl != null + ? createSourceSection(source, moduleDecl.headerSpan()) : // no explicit module declaration; designate start of file as header section source.createSection(0, 0); - var docComment = moduleDecl != null ? createSourceSection(source, moduleDecl.t) : null; + var docComment = + moduleDecl != null ? createDocSourceSection(source, moduleDecl.getDocComment()) : null; ModuleInfo moduleInfo; if (moduleDecl == null) { @@ -123,13 +314,13 @@ public final class AstBuilder extends AbstractAstBuilder { new ModuleInfo( sourceSection, headerSection, null, moduleName, moduleKey, resolvedModuleKey, false); } else { - var declaredModuleName = moduleDecl.moduleHeader().qualifiedIdentifier(); + var declaredModuleName = moduleDecl.getName(); var moduleName = declaredModuleName != null - ? declaredModuleName.getText() + ? declaredModuleName.text() : IoUtils.inferModuleName(moduleKey); - var clause = moduleDecl.moduleHeader().moduleExtendsOrAmendsClause(); - var isAmend = clause != null && clause.t.getType() == PklLexer.AMENDS; + var clause = moduleDecl.getExtendsOrAmendsDecl(); + var isAmend = clause != null && clause.getType() == ExtendsOrAmendsClause.Type.AMENDS; moduleInfo = new ModuleInfo( sourceSection, @@ -145,50 +336,1076 @@ public final class AstBuilder extends AbstractAstBuilder { } @Override - public PklRootNode visitModule(ModuleContext ctx) { - var moduleDecl = ctx.moduleDecl(); - var moduleHeader = moduleDecl != null ? moduleDecl.moduleHeader() : null; + public UnresolvedTypeNode visitUnknownType(UnknownType type) { + return new UnresolvedTypeNode.Unknown(createSourceSection(type)); + } + + @Override + public UnresolvedTypeNode visitNothingType(NothingType type) { + return new UnresolvedTypeNode.Nothing(createSourceSection(type)); + } + + @Override + public UnresolvedTypeNode visitModuleType(ModuleType type) { + return new UnresolvedTypeNode.Module(createSourceSection(type)); + } + + @Override + public UnresolvedTypeNode visitStringConstantType(StringConstantType type) { + return new UnresolvedTypeNode.StringLiteral( + createSourceSection(type), doVisitStringConstant(type.getStr())); + } + + @Override + public UnresolvedTypeNode visitDeclaredType(DeclaredType type) { + var identifier = type.getName(); + var args = type.getArgs(); + + if (args.isEmpty()) { + if (identifier.getIdentifiers().size() == 1) { + var text = identifier.getIdentifiers().get(0).getValue(); + var typeParameter = symbolTable.findTypeParameter(text); + if (typeParameter != null) { + return new UnresolvedTypeNode.TypeVariable(createSourceSection(type), typeParameter); + } + } + + return new UnresolvedTypeNode.Declared( + createSourceSection(type), doVisitTypeName(identifier)); + } + + var argTypes = new UnresolvedTypeNode[args.size()]; + for (var i = 0; i < args.size(); i++) { + argTypes[i] = visitType(args.get(i)); + } + + return new UnresolvedTypeNode.Parameterized( + createSourceSection(type), language, doVisitTypeName(identifier), argTypes); + } + + @Override + public UnresolvedTypeNode visitParenthesizedType(ParenthesizedType type) { + return visitType(type.getType()); + } + + @Override + public UnresolvedTypeNode visitNullableType(NullableType type) { + return new UnresolvedTypeNode.Nullable(createSourceSection(type), visitType(type.getType())); + } + + @Override + public UnresolvedTypeNode visitConstrainedType(ConstrainedType type) { + var childNode = visitType(type.getType()); + + return symbolTable.enterCustomThisScope( + scope -> { + var exprs = type.getExprs(); + var constraints = new TypeConstraintNode[exprs.size()]; + for (int i = 0; i < constraints.length; i++) { + var expr = visitExpr(exprs.get(i)); + constraints[i] = TypeConstraintNodeGen.create(expr.getSourceSection(), expr); + } + return new Constrained(createSourceSection(type), childNode, constraints); + }); + } + + @Override + public UnresolvedTypeNode visitUnionType(UnionType type) { + var elementTypes = type.getTypes(); + + boolean isUnionOfStringLiterals = true; + for (var typ : elementTypes) { + if (!(typ instanceof StringConstantType)) { + isUnionOfStringLiterals = false; + break; + } + } + int defaultIndex = type.getDefaultIndex(); + + if (isUnionOfStringLiterals) { + return new UnresolvedTypeNode.UnionOfStringLiterals( + createSourceSection(type), + defaultIndex, + elementTypes.stream() + .map(it -> doVisitStringConstant(((StringConstantType) it).getStr())) + .collect(Collectors.toCollection(LinkedHashSet::new))); + } + + var elements = new UnresolvedTypeNode[elementTypes.size()]; + for (int i = 0; i < elementTypes.size(); i++) { + elements[i] = visitType(elementTypes.get(i)); + } + + return new UnresolvedTypeNode.Union(createSourceSection(type), defaultIndex, elements); + } + + @Override + public UnresolvedTypeNode visitFunctionType(FunctionType type) { + var pars = new UnresolvedTypeNode[type.getArgs().size()]; + for (int i = 0; i < pars.length; i++) { + pars[i] = visitType(type.getArgs().get(i)); + } + + return new UnresolvedTypeNode.Function( + createSourceSection(type), pars, visitType(type.getRet())); + } + + @Override + public ExpressionNode visitThisExpr(ThisExpr expr) { + if (!(expr.parent() instanceof QualifiedAccessExpr)) { + var currentScope = symbolTable.getCurrentScope(); + var needsConst = + currentScope.getConstLevel() == ConstLevel.ALL + && currentScope.getConstDepth() == -1 + && !currentScope.isCustomThisScope(); + if (needsConst) { + throw exceptionBuilder() + .withSourceSection(createSourceSection(expr)) + .evalError("thisIsNotConst") + .build(); + } + } + return VmUtils.createThisNode( + createSourceSection(expr), symbolTable.getCurrentScope().isCustomThisScope()); + } + + // TODO: `outer.` should probably have semantics similar to `super.`, + // rather than just performing a lookup in the immediately enclosing object + // also, consider interpreting `x = ... x ...` as `x = ... outer.x ...` + @Override + public OuterNode visitOuterExpr(OuterExpr expr) { + if (!(expr.parent() instanceof QualifiedAccessExpr)) { + var constLevel = symbolTable.getCurrentScope().getConstLevel(); + var outerScope = getParentLexicalScope(); + if (outerScope != null && constLevel.bigger(outerScope.getConstLevel())) { + throw exceptionBuilder() + .evalError("outerIsNotConst") + .withSourceSection(createSourceSection(expr)) + .build(); + } + } + return new OuterNode(createSourceSection(expr)); + } + + @Override + public GetModuleNode visitModuleExpr(ModuleExpr expr) { + // cannot use unqualified `module` in a const context + if (symbolTable.getCurrentScope().getConstLevel().isConst() + && !(expr.parent() instanceof QualifiedAccessExpr)) { + var scope = symbolTable.getCurrentScope(); + while (scope != null + && !(scope instanceof AnnotationScope) + && !(scope instanceof ClassScope)) { + scope = scope.getParent(); + } + if (scope == null) { + throw exceptionBuilder() + .evalError("moduleIsNotConst", symbolTable.getCurrentScope().getName().toString()) + .withSourceSection(createSourceSection(expr)) + .build(); + } + var messageKey = + scope instanceof AnnotationScope ? "moduleIsNotConstAnnotation" : "moduleIsNotConstClass"; + throw exceptionBuilder() + .evalError(messageKey) + .withSourceSection(createSourceSection(expr)) + .build(); + } + return new GetModuleNode(createSourceSection(expr)); + } + + @Override + public ConstantValueNode visitNullLiteralExpr(NullLiteralExpr expr) { + return new ConstantValueNode(createSourceSection(expr), VmNull.withoutDefault()); + } + + @Override + public ExpressionNode visitBoolLiteralExpr(BoolLiteralExpr expr) { + if (expr.isB()) { + return new TrueLiteralNode(createSourceSection(expr)); + } else { + return new FalseLiteralNode(createSourceSection(expr)); + } + } + + @Override + public IntLiteralNode visitIntLiteralExpr(IntLiteralExpr expr) { + var section = createSourceSection(expr); + var text = remove_(expr.getNumber()); + + var radix = 10; + if (text.startsWith("0x") || text.startsWith("0b") || text.startsWith("0o")) { + radix = + switch (text.charAt(1)) { + case 'x' -> 16; + case 'b' -> 2; + default -> 8; + }; + + text = text.substring(2); + } + + // relies on grammar rule nesting depth, but a breakage won't go unnoticed by tests + if (expr.parent() instanceof UnaryMinusExpr) { + // handle negation here to make parsing of base.MinInt work + // also moves negation from runtime to parse time + text = "-" + text; + } + + try { + var num = Long.parseLong(text, radix); + return new IntLiteralNode(section, num); + } catch (NumberFormatException e) { + throw exceptionBuilder().evalError("intTooLarge", text).withSourceSection(section).build(); + } + } + + @Override + public FloatLiteralNode visitFloatLiteralExpr(FloatLiteralExpr expr) { + var section = createSourceSection(expr); + var text = remove_(expr.getNumber()); + // relies on grammar rule nesting depth, but a breakage won't go unnoticed by tests + if (expr.parent() instanceof UnaryMinusExpr) { + // handle negation here for consistency with visitIntegerLiteral + // also moves negation from runtime to parse time + text = "-" + text; + } + + try { + var num = Double.parseDouble(text); + return new FloatLiteralNode(section, num); + } catch (NumberFormatException e) { + throw exceptionBuilder().evalError("floatTooLarge", text).withSourceSection(section).build(); + } + } + + private static String remove_(String number) { + var builder = new StringBuilder(number.length()); + for (var i = 0; i < number.length(); i++) { + var ch = number.charAt(i); + if (ch == '_') continue; + builder.append(ch); + } + return builder.toString(); + } + + @Override + public ExpressionNode visitThrowExpr(ThrowExpr expr) { + return ThrowNodeGen.create(createSourceSection(expr), visitExpr(expr.getExpr())); + } + + @Override + public TraceNode visitTraceExpr(TraceExpr expr) { + return new TraceNode(createSourceSection(expr), visitExpr(expr.getExpr())); + } + + @Override + public AbstractImportNode visitImportExpr(ImportExpr expr) { + var importUriCtx = expr.getImportStr(); + return doVisitImport(expr.isGlob(), expr, importUriCtx); + } + + private AbstractImportNode doVisitImport( + boolean isGlobImport, Node node, StringConstant importUriNode) { + var section = createSourceSection(node); + var importUri = doVisitStringConstant(importUriNode); + if (isGlobImport && importUri.startsWith("...")) { + throw exceptionBuilder().evalError("cannotGlobTripleDots").withSourceSection(section).build(); + } + var resolvedUri = resolveImport(importUri, importUriNode); + if (isGlobImport) { + return new ImportGlobNode(section, moduleInfo.getResolvedModuleKey(), resolvedUri, importUri); + } + return new ImportNode(language, section, moduleInfo.getResolvedModuleKey(), resolvedUri); + } + + @Override + public AbstractReadNode visitReadExpr(ReadExpr expr) { + return switch (expr.getReadType()) { + case READ -> + ReadNodeGen.create(createSourceSection(expr), moduleKey, visitExpr(expr.getExpr())); + case NULL -> + ReadOrNullNodeGen.create(createSourceSection(expr), moduleKey, visitExpr(expr.getExpr())); + case GLOB -> + ReadGlobNodeGen.create(createSourceSection(expr), moduleKey, visitExpr(expr.getExpr())); + }; + } + + @Override + public ExpressionNode visitUnqualifiedAccessExpr(UnqualifiedAccessExpr expr) { + var identifier = toIdentifier(expr.getIdentifier().getValue()); + var argList = expr.getArgumentList(); + + if (argList == null) { + return createResolveVariableNode(createSourceSection(expr), identifier); + } + + // TODO: make sure that no user-defined List/Set/Map method is in scope + // TODO: support qualified calls (e.g., `import "pkl:base"; x = base.List()/Set()/Map()`) for + // correctness + if (identifier == org.pkl.core.runtime.Identifier.LIST) { + return doVisitListLiteral(expr, argList); + } + + if (identifier == org.pkl.core.runtime.Identifier.SET) { + return doVisitSetLiteral(expr, argList); + } + + if (identifier == org.pkl.core.runtime.Identifier.MAP) { + return doVisitMapLiteral(expr, argList); + } + + var scope = symbolTable.getCurrentScope(); + + return new ResolveMethodNode( + createSourceSection(expr), + identifier, + visitArgumentList(argList), + isBaseModule, + scope.isCustomThisScope(), + scope.getConstLevel(), + scope.getConstDepth()); + } + + @Override + public ExpressionNode visitStringConstant(StringConstant expr) { + return new ConstantValueNode(createSourceSection(expr), doVisitStringConstant(expr)); + } + + @Override + public ExpressionNode visitStringPart(StringPart spart) { + return doVisitStringPart(spart, spart.span()); + } + + private ExpressionNode doVisitStringPart(StringPart spart, Span span) { + if (spart instanceof StringInterpolation si) { + return ToStringNodeGen.create(createSourceSection(span), visitExpr(si.getExpr())); + } + if (spart instanceof StringConstantParts sparts) { + var builder = new StringBuilder(); + for (var part : sparts.getParts()) { + builder.append(doVisitStringConstantPart(part)); + } + return new ConstantValueNode(createSourceSection(span), builder.toString()); + } + throw exceptionBuilder().unreachableCode().build(); + } + + @Override + public ExpressionNode visitSingleLineStringLiteralExpr(SingleLineStringLiteralExpr expr) { + var parts = expr.getParts(); + if (parts.isEmpty()) { + return new ConstantValueNode(createSourceSection(expr), ""); + } + if (parts.size() == 1) { + return doVisitStringPart(parts.get(0), expr.span()); + } + + var nodes = new ExpressionNode[parts.size()]; + for (int i = 0; i < nodes.length; i++) { + nodes[i] = visitStringPart(parts.get(i)); + } + return new InterpolatedStringLiteralNode(createSourceSection(expr), nodes); + } + + @Override + public ExpressionNode visitMultiLineStringLiteralExpr(MultiLineStringLiteralExpr expr) { + var parts = expr.getParts(); + if (parts.isEmpty()) { + throw exceptionBuilder() + .evalError("stringContentMustBeginOnNewLine") + .withSourceSection(createSourceSection(expr)) + .build(); + } + var firstPart = parts.get(0); + var newLineStart = + firstPart instanceof StringConstantParts str + && str.getParts().get(0) instanceof StringNewline; + if (!newLineStart) { + throw exceptionBuilder() + .evalError("stringContentMustBeginOnNewLine") + .withSourceSection(startOf(firstPart)) + .build(); + } + + var lastPart = parts.get(parts.size() - 1); + var commonIndent = getCommonIndent(lastPart, expr.getEndDelimiterSpan()); + + if (parts.size() == 1) { + StringConstantParts sc = (StringConstantParts) firstPart; + return new ConstantValueNode( + createSourceSection(expr), + doVisitMultiLineStringParts(sc.getParts(), commonIndent, true, true)); + } + + var nodes = new ExpressionNode[parts.size()]; + var lastIndex = nodes.length - 1; + + for (int i = 0; i <= lastIndex; i++) { + nodes[i] = doVisitMultiLineStringPart(parts.get(i), commonIndent, i == 0, i == lastIndex); + } + return new InterpolatedStringLiteralNode(createSourceSection(expr), nodes); + } + + public ExpressionNode doVisitMultiLineStringPart( + StringPart spart, String commonIndent, boolean isStringStart, boolean isStringEnd) { + if (spart instanceof StringInterpolation si) { + return ToStringNodeGen.create(createSourceSection(si), visitExpr(si.getExpr())); + } + if (spart instanceof StringConstantParts sparts) { + return new ConstantValueNode( + createSourceSection(spart), + doVisitMultiLineStringParts(sparts.getParts(), commonIndent, isStringStart, isStringEnd)); + } + throw PklBugException.unreachableCode(); + } + + private String doVisitMultiLineStringParts( + List parts, + String commonIndent, + boolean isStringStart, + boolean isStringEnd) { + + var starIndex = isStringStart ? 1 : 0; + var endIndex = parts.size() - 1; + if (isStringEnd) { + if (parts.get(endIndex) instanceof StringNewline) { + // skip trailing newline token + endIndex -= 1; + } else { + // skip trailing newline and whitespace (common indent) tokens + endIndex -= 2; + } + } + + var builder = new StringBuilder(); + var isLineStart = isStringStart; + for (var i = starIndex; i <= endIndex; i++) { + var part = parts.get(i); + if (part instanceof StringNewline) { + builder.append('\n'); + isLineStart = true; + } else if (part instanceof ConstantPart cp) { + var text = cp.getStr(); + if (isLineStart) { + if (text.startsWith(commonIndent)) { + builder.append(text, commonIndent.length(), text.length()); + } else { + String actualIndent = getLeadingIndent(text); + if (actualIndent.length() > commonIndent.length()) { + actualIndent = actualIndent.substring(0, commonIndent.length()); + } + throw exceptionBuilder() + .evalError("stringIndentationMustMatchLastLine") + .withSourceSection(shrinkLeft(createSourceSection(cp), actualIndent.length())) + .build(); + } + } else { + builder.append(text); + } + isLineStart = false; + } else if (part instanceof StringEscape || part instanceof StringUnicodeEscape) { + if (isLineStart && !commonIndent.isEmpty()) { + throw exceptionBuilder() + .evalError("stringIndentationMustMatchLastLine") + .withSourceSection(createSourceSection(part)) + .build(); + } + builder.append(doVisitStringConstantPart(part)); + isLineStart = false; + } else { + throw PklBugException.unreachableCode(); + } + } + + return builder.toString(); + } + + @Override + public ExpressionNode visitNewExpr(NewExpr expr) { + var type = expr.getType(); + return type != null + ? doVisitNewExprWithExplicitParent(expr, type) + : doVisitNewExprWithInferredParent(expr); + } + + // `new Listing {}` is sugar for: `new Listing {} as Listing` + private ExpressionNode doVisitNewExprWithExplicitParent(NewExpr newExpr, Type type) { + var parentType = visitType(type); + var expr = + doVisitObjectBody( + newExpr.getBody(), + new GetParentForTypeNode( + createSourceSection(newExpr), + parentType, + symbolTable.getCurrentScope().getQualifiedName())); + if (type instanceof DeclaredType declaredType && !declaredType.getArgs().isEmpty()) { + return new TypeCastNode(parentType.getSourceSection(), expr, parentType); + } + return expr; + } + + private ExpressionNode doVisitNewExprWithInferredParent(NewExpr expr) { + ExpressionNode inferredParentNode; + + Node child = expr; + var parent = expr.parent(); + var scope = symbolTable.getCurrentScope(); + var levelsUp = 0; + + while (parent instanceof IfExpr + || parent instanceof TraceExpr + || parent instanceof LetExpr letExpr && letExpr.getExpr() == child) { + + if (parent instanceof LetExpr) { + assert scope != null; + scope = scope.getParent(); + levelsUp += 1; + } + child = parent; + parent = parent.parent(); + } + + assert scope != null; + + if (parent instanceof ClassProperty || parent instanceof ObjectProperty) { + inferredParentNode = + InferParentWithinPropertyNodeGen.create( + createSourceSection(expr.newSpan()), + scope.getName(), + levelsUp == 0 ? new GetOwnerNode() : new GetEnclosingOwnerNode(levelsUp)); + } else if (parent instanceof ObjectElement + || parent instanceof ObjectEntry objectEntry && objectEntry.getValue() == child) { + inferredParentNode = + ApplyVmFunction1NodeGen.create( + ReadPropertyNodeGen.create( + createSourceSection(expr.newSpan()), + org.pkl.core.runtime.Identifier.DEFAULT, + levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp)), + new GetMemberKeyNode()); + } else if (parent instanceof ClassMethod || parent instanceof ObjectMethod) { + var isObjectMethod = + parent instanceof ObjectMethod + || parent.parent() instanceof Module && moduleInfo.isAmend(); + org.pkl.core.runtime.Identifier scopeName = scope.getName(); + inferredParentNode = + isObjectMethod + ? new InferParentWithinObjectMethodNode( + createSourceSection(expr.newSpan()), + language, + scopeName, + levelsUp == 0 ? new GetOwnerNode() : new GetEnclosingOwnerNode(levelsUp)) + : new InferParentWithinMethodNode( + createSourceSection(expr.newSpan()), + language, + scopeName, + levelsUp == 0 ? new GetOwnerNode() : new GetEnclosingOwnerNode(levelsUp)); + } else if (parent instanceof LetExpr letExpr && letExpr.getBindingExpr() == child) { + // TODO (unclear how to infer type now that let-expression is implemented as lambda + // invocation) + throw exceptionBuilder() + .evalError("cannotInferParent") + .withSourceSection(createSourceSection(expr.newSpan())) + .build(); + } else { + throw exceptionBuilder() + .evalError("cannotInferParent") + .withSourceSection(createSourceSection(expr.newSpan())) + .build(); + } + + return doVisitObjectBody(expr.getBody(), inferredParentNode); + } + + @Override + public ExpressionNode visitAmendsExpr(AmendsExpr expr) { + // parentExpr is always New, Amends or Parenthesized. The parser makes sure of it in + // `Parser.parseExprRest` + return doVisitObjectBody(expr.getBody(), visitExpr(expr.getExpr())); + } + + @Override + public ExpressionNode visitSuperAccessExpr(SuperAccessExpr expr) { + var sourceSection = createSourceSection(expr); + var memberName = toIdentifier(expr.getIdentifier().getValue()); + var argCtx = expr.getArgumentList(); + var currentScope = symbolTable.getCurrentScope(); + var needsConst = + currentScope.getConstLevel() == ConstLevel.ALL && currentScope.getConstDepth() == -1; + + if (argCtx != null) { // supermethod call + if (!symbolTable.getCurrentScope().isClassMemberScope()) { + throw exceptionBuilder() + .evalError("cannotInvokeSupermethodFromHere") + .withSourceSection(sourceSection) + .build(); + } + + return InvokeSuperMethodNodeGen.create( + sourceSection, memberName, visitArgumentList(argCtx), needsConst); + } + + // superproperty call + return new ReadSuperPropertyNode(createSourceSection(expr), memberName, needsConst); + } + + @Override + public ExpressionNode visitSuperSubscriptExpr(SuperSubscriptExpr expr) { + return new ReadSuperEntryNode(createSourceSection(expr), visitExpr(expr.getArg())); + } + + @Override + public ExpressionNode visitQualifiedAccessExpr(QualifiedAccessExpr expr) { + if (expr.getArgumentList() != null) { + return doVisitMethodAccessExpr(expr); + } + + return doVisitPropertyInvocationExpr(expr); + } + + @Override + public ExpressionNode visitSubscriptExpr(SubscriptExpr expr) { + return SubscriptNodeGen.create( + createSourceSection(expr), visitExpr(expr.getExpr()), visitExpr(expr.getArg())); + } + + @Override + public ExpressionNode visitNonNullExpr(NonNullExpr expr) { + return new NonNullNode(createSourceSection(expr), visitExpr(expr.getExpr())); + } + + @Override + public ExpressionNode visitUnaryMinusExpr(UnaryMinusExpr expr) { + var childNode = expr.getExpr(); + var childExpr = visitExpr(childNode); + if (childNode instanceof IntLiteralExpr || childNode instanceof FloatLiteralExpr) { + // negation already handled (see visitIntLiteral/visitFloatLiteral) + return childExpr; + } + return UnaryMinusNodeGen.create(createSourceSection(expr), childExpr); + } + + @Override + public ExpressionNode visitLogicalNotExpr(LogicalNotExpr expr) { + return LogicalNotNodeGen.create(createSourceSection(expr), visitExpr(expr.getExpr())); + } + + @Override + public ExpressionNode visitBinaryOperatorExpr(BinaryOperatorExpr expr) { + return switch (expr.getOp()) { + case POW -> + ExponentiationNodeGen.create( + createSourceSection(expr), visitExpr(expr.getLeft()), visitExpr(expr.getRight())); + case MULT -> + MultiplicationNodeGen.create( + createSourceSection(expr), visitExpr(expr.getLeft()), visitExpr(expr.getRight())); + case DIV -> + DivisionNodeGen.create( + createSourceSection(expr), visitExpr(expr.getLeft()), visitExpr(expr.getRight())); + case INT_DIV -> + TruncatingDivisionNodeGen.create( + createSourceSection(expr), visitExpr(expr.getLeft()), visitExpr(expr.getRight())); + case MOD -> + RemainderNodeGen.create( + createSourceSection(expr), visitExpr(expr.getLeft()), visitExpr(expr.getRight())); + case PLUS -> + AdditionNodeGen.create( + createSourceSection(expr), visitExpr(expr.getLeft()), visitExpr(expr.getRight())); + case MINUS -> + SubtractionNodeGen.create( + createSourceSection(expr), visitExpr(expr.getLeft()), visitExpr(expr.getRight())); + case LT -> + LessThanNodeGen.create( + createSourceSection(expr), visitExpr(expr.getLeft()), visitExpr(expr.getRight())); + case GT -> + GreaterThanNodeGen.create( + createSourceSection(expr), visitExpr(expr.getLeft()), visitExpr(expr.getRight())); + case LTE -> + LessThanOrEqualNodeGen.create( + createSourceSection(expr), visitExpr(expr.getLeft()), visitExpr(expr.getRight())); + case GTE -> + GreaterThanOrEqualNodeGen.create( + createSourceSection(expr), visitExpr(expr.getLeft()), visitExpr(expr.getRight())); + case EQ_EQ -> + EqualNodeGen.create( + createSourceSection(expr), visitExpr(expr.getLeft()), visitExpr(expr.getRight())); + case NOT_EQ -> + NotEqualNodeGen.create( + createSourceSection(expr), visitExpr(expr.getLeft()), visitExpr(expr.getRight())); + case AND -> + LogicalAndNodeGen.create( + createSourceSection(expr), visitExpr(expr.getRight()), visitExpr(expr.getLeft())); + case OR -> + LogicalOrNodeGen.create( + createSourceSection(expr), visitExpr(expr.getRight()), visitExpr(expr.getLeft())); + case PIPE -> + PipeNodeGen.create( + createSourceSection(expr), visitExpr(expr.getLeft()), visitExpr(expr.getRight())); + case NULL_COALESCE -> + NullCoalescingNodeGen.create( + createSourceSection(expr), visitExpr(expr.getRight()), visitExpr(expr.getLeft())); + default -> throw PklBugException.unreachableCode(); + }; + } + + @Override + public ExpressionNode visitTypeCheckExpr(TypeCheckExpr expr) { + return new TypeTestNode( + createSourceSection(expr), visitExpr(expr.getExpr()), visitType(expr.getType())); + } + + @Override + public ExpressionNode visitTypeCastExpr(TypeCastExpr expr) { + return new TypeCastNode( + createSourceSection(expr), visitExpr(expr.getExpr()), visitType(expr.getType())); + } + + @Override + public ExpressionNode visitIfExpr(IfExpr expr) { + return new IfElseNode( + createSourceSection(expr), + visitExpr(expr.getCond()), + visitExpr(expr.getThen()), + visitExpr(expr.getEls())); + } + + @Override + public ExpressionNode visitLetExpr(LetExpr letExpr) { + var sourceSection = createSourceSection(letExpr); + var parameter = letExpr.getParameter(); + var frameBuilder = FrameDescriptor.newBuilder(); + UnresolvedTypeNode[] typeNodes; + if (parameter instanceof TypedIdentifier par) { + typeNodes = new UnresolvedTypeNode[] {visitTypeAnnotation(par.getTypeAnnotation())}; + frameBuilder.addSlot( + FrameSlotKind.Illegal, toIdentifier(par.getIdentifier().getValue()), null); + } else { + typeNodes = new UnresolvedTypeNode[0]; + } + + var isCustomThisScope = symbolTable.getCurrentScope().isCustomThisScope(); + + UnresolvedFunctionNode functionNode = + symbolTable.enterLambda( + frameBuilder, + scope -> { + var expr = visitExpr(letExpr.getExpr()); + return new UnresolvedFunctionNode( + language, + scope.buildFrameDescriptor(), + new Lambda(createSourceSection(letExpr.getExpr()), scope.getQualifiedName()), + 1, + typeNodes, + null, + expr); + }); + + return new LetExprNode( + sourceSection, functionNode, visitExpr(letExpr.getBindingExpr()), isCustomThisScope); + } + + @Override + public ExpressionNode visitFunctionLiteralExpr(FunctionLiteralExpr expr) { + var sourceSection = createSourceSection(expr); + var params = expr.getParameterList(); + var descriptorBuilder = createFrameDescriptorBuilder(params); + var paramCount = params.getParameters().size(); + + if (paramCount > 5) { + throw exceptionBuilder() + .evalError("tooManyFunctionParameters") + .withSourceSection(sourceSection) + .build(); + } + + var isCustomThisScope = symbolTable.getCurrentScope().isCustomThisScope(); + + return symbolTable.enterLambda( + descriptorBuilder, + scope -> { + var exprNode = visitExpr(expr.getExpr()); + var functionNode = + new UnresolvedFunctionNode( + language, + scope.buildFrameDescriptor(), + new Lambda(sourceSection, scope.getQualifiedName()), + paramCount, + doVisitParameterTypes(params), + null, + exprNode); + + return new FunctionLiteralNode(sourceSection, functionNode, isCustomThisScope); + }); + } + + @Override + public ExpressionNode visitParenthesizedExpr(ParenthesizedExpr expr) { + return visitExpr(expr.getExpr()); + } + + private ExpressionNode doVisitListLiteral(Expr expr, ArgumentList argList) { + var elementNodes = createCollectionArgumentNodes(argList); + + if (elementNodes.first.length == 0) { + return new ConstantValueNode(VmList.EMPTY); + } + + return elementNodes.second + ? new ConstantValueNode( + createSourceSection(expr), VmList.createFromConstantNodes(elementNodes.first)) + : new ListLiteralNode(createSourceSection(expr), elementNodes.first); + } + + private ExpressionNode doVisitSetLiteral(Expr expr, ArgumentList argList) { + var elementNodes = createCollectionArgumentNodes(argList); + + if (elementNodes.first.length == 0) { + return new ConstantValueNode(VmSet.EMPTY); + } + + return elementNodes.second + ? new ConstantValueNode( + createSourceSection(expr), VmSet.createFromConstantNodes(elementNodes.first)) + : new SetLiteralNode(createSourceSection(expr), elementNodes.first); + } + + private ExpressionNode doVisitMapLiteral(Expr expr, ArgumentList argList) { + var keyAndValueNodes = createCollectionArgumentNodes(argList); + + if (keyAndValueNodes.first.length == 0) { + return new ConstantValueNode(VmMap.EMPTY); + } + + if (keyAndValueNodes.first.length % 2 != 0) { + throw exceptionBuilder() + .evalError("missingMapValue") + .withSourceSection(createSourceSection(argList.span().stopSpan())) + .build(); + } + + return keyAndValueNodes.second + ? new ConstantValueNode( + createSourceSection(expr), VmMap.createFromConstantNodes(keyAndValueNodes.first)) + : new MapLiteralNode(createSourceSection(expr), keyAndValueNodes.first); + } + + private Pair createCollectionArgumentNodes(ArgumentList exprs) { + var args = exprs.getArguments(); + var elementNodes = new ExpressionNode[args.size()]; + var isConstantNodes = true; + + for (var i = 0; i < elementNodes.length; i++) { + var exprNode = visitExpr(args.get(i)); + elementNodes[i] = exprNode; + isConstantNodes = isConstantNodes && exprNode instanceof ConstantNode; + } + + return Pair.of(elementNodes, isConstantNodes); + } + + public GeneratorMemberNode visitObjectMember(org.pkl.core.parser.ast.ObjectMember member) { + return (GeneratorMemberNode) member.accept(this); + } + + @Override + public GeneratorPropertyNode visitObjectProperty(ObjectProperty member) { + checkNotInsideForGenerator(member, "forGeneratorCannotGenerateProperties"); + var memberNode = doVisitObjectProperty(member); + return GeneratorPropertyNodeGen.create(memberNode); + } + + @Override + public GeneratorMemberNode visitObjectMethod(ObjectMethod memberNode) { + checkNotInsideForGenerator(memberNode, "forGeneratorCannotGenerateMethods"); + var member = doVisitObjectMethod(memberNode); + return GeneratorPropertyNodeGen.create(member); + } + + @Override + public GeneratorMemberNode visitMemberPredicate(MemberPredicate ctx) { + var keyNode = symbolTable.enterCustomThisScope(scope -> visitExpr(ctx.getPred())); + var member = + doVisitObjectEntryBody(createSourceSection(ctx), keyNode, ctx.getExpr(), ctx.getBodyList()); + var isFrameStored = + member.getMemberNode() != null && symbolTable.getCurrentScope().isForGeneratorScope(); + return GeneratorPredicateMemberNodeGen.create(keyNode, member, isFrameStored); + } + + @Override + public GeneratorMemberNode visitObjectElement(ObjectElement member) { + var memberNode = doVisitObjectElement(member); + var isFrameStored = + memberNode.getMemberNode() != null && symbolTable.getCurrentScope().isForGeneratorScope(); + return GeneratorElementNodeGen.create(memberNode, isFrameStored); + } + + @Override + public GeneratorMemberNode visitObjectEntry(ObjectEntry member) { + var keyNodeAndMember = doVisitObjectEntry(member); + var keyNode = keyNodeAndMember.first; + var memberNode = keyNodeAndMember.second; + var isFrameStored = + memberNode.getMemberNode() != null && symbolTable.getCurrentScope().isForGeneratorScope(); + + return GeneratorEntryNodeGen.create(keyNode, memberNode, isFrameStored); + } + + @Override + public GeneratorMemberNode visitObjectSpread(ObjectSpread member) { + var expr = visitExpr(member.getExpr()); + return GeneratorSpreadNodeGen.create(createSourceSection(member), expr, member.isNullable()); + } + + @Override + public GeneratorMemberNode visitWhenGenerator(WhenGenerator member) { + var sourceSection = createSourceSection(member); + var thenNodes = doVisitForWhenBody(member.getBody()); + var elseNodes = + member.getElseClause() == null + ? new GeneratorMemberNode[0] + : doVisitForWhenBody(member.getElseClause()); + + return new GeneratorWhenNode( + sourceSection, visitExpr(member.getThenClause()), thenNodes, elseNodes); + } + + private GeneratorMemberNode[] doVisitForWhenBody(ObjectBody body) { + if (!body.getParameters().isEmpty()) { + throw exceptionBuilder() + .evalError("forWhenBodyCannotHaveParameters") + .withSourceSection(createSourceSection(body.getParameters().get(0))) + .build(); + } + return doVisitGeneratorMemberNodes(body.getMembers()); + } + + @Override + public GeneratorMemberNode visitForGenerator(ForGenerator ctx) { + var keyParameter = ctx.getP2() == null ? null : ctx.getP1(); + var valueParameter = ctx.getP2() == null ? ctx.getP1() : ctx.getP2(); + TypedIdentifier keyTypedIdentifier = null; + if (keyParameter instanceof TypedIdentifier ti) keyTypedIdentifier = ti; + TypedIdentifier valueTypedIdentifier = null; + if (valueParameter instanceof TypedIdentifier ti) valueTypedIdentifier = ti; + + var keyIdentifier = + keyTypedIdentifier == null + ? null + : toIdentifier(keyTypedIdentifier.getIdentifier().getValue()); + var valueIdentifier = + valueTypedIdentifier == null + ? null + : toIdentifier(valueTypedIdentifier.getIdentifier().getValue()); + if (valueIdentifier != null && valueIdentifier == keyIdentifier) { + throw exceptionBuilder() + .evalError("duplicateDefinition", valueIdentifier) + .withSourceSection(createSourceSection(valueTypedIdentifier.getIdentifier())) + .build(); + } + var currentScope = symbolTable.getCurrentScope(); + var generatorDescriptorBuilder = currentScope.newFrameDescriptorBuilder(); + var memberDescriptorBuilder = currentScope.newForGeneratorMemberDescriptorBuilder(); + var keySlot = -1; + var valueSlot = -1; + if (keyIdentifier != null) { + keySlot = generatorDescriptorBuilder.addSlot(FrameSlotKind.Illegal, keyIdentifier, null); + memberDescriptorBuilder.addSlot(FrameSlotKind.Illegal, keyIdentifier, null); + } + if (valueIdentifier != null) { + valueSlot = generatorDescriptorBuilder.addSlot(FrameSlotKind.Illegal, valueIdentifier, null); + memberDescriptorBuilder.addSlot(FrameSlotKind.Illegal, valueIdentifier, null); + } + var unresolvedKeyTypeNode = + keyTypedIdentifier == null + ? null + : visitTypeAnnotation(keyTypedIdentifier.getTypeAnnotation()); + var unresolvedValueTypeNode = + valueTypedIdentifier == null + ? null + : visitTypeAnnotation(valueTypedIdentifier.getTypeAnnotation()); + // if possible, initialize immediately to avoid later insert + var keyTypeNode = + unresolvedKeyTypeNode == null && keySlot != -1 + ? new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection()) + .initWriteSlotNode(keySlot) + : null; + // if possible, initialize immediately to avoid later insert + var valueTypeNode = + unresolvedValueTypeNode == null && valueSlot != -1 + ? new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection()) + .initWriteSlotNode(valueSlot) + : null; + var iterableNode = visitExpr(ctx.getExpr()); + var memberNodes = + symbolTable.enterForGenerator( + generatorDescriptorBuilder, + memberDescriptorBuilder, + scope -> doVisitForWhenBody(ctx.getBody())); + return GeneratorForNodeGen.create( + createSourceSection(ctx), + generatorDescriptorBuilder.build(), + iterableNode, + unresolvedKeyTypeNode, + unresolvedValueTypeNode, + memberNodes, + keyTypeNode, + valueTypeNode); + } + + @Override + public PklRootNode visitModule(Module mod) { + var moduleDecl = mod.getDecl(); var annotationNodes = - moduleDecl != null ? doVisitAnnotations(moduleDecl.annotation()) : new ExpressionNode[] {}; + moduleDecl != null + ? doVisitAnnotations(moduleDecl.getAnnotations()) + : new ExpressionNode[] {}; int modifiers; - if (moduleHeader == null) { + if (moduleDecl == null) { modifiers = VmModifier.NONE; } else { - var modifierCtxs = moduleHeader.modifier(); + var modifierNodes = moduleDecl.getModifiers(); modifiers = doVisitModifiers( - modifierCtxs, VmModifier.VALID_MODULE_MODIFIERS, "invalidModuleModifier"); + modifierNodes, VmModifier.VALID_MODULE_MODIFIERS, "invalidModuleModifier"); // doing this in a second step gives better error messages if (moduleInfo.isAmend()) { modifiers = doVisitModifiers( - modifierCtxs, + modifierNodes, VmModifier.VALID_AMENDING_MODULE_MODIFIERS, "invalidAmendingModuleModifier"); } } - var extendsOrAmendsClause = - moduleHeader != null ? moduleHeader.moduleExtendsOrAmendsClause() : null; + var extendsOrAmendsClause = moduleDecl != null ? moduleDecl.getExtendsOrAmendsDecl() : null; var supermoduleNode = extendsOrAmendsClause == null - ? resolveBaseModuleClass(Identifier.MODULE, BaseModule::getModuleClass) - : doVisitImport( - PklLexer.IMPORT, extendsOrAmendsClause, extendsOrAmendsClause.stringConstant()); + ? resolveBaseModuleClass( + org.pkl.core.runtime.Identifier.MODULE, BaseModule::getModuleClass) + : doVisitImport(false, extendsOrAmendsClause, extendsOrAmendsClause.getUrl()); var propertyNames = CollectionUtils.newHashSet( - ctx.is.size() + ctx.cs.size() + ctx.ts.size() + ctx.ps.size()); + mod.getImports().size() + + mod.getClasses().size() + + mod.getTypeAliases().size() + + mod.getProperties().size()); if (!moduleInfo.isAmend()) { var supertypeNode = new UnresolvedTypeNode.Declared(supermoduleNode.getSourceSection(), supermoduleNode); var moduleProperties = - doVisitModuleProperties(ctx.is, ctx.cs, ctx.ts, List.of(), propertyNames, moduleInfo); - var unresolvedPropertyNodes = doVisitClassProperties(ctx.ps, propertyNames); + doVisitModuleProperties( + mod.getImports(), + mod.getClasses(), + mod.getTypeAliases(), + List.of(), + propertyNames, + moduleInfo); + var unresolvedPropertyNodes = doVisitClassProperties(mod.getProperties(), propertyNames); var classNode = new ClassNode( @@ -204,17 +1421,34 @@ public final class AstBuilder extends AbstractAstBuilder { supertypeNode, moduleProperties, unresolvedPropertyNodes, - doVisitMethodDefs(ctx.ms)); + doVisitMethodDefs(mod.getMethods())); return new ModuleNode( language, moduleInfo.getSourceSection(), moduleInfo.getModuleName(), classNode); } var moduleProperties = - doVisitModuleProperties(ctx.is, ctx.cs, ctx.ts, ctx.ps, propertyNames, moduleInfo); + doVisitModuleProperties( + mod.getImports(), + mod.getClasses(), + mod.getTypeAliases(), + mod.getProperties(), + propertyNames, + moduleInfo); - for (var methodCtx : ctx.ms) { - var localMethod = doVisitObjectMethod(methodCtx.methodHeader(), methodCtx.expr(), true); + for (var methodCtx : mod.getMethods()) { + assert methodCtx.getExpr() != null; + var localMethod = + doVisitObjectMethod( + methodCtx, + methodCtx.getModifiers(), + methodCtx.getHeaderSpan(), + methodCtx.getName(), + methodCtx.getParameterList(), + methodCtx.getTypeParameterList(), + methodCtx.getExpr(), + methodCtx.getTypeAnnotation(), + true); EconomicMaps.put(moduleProperties, localMethod.getName(), localMethod); } @@ -231,46 +1465,145 @@ public final class AstBuilder extends AbstractAstBuilder { language, moduleInfo.getSourceSection(), moduleInfo.getModuleName(), moduleNode); } - @Override - public ObjectMember visitClazz(ClazzContext ctx) { - var headerCtx = ctx.classHeader(); + private EconomicMap doVisitModuleProperties( + List imports, + List classes, + List typeAliases, + List properties, + Set propertyNames, + ModuleInfo moduleInfo) { - var sourceSection = createSourceSection(ctx); - var headerSection = createSourceSection(headerCtx); + var totalSize = imports.size() + classes.size() + typeAliases.size() + properties.size(); + var result = EconomicMaps.create(totalSize); - var bodyCtx = ctx.classBody(); - if (bodyCtx != null) { - checkClosingDelimiter(bodyCtx.err, "}", bodyCtx.stop); + for (var _import : imports) { + var member = visitImportClause(_import); + checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); + EconomicMaps.put(result, member.getName(), member); } - var typeParameters = visitTypeParameterList(headerCtx.typeParameterList()); + for (var clazz : classes) { + ObjectMember member = visitClass(clazz); - List propertyCtxs = bodyCtx == null ? List.of() : bodyCtx.ps; - List methodCtxs = bodyCtx == null ? List.of() : bodyCtx.ms; + if (moduleInfo.isAmend() && !member.isLocal()) { + throw exceptionBuilder() + .evalError("classMustBeLocal") + .withSourceSection(member.getHeaderSection()) + .build(); + } + + checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); + EconomicMaps.put(result, member.getName(), member); + } + + for (var typeAlias : typeAliases) { + var member = visitTypeAlias(typeAlias); + + if (moduleInfo.isAmend() && !member.isLocal()) { + throw exceptionBuilder() + .evalError("typeAliasMustBeLocal") + .withSourceSection(member.getHeaderSection()) + .build(); + } + + checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); + EconomicMaps.put(result, member.getName(), member); + } + + for (var ctx : properties) { + var member = + doVisitObjectProperty( + ctx, + ctx.getModifiers(), + ctx.getName(), + ctx.getTypeAnnotation(), + ctx.getExpr(), + ctx.getBodyList()); + + if (moduleInfo.isAmend() && !member.isLocal() && ctx.getTypeAnnotation() != null) { + throw exceptionBuilder() + .evalError("nonLocalObjectPropertyCannotHaveTypeAnnotation") + .withSourceSection(createSourceSection(ctx.getTypeAnnotation().getType())) + .build(); + } + + checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); + EconomicMaps.put(result, member.getName(), member); + } + + return result; + } + + @Override + public ObjectMember visitImportClause(ImportClause imp) { + var importNode = doVisitImport(imp.isGlob(), imp, imp.getImportStr()); + var moduleKey = moduleResolver.resolve(importNode.getImportUri()); + var importName = + org.pkl.core.runtime.Identifier.property( + imp.getAlias() != null ? imp.getAlias().getValue() : IoUtils.inferModuleName(moduleKey), + true); + + return symbolTable.enterProperty( + importName, + ConstLevel.NONE, + scope -> { + var modifiers = VmModifier.IMPORT | VmModifier.LOCAL | VmModifier.CONST; + if (imp.isGlob()) { + modifiers = modifiers | VmModifier.GLOB; + } + var result = + new ObjectMember( + importNode.getSourceSection(), + importNode.getSourceSection(), + modifiers, + scope.getName(), + scope.getQualifiedName()); + + result.initMemberNode( + new UntypedObjectMemberNode( + language, scope.buildFrameDescriptor(), result, importNode)); + + return result; + }); + } + + @Override + public ObjectMember visitClass(Class clazz) { + var sourceSection = createSourceSection(clazz); + var headerSection = createSourceSection(clazz.getHeaderSpan()); + + var bodyNode = clazz.getBody(); + + var typeParameters = visitTypeParameterList(clazz.getTypeParameterList()); + + List properties = bodyNode != null ? bodyNode.getProperties() : List.of(); + List methods = bodyNode != null ? bodyNode.getMethods() : List.of(); var modifiers = doVisitModifiers( - headerCtx.modifier(), VmModifier.VALID_CLASS_MODIFIERS, "invalidClassModifier") + clazz.getModifiers(), VmModifier.VALID_CLASS_MODIFIERS, "invalidClassModifier") | VmModifier.CLASS; var className = - Identifier.property(headerCtx.Identifier().getText(), VmModifier.isLocal(modifiers)); + org.pkl.core.runtime.Identifier.property( + clazz.getName().getValue(), VmModifier.isLocal(modifiers)); return symbolTable.enterClass( className, typeParameters, scope -> { - var supertypeCtx = headerCtx.type(); + var supertypeCtx = clazz.getSuperClass(); // needs to be inside `enterClass` so that class' type parameters are in scope var supertypeNode = supertypeCtx != null ? visitType(supertypeCtx) - : isBaseModule && className == Identifier.ANY + : isBaseModule && className == org.pkl.core.runtime.Identifier.ANY ? null : new UnresolvedTypeNode.Declared( VmUtils.unavailableSourceSection(), - resolveBaseModuleClass(Identifier.TYPED, BaseModule::getTypedClass)); + resolveBaseModuleClass( + org.pkl.core.runtime.Identifier.TYPED, BaseModule::getTypedClass)); if (!(supertypeNode == null || supertypeNode instanceof UnresolvedTypeNode.Declared @@ -287,22 +1620,22 @@ public final class AstBuilder extends AbstractAstBuilder { moduleInfo.getModuleName(), className.toString(), moduleInfo.getModuleKey().getUri()); - var propertyNames = CollectionUtils.newHashSet(propertyCtxs.size()); + var propertyNames = CollectionUtils.newHashSet(properties.size()); var classNode = new ClassNode( sourceSection, headerSection, - createSourceSection(ctx.t), - doVisitAnnotations(ctx.annotation()), + createDocSourceSection(clazz.getDocComment()), + doVisitAnnotations(clazz.getAnnotations()), modifiers, classInfo, typeParameters, null, supertypeNode, EconomicMaps.create(), - doVisitClassProperties(propertyCtxs, propertyNames), - doVisitMethodDefs(methodCtxs)); + doVisitClassProperties(properties, propertyNames), + doVisitMethodDefs(methods)); var isLocal = VmModifier.isLocal(modifiers); @@ -322,23 +1655,241 @@ public final class AstBuilder extends AbstractAstBuilder { }); } + private ExpressionNode resolveBaseModuleClass( + org.pkl.core.runtime.Identifier className, Supplier clazz) { + return isBaseModule + ? + // Can't access BaseModule.getXYZClass() while parsing base module + new GetBaseModuleClassNode(className) + : new ConstantValueNode(clazz.get()); + } + @Override - public ObjectMember visitTypeAlias(TypeAliasContext ctx) { - var headerCtx = ctx.typeAliasHeader(); - var sourceSection = createSourceSection(ctx); - var headerSection = createSourceSection(headerCtx); + public Integer visitModifier(Modifier modifier) { + return switch (modifier.getValue()) { + case EXTERNAL -> VmModifier.EXTERNAL; + case ABSTRACT -> VmModifier.ABSTRACT; + case OPEN -> VmModifier.OPEN; + case LOCAL -> VmModifier.LOCAL; + case HIDDEN -> VmModifier.HIDDEN; + case FIXED -> VmModifier.FIXED; + case CONST -> VmModifier.CONST; + }; + } + + private UnresolvedPropertyNode[] doVisitClassProperties( + List propertyContexts, Set propertyNames) { + var propertyNodes = new UnresolvedPropertyNode[propertyContexts.size()]; + + for (var i = 0; i < propertyNodes.length; i++) { + var propertyNode = visitClassProperty(propertyContexts.get(i)); + checkDuplicateMember(propertyNode.getName(), propertyNode.getHeaderSection(), propertyNames); + propertyNodes[i] = propertyNode; + } + + return propertyNodes; + } + + private UnresolvedMethodNode[] doVisitMethodDefs(List methodDefs) { + var methodNodes = new UnresolvedMethodNode[methodDefs.size()]; + var methodNames = CollectionUtils.newHashSet(methodDefs.size()); + + for (var i = 0; i < methodNodes.length; i++) { + var methodNode = visitClassMethod(methodDefs.get(i)); + checkDuplicateMember(methodNode.getName(), methodNode.getHeaderSection(), methodNames); + methodNodes[i] = methodNode; + } + + return methodNodes; + } + + @Override + public UnresolvedPropertyNode visitClassProperty(ClassProperty entry) { + var docCom = entry.getDocComment(); + var annotations = entry.getAnnotations(); + var modifierList = entry.getModifiers(); + var name = entry.getName(); + var typeAnnotation = entry.getTypeAnnotation(); + var expr = entry.getExpr(); + var objectBodies = entry.getBodyList(); + var docComment = createDocSourceSection(docCom); + var annotationNodes = doVisitAnnotations(annotations); + var sourceSection = createSourceSection(entry); + var headerStart = !modifierList.isEmpty() ? modifierList.get(0).span() : name.span(); + var headerEnd = typeAnnotation != null ? typeAnnotation.span() : name.span(); + var headerSection = createSourceSection(headerStart.endWith(headerEnd)); var modifiers = doVisitModifiers( - headerCtx.modifier(), + modifierList, VmModifier.VALID_PROPERTY_MODIFIERS, "invalidPropertyModifier"); + + var isLocal = VmModifier.isLocal(modifiers); + var propertyName = org.pkl.core.runtime.Identifier.property(name.getValue(), isLocal); + + return symbolTable.enterProperty( + propertyName, + getConstLevel(modifiers), + scope -> { + ExpressionNode bodyNode; + + if (expr != null) { // prop = expr + if (VmModifier.isExternal(modifiers)) { + throw exceptionBuilder() + .evalError("externalMemberCannotHaveBody") + .withSourceSection(headerSection) + .build(); + } + if (VmModifier.isAbstract(modifiers)) { + throw exceptionBuilder() + .evalError("abstractMemberCannotHaveBody") + .withSourceSection(headerSection) + .build(); + } + bodyNode = visitExpr(expr); + } else if (!objectBodies.isEmpty()) { // prop { ... } + if (typeAnnotation != null) { + throw exceptionBuilder() + .evalError("cannotAmendPropertyDefinition") + .withSourceSection(createSourceSection(entry)) + .build(); + } + bodyNode = + doVisitObjectBody( + objectBodies, + new ReadSuperPropertyNode( + unavailableSourceSection(), + scope.getName(), + scope.getConstLevel() == ConstLevel.ALL)); + } else { // no value given + if (isLocal) { + assert typeAnnotation != null; + throw missingLocalPropertyValue(typeAnnotation); + } + if (VmModifier.isExternal(modifiers)) { + bodyNode = + externalMemberRegistry.getPropertyBody(scope.getQualifiedName(), headerSection); + if (bodyNode instanceof LanguageAwareNode languageAwareNode) { + languageAwareNode.initLanguage(language); + } + } else if (VmModifier.isAbstract(modifiers)) { + bodyNode = + new CannotInvokeAbstractPropertyNode(headerSection, scope.getQualifiedName()); + } else { + bodyNode = null; // will be given a default by UnresolvedPropertyNode + } + } + + var typeAnnNode = visitTypeAnnotation(typeAnnotation); + + return new UnresolvedPropertyNode( + language, + sourceSection, + headerSection, + createSourceSection(name), + scope.buildFrameDescriptor(), + docComment, + annotationNodes, + modifiers, + scope.getName(), + scope.getQualifiedName(), + typeAnnNode, + bodyNode); + }); + } + + @Override + public UnresolvedMethodNode visitClassMethod(ClassMethod entry) { + var headerSection = createSourceSection(entry.getHeaderSpan()); + + var typeParameters = visitTypeParameterList(entry.getTypeParameterList()); + + var modifiers = + doVisitModifiers( + entry.getModifiers(), VmModifier.VALID_METHOD_MODIFIERS, "invalidMethodModifier"); + + var isLocal = VmModifier.isLocal(modifiers); + var methodName = org.pkl.core.runtime.Identifier.method(entry.getName().getValue(), isLocal); + + var bodyContext = entry.getExpr(); + var paramListCtx = entry.getParameterList(); + var descriptorBuilder = createFrameDescriptorBuilder(paramListCtx); + var paramCount = paramListCtx.getParameters().size(); + + return symbolTable.enterMethod( + methodName, + getConstLevel(modifiers), + descriptorBuilder, + typeParameters, + scope -> { + ExpressionNode bodyNode; + if (bodyContext != null) { + if (VmModifier.isExternal(modifiers)) { + throw exceptionBuilder() + .evalError("externalMemberCannotHaveBody") + .withSourceSection(headerSection) + .build(); + } + if (VmModifier.isAbstract(modifiers)) { + throw exceptionBuilder() + .evalError("abstractMemberCannotHaveBody") + .withSourceSection(headerSection) + .build(); + } + bodyNode = visitExpr(bodyContext); + } else { + if (VmModifier.isExternal(modifiers)) { + bodyNode = + externalMemberRegistry.getFunctionBody( + scope.getQualifiedName(), headerSection, paramCount); + if (bodyNode instanceof LanguageAwareNode languageAwareNode) { + languageAwareNode.initLanguage(language); + } + } else if (VmModifier.isAbstract(modifiers)) { + bodyNode = + new CannotInvokeAbstractFunctionNode(headerSection, scope.getQualifiedName()); + } else { + throw exceptionBuilder() + .evalError("missingMethodBody", methodName) + .withSourceSection(headerSection) + .build(); + } + } + + return new UnresolvedMethodNode( + language, + createSourceSection(entry), + headerSection, + scope.buildFrameDescriptor(), + createDocSourceSection(entry.getDocComment()), + doVisitAnnotations(entry.getAnnotations()), + modifiers, + methodName, + scope.getQualifiedName(), + paramCount, + typeParameters, + doVisitParameterTypes(paramListCtx), + visitTypeAnnotation(entry.getTypeAnnotation()), + isMethodReturnTypeChecked, + bodyNode); + }); + } + + @Override + public ObjectMember visitTypeAlias(TypeAlias typeAlias) { + var sourceSection = createSourceSection(typeAlias); + var headerSection = createSourceSection(typeAlias.getHeaderSpan()); + + var modifiers = + doVisitModifiers( + typeAlias.getModifiers(), VmModifier.VALID_TYPE_ALIAS_MODIFIERS, "invalidTypeAliasModifier") | VmModifier.TYPE_ALIAS; var isLocal = VmModifier.isLocal(modifiers); - var name = Identifier.property(headerCtx.Identifier().getText(), isLocal); + var name = org.pkl.core.runtime.Identifier.property(typeAlias.getName().getValue(), isLocal); - var typeParameters = visitTypeParameterList(headerCtx.typeParameterList()); + var typeParameters = visitTypeParameterList(typeAlias.getTypeParameterList()); return symbolTable.enterTypeAlias( name, @@ -349,13 +1900,13 @@ public final class AstBuilder extends AbstractAstBuilder { new TypeAliasNode( sourceSection, headerSection, - createSourceSection(ctx.t), - doVisitAnnotations(ctx.annotation()), + createDocSourceSection(typeAlias.getDocComment()), + doVisitAnnotations(typeAlias.getAnnotations()), modifiers, scopeName.toString(), scope.getQualifiedName(), typeParameters, - (UnresolvedTypeNode) ctx.type().accept(this)); + visitType(typeAlias.getType())); var result = new ObjectMember( @@ -376,47 +1927,71 @@ public final class AstBuilder extends AbstractAstBuilder { } @Override - public UnresolvedTypeNode[] visitTypeArgumentList(@Nullable TypeArgumentListContext ctx) { - if (ctx == null) return new UnresolvedTypeNode[0]; + public ExpressionNode visitAnnotation(Annotation annotation) { + var verifyNode = new CheckIsAnnotationClassNode(visitType(annotation.getType())); - checkCommaSeparatedElements(ctx, ctx.ts, ctx.errs); - checkClosingDelimiter(ctx.err, ">", ctx.stop); - - var result = new UnresolvedTypeNode[ctx.ts.size()]; - for (int i = 0; i < ctx.ts.size(); i++) { - result[i] = (UnresolvedTypeNode) ctx.ts.get(i).accept(this); + var bodyCtx = annotation.getBody(); + if (bodyCtx == null) { + var currentScope = symbolTable.getCurrentScope(); + //noinspection ConstantConditions + return PropertiesLiteralNodeGen.create( + createSourceSection(annotation), + language, + currentScope.getQualifiedName(), + currentScope.isCustomThisScope(), + null, + new UnresolvedTypeNode[0], + EconomicMaps.create(), + verifyNode); } - return result; + + return symbolTable.enterAnnotationScope((scope) -> doVisitObjectBody(bodyCtx, verifyNode)); + } + + private ExpressionNode[] doVisitAnnotations(List annotations) { + var nodes = new ExpressionNode[annotations.size()]; + for (var i = 0; i < nodes.length; i++) { + nodes[i] = visitAnnotation(annotations.get(i)); + } + return nodes; + } + + public UnresolvedTypeNode visitType(Type type) { + return (UnresolvedTypeNode) type.accept(this); + } + + public ExpressionNode visitExpr(Expr expr) { + return (ExpressionNode) expr.accept(this); } @Override - public List visitTypeParameterList(@Nullable TypeParameterListContext ctx) { + public List visitTypeParameterList(@Nullable TypeParameterList ctx) { if (ctx == null) return List.of(); - checkCommaSeparatedElements(ctx, ctx.ts, ctx.errs); - checkClosingDelimiter(ctx.err, ">", ctx.stop); - - if (!(ctx.parent instanceof TypeAliasHeaderContext) && !isStdLibModule) { + if (!(ctx.parent() instanceof TypeAlias) && !isStdLibModule) { throw exceptionBuilder() .evalError("cannotDeclareTypeParameter") - .withSourceSection(createSourceSection(ctx.ts.get(0))) + .withSourceSection(createSourceSection(ctx.getParameters().get(0))) .build(); } - var size = ctx.ts.size(); + var params = ctx.getParameters(); + var size = params.size(); var result = new ArrayList(size); for (var i = 0; i < size; i++) { - var paramCtx = ctx.ts.get(i); + var paramCtx = params.get(i); Variance variance; - if (paramCtx.t == null) { + var nodeVariance = paramCtx.getVariance(); + if (nodeVariance == null) { variance = TypeParameter.Variance.INVARIANT; - } else if (paramCtx.t.getType() == PklLexer.IN) { - variance = TypeParameter.Variance.CONTRAVARIANT; } else { - assert paramCtx.t.getType() == PklLexer.OUT; - variance = TypeParameter.Variance.COVARIANT; + variance = + switch (nodeVariance) { + case IN -> TypeParameter.Variance.CONTRAVARIANT; + case OUT -> TypeParameter.Variance.COVARIANT; + }; } - var parameterName = paramCtx.Identifier().getText(); + var parameterName = paramCtx.getIdentifier().getValue(); if (result.stream().anyMatch(it -> it.getName().equals(parameterName))) { throw exceptionBuilder() .evalError("duplicateTypeParameter", parameterName) @@ -429,629 +2004,73 @@ public final class AstBuilder extends AbstractAstBuilder { } @Override - public @Nullable UnresolvedTypeNode visitTypeAnnotation(@Nullable TypeAnnotationContext ctx) { - return ctx == null ? null : (UnresolvedTypeNode) ctx.type().accept(this); + public @Nullable UnresolvedTypeNode visitTypeAnnotation(@Nullable TypeAnnotation typeAnnotation) { + return typeAnnotation == null ? null : visitType(typeAnnotation.getType()); } @Override - public Object visitNewExpr(NewExprContext ctx) { - var typeCtx = ctx.type(); - return typeCtx != null - ? doVisitNewExprWithExplicitParent(ctx, typeCtx) - : doVisitNewExprWithInferredParent(ctx); - } - - // `new Listing {}` is sugar for: `new Listing {} as Listing` - private Object doVisitNewExprWithExplicitParent(NewExprContext ctx, TypeContext typeCtx) { - var parentType = visitType(typeCtx); - var expr = - doVisitObjectBody( - ctx.objectBody(), - new GetParentForTypeNode( - createSourceSection(ctx), - parentType, - symbolTable.getCurrentScope().getQualifiedName())); - if (typeCtx instanceof DeclaredTypeContext declaredTypeContext - && declaredTypeContext.typeArgumentList() != null) { - return new TypeCastNode(parentType.getSourceSection(), expr, parentType); + public ExpressionNode[] visitArgumentList(ArgumentList argumentList) { + var args = argumentList.getArguments(); + var res = new ExpressionNode[args.size()]; + for (int i = 0; i < res.length; i++) { + res[i] = visitExpr(args.get(i)); } - return expr; + return res; } - private Object doVisitNewExprWithInferredParent(NewExprContext ctx) { - ExpressionNode inferredParentNode; + @Override + protected Object defaultValue() { + throw PklBugException.unreachableCode(); + } - ParserRuleContext child = ctx; - var parent = ctx.getParent(); - var scope = symbolTable.getCurrentScope(); - var levelsUp = 0; - - while (parent instanceof IfExprContext - || parent instanceof TraceExprContext - || parent instanceof LetExprContext letExpr && letExpr.r == child) { - - if (parent instanceof LetExprContext) { - assert scope != null; - scope = scope.getParent(); - levelsUp += 1; + private ResolveDeclaredTypeNode doVisitTypeName(QualifiedIdentifier ctx) { + var identifiers = ctx.getIdentifiers(); + return switch (identifiers.size()) { + case 1 -> { + var identifier = identifiers.get(0); + yield new ResolveSimpleDeclaredTypeNode( + createSourceSection(identifier), + org.pkl.core.runtime.Identifier.get(identifier.getValue()), + isBaseModule); } - child = parent; - parent = parent.getParent(); - } - - assert scope != null; - - if (parent instanceof ClassPropertyContext || parent instanceof ObjectPropertyContext) { - inferredParentNode = - InferParentWithinPropertyNodeGen.create( - createSourceSection(ctx.t), - scope.getName(), - levelsUp == 0 ? new GetOwnerNode() : new GetEnclosingOwnerNode(levelsUp)); - } else if (parent instanceof ObjectElementContext - || parent instanceof ObjectEntryContext objectEntry && objectEntry.v == child) { - inferredParentNode = - ApplyVmFunction1NodeGen.create( - ReadPropertyNodeGen.create( - createSourceSection(ctx.t), - Identifier.DEFAULT, - levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp)), - new GetMemberKeyNode()); - } else if (parent instanceof ClassMethodContext || parent instanceof ObjectMethodContext) { - var isObjectMethod = - parent instanceof ObjectMethodContext - || parent.getParent() instanceof ModuleContext && moduleInfo.isAmend(); - Identifier scopeName = scope.getName(); - inferredParentNode = - isObjectMethod - ? new InferParentWithinObjectMethodNode( - createSourceSection(ctx.t), - language, - scopeName, - levelsUp == 0 ? new GetOwnerNode() : new GetEnclosingOwnerNode(levelsUp)) - : new InferParentWithinMethodNode( - createSourceSection(ctx.t), - language, - scopeName, - levelsUp == 0 ? new GetOwnerNode() : new GetEnclosingOwnerNode(levelsUp)); - } else if (parent instanceof LetExprContext letExpr && letExpr.l == child) { - // TODO (unclear how to infer type now that let-expression is implemented as lambda - // invocation) - throw exceptionBuilder() - .evalError("cannotInferParent") - .withSourceSection(createSourceSection(ctx.t)) - .build(); - } else { - throw exceptionBuilder() - .evalError("cannotInferParent") - .withSourceSection(createSourceSection(ctx.t)) - .build(); - } - - return doVisitObjectBody(ctx.objectBody(), inferredParentNode); - } - - @Override - public Object visitAmendExpr(AmendExprContext ctx) { - var parentExpr = ctx.expr(); - - if (!(parentExpr instanceof NewExprContext - || parentExpr instanceof AmendExprContext - || parentExpr instanceof ParenthesizedExprContext)) { - throw exceptionBuilder() - .evalError("unexpectedCurlyProbablyAmendsExpression", parentExpr.getText()) - .withSourceSection(createSourceSection(ctx.objectBody().start)) - .build(); - } - - return doVisitObjectBody(ctx.objectBody(), visitExpr(parentExpr)); - } - - @Override - public UnresolvedPropertyNode visitClassProperty(ClassPropertyContext ctx) { - var docComment = createSourceSection(ctx.t); - var annotationNodes = doVisitAnnotations(ctx.annotation()); - var modifierCtxs = ctx.modifier(); - var identifier = ctx.Identifier(); - var typeAnnCtx = ctx.typeAnnotation(); - var sourceSection = createSourceSection(ctx); - var identifierSymbol = identifier.getSymbol(); - var headerSection = - createSourceSection( - !modifierCtxs.isEmpty() ? modifierCtxs.get(0).start : identifierSymbol, - typeAnnCtx != null ? typeAnnCtx.getStop() : identifierSymbol); - - var modifiers = - doVisitModifiers( - ctx.modifier(), VmModifier.VALID_PROPERTY_MODIFIERS, "invalidPropertyModifier"); - - var isLocal = VmModifier.isLocal(modifiers); - var propertyName = Identifier.property(identifier.getText(), isLocal); - - return symbolTable.enterProperty( - propertyName, - getConstLevel(modifiers), - scope -> { - var exprCtx = ctx.expr(); - var objBodyCtx = ctx.objectBody(); - ExpressionNode bodyNode; - - if (exprCtx != null) { // prop = expr - if (VmModifier.isExternal(modifiers)) { - throw exceptionBuilder() - .evalError("externalMemberCannotHaveBody") - .withSourceSection(headerSection) - .build(); - } - if (VmModifier.isAbstract(modifiers)) { - throw exceptionBuilder() - .evalError("abstractMemberCannotHaveBody") - .withSourceSection(headerSection) - .build(); - } - bodyNode = visitExpr(exprCtx); - } else if (objBodyCtx != null && !objBodyCtx.isEmpty()) { // prop { ... } - if (typeAnnCtx != null) { - throw exceptionBuilder() - .evalError("cannotAmendPropertyDefinition") - .withSourceSection(createSourceSection(ctx)) - .build(); - } - bodyNode = - doVisitObjectBody( - objBodyCtx, - new ReadSuperPropertyNode( - unavailableSourceSection(), - scope.getName(), - scope.getConstLevel() == ConstLevel.ALL)); - } else { // no value given - if (isLocal) { - assert typeAnnCtx != null; - throw missingLocalPropertyValue(typeAnnCtx); - } - if (VmModifier.isExternal(modifiers)) { - bodyNode = - externalMemberRegistry.getPropertyBody(scope.getQualifiedName(), headerSection); - if (bodyNode instanceof LanguageAwareNode languageAwareNode) { - languageAwareNode.initLanguage(language); - } - } else if (VmModifier.isAbstract(modifiers)) { - bodyNode = - new CannotInvokeAbstractPropertyNode(headerSection, scope.getQualifiedName()); - } else { - bodyNode = null; // will be given a default by UnresolvedPropertyNode - } - } - - var typeAnnNode = visitTypeAnnotation(typeAnnCtx); - - return new UnresolvedPropertyNode( - language, - sourceSection, - headerSection, - createSourceSection(identifier), - scope.buildFrameDescriptor(), - docComment, - annotationNodes, - modifiers, - scope.getName(), - scope.getQualifiedName(), - typeAnnNode, - bodyNode); - }); - } - - private VmException missingLocalPropertyValue(TypeAnnotationContext typeAnnCtx) { - var stop = typeAnnCtx.stop.getStopIndex(); - return exceptionBuilder() - .evalError("missingLocalPropertyValue") - .withSourceSection(source.createSection(stop + 1, 0)) - .build(); - } - - private ObjectMember doVisitObjectProperty(ObjectPropertyContext ctx) { - return doVisitObjectProperty( - ctx, ctx.modifier(), ctx.Identifier(), ctx.typeAnnotation(), ctx.expr(), ctx.objectBody()); - } - - private ObjectMember doVisitObjectMethod(ObjectMethodContext ctx) { - return doVisitObjectMethod(ctx.methodHeader(), ctx.expr(), false); - } - - private ObjectMember doVisitObjectMethod( - MethodHeaderContext headerCtx, ExprContext exprCtx, boolean isModuleMethod) { - var modifiers = - doVisitModifiers( - headerCtx.modifier(), - VmModifier.VALID_OBJECT_MEMBER_MODIFIERS, - "invalidObjectMemberModifier"); - - if (!VmModifier.isLocal(modifiers)) { - throw exceptionBuilder() - .evalError(isModuleMethod ? "moduleMethodMustBeLocal" : "objectMethodMustBeLocal") - .withSourceSection(createSourceSection(headerCtx)) - .build(); - } - - var methodName = Identifier.method(headerCtx.Identifier().getText(), true); - - var paramListCtx = headerCtx.parameterList(); - var frameDescriptorBuilder = createFrameDescriptorBuilder(paramListCtx); - - return symbolTable.enterMethod( - methodName, - getConstLevel(modifiers), - frameDescriptorBuilder, - List.of(), - scope -> { - if (headerCtx.typeParameterList() != null) { - throw exceptionBuilder() - .evalError("cannotDeclareTypeParameter") - .withSourceSection(createSourceSection(headerCtx.typeParameterList())) - .build(); - } - - var member = - new ObjectMember( - createSourceSection(headerCtx.getParent()), - createSourceSection(headerCtx), - modifiers, - scope.getName(), - scope.getQualifiedName()); - var body = visitExpr(exprCtx); - var node = - new ObjectMethodNode( - language, - scope.buildFrameDescriptor(), - member, - body, - paramListCtx.ts.size(), - doVisitParameterTypes(paramListCtx), - visitTypeAnnotation(headerCtx.typeAnnotation())); - - member.initMemberNode(node); - return member; - }); - } - - private ObjectMember doVisitObjectProperty( - ParserRuleContext ctx, - List modifierCtxs, - TerminalNode propertyName, - @Nullable TypeAnnotationContext typeAnnCtx, - @Nullable ExprContext exprCtx, - @Nullable List bodyCtx) { - var modifiers = - doVisitModifiers( - modifierCtxs, VmModifier.VALID_OBJECT_MEMBER_MODIFIERS, "invalidObjectMemberModifier"); - if (VmModifier.isConst(modifiers) && !VmModifier.isLocal(modifiers)) { - @SuppressWarnings("OptionalGetWithoutIsPresent") - var constModifierCtx = - modifierCtxs.stream().filter((it) -> it.CONST() != null).findFirst().get(); - throw exceptionBuilder() - .evalError("invalidConstObjectMemberModifier") - .withSourceSection(createSourceSection(constModifierCtx)) - .build(); - } - return doVisitObjectProperty( - createSourceSection(ctx), - createSourceSection(propertyName), - modifiers, - propertyName.getText(), - typeAnnCtx, - exprCtx, - bodyCtx); - } - - private ObjectMember doVisitObjectProperty( - SourceSection sourceSection, - SourceSection headerSection, - int modifiers, - String propertyName, - @Nullable TypeAnnotationContext typeAnnCtx, - @Nullable ExprContext exprCtx, - @Nullable List bodyCtx) { - - var isLocal = VmModifier.isLocal(modifiers); - var identifier = Identifier.property(propertyName, isLocal); - - return symbolTable.enterProperty( - identifier, - getConstLevel(modifiers), - scope -> { - if (isLocal) { - if (exprCtx == null - && typeAnnCtx != null) { // module property that has type annotation but no value - throw missingLocalPropertyValue(typeAnnCtx); - } - } else { - if (typeAnnCtx != null) { - throw exceptionBuilder() - .evalError("nonLocalObjectPropertyCannotHaveTypeAnnotation") - .withSourceSection(createSourceSection(typeAnnCtx.type())) - .build(); - } - } - - ExpressionNode bodyNode; - if (bodyCtx != null && !bodyCtx.isEmpty()) { // foo { ... } - if (isLocal) { - throw exceptionBuilder() - .evalError("cannotAmendLocalPropertyDefinition") - .withSourceSection(createSourceSection(bodyCtx.get(0).start)) - .build(); - } - bodyNode = - doVisitObjectBody( - bodyCtx, - new ReadSuperPropertyNode( - unavailableSourceSection(), - scope.getName(), - // Never need a const check for amends declarations. In `foo { ... }`: - // 1. if `foo` is const (i.e. `const foo { ... }`, `super.foo` is required - // to be const (the const-ness of a property cannot be changed) - // 2. if in a const scope (i.e. `const bar = new { foo { ... } }`), - // `super.foo` does not reference something outside the scope. - false)); - } else { // foo = ... - assert exprCtx != null; - bodyNode = visitExpr(exprCtx); - } - - return isLocal - ? VmUtils.createLocalObjectProperty( - language, - sourceSection, - headerSection, - scope.getName(), - scope.getQualifiedName(), - scope.buildFrameDescriptor(), - modifiers, - bodyNode, - visitTypeAnnotation(typeAnnCtx)) - : VmUtils.createObjectProperty( - language, - sourceSection, - headerSection, - scope.getName(), - scope.getQualifiedName(), - scope.buildFrameDescriptor(), - modifiers, - bodyNode, - null); - }); - } - - private GeneratorMemberNode[] doVisitGeneratorMemberNodes( - List memberCtxs) { - var result = new GeneratorMemberNode[memberCtxs.size()]; - for (var i = 0; i < result.length; i++) { - result[i] = (GeneratorMemberNode) memberCtxs.get(i).accept(this); - } - return result; - } - - private GeneratorObjectLiteralNode doVisitGeneratorObjectBody( - ObjectBodyContext ctx, ExpressionNode parentNode) { - var parametersDescriptor = createFrameDescriptorBuilder(ctx); - var parameterTypes = doVisitParameterTypes(ctx); - var memberNodes = doVisitGeneratorMemberNodes(ctx.objectMember()); - var currentScope = symbolTable.getCurrentScope(); - //noinspection ConstantConditions - return GeneratorObjectLiteralNodeGen.create( - createSourceSection(ctx.getParent()), - language, - currentScope.getQualifiedName(), - currentScope.isCustomThisScope(), - parametersDescriptor == null ? null : parametersDescriptor.build(), - parameterTypes, - memberNodes, - parentNode); - } - - @Override - public GeneratorPropertyNode visitObjectProperty(ObjectPropertyContext ctx) { - checkNotInsideForGenerator(ctx, "forGeneratorCannotGenerateProperties"); - var member = doVisitObjectProperty(ctx); - return GeneratorPropertyNodeGen.create(member); - } - - @Override - public GeneratorMemberNode visitObjectMethod(ObjectMethodContext ctx) { - checkNotInsideForGenerator(ctx, "forGeneratorCannotGenerateMethods"); - var member = doVisitObjectMethod(ctx); - return GeneratorPropertyNodeGen.create(member); - } - - private void checkNotInsideForGenerator(ParserRuleContext ctx, String errorMessageKey) { - if (!symbolTable.getCurrentScope().isForGeneratorScope()) { - return; - } - var forExprCtx = ctx.getParent(); - while (forExprCtx.getClass() != ForGeneratorContext.class) { - forExprCtx = forExprCtx.getParent(); - } - throw exceptionBuilder() - .evalError(errorMessageKey) - .withSourceSection(createSourceSection(((ForGeneratorContext) forExprCtx).FOR())) - .build(); - } - - @Override - public GeneratorMemberNode visitMemberPredicate(MemberPredicateContext ctx) { - if (ctx.err1 == null && ctx.err2 == null) { - throw missingDelimiter("]]", ctx.k.stop.getStopIndex() + 1); - } else if (ctx.err1 != null - && (ctx.err2 == null || ctx.err1.getStartIndex() != ctx.err2.getStartIndex() - 1)) { - // There shouldn't be any whitespace between the first and second ']'. - throw wrongDelimiter("]]", "]", ctx.err1.getStartIndex()); - } - - var keyNode = symbolTable.enterCustomThisScope(scope -> visitExpr(ctx.k)); - var member = doVisitObjectEntryBody(createSourceSection(ctx), keyNode, ctx.v, ctx.objectBody()); - var isFrameStored = - member.getMemberNode() != null && symbolTable.getCurrentScope().isForGeneratorScope(); - return GeneratorPredicateMemberNodeGen.create(keyNode, member, isFrameStored); - } - - @Override - public GeneratorMemberNode visitObjectEntry(ObjectEntryContext ctx) { - var keyNodeAndMember = doVisitObjectEntry(ctx); - var keyNode = keyNodeAndMember.first; - var member = keyNodeAndMember.second; - var isFrameStored = - member.getMemberNode() != null && symbolTable.getCurrentScope().isForGeneratorScope(); - return GeneratorEntryNodeGen.create(keyNode, member, isFrameStored); - } - - @Override - public GeneratorMemberNode visitObjectSpread(ObjectSpreadContext ctx) { - var expr = visitExpr(ctx.expr()); - return GeneratorSpreadNodeGen.create(createSourceSection(ctx), expr, ctx.QSPREAD() != null); - } - - @Override - public GeneratorElementNode visitObjectElement(ObjectElementContext ctx) { - var member = doVisitObjectElement(ctx); - var isFrameStored = - member.getMemberNode() != null && symbolTable.getCurrentScope().isForGeneratorScope(); - return GeneratorElementNodeGen.create(member, isFrameStored); - } - - private GeneratorMemberNode[] doVisitForWhenBody(ObjectBodyContext ctx) { - if (!ctx.ps.isEmpty()) { - throw exceptionBuilder() - .evalError("forWhenBodyCannotHaveParameters") - .withSourceSection(createSourceSection(ctx.ps.get(0))) - .build(); - } - return doVisitGeneratorMemberNodes(ctx.objectMember()); - } - - @Override - public GeneratorWhenNode visitWhenGenerator(WhenGeneratorContext ctx) { - checkClosingDelimiter(ctx.err, ")", ctx.e.stop); - - var sourceSection = createSourceSection(ctx); - var thenNodes = doVisitForWhenBody(ctx.b1); - var elseNodes = ctx.b2 == null ? new GeneratorMemberNode[0] : doVisitForWhenBody(ctx.b2); - - return new GeneratorWhenNode(sourceSection, visitExpr(ctx.e), thenNodes, elseNodes); - } - - private static boolean isIgnored(@Nullable ParameterContext param) { - return param != null && param.UNDERSCORE() != null; - } - - @Override - public GeneratorForNode visitForGenerator(ForGeneratorContext ctx) { - checkClosingDelimiter(ctx.err, ")", ctx.e.stop); - var keyParameter = ctx.t2 == null ? null : ctx.t1; - var valueParameter = ctx.t2 == null ? ctx.t1 : ctx.t2; - var keyTypedIdentifier = keyParameter == null ? null : keyParameter.typedIdentifier(); - var valueTypedIdentifier = valueParameter == null ? null : valueParameter.typedIdentifier(); - var keyIdentifier = - keyTypedIdentifier == null ? null : toIdentifier(keyTypedIdentifier.Identifier()); - var valueIdentifier = - valueTypedIdentifier == null ? null : toIdentifier(valueTypedIdentifier.Identifier()); - if (valueIdentifier != null && valueIdentifier == keyIdentifier) { - throw exceptionBuilder() - .evalError("duplicateDefinition", valueIdentifier) - .withSourceSection(createSourceSection(valueTypedIdentifier.Identifier())) - .build(); - } - var currentScope = symbolTable.getCurrentScope(); - var generatorDescriptorBuilder = currentScope.newFrameDescriptorBuilder(); - var memberDescriptorBuilder = currentScope.newForGeneratorMemberDescriptorBuilder(); - var keySlot = -1; - var valueSlot = -1; - if (keyIdentifier != null) { - keySlot = generatorDescriptorBuilder.addSlot(FrameSlotKind.Illegal, keyIdentifier, null); - memberDescriptorBuilder.addSlot(FrameSlotKind.Illegal, keyIdentifier, null); - } - if (valueIdentifier != null) { - valueSlot = generatorDescriptorBuilder.addSlot(FrameSlotKind.Illegal, valueIdentifier, null); - memberDescriptorBuilder.addSlot(FrameSlotKind.Illegal, valueIdentifier, null); - } - var unresolvedKeyTypeNode = - keyTypedIdentifier == null - ? null - : visitTypeAnnotation(keyTypedIdentifier.typeAnnotation()); - var unresolvedValueTypeNode = - valueTypedIdentifier == null - ? null - : visitTypeAnnotation(valueTypedIdentifier.typeAnnotation()); - // if possible, initialize immediately to avoid later insert - var keyTypeNode = - unresolvedKeyTypeNode == null && keySlot != -1 - ? new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection()) - .initWriteSlotNode(keySlot) - : null; - // if possible, initialize immediately to avoid later insert - var valueTypeNode = - unresolvedValueTypeNode == null && valueSlot != -1 - ? new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection()) - .initWriteSlotNode(valueSlot) - : null; - var iterableNode = visitExpr(ctx.e); - var memberNodes = - symbolTable.enterForGenerator( - generatorDescriptorBuilder, - memberDescriptorBuilder, - scope -> doVisitForWhenBody(ctx.objectBody())); - return GeneratorForNodeGen.create( - createSourceSection(ctx), - generatorDescriptorBuilder.build(), - iterableNode, - unresolvedKeyTypeNode, - unresolvedValueTypeNode, - memberNodes, - keyTypeNode, - valueTypeNode); - } - - private void checkSpaceSeparatedObjectMembers(ObjectBodyContext objectBodyContext) { - assert objectBodyContext.objectMember() != null; - if (objectBodyContext.objectMember().size() < 2) { - return; - } - ObjectMemberContext prevMember = null; - for (var member : objectBodyContext.objectMember()) { - if (prevMember == null) { - prevMember = member; - continue; + case 2 -> { + var identifier1 = identifiers.get(0); + var identifier2 = identifiers.get(1); + yield new ResolveQualifiedDeclaredTypeNode( + createSourceSection(ctx), + createSourceSection(identifier1), + createSourceSection(identifier2), + org.pkl.core.runtime.Identifier.localProperty(identifier1.getValue()), + org.pkl.core.runtime.Identifier.get(identifier2.getValue())); } - var startIndex = member.getStart().getStartIndex(); - var prevStopIndex = prevMember.getStop().getStopIndex(); - if (startIndex - prevStopIndex == 1) { - throw exceptionBuilder() - .evalError("unseparatedObjectMembers") - .withSourceSection(createSourceSection(member)) - .build(); - } - } + default -> + throw exceptionBuilder() + .evalError("invalidTypeName", ctx.text()) + .withSourceSection(createSourceSection(ctx)) + .build(); + }; } private ExpressionNode doVisitObjectBody( - List ctxs, ExpressionNode parentNode) { - for (var ctx : ctxs) { + List bodies, ExpressionNode parentNode) { + for (var ctx : bodies) { parentNode = doVisitObjectBody(ctx, parentNode); } return parentNode; } - private ExpressionNode doVisitObjectBody(ObjectBodyContext ctx, ExpressionNode parentNode) { - checkClosingDelimiter(ctx.err, "}", ctx.stop); + private ExpressionNode doVisitObjectBody(ObjectBody body, ExpressionNode parentNode) { return symbolTable.enterObjectScope( (scope) -> { - var objectMemberCtx = ctx.objectMember(); - if (objectMemberCtx.isEmpty()) { + var objectMembers = body.getMembers(); + if (objectMembers.isEmpty()) { return EmptyObjectLiteralNodeGen.create( - createSourceSection(ctx.getParent()), parentNode); + Objects.requireNonNull(createSourceSection(body.parent())), parentNode); } - var sourceSection = createSourceSection(ctx.getParent()); + var sourceSection = createSourceSection(body.parent()); - var parametersDescriptorBuilder = createFrameDescriptorBuilder(ctx); - var parameterTypes = doVisitParameterTypes(ctx); + var parametersDescriptorBuilder = createFrameDescriptorBuilder(body); + var parameterTypes = doVisitParameterTypes(body); var members = EconomicMaps.create(); var elements = new ArrayList(); @@ -1059,15 +2078,15 @@ public final class AstBuilder extends AbstractAstBuilder { var values = new ArrayList(); var isConstantKeyNodes = true; - checkSpaceSeparatedObjectMembers(ctx); - for (var memberCtx : objectMemberCtx) { - if (memberCtx instanceof ObjectPropertyContext propertyCtx) { - addProperty(members, doVisitObjectProperty(propertyCtx)); + checkSpaceSeparatedObjectMembers(body); + for (var memberCtx : objectMembers) { + if (memberCtx instanceof ObjectProperty property) { + addProperty(members, doVisitObjectProperty(property)); continue; } - if (memberCtx instanceof ObjectEntryContext entryCtx) { - var keyAndValue = doVisitObjectEntry(entryCtx); + if (memberCtx instanceof ObjectEntry entry) { + var keyAndValue = doVisitObjectEntry(entry); var key = keyAndValue.first; keyNodes.add(key); isConstantKeyNodes = isConstantKeyNodes && key instanceof ConstantNode; @@ -1075,24 +2094,24 @@ public final class AstBuilder extends AbstractAstBuilder { continue; } - if (memberCtx instanceof ObjectElementContext elementCtx) { + if (memberCtx instanceof ObjectElement elementCtx) { var element = doVisitObjectElement(elementCtx); elements.add(element); continue; } - if (memberCtx instanceof ObjectMethodContext methodCtx) { + if (memberCtx instanceof ObjectMethod methodCtx) { addProperty(members, doVisitObjectMethod(methodCtx)); continue; } - assert memberCtx instanceof ForGeneratorContext - || memberCtx instanceof WhenGeneratorContext - || memberCtx instanceof MemberPredicateContext - || memberCtx instanceof ObjectSpreadContext; + assert memberCtx instanceof ForGenerator + || memberCtx instanceof WhenGenerator + || memberCtx instanceof MemberPredicate + || memberCtx instanceof ObjectSpread; // bail out and create GeneratorObjectLiteralNode instead // (but can't we easily reuse members/elements/keyNodes/values?) - return doVisitGeneratorObjectBody(ctx, parentNode); + return doVisitGeneratorObjectBody(body, parentNode); } var currentScope = symbolTable.getCurrentScope(); @@ -1168,36 +2187,183 @@ public final class AstBuilder extends AbstractAstBuilder { }); } - private void addConstantEntries( - EconomicMap members, - List keyNodes, - List values) { - - for (var i = 0; i < keyNodes.size(); i++) { - var key = ((ConstantNode) keyNodes.get(i)).getValue(); - var value = values.get(i); - var previousValue = EconomicMaps.put(members, key, value); - if (previousValue != null) { - CompilerDirectives.transferToInterpreter(); + private void checkSpaceSeparatedObjectMembers(ObjectBody objectBody) { + var members = objectBody.getMembers(); + if (members.size() < 2) { + return; + } + var previous = members.get(0).span(); + for (var i = 1; i < members.size(); i++) { + var member = members.get(i); + if (previous.adjacent(member.span())) { throw exceptionBuilder() - .evalError("duplicateDefinition", new ProgramValue("", key)) - .withSourceSection(value.getHeaderSection()) + .evalError("unseparatedObjectMembers") + .withSourceSection(createSourceSection(member.span())) .build(); } + previous = member.span(); } } - private ObjectMember doVisitObjectElement(ObjectElementContext ctx) { + private ObjectMember doVisitObjectProperty(ObjectProperty prop) { + var modifierNodes = prop.getModifiers(); + var propertyName = prop.getIdentifier(); + var modifiers = + doVisitModifiers( + modifierNodes, VmModifier.VALID_OBJECT_MEMBER_MODIFIERS, "invalidObjectMemberModifier"); + if (VmModifier.isConst(modifiers) && !VmModifier.isLocal(modifiers)) { + @SuppressWarnings("OptionalGetWithoutIsPresent") + var constModifierCtx = + modifierNodes.stream() + .filter((it) -> it.getValue() == ModifierValue.CONST) + .findFirst() + .get(); + throw exceptionBuilder() + .evalError("invalidConstObjectMemberModifier") + .withSourceSection(createSourceSection(constModifierCtx)) + .build(); + } + return doVisitObjectProperty( + createSourceSection(prop), + createSourceSection(propertyName), + modifiers, + propertyName.getValue(), + prop.getTypeAnnotation(), + prop.getExpr(), + prop.getBodyList()); + } + + private ObjectMember doVisitObjectProperty( + Node node, + List modifierNodes, + Identifier propertyName, + @Nullable TypeAnnotation typeAnn, + @Nullable Expr expr, + @Nullable List bodies) { + var modifiers = + doVisitModifiers( + modifierNodes, VmModifier.VALID_OBJECT_MEMBER_MODIFIERS, "invalidObjectMemberModifier"); + if (VmModifier.isConst(modifiers) && !VmModifier.isLocal(modifiers)) { + @SuppressWarnings("OptionalGetWithoutIsPresent") + var constModifierCtx = + modifierNodes.stream() + .filter((it) -> it.getValue() == ModifierValue.CONST) + .findFirst() + .get(); + throw exceptionBuilder() + .evalError("invalidConstObjectMemberModifier") + .withSourceSection(createSourceSection(constModifierCtx)) + .build(); + } + return doVisitObjectProperty( + createSourceSection(node), + createSourceSection(propertyName), + modifiers, + propertyName.getValue(), + typeAnn, + expr, + bodies); + } + + private ObjectMember doVisitObjectProperty( + SourceSection sourceSection, + SourceSection headerSection, + int modifiers, + String propertyName, + @Nullable TypeAnnotation typeAnn, + @Nullable Expr expr, + @Nullable List body) { + + var isLocal = VmModifier.isLocal(modifiers); + var identifier = org.pkl.core.runtime.Identifier.property(propertyName, isLocal); + + return symbolTable.enterProperty( + identifier, + getConstLevel(modifiers), + scope -> { + if (isLocal) { + if (expr == null + && typeAnn != null) { // module property that has type annotation but no value + throw missingLocalPropertyValue(typeAnn); + } + } else { + if (typeAnn != null) { + throw exceptionBuilder() + .evalError("nonLocalObjectPropertyCannotHaveTypeAnnotation") + .withSourceSection(createSourceSection(typeAnn.getType())) + .build(); + } + } + + ExpressionNode bodyNode; + if (body != null && !body.isEmpty()) { // foo { ... } + if (isLocal) { + throw exceptionBuilder() + .evalError("cannotAmendLocalPropertyDefinition") + .withSourceSection(createSourceSection(body.get(0))) + .build(); + } + bodyNode = + doVisitObjectBody( + body, + new ReadSuperPropertyNode( + unavailableSourceSection(), + scope.getName(), + // Never need a const check for amends declarations. In `foo { ... }`: + // 1. if `foo` is const, i.e. `const foo { ... }`, `super.foo` is required + // to be const (the const-ness of a property cannot be changed) + // 2. if in a const scope (i.e. `const bar = new { foo { ... } }`), + // `super.foo` does not reference something outside the scope. + false)); + } else { // foo = ... + assert expr != null; + bodyNode = visitExpr(expr); + } + + return isLocal + ? VmUtils.createLocalObjectProperty( + language, + sourceSection, + headerSection, + scope.getName(), + scope.getQualifiedName(), + scope.buildFrameDescriptor(), + modifiers, + bodyNode, + visitTypeAnnotation(typeAnn)) + : VmUtils.createObjectProperty( + language, + sourceSection, + headerSection, + scope.getName(), + scope.getQualifiedName(), + scope.buildFrameDescriptor(), + modifiers, + bodyNode, + null); + }); + } + + private Pair doVisitObjectEntry(ObjectEntry entry) { + var keyNode = visitExpr(entry.getKey()); + + var member = + doVisitObjectEntryBody( + createSourceSection(entry), keyNode, entry.getValue(), entry.getBodyList()); + return Pair.of(keyNode, member); + } + + private ObjectMember doVisitObjectElement(ObjectElement element) { var isForGeneratorScope = symbolTable.getCurrentScope().isForGeneratorScope(); return symbolTable.enterEntry( null, scope -> { - var elementNode = visitExpr(ctx.expr()); + var elementNode = visitExpr(element.getExpr()); var modifier = VmModifier.ELEMENT; var member = new ObjectMember( - createSourceSection(ctx), + createSourceSection(element), elementNode.getSourceSection(), modifier, null, @@ -1218,772 +2384,115 @@ public final class AstBuilder extends AbstractAstBuilder { }); } - private Pair doVisitObjectEntry(ObjectEntryContext ctx) { - checkClosingDelimiter(ctx.err1, "]", ctx.k.stop); - if (ctx.err2 != null) { - throw ctx.err1.getStartIndex() == ctx.err2.getStartIndex() - 1 - ? wrongDelimiter("]", "]]", ctx.err1.getStartIndex()) - : danglingDelimiter("]", ctx.err2.getStartIndex()); - } - - var keyNode = visitExpr(ctx.k); - var member = doVisitObjectEntryBody(createSourceSection(ctx), keyNode, ctx.v, ctx.objectBody()); - return Pair.of(keyNode, member); + private ObjectMember doVisitObjectMethod(ObjectMethod method) { + return doVisitObjectMethod(method, false); } - private ObjectMember doVisitObjectEntryBody( - SourceSection sourceSection, - ExpressionNode keyNode, - @Nullable ExprContext valueCtx, - List objectBodyCtxs) { - var isForGeneratorScope = symbolTable.getCurrentScope().isForGeneratorScope(); - return symbolTable.enterEntry( - keyNode, - scope -> { - var modifier = VmModifier.ENTRY; - var member = - new ObjectMember( - sourceSection, - keyNode.getSourceSection(), - modifier, - null, - scope.getQualifiedName()); - if (valueCtx != null) { // ["key"] = value - var valueNode = visitExpr(valueCtx); - if (valueNode instanceof ConstantNode constantNode) { - member.initConstantValue(constantNode); - } else { - if (isForGeneratorScope) { - valueNode = new RestoreForBindingsNode(valueNode); - } - member.initMemberNode( - ElementOrEntryNodeGen.create( - language, scope.buildFrameDescriptor(), member, valueNode)); - } - } else { // ["key"] { ... } - var objectBody = - doVisitObjectBody( - objectBodyCtxs, - new ReadSuperEntryNode(unavailableSourceSection(), new GetMemberKeyNode())); - if (isForGeneratorScope) { - objectBody = new RestoreForBindingsNode(objectBody); - } - member.initMemberNode( - ElementOrEntryNodeGen.create( - language, scope.buildFrameDescriptor(), member, objectBody)); - } - - return member; - }); + private ObjectMember doVisitObjectMethod(ObjectMethod method, boolean isModuleMethod) { + return doVisitObjectMethod( + method, + method.getModifiers(), + method.headerSpan(), + method.getIdentifier(), + method.getParamList(), + method.getTypeParameterList(), + method.getExpr(), + method.getTypeAnnotation(), + isModuleMethod); } - @Override - public ExpressionNode visitAnnotation(AnnotationContext ctx) { - var verifyNode = new CheckIsAnnotationClassNode(visitType(ctx.type())); - - var bodyCtx = ctx.objectBody(); - if (bodyCtx == null) { - var currentScope = symbolTable.getCurrentScope(); - //noinspection ConstantConditions - return PropertiesLiteralNodeGen.create( - createSourceSection(ctx), - language, - currentScope.getQualifiedName(), - currentScope.isCustomThisScope(), - null, - new UnresolvedTypeNode[0], - EconomicMaps.create(), - verifyNode); - } - - return symbolTable.enterAnnotationScope((scope) -> doVisitObjectBody(bodyCtx, verifyNode)); - } - - private ExpressionNode[] doVisitAnnotations(List ctxs) { - return ctxs.stream().map(this::visitAnnotation).toArray(ExpressionNode[]::new); - } - - @Override - public Integer visitModifier(ModifierContext ctx) { - return switch (ctx.t.getType()) { - case PklLexer.EXTERNAL -> VmModifier.EXTERNAL; - case PklLexer.ABSTRACT -> VmModifier.ABSTRACT; - case PklLexer.OPEN -> VmModifier.OPEN; - case PklLexer.LOCAL -> VmModifier.LOCAL; - case PklLexer.HIDDEN_ -> VmModifier.HIDDEN; - case PklLexer.FIXED -> VmModifier.FIXED; - case PklLexer.CONST -> VmModifier.CONST; - default -> throw createUnexpectedTokenError(ctx.t); - }; - } - - private int doVisitModifiers( - List contexts, int validModifiers, String errorMessage) { - - var result = VmModifier.NONE; - for (var ctx : contexts) { - int modifier = visitModifier(ctx); - if ((modifier & validModifiers) == 0) { - throw exceptionBuilder() - .evalError(errorMessage, ctx.t.getText()) - .withSourceSection(createSourceSection(ctx)) - .build(); - } - result += modifier; - } - - // flag modifier combinations that are never valid right away - - if (VmModifier.isExternal(result) && !ModuleKeys.isStdLibModule(moduleKey)) { - throw exceptionBuilder() - .evalError("cannotDefineExternalMember") - .withSourceSection(createSourceSection(contexts, PklLexer.EXTERNAL)) - .build(); - } - - if (VmModifier.isLocal(result) && VmModifier.isHidden(result)) { - throw exceptionBuilder() - .evalError("redundantHiddenModifier") - .withSourceSection(createSourceSection(contexts, PklLexer.HIDDEN_)) - .build(); - } - - if (VmModifier.isLocal(result) && VmModifier.isFixed(result)) { - throw exceptionBuilder() - .evalError("redundantFixedModifier") - .withSourceSection(createSourceSection(contexts, PklLexer.FIXED)) - .build(); - } - - if (VmModifier.isAbstract(result) && VmModifier.isOpen(result)) { - throw exceptionBuilder() - .evalError("redundantOpenModifier") - .withSourceSection(createSourceSection(contexts, PklLexer.OPEN)) - .build(); - } - - return result; - } - - @Override - public UnresolvedMethodNode visitClassMethod(ClassMethodContext ctx) { - var headerCtx = ctx.methodHeader(); - var headerSection = createSourceSection(headerCtx); - - var typeParameters = visitTypeParameterList(headerCtx.typeParameterList()); - + private ObjectMember doVisitObjectMethod( + Node method, + List modifierNodes, + Span headerSpan, + Identifier identifier, + ParameterList paramList, + @Nullable TypeParameterList typeParamList, + Expr expr, + @Nullable TypeAnnotation typeAnnotation, + boolean isModuleMethod) { var modifiers = doVisitModifiers( - headerCtx.modifier(), VmModifier.VALID_METHOD_MODIFIERS, "invalidMethodModifier"); + modifierNodes, VmModifier.VALID_OBJECT_MEMBER_MODIFIERS, "invalidObjectMemberModifier"); - var isLocal = VmModifier.isLocal(modifiers); - var methodName = Identifier.method(headerCtx.Identifier().getText(), isLocal); + if (!VmModifier.isLocal(modifiers)) { + throw exceptionBuilder() + .evalError(isModuleMethod ? "moduleMethodMustBeLocal" : "objectMethodMustBeLocal") + .withSourceSection(createSourceSection(headerSpan)) + .build(); + } - var bodyContext = ctx.expr(); - var paramListCtx = headerCtx.parameterList(); - var descriptorBuilder = createFrameDescriptorBuilder(paramListCtx); - var paramCount = paramListCtx.ts.size(); + var methodName = org.pkl.core.runtime.Identifier.method(identifier.getValue(), true); + + var frameDescriptorBuilder = createFrameDescriptorBuilder(paramList); return symbolTable.enterMethod( methodName, getConstLevel(modifiers), - descriptorBuilder, - typeParameters, + frameDescriptorBuilder, + List.of(), scope -> { - ExpressionNode bodyNode; - if (bodyContext != null) { - if (VmModifier.isExternal(modifiers)) { - throw exceptionBuilder() - .evalError("externalMemberCannotHaveBody") - .withSourceSection(headerSection) - .build(); - } - if (VmModifier.isAbstract(modifiers)) { - throw exceptionBuilder() - .evalError("abstractMemberCannotHaveBody") - .withSourceSection(headerSection) - .build(); - } - bodyNode = visitExpr(bodyContext); - } else { - if (VmModifier.isExternal(modifiers)) { - bodyNode = - externalMemberRegistry.getFunctionBody( - scope.getQualifiedName(), headerSection, paramCount); - if (bodyNode instanceof LanguageAwareNode languageAwareNode) { - languageAwareNode.initLanguage(language); - } - } else if (VmModifier.isAbstract(modifiers)) { - bodyNode = - new CannotInvokeAbstractFunctionNode(headerSection, scope.getQualifiedName()); - } else { - throw exceptionBuilder() - .evalError("missingMethodBody", methodName) - .withSourceSection(headerSection) - .build(); - } + if (typeParamList != null) { + throw exceptionBuilder() + .evalError("cannotDeclareTypeParameter") + .withSourceSection(createSourceSection(typeParamList)) + .build(); } - return new UnresolvedMethodNode( - language, - createSourceSection(ctx), - headerSection, - scope.buildFrameDescriptor(), - createSourceSection(ctx.t), - doVisitAnnotations(ctx.annotation()), - modifiers, - methodName, - scope.getQualifiedName(), - paramCount, - typeParameters, - doVisitParameterTypes(paramListCtx), - visitTypeAnnotation(headerCtx.typeAnnotation()), - isMethodReturnTypeChecked, - bodyNode); - }); - } - - @Override - public ExpressionNode visitFunctionLiteral(FunctionLiteralContext ctx) { - var sourceSection = createSourceSection(ctx); - var paramCtx = ctx.parameterList(); - var descriptorBuilder = createFrameDescriptorBuilder(paramCtx); - var paramCount = paramCtx.ts.size(); - - if (paramCount > 5) { - throw exceptionBuilder() - .evalError("tooManyFunctionParameters") - .withSourceSection(sourceSection) - .build(); - } - - var isCustomThisScope = symbolTable.getCurrentScope().isCustomThisScope(); - - return symbolTable.enterLambda( - descriptorBuilder, - scope -> { - var expr = visitExpr(ctx.expr()); - var functionNode = - new UnresolvedFunctionNode( - language, - scope.buildFrameDescriptor(), - new Lambda(sourceSection, scope.getQualifiedName()), - paramCount, - doVisitParameterTypes(paramCtx), - null, - expr); - - return new FunctionLiteralNode(sourceSection, functionNode, isCustomThisScope); - }); - } - - @Override - public ConstantValueNode visitNullLiteral(NullLiteralContext ctx) { - return new ConstantValueNode(createSourceSection(ctx), VmNull.withoutDefault()); - } - - @Override - public ExpressionNode visitTrueLiteral(TrueLiteralContext ctx) { - return new TrueLiteralNode(createSourceSection(ctx)); - } - - @Override - public Object visitFalseLiteral(FalseLiteralContext ctx) { - return new FalseLiteralNode(createSourceSection(ctx)); - } - - @Override - public IntLiteralNode visitIntLiteral(IntLiteralContext ctx) { - var section = createSourceSection(ctx); - var text = ctx.IntLiteral().getText(); - - var radix = 10; - if (text.startsWith("0x") || text.startsWith("0b") || text.startsWith("0o")) { - var type = text.charAt(1); - if (type == 'x') { - radix = 16; - } else if (type == 'b') { - radix = 2; - } else { - radix = 8; - } - - text = text.substring(2); - if (text.startsWith("_")) { - invalidSeparatorPosition(source.createSection(ctx.getStart().getStartIndex() + 2, 1)); - } - } - - // relies on grammar rule nesting depth, but a breakage won't go unnoticed by tests - if (ctx.getParent() instanceof UnaryMinusExprContext) { - // handle negation here to make parsing of base.MinInt work - // also moves negation from runtime to parse time - text = "-" + text; - } - - text = text.replace("_", ""); - try { - var num = Long.parseLong(text, radix); - return new IntLiteralNode(section, num); - } catch (NumberFormatException e) { - throw exceptionBuilder().evalError("intTooLarge", text).withSourceSection(section).build(); - } - } - - @Override - public FloatLiteralNode visitFloatLiteral(FloatLiteralContext ctx) { - var section = createSourceSection(ctx); - var text = ctx.FloatLiteral().getText(); - // relies on grammar rule nesting depth, but a breakage won't go unnoticed by tests - if (ctx.getParent() instanceof UnaryMinusExprContext) { - // handle negation here for consistency with visitIntegerLiteral - // also moves negation from runtime to parse time - text = "-" + text; - } - - var dotIdx = text.indexOf('.'); - if (dotIdx != -1 && text.charAt(dotIdx + 1) == '_') { - invalidSeparatorPosition( - source.createSection(ctx.getStart().getStartIndex() + dotIdx + 1, 1)); - } - var exponentIdx = text.indexOf('e'); - if (exponentIdx == -1) { - exponentIdx = text.indexOf('E'); - } - if (exponentIdx != -1 && text.charAt(exponentIdx + 1) == '_') { - invalidSeparatorPosition( - source.createSection(ctx.getStart().getStartIndex() + exponentIdx + 1, 1)); - } - - text = text.replace("_", ""); - try { - var num = Double.parseDouble(text); - return new FloatLiteralNode(section, num); - } catch (NumberFormatException e) { - throw exceptionBuilder().evalError("floatTooLarge", text).withSourceSection(section).build(); - } - } - - @Override - public Object visitSingleLineStringLiteral(SingleLineStringLiteralContext ctx) { - checkSingleLineStringDelimiters(ctx.t, ctx.t2); - - var singleParts = ctx.singleLineStringPart(); - if (singleParts.isEmpty()) { - return new ConstantValueNode(createSourceSection(ctx), ""); - } - - if (singleParts.size() == 1) { - var ts = singleParts.get(0).ts; - if (!ts.isEmpty()) { - return new ConstantValueNode( - createSourceSection(ctx), doVisitSingleLineConstantStringPart(ts)); - } - } - - return new InterpolatedStringLiteralNode( - createSourceSection(ctx), - singleParts.stream().map(this::visitSingleLineStringPart).toArray(ExpressionNode[]::new)); - } - - @Override - public Object visitMultiLineStringLiteral(MultiLineStringLiteralContext ctx) { - var multiPart = ctx.multiLineStringPart(); - - if (multiPart.isEmpty()) { - throw exceptionBuilder() - .evalError("stringContentMustBeginOnNewLine") - .withSourceSection(createSourceSection(ctx.t2)) - .build(); - } - - var firstPart = multiPart.get(0); - if (firstPart.e != null || firstPart.ts.get(0).getType() != PklLexer.MLNewline) { - throw exceptionBuilder() - .evalError("stringContentMustBeginOnNewLine") - .withSourceSection( - firstPart.e != null - ? startOf(firstPart.MLInterpolation()) - : startOf(firstPart.ts.get(0))) - .build(); - } - - var lastPart = multiPart.get(multiPart.size() - 1); - var commonIndent = getCommonIndent(lastPart, ctx.t2); - - if (multiPart.size() == 1) { - return new ConstantValueNode( - createSourceSection(ctx), - doVisitMultiLineConstantStringPart(firstPart.ts, commonIndent, true, true)); - } - - final var multiPartExprs = new ExpressionNode[multiPart.size()]; - var lastIndex = multiPart.size() - 1; - - for (var i = 0; i <= lastIndex; i++) { - multiPartExprs[i] = - doVisitMultiLineStringPart(multiPart.get(i), commonIndent, i == 0, i == lastIndex); - } - - return new InterpolatedStringLiteralNode(createSourceSection(ctx), multiPartExprs); - } - - @Override - public String visitStringConstant(StringConstantContext ctx) { - checkSingleLineStringDelimiters(ctx.t, ctx.t2); - return doVisitSingleLineConstantStringPart(ctx.ts); - } - - @Override - public ExpressionNode visitSingleLineStringPart(SingleLineStringPartContext ctx) { - if (ctx.e != null) { - return ToStringNodeGen.create(createSourceSection(ctx), visitExpr(ctx.e)); - } - - return new ConstantValueNode( - createSourceSection(ctx), doVisitSingleLineConstantStringPart(ctx.ts)); - } - - @Override - public ExpressionNode visitMultiLineStringPart(MultiLineStringPartContext ctx) { - throw exceptionBuilder().unreachableCode().build(); - } - - private ExpressionNode createResolveVariableNode(SourceSection section, Identifier propertyName) { - var scope = symbolTable.getCurrentScope(); - return new ResolveVariableNode( - section, - propertyName, - isBaseModule, - scope.isCustomThisScope(), - scope.getConstLevel(), - scope.getConstDepth()); - } - - private ExpressionNode doVisitListLiteral(ExprContext ctx, ArgumentListContext argListCtx) { - var elementNodes = createCollectionArgumentNodes(argListCtx); - - if (elementNodes.first.length == 0) { - return new ConstantValueNode(VmList.EMPTY); - } - - return elementNodes.second - ? new ConstantValueNode( - createSourceSection(ctx), VmList.createFromConstantNodes(elementNodes.first)) - : new ListLiteralNode(createSourceSection(ctx), elementNodes.first); - } - - private ExpressionNode doVisitSetLiteral(ExprContext ctx, ArgumentListContext argListCtx) { - var elementNodes = createCollectionArgumentNodes(argListCtx); - - if (elementNodes.first.length == 0) { - return new ConstantValueNode(VmSet.EMPTY); - } - - return elementNodes.second - ? new ConstantValueNode( - createSourceSection(ctx), VmSet.createFromConstantNodes(elementNodes.first)) - : new SetLiteralNode(createSourceSection(ctx), elementNodes.first); - } - - private ExpressionNode doVisitMapLiteral(ExprContext ctx, ArgumentListContext argListCtx) { - var keyAndValueNodes = createCollectionArgumentNodes(argListCtx); - - if (keyAndValueNodes.first.length == 0) { - return new ConstantValueNode(VmMap.EMPTY); - } - - if (keyAndValueNodes.first.length % 2 != 0) { - throw exceptionBuilder() - .evalError("missingMapValue") - .withSourceSection(createSourceSection(ctx.stop)) - .build(); - } - - return keyAndValueNodes.second - ? new ConstantValueNode( - createSourceSection(ctx), VmMap.createFromConstantNodes(keyAndValueNodes.first)) - : new MapLiteralNode(createSourceSection(ctx), keyAndValueNodes.first); - } - - private Pair createCollectionArgumentNodes(ArgumentListContext ctx) { - checkCommaSeparatedElements(ctx, ctx.es, ctx.errs); - checkClosingDelimiter(ctx.err, ")", ctx.stop); - - var exprCtxs = ctx.expr(); - var elementNodes = new ExpressionNode[exprCtxs.size()]; - var isConstantNodes = true; - - for (var i = 0; i < elementNodes.length; i++) { - var exprNode = visitExpr(exprCtxs.get(i)); - elementNodes[i] = exprNode; - isConstantNodes = isConstantNodes && exprNode instanceof ConstantNode; - } - - return Pair.of(elementNodes, isConstantNodes); - } - - @Override - public ExpressionNode visitExpr(ExprContext ctx) { - return (ExpressionNode) ctx.accept(this); - } - - @Override - public Object visitComparisonExpr(ComparisonExprContext ctx) { - return switch (ctx.t.getType()) { - case PklLexer.LT -> - LessThanNodeGen.create(createSourceSection(ctx), visitExpr(ctx.l), visitExpr(ctx.r)); - case PklLexer.GT -> - GreaterThanNodeGen.create(createSourceSection(ctx), visitExpr(ctx.l), visitExpr(ctx.r)); - case PklLexer.LTE -> - LessThanOrEqualNodeGen.create( - createSourceSection(ctx), visitExpr(ctx.l), visitExpr(ctx.r)); - case PklLexer.GTE -> - GreaterThanOrEqualNodeGen.create( - createSourceSection(ctx), visitExpr(ctx.l), visitExpr(ctx.r)); - default -> throw createUnexpectedTokenError(ctx.t); - }; - } - - @Override - public Object visitEqualityExpr(EqualityExprContext ctx) { - return switch (ctx.t.getType()) { - case PklLexer.EQUAL -> - EqualNodeGen.create(createSourceSection(ctx), visitExpr(ctx.l), visitExpr(ctx.r)); - case PklLexer.NOT_EQUAL -> - NotEqualNodeGen.create(createSourceSection(ctx), visitExpr(ctx.l), visitExpr(ctx.r)); - default -> throw createUnexpectedTokenError(ctx.t); - }; - } - - @Override - public ObjectMember visitImportClause(ImportClauseContext ctx) { - var importNode = doVisitImport(ctx.t.getType(), ctx, ctx.stringConstant()); - var moduleKey = moduleResolver.resolve(importNode.getImportUri()); - var importName = - Identifier.property( - ctx.Identifier() != null - ? ctx.Identifier().getText() - : IoUtils.inferModuleName(moduleKey), - true); - - return symbolTable.enterProperty( - importName, - ConstLevel.NONE, - scope -> { - var modifiers = VmModifier.IMPORT | VmModifier.LOCAL | VmModifier.CONST; - if (ctx.IMPORT_GLOB() != null) { - modifiers = modifiers | VmModifier.GLOB; - } - var result = + var member = new ObjectMember( - importNode.getSourceSection(), - importNode.getSourceSection(), + createSourceSection(method), + createSourceSection(headerSpan), modifiers, scope.getName(), scope.getQualifiedName()); + var body = visitExpr(expr); + var node = + new ObjectMethodNode( + language, + scope.buildFrameDescriptor(), + member, + body, + paramList.getParameters().size(), + doVisitParameterTypes(paramList), + visitTypeAnnotation(typeAnnotation)); - result.initMemberNode( - new UntypedObjectMemberNode( - language, scope.buildFrameDescriptor(), result, importNode)); - - return result; + member.initMemberNode(node); + return member; }); } - private URI resolveImport(String importUri, StringConstantContext importUriCtx) { - URI parsedUri; - try { - parsedUri = IoUtils.toUri(importUri); - } catch (URISyntaxException e) { - throw exceptionBuilder() - .evalError("invalidModuleUri", importUri) - .withHint(e.getReason()) - .withSourceSection(createSourceSection(importUriCtx)) - .build(); - } - URI resolvedUri; - var context = VmContext.get(null); - try { - resolvedUri = IoUtils.resolve(context.getSecurityManager(), moduleKey, parsedUri); - } catch (FileNotFoundException e) { - - var exceptionBuilder = - exceptionBuilder() - .evalError("cannotFindModule", importUri) - .withSourceSection(createSourceSection(importUriCtx)); - var path = parsedUri.getPath(); - if (path != null && path.contains("\\")) { - exceptionBuilder.withHint( - "To resolve modules in nested directories, use `/` as the directory separator."); - } - throw exceptionBuilder.build(); - } catch (URISyntaxException e) { - throw exceptionBuilder() - .evalError("invalidModuleUri", importUri) - .withHint(e.getReason()) - .withSourceSection(createSourceSection(importUriCtx)) - .build(); - } catch (IOException e) { - throw exceptionBuilder() - .evalError("ioErrorLoadingModule", importUri) - .withCause(e) - .withSourceSection(createSourceSection(importUriCtx)) - .build(); - } catch (SecurityManagerException | PackageLoadError e) { - throw exceptionBuilder() - .withSourceSection(createSourceSection(importUriCtx)) - .withCause(e) - .build(); - } catch (VmException e) { - throw exceptionBuilder() - .evalError(e.getMessage(), e.getMessageArguments()) - .withCause(e.getCause()) - .withHint(e.getHint()) - .withSourceSection(createSourceSection(importUriCtx)) - .build(); - } catch (ExternalReaderProcessException e) { - throw exceptionBuilder() - .evalError("externalReaderFailure") - .withCause(e.getCause()) - .withSourceSection(createSourceSection(importUriCtx)) - .build(); - } - - if (!resolvedUri.isAbsolute()) { - throw exceptionBuilder() - .evalError("cannotHaveRelativeImport", moduleKey.getUri()) - .withSourceSection(createSourceSection(importUriCtx)) - .build(); - } - return resolvedUri; + private GeneratorObjectLiteralNode doVisitGeneratorObjectBody( + ObjectBody body, ExpressionNode parentNode) { + var parametersDescriptor = createFrameDescriptorBuilder(body); + var parameterTypes = doVisitParameterTypes(body); + var memberNodes = doVisitGeneratorMemberNodes(body.getMembers()); + var currentScope = symbolTable.getCurrentScope(); + //noinspection ConstantConditions + return GeneratorObjectLiteralNodeGen.create( + createSourceSection(body.parent()), + language, + currentScope.getQualifiedName(), + currentScope.isCustomThisScope(), + parametersDescriptor == null ? null : parametersDescriptor.build(), + parameterTypes, + memberNodes, + parentNode); } - @Override - public ExpressionNode visitQualifiedIdentifier(QualifiedIdentifierContext ctx) { - var firstToken = ctx.ts.get(0); - var result = - createResolveVariableNode(createSourceSection(firstToken), toIdentifier(firstToken)); - - for (var i = 1; i < ctx.ts.size(); i++) { - var token = ctx.ts.get(i); - result = ReadPropertyNodeGen.create(createSourceSection(token), toIdentifier(token), result); + private GeneratorMemberNode[] doVisitGeneratorMemberNodes( + List members) { + var result = new GeneratorMemberNode[members.size()]; + for (var i = 0; i < result.length; i++) { + result[i] = visitObjectMember(members.get(i)); } - return result; } - @Override - public Object visitNonNullExpr(NonNullExprContext ctx) { - return new NonNullNode(createSourceSection(ctx), visitExpr(ctx.expr())); - } - - @Override - public ExpressionNode visitUnaryMinusExpr(UnaryMinusExprContext ctx) { - var childCtx = ctx.expr(); - var childExpr = visitExpr(childCtx); - if (childCtx instanceof IntLiteralContext || childCtx instanceof FloatLiteralContext) { - // negation already handled (see visitIntLiteral/visitFloatLiteral) - return childExpr; - } - return UnaryMinusNodeGen.create(createSourceSection(ctx), childExpr); - } - - @Override - public ExpressionNode visitAdditiveExpr(AdditiveExprContext ctx) { - return switch (ctx.t.getType()) { - case PklLexer.PLUS -> - AdditionNodeGen.create(createSourceSection(ctx), visitExpr(ctx.l), visitExpr(ctx.r)); - case PklLexer.MINUS -> - SubtractionNodeGen.create(createSourceSection(ctx), visitExpr(ctx.l), visitExpr(ctx.r)); - default -> throw createUnexpectedTokenError(ctx.t); - }; - } - - @Override - public ExpressionNode visitMultiplicativeExpr(MultiplicativeExprContext ctx) { - return switch (ctx.t.getType()) { - case PklLexer.STAR -> - MultiplicationNodeGen.create( - createSourceSection(ctx), visitExpr(ctx.l), visitExpr(ctx.r)); - case PklLexer.DIV -> - DivisionNodeGen.create(createSourceSection(ctx), visitExpr(ctx.l), visitExpr(ctx.r)); - case PklLexer.INT_DIV -> - TruncatingDivisionNodeGen.create( - createSourceSection(ctx), visitExpr(ctx.l), visitExpr(ctx.r)); - case PklLexer.MOD -> - RemainderNodeGen.create(createSourceSection(ctx), visitExpr(ctx.l), visitExpr(ctx.r)); - default -> throw createUnexpectedTokenError(ctx.t); - }; - } - - @Override - public Object visitExponentiationExpr(ExponentiationExprContext ctx) { - return ExponentiationNodeGen.create( - createSourceSection(ctx), visitExpr(ctx.l), visitExpr(ctx.r)); - } - - @Override - public ExpressionNode visitLogicalAndExpr(LogicalAndExprContext ctx) { - return LogicalAndNodeGen.create(createSourceSection(ctx), visitExpr(ctx.r), visitExpr(ctx.l)); - } - - @Override - public ExpressionNode visitLogicalOrExpr(LogicalOrExprContext ctx) { - return LogicalOrNodeGen.create(createSourceSection(ctx), visitExpr(ctx.r), visitExpr(ctx.l)); - } - - @Override - public ExpressionNode visitLogicalNotExpr(LogicalNotExprContext ctx) { - return LogicalNotNodeGen.create(createSourceSection(ctx), visitExpr(ctx.expr())); - } - - @Override - public ExpressionNode visitQualifiedAccessExpr(QualifiedAccessExprContext ctx) { - if (ctx.argumentList() != null) { - return doVisitMethodAccessExpr(ctx); - } - - return doVisitPropertyInvocationExpr(ctx); - } - - private ExpressionNode doVisitMethodAccessExpr(QualifiedAccessExprContext ctx) { - var sourceSection = createSourceSection(ctx); - var functionName = toIdentifier(ctx.Identifier()); - var argCtx = ctx.argumentList(); - var receiver = visitExpr(ctx.expr()); - var needsConst = needsConst(receiver); - - if (ctx.t.getType() == PklLexer.QDOT) { - //noinspection ConstantConditions - return new NullPropagatingOperationNode( - sourceSection, - InvokeMethodVirtualNodeGen.create( - sourceSection, - functionName, - visitArgumentList(argCtx), - MemberLookupMode.EXPLICIT_RECEIVER, - needsConst, - PropagateNullReceiverNodeGen.create(unavailableSourceSection(), receiver), - GetClassNodeGen.create(null))); - } - - assert ctx.t.getType() == PklLexer.DOT; - //noinspection ConstantConditions - return InvokeMethodVirtualNodeGen.create( - sourceSection, - functionName, - visitArgumentList(argCtx), - MemberLookupMode.EXPLICIT_RECEIVER, - needsConst, - receiver, - GetClassNodeGen.create(null)); - } - - private ExpressionNode doVisitPropertyInvocationExpr(QualifiedAccessExprContext ctx) { - var sourceSection = createSourceSection(ctx); - var propertyName = toIdentifier(ctx.Identifier()); - var receiver = visitExpr(ctx.expr()); + private ExpressionNode doVisitPropertyInvocationExpr(QualifiedAccessExpr expr) { + var sourceSection = createSourceSection(expr); + var propertyName = toIdentifier(expr.getIdentifier().getValue()); + var receiver = visitExpr(expr.getExpr()); if (receiver instanceof IntLiteralNode intLiteralNode) { var durationUnit = VmDuration.toUnit(propertyName); @@ -2018,7 +2527,7 @@ public final class AstBuilder extends AbstractAstBuilder { } var needsConst = needsConst(receiver); - if (ctx.t.getType() == PklLexer.QDOT) { + if (expr.isNullable()) { return new NullPropagatingOperationNode( sourceSection, ReadPropertyNodeGen.create( @@ -2028,95 +2537,184 @@ public final class AstBuilder extends AbstractAstBuilder { PropagateNullReceiverNodeGen.create(unavailableSourceSection(), receiver))); } - assert ctx.t.getType() == PklLexer.DOT; return ReadPropertyNodeGen.create(sourceSection, propertyName, needsConst, receiver); } - @Override - public ExpressionNode visitSuperAccessExpr(SuperAccessExprContext ctx) { - var sourceSection = createSourceSection(ctx); - var memberName = toIdentifier(ctx.Identifier()); - var argCtx = ctx.argumentList(); - var currentScope = symbolTable.getCurrentScope(); - var needsConst = - currentScope.getConstLevel() == ConstLevel.ALL && currentScope.getConstDepth() == -1; + private ExpressionNode doVisitMethodAccessExpr(QualifiedAccessExpr expr) { + var sourceSection = createSourceSection(expr); + var functionName = toIdentifier(expr.getIdentifier().getValue()); + var argCtx = expr.getArgumentList(); + var receiver = visitExpr(expr.getExpr()); + var needsConst = needsConst(receiver); - if (argCtx != null) { // supermethod call - if (!symbolTable.getCurrentScope().isClassMemberScope()) { + if (expr.isNullable()) { + //noinspection ConstantConditions + return new NullPropagatingOperationNode( + sourceSection, + InvokeMethodVirtualNodeGen.create( + sourceSection, + functionName, + visitArgumentList(argCtx), + MemberLookupMode.EXPLICIT_RECEIVER, + needsConst, + PropagateNullReceiverNodeGen.create(unavailableSourceSection(), receiver), + GetClassNodeGen.create(null))); + } + + //noinspection ConstantConditions + return InvokeMethodVirtualNodeGen.create( + sourceSection, + functionName, + visitArgumentList(argCtx), + MemberLookupMode.EXPLICIT_RECEIVER, + needsConst, + receiver, + GetClassNodeGen.create(null)); + } + + private void addConstantEntries( + EconomicMap members, + List keyNodes, + List values) { + + for (var i = 0; i < keyNodes.size(); i++) { + var key = ((ConstantNode) keyNodes.get(i)).getValue(); + var value = values.get(i); + var previousValue = EconomicMaps.put(members, key, value); + if (previousValue != null) { + CompilerDirectives.transferToInterpreter(); throw exceptionBuilder() - .evalError("cannotInvokeSupermethodFromHere") - .withSourceSection(sourceSection) + .evalError("duplicateDefinition", new ProgramValue("", key)) + .withSourceSection(value.getHeaderSection()) .build(); } - - return InvokeSuperMethodNodeGen.create( - sourceSection, memberName, visitArgumentList(argCtx), needsConst); } - - // superproperty call - return new ReadSuperPropertyNode(createSourceSection(ctx), memberName, needsConst); } - @Override - public ExpressionNode visitSuperSubscriptExpr(SuperSubscriptExprContext ctx) { - checkClosingDelimiter(ctx.err, "]", ctx.e.stop); + private int doVisitModifiers( + List modifiers, int validModifiers, String errorMessage) { - return new ReadSuperEntryNode(createSourceSection(ctx), visitExpr(ctx.e)); - } - - @Override - public Object visitPipeExpr(PipeExprContext ctx) { - return PipeNodeGen.create(createSourceSection(ctx), visitExpr(ctx.l), visitExpr(ctx.r)); - } - - @Override - public ExpressionNode visitNullCoalesceExpr(NullCoalesceExprContext ctx) { - return NullCoalescingNodeGen.create( - createSourceSection(ctx), visitExpr(ctx.r), visitExpr(ctx.l)); - } - - @Override - public ExpressionNode visitUnqualifiedAccessExpr(UnqualifiedAccessExprContext ctx) { - var identifier = toIdentifier(ctx.Identifier()); - var argListCtx = ctx.argumentList(); - - if (argListCtx == null) { - return createResolveVariableNode(createSourceSection(ctx), identifier); + var result = VmModifier.NONE; + for (var ctx : modifiers) { + int modifier = visitModifier(ctx); + if ((modifier & validModifiers) == 0) { + throw exceptionBuilder() + .evalError(errorMessage, ctx.getValue().name().toLowerCase()) + .withSourceSection(createSourceSection(ctx)) + .build(); + } + result += modifier; } - // TODO: make sure that no user-defined List/Set/Map method is in scope - // TODO: support qualified calls (e.g., `import "pkl:base"; x = base.List()/Set()/Map()`) for - // correctness - if (identifier == Identifier.LIST) { - return doVisitListLiteral(ctx, argListCtx); + // flag modifier combinations that are never valid right away + + if (VmModifier.isExternal(result) && !ModuleKeys.isStdLibModule(moduleKey)) { + throw exceptionBuilder() + .evalError("cannotDefineExternalMember") + .withSourceSection(createSourceSection(modifiers, ModifierValue.EXTERNAL)) + .build(); } - if (identifier == Identifier.SET) { - return doVisitSetLiteral(ctx, argListCtx); + if (VmModifier.isLocal(result) && VmModifier.isHidden(result)) { + throw exceptionBuilder() + .evalError("redundantHiddenModifier") + .withSourceSection(createSourceSection(modifiers, ModifierValue.HIDDEN)) + .build(); } - if (identifier == Identifier.MAP) { - return doVisitMapLiteral(ctx, argListCtx); + if (VmModifier.isLocal(result) && VmModifier.isFixed(result)) { + throw exceptionBuilder() + .evalError("redundantFixedModifier") + .withSourceSection(createSourceSection(modifiers, ModifierValue.FIXED)) + .build(); } - var scope = symbolTable.getCurrentScope(); + if (VmModifier.isAbstract(result) && VmModifier.isOpen(result)) { + throw exceptionBuilder() + .evalError("redundantOpenModifier") + .withSourceSection(createSourceSection(modifiers, ModifierValue.OPEN)) + .build(); + } - return new ResolveMethodNode( - createSourceSection(ctx), - identifier, - visitArgumentList(argListCtx), - isBaseModule, - scope.isCustomThisScope(), - scope.getConstLevel(), - scope.getConstDepth()); + return result; } - @Override - public ExpressionNode[] visitArgumentList(ArgumentListContext ctx) { - checkCommaSeparatedElements(ctx, ctx.es, ctx.errs); - checkClosingDelimiter(ctx.err, ")", ctx.stop); + private UnresolvedTypeNode[] doVisitParameterTypes(ObjectBody body) { + return doVisitParameterTypes(body.getParameters()); + } - return ctx.es.stream().map(this::visitExpr).toArray(ExpressionNode[]::new); + private UnresolvedTypeNode[] doVisitParameterTypes(ParameterList paramList) { + return doVisitParameterTypes(paramList.getParameters()); + } + + private UnresolvedTypeNode[] doVisitParameterTypes(List params) { + var typeNodes = new UnresolvedTypeNode[params.size()]; + for (int i = 0; i < typeNodes.length; i++) { + if (params.get(i) instanceof TypedIdentifier typedIdentifier) { + typeNodes[i] = visitTypeAnnotation(typedIdentifier.getTypeAnnotation()); + } else { + typeNodes[i] = null; + } + } + return typeNodes; + } + + // TODO: use Set and checkDuplicateMember() to find duplicates between local and non-local + // properties + private void addProperty(EconomicMap objectMembers, ObjectMember property) { + if (EconomicMaps.put(objectMembers, property.getName(), property) != null) { + throw exceptionBuilder() + .evalError("duplicateDefinition", property.getName()) + .withSourceSection(property.getHeaderSection()) + .build(); + } + } + + private ObjectMember doVisitObjectEntryBody( + SourceSection sourceSection, + ExpressionNode keyNode, + @Nullable Expr valueCtx, + @Nullable List objectBodyCtxs) { + var isForGeneratorScope = symbolTable.getCurrentScope().isForGeneratorScope(); + return symbolTable.enterEntry( + keyNode, + scope -> { + var modifier = VmModifier.ENTRY; + var member = + new ObjectMember( + sourceSection, + keyNode.getSourceSection(), + modifier, + null, + scope.getQualifiedName()); + if (valueCtx != null) { // ["key"] = value + var valueNode = visitExpr(valueCtx); + if (valueNode instanceof ConstantNode constantNode) { + member.initConstantValue(constantNode); + } else { + if (isForGeneratorScope) { + valueNode = new RestoreForBindingsNode(valueNode); + } + member.initMemberNode( + ElementOrEntryNodeGen.create( + language, scope.buildFrameDescriptor(), member, valueNode)); + } + } else { // ["key"] { ... } + assert objectBodyCtxs != null; + var objectBody = + doVisitObjectBody( + objectBodyCtxs, + new ReadSuperEntryNode(unavailableSourceSection(), new GetMemberKeyNode())); + if (isForGeneratorScope) { + objectBody = new RestoreForBindingsNode(objectBody); + } + member.initMemberNode( + ElementOrEntryNodeGen.create( + language, scope.buildFrameDescriptor(), member, objectBody)); + } + + return member; + }); } private boolean needsConst(ExpressionNode receiver) { @@ -2142,504 +2740,52 @@ public final class AstBuilder extends AbstractAstBuilder { return needsConst; } - private Identifier toIdentifier(TerminalNode node) { - return Identifier.get(node.getText()); - } - - private Identifier toIdentifier(Token token) { - return Identifier.get(token.getText()); - } - - private FrameDescriptor.Builder createFrameDescriptorBuilder(ParameterListContext ctx) { - checkCommaSeparatedElements(ctx, ctx.ts, ctx.errs); - checkClosingDelimiter(ctx.err, ")", ctx.stop); - - var builder = FrameDescriptor.newBuilder(ctx.ts.size()); - for (var param : ctx.ts) { - var ident = isIgnored(param) ? null : toIdentifier(param.typedIdentifier().Identifier()); - builder.addSlot(FrameSlotKind.Illegal, ident, null); + private FrameDescriptor.Builder createFrameDescriptorBuilder(ParameterList params) { + var builder = FrameDescriptor.newBuilder(params.getParameters().size()); + for (var param : params.getParameters()) { + org.pkl.core.runtime.Identifier identifier = null; + if (param instanceof TypedIdentifier typedIdentifier) { + identifier = toIdentifier(typedIdentifier.getIdentifier().getValue()); + } + builder.addSlot(FrameSlotKind.Illegal, identifier, null); } return builder; } - private @Nullable FrameDescriptor.Builder createFrameDescriptorBuilder(ObjectBodyContext ctx) { - if (ctx.ps.isEmpty()) return null; + private @Nullable FrameDescriptor.Builder createFrameDescriptorBuilder(ObjectBody body) { + if (body.getParameters().isEmpty()) return null; - checkCommaSeparatedElements(ctx, ctx.ps, ctx.errs); - - var builder = FrameDescriptor.newBuilder(ctx.ps.size()); - for (var param : ctx.ps) { - var ident = isIgnored(param) ? null : toIdentifier(param.typedIdentifier().Identifier()); - builder.addSlot(FrameSlotKind.Illegal, ident, null); + var builder = FrameDescriptor.newBuilder(body.getParameters().size()); + for (var param : body.getParameters()) { + org.pkl.core.runtime.Identifier identifier = null; + if (param instanceof TypedIdentifier typedIdentifier) { + identifier = toIdentifier(typedIdentifier.getIdentifier().getValue()); + } + builder.addSlot(FrameSlotKind.Illegal, identifier, null); } return builder; } - private UnresolvedTypeNode[] doVisitParameterTypes(ParameterListContext ctx) { - return ctx.ts.stream() - .map( - it -> isIgnored(it) ? null : visitTypeAnnotation(it.typedIdentifier().typeAnnotation())) - .toArray(UnresolvedTypeNode[]::new); - } - - private UnresolvedTypeNode[] doVisitParameterTypes(ObjectBodyContext ctx) { - return ctx.ps.stream() - .map( - it -> isIgnored(it) ? null : visitTypeAnnotation(it.typedIdentifier().typeAnnotation())) - .toArray(UnresolvedTypeNode[]::new); - } - - @Override - public Object visitTypedIdentifier(TypedIdentifierContext ctx) { - throw exceptionBuilder().unreachableCode().build(); // handled directly - } - - @Override - public Object visitThrowExpr(ThrowExprContext ctx) { - var exprCtx = ctx.expr(); - checkClosingDelimiter(ctx.err, ")", exprCtx.stop); - - return ThrowNodeGen.create(createSourceSection(ctx), visitExpr(exprCtx)); - } - - @Override - public Object visitTraceExpr(TraceExprContext ctx) { - var exprCtx = ctx.expr(); - checkClosingDelimiter(ctx.err, ")", exprCtx.stop); - - return new TraceNode(createSourceSection(ctx), visitExpr(exprCtx)); - } - - @Override - public Object visitImportExpr(ImportExprContext ctx) { - var importUriCtx = ctx.stringConstant(); - checkClosingDelimiter(ctx.err, ")", importUriCtx.stop); - return doVisitImport(ctx.t.getType(), ctx, importUriCtx); - } - - @Override - public Object visitIfExpr(IfExprContext ctx) { - checkClosingDelimiter(ctx.err, ")", ctx.c.stop); - - return new IfElseNode( - createSourceSection(ctx), visitExpr(ctx.c), visitExpr(ctx.l), visitExpr(ctx.r)); - } - - @Override - public Object visitReadExpr(ReadExprContext ctx) { - var exprCtx = ctx.expr(); - checkClosingDelimiter(ctx.err, ")", exprCtx.stop); - - var tokenType = ctx.t.getType(); - - if (tokenType == PklLexer.READ) { - return ReadNodeGen.create(createSourceSection(ctx), moduleKey, visitExpr(exprCtx)); + private void checkNotInsideForGenerator(Node ctx, String errorMessageKey) { + if (!symbolTable.getCurrentScope().isForGeneratorScope()) { + return; } - if (tokenType == PklLexer.READ_OR_NULL) { - return ReadOrNullNodeGen.create(createSourceSection(ctx), moduleKey, visitExpr(exprCtx)); + var forExprCtx = ctx.parent(); + while (forExprCtx != null + && forExprCtx.getClass() != org.pkl.core.parser.ast.ObjectMember.ForGenerator.class) { + forExprCtx = forExprCtx.parent(); } - assert tokenType == PklLexer.READ_GLOB; - return ReadGlobNodeGen.create(createSourceSection(ctx), moduleKey, visitExpr(exprCtx)); - } - - @Override - public Object visitLetExpr(LetExprContext ctx) { - checkClosingDelimiter(ctx.err, ")", ctx.l.stop); - - var sourceSection = createSourceSection(ctx); - var idCtx = ctx.parameter(); - var frameBuilder = FrameDescriptor.newBuilder(); - var isIgnored = isIgnored(idCtx); - var typeNodes = - isIgnored - ? new UnresolvedTypeNode[0] - : new UnresolvedTypeNode[] { - visitTypeAnnotation(idCtx.typedIdentifier().typeAnnotation()) - }; - if (!isIgnored) { - frameBuilder.addSlot( - FrameSlotKind.Illegal, toIdentifier(idCtx.typedIdentifier().Identifier()), null); - } - - var isCustomThisScope = symbolTable.getCurrentScope().isCustomThisScope(); - - UnresolvedFunctionNode functionNode = - symbolTable.enterLambda( - frameBuilder, - scope -> { - var expr = visitExpr(ctx.r); - return new UnresolvedFunctionNode( - language, - scope.buildFrameDescriptor(), - new Lambda(createSourceSection(ctx.r), scope.getQualifiedName()), - 1, - typeNodes, - null, - expr); - }); - - return new LetExprNode(sourceSection, functionNode, visitExpr(ctx.l), isCustomThisScope); - } - - @Override - public ExpressionNode visitThisExpr(ThisExprContext ctx) { - if (!(ctx.parent instanceof QualifiedAccessExprContext)) { - var currentScope = symbolTable.getCurrentScope(); - var needsConst = - currentScope.getConstLevel() == ConstLevel.ALL - && currentScope.getConstDepth() == -1 - && !currentScope.isCustomThisScope(); - if (needsConst) { - throw exceptionBuilder() - .withSourceSection(createSourceSection(ctx)) - .evalError("thisIsNotConst") - .build(); - } - } - return VmUtils.createThisNode( - createSourceSection(ctx), symbolTable.getCurrentScope().isCustomThisScope()); - } - - // TODO: `outer.` should probably have semantics similar to `super.`, - // rather than just performing a lookup in the immediately enclosing object - // also, consider interpreting `x = ... x ...` as `x = ... outer.x ...` - @Override - public OuterNode visitOuterExpr(OuterExprContext ctx) { - if (!(ctx.parent instanceof QualifiedAccessExprContext)) { - var constLevel = symbolTable.getCurrentScope().getConstLevel(); - var outerScope = getParentLexicalScope(); - if (outerScope != null && constLevel.bigger(outerScope.getConstLevel())) { - throw exceptionBuilder() - .evalError("outerIsNotConst") - .withSourceSection(createSourceSection(ctx)) - .build(); - } - } - return new OuterNode(createSourceSection(ctx)); - } - - @Override - public Object visitModuleExpr(ModuleExprContext ctx) { - // cannot use unqualified `module` in a const context - if (symbolTable.getCurrentScope().getConstLevel().isConst() - && !(ctx.parent instanceof QualifiedAccessExprContext)) { - var scope = symbolTable.getCurrentScope(); - while (scope != null - && !(scope instanceof AnnotationScope) - && !(scope instanceof ClassScope)) { - scope = scope.getParent(); - } - if (scope == null) { - throw exceptionBuilder() - .evalError("moduleIsNotConst", symbolTable.getCurrentScope().getName().toString()) - .withSourceSection(createSourceSection(ctx)) - .build(); - } - var messageKey = - scope instanceof AnnotationScope ? "moduleIsNotConstAnnotation" : "moduleIsNotConstClass"; - throw exceptionBuilder() - .evalError(messageKey) - .withSourceSection(createSourceSection(ctx)) - .build(); - } - return new GetModuleNode(createSourceSection(ctx)); - } - - @Override - public ExpressionNode visitParenthesizedExpr(ParenthesizedExprContext ctx) { - checkClosingDelimiter(ctx.err, ")", ctx.stop); - - return visitExpr(ctx.expr()); - } - - @Override - public Object visitSubscriptExpr(SubscriptExprContext ctx) { - checkClosingDelimiter(ctx.err, "]", ctx.stop); - - return SubscriptNodeGen.create(createSourceSection(ctx), visitExpr(ctx.l), visitExpr(ctx.r)); - } - - @Override - public Object visitTypeTestExpr(TypeTestExprContext ctx) { - if (ctx.t.getType() == PklLexer.IS) { - return new TypeTestNode(createSourceSection(ctx), visitExpr(ctx.l), visitType(ctx.r)); - } - - assert ctx.t.getType() == PklLexer.AS; - return new TypeCastNode(createSourceSection(ctx), visitExpr(ctx.l), visitType(ctx.r)); - } - - @Override - public Object visitUnknownType(UnknownTypeContext ctx) { - return new UnresolvedTypeNode.Unknown(createSourceSection(ctx)); - } - - @Override - public Object visitNothingType(NothingTypeContext ctx) { - return new UnresolvedTypeNode.Nothing(createSourceSection(ctx)); - } - - @Override - public Object visitModuleType(ModuleTypeContext ctx) { - return new UnresolvedTypeNode.Module(createSourceSection(ctx)); - } - - @Override - public Object visitStringLiteralType(StringLiteralTypeContext ctx) { - return new UnresolvedTypeNode.StringLiteral( - createSourceSection(ctx), visitStringConstant(ctx.stringConstant())); - } - - @Override - public UnresolvedTypeNode visitType(TypeContext ctx) { - return (UnresolvedTypeNode) ctx.accept(this); - } - - @Override - public UnresolvedTypeNode visitDeclaredType(DeclaredTypeContext ctx) { - var idCtx = ctx.qualifiedIdentifier(); - var argCtx = ctx.typeArgumentList(); - - if (argCtx == null) { - if (idCtx.ts.size() == 1) { - String text = idCtx.ts.get(0).getText(); - TypeParameter typeParameter = symbolTable.findTypeParameter(text); - if (typeParameter != null) { - return new UnresolvedTypeNode.TypeVariable(createSourceSection(ctx), typeParameter); - } - } - - return new UnresolvedTypeNode.Declared(createSourceSection(ctx), doVisitTypeName(idCtx)); - } - - checkCommaSeparatedElements(argCtx, argCtx.ts, argCtx.errs); - checkClosingDelimiter(argCtx.err, ">", argCtx.stop); - - return new UnresolvedTypeNode.Parameterized( - createSourceSection(ctx), - language, - doVisitTypeName(idCtx), - argCtx.ts.stream().map(this::visitType).toArray(UnresolvedTypeNode[]::new)); - } - - @Override - public UnresolvedTypeNode visitParenthesizedType(ParenthesizedTypeContext ctx) { - checkClosingDelimiter(ctx.err, ")", ctx.stop); - - return visitType(ctx.type()); - } - - @Override - public Object visitDefaultUnionType(DefaultUnionTypeContext ctx) { + assert forExprCtx != null; throw exceptionBuilder() - .evalError("notAUnion") - .withSourceSection(createSourceSection(ctx)) + .evalError(errorMessageKey) + .withSourceSection( + createSourceSection( + ((org.pkl.core.parser.ast.ObjectMember.ForGenerator) forExprCtx).forSpan())) .build(); } - @Override - public UnresolvedTypeNode visitUnionType(UnionTypeContext ctx) { - var elementTypeCtxs = new ArrayList(); - - var result = flattenUnionType(ctx, elementTypeCtxs); - boolean isUnionOfStringLiterals = result.first; - int defaultIndex = result.second; - - if (isUnionOfStringLiterals) { - return new UnresolvedTypeNode.UnionOfStringLiterals( - createSourceSection(ctx), - defaultIndex, - elementTypeCtxs.stream() - .map(it -> visitStringConstant(((StringLiteralTypeContext) it).stringConstant())) - .collect(Collectors.toCollection(LinkedHashSet::new))); - } - - return new UnresolvedTypeNode.Union( - createSourceSection(ctx), - defaultIndex, - elementTypeCtxs.stream().map(this::visitType).toArray(UnresolvedTypeNode[]::new)); - } - - private Pair flattenUnionType( - UnionTypeContext ctx, List collector) { - boolean isUnionOfStringLiterals = true; - int index = 0; - int defaultIndex = -1; - var list = new ArrayDeque(); - list.addLast(ctx.l); - list.addLast(ctx.r); - - while (!list.isEmpty()) { - var current = list.removeFirst(); - if (current instanceof UnionTypeContext unionType) { - list.addFirst(unionType.r); - list.addFirst(unionType.l); - continue; - } - if (current instanceof DefaultUnionTypeContext defaultUnionType) { - if (defaultIndex == -1) { - defaultIndex = index; - } else { - throw exceptionBuilder() - .evalError("multipleUnionDefaults") - .withSourceSection(createSourceSection(ctx)) - .build(); - } - isUnionOfStringLiterals = - isUnionOfStringLiterals && defaultUnionType.type() instanceof StringLiteralTypeContext; - collector.add(defaultUnionType.type()); - } else { - isUnionOfStringLiterals = - isUnionOfStringLiterals && current instanceof StringLiteralTypeContext; - collector.add(current); - } - index++; - } - return Pair.of(isUnionOfStringLiterals, defaultIndex); - } - - @Override - public UnresolvedTypeNode visitNullableType(NullableTypeContext ctx) { - return new UnresolvedTypeNode.Nullable( - createSourceSection(ctx), (UnresolvedTypeNode) ctx.type().accept(this)); - } - - @Override - public UnresolvedTypeNode visitConstrainedType(ConstrainedTypeContext ctx) { - checkCommaSeparatedElements(ctx, ctx.es, ctx.errs); - checkClosingDelimiter(ctx.err, ")", ctx.stop); - - var childNode = (UnresolvedTypeNode) ctx.type().accept(this); - - return symbolTable.enterCustomThisScope( - scope -> - new UnresolvedTypeNode.Constrained( - createSourceSection(ctx), - childNode, - ctx.es.stream() - .map(this::visitExpr) - .map(it -> TypeConstraintNodeGen.create(it.getSourceSection(), it)) - .toArray(TypeConstraintNode[]::new))); - } - - @Override - public UnresolvedTypeNode visitFunctionType(FunctionTypeContext ctx) { - checkCommaSeparatedElements(ctx, ctx.ps, ctx.errs); - checkClosingDelimiter( - ctx.err, ")", ctx.ps.isEmpty() ? ctx.t : ctx.ps.get(ctx.ps.size() - 1).stop); - - return new UnresolvedTypeNode.Function( - createSourceSection(ctx), - ctx.ps.stream().map(this::visitType).toArray(UnresolvedTypeNode[]::new), - (UnresolvedTypeNode) ctx.r.accept(this)); - } - - private ExpressionNode resolveBaseModuleClass(Identifier className, Supplier clazz) { - return isBaseModule - ? - // Can't access BaseModule.getXYZClass() while parsing base module - new GetBaseModuleClassNode(className) - : new ConstantValueNode(clazz.get()); - } - - private UnresolvedPropertyNode[] doVisitClassProperties( - List propertyContexts, Set propertyNames) { - var propertyNodes = new UnresolvedPropertyNode[propertyContexts.size()]; - - for (var i = 0; i < propertyNodes.length; i++) { - var propertyCtx = propertyContexts.get(i); - var propertyNode = visitClassProperty(propertyCtx); - checkDuplicateMember(propertyNode.getName(), propertyNode.getHeaderSection(), propertyNames); - propertyNodes[i] = propertyNode; - } - - return propertyNodes; - } - - private UnresolvedMethodNode[] doVisitMethodDefs(List methodDefs) { - var methodNodes = new UnresolvedMethodNode[methodDefs.size()]; - var methodNames = CollectionUtils.newHashSet(methodDefs.size()); - - for (var i = 0; i < methodNodes.length; i++) { - var methodNode = visitClassMethod(methodDefs.get(i)); - checkDuplicateMember(methodNode.getName(), methodNode.getHeaderSection(), methodNames); - methodNodes[i] = methodNode; - } - - return methodNodes; - } - - private EconomicMap doVisitModuleProperties( - List importCtxs, - List classCtxs, - List typeAliasCtxs, - List propertyCtxs, - Set propertyNames, - ModuleInfo moduleInfo) { - - var totalSize = importCtxs.size() + classCtxs.size() + typeAliasCtxs.size(); - var result = EconomicMaps.create(totalSize); - - for (var ctx : importCtxs) { - var member = visitImportClause(ctx); - checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); - EconomicMaps.put(result, member.getName(), member); - } - - for (var ctx : classCtxs) { - ObjectMember member = visitClazz(ctx); - - if (moduleInfo.isAmend() && !member.isLocal()) { - throw exceptionBuilder() - .evalError("classMustBeLocal") - .withSourceSection(member.getHeaderSection()) - .build(); - } - - checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); - EconomicMaps.put(result, member.getName(), member); - } - - for (TypeAliasContext ctx : typeAliasCtxs) { - var member = visitTypeAlias(ctx); - - if (moduleInfo.isAmend() && !member.isLocal()) { - throw exceptionBuilder() - .evalError("typeAliasMustBeLocal") - .withSourceSection(member.getHeaderSection()) - .build(); - } - - checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); - EconomicMaps.put(result, member.getName(), member); - } - - for (var ctx : propertyCtxs) { - var member = - doVisitObjectProperty( - ctx, - ctx.modifier(), - ctx.Identifier(), - ctx.typeAnnotation(), - ctx.expr(), - ctx.objectBody()); - - if (moduleInfo.isAmend() && !member.isLocal() && ctx.typeAnnotation() != null) { - throw exceptionBuilder() - .evalError("nonLocalObjectPropertyCannotHaveTypeAnnotation") - .withSourceSection(createSourceSection(ctx.typeAnnotation().type())) - .build(); - } - - checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); - EconomicMaps.put(result, member.getName(), member); - } - - return result; - } - private void checkDuplicateMember( - Identifier memberName, + org.pkl.core.runtime.Identifier memberName, SourceSection headerSection, // use Set rather than Set // to detect conflicts between local and non-local identifiers @@ -2653,96 +2799,66 @@ public final class AstBuilder extends AbstractAstBuilder { } } - // TODO: use Set and checkDuplicateMember() to find duplicates between local and non-local - // properties - private void addProperty(EconomicMap objectMembers, ObjectMember property) { - if (EconomicMaps.put(objectMembers, property.getName(), property) != null) { - throw exceptionBuilder() - .evalError("duplicateDefinition", property.getName()) - .withSourceSection(property.getHeaderSection()) - .build(); - } - } - - private void invalidSeparatorPosition(SourceSection source) { - throw exceptionBuilder() - .evalError("invalidSeparatorPosition") - .withSourceSection(source) - .build(); - } - - private AbstractImportNode doVisitImport( - int lexerToken, ParserRuleContext ctx, StringConstantContext importUriCtx) { - var isGlobImport = lexerToken == PklLexer.IMPORT_GLOB; - var section = createSourceSection(ctx); - var importUri = visitStringConstant(importUriCtx); - if (isGlobImport && importUri.startsWith("...")) { - throw exceptionBuilder().evalError("cannotGlobTripleDots").withSourceSection(section).build(); - } - var resolvedUri = resolveImport(importUri, importUriCtx); - if (isGlobImport) { - return new ImportGlobNode(section, moduleInfo.getResolvedModuleKey(), resolvedUri, importUri); - } - return new ImportNode(language, section, moduleInfo.getResolvedModuleKey(), resolvedUri); - } - - private SourceSection startOf(TerminalNode node) { - return startOf(node.getSymbol()); - } - - private SourceSection startOf(Token token) { - return source.createSection(token.getStartIndex(), 1); - } - - private SourceSection shrinkLeft(SourceSection section, int length) { - return source.createSection(section.getCharIndex() + length, section.getCharLength() - length); - } - - private VmException createUnexpectedTokenError(Token token) { - return exceptionBuilder().bug("Unexpected token `%s`.", token).build(); - } - - @Override protected VmExceptionBuilder exceptionBuilder() { return new VmExceptionBuilder() .withMemberName(symbolTable.getCurrentScope().getQualifiedName()); } - private static SourceSection unavailableSourceSection() { - return VmUtils.unavailableSourceSection(); + private @Nullable SymbolTable.Scope getParentLexicalScope() { + var parent = symbolTable.getCurrentScope().getLexicalScope().getParent(); + if (parent != null) return parent.getLexicalScope(); + return null; } - private String getCommonIndent(MultiLineStringPartContext lastPart, Token endQuoteToken) { - if (lastPart.e != null) { + private org.pkl.core.runtime.Identifier toIdentifier(String text) { + return org.pkl.core.runtime.Identifier.get(text); + } + + private ExpressionNode createResolveVariableNode( + SourceSection section, org.pkl.core.runtime.Identifier propertyName) { + var scope = symbolTable.getCurrentScope(); + return new ResolveVariableNode( + section, + propertyName, + isBaseModule, + scope.isCustomThisScope(), + scope.getConstLevel(), + scope.getConstDepth()); + } + + private String getCommonIndent(Node lastParts, Span endQuoteSpan) { + if (!(lastParts instanceof StringConstantParts sparts)) { throw exceptionBuilder() .evalError("closingStringDelimiterMustBeginOnNewLine") - .withSourceSection(startOf(endQuoteToken)) + .withSourceSection(startOf(endQuoteSpan)) .build(); } - var tokens = lastPart.ts; - assert !tokens.isEmpty(); - var lastToken = tokens.get(tokens.size() - 1); - - if (lastToken.getType() == PklLexer.MLNewline) { + var parts = sparts.getParts(); + assert !parts.isEmpty(); + var lastPart = parts.get(parts.size() - 1); + if (lastPart instanceof StringNewline) { return ""; } - if (tokens.size() > 1) { - var lastButOneToken = tokens.get(tokens.size() - 2); - if (lastButOneToken.getType() == PklLexer.MLNewline && isIndentChars(lastToken)) { - return lastToken.getText(); + if (parts.size() > 1) { + var lastButOne = parts.get(parts.size() - 2); + if (lastButOne instanceof StringNewline && isIndentChars(lastPart)) { + return ((ConstantPart) lastPart).getStr(); } } throw exceptionBuilder() .evalError("closingStringDelimiterMustBeginOnNewLine") - .withSourceSection(startOf(endQuoteToken)) + .withSourceSection(startOf(endQuoteSpan)) .build(); } - private static boolean isIndentChars(Token token) { - var text = token.getText(); + private static boolean isIndentChars(Node node) { + if (!(node instanceof ConstantPart part)) { + return false; + } + var text = part.getStr(); for (var i = 0; i < text.length(); i++) { var ch = text.charAt(i); @@ -2752,9 +2868,89 @@ public final class AstBuilder extends AbstractAstBuilder { return true; } - private static String getLeadingIndent(Token token) { - var text = token.getText(); + private URI resolveImport(String importUri, StringConstant ctx) { + URI parsedUri; + try { + parsedUri = IoUtils.toUri(importUri); + } catch (URISyntaxException e) { + throw exceptionBuilder() + .evalError("invalidModuleUri", importUri) + .withHint(e.getReason()) + .withSourceSection(createSourceSection(ctx)) + .build(); + } + URI resolvedUri; + var context = VmContext.get(null); + try { + resolvedUri = IoUtils.resolve(context.getSecurityManager(), moduleKey, parsedUri); + } catch (FileNotFoundException e) { + var exceptionBuilder = + exceptionBuilder() + .evalError("cannotFindModule", importUri) + .withSourceSection(createSourceSection(ctx)); + var path = parsedUri.getPath(); + if (path != null && path.contains("\\")) { + exceptionBuilder.withHint( + "To resolve modules in nested directories, use `/` as the directory separator."); + } + throw exceptionBuilder.build(); + } catch (URISyntaxException e) { + throw exceptionBuilder() + .evalError("invalidModuleUri", importUri) + .withHint(e.getReason()) + .withSourceSection(createSourceSection(ctx)) + .build(); + } catch (IOException e) { + throw exceptionBuilder() + .evalError("ioErrorLoadingModule", importUri) + .withCause(e) + .withSourceSection(createSourceSection(ctx)) + .build(); + } catch (SecurityManagerException | PackageLoadError e) { + throw exceptionBuilder().withSourceSection(createSourceSection(ctx)).withCause(e).build(); + } catch (VmException e) { + throw exceptionBuilder() + .evalError(e.getMessage(), e.getMessageArguments()) + .withCause(e.getCause()) + .withHint(e.getHint()) + .withSourceSection(createSourceSection(ctx)) + .build(); + } catch (ExternalReaderProcessException e) { + throw exceptionBuilder() + .evalError("externalReaderFailure") + .withCause(e.getCause()) + .withSourceSection(createSourceSection(ctx)) + .build(); + } + + if (!resolvedUri.isAbsolute()) { + throw exceptionBuilder() + .evalError("cannotHaveRelativeImport", moduleKey.getUri()) + .withSourceSection(createSourceSection(ctx)) + .build(); + } + return resolvedUri; + } + + private ConstLevel getConstLevel(int modifiers) { + if (VmModifier.isConst(modifiers)) return ConstLevel.ALL; + return symbolTable.getCurrentScope().getConstLevel(); + } + + private VmException missingLocalPropertyValue(TypeAnnotation typeAnn) { + var stop = typeAnn.span().stopIndex(); + return exceptionBuilder() + .evalError("missingLocalPropertyValue") + .withSourceSection(source.createSection(stop + 1, 0)) + .build(); + } + + private static SourceSection unavailableSourceSection() { + return VmUtils.unavailableSourceSection(); + } + + private static String getLeadingIndent(String text) { for (var i = 0; i < text.length(); i++) { var ch = text.charAt(i); if (ch != ' ' && ch != '\t') { @@ -2764,205 +2960,4 @@ public final class AstBuilder extends AbstractAstBuilder { return text; } - - private ExpressionNode doVisitMultiLineStringPart( - MultiLineStringPartContext ctx, - String commonIndent, - boolean isStringStart, - boolean isStringEnd) { - - if (ctx.e != null) { - return ToStringNodeGen.create(createSourceSection(ctx), visitExpr(ctx.e)); - } - - return new ConstantValueNode( - createSourceSection(ctx), - doVisitMultiLineConstantStringPart(ctx.ts, commonIndent, isStringStart, isStringEnd)); - } - - private String doVisitMultiLineConstantStringPart( - List tokens, String commonIndent, boolean isStringStart, boolean isStringEnd) { - - int startIndex = 0; - if (isStringStart) { - // skip leading newline token - startIndex = 1; - } - - var endIndex = tokens.size() - 1; - if (isStringEnd) { - if (tokens.get(endIndex).getType() == PklLexer.MLNewline) { - // skip trailing newline token - endIndex -= 1; - } else { - // skip trailing newline and whitespace (common indent) tokens - endIndex -= 2; - } - } - - var builder = new StringBuilder(); - var isLineStart = isStringStart; - - for (var i = startIndex; i <= endIndex; i++) { - Token token = tokens.get(i); - - switch (token.getType()) { - case PklLexer.MLNewline -> { - builder.append('\n'); - isLineStart = true; - } - case PklLexer.MLCharacters -> { - var text = token.getText(); - if (isLineStart) { - if (text.startsWith(commonIndent)) { - builder.append(text, commonIndent.length(), text.length()); - } else { - String actualIndent = getLeadingIndent(token); - if (actualIndent.length() > commonIndent.length()) { - actualIndent = actualIndent.substring(0, commonIndent.length()); - } - throw exceptionBuilder() - .evalError("stringIndentationMustMatchLastLine") - .withSourceSection(shrinkLeft(createSourceSection(token), actualIndent.length())) - .build(); - } - } else { - builder.append(text); - } - isLineStart = false; - } - case PklLexer.MLCharacterEscape -> { - if (isLineStart && !commonIndent.isEmpty()) { - throw exceptionBuilder() - .evalError("stringIndentationMustMatchLastLine") - .withSourceSection(createSourceSection(token)) - .build(); - } - builder.append(parseCharacterEscapeSequence(token)); - isLineStart = false; - } - case PklLexer.MLUnicodeEscape -> { - if (isLineStart && !commonIndent.isEmpty()) { - throw exceptionBuilder() - .evalError("stringIndentationMustMatchLastLine") - .withSourceSection(createSourceSection(token)) - .build(); - } - builder.appendCodePoint(parseUnicodeEscapeSequence(token)); - isLineStart = false; - } - default -> throw exceptionBuilder().unreachableCode().build(); - } - } - - return builder.toString(); - } - - private ResolveDeclaredTypeNode doVisitTypeName(QualifiedIdentifierContext ctx) { - var tokens = ctx.ts; - return switch (tokens.size()) { - case 1 -> { - var token = tokens.get(0); - yield new ResolveSimpleDeclaredTypeNode( - createSourceSection(token), Identifier.get(token.getText()), isBaseModule); - } - case 2 -> { - var token1 = tokens.get(0); - var token2 = tokens.get(1); - yield new ResolveQualifiedDeclaredTypeNode( - createSourceSection(ctx), - createSourceSection(token1), - createSourceSection(token2), - Identifier.localProperty(token1.getText()), - Identifier.get(token2.getText())); - } - default -> - throw exceptionBuilder() - .evalError("invalidTypeName", ctx.getText()) - .withSourceSection(createSourceSection(ctx)) - .build(); - }; - } - - private void checkCommaSeparatedElements( - ParserRuleContext ctx, List elements, List separators) { - - 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 - ParseTree prevChild = null; - for (ParseTree child : ctx.children) { - @SuppressWarnings("SuspiciousMethodCalls") - var index = elements.indexOf(child); - if (index > 0) { // 0 rather than -1 because no separator is expected before first element - assert prevChild != null; - if (!(prevChild instanceof TerminalNode terminalNode) - || !separators.contains(terminalNode.getSymbol())) { - var prevToken = - prevChild instanceof TerminalNode terminalNode - ? terminalNode.getSymbol() - : ((ParserRuleContext) prevChild).getStop(); - throw exceptionBuilder() - .evalError("missingCommaSeparator") - .withSourceSection(source.createSection(prevToken.getStopIndex() + 1, 1)) - .build(); - } - } - prevChild = child; - } - - throw exceptionBuilder().unreachableCode().build(); - } - - private void checkClosingDelimiter( - @Nullable Token delimiter, String delimiterSymbol, Token tokenBeforeDelimiter) { - - if (delimiter == null) { - throw missingDelimiter(delimiterSymbol, tokenBeforeDelimiter.getStopIndex() + 1); - } - } - - private void checkSingleLineStringDelimiters(Token openingDelimiter, Token closingDelimiter) { - var closingText = closingDelimiter.getText(); - var lastChar = closingText.charAt(closingText.length() - 1); - if (lastChar == '"' || lastChar == '#') return; - - assert lastChar == '\n' || lastChar == '\r'; - var openingText = openingDelimiter.getText(); - throw missingDelimiter( - "\"" + openingText.substring(0, openingText.length() - 1), closingDelimiter.getStopIndex()); - } - - private VmException missingDelimiter(String delimiter, int charIndex) { - return exceptionBuilder() - .evalError("missingDelimiter", delimiter) - .withSourceSection(source.createSection(charIndex, 0)) - .build(); - } - - private VmException wrongDelimiter(String expected, String actual, int charIndex) { - return exceptionBuilder() - .evalError("wrongDelimiter", expected, actual) - .withSourceSection(source.createSection(charIndex, 0)) - .build(); - } - - private VmException danglingDelimiter(String delimiter, int charIndex) { - return exceptionBuilder() - .evalError("danglingDelimiter", delimiter) - .withSourceSection(source.createSection(charIndex, 0)) - .build(); - } - - private @Nullable Scope getParentLexicalScope() { - var parent = symbolTable.getCurrentScope().getLexicalScope().getParent(); - if (parent != null) return parent.getLexicalScope(); - return null; - } - - private ConstLevel getConstLevel(int modifiers) { - if (VmModifier.isConst(modifiers)) return ConstLevel.ALL; - return symbolTable.getCurrentScope().getConstLevel(); - } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/builder/ImportsAndReadsParser.java b/pkl-core/src/main/java/org/pkl/core/ast/builder/ImportsAndReadsParser.java index 93e3c247..c5eb8243 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/builder/ImportsAndReadsParser.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/builder/ImportsAndReadsParser.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -24,14 +24,17 @@ import java.util.List; import org.pkl.core.ast.builder.ImportsAndReadsParser.Entry; import org.pkl.core.module.ModuleKey; import org.pkl.core.module.ResolvedModuleKey; -import org.pkl.core.parser.LexParseException; import org.pkl.core.parser.Parser; -import org.pkl.core.parser.antlr.PklLexer; -import org.pkl.core.parser.antlr.PklParser.ImportClauseContext; -import org.pkl.core.parser.antlr.PklParser.ImportExprContext; -import org.pkl.core.parser.antlr.PklParser.ModuleExtendsOrAmendsClauseContext; -import org.pkl.core.parser.antlr.PklParser.ReadExprContext; -import org.pkl.core.parser.antlr.PklParser.SingleLineStringLiteralContext; +import org.pkl.core.parser.ParserError; +import org.pkl.core.parser.ast.Expr; +import org.pkl.core.parser.ast.Expr.ImportExpr; +import org.pkl.core.parser.ast.Expr.ReadExpr; +import org.pkl.core.parser.ast.Expr.ReadType; +import org.pkl.core.parser.ast.Expr.SingleLineStringLiteralExpr; +import org.pkl.core.parser.ast.ExtendsOrAmendsClause; +import org.pkl.core.parser.ast.ExtendsOrAmendsClause.Type; +import org.pkl.core.parser.ast.ImportClause; +import org.pkl.core.parser.ast.StringPart.StringConstantParts; import org.pkl.core.runtime.VmExceptionBuilder; import org.pkl.core.runtime.VmUtils; import org.pkl.core.util.IoUtils; @@ -68,7 +71,7 @@ public class ImportsAndReadsParser extends AbstractAstBuilder<@Nullable List visitModuleExtendsOrAmendsClause( - ModuleExtendsOrAmendsClauseContext ctx) { - var importStr = doVisitSingleLineConstantStringPart(ctx.stringConstant().ts); - var sourceSection = createSourceSection(ctx.stringConstant()); + public @Nullable List visitExtendsOrAmendsClause(ExtendsOrAmendsClause decl) { + var importStr = doVisitStringConstant(decl.getUrl()); + var sourceSection = createSourceSection(decl.getUrl()); + assert sourceSection != null; return Collections.singletonList( new Entry( - true, false, ctx.EXTENDS() != null, ctx.AMENDS() != null, importStr, sourceSection)); + true, + false, + decl.getType() == Type.EXTENDS, + decl.getType() == Type.AMENDS, + importStr, + sourceSection)); } @Override - public List visitImportClause(ImportClauseContext ctx) { - var importStr = doVisitSingleLineConstantStringPart(ctx.stringConstant().ts); - var sourceSection = createSourceSection(ctx.stringConstant()); + public List visitImportClause(ImportClause imp) { + var importStr = doVisitStringConstant(imp.getImportStr()); + var sourceSection = createSourceSection(imp.getImportStr()); + assert sourceSection != null; return Collections.singletonList( - new Entry( - true, ctx.t.getType() == PklLexer.IMPORT_GLOB, false, false, importStr, sourceSection)); + new Entry(true, imp.isGlob(), false, false, importStr, sourceSection)); } @Override - public List visitImportExpr(ImportExprContext ctx) { - var importStr = doVisitSingleLineConstantStringPart(ctx.stringConstant().ts); - var sourceSection = createSourceSection(ctx.stringConstant()); + public List visitImportExpr(ImportExpr expr) { + var importStr = doVisitStringConstant(expr.getImportStr()); + var sourceSection = createSourceSection(expr.getImportStr()); + assert sourceSection != null; return Collections.singletonList( - new Entry( - true, ctx.t.getType() == PklLexer.IMPORT_GLOB, false, false, importStr, sourceSection)); + new Entry(true, expr.isGlob(), false, false, importStr, sourceSection)); } @Override - public List visitReadExpr(ReadExprContext ctx) { - var expr = ctx.expr(); - if (!(expr instanceof SingleLineStringLiteralContext slCtx)) { + public @Nullable List visitReadExpr(ReadExpr expr) { + return doVisitReadExpr(expr.getExpr(), expr.getReadType() == ReadType.GLOB); + } + + @SuppressWarnings("DataFlowIssue") + public List doVisitReadExpr(Expr expr, boolean isGlob) { + if (!(expr instanceof SingleLineStringLiteralExpr slStr)) { return Collections.emptyList(); } // best-effort approach; only collect read expressions that are string constants. - var singleParts = slCtx.singleLineStringPart(); String importString; + var singleParts = slStr.getParts(); if (singleParts.isEmpty()) { importString = ""; - } else if (singleParts.size() == 1) { - var ts = singleParts.get(0).ts; - if (!ts.isEmpty()) { - importString = doVisitSingleLineConstantStringPart(ts); - } else { - return Collections.emptyList(); - } + } else if (singleParts.size() == 1 + && singleParts.get(0) instanceof StringConstantParts cparts + && !cparts.getParts().isEmpty()) { + importString = doVisitStringConstant(cparts.getParts()); } else { return Collections.emptyList(); } + return Collections.singletonList( - new Entry( - false, - ctx.t.getType() == PklLexer.READ_GLOB, - false, - false, - importString, - createSourceSection(slCtx))); + new Entry(false, isGlob, false, false, importString, createSourceSection(slStr))); } @Override @@ -156,4 +160,9 @@ public class ImportsAndReadsParser extends AbstractAstBuilder<@Nullable List defaultValue() { + return List.of(); + } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassMember.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassMember.java index a79566c9..7fdc59e5 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassMember.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassMember.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -23,7 +23,7 @@ import org.pkl.core.runtime.VmTyped; import org.pkl.core.util.Nullable; public abstract class ClassMember extends Member { - protected final @Nullable SourceSection docComment; + protected final SourceSection @Nullable [] docComment; protected final List annotations; // store prototype instead of class because the former is needed much more often private final VmTyped owner; @@ -34,7 +34,7 @@ public abstract class ClassMember extends Member { int modifiers, Identifier name, String qualifiedName, - @Nullable SourceSection docComment, + SourceSection @Nullable [] docComment, List annotations, VmTyped owner) { @@ -45,7 +45,7 @@ public abstract class ClassMember extends Member { this.owner = owner; } - public final @Nullable SourceSection getDocComment() { + public final SourceSection @Nullable [] getDocComment() { return docComment; } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassMethod.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassMethod.java index 2f4bf181..e0862c7c 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassMethod.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassMethod.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -41,7 +41,7 @@ public final class ClassMethod extends ClassMember { int modifiers, Identifier name, String qualifiedName, - @Nullable SourceSection docComment, + SourceSection @Nullable [] docComment, List annotations, VmTyped owner, List typeParameters, diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java index fb82e92c..96956765 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -35,7 +35,7 @@ import org.pkl.core.util.Nullable; @NodeInfo(shortName = "class") public final class ClassNode extends ExpressionNode { private final SourceSection headerSection; - private final @Nullable SourceSection docComment; + private final SourceSection @Nullable [] docComment; @Children private final ExpressionNode[] annotationNodes; private final int modifiers; private final PClassInfo classInfo; @@ -52,7 +52,7 @@ public final class ClassNode extends ExpressionNode { public ClassNode( SourceSection section, SourceSection headerSection, - @Nullable SourceSection docComment, + SourceSection @Nullable [] docComment, ExpressionNode[] annotationNodes, int modifiers, PClassInfo classInfo, diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassProperty.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassProperty.java index 010ffcd1..d3f602c2 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassProperty.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassProperty.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -33,7 +33,7 @@ public final class ClassProperty extends ClassMember { int modifiers, Identifier name, String qualifiedName, - @Nullable SourceSection docComment, + SourceSection @Nullable [] docComment, List annotations, VmTyped owner, @Nullable PropertyTypeNode typeNode, diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java index 5668d180..e1227e0b 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java @@ -140,7 +140,7 @@ public final class FunctionNode extends RegularMemberNode { public PClass.Method export( PClass owner, - @Nullable SourceSection docComment, + SourceSection @Nullable [] docComment, List annotations, int modifiers, List typeParameters) { diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/TypeAliasNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/TypeAliasNode.java index 540512c5..5724cb33 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/TypeAliasNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/TypeAliasNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -31,7 +31,7 @@ import org.pkl.core.util.Nullable; public final class TypeAliasNode extends ExpressionNode { private final SourceSection headerSection; - private final @Nullable SourceSection docComment; + private final SourceSection @Nullable [] docComment; @Children private final ExpressionNode[] annotationNodes; private final int modifiers; private final String simpleName; @@ -45,7 +45,7 @@ public final class TypeAliasNode extends ExpressionNode { public TypeAliasNode( SourceSection sourceSection, SourceSection headerSection, - @Nullable SourceSection docComment, + SourceSection @Nullable [] docComment, ExpressionNode[] annotationNodes, int modifiers, String simpleName, diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedClassMemberNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedClassMemberNode.java index aafadc49..109b1899 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedClassMemberNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedClassMemberNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -29,7 +29,7 @@ public abstract class UnresolvedClassMemberNode extends PklNode { protected final SourceSection headerSection; protected final VmLanguage language; protected final FrameDescriptor descriptor; - protected final @Nullable SourceSection docComment; + protected final SourceSection @Nullable [] docComment; protected final @Children ExpressionNode[] annotationNodes; protected final int modifiers; protected final Identifier name; @@ -40,7 +40,7 @@ public abstract class UnresolvedClassMemberNode extends PklNode { SourceSection sourceSection, SourceSection headerSection, FrameDescriptor descriptor, - @Nullable SourceSection docComment, + SourceSection @Nullable [] docComment, ExpressionNode[] annotationNodes, int modifiers, Identifier name, diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedMethodNode.java index 72f166f1..769ccd7a 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedMethodNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -40,7 +40,7 @@ public final class UnresolvedMethodNode extends UnresolvedClassMemberNode { SourceSection sourceSection, SourceSection headerSection, FrameDescriptor descriptor, - @Nullable SourceSection docComment, + SourceSection @Nullable [] docComment, ExpressionNode[] annotationNodes, int modifiers, Identifier name, diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedPropertyNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedPropertyNode.java index 8d67ef2e..746f4c8f 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedPropertyNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedPropertyNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -36,7 +36,7 @@ public final class UnresolvedPropertyNode extends UnresolvedClassMemberNode { SourceSection headerSection, SourceSection propertyNameSection, FrameDescriptor descriptor, - @Nullable SourceSection docComment, + SourceSection @Nullable [] docComment, ExpressionNode[] annotationNodes, int modifiers, Identifier name, diff --git a/pkl-core/src/main/java/org/pkl/core/parser/BaseParserVisitor.java b/pkl-core/src/main/java/org/pkl/core/parser/BaseParserVisitor.java new file mode 100644 index 00000000..931b1799 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/BaseParserVisitor.java @@ -0,0 +1,476 @@ +/* + * Copyright © 2025 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.core.parser; + +import org.pkl.core.parser.ast.Annotation; +import org.pkl.core.parser.ast.ArgumentList; +import org.pkl.core.parser.ast.Class; +import org.pkl.core.parser.ast.ClassBody; +import org.pkl.core.parser.ast.ClassMethod; +import org.pkl.core.parser.ast.ClassProperty; +import org.pkl.core.parser.ast.DocComment; +import org.pkl.core.parser.ast.Expr.AmendsExpr; +import org.pkl.core.parser.ast.Expr.BinaryOperatorExpr; +import org.pkl.core.parser.ast.Expr.BoolLiteralExpr; +import org.pkl.core.parser.ast.Expr.FloatLiteralExpr; +import org.pkl.core.parser.ast.Expr.FunctionLiteralExpr; +import org.pkl.core.parser.ast.Expr.IfExpr; +import org.pkl.core.parser.ast.Expr.ImportExpr; +import org.pkl.core.parser.ast.Expr.IntLiteralExpr; +import org.pkl.core.parser.ast.Expr.LetExpr; +import org.pkl.core.parser.ast.Expr.LogicalNotExpr; +import org.pkl.core.parser.ast.Expr.ModuleExpr; +import org.pkl.core.parser.ast.Expr.MultiLineStringLiteralExpr; +import org.pkl.core.parser.ast.Expr.NewExpr; +import org.pkl.core.parser.ast.Expr.NonNullExpr; +import org.pkl.core.parser.ast.Expr.NullLiteralExpr; +import org.pkl.core.parser.ast.Expr.OuterExpr; +import org.pkl.core.parser.ast.Expr.ParenthesizedExpr; +import org.pkl.core.parser.ast.Expr.QualifiedAccessExpr; +import org.pkl.core.parser.ast.Expr.ReadExpr; +import org.pkl.core.parser.ast.Expr.SingleLineStringLiteralExpr; +import org.pkl.core.parser.ast.Expr.SubscriptExpr; +import org.pkl.core.parser.ast.Expr.SuperAccessExpr; +import org.pkl.core.parser.ast.Expr.SuperSubscriptExpr; +import org.pkl.core.parser.ast.Expr.ThisExpr; +import org.pkl.core.parser.ast.Expr.ThrowExpr; +import org.pkl.core.parser.ast.Expr.TraceExpr; +import org.pkl.core.parser.ast.Expr.TypeCastExpr; +import org.pkl.core.parser.ast.Expr.TypeCheckExpr; +import org.pkl.core.parser.ast.Expr.UnaryMinusExpr; +import org.pkl.core.parser.ast.Expr.UnqualifiedAccessExpr; +import org.pkl.core.parser.ast.ExtendsOrAmendsClause; +import org.pkl.core.parser.ast.Identifier; +import org.pkl.core.parser.ast.ImportClause; +import org.pkl.core.parser.ast.Modifier; +import org.pkl.core.parser.ast.ModuleDecl; +import org.pkl.core.parser.ast.Node; +import org.pkl.core.parser.ast.ObjectBody; +import org.pkl.core.parser.ast.ObjectMember.ForGenerator; +import org.pkl.core.parser.ast.ObjectMember.MemberPredicate; +import org.pkl.core.parser.ast.ObjectMember.ObjectElement; +import org.pkl.core.parser.ast.ObjectMember.ObjectEntry; +import org.pkl.core.parser.ast.ObjectMember.ObjectMethod; +import org.pkl.core.parser.ast.ObjectMember.ObjectProperty; +import org.pkl.core.parser.ast.ObjectMember.ObjectSpread; +import org.pkl.core.parser.ast.ObjectMember.WhenGenerator; +import org.pkl.core.parser.ast.Parameter; +import org.pkl.core.parser.ast.ParameterList; +import org.pkl.core.parser.ast.QualifiedIdentifier; +import org.pkl.core.parser.ast.ReplInput; +import org.pkl.core.parser.ast.StringConstant; +import org.pkl.core.parser.ast.StringConstantPart; +import org.pkl.core.parser.ast.StringPart; +import org.pkl.core.parser.ast.Type.ConstrainedType; +import org.pkl.core.parser.ast.Type.DeclaredType; +import org.pkl.core.parser.ast.Type.FunctionType; +import org.pkl.core.parser.ast.Type.ModuleType; +import org.pkl.core.parser.ast.Type.NothingType; +import org.pkl.core.parser.ast.Type.NullableType; +import org.pkl.core.parser.ast.Type.ParenthesizedType; +import org.pkl.core.parser.ast.Type.StringConstantType; +import org.pkl.core.parser.ast.Type.UnionType; +import org.pkl.core.parser.ast.Type.UnknownType; +import org.pkl.core.parser.ast.TypeAlias; +import org.pkl.core.parser.ast.TypeAnnotation; +import org.pkl.core.parser.ast.TypeParameter; +import org.pkl.core.parser.ast.TypeParameterList; + +public abstract class BaseParserVisitor implements ParserVisitor { + + @Override + public T visitUnknownType(UnknownType type) { + return defaultValue(); + } + + @Override + public T visitNothingType(NothingType type) { + return defaultValue(); + } + + @Override + public T visitModuleType(ModuleType type) { + return defaultValue(); + } + + @Override + public T visitStringConstantType(StringConstantType type) { + return visitChildren(type); + } + + @Override + public T visitDeclaredType(DeclaredType type) { + return visitChildren(type); + } + + @Override + public T visitParenthesizedType(ParenthesizedType type) { + return visitChildren(type); + } + + @Override + public T visitNullableType(NullableType type) { + return visitChildren(type); + } + + @Override + public T visitConstrainedType(ConstrainedType type) { + return visitChildren(type); + } + + @Override + public T visitUnionType(UnionType type) { + return visitChildren(type); + } + + @Override + public T visitFunctionType(FunctionType type) { + return visitChildren(type); + } + + @Override + public T visitThisExpr(ThisExpr expr) { + return defaultValue(); + } + + @Override + public T visitOuterExpr(OuterExpr expr) { + return defaultValue(); + } + + @Override + public T visitModuleExpr(ModuleExpr expr) { + return defaultValue(); + } + + @Override + public T visitNullLiteralExpr(NullLiteralExpr expr) { + return defaultValue(); + } + + @Override + public T visitBoolLiteralExpr(BoolLiteralExpr expr) { + return defaultValue(); + } + + @Override + public T visitIntLiteralExpr(IntLiteralExpr expr) { + return defaultValue(); + } + + @Override + public T visitFloatLiteralExpr(FloatLiteralExpr expr) { + return defaultValue(); + } + + @Override + public T visitThrowExpr(ThrowExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitTraceExpr(TraceExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitImportExpr(ImportExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitReadExpr(ReadExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitUnqualifiedAccessExpr(UnqualifiedAccessExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitStringConstant(StringConstant expr) { + return visitChildren(expr); + } + + @Override + public T visitSingleLineStringLiteralExpr(SingleLineStringLiteralExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitMultiLineStringLiteralExpr(MultiLineStringLiteralExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitNewExpr(NewExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitAmendsExpr(AmendsExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitSuperAccessExpr(SuperAccessExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitSuperSubscriptExpr(SuperSubscriptExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitQualifiedAccessExpr(QualifiedAccessExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitSubscriptExpr(SubscriptExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitNonNullExpr(NonNullExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitUnaryMinusExpr(UnaryMinusExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitLogicalNotExpr(LogicalNotExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitBinaryOperatorExpr(BinaryOperatorExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitTypeCheckExpr(TypeCheckExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitTypeCastExpr(TypeCastExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitIfExpr(IfExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitLetExpr(LetExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitFunctionLiteralExpr(FunctionLiteralExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitParenthesizedExpr(ParenthesizedExpr expr) { + return visitChildren(expr); + } + + @Override + public T visitObjectProperty(ObjectProperty member) { + return visitChildren(member); + } + + @Override + public T visitObjectMethod(ObjectMethod member) { + return visitChildren(member); + } + + @Override + public T visitMemberPredicate(MemberPredicate member) { + return visitChildren(member); + } + + @Override + public T visitObjectElement(ObjectElement member) { + return visitChildren(member); + } + + @Override + public T visitObjectEntry(ObjectEntry member) { + return visitChildren(member); + } + + @Override + public T visitObjectSpread(ObjectSpread member) { + return visitChildren(member); + } + + @Override + public T visitWhenGenerator(WhenGenerator member) { + return visitChildren(member); + } + + @Override + public T visitForGenerator(ForGenerator member) { + return visitChildren(member); + } + + @Override + public T visitModule(org.pkl.core.parser.ast.Module module) { + return visitChildren(module); + } + + @Override + public T visitModuleDecl(ModuleDecl decl) { + return visitChildren(decl); + } + + @Override + public T visitExtendsOrAmendsClause(ExtendsOrAmendsClause decl) { + return visitChildren(decl); + } + + @Override + public T visitImportClause(ImportClause imp) { + return visitChildren(imp); + } + + @Override + public T visitClass(Class clazz) { + return visitChildren(clazz); + } + + @Override + public T visitModifier(Modifier modifier) { + return defaultValue(); + } + + @Override + public T visitClassProperty(ClassProperty prop) { + return visitChildren(prop); + } + + @Override + public T visitClassMethod(ClassMethod method) { + return visitChildren(method); + } + + @Override + public T visitTypeAlias(TypeAlias typeAlias) { + return visitChildren(typeAlias); + } + + @Override + public T visitAnnotation(Annotation annotation) { + return visitChildren(annotation); + } + + @Override + public T visitParameter(Parameter param) { + return visitChildren(param); + } + + @Override + public T visitParameterList(ParameterList paramList) { + return visitChildren(paramList); + } + + @Override + public T visitTypeParameterList(TypeParameterList typeParameterList) { + return visitChildren(typeParameterList); + } + + @Override + public T visitTypeAnnotation(TypeAnnotation typeAnnotation) { + return visitChildren(typeAnnotation); + } + + @Override + public T visitArgumentList(ArgumentList argumentList) { + return visitChildren(argumentList); + } + + @Override + public T visitStringPart(StringPart part) { + return visitChildren(part); + } + + @Override + public T visitStringConstantPart(StringConstantPart part) { + return defaultValue(); + } + + @Override + public T visitClassBody(ClassBody classBody) { + return visitChildren(classBody); + } + + @Override + public T visitDocComment(DocComment docComment) { + return defaultValue(); + } + + @Override + public T visitIdentifier(Identifier identifier) { + return defaultValue(); + } + + @Override + public T visitQualifiedIdentifier(QualifiedIdentifier qualifiedIdentifier) { + return visitChildren(qualifiedIdentifier); + } + + @Override + public T visitObjectBody(ObjectBody objectBody) { + return visitChildren(objectBody); + } + + @Override + public T visitTypeParameter(TypeParameter typeParameter) { + return visitChildren(typeParameter); + } + + @Override + public T visitReplInput(ReplInput replInput) { + return visitChildren(replInput); + } + + private T visitChildren(Node node) { + T result = defaultValue(); + var children = node.children(); + if (children == null) return result; + for (var child : children) { + if (child != null) { + result = aggregateResult(result, child.accept(this)); + } + } + return result; + } + + protected abstract T defaultValue(); + + protected T aggregateResult(T result, T nextResult) { + return nextResult; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ErrorStrategy.java b/pkl-core/src/main/java/org/pkl/core/parser/ErrorStrategy.java deleted file mode 100644 index b3ff0ac4..00000000 --- a/pkl-core/src/main/java/org/pkl/core/parser/ErrorStrategy.java +++ /dev/null @@ -1,120 +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.core.parser; - -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.Parser; -import org.antlr.v4.runtime.misc.IntervalSet; -import org.pkl.core.parser.antlr.PklParser; - -final class ErrorStrategy extends DefaultErrorStrategy { - @Override - protected void reportNoViableAlternative(Parser parser, NoViableAltException e) { - var builder = new StringBuilder(); - var offendingToken = e.getOffendingToken(); - - if (Lexer.isKeyword(offendingToken)) { - appendKeywordNotAllowedMessage(builder, e.getOffendingToken(), e.getExpectedTokens()); - } else { - builder.append("No viable alternative at input "); - var tokens = parser.getInputStream(); - if (e.getStartToken().getType() == Token.EOF) { - builder.append(""); - } else { - builder.append(escapeWSAndQuote(tokens.getText(e.getStartToken(), offendingToken))); - } - } - - parser.notifyErrorListeners(offendingToken, builder.toString(), e); - } - - @Override - protected void reportInputMismatch(Parser parser, InputMismatchException e) { - var builder = new StringBuilder(); - var offendingToken = e.getOffendingToken(); - - if (Lexer.isKeyword(offendingToken)) { - appendKeywordNotAllowedMessage(builder, e.getOffendingToken(), e.getExpectedTokens()); - } else { - // improve formatting compared to DefaultErrorStrategy - builder - .append("Mismatched input: ") - .append(getTokenErrorDisplay(offendingToken)) - .append(". "); - appendExpectedTokensMessage(builder, parser); - } - - parser.notifyErrorListeners(offendingToken, builder.toString(), e); - } - - // improve formatting compared to DefaultErrorStrategy - @Override - protected void reportUnwantedToken(Parser parser) { - if (inErrorRecoveryMode(parser)) return; - - beginErrorCondition(parser); - var builder = new StringBuilder(); - var currentToken = parser.getCurrentToken(); - - if (Lexer.isKeyword(currentToken)) { - appendKeywordNotAllowedMessage(builder, currentToken, parser.getExpectedTokens()); - } else { - builder.append("Extraneous input: ").append(getTokenErrorDisplay(currentToken)).append(". "); - appendExpectedTokensMessage(builder, parser); - } - - parser.notifyErrorListeners(currentToken, builder.toString(), null); - } - - // improve formatting compared to DefaultErrorStrategy - protected void reportMissingToken(Parser parser) { - if (inErrorRecoveryMode(parser)) return; - - beginErrorCondition(parser); - var builder = new StringBuilder(); - var currentToken = parser.getCurrentToken(); - - var expecting = getExpectedTokens(parser); - builder - .append("Missing ") - .append(expecting.toString(parser.getVocabulary())) - .append(" at ") - .append(getTokenErrorDisplay(currentToken)) - .append(". "); - - parser.notifyErrorListeners(currentToken, builder.toString(), null); - } - - private void appendExpectedTokensMessage(StringBuilder builder, Parser parser) { - var expectedTokens = parser.getExpectedTokens(); - var size = expectedTokens.size(); - if (size == 0) return; - - builder.append(size == 1 ? "Expected: " : "Expected one of: "); - var msg = expectedTokens.toString(parser.getVocabulary()); - if (msg.startsWith("{")) msg = msg.substring(1); - if (msg.endsWith("}")) msg = msg.substring(0, msg.length() - 1); - builder.append(msg); - } - - private void appendKeywordNotAllowedMessage( - StringBuilder builder, Token offendingToken, IntervalSet expectedTokens) { - builder.append("Keyword `").append(offendingToken.getText()).append("` is not allowed here."); - if (expectedTokens.contains(PklParser.Identifier)) { - builder.append(" (If you must use this name as identifier, enclose it in backticks.)"); - } - } -} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/LexParseException.java b/pkl-core/src/main/java/org/pkl/core/parser/LexParseException.java deleted file mode 100644 index 055e0fe2..00000000 --- a/pkl-core/src/main/java/org/pkl/core/parser/LexParseException.java +++ /dev/null @@ -1,95 +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.core.parser; - -import org.antlr.v4.runtime.ParserRuleContext; -import org.pkl.core.util.IoUtils; -import org.pkl.core.util.Nullable; - -public abstract class LexParseException extends RuntimeException { - // line of the error's start position, 1-based - private final int line; - - // column of the error's start position, 1-based - private final int column; - - // number of characters, starting from line/column, belonging to the offending token - private final int length; - - private final int relevance; - - private @Nullable ParserRuleContext partialParseResult; - - public LexParseException(String message, int line, int column, int length, int relevance) { - super(format(message)); - this.line = line; - this.column = column; - this.length = length; - this.relevance = relevance; - partialParseResult = null; - } - - public int getLine() { - return line; - } - - public int getColumn() { - return column; - } - - public int getLength() { - return length; - } - - public int getRelevance() { - return relevance; - } - - public @Nullable ParserRuleContext getPartialParseResult() { - return partialParseResult; - } - - public LexParseException withPartialParseResult(ParserRuleContext partialParseResult) { - this.partialParseResult = partialParseResult; - return this; - } - - public static final class LexError extends LexParseException { - public LexError(String message, int line, int column, int length) { - super(message, line, column, length, Integer.MAX_VALUE); - } - } - - public static class ParseError extends LexParseException { - public ParseError(String message, int line, int column, int length, int relevance) { - super(message, line, column, length, relevance); - } - } - - public static final class IncompleteInput extends ParseError { - public IncompleteInput(String message, int line, int column, int length) { - super(message, line, column, length, Integer.MAX_VALUE - 1); - } - } - - // format ANTLR error messages like Pkl's own error messages - private static String format(String msg) { - var result = IoUtils.capitalize(msg); - result = result.replace("'", "`"); - if (!result.contains(":") && !result.endsWith(")") && !result.endsWith(".")) result += "."; - return result; - } -} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/Lexer.java b/pkl-core/src/main/java/org/pkl/core/parser/Lexer.java index 1a93dbc5..d35f60c5 100644 --- a/pkl-core/src/main/java/org/pkl/core/parser/Lexer.java +++ b/pkl-core/src/main/java/org/pkl/core/parser/Lexer.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -15,73 +15,712 @@ */ package org.pkl.core.parser; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import org.antlr.v4.runtime.*; -import org.pkl.core.parser.antlr.PklLexer; -import org.pkl.core.util.Nullable; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Deque; +import org.pkl.core.util.ErrorMessages; -public final class Lexer { - public static final Set KEYWORD_TYPES; - public static final Set KEYWORD_NAMES; +public class Lexer { - static { - var keywordTypes = new HashSet(); - var keywordNames = new HashSet(); - var vocabulary = PklLexer.VOCABULARY; + private final char[] source; + private final int size; + protected int cursor = 0; + protected int sCursor = 0; + private char lookahead; + private State state = State.DEFAULT; + private final Deque interpolationStack = new ArrayDeque<>(); + private boolean stringEnded = false; + private boolean isEscape = false; + // how many newlines exist between two subsequent tokens + protected int newLinesBetween = 0; - for (var i = 0; i <= vocabulary.getMaxTokenType(); i++) { - var literal = vocabulary.getLiteralName(i); - if (literal == null) continue; - // remove leading and trailing quotes - literal = literal.substring(1, literal.length() - 1); - if (Character.isLetter(literal.charAt(0)) || literal.equals("_")) { - keywordTypes.add(i); - keywordNames.add(literal); + private static final char EOF = Short.MAX_VALUE; + + public Lexer(String input) { + source = input.toCharArray(); + size = source.length; + if (size > 0) { + lookahead = source[cursor]; + } else { + lookahead = EOF; + } + } + + // The span of the last lexed token + public Span span() { + return new Span(sCursor, cursor - sCursor); + } + + // The text of the last lexed token + public String text() { + return new String(source, sCursor, cursor - sCursor); + } + + public char[] getSource() { + return source; + } + + public String textFor(int offset, int size) { + return new String(source, offset, size); + } + + public Token next() { + sCursor = cursor; + newLinesBetween = 0; + return switch (state) { + case DEFAULT -> nextDefault(); + case STRING -> nextString(); + }; + } + + private Token nextDefault() { + var ch = nextChar(); + // ignore spaces + while (ch == ' ' || ch == '\n' || ch == '\t' || ch == '\f' || ch == '\r') { + sCursor = cursor; + if (ch == '\n') { + newLinesBetween++; + } + ch = nextChar(); + } + return switch (ch) { + case EOF -> { + // when EOF is reached we overshot the span + cursor--; + yield Token.EOF; + } + case ';' -> Token.SEMICOLON; + case '(' -> { + var scope = interpolationStack.peek(); + if (scope != null) { + scope.parens++; + } + yield Token.LPAREN; + } + case ')' -> { + var scope = interpolationStack.peek(); + if (scope != null) { + scope.parens--; + if (scope.parens <= 0) { + // interpolation is over. Back to string + state = State.STRING; + } + } + yield Token.RPAREN; + } + case '{' -> Token.LBRACE; + case '}' -> Token.RBRACE; + case ',' -> Token.COMMA; + case '@' -> Token.AT; + case ':' -> Token.COLON; + case '+' -> Token.PLUS; + case '%' -> Token.MOD; + case '[' -> { + if (lookahead == '[') { + nextChar(); + yield Token.LPRED; + } else yield Token.LBRACK; + } + case ']' -> Token.RBRACK; + case '=' -> { + if (lookahead == '=') { + nextChar(); + yield Token.EQUAL; + } else yield Token.ASSIGN; + } + case '>' -> { + if (lookahead == '=') { + nextChar(); + yield Token.GTE; + } else yield Token.GT; + } + case '<' -> { + if (lookahead == '=') { + nextChar(); + yield Token.LTE; + } else yield Token.LT; + } + case '-' -> { + if (lookahead == '>') { + nextChar(); + yield Token.ARROW; + } else yield Token.MINUS; + } + case '!' -> { + if (lookahead == '!') { + nextChar(); + yield Token.NON_NULL; + } else if (lookahead == '=') { + nextChar(); + yield Token.NOT_EQUAL; + } else yield Token.NOT; + } + case '?' -> { + if (lookahead == '.') { + nextChar(); + yield Token.QDOT; + } else if (lookahead == '?') { + nextChar(); + yield Token.COALESCE; + } else yield Token.QUESTION; + } + case '&' -> { + if (lookahead == '&') { + nextChar(); + yield Token.AND; + } else { + throw unexpectedChar(ch, "&&"); + } + } + case '|' -> { + if (lookahead == '>') { + nextChar(); + yield Token.PIPE; + } else if (lookahead == '|') { + nextChar(); + yield Token.OR; + } else { + yield Token.UNION; + } + } + case '*' -> { + if (lookahead == '*') { + nextChar(); + yield Token.POW; + } else yield Token.STAR; + } + case '~' -> { + if (lookahead == '/') { + nextChar(); + yield Token.INT_DIV; + } else { + throw unexpectedChar(ch, "~/"); + } + } + case '.' -> { + if (lookahead == '.') { + nextChar(); + if (lookahead == '.') { + nextChar(); + if (lookahead == '?') { + nextChar(); + yield Token.QSPREAD; + } else { + yield Token.SPREAD; + } + } else { + throw unexpectedChar("..", ".", "...", "...?"); + } + } else if (lookahead >= 48 && lookahead <= 57) { + yield lexNumber(ch); + } else { + yield Token.DOT; + } + } + case '`' -> { + lexQuotedIdentifier(); + yield Token.IDENTIFIER; + } + case '/' -> lexSlash(); + case '"' -> lexStringStart(0); + case '#' -> { + if (lookahead == '!') { + yield lexShebang(); + } else { + yield lexStringStartPounds(); + } + } + default -> { + if (Character.isDigit(ch)) { + yield lexNumber(ch); + } else if (isIdentifierStart(ch)) { + yield lexIdentifier(); + } else throw lexError(ErrorMessages.create("invalidCharacter", ch), cursor - 1, 1); + } + }; + } + + private Token nextString() { + var scope = interpolationStack.getFirst(); + if (stringEnded) { + lexStringEnd(scope); + stringEnded = false; + interpolationStack.pop(); + state = State.DEFAULT; + return Token.STRING_END; + } + if (lookahead == EOF) return Token.EOF; + if (isEscape) { + isEscape = false; + // consume the `\#*` + for (var i = 0; i < scope.pounds + 1; i++) { + nextChar(); + } + return lexEscape(); + } + if (scope.quotes == 1) { + lexString(scope.pounds); + } else { + if (lookahead == '\r') { + nextChar(); + if (lookahead == '\n') { + nextChar(); + } + return Token.STRING_NEWLINE; + } + if (lookahead == '\n') { + nextChar(); + return Token.STRING_NEWLINE; + } + lexMultiString(scope.pounds); + } + return Token.STRING_PART; + } + + private Token lexStringStartPounds() { + int pounds = 1; + while (lookahead == '#') { + nextChar(); + pounds++; + } + if (lookahead == EOF) { + throw lexError(ErrorMessages.create("unexpectedEndOfFile"), span()); + } + if (lookahead != '"') { + throw unexpectedChar(lookahead, "\""); + } + nextChar(); + return lexStringStart(pounds); + } + + private Token lexStringStart(int pounds) { + var quotes = 1; + if (lookahead == '"') { + nextChar(); + if (lookahead == '"') { + nextChar(); + quotes = 3; + } else { + backup(); } } - - KEYWORD_TYPES = Collections.unmodifiableSet(keywordTypes); - KEYWORD_NAMES = Collections.unmodifiableSet(keywordNames); + state = State.STRING; + interpolationStack.push(new InterpolationScope(quotes, pounds)); + stringEnded = false; + if (quotes == 1) return Token.STRING_START; + return Token.STRING_MULTI_START; } - @TruffleBoundary - public static PklLexer createLexer(CharStream source) { - var lexer = new PklLexer(source); - lexer.removeErrorListeners(); - lexer.addErrorListener( - new ANTLRErrorListener<>() { - @Override - public void syntaxError( - Recognizer recognizer, - T offendingSymbol, - int line, - int charPositionInLine, - String msg, - RecognitionException e) { - var lexer = ((org.antlr.v4.runtime.Lexer) recognizer); - throw new LexParseException.LexError( - msg, - line, - charPositionInLine + 1, - lexer._input.index() - lexer._tokenStartCharIndex); + private void lexStringEnd(InterpolationScope scope) { + // don't actually need to check it here + for (var i = 0; i < scope.quotes + scope.pounds; i++) { + nextChar(); + } + } + + private void lexString(int pounds) { + var poundsInARow = 0; + var foundQuote = false; + var foundBackslash = false; + while (lookahead != EOF) { + var ch = nextChar(); + switch (ch) { + case '\n', '\r' -> + throw lexError( + ErrorMessages.create("missingDelimiter", "\"" + "#".repeat(pounds)), cursor - 1, 1); + case '"' -> { + if (pounds == 0) { + backup(); + stringEnded = true; + return; } - }); - return lexer; + foundQuote = true; + foundBackslash = false; + poundsInARow = 0; + } + case '\\' -> { + foundQuote = false; + foundBackslash = true; + poundsInARow = 0; + if (pounds == poundsInARow) { + backup(pounds + 1); + isEscape = true; + return; + } + } + case '#' -> { + poundsInARow++; + if (foundQuote && (pounds == poundsInARow)) { + backup(pounds + 1); + stringEnded = true; + return; + } + if (foundBackslash && pounds == poundsInARow) { + backup(pounds + 1); + isEscape = true; + return; + } + } + default -> { + foundQuote = false; + foundBackslash = false; + poundsInARow = 0; + } + } + } } - @TruffleBoundary - public static boolean isKeyword(@Nullable Token token) { - return token != null && KEYWORD_TYPES.contains(token.getType()); + private void lexMultiString(int pounds) { + var poundsInARow = 0; + var quotesInARow = 0; + var foundBackslash = false; + while (lookahead != EOF && lookahead != '\n' && lookahead != '\r') { + var ch = nextChar(); + switch (ch) { + case '"' -> { + quotesInARow++; + if (quotesInARow == 3 && pounds == 0) { + backup(3); + stringEnded = true; + return; + } + poundsInARow = 0; + foundBackslash = false; + } + case '\\' -> { + quotesInARow = 0; + poundsInARow = 0; + foundBackslash = true; + if (pounds == poundsInARow) { + backup(pounds + 1); + isEscape = true; + return; + } + } + case '#' -> { + poundsInARow++; + if (quotesInARow == 3 && pounds == poundsInARow) { + backup(pounds + 3); + stringEnded = true; + return; + } + if (foundBackslash && pounds == poundsInARow) { + backup(pounds + 1); + isEscape = true; + return; + } + } + default -> { + quotesInARow = 0; + poundsInARow = 0; + foundBackslash = false; + } + } + } + } + + private Token lexEscape() { + if (lookahead == EOF) throw unexpectedEndOfFile(); + var ch = nextChar(); + return switch (ch) { + case 'n' -> Token.STRING_ESCAPE_NEWLINE; + case '"' -> Token.STRING_ESCAPE_QUOTE; + case '\\' -> Token.STRING_ESCAPE_BACKSLASH; + case 't' -> Token.STRING_ESCAPE_TAB; + case 'r' -> Token.STRING_ESCAPE_RETURN; + case '(' -> { + var scope = interpolationStack.getFirst(); + scope.parens++; + state = State.DEFAULT; + yield Token.INTERPOLATION_START; + } + case 'u' -> lexUnicodeEscape(); + default -> + throw lexError( + ErrorMessages.create("invalidCharacterEscapeSequence", "\\" + ch, "\\"), + cursor - 2, + 2); + }; + } + + private Token lexUnicodeEscape() { + if (lookahead != '{') { + throw unexpectedChar(lookahead, "{"); + } + do { + nextChar(); + } while (lookahead != '}' && lookahead != EOF && Character.isLetterOrDigit(lookahead)); + if (lookahead == '}') { + // consume the close bracket + nextChar(); + } else { + throw lexError(ErrorMessages.create("unterminatedUnicodeEscapeSequence", text()), span()); + } + return Token.STRING_ESCAPE_UNICODE; + } + + private Token lexIdentifier() { + while (isIdentifierPart(lookahead)) { + nextChar(); + } + + var identifierStr = text(); + var identifier = getKeywordOrIdentifier(identifierStr); + return switch (identifier) { + case IMPORT -> { + if (lookahead == '*') { + nextChar(); + yield Token.IMPORT_STAR; + } else yield Token.IMPORT; + } + case READ -> + switch (lookahead) { + case '*' -> { + nextChar(); + yield Token.READ_STAR; + } + case '?' -> { + nextChar(); + yield Token.READ_QUESTION; + } + default -> Token.READ; + }; + default -> identifier; + }; + } + + private void lexQuotedIdentifier() { + while (lookahead != '`' && lookahead != '\n' && lookahead != '\r') { + nextChar(); + } + if (lookahead == '`') { + nextChar(); + } else { + throw unexpectedChar(lookahead, "backquote"); + } + } + + private Token lexNumber(char start) { + if (start == '0') { + if (lookahead == 'x' || lookahead == 'X') { + nextChar(); + lexHexNumber(); + return Token.HEX; + } + if (lookahead == 'b' || lookahead == 'B') { + nextChar(); + lexBinNumber(); + return Token.BIN; + } + if (lookahead == 'o' || lookahead == 'O') { + nextChar(); + lexOctNumber(); + return Token.OCT; + } + if (lookahead == 'e' || lookahead == 'E') { + nextChar(); + lexExponent(); + return Token.FLOAT; + } + } else if (start == '.') { + lexDotNumber(); + return Token.FLOAT; + } + + while ((lookahead >= 48 && lookahead <= 57) || lookahead == '_') { + nextChar(); + } + + if (lookahead == 'e' || lookahead == 'E') { + nextChar(); + lexExponent(); + return Token.FLOAT; + } else if (lookahead == '.') { + nextChar(); + if (lookahead == '_') { + throw lexError("invalidSeparatorPosition"); + } + if (lookahead < 48 || lookahead > 57) { + backup(); + return Token.INT; + } + lexDotNumber(); + return Token.FLOAT; + } + return Token.INT; + } + + private Token lexSlash() { + switch (lookahead) { + case '/': + { + nextChar(); + var token = lookahead == '/' ? Token.DOC_COMMENT : Token.LINE_COMMENT; + while (lookahead != '\n' && lookahead != '\r' && lookahead != EOF) { + nextChar(); + } + return token; + } + case '*': + { + nextChar(); + lexBlockComment(); + return Token.BLOCK_COMMENT; + } + default: + return Token.DIV; + } + } + + private void lexBlockComment() { + if (lookahead == EOF) throw unexpectedEndOfFile(); + var prev = nextChar(); + // block comments in Pkl can stack + var stack = 1; + while (stack > 0 && lookahead != EOF) { + if (prev == '*' && lookahead == '/') stack--; + if (prev == '/' && lookahead == '*') stack++; + prev = nextChar(); + } + if (lookahead == EOF) throw unexpectedEndOfFile(); + } + + private void lexHexNumber() { + if (lookahead == '_') { + throw lexError("invalidSeparatorPosition"); + } + if (!isHex(lookahead)) { + throw unexpectedChar(lookahead, "hexadecimal number"); + } + while (isHex(lookahead) || lookahead == '_') { + nextChar(); + } + } + + private void lexBinNumber() { + if (lookahead == '_') { + throw lexError("invalidSeparatorPosition"); + } + if (!(lookahead == '0' || lookahead == '1')) { + throw unexpectedChar(lookahead, "binary number"); + } + while (lookahead == '0' || lookahead == '1' || lookahead == '_') { + nextChar(); + } + } + + private void lexOctNumber() { + if (lookahead == '_') { + throw lexError("invalidSeparatorPosition"); + } + var ch = (int) lookahead; + if (!(ch >= 48 && ch <= 55)) { + throw unexpectedChar((char) ch, "octal number"); + } + while ((ch >= 48 && ch <= 55) || ch == '_') { + nextChar(); + ch = lookahead; + } + } + + private void lexExponent() { + if (lookahead == '+' || lookahead == '-') { + nextChar(); + } + if (lookahead == '_') { + throw lexError("invalidSeparatorPosition"); + } + if (lookahead < 48 || lookahead > 57) { + throw unexpectedChar(lookahead, "number"); + } + while ((lookahead >= 48 && lookahead <= 57) || lookahead == '_') { + nextChar(); + } + } + + private void lexDotNumber() { + if (lookahead == '_') { + throw lexError("invalidSeparatorPosition"); + } + while ((lookahead >= 48 && lookahead <= 57) || lookahead == '_') { + nextChar(); + } + if (lookahead == 'e' || lookahead == 'E') { + nextChar(); + lexExponent(); + } + } + + private Token lexShebang() { + do { + nextChar(); + } while (lookahead != '\n' && lookahead != '\r' && lookahead != EOF); + return Token.SHEBANG; + } + + private boolean isHex(char ch) { + var code = (int) ch; + return (code >= 48 && code <= 57) || (code >= 97 && code <= 102) || (code >= 65 && code <= 70); + } + + private static boolean isIdentifierStart(char c) { + return c == '_' || c == '$' || Character.isUnicodeIdentifierStart(c); + } + + private static boolean isIdentifierPart(char c) { + return c != EOF && (c == '$' || Character.isUnicodeIdentifierPart(c)); + } + + private char nextChar() { + var tmp = lookahead; + cursor++; + if (cursor >= size) { + lookahead = EOF; + } else { + lookahead = source[cursor]; + } + return tmp; + } + + private void backup() { + lookahead = source[--cursor]; + } + + private void backup(int amount) { + cursor -= amount; + lookahead = source[cursor]; + } + + private ParserError lexError(String msg, Object... args) { + var length = lookahead == EOF ? 0 : 1; + var index = lookahead == EOF ? cursor - 1 : cursor; + return new ParserError(ErrorMessages.create(msg, args), new Span(index, length)); + } + + private ParserError lexError(String msg, int charIndex, int length) { + return new ParserError(msg, new Span(charIndex, length)); + } + + private ParserError lexError(String msg, Span span) { + return new ParserError(msg, span); + } + + private ParserError unexpectedChar(char got, String didYouMean) { + return lexError("unexpectedCharacter", got, didYouMean); + } + + private ParserError unexpectedChar(String got, String option1, String option2, String option3) { + return lexError("unexpectedCharacter3", got, option1, option2, option3); + } + + private ParserError unexpectedEndOfFile() { + return lexError(ErrorMessages.create("unexpectedEndOfFile"), cursor, 0); } - @TruffleBoundary public static boolean isRegularIdentifier(String identifier) { if (identifier.isEmpty()) return false; - if (KEYWORD_NAMES.contains(identifier)) return false; + if (isKeyword(identifier)) return false; var firstCp = identifier.codePointAt(0); return (firstCp == '$' || firstCp == '_' || Character.isUnicodeIdentifierStart(firstCp)) @@ -91,8 +730,89 @@ public final class Lexer { .allMatch(cp -> cp == '$' || Character.isUnicodeIdentifierPart(cp)); } - @TruffleBoundary public static String maybeQuoteIdentifier(String identifier) { return isRegularIdentifier(identifier) ? identifier : "`" + identifier + "`"; } + + @SuppressWarnings("SuspiciousArrayMethodCall") + private static boolean isKeyword(String text) { + var index = Arrays.binarySearch(KEYWORDS, text); + return index >= 0; + } + + @SuppressWarnings("SuspiciousArrayMethodCall") + private static Token getKeywordOrIdentifier(String text) { + var index = Arrays.binarySearch(KEYWORDS, text); + if (index < 0) return Token.IDENTIFIER; + return KEYWORDS[index].token; + } + + protected static final KeywordEntry[] KEYWORDS = { + new KeywordEntry("_", Token.UNDERSCORE), + new KeywordEntry("abstract", Token.ABSTRACT), + new KeywordEntry("amends", Token.AMENDS), + new KeywordEntry("as", Token.AS), + new KeywordEntry("case", Token.CASE), + new KeywordEntry("class", Token.CLASS), + new KeywordEntry("const", Token.CONST), + new KeywordEntry("delete", Token.DELETE), + new KeywordEntry("else", Token.ELSE), + new KeywordEntry("extends", Token.EXTENDS), + new KeywordEntry("external", Token.EXTERNAL), + new KeywordEntry("false", Token.FALSE), + new KeywordEntry("fixed", Token.FIXED), + new KeywordEntry("for", Token.FOR), + new KeywordEntry("function", Token.FUNCTION), + new KeywordEntry("hidden", Token.HIDDEN), + new KeywordEntry("if", Token.IF), + new KeywordEntry("import", Token.IMPORT), + new KeywordEntry("in", Token.IN), + new KeywordEntry("is", Token.IS), + new KeywordEntry("let", Token.LET), + new KeywordEntry("local", Token.LOCAL), + new KeywordEntry("module", Token.MODULE), + new KeywordEntry("new", Token.NEW), + new KeywordEntry("nothing", Token.NOTHING), + new KeywordEntry("null", Token.NULL), + new KeywordEntry("open", Token.OPEN), + new KeywordEntry("out", Token.OUT), + new KeywordEntry("outer", Token.OUTER), + new KeywordEntry("override", Token.OVERRIDE), + new KeywordEntry("protected", Token.PROTECTED), + new KeywordEntry("read", Token.READ), + new KeywordEntry("record", Token.RECORD), + new KeywordEntry("super", Token.SUPER), + new KeywordEntry("switch", Token.SWITCH), + new KeywordEntry("this", Token.THIS), + new KeywordEntry("throw", Token.THROW), + new KeywordEntry("trace", Token.TRACE), + new KeywordEntry("true", Token.TRUE), + new KeywordEntry("typealias", Token.TYPE_ALIAS), + new KeywordEntry("unknown", Token.UNKNOWN), + new KeywordEntry("vararg", Token.VARARG), + new KeywordEntry("when", Token.WHEN) + }; + + protected record KeywordEntry(String name, Token token) implements Comparable { + @Override + public int compareTo(String o) { + return name.compareTo(o); + } + } + + private static class InterpolationScope { + final int quotes; + final int pounds; + int parens = 0; + + protected InterpolationScope(int quotes, int pounds) { + this.quotes = quotes; + this.pounds = pounds; + } + } + + private enum State { + DEFAULT, + STRING + } } diff --git a/pkl-core/src/main/java/org/pkl/core/parser/OperatorResolver.java b/pkl-core/src/main/java/org/pkl/core/parser/OperatorResolver.java new file mode 100644 index 00000000..6dc77b56 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/OperatorResolver.java @@ -0,0 +1,140 @@ +/* + * Copyright © 2024-2025 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.core.parser; + +import java.util.ArrayList; +import java.util.List; +import org.pkl.core.parser.ast.Expr; +import org.pkl.core.parser.ast.Expr.BinaryOperatorExpr; +import org.pkl.core.parser.ast.Expr.OperatorExpr; +import org.pkl.core.parser.ast.Expr.TypeCastExpr; +import org.pkl.core.parser.ast.Expr.TypeCheckExpr; +import org.pkl.core.parser.ast.Expr.TypeExpr; +import org.pkl.core.parser.ast.Operator; +import org.pkl.core.util.Nullable; + +class OperatorResolver { + private OperatorResolver() {} + + private enum Associativity { + LEFT, + RIGHT + } + + public static int getPrecedence(Operator op) { + return switch (op) { + case NULL_COALESCE -> 0; + case PIPE -> 1; + case OR -> 2; + case AND -> 3; + case EQ_EQ, NOT_EQ -> 4; + case IS, AS -> 5; + case LT, LTE, GT, GTE -> 6; + case PLUS, MINUS -> 7; + case MULT, DIV, INT_DIV, MOD -> 8; + case POW -> 9; + case DOT, QDOT -> 10; + }; + } + + private static Associativity getAssociativity(Operator op) { + return switch (op) { + case POW, NULL_COALESCE -> Associativity.RIGHT; + default -> Associativity.LEFT; + }; + } + + private static @Nullable Operator getHighestPrecedence(List exprs, int min) { + var highest = -1; + Operator op = null; + for (var expr : exprs) { + if (expr instanceof OperatorExpr o) { + var precedence = getPrecedence(o.getOp()); + if (precedence > highest && precedence >= min) { + highest = precedence; + op = o.getOp(); + } + } + } + return op; + } + + private static int index(List exprs, Associativity associativity, Operator op) { + if (associativity == Associativity.LEFT) { + for (var i = 0; i < exprs.size(); i++) { + if (exprs.get(i) instanceof OperatorExpr operator && operator.getOp() == op) { + return i; + } + } + } else { + for (var i = exprs.size() - 1; i >= 0; i--) { + if (exprs.get(i) instanceof OperatorExpr operator && operator.getOp() == op) { + return i; + } + } + } + return -1; + } + + private static List resolveOperator( + List exprs, Associativity associativity, Operator op) { + var res = new ArrayList<>(exprs); + + var i = index(res, associativity, op); + var left = res.get(i - 1); + var right = res.get(i + 1); + var span = left.span().endWith(right.span()); + var binOp = + switch (op) { + case IS -> new TypeCheckExpr(left, ((TypeExpr) right).getType(), span); + case AS -> new TypeCastExpr(left, ((TypeExpr) right).getType(), span); + default -> new BinaryOperatorExpr(left, right, op, span); + }; + res.remove(i - 1); + res.remove(i - 1); + res.remove(i - 1); + res.add(i - 1, binOp); + return res; + } + + /** + * Resolve all operators based on their precedence and associativity. This requires that the list + * has a valid form: `expr` `op` `expr` ... + */ + public static Expr resolveOperators(List exprs) { + if (exprs.size() == 1) return exprs.get(0); + + var res = resolveOperatorsHigherThan(exprs, 0); + if (res.size() > 1) { + throw new ParserError( + "Malformed expression", exprs.get(0).span().endWith(exprs.get(exprs.size() - 1).span())); + } + + return res.get(0); + } + + public static List resolveOperatorsHigherThan(List exprs, int minPrecedence) { + var res = exprs; + var highest = getHighestPrecedence(res, minPrecedence); + while (highest != null) { + var associativity = getAssociativity(highest); + res = resolveOperator(res, associativity, highest); + highest = getHighestPrecedence(res, minPrecedence); + } + + return res; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/Parser.java b/pkl-core/src/main/java/org/pkl/core/parser/Parser.java index 37d54916..1b50cff4 100644 --- a/pkl-core/src/main/java/org/pkl/core/parser/Parser.java +++ b/pkl-core/src/main/java/org/pkl/core/parser/Parser.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -15,218 +15,1612 @@ */ package org.pkl.core.parser; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import java.util.ArrayList; -import java.util.Comparator; +import java.util.Arrays; +import java.util.Collections; import java.util.List; -import java.util.function.Function; -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.PredictionMode; -import org.antlr.v4.runtime.tree.ParseTree; -import org.pkl.core.parser.LexParseException.IncompleteInput; -import org.pkl.core.parser.antlr.PklLexer; -import org.pkl.core.parser.antlr.PklParser; -import org.pkl.core.parser.antlr.PklParser.*; +import java.util.function.Supplier; +import org.pkl.core.PklBugException; +import org.pkl.core.parser.ast.Annotation; +import org.pkl.core.parser.ast.ArgumentList; +import org.pkl.core.parser.ast.Class; +import org.pkl.core.parser.ast.ClassBody; +import org.pkl.core.parser.ast.ClassMethod; +import org.pkl.core.parser.ast.ClassProperty; +import org.pkl.core.parser.ast.DocComment; +import org.pkl.core.parser.ast.Expr; +import org.pkl.core.parser.ast.Expr.AmendsExpr; +import org.pkl.core.parser.ast.Expr.BoolLiteralExpr; +import org.pkl.core.parser.ast.Expr.FloatLiteralExpr; +import org.pkl.core.parser.ast.Expr.FunctionLiteralExpr; +import org.pkl.core.parser.ast.Expr.IfExpr; +import org.pkl.core.parser.ast.Expr.IntLiteralExpr; +import org.pkl.core.parser.ast.Expr.LetExpr; +import org.pkl.core.parser.ast.Expr.LogicalNotExpr; +import org.pkl.core.parser.ast.Expr.ModuleExpr; +import org.pkl.core.parser.ast.Expr.MultiLineStringLiteralExpr; +import org.pkl.core.parser.ast.Expr.NewExpr; +import org.pkl.core.parser.ast.Expr.NonNullExpr; +import org.pkl.core.parser.ast.Expr.NullLiteralExpr; +import org.pkl.core.parser.ast.Expr.OperatorExpr; +import org.pkl.core.parser.ast.Expr.OuterExpr; +import org.pkl.core.parser.ast.Expr.ParenthesizedExpr; +import org.pkl.core.parser.ast.Expr.QualifiedAccessExpr; +import org.pkl.core.parser.ast.Expr.ReadExpr; +import org.pkl.core.parser.ast.Expr.ReadType; +import org.pkl.core.parser.ast.Expr.SingleLineStringLiteralExpr; +import org.pkl.core.parser.ast.Expr.SubscriptExpr; +import org.pkl.core.parser.ast.Expr.SuperAccessExpr; +import org.pkl.core.parser.ast.Expr.SuperSubscriptExpr; +import org.pkl.core.parser.ast.Expr.ThisExpr; +import org.pkl.core.parser.ast.Expr.ThrowExpr; +import org.pkl.core.parser.ast.Expr.TraceExpr; +import org.pkl.core.parser.ast.Expr.UnaryMinusExpr; +import org.pkl.core.parser.ast.Expr.UnqualifiedAccessExpr; +import org.pkl.core.parser.ast.ExtendsOrAmendsClause; +import org.pkl.core.parser.ast.Identifier; +import org.pkl.core.parser.ast.ImportClause; +import org.pkl.core.parser.ast.Modifier; +import org.pkl.core.parser.ast.Module; +import org.pkl.core.parser.ast.ModuleDecl; +import org.pkl.core.parser.ast.Node; +import org.pkl.core.parser.ast.ObjectBody; +import org.pkl.core.parser.ast.ObjectMember; +import org.pkl.core.parser.ast.Operator; +import org.pkl.core.parser.ast.Parameter; +import org.pkl.core.parser.ast.Parameter.TypedIdentifier; +import org.pkl.core.parser.ast.ParameterList; +import org.pkl.core.parser.ast.QualifiedIdentifier; +import org.pkl.core.parser.ast.ReplInput; +import org.pkl.core.parser.ast.StringConstant; +import org.pkl.core.parser.ast.StringConstantPart; +import org.pkl.core.parser.ast.StringConstantPart.EscapeType; +import org.pkl.core.parser.ast.StringConstantPart.StringEscape; +import org.pkl.core.parser.ast.StringConstantPart.StringNewline; +import org.pkl.core.parser.ast.StringPart; +import org.pkl.core.parser.ast.StringPart.StringConstantParts; +import org.pkl.core.parser.ast.Type; +import org.pkl.core.parser.ast.Type.DeclaredType; +import org.pkl.core.parser.ast.Type.ParenthesizedType; +import org.pkl.core.parser.ast.Type.StringConstantType; +import org.pkl.core.parser.ast.TypeAlias; +import org.pkl.core.parser.ast.TypeAnnotation; +import org.pkl.core.parser.ast.TypeParameter; +import org.pkl.core.parser.ast.TypeParameterList; +import org.pkl.core.util.ErrorMessages; import org.pkl.core.util.Nullable; -public final class Parser { - @TruffleBoundary - public PklParser createParser( - TokenStream stream, @Nullable List errorCollector) { - var parser = new PklParser(stream); - parser.setErrorHandler(new ErrorStrategy()); - registerErrorListener(parser, errorCollector); - return parser; +@SuppressWarnings("DuplicatedCode") +public class Parser { + + private Lexer lexer; + private Token lookahead; + private Span spanLookahead; + private boolean backtracking = false; + private FullToken prev; + private FullToken _lookahead; + private boolean precededBySemicolon = false; + + public Parser() {} + + private void init(String source) { + this.lexer = new Lexer(source); + _lookahead = forceNext(); + lookahead = _lookahead.token; + spanLookahead = _lookahead.span; } - @TruffleBoundary - public ModuleContext parseModule(CharStream source) throws LexParseException { - return parseProduction(source, PklParser::module); + public Module parseModule(String source) { + init(source); + if (lookahead == Token.EOF) { + return new Module(Collections.singletonList(null), new Span(0, 0)); + } + var start = spanLookahead; + Span end = null; + ModuleDecl moduleDecl; + var nodes = new ArrayList(); + try { + var header = parseMemberHeader(); + + moduleDecl = parseModuleDecl(header); + if (moduleDecl != null) { + end = moduleDecl.span(); + header = null; + } + nodes.add(moduleDecl); + // imports + while (lookahead == Token.IMPORT || lookahead == Token.IMPORT_STAR) { + if (header != null && header.isNotEmpty()) { + throw parserError("wrongHeaders", "Imports"); + } + var _import = parseImportDecl(); + nodes.add(_import); + end = _import.span(); + } + + // entries + if (header != null && header.isNotEmpty()) { + end = parseModuleMember(header, nodes); + } + + while (lookahead != Token.EOF) { + header = parseMemberHeader(); + end = parseModuleMember(header, nodes); + } + assert end != null; + return new Module(nodes, start.endWith(end)); + } catch (ParserError pe) { + var spanEnd = end != null ? end : start; + pe.setPartialParseResult(new Module(nodes, start.endWith(spanEnd))); + throw pe; + } } - @TruffleBoundary - public ModuleContext parseModule(String source) throws LexParseException { - return parseModule(toCharStream(source)); + public Expr parseExpressionInput(String source) { + init(source); + var expr = parseExpr(); + expect(Token.EOF, "unexpectedToken", "end of file"); + return expr; } - @TruffleBoundary - public ReplInputContext parseReplInput(CharStream source) throws LexParseException { - var ctx = parseProduction(source, PklParser::replInput); - checkIsCompleteInput(ctx); - return ctx; + public ReplInput parseReplInput(String source) { + init(source); + var nodes = new ArrayList(); + while (lookahead != Token.EOF) { + var header = parseMemberHeader(); + switch (lookahead) { + case IMPORT, IMPORT_STAR -> { + ensureEmptyHeaders(header, "Imports"); + nodes.add(parseImportDecl()); + } + case MODULE, AMENDS, EXTENDS -> nodes.add(parseModuleDecl(header)); + case CLASS -> nodes.add(parseClass(header)); + case TYPE_ALIAS -> nodes.add(parseTypeAlias(header)); + case FUNCTION -> nodes.add(parseClassMethod(header)); + case IDENTIFIER -> { + next(); + switch (lookahead) { + case COLON, ASSIGN, LBRACE -> { + backtrack(); + nodes.add(parseClassProperty(header)); + } + default -> { + backtrack(); + ensureEmptyHeaders(header, "Expressions"); + nodes.add(parseExpr()); + } + } + } + default -> { + ensureEmptyHeaders(header, "Expressions"); + nodes.add(parseExpr()); + } + } + } + Span span; + if (nodes.isEmpty()) { + span = new Span(0, 0); + } else { + span = nodes.get(0).span().endWith(nodes.get(nodes.size() - 1).span()); + } + return new ReplInput(nodes, span); } - @TruffleBoundary - public ReplInputContext parseReplInput(String source) throws LexParseException { - return parseReplInput(toCharStream(source)); + private @Nullable ModuleDecl parseModuleDecl(MemberHeader header) { + QualifiedIdentifier moduleName = null; + Span start = null; + Span end = null; + if (lookahead == Token.MODULE) { + start = expect(Token.MODULE, "unexpectedToken", "module").span; + moduleName = parseQualifiedIdentifier(); + end = moduleName.span(); + } + var extendsOrAmendsDecl = parseExtendsAmendsDecl(); + if (extendsOrAmendsDecl != null) { + if (start == null) { + start = extendsOrAmendsDecl.span(); + } + end = extendsOrAmendsDecl.span(); + } + if (moduleName != null || extendsOrAmendsDecl != null) { + var children = new ArrayList(); + children.add(header.docComment); + children.addAll(header.annotations); + var modifiersOffset = children.size(); + children.addAll(header.modifiers); + var nameOffset = children.size(); + children.add(moduleName); + children.add(extendsOrAmendsDecl); + return new ModuleDecl(children, modifiersOffset, nameOffset, start.endWith(end)); + } + return null; } - @TruffleBoundary - public ExprInputContext parseExpressionInput(CharStream source) throws LexParseException { - var ctx = parseProduction(source, PklParser::exprInput); - checkIsCompleteInput(ctx); - return ctx; + private QualifiedIdentifier parseQualifiedIdentifier() { + var idents = parseListOf(Token.DOT, this::parseIdentifier); + return new QualifiedIdentifier(idents); + } + + private @Nullable ExtendsOrAmendsClause parseExtendsAmendsDecl() { + if (lookahead == Token.EXTENDS) { + var tk = next().span; + var url = parseStringConstant(); + return new ExtendsOrAmendsClause( + url, ExtendsOrAmendsClause.Type.EXTENDS, tk.endWith(url.span())); + } + if (lookahead == Token.AMENDS) { + var tk = next().span; + var url = parseStringConstant(); + return new ExtendsOrAmendsClause( + url, ExtendsOrAmendsClause.Type.AMENDS, tk.endWith(url.span())); + } + return null; + } + + private ImportClause parseImportDecl() { + Span start; + boolean isGlob = false; + if (lookahead == Token.IMPORT_STAR) { + start = next().span; + isGlob = true; + } else { + start = expect(Token.IMPORT, "unexpectedToken2", "import", "import*").span; + } + var str = parseStringConstant(); + var end = str.span(); + Identifier alias = null; + if (lookahead == Token.AS) { + next(); + alias = parseIdentifier(); + end = alias.span(); + } + return new ImportClause(str, isGlob, alias, start.endWith(end)); + } + + private MemberHeader parseMemberHeader() { + DocComment docComment = null; + var annotations = new ArrayList(); + var modifiers = new ArrayList(); + if (lookahead == Token.DOC_COMMENT) { + docComment = parseDocComment(); + } + while (lookahead == Token.AT) { + annotations.add(parseAnnotation()); + } + while (lookahead.isModifier()) { + modifiers.add(parseModifier()); + } + return new MemberHeader(docComment, annotations, modifiers); + } + + private DocComment parseDocComment() { + var spans = new ArrayList(); + spans.add(nextComment().span); + while (lookahead == Token.DOC_COMMENT + || lookahead == Token.LINE_COMMENT + || lookahead == Token.BLOCK_COMMENT) { + var next = nextComment(); + // newlines are not allowed in doc comments + if (next.newLinesBetween > 1) { + if (next.token == Token.DOC_COMMENT) { + backtrack(); + } + break; + } + if (next.token == Token.DOC_COMMENT) { + spans.add(next.span); + } + } + while (lookahead == Token.LINE_COMMENT || lookahead == Token.BLOCK_COMMENT) { + nextComment(); + } + return new DocComment(spans); + } + + private Span parseModuleMember(MemberHeader header, List nodes) { + switch (lookahead) { + case IDENTIFIER -> { + var node = parseClassProperty(header); + nodes.add(node); + return node.span(); + } + case TYPE_ALIAS -> { + var node = parseTypeAlias(header); + nodes.add(node); + return node.span(); + } + case CLASS -> { + var node = parseClass(header); + nodes.add(node); + return node.span(); + } + case FUNCTION -> { + var node = parseClassMethod(header); + nodes.add(node); + return node.span(); + } + case EOF -> throw parserError("unexpectedEndOfFile"); + default -> { + if (lookahead.isKeyword()) { + throw parserError("keywordNotAllowedHere", lookahead.text()); + } + if (lookahead == Token.DOC_COMMENT) { + throw parserError("danglingDocComment"); + } + throw parserError("invalidTopLevelToken"); + } + } + } + + private TypeAlias parseTypeAlias(MemberHeader header) { + var start = next().span; + var startSpan = header.span(start); + + var identifier = parseIdentifier(); + TypeParameterList typePars = null; + if (lookahead == Token.LT) { + typePars = parseTypeParameterList(); + } + expect(Token.ASSIGN, "unexpectedToken", "="); + var type = parseType(); + var children = new ArrayList(header.annotations.size() + header.modifiers.size() + 4); + children.add(header.docComment); + children.addAll(header.annotations); + var modifiersOffset = header.annotations.size() + 1; + children.addAll(header.modifiers); + var nameOffset = modifiersOffset + header.modifiers.size(); + children.add(identifier); + children.add(typePars); + children.add(type); + return new TypeAlias(children, modifiersOffset, nameOffset, startSpan.endWith(type.span())); + } + + private Class parseClass(MemberHeader header) { + var start = next().span; + var startSpan = header.span(start); + var children = new ArrayList(); + children.add(header.docComment); + children.addAll(header.annotations); + var modifiersOffset = header.annotations.size() + 1; + children.addAll(header.modifiers); + var nameOffset = modifiersOffset + header.modifiers.size(); + var name = parseIdentifier(); + children.add(name); + TypeParameterList typePars = null; + var end = name.span(); + if (lookahead == Token.LT) { + typePars = parseTypeParameterList(); + end = typePars.span(); + } + children.add(typePars); + Type superClass = null; + if (lookahead == Token.EXTENDS) { + next(); + superClass = parseType(); + end = superClass.span(); + } + children.add(superClass); + + ClassBody body = null; + if (lookahead == Token.LBRACE) { + body = parseClassBody(); + end = body.span(); + } + children.add(body); + + return new Class(children, modifiersOffset, nameOffset, startSpan.endWith(end)); + } + + private ClassBody parseClassBody() { + var start = expect(Token.LBRACE, "missingDelimiter", "{").span; + var children = new ArrayList(); + while (lookahead != Token.RBRACE && lookahead != Token.EOF) { + var entryHeader = parseMemberHeader(); + if (lookahead == Token.FUNCTION) { + children.add(parseClassMethod(entryHeader)); + } else { + children.add(parseClassProperty(entryHeader)); + } + } + if (lookahead == Token.EOF) { + throw new ParserError( + ErrorMessages.create("missingDelimiter", "}"), prev.span.stopSpan().move(1)); + } + var end = expect(Token.RBRACE, "missingDelimiter", "}").span; + return new ClassBody(children, start.endWith(end)); + } + + private ClassProperty parseClassProperty(MemberHeader header) { + var name = parseIdentifier(); + var start = header.span(name.span()); + var children = new ArrayList(); + children.add(header.docComment); + children.addAll(header.annotations); + var modifiersOffset = header.annotations.size() + 1; + children.addAll(header.modifiers); + var nameOffset = modifiersOffset + header.modifiers.size(); + TypeAnnotation typeAnnotation = null; + Expr expr = null; + var bodies = new ArrayList(); + if (lookahead == Token.COLON) { + typeAnnotation = parseTypeAnnotation(); + } + if (lookahead == Token.ASSIGN) { + next(); + expr = parseExpr(); + } else if (lookahead == Token.LBRACE) { + if (typeAnnotation != null) { + throw parserError("typeAnnotationInAmends"); + } + while (lookahead == Token.LBRACE) { + bodies.add(parseObjectBody()); + } + } + children.add(name); + children.add(typeAnnotation); + children.add(expr); + children.addAll(bodies); + if (expr != null) { + return new ClassProperty(children, modifiersOffset, nameOffset, start.endWith(expr.span())); + } + if (!bodies.isEmpty()) { + return new ClassProperty( + children, + modifiersOffset, + nameOffset, + start.endWith(bodies.get(bodies.size() - 1).span())); + } + if (typeAnnotation == null) { + throw new ParserError(ErrorMessages.create("invalidProperty"), name.span()); + } + return new ClassProperty( + children, modifiersOffset, nameOffset, start.endWith(typeAnnotation.span())); + } + + private ClassMethod parseClassMethod(MemberHeader header) { + var func = expect(Token.FUNCTION, "unexpectedToken", "function").span; + var start = header.span(func); + var headerSpanStart = header.modifierSpan(func); + var children = new ArrayList(); + children.add(header.docComment); + children.addAll(header.annotations); + var modifiersOffset = header.annotations.size() + 1; + children.addAll(header.modifiers); + var nameOffset = modifiersOffset + header.modifiers.size(); + var name = parseIdentifier(); + children.add(name); + TypeParameterList typePars = null; + if (lookahead == Token.LT) { + typePars = parseTypeParameterList(); + } + children.add(typePars); + var parameterList = parseParameterList(); + children.add(parameterList); + var end = parameterList.span(); + var endHeader = end; + TypeAnnotation typeAnnotation = null; + if (lookahead == Token.COLON) { + typeAnnotation = parseTypeAnnotation(); + end = typeAnnotation.span(); + endHeader = end; + } + children.add(typeAnnotation); + Expr expr = null; + if (lookahead == Token.ASSIGN) { + next(); + expr = parseExpr(); + end = expr.span(); + } + children.add(expr); + return new ClassMethod( + children, + modifiersOffset, + nameOffset, + headerSpanStart.endWith(endHeader), + start.endWith(end)); + } + + private ObjectBody parseObjectBody() { + var start = expect(Token.LBRACE, "unexpectedToken", "{").span; + List nodes = new ArrayList<>(); + var membersOffset = -1; + if (lookahead == Token.RBRACE) { + return new ObjectBody(List.of(), 0, start.endWith(next().span)); + } else if (lookahead == Token.UNDERSCORE) { + // it's a parameter + nodes.addAll(parseListOfParameter(Token.COMMA)); + expect(Token.ARROW, "unexpectedToken2", ",", "->"); + } else if (lookahead == Token.IDENTIFIER) { + // not sure what it is yet + var identifier = parseIdentifier(); + if (lookahead == Token.ARROW) { + // it's a parameter + next(); + nodes.add(new TypedIdentifier(identifier, null, identifier.span())); + } else if (lookahead == Token.COMMA) { + // it's a parameter + backtrack(); + nodes.addAll(parseListOfParameter(Token.COMMA)); + expect(Token.ARROW, "unexpectedToken2", ",", "->"); + } else if (lookahead == Token.COLON) { + // still not sure + var colon = next().span; + var type = parseType(); + var typeAnnotation = new TypeAnnotation(type, colon.endWith(type.span())); + if (lookahead == Token.COMMA) { + // it's a parameter + next(); + nodes.add(new TypedIdentifier(identifier, typeAnnotation, identifier.span())); + nodes.addAll(parseListOfParameter(Token.COMMA)); + expect(Token.ARROW, "unexpectedToken2", ",", "->"); + } else if (lookahead == Token.ARROW) { + // it's a parameter + next(); + nodes.add(new TypedIdentifier(identifier, typeAnnotation, identifier.span())); + } else { + // it's a member + expect(Token.ASSIGN, "unexpectedToken", "="); + var expr = parseExpr(); + membersOffset = 0; + nodes.add( + new ObjectMember.ObjectProperty( + Arrays.asList(identifier, typeAnnotation, expr), + 0, + identifier.span().endWith(expr.span()))); + } + } else { + // member + backtrack(); + } + } + + if (membersOffset < 0) { + membersOffset = nodes.size(); + } + // members + while (lookahead != Token.RBRACE) { + if (lookahead == Token.EOF) { + throw new ParserError( + ErrorMessages.create("missingDelimiter", "}"), prev.span.stopSpan().move(1)); + } + nodes.add(parseObjectMember()); + } + var end = next().span; + return new ObjectBody(nodes, membersOffset, start.endWith(end)); + } + + private ObjectMember parseObjectMember() { + return switch (lookahead) { + case IDENTIFIER -> { + next(); + if (lookahead == Token.LBRACE || lookahead == Token.COLON || lookahead == Token.ASSIGN) { + // it's an objectProperty + backtrack(); + yield parseObjectProperty(null); + } else { + backtrack(); + // it's an expression + yield parseObjectElement(); + } + } + case FUNCTION -> parseObjectMethod(List.of()); + case LPRED -> parseMemberPredicate(); + case LBRACK -> parseObjectEntry(); + case SPREAD, QSPREAD -> parseObjectSpread(); + case WHEN -> parseWhenGenerator(); + case FOR -> parseForGenerator(); + case TYPE_ALIAS, CLASS -> + throw new ParserError( + ErrorMessages.create("missingDelimiter", "}"), prev.span.stopSpan().move(1)); + default -> { + var modifiers = new ArrayList(); + while (lookahead.isModifier()) { + modifiers.add(parseModifier()); + } + if (!modifiers.isEmpty()) { + if (lookahead == Token.FUNCTION) { + yield parseObjectMethod(modifiers); + } else { + yield parseObjectProperty(modifiers); + } + } else { + yield parseObjectElement(); + } + } + }; + } + + private ObjectMember.ObjectElement parseObjectElement() { + var expr = parseExpr("}"); + return new ObjectMember.ObjectElement(expr, expr.span()); + } + + private ObjectMember parseObjectProperty(@Nullable List modifiers) { + var start = spanLookahead; + var allModifiers = modifiers; + if (allModifiers == null) { + allModifiers = parseModifierList(); + } + var identifier = parseIdentifier(); + TypeAnnotation typeAnnotation = null; + if (lookahead == Token.COLON) { + typeAnnotation = parseTypeAnnotation(); + } + if (typeAnnotation != null || lookahead == Token.ASSIGN) { + expect(Token.ASSIGN, "unexpectedToken", "="); + var expr = parseExpr("}"); + var nodes = new ArrayList(allModifiers.size() + 4); + nodes.addAll(allModifiers); + nodes.add(identifier); + nodes.add(typeAnnotation); + nodes.add(expr); + return new ObjectMember.ObjectProperty( + nodes, allModifiers.size(), start.endWith(expr.span())); + } + var bodies = parseBodyList(); + var end = bodies.get(bodies.size() - 1).span(); + var nodes = new ArrayList(allModifiers.size() + 4); + nodes.addAll(allModifiers); + nodes.add(identifier); + nodes.add(null); + nodes.add(null); + nodes.addAll(bodies); + return new ObjectMember.ObjectProperty(nodes, allModifiers.size(), start.endWith(end)); + } + + private ObjectMember.ObjectMethod parseObjectMethod(List modifiers) { + var start = spanLookahead; + expect(Token.FUNCTION, "unexpectedToken", "function"); + var identifier = parseIdentifier(); + TypeParameterList params = null; + if (lookahead == Token.LT) { + params = parseTypeParameterList(); + } + var args = parseParameterList(); + TypeAnnotation typeAnnotation = null; + if (lookahead == Token.COLON) { + typeAnnotation = parseTypeAnnotation(); + } + expect(Token.ASSIGN, "unexpectedToken", "="); + var expr = parseExpr("}"); + var nodes = new ArrayList(modifiers.size() + 5); + nodes.addAll(modifiers); + nodes.add(identifier); + nodes.add(params); + nodes.add(args); + nodes.add(typeAnnotation); + nodes.add(expr); + return new ObjectMember.ObjectMethod(nodes, modifiers.size(), start.endWith(expr.span())); + } + + private ObjectMember parseMemberPredicate() { + var start = next().span; + var pred = parseExpr("]]"); + var firstBrack = expect(Token.RBRACK, "unexpectedToken", "]]").span; + Span secondbrack; + if (lookahead != Token.RBRACK) { + var text = _lookahead.text(lexer); + throw new ParserError(ErrorMessages.create("unexpectedToken", text, "]]"), firstBrack); + } else { + secondbrack = next().span; + } + if (firstBrack.charIndex() != secondbrack.charIndex() - 1) { + // There shouldn't be any whitespace between the first and second ']'. + var span = firstBrack.endWith(secondbrack); + var text = lexer.textFor(span.charIndex(), span.length()); + throw new ParserError(ErrorMessages.create("unexpectedToken", text, "]]"), firstBrack); + } + if (lookahead == Token.ASSIGN) { + next(); + var expr = parseExpr("}"); + return new ObjectMember.MemberPredicate(List.of(pred, expr), start.endWith(expr.span())); + } + var bodies = parseBodyList(); + var end = bodies.get(bodies.size() - 1).span(); + var nodes = new ArrayList(bodies.size() + 2); + nodes.add(pred); + nodes.add(null); + nodes.addAll(bodies); + return new ObjectMember.MemberPredicate(nodes, start.endWith(end)); + } + + private ObjectMember parseObjectEntry() { + var start = expect(Token.LBRACK, "unexpectedToken", "[").span; + var key = parseExpr("]"); + expect(Token.RBRACK, "unexpectedToken", "]"); + if (lookahead == Token.ASSIGN) { + next(); + var expr = parseExpr("}"); + return new ObjectMember.ObjectEntry(List.of(key, expr), start.endWith(expr.span())); + } + var bodies = parseBodyList(); + var end = bodies.get(bodies.size() - 1).span(); + var nodes = new ArrayList(bodies.size() + 2); + nodes.add(key); + nodes.add(null); + nodes.addAll(bodies); + return new ObjectMember.ObjectEntry(nodes, start.endWith(end)); + } + + private ObjectMember.ObjectSpread parseObjectSpread() { + var start = next(); + boolean isNullable = start.token == Token.QSPREAD; + var expr = parseExpr("}"); + return new ObjectMember.ObjectSpread(expr, isNullable, start.span.endWith(expr.span())); + } + + private ObjectMember.WhenGenerator parseWhenGenerator() { + var start = next().span; + expect(Token.LPAREN, "unexpectedToken", "("); + var pred = parseExpr(")"); + expect(Token.RPAREN, "unexpectedToken", ")"); + var body = parseObjectBody(); + var end = body.span(); + ObjectBody elseBody = null; + if (lookahead == Token.ELSE) { + next(); + elseBody = parseObjectBody(); + end = elseBody.span(); + } + return new ObjectMember.WhenGenerator(pred, body, elseBody, start.endWith(end)); + } + + private ObjectMember.ForGenerator parseForGenerator() { + var start = next().span; + expect(Token.LPAREN, "unexpectedToken", "("); + var par1 = parseParameter(); + Parameter par2 = null; + if (lookahead == Token.COMMA) { + next(); + par2 = parseParameter(); + } + expect(Token.IN, "unexpectedToken", "in"); + var expr = parseExpr(")"); + expect(Token.RPAREN, "unexpectedToken", ")"); + var body = parseObjectBody(); + return new ObjectMember.ForGenerator(par1, par2, expr, body, start.endWith(body.span())); + } + + private Expr parseExpr() { + return parseExpr(null); + } + + @SuppressWarnings("DuplicatedCode") + private Expr parseExpr(@Nullable String expectation) { + List exprs = new ArrayList<>(); + exprs.add(parseExprAtom(expectation)); + var op = getOperator(); + loop: + while (op != null) { + switch (op) { + case IS, AS -> { + exprs.add(new OperatorExpr(op, next().span)); + exprs.add(new Expr.TypeExpr(parseType())); + var precedence = OperatorResolver.getPrecedence(op); + exprs = OperatorResolver.resolveOperatorsHigherThan(exprs, precedence); + } + case MINUS -> { + if (!precededBySemicolon && _lookahead.newLinesBetween == 0) { + exprs.add(new OperatorExpr(op, next().span)); + exprs.add(parseExprAtom(expectation)); + } else { + break loop; + } + } + case DOT, QDOT -> { + // this exists just to keep backward compatibility with code as `x + y as List.distinct` + // which should be removed at some point + next(); + var expr = exprs.remove(exprs.size() - 1); + var isNullable = op == Operator.QDOT; + var identifier = parseIdentifier(); + ArgumentList argumentList = null; + if (lookahead == Token.LPAREN + && !precededBySemicolon + && _lookahead.newLinesBetween == 0) { + argumentList = parseArgumentList(); + } + var lastSpan = argumentList != null ? argumentList.span() : identifier.span(); + exprs.add( + new QualifiedAccessExpr( + expr, identifier, isNullable, argumentList, expr.span().endWith(lastSpan))); + } + default -> { + exprs.add(new OperatorExpr(op, next().span)); + exprs.add(parseExprAtom(expectation)); + } + } + op = getOperator(); + } + return OperatorResolver.resolveOperators(exprs); + } + + private @Nullable Operator getOperator() { + return switch (lookahead) { + case POW -> Operator.POW; + case STAR -> Operator.MULT; + case DIV -> Operator.DIV; + case INT_DIV -> Operator.INT_DIV; + case MOD -> Operator.MOD; + case PLUS -> Operator.PLUS; + case MINUS -> Operator.MINUS; + case GT -> Operator.GT; + case GTE -> Operator.GTE; + case LT -> Operator.LT; + case LTE -> Operator.LTE; + case IS -> Operator.IS; + case AS -> Operator.AS; + case EQUAL -> Operator.EQ_EQ; + case NOT_EQUAL -> Operator.NOT_EQ; + case AND -> Operator.AND; + case OR -> Operator.OR; + case PIPE -> Operator.PIPE; + case COALESCE -> Operator.NULL_COALESCE; + case DOT -> Operator.DOT; + case QDOT -> Operator.QDOT; + default -> null; + }; + } + + private Expr parseExprAtom(@Nullable String expectation) { + var expr = + switch (lookahead) { + case THIS -> new ThisExpr(next().span); + case OUTER -> new OuterExpr(next().span); + case MODULE -> new ModuleExpr(next().span); + case NULL -> new NullLiteralExpr(next().span); + case THROW -> { + var start = next().span; + expect(Token.LPAREN, "unexpectedToken", "("); + var exp = parseExpr(")"); + var end = expect(Token.RPAREN, "unexpectedToken", ")").span; + yield new ThrowExpr(exp, start.endWith(end)); + } + case TRACE -> { + var start = next().span; + expect(Token.LPAREN, "unexpectedToken", "("); + var exp = parseExpr(")"); + var end = expect(Token.RPAREN, "unexpectedToken", ")").span; + yield new TraceExpr(exp, start.endWith(end)); + } + case IMPORT -> { + var start = next().span; + expect(Token.LPAREN, "unexpectedToken", "("); + var strConst = parseStringConstant(); + var end = expect(Token.RPAREN, "unexpectedToken", ")").span; + yield new Expr.ImportExpr(strConst, false, start.endWith(end)); + } + case IMPORT_STAR -> { + var start = next().span; + expect(Token.LPAREN, "unexpectedToken", "("); + var strConst = parseStringConstant(); + var end = expect(Token.RPAREN, "unexpectedToken", ")").span; + yield new Expr.ImportExpr(strConst, true, start.endWith(end)); + } + case READ, READ_STAR, READ_QUESTION -> { + var readType = + switch (lookahead) { + case READ_QUESTION -> ReadType.NULL; + case READ_STAR -> ReadType.GLOB; + default -> ReadType.READ; + }; + var start = next().span; + expect(Token.LPAREN, "unexpectedToken", "("); + var exp = parseExpr(")"); + var end = expect(Token.RPAREN, "unexpectedToken", ")").span; + yield new ReadExpr(exp, readType, start.endWith(end)); + } + case NEW -> { + var start = next().span; + Type type = null; + if (lookahead != Token.LBRACE) { + type = parseType("{"); + } + var body = parseObjectBody(); + yield new NewExpr(type, body, start.endWith(body.span())); + } + case MINUS -> { + var start = next().span; + // calling `parseExprAtom` here and not `parseExpr` because + // unary minus has higher precendence than binary operators + var exp = parseExprAtom(expectation); + yield new UnaryMinusExpr(exp, start.endWith(exp.span())); + } + case NOT -> { + var start = next().span; + // calling `parseExprAtom` here and not `parseExpr` because + // logical not has higher precendence than binary operators + var exp = parseExprAtom(expectation); + yield new LogicalNotExpr(exp, start.endWith(exp.span())); + } + case LPAREN -> { + // can be function literal or parenthesized expression + var start = next().span; + yield switch (lookahead) { + case UNDERSCORE -> parseFunctionLiteral(start); + case IDENTIFIER -> parseFunctionLiteralOrParenthesized(start); + case RPAREN -> { + var endParen = next().span; + var paramList = new ParameterList(List.of(), start.endWith(endParen)); + expect(Token.ARROW, "unexpectedToken", "->"); + var exp = parseExpr(expectation); + yield new FunctionLiteralExpr(paramList, exp, start.endWith(exp.span())); + } + default -> { + // expression + var exp = parseExpr(")"); + var end = expect(Token.RPAREN, "unexpectedToken", ")").span; + yield new ParenthesizedExpr(exp, start.endWith(end)); + } + }; + } + case SUPER -> { + var start = next().span; + if (lookahead == Token.DOT) { + next(); + var identifier = parseIdentifier(); + if (lookahead == Token.LPAREN) { + var args = parseArgumentList(); + yield new SuperAccessExpr(identifier, args, start.endWith(args.span())); + } else { + yield new SuperAccessExpr(identifier, null, start.endWith(identifier.span())); + } + } else { + expect(Token.LBRACK, "unexpectedToken", "["); + var exp = parseExpr("]"); + var end = expect(Token.RBRACK, "unexpectedToken", "]").span; + yield new SuperSubscriptExpr(exp, start.endWith(end)); + } + } + case IF -> { + var start = next().span; + expect(Token.LPAREN, "unexpectedToken", "("); + var pred = parseExpr(")"); + expect(Token.RPAREN, "unexpectedToken", ")"); + var then = parseExpr("else"); + expect(Token.ELSE, "unexpectedToken", "else"); + var elseCase = parseExpr(expectation); + yield new IfExpr(pred, then, elseCase, start.endWith(elseCase.span())); + } + case LET -> { + var start = next().span(); + expect(Token.LPAREN, "unexpectedToken", "("); + var param = parseParameter(); + expect(Token.ASSIGN, "unexpectedToken", "="); + var bindExpr = parseExpr(")"); + expect(Token.RPAREN, "unexpectedToken", ")"); + var exp = parseExpr(expectation); + yield new LetExpr(param, bindExpr, exp, start.endWith(exp.span())); + } + case TRUE -> new BoolLiteralExpr(true, next().span); + case FALSE -> new BoolLiteralExpr(false, next().span); + case INT, HEX, BIN, OCT -> { + var tk = next(); + yield new IntLiteralExpr(tk.text(lexer), tk.span); + } + case FLOAT -> { + var tk = next(); + yield new FloatLiteralExpr(tk.text(lexer), tk.span); + } + case STRING_START, STRING_MULTI_START -> { + var start = next(); + var parts = new ArrayList(); + var temp = new ArrayList(); + while (lookahead != Token.STRING_END) { + switch (lookahead) { + case STRING_PART -> { + var tk = next(); + var text = tk.text(lexer); + if (!text.isEmpty()) { + temp.add(new StringConstantPart.ConstantPart(text, tk.span)); + } + } + // lexer makes sure we don't get newlines in single quoted strings + case STRING_NEWLINE -> temp.add(new StringNewline(next().span)); + case STRING_ESCAPE_NEWLINE -> + temp.add(new StringEscape(EscapeType.NEWLINE, next().span)); + case STRING_ESCAPE_TAB -> temp.add(new StringEscape(EscapeType.TAB, next().span)); + case STRING_ESCAPE_QUOTE -> + temp.add(new StringEscape(EscapeType.QUOTE, next().span)); + case STRING_ESCAPE_BACKSLASH -> + temp.add(new StringEscape(EscapeType.BACKSLASH, next().span)); + case STRING_ESCAPE_RETURN -> + temp.add(new StringEscape(EscapeType.RETURN, next().span)); + case STRING_ESCAPE_UNICODE -> { + var tk = next(); + var text = tk.text(lexer); + temp.add(new StringConstantPart.StringUnicodeEscape(text, tk.span)); + } + case INTERPOLATION_START -> { + var istart = next().span; + if (!temp.isEmpty()) { + var span = temp.get(0).span().endWith(temp.get(temp.size() - 1).span()); + parts.add(new StringPart.StringConstantParts(temp, span)); + temp = new ArrayList<>(); + } + var exp = parseExpr(")"); + var end = expect(Token.RPAREN, "unexpectedToken", ")").span; + parts.add(new StringPart.StringInterpolation(exp, istart.endWith(end))); + } + case EOF -> throw parserError("unexpectedEndOfFile"); + // the lexer makes sure we only get the above tokens inside a string + default -> throw PklBugException.unreachableCode(); + } + } + if (!temp.isEmpty()) { + var span = temp.get(0).span().endWith(temp.get(temp.size() - 1).span()); + parts.add(new StringPart.StringConstantParts(temp, span)); + } + var expectedDelimiter = start.token == Token.STRING_START ? "\"" : "\"\"\""; + var end = expect(Token.STRING_END, "missingDelimiter", expectedDelimiter).span; + if (start.token == Token.STRING_START) { + yield new SingleLineStringLiteralExpr( + parts, start.span, end, start.span.endWith(end)); + } else { + yield new MultiLineStringLiteralExpr(parts, start.span, end, start.span.endWith(end)); + } + } + case IDENTIFIER -> { + var identifier = parseIdentifier(); + if (lookahead == Token.LPAREN + && !precededBySemicolon + && _lookahead.newLinesBetween == 0) { + var args = parseArgumentList(); + yield new UnqualifiedAccessExpr( + identifier, args, identifier.span().endWith(args.span())); + } else { + yield new UnqualifiedAccessExpr(identifier, null, identifier.span()); + } + } + case EOF -> + throw new ParserError( + ErrorMessages.create("unexpectedEndOfFile"), prev.span.stopSpan().move(1)); + default -> { + var text = _lookahead.text(lexer); + if (expectation != null) { + throw parserError("unexpectedToken", text, expectation); + } + throw parserError("unexpectedTokenForExpression", text); + } + }; + return parseExprRest(expr); + } + + @SuppressWarnings("DuplicatedCode") + private Expr parseExprRest(Expr expr) { + // non null + if (lookahead == Token.NON_NULL) { + var end = next().span; + var res = new NonNullExpr(expr, expr.span().endWith(end)); + return parseExprRest(res); + } + // amends + if (lookahead == Token.LBRACE) { + if (expr instanceof ParenthesizedExpr + || expr instanceof AmendsExpr + || expr instanceof NewExpr) { + var body = parseObjectBody(); + return parseExprRest(new AmendsExpr(expr, body, expr.span().endWith(body.span()))); + } + throw parserError("unexpectedCurlyProbablyAmendsExpression", expr.text(lexer.getSource())); + } + // qualified access + if (lookahead == Token.DOT || lookahead == Token.QDOT) { + var isNullable = next().token == Token.QDOT; + var identifier = parseIdentifier(); + ArgumentList argumentList = null; + if (lookahead == Token.LPAREN && !precededBySemicolon && _lookahead.newLinesBetween == 0) { + argumentList = parseArgumentList(); + } + var lastSpan = argumentList != null ? argumentList.span() : identifier.span(); + var res = + new QualifiedAccessExpr( + expr, identifier, isNullable, argumentList, expr.span().endWith(lastSpan)); + return parseExprRest(res); + } + // subscript (needs to be in the same line as the expression) + if (lookahead == Token.LBRACK && !precededBySemicolon && _lookahead.newLinesBetween == 0) { + next(); + var exp = parseExpr("]"); + var end = expect(Token.RBRACK, "unexpectedToken", "]").span; + var res = new SubscriptExpr(expr, exp, expr.span().endWith(end)); + return parseExprRest(res); + } + return expr; + } + + @SuppressWarnings("DuplicatedCode") + private Expr parseFunctionLiteralOrParenthesized(Span start) { + var identifier = parseIdentifier(); + return switch (lookahead) { + case COMMA -> { + next(); + var params = new ArrayList(); + params.add(new TypedIdentifier(identifier, null, identifier.span())); + params.addAll(parseListOfParameter(Token.COMMA)); + var endParen = expect(Token.RPAREN, "unexpectedToken2", ",", ")").span; + var paramList = new ParameterList(params, start.endWith(endParen)); + expect(Token.ARROW, "unexpectedToken", "->"); + var expr = parseExpr(); + yield new FunctionLiteralExpr(paramList, expr, start.endWith(expr.span())); + } + case COLON -> { + var typeAnnotation = parseTypeAnnotation(); + var params = new ArrayList(); + params.add(new TypedIdentifier(identifier, typeAnnotation, identifier.span())); + if (lookahead == Token.COMMA) { + next(); + params.addAll(parseListOfParameter(Token.COMMA)); + } + var endParen = expect(Token.RPAREN, "unexpectedToken2", ",", ")").span; + var paramList = new ParameterList(params, start.endWith(endParen)); + expect(Token.ARROW, "unexpectedToken", "->"); + var expr = parseExpr(")"); + yield new FunctionLiteralExpr(paramList, expr, start.endWith(expr.span())); + } + case RPAREN -> { + // still not sure + var end = next().span; + if (lookahead == Token.ARROW) { + next(); + var expr = parseExpr(); + var params = new ArrayList(); + params.add(new TypedIdentifier(identifier, null, identifier.span())); + var paramList = new ParameterList(params, start.endWith(end)); + yield new FunctionLiteralExpr(paramList, expr, start.endWith(expr.span())); + } else { + var exp = new UnqualifiedAccessExpr(identifier, null, identifier.span()); + yield new ParenthesizedExpr(exp, start.endWith(end)); + } + } + default -> { + // this is an expression + backtrack(); + var expr = parseExpr(")"); + var end = expect(Token.RPAREN, "unexpectedToken", ")").span; + yield new ParenthesizedExpr(expr, start.endWith(end)); + } + }; + } + + private FunctionLiteralExpr parseFunctionLiteral(Span start) { + // the open parens is already parsed + var params = parseListOfParameter(Token.COMMA); + var endParen = expect(Token.RPAREN, "unexpectedToken2", ",", ")").span; + var paramList = new ParameterList(params, start.endWith(endParen)); + expect(Token.ARROW, "unexpectedToken", "->"); + var expr = parseExpr(); + return new FunctionLiteralExpr(paramList, expr, start.endWith(expr.span())); + } + + private Type parseType() { + return parseType(null); + } + + private Type parseType(@Nullable String expectation) { + var defaultIndex = -1; + Span start = null; + if (lookahead == Token.STAR) { + defaultIndex = 0; + start = next().span; + } + var first = parseTypeAtom(expectation); + if (start == null) { + start = first.span(); + } + + if (lookahead != Token.UNION) { + if (defaultIndex == 0) { + throw new ParserError(ErrorMessages.create("notAUnion"), start.endWith(first.span())); + } + return first; + } + + var types = new ArrayList(); + types.add(first); + var end = start; + var i = 1; + while (lookahead == Token.UNION) { + next(); + if (lookahead == Token.STAR) { + if (defaultIndex != -1) { + throw parserError("multipleUnionDefaults"); + } + defaultIndex = i; + next(); + } + var type = parseTypeAtom(expectation); + types.add(type); + end = type.span(); + i++; + } + return new Type.UnionType(types, defaultIndex, start.endWith(end)); + } + + private Type parseTypeAtom(@Nullable String expectation) { + Type typ; + switch (lookahead) { + case UNKNOWN -> typ = new Type.UnknownType(next().span); + case NOTHING -> typ = new Type.NothingType(next().span); + case MODULE -> typ = new Type.ModuleType(next().span); + case LPAREN -> { + var tk = next(); + var children = new ArrayList(); + Span end; + if (lookahead == Token.RPAREN) { + end = next().span; + } else { + children.addAll(parseListOf(Token.COMMA, () -> parseType(")"))); + end = expect(Token.RPAREN, "unexpectedToken2", ",", ")").span; + } + if (lookahead == Token.ARROW || children.size() > 1) { + expect(Token.ARROW, "unexpectedToken", "->"); + var ret = parseType(expectation); + children.add(ret); + typ = new Type.FunctionType(children, tk.span.endWith(end)); + } else { + typ = new ParenthesizedType((Type) children.get(0), tk.span.endWith(end)); + } + } + case IDENTIFIER -> { + var start = spanLookahead; + var nodes = new ArrayList(1); + var name = parseQualifiedIdentifier(); + var end = name.span(); + nodes.add(name); + if (lookahead == Token.LT) { + next(); + nodes.addAll(parseListOf(Token.COMMA, () -> parseType(">"))); + end = expect(Token.GT, "unexpectedToken2", ",", ">").span; + } + typ = new DeclaredType(nodes, start.endWith(end)); + } + case STRING_START -> { + var str = parseStringConstant(); + typ = new StringConstantType(str, str.span()); + } + default -> { + var text = _lookahead.text(lexer); + if (expectation != null) { + throw parserError("unexpectedTokenForType2", text, expectation); + } + throw parserError("unexpectedTokenForType", text); + } + } + + if (typ instanceof Type.FunctionType) return typ; + return parseTypeEnd(typ); + } + + private Type parseTypeEnd(Type type) { + // nullable types + if (lookahead == Token.QUESTION) { + var end = spanLookahead; + next(); + var res = new Type.NullableType(type, type.span().endWith(end)); + return parseTypeEnd(res); + } + // constrained types: have to start in the same line as the type + if (lookahead == Token.LPAREN && !precededBySemicolon && _lookahead.newLinesBetween == 0) { + next(); + var constraints = parseListOf(Token.COMMA, () -> parseExpr(")")); + var end = expect(Token.RPAREN, "unexpectedToken2", ",", ")").span; + var children = new ArrayList(constraints.size() + 1); + children.add(type); + children.addAll(constraints); + var res = new Type.ConstrainedType(children, type.span().endWith(end)); + return parseTypeEnd(res); + } + return type; + } + + private Annotation parseAnnotation() { + var start = next().span; + var children = new ArrayList(2); + var type = parseType(); + children.add(type); + ObjectBody body = null; + var end = type.span(); + if (lookahead == Token.LBRACE) { + body = parseObjectBody(); + end = body.span(); + } + children.add(body); + return new Annotation(children, start.endWith(end)); + } + + private Parameter parseParameter() { + if (lookahead == Token.UNDERSCORE) { + var span = next().span; + return new Parameter.Underscore(span); + } + return parseTypedIdentifier(); + } + + private Modifier parseModifier() { + return switch (lookahead) { + case EXTERNAL -> new Modifier(Modifier.ModifierValue.EXTERNAL, next().span); + case ABSTRACT -> new Modifier(Modifier.ModifierValue.ABSTRACT, next().span); + case OPEN -> new Modifier(Modifier.ModifierValue.OPEN, next().span); + case LOCAL -> new Modifier(Modifier.ModifierValue.LOCAL, next().span); + case HIDDEN -> new Modifier(Modifier.ModifierValue.HIDDEN, next().span); + case FIXED -> new Modifier(Modifier.ModifierValue.FIXED, next().span); + case CONST -> new Modifier(Modifier.ModifierValue.CONST, next().span); + default -> throw PklBugException.unreachableCode(); + }; + } + + private List parseModifierList() { + var modifiers = new ArrayList(); + while (lookahead.isModifier()) { + modifiers.add(parseModifier()); + } + return modifiers; + } + + private ParameterList parseParameterList() { + var start = expect(Token.LPAREN, "unexpectedToken", "(").span; + Span end; + List args = new ArrayList<>(); + if (lookahead == Token.RPAREN) { + end = next().span; + } else { + args = parseListOfParameter(Token.COMMA); + end = expect(Token.RPAREN, "unexpectedToken2", ",", ")").span; + } + return new ParameterList(args, start.endWith(end)); + } + + private List parseBodyList() { + if (lookahead != Token.LBRACE) { + throw parserError("unexpectedToken2", _lookahead.text(lexer), "{", "="); + } + var bodies = new ArrayList(); + do { + bodies.add(parseObjectBody()); + } while (lookahead == Token.LBRACE); + return bodies; + } + + private TypeParameterList parseTypeParameterList() { + var start = expect(Token.LT, "unexpectedToken", "<").span; + var pars = parseListOf(Token.COMMA, this::parseTypeParameter); + var end = expect(Token.GT, "unexpectedToken2", ",", ">").span; + return new TypeParameterList(pars, start.endWith(end)); + } + + private ArgumentList parseArgumentList() { + var start = expect(Token.LPAREN, "unexpectedToken", "(").span; + if (lookahead == Token.RPAREN) { + return new ArgumentList(new ArrayList<>(), start.endWith(next().span)); + } + var exprs = parseListOf(Token.COMMA, this::parseExpr); + var end = expect(Token.RPAREN, "unexpectedToken2", ",", ")").span; + return new ArgumentList(exprs, start.endWith(end)); + } + + private TypeParameter parseTypeParameter() { + TypeParameter.Variance variance = null; + var start = spanLookahead; + if (lookahead == Token.IN) { + next(); + variance = TypeParameter.Variance.IN; + } else if (lookahead == Token.OUT) { + next(); + variance = TypeParameter.Variance.OUT; + } + var identifier = parseIdentifier(); + return new TypeParameter(variance, identifier, start.endWith(identifier.span())); + } + + private TypedIdentifier parseTypedIdentifier() { + var identifier = parseIdentifier(); + TypeAnnotation typeAnnotation = null; + var end = identifier.span(); + if (lookahead == Token.COLON) { + typeAnnotation = parseTypeAnnotation(); + end = typeAnnotation.span(); + } + return new TypedIdentifier(identifier, typeAnnotation, identifier.span().endWith(end)); + } + + private TypeAnnotation parseTypeAnnotation() { + var start = expect(Token.COLON, "unexpectedToken", ":").span; + var type = parseType(); + return new TypeAnnotation(type, start.endWith(type.span())); + } + + private Identifier parseIdentifier() { + if (lookahead != Token.IDENTIFIER) { + if (lookahead.isKeyword()) { + throw parserError("keywordNotAllowedHere", lookahead.text()); + } + throw parserError("unexpectedToken", _lookahead.text(lexer), "identifier"); + } + var tk = next(); + var text = tk.text(lexer); + return new Identifier(text, tk.span); + } + + private StringConstant parseStringConstant() { + var start = spanLookahead; + expect(Token.STRING_START, "unexpectedToken", "\""); + var parts = new ArrayList(); + while (lookahead != Token.STRING_END) { + switch (lookahead) { + case STRING_PART -> { + var tk = next(); + var text = tk.text(lexer); + parts.add(new StringConstantPart.ConstantPart(text, tk.span)); + } + case STRING_ESCAPE_NEWLINE -> parts.add(new StringEscape(EscapeType.NEWLINE, next().span)); + case STRING_ESCAPE_TAB -> parts.add(new StringEscape(EscapeType.TAB, next().span)); + case STRING_ESCAPE_QUOTE -> parts.add(new StringEscape(EscapeType.QUOTE, next().span)); + case STRING_ESCAPE_BACKSLASH -> + parts.add(new StringEscape(EscapeType.BACKSLASH, next().span)); + case STRING_ESCAPE_RETURN -> parts.add(new StringEscape(EscapeType.RETURN, next().span)); + case STRING_ESCAPE_UNICODE -> { + var tk = next(); + var text = tk.text(lexer); + parts.add(new StringConstantPart.StringUnicodeEscape(text, tk.span)); + } + case EOF -> throw parserError("unexpectedEndOfFile"); + case INTERPOLATION_START -> throw parserError("interpolationInConstant"); + // the lexer makes sure we only get the above tokens inside a string + default -> throw PklBugException.unreachableCode(); + } + } + var end = expect(Token.STRING_END, "missingDelimiter", "\"").span; + assert !parts.isEmpty(); + var constSpan = parts.get(0).span().endWith(parts.get(parts.size() - 1).span()); + return new StringConstant(new StringConstantParts(parts, constSpan), start.endWith(end)); + } + + private FullToken expect(Token type, String errorKey, Object... messageArgs) { + if (lookahead != type) { + var span = spanLookahead; + if (lookahead == Token.EOF || _lookahead.newLinesBetween > 0) { + // don't point at the EOF or the next line, but at the end of the last token + span = prev.span.stopSpan().move(1); + } + var args = messageArgs; + if (errorKey.startsWith("unexpectedToken")) { + args = new Object[messageArgs.length + 1]; + args[0] = lookahead == Token.EOF ? "EOF" : _lookahead.text(lexer); + System.arraycopy(messageArgs, 0, args, 1, messageArgs.length); + } + throw new ParserError(ErrorMessages.create(errorKey, args), span); + } + return next(); + } + + private List parseListOf(Token separator, Supplier parser) { + var res = new ArrayList(); + res.add(parser.get()); + while (lookahead == separator) { + next(); + res.add(parser.get()); + } + return res; + } + + private List parseListOfParameter(Token separator) { + var res = new ArrayList(); + res.add(parseParameter()); + while (lookahead == separator) { + next(); + res.add(parseParameter()); + } + return res; + } + + private ParserError parserError(String messageKey, Object... args) { + return new ParserError(ErrorMessages.create(messageKey, args), spanLookahead); + } + + private record MemberHeader( + @Nullable DocComment docComment, List annotations, List modifiers) { + boolean isNotEmpty() { + return !(docComment == null && annotations.isEmpty() && modifiers.isEmpty()); + } + + Span span(Span or) { + Span start = null; + Span end = null; + if (!annotations().isEmpty()) { + start = annotations.get(0).span(); + end = annotations.get(annotations.size() - 1).span(); + } + if (!modifiers().isEmpty()) { + if (start == null) start = modifiers.get(0).span(); + end = modifiers.get(modifiers.size() - 1).span(); + return start.endWith(end); + } + if (end != null) { + return start.endWith(end); + } + return or; + } + + Span modifierSpan(Span or) { + if (!modifiers.isEmpty()) { + return modifiers.get(0).span(); + } + return or; + } + } + + private FullToken next() { + if (backtracking) { + backtracking = false; + lookahead = _lookahead.token; + spanLookahead = _lookahead.span; + return prev; + } + prev = _lookahead; + _lookahead = forceNext(); + lookahead = _lookahead.token; + spanLookahead = _lookahead.span; + return prev; + } + + private FullToken forceNext() { + var tk = lexer.next(); + precededBySemicolon = false; + while (tk == Token.LINE_COMMENT + || tk == Token.BLOCK_COMMENT + || tk == Token.SEMICOLON + || tk == Token.SHEBANG) { + precededBySemicolon = precededBySemicolon || tk == Token.SEMICOLON; + tk = lexer.next(); + } + return new FullToken( + tk, lexer.span(), lexer.sCursor, lexer.cursor - lexer.sCursor, lexer.newLinesBetween); + } + + // Like next, but don't ignore comments + private FullToken nextComment() { + prev = _lookahead; + _lookahead = forceNextComment(); + lookahead = _lookahead.token; + spanLookahead = _lookahead.span; + return prev; + } + + private FullToken forceNextComment() { + var tk = lexer.next(); + precededBySemicolon = false; + while (tk == Token.SEMICOLON) { + precededBySemicolon = true; + tk = lexer.next(); + } + return new FullToken( + tk, lexer.span(), lexer.sCursor, lexer.cursor - lexer.sCursor, lexer.newLinesBetween); } /** - * Two-step parse as recommended in chapter "Maximizing Parser Speed" of "The Definitive ANTLR 4 - * Reference, 2nd Ed". + * Backtrack to the previous token. + * + *

Can only backtrack one token. */ - @TruffleBoundary - public T parseProduction( - CharStream source, Function production) throws LexParseException { - var lexer = Lexer.createLexer(source); - var errorCollector = new ArrayList(); - var parser = createParser(new CommonTokenStream(lexer), errorCollector); - // TODO: investigate why SLL is often not enough to parse Pkl code - parser.getInterpreter().setPredictionMode(PredictionMode.SLL); - - var result = production.apply(parser); - - // TODO: only necessary to retry for parse (vs. lex) errors? - if (!errorCollector.isEmpty()) { - errorCollector.clear(); - parser.reset(); - parser.getInterpreter().setPredictionMode(PredictionMode.LL); - result = production.apply(parser); - } - - var mostRelevant = - errorCollector.stream().max(Comparator.comparingInt(LexParseException::getRelevance)); - if (mostRelevant.isPresent()) { - throw mostRelevant.get().withPartialParseResult(result); - } - - return result; + private void backtrack() { + assert !backtracking; + lookahead = prev.token; + spanLookahead = prev.span; + backtracking = true; } - @TruffleBoundary - public ExprInputContext parseExpressionInput(String source) throws LexParseException { - return parseExpressionInput(toCharStream(source)); - } - - @SuppressWarnings("deprecation") - private CharStream toCharStream(String source) { - // `ANTLRInputStream` has been deprecated and should be replaced with `CharStreams.ofString()`. - // It seems that the bugs we formerly encountered with `CharStreams.ofString()` are fixed in - // 4.7.2. - // However, switching to `CharStreams.ofString()` means that ANTLR's column numbers are measured - // in number of code points, - // which makes them incompatible with Truffle's `SourceSection` (which uses number of code - // units). - return new ANTLRInputStream(source); - } - - // To improve error reporting, missing closing delimiters - // are tolerated by the grammar and only caught in AstBuilder. - // This method compensates by flagging a missing closing delimiter. - private void checkIsCompleteInput(ParserRuleContext ctx) { - if (ctx.getChildCount() == 1) return; // EOF - - var curr = ctx.getChild(ctx.getChildCount() - 2); // last child before EOF - while (curr.getChildCount() > 0) { - if (curr instanceof ClassBodyContext classBody) { - if (classBody.err == null) throw incompleteInput(curr, "}"); - else return; - } - if (curr instanceof ParameterListContext parameterList) { - if (parameterList.err == null) throw incompleteInput(curr, ")"); - else return; - } - if (curr instanceof ArgumentListContext argumentList) { - if (argumentList.err == null) throw incompleteInput(curr, ")"); - else return; - } - if (curr instanceof TypeParameterListContext typeParameterList) { - if (typeParameterList.err == null) throw incompleteInput(curr, ">"); - else return; - } - if (curr instanceof TypeArgumentListContext typeArgumentList) { - if (typeArgumentList.err == null) throw incompleteInput(curr, ">"); - else return; - } - if (curr instanceof ParenthesizedTypeContext parenthesizedType) { - if (parenthesizedType.err == null) throw incompleteInput(curr, ")"); - else return; - } - if (curr instanceof ConstrainedTypeContext constrainedType) { - if (constrainedType.err == null) throw incompleteInput(curr, ")"); - else return; - } - if (curr instanceof ParenthesizedExprContext parenthesizedExpr) { - if (parenthesizedExpr.err == null) throw incompleteInput(curr, ")"); - else return; - } - if (curr instanceof SuperSubscriptExprContext superSubscriptExpr) { - if (superSubscriptExpr.err == null) throw incompleteInput(curr, "]"); - else return; - } - if (curr instanceof SubscriptExprContext subscriptExpr) { - if (subscriptExpr.err == null) throw incompleteInput(curr, "]"); - else return; - } - if (curr instanceof ObjectBodyContext objectBody) { - if (objectBody.err == null) throw incompleteInput(curr, "}"); - else return; - } - curr = curr.getChild(curr.getChildCount() - 1); + private void ensureEmptyHeaders(MemberHeader header, String messageArg) { + if (header.isNotEmpty()) { + throw new ParserError( + ErrorMessages.create("wrongHeaders", messageArg), header.span(spanLookahead)); } } - private void registerErrorListener( - PklParser parser, @Nullable List errorCollector) { - parser.removeErrorListeners(); - parser.addErrorListener( - new BaseErrorListener() { - @Override - public void syntaxError( - Recognizer recognizer, - T offendingToken, - int line, - int charPositionInLine, - String msg, - @Nullable RecognitionException e) { - assert charPositionInLine == offendingToken.getCharPositionInLine(); - var length = offendingToken.getStopIndex() - offendingToken.getStartIndex() + 1; - - LexParseException exception; - // For incomplete input similar to `foo { bar {`, e can (at least) be null, - // NoViableAltException, or InputMismatchException. Therefore, just check for EOF. - if (offendingToken.getType() == PklLexer.EOF) { - exception = - new LexParseException.IncompleteInput(msg, line, charPositionInLine + 1, length); - } else { - exception = - new LexParseException.ParseError( - msg, line, charPositionInLine + 1, length, getAstDepth(e)); - } - - if (errorCollector != null) { - errorCollector.add(exception); - } else { - throw exception; - } - } - }); - } - - private LexParseException incompleteInput(ParseTree tree, String missingDelimiter) { - var ctx = (ParserRuleContext) tree; - return new IncompleteInput( - "Missing closing delimiter `" + missingDelimiter + "`.", - ctx.stop.getLine(), - ctx.stop.getCharPositionInLine() + 1, - ctx.stop.getStopIndex() - ctx.stop.getStartIndex() + 1); - } - - private static int getAstDepth(@Nullable RecognitionException e) { - if (e == null) return 0; - - var depth = 0; - for (var context = e.getContext(); context != null; context = context.getParent()) { - depth += 1; + private record FullToken( + Token token, Span span, int textOffset, int textSize, int newLinesBetween) { + String text(Lexer lexer) { + return lexer.textFor(textOffset, textSize); } - - return depth; } } diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ParserError.java b/pkl-core/src/main/java/org/pkl/core/parser/ParserError.java new file mode 100644 index 00000000..89a2791b --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ParserError.java @@ -0,0 +1,41 @@ +/* + * Copyright © 2025 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.core.parser; + +import org.pkl.core.parser.ast.Module; +import org.pkl.core.util.Nullable; + +public class ParserError extends RuntimeException { + private final Span span; + private @Nullable Module partialParseResult; + + public ParserError(String msg, Span span) { + super(msg); + this.span = span; + } + + public Span span() { + return span; + } + + public void setPartialParseResult(@Nullable Module partialParseResult) { + this.partialParseResult = partialParseResult; + } + + public @Nullable Module getPartialParseResult() { + return partialParseResult; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ParserVisitor.java b/pkl-core/src/main/java/org/pkl/core/parser/ParserVisitor.java new file mode 100644 index 00000000..e9b4a61d --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ParserVisitor.java @@ -0,0 +1,223 @@ +/* + * Copyright © 2024-2025 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.core.parser; + +import org.pkl.core.parser.ast.Annotation; +import org.pkl.core.parser.ast.ArgumentList; +import org.pkl.core.parser.ast.Class; +import org.pkl.core.parser.ast.ClassBody; +import org.pkl.core.parser.ast.ClassMethod; +import org.pkl.core.parser.ast.ClassProperty; +import org.pkl.core.parser.ast.DocComment; +import org.pkl.core.parser.ast.Expr; +import org.pkl.core.parser.ast.Expr.AmendsExpr; +import org.pkl.core.parser.ast.Expr.BinaryOperatorExpr; +import org.pkl.core.parser.ast.Expr.BoolLiteralExpr; +import org.pkl.core.parser.ast.Expr.FloatLiteralExpr; +import org.pkl.core.parser.ast.Expr.FunctionLiteralExpr; +import org.pkl.core.parser.ast.Expr.IfExpr; +import org.pkl.core.parser.ast.Expr.IntLiteralExpr; +import org.pkl.core.parser.ast.Expr.LetExpr; +import org.pkl.core.parser.ast.Expr.LogicalNotExpr; +import org.pkl.core.parser.ast.Expr.ModuleExpr; +import org.pkl.core.parser.ast.Expr.MultiLineStringLiteralExpr; +import org.pkl.core.parser.ast.Expr.NewExpr; +import org.pkl.core.parser.ast.Expr.NonNullExpr; +import org.pkl.core.parser.ast.Expr.NullLiteralExpr; +import org.pkl.core.parser.ast.Expr.OuterExpr; +import org.pkl.core.parser.ast.Expr.ParenthesizedExpr; +import org.pkl.core.parser.ast.Expr.QualifiedAccessExpr; +import org.pkl.core.parser.ast.Expr.ReadExpr; +import org.pkl.core.parser.ast.Expr.SingleLineStringLiteralExpr; +import org.pkl.core.parser.ast.Expr.SubscriptExpr; +import org.pkl.core.parser.ast.Expr.SuperAccessExpr; +import org.pkl.core.parser.ast.Expr.SuperSubscriptExpr; +import org.pkl.core.parser.ast.Expr.ThisExpr; +import org.pkl.core.parser.ast.Expr.ThrowExpr; +import org.pkl.core.parser.ast.Expr.TraceExpr; +import org.pkl.core.parser.ast.Expr.TypeCastExpr; +import org.pkl.core.parser.ast.Expr.TypeCheckExpr; +import org.pkl.core.parser.ast.Expr.UnaryMinusExpr; +import org.pkl.core.parser.ast.Expr.UnqualifiedAccessExpr; +import org.pkl.core.parser.ast.ExtendsOrAmendsClause; +import org.pkl.core.parser.ast.Identifier; +import org.pkl.core.parser.ast.ImportClause; +import org.pkl.core.parser.ast.Modifier; +import org.pkl.core.parser.ast.Module; +import org.pkl.core.parser.ast.ModuleDecl; +import org.pkl.core.parser.ast.ObjectBody; +import org.pkl.core.parser.ast.ObjectMember; +import org.pkl.core.parser.ast.Parameter; +import org.pkl.core.parser.ast.ParameterList; +import org.pkl.core.parser.ast.QualifiedIdentifier; +import org.pkl.core.parser.ast.ReplInput; +import org.pkl.core.parser.ast.StringConstant; +import org.pkl.core.parser.ast.StringConstantPart; +import org.pkl.core.parser.ast.StringPart; +import org.pkl.core.parser.ast.Type; +import org.pkl.core.parser.ast.TypeAlias; +import org.pkl.core.parser.ast.TypeAnnotation; +import org.pkl.core.parser.ast.TypeParameter; +import org.pkl.core.parser.ast.TypeParameterList; + +public interface ParserVisitor { + + Result visitUnknownType(Type.UnknownType type); + + Result visitNothingType(Type.NothingType type); + + Result visitModuleType(Type.ModuleType type); + + Result visitStringConstantType(Type.StringConstantType type); + + Result visitDeclaredType(Type.DeclaredType type); + + Result visitParenthesizedType(Type.ParenthesizedType type); + + Result visitNullableType(Type.NullableType type); + + Result visitConstrainedType(Type.ConstrainedType type); + + Result visitUnionType(Type.UnionType type); + + Result visitFunctionType(Type.FunctionType type); + + Result visitThisExpr(ThisExpr expr); + + Result visitOuterExpr(OuterExpr expr); + + Result visitModuleExpr(ModuleExpr expr); + + Result visitNullLiteralExpr(NullLiteralExpr expr); + + Result visitBoolLiteralExpr(BoolLiteralExpr expr); + + Result visitIntLiteralExpr(IntLiteralExpr expr); + + Result visitFloatLiteralExpr(FloatLiteralExpr expr); + + Result visitThrowExpr(ThrowExpr expr); + + Result visitTraceExpr(TraceExpr expr); + + Result visitImportExpr(Expr.ImportExpr expr); + + Result visitReadExpr(ReadExpr expr); + + Result visitUnqualifiedAccessExpr(UnqualifiedAccessExpr expr); + + Result visitStringConstant(StringConstant expr); + + Result visitSingleLineStringLiteralExpr(SingleLineStringLiteralExpr expr); + + Result visitMultiLineStringLiteralExpr(MultiLineStringLiteralExpr expr); + + Result visitNewExpr(NewExpr expr); + + Result visitAmendsExpr(AmendsExpr expr); + + Result visitSuperAccessExpr(SuperAccessExpr expr); + + Result visitSuperSubscriptExpr(SuperSubscriptExpr expr); + + Result visitQualifiedAccessExpr(QualifiedAccessExpr expr); + + Result visitSubscriptExpr(SubscriptExpr expr); + + Result visitNonNullExpr(NonNullExpr expr); + + Result visitUnaryMinusExpr(UnaryMinusExpr expr); + + Result visitLogicalNotExpr(LogicalNotExpr expr); + + Result visitBinaryOperatorExpr(BinaryOperatorExpr expr); + + Result visitTypeCheckExpr(TypeCheckExpr expr); + + Result visitTypeCastExpr(TypeCastExpr expr); + + Result visitIfExpr(IfExpr expr); + + Result visitLetExpr(LetExpr expr); + + Result visitFunctionLiteralExpr(FunctionLiteralExpr expr); + + Result visitParenthesizedExpr(ParenthesizedExpr expr); + + Result visitObjectProperty(ObjectMember.ObjectProperty member); + + Result visitObjectMethod(ObjectMember.ObjectMethod member); + + Result visitMemberPredicate(ObjectMember.MemberPredicate member); + + Result visitObjectElement(ObjectMember.ObjectElement member); + + Result visitObjectEntry(ObjectMember.ObjectEntry member); + + Result visitObjectSpread(ObjectMember.ObjectSpread member); + + Result visitWhenGenerator(ObjectMember.WhenGenerator member); + + Result visitForGenerator(ObjectMember.ForGenerator member); + + Result visitModule(Module module); + + Result visitModuleDecl(ModuleDecl decl); + + Result visitExtendsOrAmendsClause(ExtendsOrAmendsClause decl); + + Result visitImportClause(ImportClause imp); + + Result visitClass(Class clazz); + + Result visitModifier(Modifier modifier); + + Result visitClassProperty(ClassProperty entry); + + Result visitClassMethod(ClassMethod entry); + + Result visitClassBody(ClassBody classBody); + + Result visitTypeAlias(TypeAlias typeAlias); + + Result visitAnnotation(Annotation annotation); + + Result visitParameter(Parameter param); + + Result visitParameterList(ParameterList paramList); + + Result visitTypeParameter(TypeParameter typeParameter); + + Result visitTypeParameterList(TypeParameterList typeParameterList); + + Result visitTypeAnnotation(TypeAnnotation typeAnnotation); + + Result visitArgumentList(ArgumentList argumentList); + + Result visitStringPart(StringPart part); + + Result visitStringConstantPart(StringConstantPart part); + + Result visitDocComment(DocComment docComment); + + Result visitIdentifier(Identifier identifier); + + Result visitQualifiedIdentifier(QualifiedIdentifier qualifiedIdentifier); + + Result visitObjectBody(ObjectBody objectBody); + + Result visitReplInput(ReplInput replInput); +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/Span.java b/pkl-core/src/main/java/org/pkl/core/parser/Span.java new file mode 100644 index 00000000..ca809c6c --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/Span.java @@ -0,0 +1,41 @@ +/* + * Copyright © 2025 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.core.parser; + +public record Span(int charIndex, int length) { + + /** Returns a span that starts with this span and ends with {@code end}. */ + public Span endWith(Span end) { + return new Span(charIndex, end.charIndex - charIndex + end.length); + } + + /** Checks wheter {@code other} starts directly after this span ends */ + public boolean adjacent(Span other) { + return charIndex + length == other.charIndex; + } + + public int stopIndex() { + return charIndex + length - 1; + } + + public Span stopSpan() { + return new Span(charIndex + length - 1, 1); + } + + public Span move(int amount) { + return new Span(charIndex + amount, length); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/Token.java b/pkl-core/src/main/java/org/pkl/core/parser/Token.java new file mode 100644 index 00000000..eacf0ef6 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/Token.java @@ -0,0 +1,200 @@ +/* + * Copyright © 2024-2025 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.core.parser; + +public enum Token { + ABSTRACT, + AMENDS, + AS, + CLASS, + CONST, + ELSE, + EXTENDS, + EXTERNAL, + FALSE, + FIXED, + FOR, + FUNCTION, + HIDDEN, + IF, + IMPORT, + IMPORT_STAR, + IN, + IS, + LET, + LOCAL, + MODULE, + NEW, + NOTHING, + NULL, + OPEN, + OUT, + OUTER, + READ, + READ_STAR, + READ_QUESTION, + SUPER, + THIS, + THROW, + TRACE, + TRUE, + TYPE_ALIAS, + UNKNOWN, + WHEN, + + // reserved for future use + PROTECTED, + OVERRIDE, + RECORD, + DELETE, + CASE, + SWITCH, + VARARG, + + // punctuation + LPAREN, + RPAREN, + LBRACE, + RBRACE, + LBRACK, + RBRACK, + LPRED, + COMMA, + DOT, + QDOT, + COALESCE, + NON_NULL, + AT, + ASSIGN, + GT, + LT, + + // rest + NOT, + QUESTION, + COLON, + ARROW, + EQUAL, + NOT_EQUAL, + LTE, + GTE, + AND, + OR, + PLUS, + MINUS, + POW, + STAR, + DIV, + INT_DIV, + MOD, + UNION, + PIPE, + SPREAD, + QSPREAD, + UNDERSCORE, + EOF, + SEMICOLON, + + INT, + FLOAT, + BIN, + OCT, + HEX, + IDENTIFIER, + LINE_COMMENT, + BLOCK_COMMENT, + DOC_COMMENT, + SHEBANG, + INTERPOLATION_START, + STRING_START, + STRING_MULTI_START, + STRING_NEWLINE, + STRING_ESCAPE_NEWLINE, + STRING_ESCAPE_TAB, + STRING_ESCAPE_RETURN, + STRING_ESCAPE_QUOTE, + STRING_ESCAPE_BACKSLASH, + STRING_ESCAPE_UNICODE, + STRING_END, + STRING_PART; + + public boolean isModifier() { + return switch (this) { + case EXTERNAL, ABSTRACT, OPEN, LOCAL, HIDDEN, FIXED, CONST -> true; + default -> false; + }; + } + + public boolean isKeyword() { + return switch (this) { + case ABSTRACT, + AMENDS, + AS, + CLASS, + CONST, + ELSE, + EXTENDS, + EXTERNAL, + FALSE, + FIXED, + FOR, + FUNCTION, + HIDDEN, + IF, + IMPORT, + IMPORT_STAR, + IN, + IS, + LET, + LOCAL, + MODULE, + NEW, + NOTHING, + NULL, + OPEN, + OUT, + OUTER, + READ, + READ_STAR, + READ_QUESTION, + SUPER, + THIS, + THROW, + TRACE, + TRUE, + TYPE_ALIAS, + UNKNOWN, + WHEN, + UNDERSCORE, + PROTECTED, + OVERRIDE, + RECORD, + DELETE, + CASE, + SWITCH, + VARARG -> + true; + default -> false; + }; + } + + public String text() { + if (this == UNDERSCORE) { + return "_"; + } + return name().toLowerCase(); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/AbstractNode.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/AbstractNode.java new file mode 100644 index 00000000..ede02d23 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/AbstractNode.java @@ -0,0 +1,88 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +public abstract class AbstractNode implements Node { + protected final Span span; + protected final @Nullable List children; + protected @Nullable Node parent; + + public AbstractNode(Span span, @Nullable List children) { + this.span = span; + if (children != null) { + this.children = Collections.unmodifiableList(children); + } else { + this.children = null; + } + + if (children != null) { + for (var node : children) { + if (node != null) { + node.setParent(this); + } + } + } + } + + @Override + public Span span() { + return span; + } + + @Override + public @Nullable Node parent() { + return parent; + } + + @Override + public void setParent(Node parent) { + this.parent = parent; + } + + @Override + public @Nullable List children() { + return children; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AbstractNode that = (AbstractNode) o; + return Objects.equals(span, that.span) && Objects.deepEquals(children, that.children); + } + + @Override + public int hashCode() { + return Objects.hash(span, children); + } + + @Override + public String toString() { + var name = getClass().getSimpleName(); + return name + "{span=" + span + ", children=" + children + '}'; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/Annotation.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/Annotation.java new file mode 100644 index 00000000..88c4f17f --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/Annotation.java @@ -0,0 +1,42 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +public class Annotation extends AbstractNode { + public Annotation(List nodes, Span span) { + super(span, nodes); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitAnnotation(this); + } + + public Type getType() { + assert children != null; + return (Type) children.get(0); + } + + public @Nullable ObjectBody getBody() { + assert children != null; + return (ObjectBody) children.get(1); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/ArgumentList.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/ArgumentList.java new file mode 100644 index 00000000..a00948b5 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/ArgumentList.java @@ -0,0 +1,39 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +@SuppressWarnings("unchecked") +public class ArgumentList extends AbstractNode { + + public ArgumentList(List arguments, Span span) { + super(span, arguments); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitArgumentList(this); + } + + public List getArguments() { + assert children != null; + return (List) children; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/Class.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/Class.java new file mode 100644 index 00000000..009b8ac4 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/Class.java @@ -0,0 +1,78 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +@SuppressWarnings({"unchecked", "DataFlowIssue"}) +public final class Class extends AbstractNode { + private final int modifiersOffset; + private final int nameOffset; + + public Class(List nodes, int modifiersOffset, int nameOffset, Span span) { + super(span, nodes); + this.modifiersOffset = modifiersOffset; + this.nameOffset = nameOffset; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitClass(this); + } + + public @Nullable DocComment getDocComment() { + return (DocComment) children.get(0); + } + + public List getAnnotations() { + return (List) children.subList(1, modifiersOffset); + } + + public List getModifiers() { + return (List) children.subList(modifiersOffset, nameOffset); + } + + public Identifier getName() { + return (Identifier) children.get(nameOffset); + } + + public @Nullable TypeParameterList getTypeParameterList() { + return (TypeParameterList) children.get(nameOffset + 1); + } + + public @Nullable Type getSuperClass() { + return (Type) children.get(nameOffset + 2); + } + + public @Nullable ClassBody getBody() { + return (ClassBody) children.get(nameOffset + 3); + } + + public Span getHeaderSpan() { + Span end; + if (getSuperClass() != null) { + end = getSuperClass().span(); + } else if (getTypeParameterList() != null) { + end = getTypeParameterList().span(); + } else { + end = getName().span(); + } + return span.endWith(end); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/ClassBody.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/ClassBody.java new file mode 100644 index 00000000..06f6fca1 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/ClassBody.java @@ -0,0 +1,56 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.ArrayList; +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +public class ClassBody extends AbstractNode { + + public ClassBody(List nodes, Span span) { + super(span, nodes); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitClassBody(this); + } + + public List getProperties() { + var props = new ArrayList(); + assert children != null; + for (var child : children) { + if (child instanceof ClassProperty prop) { + props.add(prop); + } + } + return props; + } + + public List getMethods() { + var methods = new ArrayList(); + assert children != null; + for (var child : children) { + if (child instanceof ClassMethod method) { + methods.add(method); + } + } + return methods; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/ClassMethod.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/ClassMethod.java new file mode 100644 index 00000000..d86070e8 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/ClassMethod.java @@ -0,0 +1,85 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +@SuppressWarnings("unchecked") +public class ClassMethod extends AbstractNode { + private final int modifiersOffset; + private final int nameOffset; + private final Span headerSpan; + + public ClassMethod( + List nodes, int modifiersOffset, int nameOffset, Span headerSpan, Span span) { + super(span, nodes); + this.headerSpan = headerSpan; + this.modifiersOffset = modifiersOffset; + this.nameOffset = nameOffset; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitClassMethod(this); + } + + public @Nullable DocComment getDocComment() { + assert children != null; + return (DocComment) children.get(0); + } + + public List getAnnotations() { + assert children != null; + return (List) children.subList(1, modifiersOffset); + } + + public List getModifiers() { + assert children != null; + return (List) children.subList(modifiersOffset, nameOffset); + } + + public Identifier getName() { + assert children != null; + return (Identifier) children.get(nameOffset); + } + + public @Nullable TypeParameterList getTypeParameterList() { + assert children != null; + return (TypeParameterList) children.get(nameOffset + 1); + } + + public ParameterList getParameterList() { + assert children != null; + return (ParameterList) children.get(nameOffset + 2); + } + + public @Nullable TypeAnnotation getTypeAnnotation() { + assert children != null; + return (TypeAnnotation) children.get(nameOffset + 3); + } + + public @Nullable Expr getExpr() { + assert children != null; + return (Expr) children.get(nameOffset + 4); + } + + public Span getHeaderSpan() { + return headerSpan; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/ClassProperty.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/ClassProperty.java new file mode 100644 index 00000000..1934d53a --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/ClassProperty.java @@ -0,0 +1,73 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +@SuppressWarnings({"DuplicatedCode", "unchecked"}) +public final class ClassProperty extends AbstractNode { + private final int modifiersOffset; + private final int nameOffset; + + public ClassProperty(List nodes, int modifiersOffset, int nameOffset, Span span) { + super(span, nodes); + this.modifiersOffset = modifiersOffset; + this.nameOffset = nameOffset; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitClassProperty(this); + } + + public @Nullable DocComment getDocComment() { + assert children != null; + return (DocComment) children.get(0); + } + + public List getAnnotations() { + assert children != null; + return (List) children.subList(1, modifiersOffset); + } + + public List getModifiers() { + assert children != null; + return (List) children.subList(modifiersOffset, nameOffset); + } + + public Identifier getName() { + assert children != null; + return (Identifier) children.get(nameOffset); + } + + public @Nullable TypeAnnotation getTypeAnnotation() { + assert children != null; + return (TypeAnnotation) children.get(nameOffset + 1); + } + + public @Nullable Expr getExpr() { + assert children != null; + return (Expr) children.get(nameOffset + 2); + } + + public List getBodyList() { + assert children != null; + return (List) children.subList(nameOffset + 3, children.size()); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/DocComment.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/DocComment.java new file mode 100644 index 00000000..12095358 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/DocComment.java @@ -0,0 +1,54 @@ +/* + * Copyright © 2024-2025 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.core.parser.ast; + +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +public final class DocComment extends AbstractNode { + private final List spans; + + public DocComment(List spans) { + super(spans.get(0).endWith(spans.get(spans.size() - 1)), null); + this.spans = spans; + } + + @Override + public Span span() { + return spans.get(0).endWith(spans.get(spans.size() - 1)); + } + + public List getSpans() { + return spans; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitDocComment(this); + } + + @Override + public String text(char[] source) { + var builder = new StringBuilder(); + for (var span : spans) { + builder.append(new String(source, span.charIndex(), span.length())); + builder.append('\n'); + } + return builder.toString(); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/Expr.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/Expr.java new file mode 100644 index 00000000..616d8573 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/Expr.java @@ -0,0 +1,691 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import org.pkl.core.PklBugException; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +@SuppressWarnings("ALL") +public abstract sealed class Expr extends AbstractNode { + + public Expr(Span span, @Nullable List children) { + super(span, children); + } + + public static final class ThisExpr extends Expr { + public ThisExpr(Span span) { + super(span, null); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitThisExpr(this); + } + } + + public static final class OuterExpr extends Expr { + public OuterExpr(Span span) { + super(span, null); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitOuterExpr(this); + } + } + + public static final class ModuleExpr extends Expr { + public ModuleExpr(Span span) { + super(span, null); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitModuleExpr(this); + } + } + + public static final class NullLiteralExpr extends Expr { + public NullLiteralExpr(Span span) { + super(span, null); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitNullLiteralExpr(this); + } + } + + public static final class BoolLiteralExpr extends Expr { + private final boolean b; + + public BoolLiteralExpr(boolean b, Span span) { + super(span, null); + this.b = b; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitBoolLiteralExpr(this); + } + + public boolean isB() { + return b; + } + } + + public static final class IntLiteralExpr extends Expr { + private final String number; + + public IntLiteralExpr(String number, Span span) { + super(span, null); + this.number = number; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitIntLiteralExpr(this); + } + + public String getNumber() { + return number; + } + } + + public static final class FloatLiteralExpr extends Expr { + private final String number; + + public FloatLiteralExpr(String number, Span span) { + super(span, null); + this.number = number; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitFloatLiteralExpr(this); + } + + public String getNumber() { + return number; + } + } + + public static final class SingleLineStringLiteralExpr extends Expr { + private final Span startDelimiterSpan; + private final Span endDelimiterSpan; + + public SingleLineStringLiteralExpr( + List parts, Span startDelimiterSpan, Span endDelimiterSpan, Span span) { + super(span, parts); + this.startDelimiterSpan = startDelimiterSpan; + this.endDelimiterSpan = endDelimiterSpan; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitSingleLineStringLiteralExpr(this); + } + + public List getParts() { + assert children != null; + return (List) children; + } + + public Span getStartDelimiterSpan() { + return startDelimiterSpan; + } + + public Span getEndDelimiterSpan() { + return endDelimiterSpan; + } + } + + public static final class MultiLineStringLiteralExpr extends Expr { + private final Span startDelimiterSpan; + private final Span endDelimiterSpan; + + public MultiLineStringLiteralExpr( + List parts, Span startDelimiterSpan, Span endDelimiterSpan, Span span) { + super(span, parts); + this.startDelimiterSpan = startDelimiterSpan; + this.endDelimiterSpan = endDelimiterSpan; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitMultiLineStringLiteralExpr(this); + } + + public List getParts() { + return (List) children; + } + + public Span getStartDelimiterSpan() { + return startDelimiterSpan; + } + + public Span getEndDelimiterSpan() { + return endDelimiterSpan; + } + } + + public static final class ThrowExpr extends Expr { + public ThrowExpr(Expr expr, Span span) { + super(span, List.of(expr)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitThrowExpr(this); + } + + public Expr getExpr() { + return (Expr) children.get(0); + } + } + + public static final class TraceExpr extends Expr { + public TraceExpr(Expr expr, Span span) { + super(span, List.of(expr)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitTraceExpr(this); + } + + public Expr getExpr() { + return (Expr) children.get(0); + } + } + + public static final class ImportExpr extends Expr { + private final boolean isGlob; + + public ImportExpr(StringConstant importStr, boolean isGlob, Span span) { + super(span, List.of(importStr)); + this.isGlob = isGlob; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitImportExpr(this); + } + + public StringConstant getImportStr() { + return (StringConstant) children.get(0); + } + + public boolean isGlob() { + return isGlob; + } + } + + public static final class ReadExpr extends Expr { + private final ReadType readType; + + public ReadExpr(Expr expr, ReadType readType, Span span) { + super(span, List.of(expr)); + this.readType = readType; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitReadExpr(this); + } + + public Expr getExpr() { + return (Expr) children.get(0); + } + + public ReadType getReadType() { + return readType; + } + } + + public enum ReadType { + READ, + GLOB, + NULL + } + + public static final class UnqualifiedAccessExpr extends Expr { + public UnqualifiedAccessExpr( + Identifier identifier, @Nullable ArgumentList argumentList, Span span) { + super(span, Arrays.asList(identifier, argumentList)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitUnqualifiedAccessExpr(this); + } + + public Identifier getIdentifier() { + return (Identifier) children.get(0); + } + + public @Nullable ArgumentList getArgumentList() { + return (ArgumentList) children.get(1); + } + } + + public static final class QualifiedAccessExpr extends Expr { + private final boolean isNullable; + + public QualifiedAccessExpr( + Expr expr, + Identifier identifier, + boolean isNullable, + @Nullable ArgumentList argumentList, + Span span) { + super(span, Arrays.asList(expr, identifier, argumentList)); + this.isNullable = isNullable; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitQualifiedAccessExpr(this); + } + + public Expr getExpr() { + return (Expr) children.get(0); + } + + public Identifier getIdentifier() { + return (Identifier) children.get(1); + } + + public boolean isNullable() { + return isNullable; + } + + public @Nullable ArgumentList getArgumentList() { + return (ArgumentList) children.get(2); + } + } + + public static final class SuperAccessExpr extends Expr { + public SuperAccessExpr(Identifier identifier, @Nullable ArgumentList argumentList, Span span) { + super(span, Arrays.asList(identifier, argumentList)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitSuperAccessExpr(this); + } + + public Identifier getIdentifier() { + return (Identifier) children.get(0); + } + + public @Nullable ArgumentList getArgumentList() { + return (ArgumentList) children.get(1); + } + } + + public static final class SuperSubscriptExpr extends Expr { + public SuperSubscriptExpr(Expr arg, Span span) { + super(span, List.of(arg)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitSuperSubscriptExpr(this); + } + + public Expr getArg() { + return (Expr) children.get(0); + } + } + + public static final class SubscriptExpr extends Expr { + public SubscriptExpr(Expr expr, Expr arg, Span span) { + super(span, List.of(expr, arg)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitSubscriptExpr(this); + } + + public Expr getExpr() { + return (Expr) children.get(0); + } + + public Expr getArg() { + return (Expr) children.get(1); + } + } + + public static final class IfExpr extends Expr { + public IfExpr(Expr cond, Expr then, Expr els, Span span) { + super(span, List.of(cond, then, els)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitIfExpr(this); + } + + public Expr getCond() { + return (Expr) children.get(0); + } + + public Expr getThen() { + return (Expr) children.get(1); + } + + public Expr getEls() { + return (Expr) children.get(2); + } + } + + public static final class LetExpr extends Expr { + public LetExpr(Parameter parameter, Expr bindingExpr, Expr expr, Span span) { + super(span, List.of(parameter, bindingExpr, expr)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitLetExpr(this); + } + + public Parameter getParameter() { + return (Parameter) children.get(0); + } + + public Expr getBindingExpr() { + return (Expr) children.get(1); + } + + public Expr getExpr() { + return (Expr) children.get(2); + } + } + + public static final class FunctionLiteralExpr extends Expr { + public FunctionLiteralExpr(ParameterList parameterList, Expr expr, Span span) { + super(span, List.of(parameterList, expr)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitFunctionLiteralExpr(this); + } + + public ParameterList getParameterList() { + return (ParameterList) children.get(0); + } + + public Expr getExpr() { + return (Expr) children.get(1); + } + } + + public static final class ParenthesizedExpr extends Expr { + public ParenthesizedExpr(Expr expr, Span span) { + super(span, List.of(expr)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitParenthesizedExpr(this); + } + + public Expr getExpr() { + return (Expr) children.get(0); + } + } + + public static final class NewExpr extends Expr { + public NewExpr(@Nullable Type type, ObjectBody body, Span span) { + super(span, Arrays.asList(type, body)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitNewExpr(this); + } + + public @Nullable Type getType() { + return (Type) children.get(0); + } + + public ObjectBody getBody() { + return (ObjectBody) children.get(1); + } + + public Span newSpan() { + return new Span(span.charIndex(), 3); + } + } + + public static final class AmendsExpr extends Expr { + public AmendsExpr(Expr expr, ObjectBody body, Span span) { + super(span, List.of(expr, body)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitAmendsExpr(this); + } + + public Expr getExpr() { + return (Expr) children.get(0); + } + + public ObjectBody getBody() { + return (ObjectBody) children.get(1); + } + } + + public static final class NonNullExpr extends Expr { + public NonNullExpr(Expr expr, Span span) { + super(span, List.of(expr)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitNonNullExpr(this); + } + + public Expr getExpr() { + return (Expr) children.get(0); + } + } + + public static final class UnaryMinusExpr extends Expr { + public UnaryMinusExpr(Expr expr, Span span) { + super(span, List.of(expr)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitUnaryMinusExpr(this); + } + + public Expr getExpr() { + return (Expr) children.get(0); + } + } + + public static final class LogicalNotExpr extends Expr { + public LogicalNotExpr(Expr expr, Span span) { + super(span, List.of(expr)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitLogicalNotExpr(this); + } + + public Expr getExpr() { + return (Expr) children.get(0); + } + } + + public static final class BinaryOperatorExpr extends Expr { + private final Operator op; + + public BinaryOperatorExpr(Expr left, Expr right, Operator op, Span span) { + super(span, List.of(left, right)); + this.op = op; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitBinaryOperatorExpr(this); + } + + public Expr getLeft() { + return (Expr) children.get(0); + } + + public Expr getRight() { + return (Expr) children.get(1); + } + + public Operator getOp() { + return op; + } + + @Override + public String toString() { + return "BinaryOp{" + "children=" + children + ", op=" + op + ", span=" + span + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BinaryOperatorExpr binaryOp = (BinaryOperatorExpr) o; + return Objects.deepEquals(children, binaryOp.children) + && op == binaryOp.op + && Objects.equals(span, binaryOp.span); + } + + @Override + public int hashCode() { + return Objects.hash(children, op, span); + } + } + + public static final class TypeCheckExpr extends Expr { + public TypeCheckExpr(Expr expr, Type type, Span span) { + super(span, List.of(expr, type)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitTypeCheckExpr(this); + } + + public Expr getExpr() { + return (Expr) children.get(0); + } + + public Type getType() { + return (Type) children.get(1); + } + } + + public static final class TypeCastExpr extends Expr { + public TypeCastExpr(Expr expr, Type type, Span span) { + super(span, List.of(expr, type)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitTypeCastExpr(this); + } + + public Expr getExpr() { + return (Expr) children.get(0); + } + + public Type getType() { + return (Type) children.get(1); + } + } + + /** This is a synthetic class only used at parse time. */ + public static final class OperatorExpr extends Expr { + private final Operator op; + + public OperatorExpr(Operator op, Span span) { + super(span, null); + this.op = op; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + // should never be called + throw PklBugException.unreachableCode(); + } + + public Operator getOp() { + return op; + } + + @Override + public String toString() { + return "OperatorExpr{op=" + op + ", span=" + span + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + OperatorExpr that = (OperatorExpr) o; + return op == that.op && Objects.equals(span, that.span); + } + + @Override + public int hashCode() { + return Objects.hash(op, span); + } + } + + /** This is a synthetic class only used at parse time. */ + public static final class TypeExpr extends Expr { + public TypeExpr(Type type) { + super(type.span(), List.of(type)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + // should never be called + throw PklBugException.unreachableCode(); + } + + public Type getType() { + return (Type) children.get(0); + } + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/ExtendsOrAmendsClause.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/ExtendsOrAmendsClause.java new file mode 100644 index 00000000..03346048 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/ExtendsOrAmendsClause.java @@ -0,0 +1,83 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import java.util.Objects; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +public class ExtendsOrAmendsClause extends AbstractNode { + private final Type type; + + public ExtendsOrAmendsClause(StringConstant url, Type type, Span span) { + super(span, List.of(url)); + this.type = type; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitExtendsOrAmendsClause(this); + } + + public StringConstant getUrl() { + assert children != null; + return (StringConstant) children.get(0); + } + + public Type getType() { + return type; + } + + @Override + public String toString() { + return "ExtendsOrAmendsClause{" + + "type=" + + type + + ", span=" + + span + + ", children=" + + children + + '}'; + } + + @SuppressWarnings("ConstantValue") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + ExtendsOrAmendsClause that = (ExtendsOrAmendsClause) o; + return type == that.type; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), type); + } + + public enum Type { + EXTENDS, + AMENDS + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/Identifier.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/Identifier.java new file mode 100644 index 00000000..daaabffc --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/Identifier.java @@ -0,0 +1,73 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.Objects; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; + +public final class Identifier extends AbstractNode { + private final String value; + + public Identifier(String value, Span span) { + super(span, null); + this.value = value; + } + + @Override + public T accept(ParserVisitor visitor) { + return visitor.visitIdentifier(this); + } + + public String getValue() { + return removeBackticks(value); + } + + public String getRawValue() { + return value; + } + + private static String removeBackticks(String text) { + if (!text.isEmpty() && text.charAt(0) == '`') { + // lexer makes sure there's a ` at the end + return text.substring(1, text.length() - 1); + } + return text; + } + + @Override + public String toString() { + return "Identifier{value='" + value + '\'' + ", span=" + span + '}'; + } + + @SuppressWarnings("ConstantValue") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Identifier identifier = (Identifier) o; + return Objects.equals(value, identifier.value) && Objects.equals(span, identifier.span); + } + + @Override + public int hashCode() { + return Objects.hash(value, span); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/ImportClause.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/ImportClause.java new file mode 100644 index 00000000..635d10ec --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/ImportClause.java @@ -0,0 +1,76 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.Arrays; +import java.util.Objects; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +@SuppressWarnings("DataFlowIssue") +public final class ImportClause extends AbstractNode { + private final boolean isGlob; + + public ImportClause( + StringConstant importStr, boolean isGlob, @Nullable Identifier alias, Span span) { + super(span, Arrays.asList(importStr, alias)); + this.isGlob = isGlob; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitImportClause(this); + } + + public StringConstant getImportStr() { + return (StringConstant) children.get(0); + } + + public boolean isGlob() { + return isGlob; + } + + public @Nullable Identifier getAlias() { + return (Identifier) children.get(1); + } + + @Override + public String toString() { + return "Import{isGlob=" + isGlob + ", span=" + span + ", children=" + children + '}'; + } + + @SuppressWarnings("ConstantValue") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + ImportClause anImport = (ImportClause) o; + return isGlob == anImport.isGlob; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), isGlob); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/Modifier.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/Modifier.java new file mode 100644 index 00000000..998529bd --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/Modifier.java @@ -0,0 +1,72 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.Objects; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +public final class Modifier extends AbstractNode { + private final ModifierValue value; + + public Modifier(ModifierValue value, Span span) { + super(span, null); + this.value = value; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitModifier(this); + } + + public ModifierValue getValue() { + return value; + } + + @Override + public String toString() { + return "Modifier{value=" + value + ", span=" + span + '}'; + } + + @SuppressWarnings("ConstantValue") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Modifier modifier = (Modifier) o; + return value == modifier.value && Objects.equals(span, modifier.span); + } + + @Override + public int hashCode() { + return Objects.hash(value, span); + } + + public enum ModifierValue { + EXTERNAL, + ABSTRACT, + OPEN, + LOCAL, + HIDDEN, + FIXED, + CONST + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/Module.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/Module.java new file mode 100644 index 00000000..cda1e24e --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/Module.java @@ -0,0 +1,93 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.ArrayList; +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +@SuppressWarnings("DataFlowIssue") +public final class Module extends AbstractNode { + public Module(List nodes, Span span) { + super(span, nodes); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitModule(this); + } + + public @Nullable ModuleDecl getDecl() { + return (ModuleDecl) children.get(0); + } + + public List getImports() { + if (children.size() < 2) return List.of(); + var res = new ArrayList(); + for (int i = 1; i < children.size(); i++) { + var child = children.get(i); + if (child instanceof ImportClause imp) { + res.add(imp); + } else { + // imports are sequential + break; + } + } + return res; + } + + public List getClasses() { + var res = new ArrayList(); + for (var child : children) { + if (child instanceof Class clazz) { + res.add(clazz); + } + } + return res; + } + + public List getTypeAliases() { + var res = new ArrayList(); + for (var child : children) { + if (child instanceof TypeAlias typeAlias) { + res.add(typeAlias); + } + } + return res; + } + + public List getProperties() { + var res = new ArrayList(); + for (var child : children) { + if (child instanceof ClassProperty classProperty) { + res.add(classProperty); + } + } + return res; + } + + public List getMethods() { + var res = new ArrayList(); + for (var child : children) { + if (child instanceof ClassMethod classMethod) { + res.add(classMethod); + } + } + return res; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/ModuleDecl.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/ModuleDecl.java new file mode 100644 index 00000000..bef1e004 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/ModuleDecl.java @@ -0,0 +1,73 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +@SuppressWarnings({"DataFlowIssue", "unchecked"}) +public final class ModuleDecl extends AbstractNode { + private final int modifiersOffset; + private final int nameOffset; + + public ModuleDecl(List nodes, int modifiersOffset, int nameOffset, Span span) { + super(span, nodes); + this.modifiersOffset = modifiersOffset; + this.nameOffset = nameOffset; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitModuleDecl(this); + } + + public @Nullable DocComment getDocComment() { + return (DocComment) children.get(0); + } + + public List getAnnotations() { + return (List) children.subList(1, modifiersOffset); + } + + public List getModifiers() { + return (List) children.subList(modifiersOffset, nameOffset); + } + + public @Nullable QualifiedIdentifier getName() { + return (QualifiedIdentifier) children.get(nameOffset); + } + + public @Nullable ExtendsOrAmendsClause getExtendsOrAmendsDecl() { + return (ExtendsOrAmendsClause) children.get(nameOffset + 1); + } + + public Span headerSpan() { + Span start = null; + Span end = null; + for (var i = modifiersOffset; i < children.size(); i++) { + var child = children.get(i); + if (child != null) { + if (start == null) { + start = child.span(); + } + end = child.span(); + } + } + return start.endWith(end); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/Node.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/Node.java new file mode 100644 index 00000000..7d7b8856 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/Node.java @@ -0,0 +1,39 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +public interface Node { + Span span(); + + @Nullable + Node parent(); + + void setParent(Node parent); + + @Nullable + List children(); + + T accept(ParserVisitor visitor); + + default String text(char[] source) { + return new String(source, span().charIndex(), span().length()); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/ObjectBody.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/ObjectBody.java new file mode 100644 index 00000000..24677c61 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/ObjectBody.java @@ -0,0 +1,44 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +@SuppressWarnings({"unchecked", "DataFlowIssue"}) +public final class ObjectBody extends AbstractNode { + private final int membersOffset; + + public ObjectBody(List nodes, int membersOffset, Span span) { + super(span, nodes); + this.membersOffset = membersOffset; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitObjectBody(this); + } + + public List getParameters() { + return (List) children.subList(0, membersOffset); + } + + public List getMembers() { + return (List) children.subList(membersOffset, children.size()); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/ObjectMember.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/ObjectMember.java new file mode 100644 index 00000000..123e5d8c --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/ObjectMember.java @@ -0,0 +1,285 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +@SuppressWarnings("ALL") +public abstract sealed class ObjectMember extends AbstractNode { + + public ObjectMember(Span span, @Nullable List children) { + super(span, children); + } + + public static final class ObjectElement extends ObjectMember { + public ObjectElement(Expr expr, Span span) { + super(span, List.of(expr)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitObjectElement(this); + } + + public Expr getExpr() { + return (Expr) children.get(0); + } + } + + public static final class ObjectProperty extends ObjectMember { + private final int identifierOffset; + + public ObjectProperty(List nodes, int identifierOffset, Span span) { + super(span, nodes); + this.identifierOffset = identifierOffset; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitObjectProperty(this); + } + + public List getModifiers() { + return (List) children.subList(0, identifierOffset); + } + + public Identifier getIdentifier() { + return (Identifier) children.get(identifierOffset); + } + + public @Nullable TypeAnnotation getTypeAnnotation() { + return (TypeAnnotation) children.get(identifierOffset + 1); + } + + public @Nullable Expr getExpr() { + return (Expr) children.get(identifierOffset + 2); + } + + public @Nullable List getBodyList() { + return (List) children.subList(identifierOffset + 3, children.size()); + } + } + + public static final class ObjectMethod extends ObjectMember { + private final int identifierOffset; + + public ObjectMethod(List nodes, int identifierOffset, Span span) { + super(span, nodes); + this.identifierOffset = identifierOffset; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitObjectMethod(this); + } + + public List getModifiers() { + return (List) children.subList(0, identifierOffset); + } + + public Identifier getIdentifier() { + return (Identifier) children.get(identifierOffset); + } + + public @Nullable TypeParameterList getTypeParameterList() { + return (TypeParameterList) children.get(identifierOffset + 1); + } + + public ParameterList getParamList() { + return (ParameterList) children.get(identifierOffset + 2); + } + + public @Nullable TypeAnnotation getTypeAnnotation() { + return (TypeAnnotation) children.get(identifierOffset + 3); + } + + public Expr getExpr() { + return (Expr) children.get(identifierOffset + 4); + } + + public Span headerSpan() { + Span end; + var typeAnnotation = children.get(identifierOffset + 3); + if (typeAnnotation == null) { + end = children.get(identifierOffset + 2).span(); + } else { + end = typeAnnotation.span(); + } + return span.endWith(end); + } + } + + public static final class MemberPredicate extends ObjectMember { + public MemberPredicate(List nodes, Span span) { + super(span, nodes); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitMemberPredicate(this); + } + + public Expr getPred() { + return (Expr) children.get(0); + } + + public @Nullable Expr getExpr() { + return (Expr) children.get(1); + } + + public List getBodyList() { + return (List) children.subList(2, children.size()); + } + } + + public static final class ObjectEntry extends ObjectMember { + public ObjectEntry(List nodes, Span span) { + super(span, nodes); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitObjectEntry(this); + } + + public Expr getKey() { + return (Expr) children.get(0); + } + + public @Nullable Expr getValue() { + return (Expr) children.get(1); + } + + public List getBodyList() { + return (List) children.subList(2, children.size()); + } + } + + public static final class ObjectSpread extends ObjectMember { + private final boolean isNullable; + + public ObjectSpread(Expr expr, boolean isNullable, Span span) { + super(span, List.of(expr)); + this.isNullable = isNullable; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitObjectSpread(this); + } + + public Expr getExpr() { + return (Expr) children.get(0); + } + + public boolean isNullable() { + return isNullable; + } + + @Override + public String toString() { + return "ObjectSpread{" + + "isNullable=" + + isNullable + + ", span=" + + span + + ", children=" + + children + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + ObjectSpread that = (ObjectSpread) o; + return isNullable == that.isNullable; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), isNullable); + } + } + + public static final class WhenGenerator extends ObjectMember { + public WhenGenerator( + Expr thenClause, ObjectBody body, @Nullable ObjectBody elseClause, Span span) { + super(span, Arrays.asList(thenClause, body, elseClause)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitWhenGenerator(this); + } + + public Expr getThenClause() { + return (Expr) children.get(0); + } + + public ObjectBody getBody() { + return (ObjectBody) children.get(1); + } + + public @Nullable ObjectBody getElseClause() { + return (ObjectBody) children.get(2); + } + } + + public static final class ForGenerator extends ObjectMember { + public ForGenerator( + Parameter p1, @Nullable Parameter p2, Expr expr, ObjectBody body, Span span) { + super(span, Arrays.asList(p1, p2, expr, body)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitForGenerator(this); + } + + public Parameter getP1() { + return (Parameter) children.get(0); + } + + public @Nullable Parameter getP2() { + return (Parameter) children.get(1); + } + + public Expr getExpr() { + return (Expr) children.get(2); + } + + public ObjectBody getBody() { + return (ObjectBody) children.get(3); + } + + public Span forSpan() { + return new Span(span.charIndex(), 3); + } + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/Operator.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/Operator.java new file mode 100644 index 00000000..649ef729 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/Operator.java @@ -0,0 +1,40 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +public enum Operator { + POW, + MULT, + DIV, + INT_DIV, + MOD, + PLUS, + MINUS, + LT, + GT, + LTE, + GTE, + IS, + AS, + EQ_EQ, + NOT_EQ, + AND, + OR, + PIPE, + NULL_COALESCE, + DOT, + QDOT, +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/Parameter.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/Parameter.java new file mode 100644 index 00000000..c90aebbf --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/Parameter.java @@ -0,0 +1,56 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.Arrays; +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +@SuppressWarnings("ALL") +public abstract sealed class Parameter extends AbstractNode { + + public Parameter(Span span, @Nullable List children) { + super(span, children); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitParameter(this); + } + + public static final class Underscore extends Parameter { + public Underscore(Span span) { + super(span, null); + } + } + + public static final class TypedIdentifier extends Parameter { + public TypedIdentifier( + Identifier identifier, @Nullable TypeAnnotation typeAnnotation, Span span) { + super(span, Arrays.asList(identifier, typeAnnotation)); + } + + public Identifier getIdentifier() { + return (Identifier) children.get(0); + } + + public @Nullable TypeAnnotation getTypeAnnotation() { + return (TypeAnnotation) children.get(1); + } + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/ParameterList.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/ParameterList.java new file mode 100644 index 00000000..f5cc0d07 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/ParameterList.java @@ -0,0 +1,38 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +@SuppressWarnings("unchecked") +public class ParameterList extends AbstractNode { + public ParameterList(List parameters, Span span) { + super(span, parameters); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitParameterList(this); + } + + public List getParameters() { + assert children != null; + return (List) children; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/QualifiedIdentifier.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/QualifiedIdentifier.java new file mode 100644 index 00000000..ae71f866 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/QualifiedIdentifier.java @@ -0,0 +1,42 @@ +/* + * Copyright © 2024-2025 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.core.parser.ast; + +import java.util.List; +import java.util.stream.Collectors; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.util.Nullable; + +public final class QualifiedIdentifier extends AbstractNode { + public QualifiedIdentifier(List identifiers) { + super( + identifiers.get(0).span.endWith(identifiers.get(identifiers.size() - 1).span), identifiers); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitQualifiedIdentifier(this); + } + + @SuppressWarnings({"unchecked", "DataFlowIssue"}) + public List getIdentifiers() { + return (List) children; + } + + public String text() { + return getIdentifiers().stream().map(Identifier::getValue).collect(Collectors.joining(".")); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/ReplInput.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/ReplInput.java new file mode 100644 index 00000000..7fc15caa --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/ReplInput.java @@ -0,0 +1,38 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +public class ReplInput extends AbstractNode { + public ReplInput(List nodes, Span span) { + super(span, nodes); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitReplInput(this); + } + + @SuppressWarnings("unchecked") + public List getNodes() { + assert children != null; + return (List) children; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/StringConstant.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/StringConstant.java new file mode 100644 index 00000000..9c6a81c4 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/StringConstant.java @@ -0,0 +1,38 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.parser.ast.StringPart.StringConstantParts; +import org.pkl.core.util.Nullable; + +public class StringConstant extends AbstractNode { + public StringConstant(StringConstantParts strParts, Span span) { + super(span, List.of(strParts)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitStringConstant(this); + } + + public StringConstantParts getStrParts() { + assert children != null; + return (StringConstantParts) children.get(0); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/StringConstantPart.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/StringConstantPart.java new file mode 100644 index 00000000..20a7d903 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/StringConstantPart.java @@ -0,0 +1,154 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import java.util.Objects; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +@SuppressWarnings("ALL") +public abstract sealed class StringConstantPart extends AbstractNode { + + public StringConstantPart(Span span, @Nullable List children) { + super(span, children); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitStringConstantPart(this); + } + + public static final class StringNewline extends StringConstantPart { + public StringNewline(Span span) { + super(span, null); + } + } + + public static final class ConstantPart extends StringConstantPart { + private final String str; + + public ConstantPart(String str, Span span) { + super(span, null); + this.str = str; + } + + public String getStr() { + return str; + } + + @Override + public String toString() { + return "ConstantPart{str='" + str + '\'' + ", span=" + span + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ConstantPart that = (ConstantPart) o; + return Objects.equals(str, that.str) && Objects.equals(span, that.span); + } + + @Override + public int hashCode() { + return Objects.hash(str, span); + } + } + + public static final class StringUnicodeEscape extends StringConstantPart { + private final String escape; + + public StringUnicodeEscape(String escape, Span span) { + super(span, null); + this.escape = escape; + } + + public String getEscape() { + return escape; + } + + @Override + public String toString() { + return "StringUnicodeEscape{escape='" + escape + '\'' + ", span=" + span + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StringUnicodeEscape that = (StringUnicodeEscape) o; + return Objects.equals(escape, that.escape) && Objects.equals(span, that.span); + } + + @Override + public int hashCode() { + return Objects.hash(escape, span); + } + } + + public static final class StringEscape extends StringConstantPart { + private final EscapeType type; + + public StringEscape(EscapeType type, Span span) { + super(span, null); + this.type = type; + } + + public EscapeType getType() { + return type; + } + + @Override + public String toString() { + return "StringEscape{type=" + type + ", span=" + span + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StringEscape that = (StringEscape) o; + return type == that.type && Objects.equals(span, that.span); + } + + @Override + public int hashCode() { + return Objects.hash(type, span); + } + } + + public enum EscapeType { + NEWLINE, + TAB, + RETURN, + QUOTE, + BACKSLASH + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/StringPart.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/StringPart.java new file mode 100644 index 00000000..5847ad60 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/StringPart.java @@ -0,0 +1,54 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +@SuppressWarnings("ALL") +public abstract sealed class StringPart extends AbstractNode { + + public StringPart(Span span, @Nullable List children) { + super(span, children); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitStringPart(this); + } + + public static final class StringConstantParts extends StringPart { + public StringConstantParts(List parts, Span span) { + super(span, parts); + } + + public List getParts() { + return (List) children; + } + } + + public static final class StringInterpolation extends StringPart { + public StringInterpolation(Expr expr, Span span) { + super(span, List.of(expr)); + } + + public Expr getExpr() { + return (Expr) children.get(0); + } + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/Type.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/Type.java new file mode 100644 index 00000000..aefc3916 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/Type.java @@ -0,0 +1,219 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import java.util.Objects; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +@SuppressWarnings("ALL") +public abstract sealed class Type extends AbstractNode { + + public Type(Span span, @Nullable List children) { + super(span, children); + } + + public static final class UnknownType extends Type { + public UnknownType(Span span) { + super(span, null); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitUnknownType(this); + } + } + + public static final class NothingType extends Type { + public NothingType(Span span) { + super(span, null); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitNothingType(this); + } + } + + public static final class ModuleType extends Type { + public ModuleType(Span span) { + super(span, null); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitModuleType(this); + } + } + + public static final class StringConstantType extends Type { + public StringConstantType(StringConstant str, Span span) { + super(span, List.of(str)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitStringConstantType(this); + } + + public StringConstant getStr() { + return (StringConstant) children.get(0); + } + } + + public static final class DeclaredType extends Type { + public DeclaredType(List nodes, Span span) { + super(span, nodes); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitDeclaredType(this); + } + + public QualifiedIdentifier getName() { + return (QualifiedIdentifier) children.get(0); + } + + public List getArgs() { + return (List) children.subList(1, children.size()); + } + } + + public static final class ParenthesizedType extends Type { + public ParenthesizedType(Type type, Span span) { + super(span, List.of(type)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitParenthesizedType(this); + } + + public Type getType() { + return (Type) children.get(0); + } + } + + public static final class NullableType extends Type { + public NullableType(Type type, Span span) { + super(span, List.of(type)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitNullableType(this); + } + + public Type getType() { + return (Type) children.get(0); + } + } + + public static final class ConstrainedType extends Type { + public ConstrainedType(List nodes, Span span) { + super(span, nodes); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitConstrainedType(this); + } + + public Type getType() { + return (Type) children.get(0); + } + + public List getExprs() { + return (List) children.subList(1, children.size()); + } + } + + public static final class UnionType extends Type { + private final int defaultIndex; + + public UnionType(List types, int defaultIndex, Span span) { + super(span, types); + this.defaultIndex = defaultIndex; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitUnionType(this); + } + + public List getTypes() { + return (List) children; + } + + public int getDefaultIndex() { + return defaultIndex; + } + + @Override + public String toString() { + return "UnionType{" + + "defaultIndex=" + + defaultIndex + + ", span=" + + span + + ", children=" + + children + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + UnionType unionType = (UnionType) o; + return defaultIndex == unionType.defaultIndex; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), defaultIndex); + } + } + + public static final class FunctionType extends Type { + public FunctionType(List children, Span span) { + super(span, children); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitFunctionType(this); + } + + public List getArgs() { + return (List) children.subList(0, children.size() - 1); + } + + public Type getRet() { + return (Type) children.get(children.size() - 1); + } + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/TypeAlias.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/TypeAlias.java new file mode 100644 index 00000000..be0f8246 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/TypeAlias.java @@ -0,0 +1,71 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +@SuppressWarnings("ALL") +public final class TypeAlias extends AbstractNode { + private final int modifiersOffset; + private final int nameOffset; + + public TypeAlias(List children, int modifiersOffset, int nameOffset, Span span) { + super(span, children); + this.modifiersOffset = modifiersOffset; + this.nameOffset = nameOffset; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitTypeAlias(this); + } + + public @Nullable DocComment getDocComment() { + return (DocComment) children.get(0); + } + + public List getAnnotations() { + return (List) children.subList(1, modifiersOffset); + } + + public List getModifiers() { + return (List) children.subList(modifiersOffset, nameOffset); + } + + public Identifier getName() { + return (Identifier) children.get(nameOffset); + } + + public @Nullable TypeParameterList getTypeParameterList() { + return (TypeParameterList) children.get(nameOffset + 1); + } + + public Type getType() { + return (Type) children.get(nameOffset + 2); + } + + public Span getHeaderSpan() { + var end = children.get(nameOffset).span(); + var tparList = children.get(nameOffset + 1); + if (tparList != null) { + end = tparList.span(); + } + return span.endWith(end); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/TypeAnnotation.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/TypeAnnotation.java new file mode 100644 index 00000000..99c49077 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/TypeAnnotation.java @@ -0,0 +1,37 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +public class TypeAnnotation extends AbstractNode { + public TypeAnnotation(Type type, Span span) { + super(span, List.of(type)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitTypeAnnotation(this); + } + + public Type getType() { + assert children != null; + return (Type) children.get(0); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/TypeParameter.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/TypeParameter.java new file mode 100644 index 00000000..098111ce --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/TypeParameter.java @@ -0,0 +1,82 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import java.util.Objects; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +public final class TypeParameter extends AbstractNode { + private final @Nullable Variance variance; + + public TypeParameter(@Nullable Variance variance, Identifier identifier, Span span) { + super(span, List.of(identifier)); + this.variance = variance; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitTypeParameter(this); + } + + public @Nullable Variance getVariance() { + return variance; + } + + public Identifier getIdentifier() { + assert children != null; + return (Identifier) children.get(0); + } + + @Override + public String toString() { + return "TypeParameter{" + + "variance=" + + variance + + ", children=" + + children + + ", span=" + + span + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + TypeParameter that = (TypeParameter) o; + return variance == that.variance; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), variance); + } + + public enum Variance { + IN, + OUT + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/TypeParameterList.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/TypeParameterList.java new file mode 100644 index 00000000..07f156d1 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/TypeParameterList.java @@ -0,0 +1,38 @@ +/* + * Copyright © 2025 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.core.parser.ast; + +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.util.Nullable; + +public class TypeParameterList extends AbstractNode { + public TypeParameterList(List parameters, Span span) { + super(span, parameters); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitTypeParameterList(this); + } + + @SuppressWarnings("unchecked") + public List getParameters() { + assert children != null; + return (List) children; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/parser/ast/package-info.java b/pkl-core/src/main/java/org/pkl/core/parser/ast/package-info.java new file mode 100644 index 00000000..9ff7bdc1 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ast/package-info.java @@ -0,0 +1,4 @@ +@NonnullByDefault +package org.pkl.core.parser.ast; + +import org.pkl.core.util.NonnullByDefault; diff --git a/pkl-core/src/main/java/org/pkl/core/repl/ReplServer.java b/pkl-core/src/main/java/org/pkl/core/repl/ReplServer.java index 3b437a29..391b9fb2 100644 --- a/pkl-core/src/main/java/org/pkl/core/repl/ReplServer.java +++ b/pkl-core/src/main/java/org/pkl/core/repl/ReplServer.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -23,7 +23,6 @@ import java.net.URI; import java.nio.file.Path; import java.util.*; import java.util.stream.Collectors; -import org.antlr.v4.runtime.tree.TerminalNode; import org.graalvm.collections.UnmodifiableEconomicMap; import org.graalvm.polyglot.Context; import org.pkl.core.*; @@ -36,10 +35,14 @@ import org.pkl.core.ast.type.TypeNode; import org.pkl.core.http.HttpClient; import org.pkl.core.module.*; import org.pkl.core.packages.PackageResolver; -import org.pkl.core.parser.LexParseException; import org.pkl.core.parser.Parser; -import org.pkl.core.parser.antlr.PklParser; -import org.pkl.core.parser.antlr.PklParser.*; +import org.pkl.core.parser.ParserError; +import org.pkl.core.parser.ast.Class; +import org.pkl.core.parser.ast.ClassProperty; +import org.pkl.core.parser.ast.Expr; +import org.pkl.core.parser.ast.ImportClause; +import org.pkl.core.parser.ast.ModuleDecl; +import org.pkl.core.parser.ast.ReplInput; import org.pkl.core.project.DeclaredDependencies; import org.pkl.core.repl.ReplRequest.Eval; import org.pkl.core.repl.ReplRequest.Load; @@ -166,7 +169,7 @@ public class ReplServer implements AutoCloseable { .collect(Collectors.toList()); } - @SuppressWarnings("StatementWithEmptyBody") + @SuppressWarnings({"StatementWithEmptyBody", "DataFlowIssue"}) private List evaluate( ReplState replState, String requestId, @@ -174,14 +177,12 @@ public class ReplServer implements AutoCloseable { boolean evalDefinitions, boolean forceResults) { var parser = new Parser(); - PklParser.ReplInputContext replInputContext; + ReplInput replInputContext; var uri = URI.create("repl:" + requestId); try { replInputContext = parser.parseReplInput(text); - } catch (LexParseException.IncompleteInput e) { - return List.of(new ReplResponse.IncompleteInput(e.getMessage())); - } catch (LexParseException e) { + } catch (ParserError e) { var exception = VmUtils.toVmException(e, text, uri, uri.toString()); var errorMessage = errorRenderer.render(exception); return List.of(new EvalError(errorMessage)); @@ -205,32 +206,28 @@ public class ReplServer implements AutoCloseable { language, replState.module.getModuleInfo(), moduleResolver); - var childrenExceptEof = - replInputContext.children.subList(0, replInputContext.children.size() - 1); - for (var tree : childrenExceptEof) { + for (var tree : replInputContext.getNodes()) { try { - if (tree instanceof ExprContext) { - var exprNode = (ExpressionNode) tree.accept(builder); + if (tree instanceof Expr expr) { + var exprNode = builder.visitExpr(expr); evaluateExpr(replState, exprNode, forceResults, results); - } else if (tree instanceof ImportClauseContext importClause) { + } else if (tree instanceof ImportClause importClause) { addStaticModuleProperty(builder.visitImportClause(importClause)); - } else if (tree instanceof ClassPropertyContext classProperty) { + } else if (tree instanceof ClassProperty classProperty) { var propertyNode = builder.visitClassProperty(classProperty); var property = addModuleProperty(propertyNode); if (evalDefinitions) { evaluateMemberDef(replState, property, forceResults, results); } - } else if (tree instanceof ClazzContext clazz) { - addStaticModuleProperty(builder.visitClazz(clazz)); - } else if (tree instanceof TypeAliasContext typeAlias) { + } else if (tree instanceof Class clazz) { + addStaticModuleProperty(builder.visitClass(clazz)); + } else if (tree instanceof org.pkl.core.parser.ast.TypeAlias typeAlias) { addStaticModuleProperty(builder.visitTypeAlias(typeAlias)); - } else if (tree instanceof ClassMethodContext classMethod) { + } else if (tree instanceof org.pkl.core.parser.ast.ClassMethod classMethod) { addModuleMethodDef(builder.visitClassMethod(classMethod)); - } else if (tree instanceof ModuleDeclContext) { + } else if (tree instanceof ModuleDecl) { // do nothing for now - } else if (tree instanceof TerminalNode && tree.toString().equals(",")) { - // do nothing } else { results.add( new ReplResponse.InternalError(new IllegalStateException("Unexpected parse result"))); @@ -261,7 +258,7 @@ public class ReplServer implements AutoCloseable { language, new FrameDescriptor(), propertyNode, replState.module.getVmClass()); var property = - (ClassProperty) + (org.pkl.core.ast.member.ClassProperty) callNode.call(resolveNode.getCallTarget(), replState.module, replState.module); replState.module.getVmClass().addProperty(property); @@ -397,7 +394,7 @@ public class ReplServer implements AutoCloseable { } private VmTyped createReplModule( - Iterable propertyDefs, + Iterable propertyDefs, Iterable methodDefs, UnmodifiableEconomicMap moduleMembers, @Nullable VmTyped parent) { diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/MinPklVersionChecker.java b/pkl-core/src/main/java/org/pkl/core/runtime/MinPklVersionChecker.java index cce6ec58..696215cc 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/MinPklVersionChecker.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/MinPklVersionChecker.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -16,12 +16,12 @@ package org.pkl.core.runtime; import com.oracle.truffle.api.nodes.Node; -import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.RuleContext; -import org.antlr.v4.runtime.tree.TerminalNode; import org.pkl.core.Release; import org.pkl.core.Version; -import org.pkl.core.parser.antlr.PklParser.*; +import org.pkl.core.parser.ast.Module; +import org.pkl.core.parser.ast.ObjectMember.ObjectProperty; +import org.pkl.core.parser.ast.Type; +import org.pkl.core.parser.ast.Type.DeclaredType; import org.pkl.core.util.Nullable; final class MinPklVersionChecker { @@ -44,26 +44,26 @@ final class MinPklVersionChecker { } } - static void check(String moduleName, @Nullable ParserRuleContext ctx, @Nullable Node importNode) { - if (!(ctx instanceof ModuleContext moduleCtx)) return; + static void check( + String moduleName, @Nullable Module mod, @Nullable Node importNode, String source) { + if (mod == null) return; - var moduleDeclCtx = moduleCtx.moduleDecl(); - if (moduleDeclCtx == null) return; + var moduleDecl = mod.getDecl(); + if (moduleDecl == null) return; - for (var annCtx : moduleDeclCtx.annotation()) { - if (!Identifier.MODULE_INFO.toString().equals(getLastIdText(annCtx.type()))) continue; + for (var ann : moduleDecl.getAnnotations()) { + if (!Identifier.MODULE_INFO.toString().equals(getLastIdText(ann.getType()))) continue; - var objectBodyCtx = annCtx.objectBody(); - if (objectBodyCtx == null) continue; + var objectBody = ann.getBody(); + if (objectBody == null) continue; - for (var memberCtx : objectBodyCtx.objectMember()) { - if (!(memberCtx instanceof ObjectPropertyContext propertyCtx)) continue; + for (var member : objectBody.getMembers()) { + if (!(member instanceof ObjectProperty prop)) continue; - if (!Identifier.MIN_PKL_VERSION.toString().equals(getText(propertyCtx.Identifier()))) + if (!Identifier.MIN_PKL_VERSION.toString().equals(prop.getIdentifier().getValue())) continue; - var versionText = getText(propertyCtx.expr()); - if (versionText == null) continue; + var versionText = prop.getExpr().text(source.toCharArray()); Version version; try { @@ -78,20 +78,6 @@ final class MinPklVersionChecker { } } - private static @Nullable String getText(@Nullable RuleContext ruleCtx) { - return ruleCtx == null ? null : ruleCtx.getText(); - } - - private static @Nullable String getLastIdText(@Nullable TypeContext typeCtx) { - if (!(typeCtx instanceof DeclaredTypeContext declCtx)) return null; - var token = declCtx.qualifiedIdentifier().Identifier; - return token == null ? null : token.getText(); - } - - private static @Nullable String getText(@Nullable TerminalNode idCtx) { - return idCtx == null ? null : idCtx.getText(); - } - private static void doCheck( String moduleName, @Nullable Version requiredVersion, @Nullable Node importNode) { if (requiredVersion == null || currentMajorMinorPatchVersion.compareTo(requiredVersion) >= 0) @@ -102,4 +88,10 @@ final class MinPklVersionChecker { .evalError("incompatiblePklVersion", moduleName, requiredVersion, currentVersion) .build(); } + + private static @Nullable String getLastIdText(@Nullable Type type) { + if (!(type instanceof DeclaredType declType)) return null; + var identifiers = declType.getName().getIdentifiers(); + return identifiers.get(identifiers.size() - 1).getValue(); + } } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/ModuleInfo.java b/pkl-core/src/main/java/org/pkl/core/runtime/ModuleInfo.java index efa7b9b7..8d15fc1d 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/ModuleInfo.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/ModuleInfo.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -32,7 +32,7 @@ import org.pkl.core.util.Nullable; public final class ModuleInfo { private final SourceSection headerSection; private final SourceSection sourceSection; - private final @Nullable SourceSection docComment; + private final SourceSection @Nullable [] docComment; private final String moduleName; private final ModuleKey moduleKey; private final ResolvedModuleKey resolvedModuleKey; @@ -49,7 +49,7 @@ public final class ModuleInfo { public ModuleInfo( SourceSection sourceSection, SourceSection headerSection, - @Nullable SourceSection docComment, + SourceSection @Nullable [] docComment, String moduleName, ModuleKey moduleKey, ResolvedModuleKey resolvedModuleKey, @@ -82,7 +82,7 @@ public final class ModuleInfo { return headerSection; } - public @Nullable SourceSection getDocComment() { + public SourceSection @Nullable [] getDocComment() { return docComment; } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmClass.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmClass.java index 36725ea8..c693267c 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmClass.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmClass.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -46,7 +46,7 @@ import org.pkl.core.util.Nullable; public final class VmClass extends VmValue { private final SourceSection sourceSection; private final SourceSection headerSection; - private final @Nullable SourceSection docComment; + private final SourceSection @Nullable [] docComment; private final List annotations; private final int modifiers; private final PClassInfo classInfo; @@ -126,7 +126,7 @@ public final class VmClass extends VmValue { public VmClass( SourceSection sourceSection, SourceSection headerSection, - @Nullable SourceSection docComment, + SourceSection @Nullable [] docComment, List annotations, int modifiers, PClassInfo classInfo, @@ -223,7 +223,7 @@ public final class VmClass extends VmValue { return headerSection; } - public @Nullable SourceSection getDocComment() { + public SourceSection @Nullable [] getDocComment() { return docComment; } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java index b735bbca..51f8ca67 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -16,7 +16,6 @@ package org.pkl.core.runtime; import com.oracle.truffle.api.CallTarget; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.TruffleLanguage.ContextPolicy; import com.oracle.truffle.api.nodes.Node; @@ -24,9 +23,9 @@ import com.oracle.truffle.api.source.Source; import org.pkl.core.ast.builder.AstBuilder; import org.pkl.core.module.ModuleKey; import org.pkl.core.module.ResolvedModuleKey; -import org.pkl.core.parser.LexParseException; import org.pkl.core.parser.Parser; -import org.pkl.core.parser.antlr.PklParser; +import org.pkl.core.parser.ParserError; +import org.pkl.core.parser.ast.Module; import org.pkl.core.util.IoUtils; import org.pkl.core.util.Nullable; @@ -56,7 +55,6 @@ public final class VmLanguage extends TruffleLanguage { throw new UnsupportedOperationException("parse"); } - @TruffleBoundary public VmTyped loadModule(ModuleKey moduleKey) { var context = VmContext.get(null); @@ -71,7 +69,6 @@ public final class VmLanguage extends TruffleLanguage { null); } - @TruffleBoundary public VmTyped loadModule(ModuleKey moduleKey, @Nullable Node importNode) { var context = VmContext.get(null); @@ -86,7 +83,6 @@ public final class VmLanguage extends TruffleLanguage { importNode); } - @TruffleBoundary void initializeModule( ModuleKey moduleKey, ResolvedModuleKey resolvedModuleKey, @@ -95,12 +91,13 @@ public final class VmLanguage extends TruffleLanguage { VmTyped emptyModule, @Nullable Node importNode) { var parser = new Parser(); - PklParser.ModuleContext moduleContext; + Module moduleContext; + var sourceStr = source.getCharacters().toString(); try { - moduleContext = parser.parseModule(source.getCharacters().toString()); - } catch (LexParseException e) { + moduleContext = parser.parseModule(sourceStr); + } catch (ParserError e) { var moduleName = IoUtils.inferModuleName(moduleKey); - MinPklVersionChecker.check(moduleName, e.getPartialParseResult(), importNode); + MinPklVersionChecker.check(moduleName, e.getPartialParseResult(), importNode, sourceStr); throw VmUtils.toVmException(e, source, moduleName); } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmTypeAlias.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmTypeAlias.java index d3a07ef3..33b324bb 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmTypeAlias.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmTypeAlias.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -37,7 +37,7 @@ import org.pkl.core.util.Nullable; public final class VmTypeAlias extends VmValue { private final SourceSection sourceSection; private final SourceSection headerSection; - private final @Nullable SourceSection docComment; + private final SourceSection @Nullable [] docComment; private final int modifiers; private final List annotations; private final String simpleName; @@ -63,7 +63,7 @@ public final class VmTypeAlias extends VmValue { public VmTypeAlias( SourceSection sourceSection, SourceSection headerSection, - @Nullable SourceSection docComment, + SourceSection @Nullable [] docComment, int modifiers, List annotations, String simpleName, @@ -128,7 +128,7 @@ public final class VmTypeAlias extends VmValue { return typeNode != null; } - public @Nullable SourceSection getDocComment() { + public SourceSection @Nullable [] getDocComment() { return docComment; } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmUndefinedValueException.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmUndefinedValueException.java index dea2eea9..bf76dc2b 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmUndefinedValueException.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmUndefinedValueException.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java index b2f3ee2a..e4bae953 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java @@ -56,9 +56,9 @@ import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.module.ModuleKey; import org.pkl.core.module.ModuleKeys; import org.pkl.core.module.ResolvedModuleKey; -import org.pkl.core.parser.LexParseException; import org.pkl.core.parser.Parser; -import org.pkl.core.parser.antlr.PklParser.ExprContext; +import org.pkl.core.parser.ParserError; +import org.pkl.core.parser.ast.Expr; import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.Nullable; @@ -506,7 +506,7 @@ public final class VmUtils { } public static VmException toVmException( - LexParseException e, String text, URI moduleUri, String moduleName) { + ParserError e, String text, URI moduleUri, String moduleName) { var source = Source.newBuilder("pkl", text, moduleName) .mimeType(VmLanguage.MIME_TYPE) @@ -516,47 +516,36 @@ public final class VmUtils { return toVmException(e, source, moduleName); } - // wanted to keep Parser/LexParseException API free from + // wanted to keep Parser/ParserError API free from // Truffle classes (Source), hence put this method here - public static VmException toVmException(LexParseException e, Source source, String moduleName) { - int lineStartOffset; - try { - lineStartOffset = source.getLineStartOffset(e.getLine()); - } catch (IllegalArgumentException iae) { - // work around the fact that antlr and truffle disagree on how many lines a file that is - // ending in a newline has - lineStartOffset = source.getLineStartOffset(e.getLine() - 1); - } - + public static VmException toVmException(ParserError e, Source source, String moduleName) { return new VmExceptionBuilder() .adhocEvalError(e.getMessage()) - .withSourceSection( - source.createSection( - // compute char offset manually to work around - // https://github.com/graalvm/truffle/issues/184 - lineStartOffset + e.getColumn() - 1, e.getLength())) + .withSourceSection(source.createSection(e.span().charIndex(), e.span().length())) .withMemberName(moduleName) .build(); } - public static @Nullable String exportDocComment(@Nullable SourceSection docComment) { + public static @Nullable String exportDocComment(SourceSection @Nullable [] docComment) { if (docComment == null) return null; var builder = new StringBuilder(); - var matcher = DOC_COMMENT_LINE_START.matcher(docComment.getCharacters()); - var firstMatch = true; - while (matcher.find()) { - if (firstMatch) { - matcher.appendReplacement(builder, ""); - firstMatch = false; - } else { - matcher.appendReplacement(builder, "\n"); + for (var i = 0; i < docComment.length; i++) { + if (i > 0) { + builder.append("\n"); } + var matcher = DOC_COMMENT_LINE_START.matcher(docComment[i].getCharacters()); + var firstMatch = true; + while (matcher.find()) { + if (firstMatch) { + matcher.appendReplacement(builder, ""); + firstMatch = false; + } else { + matcher.appendReplacement(builder, "\n"); + } + } + matcher.appendTail(builder); } - matcher.appendTail(builder); - var newLength = builder.length() - 1; - assert builder.charAt(newLength) == '\n'; - builder.setLength(newLength); return builder.toString(); } @@ -857,10 +846,10 @@ public final class VmUtils { section.getEndColumn()); } - private static ExprContext parseExpressionContext(String expression, Source source) { + private static Expr parseExpressionNode(String expression, Source source) { try { - return new Parser().parseExpressionInput(expression).expr(); - } catch (LexParseException e) { + return new Parser().parseExpressionInput(expression); + } catch (ParserError e) { throw VmUtils.toVmException(e, source, REPL_TEXT); } } @@ -891,8 +880,8 @@ public final class VmUtils { false); var language = VmLanguage.get(null); var builder = new AstBuilder(source, language, moduleInfo, moduleResolver); - var exprContext = parseExpressionContext(expression, source); - var exprNode = (ExpressionNode) exprContext.accept(builder); + var parsedExpression = parseExpressionNode(expression, source); + var exprNode = builder.visitExpr(parsedExpression); var rootNode = new SimpleRootNode( language, new FrameDescriptor(), exprNode.getSourceSection(), "", exprNode); diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmValueRenderer.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmValueRenderer.java index 602141d1..5051fa0e 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmValueRenderer.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmValueRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/base/PcfRenderer.java b/pkl-core/src/main/java/org/pkl/core/stdlib/base/PcfRenderer.java index cee2f76f..22db522f 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/base/PcfRenderer.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/base/PcfRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. diff --git a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties index 51c0c2eb..e5066767 100644 --- a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties +++ b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties @@ -797,7 +797,8 @@ Modifier `const` cannot be applied to property `{0}`.\n\ Property `{0}` cannot be declared const, because it overrides a non-const property on parent class `{1}`. unseparatedObjectMembers=\ -Object members must be separated by whitespace, newline, or semicolon. +Object members must be separated by whitespace, newline, or semicolon.\n\ +Object entries must be separated by newline or semicolon. unsupportedResourceType=\ Resource reader `{0}` returned unsupported resource type `{1}`. @@ -1126,3 +1127,58 @@ External {0} reader does not support scheme `{1}`. externalReaderAlreadyTerminated=\ External reader process has already terminated. + +keywordNotAllowedHere=\ +Keyword `{0}` is not allowed here.\n\ +\n\ +If you must use this name as identifier, enclose it in backticks. + +unexpectedEndOfFile=\ +Unexpected end of file. + +wrongHeaders=\ +{0} cannot have doc comments, annotations or modifiers. + +invalidTopLevelToken=\ +Invalid token at position. Expected a class, typealias, method, or property. + +interpolationInConstant=\ +String constant cannot have interpolated values. + +unexpectedToken=\ +Unexpected token `{0}`. Expected `{1}`. + +unexpectedToken2=\ +Unexpected token `{0}`. Expected `{1}` or `{2}`. + +unexpectedTokenForExpression=\ +Unexpected token `{0}`. + +unexpectedTokenForType=\ +Unexpected token `{0}`. Expected a type. + +unexpectedTokenForType2=\ +Unexpected token `{0}`. Expected a type or `{1}`. + +typeAnnotationInAmends=\ +Properties with type annotations cannot have object bodies.\n\ +\n\ +To define both a type annotation and an object body, try using assignment instead.\n\ +For example: `obj: Bird = new { ... }`. + +invalidProperty=\ +Invalid property definition. Expected a type annotation, `=` or `{`. + +unexpectedCharacter=\ +Unexpected character `{0}`. Did you mean `{1}`? + +unexpectedCharacter3=\ +Unexpected character `{0}`. Did you mean `{1}`, `{2}` or `{3}`? + +invalidCharacter=\ +Invalid identifier `{0}`. + +danglingDocComment=\ +Dangling documentation comment.\n\ +\n\ +Documentation comments must be attached to modules, classes, typealiases, methods, or properties. diff --git a/pkl-core/src/main/antlr/PklLexer.g4 b/pkl-core/src/test/antlr/PklLexer.g4 similarity index 99% rename from pkl-core/src/main/antlr/PklLexer.g4 rename to pkl-core/src/test/antlr/PklLexer.g4 index 8b084c80..517e8d4d 100644 --- a/pkl-core/src/main/antlr/PklLexer.g4 +++ b/pkl-core/src/test/antlr/PklLexer.g4 @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. diff --git a/pkl-core/src/main/antlr/PklParser.g4 b/pkl-core/src/test/antlr/PklParser.g4 similarity index 99% rename from pkl-core/src/main/antlr/PklParser.g4 rename to pkl-core/src/test/antlr/PklParser.g4 index b7791798..b3c624ec 100644 --- a/pkl-core/src/main/antlr/PklParser.g4 +++ b/pkl-core/src/test/antlr/PklParser.g4 @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/basic/amendsChains.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/basic/amendsChains.pkl index c2fbe98a..6927e08f 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/basic/amendsChains.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/basic/amendsChains.pkl @@ -29,5 +29,5 @@ baz = new Listing>> { inner.fold(_acc, (__acc, it) -> __acc.add(it)))) qux { - (foo.bar) { "world" "!" } { [0] = "Goodbye " [1] = "cruel " }.toList().join("") + (foo.bar) { "world" "!" } { [0] = "Goodbye "; [1] = "cruel " }.toList().join("") } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/basic/objectMember.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/basic/objectMember.pkl index e788f9a8..19b66b1a 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/basic/objectMember.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/basic/objectMember.pkl @@ -11,6 +11,6 @@ res6{1;2} res7{foo = 1} res8{foo = 1 bar = 2} res9{nested{1}} -res10{["foo"] = 1 ["bar"] = 2} +res10{["foo"] = 1; ["bar"] = 2} res11 { new {} new {} new {} } res12 {1.2;.3;.4;.5} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/delimiters/missingContainerAmendExprDelimiter.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/delimiters/missingContainerAmendExprDelimiter.pkl index 04ff7050..9081af1a 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/delimiters/missingContainerAmendExprDelimiter.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/delimiters/missingContainerAmendExprDelimiter.pkl @@ -1,2 +1,4 @@ foo = (bar) { x = 1 + +class Foo diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidClassMethodModifier.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidClassMethodModifier.pkl index 204f5426..320e3f5f 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidClassMethodModifier.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidClassMethodModifier.pkl @@ -2,4 +2,4 @@ class Foo { open function foo() = 42 } -res1 = Foo {} \ No newline at end of file +res1 = new Foo {} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidClassPropertyModifier.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidClassPropertyModifier.pkl index 4320c327..adf9bfe5 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidClassPropertyModifier.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidClassPropertyModifier.pkl @@ -2,4 +2,4 @@ class Foo { open foo: Int = 42 } -res1 = Foo {} \ No newline at end of file +res1 = new Foo {} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/generators/spreadSyntaxNoSpace.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/generators/spreadSyntaxNoSpace.pkl new file mode 100644 index 00000000..f9105b60 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/generators/spreadSyntaxNoSpace.pkl @@ -0,0 +1,3 @@ +foo { + 1 2 3...IntSeq(4, 10)...IntSeq(11, 20) +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/parser/constantStringInterpolation.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/parser/constantStringInterpolation.pkl new file mode 100644 index 00000000..30527fc5 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/parser/constantStringInterpolation.pkl @@ -0,0 +1,3 @@ +import "\(name).pkl" + +name = "Bird" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/parser/invalidCharacter.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/parser/invalidCharacter.pkl new file mode 100644 index 00000000..fb7ab4d6 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/parser/invalidCharacter.pkl @@ -0,0 +1 @@ +foo = 1 ^ 1 diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/parser/lineCommentBetween.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/parser/lineCommentBetween.pkl new file mode 100644 index 00000000..67999369 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/parser/lineCommentBetween.pkl @@ -0,0 +1,11 @@ +import "pkl:reflect" + +/// doc comment +// line comments can appear between doc comments +/* + block comments also +*/ +/// doc continuation +foo = 1 + +theComment = reflect.Class(module.getClass()).properties["foo"].docComment diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/parser/spacesBetweenDocComments.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/parser/spacesBetweenDocComments.pkl new file mode 100644 index 00000000..32b97cd8 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/parser/spacesBetweenDocComments.pkl @@ -0,0 +1,4 @@ +/// doc comment start + +/// doc comment continuation +foo = 1 diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/parser/typeAnnotationInAmends.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/parser/typeAnnotationInAmends.pkl new file mode 100644 index 00000000..c97165e2 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/parser/typeAnnotationInAmends.pkl @@ -0,0 +1,3 @@ +bird: Listing { + "Crow" +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/parser/wrongDocComment.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/parser/wrongDocComment.pkl new file mode 100644 index 00000000..978fbc51 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/parser/wrongDocComment.pkl @@ -0,0 +1,2 @@ +/// not a valid doc comment +import "../../input-helper/basic/read/module1.pkl" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/basic/objectMemberInvalid.err b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/objectMemberInvalid.err index 552de5c2..86a88102 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/basic/objectMemberInvalid.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/objectMemberInvalid.err @@ -1,5 +1,6 @@ –– Pkl Error –– Object members must be separated by whitespace, newline, or semicolon. +Object entries must be separated by newline or semicolon. x | 1.2.3 ^^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/basic/objectMemberInvalid2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/objectMemberInvalid2.err index e07485ab..6b6bce7f 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/basic/objectMemberInvalid2.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/objectMemberInvalid2.err @@ -1,5 +1,6 @@ –– Pkl Error –– Object members must be separated by whitespace, newline, or semicolon. +Object entries must be separated by newline or semicolon. x | res1 {foo=1bar=2} ^^^^^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/basic/objectMemberInvalid3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/objectMemberInvalid3.err index 7f834d26..cafa58be 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/basic/objectMemberInvalid3.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/objectMemberInvalid3.err @@ -1,5 +1,6 @@ –– Pkl Error –– Object members must be separated by whitespace, newline, or semicolon. +Object entries must be separated by newline or semicolon. x | }new{ ^^^^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/basic/stringError1.err b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/stringError1.err index 8b986a50..bd4bd1e3 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/basic/stringError1.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/stringError1.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Mismatched input: `are`. Expected one of: `{`, `=`, `:` +Missing `"` delimiter. -x | How are you today? - ^^^ +x | singleLineStringCannotSpanLines = " + ^ at stringError1 (file:///$snippetsDir/input/basic/stringError1.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingConstrainedTypeSeparator.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingConstrainedTypeSeparator.err index 4693c1c3..bc11ad63 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingConstrainedTypeSeparator.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingConstrainedTypeSeparator.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `,` separator. +Unexpected token `this`. Expected `,` or `)`. x | x: Int(isPositive this > 10) - ^ -at missingConstrainedTypeSeparator#x (file:///$snippetsDir/input/errors/delimiters/missingConstrainedTypeSeparator.pkl) + ^^^^ +at missingConstrainedTypeSeparator (file:///$snippetsDir/input/errors/delimiters/missingConstrainedTypeSeparator.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingContainerAmendDefDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingContainerAmendDefDelimiter.err index 0a5cfeae..02608f55 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingContainerAmendDefDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingContainerAmendDefDelimiter.err @@ -3,4 +3,4 @@ Missing `}` delimiter. x | x = 1 ^ -at missingContainerAmendDefDelimiter#foo (file:///$snippetsDir/input/errors/delimiters/missingContainerAmendDefDelimiter.pkl) +at missingContainerAmendDefDelimiter (file:///$snippetsDir/input/errors/delimiters/missingContainerAmendDefDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingContainerAmendExprDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingContainerAmendExprDelimiter.err index ce95fd9b..00558674 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingContainerAmendExprDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingContainerAmendExprDelimiter.err @@ -3,4 +3,4 @@ Missing `}` delimiter. x | x = 1 ^ -at missingContainerAmendExprDelimiter#foo (file:///$snippetsDir/input/errors/delimiters/missingContainerAmendExprDelimiter.pkl) +at missingContainerAmendExprDelimiter (file:///$snippetsDir/input/errors/delimiters/missingContainerAmendExprDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingEmptyMultiLineStringDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingEmptyMultiLineStringDelimiter.err index 536d5b52..9db05ae3 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingEmptyMultiLineStringDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingEmptyMultiLineStringDelimiter.err @@ -1,5 +1,5 @@ –– Pkl Error –– -Extraneous input: ``. Expected one of: MLEndQuote, MLInterpolation, MLUnicodeEscape, MLCharacterEscape, MLNewline, MLCharacters +Unexpected end of file. x | res2 = 42 ^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingEmptyMultiLineStringDelimiterAtEof.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingEmptyMultiLineStringDelimiterAtEof.err index 2475d6a8..a32a6541 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingEmptyMultiLineStringDelimiterAtEof.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingEmptyMultiLineStringDelimiterAtEof.err @@ -1,5 +1,5 @@ –– Pkl Error –– -Mismatched input: ``. Expected one of: MLEndQuote, MLInterpolation, MLUnicodeEscape, MLCharacterEscape, MLNewline, MLCharacters +Unexpected end of file. x | res1 = """ ^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingEmptyStringDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingEmptyStringDelimiter.err index ea5c89e1..31ecb36b 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingEmptyStringDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingEmptyStringDelimiter.err @@ -3,4 +3,4 @@ Missing `"` delimiter. x | res1 = " ^ -at missingEmptyStringDelimiter#res1 (file:///$snippetsDir/input/errors/delimiters/missingEmptyStringDelimiter.pkl) +at missingEmptyStringDelimiter (file:///$snippetsDir/input/errors/delimiters/missingEmptyStringDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingEmptyStringDelimiterAtEof.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingEmptyStringDelimiterAtEof.err index bf6597e5..4dc9cab2 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingEmptyStringDelimiterAtEof.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingEmptyStringDelimiterAtEof.err @@ -1,5 +1,5 @@ –– Pkl Error –– -Mismatched input: ``. Expected one of: SLEndQuote, SLInterpolation, SLUnicodeEscape, SLCharacterEscape, SLCharacters +Unexpected end of file. x | res1 = " ^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunction0ParameterListDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunction0ParameterListDelimiter.err index 4ac9c205..db759018 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunction0ParameterListDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunction0ParameterListDelimiter.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `)` delimiter. +Unexpected token `->`. Expected `)`. x | f = ( -> 42 - ^ -at missingFunction0ParameterListDelimiter#f (file:///$snippetsDir/input/errors/delimiters/missingFunction0ParameterListDelimiter.pkl) + ^^ +at missingFunction0ParameterListDelimiter (file:///$snippetsDir/input/errors/delimiters/missingFunction0ParameterListDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunction1ParameterListDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunction1ParameterListDelimiter.err index ca44b397..504ab47a 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunction1ParameterListDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunction1ParameterListDelimiter.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `)` delimiter. +Unexpected token `->`. Expected `)`. x | f = (arg -> arg + 1 - ^ -at missingFunction1ParameterListDelimiter#f (file:///$snippetsDir/input/errors/delimiters/missingFunction1ParameterListDelimiter.pkl) + ^^ +at missingFunction1ParameterListDelimiter (file:///$snippetsDir/input/errors/delimiters/missingFunction1ParameterListDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionAmendParameterListSeparator.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionAmendParameterListSeparator.err index 803db7b0..b80980cd 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionAmendParameterListSeparator.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionAmendParameterListSeparator.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `,` separator. +Unexpected token `arg3`. Expected `,` or `->`. x | f { arg1, arg2 arg3 -> - ^ -at missingFunctionAmendParameterListSeparator#f (file:///$snippetsDir/input/errors/delimiters/missingFunctionAmendParameterListSeparator.pkl) + ^^^^ +at missingFunctionAmendParameterListSeparator (file:///$snippetsDir/input/errors/delimiters/missingFunctionAmendParameterListSeparator.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionNParameterListDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionNParameterListDelimiter.err index ca4adb16..9a34a9cd 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionNParameterListDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionNParameterListDelimiter.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `)` delimiter. +Unexpected token `->`. Expected `,` or `)`. x | f = (arg1, arg2, arg3 -> arg1 + arg2 + arg3 - ^ -at missingFunctionNParameterListDelimiter#f (file:///$snippetsDir/input/errors/delimiters/missingFunctionNParameterListDelimiter.pkl) + ^^ +at missingFunctionNParameterListDelimiter (file:///$snippetsDir/input/errors/delimiters/missingFunctionNParameterListDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionParameterListSeparator.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionParameterListSeparator.err index 1f3e4eba..eee269d1 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionParameterListSeparator.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionParameterListSeparator.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `,` separator. +Unexpected token `arg3`. Expected `,` or `)`. x | f = (arg1, arg2 arg3) -> arg1 + arg2 + arg3 - ^ -at missingFunctionParameterListSeparator#f (file:///$snippetsDir/input/errors/delimiters/missingFunctionParameterListSeparator.pkl) + ^^^^ +at missingFunctionParameterListSeparator (file:///$snippetsDir/input/errors/delimiters/missingFunctionParameterListSeparator.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionType0ParameterListDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionType0ParameterListDelimiter.err index f6443cd8..b26e6f7c 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionType0ParameterListDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionType0ParameterListDelimiter.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `)` delimiter. +Unexpected token `->`. Expected a type or `)`. x | f: ( -> String - ^ -at missingFunctionType0ParameterListDelimiter#f (file:///$snippetsDir/input/errors/delimiters/missingFunctionType0ParameterListDelimiter.pkl) + ^^ +at missingFunctionType0ParameterListDelimiter (file:///$snippetsDir/input/errors/delimiters/missingFunctionType0ParameterListDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionType1ParameterListDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionType1ParameterListDelimiter.err index 0716c15f..1a49276f 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionType1ParameterListDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionType1ParameterListDelimiter.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `)` delimiter. +Unexpected token `->`. Expected `,` or `)`. x | f: (String -> String - ^ -at missingFunctionType1ParameterListDelimiter#f (file:///$snippetsDir/input/errors/delimiters/missingFunctionType1ParameterListDelimiter.pkl) + ^^ +at missingFunctionType1ParameterListDelimiter (file:///$snippetsDir/input/errors/delimiters/missingFunctionType1ParameterListDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionTypeNParameterListDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionTypeNParameterListDelimiter.err index 26a14e03..3fc8a244 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionTypeNParameterListDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionTypeNParameterListDelimiter.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `)` delimiter. +Unexpected token `->`. Expected `,` or `)`. x | f: (String, Int, String -> String - ^ -at missingFunctionTypeNParameterListDelimiter#f (file:///$snippetsDir/input/errors/delimiters/missingFunctionTypeNParameterListDelimiter.pkl) + ^^ +at missingFunctionTypeNParameterListDelimiter (file:///$snippetsDir/input/errors/delimiters/missingFunctionTypeNParameterListDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionTypeParameterListSeparator.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionTypeParameterListSeparator.err index fad93ab2..70b8a68b 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionTypeParameterListSeparator.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingFunctionTypeParameterListSeparator.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `,` separator. +Unexpected token `String`. Expected `,` or `)`. x | f: (String, Int String) -> String - ^ -at missingFunctionTypeParameterListSeparator#f (file:///$snippetsDir/input/errors/delimiters/missingFunctionTypeParameterListSeparator.pkl) + ^^^^^^ +at missingFunctionTypeParameterListSeparator (file:///$snippetsDir/input/errors/delimiters/missingFunctionTypeParameterListSeparator.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingIfExprDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingIfExprDelimiter.err index d545e83c..00ec6b8c 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingIfExprDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingIfExprDelimiter.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `)` delimiter. +Unexpected token `3`. Expected `)`. x | res1 = if (cond 3 else 4 - ^ -at missingIfExprDelimiter#res1 (file:///$snippetsDir/input/errors/delimiters/missingIfExprDelimiter.pkl) + ^ +at missingIfExprDelimiter (file:///$snippetsDir/input/errors/delimiters/missingIfExprDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingListDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingListDelimiter.err index 4213e059..6535aed6 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingListDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingListDelimiter.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `)` delimiter. +Unexpected token `EOF`. Expected `,` or `)`. x | l = List("one", "two" ^ -at missingListDelimiter#l (file:///$snippetsDir/input/errors/delimiters/missingListDelimiter.pkl) +at missingListDelimiter (file:///$snippetsDir/input/errors/delimiters/missingListDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingListSeparator.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingListSeparator.err index 75b53067..eb4bd3c1 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingListSeparator.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingListSeparator.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `,` separator. +Unexpected token `3`. Expected `,` or `)`. x | list = List(1, 2 3, 4) - ^ -at missingListSeparator#list (file:///$snippetsDir/input/errors/delimiters/missingListSeparator.pkl) + ^ +at missingListSeparator (file:///$snippetsDir/input/errors/delimiters/missingListSeparator.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMapDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMapDelimiter.err index b623b130..850e5572 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMapDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMapDelimiter.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `)` delimiter. +Unexpected token `EOF`. Expected `,` or `)`. x | map = Map("one", 1, "two", 2 ^ -at missingMapDelimiter#map (file:///$snippetsDir/input/errors/delimiters/missingMapDelimiter.pkl) +at missingMapDelimiter (file:///$snippetsDir/input/errors/delimiters/missingMapDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMapSeparator.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMapSeparator.err index cf9e13e3..489fecd4 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMapSeparator.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMapSeparator.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `,` separator. +Unexpected token `4`. Expected `,` or `)`. x | map = Map(1, 2, 3 4, 5, 6) - ^ -at missingMapSeparator#map (file:///$snippetsDir/input/errors/delimiters/missingMapSeparator.pkl) + ^ +at missingMapSeparator (file:///$snippetsDir/input/errors/delimiters/missingMapSeparator.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMethodArgumentListDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMethodArgumentListDelimiter.err index 36b818c1..65eee2f7 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMethodArgumentListDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMethodArgumentListDelimiter.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `)` delimiter. +Unexpected token `res2`. Expected `,` or `)`. x | res1 = "abc".substring(0, 1 + "def" ^ -at missingMethodArgumentListDelimiter#res1 (file:///$snippetsDir/input/errors/delimiters/missingMethodArgumentListDelimiter.pkl) +at missingMethodArgumentListDelimiter (file:///$snippetsDir/input/errors/delimiters/missingMethodArgumentListDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMethodArgumentListSeparator.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMethodArgumentListSeparator.err index cd8946db..a7810e4a 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMethodArgumentListSeparator.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMethodArgumentListSeparator.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `,` separator. +Unexpected token `3`. Expected `,` or `)`. x | res1 = foo(1, 2 3) - ^ -at missingMethodArgumentListSeparator#res1 (file:///$snippetsDir/input/errors/delimiters/missingMethodArgumentListSeparator.pkl) + ^ +at missingMethodArgumentListSeparator (file:///$snippetsDir/input/errors/delimiters/missingMethodArgumentListSeparator.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMethodParameterListDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMethodParameterListDelimiter.err index 8a15e2d6..74aab8de 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMethodParameterListDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMethodParameterListDelimiter.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `)` delimiter. +Unexpected token `=`. Expected `,` or `)`. x | function foo(arg1, arg2 = arg1 + arg2 - ^ + ^ at missingMethodParameterListDelimiter (file:///$snippetsDir/input/errors/delimiters/missingMethodParameterListDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMethodParameterListSeparator.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMethodParameterListSeparator.err index 6b630c03..cc4ec799 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMethodParameterListSeparator.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMethodParameterListSeparator.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `,` separator. +Unexpected token `arg2`. Expected `,` or `)`. x | function foo(arg1 arg2) = arg1 + arg2 - ^ + ^^^^ at missingMethodParameterListSeparator (file:///$snippetsDir/input/errors/delimiters/missingMethodParameterListSeparator.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMultiLineStringDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMultiLineStringDelimiter.err index 5b412183..93d32f3f 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMultiLineStringDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingMultiLineStringDelimiter.err @@ -1,5 +1,5 @@ –– Pkl Error –– -Extraneous input: ``. Expected one of: MLEndQuote, MLInterpolation, MLUnicodeEscape, MLCharacterEscape, MLNewline, MLCharacters +Unexpected end of file. x | ^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectAmendDefDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectAmendDefDelimiter.err index caa64853..d7a9ac85 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectAmendDefDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectAmendDefDelimiter.err @@ -3,4 +3,4 @@ Missing `}` delimiter. x | x = 1 ^ -at missingObjectAmendDefDelimiter#foo (file:///$snippetsDir/input/errors/delimiters/missingObjectAmendDefDelimiter.pkl) +at missingObjectAmendDefDelimiter (file:///$snippetsDir/input/errors/delimiters/missingObjectAmendDefDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectAmendDefDelimiter2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectAmendDefDelimiter2.err index 34f25075..da2a1165 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectAmendDefDelimiter2.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectAmendDefDelimiter2.err @@ -3,4 +3,4 @@ Missing `}` delimiter. x | } ^ -at missingObjectAmendDefDelimiter2#foo (file:///$snippetsDir/input/errors/delimiters/missingObjectAmendDefDelimiter2.pkl) +at missingObjectAmendDefDelimiter2 (file:///$snippetsDir/input/errors/delimiters/missingObjectAmendDefDelimiter2.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectAmendDefDelimiter3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectAmendDefDelimiter3.err index 702accee..73d3b038 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectAmendDefDelimiter3.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectAmendDefDelimiter3.err @@ -3,4 +3,4 @@ Missing `}` delimiter. x | } ^ -at missingObjectAmendDefDelimiter3#foo (file:///$snippetsDir/input/errors/delimiters/missingObjectAmendDefDelimiter3.pkl) +at missingObjectAmendDefDelimiter3 (file:///$snippetsDir/input/errors/delimiters/missingObjectAmendDefDelimiter3.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectAmendExprDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectAmendExprDelimiter.err index 0376aece..7e8700e2 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectAmendExprDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectAmendExprDelimiter.err @@ -3,4 +3,4 @@ Missing `}` delimiter. x | x = 1 ^ -at missingObjectAmendExprDelimiter#foo (file:///$snippetsDir/input/errors/delimiters/missingObjectAmendExprDelimiter.pkl) +at missingObjectAmendExprDelimiter (file:///$snippetsDir/input/errors/delimiters/missingObjectAmendExprDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectDelimiter.err index a0e61a90..3a836c49 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingObjectDelimiter.err @@ -3,4 +3,4 @@ Missing `}` delimiter. x | x = 1 ^ -at missingObjectDelimiter#foo (file:///$snippetsDir/input/errors/delimiters/missingObjectDelimiter.pkl) +at missingObjectDelimiter (file:///$snippetsDir/input/errors/delimiters/missingObjectDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingParenthesizedExprDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingParenthesizedExprDelimiter.err index 65a6f648..f91c3c44 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingParenthesizedExprDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingParenthesizedExprDelimiter.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `)` delimiter. +Unexpected token `y`. Expected `)`. x | x = 3 * (1 + 2 ^ -at missingParenthesizedExprDelimiter#x (file:///$snippetsDir/input/errors/delimiters/missingParenthesizedExprDelimiter.pkl) +at missingParenthesizedExprDelimiter (file:///$snippetsDir/input/errors/delimiters/missingParenthesizedExprDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingParenthesizedTypeDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingParenthesizedTypeDelimiter.err index 009c8ff7..fb5652bf 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingParenthesizedTypeDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingParenthesizedTypeDelimiter.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `)` delimiter. +Unexpected token `res2`. Expected `,` or `)`. x | res1: ((String) -> String | List ^ -at missingParenthesizedTypeDelimiter#res1 (file:///$snippetsDir/input/errors/delimiters/missingParenthesizedTypeDelimiter.pkl) +at missingParenthesizedTypeDelimiter (file:///$snippetsDir/input/errors/delimiters/missingParenthesizedTypeDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingRawMultiLineStringDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingRawMultiLineStringDelimiter.err index 5f7cf6f3..14116e67 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingRawMultiLineStringDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingRawMultiLineStringDelimiter.err @@ -1,5 +1,5 @@ –– Pkl Error –– -Extraneous input: ``. Expected one of: MLEndQuote, MLInterpolation, MLUnicodeEscape, MLCharacterEscape, MLNewline, MLCharacters +Unexpected end of file. x | ^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingRawStringDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingRawStringDelimiter.err index 0d0c9401..8de228ae 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingRawStringDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingRawStringDelimiter.err @@ -3,4 +3,4 @@ Missing `"#` delimiter. x | res1 = #"abc ^ -at missingRawStringDelimiter#res1 (file:///$snippetsDir/input/errors/delimiters/missingRawStringDelimiter.pkl) +at missingRawStringDelimiter (file:///$snippetsDir/input/errors/delimiters/missingRawStringDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingSetSeparator.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingSetSeparator.err index 146ce51e..9faf08ca 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingSetSeparator.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingSetSeparator.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `,` separator. +Unexpected token `3`. Expected `,` or `)`. x | set = Set(1, 2 3, 4) - ^ -at missingSetSeparator#set (file:///$snippetsDir/input/errors/delimiters/missingSetSeparator.pkl) + ^ +at missingSetSeparator (file:///$snippetsDir/input/errors/delimiters/missingSetSeparator.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingStringDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingStringDelimiter.err index df6217ae..888028df 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingStringDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingStringDelimiter.err @@ -3,4 +3,4 @@ Missing `"` delimiter. x | res1 = "abc ^ -at missingStringDelimiter#res1 (file:///$snippetsDir/input/errors/delimiters/missingStringDelimiter.pkl) +at missingStringDelimiter (file:///$snippetsDir/input/errors/delimiters/missingStringDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingSubscriptDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingSubscriptDelimiter.err index 4393c579..4153252f 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingSubscriptDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingSubscriptDelimiter.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `]` delimiter. +Unexpected token `EOF`. Expected `]`. x | res1 = x[3 + 4 ^ -at missingSubscriptDelimiter#res1 (file:///$snippetsDir/input/errors/delimiters/missingSubscriptDelimiter.pkl) +at missingSubscriptDelimiter (file:///$snippetsDir/input/errors/delimiters/missingSubscriptDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingTypeConstraintListDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingTypeConstraintListDelimiter.err index 363d3e88..3a4b3604 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingTypeConstraintListDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingTypeConstraintListDelimiter.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `)` delimiter. +Unexpected token `=`. Expected `,` or `)`. x | res1: String(!isEmpty, length < 5 = "abc" - ^ -at missingTypeConstraintListDelimiter#res1 (file:///$snippetsDir/input/errors/delimiters/missingTypeConstraintListDelimiter.pkl) + ^ +at missingTypeConstraintListDelimiter (file:///$snippetsDir/input/errors/delimiters/missingTypeConstraintListDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingTypedMethodParameterListDelimiter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingTypedMethodParameterListDelimiter.err index 4b1297b5..9104bd63 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingTypedMethodParameterListDelimiter.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingTypedMethodParameterListDelimiter.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `)` delimiter. +Unexpected token `=`. Expected `,` or `)`. x | function foo(arg1: String, arg2: String = arg1 + arg2 - ^ + ^ at missingTypedMethodParameterListDelimiter (file:///$snippetsDir/input/errors/delimiters/missingTypedMethodParameterListDelimiter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingTypedMethodParameterListSeparator.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingTypedMethodParameterListSeparator.err index e625f1cf..00a66231 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingTypedMethodParameterListSeparator.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/missingTypedMethodParameterListSeparator.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `,` separator. +Unexpected token `arg2`. Expected `,` or `)`. x | function foo(arg1: String arg2: String) = arg1 + arg2 - ^ + ^^^^ at missingTypedMethodParameterListSeparator (file:///$snippetsDir/input/errors/delimiters/missingTypedMethodParameterListSeparator.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/unbalancedEntryBrackets1.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/unbalancedEntryBrackets1.err index fa738969..a70bb02c 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/unbalancedEntryBrackets1.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/unbalancedEntryBrackets1.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Expected delimiter `]]`, but got `]`. +Unexpected token `{`. Expected `]]`. x | [[name == "Pigeon"] { age = 42 } ^ -at unbalancedEntryBrackets1#res (file:///$snippetsDir/input/errors/delimiters/unbalancedEntryBrackets1.pkl) +at unbalancedEntryBrackets1 (file:///$snippetsDir/input/errors/delimiters/unbalancedEntryBrackets1.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/unbalancedEntryBrackets2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/unbalancedEntryBrackets2.err index e067143d..4f437b13 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/unbalancedEntryBrackets2.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/unbalancedEntryBrackets2.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Expected delimiter `]`, but got `]]`. +Unexpected token `]`. Expected `{` or `=`. x | [name == "Pigeon"]] { age = 42 } - ^ -at unbalancedEntryBrackets2#res (file:///$snippetsDir/input/errors/delimiters/unbalancedEntryBrackets2.pkl) + ^ +at unbalancedEntryBrackets2 (file:///$snippetsDir/input/errors/delimiters/unbalancedEntryBrackets2.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/unbalancedEntryBrackets3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/unbalancedEntryBrackets3.err index e1d598fa..c308ab06 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/unbalancedEntryBrackets3.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/unbalancedEntryBrackets3.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Expected delimiter `]]`, but got `]`. +Unexpected token `] ]`. Expected `]]`. x | [[name == "Pigeon"] ] { age = 42 } ^ -at unbalancedEntryBrackets3#res (file:///$snippetsDir/input/errors/delimiters/unbalancedEntryBrackets3.pkl) +at unbalancedEntryBrackets3 (file:///$snippetsDir/input/errors/delimiters/unbalancedEntryBrackets3.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/unbalancedEntryBrackets4.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/unbalancedEntryBrackets4.err index 52b61bd3..ae2f2630 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/unbalancedEntryBrackets4.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/delimiters/unbalancedEntryBrackets4.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Unmatched delimiter `]`. +Unexpected token `]`. Expected `{` or `=`. x | [name == "Pigeon"] ] { age = 42 } ^ -at unbalancedEntryBrackets4#res (file:///$snippetsDir/input/errors/delimiters/unbalancedEntryBrackets4.pkl) +at unbalancedEntryBrackets4 (file:///$snippetsDir/input/errors/delimiters/unbalancedEntryBrackets4.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidCharacterEscape.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidCharacterEscape.err index 356b53f2..076e2c25 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidCharacterEscape.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidCharacterEscape.err @@ -1,8 +1,8 @@ –– Pkl Error –– Invalid character escape sequence `\a`. +Valid character escape sequences are: \n \r \t \" \\ + x | res1 = "xxx\axxx" ^^ -at invalidCharacterEscape#res1 (file:///$snippetsDir/input/errors/invalidCharacterEscape.pkl) - -Valid character escape sequences are: \n \r \t \" \\ +at invalidCharacterEscape (file:///$snippetsDir/input/errors/invalidCharacterEscape.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/keywordNotAllowedHere1.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/keywordNotAllowedHere1.err index 760dcf03..aaa346a0 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/keywordNotAllowedHere1.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/keywordNotAllowedHere1.err @@ -1,5 +1,7 @@ –– Pkl Error –– -Keyword `outer` is not allowed here. (If you must use this name as identifier, enclose it in backticks.) +Keyword `outer` is not allowed here. + +If you must use this name as identifier, enclose it in backticks. x | outer = 42 ^^^^^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/keywordNotAllowedHere2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/keywordNotAllowedHere2.err index fd0371bf..9ae7cb91 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/keywordNotAllowedHere2.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/keywordNotAllowedHere2.err @@ -1,5 +1,7 @@ –– Pkl Error –– -Keyword `outer` is not allowed here. (If you must use this name as identifier, enclose it in backticks.) +Keyword `outer` is not allowed here. + +If you must use this name as identifier, enclose it in backticks. x | function outer() = 42 ^^^^^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/keywordNotAllowedHere3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/keywordNotAllowedHere3.err index 05fff4d3..f1fe87b4 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/keywordNotAllowedHere3.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/keywordNotAllowedHere3.err @@ -1,5 +1,7 @@ –– Pkl Error –– -Keyword `outer` is not allowed here. (If you must use this name as identifier, enclose it in backticks.) +Keyword `outer` is not allowed here. + +If you must use this name as identifier, enclose it in backticks. x | res1 = outer.outer ^^^^^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/keywordNotAllowedHere4.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/keywordNotAllowedHere4.err index 8561b5fc..12d4e060 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/keywordNotAllowedHere4.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/keywordNotAllowedHere4.err @@ -1,5 +1,7 @@ –– Pkl Error –– -Keyword `record` is not allowed here. (If you must use this name as identifier, enclose it in backticks.) +Keyword `record` is not allowed here. + +If you must use this name as identifier, enclose it in backticks. x | record = 5 ^^^^^^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/moduleAmendsSelf.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/moduleAmendsSelf.err index fe502db9..5f934ba7 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/moduleAmendsSelf.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/moduleAmendsSelf.err @@ -2,5 +2,5 @@ Module `moduleAmendsSelf` cannot amend itself. x | amends "moduleAmendsSelf.pkl" - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at moduleAmendsSelf (file:///$snippetsDir/input/errors/moduleAmendsSelf.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/multipleDefaults.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/multipleDefaults.err index d92b022a..74307624 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/multipleDefaults.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/multipleDefaults.err @@ -2,5 +2,5 @@ A type union cannot have more than one default type. x | foo: *Int|*String|*"foo" - ^^^^^^^^^^^^^^^^^^^ -at multipleDefaults#foo (file:///$snippetsDir/input/errors/multipleDefaults.pkl) + ^ +at multipleDefaults (file:///$snippetsDir/input/errors/multipleDefaults.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/nested1.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/nested1.err index f222135f..133bd5d1 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/nested1.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/nested1.err @@ -1,5 +1,7 @@ –– Pkl Error –– -Keyword `outer` is not allowed here. (If you must use this name as identifier, enclose it in backticks.) +Keyword `outer` is not allowed here. + +If you must use this name as identifier, enclose it in backticks. x | x = outer.outer // nested syntax error ^^^^^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/notAUnionDefault.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/notAUnionDefault.err index 3541df8a..cde036dc 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/notAUnionDefault.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/notAUnionDefault.err @@ -3,4 +3,4 @@ Only type unions can have a default marker (*). x | foo: *"foo" ^^^^^^ -at notAUnionDefault#foo (file:///$snippetsDir/input/errors/notAUnionDefault.pkl) +at notAUnionDefault (file:///$snippetsDir/input/errors/notAUnionDefault.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser1.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser1.err index 16e81081..3896513d 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser1.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser1.err @@ -1,5 +1,5 @@ –– Pkl Error –– -Token recognition error at: `#` +Unexpected end of file. x | res1 = 9# ^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser10.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser10.err index 866ed722..01311296 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser10.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser10.err @@ -1,5 +1,5 @@ –– Pkl Error –– -Mismatched input: `[`. Expected one of: `module`, `nothing`, `unknown`, `(`, `{`, `*`, SLQuote, Identifier +Unexpected token `[`. Expected a type or `{`. x | res1 = new [ 42 } ^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser11.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser11.err index 13608c44..be1ede9e 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser11.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser11.err @@ -1,5 +1,5 @@ –– Pkl Error –– -No viable alternative at input `foo = 42 ]`. +Unexpected token `]`. Expected `}`. x | res1 = new { foo = 42 ] ^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser12.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser12.err index f5a92304..8334c41a 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser12.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser12.err @@ -1,5 +1,5 @@ –– Pkl Error –– -Extraneous input: `}`. Expected one of: , `abstract`, `class`, `const`, `external`, `fixed`, `function`, `hidden`, `local`, `open`, `typealias`, `@`, Identifier, DocComment +Invalid token at position. Expected a class, typealias, method, or property. x | res1 = new { foo = 42 }} ^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser14.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser14.err index f02a77dd..21799480 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser14.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser14.err @@ -1,5 +1,5 @@ –– Pkl Error –– -No viable alternative at input `///[1,2,3]\n`. +Unexpected end of file. x | ^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser15.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser15.err index d9ce6f9e..7f9029bf 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser15.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser15.err @@ -1,8 +1,8 @@ –– Pkl Error –– Unexpected separator character. +The separator character (`_`) cannot follow `0x`, `0b`, `.`, `e`, or 'E' in a number literal. + x | res = 100e_10 ^ -at parser15#res (file:///$snippetsDir/input/errors/parser15.pkl) - -The separator character (`_`) cannot follow `0x`, `0b`, `.`, `e`, or 'E' in a number literal. +at parser15 (file:///$snippetsDir/input/errors/parser15.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser16.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser16.err index cd8f26e2..443ebb6d 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser16.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser16.err @@ -1,8 +1,8 @@ –– Pkl Error –– Unexpected separator character. +The separator character (`_`) cannot follow `0x`, `0b`, `.`, `e`, or 'E' in a number literal. + x | res = 0x_01 ^ -at parser16#res (file:///$snippetsDir/input/errors/parser16.pkl) - -The separator character (`_`) cannot follow `0x`, `0b`, `.`, `e`, or 'E' in a number literal. +at parser16 (file:///$snippetsDir/input/errors/parser16.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser17.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser17.err index 915a62d9..27e3d658 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser17.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser17.err @@ -1,8 +1,8 @@ –– Pkl Error –– Unexpected separator character. +The separator character (`_`) cannot follow `0x`, `0b`, `.`, `e`, or 'E' in a number literal. + x | res = 0b_01 ^ -at parser17#res (file:///$snippetsDir/input/errors/parser17.pkl) - -The separator character (`_`) cannot follow `0x`, `0b`, `.`, `e`, or 'E' in a number literal. +at parser17 (file:///$snippetsDir/input/errors/parser17.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser18.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser18.err index 794ee166..57289c5e 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser18.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser18.err @@ -1,8 +1,8 @@ –– Pkl Error –– Unexpected separator character. +The separator character (`_`) cannot follow `0x`, `0b`, `.`, `e`, or 'E' in a number literal. + x | res = 100._01 ^ -at parser18#res (file:///$snippetsDir/input/errors/parser18.pkl) - -The separator character (`_`) cannot follow `0x`, `0b`, `.`, `e`, or 'E' in a number literal. +at parser18 (file:///$snippetsDir/input/errors/parser18.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser2.err index 4a358224..73ab14d7 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser2.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser2.err @@ -3,4 +3,4 @@ Missing `"` delimiter. x | res1 = "some string ^ -at parser2#res1 (file:///$snippetsDir/input/errors/parser2.pkl) +at parser2 (file:///$snippetsDir/input/errors/parser2.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser3.err index 0635c3b3..550c19c5 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser3.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser3.err @@ -1,5 +1,5 @@ –– Pkl Error –– -Extraneous input: ``. Expected one of: MLEndQuote, MLInterpolation, MLUnicodeEscape, MLCharacterEscape, MLNewline, MLCharacters +Unexpected end of file. x | res1 = """some string ^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser4.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser4.err index 616fe128..6e9fc103 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser4.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser4.err @@ -1,5 +1,5 @@ –– Pkl Error –– -Extraneous input: ``. Expected one of: MLEndQuote, MLInterpolation, MLUnicodeEscape, MLCharacterEscape, MLNewline, MLCharacters +Unexpected end of file. x | res2 = 2 ^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser5.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser5.err index b7b67a2f..841f04ff 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser5.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser5.err @@ -1,5 +1,5 @@ –– Pkl Error –– -Extraneous input: `"`. Expected one of: , `abstract`, `class`, `const`, `external`, `fixed`, `function`, `hidden`, `local`, `open`, `typealias`, `@`, Identifier, DocComment +Invalid token at position. Expected a class, typealias, method, or property. x | res1 = "some string""" ^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser6.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser6.err index 542d7913..05d9441e 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser6.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser6.err @@ -1,5 +1,5 @@ –– Pkl Error –– -Extraneous input: ``. Expected one of: MLEndQuote, MLInterpolation, MLUnicodeEscape, MLCharacterEscape, MLNewline, MLCharacters +Unexpected end of file. x | res1 = """some string" ^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser7.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser7.err index 742378fd..52b46241 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser7.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser7.err @@ -1,5 +1,5 @@ –– Pkl Error –– -No viable alternative at input `@11`. +Unexpected token `11`. Expected a type. x | res1 = a@11 ^^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/underscore.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/underscore.err index 717b9d5d..7cdc20f6 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/underscore.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/underscore.err @@ -1,5 +1,7 @@ –– Pkl Error –– -Keyword `_` is not allowed here. (If you must use this name as identifier, enclose it in backticks.) +Keyword `_` is not allowed here. + +If you must use this name as identifier, enclose it in backticks. x | _ = 0 ^ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/unterminatedUnicodeEscape.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/unterminatedUnicodeEscape.err index ea8844ce..754169e9 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/unterminatedUnicodeEscape.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/unterminatedUnicodeEscape.err @@ -1,8 +1,8 @@ –– Pkl Error –– Unterminated Unicode escape sequence `\u{123`. +Unicode escape sequences must end with `}`. + x | res1 = "foo \u{123 bar" ^^^^^^ -at unterminatedUnicodeEscape#res1 (file:///$snippetsDir/input/errors/unterminatedUnicodeEscape.pkl) - -Unicode escape sequences must end with `}`. +at unterminatedUnicodeEscape (file:///$snippetsDir/input/errors/unterminatedUnicodeEscape.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/generators/spreadSyntaxNoSpace.err b/pkl-core/src/test/files/LanguageSnippetTests/output/generators/spreadSyntaxNoSpace.err new file mode 100644 index 00000000..eabd5e78 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/generators/spreadSyntaxNoSpace.err @@ -0,0 +1,7 @@ +–– Pkl Error –– +Object members must be separated by whitespace, newline, or semicolon. +Object entries must be separated by newline or semicolon. + +x | 1 2 3...IntSeq(4, 10)...IntSeq(11, 20) + ^^^^^^^^^^^^^^^^ +at spreadSyntaxNoSpace#foo (file:///$snippetsDir/input/generators/spreadSyntaxNoSpace.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/parser/amendsRequiresParens.err b/pkl-core/src/test/files/LanguageSnippetTests/output/parser/amendsRequiresParens.err index 20660f2f..5c5ffa90 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/parser/amendsRequiresParens.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/parser/amendsRequiresParens.err @@ -1,8 +1,8 @@ –– Pkl Error –– Unexpected token: `{`. +If you meant to write an amends expression, wrap the parent in parentheses. Try: `(bar) { ... }` + x | (bar { "baz" }) ^ -at amendsRequiresParens#foo[#1] (file:///$snippetsDir/input/parser/amendsRequiresParens.pkl) - -If you meant to write an amends expression, wrap the parent in parentheses. Try: `(bar) { ... }` +at amendsRequiresParens (file:///$snippetsDir/input/parser/amendsRequiresParens.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/parser/constantStringInterpolation.err b/pkl-core/src/test/files/LanguageSnippetTests/output/parser/constantStringInterpolation.err new file mode 100644 index 00000000..552a4cf8 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/parser/constantStringInterpolation.err @@ -0,0 +1,6 @@ +–– Pkl Error –– +String constant cannot have interpolated values. + +x | import "\(name).pkl" + ^^ +at constantStringInterpolation (file:///$snippetsDir/input/parser/constantStringInterpolation.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/parser/invalidCharacter.err b/pkl-core/src/test/files/LanguageSnippetTests/output/parser/invalidCharacter.err new file mode 100644 index 00000000..5c459a18 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/parser/invalidCharacter.err @@ -0,0 +1,6 @@ +–– Pkl Error –– +Invalid identifier `^`. + +x | foo = 1 ^ 1 + ^ +at invalidCharacter (file:///$snippetsDir/input/parser/invalidCharacter.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/parser/lineCommentBetween.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/parser/lineCommentBetween.pcf new file mode 100644 index 00000000..4f9e315e --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/parser/lineCommentBetween.pcf @@ -0,0 +1,5 @@ +foo = 1 +theComment = """ + doc comment + doc continuation + """ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/parser/spacesBetweenDocComments.err b/pkl-core/src/test/files/LanguageSnippetTests/output/parser/spacesBetweenDocComments.err new file mode 100644 index 00000000..d0d487fc --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/parser/spacesBetweenDocComments.err @@ -0,0 +1,8 @@ +–– Pkl Error –– +Dangling documentation comment. + +Documentation comments must be attached to modules, classes, typealiases, methods, or properties. + +x | /// doc comment continuation + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at spacesBetweenDocComments (file:///$snippetsDir/input/parser/spacesBetweenDocComments.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/parser/typeAnnotationInAmends.err b/pkl-core/src/test/files/LanguageSnippetTests/output/parser/typeAnnotationInAmends.err new file mode 100644 index 00000000..361f06f5 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/parser/typeAnnotationInAmends.err @@ -0,0 +1,9 @@ +–– Pkl Error –– +Properties with type annotations cannot have object bodies. + +To define both a type annotation and an object body, try using assignment instead. +For example: `obj: Bird = new { ... }`. + +x | bird: Listing { + ^ +at typeAnnotationInAmends (file:///$snippetsDir/input/parser/typeAnnotationInAmends.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/parser/wrongDocComment.err b/pkl-core/src/test/files/LanguageSnippetTests/output/parser/wrongDocComment.err new file mode 100644 index 00000000..804a6a54 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/parser/wrongDocComment.err @@ -0,0 +1,6 @@ +–– Pkl Error –– +Imports cannot have doc comments, annotations or modifiers. + +x | import "../../input-helper/basic/read/module1.pkl" + ^^^^^^ +at wrongDocComment (file:///$snippetsDir/input/parser/wrongDocComment.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/projects/badPklProject1/bug.err b/pkl-core/src/test/files/LanguageSnippetTests/output/projects/badPklProject1/bug.err index 8dc07f5c..f3e580d3 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/projects/badPklProject1/bug.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/projects/badPklProject1/bug.err @@ -2,7 +2,7 @@ Expected `output.value` of module `file:///$snippetsDir/input/projects/badPklProject1/PklProject` to be of type `pkl.Project`, but got type `invalid.project.Module`. x | module invalid.project.Module - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ^^^^^^^^^^^^^^^^^^^^^^ at invalid.project.Module (file:///$snippetsDir/input/projects/badPklProject1/PklProject) Try adding `amends "pkl:Project"` to the module header. diff --git a/pkl-core/src/test/kotlin/org/pkl/core/EvaluateExpressionTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/EvaluateExpressionTest.kt index a13013ae..1e9dbd7b 100644 --- a/pkl-core/src/test/kotlin/org/pkl/core/EvaluateExpressionTest.kt +++ b/pkl-core/src/test/kotlin/org/pkl/core/EvaluateExpressionTest.kt @@ -116,7 +116,7 @@ class EvaluateExpressionTest { fun `evaluate expression with invalid syntax`() { val error = assertThrows { evaluate("foo = 1", "<>!!!") } - assertThat(error).hasMessageContaining("Mismatched input") + assertThat(error).hasMessageContaining("Unexpected token") assertThat(error).hasMessageContaining("<>!!!") } @@ -124,7 +124,7 @@ class EvaluateExpressionTest { fun `evaluate non-expression`() { val error = assertThrows { evaluate("bar = 2", "bar = 15") } - assertThat(error).hasMessageContaining("Mismatched input") + assertThat(error).hasMessageContaining("Unexpected token") assertThat(error).hasMessageContaining("bar = 15") } diff --git a/pkl-core/src/test/kotlin/org/pkl/core/ast/builder/ImportsAndReadsParserTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/ast/builder/ImportsAndReadsParserTest.kt index 2a23443d..064435bf 100644 --- a/pkl-core/src/test/kotlin/org/pkl/core/ast/builder/ImportsAndReadsParserTest.kt +++ b/pkl-core/src/test/kotlin/org/pkl/core/ast/builder/ImportsAndReadsParserTest.kt @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -61,7 +61,7 @@ class ImportsAndReadsParserTest { "qux/*.pkl", "/some/dir/chown.txt", "/some/dir/chowner.txt", - "/some/dir/*.txt" + "/some/dir/*.txt", ) ) } @@ -82,10 +82,10 @@ class ImportsAndReadsParserTest { .hasMessage( """ –– Pkl Error –– - Mismatched input: ``. Expected one of: `{`, `=`, `:` - + Invalid property definition. Expected a type annotation, `=` or `{`. + 1 | not valid Pkl syntax - ^ + ^^^ at text (repl:text) """ diff --git a/pkl-core/src/test/kotlin/org/pkl/core/parser/ANTLRSexpRenderer.kt b/pkl-core/src/test/kotlin/org/pkl/core/parser/ANTLRSexpRenderer.kt new file mode 100644 index 00000000..0f6493bd --- /dev/null +++ b/pkl-core/src/test/kotlin/org/pkl/core/parser/ANTLRSexpRenderer.kt @@ -0,0 +1,1058 @@ +/* + * Copyright © 2024-2025 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.core.parser + +import org.antlr.v4.runtime.ParserRuleContext +import org.pkl.core.parser.antlr.PklLexer +import org.pkl.core.parser.antlr.PklParser.* + +@Suppress("MemberVisibilityCanBePrivate") +class ANTLRSexpRenderer { + private var tab = "" + private var buf = StringBuilder() + + fun render(mod: ModuleContext): String { + renderModule(mod) + val res = buf.toString() + reset() + return res + } + + fun renderModule(mod: ModuleContext) { + buf.append(tab) + buf.append("(module") + val oldTab = increaseTab() + if (mod.moduleDecl() != null) { + buf.append('\n') + renderModuleDeclaration(mod.moduleDecl()) + } + for (imp in mod.importClause()) { + buf.append('\n') + renderImport(imp) + } + for (entry in sortModuleEntries(mod)) { + buf.append('\n') + when (entry) { + is ClazzContext -> renderClass(entry) + is TypeAliasContext -> renderTypeAlias(entry) + is ClassPropertyContext -> renderClassProperty(entry) + is ClassMethodContext -> renderClassMethod(entry) + } + } + tab = oldTab + buf.append(')') + } + + fun renderModuleDeclaration(decl: ModuleDeclContext) { + buf.append(tab) + buf.append("(moduleHeader") + val oldTab = increaseTab() + if (decl.DocComment() != null) { + buf.append('\n') + renderDocComment() + } + for (ann in decl.annotation()) { + buf.append('\n') + renderAnnotation(ann) + } + val header = decl.moduleHeader() + if (header != null) { + for (modifier in header.modifier()) { + buf.append('\n') + renderModifier(modifier) + } + if (header.qualifiedIdentifier() != null) { + buf.append('\n') + renderQualifiedIdent(header.qualifiedIdentifier()) + } + if (header.moduleExtendsOrAmendsClause() != null) { + buf.append('\n') + buf.append(tab) + buf.append("(extendsOrAmendsClause)") + } + } + tab = oldTab + buf.append(')') + } + + fun renderImport(imp: ImportClauseContext) { + buf.append(tab) + if (imp.t.type == PklLexer.IMPORT_GLOB) { + buf.append("(importGlobClause") + } else { + buf.append("(importClause") + } + val oldTab = increaseTab() + if (imp.Identifier() != null) { + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + } + buf.append(')') + tab = oldTab + } + + fun renderClass(clazz: ClazzContext) { + buf.append(tab) + buf.append("(clazz") + val oldTab = increaseTab() + if (clazz.DocComment() != null) { + buf.append('\n') + renderDocComment() + } + for (ann in clazz.annotation()) { + buf.append('\n') + renderAnnotation(ann) + } + val header = clazz.classHeader() + for (mod in header.modifier()) { + buf.append('\n') + renderModifier(mod) + } + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + val typePars = header.typeParameterList() + if (typePars != null) { + buf.append('\n') + renderTypeParameterList(typePars) + } + if (header.type() != null) { + buf.append('\n') + renderType(header.type()) + } + val body = clazz.classBody() + if (body != null) { + buf.append('\n') + renderClassBody(body) + } + buf.append(')') + tab = oldTab + } + + fun renderClassBody(body: ClassBodyContext) { + buf.append(tab) + buf.append("(classBody") + val oldTab = increaseTab() + for (entry in sortClassEntries(body)) { + buf.append('\n') + when (entry) { + is ClassPropertyContext -> renderClassProperty(entry) + is ClassMethodContext -> renderClassMethod(entry) + } + } + buf.append(')') + tab = oldTab + } + + fun renderTypeAlias(`typealias`: TypeAliasContext) { + buf.append(tab) + buf.append("(typeAlias") + val oldTab = increaseTab() + if (`typealias`.DocComment() != null) { + buf.append('\n') + renderDocComment() + } + for (ann in `typealias`.annotation()) { + buf.append('\n') + renderAnnotation(ann) + } + val header = `typealias`.typeAliasHeader() + for (mod in header.modifier()) { + buf.append('\n') + renderModifier(mod) + } + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + val typePars = header.typeParameterList() + if (typePars != null) { + renderTypeParameterList(typePars) + } + buf.append('\n') + renderType(`typealias`.type()) + buf.append(')') + tab = oldTab + } + + fun renderClassProperty(classProperty: ClassPropertyContext) { + buf.append(tab) + buf.append("(classProperty") + val oldTab = increaseTab() + if (classProperty.DocComment() != null) { + buf.append('\n') + renderDocComment() + } + for (ann in classProperty.annotation()) { + buf.append('\n') + renderAnnotation(ann) + } + for (mod in classProperty.modifier()) { + buf.append('\n') + renderModifier(mod) + } + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + if (classProperty.typeAnnotation() != null) { + buf.append('\n') + renderTypeAnnotation(classProperty.typeAnnotation()) + } + if (classProperty.expr() != null) { + buf.append('\n') + renderExpr(classProperty.expr()) + } + if (classProperty.objectBody() != null) { + for (body in classProperty.objectBody()) { + buf.append('\n') + renderObjectBody(body) + } + } + buf.append(')') + tab = oldTab + } + + fun renderClassMethod(classMethod: ClassMethodContext) { + buf.append(tab) + buf.append("(classMethod") + val oldTab = increaseTab() + if (classMethod.DocComment() != null) { + buf.append('\n') + renderDocComment() + } + for (ann in classMethod.annotation()) { + buf.append('\n') + renderAnnotation(ann) + } + val header = classMethod.methodHeader() + for (mod in header.modifier()) { + buf.append('\n') + renderModifier(mod) + } + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + if (header.typeParameterList() != null) { + renderTypeParameterList(header.typeParameterList()) + } + buf.append('\n') + renderParameterList(header.parameterList()) + if (header.typeAnnotation() != null) { + buf.append('\n') + renderTypeAnnotation(header.typeAnnotation()) + } + if (classMethod.expr() != null) { + buf.append('\n') + renderExpr(classMethod.expr()) + } + buf.append(')') + tab = oldTab + } + + fun renderAnnotation(ann: AnnotationContext) { + buf.append(tab) + buf.append("(annotation") + val oldTab = increaseTab() + buf.append('\n') + renderType(ann.type()) + if (ann.objectBody() != null) { + buf.append('\n') + renderObjectBody(ann.objectBody()) + } + buf.append(')') + tab = oldTab + } + + fun renderQualifiedIdent(name: QualifiedIdentifierContext) { + buf.append(tab) + buf.append("(qualifiedIdentifier") + val oldTab = increaseTab() + for (i in name.Identifier().indices) { + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + } + buf.append(')') + tab = oldTab + } + + fun renderTypeParameterList(typeParameterList: TypeParameterListContext) { + buf.append(tab) + buf.append("(TypeParameterList\n") + val oldTab = increaseTab() + for (tpar in typeParameterList.typeParameter()) { + buf.append('\n') + renderTypeParameter(tpar) + } + tab = oldTab + } + + @Suppress("UNUSED_PARAMETER") + fun renderTypeParameter(tpar: TypeParameterContext?) { + buf.append(tab) + buf.append("(TypeParameter\n") + val oldTab = increaseTab() + buf.append(tab) + buf.append("(identifier))") + tab = oldTab + } + + fun renderTypeAnnotation(typeAnnotation: TypeAnnotationContext) { + buf.append(tab) + buf.append("(typeAnnotation") + val oldTab = increaseTab() + buf.append('\n') + renderType(typeAnnotation.type()) + buf.append(')') + tab = oldTab + } + + fun renderType(type: TypeContext?) { + when (type) { + is UnknownTypeContext -> { + buf.append(tab) + buf.append("(unknownType)") + } + is NothingTypeContext -> { + buf.append(tab) + buf.append("(nothingType)") + } + is ModuleTypeContext -> { + buf.append(tab) + buf.append("(moduleType)") + } + is StringLiteralTypeContext -> { + buf.append(tab) + buf.append("(stringConstantType)") + } + is DeclaredTypeContext -> renderDeclaredType(type) + is ParenthesizedTypeContext -> renderParenthesizedType(type) + is NullableTypeContext -> renderNullableType(type) + is ConstrainedTypeContext -> renderConstrainedType(type) + is DefaultUnionTypeContext -> renderDefaultUnionType(type) + is UnionTypeContext -> renderUnionType(type) + is FunctionTypeContext -> renderFunctionType(type) + } + } + + fun renderDeclaredType(type: DeclaredTypeContext) { + buf.append(tab) + buf.append("(declaredType") + val oldTab = increaseTab() + buf.append('\n') + renderQualifiedIdent(type.qualifiedIdentifier()) + val args = type.typeArgumentList() + if (args != null) { + for (arg in args.type()) { + buf.append('\n') + renderType(arg) + } + } + buf.append(')') + tab = oldTab + } + + fun renderParenthesizedType(type: ParenthesizedTypeContext) { + buf.append(tab) + buf.append("(parenthesisedType") + val oldTab = increaseTab() + buf.append('\n') + renderType(type.type()) + buf.append(')') + tab = oldTab + } + + fun renderNullableType(type: NullableTypeContext) { + buf.append(tab) + buf.append("(nullableType") + val oldTab = increaseTab() + buf.append('\n') + renderType(type.type()) + buf.append(')') + tab = oldTab + } + + fun renderConstrainedType(type: ConstrainedTypeContext) { + buf.append(tab) + buf.append("(constrainedType") + val oldTab = increaseTab() + buf.append('\n') + renderType(type.type()) + for (expr in type.expr()) { + buf.append('\n') + renderExpr(expr) + } + buf.append(')') + tab = oldTab + } + + fun renderDefaultUnionType(type: DefaultUnionTypeContext) { + buf.append(tab) + buf.append("(defaultUnionType") + val oldTab = increaseTab() + buf.append('\n') + renderType(type.type()) + buf.append(')') + tab = oldTab + } + + fun renderUnionType(type: UnionTypeContext) { + buf.append(tab) + buf.append("(unionType") + val oldTab = increaseTab() + val types = flattenUnion(type) + for (typ in types) { + buf.append('\n') + renderType(typ) + } + buf.append(')') + tab = oldTab + } + + private fun flattenUnion(type: UnionTypeContext): List { + val types = mutableListOf() + val toCheck = mutableListOf(type.l, type.r) + while (toCheck.isNotEmpty()) { + val typ = toCheck.removeAt(0) + if (typ is UnionTypeContext) { + toCheck.add(0, typ.r) + toCheck.add(0, typ.l) + } else { + types += typ + } + } + return types + } + + fun renderFunctionType(type: FunctionTypeContext) { + buf.append(tab) + buf.append("(functionType") + val oldTab = increaseTab() + for (arg in type.ps) { + buf.append('\n') + renderType(arg) + } + buf.append('\n') + renderType(type.r) + buf.append(')') + tab = oldTab + } + + fun renderParameterList(parList: ParameterListContext) { + buf.append(tab) + buf.append("(parameterList") + val oldTab = increaseTab() + for (par in parList.parameter()) { + buf.append('\n') + renderParameter(par) + } + buf.append(')') + tab = oldTab + } + + fun renderParameter(par: ParameterContext) { + buf.append(tab) + buf.append("(parameter") + val oldTab = increaseTab() + val typedIdent = par.typedIdentifier() + if (typedIdent != null) { + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + if (typedIdent.typeAnnotation() != null) { + buf.append('\n') + renderTypeAnnotation(typedIdent.typeAnnotation()) + } + } + buf.append(')') + tab = oldTab + } + + fun renderObjectBody(body: ObjectBodyContext) { + buf.append(tab) + buf.append("(objectBody") + val oldTab = increaseTab() + for (par in body.parameter()) { + buf.append('\n') + renderParameter(par) + } + for (member in body.objectMember()) { + buf.append('\n') + renderMember(member) + } + buf.append(')') + tab = oldTab + } + + fun renderMember(member: ObjectMemberContext) { + when (member) { + is ObjectPropertyContext -> renderObjectProperty(member) + is ObjectMethodContext -> renderObjectMethod(member) + is MemberPredicateContext -> renderMemberPredicate(member) + is ObjectEntryContext -> renderObjectEntry(member) + is ObjectElementContext -> renderObjectElement(member) + is ObjectSpreadContext -> renderObjectSpread(member) + is WhenGeneratorContext -> renderWhenGenerator(member) + is ForGeneratorContext -> renderForGenerator(member) + } + } + + fun renderObjectElement(element: ObjectElementContext) { + buf.append(tab) + buf.append("(objectElement") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(element.expr()) + buf.append(')') + tab = oldTab + } + + fun renderObjectProperty(property: ObjectPropertyContext) { + buf.append(tab) + buf.append("(objectProperty") + val oldTab = increaseTab() + for (mod in property.modifier()) { + buf.append('\n') + renderModifier(mod) + } + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + val typeAnn = property.typeAnnotation() + if (typeAnn != null) { + buf.append('\n') + renderTypeAnnotation(typeAnn) + } + if (property.expr() != null) { + buf.append('\n') + renderExpr(property.expr()) + } + if (property.objectBody() != null) { + for (body in property.objectBody()) { + buf.append('\n') + renderObjectBody(body) + } + } + buf.append(')') + tab = oldTab + } + + fun renderObjectMethod(method: ObjectMethodContext) { + buf.append(tab) + buf.append("(objectMethod") + val oldTab = increaseTab() + buf.append('\n') + val header = method.methodHeader() + for (mod in header.modifier()) { + buf.append('\n') + renderModifier(mod) + } + buf.append('\n') + buf.append("(identifier)") + if (header.typeParameterList() != null) { + renderTypeParameterList(header.typeParameterList()) + } + buf.append('\n') + renderParameterList(header.parameterList()) + val typeAnn = header.typeAnnotation() + if (typeAnn != null) { + buf.append('\n') + renderTypeAnnotation(typeAnn) + } + buf.append('\n') + renderExpr(method.expr()) + buf.append(')') + tab = oldTab + } + + fun renderMemberPredicate(predicate: MemberPredicateContext) { + buf.append(tab) + buf.append("(memberPredicate") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(predicate.k) + if (predicate.v != null) { + buf.append('\n') + renderExpr(predicate.v) + } + if (predicate.objectBody() != null) { + for (body in predicate.objectBody()) { + buf.append('\n') + renderObjectBody(body) + } + } + buf.append(')') + tab = oldTab + } + + fun renderObjectEntry(entry: ObjectEntryContext) { + buf.append(tab) + buf.append("(objectEntry") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(entry.k) + if (entry.v != null) { + buf.append('\n') + renderExpr(entry.v) + } + if (entry.objectBody() != null) { + for (body in entry.objectBody()) { + buf.append('\n') + renderObjectBody(body) + } + } + buf.append(')') + tab = oldTab + } + + fun renderObjectSpread(spread: ObjectSpreadContext) { + buf.append(tab) + buf.append("(objectSpread") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(spread.expr()) + buf.append(')') + tab = oldTab + } + + fun renderWhenGenerator(generator: WhenGeneratorContext) { + buf.append(tab) + buf.append("(whenGenerator") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(generator.expr()) + buf.append('\n') + renderObjectBody(generator.b1) + if (generator.b2 != null) { + buf.append('\n') + renderObjectBody(generator.b2) + } + buf.append(')') + tab = oldTab + } + + fun renderForGenerator(generator: ForGeneratorContext) { + buf.append(tab) + buf.append("(forGenerator") + val oldTab = increaseTab() + buf.append('\n') + renderParameter(generator.t1) + if (generator.t2 != null) { + buf.append('\n') + renderParameter(generator.t2) + } + buf.append('\n') + renderExpr(generator.expr()) + buf.append('\n') + renderObjectBody(generator.objectBody()) + buf.append(')') + tab = oldTab + } + + fun renderArgumentList(argumentList: ArgumentListContext) { + buf.append(tab) + buf.append("(argumentList") + val oldTab = increaseTab() + for (arg in argumentList.expr()) { + buf.append('\n') + renderExpr(arg) + } + buf.append(')') + tab = oldTab + } + + fun renderExpr(expr: ExprContext) { + when (expr) { + is ThisExprContext -> { + buf.append(tab) + buf.append("(thisExpr)") + } + is OuterExprContext -> { + buf.append(tab) + buf.append("(outerExpr)") + } + is ModuleExprContext -> { + buf.append(tab) + buf.append("(moduleExpr)") + } + is NullLiteralContext -> { + buf.append(tab) + buf.append("(nullExpr)") + } + is TrueLiteralContext, + is FalseLiteralContext -> { + buf.append(tab) + buf.append("(boolLiteralExpr)") + } + is IntLiteralContext -> { + buf.append(tab) + buf.append("(intLiteralExpr)") + } + is FloatLiteralContext -> { + buf.append(tab) + buf.append("(floatLiteralExpr)") + } + is ThrowExprContext -> renderThrowExpr(expr) + is TraceExprContext -> renderTraceExpr(expr) + is ImportExprContext -> { + buf.append(tab) + val name = if (expr.t.type == PklLexer.IMPORT_GLOB) "(importGlobExpr)" else "(importExpr)" + buf.append(name) + } + is ReadExprContext -> renderReadExpr(expr) + is UnqualifiedAccessExprContext -> renderUnqualifiedAccessExpr(expr) + is SingleLineStringLiteralContext -> renderSingleLineStringExpr(expr) + is MultiLineStringLiteralContext -> renderMultiLineStringExpr(expr) + is NewExprContext -> renderNewExpr(expr) + is AmendExprContext -> renderAmendsExpr(expr) + is SuperAccessExprContext -> renderSuperAccessExpr(expr) + is SuperSubscriptExprContext -> renderSuperSubscriptExpr(expr) + is QualifiedAccessExprContext -> renderQualifiedAccessExpr(expr) + is SubscriptExprContext -> renderSubscriptExpr(expr) + is NonNullExprContext -> renderNonNullExpr(expr) + is UnaryMinusExprContext -> renderUnaryMinusExpr(expr) + is LogicalNotExprContext -> renderLogicalNotExpr(expr) + is ExponentiationExprContext -> renderBinaryOpExpr("exponentiationExpr", expr.expr()) + is MultiplicativeExprContext -> renderBinaryOpExpr("multiplicativeExpr", expr.expr()) + is AdditiveExprContext -> renderBinaryOpExpr("additiveExpr", expr.expr()) + is ComparisonExprContext -> renderBinaryOpExpr("comparisonExpr", expr.expr()) + is TypeTestExprContext -> renderTypeTestExpr(expr) + is EqualityExprContext -> renderBinaryOpExpr("equalityExpr", expr.expr()) + is LogicalAndExprContext -> renderBinaryOpExpr("logicalAndExpr", expr.expr()) + is LogicalOrExprContext -> renderBinaryOpExpr("logicalOrExpr", expr.expr()) + is PipeExprContext -> renderBinaryOpExpr("pipeExpr", expr.expr()) + is NullCoalesceExprContext -> renderBinaryOpExpr("nullCoalesceExpr", expr.expr()) + is IfExprContext -> renderIfExpr(expr) + is LetExprContext -> renderLetExpr(expr) + is FunctionLiteralContext -> renderFunctionLiteralExpr(expr) + is ParenthesizedExprContext -> renderParenthesisedExpr(expr) + } + } + + fun renderThrowExpr(expr: ThrowExprContext) { + buf.append(tab) + buf.append("(throwExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr()) + buf.append(')') + tab = oldTab + } + + fun renderTraceExpr(expr: TraceExprContext) { + buf.append(tab) + buf.append("(traceExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr()) + buf.append(')') + tab = oldTab + } + + fun renderReadExpr(expr: ReadExprContext) { + buf.append(tab) + var name = "(readExpr" + if (expr.t.type == PklLexer.READ_GLOB) { + name = "(readGlobExpr" + } else if (expr.t.type == PklLexer.READ_OR_NULL) { + name = "(readNullExpr" + } + buf.append(name) + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr()) + buf.append(')') + tab = oldTab + } + + fun renderUnqualifiedAccessExpr(expr: UnqualifiedAccessExprContext) { + buf.append(tab) + buf.append("(unqualifiedAccessExpr") + val oldTab = increaseTab() + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + val args = expr.argumentList() + if (args != null) { + buf.append('\n') + renderArgumentList(args) + } + buf.append(')') + tab = oldTab + } + + fun renderSingleLineStringExpr(expr: SingleLineStringLiteralContext) { + buf.append(tab) + buf.append("(interpolatedStringExpr") + val oldTab = increaseTab() + for (part in expr.singleLineStringPart()) { + if (part.expr() != null) { + buf.append('\n') + renderExpr(part.expr()) + } else { + buf.append('\n').append(tab) + buf.append("(stringConstantExpr)") + } + } + buf.append(')') + tab = oldTab + } + + fun renderMultiLineStringExpr(expr: MultiLineStringLiteralContext) { + buf.append(tab) + buf.append("(interpolatedMultiStringExpr") + val oldTab = increaseTab() + for (part in expr.multiLineStringPart()) { + if (part.expr() != null) { + buf.append('\n') + renderExpr(part.expr()) + } else { + buf.append('\n').append(tab) + buf.append("(stringConstantExpr)") + } + } + buf.append(')') + tab = oldTab + } + + fun renderNewExpr(expr: NewExprContext) { + buf.append(tab) + buf.append("(newExpr") + val oldTab = increaseTab() + if (expr.type() != null) { + buf.append('\n') + renderType(expr.type()) + } + buf.append('\n') + renderObjectBody(expr.objectBody()) + buf.append(')') + tab = oldTab + } + + fun renderAmendsExpr(expr: AmendExprContext) { + buf.append(tab) + buf.append("(amendsExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr()) + buf.append('\n') + renderObjectBody(expr.objectBody()) + buf.append(')') + tab = oldTab + } + + fun renderSuperAccessExpr(expr: SuperAccessExprContext) { + buf.append(tab) + buf.append("(superAccessExpr") + val oldTab = increaseTab() + buf.append('\n') + buf.append("(identifier)") + val args = expr.argumentList() + if (args != null) { + buf.append('\n') + renderArgumentList(args) + } + buf.append(')') + tab = oldTab + } + + fun renderSuperSubscriptExpr(expr: SuperSubscriptExprContext) { + buf.append(tab) + buf.append("(superSubscriptExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr()) + buf.append(')') + tab = oldTab + } + + fun renderQualifiedAccessExpr(expr: QualifiedAccessExprContext) { + buf.append(tab) + val name = + if (expr.t.type == PklLexer.QDOT) "(nullableQualifiedAccessExpr" else "(qualifiedAccessExpr" + buf.append(name) + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr()) + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + val args = expr.argumentList() + if (args != null) { + buf.append('\n') + renderArgumentList(args) + } + buf.append(')') + tab = oldTab + } + + fun renderSubscriptExpr(expr: SubscriptExprContext) { + buf.append(tab) + buf.append("(subscriptExpr") + val oldTab = increaseTab() + for (e in expr.expr()) { + buf.append('\n') + renderExpr(e) + } + buf.append(')') + tab = oldTab + } + + fun renderNonNullExpr(expr: NonNullExprContext) { + buf.append(tab) + buf.append("(nonNullExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr()) + buf.append(')') + tab = oldTab + } + + fun renderUnaryMinusExpr(expr: UnaryMinusExprContext) { + buf.append(tab) + buf.append("(unaryMinusExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr()) + buf.append(')') + tab = oldTab + } + + fun renderLogicalNotExpr(expr: LogicalNotExprContext) { + buf.append(tab) + buf.append("(logicalNotExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr()) + buf.append(')') + tab = oldTab + } + + fun renderBinaryOpExpr(name: String?, exprs: List) { + buf.append(tab) + buf.append("(").append(name) + val oldTab = increaseTab() + for (expr in exprs) { + buf.append('\n') + renderExpr(expr) + } + buf.append(')') + tab = oldTab + } + + fun renderTypeTestExpr(expr: TypeTestExprContext) { + buf.append(tab) + val name = if (expr.t.type == PklLexer.IS) "(typeCheckExpr" else "(typeCastExpr" + buf.append(name) + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr()) + buf.append('\n') + renderType(expr.type()) + buf.append(')') + tab = oldTab + } + + fun renderIfExpr(expr: IfExprContext) { + buf.append(tab) + buf.append("(ifExpr") + val oldTab = increaseTab() + for (e in expr.expr()) { + buf.append('\n') + renderExpr(e) + } + buf.append(')') + tab = oldTab + } + + fun renderLetExpr(expr: LetExprContext) { + buf.append(tab) + buf.append("(letExpr") + val oldTab = increaseTab() + buf.append('\n') + renderParameter(expr.parameter()) + for (e in expr.expr()) { + buf.append('\n') + renderExpr(e) + } + buf.append(')') + tab = oldTab + } + + fun renderFunctionLiteralExpr(expr: FunctionLiteralContext) { + buf.append(tab) + buf.append("(functionLiteralExpr") + val oldTab = increaseTab() + buf.append('\n') + renderParameterList(expr.parameterList()) + buf.append('\n') + renderExpr(expr.expr()) + buf.append(')') + tab = oldTab + } + + fun renderParenthesisedExpr(expr: ParenthesizedExprContext) { + buf.append(tab) + buf.append("(parenthesizedExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr()) + buf.append(')') + tab = oldTab + } + + @Suppress("UNUSED_PARAMETER") + fun renderModifier(mod: ModifierContext?) { + buf.append(tab) + buf.append("(modifier)") + } + + fun renderDocComment() { + buf.append(tab) + buf.append("(docComment)") + } + + fun reset() { + tab = "" + buf = StringBuilder() + } + + private fun increaseTab(): String { + val old = tab + tab += " " + return old + } + + companion object { + private fun sortModuleEntries(mod: ModuleContext): List { + val res = mutableListOf() + res += mod.clazz() + res += mod.typeAlias() + res += mod.classProperty() + res += mod.classMethod() + res.sortWith(compareBy { it.sourceInterval.a }) + return res + } + + private fun sortClassEntries(body: ClassBodyContext): List { + val res = mutableListOf() + res += body.classProperty() + res += body.classMethod() + res.sortWith(compareBy { it.sourceInterval.a }) + return res + } + } +} diff --git a/pkl-core/src/test/kotlin/org/pkl/core/parser/LexerTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/parser/LexerTest.kt index 7a2811a6..c476f34c 100644 --- a/pkl-core/src/test/kotlin/org/pkl/core/parser/LexerTest.kt +++ b/pkl-core/src/test/kotlin/org/pkl/core/parser/LexerTest.kt @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2025 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. @@ -15,12 +15,11 @@ */ package org.pkl.core.parser -import org.antlr.v4.runtime.CommonToken import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test -import org.pkl.core.parser.antlr.PklLexer class LexerTest { + @Test fun isRegularIdentifier() { assertThat(Lexer.isRegularIdentifier("pigeon")).isTrue @@ -37,12 +36,6 @@ class LexerTest { assertThat(Lexer.isRegularIdentifier("😀")).isFalse } - @Test - fun isKeyword() { - assertThat(Lexer.isKeyword(CommonToken(PklLexer.THIS))).isTrue - assertThat(Lexer.isKeyword(CommonToken(PklLexer.MINUS))).isFalse - } - @Test fun maybeQuoteIdentifier() { assertThat(Lexer.maybeQuoteIdentifier("pigeon")).isEqualTo("pigeon") @@ -53,4 +46,9 @@ class LexerTest { assertThat(Lexer.maybeQuoteIdentifier("this")).isEqualTo("`this`") assertThat(Lexer.maybeQuoteIdentifier("😀")).isEqualTo("`😀`") } + + @Test + fun `lexer keywords are sorted`() { + assertThat(Lexer.KEYWORDS).isSortedAccordingTo { a, b -> a.compareTo(b.name) } + } } diff --git a/pkl-core/src/test/kotlin/org/pkl/core/parser/ParserComparisonTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/parser/ParserComparisonTest.kt new file mode 100644 index 00000000..c1f94b04 --- /dev/null +++ b/pkl-core/src/test/kotlin/org/pkl/core/parser/ParserComparisonTest.kt @@ -0,0 +1,95 @@ +/* + * Copyright © 2024-2025 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.core.parser + +import java.nio.file.Path +import kotlin.io.path.Path +import kotlin.io.path.extension +import org.junit.jupiter.api.Test +import org.pkl.commons.walk + +class ParserComparisonTest : ParserComparisonTestInterface { + + @Test + fun testAsPrecedence() { + compare("prop = 3 + bar as Int * 5") + } + + @Test + fun testStringInterpolation() { + compare( + """ + prop = "\(bar)" + prop2 = "foo \(bar)" + prop3 = "\(bar) foo" + prop4 = "foo \(bar + baz) foo" + """ + .trimIndent() + ) + + compare( + """ + prop = ${"\"\"\""}\(bar)${"\"\"\""} + prop2 = ${"\"\"\""}foo \(bar)${"\"\"\""} + prop3 = ${"\"\"\""}\(bar) foo${"\"\"\""} + prop4 = ${"\"\"\""}foo \(bar + baz) foo${"\"\"\""} + """ + .trimIndent() + ) + } + + override fun getSnippets(): List { + return Path("src/test/files/LanguageSnippetTests/input") + .walk() + .filter { path -> + val pathStr = path.toString().replace("\\", "/") + path.extension == "pkl" && + !exceptions.any { pathStr.endsWith(it) } && + !regexExceptions.any { it.matches(pathStr) } + } + .toList() + } + + companion object { + // tests that are not syntactically valid Pkl + private val exceptions = + setOf( + "stringError1.pkl", + "annotationIsNotExpression2.pkl", + "amendsRequiresParens.pkl", + "errors/parser18.pkl", + "errors/nested1.pkl", + "errors/invalidCharacterEscape.pkl", + "errors/invalidUnicodeEscape.pkl", + "errors/unterminatedUnicodeEscape.pkl", + "errors/keywordNotAllowedHere1.pkl", + "errors/keywordNotAllowedHere2.pkl", + "errors/keywordNotAllowedHere3.pkl", + "errors/keywordNotAllowedHere4.pkl", + "errors/moduleWithHighMinPklVersionAndParseErrors.pkl", + "errors/underscore.pkl", + "notAUnionDefault.pkl", + "multipleDefaults.pkl", + ) + + private val regexExceptions = + setOf( + Regex(".*/errors/delimiters/.*"), + Regex(".*/errors/parser\\d+\\.pkl"), + Regex(".*/parser/.*"), + ) + } +} diff --git a/pkl-core/src/test/kotlin/org/pkl/core/parser/ParserComparisonTestInterface.kt b/pkl-core/src/test/kotlin/org/pkl/core/parser/ParserComparisonTestInterface.kt new file mode 100644 index 00000000..0b2b132f --- /dev/null +++ b/pkl-core/src/test/kotlin/org/pkl/core/parser/ParserComparisonTestInterface.kt @@ -0,0 +1,79 @@ +/* + * Copyright © 2024-2025 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.core.parser + +import java.nio.file.Path +import kotlin.io.path.pathString +import kotlin.io.path.readText +import org.antlr.v4.runtime.ANTLRInputStream +import org.antlr.v4.runtime.CommonTokenStream +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.SoftAssertions +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.parallel.Execution +import org.junit.jupiter.api.parallel.ExecutionMode +import org.pkl.core.parser.antlr.PklLexer +import org.pkl.core.parser.antlr.PklParser + +@Execution(ExecutionMode.CONCURRENT) +interface ParserComparisonTestInterface { + @Test + @Execution(ExecutionMode.CONCURRENT) + fun compareSnippetTests() { + SoftAssertions.assertSoftly { softly -> + getSnippets() + .parallelStream() + .map { Pair(it.pathString, it.readText()) } + .forEach { (path, snippet) -> + try { + compare(snippet, path, softly) + } catch (e: ParserError) { + softly.fail("path: $path. Message: ${e.message}", e) + } + } + } + } + + fun getSnippets(): List + + fun compare(code: String, path: String? = null, softly: SoftAssertions? = null) { + val (sexp, antlrExp) = renderBoth(code) + when { + (path != null && softly != null) -> + softly.assertThat(sexp).`as`("path: $path").isEqualTo(antlrExp) + else -> assertThat(sexp).isEqualTo(antlrExp) + } + } + + fun renderBoth(code: String): Pair = Pair(renderCode(code), renderANTLRCode(code)) + + companion object { + private fun renderCode(code: String): String { + val parser = Parser() + val mod = parser.parseModule(code) + val renderer = SexpRenderer() + return renderer.render(mod) + } + + private fun renderANTLRCode(code: String): String { + val lexer = PklLexer(ANTLRInputStream(code)) + val parser = PklParser(CommonTokenStream(lexer)) + val mod = parser.module() + val renderer = ANTLRSexpRenderer() + return renderer.render(mod) + } + } +} diff --git a/pkl-core/src/test/kotlin/org/pkl/core/parser/SexpRenderer.kt b/pkl-core/src/test/kotlin/org/pkl/core/parser/SexpRenderer.kt new file mode 100644 index 00000000..9250c192 --- /dev/null +++ b/pkl-core/src/test/kotlin/org/pkl/core/parser/SexpRenderer.kt @@ -0,0 +1,1065 @@ +/* + * Copyright © 2024-2025 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.core.parser + +import org.pkl.core.parser.ast.* +import org.pkl.core.parser.ast.Annotation +import org.pkl.core.parser.ast.Expr.* +import org.pkl.core.parser.ast.Expr.ModuleExpr +import org.pkl.core.parser.ast.ObjectMember.* +import org.pkl.core.parser.ast.Parameter.TypedIdentifier +import org.pkl.core.parser.ast.Type.* + +@Suppress("MemberVisibilityCanBePrivate") +class SexpRenderer { + private var tab = "" + private var buf = StringBuilder() + + fun render(mod: org.pkl.core.parser.ast.Module): String { + renderModule(mod) + val res = buf.toString() + reset() + return res + } + + fun renderModule(mod: org.pkl.core.parser.ast.Module) { + buf.append(tab) + buf.append("(module") + val oldTab = increaseTab() + if (mod.decl !== null) { + buf.append('\n') + renderModuleDeclaration(mod.decl!!) + } + for (imp in mod.imports) { + buf.append('\n') + renderImport(imp) + } + for (entry in sortModuleEntries(mod)) { + buf.append('\n') + when (entry) { + is Class -> renderClass(entry) + is TypeAlias -> renderTypeAlias(entry) + is ClassProperty -> renderClassPropertyEntry(entry) + is ClassMethod -> renderClassMethod(entry) + } + } + tab = oldTab + buf.append(')') + } + + fun renderModuleDeclaration(decl: ModuleDecl) { + buf.append(tab) + buf.append("(moduleHeader") + val oldTab = increaseTab() + if (decl.docComment !== null) { + buf.append('\n') + renderDocComment() + } + for (ann in decl.annotations) { + buf.append('\n') + renderAnnotation(ann) + } + for (mod in decl.modifiers) { + buf.append('\n') + renderModifier(mod) + } + if (decl.name !== null) { + buf.append('\n') + renderQualifiedIdent(decl.name!!) + } + if (decl.extendsOrAmendsDecl !== null) { + buf.append('\n') + buf.append(tab) + buf.append("(extendsOrAmendsClause)") + } + tab = oldTab + buf.append(')') + } + + fun renderImport(imp: ImportClause) { + buf.append(tab) + if (imp.isGlob) { + buf.append("(importGlobClause") + } else { + buf.append("(importClause") + } + val oldTab = increaseTab() + if (imp.alias !== null) { + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + } + buf.append(')') + tab = oldTab + } + + fun renderClass(clazz: Class) { + buf.append(tab) + buf.append("(clazz") + val oldTab = increaseTab() + if (clazz.docComment !== null) { + buf.append('\n') + renderDocComment() + } + for (ann in clazz.annotations) { + buf.append('\n') + renderAnnotation(ann) + } + for (mod in clazz.modifiers) { + buf.append('\n') + renderModifier(mod) + } + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + val tparList = clazz.typeParameterList + if (tparList !== null) { + buf.append('\n') + renderTypeParameterList(tparList) + } + if (clazz.superClass !== null) { + buf.append('\n') + renderType(clazz.superClass!!) + } + if (clazz.body !== null) { + buf.append('\n') + renderClassBody(clazz.body!!) + } + buf.append(')') + tab = oldTab + } + + fun renderClassBody(classBody: ClassBody) { + buf.append(tab) + buf.append("(classBody") + val oldTab = increaseTab() + for (entry in sortClassEntries(classBody)) { + buf.append('\n') + when (entry) { + is ClassProperty -> renderClassPropertyEntry(entry) + is ClassMethod -> renderClassMethod(entry) + } + } + buf.append(')') + tab = oldTab + } + + fun renderTypeAlias(`typealias`: TypeAlias) { + buf.append(tab) + buf.append("(typeAlias") + val oldTab = increaseTab() + if (`typealias`.docComment !== null) { + buf.append('\n') + renderDocComment() + } + for (ann in `typealias`.annotations) { + buf.append('\n') + renderAnnotation(ann) + } + for (mod in `typealias`.modifiers) { + buf.append('\n') + renderModifier(mod) + } + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + val tparList = `typealias`.typeParameterList + if (tparList !== null) { + renderTypeParameterList(tparList) + } + buf.append('\n') + renderType(`typealias`.type) + buf.append(')') + tab = oldTab + } + + fun renderClassPropertyEntry(classEntry: ClassProperty) { + buf.append(tab) + buf.append("(classProperty") + val oldTab = increaseTab() + if (classEntry.docComment !== null) { + buf.append('\n') + renderDocComment() + } + for (ann in classEntry.annotations) { + buf.append('\n') + renderAnnotation(ann) + } + for (mod in classEntry.modifiers) { + buf.append('\n') + renderModifier(mod) + } + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + classEntry.typeAnnotation?.let { typeAnnotation -> + buf.append('\n') + renderTypeAnnotation(typeAnnotation) + } + classEntry.expr?.let { expr -> + buf.append('\n') + renderExpr(expr) + } + classEntry.bodyList?.let { bodyList -> + for (body in bodyList) { + buf.append('\n') + renderObjectBody(body) + } + } + buf.append(')') + tab = oldTab + } + + fun renderClassMethod(classMethod: ClassMethod) { + buf.append(tab) + buf.append("(classMethod") + val oldTab = increaseTab() + if (classMethod.docComment !== null) { + buf.append('\n') + renderDocComment() + } + for (ann in classMethod.annotations) { + buf.append('\n') + renderAnnotation(ann) + } + for (mod in classMethod.modifiers) { + buf.append('\n') + renderModifier(mod) + } + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + val tparList = classMethod.typeParameterList + if (tparList !== null) { + renderTypeParameterList(tparList) + } + buf.append('\n') + renderParameterList(classMethod.parameterList) + if (classMethod.typeAnnotation !== null) { + buf.append('\n') + renderTypeAnnotation(classMethod.typeAnnotation!!) + } + if (classMethod.expr != null) { + buf.append('\n') + renderExpr(classMethod.expr!!) + } + buf.append(')') + tab = oldTab + } + + fun renderDocComment() { + buf.append(tab) + buf.append("(docComment)") + } + + fun renderAnnotation(ann: Annotation) { + buf.append(tab) + buf.append("(annotation") + val oldTab = increaseTab() + buf.append('\n') + renderType(ann.type) + if (ann.body !== null) { + buf.append('\n') + renderObjectBody(ann.body!!) + } + buf.append(')') + tab = oldTab + } + + fun renderQualifiedIdent(name: QualifiedIdentifier) { + buf.append(tab) + buf.append("(qualifiedIdentifier") + val oldTab = increaseTab() + for (i in name.identifiers.indices) { + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + } + buf.append(')') + tab = oldTab + } + + fun renderObjectBody(body: ObjectBody) { + buf.append(tab) + buf.append("(objectBody") + val oldTab = increaseTab() + for (par in body.parameters) { + buf.append('\n') + renderParameter(par) + } + for (member in body.members) { + buf.append('\n') + renderMember(member) + } + buf.append(')') + tab = oldTab + } + + fun renderParameterList(parList: ParameterList) { + buf.append(tab) + buf.append("(parameterList") + val oldTab = increaseTab() + for (par in parList.parameters) { + buf.append('\n') + renderParameter(par) + } + buf.append(')') + tab = oldTab + } + + fun renderParameter(par: Parameter) { + buf.append(tab) + if (par is TypedIdentifier) { + buf.append("(parameter") + val oldTab = increaseTab() + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + if (par.typeAnnotation !== null) { + buf.append('\n') + renderTypeAnnotation(par.typeAnnotation!!) + } + buf.append(')') + tab = oldTab + } else { + buf.append("(parameter)") + } + } + + fun renderArgumentList(argumentList: ArgumentList) { + buf.append(tab) + buf.append("(argumentList") + val oldTab = increaseTab() + for (arg in argumentList.arguments) { + buf.append('\n') + renderExpr(arg) + } + buf.append(')') + tab = oldTab + } + + fun renderExpr(expr: Expr) { + when (expr) { + is ThisExpr -> { + buf.append(tab) + buf.append("(thisExpr)") + } + is OuterExpr -> { + buf.append(tab) + buf.append("(outerExpr)") + } + is ModuleExpr -> { + buf.append(tab) + buf.append("(moduleExpr)") + } + is NullLiteralExpr -> { + buf.append(tab) + buf.append("(nullExpr)") + } + is BoolLiteralExpr -> { + buf.append(tab) + buf.append("(boolLiteralExpr)") + } + is IntLiteralExpr -> { + buf.append(tab) + buf.append("(intLiteralExpr)") + } + is FloatLiteralExpr -> { + buf.append(tab) + buf.append("(floatLiteralExpr)") + } + is StringConstant -> { + buf.append(tab) + buf.append("(stringConstantExpr)") + } + is SingleLineStringLiteralExpr -> renderSingleLineStringLiteral(expr) + is MultiLineStringLiteralExpr -> renderMultiLineStringLiteral(expr) + is ThrowExpr -> renderThrowExpr(expr) + is TraceExpr -> renderTraceExpr(expr) + is ImportExpr -> { + buf.append(tab) + val name = if (expr.isGlob) "(importGlobExpr)" else "(importExpr)" + buf.append(name) + } + is ReadExpr -> renderReadExpr(expr) + is UnqualifiedAccessExpr -> renderUnqualifiedAccessExpr(expr) + is QualifiedAccessExpr -> renderQualifiedAccessExpr(expr) + is SuperAccessExpr -> renderSuperAccessExpr(expr) + is SuperSubscriptExpr -> renderSuperSubscriptExpr(expr) + is SubscriptExpr -> renderSubscriptExpr(expr) + is IfExpr -> renderIfExpr(expr) + is LetExpr -> renderLetExpr(expr) + is FunctionLiteralExpr -> renderFunctionLiteralExpr(expr) + is ParenthesizedExpr -> renderParenthesisedExpr(expr) + is NewExpr -> renderNewExpr(expr) + is AmendsExpr -> renderAmendsExpr(expr) + is NonNullExpr -> renderNonNullExpr(expr) + is UnaryMinusExpr -> renderUnaryMinusExpr(expr) + is LogicalNotExpr -> renderLogicalNotExpr(expr) + is BinaryOperatorExpr -> renderBinaryOpExpr(expr) + is TypeCheckExpr -> renderTypeCheckExpr(expr) + is TypeCastExpr -> renderTypeCastExpr(expr) + is OperatorExpr -> throw RuntimeException("Operator expr should not exist after parsing") + is TypeExpr -> throw RuntimeException("Type expr should not exist after parsing") + } + } + + fun renderSingleLineStringLiteral(expr: SingleLineStringLiteralExpr) { + buf.append(tab) + buf.append("(interpolatedStringExpr") + val oldTab = increaseTab() + for (part in expr.parts) { + if (part is StringPart.StringInterpolation) { + buf.append('\n') + renderExpr(part.expr) + } else { + buf.append('\n').append(tab) + buf.append("(stringConstantExpr)") + } + } + buf.append(')') + tab = oldTab + } + + fun renderMultiLineStringLiteral(expr: MultiLineStringLiteralExpr) { + buf.append(tab) + buf.append("(interpolatedMultiStringExpr") + val oldTab = increaseTab() + for (part in expr.parts) { + if (part is StringPart.StringInterpolation) { + buf.append('\n') + renderExpr(part.expr) + } else { + buf.append('\n').append(tab) + buf.append("(stringConstantExpr)") + } + } + buf.append(')') + tab = oldTab + } + + fun renderThrowExpr(expr: ThrowExpr) { + buf.append(tab) + buf.append("(throwExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr) + buf.append(')') + tab = oldTab + } + + fun renderTraceExpr(expr: TraceExpr) { + buf.append(tab) + buf.append("(traceExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr) + buf.append(')') + tab = oldTab + } + + fun renderReadExpr(expr: ReadExpr) { + val name = + when (expr.readType) { + ReadType.READ -> "(readExpr" + ReadType.GLOB -> "(readGlobExpr" + ReadType.NULL -> "(readNullExpr" + } + buf.append(tab) + buf.append(name) + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr) + buf.append(')') + tab = oldTab + } + + fun renderUnqualifiedAccessExpr(expr: UnqualifiedAccessExpr) { + buf.append(tab) + buf.append("(unqualifiedAccessExpr") + val oldTab = increaseTab() + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + if (expr.argumentList !== null) { + buf.append('\n') + renderArgumentList(expr.argumentList!!) + } + buf.append(')') + tab = oldTab + } + + fun renderQualifiedAccessExpr(expr: QualifiedAccessExpr) { + buf.append(tab) + buf.append(if (expr.isNullable) "(nullableQualifiedAccessExpr" else "(qualifiedAccessExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr) + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + if (expr.argumentList !== null) { + buf.append('\n') + renderArgumentList(expr.argumentList!!) + } + buf.append(')') + tab = oldTab + } + + fun renderSuperAccessExpr(expr: SuperAccessExpr) { + buf.append(tab) + buf.append("(superAccessExpr") + val oldTab = increaseTab() + buf.append('\n') + buf.append("(identifier)") + if (expr.argumentList !== null) { + buf.append('\n') + renderArgumentList(expr.argumentList!!) + } + buf.append(')') + tab = oldTab + } + + fun renderSuperSubscriptExpr(expr: SuperSubscriptExpr) { + buf.append(tab) + buf.append("(superSubscriptExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.arg) + buf.append(')') + tab = oldTab + } + + fun renderSubscriptExpr(expr: SubscriptExpr) { + buf.append(tab) + buf.append("(subscriptExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr) + buf.append('\n') + renderExpr(expr.arg) + buf.append(')') + tab = oldTab + } + + fun renderIfExpr(expr: IfExpr) { + buf.append(tab) + buf.append("(ifExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.cond) + buf.append('\n') + renderExpr(expr.then) + buf.append('\n') + renderExpr(expr.els) + buf.append(')') + tab = oldTab + } + + fun renderLetExpr(expr: LetExpr) { + buf.append(tab) + buf.append("(letExpr") + val oldTab = increaseTab() + buf.append('\n') + renderParameter(expr.parameter) + buf.append('\n') + renderExpr(expr.bindingExpr) + buf.append('\n') + renderExpr(expr.expr) + buf.append(')') + tab = oldTab + } + + fun renderFunctionLiteralExpr(expr: FunctionLiteralExpr) { + buf.append(tab) + buf.append("(functionLiteralExpr") + val oldTab = increaseTab() + buf.append('\n') + renderParameterList(expr.parameterList) + buf.append('\n') + renderExpr(expr.expr) + buf.append(')') + tab = oldTab + } + + fun renderParenthesisedExpr(expr: ParenthesizedExpr) { + buf.append(tab) + buf.append("(parenthesizedExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr) + buf.append(')') + tab = oldTab + } + + fun renderNewExpr(expr: NewExpr) { + buf.append(tab) + buf.append("(newExpr") + val oldTab = increaseTab() + if (expr.type != null) { + buf.append('\n') + renderType(expr.type!!) + } + buf.append('\n') + renderObjectBody(expr.body) + buf.append(')') + tab = oldTab + } + + fun renderAmendsExpr(expr: AmendsExpr) { + buf.append(tab) + buf.append("(amendsExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr) + buf.append('\n') + renderObjectBody(expr.body) + buf.append(')') + tab = oldTab + } + + fun renderNonNullExpr(expr: NonNullExpr) { + buf.append(tab) + buf.append("(nonNullExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr) + buf.append(')') + tab = oldTab + } + + fun renderUnaryMinusExpr(expr: UnaryMinusExpr) { + buf.append(tab) + buf.append("(unaryMinusExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr) + buf.append(')') + tab = oldTab + } + + fun renderLogicalNotExpr(expr: LogicalNotExpr) { + buf.append(tab) + buf.append("(logicalNotExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr) + buf.append(')') + tab = oldTab + } + + fun renderBinaryOpExpr(expr: BinaryOperatorExpr) { + buf.append(tab) + val name = + when (expr.op) { + Operator.POW -> "(exponentiationExpr" + Operator.MULT, + Operator.DIV, + Operator.INT_DIV, + Operator.MOD -> "(multiplicativeExpr" + Operator.PLUS, + Operator.MINUS -> "(additiveExpr" + Operator.LT, + Operator.GT, + Operator.LTE, + Operator.GTE -> "(comparisonExpr" + Operator.IS, + Operator.AS -> "(typeTestExpr" + Operator.EQ_EQ, + Operator.NOT_EQ -> "(equalityExpr" + Operator.AND -> "(logicalAndExpr" + Operator.OR -> "(logicalOrExpr" + Operator.PIPE -> "(pipeExpr" + Operator.NULL_COALESCE -> "(nullCoalesceExpr" + else -> throw RuntimeException("Should never receive a dot operator here") + } + buf.append(name) + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.left) + buf.append('\n') + renderExpr(expr.right) + buf.append(')') + tab = oldTab + } + + fun renderTypeCheckExpr(expr: TypeCheckExpr) { + buf.append(tab) + buf.append("(typeCheckExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr) + buf.append('\n') + renderType(expr.type) + buf.append(')') + tab = oldTab + } + + fun renderTypeCastExpr(expr: TypeCastExpr) { + buf.append(tab) + buf.append("(typeCastExpr") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(expr.expr) + buf.append('\n') + renderType(expr.type) + buf.append(')') + tab = oldTab + } + + fun renderTypeAnnotation(typeAnnotation: TypeAnnotation) { + buf.append(tab) + buf.append("(typeAnnotation") + val oldTab = increaseTab() + buf.append('\n') + renderType(typeAnnotation.type) + buf.append(')') + tab = oldTab + } + + fun renderType(type: Type) { + when (type) { + is UnknownType -> { + buf.append(tab) + buf.append("(unknownType)") + } + is NothingType -> { + buf.append(tab) + buf.append("(nothingType)") + } + is ModuleType -> { + buf.append(tab) + buf.append("(moduleType)") + } + is StringConstantType -> { + buf.append(tab) + buf.append("(stringConstantType)") + } + is DeclaredType -> renderDeclaredType(type) + is ParenthesizedType -> renderParenthesizedType(type) + is NullableType -> renderNullableType(type) + is ConstrainedType -> renderConstrainedType(type) + is UnionType -> renderUnionType(type) + is FunctionType -> renderFunctionType(type) + } + } + + fun renderDeclaredType(type: DeclaredType) { + buf.append(tab) + buf.append("(declaredType") + val oldTab = increaseTab() + buf.append('\n') + renderQualifiedIdent(type.name) + for (arg in type.args) { + buf.append('\n') + renderType(arg) + } + buf.append(')') + tab = oldTab + } + + fun renderParenthesizedType(type: ParenthesizedType) { + buf.append(tab) + buf.append("(parenthesisedType") + val oldTab = increaseTab() + buf.append('\n') + renderType(type.type) + buf.append(')') + tab = oldTab + } + + fun renderNullableType(type: NullableType) { + buf.append(tab) + buf.append("(nullableType") + val oldTab = increaseTab() + buf.append('\n') + renderType(type.type) + buf.append(')') + tab = oldTab + } + + fun renderConstrainedType(type: ConstrainedType) { + buf.append(tab) + buf.append("(constrainedType") + val oldTab = increaseTab() + buf.append('\n') + renderType(type.type) + for (expr in type.exprs) { + buf.append('\n') + renderExpr(expr) + } + buf.append(')') + tab = oldTab + } + + fun renderUnionType(type: UnionType) { + buf.append(tab) + buf.append("(unionType") + val oldTab = increaseTab() + for (idx in type.types.indices) { + val typ = type.types[idx] + buf.append('\n') + if (type.defaultIndex == idx) { + buf.append(tab) + buf.append("(defaultUnionType") + val oldTab2 = increaseTab() + buf.append('\n') + renderType(typ) + buf.append(')') + tab = oldTab2 + } else { + renderType(typ) + } + } + buf.append(')') + tab = oldTab + } + + fun renderFunctionType(type: FunctionType) { + buf.append(tab) + buf.append("(functionType") + val oldTab = increaseTab() + for (arg in type.args) { + buf.append('\n') + renderType(arg) + } + buf.append('\n') + renderType(type.ret) + buf.append(')') + tab = oldTab + } + + fun renderMember(member: ObjectMember) { + when (member) { + is ObjectElement -> renderObjectElement(member) + is ObjectProperty -> renderObjectProperty(member) + is ObjectMethod -> renderObjectMethod(member) + is MemberPredicate -> renderMemberPredicate(member) + is ObjectEntry -> renderObjectEntry(member) + is ObjectSpread -> renderObjectSpread(member) + is WhenGenerator -> renderWhenGenerator(member) + is ForGenerator -> renderForGenerator(member) + } + } + + fun renderObjectElement(element: ObjectElement) { + buf.append(tab) + buf.append("(objectElement") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(element.expr) + buf.append(')') + tab = oldTab + } + + fun renderObjectProperty(property: ObjectProperty) { + buf.append(tab) + buf.append("(objectProperty") + val oldTab = increaseTab() + for (mod in property.modifiers) { + buf.append('\n') + renderModifier(mod) + } + buf.append('\n') + buf.append(tab) + buf.append("(identifier)") + if (property.typeAnnotation !== null) { + buf.append('\n') + renderTypeAnnotation(property.typeAnnotation!!) + } + property.expr?.let { + buf.append('\n') + renderExpr(it) + } + property.bodyList?.let { bodies -> + for (body in bodies) { + buf.append('\n') + renderObjectBody(body) + } + } + buf.append(')') + tab = oldTab + } + + fun renderObjectMethod(method: ObjectMethod) { + buf.append(tab) + buf.append("(objectMethod") + val oldTab = increaseTab() + buf.append('\n') + for (mod in method.modifiers) { + buf.append('\n') + renderModifier(mod) + } + buf.append('\n') + buf.append("(identifier)") + val tparList = method.typeParameterList + if (tparList !== null) { + renderTypeParameterList(tparList) + } + buf.append('\n') + renderParameterList(method.paramList) + if (method.typeAnnotation !== null) { + buf.append('\n') + renderTypeAnnotation(method.typeAnnotation!!) + } + buf.append('\n') + renderExpr(method.expr) + buf.append(')') + tab = oldTab + } + + fun renderMemberPredicate(predicate: MemberPredicate) { + buf.append(tab) + buf.append("(memberPredicate") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(predicate.pred) + predicate.expr?.let { expr -> + buf.append('\n') + renderExpr(expr) + } + predicate.bodyList?.let { bodyList -> + for (body in bodyList) { + buf.append('\n') + renderObjectBody(body) + } + } + buf.append(')') + tab = oldTab + } + + fun renderObjectEntry(entry: ObjectEntry) { + buf.append(tab) + buf.append("(objectEntry") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(entry.key) + entry.value?.let { value -> + buf.append('\n') + renderExpr(value) + } + entry.bodyList?.let { bodyList -> + for (body in bodyList) { + buf.append('\n') + renderObjectBody(body) + } + } + buf.append(')') + tab = oldTab + } + + fun renderObjectSpread(spread: ObjectSpread) { + buf.append(tab) + buf.append("(objectSpread") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(spread.expr) + buf.append(')') + tab = oldTab + } + + fun renderWhenGenerator(generator: WhenGenerator) { + buf.append(tab) + buf.append("(whenGenerator") + val oldTab = increaseTab() + buf.append('\n') + renderExpr(generator.thenClause) + buf.append('\n') + renderObjectBody(generator.body) + if (generator.elseClause !== null) { + buf.append('\n') + renderObjectBody(generator.elseClause!!) + } + buf.append(')') + tab = oldTab + } + + fun renderForGenerator(generator: ForGenerator) { + buf.append(tab) + buf.append("(forGenerator") + val oldTab = increaseTab() + buf.append('\n') + renderParameter(generator.p1) + if (generator.p2 != null) { + buf.append('\n') + renderParameter(generator.p2!!) + } + buf.append('\n') + renderExpr(generator.expr) + buf.append('\n') + renderObjectBody(generator.body) + buf.append(')') + tab = oldTab + } + + fun renderTypeParameterList(typeParameterList: TypeParameterList) { + buf.append(tab) + buf.append("(TypeParameterList\n") + val oldTab = increaseTab() + for (tpar in typeParameterList.parameters) { + buf.append('\n') + renderTypeParameter(tpar) + } + tab = oldTab + } + + @Suppress("UNUSED_PARAMETER") + fun renderTypeParameter(ignored: TypeParameter?) { + buf.append(tab) + buf.append("(TypeParameter\n") + val oldTab = increaseTab() + buf.append(tab) + buf.append("(identifier))") + tab = oldTab + } + + @Suppress("UNUSED_PARAMETER") + fun renderModifier(ignored: Modifier?) { + buf.append(tab) + buf.append("(modifier)") + } + + fun reset() { + tab = "" + buf = StringBuilder() + } + + private fun increaseTab(): String { + val old = tab + tab += " " + return old + } + + companion object { + private fun sortModuleEntries(mod: org.pkl.core.parser.ast.Module): List { + val res = mutableListOf() + res += mod.classes + res += mod.typeAliases + res += mod.properties + res += mod.methods + res.sortWith(compareBy { it.span().charIndex }) + return res + } + + private fun sortClassEntries(body: ClassBody): List { + val res = mutableListOf() + res += body.properties + res += body.methods + res.sortWith(compareBy { it.span().charIndex }) + return res + } + } +} diff --git a/pkl-core/src/test/kotlin/org/pkl/core/parser/SpanTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/parser/SpanTest.kt new file mode 100644 index 00000000..4dcd7af0 --- /dev/null +++ b/pkl-core/src/test/kotlin/org/pkl/core/parser/SpanTest.kt @@ -0,0 +1,41 @@ +/* + * Copyright © 2025 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.core.parser + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class SpanTest { + + @Test + fun `endWith test`() { + var span1 = Span(10, 20) + var span2 = Span(20, 20) + assertThat(span1.endWith(span2)).isEqualTo(Span(10, 30)) + + span1 = Span(10, 20) + span2 = Span(0, 40) + assertThat(span1.endWith(span2)).isEqualTo(Span(10, 30)) + + span1 = Span(10, 30) + span2 = Span(20, 20) + assertThat(span1.endWith(span2)).isEqualTo(Span(10, 30)) + + span1 = Span(10, 30) + span2 = Span(20, 5) + assertThat(span1.endWith(span2)).isEqualTo(Span(10, 15)) + } +} diff --git a/pkl-doc/gradle.lockfile b/pkl-doc/gradle.lockfile index e6fb76fd..86da7653 100644 --- a/pkl-doc/gradle.lockfile +++ b/pkl-doc/gradle.lockfile @@ -12,7 +12,6 @@ com.google.j2objc:j2objc-annotations:2.8=testCompileClasspath,testImplementation com.google.jimfs:jimfs:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.ibm.icu:icu4j:58.2=validator com.shapesecurity:salvation:2.7.2=validator -com.tunnelvisionlabs:antlr4-runtime:4.9.0=runtimeClasspath,testRuntimeClasspath commons-codec:commons-codec:1.10=validator commons-io:commons-io:2.4=validator commons-logging:commons-logging:1.2=validator diff --git a/pkl-executor/gradle.lockfile b/pkl-executor/gradle.lockfile index bf803428..03ffd664 100644 --- a/pkl-executor/gradle.lockfile +++ b/pkl-executor/gradle.lockfile @@ -1,7 +1,6 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -com.tunnelvisionlabs:antlr4-runtime:4.9.0=testRuntimeClasspath net.bytebuddy:byte-buddy:1.15.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata org.assertj:assertj-core:3.27.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath diff --git a/pkl-server/gradle.lockfile b/pkl-server/gradle.lockfile index 0142ad90..97655b33 100644 --- a/pkl-server/gradle.lockfile +++ b/pkl-server/gradle.lockfile @@ -1,7 +1,6 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -com.tunnelvisionlabs:antlr4-runtime:4.9.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.bytebuddy:byte-buddy:1.15.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata org.assertj:assertj-core:3.27.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath diff --git a/pkl-server/pkl-server.gradle.kts b/pkl-server/pkl-server.gradle.kts index 8be9e5fd..7f457bfc 100644 --- a/pkl-server/pkl-server.gradle.kts +++ b/pkl-server/pkl-server.gradle.kts @@ -24,7 +24,6 @@ dependencies { implementation(projects.pklCore) implementation(libs.msgpack) implementation(libs.truffleApi) - implementation(libs.antlrRuntime) testImplementation(projects.pklCommonsTest) } diff --git a/pkl-server/src/test/kotlin/org/pkl/server/BinaryEvaluatorTest.kt b/pkl-server/src/test/kotlin/org/pkl/server/BinaryEvaluatorTest.kt index 63c943df..fa0bbcbc 100644 --- a/pkl-server/src/test/kotlin/org/pkl/server/BinaryEvaluatorTest.kt +++ b/pkl-server/src/test/kotlin/org/pkl/server/BinaryEvaluatorTest.kt @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -33,7 +33,7 @@ class BinaryEvaluatorTest { listOf(Pattern.compile(".*")), listOf(Pattern.compile(".*")), SecurityManagers.defaultTrustLevels, - Path.of("") + Path.of(""), ), HttpClient.dummyClient(), Loggers.noop(), @@ -44,7 +44,7 @@ class BinaryEvaluatorTest { null, null, null, - null + null, ) private fun evaluate(text: String, expression: String?) = @@ -79,7 +79,7 @@ class BinaryEvaluatorTest { } """ .trimIndent(), - "foo.bar" + "foo.bar", ) assertThat(bytes.asInt()).isEqualTo(2) @@ -99,7 +99,7 @@ class BinaryEvaluatorTest { } """ .trimIndent(), - "output.text" + "output.text", ) assertThat(bytes.asString()) @@ -131,7 +131,7 @@ class BinaryEvaluatorTest { fun `evaluate expression with invalid syntax`() { val error = assertThrows { evaluate("foo = 1", "<>!!!") } - assertThat(error).hasMessageContaining("Mismatched input") + assertThat(error).hasMessageContaining("Unexpected token") assertThat(error).hasMessageContaining("<>!!!") } @@ -139,7 +139,7 @@ class BinaryEvaluatorTest { fun `evaluate non-expression`() { val error = assertThrows { evaluate("bar = 2", "bar = 15") } - assertThat(error).hasMessageContaining("Mismatched input") + assertThat(error).hasMessageContaining("Unexpected token") assertThat(error).hasMessageContaining("bar = 15") } diff --git a/pkl-tools/gradle.lockfile b/pkl-tools/gradle.lockfile index 64b141b6..9d3771da 100644 --- a/pkl-tools/gradle.lockfile +++ b/pkl-tools/gradle.lockfile @@ -5,7 +5,6 @@ com.github.ajalt.clikt:clikt-jvm:3.5.4=compileClasspath,runtimeClasspath,testCom com.github.ajalt.clikt:clikt:3.5.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.palantir.javapoet:javapoet:0.6.0=runtimeClasspath,testRuntimeClasspath com.squareup:kotlinpoet:1.6.0=runtimeClasspath,testRuntimeClasspath -com.tunnelvisionlabs:antlr4-runtime:4.9.0=runtimeClasspath,testRuntimeClasspath io.leangen.geantyref:geantyref:1.3.16=runtimeClasspath,testRuntimeClasspath org.commonmark:commonmark-ext-gfm-tables:0.24.0=runtimeClasspath,testRuntimeClasspath org.commonmark:commonmark:0.24.0=runtimeClasspath,testRuntimeClasspath