diff --git a/.gitignore b/.gitignore index 8289f45e5..9957dac93 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ .gradle/ build/ generated/ +testgenerated/ # IntelliJ .idea/ diff --git a/bench/bench.gradle.kts b/bench/bench.gradle.kts index 50f0ae0df..b5fbfa29a 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 dcfd6f7a0..f5bf7dc53 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 f1a6a3af6..d7ee22126 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 15345809b..923c2700b 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/src/test/kotlin/DocSnippetTests.kt b/docs/src/test/kotlin/DocSnippetTests.kt index 14b9a2fb4..f970dbb88 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.cst.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 43bb8834f..6bc720966 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 a424ae81e..24187ccb6 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 8f7660276..44149732b 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 590e18cbe..f0a2185ee 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 beba88d98..ccb22274b 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 7849b393c..e9808b1a3 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 869690209..5d7fae292 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 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) @@ -98,12 +99,14 @@ tasks.generateGrammarSource { // generate only visitor arguments = arguments + listOf("-visitor", "-no-listener") + source = fileTree("src/test/antlr") { include("*.g4") } + // 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, // 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) } @@ -119,8 +122,8 @@ tasks.named("generateGeneratorGrammarSource") { enabled = false } 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 +144,7 @@ tasks.processResources { mapOf( "version" to buildInfo.pklVersion, "commitId" to buildInfo.commitId, - "stdlibModules" to stdlibModules.joinToString(",") + "stdlibModules" to stdlibModules.joinToString(","), ) ) } 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 9a5c32ac6..edbbbd7ee 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 5e25bc8ad..b48d16f10 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 © 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,115 @@ 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.cst.Modifier; +import org.pkl.core.parser.cst.Modifier.ModifierValue; +import org.pkl.core.parser.cst.Node; +import org.pkl.core.parser.cst.StringConstant; +import org.pkl.core.parser.cst.StringConstantPart; +import org.pkl.core.parser.cst.StringConstantPart.ConstantPart; +import org.pkl.core.parser.cst.StringConstantPart.StringEscape; +import org.pkl.core.parser.cst.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 SourceSection createSourceSection(ParserRuleContext ctx) { - return createSourceSection(ctx.getStart(), ctx.getStop()); + protected @Nullable SourceSection createSourceSection(@Nullable Node node) { + return node == null + ? null + : source.createSection(node.span().charIndex(), node.span().length()); } - 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()); } + @SuppressWarnings("DataFlowIssue") 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 SourceSection createSourceSection(Source source, Span span) { + return source.createSection(span.charIndex(), span.length()); } - protected static @Nullable SourceSection createSourceSection( - Source source, @Nullable Token token) { - if (token == null) return null; + protected SourceSection startOf(Node node) { + return startOf(node.span()); + } + + protected SourceSection startOf(Span span) { + return source.createSection(span.charIndex(), 1); + } - var start = token.getStartIndex(); - var stop = token.getStopIndex(); - return source.createSection(start, stop - start + 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 0013a833e..d92fad20c 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,236 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.util.*; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +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.cst.Annotation; +import org.pkl.core.parser.cst.ArgumentList; +import org.pkl.core.parser.cst.Class; +import org.pkl.core.parser.cst.ClassMethod; +import org.pkl.core.parser.cst.ClassProperty; +import org.pkl.core.parser.cst.Expr; +import org.pkl.core.parser.cst.Expr.AmendsExpr; +import org.pkl.core.parser.cst.Expr.BinaryOperatorExpr; +import org.pkl.core.parser.cst.Expr.BoolLiteralExpr; +import org.pkl.core.parser.cst.Expr.FloatLiteralExpr; +import org.pkl.core.parser.cst.Expr.FunctionLiteralExpr; +import org.pkl.core.parser.cst.Expr.IfExpr; +import org.pkl.core.parser.cst.Expr.ImportExpr; +import org.pkl.core.parser.cst.Expr.IntLiteralExpr; +import org.pkl.core.parser.cst.Expr.LetExpr; +import org.pkl.core.parser.cst.Expr.LogicalNotExpr; +import org.pkl.core.parser.cst.Expr.ModuleExpr; +import org.pkl.core.parser.cst.Expr.MultiLineStringLiteralExpr; +import org.pkl.core.parser.cst.Expr.NewExpr; +import org.pkl.core.parser.cst.Expr.NonNullExpr; +import org.pkl.core.parser.cst.Expr.NullLiteralExpr; +import org.pkl.core.parser.cst.Expr.OuterExpr; +import org.pkl.core.parser.cst.Expr.ParenthesizedExpr; +import org.pkl.core.parser.cst.Expr.QualifiedAccessExpr; +import org.pkl.core.parser.cst.Expr.ReadExpr; +import org.pkl.core.parser.cst.Expr.SingleLineStringLiteralExpr; +import org.pkl.core.parser.cst.Expr.SubscriptExpr; +import org.pkl.core.parser.cst.Expr.SuperAccessExpr; +import org.pkl.core.parser.cst.Expr.SuperSubscriptExpr; +import org.pkl.core.parser.cst.Expr.ThisExpr; +import org.pkl.core.parser.cst.Expr.ThrowExpr; +import org.pkl.core.parser.cst.Expr.TraceExpr; +import org.pkl.core.parser.cst.Expr.TypeCastExpr; +import org.pkl.core.parser.cst.Expr.TypeCheckExpr; +import org.pkl.core.parser.cst.Expr.UnaryMinusExpr; +import org.pkl.core.parser.cst.Expr.UnqualifiedAccessExpr; +import org.pkl.core.parser.cst.ExtendsOrAmendsClause; +import org.pkl.core.parser.cst.Identifier; +import org.pkl.core.parser.cst.ImportClause; +import org.pkl.core.parser.cst.Modifier; +import org.pkl.core.parser.cst.Modifier.ModifierValue; +import org.pkl.core.parser.cst.Module; +import org.pkl.core.parser.cst.Node; +import org.pkl.core.parser.cst.ObjectBody; +import org.pkl.core.parser.cst.ObjectMember.ForGenerator; +import org.pkl.core.parser.cst.ObjectMember.MemberPredicate; +import org.pkl.core.parser.cst.ObjectMember.ObjectElement; +import org.pkl.core.parser.cst.ObjectMember.ObjectEntry; +import org.pkl.core.parser.cst.ObjectMember.ObjectMethod; +import org.pkl.core.parser.cst.ObjectMember.ObjectProperty; +import org.pkl.core.parser.cst.ObjectMember.ObjectSpread; +import org.pkl.core.parser.cst.ObjectMember.WhenGenerator; +import org.pkl.core.parser.cst.Parameter; +import org.pkl.core.parser.cst.Parameter.TypedIdentifier; +import org.pkl.core.parser.cst.ParameterList; +import org.pkl.core.parser.cst.QualifiedIdentifier; +import org.pkl.core.parser.cst.StringConstant; +import org.pkl.core.parser.cst.StringConstantPart; +import org.pkl.core.parser.cst.StringConstantPart.ConstantPart; +import org.pkl.core.parser.cst.StringConstantPart.StringEscape; +import org.pkl.core.parser.cst.StringConstantPart.StringNewline; +import org.pkl.core.parser.cst.StringConstantPart.StringUnicodeEscape; +import org.pkl.core.parser.cst.StringPart; +import org.pkl.core.parser.cst.StringPart.StringConstantParts; +import org.pkl.core.parser.cst.StringPart.StringInterpolation; +import org.pkl.core.parser.cst.Type; +import org.pkl.core.parser.cst.Type.ConstrainedType; +import org.pkl.core.parser.cst.Type.DeclaredType; +import org.pkl.core.parser.cst.Type.DefaultUnionType; +import org.pkl.core.parser.cst.Type.FunctionType; +import org.pkl.core.parser.cst.Type.ModuleType; +import org.pkl.core.parser.cst.Type.NothingType; +import org.pkl.core.parser.cst.Type.NullableType; +import org.pkl.core.parser.cst.Type.ParenthesizedType; +import org.pkl.core.parser.cst.Type.StringConstantType; +import org.pkl.core.parser.cst.Type.UnionType; +import org.pkl.core.parser.cst.Type.UnknownType; +import org.pkl.core.parser.cst.TypeAlias; +import org.pkl.core.parser.cst.TypeAnnotation; +import org.pkl.core.parser.cst.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 +263,8 @@ import org.pkl.core.util.Nullable; import org.pkl.core.util.Pair; -public final class AstBuilder extends AbstractAstBuilder { +@SuppressWarnings("DataFlowIssue") +public class AstBuilder extends AbstractAstBuilder { private final VmLanguage language; private final ModuleInfo moduleInfo; @@ -101,20 +294,20 @@ public AstBuilder( 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 ? createSourceSection(source, moduleDecl.getDocComment()) : null; ModuleInfo moduleInfo; if (moduleDecl == null) { @@ -123,13 +316,13 @@ public static AstBuilder create( 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,818 +338,1001 @@ public static AstBuilder create( } @Override - public PklRootNode visitModule(ModuleContext ctx) { - var moduleDecl = ctx.moduleDecl(); - var moduleHeader = moduleDecl != null ? moduleDecl.moduleHeader() : null; - - var annotationNodes = - moduleDecl != null ? doVisitAnnotations(moduleDecl.annotation()) : new ExpressionNode[] {}; - - int modifiers; - if (moduleHeader == null) { - modifiers = VmModifier.NONE; - } else { - var modifierCtxs = moduleHeader.modifier(); - modifiers = - doVisitModifiers( - modifierCtxs, VmModifier.VALID_MODULE_MODIFIERS, "invalidModuleModifier"); - // doing this in a second step gives better error messages - if (moduleInfo.isAmend()) { - modifiers = - doVisitModifiers( - modifierCtxs, - VmModifier.VALID_AMENDING_MODULE_MODIFIERS, - "invalidAmendingModuleModifier"); - } - } + public UnresolvedTypeNode visitUnknownType(UnknownType type) { + return new UnresolvedTypeNode.Unknown(createSourceSection(type)); + } - var extendsOrAmendsClause = - moduleHeader != null ? moduleHeader.moduleExtendsOrAmendsClause() : null; + @Override + public UnresolvedTypeNode visitNothingType(NothingType type) { + return new UnresolvedTypeNode.Nothing(createSourceSection(type)); + } - var supermoduleNode = - extendsOrAmendsClause == null - ? resolveBaseModuleClass(Identifier.MODULE, BaseModule::getModuleClass) - : doVisitImport( - PklLexer.IMPORT, extendsOrAmendsClause, extendsOrAmendsClause.stringConstant()); + @Override + public UnresolvedTypeNode visitModuleType(ModuleType type) { + return new UnresolvedTypeNode.Module(createSourceSection(type)); + } - var propertyNames = - CollectionUtils.newHashSet( - ctx.is.size() + ctx.cs.size() + ctx.ts.size() + ctx.ps.size()); + @Override + public UnresolvedTypeNode visitStringConstantType(StringConstantType type) { + return new UnresolvedTypeNode.StringLiteral( + createSourceSection(type), doVisitStringConstant(type.getStr())); + } - 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); + @Override + public UnresolvedTypeNode visitDeclaredType(DeclaredType type) { + var identifier = type.getName(); + var args = type.getArgs(); - var classNode = - new ClassNode( - moduleInfo.getSourceSection(), - moduleInfo.getHeaderSection(), - moduleInfo.getDocComment(), - annotationNodes, - modifiers, - PClassInfo.forModuleClass( - moduleInfo.getModuleName(), moduleInfo.getModuleKey().getUri()), - List.of(), - moduleInfo, - supertypeNode, - moduleProperties, - unresolvedPropertyNodes, - doVisitMethodDefs(ctx.ms)); + 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 ModuleNode( - language, moduleInfo.getSourceSection(), moduleInfo.getModuleName(), classNode); + return new UnresolvedTypeNode.Declared( + createSourceSection(type), doVisitTypeName(identifier)); } - var moduleProperties = - doVisitModuleProperties(ctx.is, ctx.cs, ctx.ts, ctx.ps, propertyNames, moduleInfo); - - for (var methodCtx : ctx.ms) { - var localMethod = doVisitObjectMethod(methodCtx.methodHeader(), methodCtx.expr(), true); - EconomicMaps.put(moduleProperties, localMethod.getName(), localMethod); + var argTypes = new UnresolvedTypeNode[args.size()]; + for (var i = 0; i < args.size(); i++) { + argTypes[i] = visitType(args.get(i)); } - var moduleNode = - AmendModuleNodeGen.create( - moduleInfo.getSourceSection(), - language, - annotationNodes, - moduleProperties, - moduleInfo, - supermoduleNode); - - return new ModuleNode( - language, moduleInfo.getSourceSection(), moduleInfo.getModuleName(), moduleNode); + return new UnresolvedTypeNode.Parameterized( + createSourceSection(type), language, doVisitTypeName(identifier), argTypes); } @Override - public ObjectMember visitClazz(ClazzContext ctx) { - var headerCtx = ctx.classHeader(); - - var sourceSection = createSourceSection(ctx); - var headerSection = createSourceSection(headerCtx); - - var bodyCtx = ctx.classBody(); - if (bodyCtx != null) { - checkClosingDelimiter(bodyCtx.err, "}", bodyCtx.stop); - } - - var typeParameters = visitTypeParameterList(headerCtx.typeParameterList()); - - List propertyCtxs = bodyCtx == null ? List.of() : bodyCtx.ps; - List methodCtxs = bodyCtx == null ? List.of() : bodyCtx.ms; + public UnresolvedTypeNode visitParenthesizedType(ParenthesizedType type) { + return visitType(type.getType()); + } - var modifiers = - doVisitModifiers( - headerCtx.modifier(), VmModifier.VALID_CLASS_MODIFIERS, "invalidClassModifier") - | VmModifier.CLASS; + @Override + public UnresolvedTypeNode visitNullableType(NullableType type) { + return new UnresolvedTypeNode.Nullable(createSourceSection(type), visitType(type.getType())); + } - var className = - Identifier.property(headerCtx.Identifier().getText(), VmModifier.isLocal(modifiers)); + @Override + public UnresolvedTypeNode visitConstrainedType(ConstrainedType type) { + var childNode = visitType(type.getType()); - return symbolTable.enterClass( - className, - typeParameters, + return symbolTable.enterCustomThisScope( scope -> { - var supertypeCtx = headerCtx.type(); - - // needs to be inside `enterClass` so that class' type parameters are in scope - var supertypeNode = - supertypeCtx != null - ? visitType(supertypeCtx) - : isBaseModule && className == Identifier.ANY - ? null - : new UnresolvedTypeNode.Declared( - VmUtils.unavailableSourceSection(), - resolveBaseModuleClass(Identifier.TYPED, BaseModule::getTypedClass)); - - if (!(supertypeNode == null - || supertypeNode instanceof UnresolvedTypeNode.Declared - || supertypeNode instanceof UnresolvedTypeNode.Parameterized - || supertypeNode instanceof UnresolvedTypeNode.Module)) { - throw exceptionBuilder() - .evalError("invalidSupertype", supertypeNode.getSourceSection().getCharacters()) - .withSourceSection(supertypeNode.getSourceSection()) - .build(); + 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); } - - var classInfo = - PClassInfo.get( - moduleInfo.getModuleName(), - className.toString(), - moduleInfo.getModuleKey().getUri()); - var propertyNames = CollectionUtils.newHashSet(propertyCtxs.size()); - - var classNode = - new ClassNode( - sourceSection, - headerSection, - createSourceSection(ctx.t), - doVisitAnnotations(ctx.annotation()), - modifiers, - classInfo, - typeParameters, - null, - supertypeNode, - EconomicMaps.create(), - doVisitClassProperties(propertyCtxs, propertyNames), - doVisitMethodDefs(methodCtxs)); - - var isLocal = VmModifier.isLocal(modifiers); - - var result = - new ObjectMember( - sourceSection, - headerSection, - isLocal ? VmModifier.LOCAL_CLASS_OBJECT_MEMBER : VmModifier.CLASS_OBJECT_MEMBER, - scope.getName(), - scope.getQualifiedName()); - - result.initMemberNode( - new UntypedObjectMemberNode( - language, scope.buildFrameDescriptor(), result, classNode)); - - return result; + return new Constrained(createSourceSection(type), childNode, constraints); }); } @Override - public ObjectMember visitTypeAlias(TypeAliasContext ctx) { - var headerCtx = ctx.typeAliasHeader(); - var sourceSection = createSourceSection(ctx); - var headerSection = createSourceSection(headerCtx); - - var modifiers = - doVisitModifiers( - headerCtx.modifier(), - VmModifier.VALID_TYPE_ALIAS_MODIFIERS, - "invalidTypeAliasModifier") - | VmModifier.TYPE_ALIAS; - - var isLocal = VmModifier.isLocal(modifiers); - var name = Identifier.property(headerCtx.Identifier().getText(), isLocal); - - var typeParameters = visitTypeParameterList(headerCtx.typeParameterList()); - - return symbolTable.enterTypeAlias( - name, - typeParameters, - scope -> { - var scopeName = scope.getName(); - var typeAliasNode = - new TypeAliasNode( - sourceSection, - headerSection, - createSourceSection(ctx.t), - doVisitAnnotations(ctx.annotation()), - modifiers, - scopeName.toString(), - scope.getQualifiedName(), - typeParameters, - (UnresolvedTypeNode) ctx.type().accept(this)); - - var result = - new ObjectMember( - sourceSection, - headerSection, - isLocal - ? VmModifier.LOCAL_TYPEALIAS_OBJECT_MEMBER - : VmModifier.TYPEALIAS_OBJECT_MEMBER, - scopeName, - scope.getQualifiedName()); - - result.initMemberNode( - new UntypedObjectMemberNode( - language, scope.buildFrameDescriptor(), result, typeAliasNode)); - - return result; - }); + public UnresolvedTypeNode visitDefaultUnionType(DefaultUnionType type) { + throw exceptionBuilder() + .evalError("notAUnion") + .withSourceSection(createSourceSection(type)) + .build(); } @Override - public UnresolvedTypeNode[] visitTypeArgumentList(@Nullable TypeArgumentListContext ctx) { - if (ctx == null) return new UnresolvedTypeNode[0]; + public UnresolvedTypeNode visitUnionType(UnionType type) { + var elementTypes = new ArrayList(); - checkCommaSeparatedElements(ctx, ctx.ts, ctx.errs); - checkClosingDelimiter(ctx.err, ">", ctx.stop); + var result = flattenUnionType(type, elementTypes); + boolean isUnionOfStringLiterals = result.isUnionOfStringLiterals; + int defaultIndex = result.defaultIndex; - 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); + if (isUnionOfStringLiterals) { + return new UnresolvedTypeNode.UnionOfStringLiterals( + createSourceSection(type), + defaultIndex, + elementTypes.stream() + .map(it -> doVisitStringConstant(((StringConstantType) it).getStr())) + .collect(Collectors.toCollection(LinkedHashSet::new))); } - return result; - } - @Override - public List visitTypeParameterList(@Nullable TypeParameterListContext ctx) { - if (ctx == null) return List.of(); + var elements = new UnresolvedTypeNode[elementTypes.size()]; + for (int i = 0; i < elementTypes.size(); i++) { + elements[i] = visitType(elementTypes.get(i)); + } - checkCommaSeparatedElements(ctx, ctx.ts, ctx.errs); - checkClosingDelimiter(ctx.err, ">", ctx.stop); + return new UnresolvedTypeNode.Union(createSourceSection(type), defaultIndex, elements); + } - if (!(ctx.parent instanceof TypeAliasHeaderContext) && !isStdLibModule) { - throw exceptionBuilder() - .evalError("cannotDeclareTypeParameter") - .withSourceSection(createSourceSection(ctx.ts.get(0))) - .build(); - } + private FlatUnionResult flattenUnionType(UnionType type, List collector) { + boolean isUnionOfStringLiterals = true; + int index = 0; + int defaultIndex = -1; + var list = new ArrayDeque(); + list.addLast(type.getLeft()); + list.addLast(type.getRight()); - var size = ctx.ts.size(); - var result = new ArrayList(size); - for (var i = 0; i < size; i++) { - var paramCtx = ctx.ts.get(i); - Variance variance; - if (paramCtx.t == 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; + while (!list.isEmpty()) { + var current = list.removeFirst(); + if (current instanceof UnionType unionType) { + list.addFirst(unionType.getRight()); + list.addFirst(unionType.getLeft()); + continue; } - var parameterName = paramCtx.Identifier().getText(); - if (result.stream().anyMatch(it -> it.getName().equals(parameterName))) { - throw exceptionBuilder() - .evalError("duplicateTypeParameter", parameterName) - .withSourceSection(createSourceSection(paramCtx)) - .build(); + if (current instanceof DefaultUnionType defaultUnionType) { + if (defaultIndex == -1) { + defaultIndex = index; + } else { + throw exceptionBuilder() + .evalError("multipleUnionDefaults") + .withSourceSection(createSourceSection(type)) + .build(); + } + isUnionOfStringLiterals = + isUnionOfStringLiterals && defaultUnionType.getType() instanceof StringConstantType; + collector.add(defaultUnionType.getType()); + } else { + isUnionOfStringLiterals = isUnionOfStringLiterals && current instanceof StringConstantType; + collector.add(current); } - result.add(new TypeParameter(variance, parameterName, i)); + index++; } - return result; + return new FlatUnionResult(isUnionOfStringLiterals, defaultIndex); } + private record FlatUnionResult(boolean isUnionOfStringLiterals, int defaultIndex) {} + @Override - public @Nullable UnresolvedTypeNode visitTypeAnnotation(@Nullable TypeAnnotationContext ctx) { - return ctx == null ? null : (UnresolvedTypeNode) ctx.type().accept(this); + 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 Object visitNewExpr(NewExprContext ctx) { - var typeCtx = ctx.type(); - return typeCtx != null - ? doVisitNewExprWithExplicitParent(ctx, typeCtx) - : doVisitNewExprWithInferredParent(ctx); + 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()); } - // `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); + // 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 expr; + return new OuterNode(createSourceSection(expr)); } - private Object doVisitNewExprWithInferredParent(NewExprContext ctx) { - ExpressionNode inferredParentNode; + @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 = 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 = 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(); + } + } + + @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()); + } - ParserRuleContext child = ctx; - var parent = ctx.getParent(); + 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 IfExprContext - || parent instanceof TraceExprContext - || parent instanceof LetExprContext letExpr && letExpr.r == child) { + while (parent instanceof IfExpr + || parent instanceof TraceExpr + || parent instanceof LetExpr letExpr && letExpr.getExpr() == child) { - if (parent instanceof LetExprContext) { + if (parent instanceof LetExpr) { assert scope != null; scope = scope.getParent(); levelsUp += 1; } child = parent; - parent = parent.getParent(); + parent = parent.parent(); } assert scope != null; - if (parent instanceof ClassPropertyContext || parent instanceof ObjectPropertyContext) { + if (parent instanceof ClassProperty || parent instanceof ObjectProperty) { inferredParentNode = InferParentWithinPropertyNodeGen.create( - createSourceSection(ctx.t), + createSourceSection(expr.newSpan()), scope.getName(), levelsUp == 0 ? new GetOwnerNode() : new GetEnclosingOwnerNode(levelsUp)); - } else if (parent instanceof ObjectElementContext - || parent instanceof ObjectEntryContext objectEntry && objectEntry.v == child) { + } else if (parent instanceof ObjectElement + || parent instanceof ObjectEntry objectEntry && objectEntry.getValue() == child) { inferredParentNode = ApplyVmFunction1NodeGen.create( ReadPropertyNodeGen.create( - createSourceSection(ctx.t), - Identifier.DEFAULT, + createSourceSection(expr.newSpan()), + org.pkl.core.runtime.Identifier.DEFAULT, levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp)), new GetMemberKeyNode()); - } else if (parent instanceof ClassMethodContext || parent instanceof ObjectMethodContext) { + } else if (parent instanceof ClassMethod || parent instanceof ObjectMethod) { var isObjectMethod = - parent instanceof ObjectMethodContext - || parent.getParent() instanceof ModuleContext && moduleInfo.isAmend(); - Identifier scopeName = scope.getName(); + parent instanceof ObjectMethod + || parent.parent() instanceof Module && moduleInfo.isAmend(); + org.pkl.core.runtime.Identifier scopeName = scope.getName(); inferredParentNode = isObjectMethod ? new InferParentWithinObjectMethodNode( - createSourceSection(ctx.t), + createSourceSection(expr.newSpan()), language, scopeName, levelsUp == 0 ? new GetOwnerNode() : new GetEnclosingOwnerNode(levelsUp)) : new InferParentWithinMethodNode( - createSourceSection(ctx.t), + createSourceSection(expr.newSpan()), language, scopeName, levelsUp == 0 ? new GetOwnerNode() : new GetEnclosingOwnerNode(levelsUp)); - } else if (parent instanceof LetExprContext letExpr && letExpr.l == child) { + } 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(ctx.t)) + .withSourceSection(createSourceSection(expr.newSpan())) .build(); } else { throw exceptionBuilder() .evalError("cannotInferParent") - .withSourceSection(createSourceSection(ctx.t)) + .withSourceSection(createSourceSection(expr.newSpan())) .build(); } - return doVisitObjectBody(ctx.objectBody(), inferredParentNode); + return doVisitObjectBody(expr.getBody(), inferredParentNode); } @Override - public Object visitAmendExpr(AmendExprContext ctx) { - var parentExpr = ctx.expr(); + 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())); + } - if (!(parentExpr instanceof NewExprContext - || parentExpr instanceof AmendExprContext - || parentExpr instanceof ParenthesizedExprContext)) { - throw exceptionBuilder() - .evalError("unexpectedCurlyProbablyAmendsExpression", parentExpr.getText()) - .withSourceSection(createSourceSection(ctx.objectBody().start)) - .build(); + @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); } - return doVisitObjectBody(ctx.objectBody(), visitExpr(parentExpr)); + // superproperty call + return new ReadSuperPropertyNode(createSourceSection(expr), memberName, needsConst); } @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); + public ExpressionNode visitSuperSubscriptExpr(SuperSubscriptExpr expr) { + return new ReadSuperEntryNode(createSourceSection(expr), visitExpr(expr.getArg())); + } - var modifiers = - doVisitModifiers( - ctx.modifier(), VmModifier.VALID_PROPERTY_MODIFIERS, "invalidPropertyModifier"); + @Override + public ExpressionNode visitQualifiedAccessExpr(QualifiedAccessExpr expr) { + if (expr.getArgumentList() != null) { + return doVisitMethodAccessExpr(expr); + } - var isLocal = VmModifier.isLocal(modifiers); - var propertyName = Identifier.property(identifier.getText(), isLocal); + return doVisitPropertyInvocationExpr(expr); + } - return symbolTable.enterProperty( - propertyName, - getConstLevel(modifiers), - scope -> { - var exprCtx = ctx.expr(); - var objBodyCtx = ctx.objectBody(); - ExpressionNode bodyNode; + @Override + public ExpressionNode visitSubscriptExpr(SubscriptExpr expr) { + return SubscriptNodeGen.create( + createSourceSection(expr), visitExpr(expr.getExpr()), visitExpr(expr.getArg())); + } - 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 - } - } + @Override + public ExpressionNode visitNonNullExpr(NonNullExpr expr) { + return new NonNullNode(createSourceSection(expr), visitExpr(expr.getExpr())); + } - var typeAnnNode = visitTypeAnnotation(typeAnnCtx); + @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); + } - return new UnresolvedPropertyNode( - language, - sourceSection, - headerSection, - createSourceSection(identifier), - scope.buildFrameDescriptor(), - docComment, - annotationNodes, - modifiers, - scope.getName(), - scope.getQualifiedName(), - typeAnnNode, - bodyNode); - }); + @Override + public ExpressionNode visitLogicalNotExpr(LogicalNotExpr expr) { + return LogicalNotNodeGen.create(createSourceSection(expr), visitExpr(expr.getExpr())); } - private VmException missingLocalPropertyValue(TypeAnnotationContext typeAnnCtx) { - var stop = typeAnnCtx.stop.getStopIndex(); - return exceptionBuilder() - .evalError("missingLocalPropertyValue") - .withSourceSection(source.createSection(stop + 1, 0)) - .build(); + @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(); + }; } - private ObjectMember doVisitObjectProperty(ObjectPropertyContext ctx) { - return doVisitObjectProperty( - ctx, ctx.modifier(), ctx.Identifier(), ctx.typeAnnotation(), ctx.expr(), ctx.objectBody()); + @Override + public ExpressionNode visitTypeCheckExpr(TypeCheckExpr expr) { + return new TypeTestNode( + createSourceSection(expr), visitExpr(expr.getExpr()), visitType(expr.getType())); } - private ObjectMember doVisitObjectMethod(ObjectMethodContext ctx) { - return doVisitObjectMethod(ctx.methodHeader(), ctx.expr(), false); + @Override + public ExpressionNode visitTypeCastExpr(TypeCastExpr expr) { + return new TypeCastNode( + createSourceSection(expr), visitExpr(expr.getExpr()), visitType(expr.getType())); } - private ObjectMember doVisitObjectMethod( - MethodHeaderContext headerCtx, ExprContext exprCtx, boolean isModuleMethod) { - var modifiers = - doVisitModifiers( - headerCtx.modifier(), - VmModifier.VALID_OBJECT_MEMBER_MODIFIERS, - "invalidObjectMemberModifier"); + @Override + public ExpressionNode visitIfExpr(IfExpr expr) { + return new IfElseNode( + createSourceSection(expr), + visitExpr(expr.getCond()), + visitExpr(expr.getThen()), + visitExpr(expr.getEls())); + } - if (!VmModifier.isLocal(modifiers)) { - throw exceptionBuilder() - .evalError(isModuleMethod ? "moduleMethodMustBeLocal" : "objectMethodMustBeLocal") - .withSourceSection(createSourceSection(headerCtx)) - .build(); + @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 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 isCustomThisScope = symbolTable.getCurrentScope().isCustomThisScope(); - var member = - new ObjectMember( - createSourceSection(headerCtx.getParent()), - createSourceSection(headerCtx), - modifiers, - scope.getName(), - scope.getQualifiedName()); - var body = visitExpr(exprCtx); - var node = - new ObjectMethodNode( + UnresolvedFunctionNode functionNode = + symbolTable.enterLambda( + frameBuilder, + scope -> { + var expr = visitExpr(letExpr.getExpr()); + return new UnresolvedFunctionNode( language, scope.buildFrameDescriptor(), - member, - body, - paramListCtx.ts.size(), - doVisitParameterTypes(paramListCtx), - visitTypeAnnotation(headerCtx.typeAnnotation())); + new Lambda(createSourceSection(letExpr.getExpr()), scope.getQualifiedName()), + 1, + typeNodes, + null, + expr); + }); - member.initMemberNode(node); - return member; - }); + return new LetExprNode( + sourceSection, functionNode, visitExpr(letExpr.getBindingExpr()), isCustomThisScope); } - 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(); + @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("invalidConstObjectMemberModifier") - .withSourceSection(createSourceSection(constModifierCtx)) + .evalError("tooManyFunctionParameters") + .withSourceSection(sourceSection) .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); + var isCustomThisScope = symbolTable.getCurrentScope().isCustomThisScope(); - return symbolTable.enterProperty( - identifier, - getConstLevel(modifiers), + return symbolTable.enterLambda( + descriptorBuilder, 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( + var exprNode = visitExpr(expr.getExpr()); + var functionNode = + new UnresolvedFunctionNode( language, - sourceSection, - headerSection, - scope.getName(), - scope.getQualifiedName(), scope.buildFrameDescriptor(), - modifiers, - bodyNode, - null); + new Lambda(sourceSection, scope.getQualifiedName()), + paramCount, + doVisitParameterTypes(params), + null, + exprNode); + + return new FunctionLiteralNode(sourceSection, functionNode, isCustomThisScope); }); } - 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; + @Override + public ExpressionNode visitParenthesizedExpr(ParenthesizedExpr expr) { + return visitExpr(expr.getExpr()); } - 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); - } + private ExpressionNode doVisitListLiteral(Expr expr, ArgumentList argList) { + var elementNodes = createCollectionArgumentNodes(argList); - @Override - public GeneratorPropertyNode visitObjectProperty(ObjectPropertyContext ctx) { - checkNotInsideForGenerator(ctx, "forGeneratorCannotGenerateProperties"); - var member = doVisitObjectProperty(ctx); - return GeneratorPropertyNodeGen.create(member); + 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); } - @Override - public GeneratorMemberNode visitObjectMethod(ObjectMethodContext ctx) { - checkNotInsideForGenerator(ctx, "forGeneratorCannotGenerateMethods"); - var member = doVisitObjectMethod(ctx); - return GeneratorPropertyNodeGen.create(member); + 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 void checkNotInsideForGenerator(ParserRuleContext ctx, String errorMessageKey) { - if (!symbolTable.getCurrentScope().isForGeneratorScope()) { - return; + private ExpressionNode doVisitMapLiteral(Expr expr, ArgumentList argList) { + var keyAndValueNodes = createCollectionArgumentNodes(argList); + + if (keyAndValueNodes.first.length == 0) { + return new ConstantValueNode(VmMap.EMPTY); } - var forExprCtx = ctx.getParent(); - while (forExprCtx.getClass() != ForGeneratorContext.class) { - forExprCtx = forExprCtx.getParent(); + + if (keyAndValueNodes.first.length % 2 != 0) { + throw exceptionBuilder() + .evalError("missingMapValue") + .withSourceSection(createSourceSection(argList.span().stopSpan())) + .build(); } - throw exceptionBuilder() - .evalError(errorMessageKey) - .withSourceSection(createSourceSection(((ForGeneratorContext) forExprCtx).FOR())) - .build(); + + return keyAndValueNodes.second + ? new ConstantValueNode( + createSourceSection(expr), VmMap.createFromConstantNodes(keyAndValueNodes.first)) + : new MapLiteralNode(createSourceSection(expr), keyAndValueNodes.first); } - @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()); + 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; } - 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); + return Pair.of(elementNodes, isConstantNodes); } @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); + public GeneratorMemberNode visitObjectMember(org.pkl.core.parser.cst.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 visitObjectSpread(ObjectSpreadContext ctx) { - var expr = visitExpr(ctx.expr()); - return GeneratorSpreadNodeGen.create(createSourceSection(ctx), expr, ctx.QSPREAD() != null); + public GeneratorMemberNode visitObjectMethod(ObjectMethod memberNode) { + checkNotInsideForGenerator(memberNode, "forGeneratorCannotGenerateMethods"); + var member = doVisitObjectMethod(memberNode); + return GeneratorPropertyNodeGen.create(member); } @Override - public GeneratorElementNode visitObjectElement(ObjectElementContext ctx) { - var member = doVisitObjectElement(ctx); + 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 GeneratorElementNodeGen.create(member, isFrameStored); + return GeneratorPredicateMemberNodeGen.create(keyNode, 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 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 GeneratorWhenNode visitWhenGenerator(WhenGeneratorContext ctx) { - checkClosingDelimiter(ctx.err, ")", ctx.e.stop); + public GeneratorMemberNode visitObjectSpread(ObjectSpread member) { + var expr = visitExpr(member.getExpr()); + return GeneratorSpreadNodeGen.create(createSourceSection(member), expr, member.isNullable()); + } - var sourceSection = createSourceSection(ctx); - var thenNodes = doVisitForWhenBody(ctx.b1); - var elseNodes = ctx.b2 == null ? new GeneratorMemberNode[0] : doVisitForWhenBody(ctx.b2); + @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(ctx.e), thenNodes, elseNodes); + return new GeneratorWhenNode(sourceSection, visitExpr(member.getCond()), thenNodes, elseNodes); } - private static boolean isIgnored(@Nullable ParameterContext param) { - return param != null && param.UNDERSCORE() != null; + 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 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(); + 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.Identifier()); + keyTypedIdentifier == null + ? null + : toIdentifier(keyTypedIdentifier.getIdentifier().getValue()); var valueIdentifier = - valueTypedIdentifier == null ? null : toIdentifier(valueTypedIdentifier.Identifier()); + valueTypedIdentifier == null + ? null + : toIdentifier(valueTypedIdentifier.getIdentifier().getValue()); if (valueIdentifier != null && valueIdentifier == keyIdentifier) { throw exceptionBuilder() .evalError("duplicateDefinition", valueIdentifier) - .withSourceSection(createSourceSection(valueTypedIdentifier.Identifier())) + .withSourceSection(createSourceSection(valueTypedIdentifier.getIdentifier())) .build(); } var currentScope = symbolTable.getCurrentScope(); @@ -975,11 +1351,11 @@ public GeneratorForNode visitForGenerator(ForGeneratorContext ctx) { var unresolvedKeyTypeNode = keyTypedIdentifier == null ? null - : visitTypeAnnotation(keyTypedIdentifier.typeAnnotation()); + : visitTypeAnnotation(keyTypedIdentifier.getTypeAnnotation()); var unresolvedValueTypeNode = valueTypedIdentifier == null ? null - : visitTypeAnnotation(valueTypedIdentifier.typeAnnotation()); + : visitTypeAnnotation(valueTypedIdentifier.getTypeAnnotation()); // if possible, initialize immediately to avoid later insert var keyTypeNode = unresolvedKeyTypeNode == null && keySlot != -1 @@ -992,12 +1368,12 @@ public GeneratorForNode visitForGenerator(ForGeneratorContext ctx) { ? new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection()) .initWriteSlotNode(valueSlot) : null; - var iterableNode = visitExpr(ctx.e); + var iterableNode = visitExpr(ctx.getExpr()); var memberNodes = symbolTable.enterForGenerator( generatorDescriptorBuilder, memberDescriptorBuilder, - scope -> doVisitForWhenBody(ctx.objectBody())); + scope -> doVisitForWhenBody(ctx.getBody())); return GeneratorForNodeGen.create( createSourceSection(ctx), generatorDescriptorBuilder.build(), @@ -1009,389 +1385,386 @@ public GeneratorForNode visitForGenerator(ForGeneratorContext ctx) { 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; - } - var startIndex = member.getStart().getStartIndex(); - var prevStopIndex = prevMember.getStop().getStopIndex(); - if (startIndex - prevStopIndex == 1) { - throw exceptionBuilder() - .evalError("unseparatedObjectMembers") - .withSourceSection(createSourceSection(member)) - .build(); + @Override + public PklRootNode visitModule(Module mod) { + var moduleDecl = mod.getDecl(); + + var annotationNodes = + moduleDecl != null + ? doVisitAnnotations(moduleDecl.getAnnotations()) + : new ExpressionNode[] {}; + + int modifiers; + if (moduleDecl == null) { + modifiers = VmModifier.NONE; + } else { + var modifierNodes = moduleDecl.getModifiers(); + modifiers = + doVisitModifiers( + modifierNodes, VmModifier.VALID_MODULE_MODIFIERS, "invalidModuleModifier"); + // doing this in a second step gives better error messages + if (moduleInfo.isAmend()) { + modifiers = + doVisitModifiers( + modifierNodes, + VmModifier.VALID_AMENDING_MODULE_MODIFIERS, + "invalidAmendingModuleModifier"); } } - } - private ExpressionNode doVisitObjectBody( - List ctxs, ExpressionNode parentNode) { - for (var ctx : ctxs) { - parentNode = doVisitObjectBody(ctx, parentNode); - } - return parentNode; - } + var extendsOrAmendsClause = moduleDecl != null ? moduleDecl.getExtendsOrAmendsDecl() : null; - private ExpressionNode doVisitObjectBody(ObjectBodyContext ctx, ExpressionNode parentNode) { - checkClosingDelimiter(ctx.err, "}", ctx.stop); - return symbolTable.enterObjectScope( - (scope) -> { - var objectMemberCtx = ctx.objectMember(); - if (objectMemberCtx.isEmpty()) { - return EmptyObjectLiteralNodeGen.create( - createSourceSection(ctx.getParent()), parentNode); - } - var sourceSection = createSourceSection(ctx.getParent()); + var supermoduleNode = + extendsOrAmendsClause == null + ? resolveBaseModuleClass( + org.pkl.core.runtime.Identifier.MODULE, BaseModule::getModuleClass) + : doVisitImport(false, extendsOrAmendsClause, extendsOrAmendsClause.getUrl()); - var parametersDescriptorBuilder = createFrameDescriptorBuilder(ctx); - var parameterTypes = doVisitParameterTypes(ctx); + var propertyNames = + CollectionUtils.newHashSet( + mod.getImports().size() + + mod.getClasses().size() + + mod.getTypeAliases().size() + + mod.getProperties().size()); - var members = EconomicMaps.create(); - var elements = new ArrayList(); - var keyNodes = new ArrayList(); - var values = new ArrayList(); - var isConstantKeyNodes = true; + if (!moduleInfo.isAmend()) { + var supertypeNode = + new UnresolvedTypeNode.Declared(supermoduleNode.getSourceSection(), supermoduleNode); + var moduleProperties = + doVisitModuleProperties( + mod.getImports(), + mod.getClasses(), + mod.getTypeAliases(), + List.of(), + propertyNames, + moduleInfo); + var unresolvedPropertyNodes = doVisitClassProperties(mod.getProperties(), propertyNames); - checkSpaceSeparatedObjectMembers(ctx); - for (var memberCtx : objectMemberCtx) { - if (memberCtx instanceof ObjectPropertyContext propertyCtx) { - addProperty(members, doVisitObjectProperty(propertyCtx)); - continue; - } + var classNode = + new ClassNode( + moduleInfo.getSourceSection(), + moduleInfo.getHeaderSection(), + moduleInfo.getDocComment(), + annotationNodes, + modifiers, + PClassInfo.forModuleClass( + moduleInfo.getModuleName(), moduleInfo.getModuleKey().getUri()), + List.of(), + moduleInfo, + supertypeNode, + moduleProperties, + unresolvedPropertyNodes, + doVisitMethodDefs(mod.getMethods())); - if (memberCtx instanceof ObjectEntryContext entryCtx) { - var keyAndValue = doVisitObjectEntry(entryCtx); - var key = keyAndValue.first; - keyNodes.add(key); - isConstantKeyNodes = isConstantKeyNodes && key instanceof ConstantNode; - values.add(keyAndValue.second); - continue; - } + return new ModuleNode( + language, moduleInfo.getSourceSection(), moduleInfo.getModuleName(), classNode); + } - if (memberCtx instanceof ObjectElementContext elementCtx) { - var element = doVisitObjectElement(elementCtx); - elements.add(element); - continue; - } + var moduleProperties = + doVisitModuleProperties( + mod.getImports(), + mod.getClasses(), + mod.getTypeAliases(), + mod.getProperties(), + propertyNames, + moduleInfo); + + for (var methodCtx : mod.getMethods()) { + 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); + } - if (memberCtx instanceof ObjectMethodContext methodCtx) { - addProperty(members, doVisitObjectMethod(methodCtx)); - continue; - } + var moduleNode = + AmendModuleNodeGen.create( + moduleInfo.getSourceSection(), + language, + annotationNodes, + moduleProperties, + moduleInfo, + supermoduleNode); - assert memberCtx instanceof ForGeneratorContext - || memberCtx instanceof WhenGeneratorContext - || memberCtx instanceof MemberPredicateContext - || memberCtx instanceof ObjectSpreadContext; - // bail out and create GeneratorObjectLiteralNode instead - // (but can't we easily reuse members/elements/keyNodes/values?) - return doVisitGeneratorObjectBody(ctx, parentNode); - } + return new ModuleNode( + language, moduleInfo.getSourceSection(), moduleInfo.getModuleName(), moduleNode); + } - var currentScope = symbolTable.getCurrentScope(); - var parametersDescriptor = - parametersDescriptorBuilder == null ? null : parametersDescriptorBuilder.build(); - if (!elements.isEmpty()) { - if (isConstantKeyNodes) { // true if zero key nodes - addConstantEntries(members, keyNodes, values); - //noinspection ConstantConditions - return ElementsLiteralNodeGen.create( - sourceSection, - language, - currentScope.getQualifiedName(), - currentScope.isCustomThisScope(), - parametersDescriptor, - parameterTypes, - members, - elements.toArray(new ObjectMember[0]), - parentNode); - } - //noinspection ConstantConditions - return ElementsEntriesLiteralNodeGen.create( - sourceSection, - language, - currentScope.getQualifiedName(), - currentScope.isCustomThisScope(), - parametersDescriptor, - parameterTypes, - members, - elements.toArray(new ObjectMember[0]), - keyNodes.toArray(new ExpressionNode[0]), - values.toArray(new ObjectMember[0]), - parentNode); - } + private EconomicMap doVisitModuleProperties( + List imports, + List classes, + List typeAliases, + List properties, + Set propertyNames, + ModuleInfo moduleInfo) { - if (!keyNodes.isEmpty()) { - if (isConstantKeyNodes) { - addConstantEntries(members, keyNodes, values); - //noinspection ConstantConditions - return ConstantEntriesLiteralNodeGen.create( - sourceSection, - language, - currentScope.getQualifiedName(), - currentScope.isCustomThisScope(), - parametersDescriptor, - parameterTypes, - members, - parentNode); - } - //noinspection ConstantConditions - return EntriesLiteralNodeGen.create( - sourceSection, - language, - currentScope.getQualifiedName(), - currentScope.isCustomThisScope(), - parametersDescriptor, - parameterTypes, - members, - keyNodes.toArray(new ExpressionNode[0]), - values.toArray(new ObjectMember[0]), - parentNode); - } - //noinspection ConstantConditions - return PropertiesLiteralNodeGen.create( - sourceSection, - language, - currentScope.getQualifiedName(), - currentScope.isCustomThisScope(), - parametersDescriptor, - parameterTypes, - members, - parentNode); - }); - } + var totalSize = imports.size() + classes.size() + typeAliases.size() + properties.size(); + var result = EconomicMaps.create(totalSize); - private void addConstantEntries( - EconomicMap members, - List keyNodes, - List values) { + for (var _import : imports) { + var member = visitImportClause(_import); + checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); + EconomicMaps.put(result, member.getName(), member); + } - 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(); + for (var clazz : classes) { + ObjectMember member = visitClass(clazz); + + if (moduleInfo.isAmend() && !member.isLocal()) { throw exceptionBuilder() - .evalError("duplicateDefinition", new ProgramValue("", key)) - .withSourceSection(value.getHeaderSection()) + .evalError("classMustBeLocal") + .withSourceSection(member.getHeaderSection()) .build(); } - } - } - - private ObjectMember doVisitObjectElement(ObjectElementContext ctx) { - var isForGeneratorScope = symbolTable.getCurrentScope().isForGeneratorScope(); - return symbolTable.enterEntry( - null, - scope -> { - var elementNode = visitExpr(ctx.expr()); - var modifier = VmModifier.ELEMENT; - var member = - new ObjectMember( - createSourceSection(ctx), - elementNode.getSourceSection(), - modifier, - null, - scope.getQualifiedName()); + checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); + EconomicMaps.put(result, member.getName(), member); + } - if (elementNode instanceof ConstantNode constantNode) { - member.initConstantValue(constantNode); - } else { - if (isForGeneratorScope) { - elementNode = new RestoreForBindingsNode(elementNode); - } - member.initMemberNode( - ElementOrEntryNodeGen.create( - language, scope.buildFrameDescriptor(), member, elementNode)); - } + for (var typeAlias : typeAliases) { + var member = visitTypeAlias(typeAlias); - return member; - }); - } + if (moduleInfo.isAmend() && !member.isLocal()) { + throw exceptionBuilder() + .evalError("typeAliasMustBeLocal") + .withSourceSection(member.getHeaderSection()) + .build(); + } - 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()); + checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); + EconomicMaps.put(result, member.getName(), member); } - var keyNode = visitExpr(ctx.k); - var member = doVisitObjectEntryBody(createSourceSection(ctx), keyNode, ctx.v, ctx.objectBody()); - return Pair.of(keyNode, member); - } + for (var ctx : properties) { + var member = + doVisitObjectProperty( + ctx, + ctx.getModifiers(), + ctx.getName(), + ctx.getTypeAnnotation(), + ctx.getExpr(), + ctx.getBodyList()); - private ObjectMember doVisitObjectEntryBody( - SourceSection sourceSection, - ExpressionNode keyNode, - @Nullable ExprContext valueCtx, - List objectBodyCtxs) { - var isForGeneratorScope = symbolTable.getCurrentScope().isForGeneratorScope(); - return symbolTable.enterEntry( - keyNode, + 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 modifier = VmModifier.ENTRY; - var member = + var modifiers = VmModifier.IMPORT | VmModifier.LOCAL | VmModifier.CONST; + if (imp.isGlob()) { + modifiers = modifiers | VmModifier.GLOB; + } + var result = new ObjectMember( - sourceSection, - keyNode.getSourceSection(), - modifier, - null, + importNode.getSourceSection(), + importNode.getSourceSection(), + modifiers, + scope.getName(), 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; + result.initMemberNode( + new UntypedObjectMemberNode( + language, scope.buildFrameDescriptor(), result, importNode)); + + return result; }); } @Override - public ExpressionNode visitAnnotation(AnnotationContext ctx) { - var verifyNode = new CheckIsAnnotationClassNode(visitType(ctx.type())); + public ObjectMember visitClass(Class clazz) { + var sourceSection = createSourceSection(clazz); + var headerSection = createSourceSection(clazz.getHeaderSpan()); - 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); - } + var bodyNode = clazz.getBody(); - return symbolTable.enterAnnotationScope((scope) -> doVisitObjectBody(bodyCtx, verifyNode)); + var typeParameters = visitTypeParameterList(clazz.getTypeParameterList()); + + List properties = bodyNode != null ? bodyNode.getProperties() : List.of(); + List methods = bodyNode != null ? bodyNode.getMethods() : List.of(); + + var modifiers = + doVisitModifiers( + clazz.getModifiers(), VmModifier.VALID_CLASS_MODIFIERS, "invalidClassModifier") + | VmModifier.CLASS; + + var className = + org.pkl.core.runtime.Identifier.property( + clazz.getName().getValue(), VmModifier.isLocal(modifiers)); + + return symbolTable.enterClass( + className, + typeParameters, + scope -> { + 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 == org.pkl.core.runtime.Identifier.ANY + ? null + : new UnresolvedTypeNode.Declared( + VmUtils.unavailableSourceSection(), + resolveBaseModuleClass( + org.pkl.core.runtime.Identifier.TYPED, BaseModule::getTypedClass)); + + if (!(supertypeNode == null + || supertypeNode instanceof UnresolvedTypeNode.Declared + || supertypeNode instanceof UnresolvedTypeNode.Parameterized + || supertypeNode instanceof UnresolvedTypeNode.Module)) { + throw exceptionBuilder() + .evalError("invalidSupertype", supertypeNode.getSourceSection().getCharacters()) + .withSourceSection(supertypeNode.getSourceSection()) + .build(); + } + + var classInfo = + PClassInfo.get( + moduleInfo.getModuleName(), + className.toString(), + moduleInfo.getModuleKey().getUri()); + var propertyNames = CollectionUtils.newHashSet(properties.size()); + + var classNode = + new ClassNode( + sourceSection, + headerSection, + createSourceSection(clazz.getDocComment()), + doVisitAnnotations(clazz.getAnnotations()), + modifiers, + classInfo, + typeParameters, + null, + supertypeNode, + EconomicMaps.create(), + doVisitClassProperties(properties, propertyNames), + doVisitMethodDefs(methods)); + + var isLocal = VmModifier.isLocal(modifiers); + + var result = + new ObjectMember( + sourceSection, + headerSection, + isLocal ? VmModifier.LOCAL_CLASS_OBJECT_MEMBER : VmModifier.CLASS_OBJECT_MEMBER, + scope.getName(), + scope.getQualifiedName()); + + result.initMemberNode( + new UntypedObjectMemberNode( + language, scope.buildFrameDescriptor(), result, classNode)); + + return result; + }); } - private ExpressionNode[] doVisitAnnotations(List ctxs) { - return ctxs.stream().map(this::visitAnnotation).toArray(ExpressionNode[]::new); + 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 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); + 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 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 + private UnresolvedPropertyNode[] doVisitClassProperties( + List propertyContexts, Set propertyNames) { + var propertyNodes = new UnresolvedPropertyNode[propertyContexts.size()]; - if (VmModifier.isExternal(result) && !ModuleKeys.isStdLibModule(moduleKey)) { - throw exceptionBuilder() - .evalError("cannotDefineExternalMember") - .withSourceSection(createSourceSection(contexts, PklLexer.EXTERNAL)) - .build(); + for (var i = 0; i < propertyNodes.length; i++) { + var propertyNode = visitClassProperty(propertyContexts.get(i)); + checkDuplicateMember(propertyNode.getName(), propertyNode.getHeaderSection(), propertyNames); + propertyNodes[i] = propertyNode; } - if (VmModifier.isLocal(result) && VmModifier.isHidden(result)) { - throw exceptionBuilder() - .evalError("redundantHiddenModifier") - .withSourceSection(createSourceSection(contexts, PklLexer.HIDDEN_)) - .build(); - } + return propertyNodes; + } - if (VmModifier.isLocal(result) && VmModifier.isFixed(result)) { - throw exceptionBuilder() - .evalError("redundantFixedModifier") - .withSourceSection(createSourceSection(contexts, PklLexer.FIXED)) - .build(); - } + private UnresolvedMethodNode[] doVisitMethodDefs(List methodDefs) { + var methodNodes = new UnresolvedMethodNode[methodDefs.size()]; + var methodNames = CollectionUtils.newHashSet(methodDefs.size()); - if (VmModifier.isAbstract(result) && VmModifier.isOpen(result)) { - throw exceptionBuilder() - .evalError("redundantOpenModifier") - .withSourceSection(createSourceSection(contexts, PklLexer.OPEN)) - .build(); + for (var i = 0; i < methodNodes.length; i++) { + var methodNode = visitClassMethod(methodDefs.get(i)); + checkDuplicateMember(methodNode.getName(), methodNode.getHeaderSection(), methodNames); + methodNodes[i] = methodNode; } - return result; + return methodNodes; } @Override - public UnresolvedMethodNode visitClassMethod(ClassMethodContext ctx) { - var headerCtx = ctx.methodHeader(); - var headerSection = createSourceSection(headerCtx); - - var typeParameters = visitTypeParameterList(headerCtx.typeParameterList()); + 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 = createSourceSection(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(), VmModifier.VALID_METHOD_MODIFIERS, "invalidMethodModifier"); + modifierList, VmModifier.VALID_PROPERTY_MODIFIERS, "invalidPropertyModifier"); var isLocal = VmModifier.isLocal(modifiers); - var methodName = Identifier.method(headerCtx.Identifier().getText(), isLocal); + var propertyName = org.pkl.core.runtime.Identifier.property(name.getValue(), isLocal); - var bodyContext = ctx.expr(); - var paramListCtx = headerCtx.parameterList(); - var descriptorBuilder = createFrameDescriptorBuilder(paramListCtx); - var paramCount = paramListCtx.ts.size(); - - return symbolTable.enterMethod( - methodName, + return symbolTable.enterProperty( + propertyName, getConstLevel(modifiers), - descriptorBuilder, - typeParameters, scope -> { ExpressionNode bodyNode; - if (bodyContext != null) { + + if (expr != null) { // prop = expr if (VmModifier.isExternal(modifiers)) { throw exceptionBuilder() .evalError("externalMemberCannotHaveBody") @@ -1404,1242 +1777,1040 @@ public UnresolvedMethodNode visitClassMethod(ClassMethodContext ctx) { .withSourceSection(headerSection) .build(); } - bodyNode = visitExpr(bodyContext); - } else { + bodyNode = visitExpr(expr); + } else if (objectBodies != null && !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.getFunctionBody( - scope.getQualifiedName(), headerSection, paramCount); + externalMemberRegistry.getPropertyBody(scope.getQualifiedName(), headerSection); if (bodyNode instanceof LanguageAwareNode languageAwareNode) { languageAwareNode.initLanguage(language); } } else if (VmModifier.isAbstract(modifiers)) { bodyNode = - new CannotInvokeAbstractFunctionNode(headerSection, scope.getQualifiedName()); + new CannotInvokeAbstractPropertyNode(headerSection, scope.getQualifiedName()); } else { - throw exceptionBuilder() - .evalError("missingMethodBody", methodName) - .withSourceSection(headerSection) - .build(); + bodyNode = null; // will be given a default by UnresolvedPropertyNode } } - return new UnresolvedMethodNode( + var typeAnnNode = visitTypeAnnotation(typeAnnotation); + + return new UnresolvedPropertyNode( language, - createSourceSection(ctx), + sourceSection, headerSection, + createSourceSection(name), scope.buildFrameDescriptor(), - createSourceSection(ctx.t), - doVisitAnnotations(ctx.annotation()), + docComment, + annotationNodes, modifiers, - methodName, + scope.getName(), scope.getQualifiedName(), - paramCount, - typeParameters, - doVisitParameterTypes(paramListCtx), - visitTypeAnnotation(headerCtx.typeAnnotation()), - isMethodReturnTypeChecked, + typeAnnNode, bodyNode); }); } @Override - public ExpressionNode visitFunctionLiteral(FunctionLiteralContext ctx) { - var sourceSection = createSourceSection(ctx); - var paramCtx = ctx.parameterList(); - var descriptorBuilder = createFrameDescriptorBuilder(paramCtx); - var paramCount = paramCtx.ts.size(); + public UnresolvedMethodNode visitClassMethod(ClassMethod entry) { + var headerSection = createSourceSection(entry.getHeaderSpan()); - if (paramCount > 5) { - throw exceptionBuilder() - .evalError("tooManyFunctionParameters") - .withSourceSection(sourceSection) - .build(); - } + var typeParameters = visitTypeParameterList(entry.getTypeParameterList()); - var isCustomThisScope = symbolTable.getCurrentScope().isCustomThisScope(); + var modifiers = + doVisitModifiers( + entry.getModifiers(), VmModifier.VALID_METHOD_MODIFIERS, "invalidMethodModifier"); - 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); + var isLocal = VmModifier.isLocal(modifiers); + var methodName = org.pkl.core.runtime.Identifier.method(entry.getName().getValue(), isLocal); - return new FunctionLiteralNode(sourceSection, functionNode, isCustomThisScope); + 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(), + createSourceSection(entry.getDocComment()), + doVisitAnnotations(entry.getAnnotations()), + modifiers, + methodName, + scope.getQualifiedName(), + paramCount, + typeParameters, + doVisitParameterTypes(paramListCtx), + visitTypeAnnotation(entry.getTypeAnnotation()), + isMethodReturnTypeChecked, + bodyNode); }); } @Override - public ConstantValueNode visitNullLiteral(NullLiteralContext ctx) { - return new ConstantValueNode(createSourceSection(ctx), VmNull.withoutDefault()); - } + public ObjectMember visitTypeAlias(TypeAlias typeAlias) { + var sourceSection = createSourceSection(typeAlias); + var headerSection = createSourceSection(typeAlias.getHeaderSpan()); - @Override - public ExpressionNode visitTrueLiteral(TrueLiteralContext ctx) { - return new TrueLiteralNode(createSourceSection(ctx)); - } + var modifiers = + doVisitModifiers( + typeAlias.getModifiers(), + VmModifier.VALID_TYPE_ALIAS_MODIFIERS, + "invalidTypeAliasModifier") + | VmModifier.TYPE_ALIAS; - @Override - public Object visitFalseLiteral(FalseLiteralContext ctx) { - return new FalseLiteralNode(createSourceSection(ctx)); - } + var isLocal = VmModifier.isLocal(modifiers); + var name = org.pkl.core.runtime.Identifier.property(typeAlias.getName().getValue(), isLocal); - @Override - public IntLiteralNode visitIntLiteral(IntLiteralContext ctx) { - var section = createSourceSection(ctx); - var text = ctx.IntLiteral().getText(); + var typeParameters = visitTypeParameterList(typeAlias.getTypeParameterList()); - 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; - } + return symbolTable.enterTypeAlias( + name, + typeParameters, + scope -> { + var scopeName = scope.getName(); + var typeAliasNode = + new TypeAliasNode( + sourceSection, + headerSection, + createSourceSection(typeAlias.getDocComment()), + doVisitAnnotations(typeAlias.getAnnotations()), + modifiers, + scopeName.toString(), + scope.getQualifiedName(), + typeParameters, + visitType(typeAlias.getType())); - text = text.substring(2); - if (text.startsWith("_")) { - invalidSeparatorPosition(source.createSection(ctx.getStart().getStartIndex() + 2, 1)); - } - } + var result = + new ObjectMember( + sourceSection, + headerSection, + isLocal + ? VmModifier.LOCAL_TYPEALIAS_OBJECT_MEMBER + : VmModifier.TYPEALIAS_OBJECT_MEMBER, + scopeName, + scope.getQualifiedName()); - // 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; - } + result.initMemberNode( + new UntypedObjectMemberNode( + language, scope.buildFrameDescriptor(), result, typeAliasNode)); - 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(); - } + return result; + }); } @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; - } + public ExpressionNode visitAnnotation(Annotation annotation) { + var verifyNode = new CheckIsAnnotationClassNode(visitType(annotation.getType())); - 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)); + 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); } - 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(); - } + return symbolTable.enterAnnotationScope((scope) -> doVisitObjectBody(bodyCtx, verifyNode)); } - @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)); - } + 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; + } - return new InterpolatedStringLiteralNode( - createSourceSection(ctx), - singleParts.stream().map(this::visitSingleLineStringPart).toArray(ExpressionNode[]::new)); + @Override + public UnresolvedTypeNode visitType(Type type) { + return (UnresolvedTypeNode) type.accept(this); } @Override - public Object visitMultiLineStringLiteral(MultiLineStringLiteralContext ctx) { - var multiPart = ctx.multiLineStringPart(); + public ExpressionNode visitExpr(Expr expr) { + return (ExpressionNode) expr.accept(this); + } - if (multiPart.isEmpty()) { - throw exceptionBuilder() - .evalError("stringContentMustBeginOnNewLine") - .withSourceSection(createSourceSection(ctx.t2)) - .build(); - } + @Override + public List visitTypeParameterList(@Nullable TypeParameterList ctx) { + if (ctx == null) return List.of(); - var firstPart = multiPart.get(0); - if (firstPart.e != null || firstPart.ts.get(0).getType() != PklLexer.MLNewline) { + if (!(ctx.parent() instanceof TypeAlias) && !isStdLibModule) { throw exceptionBuilder() - .evalError("stringContentMustBeginOnNewLine") - .withSourceSection( - firstPart.e != null - ? startOf(firstPart.MLInterpolation()) - : startOf(firstPart.ts.get(0))) + .evalError("cannotDeclareTypeParameter") + .withSourceSection(createSourceSection(ctx.getParameters().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); + var params = ctx.getParameters(); + var size = params.size(); + var result = new ArrayList(size); + for (var i = 0; i < size; i++) { + var paramCtx = params.get(i); + Variance variance; + var nodeVariance = paramCtx.getVariance(); + if (nodeVariance == null) { + variance = TypeParameter.Variance.INVARIANT; + } else { + variance = + switch (nodeVariance) { + case IN -> TypeParameter.Variance.CONTRAVARIANT; + case OUT -> TypeParameter.Variance.COVARIANT; + }; + } + var parameterName = paramCtx.getIdentifier().getValue(); + if (result.stream().anyMatch(it -> it.getName().equals(parameterName))) { + throw exceptionBuilder() + .evalError("duplicateTypeParameter", parameterName) + .withSourceSection(createSourceSection(paramCtx)) + .build(); + } + result.add(new TypeParameter(variance, parameterName, i)); } - - return new InterpolatedStringLiteralNode(createSourceSection(ctx), multiPartExprs); + return result; } @Override - public String visitStringConstant(StringConstantContext ctx) { - checkSingleLineStringDelimiters(ctx.t, ctx.t2); - return doVisitSingleLineConstantStringPart(ctx.ts); + public @Nullable UnresolvedTypeNode visitTypeAnnotation(@Nullable TypeAnnotation typeAnnotation) { + return typeAnnotation == null ? null : visitType(typeAnnotation.getType()); } @Override - public ExpressionNode visitSingleLineStringPart(SingleLineStringPartContext ctx) { - if (ctx.e != null) { - return ToStringNodeGen.create(createSourceSection(ctx), visitExpr(ctx.e)); + 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 new ConstantValueNode( - createSourceSection(ctx), doVisitSingleLineConstantStringPart(ctx.ts)); + return res; } - @Override - public ExpressionNode visitMultiLineStringPart(MultiLineStringPartContext ctx) { - throw exceptionBuilder().unreachableCode().build(); + 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); + } + 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())); + } + default -> + throw exceptionBuilder() + .evalError("invalidTypeName", ctx.text()) + .withSourceSection(createSourceSection(ctx)) + .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 doVisitObjectBody( + List bodies, ExpressionNode parentNode) { + for (var ctx : bodies) { + parentNode = doVisitObjectBody(ctx, parentNode); + } + return parentNode; } - private ExpressionNode doVisitListLiteral(ExprContext ctx, ArgumentListContext argListCtx) { - var elementNodes = createCollectionArgumentNodes(argListCtx); + private ExpressionNode doVisitObjectBody(ObjectBody body, ExpressionNode parentNode) { + return symbolTable.enterObjectScope( + (scope) -> { + var objectMembers = body.getMembers(); + if (objectMembers.isEmpty()) { + return EmptyObjectLiteralNodeGen.create(createSourceSection(body.parent()), parentNode); + } + var sourceSection = createSourceSection(body.parent()); - if (elementNodes.first.length == 0) { - return new ConstantValueNode(VmList.EMPTY); - } + var parametersDescriptorBuilder = createFrameDescriptorBuilder(body); + var parameterTypes = doVisitParameterTypes(body); - return elementNodes.second - ? new ConstantValueNode( - createSourceSection(ctx), VmList.createFromConstantNodes(elementNodes.first)) - : new ListLiteralNode(createSourceSection(ctx), elementNodes.first); - } + var members = EconomicMaps.create(); + var elements = new ArrayList(); + var keyNodes = new ArrayList(); + var values = new ArrayList(); + var isConstantKeyNodes = true; - private ExpressionNode doVisitSetLiteral(ExprContext ctx, ArgumentListContext argListCtx) { - var elementNodes = createCollectionArgumentNodes(argListCtx); + checkSpaceSeparatedObjectMembers(body); + for (var memberCtx : objectMembers) { + if (memberCtx instanceof ObjectProperty property) { + addProperty(members, doVisitObjectProperty(property)); + continue; + } - if (elementNodes.first.length == 0) { - return new ConstantValueNode(VmSet.EMPTY); - } + if (memberCtx instanceof ObjectEntry entry) { + var keyAndValue = doVisitObjectEntry(entry); + var key = keyAndValue.first; + keyNodes.add(key); + isConstantKeyNodes = isConstantKeyNodes && key instanceof ConstantNode; + values.add(keyAndValue.second); + continue; + } - return elementNodes.second - ? new ConstantValueNode( - createSourceSection(ctx), VmSet.createFromConstantNodes(elementNodes.first)) - : new SetLiteralNode(createSourceSection(ctx), elementNodes.first); - } + if (memberCtx instanceof ObjectElement elementCtx) { + var element = doVisitObjectElement(elementCtx); + elements.add(element); + continue; + } - private ExpressionNode doVisitMapLiteral(ExprContext ctx, ArgumentListContext argListCtx) { - var keyAndValueNodes = createCollectionArgumentNodes(argListCtx); + if (memberCtx instanceof ObjectMethod methodCtx) { + addProperty(members, doVisitObjectMethod(methodCtx)); + continue; + } - if (keyAndValueNodes.first.length == 0) { - return new ConstantValueNode(VmMap.EMPTY); - } + 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(body, parentNode); + } - 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 currentScope = symbolTable.getCurrentScope(); + var parametersDescriptor = + parametersDescriptorBuilder == null ? null : parametersDescriptorBuilder.build(); + if (!elements.isEmpty()) { + if (isConstantKeyNodes) { // true if zero key nodes + addConstantEntries(members, keyNodes, values); + //noinspection ConstantConditions + return ElementsLiteralNodeGen.create( + sourceSection, + language, + currentScope.getQualifiedName(), + currentScope.isCustomThisScope(), + parametersDescriptor, + parameterTypes, + members, + elements.toArray(new ObjectMember[0]), + parentNode); + } + //noinspection ConstantConditions + return ElementsEntriesLiteralNodeGen.create( + sourceSection, + language, + currentScope.getQualifiedName(), + currentScope.isCustomThisScope(), + parametersDescriptor, + parameterTypes, + members, + elements.toArray(new ObjectMember[0]), + keyNodes.toArray(new ExpressionNode[0]), + values.toArray(new ObjectMember[0]), + parentNode); } - var result = - new ObjectMember( - importNode.getSourceSection(), - importNode.getSourceSection(), - modifiers, - scope.getName(), - scope.getQualifiedName()); - - result.initMemberNode( - new UntypedObjectMemberNode( - language, scope.buildFrameDescriptor(), result, importNode)); - - return result; - }); - } - - 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; - } - - @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); - } - - 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( + if (!keyNodes.isEmpty()) { + if (isConstantKeyNodes) { + addConstantEntries(members, keyNodes, values); + //noinspection ConstantConditions + return ConstantEntriesLiteralNodeGen.create( + sourceSection, + language, + currentScope.getQualifiedName(), + currentScope.isCustomThisScope(), + parametersDescriptor, + parameterTypes, + members, + parentNode); + } + //noinspection ConstantConditions + return EntriesLiteralNodeGen.create( + sourceSection, + language, + currentScope.getQualifiedName(), + currentScope.isCustomThisScope(), + parametersDescriptor, + parameterTypes, + members, + keyNodes.toArray(new ExpressionNode[0]), + values.toArray(new ObjectMember[0]), + parentNode); + } + //noinspection ConstantConditions + return PropertiesLiteralNodeGen.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)); + language, + currentScope.getQualifiedName(), + currentScope.isCustomThisScope(), + parametersDescriptor, + parameterTypes, + members, + parentNode); + }); } - private ExpressionNode doVisitPropertyInvocationExpr(QualifiedAccessExprContext ctx) { - var sourceSection = createSourceSection(ctx); - var propertyName = toIdentifier(ctx.Identifier()); - var receiver = visitExpr(ctx.expr()); - - if (receiver instanceof IntLiteralNode intLiteralNode) { - var durationUnit = VmDuration.toUnit(propertyName); - if (durationUnit != null) { - //noinspection ConstantConditions - return new ConstantValueNode( - sourceSection, new VmDuration(intLiteralNode.executeInt(null), durationUnit)); - } - var dataSizeUnit = VmDataSize.toUnit(propertyName); - if (dataSizeUnit != null) { - //noinspection ConstantConditions - return new ConstantValueNode( - sourceSection, - new VmDataSize(((IntLiteralNode) receiver).executeInt(null), dataSizeUnit)); - } - } - - if (receiver instanceof FloatLiteralNode floatLiteralNode) { - var durationUnit = VmDuration.toUnit(propertyName); - if (durationUnit != null) { - //noinspection ConstantConditions - return new ConstantValueNode( - sourceSection, new VmDuration(floatLiteralNode.executeFloat(null), durationUnit)); - } - var dataSizeUnit = VmDataSize.toUnit(propertyName); - if (dataSizeUnit != null) { - //noinspection ConstantConditions - return new ConstantValueNode( - sourceSection, - new VmDataSize(((FloatLiteralNode) receiver).executeFloat(null), dataSizeUnit)); - } - } - - var needsConst = needsConst(receiver); - if (ctx.t.getType() == PklLexer.QDOT) { - return new NullPropagatingOperationNode( - sourceSection, - ReadPropertyNodeGen.create( - sourceSection, - propertyName, - needsConst, - PropagateNullReceiverNodeGen.create(unavailableSourceSection(), receiver))); + private void checkSpaceSeparatedObjectMembers(ObjectBody objectBody) { + var members = objectBody.getMembers(); + if (members.size() < 2) { + return; } - - 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; - - if (argCtx != null) { // supermethod call - if (!symbolTable.getCurrentScope().isClassMemberScope()) { + 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("cannotInvokeSupermethodFromHere") - .withSourceSection(sourceSection) + .evalError("unseparatedObjectMembers") + .withSourceSection(createSourceSection(member.span())) .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); - - 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); - } - - // 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); - } - - if (identifier == Identifier.SET) { - return doVisitSetLiteral(ctx, argListCtx); - } - - if (identifier == Identifier.MAP) { - return doVisitMapLiteral(ctx, argListCtx); - } - - var scope = symbolTable.getCurrentScope(); - - return new ResolveMethodNode( - createSourceSection(ctx), - identifier, - visitArgumentList(argListCtx), - isBaseModule, - scope.isCustomThisScope(), - scope.getConstLevel(), - scope.getConstDepth()); - } - - @Override - public ExpressionNode[] visitArgumentList(ArgumentListContext ctx) { - checkCommaSeparatedElements(ctx, ctx.es, ctx.errs); - checkClosingDelimiter(ctx.err, ")", ctx.stop); - - return ctx.es.stream().map(this::visitExpr).toArray(ExpressionNode[]::new); - } - - private boolean needsConst(ExpressionNode receiver) { - var scope = symbolTable.getCurrentScope(); - var constLevel = scope.getConstLevel(); - var needsConst = false; - if (receiver instanceof OuterNode) { - var outerScope = getParentLexicalScope(); - if (outerScope != null) { - needsConst = - switch (constLevel) { - case MODULE -> outerScope.isModuleScope(); - case ALL -> outerScope.getConstLevel() != ConstLevel.ALL; - case NONE -> false; - }; - } - } else if (receiver instanceof GetModuleNode) { - needsConst = constLevel != ConstLevel.NONE; - } else if (receiver instanceof ThisNode) { - var constDepth = scope.getConstDepth(); - needsConst = constLevel == ConstLevel.ALL && constDepth == -1; - } - return needsConst; - } - - private Identifier toIdentifier(TerminalNode node) { - return Identifier.get(node.getText()); - } - - private Identifier toIdentifier(Token token) { - return Identifier.get(token.getText()); + } + previous = member.span(); + } } - 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 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 builder; + return doVisitObjectProperty( + createSourceSection(prop), + createSourceSection(propertyName), + modifiers, + propertyName.getValue(), + prop.getTypeAnnotation(), + prop.getExpr(), + prop.getBodyList()); } - private @Nullable FrameDescriptor.Builder createFrameDescriptorBuilder(ObjectBodyContext ctx) { - if (ctx.ps.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); + 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 builder; + return doVisitObjectProperty( + createSourceSection(node), + createSourceSection(propertyName), + modifiers, + propertyName.getValue(), + typeAnn, + expr, + bodies); } - private UnresolvedTypeNode[] doVisitParameterTypes(ParameterListContext ctx) { - return ctx.ts.stream() - .map( - it -> isIgnored(it) ? null : visitTypeAnnotation(it.typedIdentifier().typeAnnotation())) - .toArray(UnresolvedTypeNode[]::new); - } + private ObjectMember doVisitObjectProperty( + SourceSection sourceSection, + SourceSection headerSection, + int modifiers, + String propertyName, + @Nullable TypeAnnotation typeAnn, + @Nullable Expr expr, + @Nullable List body) { - private UnresolvedTypeNode[] doVisitParameterTypes(ObjectBodyContext ctx) { - return ctx.ps.stream() - .map( - it -> isIgnored(it) ? null : visitTypeAnnotation(it.typedIdentifier().typeAnnotation())) - .toArray(UnresolvedTypeNode[]::new); - } + var isLocal = VmModifier.isLocal(modifiers); + var identifier = org.pkl.core.runtime.Identifier.property(propertyName, isLocal); - @Override - public Object visitTypedIdentifier(TypedIdentifierContext ctx) { - throw exceptionBuilder().unreachableCode().build(); // handled directly - } + 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(); + } + } - @Override - public Object visitThrowExpr(ThrowExprContext ctx) { - var exprCtx = ctx.expr(); - checkClosingDelimiter(ctx.err, ")", exprCtx.stop); + 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 ThrowNodeGen.create(createSourceSection(ctx), visitExpr(exprCtx)); + 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); + }); } - @Override - public Object visitTraceExpr(TraceExprContext ctx) { - var exprCtx = ctx.expr(); - checkClosingDelimiter(ctx.err, ")", exprCtx.stop); - - return new TraceNode(createSourceSection(ctx), visitExpr(exprCtx)); - } + private Pair doVisitObjectEntry(ObjectEntry entry) { + var keyNode = visitExpr(entry.getKey()); - @Override - public Object visitImportExpr(ImportExprContext ctx) { - var importUriCtx = ctx.stringConstant(); - checkClosingDelimiter(ctx.err, ")", importUriCtx.stop); - return doVisitImport(ctx.t.getType(), ctx, importUriCtx); + var member = + doVisitObjectEntryBody( + createSourceSection(entry), keyNode, entry.getValue(), entry.getBodyList()); + return Pair.of(keyNode, member); } - @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)); - } + private ObjectMember doVisitObjectElement(ObjectElement element) { + var isForGeneratorScope = symbolTable.getCurrentScope().isForGeneratorScope(); + return symbolTable.enterEntry( + null, + scope -> { + var elementNode = visitExpr(element.getExpr()); - @Override - public Object visitReadExpr(ReadExprContext ctx) { - var exprCtx = ctx.expr(); - checkClosingDelimiter(ctx.err, ")", exprCtx.stop); + var modifier = VmModifier.ELEMENT; + var member = + new ObjectMember( + createSourceSection(element), + elementNode.getSourceSection(), + modifier, + null, + scope.getQualifiedName()); - var tokenType = ctx.t.getType(); + if (elementNode instanceof ConstantNode constantNode) { + member.initConstantValue(constantNode); + } else { + if (isForGeneratorScope) { + elementNode = new RestoreForBindingsNode(elementNode); + } + member.initMemberNode( + ElementOrEntryNodeGen.create( + language, scope.buildFrameDescriptor(), member, elementNode)); + } - if (tokenType == PklLexer.READ) { - return ReadNodeGen.create(createSourceSection(ctx), moduleKey, visitExpr(exprCtx)); - } - if (tokenType == PklLexer.READ_OR_NULL) { - return ReadOrNullNodeGen.create(createSourceSection(ctx), moduleKey, visitExpr(exprCtx)); - } - assert tokenType == PklLexer.READ_GLOB; - return ReadGlobNodeGen.create(createSourceSection(ctx), moduleKey, visitExpr(exprCtx)); + return member; + }); } - @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); + private ObjectMember doVisitObjectMethod(ObjectMethod method) { + return doVisitObjectMethod(method, false); } - @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()); + 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); } - // 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)); - } + 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( + modifierNodes, VmModifier.VALID_OBJECT_MEMBER_MODIFIERS, "invalidObjectMemberModifier"); - @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"; + if (!VmModifier.isLocal(modifiers)) { throw exceptionBuilder() - .evalError(messageKey) - .withSourceSection(createSourceSection(ctx)) + .evalError(isModuleMethod ? "moduleMethodMustBeLocal" : "objectMethodMustBeLocal") + .withSourceSection(createSourceSection(headerSpan)) .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)); - } + var methodName = org.pkl.core.runtime.Identifier.method(identifier.getValue(), true); - assert ctx.t.getType() == PklLexer.AS; - return new TypeCastNode(createSourceSection(ctx), visitExpr(ctx.l), visitType(ctx.r)); - } + var frameDescriptorBuilder = createFrameDescriptorBuilder(paramList); - @Override - public Object visitUnknownType(UnknownTypeContext ctx) { - return new UnresolvedTypeNode.Unknown(createSourceSection(ctx)); - } + return symbolTable.enterMethod( + methodName, + getConstLevel(modifiers), + frameDescriptorBuilder, + List.of(), + scope -> { + if (typeParamList != null) { + throw exceptionBuilder() + .evalError("cannotDeclareTypeParameter") + .withSourceSection(createSourceSection(typeParamList)) + .build(); + } - @Override - public Object visitNothingType(NothingTypeContext ctx) { - return new UnresolvedTypeNode.Nothing(createSourceSection(ctx)); - } + var member = + new ObjectMember( + 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)); - @Override - public Object visitModuleType(ModuleTypeContext ctx) { - return new UnresolvedTypeNode.Module(createSourceSection(ctx)); + member.initMemberNode(node); + return member; + }); } - @Override - public Object visitStringLiteralType(StringLiteralTypeContext ctx) { - return new UnresolvedTypeNode.StringLiteral( - createSourceSection(ctx), visitStringConstant(ctx.stringConstant())); + 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 UnresolvedTypeNode visitType(TypeContext ctx) { - return (UnresolvedTypeNode) ctx.accept(this); + 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 UnresolvedTypeNode visitDeclaredType(DeclaredTypeContext ctx) { - var idCtx = ctx.qualifiedIdentifier(); - var argCtx = ctx.typeArgumentList(); + private ExpressionNode doVisitPropertyInvocationExpr(QualifiedAccessExpr expr) { + var sourceSection = createSourceSection(expr); + var propertyName = toIdentifier(expr.getIdentifier().getValue()); + var receiver = visitExpr(expr.getExpr()); - 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); - } + if (receiver instanceof IntLiteralNode intLiteralNode) { + var durationUnit = VmDuration.toUnit(propertyName); + if (durationUnit != null) { + //noinspection ConstantConditions + return new ConstantValueNode( + sourceSection, new VmDuration(intLiteralNode.executeInt(null), durationUnit)); + } + var dataSizeUnit = VmDataSize.toUnit(propertyName); + if (dataSizeUnit != null) { + //noinspection ConstantConditions + return new ConstantValueNode( + sourceSection, + new VmDataSize(((IntLiteralNode) receiver).executeInt(null), dataSizeUnit)); } + } - return new UnresolvedTypeNode.Declared(createSourceSection(ctx), doVisitTypeName(idCtx)); + if (receiver instanceof FloatLiteralNode floatLiteralNode) { + var durationUnit = VmDuration.toUnit(propertyName); + if (durationUnit != null) { + //noinspection ConstantConditions + return new ConstantValueNode( + sourceSection, new VmDuration(floatLiteralNode.executeFloat(null), durationUnit)); + } + var dataSizeUnit = VmDataSize.toUnit(propertyName); + if (dataSizeUnit != null) { + //noinspection ConstantConditions + return new ConstantValueNode( + sourceSection, + new VmDataSize(((FloatLiteralNode) receiver).executeFloat(null), dataSizeUnit)); + } } - checkCommaSeparatedElements(argCtx, argCtx.ts, argCtx.errs); - checkClosingDelimiter(argCtx.err, ">", argCtx.stop); + var needsConst = needsConst(receiver); + if (expr.isNullable()) { + return new NullPropagatingOperationNode( + sourceSection, + ReadPropertyNodeGen.create( + sourceSection, + propertyName, + needsConst, + PropagateNullReceiverNodeGen.create(unavailableSourceSection(), receiver))); + } - return new UnresolvedTypeNode.Parameterized( - createSourceSection(ctx), - language, - doVisitTypeName(idCtx), - argCtx.ts.stream().map(this::visitType).toArray(UnresolvedTypeNode[]::new)); + return ReadPropertyNodeGen.create(sourceSection, propertyName, needsConst, receiver); } - @Override - public UnresolvedTypeNode visitParenthesizedType(ParenthesizedTypeContext ctx) { - checkClosingDelimiter(ctx.err, ")", ctx.stop); + 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); - return visitType(ctx.type()); - } + 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))); + } - @Override - public Object visitDefaultUnionType(DefaultUnionTypeContext ctx) { - throw exceptionBuilder() - .evalError("notAUnion") - .withSourceSection(createSourceSection(ctx)) - .build(); + //noinspection ConstantConditions + return InvokeMethodVirtualNodeGen.create( + sourceSection, + functionName, + visitArgumentList(argCtx), + MemberLookupMode.EXPLICIT_RECEIVER, + needsConst, + receiver, + GetClassNodeGen.create(null)); } - @Override - public UnresolvedTypeNode visitUnionType(UnionTypeContext ctx) { - var elementTypeCtxs = new ArrayList(); - - var result = flattenUnionType(ctx, elementTypeCtxs); - boolean isUnionOfStringLiterals = result.first; - int defaultIndex = result.second; + private void addConstantEntries( + EconomicMap members, + List keyNodes, + List values) { - if (isUnionOfStringLiterals) { - return new UnresolvedTypeNode.UnionOfStringLiterals( - createSourceSection(ctx), - defaultIndex, - elementTypeCtxs.stream() - .map(it -> visitStringConstant(((StringLiteralTypeContext) it).stringConstant())) - .collect(Collectors.toCollection(LinkedHashSet::new))); + 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("duplicateDefinition", new ProgramValue("", key)) + .withSourceSection(value.getHeaderSection()) + .build(); + } } - - 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); + private int doVisitModifiers( + List modifiers, int validModifiers, String errorMessage) { - 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); + 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(); } - index++; + result += modifier; } - return Pair.of(isUnionOfStringLiterals, defaultIndex); - } - @Override - public UnresolvedTypeNode visitNullableType(NullableTypeContext ctx) { - return new UnresolvedTypeNode.Nullable( - createSourceSection(ctx), (UnresolvedTypeNode) ctx.type().accept(this)); - } + // flag modifier combinations that are never valid right away - @Override - public UnresolvedTypeNode visitConstrainedType(ConstrainedTypeContext ctx) { - checkCommaSeparatedElements(ctx, ctx.es, ctx.errs); - checkClosingDelimiter(ctx.err, ")", ctx.stop); + if (VmModifier.isExternal(result) && !ModuleKeys.isStdLibModule(moduleKey)) { + throw exceptionBuilder() + .evalError("cannotDefineExternalMember") + .withSourceSection(createSourceSection(modifiers, ModifierValue.EXTERNAL)) + .build(); + } - var childNode = (UnresolvedTypeNode) ctx.type().accept(this); + if (VmModifier.isLocal(result) && VmModifier.isHidden(result)) { + throw exceptionBuilder() + .evalError("redundantHiddenModifier") + .withSourceSection(createSourceSection(modifiers, ModifierValue.HIDDEN)) + .build(); + } - 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))); - } + if (VmModifier.isLocal(result) && VmModifier.isFixed(result)) { + throw exceptionBuilder() + .evalError("redundantFixedModifier") + .withSourceSection(createSourceSection(modifiers, ModifierValue.FIXED)) + .build(); + } - @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); + if (VmModifier.isAbstract(result) && VmModifier.isOpen(result)) { + throw exceptionBuilder() + .evalError("redundantOpenModifier") + .withSourceSection(createSourceSection(modifiers, ModifierValue.OPEN)) + .build(); + } - return new UnresolvedTypeNode.Function( - createSourceSection(ctx), - ctx.ps.stream().map(this::visitType).toArray(UnresolvedTypeNode[]::new), - (UnresolvedTypeNode) ctx.r.accept(this)); + return result; } - 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 UnresolvedTypeNode[] doVisitParameterTypes(ObjectBody body) { + return doVisitParameterTypes(body.getParameters()); } - private UnresolvedPropertyNode[] doVisitClassProperties( - List propertyContexts, Set propertyNames) { - var propertyNodes = new UnresolvedPropertyNode[propertyContexts.size()]; + private UnresolvedTypeNode[] doVisitParameterTypes(ParameterList paramList) { + return doVisitParameterTypes(paramList.getParameters()); + } - 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; + 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 propertyNodes; + return typeNodes; } - 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; + // 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(); } - - 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); - } + 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"] { ... } + var objectBody = + doVisitObjectBody( + objectBodyCtxs, + new ReadSuperEntryNode(unavailableSourceSection(), new GetMemberKeyNode())); + if (isForGeneratorScope) { + objectBody = new RestoreForBindingsNode(objectBody); + } + member.initMemberNode( + ElementOrEntryNodeGen.create( + language, scope.buildFrameDescriptor(), member, objectBody)); + } - for (var ctx : classCtxs) { - ObjectMember member = visitClazz(ctx); + return member; + }); + } - if (moduleInfo.isAmend() && !member.isLocal()) { - throw exceptionBuilder() - .evalError("classMustBeLocal") - .withSourceSection(member.getHeaderSection()) - .build(); + private boolean needsConst(ExpressionNode receiver) { + var scope = symbolTable.getCurrentScope(); + var constLevel = scope.getConstLevel(); + var needsConst = false; + if (receiver instanceof OuterNode) { + var outerScope = getParentLexicalScope(); + if (outerScope != null) { + needsConst = + switch (constLevel) { + case MODULE -> outerScope.isModuleScope(); + case ALL -> outerScope.getConstLevel() != ConstLevel.ALL; + case NONE -> false; + }; } - - checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); - EconomicMaps.put(result, member.getName(), member); + } else if (receiver instanceof GetModuleNode) { + needsConst = constLevel != ConstLevel.NONE; + } else if (receiver instanceof ThisNode) { + var constDepth = scope.getConstDepth(); + needsConst = constLevel == ConstLevel.ALL && constDepth == -1; } + return needsConst; + } - for (TypeAliasContext ctx : typeAliasCtxs) { - var member = visitTypeAlias(ctx); - - if (moduleInfo.isAmend() && !member.isLocal()) { - throw exceptionBuilder() - .evalError("typeAliasMustBeLocal") - .withSourceSection(member.getHeaderSection()) - .build(); + 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()); } - - checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); - EconomicMaps.put(result, member.getName(), member); + builder.addSlot(FrameSlotKind.Illegal, identifier, null); } + return builder; + } - for (var ctx : propertyCtxs) { - var member = - doVisitObjectProperty( - ctx, - ctx.modifier(), - ctx.Identifier(), - ctx.typeAnnotation(), - ctx.expr(), - ctx.objectBody()); + private @Nullable FrameDescriptor.Builder createFrameDescriptorBuilder(ObjectBody body) { + if (body.getParameters().isEmpty()) return null; - if (moduleInfo.isAmend() && !member.isLocal() && ctx.typeAnnotation() != null) { - throw exceptionBuilder() - .evalError("nonLocalObjectPropertyCannotHaveTypeAnnotation") - .withSourceSection(createSourceSection(ctx.typeAnnotation().type())) - .build(); + 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()); } - - checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); - EconomicMaps.put(result, member.getName(), member); + builder.addSlot(FrameSlotKind.Illegal, identifier, null); } + return builder; + } - return result; + private void checkNotInsideForGenerator(Node ctx, String errorMessageKey) { + if (!symbolTable.getCurrentScope().isForGeneratorScope()) { + return; + } + var forExprCtx = ctx.parent(); + while (forExprCtx.getClass() != org.pkl.core.parser.cst.ObjectMember.ForGenerator.class) { + forExprCtx = forExprCtx.parent(); + } + throw exceptionBuilder() + .evalError(errorMessageKey) + .withSourceSection( + createSourceSection( + ((org.pkl.core.parser.cst.ObjectMember.ForGenerator) forExprCtx).forSpan())) + .build(); } 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 +2824,66 @@ private void checkDuplicateMember( } } - // 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); + protected VmExceptionBuilder exceptionBuilder() { + return new VmExceptionBuilder() + .withMemberName(symbolTable.getCurrentScope().getQualifiedName()); } - private VmException createUnexpectedTokenError(Token token) { - return exceptionBuilder().bug("Unexpected token `%s`.", token).build(); + private @Nullable SymbolTable.Scope getParentLexicalScope() { + var parent = symbolTable.getCurrentScope().getLexicalScope().getParent(); + if (parent != null) return parent.getLexicalScope(); + return null; } - @Override - protected VmExceptionBuilder exceptionBuilder() { - return new VmExceptionBuilder() - .withMemberName(symbolTable.getCurrentScope().getQualifiedName()); + private org.pkl.core.runtime.Identifier toIdentifier(String text) { + return org.pkl.core.runtime.Identifier.get(text); } - private static SourceSection unavailableSourceSection() { - return VmUtils.unavailableSourceSection(); + 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(MultiLineStringPartContext lastPart, Token endQuoteToken) { - if (lastPart.e != null) { + 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,217 +2893,96 @@ private static boolean isIndentChars(Token token) { return true; } - private static String getLeadingIndent(Token token) { - var text = token.getText(); - - for (var i = 0; i < text.length(); i++) { - var ch = text.charAt(i); - if (ch != ' ' && ch != '\t') { - return text.substring(0, i); - } - } - - 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(); - } + 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) { - 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(); - } + 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."); } - prevChild = child; + 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(); } - throw exceptionBuilder().unreachableCode().build(); - } - - private void checkClosingDelimiter( - @Nullable Token delimiter, String delimiterSymbol, Token tokenBeforeDelimiter) { - - if (delimiter == null) { - throw missingDelimiter(delimiterSymbol, tokenBeforeDelimiter.getStopIndex() + 1); + if (!resolvedUri.isAbsolute()) { + throw exceptionBuilder() + .evalError("cannotHaveRelativeImport", moduleKey.getUri()) + .withSourceSection(createSourceSection(ctx)) + .build(); } + return resolvedUri; } - 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 ConstLevel getConstLevel(int modifiers) { + if (VmModifier.isConst(modifiers)) return ConstLevel.ALL; + return symbolTable.getCurrentScope().getConstLevel(); } - private VmException wrongDelimiter(String expected, String actual, int charIndex) { + private VmException missingLocalPropertyValue(TypeAnnotation typeAnn) { + var stop = typeAnn.span().stopIndex(); return exceptionBuilder() - .evalError("wrongDelimiter", expected, actual) - .withSourceSection(source.createSection(charIndex, 0)) + .evalError("missingLocalPropertyValue") + .withSourceSection(source.createSection(stop + 1, 0)) .build(); } - private VmException danglingDelimiter(String delimiter, int charIndex) { - return exceptionBuilder() - .evalError("danglingDelimiter", delimiter) - .withSourceSection(source.createSection(charIndex, 0)) - .build(); + private static SourceSection unavailableSourceSection() { + return VmUtils.unavailableSourceSection(); } - private @Nullable Scope getParentLexicalScope() { - var parent = symbolTable.getCurrentScope().getLexicalScope().getParent(); - if (parent != null) return parent.getLexicalScope(); - return null; - } + private static String getLeadingIndent(String text) { + for (var i = 0; i < text.length(); i++) { + var ch = text.charAt(i); + if (ch != ' ' && ch != '\t') { + return text.substring(0, i); + } + } - private ConstLevel getConstLevel(int modifiers) { - if (VmModifier.isConst(modifiers)) return ConstLevel.ALL; - return symbolTable.getCurrentScope().getConstLevel(); + return text; } } 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 93e3c2475..195230c05 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 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.cst.Expr; +import org.pkl.core.parser.cst.Expr.ImportExpr; +import org.pkl.core.parser.cst.Expr.ReadExpr; +import org.pkl.core.parser.cst.Expr.ReadType; +import org.pkl.core.parser.cst.Expr.SingleLineStringLiteralExpr; +import org.pkl.core.parser.cst.ExtendsOrAmendsClause; +import org.pkl.core.parser.cst.ExtendsOrAmendsClause.Type; +import org.pkl.core.parser.cst.ImportClause; +import org.pkl.core.parser.cst.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 record Entry( var importListParser = new ImportsAndReadsParser(source); try { return parser.parseModule(text).accept(importListParser); - } catch (LexParseException e) { + } catch (ParserError e) { var moduleName = IoUtils.inferModuleName(moduleKey); throw VmUtils.toVmException(e, source, moduleName); } @@ -84,62 +87,63 @@ protected VmExceptionBuilder exceptionBuilder() { } @Override - public @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 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 000000000..d2003ba46 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/BaseParserVisitor.java @@ -0,0 +1,503 @@ +/* + * 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.cst.Annotation; +import org.pkl.core.parser.cst.ArgumentList; +import org.pkl.core.parser.cst.Class; +import org.pkl.core.parser.cst.ClassBody; +import org.pkl.core.parser.cst.ClassMethod; +import org.pkl.core.parser.cst.ClassProperty; +import org.pkl.core.parser.cst.DocComment; +import org.pkl.core.parser.cst.Expr; +import org.pkl.core.parser.cst.Expr.AmendsExpr; +import org.pkl.core.parser.cst.Expr.BinaryOperatorExpr; +import org.pkl.core.parser.cst.Expr.BoolLiteralExpr; +import org.pkl.core.parser.cst.Expr.FloatLiteralExpr; +import org.pkl.core.parser.cst.Expr.FunctionLiteralExpr; +import org.pkl.core.parser.cst.Expr.IfExpr; +import org.pkl.core.parser.cst.Expr.ImportExpr; +import org.pkl.core.parser.cst.Expr.IntLiteralExpr; +import org.pkl.core.parser.cst.Expr.LetExpr; +import org.pkl.core.parser.cst.Expr.LogicalNotExpr; +import org.pkl.core.parser.cst.Expr.ModuleExpr; +import org.pkl.core.parser.cst.Expr.MultiLineStringLiteralExpr; +import org.pkl.core.parser.cst.Expr.NewExpr; +import org.pkl.core.parser.cst.Expr.NonNullExpr; +import org.pkl.core.parser.cst.Expr.NullLiteralExpr; +import org.pkl.core.parser.cst.Expr.OuterExpr; +import org.pkl.core.parser.cst.Expr.ParenthesizedExpr; +import org.pkl.core.parser.cst.Expr.QualifiedAccessExpr; +import org.pkl.core.parser.cst.Expr.ReadExpr; +import org.pkl.core.parser.cst.Expr.SingleLineStringLiteralExpr; +import org.pkl.core.parser.cst.Expr.SubscriptExpr; +import org.pkl.core.parser.cst.Expr.SuperAccessExpr; +import org.pkl.core.parser.cst.Expr.SuperSubscriptExpr; +import org.pkl.core.parser.cst.Expr.ThisExpr; +import org.pkl.core.parser.cst.Expr.ThrowExpr; +import org.pkl.core.parser.cst.Expr.TraceExpr; +import org.pkl.core.parser.cst.Expr.TypeCastExpr; +import org.pkl.core.parser.cst.Expr.TypeCheckExpr; +import org.pkl.core.parser.cst.Expr.UnaryMinusExpr; +import org.pkl.core.parser.cst.Expr.UnqualifiedAccessExpr; +import org.pkl.core.parser.cst.ExtendsOrAmendsClause; +import org.pkl.core.parser.cst.Identifier; +import org.pkl.core.parser.cst.ImportClause; +import org.pkl.core.parser.cst.Modifier; +import org.pkl.core.parser.cst.ModuleDecl; +import org.pkl.core.parser.cst.Node; +import org.pkl.core.parser.cst.ObjectBody; +import org.pkl.core.parser.cst.ObjectMember; +import org.pkl.core.parser.cst.ObjectMember.ForGenerator; +import org.pkl.core.parser.cst.ObjectMember.MemberPredicate; +import org.pkl.core.parser.cst.ObjectMember.ObjectElement; +import org.pkl.core.parser.cst.ObjectMember.ObjectEntry; +import org.pkl.core.parser.cst.ObjectMember.ObjectMethod; +import org.pkl.core.parser.cst.ObjectMember.ObjectProperty; +import org.pkl.core.parser.cst.ObjectMember.ObjectSpread; +import org.pkl.core.parser.cst.ObjectMember.WhenGenerator; +import org.pkl.core.parser.cst.Parameter; +import org.pkl.core.parser.cst.ParameterList; +import org.pkl.core.parser.cst.QualifiedIdentifier; +import org.pkl.core.parser.cst.ReplInput; +import org.pkl.core.parser.cst.StringConstant; +import org.pkl.core.parser.cst.StringConstantPart; +import org.pkl.core.parser.cst.StringPart; +import org.pkl.core.parser.cst.Type; +import org.pkl.core.parser.cst.Type.ConstrainedType; +import org.pkl.core.parser.cst.Type.DeclaredType; +import org.pkl.core.parser.cst.Type.DefaultUnionType; +import org.pkl.core.parser.cst.Type.FunctionType; +import org.pkl.core.parser.cst.Type.ModuleType; +import org.pkl.core.parser.cst.Type.NothingType; +import org.pkl.core.parser.cst.Type.NullableType; +import org.pkl.core.parser.cst.Type.ParenthesizedType; +import org.pkl.core.parser.cst.Type.StringConstantType; +import org.pkl.core.parser.cst.Type.UnionType; +import org.pkl.core.parser.cst.Type.UnknownType; +import org.pkl.core.parser.cst.TypeAlias; +import org.pkl.core.parser.cst.TypeAnnotation; +import org.pkl.core.parser.cst.TypeParameter; +import org.pkl.core.parser.cst.TypeParameterList; +import org.pkl.core.util.Nullable; + +public abstract class BaseParserVisitor implements ParserVisitor { + + @Override + public @Nullable T visitUnknownType(UnknownType type) { + return null; + } + + @Override + public @Nullable T visitNothingType(NothingType type) { + return null; + } + + @Override + public @Nullable T visitModuleType(ModuleType type) { + return null; + } + + @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 visitDefaultUnionType(DefaultUnionType 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 visitType(Type type) { + return type.accept(this); + } + + @Override + public @Nullable T visitThisExpr(ThisExpr expr) { + return null; + } + + @Override + public @Nullable T visitOuterExpr(OuterExpr expr) { + return null; + } + + @Override + public T visitModuleExpr(ModuleExpr expr) { + return null; + } + + @Override + public T visitNullLiteralExpr(NullLiteralExpr expr) { + return null; + } + + @Override + public T visitBoolLiteralExpr(BoolLiteralExpr expr) { + return null; + } + + @Override + public T visitIntLiteralExpr(IntLiteralExpr expr) { + return null; + } + + @Override + public T visitFloatLiteralExpr(FloatLiteralExpr expr) { + return null; + } + + @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 visitExpr(Expr 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 visitObjectMember(ObjectMember member) { + return visitChildren(member); + } + + @Override + public T visitModule(org.pkl.core.parser.cst.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 null; + } + + @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 null; + } + + @Override + public T visitClassBody(ClassBody classBody) { + return visitChildren(classBody); + } + + @Override + public T visitDocComment(DocComment docComment) { + return null; + } + + @Override + public T visitIdentifier(Identifier identifier) { + return null; + } + + @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 @Nullable 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 @Nullable T defaultValue() { + return null; + } + + protected @Nullable T aggregateResult(@Nullable T result, @Nullable 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 b3ff0ac41..000000000 --- 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 055e0fe2d..000000000 --- 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 1a93dbc57..19a4fe086 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,711 @@ */ 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; - -public final class Lexer { - public static final Set KEYWORD_TYPES; - public static final Set KEYWORD_NAMES; - - static { - var keywordTypes = new HashSet(); - var keywordNames = new HashSet(); - var vocabulary = PklLexer.VOCABULARY; - - 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); - } - } - - KEYWORD_TYPES = Collections.unmodifiableSet(keywordTypes); - KEYWORD_NAMES = Collections.unmodifiableSet(keywordNames); - } - - @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); +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Deque; +import org.pkl.core.util.ErrorMessages; + +public class Lexer { + + 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; + + 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("..", "`.`, `...` or `...?`"); + } + } 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("invalidCharacter", ch); + } + }; + } + + 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(); + } + } + state = State.STRING; + interpolationStack.push(new InterpolationScope(quotes, pounds)); + stringEnded = false; + if (quotes == 1) return Token.STRING_START; + return Token.STRING_MULTI_START; + } + + 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("singleQuoteStringNewline"), cursor - 1, 1); + case '"' -> { + if (pounds == 0) { + backup(); + stringEnded = true; + return; + } + 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; + } + } + } + } + + 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; } - }); - return lexer; + } + 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); + }; } - @TruffleBoundary - public static boolean isKeyword(@Nullable Token token) { - return token != null && KEYWORD_TYPES.contains(token.getType()); + 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, "`0` .. `9`, `a` .. `f` or `A` .. `F`"); + } + while (isHex(lookahead) || lookahead == '_') { + nextChar(); + } + } + + private void lexBinNumber() { + if (lookahead == '_') { + throw lexError("invalidSeparatorPosition"); + } + if (!(lookahead == '0' || lookahead == '1')) { + throw unexpectedChar(lookahead, "`0` or `1`"); + } + 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, "`0` .. `7`"); + } + 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, "`0` .. `9`"); + } + 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 didYouMean) { + return lexError("unexpectedCharacter", got, didYouMean); + } + + 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 +729,89 @@ public static boolean isRegularIdentifier(String identifier) { .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 000000000..4f934d39b --- /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.cst.Expr; +import org.pkl.core.parser.cst.Expr.BinaryOperatorExpr; +import org.pkl.core.parser.cst.Expr.OperatorExpr; +import org.pkl.core.parser.cst.Expr.TypeCastExpr; +import org.pkl.core.parser.cst.Expr.TypeCheckExpr; +import org.pkl.core.parser.cst.Expr.TypeExpr; +import org.pkl.core.parser.cst.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 37d549163..406362d30 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,1595 @@ */ 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 java.util.stream.Collectors; +import org.pkl.core.PklBugException; +import org.pkl.core.parser.cst.Annotation; +import org.pkl.core.parser.cst.ArgumentList; +import org.pkl.core.parser.cst.Class; +import org.pkl.core.parser.cst.ClassBody; +import org.pkl.core.parser.cst.ClassMethod; +import org.pkl.core.parser.cst.ClassProperty; +import org.pkl.core.parser.cst.DocComment; +import org.pkl.core.parser.cst.Expr; +import org.pkl.core.parser.cst.Expr.AmendsExpr; +import org.pkl.core.parser.cst.Expr.BoolLiteralExpr; +import org.pkl.core.parser.cst.Expr.FloatLiteralExpr; +import org.pkl.core.parser.cst.Expr.FunctionLiteralExpr; +import org.pkl.core.parser.cst.Expr.IfExpr; +import org.pkl.core.parser.cst.Expr.IntLiteralExpr; +import org.pkl.core.parser.cst.Expr.LetExpr; +import org.pkl.core.parser.cst.Expr.LogicalNotExpr; +import org.pkl.core.parser.cst.Expr.ModuleExpr; +import org.pkl.core.parser.cst.Expr.MultiLineStringLiteralExpr; +import org.pkl.core.parser.cst.Expr.NewExpr; +import org.pkl.core.parser.cst.Expr.NonNullExpr; +import org.pkl.core.parser.cst.Expr.NullLiteralExpr; +import org.pkl.core.parser.cst.Expr.OperatorExpr; +import org.pkl.core.parser.cst.Expr.OuterExpr; +import org.pkl.core.parser.cst.Expr.ParenthesizedExpr; +import org.pkl.core.parser.cst.Expr.QualifiedAccessExpr; +import org.pkl.core.parser.cst.Expr.ReadExpr; +import org.pkl.core.parser.cst.Expr.ReadType; +import org.pkl.core.parser.cst.Expr.SingleLineStringLiteralExpr; +import org.pkl.core.parser.cst.Expr.SubscriptExpr; +import org.pkl.core.parser.cst.Expr.SuperAccessExpr; +import org.pkl.core.parser.cst.Expr.SuperSubscriptExpr; +import org.pkl.core.parser.cst.Expr.ThisExpr; +import org.pkl.core.parser.cst.Expr.ThrowExpr; +import org.pkl.core.parser.cst.Expr.TraceExpr; +import org.pkl.core.parser.cst.Expr.UnaryMinusExpr; +import org.pkl.core.parser.cst.Expr.UnqualifiedAccessExpr; +import org.pkl.core.parser.cst.ExtendsOrAmendsClause; +import org.pkl.core.parser.cst.Identifier; +import org.pkl.core.parser.cst.ImportClause; +import org.pkl.core.parser.cst.Modifier; +import org.pkl.core.parser.cst.Modifier.ModifierValue; +import org.pkl.core.parser.cst.Module; +import org.pkl.core.parser.cst.ModuleDecl; +import org.pkl.core.parser.cst.Node; +import org.pkl.core.parser.cst.ObjectBody; +import org.pkl.core.parser.cst.ObjectMember; +import org.pkl.core.parser.cst.Operator; +import org.pkl.core.parser.cst.Parameter; +import org.pkl.core.parser.cst.Parameter.TypedIdentifier; +import org.pkl.core.parser.cst.ParameterList; +import org.pkl.core.parser.cst.QualifiedIdentifier; +import org.pkl.core.parser.cst.ReplInput; +import org.pkl.core.parser.cst.StringConstant; +import org.pkl.core.parser.cst.StringConstantPart; +import org.pkl.core.parser.cst.StringConstantPart.EscapeType; +import org.pkl.core.parser.cst.StringConstantPart.StringEscape; +import org.pkl.core.parser.cst.StringConstantPart.StringNewline; +import org.pkl.core.parser.cst.StringPart; +import org.pkl.core.parser.cst.StringPart.StringConstantParts; +import org.pkl.core.parser.cst.Type; +import org.pkl.core.parser.cst.Type.DeclaredType; +import org.pkl.core.parser.cst.Type.DefaultUnionType; +import org.pkl.core.parser.cst.Type.ParenthesizedType; +import org.pkl.core.parser.cst.Type.StringConstantType; +import org.pkl.core.parser.cst.TypeAlias; +import org.pkl.core.parser.cst.TypeAnnotation; +import org.pkl.core.parser.cst.TypeParameter; +import org.pkl.core.parser.cst.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; + } + + 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; + } + } + + public Expr parseExpressionInput(String source) { + init(source); + var expr = parseExpr(); + expect(Token.EOF, "unexpectedToken", "end of file"); + return expr; + } + + 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); + } + + 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; + } + + private QualifiedIdentifier parseQualifiedIdentifier() { + var idents = parseListOf(Token.DOT, this::parseIdentifier); + return new QualifiedIdentifier(idents); } - @TruffleBoundary - public ModuleContext parseModule(CharStream source) throws LexParseException { - return parseProduction(source, PklParser::module); + 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; } - @TruffleBoundary - public ModuleContext parseModule(String source) throws LexParseException { - return parseModule(toCharStream(source)); + 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)); } - @TruffleBoundary - public ReplInputContext parseReplInput(CharStream source) throws LexParseException { - var ctx = parseProduction(source, PklParser::replInput); - checkIsCompleteInput(ctx); - return ctx; + 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); } - @TruffleBoundary - public ReplInputContext parseReplInput(String source) throws LexParseException { - return parseReplInput(toCharStream(source)); + 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); } - @TruffleBoundary - public ExprInputContext parseExpressionInput(CharStream source) throws LexParseException { - var ctx = parseProduction(source, PklParser::exprInput); - checkIsCompleteInput(ctx); - return ctx; + 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()); + } + throw parserError("invalidTopLevelToken"); + } + } } - /** - * Two-step parse as recommended in chapter "Maximizing Parser Speed" of "The Definitive ANTLR 4 - * Reference, 2nd Ed". - */ - @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); + private TypeAlias parseTypeAlias(MemberHeader header) { + var start = expect(Token.TYPE_ALIAS, "unexpectedToken", "typealias").span; + var startSpan = header.span(start); - var result = production.apply(parser); + 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())); + } - // 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); + private Class parseClass(MemberHeader header) { + var start = expect(Token.CLASS, "unexpectedToken", "class").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); - var mostRelevant = - errorCollector.stream().max(Comparator.comparingInt(LexParseException::getRelevance)); - if (mostRelevant.isPresent()) { - throw mostRelevant.get().withPartialParseResult(result); + ClassBody body = null; + if (lookahead == Token.LBRACE) { + body = parseClassBody(); + end = body.span(); } + children.add(body); - return result; + return new Class(children, modifiersOffset, nameOffset, startSpan.endWith(end)); } - @TruffleBoundary - public ExprInputContext parseExpressionInput(String source) throws LexParseException { - return parseExpressionInput(toCharStream(source)); + 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)); } - @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); + 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())); } - // 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 + 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)); + } - 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; + 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 (curr instanceof ParameterListContext parameterList) { - if (parameterList.err == null) throw incompleteInput(curr, ")"); - else return; + } + + if (membersOffset < 0) { + membersOffset = nodes.size(); + } + // members + while (lookahead != Token.RBRACE) { + nodes.add(parseObjectMember()); + } + var end = expect(Token.RBRACE, "unexpectedToken", "}").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(); + } } - if (curr instanceof ArgumentListContext argumentList) { - if (argumentList.err == null) throw incompleteInput(curr, ")"); - else return; + 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(); + } } - if (curr instanceof TypeParameterListContext typeParameterList) { - if (typeParameterList.err == null) throw incompleteInput(curr, ">"); - else return; + }; + } + + 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) { + throw new ParserError(ErrorMessages.create("unexpectedToken", "]]"), firstBrack); + } else { + secondbrack = next().span; + } + if (firstBrack.charIndex() != secondbrack.charIndex() - 1) { + // There shouldn't be any whitespace between the first and second ']'. + throw new ParserError(ErrorMessages.create("unexpectedToken", "]]"), 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() { + if (lookahead != Token.SPREAD && lookahead != Token.QSPREAD) { + throw parserError("unexpectedToken2", "...", "...?"); + } + var peek = next(); + boolean isNullable = peek.token == Token.QSPREAD; + var expr = parseExpr("}"); + return new ObjectMember.ObjectSpread(expr, isNullable, peek.span.endWith(expr.span())); + } + + private ObjectMember.WhenGenerator parseWhenGenerator() { + var start = expect(Token.WHEN, "unexpectedToken", "when").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 = expect(Token.FOR, "unexpectedToken", "for").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)); + } } - 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 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; + op = getOperator(); + } + return OperatorResolver.resolveOperators(exprs); + } - 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); + 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 { - exception = - new LexParseException.ParseError( - msg, line, charPositionInLine + 1, length, getAstDepth(e)); + expect(Token.LBRACK, "unexpectedToken", "["); + var exp = parseExpr("]"); + var end = expect(Token.RBRACK, "unexpectedToken", "]").span; + yield new SuperSubscriptExpr(exp, start.endWith(end)); } - - if (errorCollector != null) { - errorCollector.add(exception); + } + 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(); + var text = remove_(tk.text(lexer)); + yield new IntLiteralExpr(text, tk.span); + } + case FLOAT -> { + var tk = next(); + var text = remove_(tk.text(lexer)); + yield new FloatLiteralExpr(text, 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 end = expect(Token.STRING_END, "noError").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 { - throw exception; + yield new UnqualifiedAccessExpr(identifier, null, identifier.span()); } } - }); + case EOF -> + throw new ParserError( + ErrorMessages.create("unexpectedEndOfFile"), prev.span.stopSpan().move(1)); + default -> { + if (expectation != null) { + throw parserError("unexpectedToken", expectation); + } + throw parserError("unexpectedTokenForExpression"); + } + }; + 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(false, null); + } + + private Type parseType(@Nullable String expectation) { + return parseType(false, expectation); + } + + private Type parseType(boolean shortCircuit, @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 STAR -> { + var tk = next(); + var type = parseType(true, expectation); + typ = new DefaultUnionType(type, tk.span.endWith(type.span())); + } + 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 -> { + if (expectation != null) { + throw parserError("unexpectedToken", expectation); + } + throw parserError("unexpectedTokenForType"); + } + } + + if (typ instanceof Type.FunctionType) return typ; + return parseTypeEnd(typ, shortCircuit, expectation); } - 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 Type parseTypeEnd(Type type, boolean shortCircuit, @Nullable String expectation) { + // nullable types + if (lookahead == Token.QUESTION) { + var end = spanLookahead; + next(); + var res = new Type.NullableType(type, type.span().endWith(end)); + return parseTypeEnd(res, shortCircuit, expectation); + } + // 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, shortCircuit, expectation); + } + // union types + if (lookahead == Token.UNION && !shortCircuit) { + next(); + // union types are left associative + var right = parseType(true, expectation); + var res = new Type.UnionType(type, right, type.span().endWith(right.span())); + return parseTypeEnd(res, false, expectation); + } + return type; } - private static int getAstDepth(@Nullable RecognitionException e) { - if (e == null) return 0; + 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)); + } - var depth = 0; - for (var context = e.getContext(); context != null; context = context.getParent()) { - depth += 1; + private Parameter parseParameter() { + if (lookahead == Token.UNDERSCORE) { + var span = next().span; + return new Parameter.Underscore(span); } + return parseTypedIdentifier(); + } - return depth; + 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 -> { + var modifierNames = + Arrays.stream(ModifierValue.values()) + .map((m) -> "`" + m.name().toLowerCase() + "`") + .collect(Collectors.joining(", ")); + throw parserError("unexpectedTokenMany", modifierNames); + } + }; + } + + 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() { + 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", "identifier"); + } + var tk = next(); + var text = removeBackticks(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 STRING_NEWLINE -> throw parserError("singleQuoteStringNewline"); + 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) { + 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 + throw new ParserError( + ErrorMessages.create(errorKey, messageArgs), prev.span.stopSpan().move(1)); + } + throw new ParserError(ErrorMessages.create(errorKey, messageArgs), spanLookahead); + } + 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); + } + + // backtrack to the previous token + private void backtrack() { + lookahead = prev.token; + spanLookahead = prev.span; + backtracking = true; + } + + private void ensureEmptyHeaders(MemberHeader header, String messageArg) { + if (header.isNotEmpty()) { + throw new ParserError( + ErrorMessages.create("wrongHeaders", messageArg), header.span(spanLookahead)); + } + } + + private 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(); + } + + private 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; + } + + private record FullToken( + Token token, Span span, int textOffset, int textSize, int newLinesBetween) { + String text(Lexer lexer) { + return lexer.textFor(textOffset, textSize); + } } } 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 000000000..94a4c6d7d --- /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.cst.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 000000000..16c093df4 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/ParserVisitor.java @@ -0,0 +1,309 @@ +/* + * 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.cst.Annotation; +import org.pkl.core.parser.cst.ArgumentList; +import org.pkl.core.parser.cst.Class; +import org.pkl.core.parser.cst.ClassBody; +import org.pkl.core.parser.cst.ClassMethod; +import org.pkl.core.parser.cst.ClassProperty; +import org.pkl.core.parser.cst.DocComment; +import org.pkl.core.parser.cst.Expr; +import org.pkl.core.parser.cst.Expr.AmendsExpr; +import org.pkl.core.parser.cst.Expr.BinaryOperatorExpr; +import org.pkl.core.parser.cst.Expr.BoolLiteralExpr; +import org.pkl.core.parser.cst.Expr.FloatLiteralExpr; +import org.pkl.core.parser.cst.Expr.FunctionLiteralExpr; +import org.pkl.core.parser.cst.Expr.IfExpr; +import org.pkl.core.parser.cst.Expr.IntLiteralExpr; +import org.pkl.core.parser.cst.Expr.LetExpr; +import org.pkl.core.parser.cst.Expr.LogicalNotExpr; +import org.pkl.core.parser.cst.Expr.ModuleExpr; +import org.pkl.core.parser.cst.Expr.MultiLineStringLiteralExpr; +import org.pkl.core.parser.cst.Expr.NewExpr; +import org.pkl.core.parser.cst.Expr.NonNullExpr; +import org.pkl.core.parser.cst.Expr.NullLiteralExpr; +import org.pkl.core.parser.cst.Expr.OuterExpr; +import org.pkl.core.parser.cst.Expr.ParenthesizedExpr; +import org.pkl.core.parser.cst.Expr.QualifiedAccessExpr; +import org.pkl.core.parser.cst.Expr.ReadExpr; +import org.pkl.core.parser.cst.Expr.SingleLineStringLiteralExpr; +import org.pkl.core.parser.cst.Expr.SubscriptExpr; +import org.pkl.core.parser.cst.Expr.SuperAccessExpr; +import org.pkl.core.parser.cst.Expr.SuperSubscriptExpr; +import org.pkl.core.parser.cst.Expr.ThisExpr; +import org.pkl.core.parser.cst.Expr.ThrowExpr; +import org.pkl.core.parser.cst.Expr.TraceExpr; +import org.pkl.core.parser.cst.Expr.TypeCastExpr; +import org.pkl.core.parser.cst.Expr.TypeCheckExpr; +import org.pkl.core.parser.cst.Expr.UnaryMinusExpr; +import org.pkl.core.parser.cst.Expr.UnqualifiedAccessExpr; +import org.pkl.core.parser.cst.ExtendsOrAmendsClause; +import org.pkl.core.parser.cst.Identifier; +import org.pkl.core.parser.cst.ImportClause; +import org.pkl.core.parser.cst.Modifier; +import org.pkl.core.parser.cst.Module; +import org.pkl.core.parser.cst.ModuleDecl; +import org.pkl.core.parser.cst.ObjectBody; +import org.pkl.core.parser.cst.ObjectMember; +import org.pkl.core.parser.cst.Parameter; +import org.pkl.core.parser.cst.ParameterList; +import org.pkl.core.parser.cst.QualifiedIdentifier; +import org.pkl.core.parser.cst.ReplInput; +import org.pkl.core.parser.cst.StringConstant; +import org.pkl.core.parser.cst.StringConstantPart; +import org.pkl.core.parser.cst.StringPart; +import org.pkl.core.parser.cst.Type; +import org.pkl.core.parser.cst.TypeAlias; +import org.pkl.core.parser.cst.TypeAnnotation; +import org.pkl.core.parser.cst.TypeParameter; +import org.pkl.core.parser.cst.TypeParameterList; +import org.pkl.core.util.Nullable; + +public interface ParserVisitor { + + @Nullable + Result visitUnknownType(Type.UnknownType type); + + @Nullable + Result visitNothingType(Type.NothingType type); + + @Nullable + Result visitModuleType(Type.ModuleType type); + + @Nullable + Result visitStringConstantType(Type.StringConstantType type); + + @Nullable + Result visitDeclaredType(Type.DeclaredType type); + + @Nullable + Result visitParenthesizedType(Type.ParenthesizedType type); + + @Nullable + Result visitNullableType(Type.NullableType type); + + @Nullable + Result visitConstrainedType(Type.ConstrainedType type); + + @Nullable + Result visitDefaultUnionType(Type.DefaultUnionType type); + + @Nullable + Result visitUnionType(Type.UnionType type); + + @Nullable + Result visitFunctionType(Type.FunctionType type); + + @Nullable + Result visitType(Type type); + + @Nullable + Result visitThisExpr(ThisExpr expr); + + @Nullable + Result visitOuterExpr(OuterExpr expr); + + @Nullable + Result visitModuleExpr(ModuleExpr expr); + + @Nullable + Result visitNullLiteralExpr(NullLiteralExpr expr); + + @Nullable + Result visitBoolLiteralExpr(BoolLiteralExpr expr); + + @Nullable + Result visitIntLiteralExpr(IntLiteralExpr expr); + + @Nullable + Result visitFloatLiteralExpr(FloatLiteralExpr expr); + + @Nullable + Result visitThrowExpr(ThrowExpr expr); + + @Nullable + Result visitTraceExpr(TraceExpr expr); + + @Nullable + Result visitImportExpr(Expr.ImportExpr expr); + + @Nullable + Result visitReadExpr(ReadExpr expr); + + @Nullable + Result visitUnqualifiedAccessExpr(UnqualifiedAccessExpr expr); + + @Nullable + Result visitStringConstant(StringConstant expr); + + @Nullable + Result visitSingleLineStringLiteralExpr(SingleLineStringLiteralExpr expr); + + @Nullable + Result visitMultiLineStringLiteralExpr(MultiLineStringLiteralExpr expr); + + @Nullable + Result visitNewExpr(NewExpr expr); + + @Nullable + Result visitAmendsExpr(AmendsExpr expr); + + @Nullable + Result visitSuperAccessExpr(SuperAccessExpr expr); + + @Nullable + Result visitSuperSubscriptExpr(SuperSubscriptExpr expr); + + @Nullable + Result visitQualifiedAccessExpr(QualifiedAccessExpr expr); + + @Nullable + Result visitSubscriptExpr(SubscriptExpr expr); + + @Nullable + Result visitNonNullExpr(NonNullExpr expr); + + @Nullable + Result visitUnaryMinusExpr(UnaryMinusExpr expr); + + @Nullable + Result visitLogicalNotExpr(LogicalNotExpr expr); + + @Nullable + Result visitBinaryOperatorExpr(BinaryOperatorExpr expr); + + @Nullable + Result visitTypeCheckExpr(TypeCheckExpr expr); + + @Nullable + Result visitTypeCastExpr(TypeCastExpr expr); + + @Nullable + Result visitIfExpr(IfExpr expr); + + @Nullable + Result visitLetExpr(LetExpr expr); + + @Nullable + Result visitFunctionLiteralExpr(FunctionLiteralExpr expr); + + @Nullable + Result visitParenthesizedExpr(ParenthesizedExpr expr); + + @Nullable + Result visitExpr(Expr expr); + + @Nullable + Result visitObjectProperty(ObjectMember.ObjectProperty member); + + @Nullable + Result visitObjectMethod(ObjectMember.ObjectMethod member); + + @Nullable + Result visitMemberPredicate(ObjectMember.MemberPredicate member); + + @Nullable + Result visitObjectElement(ObjectMember.ObjectElement member); + + @Nullable + Result visitObjectEntry(ObjectMember.ObjectEntry member); + + @Nullable + Result visitObjectSpread(ObjectMember.ObjectSpread member); + + @Nullable + Result visitWhenGenerator(ObjectMember.WhenGenerator member); + + @Nullable + Result visitForGenerator(ObjectMember.ForGenerator member); + + @Nullable + Result visitObjectMember(ObjectMember member); + + @Nullable + Result visitModule(Module module); + + @Nullable + Result visitModuleDecl(ModuleDecl decl); + + @Nullable + Result visitExtendsOrAmendsClause(ExtendsOrAmendsClause decl); + + @Nullable + Result visitImportClause(ImportClause imp); + + @Nullable + Result visitClass(Class clazz); + + @Nullable + Result visitModifier(Modifier modifier); + + @Nullable + Result visitClassProperty(ClassProperty entry); + + @Nullable + Result visitClassMethod(ClassMethod entry); + + @Nullable + Result visitClassBody(ClassBody classBody); + + @Nullable + Result visitTypeAlias(TypeAlias typeAlias); + + @Nullable + Result visitAnnotation(Annotation annotation); + + @Nullable + Result visitParameter(Parameter param); + + @Nullable + Result visitParameterList(ParameterList paramList); + + @Nullable + Result visitTypeParameter(TypeParameter typeParameter); + + @Nullable + Result visitTypeParameterList(TypeParameterList typeParameterList); + + @Nullable + Result visitTypeAnnotation(TypeAnnotation typeAnnotation); + + @Nullable + Result visitArgumentList(ArgumentList argumentList); + + @Nullable + Result visitStringPart(StringPart part); + + @Nullable + Result visitStringConstantPart(StringConstantPart part); + + @Nullable + Result visitDocComment(DocComment docComment); + + @Nullable + Result visitIdentifier(Identifier identifier); + + @Nullable + Result visitQualifiedIdentifier(QualifiedIdentifier qualifiedIdentifier); + + @Nullable + Result visitObjectBody(ObjectBody objectBody); + + @Nullable + 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 000000000..ca809c6c4 --- /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 000000000..eacf0ef6e --- /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/cst/AbstractNode.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/AbstractNode.java new file mode 100644 index 000000000..8df69cf81 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/Annotation.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/Annotation.java new file mode 100644 index 000000000..b0167a417 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/ArgumentList.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/ArgumentList.java new file mode 100644 index 000000000..53b1dc41b --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/Class.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/Class.java new file mode 100644 index 000000000..ed8d97327 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/ClassBody.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/ClassBody.java new file mode 100644 index 000000000..5d131fdd2 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/ClassMethod.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/ClassMethod.java new file mode 100644 index 000000000..8664969aa --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/ClassProperty.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/ClassProperty.java new file mode 100644 index 000000000..1efebbe71 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/DocComment.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/DocComment.java new file mode 100644 index 000000000..fbd679397 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/DocComment.java @@ -0,0 +1,50 @@ +/* + * 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.cst; + +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)); + } + + @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/cst/Expr.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/Expr.java new file mode 100644 index 000000000..98fb816e3 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/ExtendsOrAmendsClause.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/ExtendsOrAmendsClause.java new file mode 100644 index 000000000..1c53cca61 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/Identifier.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/Identifier.java new file mode 100644 index 000000000..44c9a7e6c --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/Identifier.java @@ -0,0 +1,62 @@ +/* + * 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.cst; + +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 Identifier extends AbstractNode { + private final String value; + + public Identifier(String value, Span span) { + super(span, null); + this.value = value; + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitIdentifier(this); + } + + public String getValue() { + return value; + } + + @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/cst/ImportClause.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/ImportClause.java new file mode 100644 index 000000000..d243ee37e --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/Modifier.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/Modifier.java new file mode 100644 index 000000000..e96de248e --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/Module.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/Module.java new file mode 100644 index 000000000..a96062a48 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/ModuleDecl.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/ModuleDecl.java new file mode 100644 index 000000000..460c4211b --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/Node.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/Node.java new file mode 100644 index 000000000..2b779c672 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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(); + + @Nullable 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/cst/ObjectBody.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/ObjectBody.java new file mode 100644 index 000000000..ffa800558 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/ObjectMember.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/ObjectMember.java new file mode 100644 index 000000000..87012a0cc --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/ObjectMember.java @@ -0,0 +1,284 @@ +/* + * 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.cst; + +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 cond, ObjectBody body, @Nullable ObjectBody elseClause, Span span) { + super(span, Arrays.asList(cond, body, elseClause)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitWhenGenerator(this); + } + + public Expr getCond() { + 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/cst/Operator.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/Operator.java new file mode 100644 index 000000000..547d8cec9 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/Parameter.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/Parameter.java new file mode 100644 index 000000000..23bfbcfee --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/ParameterList.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/ParameterList.java new file mode 100644 index 000000000..f1621fc5b --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/QualifiedIdentifier.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/QualifiedIdentifier.java new file mode 100644 index 000000000..bc4493c28 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/ReplInput.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/ReplInput.java new file mode 100644 index 000000000..54f218cb6 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/StringConstant.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/StringConstant.java new file mode 100644 index 000000000..bc35328fc --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +import java.util.List; +import org.pkl.core.parser.ParserVisitor; +import org.pkl.core.parser.Span; +import org.pkl.core.parser.cst.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/cst/StringConstantPart.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/StringConstantPart.java new file mode 100644 index 000000000..1934f072a --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/StringPart.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/StringPart.java new file mode 100644 index 000000000..9647f88bb --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/Type.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/Type.java new file mode 100644 index 000000000..54c89b90d --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/Type.java @@ -0,0 +1,198 @@ +/* + * 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.cst; + +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 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 DefaultUnionType extends Type { + public DefaultUnionType(Type type, Span span) { + super(span, List.of(type)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitDefaultUnionType(this); + } + + public Type getType() { + return (Type) children.get(0); + } + } + + public static final class UnionType extends Type { + public UnionType(Type left, Type right, Span span) { + super(span, List.of(left, right)); + } + + @Override + public @Nullable T accept(ParserVisitor visitor) { + return visitor.visitUnionType(this); + } + + public Type getLeft() { + return (Type) children.get(0); + } + + public Type getRight() { + return (Type) children.get(1); + } + } + + 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/cst/TypeAlias.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/TypeAlias.java new file mode 100644 index 000000000..753219614 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/TypeAnnotation.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/TypeAnnotation.java new file mode 100644 index 000000000..4dee5c0e6 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/TypeParameter.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/TypeParameter.java new file mode 100644 index 000000000..b4c5f293f --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/TypeParameterList.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/TypeParameterList.java new file mode 100644 index 000000000..21288d1ac --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/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.cst; + +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/cst/package-info.java b/pkl-core/src/main/java/org/pkl/core/parser/cst/package-info.java new file mode 100644 index 000000000..84384cbf5 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/parser/cst/package-info.java @@ -0,0 +1,4 @@ +@NonnullByDefault +package org.pkl.core.parser.cst; + +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 3b437a297..d216307e9 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.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.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.cst.Class; +import org.pkl.core.parser.cst.ClassProperty; +import org.pkl.core.parser.cst.Expr; +import org.pkl.core.parser.cst.ImportClause; +import org.pkl.core.parser.cst.ModuleDecl; +import org.pkl.core.parser.cst.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 @@ private List handleEval(Eval request) { .collect(Collectors.toList()); } - @SuppressWarnings("StatementWithEmptyBody") + @SuppressWarnings({"StatementWithEmptyBody", "DataFlowIssue"}) private List evaluate( ReplState replState, String requestId, @@ -174,14 +177,12 @@ private List evaluate( 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 @@ private List evaluate( 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.cst.TypeAlias typeAlias) { addStaticModuleProperty(builder.visitTypeAlias(typeAlias)); - } else if (tree instanceof ClassMethodContext classMethod) { + } else if (tree instanceof org.pkl.core.parser.cst.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 @@ private ObjectMember addModuleProperty(UnresolvedPropertyNode propertyNode) { 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 @@ private VmTyped createEmptyReplModule(@Nullable VmTyped parent) { } 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 cce6ec58a..084d34f78 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.cst.Module; +import org.pkl.core.parser.cst.ObjectMember.ObjectProperty; +import org.pkl.core.parser.cst.Type; +import org.pkl.core.parser.cst.Type.DeclaredType; import org.pkl.core.util.Nullable; final class MinPklVersionChecker { @@ -44,26 +44,26 @@ static void check(VmTyped module, @Nullable Node importNode) { } } - 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 @@ static void check(String moduleName, @Nullable ParserRuleContext ctx, @Nullable } } - 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 @@ private static void doCheck( .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/VmLanguage.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java index b735bbcaf..a5e943e7a 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 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.cst.Module; import org.pkl.core.util.IoUtils; import org.pkl.core.util.Nullable; @@ -56,7 +55,6 @@ public CallTarget parse(ParsingRequest request) { throw new UnsupportedOperationException("parse"); } - @TruffleBoundary public VmTyped loadModule(ModuleKey moduleKey) { var context = VmContext.get(null); @@ -71,7 +69,6 @@ public VmTyped loadModule(ModuleKey moduleKey) { null); } - @TruffleBoundary public VmTyped loadModule(ModuleKey moduleKey, @Nullable Node importNode) { var context = VmContext.get(null); @@ -86,7 +83,6 @@ public VmTyped loadModule(ModuleKey moduleKey, @Nullable Node importNode) { importNode); } - @TruffleBoundary void initializeModule( ModuleKey moduleKey, ResolvedModuleKey resolvedModuleKey, @@ -95,12 +91,13 @@ void initializeModule( 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/VmUndefinedValueException.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmUndefinedValueException.java index dea2eea92..bf76dc2b7 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 b2f3ee2ac..b7367c62d 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.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.cst.Expr; import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.Nullable; @@ -506,7 +506,7 @@ public static Source createSource(ModuleKey moduleKey, String text) { } 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,25 +516,12 @@ public static VmException toVmException( 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(); } @@ -554,9 +541,6 @@ public static VmException toVmException(LexParseException e, Source source, Stri } } matcher.appendTail(builder); - var newLength = builder.length() - 1; - assert builder.charAt(newLength) == '\n'; - builder.setLength(newLength); return builder.toString(); } @@ -857,10 +841,10 @@ public static StackFrame createStackFrame(SourceSection section, @Nullable Strin 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 +875,9 @@ public static Object evaluateExpression( 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); + assert exprNode != null; 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 602141d11..5051fa0ea 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 cee2f76f2..22db522fb 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 51c0c2eb4..354d9b628 100644 --- a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties +++ b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties @@ -243,6 +243,9 @@ The content of a multi-line string must begin on a new line. closingStringDelimiterMustBeginOnNewLine=\ The closing delimiter of a multi-line string must begin on a new line. +singleQuoteStringNewline=\ +Single quoted strings cannot span multiple lines. + stringIndentationMustMatchLastLine=\ Line must match or exceed indentation of the String's last line. @@ -797,7 +800,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 properties and elements 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 +1130,45 @@ External {0} reader does not support scheme `{1}`. externalReaderAlreadyTerminated=\ External reader process has already terminated. + +keywordNotAllowedHere=\ +Keyword `{0}` is not allowed here. (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 class, typealias, method or property. + +interpolationInConstant=\ +String constant cannot have interpolated values. + +unexpectedToken=\ +Unexpected token. Expected `{0}`. + +unexpectedToken2=\ +Unexpected token. Expected `{0}` or `{1}`. + +unexpectedTokenMany=\ +Unexpected token. Expected one of {0}. + +unexpectedTokenForExpression=\ +Unexpected token. + +unexpectedTokenForType=\ +Unexpected token. Expected a type. + +typeAnnotationInAmends=\ +Expected expression but got object body. Amended properties cannot have type annotations. + +invalidProperty=\ +Invalid property definition. Expected type annotation, assignment or amending. + +unexpectedCharacter=\ +Unexpected character `{0}`. Did you mean {1}? + +invalidCharacter=\ +Invalid identifier `{0}`. diff --git a/pkl-core/src/main/antlr/PklLexer.g4 b/pkl-core/src/test/antlr/PklLexer.g4 similarity index 100% rename from pkl-core/src/main/antlr/PklLexer.g4 rename to pkl-core/src/test/antlr/PklLexer.g4 diff --git a/pkl-core/src/main/antlr/PklParser.g4 b/pkl-core/src/test/antlr/PklParser.g4 similarity index 100% rename from pkl-core/src/main/antlr/PklParser.g4 rename to pkl-core/src/test/antlr/PklParser.g4 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 c2fbe98aa..6927e08f4 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 e788f9a87..19b66b1a2 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 04ff7050e..9081af1a5 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 204f54268..320e3f5f3 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 4320c3275..adf9bfe5a 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 000000000..f9105b604 --- /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/lineCommentBetween.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/parser/lineCommentBetween.pkl new file mode 100644 index 000000000..9613b9834 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/parser/lineCommentBetween.pkl @@ -0,0 +1,7 @@ +/// doc comment +// line comments can appear between doc comments +/* + block comments also +*/ +/// doc continuation +foo = 1 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 000000000..32b97cd88 --- /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/output/basic/objectMemberInvalid.err b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/objectMemberInvalid.err index 552de5c22..aa15c2a84 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 properties and elements 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 e07485abd..188ff0f9c 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 properties and elements 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 7f834d265..87f3b3570 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 properties and elements 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 8b986a502..892154ae1 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: `{`, `=`, `:` +Single quoted strings cannot span multiple lines. -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 4693c1c36..94388ac4a 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. 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 0a5cfeaee..dc1f3262d 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 @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `}` delimiter. +Unexpected end of file. 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 ce95fd9bb..005586744 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 536d5b520..9db05ae38 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 2475d6a8a..a32a65416 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 ea5c89e17..610564936 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 @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `"` delimiter. +Single quoted strings cannot span multiple lines. 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 bf6597e51..4dc9cab22 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 4ac9c2051..6d15ec624 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 ca44b397f..eda48c339 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 803db7b09..ee2641444 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. 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 ca4adb163..22322dc1b 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 1f3e4ebae..d0667f45a 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. 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 f6443cd84..a13ca4b53 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 `)`. 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 0716c15f4..60a36a11e 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 26a14e031..ed2533eb3 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 fad93ab20..919b769ba 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. 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 d545e83cf..549697d22 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. 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 4213e0595..e5dd99cfb 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. 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 75b530672..50de480e6 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. 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 b623b1309..ba300d9ff 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. 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 cf9e13e32..b46444067 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. 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 36b818c1a..f12909851 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. 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 cd8946db7..cb8ca92f6 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. 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 8a15e2d6d..3670b62be 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 6b630c039..8900dac8d 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. 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 5b412183e..93d32f3f7 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 caa64853f..1b33d9a3c 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 @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `}` delimiter. +Unexpected end of file. 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 34f25075e..837f877b9 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 @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `}` delimiter. +Unexpected end of file. 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 702accee6..d5acf948d 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 @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `}` delimiter. +Unexpected end of file. 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 0376aecea..cc07a3935 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 @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `}` delimiter. +Unexpected end of file. 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 a0e61a909..7d7653e70 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 @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `}` delimiter. +Unexpected end of file. 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 65a6f6487..8bbc40f26 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. 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 009c8ff78..1e8cbdbe1 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. 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 5f7cf6f33..14116e67f 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 0d0c94019..dbca24fc9 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 @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `"#` delimiter. +Single quoted strings cannot span multiple lines. 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 146ce51ea..7d9041f33 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. 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 df6217ae0..efe21b5a4 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 @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `"` delimiter. +Single quoted strings cannot span multiple lines. 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 4393c579d..a8754dea5 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. 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 363d3e88f..fe5a52b26 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 4b1297b59..00faf2510 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 e625f1cf5..a8fc3162f 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. 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 fa7389698..d5d33887f 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 e067143d1..6fb53a1f3 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 `{`. 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 e1d598fa7..0ee3b855c 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 52b61bd34..d980e6bb5 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 `{`. 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 356b53f21..076e2c258 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/moduleAmendsSelf.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/moduleAmendsSelf.err index fe502db97..5f934ba7e 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/parser1.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser1.err index 16e81081a..3896513dd 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 866ed7224..bbb09b700 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 `{`. 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 13608c44a..2275c7146 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 f5a92304b..a400cd0a4 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 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 f02a77dda..217994803 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 d9ce6f9ef..7f9029bf5 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 cd8f26e21..443ebb6d3 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 915a62d99..27e3d658d 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 794ee1665..57289c5ec 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 4a3582242..d655929c8 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser2.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/parser2.err @@ -1,6 +1,6 @@ –– Pkl Error –– -Missing `"` delimiter. +Single quoted strings cannot span multiple lines. 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 0635c3b3e..550c19c5d 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 616fe1280..6e9fc1038 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 b7b67a2f4..080608bd7 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 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 542d79134..05d9441e7 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 742378fd7..d00790c7b 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. Expected a type. x | res1 = a@11 ^^ 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 ea8844ce6..754169e9d 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 000000000..a00a3800f --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/generators/spreadSyntaxNoSpace.err @@ -0,0 +1,7 @@ +–– Pkl Error –– +Object properties and elements 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 20660f2f5..5c5ffa907 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/lineCommentBetween.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/parser/lineCommentBetween.pcf new file mode 100644 index 000000000..c4e5bcc80 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/parser/lineCommentBetween.pcf @@ -0,0 +1 @@ +foo = 1 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 000000000..6311442c4 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/parser/spacesBetweenDocComments.err @@ -0,0 +1,6 @@ +–– Pkl Error –– +Invalid token at position. Expected class, typealias, method or property. + +x | /// doc comment continuation + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at spacesBetweenDocComments (file:///$snippetsDir/input/parser/spacesBetweenDocComments.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 8dc07f5c3..f3e580d36 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 a13013ae7..1e9dbd7b1 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 2a23443d2..938ba75ee 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 type annotation, assignment or amending. + 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 000000000..6861c4ed7 --- /dev/null +++ b/pkl-core/src/test/kotlin/org/pkl/core/parser/ANTLRSexpRenderer.kt @@ -0,0 +1,1041 @@ +/* + * 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() + buf.append('\n') + renderType(type.l) + buf.append('\n') + renderType(type.r) + buf.append(')') + tab = oldTab + } + + 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() + 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 7a2811a68..c476f34c5 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 000000000..7c9e3330c --- /dev/null +++ b/pkl-core/src/test/kotlin/org/pkl/core/parser/ParserComparisonTest.kt @@ -0,0 +1,91 @@ +/* + * 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", + "spacesBetweenDocComments.pkl", + "lineCommentBetween.pkl", + ) + + private val regexExceptions = + setOf(Regex(".*/errors/delimiters/.*"), Regex(".*/errors/parser\\d+\\.pkl")) + } +} 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 000000000..0b2b132f2 --- /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 000000000..8e7649a14 --- /dev/null +++ b/pkl-core/src/test/kotlin/org/pkl/core/parser/SexpRenderer.kt @@ -0,0 +1,1064 @@ +/* + * 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.cst.* +import org.pkl.core.parser.cst.Annotation +import org.pkl.core.parser.cst.Expr.* +import org.pkl.core.parser.cst.Expr.ModuleExpr +import org.pkl.core.parser.cst.ObjectMember.* +import org.pkl.core.parser.cst.Parameter.TypedIdentifier +import org.pkl.core.parser.cst.Type.* + +@Suppress("MemberVisibilityCanBePrivate") +class SexpRenderer { + private var tab = "" + private var buf = StringBuilder() + + fun render(mod: org.pkl.core.parser.cst.Module): String { + renderModule(mod) + val res = buf.toString() + reset() + return res + } + + fun renderModule(mod: org.pkl.core.parser.cst.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() + 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 DefaultUnionType -> renderDefaultUnionType(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 renderDefaultUnionType(type: DefaultUnionType) { + buf.append(tab) + buf.append("(defaultUnionType") + val oldTab = increaseTab() + buf.append('\n') + renderType(type.type) + buf.append(')') + tab = oldTab + } + + fun renderUnionType(type: UnionType) { + buf.append(tab) + buf.append("(unionType") + val oldTab = increaseTab() + buf.append('\n') + renderType(type.left) + buf.append('\n') + renderType(type.right) + 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.cond) + 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.cst.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 000000000..4dcd7af0a --- /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 e6fb76fdd..86da7653c 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 bf8034285..03ffd664f 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 0142ad905..97655b333 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 8be9e5fda..7f457bfc5 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 63c943df4..fa0bbcbcb 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 64b141b63..9d3771daa 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