From 75d540f1ec1b147ddbbfa005a0c57dec93b2a193 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Thu, 9 Apr 2026 14:09:16 +0200 Subject: [PATCH 01/49] Add syntax package --- .../org/pkl/core/runtime/ModuleCache.java | 2 + .../org/pkl/core/runtime/SyntaxModule.java | 61 + .../pkl/core/stdlib/syntax/SyntaxNodes.java | 124 ++ .../pkl/core/stdlib/syntax/package-info.java | 4 + .../input/syntax/expressions.pkl | 226 +++ .../input/syntax/moduleStructure.pkl | 221 +++ .../input/syntax/objectMembers.pkl | 170 ++ .../input/syntax/types.pkl | 104 ++ .../output/errors/cannotFindStdLibModule.err | 1 + .../output/syntax/expressions.pcf | 132 ++ .../output/syntax/moduleStructure.pcf | 135 ++ .../output/syntax/objectMembers.pcf | 113 ++ .../output/syntax/types.pcf | 64 + stdlib/syntax.pkl | 1601 +++++++++++++++++ 14 files changed, 2958 insertions(+) create mode 100644 pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java create mode 100644 pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java create mode 100644 pkl-core/src/main/java/org/pkl/core/stdlib/syntax/package-info.java create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/syntax/types.pcf create mode 100644 stdlib/syntax.pkl diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/ModuleCache.java b/pkl-core/src/main/java/org/pkl/core/runtime/ModuleCache.java index 3467a381c..b5060019c 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/ModuleCache.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/ModuleCache.java @@ -115,6 +115,8 @@ public synchronized VmTyped getOrLoad( case "settings": // always needed if ~/.pkl/settings.pkl is present return SettingsModule.getModule(); + case "syntax": + return SyntaxModule.getModule(); case "test": return TestModule.getModule(); case "xml": diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java new file mode 100644 index 000000000..923831e0a --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java @@ -0,0 +1,61 @@ +/* + * Copyright © 2025-2026 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.runtime; + +import com.oracle.truffle.api.CompilerDirectives; +import java.net.URI; + +public final class SyntaxModule extends StdLibModule { + private static final VmTyped instance = VmUtils.createEmptyModule(); + + static { + loadModule(URI.create("pkl:syntax"), instance); + } + + public static VmTyped getModule() { + return instance; + } + + public static VmClass getNodeClass() { + return NodeClass.instance; + } + + public static VmClass getSpanClass() { + return SpanClass.instance; + } + + public static VmClass getParserErrorClass() { + return ParserErrorClass.instance; + } + + private static final class NodeClass { + static final VmClass instance = loadClass("Node"); + } + + private static final class SpanClass { + static final VmClass instance = loadClass("Span"); + } + + private static final class ParserErrorClass { + static final VmClass instance = loadClass("ParserError"); + } + + @CompilerDirectives.TruffleBoundary + private static VmClass loadClass(String className) { + var theModule = getModule(); + return (VmClass) VmUtils.readMember(theModule, Identifier.get(className)); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java new file mode 100644 index 000000000..9018bc2ea --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java @@ -0,0 +1,124 @@ +/* + * Copyright © 2025-2026 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.stdlib.syntax; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.Specialization; +import java.util.ArrayList; +import org.pkl.core.runtime.SyntaxModule; +import org.pkl.core.runtime.VmList; +import org.pkl.core.runtime.VmNull; +import org.pkl.core.runtime.VmTyped; +import org.pkl.core.stdlib.ExternalMethod1Node; +import org.pkl.core.stdlib.VmObjectFactory; +import org.pkl.parser.GenericParser; +import org.pkl.parser.GenericParserError; +import org.pkl.parser.syntax.generic.FullSpan; +import org.pkl.parser.syntax.generic.Node; + +public final class SyntaxNodes { + private SyntaxNodes() {} + + /** Extra storage backing a Pkl {@code Node} instance. */ + static final class NodeData { + final Node node; + final char[] source; + VmTyped parentVm; + VmList childrenVm; + VmTyped spanVm; + + NodeData(Node node, char[] source) { + this.node = node; + this.source = source; + } + } + + /** Extra storage backing a Pkl {@code ParserError} instance. */ + static final class ErrorData { + final String text; + final VmTyped spanVm; + + ErrorData(String text, VmTyped spanVm) { + this.text = text; + this.spanVm = spanVm; + } + } + + private static final VmObjectFactory spanFactory = + new VmObjectFactory(SyntaxModule::getSpanClass) + .addIntProperty("lineStart", FullSpan::lineBegin) + .addIntProperty("colStart", FullSpan::colBegin) + .addIntProperty("lineEnd", FullSpan::lineEnd) + .addIntProperty("colEnd", FullSpan::colEnd); + + private static final VmObjectFactory nodeFactory = + new VmObjectFactory(SyntaxModule::getNodeClass) + .addStringProperty("type", nd -> nd.node.type.name().toLowerCase()) + .addListProperty("children", nd -> nd.childrenVm) + .addProperty("parent", nd -> VmNull.lift(nd.parentVm)) + .addProperty( + "text", + nd -> nd.node.children.isEmpty() ? nd.node.text(nd.source) : VmNull.withoutDefault()) + .addTypedProperty("span", nd -> nd.spanVm); + + private static final VmObjectFactory parserErrorFactory = + new VmObjectFactory(SyntaxModule::getParserErrorClass) + .addStringProperty("text", ed -> ed.text) + .addTypedProperty("span", ed -> ed.spanVm); + + public abstract static class parseNodes extends ExternalMethod1Node { + @Specialization + @TruffleBoundary + protected Object eval(VmTyped self, String source) { + var sourceChars = source.toCharArray(); + + try { + var parser = new GenericParser(); + var root = parser.parseModule(source); + return convertNode(root, sourceChars); + } catch (GenericParserError e) { + var errorSpanVm = spanFactory.create(e.getSpan()); + var text = e.getMessage() != null ? e.getMessage() : "Parse error"; + return parserErrorFactory.create(new ErrorData(text, errorSpanVm)); + } + } + + @TruffleBoundary + private static VmTyped convertNode(Node genericNode, char[] sourceChars) { + // Convert children recursively + var childrenList = new ArrayList(genericNode.children.size()); + for (var child : genericNode.children) { + childrenList.add(convertNode(child, sourceChars)); + } + + // Build NodeData + var data = new NodeData(genericNode, sourceChars); + data.childrenVm = VmList.create(childrenList.toArray()); + data.spanVm = spanFactory.create(genericNode.span); + + // Create VmTyped node + var result = nodeFactory.create(data); + + // Set parent back-reference on each child + for (var childVm : childrenList) { + var childData = (NodeData) childVm.getExtraStorage(); + childData.parentVm = result; + } + + return result; + } + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/package-info.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/package-info.java new file mode 100644 index 000000000..6f12d5850 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/package-info.java @@ -0,0 +1,4 @@ +@NonnullByDefault +package org.pkl.core.stdlib.syntax; + +import org.pkl.core.util.NonnullByDefault; diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl new file mode 100644 index 000000000..8ca130a2a --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl @@ -0,0 +1,226 @@ +amends "../snippetTest.pkl" + +import "pkl:syntax" + +local function parse(source: String) = syntax.parse(source) + +local function expr(source: String) = + let (result = parse("x = \(source)")) + (result as syntax.ModuleNode).properties.first.value + +examples { + ["literals"] { + local boolTrue = expr("true") + boolTrue is syntax.BoolLiteralExprNode + (boolTrue as syntax.BoolLiteralExprNode).value == true + + local boolFalse = expr("false") + boolFalse is syntax.BoolLiteralExprNode + (boolFalse as syntax.BoolLiteralExprNode).value == false + + local intLit = expr("42") + intLit is syntax.IntLiteralExprNode + (intLit as syntax.IntLiteralExprNode).text == "42" + + local hexLit = expr("0xFF") + hexLit is syntax.IntLiteralExprNode + (hexLit as syntax.IntLiteralExprNode).text == "0xFF" + + local floatLit = expr("3.14") + floatLit is syntax.FloatLiteralExprNode + (floatLit as syntax.FloatLiteralExprNode).text == "3.14" + + local nullLit = expr("null") + nullLit is syntax.NullLiteralExprNode + } + + ["strings"] { + local simple = expr(#""hello""#) + simple is syntax.SingleLineStringLiteralExprNode + (simple as syntax.SingleLineStringLiteralExprNode).parts.length == 1 + (simple as syntax.SingleLineStringLiteralExprNode).parts.first is syntax.StringCharsNode + ((simple as syntax.SingleLineStringLiteralExprNode).parts.first as syntax.StringCharsNode).value == "hello" + + local withEscape = expr(#""hello\nworld""#) + withEscape is syntax.SingleLineStringLiteralExprNode + local escapeParts = (withEscape as syntax.SingleLineStringLiteralExprNode).parts + escapeParts.length == 3 + escapeParts[0] is syntax.StringCharsNode + escapeParts[1] is syntax.StringEscapeNode + (escapeParts[1] as syntax.StringEscapeNode).value == #"\n"# + + local withInterp = expr(#""hello \(name)""#) + withInterp is syntax.SingleLineStringLiteralExprNode + local interpParts = (withInterp as syntax.SingleLineStringLiteralExprNode).parts + interpParts.length == 2 + interpParts[0] is syntax.StringCharsNode + interpParts[1] is syntax.StringInterpolationNode + (interpParts[1] as syntax.StringInterpolationNode).expression is syntax.UnqualifiedAccessExprNode + + local multiLine = expr(#""" + """ + hello + """ + """#) + multiLine is syntax.MultiLineStringLiteralExprNode + local mlParts = (multiLine as syntax.MultiLineStringLiteralExprNode).parts + mlParts.length >= 1 + } + + ["keyword expressions"] { + local thisExpr = expr("this") + thisExpr is syntax.ThisExprNode + + local outerExpr = expr("outer") + outerExpr is syntax.OuterExprNode + + local moduleExpr = expr("module") + moduleExpr is syntax.ModuleExprNode + } + + ["access expressions"] { + local unqual = expr("foo") + unqual is syntax.UnqualifiedAccessExprNode + (unqual as syntax.UnqualifiedAccessExprNode).identifier.value == "foo" + (unqual as syntax.UnqualifiedAccessExprNode).argumentList == null + + local withArgs = expr("foo(1, 2)") + withArgs is syntax.UnqualifiedAccessExprNode + (withArgs as syntax.UnqualifiedAccessExprNode).identifier.value == "foo" + (withArgs as syntax.UnqualifiedAccessExprNode).argumentList != null + (withArgs as syntax.UnqualifiedAccessExprNode).argumentList!!.arguments.length == 2 + + local qual = expr("foo.bar") + qual is syntax.QualifiedAccessExprNode + (qual as syntax.QualifiedAccessExprNode).receiver is syntax.UnqualifiedAccessExprNode + (qual as syntax.QualifiedAccessExprNode).isNullSafe == false + (qual as syntax.QualifiedAccessExprNode).member.identifier.value == "bar" + + local nullSafe = expr("foo?.bar") + nullSafe is syntax.QualifiedAccessExprNode + (nullSafe as syntax.QualifiedAccessExprNode).isNullSafe == true + + local subscript = expr("foo[0]") + subscript is syntax.SubscriptExprNode + (subscript as syntax.SubscriptExprNode).receiver is syntax.UnqualifiedAccessExprNode + (subscript as syntax.SubscriptExprNode).index is syntax.IntLiteralExprNode + } + + ["binary operators"] { + local add = expr("1 + 2") + add is syntax.BinaryOpExprNode + (add as syntax.BinaryOpExprNode).operator == "+" + (add as syntax.BinaryOpExprNode).leftExpr is syntax.IntLiteralExprNode + (add as syntax.BinaryOpExprNode).rightExpr is syntax.IntLiteralExprNode + + local eq = expr("a == b") + eq is syntax.BinaryOpExprNode + (eq as syntax.BinaryOpExprNode).operator == "==" + + local pipeline = expr("a |> b") + pipeline is syntax.BinaryOpExprNode + (pipeline as syntax.BinaryOpExprNode).operator == "|>" + + local nullCoalesce = expr("a ?? b") + nullCoalesce is syntax.BinaryOpExprNode + (nullCoalesce as syntax.BinaryOpExprNode).operator == "??" + + local isOp = expr("a is String") + isOp is syntax.BinaryOpExprNode + (isOp as syntax.BinaryOpExprNode).operator == "is" + (isOp as syntax.BinaryOpExprNode).rightType is syntax.DeclaredTypeNode + + local asOp = expr("a as String") + asOp is syntax.BinaryOpExprNode + (asOp as syntax.BinaryOpExprNode).operator == "as" + (asOp as syntax.BinaryOpExprNode).rightType is syntax.DeclaredTypeNode + } + + ["unary operators"] { + local neg = expr("-42") + neg is syntax.UnaryMinusExprNode + (neg as syntax.UnaryMinusExprNode).operand is syntax.IntLiteralExprNode + + local notExpr = expr("!flag") + notExpr is syntax.LogicalNotExprNode + (notExpr as syntax.LogicalNotExprNode).operand is syntax.UnqualifiedAccessExprNode + + local nonNull = expr("x!!") + nonNull is syntax.NonNullExprNode + (nonNull as syntax.NonNullExprNode).operand is syntax.UnqualifiedAccessExprNode + } + + ["if expression"] { + local ifExpr = expr("if (x) 1 else 2") + ifExpr is syntax.IfExprNode + (ifExpr as syntax.IfExprNode).condition is syntax.UnqualifiedAccessExprNode + (ifExpr as syntax.IfExprNode).thenExpr is syntax.IntLiteralExprNode + (ifExpr as syntax.IfExprNode).elseExpr is syntax.IntLiteralExprNode + } + + ["let expression"] { + local letExpr = expr("let (y = 1) y + 1") + letExpr is syntax.LetExprNode + (letExpr as syntax.LetExprNode).parameter.identifier!!.value == "y" + (letExpr as syntax.LetExprNode).bindingValue is syntax.IntLiteralExprNode + (letExpr as syntax.LetExprNode).bodyExpr is syntax.BinaryOpExprNode + } + + ["new expression"] { + local newExpr = expr("new Mapping { [\"a\"] = 1 }") + newExpr is syntax.NewExprNode + (newExpr as syntax.NewExprNode).type is syntax.DeclaredTypeNode + (newExpr as syntax.NewExprNode).body is syntax.ObjectBodyNode + } + + ["function literal"] { + local fn = expr("(x, y) -> x + y") + fn is syntax.FunctionLiteralExprNode + (fn as syntax.FunctionLiteralExprNode).parameterList.parameters.length == 2 + (fn as syntax.FunctionLiteralExprNode).parameterList.parameters[0].identifier!!.value == "x" + (fn as syntax.FunctionLiteralExprNode).parameterList.parameters[1].identifier!!.value == "y" + (fn as syntax.FunctionLiteralExprNode).body is syntax.BinaryOpExprNode + } + + ["parenthesized expression"] { + local paren = expr("(1 + 2)") + paren is syntax.ParenthesizedExprNode + (paren as syntax.ParenthesizedExprNode).expression is syntax.BinaryOpExprNode + } + + ["throw and trace"] { + local throwExpr = expr(#"throw("error")"#) + throwExpr is syntax.ThrowExprNode + (throwExpr as syntax.ThrowExprNode).expression is syntax.SingleLineStringLiteralExprNode + + local traceExpr = expr("trace(42)") + traceExpr is syntax.TraceExprNode + (traceExpr as syntax.TraceExprNode).expression is syntax.IntLiteralExprNode + } + + ["import expression"] { + local impExpr = expr(#"import("foo.pkl")"#) + impExpr is syntax.ImportExprNode + (impExpr as syntax.ImportExprNode).isGlob == false + (impExpr as syntax.ImportExprNode).uri == "foo.pkl" + + local impGlob = expr(#"import*("*.pkl")"#) + impGlob is syntax.ImportExprNode + (impGlob as syntax.ImportExprNode).isGlob == true + (impGlob as syntax.ImportExprNode).uri == "*.pkl" + } + + ["read expression"] { + local readExpr = expr(#"read("file.txt")"#) + readExpr is syntax.ReadExprNode + (readExpr as syntax.ReadExprNode).keyword == "read" + + local readOpt = expr(#"read?("file.txt")"#) + readOpt is syntax.ReadExprNode + (readOpt as syntax.ReadExprNode).keyword == "read?" + + local readGlob = expr(#"read*("*.txt")"#) + readGlob is syntax.ReadExprNode + (readGlob as syntax.ReadExprNode).keyword == "read*" + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl new file mode 100644 index 000000000..780faf4a4 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl @@ -0,0 +1,221 @@ +amends "../snippetTest.pkl" + +import "pkl:syntax" + +local function parse(source: String) = syntax.parse(source) + +examples { + ["module declaration"] { + local result = parse("module my.app") + result is syntax.ModuleNode + local mod = result as syntax.ModuleNode + mod.declaration != null + mod.declaration!!.name != null + mod.declaration!!.name!!.identifiers.length == 2 + mod.declaration!!.name!!.identifiers[0].value == "my" + mod.declaration!!.name!!.identifiers[1].value == "app" + mod.declaration!!.docComment == null + mod.declaration!!.annotations.length == 0 + mod.declaration!!.modifiers == null + mod.declaration!!.amendsClause == null + mod.declaration!!.extendsClause == null + } + + ["module with modifiers and doc comment"] { + local result = parse(""" + /// This is my module. + @Deprecated { message = "use other" } + open module my.mod + """) + local mod = result as syntax.ModuleNode + mod.declaration != null + mod.declaration!!.docComment != null + mod.declaration!!.docComment!! is syntax.DocCommentNode + mod.declaration!!.docComment!!.lines.length == 1 + + mod.declaration!!.annotations.length == 1 + mod.declaration!!.annotations.first is syntax.AnnotationNode + mod.declaration!!.annotations.first.type is syntax.DeclaredTypeNode + + mod.declaration!!.modifiers != null + mod.declaration!!.modifiers!! is syntax.ModifierListNode + mod.declaration!!.modifiers!!.modifiers == List("open") + } + + ["amends clause"] { + local result = parse(#"amends "base.pkl""#) + local mod = result as syntax.ModuleNode + mod.declaration != null + mod.declaration!!.amendsClause != null + mod.declaration!!.amendsClause!! is syntax.AmendsClauseNode + mod.declaration!!.amendsClause!!.uri == "base.pkl" + mod.declaration!!.extendsClause == null + } + + ["extends clause"] { + local result = parse(#"extends "base.pkl""#) + local mod = result as syntax.ModuleNode + mod.declaration != null + mod.declaration!!.extendsClause != null + mod.declaration!!.extendsClause!! is syntax.ExtendsClauseNode + mod.declaration!!.extendsClause!!.uri == "base.pkl" + mod.declaration!!.amendsClause == null + } + + ["imports"] { + local result = parse(""" + import "foo.pkl" + import "bar.pkl" as myBar + import* "*.pkl" + """) + local mod = result as syntax.ModuleNode + mod.imports.length == 3 + + mod.imports[0] is syntax.ImportNode + mod.imports[0].uri == "foo.pkl" + mod.imports[0].isGlob == false + mod.imports[0].alias == null + + mod.imports[1].uri == "bar.pkl" + mod.imports[1].alias != null + mod.imports[1].alias!!.value == "myBar" + + mod.imports[2].uri == "*.pkl" + mod.imports[2].isGlob == true + } + + ["class declaration"] { + local result = parse(""" + /// A bird class. + abstract class Bird { + name: String + function fly(speed: Int): Boolean = true + } + """) + local mod = result as syntax.ModuleNode + mod.classes.length == 1 + local cls = mod.classes.first + cls is syntax.ClassNode + + cls.docComment != null + cls.docComment!!.lines.length == 1 + + cls.modifiers != null + cls.modifiers!!.modifiers == List("abstract") + + cls.name.value == "Bird" + + cls.typeParameterList == null + cls.extendsClause == null + + cls.body != null + cls.body!!.properties.length == 1 + cls.body!!.properties.first.name.value == "name" + cls.body!!.properties.first.typeAnnotation != null + cls.body!!.properties.first.typeAnnotation!!.type is syntax.DeclaredTypeNode + cls.body!!.properties.first.value == null + + cls.body!!.methods.length == 1 + cls.body!!.methods.first.name.value == "fly" + cls.body!!.methods.first.parameterList.parameters.length == 1 + cls.body!!.methods.first.parameterList.parameters.first.identifier!!.value == "speed" + cls.body!!.methods.first.returnType != null + cls.body!!.methods.first.body != null + cls.body!!.methods.first.body is syntax.BoolLiteralExprNode + } + + ["class with extends and type parameters"] { + local result = parse(""" + class Container extends Base { + item: T + } + """) + local mod = result as syntax.ModuleNode + local cls = mod.classes.first + + cls.name.value == "Container" + cls.typeParameterList != null + cls.typeParameterList!!.typeParameters.length == 1 + cls.typeParameterList!!.typeParameters.first.name.value == "T" + cls.typeParameterList!!.typeParameters.first.variance == null + + cls.extendsClause != null + cls.extendsClause is syntax.DeclaredTypeNode + } + + ["typealias"] { + local result = parse("typealias Positive = Int(this > 0)") + local mod = result as syntax.ModuleNode + mod.typeAliases.length == 1 + local ta = mod.typeAliases.first + ta is syntax.TypeAliasNode + ta.name.value == "Positive" + ta.typeParameterList == null + ta.type is syntax.ConstrainedTypeNode + } + + ["top-level properties and methods"] { + local result = parse(""" + hidden name: String = "pkl" + local count: Int = 42 + function greet(who: String): String = "hi" + """) + local mod = result as syntax.ModuleNode + + mod.properties.length == 2 + mod.properties[0].name.value == "name" + mod.properties[0].modifiers != null + mod.properties[0].modifiers!!.modifiers == List("hidden") + mod.properties[0].value is syntax.SingleLineStringLiteralExprNode + + mod.properties[1].name.value == "count" + mod.properties[1].modifiers != null + mod.properties[1].modifiers!!.modifiers == List("local") + + mod.methods.length == 1 + mod.methods.first.name.value == "greet" + mod.methods.first.parameterList.parameters.length == 1 + mod.methods.first.returnType != null + mod.methods.first.body is syntax.SingleLineStringLiteralExprNode + } + + ["parameter variations"] { + local result = parse("function f(x: Int, _, y): Boolean = true") + local mod = result as syntax.ModuleNode + local params = mod.methods.first.parameterList.parameters + + params.length == 3 + + params[0].identifier != null + params[0].identifier!!.value == "x" + params[0].typeAnnotation != null + params[0].isWildcard == false + + params[1].identifier == null + params[1].isWildcard == true + params[1].typeAnnotation == null + + params[2].identifier != null + params[2].identifier!!.value == "y" + params[2].typeAnnotation == null + params[2].isWildcard == false + } + + ["parser error"] { + local result = parse("x = {{{") + result is syntax.ParserError + (result as syntax.ParserError).text.length > 0 + } + + ["empty module"] { + local result = parse("") + result is syntax.ModuleNode + local mod = result as syntax.ModuleNode + mod.declaration == null + mod.imports.length == 0 + mod.classes.length == 0 + mod.typeAliases.length == 0 + mod.properties.length == 0 + mod.methods.length == 0 + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl new file mode 100644 index 000000000..7f7baafb2 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl @@ -0,0 +1,170 @@ +amends "../snippetTest.pkl" + +import "pkl:syntax" + +local function parse(source: String) = syntax.parse(source) + +local function body(source: String) = + let (result = parse("x { \(source) }")) + let (mod = result as syntax.ModuleNode) + mod.properties.first.objectBodies.first + +examples { + ["object property"] { + local b = body("name = \"hello\"") + b.properties.length == 1 + b.properties.first is syntax.ObjectPropertyNode + b.properties.first.name.value == "name" + b.properties.first.value is syntax.SingleLineStringLiteralExprNode + b.properties.first.modifiers == null + b.properties.first.typeAnnotation == null + b.properties.first.objectBodies.length == 0 + } + + ["object property with type and modifiers"] { + local b = body("hidden name: String = \"hello\"") + b.properties.length == 1 + b.properties.first.modifiers != null + b.properties.first.modifiers!!.modifiers == List("hidden") + b.properties.first.typeAnnotation != null + b.properties.first.typeAnnotation!!.type is syntax.DeclaredTypeNode + } + + ["object property with amending body"] { + local b = body("inner { x = 1 }") + b.properties.length == 1 + b.properties.first.name.value == "inner" + b.properties.first.value == null + b.properties.first.objectBodies.length == 1 + b.properties.first.objectBodies.first is syntax.ObjectBodyNode + } + + ["object method"] { + local b = body("function greet(who: String): String = \"hi\"") + b.methods.length == 1 + b.methods.first is syntax.ObjectMethodNode + b.methods.first.name.value == "greet" + b.methods.first.parameterList.parameters.length == 1 + b.methods.first.parameterList.parameters.first.identifier!!.value == "who" + b.methods.first.returnType != null + b.methods.first.body is syntax.SingleLineStringLiteralExprNode + } + + ["object element"] { + local b = body("1\n 2\n 3") + b.elements.length == 3 + b.elements[0] is syntax.ObjectElementNode + b.elements[0].expression is syntax.IntLiteralExprNode + b.elements[1].expression is syntax.IntLiteralExprNode + b.elements[2].expression is syntax.IntLiteralExprNode + } + + ["object entry"] { + local b = body("[\"key\"] = 42") + b.entries.length == 1 + b.entries.first is syntax.ObjectEntryNode + b.entries.first.key is syntax.SingleLineStringLiteralExprNode + b.entries.first.value is syntax.IntLiteralExprNode + } + + ["object entry with amending body"] { + local b = body("[\"key\"] { x = 1 }") + b.entries.length == 1 + b.entries.first.key is syntax.SingleLineStringLiteralExprNode + b.entries.first.objectBodies.length == 1 + } + + ["object spread"] { + local b = body("...other") + b.members.length == 1 + b.members.first is syntax.ObjectSpreadNode + (b.members.first as syntax.ObjectSpreadNode).isNullable == false + (b.members.first as syntax.ObjectSpreadNode).expression is syntax.UnqualifiedAccessExprNode + } + + ["nullable object spread"] { + local b = body("...?other") + b.members.length == 1 + b.members.first is syntax.ObjectSpreadNode + (b.members.first as syntax.ObjectSpreadNode).isNullable == true + } + + ["member predicate"] { + local b = body("[[name == \"foo\"]] = 1") + b.members.length == 1 + b.members.first is syntax.MemberPredicateNode + (b.members.first as syntax.MemberPredicateNode).condition is syntax.BinaryOpExprNode + (b.members.first as syntax.MemberPredicateNode).value is syntax.IntLiteralExprNode + } + + ["member predicate with amending body"] { + local b = body("[[name == \"foo\"]] { x = 1 }") + local pred = b.members.first as syntax.MemberPredicateNode + pred.condition is syntax.BinaryOpExprNode + pred.value == null + pred.objectBodies.length == 1 + } + + ["for generator"] { + local b = body("for (item in items) { item }") + b.members.length == 1 + b.members.first is syntax.ForGeneratorNode + local gen = b.members.first as syntax.ForGeneratorNode + gen.keyParameter == null + gen.valueParameter.identifier!!.value == "item" + gen.iterable is syntax.UnqualifiedAccessExprNode + gen.body is syntax.ObjectBodyNode + } + + ["for generator with key"] { + local b = body("for (k, v in items) { v }") + local gen = b.members.first as syntax.ForGeneratorNode + gen.keyParameter != null + gen.keyParameter!!.identifier!!.value == "k" + gen.valueParameter.identifier!!.value == "v" + } + + ["when generator"] { + local b = body("when (flag) { 1 }") + b.members.length == 1 + b.members.first is syntax.WhenGeneratorNode + local gen = b.members.first as syntax.WhenGeneratorNode + gen.condition is syntax.UnqualifiedAccessExprNode + gen.thenBody is syntax.ObjectBodyNode + gen.elseBody == null + } + + ["when generator with else"] { + local b = body("when (flag) { 1 } else { 2 }") + local gen = b.members.first as syntax.WhenGeneratorNode + gen.condition is syntax.UnqualifiedAccessExprNode + gen.thenBody is syntax.ObjectBodyNode + gen.elseBody != null + gen.elseBody is syntax.ObjectBodyNode + } + + ["object body with parameters"] { + local result = parse("x = new Listing { a, b -> a }") + local mod = result as syntax.ModuleNode + local propVal = mod.properties.first.value + propVal is syntax.NewExprNode + local newBody = (propVal as syntax.NewExprNode).body + newBody.parameters.length == 2 + newBody.parameters[0].identifier!!.value == "a" + newBody.parameters[1].identifier!!.value == "b" + } + + ["mixed object members"] { + local b = body(""" + name = "pkl" + 1 + ["key"] = 2 + function f() = 3 + """) + b.properties.length == 1 + b.elements.length == 1 + b.entries.length == 1 + b.methods.length == 1 + b.members.length == 4 + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl new file mode 100644 index 000000000..9b280e04a --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl @@ -0,0 +1,104 @@ +amends "../snippetTest.pkl" + +import "pkl:syntax" + +local function parse(source: String) = syntax.parse(source) + +local function typeOf(typeSource: String) = + let (result = parse("x: \(typeSource) = 0")) + (result as syntax.ModuleNode).properties.first.typeAnnotation!!.type + +examples { + ["simple types"] { + local unknownT = typeOf("unknown") + unknownT is syntax.UnknownTypeNode + + local nothingT = typeOf("nothing") + nothingT is syntax.NothingTypeNode + + local moduleT = typeOf("module") + moduleT is syntax.ModuleTypeNode + } + + ["declared type"] { + local simple = typeOf("String") + simple is syntax.DeclaredTypeNode + (simple as syntax.DeclaredTypeNode).name.identifiers.length == 1 + (simple as syntax.DeclaredTypeNode).name.identifiers.first.value == "String" + (simple as syntax.DeclaredTypeNode).typeArgumentList == null + + local withArgs = typeOf("List") + withArgs is syntax.DeclaredTypeNode + (withArgs as syntax.DeclaredTypeNode).name.identifiers.first.value == "List" + (withArgs as syntax.DeclaredTypeNode).typeArgumentList != null + (withArgs as syntax.DeclaredTypeNode).typeArgumentList!!.typeArguments.length == 1 + (withArgs as syntax.DeclaredTypeNode).typeArgumentList!!.typeArguments.first is syntax.DeclaredTypeNode + + local multiArgs = typeOf("Map") + multiArgs is syntax.DeclaredTypeNode + (multiArgs as syntax.DeclaredTypeNode).typeArgumentList!!.typeArguments.length == 2 + } + + ["nullable type"] { + local nullable = typeOf("String?") + nullable is syntax.NullableTypeNode + (nullable as syntax.NullableTypeNode).baseType is syntax.DeclaredTypeNode + } + + ["union type"] { + local union = typeOf("String|Int|Boolean") + union is syntax.UnionTypeNode + (union as syntax.UnionTypeNode).members.length == 3 + (union as syntax.UnionTypeNode).members[0] is syntax.DeclaredTypeNode + (union as syntax.UnionTypeNode).members[1] is syntax.DeclaredTypeNode + (union as syntax.UnionTypeNode).members[2] is syntax.DeclaredTypeNode + } + + ["function type"] { + local fnType = typeOf("(Int, String) -> Boolean") + fnType is syntax.FunctionTypeNode + (fnType as syntax.FunctionTypeNode).parameterTypes.length == 2 + (fnType as syntax.FunctionTypeNode).parameterTypes[0] is syntax.DeclaredTypeNode + (fnType as syntax.FunctionTypeNode).parameterTypes[1] is syntax.DeclaredTypeNode + (fnType as syntax.FunctionTypeNode).returnType is syntax.DeclaredTypeNode + + local noParams = typeOf("() -> String") + noParams is syntax.FunctionTypeNode + (noParams as syntax.FunctionTypeNode).parameterTypes.length == 0 + (noParams as syntax.FunctionTypeNode).returnType is syntax.DeclaredTypeNode + } + + ["constrained type"] { + local constrained = typeOf("Int(this >= 0)") + constrained is syntax.ConstrainedTypeNode + (constrained as syntax.ConstrainedTypeNode).baseType is syntax.DeclaredTypeNode + (constrained as syntax.ConstrainedTypeNode).constraints.length == 1 + (constrained as syntax.ConstrainedTypeNode).constraints.first is syntax.BinaryOpExprNode + } + + ["parenthesized type"] { + local paren = typeOf("(String)") + paren is syntax.ParenthesizedTypeNode + (paren as syntax.ParenthesizedTypeNode).type is syntax.DeclaredTypeNode + } + + ["string constant type"] { + local result = parse(#"typealias Foo = "bar"|"baz""#) + local ta = (result as syntax.ModuleNode).typeAliases.first + ta.type is syntax.UnionTypeNode + local members = (ta.type as syntax.UnionTypeNode).members + members.length == 2 + members[0] is syntax.StringConstantTypeNode + (members[0] as syntax.StringConstantTypeNode).value == "bar" + members[1] is syntax.StringConstantTypeNode + (members[1] as syntax.StringConstantTypeNode).value == "baz" + } + + ["type annotation"] { + local result = parse("x: String = \"hello\"") + local prop = (result as syntax.ModuleNode).properties.first + prop.typeAnnotation != null + prop.typeAnnotation!! is syntax.TypeAnnotationNode + prop.typeAnnotation!!.type is syntax.DeclaredTypeNode + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/cannotFindStdLibModule.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/cannotFindStdLibModule.err index 8e835110d..236355ef6 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/cannotFindStdLibModule.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/cannotFindStdLibModule.err @@ -26,6 +26,7 @@ pkl:release pkl:semver pkl:settings pkl:shell +pkl:syntax pkl:test pkl:xml pkl:yaml diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf new file mode 100644 index 000000000..4a44f87b6 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf @@ -0,0 +1,132 @@ +examples { + ["literals"] { + true + true + true + true + true + true + true + true + true + true + true + } + ["strings"] { + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + } + ["keyword expressions"] { + true + true + true + } + ["access expressions"] { + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + } + ["binary operators"] { + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + } + ["unary operators"] { + true + true + true + true + true + true + } + ["if expression"] { + true + true + true + true + } + ["let expression"] { + true + true + true + true + } + ["new expression"] { + true + true + true + } + ["function literal"] { + true + true + true + true + true + } + ["parenthesized expression"] { + true + true + } + ["throw and trace"] { + true + true + true + true + } + ["import expression"] { + true + true + true + true + true + true + } + ["read expression"] { + true + true + true + true + true + true + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf new file mode 100644 index 000000000..254b9fbbe --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf @@ -0,0 +1,135 @@ +examples { + ["module declaration"] { + true + true + true + true + true + true + true + true + true + true + true + } + ["module with modifiers and doc comment"] { + true + true + true + true + true + true + true + true + true + true + } + ["amends clause"] { + true + true + true + true + true + } + ["extends clause"] { + true + true + true + true + true + } + ["imports"] { + true + true + true + true + true + true + true + true + true + true + } + ["class declaration"] { + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + } + ["class with extends and type parameters"] { + true + true + true + true + true + true + true + } + ["typealias"] { + true + true + true + true + true + } + ["top-level properties and methods"] { + true + true + true + true + true + true + true + true + true + true + true + true + true + } + ["parameter variations"] { + true + true + true + true + true + true + true + true + true + true + true + true + } + ["parser error"] { + true + true + } + ["empty module"] { + true + true + true + true + true + true + true + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf new file mode 100644 index 000000000..963532ff0 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf @@ -0,0 +1,113 @@ +examples { + ["object property"] { + true + true + true + true + true + true + true + } + ["object property with type and modifiers"] { + true + true + true + true + true + } + ["object property with amending body"] { + true + true + true + true + true + } + ["object method"] { + true + true + true + true + true + true + true + } + ["object element"] { + true + true + true + true + true + } + ["object entry"] { + true + true + true + true + } + ["object entry with amending body"] { + true + true + true + } + ["object spread"] { + true + true + true + true + } + ["nullable object spread"] { + true + true + true + } + ["member predicate"] { + true + true + true + true + } + ["member predicate with amending body"] { + true + true + true + } + ["for generator"] { + true + true + true + true + true + true + } + ["for generator with key"] { + true + true + true + } + ["when generator"] { + true + true + true + true + true + } + ["when generator with else"] { + true + true + true + true + } + ["object body with parameters"] { + true + true + true + true + } + ["mixed object members"] { + true + true + true + true + true + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/types.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/types.pcf new file mode 100644 index 000000000..450ba35d2 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/types.pcf @@ -0,0 +1,64 @@ +examples { + ["simple types"] { + true + true + true + } + ["declared type"] { + true + true + true + true + true + true + true + true + true + true + true + } + ["nullable type"] { + true + true + } + ["union type"] { + true + true + true + true + true + } + ["function type"] { + true + true + true + true + true + true + true + true + } + ["constrained type"] { + true + true + true + true + } + ["parenthesized type"] { + true + true + } + ["string constant type"] { + true + true + true + true + true + true + } + ["type annotation"] { + true + true + true + } +} diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl new file mode 100644 index 000000000..740ab22ea --- /dev/null +++ b/stdlib/syntax.pkl @@ -0,0 +1,1601 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2026 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. +//===----------------------------------------------------------------------===// + +/// Utilities for managing Pkl source code +module pkl.syntax + +/// Parse the string as a Pkl module, returning either a typed AST node or an error. +function parse(source: String): ModuleNode | ParserError = + let (result = parseNodes(source)) + if (result is ParserError) + result + else + new ModuleNode { node = result } + +/// Parse a resource as a Pkl module, returning either a typed AST node or an error. +function parseResource(resourceURI: String): ModuleNode | ParserError = + parse(read(resourceURI).text) + +external local function parseNodes(source: String): Node | ParserError + +class Node { + type: NodeType + children: List + parent: Node? + text: String? + @ConvertSpan + span: Span +} + +class Span { + lineStart: UInt + colStart: UInt + lineEnd: UInt + colEnd: UInt +} + +// noinspection TypeMismatch +class ConvertSpan extends ConvertProperty { + render = (property: Pair, _) -> + let (span = property.value) + Pair(property.key, "\(span.lineStart):\(span.colStart)-\(span.lineEnd):\(span.colEnd)") +} + +class ParserError { + text: String + span: Span +} + +typealias NodeType = + // terminals and affixes + "terminal" + | "shebang" + | "line_comment" + | "block_comment" + | "semicolon" + // module structure + | "module" + | "doc_comment" + | "doc_comment_line" + | "modifier" + | "modifier_list" + | "amends_clause" + | "extends_clause" + | "module_declaration" + | "module_definition" + | "annotation" + | "identifier" + | "qualified_identifier" + | "import" + | "import_alias" + | "import_list" + | "typealias" + | "typealias_header" + | "typealias_body" + | "class" + | "class_header" + | "class_header_extends" + | "class_body" + | "class_body_elements" + | "class_method" + | "class_method_header" + | "class_method_body" + | "class_property" + | "class_property_header" + | "class_property_header_begin" + | "class_property_body" + | "object_body" + | "object_member_list" + | "parameter" + | "type_annotation" + | "parameter_list" + | "parameter_list_elements" + | "type_parameter_list" + | "type_parameter_list_elements" + | "argument_list" + | "argument_list_elements" + | "type_argument_list" + | "type_argument_list_elements" + | "object_parameter_list" + | "type_parameter" + | "string_chars" + | "operator" + | "string_newline" + | "string_escape" + // members + | "object_element" + | "object_property" + | "object_property_header" + | "object_property_header_begin" + | "object_property_body" + | "object_method" + | "member_predicate" + | "object_entry" + | "object_entry_header" + | "object_spread" + | "when_generator" + | "when_generator_header" + | "for_generator" + | "for_generator_header" + | "for_generator_header_definition" + | "for_generator_header_definition_header" + // expressions + | "this_expr" + | "outer_expr" + | "module_expr" + | "null_expr" + | "throw_expr" + | "trace_expr" + | "import_expr" + | "read_expr" + | "new_expr" + | "new_header" + | "unary_minus_expr" + | "logical_not_expr" + | "function_literal_expr" + | "function_literal_body" + | "parenthesized_expr" + | "parenthesized_expr_elements" + | "super_subscript_expr" + | "super_access_expr" + | "subscript_expr" + | "qualified_access_expr" + | "if_expr" + | "if_header" + | "if_condition" + | "if_condition_expr" + | "if_then_expr" + | "if_else_expr" + | "let_expr" + | "let_parameter_definition" + | "let_parameter" + | "bool_literal_expr" + | "int_literal_expr" + | "float_literal_expr" + | "single_line_string_literal_expr" + | "multi_line_string_literal_expr" + | "unqualified_access_expr" + | "non_null_expr" + | "amends_expr" + | "binary_op_expr" + // types + | "unknown_type" + | "nothing_type" + | "module_type" + | "union_type" + | "function_type" + | "function_type_parameters" + | "parenthesized_type" + | "parenthesized_type_elements" + | "declared_type" + | "nullable_type" + | "string_constant_type" + | "constrained_type" + | "constrained_type_constraint" + | "constrained_type_elements" + +// Find first child of a given type within a node. +local const function findChild(n: Node, t: NodeType): Node? = + n.children.findOrNull((c) -> c.type == t) + +// Find all children of a given type. +local const function findChildren(n: Node, t: NodeType): List = + n.children.filter((c) -> c.type == t) + +// Find the first child whose type is one of the expression types. +local const function findExprChild(n: Node): Node? = + n.children.findOrNull((c) -> isExprType(c.type)) + +// Find all children whose type is one of the expression types. +local const function findExprChildren(n: Node): List = + n.children.filter((c) -> isExprType(c.type)) + +// Find the first child whose type is one of the type node types. +local const function findTypeChild(n: Node): Node? = + n.children.findOrNull((c) -> isTypeType(c.type)) + +// Find all children whose type is one of the type node types. +local const function findTypeChildren(n: Node): List = + n.children.filter((c) -> isTypeType(c.type)) + +// Check if a NodeType represents an expression. +local const function isExprType(t: NodeType): Boolean = + t == "this_expr" + || t == "outer_expr" + || t == "module_expr" + || t == "null_expr" + || t == "throw_expr" + || t == "trace_expr" + || t == "import_expr" + || t == "read_expr" + || t == "new_expr" + || t == "unary_minus_expr" + || t == "logical_not_expr" + || t == "function_literal_expr" + || t == "parenthesized_expr" + || t == "super_subscript_expr" + || t == "super_access_expr" + || t == "subscript_expr" + || t == "qualified_access_expr" + || t == "if_expr" + || t == "let_expr" + || t == "bool_literal_expr" + || t == "int_literal_expr" + || t == "float_literal_expr" + || t == "single_line_string_literal_expr" + || t == "multi_line_string_literal_expr" + || t == "unqualified_access_expr" + || t == "non_null_expr" + || t == "amends_expr" + || t == "binary_op_expr" + +// Check if a NodeType represents a type node. +local const function isTypeType(t: NodeType): Boolean = + t == "unknown_type" + || t == "nothing_type" + || t == "module_type" + || t == "union_type" + || t == "function_type" + || t == "parenthesized_type" + || t == "declared_type" + || t == "nullable_type" + || t == "string_constant_type" + || t == "constrained_type" + +// Check if a NodeType represents an object member. +local const function isObjectMemberType(t: NodeType): Boolean = + t == "object_element" + || t == "object_property" + || t == "object_method" + || t == "member_predicate" + || t == "object_entry" + || t == "object_spread" + || t == "when_generator" + || t == "for_generator" + +// Wrap a raw Node into the appropriate Expr subclass. +local const function wrapExpr(n: Node): Expr = + if (n.type == "this_expr") + new ThisExprNode { node = n } + else if (n.type == "outer_expr") + new OuterExprNode { node = n } + else if (n.type == "module_expr") + new ModuleExprNode { node = n } + else if (n.type == "null_expr") + new NullLiteralExprNode { node = n } + else if (n.type == "bool_literal_expr") + new BoolLiteralExprNode { node = n } + else if (n.type == "int_literal_expr") + new IntLiteralExprNode { node = n } + else if (n.type == "float_literal_expr") + new FloatLiteralExprNode { node = n } + else if (n.type == "single_line_string_literal_expr") + new SingleLineStringLiteralExprNode { node = n } + else if (n.type == "multi_line_string_literal_expr") + new MultiLineStringLiteralExprNode { node = n } + else if (n.type == "unqualified_access_expr") + new UnqualifiedAccessExprNode { node = n } + else if (n.type == "qualified_access_expr") + new QualifiedAccessExprNode { node = n } + else if (n.type == "subscript_expr") + new SubscriptExprNode { node = n } + else if (n.type == "super_access_expr") + new SuperAccessExprNode { node = n } + else if (n.type == "super_subscript_expr") + new SuperSubscriptExprNode { node = n } + else if (n.type == "if_expr") + new IfExprNode { node = n } + else if (n.type == "let_expr") + new LetExprNode { node = n } + else if (n.type == "throw_expr") + new ThrowExprNode { node = n } + else if (n.type == "trace_expr") + new TraceExprNode { node = n } + else if (n.type == "import_expr") + new ImportExprNode { node = n } + else if (n.type == "read_expr") + new ReadExprNode { node = n } + else if (n.type == "new_expr") + new NewExprNode { node = n } + else if (n.type == "amends_expr") + new AmendsExprNode { node = n } + else if (n.type == "binary_op_expr") + new BinaryOpExprNode { node = n } + else if (n.type == "unary_minus_expr") + new UnaryMinusExprNode { node = n } + else if (n.type == "logical_not_expr") + new LogicalNotExprNode { node = n } + else if (n.type == "non_null_expr") + new NonNullExprNode { node = n } + else if (n.type == "function_literal_expr") + new FunctionLiteralExprNode { node = n } + else if (n.type == "parenthesized_expr") + new ParenthesizedExprNode { node = n } + else + throw("Unknown expression type: \(n.type)") + +// Wrap a raw Node into the appropriate TypeNode subclass. +local const function wrapTypeNode(n: Node): TypeNode = + if (n.type == "unknown_type") + new UnknownTypeNode { node = n } + else if (n.type == "nothing_type") + new NothingTypeNode { node = n } + else if (n.type == "module_type") + new ModuleTypeNode { node = n } + else if (n.type == "declared_type") + new DeclaredTypeNode { node = n } + else if (n.type == "nullable_type") + new NullableTypeNode { node = n } + else if (n.type == "union_type") + new UnionTypeNode { node = n } + else if (n.type == "function_type") + new FunctionTypeNode { node = n } + else if (n.type == "constrained_type") + new ConstrainedTypeNode { node = n } + else if (n.type == "parenthesized_type") + new ParenthesizedTypeNode { node = n } + else if (n.type == "string_constant_type") + new StringConstantTypeNode { node = n } + else + throw("Unknown type node type: \(n.type)") + +// Wrap a raw Node into the appropriate ObjectMemberNode subclass. +local const function wrapObjectMember(n: Node): ObjectMemberNode = + if (n.type == "object_element") + new ObjectElementNode { node = n } + else if (n.type == "object_property") + new ObjectPropertyNode { node = n } + else if (n.type == "object_method") + new ObjectMethodNode { node = n } + else if (n.type == "member_predicate") + new MemberPredicateNode { node = n } + else if (n.type == "object_entry") + new ObjectEntryNode { node = n } + else if (n.type == "object_spread") + new ObjectSpreadNode { node = n } + else if (n.type == "when_generator") + new WhenGeneratorNode { node = n } + else if (n.type == "for_generator") + new ForGeneratorNode { node = n } + else + throw("Unknown object member type: \(n.type)") + +// Extract the string constant from a STRING_CHARS node. +local const function extractStringConstant(n: Node?): String = + if (n == null) + "" + else + let (inner = n.children.filter((c) -> c.type == "terminal").drop(1).dropLast(1)) + inner.map((c) -> c.text ?? "").join("") + +// Find and extract the string_chars of a node. +local const function getStringChars(node: Node): String = + let (sc = findChild(node, "string_chars")) + extractStringConstant(sc) + +/// Base class for all typed syntax nodes. +abstract class SyntaxNode { + hidden node: Node + + hidden children: List = node.children + + hidden text: String? = node.text + + /// The source span of this node. + @ConvertSpan + span: Span = node.span + + /// All terminal children (keywords, punctuation, operators). + hidden terminals: List = children.filter((n) -> n.type == "terminal") + + /// All comment children. + hidden comments: List = + children.filter((n) -> n.type == "line_comment" || n.type == "block_comment") +} + +/// Base class for expression nodes. +abstract class Expr extends SyntaxNode {} + +/// Base class for type nodes. +abstract class TypeNode extends SyntaxNode {} + +/// Base class for object member nodes. +abstract class ObjectMemberNode extends SyntaxNode {} + +/// The top-level module node. +class ModuleNode extends SyntaxNode { + /// The module declaration, if present. + declaration: ModuleDeclarationNode? = + let (n = findChild(node, "module_declaration")) + if (n == null) + null + else + new ModuleDeclarationNode { node = n } + + /// All imports in this module. + imports: List = + let (importList = findChild(node, "import_list")) + if (importList == null) + List() + else + findChildren(importList, "import").map((n) -> new ImportNode { node = n }) + + /// All class declarations in this module. + classes: List = findChildren(node, "class").map((n) -> new ClassNode { node = n }) + + /// All typealias declarations in this module. + typeAliases: List = + findChildren(node, "typealias").map((n) -> new TypeAliasNode { node = n }) + + /// All top-level properties in this module. + properties: List = + findChildren(node, "class_property").map((n) -> new ClassPropertyNode { node = n }) + + /// All top-level methods in this module. + methods: List = + findChildren(node, "class_method").map((n) -> new ClassMethodNode { node = n }) +} + +/// A module declaration (including doc comment, annotations, modifiers, name, amends/extends). +class ModuleDeclarationNode extends SyntaxNode { + local moduleDefinition: Node? = findChild(node, "module_definition") + + /// The doc comment on the module declaration, if present. + docComment: DocCommentNode? = + let (n = findChild(node, "doc_comment")) + if (n == null) + null + else + new DocCommentNode { node = n } + + /// Annotations on the module declaration. + annotations: List = + findChildren(node, "annotation").map((n) -> new AnnotationNode { node = n }) + + /// The modifier list on the module declaration, if present. + modifiers: ModifierListNode? = + if (moduleDefinition == null) + null + else + let (n = findChild(moduleDefinition, "modifier_list")) + if (n == null) + null + else + new ModifierListNode { node = n } + + /// The qualified name of the module, if present. + name: QualifiedIdentifierNode? = + if (moduleDefinition == null) + null + else + let (n = findChild(moduleDefinition, "qualified_identifier")) + if (n == null) + null + else + new QualifiedIdentifierNode { node = n } + + /// The amends clause, if present. + amendsClause: AmendsClauseNode? = + let (n = findChild(node, "amends_clause")) + if (n == null) + null + else + new AmendsClauseNode { node = n } + + /// The extends clause, if present. + extendsClause: ExtendsClauseNode? = + let (n = findChild(node, "extends_clause")) + if (n == null) + null + else + new ExtendsClauseNode { node = n } +} + +/// An `amends "..."` clause. +class AmendsClauseNode extends SyntaxNode { + /// The URI string of the amended module. + uri: String = getStringChars(node) +} + +/// An `extends "..."` clause. +class ExtendsClauseNode extends SyntaxNode { + /// The URI string of the extended module. + uri: String = getStringChars(node) +} + +/// An import declaration. +class ImportNode extends SyntaxNode { + /// Whether this is a glob import (`import*`). + isGlob: Boolean = terminals.firstOrNull?.text == "import*" + + /// The URI string of the import. + uri: String = getStringChars(node) + + /// The alias for this import, if present. + alias: IdentifierNode? = + let (aliasNode = findChild(node, "import_alias")) + if (aliasNode == null) + null + else + let (id = findChild(aliasNode, "identifier")) + if (id == null) + null + else + new IdentifierNode { node = id } +} + +/// A class declaration. +class ClassNode extends SyntaxNode { + local header: Node = findChild(node, "class_header")!! + + /// The doc comment, if present. + docComment: DocCommentNode? = + let (n = findChild(node, "doc_comment")) + if (n == null) + null + else + new DocCommentNode { node = n } + + /// Annotations on the class. + annotations: List = + findChildren(node, "annotation").map((n) -> new AnnotationNode { node = n }) + + /// The modifier list, if present. + modifiers: ModifierListNode? = + let (n = findChild(header, "modifier_list")) + if (n == null) + null + else + new ModifierListNode { node = n } + + /// The class name. + name: IdentifierNode = + let (n = findChild(header, "identifier")) + new IdentifierNode { node = n!! } + + /// The type parameter list, if present. + typeParameterList: TypeParameterListNode? = + let (n = findChild(header, "type_parameter_list")) + if (n == null) + null + else + new TypeParameterListNode { node = n } + + /// The supertype this class extends, if present. + extendsClause: TypeNode? = + let (ext = findChild(header, "class_header_extends")) + if (ext == null) + null + else + let (t = findTypeChild(ext)) + if (t == null) + null + else + wrapTypeNode(t) + + /// The class body, if present. + body: ClassBodyNode? = + let (n = findChild(node, "class_body")) + if (n == null) + null + else + new ClassBodyNode { node = n } +} + +/// A typealias declaration. +class TypeAliasNode extends SyntaxNode { + local header: Node = findChild(node, "typealias_header")!! + + /// The doc comment, if present. + docComment: DocCommentNode? = + let (n = findChild(node, "doc_comment")) + if (n == null) + null + else + new DocCommentNode { node = n } + + /// Annotations on the typealias. + annotations: List = + findChildren(node, "annotation").map((n) -> new AnnotationNode { node = n }) + + /// The modifier list, if present. + modifiers: ModifierListNode? = + let (n = findChild(header, "modifier_list")) + if (n == null) + null + else + new ModifierListNode { node = n } + + /// The typealias name. + name: IdentifierNode = + let (n = findChild(header, "identifier")) + new IdentifierNode { node = n!! } + + /// The type parameter list, if present. + typeParameterList: TypeParameterListNode? = + let (n = findChild(header, "type_parameter_list")) + if (n == null) + null + else + new TypeParameterListNode { node = n } + + /// The type that this alias resolves to. + type: TypeNode = + let (body = findChild(node, "typealias_body")) + let (t = findTypeChild(body!!)) + wrapTypeNode(t!!) +} + +/// A class body delimited by braces. +class ClassBodyNode extends SyntaxNode { + local elements: Node? = findChild(node, "class_body_elements") + + /// Properties declared in this class body. + properties: List = + if (elements == null) + List() + else + findChildren(elements, "class_property").map((n) -> new ClassPropertyNode { node = n }) + + /// Methods declared in this class body. + methods: List = + if (elements == null) + List() + else + findChildren(elements, "class_method").map((n) -> new ClassMethodNode { node = n }) +} + +/// A class property declaration. +class ClassPropertyNode extends SyntaxNode { + local header: Node = findChild(node, "class_property_header")!! + local headerBegin: Node = findChild(header, "class_property_header_begin")!! + + /// The doc comment, if present. + docComment: DocCommentNode? = + let (n = findChild(node, "doc_comment")) + if (n == null) + null + else + new DocCommentNode { node = n } + + /// Annotations on the property. + annotations: List = + findChildren(node, "annotation").map((n) -> new AnnotationNode { node = n }) + + /// The modifier list, if present. + modifiers: ModifierListNode? = + let (n = findChild(headerBegin, "modifier_list")) + if (n == null) + null + else + new ModifierListNode { node = n } + + /// The property name. + name: IdentifierNode = + let (n = findChild(headerBegin, "identifier")) + new IdentifierNode { node = n!! } + + /// The type annotation, if present. + typeAnnotation: TypeAnnotationNode? = + let (n = findChild(header, "type_annotation")) + if (n == null) + null + else + new TypeAnnotationNode { node = n } + + /// The value expression, if present (from `= expr`). + value: Expr? = + let (body = findChild(node, "class_property_body")) + if (body == null) + null + else + let (e = findExprChild(body)) + if (e == null) + null + else + wrapExpr(e) + + /// Object bodies for amending (from `{ ... }` blocks). + objectBodies: List = + findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) +} + +/// A class method declaration. +class ClassMethodNode extends SyntaxNode { + local methodHeader: Node = findChild(node, "class_method_header")!! + + /// The doc comment, if present. + docComment: DocCommentNode? = + let (n = findChild(node, "doc_comment")) + if (n == null) + null + else + new DocCommentNode { node = n } + + /// Annotations on the method. + annotations: List = + findChildren(node, "annotation").map((n) -> new AnnotationNode { node = n }) + + /// The modifier list, if present. + modifiers: ModifierListNode? = + let (n = findChild(methodHeader, "modifier_list")) + if (n == null) + null + else + new ModifierListNode { node = n } + + /// The method name. + name: IdentifierNode = + let (n = findChild(methodHeader, "identifier")) + new IdentifierNode { node = n!! } + + /// The type parameter list, if present. + typeParameterList: TypeParameterListNode? = + let (n = findChild(node, "type_parameter_list")) + if (n == null) + null + else + new TypeParameterListNode { node = n } + + /// The parameter list. + parameterList: ParameterListNode = + let (n = findChild(node, "parameter_list")) + new ParameterListNode { node = n!! } + + /// The return type annotation, if present. + returnType: TypeAnnotationNode? = + let (n = findChild(node, "type_annotation")) + if (n == null) + null + else + new TypeAnnotationNode { node = n } + + /// The method body expression, if present. + body: Expr? = + let (bodyNode = findChild(node, "class_method_body")) + if (bodyNode == null) + null + else + let (e = findExprChild(bodyNode)) + if (e == null) + null + else + wrapExpr(e) +} + +/// An object body delimited by braces. +class ObjectBodyNode extends SyntaxNode { + local paramList: Node? = findChild(node, "object_parameter_list") + local memberList: Node? = findChild(node, "object_member_list") + + /// Parameters for this object body (e.g., `{ x, y -> ... }`). + parameters: List = + if (paramList == null) + List() + else + findChildren(paramList, "parameter").map((n) -> new ParameterNode { node = n }) + + /// All object members (properties, methods, elements, entries, spreads, generators). + members: List = + if (memberList == null) + List() + else + memberList.children + .filter((c) -> isObjectMemberType(c.type)) + .map((n) -> wrapObjectMember(n)) + + /// Object properties in this body. + properties: List = + if (memberList == null) + List() + else + findChildren(memberList, "object_property").map((n) -> new ObjectPropertyNode { node = n }) + + /// Object methods in this body. + methods: List = + if (memberList == null) + List() + else + findChildren(memberList, "object_method").map((n) -> new ObjectMethodNode { node = n }) + + /// Object elements in this body. + elements: List = + if (memberList == null) + List() + else + findChildren(memberList, "object_element").map((n) -> new ObjectElementNode { node = n }) + + /// Object entries in this body. + entries: List = + if (memberList == null) + List() + else + findChildren(memberList, "object_entry").map((n) -> new ObjectEntryNode { node = n }) +} + +/// An object property declaration. +class ObjectPropertyNode extends ObjectMemberNode { + local header: Node = findChild(node, "object_property_header")!! + local headerBegin: Node = findChild(header, "object_property_header_begin")!! + + /// The modifier list, if present. + modifiers: ModifierListNode? = + let (n = findChild(headerBegin, "modifier_list")) + if (n == null) + null + else + new ModifierListNode { node = n } + + /// The property name. + name: IdentifierNode = + let (n = findChild(headerBegin, "identifier")) + new IdentifierNode { node = n!! } + + /// The type annotation, if present. + typeAnnotation: TypeAnnotationNode? = + let (n = findChild(header, "type_annotation")) + if (n == null) + null + else + new TypeAnnotationNode { node = n } + + /// The value expression, if present (from `= expr`). + value: Expr? = + let (body = findChild(node, "object_property_body")) + if (body == null) + null + else + let (e = findExprChild(body)) + if (e == null) + null + else + wrapExpr(e) + + /// Object bodies for amending. + objectBodies: List = + findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) +} + +/// An object method declaration. +class ObjectMethodNode extends ObjectMemberNode { + local methodHeader: Node = findChild(node, "class_method_header")!! + + /// The modifier list, if present. + modifiers: ModifierListNode? = + let (n = findChild(methodHeader, "modifier_list")) + if (n == null) + null + else + new ModifierListNode { node = n } + + /// The method name. + name: IdentifierNode = + let (n = findChild(methodHeader, "identifier")) + new IdentifierNode { node = n!! } + + /// The type parameter list, if present. + typeParameterList: TypeParameterListNode? = + let (n = findChild(node, "type_parameter_list")) + if (n == null) + null + else + new TypeParameterListNode { node = n } + + /// The parameter list. + parameterList: ParameterListNode = + let (n = findChild(node, "parameter_list")) + new ParameterListNode { node = n!! } + + /// The return type annotation, if present. + returnType: TypeAnnotationNode? = + let (n = findChild(node, "type_annotation")) + if (n == null) + null + else + new TypeAnnotationNode { node = n } + + /// The method body expression. + body: Expr? = + let (bodyNode = findChild(node, "class_method_body")) + if (bodyNode == null) + null + else + let (e = findExprChild(bodyNode)) + if (e == null) + null + else + wrapExpr(e) +} + +/// An object element (a positional expression in an object body). +class ObjectElementNode extends ObjectMemberNode { + /// The expression value. + expression: Expr = wrapExpr(findExprChild(node)!!) +} + +/// An object entry (`[key] = value` or `[key] { ... }`). +class ObjectEntryNode extends ObjectMemberNode { + local entryHeader: Node = findChild(node, "object_entry_header")!! + + /// The key expression. + key: Expr = wrapExpr(findExprChild(entryHeader)!!) + + /// The value expression, if present (from `[key] = value`). + value: Expr? = + let (e = findExprChildren(node).findOrNull((c) -> c != findExprChild(entryHeader))) + if (e == null) + null + else + wrapExpr(e) + + /// Object bodies for amending. + objectBodies: List = + findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) +} + +/// An object spread (`...expr` or `...?expr`). +class ObjectSpreadNode extends ObjectMemberNode { + /// Whether this is a nullable spread (`...?`). + isNullable: Boolean = terminals.firstOrNull?.text == "...?" + + /// The spread expression. + expression: Expr = wrapExpr(findExprChild(node)!!) +} + +/// A member predicate (`[[condition]] = value` or `[[condition]] { ... }`). +class MemberPredicateNode extends ObjectMemberNode { + local exprs = findExprChildren(node) + + /// The condition expression. + condition: Expr = wrapExpr(exprs.first) + + /// The value expression, if present. + value: Expr? = + if (exprs.length < 2) + null + else + wrapExpr(exprs[1]) + + /// Object bodies for amending. + objectBodies: List = + findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) +} + +/// A `for (param in iterable) { ... }` generator. +class ForGeneratorNode extends ObjectMemberNode { + local forHeader: Node = findChild(node, "for_generator_header")!! + local forDef: Node = findChild(forHeader, "for_generator_header_definition")!! + local forDefHeader: Node = findChild(forDef, "for_generator_header_definition_header")!! + local paramNodes: List = findChildren(forDefHeader, "parameter") + + /// The key parameter (first parameter when two are present), if present. + keyParameter: ParameterNode? = + if (paramNodes.length < 2) + null + else + new ParameterNode { node = paramNodes.first } + + /// The value parameter (or the only parameter when just one is present). + valueParameter: ParameterNode = new ParameterNode { node = paramNodes.last } + + /// The iterable expression. + iterable: Expr = + let (e = findExprChild(forDef)) + wrapExpr(e!!) + + /// The body. + body: ObjectBodyNode = + let (n = findChild(node, "object_body")) + new ObjectBodyNode { node = n!! } +} + +/// A `when (condition) { ... }` generator. +class WhenGeneratorNode extends ObjectMemberNode { + local whenHeader: Node = findChild(node, "when_generator_header")!! + local bodyNodes: List = findChildren(node, "object_body") + + /// The condition expression. + condition: Expr = + let (e = findExprChild(whenHeader)) + wrapExpr(e!!) + + /// The "then" body. + thenBody: ObjectBodyNode = new ObjectBodyNode { node = bodyNodes.first } + + /// The "else" body, if present. + elseBody: ObjectBodyNode? = + if (bodyNodes.length < 2) + null + else + new ObjectBodyNode { node = bodyNodes[1] } +} + +/// The `this` expression. +class ThisExprNode extends Expr {} + +/// The `outer` expression. +class OuterExprNode extends Expr {} + +/// The `module` expression. +class ModuleExprNode extends Expr {} + +/// A `null` literal expression. +class NullLiteralExprNode extends Expr {} + +/// A boolean literal expression (`true` or `false`). +class BoolLiteralExprNode extends Expr { + /// The boolean value. + value: Boolean = node.text == "true" +} + +/// An integer literal expression. +class IntLiteralExprNode extends Expr { + /// The raw text of the integer literal. + text: String = node.text ?? "" +} + +/// A float literal expression. +class FloatLiteralExprNode extends Expr { + /// The raw text of the float literal. + text: String = node.text ?? "" +} + +/// A single-line string literal expression. +class SingleLineStringLiteralExprNode extends Expr { + /// The string parts (chars, escapes, interpolations). + parts: List = buildStringParts(children) +} + +/// A multi-line string literal expression. +class MultiLineStringLiteralExprNode extends Expr { + /// The string parts (chars, escapes, interpolations). + parts: List = buildStringParts(children) +} + +/// An unqualified access expression (`name` or `name(args)`). +class UnqualifiedAccessExprNode extends Expr { + /// The identifier being accessed. + identifier: IdentifierNode = + let (n = findChild(node, "identifier")) + new IdentifierNode { node = n!! } + + /// The argument list, if present. + argumentList: ArgumentListNode? = + let (n = findChild(node, "argument_list")) + if (n == null) + null + else + new ArgumentListNode { node = n } +} + +/// A qualified access expression (`receiver.member` or `receiver?.member`). +class QualifiedAccessExprNode extends Expr { + /// The receiver expression. + receiver: Expr = + let (exprs = findExprChildren(node)) + wrapExpr(exprs.first) + + /// Whether this is a null-safe access (`?.`). + isNullSafe: Boolean = findChild(node, "operator")?.text == "?." + + /// The accessed member. + member: UnqualifiedAccessExprNode = + let (n = findChildren(node, "unqualified_access_expr").last) + new UnqualifiedAccessExprNode { node = n } +} + +/// A subscript expression (`receiver[index]`). +class SubscriptExprNode extends Expr { + /// The receiver expression. + receiver: Expr = + let (exprs = findExprChildren(node)) + wrapExpr(exprs.first) + + /// The index expression. + index: Expr = + let (exprs = findExprChildren(node)) + wrapExpr(exprs[1]) +} + +/// A `super.member` access expression. +class SuperAccessExprNode extends Expr {} + +/// A `super[index]` subscript expression. +class SuperSubscriptExprNode extends Expr {} + +/// An `if (condition) thenExpr else elseExpr` expression. +class IfExprNode extends Expr { + local ifHeader: Node = findChild(node, "if_header")!! + local ifCondition: Node = findChild(ifHeader, "if_condition")!! + local ifConditionExpr: Node = findChild(ifCondition, "if_condition_expr")!! + + /// The condition expression. + condition: Expr = + let (e = findExprChild(ifConditionExpr)) + wrapExpr(e!!) + + /// The then-branch expression. + thenExpr: Expr = + let (thenNode = findChild(node, "if_then_expr")) + let (e = findExprChild(thenNode!!)) + wrapExpr(e!!) + + /// The else-branch expression. + elseExpr: Expr = + let (elseNode = findChild(node, "if_else_expr")) + let (e = findExprChild(elseNode!!)) + wrapExpr(e!!) +} + +/// A `let (param = value) body` expression. +class LetExprNode extends Expr { + local letParamDef: Node = findChild(node, "let_parameter_definition")!! + local letParam: Node = findChild(letParamDef, "let_parameter")!! + + /// The let-binding parameter. + parameter: ParameterNode = + let (p = findChild(letParam, "parameter")) + new ParameterNode { node = p!! } + + /// The binding value expression. + bindingValue: Expr = + let (e = findExprChild(letParam)) + wrapExpr(e!!) + + /// The body expression. + bodyExpr: Expr = wrapExpr(findExprChild(node)!!) +} + +/// A `throw(expr)` expression. +class ThrowExprNode extends Expr { + /// The expression being thrown. + expression: Expr = wrapExpr(findExprChild(node)!!) +} + +/// A `trace(expr)` expression. +class TraceExprNode extends Expr { + /// The expression being traced. + expression: Expr = wrapExpr(findExprChild(node)!!) +} + +/// An `import("uri")` or `import*("uri")` expression. +class ImportExprNode extends Expr { + /// Whether this is a glob import expression (`import*`). + isGlob: Boolean = terminals.firstOrNull?.text == "import*" + + /// The import URI string. + uri: String = getStringChars(node) +} + +/// A `read(expr)`, `read*(expr)`, or `read?(expr)` expression. +class ReadExprNode extends Expr { + /// The keyword used (`"read"`, `"read?"`, or `"read*"`). + keyword: String = terminals.firstOrNull?.text ?? "read" + + // The expression to be read + expr: Expr = wrapExpr(findExprChild(node)!!) +} + +/// A `new Type { ... }` expression. +class NewExprNode extends Expr { + local newHeader: Node = findChild(node, "new_header")!! + + /// The type being constructed, if present. + type: TypeNode? = + let (t = findTypeChild(newHeader)) + if (t == null) + null + else + wrapTypeNode(t) + + /// The object body. + body: ObjectBodyNode = + let (n = findChild(node, "object_body")) + new ObjectBodyNode { node = n!! } +} + +/// An `(expr) { ... }` amends expression. +class AmendsExprNode extends Expr { + /// The expression being amended. + parentExpr: Expr = + let (exprs = findExprChildren(node)) + wrapExpr(exprs.first) + + /// The object body. + body: ObjectBodyNode = + let (n = findChild(node, "object_body")) + new ObjectBodyNode { node = n!! } +} + +/// A binary operator expression (`left op right`). +class BinaryOpExprNode extends Expr { + local exprs = findExprChildren(node) + + /// The operator string. + operator: String = findChild(node, "operator")?.text ?? "" + + /// The left-hand expression. + leftExpr: Expr = wrapExpr(exprs.first) + + /// The right-hand expression, if present (not present for `is`/`as` which use a type). + rightExpr: Expr? = + if (exprs.length < 2) + null + else + wrapExpr(exprs[1]) + + /// The right-hand type, if this is an `is` or `as` operation. + rightType: TypeNode? = + let (t = findTypeChild(node)) + if (t == null) + null + else + wrapTypeNode(t) +} + +/// A unary minus expression (`-expr`). +class UnaryMinusExprNode extends Expr { + /// The operand expression. + operand: Expr = wrapExpr(findExprChild(node)!!) +} + +/// A logical not expression (`!expr`). +class LogicalNotExprNode extends Expr { + /// The operand expression. + operand: Expr = wrapExpr(findExprChild(node)!!) +} + +/// A non-null assertion expression (`expr!!`). +class NonNullExprNode extends Expr { + /// The operand expression. + operand: Expr = wrapExpr(findExprChild(node)!!) +} + +/// A function literal expression (`(params) -> body`). +class FunctionLiteralExprNode extends Expr { + /// The parameter list. + parameterList: ParameterListNode = + let (n = findChild(node, "parameter_list")) + new ParameterListNode { node = n!! } + + /// The body expression. + body: Expr = + let (bodyNode = findChild(node, "function_literal_body")) + let (e = findExprChild(bodyNode!!)) + wrapExpr(e!!) +} + +/// A parenthesized expression (`(expr)`). +class ParenthesizedExprNode extends Expr { + /// The inner expression, if present (may be empty for `()`). + expression: Expr? = + let (elems = findChild(node, "parenthesized_expr_elements")) + if (elems == null) + null + else + let (e = findExprChild(elems)) + if (e == null) + null + else + wrapExpr(e) +} + +/// The `unknown` type. +class UnknownTypeNode extends TypeNode {} + +/// The `nothing` type. +class NothingTypeNode extends TypeNode {} + +/// The `module` type. +class ModuleTypeNode extends TypeNode {} + +/// A declared type (e.g., `String`, `List`). +class DeclaredTypeNode extends TypeNode { + /// The type name. + name: QualifiedIdentifierNode = + let (n = findChild(node, "qualified_identifier")) + new QualifiedIdentifierNode { node = n!! } + + /// The type argument list, if present. + typeArgumentList: TypeArgumentListNode? = + let (n = findChild(node, "type_argument_list")) + if (n == null) + null + else + new TypeArgumentListNode { node = n } +} + +/// A nullable type (`Type?`). +class NullableTypeNode extends TypeNode { + /// The base type. + baseType: TypeNode = wrapTypeNode(findTypeChild(node)!!) +} + +/// A union type (`TypeA|TypeB|TypeC`). +class UnionTypeNode extends TypeNode { + /// The member types. + members: List = findTypeChildren(node).map((n) -> wrapTypeNode(n)) +} + +/// A function type (`(ParamTypes) -> ReturnType`). +class FunctionTypeNode extends TypeNode { + local params: Node = findChild(node, "function_type_parameters")!! + local paramElems: Node? = findChild(params, "parenthesized_type_elements") + + /// The parameter types. + parameterTypes: List = + if (paramElems == null) + List() + else + findTypeChildren(paramElems).map((n) -> wrapTypeNode(n)) + + /// The return type. + returnType: TypeNode = + let (types = findTypeChildren(node)) + wrapTypeNode(types.last) +} + +/// A constrained type (`Type(constraint)`). +class ConstrainedTypeNode extends TypeNode { + /// The base type. + baseType: TypeNode = wrapTypeNode(findTypeChild(node)!!) + + local constraint: Node = findChild(node, "constrained_type_constraint")!! + local constraintElems: Node = findChild(constraint, "constrained_type_elements")!! + + /// The constraint expressions. + constraints: List = findExprChildren(constraintElems).map((n) -> wrapExpr(n)) +} + +/// A parenthesized type (`(Type)`). +class ParenthesizedTypeNode extends TypeNode { + /// The inner type, if present. + type: TypeNode? = + let (elems = findChild(node, "parenthesized_type_elements")) + if (elems == null) + null + else + let (t = findTypeChild(elems)) + if (t == null) + null + else + wrapTypeNode(t) +} + +/// A string constant type (e.g., `"foo"`). +class StringConstantTypeNode extends TypeNode { + /// The string value. + value: String = getStringChars(node) +} + +/// An annotation (`@Type { ... }`). +class AnnotationNode extends SyntaxNode { + /// The annotation type. + type: TypeNode = wrapTypeNode(findTypeChild(node)!!) + + /// The annotation body, if present. + body: ObjectBodyNode? = + let (n = findChild(node, "object_body")) + if (n == null) + null + else + new ObjectBodyNode { node = n } +} + +/// A parameter declaration. +class ParameterNode extends SyntaxNode { + /// Whether this is a wildcard parameter (`_`). + isWildcard: Boolean = + identifier == null + && children.findOrNull((c) -> c.type == "terminal" && c.text == "_") != null + + /// The parameter identifier, if not a wildcard. + identifier: IdentifierNode? = + let (n = findChild(node, "identifier")) + if (n == null) + null + else + new IdentifierNode { node = n } + + /// The type annotation, if present. + typeAnnotation: TypeAnnotationNode? = + let (n = findChild(node, "type_annotation")) + if (n == null) + null + else + new TypeAnnotationNode { node = n } +} + +/// A parameter list (`(param1, param2)`). +class ParameterListNode extends SyntaxNode { + local elems: Node? = findChild(node, "parameter_list_elements") + + /// The parameters in this list. + parameters: List = + if (elems == null) + List() + else + findChildren(elems, "parameter").map((n) -> new ParameterNode { node = n }) +} + +/// An argument list (`(arg1, arg2)`). +class ArgumentListNode extends SyntaxNode { + local elems: Node? = findChild(node, "argument_list_elements") + + /// The argument expressions. + arguments: List = + if (elems == null) + List() + else + findExprChildren(elems).map((n) -> wrapExpr(n)) +} + +/// A type annotation (`: Type`). +class TypeAnnotationNode extends SyntaxNode { + /// The type. + type: TypeNode = wrapTypeNode(findTypeChild(node)!!) +} + +/// A type parameter declaration. +class TypeParameterNode extends SyntaxNode { + /// The variance modifier (`"in"`, `"out"`, or null). + variance: String? = terminals.findOrNull((t) -> t.text == "in" || t.text == "out")?.text + + /// The type parameter name. + name: IdentifierNode = + let (n = findChild(node, "identifier")) + new IdentifierNode { node = n!! } +} + +/// A type parameter list (``). +class TypeParameterListNode extends SyntaxNode { + local elems: Node? = findChild(node, "type_parameter_list_elements") + + /// The type parameters. + typeParameters: List = + if (elems == null) + List() + else + findChildren(elems, "type_parameter").map((n) -> new TypeParameterNode { node = n }) +} + +/// A type argument list (``). +class TypeArgumentListNode extends SyntaxNode { + local elems: Node? = findChild(node, "type_argument_list_elements") + + /// The type arguments. + typeArguments: List = + if (elems == null) + List() + else + findTypeChildren(elems).map((n) -> wrapTypeNode(n)) +} + +/// An identifier node. +class IdentifierNode extends SyntaxNode { + /// The identifier text. + value: String = node.text ?? "" +} + +/// A qualified identifier (`a.b.c`). +class QualifiedIdentifierNode extends SyntaxNode { + /// The identifiers in this qualified name. + identifiers: List = + findChildren(node, "identifier").map((n) -> new IdentifierNode { node = n }) +} + +/// A doc comment. +class DocCommentNode extends SyntaxNode { + /// The text of each doc comment line. + lines: List = findChildren(node, "doc_comment_line").map((n) -> n.text ?? "") +} + +/// A modifier list (e.g., `open`, `abstract external`). +class ModifierListNode extends SyntaxNode { + /// The modifier keywords. + modifiers: List = findChildren(node, "modifier").map((n) -> n.text ?? "") +} + +/// Base class for parts of a string literal. +abstract class StringPartNode extends SyntaxNode {} + +/// A plain text part of a string literal. +class StringCharsNode extends StringPartNode { + /// The text content. + value: String = node.text ?? "" +} + +/// An escape sequence in a string literal. +class StringEscapeNode extends StringPartNode { + /// The escape sequence text. + value: String = node.text ?? "" +} + +/// A newline in a multi-line string literal. +class StringNewlineNode extends StringPartNode {} + +/// An interpolation in a string literal. +class StringInterpolationNode extends StringPartNode { + /// The interpolated expression. + expression: Expr = wrapExpr(node) +} + +/// Build string parts from the children of a string literal node. +local const function buildStringParts(cs: List): List = + // skip opening and closing terminals + let (inner = cs.drop(1).dropLast(1)) + buildStringPartsInner(inner, 0, List()) + +local const function buildStringPartsInner( + cs: List, + i: Int, + acc: List, +): List = + if (i >= cs.length) + acc + else + let (c = cs[i]) + if (c.type == "string_chars") + buildStringPartsInner(cs, i + 1, acc.add(new StringCharsNode { node = c })) + else if (c.type == "string_escape") + buildStringPartsInner(cs, i + 1, acc.add(new StringEscapeNode { node = c })) + else if (c.type == "string_newline") + buildStringPartsInner(cs, i + 1, acc.add(new StringNewlineNode { node = c })) + else if (c.type == "terminal" && isInterpolationStart(c)) + let (exprAndClose = findInterpolationExpr(cs, i + 1)) + buildStringPartsInner( + cs, + exprAndClose.second, + acc.add(new StringInterpolationNode { node = exprAndClose.first }), + ) + else if (c.type == "line_comment" || c.type == "block_comment" || c.type == "semicolon") + // skip affixes + buildStringPartsInner(cs, i + 1, acc) + else + // skip other terminals (shouldn't normally happen) + buildStringPartsInner(cs, i + 1, acc) + +local const function isInterpolationStart(n: Node): Boolean = + let (t = n.text) + if (t == null) + false + else + t.endsWith("(") + && (t.startsWith("\\") || t.startsWith("#")) + +/// Find the interpolation expression and return it along with the index past the closing paren. +local const function findInterpolationExpr(cs: List, startIdx: Int): Pair = + // walk forward to find the expression node (skip affixes) + let (exprIdx = findNextNonAffix(cs, startIdx)) + if (exprIdx >= cs.length) + Pair(cs[startIdx - 1], cs.length) + else + let (exprNode = cs[exprIdx]) + // next should be the closing terminal ")" + let (closeIdx = findNextNonAffix(cs, exprIdx + 1)) + Pair(exprNode, closeIdx + 1) + +local const function findNextNonAffix(cs: List, startIdx: Int): Int = + if (startIdx >= cs.length) + startIdx + else if ( + cs[startIdx].type == "line_comment" + || cs[startIdx].type == "block_comment" + || cs[startIdx].type == "semicolon" + ) + findNextNonAffix(cs, startIdx + 1) + else + startIdx From 1e29ca9cf4a863529c08f94f13ec4ec0b7547017 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Thu, 30 Apr 2026 17:25:19 +0200 Subject: [PATCH 02/49] Add support for full round-trip of Pkl code --- pkl-core/pkl-core.gradle.kts | 1 + .../pkl/core/stdlib/syntax/SyntaxNodes.java | 69 +++- .../input/syntax/format.pkl | 367 ++++++++++++++++++ .../output/syntax/format.pcf | 130 +++++++ .../java/org/pkl/formatter/Formatter.java | 19 + .../org/pkl/parser/syntax/generic/Node.java | 4 + stdlib/syntax.pkl | 15 +- 7 files changed, 595 insertions(+), 10 deletions(-) create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/syntax/format.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/syntax/format.pcf diff --git a/pkl-core/pkl-core.gradle.kts b/pkl-core/pkl-core.gradle.kts index 5d2b2920d..762818ef8 100644 --- a/pkl-core/pkl-core.gradle.kts +++ b/pkl-core/pkl-core.gradle.kts @@ -59,6 +59,7 @@ dependencies { compileOnly(projects.pklExecutor) implementation(projects.pklParser) + implementation(projects.pklFormatter) implementation(libs.msgpack) implementation(libs.truffleApi) implementation(libs.graalSdk) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java index 9018bc2ea..40db0e1fd 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java @@ -18,20 +18,35 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Specialization; import java.util.ArrayList; +import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.SyntaxModule; import org.pkl.core.runtime.VmList; import org.pkl.core.runtime.VmNull; import org.pkl.core.runtime.VmTyped; +import org.pkl.core.runtime.VmUtils; import org.pkl.core.stdlib.ExternalMethod1Node; +import org.pkl.core.stdlib.ExternalMethod2Node; import org.pkl.core.stdlib.VmObjectFactory; +import org.pkl.formatter.Formatter; +import org.pkl.formatter.GrammarVersion; import org.pkl.parser.GenericParser; import org.pkl.parser.GenericParserError; import org.pkl.parser.syntax.generic.FullSpan; import org.pkl.parser.syntax.generic.Node; +import org.pkl.parser.syntax.generic.NodeType; public final class SyntaxNodes { private SyntaxNodes() {} + private static final Identifier TYPE_ID = Identifier.get("type"); + private static final Identifier CHILDREN_ID = Identifier.get("children"); + private static final Identifier SPAN_ID = Identifier.get("span"); + private static final Identifier LINE_START_ID = Identifier.get("lineStart"); + private static final Identifier COL_START_ID = Identifier.get("colStart"); + private static final Identifier LINE_END_ID = Identifier.get("lineEnd"); + private static final Identifier COL_END_ID = Identifier.get("colEnd"); + private static final char[] EMPTY_SOURCE = new char[0]; + /** Extra storage backing a Pkl {@code Node} instance. */ static final class NodeData { final Node node; @@ -96,23 +111,20 @@ protected Object eval(VmTyped self, String source) { } } - @TruffleBoundary private static VmTyped convertNode(Node genericNode, char[] sourceChars) { - // Convert children recursively + // convert children recursively var childrenList = new ArrayList(genericNode.children.size()); for (var child : genericNode.children) { childrenList.add(convertNode(child, sourceChars)); } - // Build NodeData var data = new NodeData(genericNode, sourceChars); data.childrenVm = VmList.create(childrenList.toArray()); data.spanVm = spanFactory.create(genericNode.span); - // Create VmTyped node var result = nodeFactory.create(data); - // Set parent back-reference on each child + // set parent back-reference on each child for (var childVm : childrenList) { var childData = (NodeData) childVm.getExtraStorage(); childData.parentVm = result; @@ -121,4 +133,51 @@ private static VmTyped convertNode(Node genericNode, char[] sourceChars) { return result; } } + + public abstract static class formatToString extends ExternalMethod2Node { + @Specialization + @TruffleBoundary + protected String eval(VmTyped self, VmTyped nodeVm, String grammarVersion) { + var node = convertVmToNode(nodeVm); + return new Formatter(GrammarVersion.valueOf(grammarVersion)).format(node); + } + } + + private static Node convertVmToNode(VmTyped nodeVm) { + var typeStr = (String) VmUtils.readMember(nodeVm, TYPE_ID); + var nodeType = NodeType.valueOf(typeStr.toUpperCase()); + + var childrenVm = (VmList) VmUtils.readMember(nodeVm, CHILDREN_ID); + var children = new ArrayList(childrenVm.getLength()); + for (var i = 0; i < childrenVm.getLength(); i++) { + children.add(convertVmToNode((VmTyped) childrenVm.get(i))); + } + + var spanVm = (VmTyped) VmUtils.readMember(nodeVm, SPAN_ID); + var lineStart = ((Long) VmUtils.readMember(spanVm, LINE_START_ID)).intValue(); + var colStart = ((Long) VmUtils.readMember(spanVm, COL_START_ID)).intValue(); + var lineEnd = ((Long) VmUtils.readMember(spanVm, LINE_END_ID)).intValue(); + var colEnd = ((Long) VmUtils.readMember(spanVm, COL_END_ID)).intValue(); + var span = new FullSpan(0, 0, lineStart, colStart, lineEnd, colEnd); + + Node node; + if (children.isEmpty()) { + node = new Node(nodeType, span); + } else { + node = new Node(nodeType, span, children); + } + + var textObj = VmUtils.readMember(nodeVm, Identifier.TEXT); + if (textObj instanceof String text) { + node.setText(text); + } else if (nodeType == NodeType.STRING_CHARS) { + var sb = new StringBuilder(); + for (var child : children) { + sb.append(child.text(EMPTY_SOURCE)); + } + node.setText(sb.toString()); + } + + return node; + } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/format.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/format.pkl new file mode 100644 index 000000000..96635c630 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/format.pkl @@ -0,0 +1,367 @@ +amends "../snippetTest.pkl" + +import "pkl:syntax" + +local function roundTrip(source: String) = syntax.format(parseNode(source)) + +local function parseNode(source: String) = syntax.parse(source).node + +local function replaceLeaf( + node: syntax.Node, + targetType: syntax.NodeType, + oldText: String, + newText: String, +): syntax.Node = + if (node.type == targetType && node.text == oldText) + (node) { text = newText } + else + (node) { + children = node.children.map((c) -> replaceLeaf(c, targetType, oldText, newText)) + } + +local function transformFirst( + node: syntax.Node, + targetType: syntax.NodeType, + transform: (syntax.Node) -> syntax.Node, +): syntax.Node = + if (node.type == targetType) + transform.apply(node) + else + (node) { + children = node.children.map((c) -> transformFirst(c, targetType, transform)) + } + +examples { + ["empty module"] { + roundTrip("") == "\n" + } + + ["simple property"] { + roundTrip("x = 1") == "x = 1\n" + } + + ["multiple properties"] { + roundTrip( + """ + x = 1 + y = 2 + """, + ) + == """ + x = 1 + y = 2 + + """ + } + + ["string literals"] { + roundTrip(#"x = "hello""#) == #"x = "hello"\#n"# + roundTrip(#"x = "hello\nworld""#) == #"x = "hello\nworld"\#n"# + roundTrip(#"x = "hello \(name)""#) == #"x = "hello \(name)"\#n"# + } + + ["multiline string"] { + roundTrip( + #""" + x = """ + hello + world + """ + """#, + ) + == #""" + x = + """ + hello + world + """ + + """# + } + + ["comments"] { + roundTrip( + """ + // this is a comment + x = 1 + """, + ) + == """ + // this is a comment + x = 1 + + """ + } + + ["doc comments"] { + roundTrip( + """ + /// A documented property. + x = 1 + """, + ) + == """ + /// A documented property. + x = 1 + + """ + } + + ["class declaration"] { + roundTrip( + """ + class Bird { + name: String + canFly: Boolean = true + } + """, + ) + == """ + class Bird { + name: String + canFly: Boolean = true + } + + """ + } + + ["module declaration"] { + roundTrip("module my.app") == "module my.app\n" + } + + ["imports"] { + roundTrip( + """ + import "foo.pkl" + import "bar.pkl" as myBar + """, + ) + == """ + import "bar.pkl" as myBar + import "foo.pkl" + + """ + } + + ["typealias"] { + roundTrip("typealias Positive = Int(this>0)") == "typealias Positive = Int(this > 0)\n" + } + + ["boolean literals"] { + roundTrip("x = true") == "x = true\n" + roundTrip("x = false") == "x = false\n" + } + + ["numeric literals"] { + roundTrip("x = 42") == "x = 42\n" + roundTrip("x = 3.14") == "x = 3.14\n" + roundTrip("x = 0xFF") == "x = 0xFF\n" + } + + ["null literal"] { + roundTrip("x = null") == "x = null\n" + } + + ["keyword expressions"] { + roundTrip("x = this") == "x = this\n" + roundTrip("x = outer") == "x = outer\n" + roundTrip("x = module") == "x = module\n" + } + + ["binary operators"] { + roundTrip("x = 1 + 2") == "x = 1 + 2\n" + roundTrip("x = a && b") == "x = a && b\n" + roundTrip("x = a is String") == "x = a is String\n" + } + + ["unary operators"] { + roundTrip("x = -1") == "x = -1\n" + roundTrip("x = !flag") == "x = !flag\n" + roundTrip("x = value!!") == "x = value!!\n" + } + + ["if expression"] { + roundTrip("x = if(a) b else c") == "x = if (a) b else c\n" + } + + ["function literal"] { + roundTrip("x = (a) -> a + 1") == "x = (a) -> a + 1\n" + } + + ["method declaration"] { + roundTrip("function greet(name: String): String = name") + == "function greet(name: String): String = name\n" + } + + ["modifiers"] { + roundTrip("hidden x = 1") == "hidden x = 1\n" + roundTrip("local x = 1") == "local x = 1\n" + } + + ["object body"] { + roundTrip( + """ + x { z -> + a = 1 + b = 2 + } + """, + ) + == """ + x { z -> + a = 1 + b = 2 + } + + """ + } + + ["annotations"] { + roundTrip( + """ + @Deprecated { message = "use other" } + x = 1 + """, + ) + == """ + @Deprecated { message = "use other" } + x = 1 + + """ + } + + ["type annotations"] { + roundTrip("x: String = \"hello\"") == "x: String = \"hello\"\n" + roundTrip("x: Int? = null") == "x: Int? = null\n" + roundTrip("x: String|Int = 1") == "x: String | Int = 1\n" + } + + ["for generator"] { + roundTrip( + """ + x { + for (k, v in items) { + [k] = v + } + } + """, + ) + == """ + x { + for (k, v in items) { + [k] = v + } + } + + """ + } + + ["when generator"] { + roundTrip( + """ + x { + when (flag) { + a = 1 + } else { + b = 2 + } + } + """, + ) + == """ + x { + when (flag) { + a = 1 + } else { + b = 2 + } + } + + """ + } + + ["let expression"] { + roundTrip("x = let (y = 1) y + 1") == "x = let (y = 1) y + 1\n" + } + + ["new expression"] { + roundTrip( + """ + x = new Dynamic { + a = 1 + } + """, + ) + == """ + x = new Dynamic { + a = 1 + } + + """ + } + + ["qualified access"] { + roundTrip("x = foo.bar.baz") == "x = foo.bar.baz\n" + } + + ["subscript"] { + roundTrip("x = list[0]") == "x = list[0]\n" + } + + ["parenthesized expression"] { + roundTrip("x = (1 + 2)") == "x = (1 + 2)\n" + } + + ["modify identifier"] { + local root = parseNode("x = 1") + local modified = replaceLeaf(root, "identifier", "x", "y") + syntax.format(modified) == "y = 1\n" + } + + ["modify modifier"] { + local root = parseNode("hidden x = 1") + local modified = replaceLeaf(root, "modifier", "hidden", "local") + syntax.format(modified) == "local x = 1\n" + } + + ["modify string content"] { + local root = parseNode(#"x = "hello""#) + local modified = replaceLeaf(root, "string_chars", "hello", "world") + syntax.format(modified) == #"x = "world"\#n"# + } + + ["modify int literal"] { + local root = parseNode("x = 42") + local modified = replaceLeaf(root, "int_literal_expr", "42", "99") + syntax.format(modified) == "x = 99\n" + } + + ["modify boolean literal"] { + local root = parseNode("x = true") + local modified = replaceLeaf(root, "bool_literal_expr", "true", "false") + syntax.format(modified) == "x = false\n" + } + + ["modify float literal"] { + local root = parseNode("x = 3.14") + local modified = replaceLeaf(root, "float_literal_expr", "3.14", "2.72") + syntax.format(modified) == "x = 2.72\n" + } + + ["add new modifier"] { + local root = parseNode("local x = 1") + local constModifier = new syntax.Node { + type = "modifier" + text = "const" + } + local modified = + transformFirst(root, "modifier_list", (n) -> (n) { + children = List(constModifier) + n.children + }) + // modifier order is switched by the formatter + syntax.format(modified) == """ + local const x = 1 + + """ + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/format.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/format.pcf new file mode 100644 index 000000000..4a5572a28 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/format.pcf @@ -0,0 +1,130 @@ +examples { + ["empty module"] { + true + } + ["simple property"] { + true + } + ["multiple properties"] { + true + } + ["string literals"] { + true + true + true + } + ["multiline string"] { + true + } + ["comments"] { + true + } + ["doc comments"] { + true + } + ["class declaration"] { + true + } + ["module declaration"] { + true + } + ["imports"] { + true + } + ["typealias"] { + true + } + ["boolean literals"] { + true + true + } + ["numeric literals"] { + true + true + true + } + ["null literal"] { + true + } + ["keyword expressions"] { + true + true + true + } + ["binary operators"] { + true + true + true + } + ["unary operators"] { + true + true + true + } + ["if expression"] { + true + } + ["function literal"] { + true + } + ["method declaration"] { + true + } + ["modifiers"] { + true + true + } + ["object body"] { + true + } + ["annotations"] { + true + } + ["type annotations"] { + true + true + true + } + ["for generator"] { + true + } + ["when generator"] { + true + } + ["let expression"] { + true + } + ["new expression"] { + true + } + ["qualified access"] { + true + } + ["subscript"] { + true + } + ["parenthesized expression"] { + true + } + ["modify identifier"] { + true + } + ["modify modifier"] { + true + } + ["modify string content"] { + true + } + ["modify int literal"] { + true + } + ["modify boolean literal"] { + true + } + ["modify float literal"] { + true + } + ["add new modifier"] { + true + } +} diff --git a/pkl-formatter/src/main/java/org/pkl/formatter/Formatter.java b/pkl-formatter/src/main/java/org/pkl/formatter/Formatter.java index 6e9487a76..543a991dd 100644 --- a/pkl-formatter/src/main/java/org/pkl/formatter/Formatter.java +++ b/pkl-formatter/src/main/java/org/pkl/formatter/Formatter.java @@ -21,6 +21,7 @@ import java.nio.file.Path; import java.util.List; import org.pkl.parser.GenericParser; +import org.pkl.parser.syntax.generic.Node; /** * A formatter for Pkl files that applies canonical formatting rules. @@ -111,6 +112,24 @@ public void format(Reader input, Appendable output) throws IOException { format(sb.toString(), output); } + /** + * Format the given Pkl AST as text. + * + *

The AST terminal nodes should have their text property set before calling format, as this + * method does not have access to the original source. + * + * @param ast the Pkl module node to format + * @return the formatted Pkl source code + */ + public String format(Node ast) { + var formatAst = new Builder("", grammarVersion).format(ast); + // force a line at the end of the file + var nodes = new Nodes(List.of(formatAst, ForceLine.INSTANCE)); + var output = new StringBuilder(); + new Generator(output).generate(nodes); + return output.toString(); + } + private void format(String input, Appendable output) { var ast = new GenericParser().parseModule(input); var formatAst = new Builder(input, grammarVersion).format(ast); diff --git a/pkl-parser/src/main/java/org/pkl/parser/syntax/generic/Node.java b/pkl-parser/src/main/java/org/pkl/parser/syntax/generic/Node.java index bc481b15d..d15cd7308 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/syntax/generic/Node.java +++ b/pkl-parser/src/main/java/org/pkl/parser/syntax/generic/Node.java @@ -51,6 +51,10 @@ public String text(char[] source) { return text; } + public void setText(String text) { + this.text = text; + } + /** Returns the first child of type {@code type} or {@code null}. */ public @Nullable Node findChildByType(NodeType type) { for (var child : children) { diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 740ab22ea..62c252b41 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -31,20 +31,25 @@ function parseResource(resourceURI: String): ModuleNode | ParserError = external local function parseNodes(source: String): Node | ParserError +/// Format a syntax node back to Pkl source code. +function format(node: Node): String = formatToString(node, "V2") + +external function formatToString(node: Node, grammarVersion: "V1" | "V2"): String + class Node { type: NodeType children: List - parent: Node? + hidden parent: Node? text: String? @ConvertSpan span: Span } class Span { - lineStart: UInt - colStart: UInt - lineEnd: UInt - colEnd: UInt + lineStart: UInt = 0 + colStart: UInt = 0 + lineEnd: UInt = 0 + colEnd: UInt = 0 } // noinspection TypeMismatch From b196781dd68f556643e1a98e25fef0e351f350f9 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Mon, 18 May 2026 17:00:10 +0200 Subject: [PATCH 03/49] Add ast builders --- .../input/syntax/builders.pkl | 816 ++++++ .../output/syntax/builders.pcf | 244 ++ stdlib/syntax.pkl | 2258 ++++++++++++++++- 3 files changed, 3304 insertions(+), 14 deletions(-) create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/syntax/builders.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/syntax/builders.pcf diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/builders.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/builders.pkl new file mode 100644 index 000000000..095c12062 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/builders.pkl @@ -0,0 +1,816 @@ +amends "../snippetTest.pkl" + +import "pkl:syntax" + +local function formatExpr(builder: syntax.ExprBuilder): String = + syntax.format(builder.build().node) + +local function formatType(builder: syntax.TypeBuilder): String = + syntax.format(builder.build().node) + +local function formatBody(builder: syntax.ObjectBodyBuilder): String = + syntax.format(builder.build().node) + +local function formatModule(builder: syntax.ModuleBuilder): String = + syntax.format(builder.build().node) + +examples { + ["int literal"] { + formatExpr(new syntax.IntLiteralBuilder { value = 42 }) == "42\n" + formatExpr(new syntax.IntLiteralBuilder { value = "0xFF" }) == "0xFF\n" + } + + ["float literal"] { + formatExpr(new syntax.FloatLiteralBuilder { value = 3.14 }) == "3.14\n" + } + + ["bool literal"] { + formatExpr(new syntax.BoolLiteralBuilder { value = true }) == "true\n" + formatExpr(new syntax.BoolLiteralBuilder { value = false }) == "false\n" + } + + ["null literal"] { + formatExpr(new syntax.NullLiteralBuilder {}) == "null\n" + } + + ["this expr"] { + formatExpr(new syntax.ThisExprBuilder {}) == "this\n" + } + + ["outer expr"] { + formatExpr(new syntax.OuterExprBuilder {}) == "outer\n" + } + + ["module expr"] { + formatExpr(new syntax.ModuleExprBuilder {}) == "module\n" + } + + ["identifier expr"] { + formatExpr(new syntax.IdentifierExprBuilder { name = "foo" }) == "foo\n" + } + + ["string literal"] { + formatExpr(new syntax.StringLiteralBuilder { value = "hello" }) == #""hello"\#n"# + } + + ["unary minus"] { + formatExpr(new syntax.UnaryMinusExprBuilder { + operand = new syntax.IntLiteralBuilder { value = 5 } + }) == "-5\n" + } + + ["logical not"] { + formatExpr(new syntax.LogicalNotExprBuilder { + operand = new syntax.IdentifierExprBuilder { name = "flag" } + }) == "!flag\n" + } + + ["non-null"] { + formatExpr(new syntax.NonNullExprBuilder { + operand = new syntax.IdentifierExprBuilder { name = "x" } + }) == "x!!\n" + } + + ["throw"] { + formatExpr(new syntax.ThrowExprBuilder { + expression = new syntax.StringLiteralBuilder { value = "oops" } + }) == #"throw("oops")\#n"# + } + + ["trace"] { + formatExpr(new syntax.TraceExprBuilder { + expression = new syntax.IdentifierExprBuilder { name = "x" } + }) == "trace(x)\n" + } + + ["parenthesized"] { + formatExpr(new syntax.ParenthesizedExprBuilder { + expression = new syntax.IntLiteralBuilder { value = 1 } + }) == "(1)\n" + } + + ["binary op"] { + formatExpr(new syntax.BinaryOpExprBuilder { + left = new syntax.IdentifierExprBuilder { name = "x" } + operator = "+" + right = new syntax.IntLiteralBuilder { value = 1 } + }) == "x + 1\n" + formatExpr(new syntax.BinaryOpExprBuilder { + left = new syntax.IdentifierExprBuilder { name = "a" } + operator = "&&" + right = new syntax.IdentifierExprBuilder { name = "b" } + }) == "a && b\n" + } + + ["is expr"] { + formatExpr(new syntax.IsExprBuilder { + operand = new syntax.IdentifierExprBuilder { name = "x" } + type = new syntax.DeclaredTypeBuilder { name = "String" } + }) == "x is String\n" + } + + ["if expr"] { + formatExpr(new syntax.IfExprBuilder { + condition = new syntax.BinaryOpExprBuilder { + left = new syntax.IdentifierExprBuilder { name = "x" } + operator = ">" + right = new syntax.IntLiteralBuilder { value = 0 } + } + thenExpr = new syntax.StringLiteralBuilder { value = "positive" } + elseExpr = new syntax.StringLiteralBuilder { value = "non-positive" } + }) == #"if (x > 0) "positive" else "non-positive"\#n"# + } + + ["import expr"] { + formatExpr(new syntax.ImportExprBuilder { uri = "pkl:json" }) == #"import("pkl:json")\#n"# + } + + ["read expr"] { + formatExpr(new syntax.ReadExprBuilder { + expression = new syntax.StringLiteralBuilder { value = "file.txt" } + }) == #"read("file.txt")\#n"# + } + + ["let expr"] { + formatExpr(new syntax.LetExprBuilder { + parameterName = "x" + bindingValue = new syntax.IntLiteralBuilder { value = 1 } + body = new syntax.IdentifierExprBuilder { name = "x" } + }) == "let (x = 1) x\n" + formatExpr(new syntax.LetExprBuilder { + parameterName = "x" + parameterType = new syntax.DeclaredTypeBuilder { name = "Int" } + bindingValue = new syntax.IntLiteralBuilder { value = 1 } + body = new syntax.IdentifierExprBuilder { name = "x" } + }) == "let (x: Int = 1) x\n" + } + + ["function literal"] { + formatExpr(new syntax.FunctionLiteralBuilder { + parameters = List(new syntax.ParameterBuilder { name = "x" }) + body = new syntax.BinaryOpExprBuilder { + left = new syntax.IdentifierExprBuilder { name = "x" } + operator = "+" + right = new syntax.IntLiteralBuilder { value = 1 } + } + }) == "(x) -> x + 1\n" + formatExpr(new syntax.FunctionLiteralBuilder { + parameters = List() + body = new syntax.IntLiteralBuilder { value = 0 } + }) == "() -> 0\n" + formatExpr(new syntax.FunctionLiteralBuilder { + parameters = List( + new syntax.ParameterBuilder { name = "x"; typeAnnotation = new syntax.DeclaredTypeBuilder { name = "Int" } }, + new syntax.ParameterBuilder { name = "y" } + ) + body = new syntax.IdentifierExprBuilder { name = "x" } + }) == "(x: Int, y) -> x\n" + } + + ["function call"] { + formatExpr(new syntax.FunctionCallBuilder { + name = "f" + arguments = List() + }) == "f()\n" + formatExpr(new syntax.FunctionCallBuilder { + name = "max" + arguments = List( + new syntax.IntLiteralBuilder { value = 1 }, + new syntax.IntLiteralBuilder { value = 2 } + ) + }) == "max(1, 2)\n" + } + + ["qualified access"] { + formatExpr(new syntax.QualifiedAccessBuilder { + receiver = new syntax.IdentifierExprBuilder { name = "obj" } + member = "field" + }) == "obj.field\n" + formatExpr(new syntax.QualifiedAccessBuilder { + receiver = new syntax.IdentifierExprBuilder { name = "obj" } + member = "field" + isNullSafe = true + }) == "obj?.field\n" + formatExpr(new syntax.QualifiedAccessBuilder { + receiver = new syntax.IdentifierExprBuilder { name = "obj" } + member = "method" + arguments = List(new syntax.IntLiteralBuilder { value = 1 }) + }) == "obj.method(1)\n" + } + + ["subscript"] { + formatExpr(new syntax.SubscriptBuilder { + receiver = new syntax.IdentifierExprBuilder { name = "list" } + index = new syntax.IntLiteralBuilder { value = 0 } + }) == "list[0]\n" + } + + ["unknown type"] { + formatType(new syntax.UnknownTypeBuilder {}) == "unknown\n" + } + + ["nothing type"] { + formatType(new syntax.NothingTypeBuilder {}) == "nothing\n" + } + + ["module type"] { + formatType(new syntax.ModuleTypeBuilder {}) == "module\n" + } + + ["declared type"] { + formatType(new syntax.DeclaredTypeBuilder { name = "String" }) == "String\n" + formatType(new syntax.DeclaredTypeBuilder { + name = "List" + typeArguments = List(new syntax.DeclaredTypeBuilder { name = "Int" }) + }) == "List\n" + formatType(new syntax.DeclaredTypeBuilder { + name = "Map" + typeArguments = List( + new syntax.DeclaredTypeBuilder { name = "String" }, + new syntax.DeclaredTypeBuilder { name = "Int" } + ) + }) == "Map\n" + } + + ["nullable type"] { + formatType(new syntax.NullableTypeBuilder { + baseType = new syntax.DeclaredTypeBuilder { name = "String" } + }) == "String?\n" + } + + ["union type"] { + formatType(new syntax.UnionTypeBuilder { + members = List( + new syntax.DeclaredTypeBuilder { name = "Int" }, + new syntax.DeclaredTypeBuilder { name = "String" } + ) + }) == "Int | String\n" + formatType(new syntax.UnionTypeBuilder { + members = List( + new syntax.DeclaredTypeBuilder { name = "Int" }, + new syntax.DeclaredTypeBuilder { name = "String" }, + new syntax.DeclaredTypeBuilder { name = "Boolean" } + ) + }) == "Int | String | Boolean\n" + } + + ["function type"] { + formatType(new syntax.FunctionTypeBuilder { + parameterTypes = List(new syntax.DeclaredTypeBuilder { name = "Int" }) + returnType = new syntax.DeclaredTypeBuilder { name = "String" } + }) == "(Int) -> String\n" + formatType(new syntax.FunctionTypeBuilder { + parameterTypes = List() + returnType = new syntax.DeclaredTypeBuilder { name = "Int" } + }) == "() -> Int\n" + formatType(new syntax.FunctionTypeBuilder { + parameterTypes = List( + new syntax.DeclaredTypeBuilder { name = "Int" }, + new syntax.DeclaredTypeBuilder { name = "Int" } + ) + returnType = new syntax.DeclaredTypeBuilder { name = "Int" } + }) == "(Int, Int) -> Int\n" + } + + ["constrained type"] { + formatType(new syntax.ConstrainedTypeBuilder { + baseType = new syntax.DeclaredTypeBuilder { name = "Int" } + constraints = List( + new syntax.BinaryOpExprBuilder { + left = new syntax.IdentifierExprBuilder { name = "this" } + operator = ">" + right = new syntax.IntLiteralBuilder { value = 0 } + } + ) + }) == "Int(this > 0)\n" + } + + ["parenthesized type"] { + formatType(new syntax.ParenthesizedTypeBuilder { + type = new syntax.UnionTypeBuilder { + members = List( + new syntax.DeclaredTypeBuilder { name = "Int" }, + new syntax.DeclaredTypeBuilder { name = "String" } + ) + } + }) == "(Int | String)\n" + } + + ["string constant type"] { + formatType(new syntax.StringConstantTypeBuilder { value = "foo" }) == #""foo"\#n"# + } + + ["empty body"] { + formatBody(new syntax.ObjectBodyBuilder {}) == "{}\n" + } + + ["object element"] { + formatBody(new syntax.ObjectBodyBuilder { + members = List( + new syntax.ObjectElementBuilder { + expression = new syntax.IntLiteralBuilder { value = 1 } + } + ) + }) == "{ 1 }\n" + } + + ["object spread"] { + formatBody(new syntax.ObjectBodyBuilder { + members = List( + new syntax.ObjectSpreadBuilder { + expression = new syntax.IdentifierExprBuilder { name = "other" } + } + ) + }) == "{ ...other }\n" + formatBody(new syntax.ObjectBodyBuilder { + members = List( + new syntax.ObjectSpreadBuilder { + expression = new syntax.IdentifierExprBuilder { name = "maybe" } + isNullable = true + } + ) + }) == "{ ...?maybe }\n" + } + + ["object property"] { + formatBody(new syntax.ObjectBodyBuilder { + members = List( + new syntax.ObjectPropertyBuilder { + name = "x" + value = new syntax.IntLiteralBuilder { value = 1 } + } + ) + }) == "{ x = 1 }\n" + formatBody(new syntax.ObjectBodyBuilder { + members = List( + new syntax.ObjectPropertyBuilder { + name = "x" + typeAnnotation = new syntax.DeclaredTypeBuilder { name = "Int" } + value = new syntax.IntLiteralBuilder { value = 1 } + } + ) + }) == "{ x: Int = 1 }\n" + formatBody(new syntax.ObjectBodyBuilder { + members = List( + new syntax.ObjectPropertyBuilder { + modifiers = List("hidden") + name = "x" + value = new syntax.IntLiteralBuilder { value = 1 } + } + ) + }) == "{ hidden x = 1 }\n" + formatBody(new syntax.ObjectBodyBuilder { + members = List( + new syntax.ObjectPropertyBuilder { + name = "x" + objectBodies = List( + new syntax.ObjectBodyBuilder { + members = List( + new syntax.ObjectPropertyBuilder { + name = "y" + value = new syntax.IntLiteralBuilder { value = 2 } + } + ) + } + ) + } + ) + }) == "{ x { y = 2 } }\n" + } + + ["object method"] { + formatBody(new syntax.ObjectBodyBuilder { + members = List( + new syntax.ObjectMethodBuilder { + name = "f" + parameters = List(new syntax.ParameterBuilder { name = "x" }) + body = new syntax.IdentifierExprBuilder { name = "x" } + } + ) + }) == "{ function f(x) = x }\n" + formatBody(new syntax.ObjectBodyBuilder { + members = List( + new syntax.ObjectMethodBuilder { + name = "f" + parameters = List(new syntax.ParameterBuilder { name = "x"; typeAnnotation = new syntax.DeclaredTypeBuilder { name = "Int" } }) + returnType = new syntax.DeclaredTypeBuilder { name = "Int" } + body = new syntax.IdentifierExprBuilder { name = "x" } + } + ) + }) == "{ function f(x: Int): Int = x }\n" + } + + ["object entry"] { + formatBody(new syntax.ObjectBodyBuilder { + members = List( + new syntax.ObjectEntryBuilder { + key = new syntax.StringLiteralBuilder { value = "k" } + value = new syntax.IntLiteralBuilder { value = 1 } + } + ) + }) == "{ [\"k\"] = 1 }\n" + formatBody(new syntax.ObjectBodyBuilder { + members = List( + new syntax.ObjectEntryBuilder { + key = new syntax.StringLiteralBuilder { value = "k" } + objectBodies = List( + new syntax.ObjectBodyBuilder { + members = List( + new syntax.ObjectPropertyBuilder { + name = "x" + value = new syntax.IntLiteralBuilder { value = 1 } + } + ) + } + ) + } + ) + }) == "{ [\"k\"] { x = 1 } }\n" + } + + ["member predicate"] { + formatBody(new syntax.ObjectBodyBuilder { + members = List( + new syntax.MemberPredicateBuilder { + condition = new syntax.IdentifierExprBuilder { name = "cond" } + value = new syntax.IntLiteralBuilder { value = 1 } + } + ) + }) == "{ [[cond]] = 1 }\n" + } + + ["for generator"] { + formatBody(new syntax.ObjectBodyBuilder { + members = List( + new syntax.ForGeneratorBuilder { + valueParameter = new syntax.ParameterBuilder { name = "x" } + iterable = new syntax.IdentifierExprBuilder { name = "items" } + body = new syntax.ObjectBodyBuilder { + members = List( + new syntax.ObjectElementBuilder { + expression = new syntax.IdentifierExprBuilder { name = "x" } + } + ) + } + } + ) + }) == "{ for (x in items) { x } }\n" + formatBody(new syntax.ObjectBodyBuilder { + members = List( + new syntax.ForGeneratorBuilder { + keyParameter = new syntax.ParameterBuilder { name = "k" } + valueParameter = new syntax.ParameterBuilder { name = "v" } + iterable = new syntax.IdentifierExprBuilder { name = "items" } + body = new syntax.ObjectBodyBuilder {} + } + ) + }) == "{ for (k, v in items) {} }\n" + } + + ["when generator"] { + formatBody(new syntax.ObjectBodyBuilder { + members = List( + new syntax.WhenGeneratorBuilder { + condition = new syntax.IdentifierExprBuilder { name = "cond" } + thenBody = new syntax.ObjectBodyBuilder {} + } + ) + }) == "{ when (cond) {} }\n" + formatBody(new syntax.ObjectBodyBuilder { + members = List( + new syntax.WhenGeneratorBuilder { + condition = new syntax.IdentifierExprBuilder { name = "cond" } + thenBody = new syntax.ObjectBodyBuilder {} + elseBody = new syntax.ObjectBodyBuilder {} + } + ) + }) == "{ when (cond) {} else {} }\n" + } + + ["body with parameters"] { + formatBody(new syntax.ObjectBodyBuilder { + parameters = List( + new syntax.ParameterBuilder { name = "x" }, + new syntax.ParameterBuilder { name = "y" } + ) + members = List( + new syntax.ObjectElementBuilder { + expression = new syntax.IdentifierExprBuilder { name = "x" } + } + ) + }) == "{ x, y -> x }\n" + } + + ["new expr"] { + formatExpr(new syntax.NewExprBuilder { + body = new syntax.ObjectBodyBuilder {} + }) == "new {}\n" + formatExpr(new syntax.NewExprBuilder { + type = new syntax.DeclaredTypeBuilder { name = "Foo" } + body = new syntax.ObjectBodyBuilder { + members = List( + new syntax.ObjectPropertyBuilder { + name = "x" + value = new syntax.IntLiteralBuilder { value = 1 } + } + ) + } + }) == "new Foo { x = 1 }\n" + } + + ["amends expr"] { + formatExpr(new syntax.AmendsExprBuilder { + parentExpr = new syntax.ParenthesizedExprBuilder { + expression = new syntax.IdentifierExprBuilder { name = "base" } + } + body = new syntax.ObjectBodyBuilder { + members = List( + new syntax.ObjectPropertyBuilder { + name = "x" + value = new syntax.IntLiteralBuilder { value = 1 } + } + ) + } + }) == "(base) { x = 1 }\n" + } + + ["doc comment"] { + syntax.format((new syntax.DocCommentBuilder { + lines = List(" line 1", " line 2") + }).build().node) == "/// line 1\n/// line 2\n" + } + + ["annotation"] { + syntax.format((new syntax.AnnotationBuilder { + type = new syntax.DeclaredTypeBuilder { name = "Deprecated" } + }).build().node) == "@Deprecated\n" + syntax.format((new syntax.AnnotationBuilder { + type = new syntax.DeclaredTypeBuilder { name = "Deprecated" } + body = new syntax.ObjectBodyBuilder { + members = List( + new syntax.ObjectPropertyBuilder { + name = "message" + value = new syntax.StringLiteralBuilder { value = "old" } + } + ) + } + }).build().node) == #"@Deprecated { message = "old" }\#n"# + } + + ["import"] { + syntax.format((new syntax.ImportBuilder { uri = "pkl:json" }).build().node) + == #"import "pkl:json"\#n"# + syntax.format((new syntax.ImportBuilder { uri = "pkl:json"; isGlob = true }).build().node) + == #"import* "pkl:json"\#n"# + syntax.format((new syntax.ImportBuilder { uri = "pkl:json"; alias = "j" }).build().node) + == #"import "pkl:json" as j\#n"# + } + + ["class property (top-level)"] { + syntax.format((new syntax.ClassPropertyBuilder { + name = "x" + value = new syntax.IntLiteralBuilder { value = 1 } + }).build().node) == "x = 1\n" + syntax.format((new syntax.ClassPropertyBuilder { + modifiers = List("hidden") + name = "x" + typeAnnotation = new syntax.DeclaredTypeBuilder { name = "Int" } + value = new syntax.IntLiteralBuilder { value = 1 } + }).build().node) == "hidden x: Int = 1\n" + } + + ["class method (top-level)"] { + syntax.format((new syntax.ClassMethodBuilder { + name = "f" + parameters = List(new syntax.ParameterBuilder { name = "x" }) + body = new syntax.IdentifierExprBuilder { name = "x" } + }).build().node) == "function f(x) = x\n" + syntax.format((new syntax.ClassMethodBuilder { + modifiers = List("abstract") + name = "f" + parameters = List() + returnType = new syntax.DeclaredTypeBuilder { name = "Int" } + }).build().node) == "abstract function f(): Int\n" + } + + ["typealias"] { + syntax.format((new syntax.TypeAliasBuilder { + name = "MyInt" + type = new syntax.DeclaredTypeBuilder { name = "Int" } + }).build().node) == "typealias MyInt = Int\n" + syntax.format((new syntax.TypeAliasBuilder { + name = "Pair" + typeParameters = List( + new syntax.TypeParameterBuilder { name = "A" }, + new syntax.TypeParameterBuilder { name = "B" } + ) + type = new syntax.DeclaredTypeBuilder { name = "Mapping" } + }).build().node) == "typealias Pair = Mapping\n" + } + + ["class"] { + syntax.format((new syntax.ClassBuilder { + name = "Foo" + }).build().node) == "class Foo\n" + syntax.format((new syntax.ClassBuilder { + modifiers = List("open") + name = "Foo" + extendsType = new syntax.DeclaredTypeBuilder { name = "Bar" } + body = new syntax.ClassBodyBuilder { + properties = List( + new syntax.ClassPropertyBuilder { + name = "x" + value = new syntax.IntLiteralBuilder { value = 1 } + } + ) + } + }).build().node) == "open class Foo extends Bar {\n x = 1\n}\n" + } + + ["module declaration"] { + syntax.format((new syntax.ModuleDeclarationBuilder { + name = "my.config" + }).build().node) == "module my.config\n" + syntax.format((new syntax.ModuleDeclarationBuilder { + amendsUri = "pkl:base" + }).build().node) == #"amends "pkl:base"\#n"# + syntax.format((new syntax.ModuleDeclarationBuilder { + modifiers = List("open") + name = "my.config" + extendsUri = "pkl:base" + }).build().node) == #""" + open module my.config + + extends "pkl:base" + + """# + } + + ["module"] { + formatModule(new syntax.ModuleBuilder { + declaration = new syntax.ModuleDeclarationBuilder { name = "my.config" } + imports = List(new syntax.ImportBuilder { uri = "pkl:json" }) + properties = List( + new syntax.ClassPropertyBuilder { + name = "host" + typeAnnotation = new syntax.DeclaredTypeBuilder { name = "String" } + value = new syntax.StringLiteralBuilder { value = "localhost" } + }, + new syntax.ClassPropertyBuilder { + name = "port" + typeAnnotation = new syntax.DeclaredTypeBuilder { name = "Int" } + value = new syntax.IntLiteralBuilder { value = 8080 } + } + ) + }) == #""" + module my.config + import "pkl:json" + host: String = "localhost" + port: Int = 8080 + + """# + } + + ["round-trip: simple property"] { + let (parsed = syntax.parse("x = 1") as syntax.ModuleNode) + let (rebuilt = parsed.toBuilder().build()) + syntax.format(rebuilt.node) == "x = 1\n" + } + + ["round-trip: typed property"] { + let (parsed = syntax.parse("x: Int = 1") as syntax.ModuleNode) + let (rebuilt = parsed.toBuilder().build()) + syntax.format(rebuilt.node) == "x: Int = 1\n" + } + + ["round-trip: if expression"] { + let (parsed = syntax.parse(#"x = if (a > 0) "yes" else "no""#) as syntax.ModuleNode) + let (rebuilt = parsed.toBuilder().build()) + syntax.format(rebuilt.node) == #"x = if (a > 0) "yes" else "no"\#n"# + } + + ["round-trip: function call"] { + let (parsed = syntax.parse("x = max(1, 2)") as syntax.ModuleNode) + let (rebuilt = parsed.toBuilder().build()) + syntax.format(rebuilt.node) == "x = max(1, 2)\n" + } + + ["round-trip: qualified access"] { + let (parsed = syntax.parse("x = obj.field") as syntax.ModuleNode) + let (rebuilt = parsed.toBuilder().build()) + syntax.format(rebuilt.node) == "x = obj.field\n" + } + + ["round-trip: union type"] { + let (parsed = syntax.parse("typealias T = Int|String") as syntax.ModuleNode) + let (rebuilt = parsed.toBuilder().build()) + syntax.format(rebuilt.node) == "typealias T = Int | String\n" + } + + ["round-trip: class with body"] { + let (parsed = syntax.parse(""" + open class Foo extends Bar { + x: Int = 1 + function f(y) = y + } + """) as syntax.ModuleNode) + let (rebuilt = parsed.toBuilder().build()) + syntax.format(rebuilt.node) == #""" + open class Foo extends Bar { + x: Int = 1 + + function f(y) = y + } + + """# + } + + ["round-trip: amend modifies value"] { + let (parsed = syntax.parse("x = 1") as syntax.ModuleNode) + let (modified = (parsed.properties.first.toBuilder()) { + value = new syntax.IntLiteralBuilder { value = 99 } + }) + syntax.format(modified.build().node) == "x = 99\n" + } + + ["multi-line string"] { + formatExpr(new syntax.MultiLineStringLiteralBuilder { + parts = List( + new syntax.StringNewlineBuilder {}, + new syntax.StringCharsBuilder { value = "hello" }, + new syntax.StringNewlineBuilder {}, + new syntax.StringCharsBuilder { value = "world" }, + new syntax.StringNewlineBuilder {} + ) + }) == #""" + """ + hello + world + """ + + """# + } + + ["multi-line string with interpolation"] { + formatExpr(new syntax.MultiLineStringLiteralBuilder { + parts = List( + new syntax.StringNewlineBuilder {}, + new syntax.StringCharsBuilder { value = "hi " }, + new syntax.StringInterpolationBuilder { + expression = new syntax.IdentifierExprBuilder { name = "name" } + }, + new syntax.StringNewlineBuilder {} + ) + }) == #""" + """ + hi \(name) + """ + + """# + } + + ["round-trip: multi-line string with interpolation"] { + let (parsed = syntax.parse(#""" + x = """ + hello \(name) + world + """ + """#) as syntax.ModuleNode) + let (rebuilt = parsed.toBuilder().build()) + syntax.format(rebuilt.node) == #""" + x = + """ + hello \(name) + world + """ + + """# + } + + ["string literal with interpolation"] { + formatExpr(new syntax.StringLiteralBuilder { + parts = List( + new syntax.StringCharsBuilder { value = "hi " }, + new syntax.StringInterpolationBuilder { + expression = new syntax.IdentifierExprBuilder { name = "name" } + } + ) + }) == #""hi \(name)"\#n"# + } + + ["string literal with escape"] { + formatExpr(new syntax.StringLiteralBuilder { + parts = List( + new syntax.StringCharsBuilder { value = "line1" }, + new syntax.StringEscapeBuilder { value = "\\n" }, + new syntax.StringCharsBuilder { value = "line2" } + ) + }) == #""line1\nline2"\#n"# + } + + ["round-trip: single-line string with interpolation"] { + let (parsed = syntax.parse(#"x = "hi \(name)""#) as syntax.ModuleNode) + let (rebuilt = parsed.toBuilder().build()) + syntax.format(rebuilt.node) == #"x = "hi \(name)"\#n"# + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/builders.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/builders.pcf new file mode 100644 index 000000000..7c2f4d818 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/builders.pcf @@ -0,0 +1,244 @@ +examples { + ["int literal"] { + true + true + } + ["float literal"] { + true + } + ["bool literal"] { + true + true + } + ["null literal"] { + true + } + ["this expr"] { + true + } + ["outer expr"] { + true + } + ["module expr"] { + true + } + ["identifier expr"] { + true + } + ["string literal"] { + true + } + ["unary minus"] { + true + } + ["logical not"] { + true + } + ["non-null"] { + true + } + ["throw"] { + true + } + ["trace"] { + true + } + ["parenthesized"] { + true + } + ["binary op"] { + true + true + } + ["is expr"] { + true + } + ["if expr"] { + true + } + ["import expr"] { + true + } + ["read expr"] { + true + } + ["let expr"] { + true + true + } + ["function literal"] { + true + true + true + } + ["function call"] { + true + true + } + ["qualified access"] { + true + true + true + } + ["subscript"] { + true + } + ["unknown type"] { + true + } + ["nothing type"] { + true + } + ["module type"] { + true + } + ["declared type"] { + true + true + true + } + ["nullable type"] { + true + } + ["union type"] { + true + true + } + ["function type"] { + true + true + true + } + ["constrained type"] { + true + } + ["parenthesized type"] { + true + } + ["string constant type"] { + true + } + ["empty body"] { + true + } + ["object element"] { + true + } + ["object spread"] { + true + true + } + ["object property"] { + true + true + true + true + } + ["object method"] { + true + true + } + ["object entry"] { + true + true + } + ["member predicate"] { + true + } + ["for generator"] { + true + true + } + ["when generator"] { + true + true + } + ["body with parameters"] { + true + } + ["new expr"] { + true + true + } + ["amends expr"] { + true + } + ["doc comment"] { + true + } + ["annotation"] { + true + true + } + ["import"] { + true + true + true + } + ["class property (top-level)"] { + true + true + } + ["class method (top-level)"] { + true + true + } + ["typealias"] { + true + true + } + ["class"] { + true + true + } + ["module declaration"] { + true + true + true + } + ["module"] { + true + } + ["round-trip: simple property"] { + true + } + ["round-trip: typed property"] { + true + } + ["round-trip: if expression"] { + true + } + ["round-trip: function call"] { + true + } + ["round-trip: qualified access"] { + true + } + ["round-trip: union type"] { + true + } + ["round-trip: class with body"] { + true + } + ["round-trip: amend modifies value"] { + true + } + ["multi-line string"] { + true + } + ["multi-line string with interpolation"] { + true + } + ["round-trip: multi-line string with interpolation"] { + true + } + ["string literal with interpolation"] { + true + } + ["string literal with escape"] { + true + } + ["round-trip: single-line string with interpolation"] { + true + } +} diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 62c252b41..88c75f169 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -15,6 +15,7 @@ //===----------------------------------------------------------------------===// /// Utilities for managing Pkl source code +@ModuleInfo { minPklVersion = "0.32.0" } module pkl.syntax /// Parse the string as a Pkl module, returning either a typed AST node or an error. @@ -412,13 +413,23 @@ abstract class SyntaxNode { } /// Base class for expression nodes. -abstract class Expr extends SyntaxNode {} +abstract class Expr extends SyntaxNode { + /// Convert this expression node to its builder. + function toBuilder(): ExprBuilder = throw("toBuilder() not implemented for \(this.getClass())") +} /// Base class for type nodes. -abstract class TypeNode extends SyntaxNode {} +abstract class TypeNode extends SyntaxNode { + /// Convert this type node to its builder. + function toBuilder(): TypeBuilder = throw("toBuilder() not implemented for \(this.getClass())") +} /// Base class for object member nodes. -abstract class ObjectMemberNode extends SyntaxNode {} +abstract class ObjectMemberNode extends SyntaxNode { + /// Convert this object member node to its builder. + function toBuilder(): ObjectMemberBuilder = + throw("toBuilder() not implemented for \(this.getClass())") +} /// The top-level module node. class ModuleNode extends SyntaxNode { @@ -452,6 +463,17 @@ class ModuleNode extends SyntaxNode { /// All top-level methods in this module. methods: List = findChildren(node, "class_method").map((n) -> new ClassMethodNode { node = n }) + + function toBuilder(): ModuleBuilder = + let (self = this) + new ModuleBuilder { + declaration = self.declaration?.toBuilder() + imports = self.imports.map((i) -> i.toBuilder()) + classes = self.classes.map((c) -> c.toBuilder()) + typeAliases = self.typeAliases.map((t) -> t.toBuilder()) + properties = self.properties.map((p) -> p.toBuilder()) + methods = self.methods.map((m) -> m.toBuilder()) + } } /// A module declaration (including doc comment, annotations, modifiers, name, amends/extends). @@ -507,6 +529,21 @@ class ModuleDeclarationNode extends SyntaxNode { null else new ExtendsClauseNode { node = n } + + function toBuilder(): ModuleDeclarationBuilder = + let (self = this) + new ModuleDeclarationBuilder { + docComment = self.docComment?.toBuilder() + annotations = self.annotations.map((a) -> a.toBuilder()) + modifiers = self.modifiers?.modifiers ?? List() + name = + if (self.name == null) + null + else + self.name.identifiers.map((i) -> i.value).join(".") + amendsUri = self.amendsClause?.uri + extendsUri = self.extendsClause?.uri + } } /// An `amends "..."` clause. @@ -540,6 +577,14 @@ class ImportNode extends SyntaxNode { null else new IdentifierNode { node = id } + + function toBuilder(): ImportBuilder = + let (self = this) + new ImportBuilder { + uri = self.uri + isGlob = self.isGlob + alias = self.alias?.value + } } /// A class declaration. @@ -598,6 +643,18 @@ class ClassNode extends SyntaxNode { null else new ClassBodyNode { node = n } + + function toBuilder(): ClassBuilder = + let (self = this) + new ClassBuilder { + docComment = self.docComment?.toBuilder() + annotations = self.annotations.map((a) -> a.toBuilder()) + modifiers = self.modifiers?.modifiers ?? List() + name = self.name.value + typeParameters = self.typeParameterList?.typeParameters?.map((t) -> t.toBuilder()) ?? List() + extendsType = self.extendsClause?.toBuilder() + body = self.body?.toBuilder() + } } /// A typealias declaration. @@ -642,6 +699,17 @@ class TypeAliasNode extends SyntaxNode { let (body = findChild(node, "typealias_body")) let (t = findTypeChild(body!!)) wrapTypeNode(t!!) + + function toBuilder(): TypeAliasBuilder = + let (self = this) + new TypeAliasBuilder { + docComment = self.docComment?.toBuilder() + annotations = self.annotations.map((a) -> a.toBuilder()) + modifiers = self.modifiers?.modifiers ?? List() + name = self.name.value + typeParameters = self.typeParameterList?.typeParameters?.map((t) -> t.toBuilder()) ?? List() + type = self.type.toBuilder() + } } /// A class body delimited by braces. @@ -661,6 +729,13 @@ class ClassBodyNode extends SyntaxNode { List() else findChildren(elements, "class_method").map((n) -> new ClassMethodNode { node = n }) + + function toBuilder(): ClassBodyBuilder = + let (self = this) + new ClassBodyBuilder { + properties = self.properties.map((p) -> p.toBuilder()) + methods = self.methods.map((m) -> m.toBuilder()) + } } /// A class property declaration. @@ -716,6 +791,18 @@ class ClassPropertyNode extends SyntaxNode { /// Object bodies for amending (from `{ ... }` blocks). objectBodies: List = findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) + + function toBuilder(): ClassPropertyBuilder = + let (self = this) + new ClassPropertyBuilder { + docComment = self.docComment?.toBuilder() + annotations = self.annotations.map((a) -> a.toBuilder()) + modifiers = self.modifiers?.modifiers ?? List() + name = self.name.value + typeAnnotation = self.typeAnnotation?.type?.toBuilder() + value = self.value?.toBuilder() + objectBodies = self.objectBodies.map((b) -> b.toBuilder()) + } } /// A class method declaration. @@ -779,6 +866,19 @@ class ClassMethodNode extends SyntaxNode { null else wrapExpr(e) + + function toBuilder(): ClassMethodBuilder = + let (self = this) + new ClassMethodBuilder { + docComment = self.docComment?.toBuilder() + annotations = self.annotations.map((a) -> a.toBuilder()) + modifiers = self.modifiers?.modifiers ?? List() + name = self.name.value + typeParameters = self.typeParameterList?.typeParameters?.map((t) -> t.toBuilder()) ?? List() + parameters = self.parameterList.parameters.map((p) -> p.toBuilder()) + returnType = self.returnType?.type?.toBuilder() + body = self.body?.toBuilder() + } } /// An object body delimited by braces. @@ -829,6 +929,13 @@ class ObjectBodyNode extends SyntaxNode { List() else findChildren(memberList, "object_entry").map((n) -> new ObjectEntryNode { node = n }) + + function toBuilder(): ObjectBodyBuilder = + let (self = this) + new ObjectBodyBuilder { + parameters = self.parameters.map((p) -> p.toBuilder()) + members = self.members.map((m) -> m.toBuilder()) + } } /// An object property declaration. @@ -872,6 +979,16 @@ class ObjectPropertyNode extends ObjectMemberNode { /// Object bodies for amending. objectBodies: List = findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) + + function toBuilder(): ObjectPropertyBuilder = + let (self = this) + new ObjectPropertyBuilder { + modifiers = self.modifiers?.modifiers ?? List() + name = self.name.value + typeAnnotation = self.typeAnnotation?.type?.toBuilder() + value = self.value?.toBuilder() + objectBodies = self.objectBodies.map((b) -> b.toBuilder()) + } } /// An object method declaration. @@ -923,12 +1040,27 @@ class ObjectMethodNode extends ObjectMemberNode { null else wrapExpr(e) + + function toBuilder(): ObjectMethodBuilder = + let (self = this) + new ObjectMethodBuilder { + modifiers = self.modifiers?.modifiers ?? List() + name = self.name.value + typeParameters = self.typeParameterList?.typeParameters?.map((t) -> t.toBuilder()) ?? List() + parameters = self.parameterList.parameters.map((p) -> p.toBuilder()) + returnType = self.returnType?.type?.toBuilder() + body = self.body!!.toBuilder() + } } /// An object element (a positional expression in an object body). class ObjectElementNode extends ObjectMemberNode { /// The expression value. expression: Expr = wrapExpr(findExprChild(node)!!) + + function toBuilder(): ObjectElementBuilder = + let (self = this) + new ObjectElementBuilder { expression = self.expression.toBuilder() } } /// An object entry (`[key] = value` or `[key] { ... }`). @@ -949,6 +1081,14 @@ class ObjectEntryNode extends ObjectMemberNode { /// Object bodies for amending. objectBodies: List = findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) + + function toBuilder(): ObjectEntryBuilder = + let (self = this) + new ObjectEntryBuilder { + key = self.key.toBuilder() + value = self.value?.toBuilder() + objectBodies = self.objectBodies.map((b) -> b.toBuilder()) + } } /// An object spread (`...expr` or `...?expr`). @@ -958,6 +1098,13 @@ class ObjectSpreadNode extends ObjectMemberNode { /// The spread expression. expression: Expr = wrapExpr(findExprChild(node)!!) + + function toBuilder(): ObjectSpreadBuilder = + let (self = this) + new ObjectSpreadBuilder { + expression = self.expression.toBuilder() + isNullable = self.isNullable + } } /// A member predicate (`[[condition]] = value` or `[[condition]] { ... }`). @@ -977,6 +1124,14 @@ class MemberPredicateNode extends ObjectMemberNode { /// Object bodies for amending. objectBodies: List = findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) + + function toBuilder(): MemberPredicateBuilder = + let (self = this) + new MemberPredicateBuilder { + condition = self.condition.toBuilder() + value = self.value?.toBuilder() + objectBodies = self.objectBodies.map((b) -> b.toBuilder()) + } } /// A `for (param in iterable) { ... }` generator. @@ -1005,6 +1160,15 @@ class ForGeneratorNode extends ObjectMemberNode { body: ObjectBodyNode = let (n = findChild(node, "object_body")) new ObjectBodyNode { node = n!! } + + function toBuilder(): ForGeneratorBuilder = + let (self = this) + new ForGeneratorBuilder { + keyParameter = self.keyParameter?.toBuilder() + valueParameter = self.valueParameter.toBuilder() + iterable = self.iterable.toBuilder() + body = self.body.toBuilder() + } } /// A `when (condition) { ... }` generator. @@ -1026,48 +1190,88 @@ class WhenGeneratorNode extends ObjectMemberNode { null else new ObjectBodyNode { node = bodyNodes[1] } + + function toBuilder(): WhenGeneratorBuilder = + let (self = this) + new WhenGeneratorBuilder { + condition = self.condition.toBuilder() + thenBody = self.thenBody.toBuilder() + elseBody = self.elseBody?.toBuilder() + } } /// The `this` expression. -class ThisExprNode extends Expr {} +class ThisExprNode extends Expr { + function toBuilder(): ThisExprBuilder = new ThisExprBuilder {} +} /// The `outer` expression. -class OuterExprNode extends Expr {} +class OuterExprNode extends Expr { + function toBuilder(): OuterExprBuilder = new OuterExprBuilder {} +} /// The `module` expression. -class ModuleExprNode extends Expr {} +class ModuleExprNode extends Expr { + function toBuilder(): ModuleExprBuilder = new ModuleExprBuilder {} +} /// A `null` literal expression. -class NullLiteralExprNode extends Expr {} +class NullLiteralExprNode extends Expr { + function toBuilder(): NullLiteralBuilder = new NullLiteralBuilder {} +} /// A boolean literal expression (`true` or `false`). class BoolLiteralExprNode extends Expr { /// The boolean value. value: Boolean = node.text == "true" + + function toBuilder(): BoolLiteralBuilder = + let (self = this) + new BoolLiteralBuilder { value = self.value } } /// An integer literal expression. class IntLiteralExprNode extends Expr { /// The raw text of the integer literal. text: String = node.text ?? "" + + function toBuilder(): IntLiteralBuilder = + let (self = this) + new IntLiteralBuilder { value = self.text } } /// A float literal expression. class FloatLiteralExprNode extends Expr { /// The raw text of the float literal. text: String = node.text ?? "" + + function toBuilder(): FloatLiteralBuilder = + let (self = this) + new FloatLiteralBuilder { value = self.text } } /// A single-line string literal expression. class SingleLineStringLiteralExprNode extends Expr { /// The string parts (chars, escapes, interpolations). parts: List = buildStringParts(children) + + function toBuilder(): StringLiteralBuilder = + let (self = this) + new StringLiteralBuilder { + parts = self.parts.map((p) -> p.toBuilder()).toList() + } } /// A multi-line string literal expression. class MultiLineStringLiteralExprNode extends Expr { /// The string parts (chars, escapes, interpolations). parts: List = buildStringParts(children) + + function toBuilder(): MultiLineStringLiteralBuilder = + let (self = this) + new MultiLineStringLiteralBuilder { + parts = self.parts.map((p) -> p.toBuilder()).toList() + } } /// An unqualified access expression (`name` or `name(args)`). @@ -1084,6 +1288,16 @@ class UnqualifiedAccessExprNode extends Expr { null else new ArgumentListNode { node = n } + + function toBuilder(): ExprBuilder = + let (self = this) + if (self.argumentList == null) + new IdentifierExprBuilder { name = self.identifier.value } + else + new FunctionCallBuilder { + name = self.identifier.value + arguments = self.argumentList.arguments.map((a) -> a.toBuilder()).toList() + } } /// A qualified access expression (`receiver.member` or `receiver?.member`). @@ -1100,6 +1314,15 @@ class QualifiedAccessExprNode extends Expr { member: UnqualifiedAccessExprNode = let (n = findChildren(node, "unqualified_access_expr").last) new UnqualifiedAccessExprNode { node = n } + + function toBuilder(): QualifiedAccessBuilder = + let (self = this) + new QualifiedAccessBuilder { + receiver = self.receiver.toBuilder() + member = self.member.identifier.value + isNullSafe = self.isNullSafe + arguments = self.member.argumentList?.arguments?.map((a) -> a.toBuilder()) + } } /// A subscript expression (`receiver[index]`). @@ -1113,6 +1336,13 @@ class SubscriptExprNode extends Expr { index: Expr = let (exprs = findExprChildren(node)) wrapExpr(exprs[1]) + + function toBuilder(): SubscriptBuilder = + let (self = this) + new SubscriptBuilder { + receiver = self.receiver.toBuilder() + index = self.index.toBuilder() + } } /// A `super.member` access expression. @@ -1143,6 +1373,14 @@ class IfExprNode extends Expr { let (elseNode = findChild(node, "if_else_expr")) let (e = findExprChild(elseNode!!)) wrapExpr(e!!) + + function toBuilder(): IfExprBuilder = + let (self = this) + new IfExprBuilder { + condition = self.condition.toBuilder() + thenExpr = self.thenExpr.toBuilder() + elseExpr = self.elseExpr.toBuilder() + } } /// A `let (param = value) body` expression. @@ -1162,18 +1400,35 @@ class LetExprNode extends Expr { /// The body expression. bodyExpr: Expr = wrapExpr(findExprChild(node)!!) + + function toBuilder(): LetExprBuilder = + let (self = this) + new LetExprBuilder { + parameterName = if (self.parameter.isWildcard) "_" else self.parameter.identifier!!.value + parameterType = self.parameter.typeAnnotation?.type?.toBuilder() + bindingValue = self.bindingValue.toBuilder() + body = self.bodyExpr.toBuilder() + } } /// A `throw(expr)` expression. class ThrowExprNode extends Expr { /// The expression being thrown. expression: Expr = wrapExpr(findExprChild(node)!!) + + function toBuilder(): ThrowExprBuilder = + let (self = this) + new ThrowExprBuilder { expression = self.expression.toBuilder() } } /// A `trace(expr)` expression. class TraceExprNode extends Expr { /// The expression being traced. expression: Expr = wrapExpr(findExprChild(node)!!) + + function toBuilder(): TraceExprBuilder = + let (self = this) + new TraceExprBuilder { expression = self.expression.toBuilder() } } /// An `import("uri")` or `import*("uri")` expression. @@ -1183,15 +1438,30 @@ class ImportExprNode extends Expr { /// The import URI string. uri: String = getStringChars(node) + + function toBuilder(): ImportExprBuilder = + let (self = this) + new ImportExprBuilder { + uri = self.uri + isGlob = self.isGlob + } } /// A `read(expr)`, `read*(expr)`, or `read?(expr)` expression. class ReadExprNode extends Expr { /// The keyword used (`"read"`, `"read?"`, or `"read*"`). - keyword: String = terminals.firstOrNull?.text ?? "read" + keyword: "read" | "read?" | "read*" = + (terminals.firstOrNull?.text ?? "read") as "read" | "read?" | "read*" // The expression to be read expr: Expr = wrapExpr(findExprChild(node)!!) + + function toBuilder(): ReadExprBuilder = + let (self = this) + new ReadExprBuilder { + expression = self.expr.toBuilder() + keyword = self.keyword + } } /// A `new Type { ... }` expression. @@ -1210,6 +1480,13 @@ class NewExprNode extends Expr { body: ObjectBodyNode = let (n = findChild(node, "object_body")) new ObjectBodyNode { node = n!! } + + function toBuilder(): NewExprBuilder = + let (self = this) + new NewExprBuilder { + type = self.type?.toBuilder() + body = self.body.toBuilder() + } } /// An `(expr) { ... }` amends expression. @@ -1223,6 +1500,13 @@ class AmendsExprNode extends Expr { body: ObjectBodyNode = let (n = findChild(node, "object_body")) new ObjectBodyNode { node = n!! } + + function toBuilder(): AmendsExprBuilder = + let (self = this) + new AmendsExprBuilder { + parentExpr = self.parentExpr.toBuilder() + body = self.body.toBuilder() + } } /// A binary operator expression (`left op right`). @@ -1249,24 +1533,55 @@ class BinaryOpExprNode extends Expr { null else wrapTypeNode(t) + + function toBuilder(): ExprBuilder = + let (self = this) + if (self.operator == "is") + new IsExprBuilder { + operand = self.leftExpr.toBuilder() + type = self.rightType!!.toBuilder() + } + else if (self.operator == "as") + new AsExprBuilder { + operand = self.leftExpr.toBuilder() + type = self.rightType!!.toBuilder() + } + else + new BinaryOpExprBuilder { + left = self.leftExpr.toBuilder() + operator = self.operator + right = self.rightExpr!!.toBuilder() + } } /// A unary minus expression (`-expr`). class UnaryMinusExprNode extends Expr { /// The operand expression. operand: Expr = wrapExpr(findExprChild(node)!!) + + function toBuilder(): UnaryMinusExprBuilder = + let (self = this) + new UnaryMinusExprBuilder { operand = self.operand.toBuilder() } } /// A logical not expression (`!expr`). class LogicalNotExprNode extends Expr { /// The operand expression. operand: Expr = wrapExpr(findExprChild(node)!!) + + function toBuilder(): LogicalNotExprBuilder = + let (self = this) + new LogicalNotExprBuilder { operand = self.operand.toBuilder() } } /// A non-null assertion expression (`expr!!`). class NonNullExprNode extends Expr { /// The operand expression. operand: Expr = wrapExpr(findExprChild(node)!!) + + function toBuilder(): NonNullExprBuilder = + let (self = this) + new NonNullExprBuilder { operand = self.operand.toBuilder() } } /// A function literal expression (`(params) -> body`). @@ -1281,6 +1596,13 @@ class FunctionLiteralExprNode extends Expr { let (bodyNode = findChild(node, "function_literal_body")) let (e = findExprChild(bodyNode!!)) wrapExpr(e!!) + + function toBuilder(): FunctionLiteralBuilder = + let (self = this) + new FunctionLiteralBuilder { + parameters = self.parameterList.parameters.map((p) -> p.toBuilder()).toList() + body = self.body.toBuilder() + } } /// A parenthesized expression (`(expr)`). @@ -1296,16 +1618,26 @@ class ParenthesizedExprNode extends Expr { null else wrapExpr(e) + + function toBuilder(): ParenthesizedExprBuilder = + let (self = this) + new ParenthesizedExprBuilder { expression = self.expression!!.toBuilder() } } /// The `unknown` type. -class UnknownTypeNode extends TypeNode {} +class UnknownTypeNode extends TypeNode { + function toBuilder(): UnknownTypeBuilder = new UnknownTypeBuilder {} +} /// The `nothing` type. -class NothingTypeNode extends TypeNode {} +class NothingTypeNode extends TypeNode { + function toBuilder(): NothingTypeBuilder = new NothingTypeBuilder {} +} /// The `module` type. -class ModuleTypeNode extends TypeNode {} +class ModuleTypeNode extends TypeNode { + function toBuilder(): ModuleTypeBuilder = new ModuleTypeBuilder {} +} /// A declared type (e.g., `String`, `List`). class DeclaredTypeNode extends TypeNode { @@ -1321,18 +1653,33 @@ class DeclaredTypeNode extends TypeNode { null else new TypeArgumentListNode { node = n } + + function toBuilder(): DeclaredTypeBuilder = + let (self = this) + new DeclaredTypeBuilder { + name = self.name.identifiers.map((i) -> i.value).join(".") + typeArguments = self.typeArgumentList?.typeArguments?.map((t) -> t.toBuilder()) ?? List() + } } /// A nullable type (`Type?`). class NullableTypeNode extends TypeNode { /// The base type. baseType: TypeNode = wrapTypeNode(findTypeChild(node)!!) + + function toBuilder(): NullableTypeBuilder = + let (self = this) + new NullableTypeBuilder { baseType = self.baseType.toBuilder() } } /// A union type (`TypeA|TypeB|TypeC`). class UnionTypeNode extends TypeNode { /// The member types. members: List = findTypeChildren(node).map((n) -> wrapTypeNode(n)) + + function toBuilder(): UnionTypeBuilder = + let (self = this) + new UnionTypeBuilder { members = self.members.map((m) -> m.toBuilder()) } } /// A function type (`(ParamTypes) -> ReturnType`). @@ -1351,6 +1698,13 @@ class FunctionTypeNode extends TypeNode { returnType: TypeNode = let (types = findTypeChildren(node)) wrapTypeNode(types.last) + + function toBuilder(): FunctionTypeBuilder = + let (self = this) + new FunctionTypeBuilder { + parameterTypes = self.parameterTypes.map((t) -> t.toBuilder()) + returnType = self.returnType.toBuilder() + } } /// A constrained type (`Type(constraint)`). @@ -1363,6 +1717,13 @@ class ConstrainedTypeNode extends TypeNode { /// The constraint expressions. constraints: List = findExprChildren(constraintElems).map((n) -> wrapExpr(n)) + + function toBuilder(): ConstrainedTypeBuilder = + let (self = this) + new ConstrainedTypeBuilder { + baseType = self.baseType.toBuilder() + constraints = self.constraints.map((c) -> c.toBuilder()) + } } /// A parenthesized type (`(Type)`). @@ -1378,12 +1739,20 @@ class ParenthesizedTypeNode extends TypeNode { null else wrapTypeNode(t) + + function toBuilder(): ParenthesizedTypeBuilder = + let (self = this) + new ParenthesizedTypeBuilder { type = self.type!!.toBuilder() } } /// A string constant type (e.g., `"foo"`). class StringConstantTypeNode extends TypeNode { /// The string value. value: String = getStringChars(node) + + function toBuilder(): StringConstantTypeBuilder = + let (self = this) + new StringConstantTypeBuilder { value = self.value } } /// An annotation (`@Type { ... }`). @@ -1398,6 +1767,13 @@ class AnnotationNode extends SyntaxNode { null else new ObjectBodyNode { node = n } + + function toBuilder(): AnnotationBuilder = + let (self = this) + new AnnotationBuilder { + type = self.type.toBuilder() + body = self.body?.toBuilder() + } } /// A parameter declaration. @@ -1422,6 +1798,13 @@ class ParameterNode extends SyntaxNode { null else new TypeAnnotationNode { node = n } + + function toBuilder(): ParameterBuilder = + let (self = this) + new ParameterBuilder { + name = if (self.isWildcard) "_" else self.identifier!!.value + typeAnnotation = self.typeAnnotation?.type?.toBuilder() + } } /// A parameter list (`(param1, param2)`). @@ -1457,12 +1840,20 @@ class TypeAnnotationNode extends SyntaxNode { /// A type parameter declaration. class TypeParameterNode extends SyntaxNode { /// The variance modifier (`"in"`, `"out"`, or null). - variance: String? = terminals.findOrNull((t) -> t.text == "in" || t.text == "out")?.text + variance: ("in" | "out")? = + terminals.findOrNull((t) -> t.text == "in" || t.text == "out")?.text as ("in" | "out")? /// The type parameter name. name: IdentifierNode = let (n = findChild(node, "identifier")) new IdentifierNode { node = n!! } + + function toBuilder(): TypeParameterBuilder = + let (self = this) + new TypeParameterBuilder { + name = self.name.value + variance = self.variance + } } /// A type parameter list (``). @@ -1493,6 +1884,7 @@ class TypeArgumentListNode extends SyntaxNode { class IdentifierNode extends SyntaxNode { /// The identifier text. value: String = node.text ?? "" + // this node doesn't have a toBuilder because parent nodes use strings directly } /// A qualified identifier (`a.b.c`). @@ -1500,12 +1892,19 @@ class QualifiedIdentifierNode extends SyntaxNode { /// The identifiers in this qualified name. identifiers: List = findChildren(node, "identifier").map((n) -> new IdentifierNode { node = n }) + // this node doesn't have a toBuilder because parent nodes use strings directly } /// A doc comment. class DocCommentNode extends SyntaxNode { /// The text of each doc comment line. lines: List = findChildren(node, "doc_comment_line").map((n) -> n.text ?? "") + + function toBuilder(): DocCommentBuilder = + let (self = this) + new DocCommentBuilder { + lines = self.lines.map((l) -> if (l.startsWith("///")) l.drop(3) else l) + } } /// A modifier list (e.g., `open`, `abstract external`). @@ -1515,27 +1914,45 @@ class ModifierListNode extends SyntaxNode { } /// Base class for parts of a string literal. -abstract class StringPartNode extends SyntaxNode {} +abstract class StringPartNode extends SyntaxNode { + /// Convert this string part to its builder. + function toBuilder(): StringPartBuilder = + throw("toBuilder() not implemented for \(this.getClass())") +} /// A plain text part of a string literal. class StringCharsNode extends StringPartNode { /// The text content. value: String = node.text ?? "" + + function toBuilder(): StringCharsBuilder = + let (self = this) + new StringCharsBuilder { value = self.value } } /// An escape sequence in a string literal. class StringEscapeNode extends StringPartNode { /// The escape sequence text. value: String = node.text ?? "" + + function toBuilder(): StringEscapeBuilder = + let (self = this) + new StringEscapeBuilder { value = self.value } } /// A newline in a multi-line string literal. -class StringNewlineNode extends StringPartNode {} +class StringNewlineNode extends StringPartNode { + function toBuilder(): StringNewlineBuilder = new StringNewlineBuilder {} +} /// An interpolation in a string literal. class StringInterpolationNode extends StringPartNode { /// The interpolated expression. expression: Expr = wrapExpr(node) + + function toBuilder(): StringInterpolationBuilder = + let (self = this) + new StringInterpolationBuilder { expression = self.expression.toBuilder() } } /// Build string parts from the children of a string literal node. @@ -1604,3 +2021,1816 @@ local const function findNextNonAffix(cs: List, startIdx: Int): Int = findNextNonAffix(cs, startIdx + 1) else startIdx + +// =============== +// Builders +// =============== + +local const terminal: Node = new Node { type = "terminal" } + +local const identifierLeaf: Node = new Node { type = "identifier" } + +local const operatorLeaf: Node = new Node { type = "operator" } + +local const commaTerminal: Node = new Node { type = "terminal"; text = "," } + +// Interleave nodes with commas, producing `[a, ",", b, ",", c]` for `[a, b, c]`. +local const function commaSeparate(items: List): List = + items.fold(List(), (acc: List, item: Node) -> + if (acc.isEmpty) List(item) else acc.add(commaTerminal).add(item) + ) + +// Build a `modifier_list` node from a list of modifier strings. +local const function modifierListNode(mods: List): Node = new Node { + type = "modifier_list" + children = mods.map((m) -> new Node { type = "modifier"; text = m }) +} + +// Build a `qualified_identifier` node from a dotted name like `"a.b.c"`. +local const function qualifiedIdentifierNode(qname: String): Node = new Node { + type = "qualified_identifier" + children = + qname + .split(".") + .fold(List(), (acc: List, part: String) -> + if (acc.isEmpty) + List((identifierLeaf) { text = part }) + else + acc.add((terminal) { text = "." }).add((identifierLeaf) { text = part }) + ) +} + +/// Base class for all syntax builders. +abstract class Builder { + /// Affix nodes (comments, semicolons) to prepend before this node. + prefixes: List + + /// Affix nodes (comments, semicolons) to append after this node. + suffixes: List + + /// Build the typed syntax node. + abstract function build(): SyntaxNode +} + +/// Base class for expression builders. +abstract class ExprBuilder extends Builder { + abstract function build(): Expr +} + +/// Base class for type builders. +abstract class TypeBuilder extends Builder { + abstract function build(): TypeNode +} + +/// Base class for object member builders. +abstract class ObjectMemberBuilder extends Builder { + abstract function build(): ObjectMemberNode +} + +/// Builds an integer literal expression. +class IntLiteralBuilder extends ExprBuilder { + value: Int | String + + function build(): IntLiteralExprNode = new IntLiteralExprNode { + node = new Node { type = "int_literal_expr"; text = value.toString() } + } +} + +/// Builds a float literal expression. +class FloatLiteralBuilder extends ExprBuilder { + value: Float | String + + function build(): FloatLiteralExprNode = new FloatLiteralExprNode { + node = new Node { type = "float_literal_expr"; text = value.toString() } + } +} + +/// Builds a boolean literal expression. +class BoolLiteralBuilder extends ExprBuilder { + value: Boolean + + function build(): BoolLiteralExprNode = new BoolLiteralExprNode { + node = new Node { type = "bool_literal_expr"; text = if (value) "true" else "false" } + } +} + +/// Builds a `null` literal expression. +class NullLiteralBuilder extends ExprBuilder { + function build(): NullLiteralExprNode = new NullLiteralExprNode { + node = new Node { type = "null_expr"; text = "null" } + } +} + +/// Builds a `this` expression. +class ThisExprBuilder extends ExprBuilder { + function build(): ThisExprNode = new ThisExprNode { + node = new Node { type = "this_expr"; text = "this" } + } +} + +/// Builds an `outer` expression. +class OuterExprBuilder extends ExprBuilder { + function build(): OuterExprNode = new OuterExprNode { + node = new Node { type = "outer_expr"; text = "outer" } + } +} + +/// Builds a `module` expression. +class ModuleExprBuilder extends ExprBuilder { + function build(): ModuleExprNode = new ModuleExprNode { + node = new Node { type = "module_expr"; text = "module" } + } +} + +/// Builds an unqualified access expression (identifier or function call). +class IdentifierExprBuilder extends ExprBuilder { + name: String + + function build(): UnqualifiedAccessExprNode = new UnqualifiedAccessExprNode { + node = new Node { + type = "unqualified_access_expr" + children = List((identifierLeaf) { text = name }) + } + } +} + +/// Builds a single-line string literal expression. +/// +/// Set `value` for plain text, or `parts` for strings that contain escapes or interpolations. +/// `parts` defaults to a single chars node for the value, so amending only `value` is the simple path. +class StringLiteralBuilder extends ExprBuilder { + /// Convenience for plain-text strings. Sets `parts` to a single [StringCharsBuilder]. + value: String? + + /// String parts (chars, escapes, interpolations). Defaults to wrapping `value`, or empty. + parts: List = + let (self = this) + if (value != null) + List(new StringCharsBuilder { value = self.value!! }) + else + List() + + function build(): SingleLineStringLiteralExprNode = + let (self = this) + new SingleLineStringLiteralExprNode { + node = new Node { + type = "single_line_string_literal_expr" + children = + List((terminal) { text = "\"" }) + + self.parts.flatMap((p) -> p.buildNodes()) + + List((terminal) { text = "\"" }) + } + } +} + +/// Base class for parts of string literal (text, escapes, newlines, interpolations). +abstract class StringPartBuilder { + /// Build the list of `Node` parts. + abstract function buildNodes(): List +} + +/// Plain text content within a string literal. +class StringCharsBuilder extends StringPartBuilder { + value: String + + function buildNodes(): List = List(new Node { type = "string_chars"; text = value }) +} + +/// An escape sequence in a string literal (e.g., `"\\n"`, `"\\t"`). +class StringEscapeBuilder extends StringPartBuilder { + /// The escape sequence text including the leading backslash (e.g., `"\\n"`). + value: String + + function buildNodes(): List = List(new Node { type = "string_escape"; text = value }) +} + +/// A newline in a multi-line string literal. +class StringNewlineBuilder extends StringPartBuilder { + function buildNodes(): List = List(new Node { type = "string_newline" }) +} + +/// An interpolation in a string literal (`\(expr)`). +class StringInterpolationBuilder extends StringPartBuilder { + expression: ExprBuilder + + function buildNodes(): List = + let (self = this) + List( + (terminal) { text = "\\(" }, + self.expression.build().node, + (terminal) { text = ")" }, + ) +} + +/// Builds a multi-line string literal expression. +/// +/// Use [StringNewlineBuilder] entries in `parts` to separate lines. +class MultiLineStringLiteralBuilder extends ExprBuilder { + parts: List + + function build(): MultiLineStringLiteralExprNode = + let (self = this) + new MultiLineStringLiteralExprNode { + node = new Node { + type = "multi_line_string_literal_expr" + children = + List((terminal) { text = "\"\"\"" }) + + self.parts.flatMap((p) -> p.buildNodes()).toList() + + List(new Node { + type = "terminal" + text = "\"\"\"" + // formatter uses span.colStart of the closing `"""` to determine the + // indentation to strip from each content line. + span = new Span { colStart = 1 } + }) + } + } +} + +/// Builds a unary minus expression (`-expr`). +class UnaryMinusExprBuilder extends ExprBuilder { + operand: ExprBuilder + + function build(): UnaryMinusExprNode = new UnaryMinusExprNode { + node = new Node { + type = "unary_minus_expr" + children = + List( + (terminal) { text = "-" }, + operand.build().node, + ) + } + } +} + +/// Builds a logical not expression (`!expr`). +class LogicalNotExprBuilder extends ExprBuilder { + operand: ExprBuilder + + function build(): LogicalNotExprNode = new LogicalNotExprNode { + node = new Node { + type = "logical_not_expr" + children = + List( + (terminal) { text = "!" }, + operand.build().node, + ) + } + } +} + +/// Builds a non-null assertion expression (`expr!!`). +class NonNullExprBuilder extends ExprBuilder { + operand: ExprBuilder + + function build(): NonNullExprNode = new NonNullExprNode { + node = new Node { + type = "non_null_expr" + children = List(operand.build().node, (operatorLeaf) { text = "!!" }) + } + } +} + +/// Builds a `throw(expr)` expression. +class ThrowExprBuilder extends ExprBuilder { + expression: ExprBuilder + + function build(): ThrowExprNode = new ThrowExprNode { + node = new Node { + type = "throw_expr" + children = + List( + (terminal) { text = "throw" }, + (terminal) { text = "(" }, + expression.build().node, + (terminal) { text = ")" }, + ) + } + } +} + +/// Builds a `trace(expr)` expression. +class TraceExprBuilder extends ExprBuilder { + expression: ExprBuilder + + function build(): TraceExprNode = new TraceExprNode { + node = new Node { + type = "trace_expr" + children = + List( + (terminal) { text = "trace" }, + (terminal) { text = "(" }, + expression.build().node, + (terminal) { text = ")" }, + ) + } + } +} + +/// Builds a parenthesized expression (`(expr)`). +class ParenthesizedExprBuilder extends ExprBuilder { + expression: ExprBuilder + + function build(): ParenthesizedExprNode = new ParenthesizedExprNode { + node = new Node { + type = "parenthesized_expr" + children = + List( + (terminal) { text = "(" }, + new Node { + type = "parenthesized_expr_elements" + children = List(expression.build().node) + }, + (terminal) { text = ")" }, + ) + } + } +} + +/// Builds a binary operator expression (`left op right`). +class BinaryOpExprBuilder extends ExprBuilder { + left: ExprBuilder + operator: String + right: ExprBuilder + + function build(): BinaryOpExprNode = new BinaryOpExprNode { + node = new Node { + type = "binary_op_expr" + children = + List( + left.build().node, + (operatorLeaf) { text = operator }, + right.build().node, + ) + } + } +} + +/// Builds an `expr is Type` expression. +class IsExprBuilder extends ExprBuilder { + operand: ExprBuilder + type: TypeBuilder + + function build(): BinaryOpExprNode = + let (self = this) + new BinaryOpExprNode { + node = new Node { + type = "binary_op_expr" + children = + List( + self.operand.build().node, + (operatorLeaf) { text = "is" }, + self.type.build().node, + ) + } + } +} + +/// Builds an `expr as Type` expression. +class AsExprBuilder extends ExprBuilder { + operand: ExprBuilder + type: TypeBuilder + + function build(): BinaryOpExprNode = + let (self = this) + new BinaryOpExprNode { + node = new Node { + type = "binary_op_expr" + children = + List( + self.operand.build().node, + (operatorLeaf) { text = "as" }, + self.type.build().node, + ) + } + } +} + +/// Builds an `if (condition) thenExpr else elseExpr` expression. +class IfExprBuilder extends ExprBuilder { + condition: ExprBuilder + thenExpr: ExprBuilder + elseExpr: ExprBuilder + + function build(): IfExprNode = new IfExprNode { + node = new Node { + type = "if_expr" + children = + List( + new Node { + type = "if_header" + children = + List( + (terminal) { text = "if" }, + new Node { + type = "if_condition" + children = + List( + (terminal) { text = "(" }, + new Node { + type = "if_condition_expr" + children = List(condition.build().node) + }, + (terminal) { text = ")" }, + ) + }, + ) + }, + new Node { + type = "if_then_expr" + children = List(thenExpr.build().node) + }, + (terminal) { text = "else" }, + new Node { + type = "if_else_expr" + children = List(elseExpr.build().node) + }, + ) + } + } +} + +/// Builds an `import("uri")` or `import*("uri")` expression. +class ImportExprBuilder extends ExprBuilder { + uri: String + isGlob: Boolean = false + + function build(): ImportExprNode = new ImportExprNode { + node = new Node { + type = "import_expr" + children = + List( + (terminal) { text = if (isGlob) "import*" else "import" }, + (terminal) { text = "(" }, + new Node { + type = "string_chars" + children = + List( + (terminal) { text = "\"" }, + (terminal) { text = uri }, + (terminal) { text = "\"" }, + ) + }, + (terminal) { text = ")" }, + ) + } + } +} + +/// Builds a `read(expr)`, `read?(expr)`, or `read*(expr)` expression. +class ReadExprBuilder extends ExprBuilder { + expression: ExprBuilder + keyword: "read" | "read?" | "read*" = "read" + + function build(): ReadExprNode = new ReadExprNode { + node = new Node { + type = "read_expr" + children = + List( + (terminal) { text = keyword }, + (terminal) { text = "(" }, + expression.build().node, + (terminal) { text = ")" }, + ) + } + } +} + +/// Builds a declared type (e.g., `String`, `List`). +class DeclaredTypeBuilder extends TypeBuilder { + name: String + typeArguments: List + + function build(): DeclaredTypeNode = new DeclaredTypeNode { + node = new Node { + type = "declared_type" + children = + if (typeArguments.isEmpty) + List(qualifiedIdentifierNode(name)) + else + List(qualifiedIdentifierNode(name), new Node { + type = "type_argument_list" + children = + List( + (terminal) { text = "<" }, + new Node { + type = "type_argument_list_elements" + children = commaSeparate(typeArguments.map((t) -> t.build().node).toList()) + }, + (terminal) { text = ">" }, + ) + }) + } + } +} + +/// Builds a parameter declaration (`name`, `name: Type`, or `_`). +class ParameterBuilder extends Builder { + /// The parameter name. Use "_" for a wildcard parameter. + name: String + + /// The optional type annotation. + typeAnnotation: TypeBuilder? + + function build(): ParameterNode = + let (self = this) + new ParameterNode { + node = new Node { + type = "parameter" + children = + if (self.name == "_") + List((terminal) { text = "_" }) + else if (self.typeAnnotation == null) + List((identifierLeaf) { text = self.name }) + else + List( + (identifierLeaf) { text = self.name }, + new Node { + type = "type_annotation" + children = + List( + (terminal) { text = ":" }, + self.typeAnnotation.build().node, + ) + }, + ) + } + } +} + +/// Builds a `let (param = value) body` expression. +class LetExprBuilder extends ExprBuilder { + parameterName: String + parameterType: TypeBuilder? + bindingValue: ExprBuilder + body: ExprBuilder + + function build(): LetExprNode = + let (self = this) + new LetExprNode { + node = new Node { + type = "let_expr" + children = + List( + (terminal) { text = "let" }, + new Node { + type = "let_parameter_definition" + children = + List( + (terminal) { text = "(" }, + new Node { + type = "let_parameter" + children = + List( + ( + new ParameterBuilder { + name = self.parameterName + typeAnnotation = self.parameterType + } + ) + .build() + .node, + (terminal) { text = "=" }, + self.bindingValue.build().node, + ) + }, + (terminal) { text = ")" }, + ) + }, + self.body.build().node, + ) + } + } +} + +/// Builds a function literal expression (`(params) -> body`). +class FunctionLiteralBuilder extends ExprBuilder { + parameters: List + body: ExprBuilder + + function build(): FunctionLiteralExprNode = + let (self = this) + new FunctionLiteralExprNode { + node = new Node { + type = "function_literal_expr" + children = + List( + new Node { + type = "parameter_list" + children = + if (self.parameters.isEmpty) + List((terminal) { text = "(" }, (terminal) { text = ")" }) + else + List( + (terminal) { text = "(" }, + new Node { + type = "parameter_list_elements" + children = commaSeparate(self.parameters.map((p) -> p.build().node)) + }, + (terminal) { text = ")" }, + ) + }, + (terminal) { text = "->" }, + new Node { + type = "function_literal_body" + children = List(self.body.build().node) + }, + ) + } + } +} + +/// Builds an unqualified function call expression (`name(args)`). +class FunctionCallBuilder extends ExprBuilder { + name: String + arguments: List + + function build(): UnqualifiedAccessExprNode = + let (self = this) + new UnqualifiedAccessExprNode { + node = new Node { + type = "unqualified_access_expr" + children = + List( + (identifierLeaf) { text = self.name }, + new Node { + type = "argument_list" + children = + if (self.arguments.isEmpty) + List((terminal) { text = "(" }, (terminal) { text = ")" }) + else + List( + (terminal) { text = "(" }, + new Node { + type = "argument_list_elements" + children = commaSeparate(self.arguments.map((a) -> a.build().node)) + }, + (terminal) { text = ")" }, + ) + }, + ) + } + } +} + +/// Builds a qualified access expression (`receiver.member` or `receiver?.member`, +/// optionally with arguments for method calls). +class QualifiedAccessBuilder extends ExprBuilder { + receiver: ExprBuilder + member: String + isNullSafe: Boolean = false + /// If non-null, this is a method call with these arguments. If null, this is a property access. + arguments: List? + + function build(): QualifiedAccessExprNode = + let (self = this) + new QualifiedAccessExprNode { + node = new Node { + type = "qualified_access_expr" + children = + List( + self.receiver.build().node, + (operatorLeaf) { text = if (self.isNullSafe) "?." else "." }, + new Node { + type = "unqualified_access_expr" + children = + if (self.arguments == null) + List((identifierLeaf) { text = self.member }) + else + List( + (identifierLeaf) { text = self.member }, + new Node { + type = "argument_list" + children = + if (self.arguments.isEmpty) + List((terminal) { text = "(" }, (terminal) { text = ")" }) + else + List( + (terminal) { text = "(" }, + new Node { + type = "argument_list_elements" + children = commaSeparate(self.arguments.map((a) -> a.build().node)) + }, + (terminal) { text = ")" }, + ) + }, + ) + }, + ) + } + } +} + +/// Builds a subscript expression (`receiver[index]`). +class SubscriptBuilder extends ExprBuilder { + receiver: ExprBuilder + index: ExprBuilder + + function build(): SubscriptExprNode = + let (self = this) + new SubscriptExprNode { + node = new Node { + type = "subscript_expr" + children = + List( + self.receiver.build().node, + (operatorLeaf) { text = "[" }, + self.index.build().node, + (terminal) { text = "]" }, + ) + } + } +} + +/// Builds the `unknown` type. +class UnknownTypeBuilder extends TypeBuilder { + function build(): UnknownTypeNode = new UnknownTypeNode { + node = new Node { type = "unknown_type"; text = "unknown" } + } +} + +/// Builds the `nothing` type. +class NothingTypeBuilder extends TypeBuilder { + function build(): NothingTypeNode = new NothingTypeNode { + node = new Node { type = "nothing_type"; text = "nothing" } + } +} + +/// Builds the `module` type. +class ModuleTypeBuilder extends TypeBuilder { + function build(): ModuleTypeNode = new ModuleTypeNode { + node = new Node { type = "module_type"; text = "module" } + } +} + +/// Builds a nullable type (`Type?`). +class NullableTypeBuilder extends TypeBuilder { + baseType: TypeBuilder + + function build(): NullableTypeNode = new NullableTypeNode { + node = new Node { + type = "nullable_type" + children = List(baseType.build().node, (terminal) { text = "?" }) + } + } +} + +/// Builds a union type (`A | B | C`). +class UnionTypeBuilder extends TypeBuilder { + members: List + + function build(): UnionTypeNode = + let (self = this) + new UnionTypeNode { + node = new Node { + type = "union_type" + children = + self.members + .map((m) -> m.build().node) + .fold(List(), (acc: List, item: Node) -> + if (acc.isEmpty) List(item) else acc.add((terminal) { text = "|" }).add(item) + ) + } + } +} + +/// Builds a function type (`(ParamTypes) -> ReturnType`). +class FunctionTypeBuilder extends TypeBuilder { + parameterTypes: List + returnType: TypeBuilder + + function build(): FunctionTypeNode = + let (self = this) + new FunctionTypeNode { + node = new Node { + type = "function_type" + children = + List( + new Node { + type = "function_type_parameters" + children = + if (self.parameterTypes.isEmpty) + List((terminal) { text = "(" }, (terminal) { text = ")" }) + else + List( + (terminal) { text = "(" }, + new Node { + type = "parenthesized_type_elements" + children = + commaSeparate(self.parameterTypes.map((t) -> t.build().node).toList()) + }, + (terminal) { text = ")" }, + ) + }, + (terminal) { text = "->" }, + self.returnType.build().node, + ) + } + } +} + +/// Builds a constrained type (`Type(constraint1, constraint2)`). +class ConstrainedTypeBuilder extends TypeBuilder { + baseType: TypeBuilder + constraints: List + + function build(): ConstrainedTypeNode = + let (self = this) + new ConstrainedTypeNode { + node = new Node { + type = "constrained_type" + children = + List(self.baseType.build().node, new Node { + type = "constrained_type_constraint" + children = + List( + (terminal) { text = "(" }, + new Node { + type = "constrained_type_elements" + children = commaSeparate(self.constraints.map((c) -> c.build().node)) + }, + (terminal) { text = ")" }, + ) + }) + } + } +} + +/// Builds a parenthesized type (`(Type)`). +class ParenthesizedTypeBuilder extends TypeBuilder { + type: TypeBuilder + + function build(): ParenthesizedTypeNode = + let (self = this) + new ParenthesizedTypeNode { + node = new Node { + type = "parenthesized_type" + children = + List( + (terminal) { text = "(" }, + new Node { + type = "parenthesized_type_elements" + children = List(self.type.build().node) + }, + (terminal) { text = ")" }, + ) + } + } +} + +/// Builds a string constant type (e.g., `"foo"`). +class StringConstantTypeBuilder extends TypeBuilder { + value: String + + function build(): StringConstantTypeNode = new StringConstantTypeNode { + node = new Node { + type = "string_constant_type" + children = + List(new Node { + type = "string_chars" + children = + List( + (terminal) { text = "\"" }, + (terminal) { text = value }, + (terminal) { text = "\"" }, + ) + }) + } + } +} + +/// Builds a type parameter declaration (`T`, `in T`, or `out T`). +class TypeParameterBuilder extends Builder { + name: String + variance: ("in" | "out")? + + function build(): TypeParameterNode = + let (self = this) + new TypeParameterNode { + node = new Node { + type = "type_parameter" + children = + if (self.variance == null) + List((identifierLeaf) { text = self.name }) + else + List( + (terminal) { text = self.variance }, + (identifierLeaf) { text = self.name }, + ) + } + } +} + +/// Builds an object body delimited by braces. +class ObjectBodyBuilder extends Builder { + parameters: List + members: List + + function build(): ObjectBodyNode = + let (self = this) + new ObjectBodyNode { + node = new Node { + type = "object_body" + children = + List((terminal) { text = "{" }) + + ( + if (self.parameters.isEmpty) + List() + else + List(new Node { + type = "object_parameter_list" + children = + commaSeparate(self.parameters.map((p) -> p.build().node)) + .add((terminal) { text = "->" }) + }) + ) + + ( + if (self.members.isEmpty) + List() + else + List(new Node { + type = "object_member_list" + children = self.members.map((m) -> m.build().node) + }) + ) + + List((terminal) { text = "}" }) + } + } +} + +/// Builds an object element. +class ObjectElementBuilder extends ObjectMemberBuilder { + expression: ExprBuilder + + function build(): ObjectElementNode = + let (self = this) + new ObjectElementNode { + node = new Node { + type = "object_element" + children = List(self.expression.build().node) + } + } +} + +/// Builds an object spread (`...expr` or `...?expr`). +class ObjectSpreadBuilder extends ObjectMemberBuilder { + expression: ExprBuilder + isNullable: Boolean = false + + function build(): ObjectSpreadNode = + let (self = this) + new ObjectSpreadNode { + node = new Node { + type = "object_spread" + children = + List( + (terminal) { text = if (self.isNullable) "...?" else "..." }, + self.expression.build().node, + ) + } + } +} + +/// Builds an object property declaration. +class ObjectPropertyBuilder extends ObjectMemberBuilder { + modifiers: List + name: String + typeAnnotation: TypeBuilder? + value: ExprBuilder? + /// Object bodies for amending. Used when there is no `=` value. + objectBodies: List + + function build(): ObjectPropertyNode = + let (self = this) + new ObjectPropertyNode { + node = new Node { + type = "object_property" + children = + List(new Node { + type = "object_property_header" + children = + List(new Node { + type = "object_property_header_begin" + children = + ( + if (self.modifiers.isEmpty) + List() + else + List(modifierListNode(self.modifiers)) + ) + + List((identifierLeaf) { text = self.name }) + }) + + ( + if (self.typeAnnotation == null) + List() + else + List(new Node { + type = "type_annotation" + children = + List( + (terminal) { text = ":" }, + self.typeAnnotation.build().node, + ) + }) + ) + }) + + ( + if (self.value != null) + List( + (terminal) { text = "=" }, + new Node { + type = "object_property_body" + children = List(self.value.build().node) + }, + ) + else + self.objectBodies.map((b) -> b.build().node) + ) + } + } +} + +/// Builds an object method declaration. +class ObjectMethodBuilder extends ObjectMemberBuilder { + modifiers: List + name: String + typeParameters: List + parameters: List + returnType: TypeBuilder? + body: ExprBuilder + + function build(): ObjectMethodNode = + let (self = this) + new ObjectMethodNode { + node = new Node { + type = "object_method" + children = + List(new Node { + type = "class_method_header" + children = + ( + if (self.modifiers.isEmpty) + List() + else + List(modifierListNode(self.modifiers)) + ) + + List( + (terminal) { text = "function" }, + (identifierLeaf) { text = self.name }, + ) + }) + + ( + if (self.typeParameters.isEmpty) + List() + else + List(new Node { + type = "type_parameter_list" + children = + List( + (terminal) { text = "<" }, + new Node { + type = "type_parameter_list_elements" + children = commaSeparate(self.typeParameters.map((t) -> t.build().node)) + }, + (terminal) { text = ">" }, + ) + }) + ) + + List(new Node { + type = "parameter_list" + children = + if (self.parameters.isEmpty) + List((terminal) { text = "(" }, (terminal) { text = ")" }) + else + List( + (terminal) { text = "(" }, + new Node { + type = "parameter_list_elements" + children = commaSeparate(self.parameters.map((p) -> p.build().node)) + }, + (terminal) { text = ")" }, + ) + }) + + ( + if (self.returnType == null) + List() + else + List(new Node { + type = "type_annotation" + children = + List( + (terminal) { text = ":" }, + self.returnType.build().node, + ) + }) + ) + + List( + (terminal) { text = "=" }, + new Node { + type = "class_method_body" + children = List(self.body.build().node) + }, + ) + } + } +} + +/// Builds an object entry (`[key] = value` or `[key] { ... }`). +class ObjectEntryBuilder extends ObjectMemberBuilder { + key: ExprBuilder + value: ExprBuilder? + objectBodies: List + + function build(): ObjectEntryNode = + let (self = this) + new ObjectEntryNode { + node = new Node { + type = "object_entry" + children = + List(new Node { + type = "object_entry_header" + children = + List( + (terminal) { text = "[" }, + self.key.build().node, + (terminal) { text = "]" }, + ) + + (if (self.value != null) List((terminal) { text = "=" }) else List()) + }) + + ( + if (self.value != null) + List(self.value.build().node) + else + self.objectBodies.map((b) -> b.build().node) + ) + } + } +} + +/// Builds a member predicate (`[[condition]] = value` or `[[condition]] { ... }`). +class MemberPredicateBuilder extends ObjectMemberBuilder { + condition: ExprBuilder + value: ExprBuilder? + objectBodies: List + + function build(): MemberPredicateNode = + let (self = this) + new MemberPredicateNode { + node = new Node { + type = "member_predicate" + children = + List( + (terminal) { text = "[[" }, + self.condition.build().node, + (terminal) { text = "]" }, + (terminal) { text = "]" }, + ) + + ( + if (self.value != null) + List( + (terminal) { text = "=" }, + self.value.build().node, + ) + else + self.objectBodies.map((b) -> b.build().node) + ) + } + } +} + +/// Builds a `for (param in iterable) { ... }` generator. +class ForGeneratorBuilder extends ObjectMemberBuilder { + /// The optional key parameter (when iterating with both key and value). + keyParameter: ParameterBuilder? + valueParameter: ParameterBuilder + iterable: ExprBuilder + body: ObjectBodyBuilder + + function build(): ForGeneratorNode = + let (self = this) + new ForGeneratorNode { + node = new Node { + type = "for_generator" + children = + List( + (terminal) { text = "for" }, + new Node { + type = "for_generator_header" + children = + List( + (terminal) { text = "(" }, + new Node { + type = "for_generator_header_definition" + children = + List( + new Node { + type = "for_generator_header_definition_header" + children = + ( + if (self.keyParameter == null) + List(self.valueParameter.build().node) + else + List( + self.keyParameter.build().node, + (terminal) { text = "," }, + self.valueParameter.build().node, + ) + ) + + List((terminal) { text = "in" }) + }, + self.iterable.build().node, + ) + }, + (terminal) { text = ")" }, + ) + }, + self.body.build().node, + ) + } + } +} + +/// Builds a `when (condition) { ... } else { ... }` generator. +class WhenGeneratorBuilder extends ObjectMemberBuilder { + condition: ExprBuilder + thenBody: ObjectBodyBuilder + elseBody: ObjectBodyBuilder? + + function build(): WhenGeneratorNode = + let (self = this) + new WhenGeneratorNode { + node = new Node { + type = "when_generator" + children = + List( + (terminal) { text = "when" }, + new Node { + type = "when_generator_header" + children = + List( + (terminal) { text = "(" }, + self.condition.build().node, + (terminal) { text = ")" }, + ) + }, + self.thenBody.build().node, + ) + + ( + if (self.elseBody == null) + List() + else + List( + (terminal) { text = "else" }, + self.elseBody.build().node, + ) + ) + } + } +} + +/// Builds a `new Type { ... }` expression. +class NewExprBuilder extends ExprBuilder { + /// The optional type. If null, this is `new { ... }`. + type: TypeBuilder? + body: ObjectBodyBuilder + + function build(): NewExprNode = + let (self = this) + new NewExprNode { + node = new Node { + type = "new_expr" + children = + List( + new Node { + type = "new_header" + children = + if (self.type == null) + List((terminal) { text = "new" }) + else + List((terminal) { text = "new" }, self.type.build().node) + }, + self.body.build().node, + ) + } + } +} + +/// Builds an amends expression (`(expr) { ... }`). +class AmendsExprBuilder extends ExprBuilder { + parentExpr: ExprBuilder + body: ObjectBodyBuilder + + function build(): AmendsExprNode = + let (self = this) + new AmendsExprNode { + node = new Node { + type = "amends_expr" + children = + List( + self.parentExpr.build().node, + self.body.build().node, + ) + } + } +} + +/// Builds a doc comment. +class DocCommentBuilder extends Builder { + /// The body text of each line, without the leading `///`. + lines: List + + function build(): DocCommentNode = + let (self = this) + new DocCommentNode { + node = new Node { + type = "doc_comment" + children = self.lines.map((l) -> new Node { type = "doc_comment_line"; text = "///" + l }) + } + } +} + +/// Builds an annotation (`@Type` or `@Type { ... }`). +class AnnotationBuilder extends Builder { + type: TypeBuilder + body: ObjectBodyBuilder? + + function build(): AnnotationNode = + let (self = this) + new AnnotationNode { + node = new Node { + type = "annotation" + children = + List((terminal) { text = "@" }, self.type.build().node) + + (if (self.body == null) List() else List(self.body.build().node)) + } + } +} + +/// Builds an import declaration. +class ImportBuilder extends Builder { + uri: String + isGlob: Boolean = false + alias: String? + + function build(): ImportNode = + let (self = this) + new ImportNode { + node = new Node { + type = "import" + children = + List( + (terminal) { text = if (self.isGlob) "import*" else "import" }, + new Node { + type = "string_chars" + children = + List( + (terminal) { text = "\"" }, + (terminal) { text = self.uri }, + (terminal) { text = "\"" }, + ) + }, + ) + + ( + if (self.alias == null) + List() + else + List(new Node { + type = "import_alias" + children = + List( + (terminal) { text = "as" }, + (identifierLeaf) { text = self.alias }, + ) + }) + ) + } + } +} + +/// Builds a class body delimited by braces. +class ClassBodyBuilder extends Builder { + properties: List + methods: List + + function build(): ClassBodyNode = + let (self = this) + new ClassBodyNode { + node = new Node { + type = "class_body" + children = + List((terminal) { text = "{" }) + + ( + let ( + members = + self.properties.map((p) -> p.build().node) + + self.methods.map((m) -> m.build().node) + ) + if (members.isEmpty) + List() + else + List(new Node { type = "class_body_elements"; children = members }) + ) + + List((terminal) { text = "}" }) + } + } +} + +/// Builds a class property declaration. +class ClassPropertyBuilder extends Builder { + docComment: DocCommentBuilder? + annotations: List + modifiers: List + name: String + typeAnnotation: TypeBuilder? + value: ExprBuilder? + /// Object bodies for amending. Used when there is no `=` value. + objectBodies: List + + function build(): ClassPropertyNode = + let (self = this) + new ClassPropertyNode { + node = new Node { + type = "class_property" + children = + (if (self.docComment == null) List() else List(self.docComment.build().node)) + + self.annotations.map((a) -> a.build().node) + + List(new Node { + type = "class_property_header" + children = + List(new Node { + type = "class_property_header_begin" + children = + ( + if (self.modifiers.isEmpty) + List() + else + List(modifierListNode(self.modifiers)) + ) + + List((identifierLeaf) { text = self.name }) + }) + + ( + if (self.typeAnnotation == null) + List() + else + List(new Node { + type = "type_annotation" + children = + List( + (terminal) { text = ":" }, + self.typeAnnotation.build().node, + ) + }) + ) + }) + + ( + if (self.value != null) + List( + (terminal) { text = "=" }, + new Node { + type = "class_property_body" + children = List(self.value.build().node) + }, + ) + else + self.objectBodies.map((b) -> b.build().node) + ) + } + } +} + +/// Builds a class method declaration. +class ClassMethodBuilder extends Builder { + docComment: DocCommentBuilder? + annotations: List + modifiers: List + name: String + typeParameters: List + parameters: List + returnType: TypeBuilder? + /// The method body. Null for abstract methods. + body: ExprBuilder? + + function build(): ClassMethodNode = + let (self = this) + new ClassMethodNode { + node = new Node { + type = "class_method" + children = + (if (self.docComment == null) List() else List(self.docComment.build().node)) + + self.annotations.map((a) -> a.build().node) + + List(new Node { + type = "class_method_header" + children = + ( + if (self.modifiers.isEmpty) + List() + else + List(modifierListNode(self.modifiers)) + ) + + List( + (terminal) { text = "function" }, + (identifierLeaf) { text = self.name }, + ) + }) + + ( + if (self.typeParameters.isEmpty) + List() + else + List(new Node { + type = "type_parameter_list" + children = + List( + (terminal) { text = "<" }, + new Node { + type = "type_parameter_list_elements" + children = commaSeparate(self.typeParameters.map((t) -> t.build().node)) + }, + (terminal) { text = ">" }, + ) + }) + ) + + List(new Node { + type = "parameter_list" + children = + if (self.parameters.isEmpty) + List((terminal) { text = "(" }, (terminal) { text = ")" }) + else + List( + (terminal) { text = "(" }, + new Node { + type = "parameter_list_elements" + children = commaSeparate(self.parameters.map((p) -> p.build().node)) + }, + (terminal) { text = ")" }, + ) + }) + + ( + if (self.returnType == null) + List() + else + List(new Node { + type = "type_annotation" + children = + List( + (terminal) { text = ":" }, + self.returnType.build().node, + ) + }) + ) + + ( + if (self.body == null) + List() + else + List( + (terminal) { text = "=" }, + new Node { + type = "class_method_body" + children = List(self.body.build().node) + }, + ) + ) + } + } +} + +/// Builds a typealias declaration. +class TypeAliasBuilder extends Builder { + docComment: DocCommentBuilder? + annotations: List + modifiers: List + name: String + typeParameters: List + type: TypeBuilder + + function build(): TypeAliasNode = + let (self = this) + new TypeAliasNode { + node = new Node { + type = "typealias" + children = + (if (self.docComment == null) List() else List(self.docComment.build().node)) + + self.annotations.map((a) -> a.build().node) + + List(new Node { + type = "typealias_header" + children = + ( + if (self.modifiers.isEmpty) + List() + else + List(modifierListNode(self.modifiers)) + ) + + List( + (terminal) { text = "typealias" }, + (identifierLeaf) { text = self.name }, + ) + + ( + if (self.typeParameters.isEmpty) + List() + else + List(new Node { + type = "type_parameter_list" + children = + List( + (terminal) { text = "<" }, + new Node { + type = "type_parameter_list_elements" + children = + commaSeparate(self.typeParameters.map((t) -> t.build().node)) + }, + (terminal) { text = ">" }, + ) + }) + ) + + List((terminal) { text = "=" }) + }) + + List(new Node { + type = "typealias_body" + children = List(self.type.build().node) + }) + } + } +} + +/// Builds a class declaration. +class ClassBuilder extends Builder { + docComment: DocCommentBuilder? + annotations: List + modifiers: List + name: String + typeParameters: List + extendsType: TypeBuilder? + body: ClassBodyBuilder? + + function build(): ClassNode = + let (self = this) + new ClassNode { + node = new Node { + type = "class" + children = + (if (self.docComment == null) List() else List(self.docComment.build().node)) + + self.annotations.map((a) -> a.build().node) + + List(new Node { + type = "class_header" + children = + ( + if (self.modifiers.isEmpty) + List() + else + List(modifierListNode(self.modifiers)) + ) + + List( + (terminal) { text = "class" }, + (identifierLeaf) { text = self.name }, + ) + + ( + if (self.typeParameters.isEmpty) + List() + else + List(new Node { + type = "type_parameter_list" + children = + List( + (terminal) { text = "<" }, + new Node { + type = "type_parameter_list_elements" + children = + commaSeparate(self.typeParameters.map((t) -> t.build().node)) + }, + (terminal) { text = ">" }, + ) + }) + ) + + ( + if (self.extendsType == null) + List() + else + List(new Node { + type = "class_header_extends" + children = + List( + (terminal) { text = "extends" }, + self.extendsType.build().node, + ) + }) + ) + }) + + (if (self.body == null) List() else List(self.body.build().node)) + } + } +} + +/// Builds a module declaration. +class ModuleDeclarationBuilder extends Builder { + docComment: DocCommentBuilder? + annotations: List + modifiers: List + /// The qualified module name, if a `module` declaration is present. + name: String? + /// The URI string of the amended module, if any. Mutually exclusive with `extendsUri`. + amendsUri: String? + /// The URI string of the extended module, if any. Mutually exclusive with `amendsUri`. + extendsUri: String? + + function build(): ModuleDeclarationNode = + let (self = this) + new ModuleDeclarationNode { + node = new Node { + type = "module_declaration" + children = + (if (self.docComment == null) List() else List(self.docComment.build().node)) + + self.annotations.map((a) -> a.build().node) + + ( + if (self.name != null) + List(new Node { + type = "module_definition" + children = + ( + if (self.modifiers.isEmpty) + List() + else + List(modifierListNode(self.modifiers)) + ) + + List( + (terminal) { text = "module" }, + qualifiedIdentifierNode(self.name!!), + ) + }) + else if (!self.modifiers.isEmpty) + List(modifierListNode(self.modifiers)) + else + List() + ) + + ( + if (self.amendsUri != null) + List(new Node { + type = "amends_clause" + children = + List( + (terminal) { text = "amends" }, + new Node { + type = "string_chars" + children = + List( + (terminal) { text = "\"" }, + (terminal) { text = self.amendsUri }, + (terminal) { text = "\"" }, + ) + }, + ) + }) + else if (self.extendsUri != null) + List(new Node { + type = "extends_clause" + children = + List( + (terminal) { text = "extends" }, + new Node { + type = "string_chars" + children = + List( + (terminal) { text = "\"" }, + (terminal) { text = self.extendsUri }, + (terminal) { text = "\"" }, + ) + }, + ) + }) + else + List() + ) + } + } +} + +/// Builds a module. +class ModuleBuilder extends Builder { + declaration: ModuleDeclarationBuilder? + imports: List + classes: List + typeAliases: List + properties: List + methods: List + + function build(): ModuleNode = + let (self = this) + new ModuleNode { + node = new Node { + type = "module" + children = + (if (self.declaration == null) List() else List(self.declaration.build().node)) + + ( + if (self.imports.isEmpty) + List() + else + List(new Node { + type = "import_list" + children = self.imports.map((i) -> i.build().node) + }) + ) + + self.classes.map((c) -> c.build().node) + + self.typeAliases.map((t) -> t.build().node) + + self.properties.map((p) -> p.build().node) + + self.methods.map((m) -> m.build().node) + } + } +} From dcf48d86dd8458a1e4100e95f0795151a76c35b9 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Wed, 27 May 2026 17:48:59 +0200 Subject: [PATCH 04/49] Fix build --- .../pkl/core/stdlib/syntax/SyntaxNodes.java | 18 +++++++++++------- .../pkl/core/stdlib/syntax/package-info.java | 4 ++-- .../input/syntax/builders.pkl | 2 ++ 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java index 40db0e1fd..8a22503c7 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java @@ -18,6 +18,8 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Specialization; import java.util.ArrayList; +import java.util.Locale; +import org.jspecify.annotations.Nullable; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.SyntaxModule; import org.pkl.core.runtime.VmList; @@ -51,13 +53,15 @@ private SyntaxNodes() {} static final class NodeData { final Node node; final char[] source; - VmTyped parentVm; + @Nullable VmTyped parentVm; VmList childrenVm; VmTyped spanVm; - NodeData(Node node, char[] source) { + NodeData(Node node, char[] source, VmList childrenVm, VmTyped spanVm) { this.node = node; this.source = source; + this.childrenVm = childrenVm; + this.spanVm = spanVm; } } @@ -81,7 +85,7 @@ static final class ErrorData { private static final VmObjectFactory nodeFactory = new VmObjectFactory(SyntaxModule::getNodeClass) - .addStringProperty("type", nd -> nd.node.type.name().toLowerCase()) + .addStringProperty("type", nd -> nd.node.type.name().toLowerCase(Locale.ROOT)) .addListProperty("children", nd -> nd.childrenVm) .addProperty("parent", nd -> VmNull.lift(nd.parentVm)) .addProperty( @@ -118,9 +122,9 @@ private static VmTyped convertNode(Node genericNode, char[] sourceChars) { childrenList.add(convertNode(child, sourceChars)); } - var data = new NodeData(genericNode, sourceChars); - data.childrenVm = VmList.create(childrenList.toArray()); - data.spanVm = spanFactory.create(genericNode.span); + var childrenVm = VmList.create(childrenList.toArray()); + var spanVm = spanFactory.create(genericNode.span); + var data = new NodeData(genericNode, sourceChars, childrenVm, spanVm); var result = nodeFactory.create(data); @@ -145,7 +149,7 @@ protected String eval(VmTyped self, VmTyped nodeVm, String grammarVersion) { private static Node convertVmToNode(VmTyped nodeVm) { var typeStr = (String) VmUtils.readMember(nodeVm, TYPE_ID); - var nodeType = NodeType.valueOf(typeStr.toUpperCase()); + var nodeType = NodeType.valueOf(typeStr.toUpperCase(Locale.ROOT)); var childrenVm = (VmList) VmUtils.readMember(nodeVm, CHILDREN_ID); var children = new ArrayList(childrenVm.getLength()); diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/package-info.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/package-info.java index 6f12d5850..14f5426cc 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/package-info.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/package-info.java @@ -1,4 +1,4 @@ -@NonnullByDefault +@NullMarked package org.pkl.core.stdlib.syntax; -import org.pkl.core.util.NonnullByDefault; +import org.jspecify.annotations.NullMarked; diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/builders.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/builders.pkl index 095c12062..cd02ffec5 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/builders.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/builders.pkl @@ -664,7 +664,9 @@ examples { ) }) == #""" module my.config + import "pkl:json" + host: String = "localhost" port: Int = 8080 From e189aa8919aacb61b29242af8405b0e4dc69c1f3 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Mon, 13 Jul 2026 17:10:51 +0200 Subject: [PATCH 05/49] Join SyntaxNodes and Builders into one; add walker, visitor, and fold --- .../pkl/core/stdlib/syntax/SyntaxNodes.java | 144 +- .../input/syntax/builders.pkl | 818 ---- .../input/syntax/expressions.pkl | 27 +- .../input/syntax/moduleStructure.pkl | 88 +- .../input/syntax/objectMembers.pkl | 94 +- .../input/syntax/traversal.pkl | 102 + .../input/syntax/types.pkl | 19 +- .../input/syntax/walk.pkl | 119 + .../output/syntax/builders.pcf | 244 - .../output/syntax/expressions.pcf | 1 - .../output/syntax/moduleStructure.pcf | 16 - .../output/syntax/objectMembers.pcf | 6 - .../output/syntax/traversal.pcf | 37 + .../output/syntax/types.pcf | 3 - .../output/syntax/walk.pcf | 37 + stdlib/syntax.pkl | 3912 ++++++----------- 16 files changed, 1895 insertions(+), 3772 deletions(-) delete mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/syntax/builders.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl delete mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/syntax/builders.pcf create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/syntax/traversal.pcf create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java index 8a22503c7..bcfe8e2d7 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java @@ -18,12 +18,16 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Specialization; import java.util.ArrayList; +import java.util.List; import java.util.Locale; import org.jspecify.annotations.Nullable; +import org.pkl.core.ast.lambda.ApplyVmFunction1Node; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.SyntaxModule; +import org.pkl.core.runtime.VmFunction; import org.pkl.core.runtime.VmList; import org.pkl.core.runtime.VmNull; +import org.pkl.core.runtime.VmPair; import org.pkl.core.runtime.VmTyped; import org.pkl.core.runtime.VmUtils; import org.pkl.core.stdlib.ExternalMethod1Node; @@ -48,6 +52,7 @@ private SyntaxNodes() {} private static final Identifier LINE_END_ID = Identifier.get("lineEnd"); private static final Identifier COL_END_ID = Identifier.get("colEnd"); private static final char[] EMPTY_SOURCE = new char[0]; + private static final FullSpan ZERO_SPAN = new FullSpan(0, 0, 0, 0, 0, 0); /** Extra storage backing a Pkl {@code Node} instance. */ static final class NodeData { @@ -90,7 +95,10 @@ static final class ErrorData { .addProperty("parent", nd -> VmNull.lift(nd.parentVm)) .addProperty( "text", - nd -> nd.node.children.isEmpty() ? nd.node.text(nd.source) : VmNull.withoutDefault()) + nd -> + nd.node.children.isEmpty() || nd.node.type == NodeType.STRING_CHARS + ? nd.node.text(nd.source) + : VmNull.withoutDefault()) .addTypedProperty("span", nd -> nd.spanVm); private static final VmObjectFactory parserErrorFactory = @@ -122,6 +130,12 @@ private static VmTyped convertNode(Node genericNode, char[] sourceChars) { childrenList.add(convertNode(child, sourceChars)); } + // materialize text now so that nodes reused verbatim by `walk`/`format` are + // self-contained + if (genericNode.children.isEmpty() || genericNode.type == NodeType.STRING_CHARS) { + genericNode.text(sourceChars); + } + var childrenVm = VmList.create(childrenList.toArray()); var spanVm = spanFactory.create(genericNode.span); var data = new NodeData(genericNode, sourceChars, childrenVm, spanVm); @@ -142,46 +156,134 @@ public abstract static class formatToString extends ExternalMethod2Node { @Specialization @TruffleBoundary protected String eval(VmTyped self, VmTyped nodeVm, String grammarVersion) { - var node = convertVmToNode(nodeVm); + var node = convertVmToNode(nodeVm, ZERO_SPAN); return new Formatter(GrammarVersion.valueOf(grammarVersion)).format(node); } } - private static Node convertVmToNode(VmTyped nodeVm) { + public abstract static class walk extends ExternalMethod2Node { + @Child private ApplyVmFunction1Node applyVisit = ApplyVmFunction1Node.create(); + + @Specialization + @TruffleBoundary + protected VmTyped eval(VmTyped self, VmTyped node, VmFunction visit) { + var result = walkNode(node, visit); + // the root of the returned tree has no parent + if (result.hasExtraStorage()) { + ((NodeData) result.getExtraStorage()).parentVm = null; + } + return result; + } + + private VmTyped walkNode(VmTyped nodeVm, VmFunction visit) { + var visited = applyVisit.execute(visit, nodeVm); + + VmTyped node; + boolean descend; + if (visited instanceof VmPair pair) { + node = (VmTyped) pair.getFirst(); + descend = (Boolean) pair.getSecond(); + } else { + // `null`: leave this node unchanged and keep descending + node = nodeVm; + descend = true; + } + if (!descend) { + return node; + } + + var childrenVm = (VmList) VmUtils.readMember(node, CHILDREN_ID); + var length = childrenVm.getLength(); + if (length == 0) { + return node; + } + + var newChildren = new Object[length]; + var changed = false; + for (var i = 0; i < length; i++) { + var child = (VmTyped) childrenVm.get(i); + var newChild = walkNode(child, visit); + newChildren[i] = newChild; + changed |= newChild != child; + } + // reuse the node (and its extra storage) untouched when nothing below changed + return changed ? rebuild(node, newChildren) : node; + } + } + + /** Rebuild a node from {@code template} (its type, span, text) with new children. */ + private static VmTyped rebuild(VmTyped template, Object[] newChildrenVm) { + var nodeType = + NodeType.valueOf(((String) VmUtils.readMember(template, TYPE_ID)).toUpperCase(Locale.ROOT)); + var spanVm = (VmTyped) VmUtils.readMember(template, SPAN_ID); + var span = readSpan(spanVm); + + var childJavaNodes = new ArrayList(newChildrenVm.length); + for (var child : newChildrenVm) { + // constructed (storage-less) children have no meaningful span; anchor them to this + // node's span so the formatter's line-break heuristics stay consistent with reused + // siblings (which keep their original spans). + childJavaNodes.add(convertVmToNode((VmTyped) child, span)); + } + var javaNode = + makeJavaNode(nodeType, span, childJavaNodes, VmUtils.readMember(template, Identifier.TEXT)); + + var childrenVm = VmList.create(newChildrenVm); + var result = nodeFactory.create(new NodeData(javaNode, EMPTY_SOURCE, childrenVm, spanVm)); + + // wire up the parent back-reference + for (var child : newChildrenVm) { + var childVm = (VmTyped) child; + if (childVm.hasExtraStorage()) { + ((NodeData) childVm.getExtraStorage()).parentVm = result; + } + } + return result; + } + + /** + * Convert a Pkl node to a generic {@link Node}, reusing the parse-time node when present. + * + *

{@code fallbackSpan} is used for constructed nodes (and their descendants) that carry no + * meaningful span of their own, so that a subtree spliced into reused siblings lines up with + * them. + */ + private static Node convertVmToNode(VmTyped nodeVm, FullSpan fallbackSpan) { + // a node still carrying its parse-time storage is verbatim from `parse`: reuse it wholesale + if (nodeVm.hasExtraStorage()) { + return ((NodeData) nodeVm.getExtraStorage()).node; + } + var typeStr = (String) VmUtils.readMember(nodeVm, TYPE_ID); var nodeType = NodeType.valueOf(typeStr.toUpperCase(Locale.ROOT)); + var ownSpan = readSpan((VmTyped) VmUtils.readMember(nodeVm, SPAN_ID)); + // a constructed node that did not set its own span inherits the insertion point's span + var span = ownSpan.equals(ZERO_SPAN) ? fallbackSpan : ownSpan; + var childrenVm = (VmList) VmUtils.readMember(nodeVm, CHILDREN_ID); var children = new ArrayList(childrenVm.getLength()); for (var i = 0; i < childrenVm.getLength(); i++) { - children.add(convertVmToNode((VmTyped) childrenVm.get(i))); + children.add(convertVmToNode((VmTyped) childrenVm.get(i), span)); } - var spanVm = (VmTyped) VmUtils.readMember(nodeVm, SPAN_ID); + return makeJavaNode(nodeType, span, children, VmUtils.readMember(nodeVm, Identifier.TEXT)); + } + + private static FullSpan readSpan(VmTyped spanVm) { var lineStart = ((Long) VmUtils.readMember(spanVm, LINE_START_ID)).intValue(); var colStart = ((Long) VmUtils.readMember(spanVm, COL_START_ID)).intValue(); var lineEnd = ((Long) VmUtils.readMember(spanVm, LINE_END_ID)).intValue(); var colEnd = ((Long) VmUtils.readMember(spanVm, COL_END_ID)).intValue(); - var span = new FullSpan(0, 0, lineStart, colStart, lineEnd, colEnd); - - Node node; - if (children.isEmpty()) { - node = new Node(nodeType, span); - } else { - node = new Node(nodeType, span, children); - } + return new FullSpan(0, 0, lineStart, colStart, lineEnd, colEnd); + } - var textObj = VmUtils.readMember(nodeVm, Identifier.TEXT); + private static Node makeJavaNode( + NodeType nodeType, FullSpan span, List children, Object textObj) { + var node = children.isEmpty() ? new Node(nodeType, span) : new Node(nodeType, span, children); if (textObj instanceof String text) { node.setText(text); - } else if (nodeType == NodeType.STRING_CHARS) { - var sb = new StringBuilder(); - for (var child : children) { - sb.append(child.text(EMPTY_SOURCE)); - } - node.setText(sb.toString()); } - return node; } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/builders.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/builders.pkl deleted file mode 100644 index cd02ffec5..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/builders.pkl +++ /dev/null @@ -1,818 +0,0 @@ -amends "../snippetTest.pkl" - -import "pkl:syntax" - -local function formatExpr(builder: syntax.ExprBuilder): String = - syntax.format(builder.build().node) - -local function formatType(builder: syntax.TypeBuilder): String = - syntax.format(builder.build().node) - -local function formatBody(builder: syntax.ObjectBodyBuilder): String = - syntax.format(builder.build().node) - -local function formatModule(builder: syntax.ModuleBuilder): String = - syntax.format(builder.build().node) - -examples { - ["int literal"] { - formatExpr(new syntax.IntLiteralBuilder { value = 42 }) == "42\n" - formatExpr(new syntax.IntLiteralBuilder { value = "0xFF" }) == "0xFF\n" - } - - ["float literal"] { - formatExpr(new syntax.FloatLiteralBuilder { value = 3.14 }) == "3.14\n" - } - - ["bool literal"] { - formatExpr(new syntax.BoolLiteralBuilder { value = true }) == "true\n" - formatExpr(new syntax.BoolLiteralBuilder { value = false }) == "false\n" - } - - ["null literal"] { - formatExpr(new syntax.NullLiteralBuilder {}) == "null\n" - } - - ["this expr"] { - formatExpr(new syntax.ThisExprBuilder {}) == "this\n" - } - - ["outer expr"] { - formatExpr(new syntax.OuterExprBuilder {}) == "outer\n" - } - - ["module expr"] { - formatExpr(new syntax.ModuleExprBuilder {}) == "module\n" - } - - ["identifier expr"] { - formatExpr(new syntax.IdentifierExprBuilder { name = "foo" }) == "foo\n" - } - - ["string literal"] { - formatExpr(new syntax.StringLiteralBuilder { value = "hello" }) == #""hello"\#n"# - } - - ["unary minus"] { - formatExpr(new syntax.UnaryMinusExprBuilder { - operand = new syntax.IntLiteralBuilder { value = 5 } - }) == "-5\n" - } - - ["logical not"] { - formatExpr(new syntax.LogicalNotExprBuilder { - operand = new syntax.IdentifierExprBuilder { name = "flag" } - }) == "!flag\n" - } - - ["non-null"] { - formatExpr(new syntax.NonNullExprBuilder { - operand = new syntax.IdentifierExprBuilder { name = "x" } - }) == "x!!\n" - } - - ["throw"] { - formatExpr(new syntax.ThrowExprBuilder { - expression = new syntax.StringLiteralBuilder { value = "oops" } - }) == #"throw("oops")\#n"# - } - - ["trace"] { - formatExpr(new syntax.TraceExprBuilder { - expression = new syntax.IdentifierExprBuilder { name = "x" } - }) == "trace(x)\n" - } - - ["parenthesized"] { - formatExpr(new syntax.ParenthesizedExprBuilder { - expression = new syntax.IntLiteralBuilder { value = 1 } - }) == "(1)\n" - } - - ["binary op"] { - formatExpr(new syntax.BinaryOpExprBuilder { - left = new syntax.IdentifierExprBuilder { name = "x" } - operator = "+" - right = new syntax.IntLiteralBuilder { value = 1 } - }) == "x + 1\n" - formatExpr(new syntax.BinaryOpExprBuilder { - left = new syntax.IdentifierExprBuilder { name = "a" } - operator = "&&" - right = new syntax.IdentifierExprBuilder { name = "b" } - }) == "a && b\n" - } - - ["is expr"] { - formatExpr(new syntax.IsExprBuilder { - operand = new syntax.IdentifierExprBuilder { name = "x" } - type = new syntax.DeclaredTypeBuilder { name = "String" } - }) == "x is String\n" - } - - ["if expr"] { - formatExpr(new syntax.IfExprBuilder { - condition = new syntax.BinaryOpExprBuilder { - left = new syntax.IdentifierExprBuilder { name = "x" } - operator = ">" - right = new syntax.IntLiteralBuilder { value = 0 } - } - thenExpr = new syntax.StringLiteralBuilder { value = "positive" } - elseExpr = new syntax.StringLiteralBuilder { value = "non-positive" } - }) == #"if (x > 0) "positive" else "non-positive"\#n"# - } - - ["import expr"] { - formatExpr(new syntax.ImportExprBuilder { uri = "pkl:json" }) == #"import("pkl:json")\#n"# - } - - ["read expr"] { - formatExpr(new syntax.ReadExprBuilder { - expression = new syntax.StringLiteralBuilder { value = "file.txt" } - }) == #"read("file.txt")\#n"# - } - - ["let expr"] { - formatExpr(new syntax.LetExprBuilder { - parameterName = "x" - bindingValue = new syntax.IntLiteralBuilder { value = 1 } - body = new syntax.IdentifierExprBuilder { name = "x" } - }) == "let (x = 1) x\n" - formatExpr(new syntax.LetExprBuilder { - parameterName = "x" - parameterType = new syntax.DeclaredTypeBuilder { name = "Int" } - bindingValue = new syntax.IntLiteralBuilder { value = 1 } - body = new syntax.IdentifierExprBuilder { name = "x" } - }) == "let (x: Int = 1) x\n" - } - - ["function literal"] { - formatExpr(new syntax.FunctionLiteralBuilder { - parameters = List(new syntax.ParameterBuilder { name = "x" }) - body = new syntax.BinaryOpExprBuilder { - left = new syntax.IdentifierExprBuilder { name = "x" } - operator = "+" - right = new syntax.IntLiteralBuilder { value = 1 } - } - }) == "(x) -> x + 1\n" - formatExpr(new syntax.FunctionLiteralBuilder { - parameters = List() - body = new syntax.IntLiteralBuilder { value = 0 } - }) == "() -> 0\n" - formatExpr(new syntax.FunctionLiteralBuilder { - parameters = List( - new syntax.ParameterBuilder { name = "x"; typeAnnotation = new syntax.DeclaredTypeBuilder { name = "Int" } }, - new syntax.ParameterBuilder { name = "y" } - ) - body = new syntax.IdentifierExprBuilder { name = "x" } - }) == "(x: Int, y) -> x\n" - } - - ["function call"] { - formatExpr(new syntax.FunctionCallBuilder { - name = "f" - arguments = List() - }) == "f()\n" - formatExpr(new syntax.FunctionCallBuilder { - name = "max" - arguments = List( - new syntax.IntLiteralBuilder { value = 1 }, - new syntax.IntLiteralBuilder { value = 2 } - ) - }) == "max(1, 2)\n" - } - - ["qualified access"] { - formatExpr(new syntax.QualifiedAccessBuilder { - receiver = new syntax.IdentifierExprBuilder { name = "obj" } - member = "field" - }) == "obj.field\n" - formatExpr(new syntax.QualifiedAccessBuilder { - receiver = new syntax.IdentifierExprBuilder { name = "obj" } - member = "field" - isNullSafe = true - }) == "obj?.field\n" - formatExpr(new syntax.QualifiedAccessBuilder { - receiver = new syntax.IdentifierExprBuilder { name = "obj" } - member = "method" - arguments = List(new syntax.IntLiteralBuilder { value = 1 }) - }) == "obj.method(1)\n" - } - - ["subscript"] { - formatExpr(new syntax.SubscriptBuilder { - receiver = new syntax.IdentifierExprBuilder { name = "list" } - index = new syntax.IntLiteralBuilder { value = 0 } - }) == "list[0]\n" - } - - ["unknown type"] { - formatType(new syntax.UnknownTypeBuilder {}) == "unknown\n" - } - - ["nothing type"] { - formatType(new syntax.NothingTypeBuilder {}) == "nothing\n" - } - - ["module type"] { - formatType(new syntax.ModuleTypeBuilder {}) == "module\n" - } - - ["declared type"] { - formatType(new syntax.DeclaredTypeBuilder { name = "String" }) == "String\n" - formatType(new syntax.DeclaredTypeBuilder { - name = "List" - typeArguments = List(new syntax.DeclaredTypeBuilder { name = "Int" }) - }) == "List\n" - formatType(new syntax.DeclaredTypeBuilder { - name = "Map" - typeArguments = List( - new syntax.DeclaredTypeBuilder { name = "String" }, - new syntax.DeclaredTypeBuilder { name = "Int" } - ) - }) == "Map\n" - } - - ["nullable type"] { - formatType(new syntax.NullableTypeBuilder { - baseType = new syntax.DeclaredTypeBuilder { name = "String" } - }) == "String?\n" - } - - ["union type"] { - formatType(new syntax.UnionTypeBuilder { - members = List( - new syntax.DeclaredTypeBuilder { name = "Int" }, - new syntax.DeclaredTypeBuilder { name = "String" } - ) - }) == "Int | String\n" - formatType(new syntax.UnionTypeBuilder { - members = List( - new syntax.DeclaredTypeBuilder { name = "Int" }, - new syntax.DeclaredTypeBuilder { name = "String" }, - new syntax.DeclaredTypeBuilder { name = "Boolean" } - ) - }) == "Int | String | Boolean\n" - } - - ["function type"] { - formatType(new syntax.FunctionTypeBuilder { - parameterTypes = List(new syntax.DeclaredTypeBuilder { name = "Int" }) - returnType = new syntax.DeclaredTypeBuilder { name = "String" } - }) == "(Int) -> String\n" - formatType(new syntax.FunctionTypeBuilder { - parameterTypes = List() - returnType = new syntax.DeclaredTypeBuilder { name = "Int" } - }) == "() -> Int\n" - formatType(new syntax.FunctionTypeBuilder { - parameterTypes = List( - new syntax.DeclaredTypeBuilder { name = "Int" }, - new syntax.DeclaredTypeBuilder { name = "Int" } - ) - returnType = new syntax.DeclaredTypeBuilder { name = "Int" } - }) == "(Int, Int) -> Int\n" - } - - ["constrained type"] { - formatType(new syntax.ConstrainedTypeBuilder { - baseType = new syntax.DeclaredTypeBuilder { name = "Int" } - constraints = List( - new syntax.BinaryOpExprBuilder { - left = new syntax.IdentifierExprBuilder { name = "this" } - operator = ">" - right = new syntax.IntLiteralBuilder { value = 0 } - } - ) - }) == "Int(this > 0)\n" - } - - ["parenthesized type"] { - formatType(new syntax.ParenthesizedTypeBuilder { - type = new syntax.UnionTypeBuilder { - members = List( - new syntax.DeclaredTypeBuilder { name = "Int" }, - new syntax.DeclaredTypeBuilder { name = "String" } - ) - } - }) == "(Int | String)\n" - } - - ["string constant type"] { - formatType(new syntax.StringConstantTypeBuilder { value = "foo" }) == #""foo"\#n"# - } - - ["empty body"] { - formatBody(new syntax.ObjectBodyBuilder {}) == "{}\n" - } - - ["object element"] { - formatBody(new syntax.ObjectBodyBuilder { - members = List( - new syntax.ObjectElementBuilder { - expression = new syntax.IntLiteralBuilder { value = 1 } - } - ) - }) == "{ 1 }\n" - } - - ["object spread"] { - formatBody(new syntax.ObjectBodyBuilder { - members = List( - new syntax.ObjectSpreadBuilder { - expression = new syntax.IdentifierExprBuilder { name = "other" } - } - ) - }) == "{ ...other }\n" - formatBody(new syntax.ObjectBodyBuilder { - members = List( - new syntax.ObjectSpreadBuilder { - expression = new syntax.IdentifierExprBuilder { name = "maybe" } - isNullable = true - } - ) - }) == "{ ...?maybe }\n" - } - - ["object property"] { - formatBody(new syntax.ObjectBodyBuilder { - members = List( - new syntax.ObjectPropertyBuilder { - name = "x" - value = new syntax.IntLiteralBuilder { value = 1 } - } - ) - }) == "{ x = 1 }\n" - formatBody(new syntax.ObjectBodyBuilder { - members = List( - new syntax.ObjectPropertyBuilder { - name = "x" - typeAnnotation = new syntax.DeclaredTypeBuilder { name = "Int" } - value = new syntax.IntLiteralBuilder { value = 1 } - } - ) - }) == "{ x: Int = 1 }\n" - formatBody(new syntax.ObjectBodyBuilder { - members = List( - new syntax.ObjectPropertyBuilder { - modifiers = List("hidden") - name = "x" - value = new syntax.IntLiteralBuilder { value = 1 } - } - ) - }) == "{ hidden x = 1 }\n" - formatBody(new syntax.ObjectBodyBuilder { - members = List( - new syntax.ObjectPropertyBuilder { - name = "x" - objectBodies = List( - new syntax.ObjectBodyBuilder { - members = List( - new syntax.ObjectPropertyBuilder { - name = "y" - value = new syntax.IntLiteralBuilder { value = 2 } - } - ) - } - ) - } - ) - }) == "{ x { y = 2 } }\n" - } - - ["object method"] { - formatBody(new syntax.ObjectBodyBuilder { - members = List( - new syntax.ObjectMethodBuilder { - name = "f" - parameters = List(new syntax.ParameterBuilder { name = "x" }) - body = new syntax.IdentifierExprBuilder { name = "x" } - } - ) - }) == "{ function f(x) = x }\n" - formatBody(new syntax.ObjectBodyBuilder { - members = List( - new syntax.ObjectMethodBuilder { - name = "f" - parameters = List(new syntax.ParameterBuilder { name = "x"; typeAnnotation = new syntax.DeclaredTypeBuilder { name = "Int" } }) - returnType = new syntax.DeclaredTypeBuilder { name = "Int" } - body = new syntax.IdentifierExprBuilder { name = "x" } - } - ) - }) == "{ function f(x: Int): Int = x }\n" - } - - ["object entry"] { - formatBody(new syntax.ObjectBodyBuilder { - members = List( - new syntax.ObjectEntryBuilder { - key = new syntax.StringLiteralBuilder { value = "k" } - value = new syntax.IntLiteralBuilder { value = 1 } - } - ) - }) == "{ [\"k\"] = 1 }\n" - formatBody(new syntax.ObjectBodyBuilder { - members = List( - new syntax.ObjectEntryBuilder { - key = new syntax.StringLiteralBuilder { value = "k" } - objectBodies = List( - new syntax.ObjectBodyBuilder { - members = List( - new syntax.ObjectPropertyBuilder { - name = "x" - value = new syntax.IntLiteralBuilder { value = 1 } - } - ) - } - ) - } - ) - }) == "{ [\"k\"] { x = 1 } }\n" - } - - ["member predicate"] { - formatBody(new syntax.ObjectBodyBuilder { - members = List( - new syntax.MemberPredicateBuilder { - condition = new syntax.IdentifierExprBuilder { name = "cond" } - value = new syntax.IntLiteralBuilder { value = 1 } - } - ) - }) == "{ [[cond]] = 1 }\n" - } - - ["for generator"] { - formatBody(new syntax.ObjectBodyBuilder { - members = List( - new syntax.ForGeneratorBuilder { - valueParameter = new syntax.ParameterBuilder { name = "x" } - iterable = new syntax.IdentifierExprBuilder { name = "items" } - body = new syntax.ObjectBodyBuilder { - members = List( - new syntax.ObjectElementBuilder { - expression = new syntax.IdentifierExprBuilder { name = "x" } - } - ) - } - } - ) - }) == "{ for (x in items) { x } }\n" - formatBody(new syntax.ObjectBodyBuilder { - members = List( - new syntax.ForGeneratorBuilder { - keyParameter = new syntax.ParameterBuilder { name = "k" } - valueParameter = new syntax.ParameterBuilder { name = "v" } - iterable = new syntax.IdentifierExprBuilder { name = "items" } - body = new syntax.ObjectBodyBuilder {} - } - ) - }) == "{ for (k, v in items) {} }\n" - } - - ["when generator"] { - formatBody(new syntax.ObjectBodyBuilder { - members = List( - new syntax.WhenGeneratorBuilder { - condition = new syntax.IdentifierExprBuilder { name = "cond" } - thenBody = new syntax.ObjectBodyBuilder {} - } - ) - }) == "{ when (cond) {} }\n" - formatBody(new syntax.ObjectBodyBuilder { - members = List( - new syntax.WhenGeneratorBuilder { - condition = new syntax.IdentifierExprBuilder { name = "cond" } - thenBody = new syntax.ObjectBodyBuilder {} - elseBody = new syntax.ObjectBodyBuilder {} - } - ) - }) == "{ when (cond) {} else {} }\n" - } - - ["body with parameters"] { - formatBody(new syntax.ObjectBodyBuilder { - parameters = List( - new syntax.ParameterBuilder { name = "x" }, - new syntax.ParameterBuilder { name = "y" } - ) - members = List( - new syntax.ObjectElementBuilder { - expression = new syntax.IdentifierExprBuilder { name = "x" } - } - ) - }) == "{ x, y -> x }\n" - } - - ["new expr"] { - formatExpr(new syntax.NewExprBuilder { - body = new syntax.ObjectBodyBuilder {} - }) == "new {}\n" - formatExpr(new syntax.NewExprBuilder { - type = new syntax.DeclaredTypeBuilder { name = "Foo" } - body = new syntax.ObjectBodyBuilder { - members = List( - new syntax.ObjectPropertyBuilder { - name = "x" - value = new syntax.IntLiteralBuilder { value = 1 } - } - ) - } - }) == "new Foo { x = 1 }\n" - } - - ["amends expr"] { - formatExpr(new syntax.AmendsExprBuilder { - parentExpr = new syntax.ParenthesizedExprBuilder { - expression = new syntax.IdentifierExprBuilder { name = "base" } - } - body = new syntax.ObjectBodyBuilder { - members = List( - new syntax.ObjectPropertyBuilder { - name = "x" - value = new syntax.IntLiteralBuilder { value = 1 } - } - ) - } - }) == "(base) { x = 1 }\n" - } - - ["doc comment"] { - syntax.format((new syntax.DocCommentBuilder { - lines = List(" line 1", " line 2") - }).build().node) == "/// line 1\n/// line 2\n" - } - - ["annotation"] { - syntax.format((new syntax.AnnotationBuilder { - type = new syntax.DeclaredTypeBuilder { name = "Deprecated" } - }).build().node) == "@Deprecated\n" - syntax.format((new syntax.AnnotationBuilder { - type = new syntax.DeclaredTypeBuilder { name = "Deprecated" } - body = new syntax.ObjectBodyBuilder { - members = List( - new syntax.ObjectPropertyBuilder { - name = "message" - value = new syntax.StringLiteralBuilder { value = "old" } - } - ) - } - }).build().node) == #"@Deprecated { message = "old" }\#n"# - } - - ["import"] { - syntax.format((new syntax.ImportBuilder { uri = "pkl:json" }).build().node) - == #"import "pkl:json"\#n"# - syntax.format((new syntax.ImportBuilder { uri = "pkl:json"; isGlob = true }).build().node) - == #"import* "pkl:json"\#n"# - syntax.format((new syntax.ImportBuilder { uri = "pkl:json"; alias = "j" }).build().node) - == #"import "pkl:json" as j\#n"# - } - - ["class property (top-level)"] { - syntax.format((new syntax.ClassPropertyBuilder { - name = "x" - value = new syntax.IntLiteralBuilder { value = 1 } - }).build().node) == "x = 1\n" - syntax.format((new syntax.ClassPropertyBuilder { - modifiers = List("hidden") - name = "x" - typeAnnotation = new syntax.DeclaredTypeBuilder { name = "Int" } - value = new syntax.IntLiteralBuilder { value = 1 } - }).build().node) == "hidden x: Int = 1\n" - } - - ["class method (top-level)"] { - syntax.format((new syntax.ClassMethodBuilder { - name = "f" - parameters = List(new syntax.ParameterBuilder { name = "x" }) - body = new syntax.IdentifierExprBuilder { name = "x" } - }).build().node) == "function f(x) = x\n" - syntax.format((new syntax.ClassMethodBuilder { - modifiers = List("abstract") - name = "f" - parameters = List() - returnType = new syntax.DeclaredTypeBuilder { name = "Int" } - }).build().node) == "abstract function f(): Int\n" - } - - ["typealias"] { - syntax.format((new syntax.TypeAliasBuilder { - name = "MyInt" - type = new syntax.DeclaredTypeBuilder { name = "Int" } - }).build().node) == "typealias MyInt = Int\n" - syntax.format((new syntax.TypeAliasBuilder { - name = "Pair" - typeParameters = List( - new syntax.TypeParameterBuilder { name = "A" }, - new syntax.TypeParameterBuilder { name = "B" } - ) - type = new syntax.DeclaredTypeBuilder { name = "Mapping" } - }).build().node) == "typealias Pair = Mapping\n" - } - - ["class"] { - syntax.format((new syntax.ClassBuilder { - name = "Foo" - }).build().node) == "class Foo\n" - syntax.format((new syntax.ClassBuilder { - modifiers = List("open") - name = "Foo" - extendsType = new syntax.DeclaredTypeBuilder { name = "Bar" } - body = new syntax.ClassBodyBuilder { - properties = List( - new syntax.ClassPropertyBuilder { - name = "x" - value = new syntax.IntLiteralBuilder { value = 1 } - } - ) - } - }).build().node) == "open class Foo extends Bar {\n x = 1\n}\n" - } - - ["module declaration"] { - syntax.format((new syntax.ModuleDeclarationBuilder { - name = "my.config" - }).build().node) == "module my.config\n" - syntax.format((new syntax.ModuleDeclarationBuilder { - amendsUri = "pkl:base" - }).build().node) == #"amends "pkl:base"\#n"# - syntax.format((new syntax.ModuleDeclarationBuilder { - modifiers = List("open") - name = "my.config" - extendsUri = "pkl:base" - }).build().node) == #""" - open module my.config - - extends "pkl:base" - - """# - } - - ["module"] { - formatModule(new syntax.ModuleBuilder { - declaration = new syntax.ModuleDeclarationBuilder { name = "my.config" } - imports = List(new syntax.ImportBuilder { uri = "pkl:json" }) - properties = List( - new syntax.ClassPropertyBuilder { - name = "host" - typeAnnotation = new syntax.DeclaredTypeBuilder { name = "String" } - value = new syntax.StringLiteralBuilder { value = "localhost" } - }, - new syntax.ClassPropertyBuilder { - name = "port" - typeAnnotation = new syntax.DeclaredTypeBuilder { name = "Int" } - value = new syntax.IntLiteralBuilder { value = 8080 } - } - ) - }) == #""" - module my.config - - import "pkl:json" - - host: String = "localhost" - port: Int = 8080 - - """# - } - - ["round-trip: simple property"] { - let (parsed = syntax.parse("x = 1") as syntax.ModuleNode) - let (rebuilt = parsed.toBuilder().build()) - syntax.format(rebuilt.node) == "x = 1\n" - } - - ["round-trip: typed property"] { - let (parsed = syntax.parse("x: Int = 1") as syntax.ModuleNode) - let (rebuilt = parsed.toBuilder().build()) - syntax.format(rebuilt.node) == "x: Int = 1\n" - } - - ["round-trip: if expression"] { - let (parsed = syntax.parse(#"x = if (a > 0) "yes" else "no""#) as syntax.ModuleNode) - let (rebuilt = parsed.toBuilder().build()) - syntax.format(rebuilt.node) == #"x = if (a > 0) "yes" else "no"\#n"# - } - - ["round-trip: function call"] { - let (parsed = syntax.parse("x = max(1, 2)") as syntax.ModuleNode) - let (rebuilt = parsed.toBuilder().build()) - syntax.format(rebuilt.node) == "x = max(1, 2)\n" - } - - ["round-trip: qualified access"] { - let (parsed = syntax.parse("x = obj.field") as syntax.ModuleNode) - let (rebuilt = parsed.toBuilder().build()) - syntax.format(rebuilt.node) == "x = obj.field\n" - } - - ["round-trip: union type"] { - let (parsed = syntax.parse("typealias T = Int|String") as syntax.ModuleNode) - let (rebuilt = parsed.toBuilder().build()) - syntax.format(rebuilt.node) == "typealias T = Int | String\n" - } - - ["round-trip: class with body"] { - let (parsed = syntax.parse(""" - open class Foo extends Bar { - x: Int = 1 - function f(y) = y - } - """) as syntax.ModuleNode) - let (rebuilt = parsed.toBuilder().build()) - syntax.format(rebuilt.node) == #""" - open class Foo extends Bar { - x: Int = 1 - - function f(y) = y - } - - """# - } - - ["round-trip: amend modifies value"] { - let (parsed = syntax.parse("x = 1") as syntax.ModuleNode) - let (modified = (parsed.properties.first.toBuilder()) { - value = new syntax.IntLiteralBuilder { value = 99 } - }) - syntax.format(modified.build().node) == "x = 99\n" - } - - ["multi-line string"] { - formatExpr(new syntax.MultiLineStringLiteralBuilder { - parts = List( - new syntax.StringNewlineBuilder {}, - new syntax.StringCharsBuilder { value = "hello" }, - new syntax.StringNewlineBuilder {}, - new syntax.StringCharsBuilder { value = "world" }, - new syntax.StringNewlineBuilder {} - ) - }) == #""" - """ - hello - world - """ - - """# - } - - ["multi-line string with interpolation"] { - formatExpr(new syntax.MultiLineStringLiteralBuilder { - parts = List( - new syntax.StringNewlineBuilder {}, - new syntax.StringCharsBuilder { value = "hi " }, - new syntax.StringInterpolationBuilder { - expression = new syntax.IdentifierExprBuilder { name = "name" } - }, - new syntax.StringNewlineBuilder {} - ) - }) == #""" - """ - hi \(name) - """ - - """# - } - - ["round-trip: multi-line string with interpolation"] { - let (parsed = syntax.parse(#""" - x = """ - hello \(name) - world - """ - """#) as syntax.ModuleNode) - let (rebuilt = parsed.toBuilder().build()) - syntax.format(rebuilt.node) == #""" - x = - """ - hello \(name) - world - """ - - """# - } - - ["string literal with interpolation"] { - formatExpr(new syntax.StringLiteralBuilder { - parts = List( - new syntax.StringCharsBuilder { value = "hi " }, - new syntax.StringInterpolationBuilder { - expression = new syntax.IdentifierExprBuilder { name = "name" } - } - ) - }) == #""hi \(name)"\#n"# - } - - ["string literal with escape"] { - formatExpr(new syntax.StringLiteralBuilder { - parts = List( - new syntax.StringCharsBuilder { value = "line1" }, - new syntax.StringEscapeBuilder { value = "\\n" }, - new syntax.StringCharsBuilder { value = "line2" } - ) - }) == #""line1\nline2"\#n"# - } - - ["round-trip: single-line string with interpolation"] { - let (parsed = syntax.parse(#"x = "hi \(name)""#) as syntax.ModuleNode) - let (rebuilt = parsed.toBuilder().build()) - syntax.format(rebuilt.node) == #"x = "hi \(name)"\#n"# - } -} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl index 8ca130a2a..332422106 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl @@ -81,20 +81,20 @@ examples { ["access expressions"] { local unqual = expr("foo") unqual is syntax.UnqualifiedAccessExprNode - (unqual as syntax.UnqualifiedAccessExprNode).identifier.value == "foo" - (unqual as syntax.UnqualifiedAccessExprNode).argumentList == null + (unqual as syntax.UnqualifiedAccessExprNode).identifier == "foo" + (unqual as syntax.UnqualifiedAccessExprNode).arguments == null local withArgs = expr("foo(1, 2)") withArgs is syntax.UnqualifiedAccessExprNode - (withArgs as syntax.UnqualifiedAccessExprNode).identifier.value == "foo" - (withArgs as syntax.UnqualifiedAccessExprNode).argumentList != null - (withArgs as syntax.UnqualifiedAccessExprNode).argumentList!!.arguments.length == 2 + (withArgs as syntax.UnqualifiedAccessExprNode).identifier == "foo" + (withArgs as syntax.UnqualifiedAccessExprNode).arguments != null + (withArgs as syntax.UnqualifiedAccessExprNode).arguments.length == 2 local qual = expr("foo.bar") qual is syntax.QualifiedAccessExprNode (qual as syntax.QualifiedAccessExprNode).receiver is syntax.UnqualifiedAccessExprNode (qual as syntax.QualifiedAccessExprNode).isNullSafe == false - (qual as syntax.QualifiedAccessExprNode).member.identifier.value == "bar" + (qual as syntax.QualifiedAccessExprNode).member == "bar" local nullSafe = expr("foo?.bar") nullSafe is syntax.QualifiedAccessExprNode @@ -110,8 +110,8 @@ examples { local add = expr("1 + 2") add is syntax.BinaryOpExprNode (add as syntax.BinaryOpExprNode).operator == "+" - (add as syntax.BinaryOpExprNode).leftExpr is syntax.IntLiteralExprNode - (add as syntax.BinaryOpExprNode).rightExpr is syntax.IntLiteralExprNode + (add as syntax.BinaryOpExprNode).left is syntax.IntLiteralExprNode + (add as syntax.BinaryOpExprNode).right is syntax.IntLiteralExprNode local eq = expr("a == b") eq is syntax.BinaryOpExprNode @@ -161,24 +161,23 @@ examples { ["let expression"] { local letExpr = expr("let (y = 1) y + 1") letExpr is syntax.LetExprNode - (letExpr as syntax.LetExprNode).parameter.identifier!!.value == "y" + (letExpr as syntax.LetExprNode).parameter.name == "y" (letExpr as syntax.LetExprNode).bindingValue is syntax.IntLiteralExprNode - (letExpr as syntax.LetExprNode).bodyExpr is syntax.BinaryOpExprNode + (letExpr as syntax.LetExprNode).body is syntax.BinaryOpExprNode } ["new expression"] { local newExpr = expr("new Mapping { [\"a\"] = 1 }") newExpr is syntax.NewExprNode (newExpr as syntax.NewExprNode).type is syntax.DeclaredTypeNode - (newExpr as syntax.NewExprNode).body is syntax.ObjectBodyNode } ["function literal"] { local fn = expr("(x, y) -> x + y") fn is syntax.FunctionLiteralExprNode - (fn as syntax.FunctionLiteralExprNode).parameterList.parameters.length == 2 - (fn as syntax.FunctionLiteralExprNode).parameterList.parameters[0].identifier!!.value == "x" - (fn as syntax.FunctionLiteralExprNode).parameterList.parameters[1].identifier!!.value == "y" + (fn as syntax.FunctionLiteralExprNode).parameters.length == 2 + (fn as syntax.FunctionLiteralExprNode).parameters[0].name == "x" + (fn as syntax.FunctionLiteralExprNode).parameters[1].name == "y" (fn as syntax.FunctionLiteralExprNode).body is syntax.BinaryOpExprNode } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl index 780faf4a4..2b93b3512 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl @@ -10,15 +10,12 @@ examples { result is syntax.ModuleNode local mod = result as syntax.ModuleNode mod.declaration != null - mod.declaration!!.name != null - mod.declaration!!.name!!.identifiers.length == 2 - mod.declaration!!.name!!.identifiers[0].value == "my" - mod.declaration!!.name!!.identifiers[1].value == "app" + mod.declaration!!.name == "my.app" mod.declaration!!.docComment == null mod.declaration!!.annotations.length == 0 - mod.declaration!!.modifiers == null - mod.declaration!!.amendsClause == null - mod.declaration!!.extendsClause == null + mod.declaration!!.modifiers.isEmpty + mod.declaration!!.amendsUri == null + mod.declaration!!.extendsUri == null } ["module with modifiers and doc comment"] { @@ -38,28 +35,21 @@ examples { mod.declaration!!.annotations.first.type is syntax.DeclaredTypeNode mod.declaration!!.modifiers != null - mod.declaration!!.modifiers!! is syntax.ModifierListNode - mod.declaration!!.modifiers!!.modifiers == List("open") + mod.declaration!!.modifiers == List("open") } ["amends clause"] { local result = parse(#"amends "base.pkl""#) local mod = result as syntax.ModuleNode mod.declaration != null - mod.declaration!!.amendsClause != null - mod.declaration!!.amendsClause!! is syntax.AmendsClauseNode - mod.declaration!!.amendsClause!!.uri == "base.pkl" - mod.declaration!!.extendsClause == null + mod.declaration!!.amendsUri == "base.pkl" } ["extends clause"] { local result = parse(#"extends "base.pkl""#) local mod = result as syntax.ModuleNode mod.declaration != null - mod.declaration!!.extendsClause != null - mod.declaration!!.extendsClause!! is syntax.ExtendsClauseNode - mod.declaration!!.extendsClause!!.uri == "base.pkl" - mod.declaration!!.amendsClause == null + mod.declaration!!.extendsUri == "base.pkl" } ["imports"] { @@ -71,14 +61,12 @@ examples { local mod = result as syntax.ModuleNode mod.imports.length == 3 - mod.imports[0] is syntax.ImportNode mod.imports[0].uri == "foo.pkl" mod.imports[0].isGlob == false mod.imports[0].alias == null mod.imports[1].uri == "bar.pkl" - mod.imports[1].alias != null - mod.imports[1].alias!!.value == "myBar" + mod.imports[1].alias == "myBar" mod.imports[2].uri == "*.pkl" mod.imports[2].isGlob == true @@ -95,30 +83,29 @@ examples { local mod = result as syntax.ModuleNode mod.classes.length == 1 local cls = mod.classes.first - cls is syntax.ClassNode cls.docComment != null cls.docComment!!.lines.length == 1 cls.modifiers != null - cls.modifiers!!.modifiers == List("abstract") + cls.modifiers == List("abstract") - cls.name.value == "Bird" + cls.name == "Bird" - cls.typeParameterList == null - cls.extendsClause == null + cls.typeParameters.isEmpty + cls.extendsType == null cls.body != null cls.body!!.properties.length == 1 - cls.body!!.properties.first.name.value == "name" + cls.body!!.properties.first.name == "name" cls.body!!.properties.first.typeAnnotation != null - cls.body!!.properties.first.typeAnnotation!!.type is syntax.DeclaredTypeNode + cls.body!!.properties.first.typeAnnotation is syntax.DeclaredTypeNode cls.body!!.properties.first.value == null cls.body!!.methods.length == 1 - cls.body!!.methods.first.name.value == "fly" - cls.body!!.methods.first.parameterList.parameters.length == 1 - cls.body!!.methods.first.parameterList.parameters.first.identifier!!.value == "speed" + cls.body!!.methods.first.name == "fly" + cls.body!!.methods.first.parameters.length == 1 + cls.body!!.methods.first.parameters.first.name == "speed" cls.body!!.methods.first.returnType != null cls.body!!.methods.first.body != null cls.body!!.methods.first.body is syntax.BoolLiteralExprNode @@ -133,14 +120,14 @@ examples { local mod = result as syntax.ModuleNode local cls = mod.classes.first - cls.name.value == "Container" - cls.typeParameterList != null - cls.typeParameterList!!.typeParameters.length == 1 - cls.typeParameterList!!.typeParameters.first.name.value == "T" - cls.typeParameterList!!.typeParameters.first.variance == null + cls.name == "Container" + cls.typeParameters != null + cls.typeParameters.length == 1 + cls.typeParameters.first.name == "T" + cls.typeParameters.first.variance == null - cls.extendsClause != null - cls.extendsClause is syntax.DeclaredTypeNode + cls.extendsType != null + cls.extendsType is syntax.DeclaredTypeNode } ["typealias"] { @@ -148,9 +135,8 @@ examples { local mod = result as syntax.ModuleNode mod.typeAliases.length == 1 local ta = mod.typeAliases.first - ta is syntax.TypeAliasNode - ta.name.value == "Positive" - ta.typeParameterList == null + ta.name == "Positive" + ta.typeParameters.isEmpty ta.type is syntax.ConstrainedTypeNode } @@ -163,18 +149,18 @@ examples { local mod = result as syntax.ModuleNode mod.properties.length == 2 - mod.properties[0].name.value == "name" + mod.properties[0].name == "name" mod.properties[0].modifiers != null - mod.properties[0].modifiers!!.modifiers == List("hidden") + mod.properties[0].modifiers == List("hidden") mod.properties[0].value is syntax.SingleLineStringLiteralExprNode - mod.properties[1].name.value == "count" + mod.properties[1].name == "count" mod.properties[1].modifiers != null - mod.properties[1].modifiers!!.modifiers == List("local") + mod.properties[1].modifiers == List("local") mod.methods.length == 1 - mod.methods.first.name.value == "greet" - mod.methods.first.parameterList.parameters.length == 1 + mod.methods.first.name == "greet" + mod.methods.first.parameters.length == 1 mod.methods.first.returnType != null mod.methods.first.body is syntax.SingleLineStringLiteralExprNode } @@ -182,21 +168,19 @@ examples { ["parameter variations"] { local result = parse("function f(x: Int, _, y): Boolean = true") local mod = result as syntax.ModuleNode - local params = mod.methods.first.parameterList.parameters + local params = mod.methods.first.parameters params.length == 3 - params[0].identifier != null - params[0].identifier!!.value == "x" + params[0].name == "x" params[0].typeAnnotation != null params[0].isWildcard == false - params[1].identifier == null + params[1].name == "_" params[1].isWildcard == true params[1].typeAnnotation == null - params[2].identifier != null - params[2].identifier!!.value == "y" + params[2].name == "y" params[2].typeAnnotation == null params[2].isWildcard == false } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl index 7f7baafb2..8b5ed99b5 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl @@ -12,66 +12,68 @@ local function body(source: String) = examples { ["object property"] { local b = body("name = \"hello\"") - b.properties.length == 1 - b.properties.first is syntax.ObjectPropertyNode - b.properties.first.name.value == "name" - b.properties.first.value is syntax.SingleLineStringLiteralExprNode - b.properties.first.modifiers == null - b.properties.first.typeAnnotation == null - b.properties.first.objectBodies.length == 0 + b.members.length == 1 + b.members.first is syntax.ObjectPropertyNode + local prop = b.members.first as syntax.ObjectPropertyNode + prop.name == "name" + prop.value is syntax.SingleLineStringLiteralExprNode + prop.modifiers.isEmpty + prop.typeAnnotation == null + prop.objectBodies.length == 0 } ["object property with type and modifiers"] { local b = body("hidden name: String = \"hello\"") - b.properties.length == 1 - b.properties.first.modifiers != null - b.properties.first.modifiers!!.modifiers == List("hidden") - b.properties.first.typeAnnotation != null - b.properties.first.typeAnnotation!!.type is syntax.DeclaredTypeNode + b.members.length == 1 + local prop = b.members.first as syntax.ObjectPropertyNode + prop.modifiers == List("hidden") + prop.typeAnnotation != null + prop.typeAnnotation is syntax.DeclaredTypeNode } ["object property with amending body"] { local b = body("inner { x = 1 }") - b.properties.length == 1 - b.properties.first.name.value == "inner" - b.properties.first.value == null - b.properties.first.objectBodies.length == 1 - b.properties.first.objectBodies.first is syntax.ObjectBodyNode + b.members.length == 1 + local prop = b.members.first as syntax.ObjectPropertyNode + prop.name == "inner" + prop.value == null + prop.objectBodies.length == 1 } ["object method"] { local b = body("function greet(who: String): String = \"hi\"") - b.methods.length == 1 - b.methods.first is syntax.ObjectMethodNode - b.methods.first.name.value == "greet" - b.methods.first.parameterList.parameters.length == 1 - b.methods.first.parameterList.parameters.first.identifier!!.value == "who" - b.methods.first.returnType != null - b.methods.first.body is syntax.SingleLineStringLiteralExprNode + b.members.length == 1 + b.members.first is syntax.ObjectMethodNode + local method = b.members.first as syntax.ObjectMethodNode + method.name == "greet" + method.parameters.length == 1 + method.parameters.first.name == "who" + method.returnType != null + method.body is syntax.SingleLineStringLiteralExprNode } ["object element"] { local b = body("1\n 2\n 3") - b.elements.length == 3 - b.elements[0] is syntax.ObjectElementNode - b.elements[0].expression is syntax.IntLiteralExprNode - b.elements[1].expression is syntax.IntLiteralExprNode - b.elements[2].expression is syntax.IntLiteralExprNode + b.members.length == 3 + b.members[0] is syntax.ObjectElementNode + b.members[0].expression is syntax.IntLiteralExprNode + b.members[1].expression is syntax.IntLiteralExprNode + b.members[2].expression is syntax.IntLiteralExprNode } ["object entry"] { local b = body("[\"key\"] = 42") - b.entries.length == 1 - b.entries.first is syntax.ObjectEntryNode - b.entries.first.key is syntax.SingleLineStringLiteralExprNode - b.entries.first.value is syntax.IntLiteralExprNode + b.members.length == 1 + b.members.first is syntax.ObjectEntryNode + b.members.first.key is syntax.SingleLineStringLiteralExprNode + b.members.first.value is syntax.IntLiteralExprNode } ["object entry with amending body"] { local b = body("[\"key\"] { x = 1 }") - b.entries.length == 1 - b.entries.first.key is syntax.SingleLineStringLiteralExprNode - b.entries.first.objectBodies.length == 1 + b.members.length == 1 + b.members.first.key is syntax.SingleLineStringLiteralExprNode + b.members.first.objectBodies.length == 1 } ["object spread"] { @@ -111,17 +113,16 @@ examples { b.members.first is syntax.ForGeneratorNode local gen = b.members.first as syntax.ForGeneratorNode gen.keyParameter == null - gen.valueParameter.identifier!!.value == "item" + gen.valueParameter.name == "item" gen.iterable is syntax.UnqualifiedAccessExprNode - gen.body is syntax.ObjectBodyNode } ["for generator with key"] { local b = body("for (k, v in items) { v }") local gen = b.members.first as syntax.ForGeneratorNode gen.keyParameter != null - gen.keyParameter!!.identifier!!.value == "k" - gen.valueParameter.identifier!!.value == "v" + gen.keyParameter!!.name == "k" + gen.valueParameter.name == "v" } ["when generator"] { @@ -130,7 +131,6 @@ examples { b.members.first is syntax.WhenGeneratorNode local gen = b.members.first as syntax.WhenGeneratorNode gen.condition is syntax.UnqualifiedAccessExprNode - gen.thenBody is syntax.ObjectBodyNode gen.elseBody == null } @@ -138,7 +138,6 @@ examples { local b = body("when (flag) { 1 } else { 2 }") local gen = b.members.first as syntax.WhenGeneratorNode gen.condition is syntax.UnqualifiedAccessExprNode - gen.thenBody is syntax.ObjectBodyNode gen.elseBody != null gen.elseBody is syntax.ObjectBodyNode } @@ -150,8 +149,8 @@ examples { propVal is syntax.NewExprNode local newBody = (propVal as syntax.NewExprNode).body newBody.parameters.length == 2 - newBody.parameters[0].identifier!!.value == "a" - newBody.parameters[1].identifier!!.value == "b" + newBody.parameters[0].name == "a" + newBody.parameters[1].name == "b" } ["mixed object members"] { @@ -161,10 +160,9 @@ examples { ["key"] = 2 function f() = 3 """) - b.properties.length == 1 - b.elements.length == 1 - b.entries.length == 1 - b.methods.length == 1 b.members.length == 4 + b.members.filterIsInstance(syntax.ObjectPropertyNode).length == 1 + b.members.filterIsInstance(syntax.ObjectEntryNode).length == 1 + b.members.filterIsInstance(syntax.ObjectMethodNode).length == 1 } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl new file mode 100644 index 000000000..8ad33d4b1 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl @@ -0,0 +1,102 @@ +amends "../snippetTest.pkl" + +import "pkl:syntax" + +local function mod(source: String): syntax.ModuleNode = syntax.parse(source) as syntax.ModuleNode + +local sample: syntax.Node = + mod( + """ + import "foo.pkl" + import "bar.pkl" as baz + + class Point { + x: Int + y: Int + } + + origin = if (useOrigin) new Point { x = 0; y = 0 } else null + + scaled = origin.x * 2 + """, + ).node!! + +examples { + ["fold counts nodes by predicate"] { + syntax.fold(sample, 0, (acc, n) -> if (n.type == "import") acc + 1 else acc) == 2 + + syntax.fold(sample, 0, (acc, n) -> if (n.type == "if_expr") acc + 1 else acc) == 1 + + syntax.fold(sample, 0, (acc, n) -> if (n.type == "when_generator") acc + 1 else acc) == 0 + } + + ["fold accumulates into a collection"] { + syntax.fold(sample, List(), (acc, n) -> if (n.type == "identifier") acc.add(n.text) else acc) + == List( + "baz", + "Point", + "x", + "Int", + "y", + "Int", + "origin", + "useOrigin", + "Point", + "x", + "y", + "scaled", + "origin", + "x", + ) + } + + ["fold visits a node before its children (pre-order)"] { + // the module node is visited first + syntax.fold(sample, List(), (acc, n) -> acc.add(n.type)).first == "module" + } + + ["descendants enumerates the whole tree"] { + syntax.descendants(sample).first == sample + + syntax.descendants(sample) == syntax.fold(sample, List(), (acc, n) -> acc.add(n)) + + syntax.descendants(sample).filter((n) -> n.type == "class").length == 1 + } + + ["descendants + wrap gives type-safe access"] { + syntax + .descendants(sample) + .map((n) -> syntax.wrap(n)) + .filter((s) -> s is syntax.ImportNode) + .map((s) -> (s as syntax.ImportNode).uri) == List("foo.pkl", "bar.pkl") + + syntax + .descendants(sample) + .map((n) -> syntax.wrap(n)) + .filter((s) -> s is syntax.ClassPropertyNode) + .map((s) -> (s as syntax.ClassPropertyNode).name) == List("x", "y", "origin", "scaled") + } + + ["wrap returns typed nodes for known kinds"] { + syntax.wrap(new syntax.Node { type = "if_expr" }) is syntax.IfExprNode + syntax.wrap(new syntax.Node { type = "declared_type" }) is syntax.DeclaredTypeNode + syntax.wrap(new syntax.Node { type = "object_property" }) is syntax.ObjectPropertyNode + syntax.wrap(new syntax.Node { type = "class" }) is syntax.ClassNode + syntax.wrap(new syntax.Node { type = "parameter" }) is syntax.ParameterNode + } + + ["wrap returns null for kinds without a typed form"] { + syntax.wrap(new syntax.Node { type = "terminal" }) == null + syntax.wrap(new syntax.Node { type = "modifier_list" }) == null + syntax.wrap(new syntax.Node { type = "if_header" }) == null + } + + ["fold reads typed fields via wrap"] { + // collect the arithmetic operators used anywhere in the module + syntax.fold(sample, List(), (acc, n) -> + let (s = syntax.wrap(n)) + if (s is syntax.BinaryOpExprNode) acc.add(s.operator) else acc + ) + == List("*") + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl index 9b280e04a..fce1b68a9 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl @@ -6,7 +6,7 @@ local function parse(source: String) = syntax.parse(source) local function typeOf(typeSource: String) = let (result = parse("x: \(typeSource) = 0")) - (result as syntax.ModuleNode).properties.first.typeAnnotation!!.type + (result as syntax.ModuleNode).properties.first.typeAnnotation examples { ["simple types"] { @@ -23,20 +23,18 @@ examples { ["declared type"] { local simple = typeOf("String") simple is syntax.DeclaredTypeNode - (simple as syntax.DeclaredTypeNode).name.identifiers.length == 1 - (simple as syntax.DeclaredTypeNode).name.identifiers.first.value == "String" - (simple as syntax.DeclaredTypeNode).typeArgumentList == null + (simple as syntax.DeclaredTypeNode).name == "String" + (simple as syntax.DeclaredTypeNode).typeArguments.isEmpty local withArgs = typeOf("List") withArgs is syntax.DeclaredTypeNode - (withArgs as syntax.DeclaredTypeNode).name.identifiers.first.value == "List" - (withArgs as syntax.DeclaredTypeNode).typeArgumentList != null - (withArgs as syntax.DeclaredTypeNode).typeArgumentList!!.typeArguments.length == 1 - (withArgs as syntax.DeclaredTypeNode).typeArgumentList!!.typeArguments.first is syntax.DeclaredTypeNode + (withArgs as syntax.DeclaredTypeNode).name == "List" + (withArgs as syntax.DeclaredTypeNode).typeArguments.length == 1 + (withArgs as syntax.DeclaredTypeNode).typeArguments.first is syntax.DeclaredTypeNode local multiArgs = typeOf("Map") multiArgs is syntax.DeclaredTypeNode - (multiArgs as syntax.DeclaredTypeNode).typeArgumentList!!.typeArguments.length == 2 + (multiArgs as syntax.DeclaredTypeNode).typeArguments.length == 2 } ["nullable type"] { @@ -98,7 +96,6 @@ examples { local result = parse("x: String = \"hello\"") local prop = (result as syntax.ModuleNode).properties.first prop.typeAnnotation != null - prop.typeAnnotation!! is syntax.TypeAnnotationNode - prop.typeAnnotation!!.type is syntax.DeclaredTypeNode + prop.typeAnnotation!! is syntax.DeclaredTypeNode } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl new file mode 100644 index 000000000..ca641559f --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl @@ -0,0 +1,119 @@ +amends "../snippetTest.pkl" + +import "pkl:syntax" + +local function mod(source: String): syntax.ModuleNode = syntax.parse(source) as syntax.ModuleNode + +local function fmt(source: String): String = syntax.format(mod(source).node) + +local function walkFormat( + source: String, + visit: (syntax.Node) -> Pair?, +): String = syntax.format(syntax.walk(mod(source).node, visit)) + +local function visitFormat(source: String, visitor: syntax.Visitor): String = + syntax.format(syntax.visit(mod(source).node, visitor)) + +examples { + ["read-only walk leaves the tree unchanged"] { + // returning `null` everywhere keeps every node and keeps descending + walkFormat("x = 1", (_) -> null) == fmt("x = 1") + walkFormat("x = if (cond) 1 else 2", (_) -> null) == fmt("x = if (cond) 1 else 2") + } + + ["rename identifiers everywhere"] { + walkFormat("foo = foo + 1", (n) -> + if (n.type == "identifier" && n.text == "foo") + Pair((n) { text = "bar" }, true) + else + null) == fmt("bar = bar + 1") + } + + ["replace a leaf via a typed node"] { + walkFormat("x = 0", (n) -> + if (n.type == "int_literal_expr" && n.text == "0") + Pair(new syntax.IntLiteralExprNode { value = 100 }.toNode(), false) + else + null) == fmt("x = 100") + } + + ["rebuilds ancestors of a changed node"] { + walkFormat("x = if (cond) 41 else 0", (n) -> + if (n.type == "int_literal_expr" && n.text == "41") + Pair(new syntax.IntLiteralExprNode { value = 42 }.toNode(), false) + else if (n.type == "int_literal_expr" && n.text == "0") + Pair(new syntax.IntLiteralExprNode { value = 100 }.toNode(), false) + else + null) == fmt("x = if (cond) 42 else 100") + } + + ["descend reprocesses emitted nodes"] { + walkFormat("x = 0", (n) -> + if (n.type == "int_literal_expr" && n.text == "0") + Pair( + new syntax.ParenthesizedExprNode { + expression = new syntax.IntLiteralExprNode { value = 1 } + }.toNode(), + true, + ) + else if (n.type == "int_literal_expr" && n.text == "1") + Pair(new syntax.IntLiteralExprNode { value = 2 }.toNode(), true) + else + null) == fmt("x = (2)") + } + + ["descend = false leaves the emitted subtree untouched"] { + walkFormat("x = 0", (n) -> + if (n.type == "int_literal_expr" && n.text == "0") + Pair( + new syntax.ParenthesizedExprNode { + expression = new syntax.IntLiteralExprNode { value = 1 } + }.toNode(), + false, + ) + else if (n.type == "int_literal_expr" && n.text == "1") + Pair(new syntax.IntLiteralExprNode { value = 2 }.toNode(), true) + else + null) == fmt("x = (1)") + } + + ["parent back-references on the result tree"] { + local result = syntax.walk(mod("x = 41").node, (n) -> + if (n.type == "int_literal_expr") + Pair(new syntax.IntLiteralExprNode { value = 42 }.toNode(), false) + else + null) + // the root of the returned tree has no parent + result.parent == null + result.children.first.parent.type == "module" + } + + // ---- typed Visitor ---- + + ["visitor rewrites matched nodes only"] { + visitFormat("x = 1 + 2 * 3", new syntax.Visitor { + visitIntLiteralExpr = (_) -> Pair(new syntax.IntLiteralExprNode { value = 0 }, false) + }) == fmt("x = 0 + 0 * 0") + } + + ["visitor reads typed fields"] { + visitFormat("x = foo + bar", new syntax.Visitor { + visitUnqualifiedAccessExpr = (it) -> + if (it.identifier == "foo") + Pair(new syntax.UnqualifiedAccessExprNode { identifier = "renamed" }, false) + else + null + }) == fmt("x = renamed + bar") + } + + ["visitor can broaden the node kind"] { + visitFormat("x = true", new syntax.Visitor { + visitBoolLiteralExpr = (_) -> + Pair(new syntax.IntLiteralExprNode { value = 1 }, false) + }) == fmt("x = 1") + } + + ["visitor leaves unmatched trees intact"] { + visitFormat("x = if (cond) 1 else 2", new syntax.Visitor {}) == fmt("x = if (cond) 1 else 2") + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/builders.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/builders.pcf deleted file mode 100644 index 7c2f4d818..000000000 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/builders.pcf +++ /dev/null @@ -1,244 +0,0 @@ -examples { - ["int literal"] { - true - true - } - ["float literal"] { - true - } - ["bool literal"] { - true - true - } - ["null literal"] { - true - } - ["this expr"] { - true - } - ["outer expr"] { - true - } - ["module expr"] { - true - } - ["identifier expr"] { - true - } - ["string literal"] { - true - } - ["unary minus"] { - true - } - ["logical not"] { - true - } - ["non-null"] { - true - } - ["throw"] { - true - } - ["trace"] { - true - } - ["parenthesized"] { - true - } - ["binary op"] { - true - true - } - ["is expr"] { - true - } - ["if expr"] { - true - } - ["import expr"] { - true - } - ["read expr"] { - true - } - ["let expr"] { - true - true - } - ["function literal"] { - true - true - true - } - ["function call"] { - true - true - } - ["qualified access"] { - true - true - true - } - ["subscript"] { - true - } - ["unknown type"] { - true - } - ["nothing type"] { - true - } - ["module type"] { - true - } - ["declared type"] { - true - true - true - } - ["nullable type"] { - true - } - ["union type"] { - true - true - } - ["function type"] { - true - true - true - } - ["constrained type"] { - true - } - ["parenthesized type"] { - true - } - ["string constant type"] { - true - } - ["empty body"] { - true - } - ["object element"] { - true - } - ["object spread"] { - true - true - } - ["object property"] { - true - true - true - true - } - ["object method"] { - true - true - } - ["object entry"] { - true - true - } - ["member predicate"] { - true - } - ["for generator"] { - true - true - } - ["when generator"] { - true - true - } - ["body with parameters"] { - true - } - ["new expr"] { - true - true - } - ["amends expr"] { - true - } - ["doc comment"] { - true - } - ["annotation"] { - true - true - } - ["import"] { - true - true - true - } - ["class property (top-level)"] { - true - true - } - ["class method (top-level)"] { - true - true - } - ["typealias"] { - true - true - } - ["class"] { - true - true - } - ["module declaration"] { - true - true - true - } - ["module"] { - true - } - ["round-trip: simple property"] { - true - } - ["round-trip: typed property"] { - true - } - ["round-trip: if expression"] { - true - } - ["round-trip: function call"] { - true - } - ["round-trip: qualified access"] { - true - } - ["round-trip: union type"] { - true - } - ["round-trip: class with body"] { - true - } - ["round-trip: amend modifies value"] { - true - } - ["multi-line string"] { - true - } - ["multi-line string with interpolation"] { - true - } - ["round-trip: multi-line string with interpolation"] { - true - } - ["string literal with interpolation"] { - true - } - ["string literal with escape"] { - true - } - ["round-trip: single-line string with interpolation"] { - true - } -} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf index 4a44f87b6..147097dc8 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf @@ -94,7 +94,6 @@ examples { ["new expression"] { true true - true } ["function literal"] { true diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf index 254b9fbbe..7d573f0fe 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf @@ -8,9 +8,6 @@ examples { true true true - true - true - true } ["module with modifiers and doc comment"] { true @@ -22,21 +19,14 @@ examples { true true true - true } ["amends clause"] { true true - true - true - true } ["extends clause"] { true true - true - true - true } ["imports"] { true @@ -47,8 +37,6 @@ examples { true true true - true - true } ["class declaration"] { true @@ -72,7 +60,6 @@ examples { true true true - true } ["class with extends and type parameters"] { true @@ -88,7 +75,6 @@ examples { true true true - true } ["top-level properties and methods"] { true @@ -116,8 +102,6 @@ examples { true true true - true - true } ["parser error"] { true diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf index 963532ff0..aa585fc75 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf @@ -13,14 +13,12 @@ examples { true true true - true } ["object property with amending body"] { true true true true - true } ["object method"] { true @@ -77,7 +75,6 @@ examples { true true true - true } ["for generator with key"] { true @@ -89,13 +86,11 @@ examples { true true true - true } ["when generator with else"] { true true true - true } ["object body with parameters"] { true @@ -108,6 +103,5 @@ examples { true true true - true } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/traversal.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/traversal.pcf new file mode 100644 index 000000000..547e8223c --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/traversal.pcf @@ -0,0 +1,37 @@ +examples { + ["fold counts nodes by predicate"] { + true + true + true + } + ["fold accumulates into a collection"] { + true + } + ["fold visits a node before its children (pre-order)"] { + true + } + ["descendants enumerates the whole tree"] { + true + true + true + } + ["descendants + wrap gives type-safe access"] { + true + true + } + ["wrap returns typed nodes for known kinds"] { + true + true + true + true + true + } + ["wrap returns null for kinds without a typed form"] { + true + true + true + } + ["fold reads typed fields via wrap"] { + true + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/types.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/types.pcf index 450ba35d2..3e17ba631 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/types.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/types.pcf @@ -14,8 +14,6 @@ examples { true true true - true - true } ["nullable type"] { true @@ -59,6 +57,5 @@ examples { ["type annotation"] { true true - true } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf new file mode 100644 index 000000000..00d5ef828 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf @@ -0,0 +1,37 @@ +examples { + ["read-only walk leaves the tree unchanged"] { + true + true + } + ["rename identifiers everywhere"] { + true + } + ["replace a leaf via a typed node"] { + true + } + ["rebuilds ancestors of a changed node"] { + true + } + ["descend reprocesses emitted nodes"] { + true + } + ["descend = false leaves the emitted subtree untouched"] { + true + } + ["parent back-references on the result tree"] { + true + true + } + ["visitor rewrites matched nodes only"] { + true + } + ["visitor reads typed fields"] { + true + } + ["visitor can broaden the node kind"] { + true + } + ["visitor leaves unmatched trees intact"] { + true + } +} diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 88c75f169..867b1cb83 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -37,6 +37,94 @@ function format(node: Node): String = formatToString(node, "V2") external function formatToString(node: Node, grammarVersion: "V1" | "V2"): String +/// Walk [node] and its descendants top-down, applying [visit] to each node and +/// returning the (possibly rewritten) tree. +/// +/// For each node, [visit] returns either: +/// - `null` to leave the node unchanged and continue walking into its children. +/// - `Pair(replacement, descend)` to replace the node with `replacement`. When +/// `descend` is `true`, the children of `replacement` are visited in turn, so +/// nodes emitted by a rewrite are themselves processed further down. When +/// `descend` is `false`, `replacement` and its subtree are left as-is. +/// +/// Constructed nodes must set their own [Node.text] where it applies. +/// [Node.span] is carried through unchanged unless set explicitly. +/// [Node.parent] is populated on the returned tree for nodes originating from [parse]; +/// nodes constructed from scratch retain their given `parent`. +external function walk(node: Node, visit: (Node) -> Pair?): Node + +/// Walk [node] with a [Visitor]. +/// +/// This is a type-safe wrapper around [walk]: each callback receives the typed +/// node and returns either `null` to leave it unchanged, or +/// `Pair(replacement, descend)` where `replacement` is any [SyntaxNode]. +/// Nodes without a matching callback are traversed and reused unchanged. +/// +/// Because [SyntaxNode.toNode] always rebuilds from fields, return `null` to keep +/// a node unchanged — never `Pair(it, ...)`, as that would rebuild its subtree +/// (dropping inner comments/spacing). +function visit(node: Node, visitor: Visitor): Node = walk(node, (n) -> dispatch(n, visitor)) + +/// Fold [accumulate] over [node] and its descendants, top-down in pre-order. +/// +/// ``` +/// // count the if-expressions in a module +/// fold(module.node, 0, (acc, n) -> if (n.type == "if_expr") acc + 1 else acc) +/// ``` +function fold(node: Node, initial: Acc, accumulate: (Acc, Node) -> Acc): Acc = + node.children.fold(accumulate.apply(initial, node), (acc, child) -> fold(child, acc, accumulate)) + +/// Every node at or below [node], in pre-order. +/// +/// A convenience over [fold] for the common case of enumerating nodes. Combine with +/// [wrap] for type-safe access: +/// +/// ``` +/// // every imported URI in a module +/// descendants(module.node) +/// .map((n) -> wrap(n)) +/// .filterIsInstance(ImportNode) +/// .map((s) -> s.uri) +/// ``` +function descendants(node: Node): List = fold(node, List(), (acc, n) -> acc.add(n)) + +/// Wrap a raw [node] into its typed [SyntaxNode], or `null` if it has no typed form. +function wrap(_node: Node): SyntaxNode? = + if (isExprType(_node.type)) + wrapExpr(_node) + else if (isTypeType(_node.type)) + wrapTypeNode(_node) + else if (isObjectMemberType(_node.type)) + wrapObjectMember(_node) + else if (_node.type == "module") + new ModuleNode { node = _node } + else if (_node.type == "module_declaration") + new ModuleDeclarationNode { node = _node } + else if (_node.type == "import") + new ImportNode { node = _node } + else if (_node.type == "class") + new ClassNode { node = _node } + else if (_node.type == "typealias") + new TypeAliasNode { node = _node } + else if (_node.type == "class_body") + new ClassBodyNode { node = _node } + else if (_node.type == "class_property") + new ClassPropertyNode { node = _node } + else if (_node.type == "class_method") + new ClassMethodNode { node = _node } + else if (_node.type == "object_body") + new ObjectBodyNode { node = _node } + else if (_node.type == "annotation") + new AnnotationNode { node = _node } + else if (_node.type == "parameter") + new ParameterNode { node = _node } + else if (_node.type == "type_parameter") + new TypeParameterNode { node = _node } + else if (_node.type == "doc_comment") + new DocCommentNode { node = _node } + else + null + class Node { type: NodeType children: List @@ -194,28 +282,28 @@ typealias NodeType = | "constrained_type_elements" // Find first child of a given type within a node. -local const function findChild(n: Node, t: NodeType): Node? = - n.children.findOrNull((c) -> c.type == t) +local const function findChild(n: Node?, t: NodeType): Node? = + if (n == null) null else n.children.findOrNull((c) -> c.type == t) // Find all children of a given type. -local const function findChildren(n: Node, t: NodeType): List = - n.children.filter((c) -> c.type == t) +local const function findChildren(n: Node?, t: NodeType): List = + if (n == null) List() else n.children.filter((c) -> c.type == t) // Find the first child whose type is one of the expression types. -local const function findExprChild(n: Node): Node? = - n.children.findOrNull((c) -> isExprType(c.type)) +local const function findExprChild(n: Node?): Node? = + if (n == null) null else n.children.findOrNull((c) -> isExprType(c.type)) // Find all children whose type is one of the expression types. -local const function findExprChildren(n: Node): List = - n.children.filter((c) -> isExprType(c.type)) +local const function findExprChildren(n: Node?): List = + if (n == null) List() else n.children.filter((c) -> isExprType(c.type)) // Find the first child whose type is one of the type node types. -local const function findTypeChild(n: Node): Node? = - n.children.findOrNull((c) -> isTypeType(c.type)) +local const function findTypeChild(n: Node?): Node? = + if (n == null) null else n.children.findOrNull((c) -> isTypeType(c.type)) // Find all children whose type is one of the type node types. -local const function findTypeChildren(n: Node): List = - n.children.filter((c) -> isTypeType(c.type)) +local const function findTypeChildren(n: Node?): List = + if (n == null) List() else n.children.filter((c) -> isTypeType(c.type)) // Check if a NodeType represents an expression. local const function isExprType(t: NodeType): Boolean = @@ -388,21 +476,37 @@ local const function extractStringConstant(n: Node?): String = inner.map((c) -> c.text ?? "").join("") // Find and extract the string_chars of a node. -local const function getStringChars(node: Node): String = - let (sc = findChild(node, "string_chars")) - extractStringConstant(sc) +local const function getStringChars(node: Node?): String = + extractStringConstant(findChild(node, "string_chars")) + +// The dotted name of a `qualified_identifier` node (e.g. "a.b.c"). +local const function qualifiedName(qid: Node?): String = + findChildren(qid, "identifier").map((n) -> n.text ?? "").join(".") + +// The identifier text of the first `identifier` child of `node`. +local const function identifierText(node: Node?): String = findChild(node, "identifier")?.text ?? "" + +// The modifier keywords of a `modifier_list` child of `node`. +local const function modifiersOf(node: Node?): List = + let (ml = findChild(node, "modifier_list")) + if (ml == null) List() else findChildren(ml, "modifier").map((n) -> n.text ?? "") /// Base class for all typed syntax nodes. +/// +/// A typed node may be *backed* by a parsed [Node] (available via [node]) or +/// constructed from scratch (in which case [node] is `null`). Its typed fields +/// are read from [node] when backed, or supplied directly when constructed. abstract class SyntaxNode { - hidden node: Node + /// The original parsed node, or `null` when this was built from scratch. + hidden node: Node? = null - hidden children: List = node.children + hidden children: List = node?.children ?? List() - hidden text: String? = node.text + hidden text: String? = node?.text /// The source span of this node. @ConvertSpan - span: Span = node.span + span: Span = node?.span ?? new Span {} /// All terminal children (keywords, punctuation, operators). hidden terminals: List = children.filter((n) -> n.type == "terminal") @@ -410,25 +514,26 @@ abstract class SyntaxNode { /// All comment children. hidden comments: List = children.filter((n) -> n.type == "line_comment" || n.type == "block_comment") + + /// Rebuild this into a generic [Node]. + /// + /// Always constructs a fresh node from this node's fields. + abstract function toNode(): Node } /// Base class for expression nodes. abstract class Expr extends SyntaxNode { - /// Convert this expression node to its builder. - function toBuilder(): ExprBuilder = throw("toBuilder() not implemented for \(this.getClass())") + abstract function toNode(): Node } /// Base class for type nodes. abstract class TypeNode extends SyntaxNode { - /// Convert this type node to its builder. - function toBuilder(): TypeBuilder = throw("toBuilder() not implemented for \(this.getClass())") + abstract function toNode(): Node } /// Base class for object member nodes. abstract class ObjectMemberNode extends SyntaxNode { - /// Convert this object member node to its builder. - function toBuilder(): ObjectMemberBuilder = - throw("toBuilder() not implemented for \(this.getClass())") + abstract function toNode(): Node } /// The top-level module node. @@ -436,10 +541,7 @@ class ModuleNode extends SyntaxNode { /// The module declaration, if present. declaration: ModuleDeclarationNode? = let (n = findChild(node, "module_declaration")) - if (n == null) - null - else - new ModuleDeclarationNode { node = n } + if (n == null) null else new ModuleDeclarationNode { node = n } /// All imports in this module. imports: List = @@ -464,15 +566,25 @@ class ModuleNode extends SyntaxNode { methods: List = findChildren(node, "class_method").map((n) -> new ClassMethodNode { node = n }) - function toBuilder(): ModuleBuilder = + function toNode(): Node = let (self = this) - new ModuleBuilder { - declaration = self.declaration?.toBuilder() - imports = self.imports.map((i) -> i.toBuilder()) - classes = self.classes.map((c) -> c.toBuilder()) - typeAliases = self.typeAliases.map((t) -> t.toBuilder()) - properties = self.properties.map((p) -> p.toBuilder()) - methods = self.methods.map((m) -> m.toBuilder()) + new Node { + type = "module" + children = + (if (self.declaration == null) List() else List(self.declaration.toNode())) + + ( + if (self.imports.isEmpty) + List() + else + List(new Node { + type = "import_list" + children = self.imports.map((i) -> i.toNode()) + }) + ) + + self.classes.map((c) -> c.toNode()) + + self.typeAliases.map((t) -> t.toNode()) + + self.properties.map((p) -> p.toNode()) + + self.methods.map((m) -> m.toNode()) } } @@ -483,81 +595,74 @@ class ModuleDeclarationNode extends SyntaxNode { /// The doc comment on the module declaration, if present. docComment: DocCommentNode? = let (n = findChild(node, "doc_comment")) - if (n == null) - null - else - new DocCommentNode { node = n } + if (n == null) null else new DocCommentNode { node = n } /// Annotations on the module declaration. annotations: List = findChildren(node, "annotation").map((n) -> new AnnotationNode { node = n }) - /// The modifier list on the module declaration, if present. - modifiers: ModifierListNode? = - if (moduleDefinition == null) - null - else - let (n = findChild(moduleDefinition, "modifier_list")) - if (n == null) - null - else - new ModifierListNode { node = n } + /// The modifiers on the module declaration. + modifiers: List = if (moduleDefinition == null) List() else modifiersOf(moduleDefinition) /// The qualified name of the module, if present. - name: QualifiedIdentifierNode? = + name: String? = if (moduleDefinition == null) null else let (n = findChild(moduleDefinition, "qualified_identifier")) - if (n == null) - null - else - new QualifiedIdentifierNode { node = n } + if (n == null) null else qualifiedName(n) - /// The amends clause, if present. - amendsClause: AmendsClauseNode? = + /// The URI string of the amended module, if any. Mutually exclusive with [extendsUri]. + amendsUri: String? = let (n = findChild(node, "amends_clause")) - if (n == null) - null - else - new AmendsClauseNode { node = n } + if (n == null) null else getStringChars(n) - /// The extends clause, if present. - extendsClause: ExtendsClauseNode? = + /// The URI string of the extended module, if any. Mutually exclusive with [amendsUri]. + extendsUri: String? = let (n = findChild(node, "extends_clause")) - if (n == null) - null - else - new ExtendsClauseNode { node = n } + if (n == null) null else getStringChars(n) - function toBuilder(): ModuleDeclarationBuilder = + function toNode(): Node = let (self = this) - new ModuleDeclarationBuilder { - docComment = self.docComment?.toBuilder() - annotations = self.annotations.map((a) -> a.toBuilder()) - modifiers = self.modifiers?.modifiers ?? List() - name = - if (self.name == null) - null - else - self.name.identifiers.map((i) -> i.value).join(".") - amendsUri = self.amendsClause?.uri - extendsUri = self.extendsClause?.uri + new Node { + type = "module_declaration" + children = + (if (self.docComment == null) List() else List(self.docComment.toNode())) + + self.annotations.map((a) -> a.toNode()) + + ( + if (self.name != null) + List(new Node { + type = "module_definition" + children = + (if (self.modifiers.isEmpty) List() else List(modifierListNode(self.modifiers))) + + List( + (terminal) { text = "module" }, + qualifiedIdentifierNode(self.name!!), + ) + }) + else if (!self.modifiers.isEmpty) + List(modifierListNode(self.modifiers)) + else + List() + ) + + ( + if (self.amendsUri != null) + List(new Node { + type = "amends_clause" + children = List((terminal) { text = "amends" }, stringCharsNode(self.amendsUri!!)) + }) + else if (self.extendsUri != null) + List(new Node { + type = "extends_clause" + children = + List((terminal) { text = "extends" }, stringCharsNode(self.extendsUri!!)) + }) + else + List() + ) } } -/// An `amends "..."` clause. -class AmendsClauseNode extends SyntaxNode { - /// The URI string of the amended module. - uri: String = getStringChars(node) -} - -/// An `extends "..."` clause. -class ExtendsClauseNode extends SyntaxNode { - /// The URI string of the extended module. - uri: String = getStringChars(node) -} - /// An import declaration. class ImportNode extends SyntaxNode { /// Whether this is a glob import (`import*`). @@ -567,132 +672,137 @@ class ImportNode extends SyntaxNode { uri: String = getStringChars(node) /// The alias for this import, if present. - alias: IdentifierNode? = + alias: String? = let (aliasNode = findChild(node, "import_alias")) - if (aliasNode == null) - null - else - let (id = findChild(aliasNode, "identifier")) - if (id == null) - null - else - new IdentifierNode { node = id } + if (aliasNode == null) null else identifierText(aliasNode) - function toBuilder(): ImportBuilder = + function toNode(): Node = let (self = this) - new ImportBuilder { - uri = self.uri - isGlob = self.isGlob - alias = self.alias?.value + new Node { + type = "import" + children = + List( + (terminal) { text = if (self.isGlob) "import*" else "import" }, + stringCharsNode(self.uri), + ) + + ( + if (self.alias == null) + List() + else + List(new Node { + type = "import_alias" + children = + List((terminal) { text = "as" }, (identifierLeaf) { text = self.alias!! }) + }) + ) } } /// A class declaration. class ClassNode extends SyntaxNode { - local header: Node = findChild(node, "class_header")!! + local header: Node? = findChild(node, "class_header") /// The doc comment, if present. docComment: DocCommentNode? = let (n = findChild(node, "doc_comment")) - if (n == null) - null - else - new DocCommentNode { node = n } + if (n == null) null else new DocCommentNode { node = n } /// Annotations on the class. annotations: List = findChildren(node, "annotation").map((n) -> new AnnotationNode { node = n }) - /// The modifier list, if present. - modifiers: ModifierListNode? = - let (n = findChild(header, "modifier_list")) - if (n == null) - null - else - new ModifierListNode { node = n } + /// The modifiers on the class. + modifiers: List = modifiersOf(header) /// The class name. - name: IdentifierNode = - let (n = findChild(header, "identifier")) - new IdentifierNode { node = n!! } - - /// The type parameter list, if present. - typeParameterList: TypeParameterListNode? = - let (n = findChild(header, "type_parameter_list")) - if (n == null) - null + name: String = identifierText(header) + + /// The type parameters. + typeParameters: List = + let (tpl = findChild(header, "type_parameter_list")) + if (tpl == null) + List() else - new TypeParameterListNode { node = n } + let (elems = findChild(tpl, "type_parameter_list_elements")) + if (elems == null) + List() + else + findChildren(elems, "type_parameter").map((n) -> new TypeParameterNode { node = n }) /// The supertype this class extends, if present. - extendsClause: TypeNode? = + extendsType: TypeNode? = let (ext = findChild(header, "class_header_extends")) if (ext == null) null else let (t = findTypeChild(ext)) - if (t == null) - null - else - wrapTypeNode(t) + if (t == null) null else wrapTypeNode(t) /// The class body, if present. body: ClassBodyNode? = let (n = findChild(node, "class_body")) - if (n == null) - null - else - new ClassBodyNode { node = n } + if (n == null) null else new ClassBodyNode { node = n } - function toBuilder(): ClassBuilder = + function toNode(): Node = let (self = this) - new ClassBuilder { - docComment = self.docComment?.toBuilder() - annotations = self.annotations.map((a) -> a.toBuilder()) - modifiers = self.modifiers?.modifiers ?? List() - name = self.name.value - typeParameters = self.typeParameterList?.typeParameters?.map((t) -> t.toBuilder()) ?? List() - extendsType = self.extendsClause?.toBuilder() - body = self.body?.toBuilder() + new Node { + type = "class" + children = + (if (self.docComment == null) List() else List(self.docComment.toNode())) + + self.annotations.map((a) -> a.toNode()) + + List(new Node { + type = "class_header" + children = + (if (self.modifiers.isEmpty) List() else List(modifierListNode(self.modifiers))) + + List( + (terminal) { text = "class" }, + (identifierLeaf) { text = self.name }, + ) + + typeParameterListNodes(self.typeParameters) + + ( + if (self.extendsType == null) + List() + else + List(new Node { + type = "class_header_extends" + children = List((terminal) { text = "extends" }, self.extendsType.toNode()) + }) + ) + }) + + (if (self.body == null) List() else List(self.body.toNode())) } } /// A typealias declaration. class TypeAliasNode extends SyntaxNode { - local header: Node = findChild(node, "typealias_header")!! + local header: Node? = findChild(node, "typealias_header") /// The doc comment, if present. docComment: DocCommentNode? = let (n = findChild(node, "doc_comment")) - if (n == null) - null - else - new DocCommentNode { node = n } + if (n == null) null else new DocCommentNode { node = n } /// Annotations on the typealias. annotations: List = findChildren(node, "annotation").map((n) -> new AnnotationNode { node = n }) - /// The modifier list, if present. - modifiers: ModifierListNode? = - let (n = findChild(header, "modifier_list")) - if (n == null) - null - else - new ModifierListNode { node = n } + /// The modifiers on the typealias. + modifiers: List = modifiersOf(header) /// The typealias name. - name: IdentifierNode = - let (n = findChild(header, "identifier")) - new IdentifierNode { node = n!! } - - /// The type parameter list, if present. - typeParameterList: TypeParameterListNode? = - let (n = findChild(header, "type_parameter_list")) - if (n == null) - null + name: String = identifierText(header) + + /// The type parameters. + typeParameters: List = + let (tpl = findChild(header, "type_parameter_list")) + if (tpl == null) + List() else - new TypeParameterListNode { node = n } + let (elems = findChild(tpl, "type_parameter_list_elements")) + if (elems == null) + List() + else + findChildren(elems, "type_parameter").map((n) -> new TypeParameterNode { node = n }) /// The type that this alias resolves to. type: TypeNode = @@ -700,15 +810,28 @@ class TypeAliasNode extends SyntaxNode { let (t = findTypeChild(body!!)) wrapTypeNode(t!!) - function toBuilder(): TypeAliasBuilder = + function toNode(): Node = let (self = this) - new TypeAliasBuilder { - docComment = self.docComment?.toBuilder() - annotations = self.annotations.map((a) -> a.toBuilder()) - modifiers = self.modifiers?.modifiers ?? List() - name = self.name.value - typeParameters = self.typeParameterList?.typeParameters?.map((t) -> t.toBuilder()) ?? List() - type = self.type.toBuilder() + new Node { + type = "typealias" + children = + (if (self.docComment == null) List() else List(self.docComment.toNode())) + + self.annotations.map((a) -> a.toNode()) + + List(new Node { + type = "typealias_header" + children = + (if (self.modifiers.isEmpty) List() else List(modifierListNode(self.modifiers))) + + List( + (terminal) { text = "typealias" }, + (identifierLeaf) { text = self.name }, + ) + + typeParameterListNodes(self.typeParameters) + + List((terminal) { text = "=" }) + }) + + List(new Node { + type = "typealias_body" + children = List(self.type.toNode()) + }) } } @@ -730,51 +853,50 @@ class ClassBodyNode extends SyntaxNode { else findChildren(elements, "class_method").map((n) -> new ClassMethodNode { node = n }) - function toBuilder(): ClassBodyBuilder = + function toNode(): Node = let (self = this) - new ClassBodyBuilder { - properties = self.properties.map((p) -> p.toBuilder()) - methods = self.methods.map((m) -> m.toBuilder()) + new Node { + type = "class_body" + children = + List((terminal) { text = "{" }) + + ( + let ( + members = + self.properties.map((p) -> p.toNode()) + self.methods.map((m) -> m.toNode()) + ) + if (members.isEmpty) + List() + else + List(new Node { type = "class_body_elements"; children = members }) + ) + + List((terminal) { text = "}" }) } } /// A class property declaration. class ClassPropertyNode extends SyntaxNode { - local header: Node = findChild(node, "class_property_header")!! - local headerBegin: Node = findChild(header, "class_property_header_begin")!! + local propHeader: Node? = findChild(node, "class_property_header") + local headerBegin: Node? = findChild(propHeader, "class_property_header_begin") /// The doc comment, if present. docComment: DocCommentNode? = let (n = findChild(node, "doc_comment")) - if (n == null) - null - else - new DocCommentNode { node = n } + if (n == null) null else new DocCommentNode { node = n } /// Annotations on the property. annotations: List = findChildren(node, "annotation").map((n) -> new AnnotationNode { node = n }) - /// The modifier list, if present. - modifiers: ModifierListNode? = - let (n = findChild(headerBegin, "modifier_list")) - if (n == null) - null - else - new ModifierListNode { node = n } + /// The modifiers on the property. + modifiers: List = modifiersOf(headerBegin) /// The property name. - name: IdentifierNode = - let (n = findChild(headerBegin, "identifier")) - new IdentifierNode { node = n!! } + name: String = identifierText(headerBegin) /// The type annotation, if present. - typeAnnotation: TypeAnnotationNode? = - let (n = findChild(header, "type_annotation")) - if (n == null) - null - else - new TypeAnnotationNode { node = n } + typeAnnotation: TypeNode? = + let (n = findChild(propHeader, "type_annotation")) + if (n == null) null else wrapTypeNode(findTypeChild(n)!!) /// The value expression, if present (from `= expr`). value: Expr? = @@ -783,101 +905,126 @@ class ClassPropertyNode extends SyntaxNode { null else let (e = findExprChild(body)) - if (e == null) - null - else - wrapExpr(e) + if (e == null) null else wrapExpr(e) /// Object bodies for amending (from `{ ... }` blocks). objectBodies: List = findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) - function toBuilder(): ClassPropertyBuilder = + function toNode(): Node = let (self = this) - new ClassPropertyBuilder { - docComment = self.docComment?.toBuilder() - annotations = self.annotations.map((a) -> a.toBuilder()) - modifiers = self.modifiers?.modifiers ?? List() - name = self.name.value - typeAnnotation = self.typeAnnotation?.type?.toBuilder() - value = self.value?.toBuilder() - objectBodies = self.objectBodies.map((b) -> b.toBuilder()) + new Node { + type = "class_property" + children = + (if (self.docComment == null) List() else List(self.docComment.toNode())) + + self.annotations.map((a) -> a.toNode()) + + List(new Node { + type = "class_property_header" + children = + List(new Node { + type = "class_property_header_begin" + children = + (if (self.modifiers.isEmpty) List() else List(modifierListNode(self.modifiers))) + + List((identifierLeaf) { text = self.name }) + }) + + typeAnnotationNodes(self.typeAnnotation) + }) + + ( + if (self.value != null) + List( + (terminal) { text = "=" }, + new Node { + type = "class_property_body" + children = List(self.value.toNode()) + }, + ) + else + self.objectBodies.map((b) -> b.toNode()) + ) } } /// A class method declaration. class ClassMethodNode extends SyntaxNode { - local methodHeader: Node = findChild(node, "class_method_header")!! + local methodHeader: Node? = findChild(node, "class_method_header") /// The doc comment, if present. docComment: DocCommentNode? = let (n = findChild(node, "doc_comment")) - if (n == null) - null - else - new DocCommentNode { node = n } + if (n == null) null else new DocCommentNode { node = n } /// Annotations on the method. annotations: List = findChildren(node, "annotation").map((n) -> new AnnotationNode { node = n }) - /// The modifier list, if present. - modifiers: ModifierListNode? = - let (n = findChild(methodHeader, "modifier_list")) - if (n == null) - null - else - new ModifierListNode { node = n } + /// The modifiers on the method. + modifiers: List = modifiersOf(methodHeader) /// The method name. - name: IdentifierNode = - let (n = findChild(methodHeader, "identifier")) - new IdentifierNode { node = n!! } - - /// The type parameter list, if present. - typeParameterList: TypeParameterListNode? = - let (n = findChild(node, "type_parameter_list")) - if (n == null) - null + name: String = identifierText(methodHeader) + + /// The type parameters. + typeParameters: List = + let (tpl = findChild(node, "type_parameter_list")) + if (tpl == null) + List() else - new TypeParameterListNode { node = n } + let (elems = findChild(tpl, "type_parameter_list_elements")) + if (elems == null) + List() + else + findChildren(elems, "type_parameter").map((n) -> new TypeParameterNode { node = n }) - /// The parameter list. - parameterList: ParameterListNode = - let (n = findChild(node, "parameter_list")) - new ParameterListNode { node = n!! } + /// The parameters. + parameters: List = + let (pl = findChild(node, "parameter_list")) + parametersOf(pl!!) /// The return type annotation, if present. - returnType: TypeAnnotationNode? = + returnType: TypeNode? = let (n = findChild(node, "type_annotation")) - if (n == null) - null - else - new TypeAnnotationNode { node = n } + if (n == null) null else wrapTypeNode(findTypeChild(n)!!) - /// The method body expression, if present. + /// The method body expression, if present. Null for abstract methods. body: Expr? = let (bodyNode = findChild(node, "class_method_body")) if (bodyNode == null) null else let (e = findExprChild(bodyNode)) - if (e == null) - null - else - wrapExpr(e) + if (e == null) null else wrapExpr(e) - function toBuilder(): ClassMethodBuilder = + function toNode(): Node = let (self = this) - new ClassMethodBuilder { - docComment = self.docComment?.toBuilder() - annotations = self.annotations.map((a) -> a.toBuilder()) - modifiers = self.modifiers?.modifiers ?? List() - name = self.name.value - typeParameters = self.typeParameterList?.typeParameters?.map((t) -> t.toBuilder()) ?? List() - parameters = self.parameterList.parameters.map((p) -> p.toBuilder()) - returnType = self.returnType?.type?.toBuilder() - body = self.body?.toBuilder() + new Node { + type = "class_method" + children = + (if (self.docComment == null) List() else List(self.docComment.toNode())) + + self.annotations.map((a) -> a.toNode()) + + List(new Node { + type = "class_method_header" + children = + (if (self.modifiers.isEmpty) List() else List(modifierListNode(self.modifiers))) + + List( + (terminal) { text = "function" }, + (identifierLeaf) { text = self.name }, + ) + }) + + typeParameterListNodes(self.typeParameters) + + List(parameterListNode(self.parameters)) + + typeAnnotationNodes(self.returnType) + + ( + if (self.body == null) + List() + else + List( + (terminal) { text = "=" }, + new Node { + type = "class_method_body" + children = List(self.body.toNode()) + }, + ) + ) } } @@ -902,67 +1049,51 @@ class ObjectBodyNode extends SyntaxNode { .filter((c) -> isObjectMemberType(c.type)) .map((n) -> wrapObjectMember(n)) - /// Object properties in this body. - properties: List = - if (memberList == null) - List() - else - findChildren(memberList, "object_property").map((n) -> new ObjectPropertyNode { node = n }) - - /// Object methods in this body. - methods: List = - if (memberList == null) - List() - else - findChildren(memberList, "object_method").map((n) -> new ObjectMethodNode { node = n }) - - /// Object elements in this body. - elements: List = - if (memberList == null) - List() - else - findChildren(memberList, "object_element").map((n) -> new ObjectElementNode { node = n }) - - /// Object entries in this body. - entries: List = - if (memberList == null) - List() - else - findChildren(memberList, "object_entry").map((n) -> new ObjectEntryNode { node = n }) - - function toBuilder(): ObjectBodyBuilder = + function toNode(): Node = let (self = this) - new ObjectBodyBuilder { - parameters = self.parameters.map((p) -> p.toBuilder()) - members = self.members.map((m) -> m.toBuilder()) + new Node { + type = "object_body" + children = + List((terminal) { text = "{" }) + + ( + if (self.parameters.isEmpty) + List() + else + List(new Node { + type = "object_parameter_list" + children = + commaSeparate(self.parameters.map((p) -> p.toNode())) + .add((terminal) { text = "->" }) + }) + ) + + ( + if (self.members.isEmpty) + List() + else + List(new Node { + type = "object_member_list" + children = self.members.map((m) -> m.toNode()) + }) + ) + + List((terminal) { text = "}" }) } } /// An object property declaration. class ObjectPropertyNode extends ObjectMemberNode { - local header: Node = findChild(node, "object_property_header")!! - local headerBegin: Node = findChild(header, "object_property_header_begin")!! + local propHeader: Node? = findChild(node, "object_property_header") + local headerBegin: Node? = findChild(propHeader, "object_property_header_begin") - /// The modifier list, if present. - modifiers: ModifierListNode? = - let (n = findChild(headerBegin, "modifier_list")) - if (n == null) - null - else - new ModifierListNode { node = n } + /// The modifiers on the property. + modifiers: List = modifiersOf(headerBegin) /// The property name. - name: IdentifierNode = - let (n = findChild(headerBegin, "identifier")) - new IdentifierNode { node = n!! } + name: String = identifierText(headerBegin) /// The type annotation, if present. - typeAnnotation: TypeAnnotationNode? = - let (n = findChild(header, "type_annotation")) - if (n == null) - null - else - new TypeAnnotationNode { node = n } + typeAnnotation: TypeNode? = + let (n = findChild(propHeader, "type_annotation")) + if (n == null) null else wrapTypeNode(findTypeChild(n)!!) /// The value expression, if present (from `= expr`). value: Expr? = @@ -971,85 +1102,104 @@ class ObjectPropertyNode extends ObjectMemberNode { null else let (e = findExprChild(body)) - if (e == null) - null - else - wrapExpr(e) + if (e == null) null else wrapExpr(e) /// Object bodies for amending. objectBodies: List = findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) - function toBuilder(): ObjectPropertyBuilder = + function toNode(): Node = let (self = this) - new ObjectPropertyBuilder { - modifiers = self.modifiers?.modifiers ?? List() - name = self.name.value - typeAnnotation = self.typeAnnotation?.type?.toBuilder() - value = self.value?.toBuilder() - objectBodies = self.objectBodies.map((b) -> b.toBuilder()) + new Node { + type = "object_property" + children = + List(new Node { + type = "object_property_header" + children = + List(new Node { + type = "object_property_header_begin" + children = + (if (self.modifiers.isEmpty) List() else List(modifierListNode(self.modifiers))) + + List((identifierLeaf) { text = self.name }) + }) + + typeAnnotationNodes(self.typeAnnotation) + }) + + ( + if (self.value != null) + List( + (terminal) { text = "=" }, + new Node { + type = "object_property_body" + children = List(self.value.toNode()) + }, + ) + else + self.objectBodies.map((b) -> b.toNode()) + ) } } /// An object method declaration. class ObjectMethodNode extends ObjectMemberNode { - local methodHeader: Node = findChild(node, "class_method_header")!! + local methodHeader: Node? = findChild(node, "class_method_header") - /// The modifier list, if present. - modifiers: ModifierListNode? = - let (n = findChild(methodHeader, "modifier_list")) - if (n == null) - null - else - new ModifierListNode { node = n } + /// The modifiers on the method. + modifiers: List = modifiersOf(methodHeader) /// The method name. - name: IdentifierNode = - let (n = findChild(methodHeader, "identifier")) - new IdentifierNode { node = n!! } - - /// The type parameter list, if present. - typeParameterList: TypeParameterListNode? = - let (n = findChild(node, "type_parameter_list")) - if (n == null) - null + name: String = identifierText(methodHeader) + + /// The type parameters. + typeParameters: List = + let (tpl = findChild(node, "type_parameter_list")) + if (tpl == null) + List() else - new TypeParameterListNode { node = n } + let (elems = findChild(tpl, "type_parameter_list_elements")) + if (elems == null) + List() + else + findChildren(elems, "type_parameter").map((n) -> new TypeParameterNode { node = n }) - /// The parameter list. - parameterList: ParameterListNode = - let (n = findChild(node, "parameter_list")) - new ParameterListNode { node = n!! } + /// The parameters. + parameters: List = + let (pl = findChild(node, "parameter_list")) + parametersOf(pl!!) /// The return type annotation, if present. - returnType: TypeAnnotationNode? = + returnType: TypeNode? = let (n = findChild(node, "type_annotation")) - if (n == null) - null - else - new TypeAnnotationNode { node = n } + if (n == null) null else wrapTypeNode(findTypeChild(n)!!) /// The method body expression. - body: Expr? = + body: Expr = let (bodyNode = findChild(node, "class_method_body")) - if (bodyNode == null) - null - else - let (e = findExprChild(bodyNode)) - if (e == null) - null - else - wrapExpr(e) + wrapExpr(findExprChild(bodyNode!!)!!) - function toBuilder(): ObjectMethodBuilder = + function toNode(): Node = let (self = this) - new ObjectMethodBuilder { - modifiers = self.modifiers?.modifiers ?? List() - name = self.name.value - typeParameters = self.typeParameterList?.typeParameters?.map((t) -> t.toBuilder()) ?? List() - parameters = self.parameterList.parameters.map((p) -> p.toBuilder()) - returnType = self.returnType?.type?.toBuilder() - body = self.body!!.toBuilder() + new Node { + type = "object_method" + children = + List(new Node { + type = "class_method_header" + children = + (if (self.modifiers.isEmpty) List() else List(modifierListNode(self.modifiers))) + + List( + (terminal) { text = "function" }, + (identifierLeaf) { text = self.name }, + ) + }) + + typeParameterListNodes(self.typeParameters) + + List(parameterListNode(self.parameters)) + + typeAnnotationNodes(self.returnType) + + List( + (terminal) { text = "=" }, + new Node { + type = "class_method_body" + children = List(self.body.toNode()) + }, + ) } } @@ -1058,14 +1208,17 @@ class ObjectElementNode extends ObjectMemberNode { /// The expression value. expression: Expr = wrapExpr(findExprChild(node)!!) - function toBuilder(): ObjectElementBuilder = + function toNode(): Node = let (self = this) - new ObjectElementBuilder { expression = self.expression.toBuilder() } + new Node { + type = "object_element" + children = List(self.expression.toNode()) + } } /// An object entry (`[key] = value` or `[key] { ... }`). class ObjectEntryNode extends ObjectMemberNode { - local entryHeader: Node = findChild(node, "object_entry_header")!! + local entryHeader: Node? = findChild(node, "object_entry_header") /// The key expression. key: Expr = wrapExpr(findExprChild(entryHeader)!!) @@ -1073,21 +1226,33 @@ class ObjectEntryNode extends ObjectMemberNode { /// The value expression, if present (from `[key] = value`). value: Expr? = let (e = findExprChildren(node).findOrNull((c) -> c != findExprChild(entryHeader))) - if (e == null) - null - else - wrapExpr(e) + if (e == null) null else wrapExpr(e) /// Object bodies for amending. objectBodies: List = findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) - function toBuilder(): ObjectEntryBuilder = + function toNode(): Node = let (self = this) - new ObjectEntryBuilder { - key = self.key.toBuilder() - value = self.value?.toBuilder() - objectBodies = self.objectBodies.map((b) -> b.toBuilder()) + new Node { + type = "object_entry" + children = + List(new Node { + type = "object_entry_header" + children = + List( + (terminal) { text = "[" }, + self.key.toNode(), + (terminal) { text = "]" }, + ) + + (if (self.value != null) List((terminal) { text = "=" }) else List()) + }) + + ( + if (self.value != null) + List(self.value.toNode()) + else + self.objectBodies.map((b) -> b.toNode()) + ) } } @@ -1099,155 +1264,206 @@ class ObjectSpreadNode extends ObjectMemberNode { /// The spread expression. expression: Expr = wrapExpr(findExprChild(node)!!) - function toBuilder(): ObjectSpreadBuilder = + function toNode(): Node = let (self = this) - new ObjectSpreadBuilder { - expression = self.expression.toBuilder() - isNullable = self.isNullable + new Node { + type = "object_spread" + children = + List( + (terminal) { text = if (self.isNullable) "...?" else "..." }, + self.expression.toNode(), + ) } } /// A member predicate (`[[condition]] = value` or `[[condition]] { ... }`). class MemberPredicateNode extends ObjectMemberNode { - local exprs = findExprChildren(node) + local exprs: List = findExprChildren(node) /// The condition expression. condition: Expr = wrapExpr(exprs.first) /// The value expression, if present. - value: Expr? = - if (exprs.length < 2) - null - else - wrapExpr(exprs[1]) + value: Expr? = if (exprs.length < 2) null else wrapExpr(exprs[1]) /// Object bodies for amending. objectBodies: List = findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) - function toBuilder(): MemberPredicateBuilder = + function toNode(): Node = let (self = this) - new MemberPredicateBuilder { - condition = self.condition.toBuilder() - value = self.value?.toBuilder() - objectBodies = self.objectBodies.map((b) -> b.toBuilder()) + new Node { + type = "member_predicate" + children = + List( + (terminal) { text = "[[" }, + self.condition.toNode(), + (terminal) { text = "]" }, + (terminal) { text = "]" }, + ) + + ( + if (self.value != null) + List((terminal) { text = "=" }, self.value.toNode()) + else + self.objectBodies.map((b) -> b.toNode()) + ) } } /// A `for (param in iterable) { ... }` generator. class ForGeneratorNode extends ObjectMemberNode { - local forHeader: Node = findChild(node, "for_generator_header")!! - local forDef: Node = findChild(forHeader, "for_generator_header_definition")!! - local forDefHeader: Node = findChild(forDef, "for_generator_header_definition_header")!! + local forHeader: Node? = findChild(node, "for_generator_header") + local forDef: Node? = findChild(forHeader, "for_generator_header_definition") + local forDefHeader: Node? = findChild(forDef, "for_generator_header_definition_header") local paramNodes: List = findChildren(forDefHeader, "parameter") /// The key parameter (first parameter when two are present), if present. keyParameter: ParameterNode? = - if (paramNodes.length < 2) - null - else - new ParameterNode { node = paramNodes.first } + if (paramNodes.length < 2) null else new ParameterNode { node = paramNodes.first } /// The value parameter (or the only parameter when just one is present). valueParameter: ParameterNode = new ParameterNode { node = paramNodes.last } /// The iterable expression. - iterable: Expr = - let (e = findExprChild(forDef)) - wrapExpr(e!!) + iterable: Expr = wrapExpr(findExprChild(forDef)!!) /// The body. body: ObjectBodyNode = let (n = findChild(node, "object_body")) new ObjectBodyNode { node = n!! } - function toBuilder(): ForGeneratorBuilder = + function toNode(): Node = let (self = this) - new ForGeneratorBuilder { - keyParameter = self.keyParameter?.toBuilder() - valueParameter = self.valueParameter.toBuilder() - iterable = self.iterable.toBuilder() - body = self.body.toBuilder() + new Node { + type = "for_generator" + children = + List( + (terminal) { text = "for" }, + new Node { + type = "for_generator_header" + children = + List( + (terminal) { text = "(" }, + new Node { + type = "for_generator_header_definition" + children = + List( + new Node { + type = "for_generator_header_definition_header" + children = + ( + if (self.keyParameter == null) + List(self.valueParameter.toNode()) + else + List( + self.keyParameter.toNode(), + (terminal) { text = "," }, + self.valueParameter.toNode(), + ) + ) + + List((terminal) { text = "in" }) + }, + self.iterable.toNode(), + ) + }, + (terminal) { text = ")" }, + ) + }, + self.body.toNode(), + ) } } /// A `when (condition) { ... }` generator. class WhenGeneratorNode extends ObjectMemberNode { - local whenHeader: Node = findChild(node, "when_generator_header")!! + local whenHeader: Node? = findChild(node, "when_generator_header") local bodyNodes: List = findChildren(node, "object_body") /// The condition expression. - condition: Expr = - let (e = findExprChild(whenHeader)) - wrapExpr(e!!) + condition: Expr = wrapExpr(findExprChild(whenHeader)!!) /// The "then" body. thenBody: ObjectBodyNode = new ObjectBodyNode { node = bodyNodes.first } /// The "else" body, if present. elseBody: ObjectBodyNode? = - if (bodyNodes.length < 2) - null - else - new ObjectBodyNode { node = bodyNodes[1] } + if (bodyNodes.length < 2) null else new ObjectBodyNode { node = bodyNodes[1] } - function toBuilder(): WhenGeneratorBuilder = + function toNode(): Node = let (self = this) - new WhenGeneratorBuilder { - condition = self.condition.toBuilder() - thenBody = self.thenBody.toBuilder() - elseBody = self.elseBody?.toBuilder() + new Node { + type = "when_generator" + children = + List( + (terminal) { text = "when" }, + new Node { + type = "when_generator_header" + children = + List( + (terminal) { text = "(" }, + self.condition.toNode(), + (terminal) { text = ")" }, + ) + }, + self.thenBody.toNode(), + ) + + ( + if (self.elseBody == null) + List() + else + List((terminal) { text = "else" }, self.elseBody.toNode()) + ) } } /// The `this` expression. class ThisExprNode extends Expr { - function toBuilder(): ThisExprBuilder = new ThisExprBuilder {} + function toNode(): Node = new Node { type = "this_expr"; text = "this" } } /// The `outer` expression. class OuterExprNode extends Expr { - function toBuilder(): OuterExprBuilder = new OuterExprBuilder {} + function toNode(): Node = new Node { type = "outer_expr"; text = "outer" } } /// The `module` expression. class ModuleExprNode extends Expr { - function toBuilder(): ModuleExprBuilder = new ModuleExprBuilder {} + function toNode(): Node = new Node { type = "module_expr"; text = "module" } } /// A `null` literal expression. class NullLiteralExprNode extends Expr { - function toBuilder(): NullLiteralBuilder = new NullLiteralBuilder {} + function toNode(): Node = new Node { type = "null_expr"; text = "null" } } /// A boolean literal expression (`true` or `false`). class BoolLiteralExprNode extends Expr { /// The boolean value. - value: Boolean = node.text == "true" + value: Boolean = node?.text == "true" - function toBuilder(): BoolLiteralBuilder = + function toNode(): Node = let (self = this) - new BoolLiteralBuilder { value = self.value } + new Node { type = "bool_literal_expr"; text = if (self.value) "true" else "false" } } /// An integer literal expression. class IntLiteralExprNode extends Expr { - /// The raw text of the integer literal. - text: String = node.text ?? "" + /// The integer literal (e.g. `42`, `"0xFF"`). + value: Int | String = node?.text ?? "" - function toBuilder(): IntLiteralBuilder = + function toNode(): Node = let (self = this) - new IntLiteralBuilder { value = self.text } + new Node { type = "int_literal_expr"; text = self.value.toString() } } /// A float literal expression. class FloatLiteralExprNode extends Expr { - /// The raw text of the float literal. - text: String = node.text ?? "" + /// The float literal (e.g. `3.14`, `"1.0e10"`). + value: Float | String = node?.text ?? "" - function toBuilder(): FloatLiteralBuilder = + function toNode(): Node = let (self = this) - new FloatLiteralBuilder { value = self.text } + new Node { type = "float_literal_expr"; text = self.value.toString() } } /// A single-line string literal expression. @@ -1255,101 +1471,136 @@ class SingleLineStringLiteralExprNode extends Expr { /// The string parts (chars, escapes, interpolations). parts: List = buildStringParts(children) - function toBuilder(): StringLiteralBuilder = + function toNode(): Node = let (self = this) - new StringLiteralBuilder { - parts = self.parts.map((p) -> p.toBuilder()).toList() + new Node { + type = "single_line_string_literal_expr" + children = + List((terminal) { text = "\"" }) + + self.parts.flatMap((p) -> p.toNodes()) + + List((terminal) { text = "\"" }) } } /// A multi-line string literal expression. +/// +/// Use [StringNewlineNode] entries in [parts] to separate lines. class MultiLineStringLiteralExprNode extends Expr { - /// The string parts (chars, escapes, interpolations). + /// The string parts (chars, escapes, newlines, interpolations). parts: List = buildStringParts(children) - function toBuilder(): MultiLineStringLiteralBuilder = - let (self = this) - new MultiLineStringLiteralBuilder { - parts = self.parts.map((p) -> p.toBuilder()).toList() + function toNode(): Node = + let (self = this) + new Node { + type = "multi_line_string_literal_expr" + children = + List((terminal) { text = "\"\"\"" }) + + self.parts.flatMap((p) -> p.toNodes()) + + List(new Node { + type = "terminal" + text = "\"\"\"" + // formatter uses span.colStart of the closing `"""` to determine the + // indentation to strip from each content line. + span = new Span { colStart = 1 } + }) } } /// An unqualified access expression (`name` or `name(args)`). class UnqualifiedAccessExprNode extends Expr { /// The identifier being accessed. - identifier: IdentifierNode = - let (n = findChild(node, "identifier")) - new IdentifierNode { node = n!! } + identifier: String = identifierText(node!!) - /// The argument list, if present. - argumentList: ArgumentListNode? = + /// The arguments, if this is a function call. Null for a plain identifier access. + arguments: List? = let (n = findChild(node, "argument_list")) - if (n == null) - null - else - new ArgumentListNode { node = n } + if (n == null) null else argumentsOf(n) - function toBuilder(): ExprBuilder = + function toNode(): Node = let (self = this) - if (self.argumentList == null) - new IdentifierExprBuilder { name = self.identifier.value } - else - new FunctionCallBuilder { - name = self.identifier.value - arguments = self.argumentList.arguments.map((a) -> a.toBuilder()).toList() - } + new Node { + type = "unqualified_access_expr" + children = + List((identifierLeaf) { text = self.identifier }) + + (if (self.arguments == null) List() else List(argumentListNode(self.arguments!!))) + } } -/// A qualified access expression (`receiver.member` or `receiver?.member`). +/// A qualified access expression (`receiver.member` or `receiver?.member`, +/// optionally with arguments for method calls). class QualifiedAccessExprNode extends Expr { /// The receiver expression. - receiver: Expr = - let (exprs = findExprChildren(node)) - wrapExpr(exprs.first) + receiver: Expr = wrapExpr(findExprChildren(node).first) /// Whether this is a null-safe access (`?.`). isNullSafe: Boolean = findChild(node, "operator")?.text == "?." - /// The accessed member. - member: UnqualifiedAccessExprNode = - let (n = findChildren(node, "unqualified_access_expr").last) - new UnqualifiedAccessExprNode { node = n } - - function toBuilder(): QualifiedAccessBuilder = - let (self = this) - new QualifiedAccessBuilder { - receiver = self.receiver.toBuilder() - member = self.member.identifier.value - isNullSafe = self.isNullSafe - arguments = self.member.argumentList?.arguments?.map((a) -> a.toBuilder()) + /// The accessed member name. + member: String = + let (m = findChildren(node, "unqualified_access_expr").last) + identifierText(m) + + /// The arguments, if this is a method call. Null for a property access. + arguments: List? = + let (m = findChildren(node, "unqualified_access_expr").last) + let (n = findChild(m, "argument_list")) + if (n == null) null else argumentsOf(n) + + function toNode(): Node = + let (self = this) + new Node { + type = "qualified_access_expr" + children = + List( + self.receiver.toNode(), + (operatorLeaf) { text = if (self.isNullSafe) "?." else "." }, + new Node { + type = "unqualified_access_expr" + children = + List((identifierLeaf) { text = self.member }) + + ( + if (self.arguments == null) List() else List(argumentListNode(self.arguments!!)) + ) + }, + ) } } /// A subscript expression (`receiver[index]`). class SubscriptExprNode extends Expr { /// The receiver expression. - receiver: Expr = - let (exprs = findExprChildren(node)) - wrapExpr(exprs.first) + receiver: Expr = wrapExpr(findExprChildren(node).first) /// The index expression. - index: Expr = - let (exprs = findExprChildren(node)) - wrapExpr(exprs[1]) - - function toBuilder(): SubscriptBuilder = - let (self = this) - new SubscriptBuilder { - receiver = self.receiver.toBuilder() - index = self.index.toBuilder() + index: Expr = wrapExpr(findExprChildren(node)[1]) + + function toNode(): Node = + let (self = this) + new Node { + type = "subscript_expr" + children = + List( + self.receiver.toNode(), + (operatorLeaf) { text = "[" }, + self.index.toNode(), + (terminal) { text = "]" }, + ) } } /// A `super.member` access expression. -class SuperAccessExprNode extends Expr {} +/// +/// Read-only: this node has no builder and cannot be constructed from scratch. +class SuperAccessExprNode extends Expr { + function toNode(): Node = node!! +} /// A `super[index]` subscript expression. -class SuperSubscriptExprNode extends Expr {} +/// +/// Read-only: this node has no builder and cannot be constructed from scratch. +class SuperSubscriptExprNode extends Expr { + function toNode(): Node = node!! +} /// An `if (condition) thenExpr else elseExpr` expression. class IfExprNode extends Expr { @@ -1358,28 +1609,53 @@ class IfExprNode extends Expr { local ifConditionExpr: Node = findChild(ifCondition, "if_condition_expr")!! /// The condition expression. - condition: Expr = - let (e = findExprChild(ifConditionExpr)) - wrapExpr(e!!) + condition: Expr = wrapExpr(findExprChild(ifConditionExpr)!!) /// The then-branch expression. thenExpr: Expr = let (thenNode = findChild(node, "if_then_expr")) - let (e = findExprChild(thenNode!!)) - wrapExpr(e!!) + wrapExpr(findExprChild(thenNode!!)!!) /// The else-branch expression. elseExpr: Expr = let (elseNode = findChild(node, "if_else_expr")) - let (e = findExprChild(elseNode!!)) - wrapExpr(e!!) + wrapExpr(findExprChild(elseNode!!)!!) - function toBuilder(): IfExprBuilder = + function toNode(): Node = let (self = this) - new IfExprBuilder { - condition = self.condition.toBuilder() - thenExpr = self.thenExpr.toBuilder() - elseExpr = self.elseExpr.toBuilder() + new Node { + type = "if_expr" + children = + List( + new Node { + type = "if_header" + children = + List( + (terminal) { text = "if" }, + new Node { + type = "if_condition" + children = + List( + (terminal) { text = "(" }, + new Node { + type = "if_condition_expr" + children = List(self.condition.toNode()) + }, + (terminal) { text = ")" }, + ) + }, + ) + }, + new Node { + type = "if_then_expr" + children = List(self.thenExpr.toNode()) + }, + (terminal) { text = "else" }, + new Node { + type = "if_else_expr" + children = List(self.elseExpr.toNode()) + }, + ) } } @@ -1394,20 +1670,37 @@ class LetExprNode extends Expr { new ParameterNode { node = p!! } /// The binding value expression. - bindingValue: Expr = - let (e = findExprChild(letParam)) - wrapExpr(e!!) + bindingValue: Expr = wrapExpr(findExprChild(letParam)!!) /// The body expression. - bodyExpr: Expr = wrapExpr(findExprChild(node)!!) + body: Expr = wrapExpr(findExprChild(node)!!) - function toBuilder(): LetExprBuilder = + function toNode(): Node = let (self = this) - new LetExprBuilder { - parameterName = if (self.parameter.isWildcard) "_" else self.parameter.identifier!!.value - parameterType = self.parameter.typeAnnotation?.type?.toBuilder() - bindingValue = self.bindingValue.toBuilder() - body = self.bodyExpr.toBuilder() + new Node { + type = "let_expr" + children = + List( + (terminal) { text = "let" }, + new Node { + type = "let_parameter_definition" + children = + List( + (terminal) { text = "(" }, + new Node { + type = "let_parameter" + children = + List( + self.parameter.toNode(), + (terminal) { text = "=" }, + self.bindingValue.toNode(), + ) + }, + (terminal) { text = ")" }, + ) + }, + self.body.toNode(), + ) } } @@ -1416,9 +1709,18 @@ class ThrowExprNode extends Expr { /// The expression being thrown. expression: Expr = wrapExpr(findExprChild(node)!!) - function toBuilder(): ThrowExprBuilder = + function toNode(): Node = let (self = this) - new ThrowExprBuilder { expression = self.expression.toBuilder() } + new Node { + type = "throw_expr" + children = + List( + (terminal) { text = "throw" }, + (terminal) { text = "(" }, + self.expression.toNode(), + (terminal) { text = ")" }, + ) + } } /// A `trace(expr)` expression. @@ -1426,9 +1728,18 @@ class TraceExprNode extends Expr { /// The expression being traced. expression: Expr = wrapExpr(findExprChild(node)!!) - function toBuilder(): TraceExprBuilder = + function toNode(): Node = let (self = this) - new TraceExprBuilder { expression = self.expression.toBuilder() } + new Node { + type = "trace_expr" + children = + List( + (terminal) { text = "trace" }, + (terminal) { text = "(" }, + self.expression.toNode(), + (terminal) { text = ")" }, + ) + } } /// An `import("uri")` or `import*("uri")` expression. @@ -1439,11 +1750,17 @@ class ImportExprNode extends Expr { /// The import URI string. uri: String = getStringChars(node) - function toBuilder(): ImportExprBuilder = + function toNode(): Node = let (self = this) - new ImportExprBuilder { - uri = self.uri - isGlob = self.isGlob + new Node { + type = "import_expr" + children = + List( + (terminal) { text = if (self.isGlob) "import*" else "import" }, + (terminal) { text = "(" }, + stringCharsNode(self.uri), + (terminal) { text = ")" }, + ) } } @@ -1453,14 +1770,20 @@ class ReadExprNode extends Expr { keyword: "read" | "read?" | "read*" = (terminals.firstOrNull?.text ?? "read") as "read" | "read?" | "read*" - // The expression to be read - expr: Expr = wrapExpr(findExprChild(node)!!) + /// The expression to be read. + expression: Expr = wrapExpr(findExprChild(node)!!) - function toBuilder(): ReadExprBuilder = + function toNode(): Node = let (self = this) - new ReadExprBuilder { - expression = self.expr.toBuilder() - keyword = self.keyword + new Node { + type = "read_expr" + children = + List( + (terminal) { text = self.keyword }, + (terminal) { text = "(" }, + self.expression.toNode(), + (terminal) { text = ")" }, + ) } } @@ -1471,87 +1794,86 @@ class NewExprNode extends Expr { /// The type being constructed, if present. type: TypeNode? = let (t = findTypeChild(newHeader)) - if (t == null) - null - else - wrapTypeNode(t) + if (t == null) null else wrapTypeNode(t) /// The object body. body: ObjectBodyNode = let (n = findChild(node, "object_body")) new ObjectBodyNode { node = n!! } - function toBuilder(): NewExprBuilder = + function toNode(): Node = let (self = this) - new NewExprBuilder { - type = self.type?.toBuilder() - body = self.body.toBuilder() + new Node { + type = "new_expr" + children = + List( + new Node { + type = "new_header" + children = + if (self.type == null) + List((terminal) { text = "new" }) + else + List((terminal) { text = "new" }, self.type.toNode()) + }, + self.body.toNode(), + ) } } /// An `(expr) { ... }` amends expression. class AmendsExprNode extends Expr { /// The expression being amended. - parentExpr: Expr = - let (exprs = findExprChildren(node)) - wrapExpr(exprs.first) + parentExpr: Expr = wrapExpr(findExprChildren(node).first) /// The object body. body: ObjectBodyNode = let (n = findChild(node, "object_body")) new ObjectBodyNode { node = n!! } - function toBuilder(): AmendsExprBuilder = + function toNode(): Node = let (self = this) - new AmendsExprBuilder { - parentExpr = self.parentExpr.toBuilder() - body = self.body.toBuilder() + new Node { + type = "amends_expr" + children = List(self.parentExpr.toNode(), self.body.toNode()) } } -/// A binary operator expression (`left op right`). +/// A binary operator expression (`left op right`), including `is`/`as`. class BinaryOpExprNode extends Expr { - local exprs = findExprChildren(node) + local exprs: List = findExprChildren(node) /// The operator string. operator: String = findChild(node, "operator")?.text ?? "" /// The left-hand expression. - leftExpr: Expr = wrapExpr(exprs.first) + left: Expr = wrapExpr(exprs.first) - /// The right-hand expression, if present (not present for `is`/`as` which use a type). - rightExpr: Expr? = - if (exprs.length < 2) - null - else - wrapExpr(exprs[1]) + /// The right-hand expression, if present (not present for `is`/`as` which use [rightType]). + right: Expr? = if (exprs.length < 2) null else wrapExpr(exprs[1]) /// The right-hand type, if this is an `is` or `as` operation. rightType: TypeNode? = let (t = findTypeChild(node)) - if (t == null) - null - else - wrapTypeNode(t) + if (t == null) null else wrapTypeNode(t) - function toBuilder(): ExprBuilder = + function toNode(): Node = let (self = this) - if (self.operator == "is") - new IsExprBuilder { - operand = self.leftExpr.toBuilder() - type = self.rightType!!.toBuilder() - } - else if (self.operator == "as") - new AsExprBuilder { - operand = self.leftExpr.toBuilder() - type = self.rightType!!.toBuilder() - } - else - new BinaryOpExprBuilder { - left = self.leftExpr.toBuilder() - operator = self.operator - right = self.rightExpr!!.toBuilder() - } + new Node { + type = "binary_op_expr" + children = + if (self.operator == "is" || self.operator == "as") + List( + self.left.toNode(), + (operatorLeaf) { text = self.operator }, + self.rightType!!.toNode(), + ) + else + List( + self.left.toNode(), + (operatorLeaf) { text = self.operator }, + self.right!!.toNode(), + ) + } } /// A unary minus expression (`-expr`). @@ -1559,9 +1881,12 @@ class UnaryMinusExprNode extends Expr { /// The operand expression. operand: Expr = wrapExpr(findExprChild(node)!!) - function toBuilder(): UnaryMinusExprBuilder = + function toNode(): Node = let (self = this) - new UnaryMinusExprBuilder { operand = self.operand.toBuilder() } + new Node { + type = "unary_minus_expr" + children = List((terminal) { text = "-" }, self.operand.toNode()) + } } /// A logical not expression (`!expr`). @@ -1569,9 +1894,12 @@ class LogicalNotExprNode extends Expr { /// The operand expression. operand: Expr = wrapExpr(findExprChild(node)!!) - function toBuilder(): LogicalNotExprBuilder = + function toNode(): Node = let (self = this) - new LogicalNotExprBuilder { operand = self.operand.toBuilder() } + new Node { + type = "logical_not_expr" + children = List((terminal) { text = "!" }, self.operand.toNode()) + } } /// A non-null assertion expression (`expr!!`). @@ -1579,29 +1907,39 @@ class NonNullExprNode extends Expr { /// The operand expression. operand: Expr = wrapExpr(findExprChild(node)!!) - function toBuilder(): NonNullExprBuilder = + function toNode(): Node = let (self = this) - new NonNullExprBuilder { operand = self.operand.toBuilder() } + new Node { + type = "non_null_expr" + children = List(self.operand.toNode(), (operatorLeaf) { text = "!!" }) + } } /// A function literal expression (`(params) -> body`). class FunctionLiteralExprNode extends Expr { - /// The parameter list. - parameterList: ParameterListNode = - let (n = findChild(node, "parameter_list")) - new ParameterListNode { node = n!! } + /// The parameters. + parameters: List = + let (pl = findChild(node, "parameter_list")) + parametersOf(pl!!) /// The body expression. body: Expr = let (bodyNode = findChild(node, "function_literal_body")) - let (e = findExprChild(bodyNode!!)) - wrapExpr(e!!) - - function toBuilder(): FunctionLiteralBuilder = - let (self = this) - new FunctionLiteralBuilder { - parameters = self.parameterList.parameters.map((p) -> p.toBuilder()).toList() - body = self.body.toBuilder() + wrapExpr(findExprChild(bodyNode!!)!!) + + function toNode(): Node = + let (self = this) + new Node { + type = "function_literal_expr" + children = + List( + parameterListNode(self.parameters), + (terminal) { text = "->" }, + new Node { + type = "function_literal_body" + children = List(self.body.toNode()) + }, + ) } } @@ -1614,51 +1952,75 @@ class ParenthesizedExprNode extends Expr { null else let (e = findExprChild(elems)) - if (e == null) - null - else - wrapExpr(e) - - function toBuilder(): ParenthesizedExprBuilder = - let (self = this) - new ParenthesizedExprBuilder { expression = self.expression!!.toBuilder() } + if (e == null) null else wrapExpr(e) + + function toNode(): Node = + let (self = this) + new Node { + type = "parenthesized_expr" + children = + List( + (terminal) { text = "(" }, + new Node { + type = "parenthesized_expr_elements" + children = List(self.expression!!.toNode()) + }, + (terminal) { text = ")" }, + ) + } } /// The `unknown` type. class UnknownTypeNode extends TypeNode { - function toBuilder(): UnknownTypeBuilder = new UnknownTypeBuilder {} + function toNode(): Node = new Node { type = "unknown_type"; text = "unknown" } } /// The `nothing` type. class NothingTypeNode extends TypeNode { - function toBuilder(): NothingTypeBuilder = new NothingTypeBuilder {} + function toNode(): Node = new Node { type = "nothing_type"; text = "nothing" } } /// The `module` type. class ModuleTypeNode extends TypeNode { - function toBuilder(): ModuleTypeBuilder = new ModuleTypeBuilder {} + function toNode(): Node = new Node { type = "module_type"; text = "module" } } /// A declared type (e.g., `String`, `List`). class DeclaredTypeNode extends TypeNode { - /// The type name. - name: QualifiedIdentifierNode = + /// The type name (dotted, e.g. `"List"` or `"foo.Bar"`). + name: String = let (n = findChild(node, "qualified_identifier")) - new QualifiedIdentifierNode { node = n!! } + qualifiedName(n!!) - /// The type argument list, if present. - typeArgumentList: TypeArgumentListNode? = - let (n = findChild(node, "type_argument_list")) - if (n == null) - null + /// The type arguments. + typeArguments: List = + let (tal = findChild(node, "type_argument_list")) + if (tal == null) + List() else - new TypeArgumentListNode { node = n } + let (elems = findChild(tal, "type_argument_list_elements")) + if (elems == null) List() else findTypeChildren(elems).map((n) -> wrapTypeNode(n)) - function toBuilder(): DeclaredTypeBuilder = + function toNode(): Node = let (self = this) - new DeclaredTypeBuilder { - name = self.name.identifiers.map((i) -> i.value).join(".") - typeArguments = self.typeArgumentList?.typeArguments?.map((t) -> t.toBuilder()) ?? List() + new Node { + type = "declared_type" + children = + if (self.typeArguments.isEmpty) + List(qualifiedIdentifierNode(self.name)) + else + List(qualifiedIdentifierNode(self.name), new Node { + type = "type_argument_list" + children = + List( + (terminal) { text = "<" }, + new Node { + type = "type_argument_list_elements" + children = commaSeparate(self.typeArguments.map((t) -> t.toNode())) + }, + (terminal) { text = ">" }, + ) + }) } } @@ -1667,9 +2029,12 @@ class NullableTypeNode extends TypeNode { /// The base type. baseType: TypeNode = wrapTypeNode(findTypeChild(node)!!) - function toBuilder(): NullableTypeBuilder = + function toNode(): Node = let (self = this) - new NullableTypeBuilder { baseType = self.baseType.toBuilder() } + new Node { + type = "nullable_type" + children = List(self.baseType.toNode(), (terminal) { text = "?" }) + } } /// A union type (`TypeA|TypeB|TypeC`). @@ -1677,9 +2042,17 @@ class UnionTypeNode extends TypeNode { /// The member types. members: List = findTypeChildren(node).map((n) -> wrapTypeNode(n)) - function toBuilder(): UnionTypeBuilder = + function toNode(): Node = let (self = this) - new UnionTypeBuilder { members = self.members.map((m) -> m.toBuilder()) } + new Node { + type = "union_type" + children = + self.members + .map((m) -> m.toNode()) + .fold(List(), (acc: List, item: Node) -> + if (acc.isEmpty) List(item) else acc.add((terminal) { text = "|" }).add(item) + ) + } } /// A function type (`(ParamTypes) -> ReturnType`). @@ -1689,21 +2062,35 @@ class FunctionTypeNode extends TypeNode { /// The parameter types. parameterTypes: List = - if (paramElems == null) - List() - else - findTypeChildren(paramElems).map((n) -> wrapTypeNode(n)) + if (paramElems == null) List() else findTypeChildren(paramElems).map((n) -> wrapTypeNode(n)) /// The return type. - returnType: TypeNode = - let (types = findTypeChildren(node)) - wrapTypeNode(types.last) + returnType: TypeNode = wrapTypeNode(findTypeChildren(node).last) - function toBuilder(): FunctionTypeBuilder = + function toNode(): Node = let (self = this) - new FunctionTypeBuilder { - parameterTypes = self.parameterTypes.map((t) -> t.toBuilder()) - returnType = self.returnType.toBuilder() + new Node { + type = "function_type" + children = + List( + new Node { + type = "function_type_parameters" + children = + if (self.parameterTypes.isEmpty) + List((terminal) { text = "(" }, (terminal) { text = ")" }) + else + List( + (terminal) { text = "(" }, + new Node { + type = "parenthesized_type_elements" + children = commaSeparate(self.parameterTypes.map((t) -> t.toNode())) + }, + (terminal) { text = ")" }, + ) + }, + (terminal) { text = "->" }, + self.returnType.toNode(), + ) } } @@ -1718,11 +2105,23 @@ class ConstrainedTypeNode extends TypeNode { /// The constraint expressions. constraints: List = findExprChildren(constraintElems).map((n) -> wrapExpr(n)) - function toBuilder(): ConstrainedTypeBuilder = + function toNode(): Node = let (self = this) - new ConstrainedTypeBuilder { - baseType = self.baseType.toBuilder() - constraints = self.constraints.map((c) -> c.toBuilder()) + new Node { + type = "constrained_type" + children = + List(self.baseType.toNode(), new Node { + type = "constrained_type_constraint" + children = + List( + (terminal) { text = "(" }, + new Node { + type = "constrained_type_elements" + children = commaSeparate(self.constraints.map((c) -> c.toNode())) + }, + (terminal) { text = ")" }, + ) + }) } } @@ -1735,14 +2134,22 @@ class ParenthesizedTypeNode extends TypeNode { null else let (t = findTypeChild(elems)) - if (t == null) - null - else - wrapTypeNode(t) - - function toBuilder(): ParenthesizedTypeBuilder = - let (self = this) - new ParenthesizedTypeBuilder { type = self.type!!.toBuilder() } + if (t == null) null else wrapTypeNode(t) + + function toNode(): Node = + let (self = this) + new Node { + type = "parenthesized_type" + children = + List( + (terminal) { text = "(" }, + new Node { + type = "parenthesized_type_elements" + children = List(self.type!!.toNode()) + }, + (terminal) { text = ")" }, + ) + } } /// A string constant type (e.g., `"foo"`). @@ -1750,9 +2157,12 @@ class StringConstantTypeNode extends TypeNode { /// The string value. value: String = getStringChars(node) - function toBuilder(): StringConstantTypeBuilder = + function toNode(): Node = let (self = this) - new StringConstantTypeBuilder { value = self.value } + new Node { + type = "string_constant_type" + children = List(stringCharsNode(self.value)) + } } /// An annotation (`@Type { ... }`). @@ -1763,196 +2173,138 @@ class AnnotationNode extends SyntaxNode { /// The annotation body, if present. body: ObjectBodyNode? = let (n = findChild(node, "object_body")) - if (n == null) - null - else - new ObjectBodyNode { node = n } + if (n == null) null else new ObjectBodyNode { node = n } - function toBuilder(): AnnotationBuilder = + function toNode(): Node = let (self = this) - new AnnotationBuilder { - type = self.type.toBuilder() - body = self.body?.toBuilder() + new Node { + type = "annotation" + children = + List((terminal) { text = "@" }, self.type.toNode()) + + (if (self.body == null) List() else List(self.body.toNode())) } } -/// A parameter declaration. +/// A parameter declaration (`name`, `name: Type`, or `_`). class ParameterNode extends SyntaxNode { /// Whether this is a wildcard parameter (`_`). - isWildcard: Boolean = - identifier == null - && children.findOrNull((c) -> c.type == "terminal" && c.text == "_") != null - - /// The parameter identifier, if not a wildcard. - identifier: IdentifierNode? = - let (n = findChild(node, "identifier")) - if (n == null) - null + isWildcard: Boolean = name == "_" + + /// The parameter name. Use `"_"` for a wildcard parameter. + name: String = + let (id = findChild(node, "identifier")) + if (id != null) + id.text ?? "" + else if (children.findOrNull((c) -> c.type == "terminal" && c.text == "_") != null) + "_" else - new IdentifierNode { node = n } + "" /// The type annotation, if present. - typeAnnotation: TypeAnnotationNode? = + typeAnnotation: TypeNode? = let (n = findChild(node, "type_annotation")) - if (n == null) - null - else - new TypeAnnotationNode { node = n } + if (n == null) null else wrapTypeNode(findTypeChild(n)!!) - function toBuilder(): ParameterBuilder = + function toNode(): Node = let (self = this) - new ParameterBuilder { - name = if (self.isWildcard) "_" else self.identifier!!.value - typeAnnotation = self.typeAnnotation?.type?.toBuilder() + new Node { + type = "parameter" + children = + if (self.name == "_") + List((terminal) { text = "_" }) + else if (self.typeAnnotation == null) + List((identifierLeaf) { text = self.name }) + else + List((identifierLeaf) { text = self.name }) + + typeAnnotationNodes(self.typeAnnotation) } } -/// A parameter list (`(param1, param2)`). -class ParameterListNode extends SyntaxNode { - local elems: Node? = findChild(node, "parameter_list_elements") - - /// The parameters in this list. - parameters: List = - if (elems == null) - List() - else - findChildren(elems, "parameter").map((n) -> new ParameterNode { node = n }) -} - -/// An argument list (`(arg1, arg2)`). -class ArgumentListNode extends SyntaxNode { - local elems: Node? = findChild(node, "argument_list_elements") - - /// The argument expressions. - arguments: List = - if (elems == null) - List() - else - findExprChildren(elems).map((n) -> wrapExpr(n)) -} - -/// A type annotation (`: Type`). -class TypeAnnotationNode extends SyntaxNode { - /// The type. - type: TypeNode = wrapTypeNode(findTypeChild(node)!!) -} - -/// A type parameter declaration. +/// A type parameter declaration (`T`, `in T`, or `out T`). class TypeParameterNode extends SyntaxNode { /// The variance modifier (`"in"`, `"out"`, or null). variance: ("in" | "out")? = terminals.findOrNull((t) -> t.text == "in" || t.text == "out")?.text as ("in" | "out")? /// The type parameter name. - name: IdentifierNode = - let (n = findChild(node, "identifier")) - new IdentifierNode { node = n!! } + name: String = identifierText(node!!) - function toBuilder(): TypeParameterBuilder = + function toNode(): Node = let (self = this) - new TypeParameterBuilder { - name = self.name.value - variance = self.variance + new Node { + type = "type_parameter" + children = + if (self.variance == null) + List((identifierLeaf) { text = self.name }) + else + List((terminal) { text = self.variance!! }, (identifierLeaf) { text = self.name }) } } -/// A type parameter list (``). -class TypeParameterListNode extends SyntaxNode { - local elems: Node? = findChild(node, "type_parameter_list_elements") - - /// The type parameters. - typeParameters: List = - if (elems == null) - List() - else - findChildren(elems, "type_parameter").map((n) -> new TypeParameterNode { node = n }) -} - -/// A type argument list (``). -class TypeArgumentListNode extends SyntaxNode { - local elems: Node? = findChild(node, "type_argument_list_elements") - - /// The type arguments. - typeArguments: List = - if (elems == null) - List() - else - findTypeChildren(elems).map((n) -> wrapTypeNode(n)) -} - -/// An identifier node. -class IdentifierNode extends SyntaxNode { - /// The identifier text. - value: String = node.text ?? "" - // this node doesn't have a toBuilder because parent nodes use strings directly -} - -/// A qualified identifier (`a.b.c`). -class QualifiedIdentifierNode extends SyntaxNode { - /// The identifiers in this qualified name. - identifiers: List = - findChildren(node, "identifier").map((n) -> new IdentifierNode { node = n }) - // this node doesn't have a toBuilder because parent nodes use strings directly -} - /// A doc comment. class DocCommentNode extends SyntaxNode { - /// The text of each doc comment line. - lines: List = findChildren(node, "doc_comment_line").map((n) -> n.text ?? "") + /// The body text of each line, without the leading `///`. + lines: List = + findChildren(node, "doc_comment_line") + .map((n) -> + let (l = n.text ?? "") + if (l.startsWith("///")) l.drop(3) else l + ) - function toBuilder(): DocCommentBuilder = + function toNode(): Node = let (self = this) - new DocCommentBuilder { - lines = self.lines.map((l) -> if (l.startsWith("///")) l.drop(3) else l) + new Node { + type = "doc_comment" + children = self.lines.map((l) -> new Node { type = "doc_comment_line"; text = "///" + l }) } } -/// A modifier list (e.g., `open`, `abstract external`). -class ModifierListNode extends SyntaxNode { - /// The modifier keywords. - modifiers: List = findChildren(node, "modifier").map((n) -> n.text ?? "") -} +/// Base class for parts of a string literal (text, escapes, newlines, interpolations). +abstract class StringPartNode { + /// The original parsed node, or `null` when built from scratch. + hidden node: Node? = null -/// Base class for parts of a string literal. -abstract class StringPartNode extends SyntaxNode { - /// Convert this string part to its builder. - function toBuilder(): StringPartBuilder = - throw("toBuilder() not implemented for \(this.getClass())") + /// Build the list of [Node] parts for this string part. + abstract function toNodes(): List } /// A plain text part of a string literal. class StringCharsNode extends StringPartNode { /// The text content. - value: String = node.text ?? "" + value: String = node?.text ?? "" - function toBuilder(): StringCharsBuilder = + function toNodes(): List = let (self = this) - new StringCharsBuilder { value = self.value } + List(new Node { type = "string_chars"; text = self.value }) } -/// An escape sequence in a string literal. +/// An escape sequence in a string literal (e.g., `"\\n"`, `"\\t"`). class StringEscapeNode extends StringPartNode { - /// The escape sequence text. - value: String = node.text ?? "" + /// The escape sequence text including the leading backslash. + value: String = node?.text ?? "" - function toBuilder(): StringEscapeBuilder = + function toNodes(): List = let (self = this) - new StringEscapeBuilder { value = self.value } + List(new Node { type = "string_escape"; text = self.value }) } /// A newline in a multi-line string literal. class StringNewlineNode extends StringPartNode { - function toBuilder(): StringNewlineBuilder = new StringNewlineBuilder {} + function toNodes(): List = List(new Node { type = "string_newline" }) } -/// An interpolation in a string literal. +/// An interpolation in a string literal (`\(expr)`). class StringInterpolationNode extends StringPartNode { /// The interpolated expression. - expression: Expr = wrapExpr(node) + expression: Expr = wrapExpr(node!!) - function toBuilder(): StringInterpolationBuilder = + function toNodes(): List = let (self = this) - new StringInterpolationBuilder { expression = self.expression.toBuilder() } + List( + (terminal) { text = "\\(" }, + self.expression.toNode(), + (terminal) { text = ")" }, + ) } /// Build string parts from the children of a string literal node. @@ -1995,8 +2347,7 @@ local const function isInterpolationStart(n: Node): Boolean = if (t == null) false else - t.endsWith("(") - && (t.startsWith("\\") || t.startsWith("#")) + t.endsWith("(") && (t.startsWith("\\") || t.startsWith("#")) /// Find the interpolation expression and return it along with the index past the closing paren. local const function findInterpolationExpr(cs: List, startIdx: Int): Pair = @@ -2023,7 +2374,7 @@ local const function findNextNonAffix(cs: List, startIdx: Int): Int = startIdx // =============== -// Builders +// Node construction helpers // =============== local const terminal: Node = new Node { type = "terminal" } @@ -2060,1777 +2411,262 @@ local const function qualifiedIdentifierNode(qname: String): Node = new Node { ) } -/// Base class for all syntax builders. -abstract class Builder { - /// Affix nodes (comments, semicolons) to prepend before this node. - prefixes: List - - /// Affix nodes (comments, semicolons) to append after this node. - suffixes: List - - /// Build the typed syntax node. - abstract function build(): SyntaxNode -} - -/// Base class for expression builders. -abstract class ExprBuilder extends Builder { - abstract function build(): Expr -} - -/// Base class for type builders. -abstract class TypeBuilder extends Builder { - abstract function build(): TypeNode -} - -/// Base class for object member builders. -abstract class ObjectMemberBuilder extends Builder { - abstract function build(): ObjectMemberNode -} - -/// Builds an integer literal expression. -class IntLiteralBuilder extends ExprBuilder { - value: Int | String - - function build(): IntLiteralExprNode = new IntLiteralExprNode { - node = new Node { type = "int_literal_expr"; text = value.toString() } - } -} - -/// Builds a float literal expression. -class FloatLiteralBuilder extends ExprBuilder { - value: Float | String - - function build(): FloatLiteralExprNode = new FloatLiteralExprNode { - node = new Node { type = "float_literal_expr"; text = value.toString() } - } -} - -/// Builds a boolean literal expression. -class BoolLiteralBuilder extends ExprBuilder { - value: Boolean - - function build(): BoolLiteralExprNode = new BoolLiteralExprNode { - node = new Node { type = "bool_literal_expr"; text = if (value) "true" else "false" } - } -} - -/// Builds a `null` literal expression. -class NullLiteralBuilder extends ExprBuilder { - function build(): NullLiteralExprNode = new NullLiteralExprNode { - node = new Node { type = "null_expr"; text = "null" } - } -} - -/// Builds a `this` expression. -class ThisExprBuilder extends ExprBuilder { - function build(): ThisExprNode = new ThisExprNode { - node = new Node { type = "this_expr"; text = "this" } - } -} - -/// Builds an `outer` expression. -class OuterExprBuilder extends ExprBuilder { - function build(): OuterExprNode = new OuterExprNode { - node = new Node { type = "outer_expr"; text = "outer" } - } -} - -/// Builds a `module` expression. -class ModuleExprBuilder extends ExprBuilder { - function build(): ModuleExprNode = new ModuleExprNode { - node = new Node { type = "module_expr"; text = "module" } - } -} - -/// Builds an unqualified access expression (identifier or function call). -class IdentifierExprBuilder extends ExprBuilder { - name: String - - function build(): UnqualifiedAccessExprNode = new UnqualifiedAccessExprNode { - node = new Node { - type = "unqualified_access_expr" - children = List((identifierLeaf) { text = name }) - } - } -} - -/// Builds a single-line string literal expression. -/// -/// Set `value` for plain text, or `parts` for strings that contain escapes or interpolations. -/// `parts` defaults to a single chars node for the value, so amending only `value` is the simple path. -class StringLiteralBuilder extends ExprBuilder { - /// Convenience for plain-text strings. Sets `parts` to a single [StringCharsBuilder]. - value: String? - - /// String parts (chars, escapes, interpolations). Defaults to wrapping `value`, or empty. - parts: List = - let (self = this) - if (value != null) - List(new StringCharsBuilder { value = self.value!! }) - else - List() - - function build(): SingleLineStringLiteralExprNode = - let (self = this) - new SingleLineStringLiteralExprNode { - node = new Node { - type = "single_line_string_literal_expr" - children = - List((terminal) { text = "\"" }) - + self.parts.flatMap((p) -> p.buildNodes()) - + List((terminal) { text = "\"" }) - } - } -} - -/// Base class for parts of string literal (text, escapes, newlines, interpolations). -abstract class StringPartBuilder { - /// Build the list of `Node` parts. - abstract function buildNodes(): List -} - -/// Plain text content within a string literal. -class StringCharsBuilder extends StringPartBuilder { - value: String - - function buildNodes(): List = List(new Node { type = "string_chars"; text = value }) -} - -/// An escape sequence in a string literal (e.g., `"\\n"`, `"\\t"`). -class StringEscapeBuilder extends StringPartBuilder { - /// The escape sequence text including the leading backslash (e.g., `"\\n"`). - value: String - - function buildNodes(): List = List(new Node { type = "string_escape"; text = value }) +// Build a quoted `string_chars` node for a string constant like `"foo"`. +// Parsed nodes derive `text` from source, built nodes must set it themselves. +local const function stringCharsNode(value: String): Node = new Node { + type = "string_chars" + text = "\"" + value + "\"" + children = + List( + (terminal) { text = "\"" }, + (terminal) { text = value }, + (terminal) { text = "\"" }, + ) } -/// A newline in a multi-line string literal. -class StringNewlineBuilder extends StringPartBuilder { - function buildNodes(): List = List(new Node { type = "string_newline" }) -} +// Read the parameters of a `parameter_list` node. +local const function parametersOf(pl: Node?): List = + let (elems = findChild(pl, "parameter_list_elements")) + if (elems == null) + List() + else + findChildren(elems, "parameter").map((n) -> new ParameterNode { node = n }) -/// An interpolation in a string literal (`\(expr)`). -class StringInterpolationBuilder extends StringPartBuilder { - expression: ExprBuilder +// Read the argument expressions of an `argument_list` node. +local const function argumentsOf(al: Node?): List = + let (elems = findChild(al, "argument_list_elements")) + if (elems == null) List() else findExprChildren(elems).map((n) -> wrapExpr(n)) - function buildNodes(): List = - let (self = this) +// Build a `parameter_list` node from typed parameters. +local const function parameterListNode(parameters: List): Node = new Node { + type = "parameter_list" + children = + if (parameters.isEmpty) + List((terminal) { text = "(" }, (terminal) { text = ")" }) + else List( - (terminal) { text = "\\(" }, - self.expression.build().node, + (terminal) { text = "(" }, + new Node { + type = "parameter_list_elements" + children = commaSeparate(parameters.map((p) -> p.toNode())) + }, (terminal) { text = ")" }, ) } -/// Builds a multi-line string literal expression. -/// -/// Use [StringNewlineBuilder] entries in `parts` to separate lines. -class MultiLineStringLiteralBuilder extends ExprBuilder { - parts: List - - function build(): MultiLineStringLiteralExprNode = - let (self = this) - new MultiLineStringLiteralExprNode { - node = new Node { - type = "multi_line_string_literal_expr" - children = - List((terminal) { text = "\"\"\"" }) - + self.parts.flatMap((p) -> p.buildNodes()).toList() - + List(new Node { - type = "terminal" - text = "\"\"\"" - // formatter uses span.colStart of the closing `"""` to determine the - // indentation to strip from each content line. - span = new Span { colStart = 1 } - }) - } - } -} - -/// Builds a unary minus expression (`-expr`). -class UnaryMinusExprBuilder extends ExprBuilder { - operand: ExprBuilder - - function build(): UnaryMinusExprNode = new UnaryMinusExprNode { - node = new Node { - type = "unary_minus_expr" - children = - List( - (terminal) { text = "-" }, - operand.build().node, - ) - } - } -} - -/// Builds a logical not expression (`!expr`). -class LogicalNotExprBuilder extends ExprBuilder { - operand: ExprBuilder - - function build(): LogicalNotExprNode = new LogicalNotExprNode { - node = new Node { - type = "logical_not_expr" - children = - List( - (terminal) { text = "!" }, - operand.build().node, - ) - } - } -} - -/// Builds a non-null assertion expression (`expr!!`). -class NonNullExprBuilder extends ExprBuilder { - operand: ExprBuilder - - function build(): NonNullExprNode = new NonNullExprNode { - node = new Node { - type = "non_null_expr" - children = List(operand.build().node, (operatorLeaf) { text = "!!" }) - } - } +// Build an `argument_list` node from typed argument expressions. +local const function argumentListNode(arguments: List): Node = new Node { + type = "argument_list" + children = + if (arguments.isEmpty) + List((terminal) { text = "(" }, (terminal) { text = ")" }) + else + List( + (terminal) { text = "(" }, + new Node { + type = "argument_list_elements" + children = commaSeparate(arguments.map((a) -> a.toNode())) + }, + (terminal) { text = ")" }, + ) } -/// Builds a `throw(expr)` expression. -class ThrowExprBuilder extends ExprBuilder { - expression: ExprBuilder - - function build(): ThrowExprNode = new ThrowExprNode { - node = new Node { - type = "throw_expr" +// Build a `type_parameter_list` node from typed type parameters (empty list → no node). +local const function typeParameterListNodes(typeParameters: List): List = + if (typeParameters.isEmpty) + List() + else + List(new Node { + type = "type_parameter_list" children = List( - (terminal) { text = "throw" }, - (terminal) { text = "(" }, - expression.build().node, - (terminal) { text = ")" }, + (terminal) { text = "<" }, + new Node { + type = "type_parameter_list_elements" + children = commaSeparate(typeParameters.map((t) -> t.toNode())) + }, + (terminal) { text = ">" }, ) - } - } -} + }) -/// Builds a `trace(expr)` expression. -class TraceExprBuilder extends ExprBuilder { - expression: ExprBuilder - - function build(): TraceExprNode = new TraceExprNode { - node = new Node { - type = "trace_expr" - children = - List( - (terminal) { text = "trace" }, - (terminal) { text = "(" }, - expression.build().node, - (terminal) { text = ")" }, - ) - } - } -} +// Build a `type_annotation` node from an optional type (null → no node). +local const function typeAnnotationNodes(_type: TypeNode?): List = + if (_type == null) + List() + else + List(new Node { + type = "type_annotation" + children = List((terminal) { text = ":" }, _type.toNode()) + }) -/// Builds a parenthesized expression (`(expr)`). -class ParenthesizedExprBuilder extends ExprBuilder { - expression: ExprBuilder +// =============== +// Visitor +// =============== - function build(): ParenthesizedExprNode = new ParenthesizedExprNode { - node = new Node { - type = "parenthesized_expr" - children = - List( - (terminal) { text = "(" }, - new Node { - type = "parenthesized_expr_elements" - children = List(expression.build().node) - }, - (terminal) { text = ")" }, - ) - } - } -} - -/// Builds a binary operator expression (`left op right`). -class BinaryOpExprBuilder extends ExprBuilder { - left: ExprBuilder - operator: String - right: ExprBuilder - - function build(): BinaryOpExprNode = new BinaryOpExprNode { - node = new Node { - type = "binary_op_expr" - children = - List( - left.build().node, - (operatorLeaf) { text = operator }, - right.build().node, - ) - } - } -} - -/// Builds an `expr is Type` expression. -class IsExprBuilder extends ExprBuilder { - operand: ExprBuilder - type: TypeBuilder - - function build(): BinaryOpExprNode = - let (self = this) - new BinaryOpExprNode { - node = new Node { - type = "binary_op_expr" - children = - List( - self.operand.build().node, - (operatorLeaf) { text = "is" }, - self.type.build().node, - ) - } - } -} - -/// Builds an `expr as Type` expression. -class AsExprBuilder extends ExprBuilder { - operand: ExprBuilder - type: TypeBuilder - - function build(): BinaryOpExprNode = - let (self = this) - new BinaryOpExprNode { - node = new Node { - type = "binary_op_expr" - children = - List( - self.operand.build().node, - (operatorLeaf) { text = "as" }, - self.type.build().node, - ) - } - } -} - -/// Builds an `if (condition) thenExpr else elseExpr` expression. -class IfExprBuilder extends ExprBuilder { - condition: ExprBuilder - thenExpr: ExprBuilder - elseExpr: ExprBuilder - - function build(): IfExprNode = new IfExprNode { - node = new Node { - type = "if_expr" - children = - List( - new Node { - type = "if_header" - children = - List( - (terminal) { text = "if" }, - new Node { - type = "if_condition" - children = - List( - (terminal) { text = "(" }, - new Node { - type = "if_condition_expr" - children = List(condition.build().node) - }, - (terminal) { text = ")" }, - ) - }, - ) - }, - new Node { - type = "if_then_expr" - children = List(thenExpr.build().node) - }, - (terminal) { text = "else" }, - new Node { - type = "if_else_expr" - children = List(elseExpr.build().node) - }, - ) - } - } -} - -/// Builds an `import("uri")` or `import*("uri")` expression. -class ImportExprBuilder extends ExprBuilder { - uri: String - isGlob: Boolean = false - - function build(): ImportExprNode = new ImportExprNode { - node = new Node { - type = "import_expr" - children = - List( - (terminal) { text = if (isGlob) "import*" else "import" }, - (terminal) { text = "(" }, - new Node { - type = "string_chars" - children = - List( - (terminal) { text = "\"" }, - (terminal) { text = uri }, - (terminal) { text = "\"" }, - ) - }, - (terminal) { text = ")" }, - ) - } - } -} - -/// Builds a `read(expr)`, `read?(expr)`, or `read*(expr)` expression. -class ReadExprBuilder extends ExprBuilder { - expression: ExprBuilder - keyword: "read" | "read?" | "read*" = "read" - - function build(): ReadExprNode = new ReadExprNode { - node = new Node { - type = "read_expr" - children = - List( - (terminal) { text = keyword }, - (terminal) { text = "(" }, - expression.build().node, - (terminal) { text = ")" }, - ) - } - } -} - -/// Builds a declared type (e.g., `String`, `List`). -class DeclaredTypeBuilder extends TypeBuilder { - name: String - typeArguments: List - - function build(): DeclaredTypeNode = new DeclaredTypeNode { - node = new Node { - type = "declared_type" - children = - if (typeArguments.isEmpty) - List(qualifiedIdentifierNode(name)) - else - List(qualifiedIdentifierNode(name), new Node { - type = "type_argument_list" - children = - List( - (terminal) { text = "<" }, - new Node { - type = "type_argument_list_elements" - children = commaSeparate(typeArguments.map((t) -> t.build().node).toList()) - }, - (terminal) { text = ">" }, - ) - }) - } - } -} - -/// Builds a parameter declaration (`name`, `name: Type`, or `_`). -class ParameterBuilder extends Builder { - /// The parameter name. Use "_" for a wildcard parameter. - name: String - - /// The optional type annotation. - typeAnnotation: TypeBuilder? - - function build(): ParameterNode = - let (self = this) - new ParameterNode { - node = new Node { - type = "parameter" - children = - if (self.name == "_") - List((terminal) { text = "_" }) - else if (self.typeAnnotation == null) - List((identifierLeaf) { text = self.name }) - else - List( - (identifierLeaf) { text = self.name }, - new Node { - type = "type_annotation" - children = - List( - (terminal) { text = ":" }, - self.typeAnnotation.build().node, - ) - }, - ) - } - } -} - -/// Builds a `let (param = value) body` expression. -class LetExprBuilder extends ExprBuilder { - parameterName: String - parameterType: TypeBuilder? - bindingValue: ExprBuilder - body: ExprBuilder - - function build(): LetExprNode = - let (self = this) - new LetExprNode { - node = new Node { - type = "let_expr" - children = - List( - (terminal) { text = "let" }, - new Node { - type = "let_parameter_definition" - children = - List( - (terminal) { text = "(" }, - new Node { - type = "let_parameter" - children = - List( - ( - new ParameterBuilder { - name = self.parameterName - typeAnnotation = self.parameterType - } - ) - .build() - .node, - (terminal) { text = "=" }, - self.bindingValue.build().node, - ) - }, - (terminal) { text = ")" }, - ) - }, - self.body.build().node, - ) - } - } -} - -/// Builds a function literal expression (`(params) -> body`). -class FunctionLiteralBuilder extends ExprBuilder { - parameters: List - body: ExprBuilder - - function build(): FunctionLiteralExprNode = - let (self = this) - new FunctionLiteralExprNode { - node = new Node { - type = "function_literal_expr" - children = - List( - new Node { - type = "parameter_list" - children = - if (self.parameters.isEmpty) - List((terminal) { text = "(" }, (terminal) { text = ")" }) - else - List( - (terminal) { text = "(" }, - new Node { - type = "parameter_list_elements" - children = commaSeparate(self.parameters.map((p) -> p.build().node)) - }, - (terminal) { text = ")" }, - ) - }, - (terminal) { text = "->" }, - new Node { - type = "function_literal_body" - children = List(self.body.build().node) - }, - ) - } - } -} - -/// Builds an unqualified function call expression (`name(args)`). -class FunctionCallBuilder extends ExprBuilder { - name: String - arguments: List - - function build(): UnqualifiedAccessExprNode = - let (self = this) - new UnqualifiedAccessExprNode { - node = new Node { - type = "unqualified_access_expr" - children = - List( - (identifierLeaf) { text = self.name }, - new Node { - type = "argument_list" - children = - if (self.arguments.isEmpty) - List((terminal) { text = "(" }, (terminal) { text = ")" }) - else - List( - (terminal) { text = "(" }, - new Node { - type = "argument_list_elements" - children = commaSeparate(self.arguments.map((a) -> a.build().node)) - }, - (terminal) { text = ")" }, - ) - }, - ) - } - } -} - -/// Builds a qualified access expression (`receiver.member` or `receiver?.member`, -/// optionally with arguments for method calls). -class QualifiedAccessBuilder extends ExprBuilder { - receiver: ExprBuilder - member: String - isNullSafe: Boolean = false - /// If non-null, this is a method call with these arguments. If null, this is a property access. - arguments: List? - - function build(): QualifiedAccessExprNode = - let (self = this) - new QualifiedAccessExprNode { - node = new Node { - type = "qualified_access_expr" - children = - List( - self.receiver.build().node, - (operatorLeaf) { text = if (self.isNullSafe) "?." else "." }, - new Node { - type = "unqualified_access_expr" - children = - if (self.arguments == null) - List((identifierLeaf) { text = self.member }) - else - List( - (identifierLeaf) { text = self.member }, - new Node { - type = "argument_list" - children = - if (self.arguments.isEmpty) - List((terminal) { text = "(" }, (terminal) { text = ")" }) - else - List( - (terminal) { text = "(" }, - new Node { - type = "argument_list_elements" - children = commaSeparate(self.arguments.map((a) -> a.build().node)) - }, - (terminal) { text = ")" }, - ) - }, - ) - }, - ) - } - } -} - -/// Builds a subscript expression (`receiver[index]`). -class SubscriptBuilder extends ExprBuilder { - receiver: ExprBuilder - index: ExprBuilder - - function build(): SubscriptExprNode = - let (self = this) - new SubscriptExprNode { - node = new Node { - type = "subscript_expr" - children = - List( - self.receiver.build().node, - (operatorLeaf) { text = "[" }, - self.index.build().node, - (terminal) { text = "]" }, - ) - } - } -} - -/// Builds the `unknown` type. -class UnknownTypeBuilder extends TypeBuilder { - function build(): UnknownTypeNode = new UnknownTypeNode { - node = new Node { type = "unknown_type"; text = "unknown" } - } -} - -/// Builds the `nothing` type. -class NothingTypeBuilder extends TypeBuilder { - function build(): NothingTypeNode = new NothingTypeNode { - node = new Node { type = "nothing_type"; text = "nothing" } - } -} - -/// Builds the `module` type. -class ModuleTypeBuilder extends TypeBuilder { - function build(): ModuleTypeNode = new ModuleTypeNode { - node = new Node { type = "module_type"; text = "module" } - } -} - -/// Builds a nullable type (`Type?`). -class NullableTypeBuilder extends TypeBuilder { - baseType: TypeBuilder - - function build(): NullableTypeNode = new NullableTypeNode { - node = new Node { - type = "nullable_type" - children = List(baseType.build().node, (terminal) { text = "?" }) - } - } -} - -/// Builds a union type (`A | B | C`). -class UnionTypeBuilder extends TypeBuilder { - members: List - - function build(): UnionTypeNode = - let (self = this) - new UnionTypeNode { - node = new Node { - type = "union_type" - children = - self.members - .map((m) -> m.build().node) - .fold(List(), (acc: List, item: Node) -> - if (acc.isEmpty) List(item) else acc.add((terminal) { text = "|" }).add(item) - ) - } - } -} - -/// Builds a function type (`(ParamTypes) -> ReturnType`). -class FunctionTypeBuilder extends TypeBuilder { - parameterTypes: List - returnType: TypeBuilder - - function build(): FunctionTypeNode = - let (self = this) - new FunctionTypeNode { - node = new Node { - type = "function_type" - children = - List( - new Node { - type = "function_type_parameters" - children = - if (self.parameterTypes.isEmpty) - List((terminal) { text = "(" }, (terminal) { text = ")" }) - else - List( - (terminal) { text = "(" }, - new Node { - type = "parenthesized_type_elements" - children = - commaSeparate(self.parameterTypes.map((t) -> t.build().node).toList()) - }, - (terminal) { text = ")" }, - ) - }, - (terminal) { text = "->" }, - self.returnType.build().node, - ) - } - } -} - -/// Builds a constrained type (`Type(constraint1, constraint2)`). -class ConstrainedTypeBuilder extends TypeBuilder { - baseType: TypeBuilder - constraints: List - - function build(): ConstrainedTypeNode = - let (self = this) - new ConstrainedTypeNode { - node = new Node { - type = "constrained_type" - children = - List(self.baseType.build().node, new Node { - type = "constrained_type_constraint" - children = - List( - (terminal) { text = "(" }, - new Node { - type = "constrained_type_elements" - children = commaSeparate(self.constraints.map((c) -> c.build().node)) - }, - (terminal) { text = ")" }, - ) - }) - } - } -} - -/// Builds a parenthesized type (`(Type)`). -class ParenthesizedTypeBuilder extends TypeBuilder { - type: TypeBuilder - - function build(): ParenthesizedTypeNode = - let (self = this) - new ParenthesizedTypeNode { - node = new Node { - type = "parenthesized_type" - children = - List( - (terminal) { text = "(" }, - new Node { - type = "parenthesized_type_elements" - children = List(self.type.build().node) - }, - (terminal) { text = ")" }, - ) - } - } -} - -/// Builds a string constant type (e.g., `"foo"`). -class StringConstantTypeBuilder extends TypeBuilder { - value: String - - function build(): StringConstantTypeNode = new StringConstantTypeNode { - node = new Node { - type = "string_constant_type" - children = - List(new Node { - type = "string_chars" - children = - List( - (terminal) { text = "\"" }, - (terminal) { text = value }, - (terminal) { text = "\"" }, - ) - }) - } - } -} - -/// Builds a type parameter declaration (`T`, `in T`, or `out T`). -class TypeParameterBuilder extends Builder { - name: String - variance: ("in" | "out")? - - function build(): TypeParameterNode = - let (self = this) - new TypeParameterNode { - node = new Node { - type = "type_parameter" - children = - if (self.variance == null) - List((identifierLeaf) { text = self.name }) - else - List( - (terminal) { text = self.variance }, - (identifierLeaf) { text = self.name }, - ) - } - } -} - -/// Builds an object body delimited by braces. -class ObjectBodyBuilder extends Builder { - parameters: List - members: List - - function build(): ObjectBodyNode = - let (self = this) - new ObjectBodyNode { - node = new Node { - type = "object_body" - children = - List((terminal) { text = "{" }) - + ( - if (self.parameters.isEmpty) - List() - else - List(new Node { - type = "object_parameter_list" - children = - commaSeparate(self.parameters.map((p) -> p.build().node)) - .add((terminal) { text = "->" }) - }) - ) - + ( - if (self.members.isEmpty) - List() - else - List(new Node { - type = "object_member_list" - children = self.members.map((m) -> m.build().node) - }) - ) - + List((terminal) { text = "}" }) - } - } -} - -/// Builds an object element. -class ObjectElementBuilder extends ObjectMemberBuilder { - expression: ExprBuilder - - function build(): ObjectElementNode = - let (self = this) - new ObjectElementNode { - node = new Node { - type = "object_element" - children = List(self.expression.build().node) - } - } -} - -/// Builds an object spread (`...expr` or `...?expr`). -class ObjectSpreadBuilder extends ObjectMemberBuilder { - expression: ExprBuilder - isNullable: Boolean = false - - function build(): ObjectSpreadNode = - let (self = this) - new ObjectSpreadNode { - node = new Node { - type = "object_spread" - children = - List( - (terminal) { text = if (self.isNullable) "...?" else "..." }, - self.expression.build().node, - ) - } - } -} - -/// Builds an object property declaration. -class ObjectPropertyBuilder extends ObjectMemberBuilder { - modifiers: List - name: String - typeAnnotation: TypeBuilder? - value: ExprBuilder? - /// Object bodies for amending. Used when there is no `=` value. - objectBodies: List - - function build(): ObjectPropertyNode = - let (self = this) - new ObjectPropertyNode { - node = new Node { - type = "object_property" - children = - List(new Node { - type = "object_property_header" - children = - List(new Node { - type = "object_property_header_begin" - children = - ( - if (self.modifiers.isEmpty) - List() - else - List(modifierListNode(self.modifiers)) - ) - + List((identifierLeaf) { text = self.name }) - }) - + ( - if (self.typeAnnotation == null) - List() - else - List(new Node { - type = "type_annotation" - children = - List( - (terminal) { text = ":" }, - self.typeAnnotation.build().node, - ) - }) - ) - }) - + ( - if (self.value != null) - List( - (terminal) { text = "=" }, - new Node { - type = "object_property_body" - children = List(self.value.build().node) - }, - ) - else - self.objectBodies.map((b) -> b.build().node) - ) - } - } -} - -/// Builds an object method declaration. -class ObjectMethodBuilder extends ObjectMemberBuilder { - modifiers: List - name: String - typeParameters: List - parameters: List - returnType: TypeBuilder? - body: ExprBuilder - - function build(): ObjectMethodNode = - let (self = this) - new ObjectMethodNode { - node = new Node { - type = "object_method" - children = - List(new Node { - type = "class_method_header" - children = - ( - if (self.modifiers.isEmpty) - List() - else - List(modifierListNode(self.modifiers)) - ) - + List( - (terminal) { text = "function" }, - (identifierLeaf) { text = self.name }, - ) - }) - + ( - if (self.typeParameters.isEmpty) - List() - else - List(new Node { - type = "type_parameter_list" - children = - List( - (terminal) { text = "<" }, - new Node { - type = "type_parameter_list_elements" - children = commaSeparate(self.typeParameters.map((t) -> t.build().node)) - }, - (terminal) { text = ">" }, - ) - }) - ) - + List(new Node { - type = "parameter_list" - children = - if (self.parameters.isEmpty) - List((terminal) { text = "(" }, (terminal) { text = ")" }) - else - List( - (terminal) { text = "(" }, - new Node { - type = "parameter_list_elements" - children = commaSeparate(self.parameters.map((p) -> p.build().node)) - }, - (terminal) { text = ")" }, - ) - }) - + ( - if (self.returnType == null) - List() - else - List(new Node { - type = "type_annotation" - children = - List( - (terminal) { text = ":" }, - self.returnType.build().node, - ) - }) - ) - + List( - (terminal) { text = "=" }, - new Node { - type = "class_method_body" - children = List(self.body.build().node) - }, - ) - } - } -} - -/// Builds an object entry (`[key] = value` or `[key] { ... }`). -class ObjectEntryBuilder extends ObjectMemberBuilder { - key: ExprBuilder - value: ExprBuilder? - objectBodies: List - - function build(): ObjectEntryNode = - let (self = this) - new ObjectEntryNode { - node = new Node { - type = "object_entry" - children = - List(new Node { - type = "object_entry_header" - children = - List( - (terminal) { text = "[" }, - self.key.build().node, - (terminal) { text = "]" }, - ) - + (if (self.value != null) List((terminal) { text = "=" }) else List()) - }) - + ( - if (self.value != null) - List(self.value.build().node) - else - self.objectBodies.map((b) -> b.build().node) - ) - } - } -} - -/// Builds a member predicate (`[[condition]] = value` or `[[condition]] { ... }`). -class MemberPredicateBuilder extends ObjectMemberBuilder { - condition: ExprBuilder - value: ExprBuilder? - objectBodies: List - - function build(): MemberPredicateNode = - let (self = this) - new MemberPredicateNode { - node = new Node { - type = "member_predicate" - children = - List( - (terminal) { text = "[[" }, - self.condition.build().node, - (terminal) { text = "]" }, - (terminal) { text = "]" }, - ) - + ( - if (self.value != null) - List( - (terminal) { text = "=" }, - self.value.build().node, - ) - else - self.objectBodies.map((b) -> b.build().node) - ) - } - } -} - -/// Builds a `for (param in iterable) { ... }` generator. -class ForGeneratorBuilder extends ObjectMemberBuilder { - /// The optional key parameter (when iterating with both key and value). - keyParameter: ParameterBuilder? - valueParameter: ParameterBuilder - iterable: ExprBuilder - body: ObjectBodyBuilder - - function build(): ForGeneratorNode = - let (self = this) - new ForGeneratorNode { - node = new Node { - type = "for_generator" - children = - List( - (terminal) { text = "for" }, - new Node { - type = "for_generator_header" - children = - List( - (terminal) { text = "(" }, - new Node { - type = "for_generator_header_definition" - children = - List( - new Node { - type = "for_generator_header_definition_header" - children = - ( - if (self.keyParameter == null) - List(self.valueParameter.build().node) - else - List( - self.keyParameter.build().node, - (terminal) { text = "," }, - self.valueParameter.build().node, - ) - ) - + List((terminal) { text = "in" }) - }, - self.iterable.build().node, - ) - }, - (terminal) { text = ")" }, - ) - }, - self.body.build().node, - ) - } - } -} - -/// Builds a `when (condition) { ... } else { ... }` generator. -class WhenGeneratorBuilder extends ObjectMemberBuilder { - condition: ExprBuilder - thenBody: ObjectBodyBuilder - elseBody: ObjectBodyBuilder? - - function build(): WhenGeneratorNode = - let (self = this) - new WhenGeneratorNode { - node = new Node { - type = "when_generator" - children = - List( - (terminal) { text = "when" }, - new Node { - type = "when_generator_header" - children = - List( - (terminal) { text = "(" }, - self.condition.build().node, - (terminal) { text = ")" }, - ) - }, - self.thenBody.build().node, - ) - + ( - if (self.elseBody == null) - List() - else - List( - (terminal) { text = "else" }, - self.elseBody.build().node, - ) - ) - } - } -} - -/// Builds a `new Type { ... }` expression. -class NewExprBuilder extends ExprBuilder { - /// The optional type. If null, this is `new { ... }`. - type: TypeBuilder? - body: ObjectBodyBuilder - - function build(): NewExprNode = - let (self = this) - new NewExprNode { - node = new Node { - type = "new_expr" - children = - List( - new Node { - type = "new_header" - children = - if (self.type == null) - List((terminal) { text = "new" }) - else - List((terminal) { text = "new" }, self.type.build().node) - }, - self.body.build().node, - ) - } - } -} - -/// Builds an amends expression (`(expr) { ... }`). -class AmendsExprBuilder extends ExprBuilder { - parentExpr: ExprBuilder - body: ObjectBodyBuilder - - function build(): AmendsExprNode = - let (self = this) - new AmendsExprNode { - node = new Node { - type = "amends_expr" - children = - List( - self.parentExpr.build().node, - self.body.build().node, - ) - } - } -} - -/// Builds a doc comment. -class DocCommentBuilder extends Builder { - /// The body text of each line, without the leading `///`. - lines: List - - function build(): DocCommentNode = - let (self = this) - new DocCommentNode { - node = new Node { - type = "doc_comment" - children = self.lines.map((l) -> new Node { type = "doc_comment_line"; text = "///" + l }) - } - } -} - -/// Builds an annotation (`@Type` or `@Type { ... }`). -class AnnotationBuilder extends Builder { - type: TypeBuilder - body: ObjectBodyBuilder? - - function build(): AnnotationNode = - let (self = this) - new AnnotationNode { - node = new Node { - type = "annotation" - children = - List((terminal) { text = "@" }, self.type.build().node) - + (if (self.body == null) List() else List(self.body.build().node)) - } - } -} - -/// Builds an import declaration. -class ImportBuilder extends Builder { - uri: String - isGlob: Boolean = false - alias: String? - - function build(): ImportNode = - let (self = this) - new ImportNode { - node = new Node { - type = "import" - children = - List( - (terminal) { text = if (self.isGlob) "import*" else "import" }, - new Node { - type = "string_chars" - children = - List( - (terminal) { text = "\"" }, - (terminal) { text = self.uri }, - (terminal) { text = "\"" }, - ) - }, - ) - + ( - if (self.alias == null) - List() - else - List(new Node { - type = "import_alias" - children = - List( - (terminal) { text = "as" }, - (identifierLeaf) { text = self.alias }, - ) - }) - ) - } - } -} - -/// Builds a class body delimited by braces. -class ClassBodyBuilder extends Builder { - properties: List - methods: List - - function build(): ClassBodyNode = - let (self = this) - new ClassBodyNode { - node = new Node { - type = "class_body" - children = - List((terminal) { text = "{" }) - + ( - let ( - members = - self.properties.map((p) -> p.build().node) - + self.methods.map((m) -> m.build().node) - ) - if (members.isEmpty) - List() - else - List(new Node { type = "class_body_elements"; children = members }) - ) - + List((terminal) { text = "}" }) - } - } -} - -/// Builds a class property declaration. -class ClassPropertyBuilder extends Builder { - docComment: DocCommentBuilder? - annotations: List - modifiers: List - name: String - typeAnnotation: TypeBuilder? - value: ExprBuilder? - /// Object bodies for amending. Used when there is no `=` value. - objectBodies: List - - function build(): ClassPropertyNode = - let (self = this) - new ClassPropertyNode { - node = new Node { - type = "class_property" - children = - (if (self.docComment == null) List() else List(self.docComment.build().node)) - + self.annotations.map((a) -> a.build().node) - + List(new Node { - type = "class_property_header" - children = - List(new Node { - type = "class_property_header_begin" - children = - ( - if (self.modifiers.isEmpty) - List() - else - List(modifierListNode(self.modifiers)) - ) - + List((identifierLeaf) { text = self.name }) - }) - + ( - if (self.typeAnnotation == null) - List() - else - List(new Node { - type = "type_annotation" - children = - List( - (terminal) { text = ":" }, - self.typeAnnotation.build().node, - ) - }) - ) - }) - + ( - if (self.value != null) - List( - (terminal) { text = "=" }, - new Node { - type = "class_property_body" - children = List(self.value.build().node) - }, - ) - else - self.objectBodies.map((b) -> b.build().node) - ) - } - } -} - -/// Builds a class method declaration. -class ClassMethodBuilder extends Builder { - docComment: DocCommentBuilder? - annotations: List - modifiers: List - name: String - typeParameters: List - parameters: List - returnType: TypeBuilder? - /// The method body. Null for abstract methods. - body: ExprBuilder? - - function build(): ClassMethodNode = - let (self = this) - new ClassMethodNode { - node = new Node { - type = "class_method" - children = - (if (self.docComment == null) List() else List(self.docComment.build().node)) - + self.annotations.map((a) -> a.build().node) - + List(new Node { - type = "class_method_header" - children = - ( - if (self.modifiers.isEmpty) - List() - else - List(modifierListNode(self.modifiers)) - ) - + List( - (terminal) { text = "function" }, - (identifierLeaf) { text = self.name }, - ) - }) - + ( - if (self.typeParameters.isEmpty) - List() - else - List(new Node { - type = "type_parameter_list" - children = - List( - (terminal) { text = "<" }, - new Node { - type = "type_parameter_list_elements" - children = commaSeparate(self.typeParameters.map((t) -> t.build().node)) - }, - (terminal) { text = ">" }, - ) - }) - ) - + List(new Node { - type = "parameter_list" - children = - if (self.parameters.isEmpty) - List((terminal) { text = "(" }, (terminal) { text = ")" }) - else - List( - (terminal) { text = "(" }, - new Node { - type = "parameter_list_elements" - children = commaSeparate(self.parameters.map((p) -> p.build().node)) - }, - (terminal) { text = ")" }, - ) - }) - + ( - if (self.returnType == null) - List() - else - List(new Node { - type = "type_annotation" - children = - List( - (terminal) { text = ":" }, - self.returnType.build().node, - ) - }) - ) - + ( - if (self.body == null) - List() - else - List( - (terminal) { text = "=" }, - new Node { - type = "class_method_body" - children = List(self.body.build().node) - }, - ) - ) - } - } -} - -/// Builds a typealias declaration. -class TypeAliasBuilder extends Builder { - docComment: DocCommentBuilder? - annotations: List - modifiers: List - name: String - typeParameters: List - type: TypeBuilder - - function build(): TypeAliasNode = - let (self = this) - new TypeAliasNode { - node = new Node { - type = "typealias" - children = - (if (self.docComment == null) List() else List(self.docComment.build().node)) - + self.annotations.map((a) -> a.build().node) - + List(new Node { - type = "typealias_header" - children = - ( - if (self.modifiers.isEmpty) - List() - else - List(modifierListNode(self.modifiers)) - ) - + List( - (terminal) { text = "typealias" }, - (identifierLeaf) { text = self.name }, - ) - + ( - if (self.typeParameters.isEmpty) - List() - else - List(new Node { - type = "type_parameter_list" - children = - List( - (terminal) { text = "<" }, - new Node { - type = "type_parameter_list_elements" - children = - commaSeparate(self.typeParameters.map((t) -> t.build().node)) - }, - (terminal) { text = ">" }, - ) - }) - ) - + List((terminal) { text = "=" }) - }) - + List(new Node { - type = "typealias_body" - children = List(self.type.build().node) - }) - } - } -} - -/// Builds a class declaration. -class ClassBuilder extends Builder { - docComment: DocCommentBuilder? - annotations: List - modifiers: List - name: String - typeParameters: List - extendsType: TypeBuilder? - body: ClassBodyBuilder? - - function build(): ClassNode = - let (self = this) - new ClassNode { - node = new Node { - type = "class" - children = - (if (self.docComment == null) List() else List(self.docComment.build().node)) - + self.annotations.map((a) -> a.build().node) - + List(new Node { - type = "class_header" - children = - ( - if (self.modifiers.isEmpty) - List() - else - List(modifierListNode(self.modifiers)) - ) - + List( - (terminal) { text = "class" }, - (identifierLeaf) { text = self.name }, - ) - + ( - if (self.typeParameters.isEmpty) - List() - else - List(new Node { - type = "type_parameter_list" - children = - List( - (terminal) { text = "<" }, - new Node { - type = "type_parameter_list_elements" - children = - commaSeparate(self.typeParameters.map((t) -> t.build().node)) - }, - (terminal) { text = ">" }, - ) - }) - ) - + ( - if (self.extendsType == null) - List() - else - List(new Node { - type = "class_header_extends" - children = - List( - (terminal) { text = "extends" }, - self.extendsType.build().node, - ) - }) - ) - }) - + (if (self.body == null) List() else List(self.body.build().node)) - } - } -} - -/// Builds a module declaration. -class ModuleDeclarationBuilder extends Builder { - docComment: DocCommentBuilder? - annotations: List - modifiers: List - /// The qualified module name, if a `module` declaration is present. - name: String? - /// The URI string of the amended module, if any. Mutually exclusive with `extendsUri`. - amendsUri: String? - /// The URI string of the extended module, if any. Mutually exclusive with `amendsUri`. - extendsUri: String? - - function build(): ModuleDeclarationNode = - let (self = this) - new ModuleDeclarationNode { - node = new Node { - type = "module_declaration" - children = - (if (self.docComment == null) List() else List(self.docComment.build().node)) - + self.annotations.map((a) -> a.build().node) - + ( - if (self.name != null) - List(new Node { - type = "module_definition" - children = - ( - if (self.modifiers.isEmpty) - List() - else - List(modifierListNode(self.modifiers)) - ) - + List( - (terminal) { text = "module" }, - qualifiedIdentifierNode(self.name!!), - ) - }) - else if (!self.modifiers.isEmpty) - List(modifierListNode(self.modifiers)) - else - List() - ) - + ( - if (self.amendsUri != null) - List(new Node { - type = "amends_clause" - children = - List( - (terminal) { text = "amends" }, - new Node { - type = "string_chars" - children = - List( - (terminal) { text = "\"" }, - (terminal) { text = self.amendsUri }, - (terminal) { text = "\"" }, - ) - }, - ) - }) - else if (self.extendsUri != null) - List(new Node { - type = "extends_clause" - children = - List( - (terminal) { text = "extends" }, - new Node { - type = "string_chars" - children = - List( - (terminal) { text = "\"" }, - (terminal) { text = self.extendsUri }, - (terminal) { text = "\"" }, - ) - }, - ) - }) - else - List() - ) - } - } -} - -/// Builds a module. -class ModuleBuilder extends Builder { - declaration: ModuleDeclarationBuilder? - imports: List - classes: List - typeAliases: List - properties: List - methods: List - - function build(): ModuleNode = - let (self = this) - new ModuleNode { - node = new Node { - type = "module" - children = - (if (self.declaration == null) List() else List(self.declaration.build().node)) - + ( - if (self.imports.isEmpty) - List() - else - List(new Node { - type = "import_list" - children = self.imports.map((i) -> i.build().node) - }) - ) - + self.classes.map((c) -> c.build().node) - + self.typeAliases.map((t) -> t.build().node) - + self.properties.map((p) -> p.build().node) - + self.methods.map((m) -> m.build().node) - } - } -} +/// A type-safe tree visitor for use with [visit]. +/// +/// See [visit] for the descent and rebuild semantics. +class Visitor { + // module structure + visitModule: (ModuleNode) -> Pair? = (_) -> null + visitModuleDeclaration: (ModuleDeclarationNode) -> Pair? = (_) -> null + visitImport: (ImportNode) -> Pair? = (_) -> null + visitClass: (ClassNode) -> Pair? = (_) -> null + visitTypeAlias: (TypeAliasNode) -> Pair? = (_) -> null + visitClassBody: (ClassBodyNode) -> Pair? = (_) -> null + visitClassProperty: (ClassPropertyNode) -> Pair? = (_) -> null + visitClassMethod: (ClassMethodNode) -> Pair? = (_) -> null + visitObjectBody: (ObjectBodyNode) -> Pair? = (_) -> null + visitAnnotation: (AnnotationNode) -> Pair? = (_) -> null + visitParameter: (ParameterNode) -> Pair? = (_) -> null + visitTypeParameter: (TypeParameterNode) -> Pair? = (_) -> null + visitDocComment: (DocCommentNode) -> Pair? = (_) -> null + + // object members + visitObjectProperty: (ObjectPropertyNode) -> Pair? = (_) -> null + visitObjectMethod: (ObjectMethodNode) -> Pair? = (_) -> null + visitObjectElement: (ObjectElementNode) -> Pair? = (_) -> null + visitObjectEntry: (ObjectEntryNode) -> Pair? = (_) -> null + visitObjectSpread: (ObjectSpreadNode) -> Pair? = (_) -> null + visitMemberPredicate: (MemberPredicateNode) -> Pair? = (_) -> null + visitForGenerator: (ForGeneratorNode) -> Pair? = (_) -> null + visitWhenGenerator: (WhenGeneratorNode) -> Pair? = (_) -> null + + // expressions + visitThisExpr: (ThisExprNode) -> Pair? = (_) -> null + visitOuterExpr: (OuterExprNode) -> Pair? = (_) -> null + visitModuleExpr: (ModuleExprNode) -> Pair? = (_) -> null + visitNullLiteralExpr: (NullLiteralExprNode) -> Pair? = (_) -> null + visitBoolLiteralExpr: (BoolLiteralExprNode) -> Pair? = (_) -> null + visitIntLiteralExpr: (IntLiteralExprNode) -> Pair? = (_) -> null + visitFloatLiteralExpr: (FloatLiteralExprNode) -> Pair? = (_) -> null + visitSingleLineStringLiteralExpr: (SingleLineStringLiteralExprNode) -> Pair? = ( + _, + ) -> null + visitMultiLineStringLiteralExpr: (MultiLineStringLiteralExprNode) -> Pair? = ( + _, + ) -> null + visitUnqualifiedAccessExpr: (UnqualifiedAccessExprNode) -> Pair? = (_) -> + null + visitQualifiedAccessExpr: (QualifiedAccessExprNode) -> Pair? = (_) -> null + visitSubscriptExpr: (SubscriptExprNode) -> Pair? = (_) -> null + visitSuperAccessExpr: (SuperAccessExprNode) -> Pair? = (_) -> null + visitSuperSubscriptExpr: (SuperSubscriptExprNode) -> Pair? = (_) -> null + visitIfExpr: (IfExprNode) -> Pair? = (_) -> null + visitLetExpr: (LetExprNode) -> Pair? = (_) -> null + visitThrowExpr: (ThrowExprNode) -> Pair? = (_) -> null + visitTraceExpr: (TraceExprNode) -> Pair? = (_) -> null + visitImportExpr: (ImportExprNode) -> Pair? = (_) -> null + visitReadExpr: (ReadExprNode) -> Pair? = (_) -> null + visitNewExpr: (NewExprNode) -> Pair? = (_) -> null + visitAmendsExpr: (AmendsExprNode) -> Pair? = (_) -> null + visitBinaryOpExpr: (BinaryOpExprNode) -> Pair? = (_) -> null + visitUnaryMinusExpr: (UnaryMinusExprNode) -> Pair? = (_) -> null + visitLogicalNotExpr: (LogicalNotExprNode) -> Pair? = (_) -> null + visitNonNullExpr: (NonNullExprNode) -> Pair? = (_) -> null + visitFunctionLiteralExpr: (FunctionLiteralExprNode) -> Pair? = (_) -> null + visitParenthesizedExpr: (ParenthesizedExprNode) -> Pair? = (_) -> null + + // types + visitUnknownType: (UnknownTypeNode) -> Pair? = (_) -> null + visitNothingType: (NothingTypeNode) -> Pair? = (_) -> null + visitModuleType: (ModuleTypeNode) -> Pair? = (_) -> null + visitDeclaredType: (DeclaredTypeNode) -> Pair? = (_) -> null + visitNullableType: (NullableTypeNode) -> Pair? = (_) -> null + visitUnionType: (UnionTypeNode) -> Pair? = (_) -> null + visitFunctionType: (FunctionTypeNode) -> Pair? = (_) -> null + visitConstrainedType: (ConstrainedTypeNode) -> Pair? = (_) -> null + visitParenthesizedType: (ParenthesizedTypeNode) -> Pair? = (_) -> null + visitStringConstantType: (StringConstantTypeNode) -> Pair? = (_) -> null +} + +// Map a typed visitor result into the raw-node result that `walk` consumes. +local const function toWalkResult(r: Pair?): Pair? = + if (r == null) null else Pair(r.first.toNode(), r.second) + +// Dispatch a raw node to the matching visitor callback (or `null` if none applies). +local function dispatch(n: Node, v: Visitor): Pair? = + let (call = dispatchers.getOrNull(n.type)) + if (call == null) null else toWalkResult(call.apply(n, v)) + +local dispatchers: Map Pair?> = + Map( + // module structure + "module", (n, v) -> v.visitModule.apply(new ModuleNode { node = n }), + "module_declaration", + (n, v) -> v.visitModuleDeclaration.apply(new ModuleDeclarationNode { node = n }), + "import", (n, v) -> v.visitImport.apply(new ImportNode { node = n }), + "class", (n, v) -> v.visitClass.apply(new ClassNode { node = n }), + "typealias", (n, v) -> v.visitTypeAlias.apply(new TypeAliasNode { node = n }), + "class_body", (n, v) -> v.visitClassBody.apply(new ClassBodyNode { node = n }), + "class_property", (n, v) -> v.visitClassProperty.apply(new ClassPropertyNode { node = n }), + "class_method", (n, v) -> v.visitClassMethod.apply(new ClassMethodNode { node = n }), + "object_body", (n, v) -> v.visitObjectBody.apply(new ObjectBodyNode { node = n }), + "annotation", (n, v) -> v.visitAnnotation.apply(new AnnotationNode { node = n }), + "parameter", (n, v) -> v.visitParameter.apply(new ParameterNode { node = n }), + "type_parameter", (n, v) -> v.visitTypeParameter.apply(new TypeParameterNode { node = n }), + "doc_comment", (n, v) -> v.visitDocComment.apply(new DocCommentNode { node = n }), + // object members + "object_property", (n, v) -> v.visitObjectProperty.apply(new ObjectPropertyNode { node = n }), + "object_method", (n, v) -> v.visitObjectMethod.apply(new ObjectMethodNode { node = n }), + "object_element", (n, v) -> v.visitObjectElement.apply(new ObjectElementNode { node = n }), + "object_entry", (n, v) -> v.visitObjectEntry.apply(new ObjectEntryNode { node = n }), + "object_spread", (n, v) -> v.visitObjectSpread.apply(new ObjectSpreadNode { node = n }), + "member_predicate", (n, v) -> v.visitMemberPredicate.apply(new MemberPredicateNode { node = n }), + "for_generator", (n, v) -> v.visitForGenerator.apply(new ForGeneratorNode { node = n }), + "when_generator", (n, v) -> v.visitWhenGenerator.apply(new WhenGeneratorNode { node = n }), + // expressions + "this_expr", (n, v) -> v.visitThisExpr.apply(new ThisExprNode { node = n }), + "outer_expr", (n, v) -> v.visitOuterExpr.apply(new OuterExprNode { node = n }), + "module_expr", (n, v) -> v.visitModuleExpr.apply(new ModuleExprNode { node = n }), + "null_expr", (n, v) -> v.visitNullLiteralExpr.apply(new NullLiteralExprNode { node = n }), + "bool_literal_expr", + (n, v) -> v.visitBoolLiteralExpr.apply(new BoolLiteralExprNode { node = n }), + "int_literal_expr", (n, v) -> v.visitIntLiteralExpr.apply(new IntLiteralExprNode { node = n }), + "float_literal_expr", + (n, v) -> v.visitFloatLiteralExpr.apply(new FloatLiteralExprNode { node = n }), + "single_line_string_literal_expr", + (n, v) -> + v.visitSingleLineStringLiteralExpr.apply(new SingleLineStringLiteralExprNode { node = n }), + "multi_line_string_literal_expr", + (n, v) -> + v.visitMultiLineStringLiteralExpr.apply(new MultiLineStringLiteralExprNode { node = n }), + "unqualified_access_expr", + (n, v) -> v.visitUnqualifiedAccessExpr.apply(new UnqualifiedAccessExprNode { node = n }), + "qualified_access_expr", + (n, v) -> v.visitQualifiedAccessExpr.apply(new QualifiedAccessExprNode { node = n }), + "subscript_expr", (n, v) -> v.visitSubscriptExpr.apply(new SubscriptExprNode { node = n }), + "super_access_expr", + (n, v) -> v.visitSuperAccessExpr.apply(new SuperAccessExprNode { node = n }), + "super_subscript_expr", + (n, v) -> v.visitSuperSubscriptExpr.apply(new SuperSubscriptExprNode { node = n }), + "if_expr", (n, v) -> v.visitIfExpr.apply(new IfExprNode { node = n }), + "let_expr", (n, v) -> v.visitLetExpr.apply(new LetExprNode { node = n }), + "throw_expr", (n, v) -> v.visitThrowExpr.apply(new ThrowExprNode { node = n }), + "trace_expr", (n, v) -> v.visitTraceExpr.apply(new TraceExprNode { node = n }), + "import_expr", (n, v) -> v.visitImportExpr.apply(new ImportExprNode { node = n }), + "read_expr", (n, v) -> v.visitReadExpr.apply(new ReadExprNode { node = n }), + "new_expr", (n, v) -> v.visitNewExpr.apply(new NewExprNode { node = n }), + "amends_expr", (n, v) -> v.visitAmendsExpr.apply(new AmendsExprNode { node = n }), + "binary_op_expr", (n, v) -> v.visitBinaryOpExpr.apply(new BinaryOpExprNode { node = n }), + "unary_minus_expr", (n, v) -> v.visitUnaryMinusExpr.apply(new UnaryMinusExprNode { node = n }), + "logical_not_expr", (n, v) -> v.visitLogicalNotExpr.apply(new LogicalNotExprNode { node = n }), + "non_null_expr", (n, v) -> v.visitNonNullExpr.apply(new NonNullExprNode { node = n }), + "function_literal_expr", + (n, v) -> v.visitFunctionLiteralExpr.apply(new FunctionLiteralExprNode { node = n }), + "parenthesized_expr", + (n, v) -> v.visitParenthesizedExpr.apply(new ParenthesizedExprNode { node = n }), + // types + "unknown_type", (n, v) -> v.visitUnknownType.apply(new UnknownTypeNode { node = n }), + "nothing_type", (n, v) -> v.visitNothingType.apply(new NothingTypeNode { node = n }), + "module_type", (n, v) -> v.visitModuleType.apply(new ModuleTypeNode { node = n }), + "declared_type", (n, v) -> v.visitDeclaredType.apply(new DeclaredTypeNode { node = n }), + "nullable_type", (n, v) -> v.visitNullableType.apply(new NullableTypeNode { node = n }), + "union_type", (n, v) -> v.visitUnionType.apply(new UnionTypeNode { node = n }), + "function_type", (n, v) -> v.visitFunctionType.apply(new FunctionTypeNode { node = n }), + "constrained_type", (n, v) -> v.visitConstrainedType.apply(new ConstrainedTypeNode { node = n }), + "parenthesized_type", + (n, v) -> v.visitParenthesizedType.apply(new ParenthesizedTypeNode { node = n }), + "string_constant_type", + (n, v) -> v.visitStringConstantType.apply(new StringConstantTypeNode { node = n }), + ) From 5458321e3533218762d53b748904f99d518579a5 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Mon, 13 Jul 2026 17:28:20 +0200 Subject: [PATCH 06/49] Changed tests from examples to facts --- .../files/LanguageSnippetTests/input/syntax/expressions.pkl | 2 +- .../src/test/files/LanguageSnippetTests/input/syntax/format.pkl | 2 +- .../files/LanguageSnippetTests/input/syntax/moduleStructure.pkl | 2 +- .../files/LanguageSnippetTests/input/syntax/objectMembers.pkl | 2 +- .../test/files/LanguageSnippetTests/input/syntax/traversal.pkl | 2 +- .../src/test/files/LanguageSnippetTests/input/syntax/types.pkl | 2 +- .../src/test/files/LanguageSnippetTests/input/syntax/walk.pkl | 2 +- .../files/LanguageSnippetTests/output/syntax/expressions.pcf | 2 +- .../test/files/LanguageSnippetTests/output/syntax/format.pcf | 2 +- .../LanguageSnippetTests/output/syntax/moduleStructure.pcf | 2 +- .../files/LanguageSnippetTests/output/syntax/objectMembers.pcf | 2 +- .../test/files/LanguageSnippetTests/output/syntax/traversal.pcf | 2 +- .../src/test/files/LanguageSnippetTests/output/syntax/types.pcf | 2 +- .../src/test/files/LanguageSnippetTests/output/syntax/walk.pcf | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl index 332422106..353034e45 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl @@ -8,7 +8,7 @@ local function expr(source: String) = let (result = parse("x = \(source)")) (result as syntax.ModuleNode).properties.first.value -examples { +facts { ["literals"] { local boolTrue = expr("true") boolTrue is syntax.BoolLiteralExprNode diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/format.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/format.pkl index 96635c630..7df9fd902 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/format.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/format.pkl @@ -31,7 +31,7 @@ local function transformFirst( children = node.children.map((c) -> transformFirst(c, targetType, transform)) } -examples { +facts { ["empty module"] { roundTrip("") == "\n" } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl index 2b93b3512..0a928e5c0 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl @@ -4,7 +4,7 @@ import "pkl:syntax" local function parse(source: String) = syntax.parse(source) -examples { +facts { ["module declaration"] { local result = parse("module my.app") result is syntax.ModuleNode diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl index 8b5ed99b5..b649d3a38 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl @@ -9,7 +9,7 @@ local function body(source: String) = let (mod = result as syntax.ModuleNode) mod.properties.first.objectBodies.first -examples { +facts { ["object property"] { local b = body("name = \"hello\"") b.members.length == 1 diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl index 8ad33d4b1..834ae0bd6 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl @@ -21,7 +21,7 @@ local sample: syntax.Node = """, ).node!! -examples { +facts { ["fold counts nodes by predicate"] { syntax.fold(sample, 0, (acc, n) -> if (n.type == "import") acc + 1 else acc) == 2 diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl index fce1b68a9..2e5011241 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl @@ -8,7 +8,7 @@ local function typeOf(typeSource: String) = let (result = parse("x: \(typeSource) = 0")) (result as syntax.ModuleNode).properties.first.typeAnnotation -examples { +facts { ["simple types"] { local unknownT = typeOf("unknown") unknownT is syntax.UnknownTypeNode diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl index ca641559f..03bf003cf 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl @@ -14,7 +14,7 @@ local function walkFormat( local function visitFormat(source: String, visitor: syntax.Visitor): String = syntax.format(syntax.visit(mod(source).node, visitor)) -examples { +facts { ["read-only walk leaves the tree unchanged"] { // returning `null` everywhere keeps every node and keeps descending walkFormat("x = 1", (_) -> null) == fmt("x = 1") diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf index 147097dc8..d95f2676b 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf @@ -1,4 +1,4 @@ -examples { +facts { ["literals"] { true true diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/format.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/format.pcf index 4a5572a28..4b8ab0fbd 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/format.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/format.pcf @@ -1,4 +1,4 @@ -examples { +facts { ["empty module"] { true } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf index 7d573f0fe..ec5e36c0c 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf @@ -1,4 +1,4 @@ -examples { +facts { ["module declaration"] { true true diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf index aa585fc75..e861c01d9 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf @@ -1,4 +1,4 @@ -examples { +facts { ["object property"] { true true diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/traversal.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/traversal.pcf index 547e8223c..7b749467c 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/traversal.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/traversal.pcf @@ -1,4 +1,4 @@ -examples { +facts { ["fold counts nodes by predicate"] { true true diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/types.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/types.pcf index 3e17ba631..1a0ff2176 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/types.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/types.pcf @@ -1,4 +1,4 @@ -examples { +facts { ["simple types"] { true true diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf index 00d5ef828..0bbf1e585 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf @@ -1,4 +1,4 @@ -examples { +facts { ["read-only walk leaves the tree unchanged"] { true true From c6b24490ea737dcecd56c4e84180816825c3f6af Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Mon, 13 Jul 2026 17:48:24 +0200 Subject: [PATCH 07/49] Move static identifiers to Identifier.java --- .../java/org/pkl/core/runtime/Identifier.java | 9 ++++++ .../pkl/core/stdlib/syntax/SyntaxNodes.java | 28 ++++++++----------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/Identifier.java b/pkl-core/src/main/java/org/pkl/core/runtime/Identifier.java index 9009ccf17..57c83d444 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/Identifier.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/Identifier.java @@ -172,6 +172,15 @@ public final class Identifier implements Comparable { // common in lambdas etc public static final Identifier IT = get("it"); + // members of pkl.syntax#Node and pkl.syntax#Span + public static final Identifier TYPE = get("type"); + public static final Identifier CHILDREN = get("children"); + public static final Identifier SPAN = get("span"); + public static final Identifier LINE_START = get("lineStart"); + public static final Identifier COL_START = get("colStart"); + public static final Identifier LINE_END = get("lineEnd"); + public static final Identifier COL_END = get("colEnd"); + private final String name; private Identifier(String name) { diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java index bcfe8e2d7..003fa70c8 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java @@ -44,13 +44,6 @@ public final class SyntaxNodes { private SyntaxNodes() {} - private static final Identifier TYPE_ID = Identifier.get("type"); - private static final Identifier CHILDREN_ID = Identifier.get("children"); - private static final Identifier SPAN_ID = Identifier.get("span"); - private static final Identifier LINE_START_ID = Identifier.get("lineStart"); - private static final Identifier COL_START_ID = Identifier.get("colStart"); - private static final Identifier LINE_END_ID = Identifier.get("lineEnd"); - private static final Identifier COL_END_ID = Identifier.get("colEnd"); private static final char[] EMPTY_SOURCE = new char[0]; private static final FullSpan ZERO_SPAN = new FullSpan(0, 0, 0, 0, 0, 0); @@ -192,7 +185,7 @@ private VmTyped walkNode(VmTyped nodeVm, VmFunction visit) { return node; } - var childrenVm = (VmList) VmUtils.readMember(node, CHILDREN_ID); + var childrenVm = (VmList) VmUtils.readMember(node, Identifier.CHILDREN); var length = childrenVm.getLength(); if (length == 0) { return node; @@ -214,8 +207,9 @@ private VmTyped walkNode(VmTyped nodeVm, VmFunction visit) { /** Rebuild a node from {@code template} (its type, span, text) with new children. */ private static VmTyped rebuild(VmTyped template, Object[] newChildrenVm) { var nodeType = - NodeType.valueOf(((String) VmUtils.readMember(template, TYPE_ID)).toUpperCase(Locale.ROOT)); - var spanVm = (VmTyped) VmUtils.readMember(template, SPAN_ID); + NodeType.valueOf( + ((String) VmUtils.readMember(template, Identifier.TYPE)).toUpperCase(Locale.ROOT)); + var spanVm = (VmTyped) VmUtils.readMember(template, Identifier.SPAN); var span = readSpan(spanVm); var childJavaNodes = new ArrayList(newChildrenVm.length); @@ -254,14 +248,14 @@ private static Node convertVmToNode(VmTyped nodeVm, FullSpan fallbackSpan) { return ((NodeData) nodeVm.getExtraStorage()).node; } - var typeStr = (String) VmUtils.readMember(nodeVm, TYPE_ID); + var typeStr = (String) VmUtils.readMember(nodeVm, Identifier.TYPE); var nodeType = NodeType.valueOf(typeStr.toUpperCase(Locale.ROOT)); - var ownSpan = readSpan((VmTyped) VmUtils.readMember(nodeVm, SPAN_ID)); + var ownSpan = readSpan((VmTyped) VmUtils.readMember(nodeVm, Identifier.SPAN)); // a constructed node that did not set its own span inherits the insertion point's span var span = ownSpan.equals(ZERO_SPAN) ? fallbackSpan : ownSpan; - var childrenVm = (VmList) VmUtils.readMember(nodeVm, CHILDREN_ID); + var childrenVm = (VmList) VmUtils.readMember(nodeVm, Identifier.CHILDREN); var children = new ArrayList(childrenVm.getLength()); for (var i = 0; i < childrenVm.getLength(); i++) { children.add(convertVmToNode((VmTyped) childrenVm.get(i), span)); @@ -271,10 +265,10 @@ private static Node convertVmToNode(VmTyped nodeVm, FullSpan fallbackSpan) { } private static FullSpan readSpan(VmTyped spanVm) { - var lineStart = ((Long) VmUtils.readMember(spanVm, LINE_START_ID)).intValue(); - var colStart = ((Long) VmUtils.readMember(spanVm, COL_START_ID)).intValue(); - var lineEnd = ((Long) VmUtils.readMember(spanVm, LINE_END_ID)).intValue(); - var colEnd = ((Long) VmUtils.readMember(spanVm, COL_END_ID)).intValue(); + var lineStart = ((Long) VmUtils.readMember(spanVm, Identifier.LINE_START)).intValue(); + var colStart = ((Long) VmUtils.readMember(spanVm, Identifier.COL_START)).intValue(); + var lineEnd = ((Long) VmUtils.readMember(spanVm, Identifier.LINE_END)).intValue(); + var colEnd = ((Long) VmUtils.readMember(spanVm, Identifier.COL_END)).intValue(); return new FullSpan(0, 0, lineStart, colStart, lineEnd, colEnd); } From fef2425ad05855db56097835b5946cec59a9d2a5 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Tue, 14 Jul 2026 11:26:48 +0200 Subject: [PATCH 08/49] Change `toNode` to a property --- .../input/syntax/walk.pkl | 16 +- stdlib/syntax.pkl | 315 +++++++++--------- 2 files changed, 161 insertions(+), 170 deletions(-) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl index 03bf003cf..68103f7ae 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl @@ -32,7 +32,7 @@ facts { ["replace a leaf via a typed node"] { walkFormat("x = 0", (n) -> if (n.type == "int_literal_expr" && n.text == "0") - Pair(new syntax.IntLiteralExprNode { value = 100 }.toNode(), false) + Pair(new syntax.IntLiteralExprNode { value = 100 }.builtNode, false) else null) == fmt("x = 100") } @@ -40,9 +40,9 @@ facts { ["rebuilds ancestors of a changed node"] { walkFormat("x = if (cond) 41 else 0", (n) -> if (n.type == "int_literal_expr" && n.text == "41") - Pair(new syntax.IntLiteralExprNode { value = 42 }.toNode(), false) + Pair(new syntax.IntLiteralExprNode { value = 42 }.builtNode, false) else if (n.type == "int_literal_expr" && n.text == "0") - Pair(new syntax.IntLiteralExprNode { value = 100 }.toNode(), false) + Pair(new syntax.IntLiteralExprNode { value = 100 }.builtNode, false) else null) == fmt("x = if (cond) 42 else 100") } @@ -53,11 +53,11 @@ facts { Pair( new syntax.ParenthesizedExprNode { expression = new syntax.IntLiteralExprNode { value = 1 } - }.toNode(), + }.builtNode, true, ) else if (n.type == "int_literal_expr" && n.text == "1") - Pair(new syntax.IntLiteralExprNode { value = 2 }.toNode(), true) + Pair(new syntax.IntLiteralExprNode { value = 2 }.builtNode, true) else null) == fmt("x = (2)") } @@ -68,11 +68,11 @@ facts { Pair( new syntax.ParenthesizedExprNode { expression = new syntax.IntLiteralExprNode { value = 1 } - }.toNode(), + }.builtNode, false, ) else if (n.type == "int_literal_expr" && n.text == "1") - Pair(new syntax.IntLiteralExprNode { value = 2 }.toNode(), true) + Pair(new syntax.IntLiteralExprNode { value = 2 }.builtNode, true) else null) == fmt("x = (1)") } @@ -80,7 +80,7 @@ facts { ["parent back-references on the result tree"] { local result = syntax.walk(mod("x = 41").node, (n) -> if (n.type == "int_literal_expr") - Pair(new syntax.IntLiteralExprNode { value = 42 }.toNode(), false) + Pair(new syntax.IntLiteralExprNode { value = 42 }.builtNode, false) else null) // the root of the returned tree has no parent diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 867b1cb83..444646545 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -18,18 +18,15 @@ @ModuleInfo { minPklVersion = "0.32.0" } module pkl.syntax -/// Parse the string as a Pkl module, returning either a typed AST node or an error. -function parse(source: String): ModuleNode | ParserError = - let (result = parseNodes(source)) +/// Parse the string or resource as a Pkl module, returning either a typed AST node or an error. +function parse(source: String | Resource): ModuleNode | ParserError = + let (src = if (source is String) source else source.text) + let (result = parseNodes(src)) if (result is ParserError) result else new ModuleNode { node = result } -/// Parse a resource as a Pkl module, returning either a typed AST node or an error. -function parseResource(resourceURI: String): ModuleNode | ParserError = - parse(read(resourceURI).text) - external local function parseNodes(source: String): Node | ParserError /// Format a syntax node back to Pkl source code. @@ -60,7 +57,7 @@ external function walk(node: Node, visit: (Node) -> Pair?): Node /// `Pair(replacement, descend)` where `replacement` is any [SyntaxNode]. /// Nodes without a matching callback are traversed and reused unchanged. /// -/// Because [SyntaxNode.toNode] always rebuilds from fields, return `null` to keep +/// Because [SyntaxNode.builtNode] always rebuilds from fields, return `null` to keep /// a node unchanged — never `Pair(it, ...)`, as that would rebuild its subtree /// (dropping inner comments/spacing). function visit(node: Node, visitor: Visitor): Node = walk(node, (n) -> dispatch(n, visitor)) @@ -515,26 +512,20 @@ abstract class SyntaxNode { hidden comments: List = children.filter((n) -> n.type == "line_comment" || n.type == "block_comment") - /// Rebuild this into a generic [Node]. + /// This node rebuilt into a generic [Node]. /// /// Always constructs a fresh node from this node's fields. - abstract function toNode(): Node + fixed builtNode: Node } /// Base class for expression nodes. -abstract class Expr extends SyntaxNode { - abstract function toNode(): Node -} +abstract class Expr extends SyntaxNode /// Base class for type nodes. -abstract class TypeNode extends SyntaxNode { - abstract function toNode(): Node -} +abstract class TypeNode extends SyntaxNode /// Base class for object member nodes. -abstract class ObjectMemberNode extends SyntaxNode { - abstract function toNode(): Node -} +abstract class ObjectMemberNode extends SyntaxNode /// The top-level module node. class ModuleNode extends SyntaxNode { @@ -566,25 +557,25 @@ class ModuleNode extends SyntaxNode { methods: List = findChildren(node, "class_method").map((n) -> new ClassMethodNode { node = n }) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "module" children = - (if (self.declaration == null) List() else List(self.declaration.toNode())) + (if (self.declaration == null) List() else List(self.declaration.builtNode)) + ( if (self.imports.isEmpty) List() else List(new Node { type = "import_list" - children = self.imports.map((i) -> i.toNode()) + children = self.imports.map((i) -> i.builtNode) }) ) - + self.classes.map((c) -> c.toNode()) - + self.typeAliases.map((t) -> t.toNode()) - + self.properties.map((p) -> p.toNode()) - + self.methods.map((m) -> m.toNode()) + + self.classes.map((c) -> c.builtNode) + + self.typeAliases.map((t) -> t.builtNode) + + self.properties.map((p) -> p.builtNode) + + self.methods.map((m) -> m.builtNode) } } @@ -622,13 +613,13 @@ class ModuleDeclarationNode extends SyntaxNode { let (n = findChild(node, "extends_clause")) if (n == null) null else getStringChars(n) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "module_declaration" children = - (if (self.docComment == null) List() else List(self.docComment.toNode())) - + self.annotations.map((a) -> a.toNode()) + (if (self.docComment == null) List() else List(self.docComment.builtNode)) + + self.annotations.map((a) -> a.builtNode) + ( if (self.name != null) List(new Node { @@ -676,7 +667,7 @@ class ImportNode extends SyntaxNode { let (aliasNode = findChild(node, "import_alias")) if (aliasNode == null) null else identifierText(aliasNode) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "import" @@ -743,13 +734,13 @@ class ClassNode extends SyntaxNode { let (n = findChild(node, "class_body")) if (n == null) null else new ClassBodyNode { node = n } - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "class" children = - (if (self.docComment == null) List() else List(self.docComment.toNode())) - + self.annotations.map((a) -> a.toNode()) + (if (self.docComment == null) List() else List(self.docComment.builtNode)) + + self.annotations.map((a) -> a.builtNode) + List(new Node { type = "class_header" children = @@ -765,11 +756,11 @@ class ClassNode extends SyntaxNode { else List(new Node { type = "class_header_extends" - children = List((terminal) { text = "extends" }, self.extendsType.toNode()) + children = List((terminal) { text = "extends" }, self.extendsType.builtNode) }) ) }) - + (if (self.body == null) List() else List(self.body.toNode())) + + (if (self.body == null) List() else List(self.body.builtNode)) } } @@ -810,13 +801,13 @@ class TypeAliasNode extends SyntaxNode { let (t = findTypeChild(body!!)) wrapTypeNode(t!!) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "typealias" children = - (if (self.docComment == null) List() else List(self.docComment.toNode())) - + self.annotations.map((a) -> a.toNode()) + (if (self.docComment == null) List() else List(self.docComment.builtNode)) + + self.annotations.map((a) -> a.builtNode) + List(new Node { type = "typealias_header" children = @@ -830,7 +821,7 @@ class TypeAliasNode extends SyntaxNode { }) + List(new Node { type = "typealias_body" - children = List(self.type.toNode()) + children = List(self.type.builtNode) }) } } @@ -853,7 +844,7 @@ class ClassBodyNode extends SyntaxNode { else findChildren(elements, "class_method").map((n) -> new ClassMethodNode { node = n }) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "class_body" @@ -862,7 +853,7 @@ class ClassBodyNode extends SyntaxNode { + ( let ( members = - self.properties.map((p) -> p.toNode()) + self.methods.map((m) -> m.toNode()) + self.properties.map((p) -> p.builtNode) + self.methods.map((m) -> m.builtNode) ) if (members.isEmpty) List() @@ -911,13 +902,13 @@ class ClassPropertyNode extends SyntaxNode { objectBodies: List = findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "class_property" children = - (if (self.docComment == null) List() else List(self.docComment.toNode())) - + self.annotations.map((a) -> a.toNode()) + (if (self.docComment == null) List() else List(self.docComment.builtNode)) + + self.annotations.map((a) -> a.builtNode) + List(new Node { type = "class_property_header" children = @@ -935,11 +926,11 @@ class ClassPropertyNode extends SyntaxNode { (terminal) { text = "=" }, new Node { type = "class_property_body" - children = List(self.value.toNode()) + children = List(self.value.builtNode) }, ) else - self.objectBodies.map((b) -> b.toNode()) + self.objectBodies.map((b) -> b.builtNode) ) } } @@ -994,13 +985,13 @@ class ClassMethodNode extends SyntaxNode { let (e = findExprChild(bodyNode)) if (e == null) null else wrapExpr(e) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "class_method" children = - (if (self.docComment == null) List() else List(self.docComment.toNode())) - + self.annotations.map((a) -> a.toNode()) + (if (self.docComment == null) List() else List(self.docComment.builtNode)) + + self.annotations.map((a) -> a.builtNode) + List(new Node { type = "class_method_header" children = @@ -1021,7 +1012,7 @@ class ClassMethodNode extends SyntaxNode { (terminal) { text = "=" }, new Node { type = "class_method_body" - children = List(self.body.toNode()) + children = List(self.body.builtNode) }, ) ) @@ -1049,7 +1040,7 @@ class ObjectBodyNode extends SyntaxNode { .filter((c) -> isObjectMemberType(c.type)) .map((n) -> wrapObjectMember(n)) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "object_body" @@ -1062,7 +1053,7 @@ class ObjectBodyNode extends SyntaxNode { List(new Node { type = "object_parameter_list" children = - commaSeparate(self.parameters.map((p) -> p.toNode())) + commaSeparate(self.parameters.map((p) -> p.builtNode)) .add((terminal) { text = "->" }) }) ) @@ -1072,7 +1063,7 @@ class ObjectBodyNode extends SyntaxNode { else List(new Node { type = "object_member_list" - children = self.members.map((m) -> m.toNode()) + children = self.members.map((m) -> m.builtNode) }) ) + List((terminal) { text = "}" }) @@ -1108,7 +1099,7 @@ class ObjectPropertyNode extends ObjectMemberNode { objectBodies: List = findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "object_property" @@ -1130,11 +1121,11 @@ class ObjectPropertyNode extends ObjectMemberNode { (terminal) { text = "=" }, new Node { type = "object_property_body" - children = List(self.value.toNode()) + children = List(self.value.builtNode) }, ) else - self.objectBodies.map((b) -> b.toNode()) + self.objectBodies.map((b) -> b.builtNode) ) } } @@ -1176,7 +1167,7 @@ class ObjectMethodNode extends ObjectMemberNode { let (bodyNode = findChild(node, "class_method_body")) wrapExpr(findExprChild(bodyNode!!)!!) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "object_method" @@ -1197,7 +1188,7 @@ class ObjectMethodNode extends ObjectMemberNode { (terminal) { text = "=" }, new Node { type = "class_method_body" - children = List(self.body.toNode()) + children = List(self.body.builtNode) }, ) } @@ -1208,11 +1199,11 @@ class ObjectElementNode extends ObjectMemberNode { /// The expression value. expression: Expr = wrapExpr(findExprChild(node)!!) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "object_element" - children = List(self.expression.toNode()) + children = List(self.expression.builtNode) } } @@ -1232,7 +1223,7 @@ class ObjectEntryNode extends ObjectMemberNode { objectBodies: List = findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "object_entry" @@ -1242,16 +1233,16 @@ class ObjectEntryNode extends ObjectMemberNode { children = List( (terminal) { text = "[" }, - self.key.toNode(), + self.key.builtNode, (terminal) { text = "]" }, ) + (if (self.value != null) List((terminal) { text = "=" }) else List()) }) + ( if (self.value != null) - List(self.value.toNode()) + List(self.value.builtNode) else - self.objectBodies.map((b) -> b.toNode()) + self.objectBodies.map((b) -> b.builtNode) ) } } @@ -1264,14 +1255,14 @@ class ObjectSpreadNode extends ObjectMemberNode { /// The spread expression. expression: Expr = wrapExpr(findExprChild(node)!!) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "object_spread" children = List( (terminal) { text = if (self.isNullable) "...?" else "..." }, - self.expression.toNode(), + self.expression.builtNode, ) } } @@ -1290,22 +1281,22 @@ class MemberPredicateNode extends ObjectMemberNode { objectBodies: List = findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "member_predicate" children = List( (terminal) { text = "[[" }, - self.condition.toNode(), + self.condition.builtNode, (terminal) { text = "]" }, (terminal) { text = "]" }, ) + ( if (self.value != null) - List((terminal) { text = "=" }, self.value.toNode()) + List((terminal) { text = "=" }, self.value.builtNode) else - self.objectBodies.map((b) -> b.toNode()) + self.objectBodies.map((b) -> b.builtNode) ) } } @@ -1332,7 +1323,7 @@ class ForGeneratorNode extends ObjectMemberNode { let (n = findChild(node, "object_body")) new ObjectBodyNode { node = n!! } - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "for_generator" @@ -1353,23 +1344,23 @@ class ForGeneratorNode extends ObjectMemberNode { children = ( if (self.keyParameter == null) - List(self.valueParameter.toNode()) + List(self.valueParameter.builtNode) else List( - self.keyParameter.toNode(), + self.keyParameter.builtNode, (terminal) { text = "," }, - self.valueParameter.toNode(), + self.valueParameter.builtNode, ) ) + List((terminal) { text = "in" }) }, - self.iterable.toNode(), + self.iterable.builtNode, ) }, (terminal) { text = ")" }, ) }, - self.body.toNode(), + self.body.builtNode, ) } } @@ -1389,7 +1380,7 @@ class WhenGeneratorNode extends ObjectMemberNode { elseBody: ObjectBodyNode? = if (bodyNodes.length < 2) null else new ObjectBodyNode { node = bodyNodes[1] } - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "when_generator" @@ -1401,39 +1392,39 @@ class WhenGeneratorNode extends ObjectMemberNode { children = List( (terminal) { text = "(" }, - self.condition.toNode(), + self.condition.builtNode, (terminal) { text = ")" }, ) }, - self.thenBody.toNode(), + self.thenBody.builtNode, ) + ( if (self.elseBody == null) List() else - List((terminal) { text = "else" }, self.elseBody.toNode()) + List((terminal) { text = "else" }, self.elseBody.builtNode) ) } } /// The `this` expression. class ThisExprNode extends Expr { - function toNode(): Node = new Node { type = "this_expr"; text = "this" } + fixed builtNode = new Node { type = "this_expr"; text = "this" } } /// The `outer` expression. class OuterExprNode extends Expr { - function toNode(): Node = new Node { type = "outer_expr"; text = "outer" } + fixed builtNode = new Node { type = "outer_expr"; text = "outer" } } /// The `module` expression. class ModuleExprNode extends Expr { - function toNode(): Node = new Node { type = "module_expr"; text = "module" } + fixed builtNode = new Node { type = "module_expr"; text = "module" } } /// A `null` literal expression. class NullLiteralExprNode extends Expr { - function toNode(): Node = new Node { type = "null_expr"; text = "null" } + fixed builtNode = new Node { type = "null_expr"; text = "null" } } /// A boolean literal expression (`true` or `false`). @@ -1441,7 +1432,7 @@ class BoolLiteralExprNode extends Expr { /// The boolean value. value: Boolean = node?.text == "true" - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "bool_literal_expr"; text = if (self.value) "true" else "false" } } @@ -1451,7 +1442,7 @@ class IntLiteralExprNode extends Expr { /// The integer literal (e.g. `42`, `"0xFF"`). value: Int | String = node?.text ?? "" - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "int_literal_expr"; text = self.value.toString() } } @@ -1461,7 +1452,7 @@ class FloatLiteralExprNode extends Expr { /// The float literal (e.g. `3.14`, `"1.0e10"`). value: Float | String = node?.text ?? "" - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "float_literal_expr"; text = self.value.toString() } } @@ -1471,7 +1462,7 @@ class SingleLineStringLiteralExprNode extends Expr { /// The string parts (chars, escapes, interpolations). parts: List = buildStringParts(children) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "single_line_string_literal_expr" @@ -1489,7 +1480,7 @@ class MultiLineStringLiteralExprNode extends Expr { /// The string parts (chars, escapes, newlines, interpolations). parts: List = buildStringParts(children) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "multi_line_string_literal_expr" @@ -1516,7 +1507,7 @@ class UnqualifiedAccessExprNode extends Expr { let (n = findChild(node, "argument_list")) if (n == null) null else argumentsOf(n) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "unqualified_access_expr" @@ -1546,13 +1537,13 @@ class QualifiedAccessExprNode extends Expr { let (n = findChild(m, "argument_list")) if (n == null) null else argumentsOf(n) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "qualified_access_expr" children = List( - self.receiver.toNode(), + self.receiver.builtNode, (operatorLeaf) { text = if (self.isNullSafe) "?." else "." }, new Node { type = "unqualified_access_expr" @@ -1574,15 +1565,15 @@ class SubscriptExprNode extends Expr { /// The index expression. index: Expr = wrapExpr(findExprChildren(node)[1]) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "subscript_expr" children = List( - self.receiver.toNode(), + self.receiver.builtNode, (operatorLeaf) { text = "[" }, - self.index.toNode(), + self.index.builtNode, (terminal) { text = "]" }, ) } @@ -1592,14 +1583,14 @@ class SubscriptExprNode extends Expr { /// /// Read-only: this node has no builder and cannot be constructed from scratch. class SuperAccessExprNode extends Expr { - function toNode(): Node = node!! + fixed builtNode = node!! } /// A `super[index]` subscript expression. /// /// Read-only: this node has no builder and cannot be constructed from scratch. class SuperSubscriptExprNode extends Expr { - function toNode(): Node = node!! + fixed builtNode = node!! } /// An `if (condition) thenExpr else elseExpr` expression. @@ -1621,7 +1612,7 @@ class IfExprNode extends Expr { let (elseNode = findChild(node, "if_else_expr")) wrapExpr(findExprChild(elseNode!!)!!) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "if_expr" @@ -1639,7 +1630,7 @@ class IfExprNode extends Expr { (terminal) { text = "(" }, new Node { type = "if_condition_expr" - children = List(self.condition.toNode()) + children = List(self.condition.builtNode) }, (terminal) { text = ")" }, ) @@ -1648,12 +1639,12 @@ class IfExprNode extends Expr { }, new Node { type = "if_then_expr" - children = List(self.thenExpr.toNode()) + children = List(self.thenExpr.builtNode) }, (terminal) { text = "else" }, new Node { type = "if_else_expr" - children = List(self.elseExpr.toNode()) + children = List(self.elseExpr.builtNode) }, ) } @@ -1675,7 +1666,7 @@ class LetExprNode extends Expr { /// The body expression. body: Expr = wrapExpr(findExprChild(node)!!) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "let_expr" @@ -1691,15 +1682,15 @@ class LetExprNode extends Expr { type = "let_parameter" children = List( - self.parameter.toNode(), + self.parameter.builtNode, (terminal) { text = "=" }, - self.bindingValue.toNode(), + self.bindingValue.builtNode, ) }, (terminal) { text = ")" }, ) }, - self.body.toNode(), + self.body.builtNode, ) } } @@ -1709,7 +1700,7 @@ class ThrowExprNode extends Expr { /// The expression being thrown. expression: Expr = wrapExpr(findExprChild(node)!!) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "throw_expr" @@ -1717,7 +1708,7 @@ class ThrowExprNode extends Expr { List( (terminal) { text = "throw" }, (terminal) { text = "(" }, - self.expression.toNode(), + self.expression.builtNode, (terminal) { text = ")" }, ) } @@ -1728,7 +1719,7 @@ class TraceExprNode extends Expr { /// The expression being traced. expression: Expr = wrapExpr(findExprChild(node)!!) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "trace_expr" @@ -1736,7 +1727,7 @@ class TraceExprNode extends Expr { List( (terminal) { text = "trace" }, (terminal) { text = "(" }, - self.expression.toNode(), + self.expression.builtNode, (terminal) { text = ")" }, ) } @@ -1750,7 +1741,7 @@ class ImportExprNode extends Expr { /// The import URI string. uri: String = getStringChars(node) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "import_expr" @@ -1773,7 +1764,7 @@ class ReadExprNode extends Expr { /// The expression to be read. expression: Expr = wrapExpr(findExprChild(node)!!) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "read_expr" @@ -1781,7 +1772,7 @@ class ReadExprNode extends Expr { List( (terminal) { text = self.keyword }, (terminal) { text = "(" }, - self.expression.toNode(), + self.expression.builtNode, (terminal) { text = ")" }, ) } @@ -1801,7 +1792,7 @@ class NewExprNode extends Expr { let (n = findChild(node, "object_body")) new ObjectBodyNode { node = n!! } - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "new_expr" @@ -1813,9 +1804,9 @@ class NewExprNode extends Expr { if (self.type == null) List((terminal) { text = "new" }) else - List((terminal) { text = "new" }, self.type.toNode()) + List((terminal) { text = "new" }, self.type.builtNode) }, - self.body.toNode(), + self.body.builtNode, ) } } @@ -1830,11 +1821,11 @@ class AmendsExprNode extends Expr { let (n = findChild(node, "object_body")) new ObjectBodyNode { node = n!! } - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "amends_expr" - children = List(self.parentExpr.toNode(), self.body.toNode()) + children = List(self.parentExpr.builtNode, self.body.builtNode) } } @@ -1856,22 +1847,22 @@ class BinaryOpExprNode extends Expr { let (t = findTypeChild(node)) if (t == null) null else wrapTypeNode(t) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "binary_op_expr" children = if (self.operator == "is" || self.operator == "as") List( - self.left.toNode(), + self.left.builtNode, (operatorLeaf) { text = self.operator }, - self.rightType!!.toNode(), + self.rightType!!.builtNode, ) else List( - self.left.toNode(), + self.left.builtNode, (operatorLeaf) { text = self.operator }, - self.right!!.toNode(), + self.right!!.builtNode, ) } } @@ -1881,11 +1872,11 @@ class UnaryMinusExprNode extends Expr { /// The operand expression. operand: Expr = wrapExpr(findExprChild(node)!!) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "unary_minus_expr" - children = List((terminal) { text = "-" }, self.operand.toNode()) + children = List((terminal) { text = "-" }, self.operand.builtNode) } } @@ -1894,11 +1885,11 @@ class LogicalNotExprNode extends Expr { /// The operand expression. operand: Expr = wrapExpr(findExprChild(node)!!) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "logical_not_expr" - children = List((terminal) { text = "!" }, self.operand.toNode()) + children = List((terminal) { text = "!" }, self.operand.builtNode) } } @@ -1907,11 +1898,11 @@ class NonNullExprNode extends Expr { /// The operand expression. operand: Expr = wrapExpr(findExprChild(node)!!) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "non_null_expr" - children = List(self.operand.toNode(), (operatorLeaf) { text = "!!" }) + children = List(self.operand.builtNode, (operatorLeaf) { text = "!!" }) } } @@ -1927,7 +1918,7 @@ class FunctionLiteralExprNode extends Expr { let (bodyNode = findChild(node, "function_literal_body")) wrapExpr(findExprChild(bodyNode!!)!!) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "function_literal_expr" @@ -1937,7 +1928,7 @@ class FunctionLiteralExprNode extends Expr { (terminal) { text = "->" }, new Node { type = "function_literal_body" - children = List(self.body.toNode()) + children = List(self.body.builtNode) }, ) } @@ -1954,7 +1945,7 @@ class ParenthesizedExprNode extends Expr { let (e = findExprChild(elems)) if (e == null) null else wrapExpr(e) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "parenthesized_expr" @@ -1963,7 +1954,7 @@ class ParenthesizedExprNode extends Expr { (terminal) { text = "(" }, new Node { type = "parenthesized_expr_elements" - children = List(self.expression!!.toNode()) + children = List(self.expression!!.builtNode) }, (terminal) { text = ")" }, ) @@ -1972,17 +1963,17 @@ class ParenthesizedExprNode extends Expr { /// The `unknown` type. class UnknownTypeNode extends TypeNode { - function toNode(): Node = new Node { type = "unknown_type"; text = "unknown" } + fixed builtNode = new Node { type = "unknown_type"; text = "unknown" } } /// The `nothing` type. class NothingTypeNode extends TypeNode { - function toNode(): Node = new Node { type = "nothing_type"; text = "nothing" } + fixed builtNode = new Node { type = "nothing_type"; text = "nothing" } } /// The `module` type. class ModuleTypeNode extends TypeNode { - function toNode(): Node = new Node { type = "module_type"; text = "module" } + fixed builtNode = new Node { type = "module_type"; text = "module" } } /// A declared type (e.g., `String`, `List`). @@ -2001,7 +1992,7 @@ class DeclaredTypeNode extends TypeNode { let (elems = findChild(tal, "type_argument_list_elements")) if (elems == null) List() else findTypeChildren(elems).map((n) -> wrapTypeNode(n)) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "declared_type" @@ -2016,7 +2007,7 @@ class DeclaredTypeNode extends TypeNode { (terminal) { text = "<" }, new Node { type = "type_argument_list_elements" - children = commaSeparate(self.typeArguments.map((t) -> t.toNode())) + children = commaSeparate(self.typeArguments.map((t) -> t.builtNode)) }, (terminal) { text = ">" }, ) @@ -2029,11 +2020,11 @@ class NullableTypeNode extends TypeNode { /// The base type. baseType: TypeNode = wrapTypeNode(findTypeChild(node)!!) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "nullable_type" - children = List(self.baseType.toNode(), (terminal) { text = "?" }) + children = List(self.baseType.builtNode, (terminal) { text = "?" }) } } @@ -2042,13 +2033,13 @@ class UnionTypeNode extends TypeNode { /// The member types. members: List = findTypeChildren(node).map((n) -> wrapTypeNode(n)) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "union_type" children = self.members - .map((m) -> m.toNode()) + .map((m) -> m.builtNode) .fold(List(), (acc: List, item: Node) -> if (acc.isEmpty) List(item) else acc.add((terminal) { text = "|" }).add(item) ) @@ -2067,7 +2058,7 @@ class FunctionTypeNode extends TypeNode { /// The return type. returnType: TypeNode = wrapTypeNode(findTypeChildren(node).last) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "function_type" @@ -2083,13 +2074,13 @@ class FunctionTypeNode extends TypeNode { (terminal) { text = "(" }, new Node { type = "parenthesized_type_elements" - children = commaSeparate(self.parameterTypes.map((t) -> t.toNode())) + children = commaSeparate(self.parameterTypes.map((t) -> t.builtNode)) }, (terminal) { text = ")" }, ) }, (terminal) { text = "->" }, - self.returnType.toNode(), + self.returnType.builtNode, ) } } @@ -2105,19 +2096,19 @@ class ConstrainedTypeNode extends TypeNode { /// The constraint expressions. constraints: List = findExprChildren(constraintElems).map((n) -> wrapExpr(n)) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "constrained_type" children = - List(self.baseType.toNode(), new Node { + List(self.baseType.builtNode, new Node { type = "constrained_type_constraint" children = List( (terminal) { text = "(" }, new Node { type = "constrained_type_elements" - children = commaSeparate(self.constraints.map((c) -> c.toNode())) + children = commaSeparate(self.constraints.map((c) -> c.builtNode)) }, (terminal) { text = ")" }, ) @@ -2136,7 +2127,7 @@ class ParenthesizedTypeNode extends TypeNode { let (t = findTypeChild(elems)) if (t == null) null else wrapTypeNode(t) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "parenthesized_type" @@ -2145,7 +2136,7 @@ class ParenthesizedTypeNode extends TypeNode { (terminal) { text = "(" }, new Node { type = "parenthesized_type_elements" - children = List(self.type!!.toNode()) + children = List(self.type!!.builtNode) }, (terminal) { text = ")" }, ) @@ -2157,7 +2148,7 @@ class StringConstantTypeNode extends TypeNode { /// The string value. value: String = getStringChars(node) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "string_constant_type" @@ -2175,13 +2166,13 @@ class AnnotationNode extends SyntaxNode { let (n = findChild(node, "object_body")) if (n == null) null else new ObjectBodyNode { node = n } - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "annotation" children = - List((terminal) { text = "@" }, self.type.toNode()) - + (if (self.body == null) List() else List(self.body.toNode())) + List((terminal) { text = "@" }, self.type.builtNode) + + (if (self.body == null) List() else List(self.body.builtNode)) } } @@ -2205,7 +2196,7 @@ class ParameterNode extends SyntaxNode { let (n = findChild(node, "type_annotation")) if (n == null) null else wrapTypeNode(findTypeChild(n)!!) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "parameter" @@ -2229,7 +2220,7 @@ class TypeParameterNode extends SyntaxNode { /// The type parameter name. name: String = identifierText(node!!) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "type_parameter" @@ -2251,7 +2242,7 @@ class DocCommentNode extends SyntaxNode { if (l.startsWith("///")) l.drop(3) else l ) - function toNode(): Node = + fixed builtNode = let (self = this) new Node { type = "doc_comment" @@ -2302,7 +2293,7 @@ class StringInterpolationNode extends StringPartNode { let (self = this) List( (terminal) { text = "\\(" }, - self.expression.toNode(), + self.expression.builtNode, (terminal) { text = ")" }, ) } @@ -2448,7 +2439,7 @@ local const function parameterListNode(parameters: List): Node = (terminal) { text = "(" }, new Node { type = "parameter_list_elements" - children = commaSeparate(parameters.map((p) -> p.toNode())) + children = commaSeparate(parameters.map((p) -> p.builtNode)) }, (terminal) { text = ")" }, ) @@ -2465,7 +2456,7 @@ local const function argumentListNode(arguments: List): Node = new Node { (terminal) { text = "(" }, new Node { type = "argument_list_elements" - children = commaSeparate(arguments.map((a) -> a.toNode())) + children = commaSeparate(arguments.map((a) -> a.builtNode)) }, (terminal) { text = ")" }, ) @@ -2483,7 +2474,7 @@ local const function typeParameterListNodes(typeParameters: List t.toNode())) + children = commaSeparate(typeParameters.map((t) -> t.builtNode)) }, (terminal) { text = ">" }, ) @@ -2496,7 +2487,7 @@ local const function typeAnnotationNodes(_type: TypeNode?): List = else List(new Node { type = "type_annotation" - children = List((terminal) { text = ":" }, _type.toNode()) + children = List((terminal) { text = ":" }, _type.builtNode) }) // =============== @@ -2582,7 +2573,7 @@ class Visitor { // Map a typed visitor result into the raw-node result that `walk` consumes. local const function toWalkResult(r: Pair?): Pair? = - if (r == null) null else Pair(r.first.toNode(), r.second) + if (r == null) null else Pair(r.first.builtNode, r.second) // Dispatch a raw node to the matching visitor callback (or `null` if none applies). local function dispatch(n: Node, v: Visitor): Pair? = From 1ab39ed0f744f9092d1b4ff5f0ddee722756ea44 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Tue, 14 Jul 2026 17:13:41 +0200 Subject: [PATCH 09/49] Refactor functions --- stdlib/syntax.pkl | 579 ++++++++++++++++++++-------------------------- 1 file changed, 255 insertions(+), 324 deletions(-) diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 444646545..1d5fa8204 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -86,41 +86,7 @@ function fold(node: Node, initial: Acc, accumulate: (Acc, Node) -> Acc): Ac function descendants(node: Node): List = fold(node, List(), (acc, n) -> acc.add(n)) /// Wrap a raw [node] into its typed [SyntaxNode], or `null` if it has no typed form. -function wrap(_node: Node): SyntaxNode? = - if (isExprType(_node.type)) - wrapExpr(_node) - else if (isTypeType(_node.type)) - wrapTypeNode(_node) - else if (isObjectMemberType(_node.type)) - wrapObjectMember(_node) - else if (_node.type == "module") - new ModuleNode { node = _node } - else if (_node.type == "module_declaration") - new ModuleDeclarationNode { node = _node } - else if (_node.type == "import") - new ImportNode { node = _node } - else if (_node.type == "class") - new ClassNode { node = _node } - else if (_node.type == "typealias") - new TypeAliasNode { node = _node } - else if (_node.type == "class_body") - new ClassBodyNode { node = _node } - else if (_node.type == "class_property") - new ClassPropertyNode { node = _node } - else if (_node.type == "class_method") - new ClassMethodNode { node = _node } - else if (_node.type == "object_body") - new ObjectBodyNode { node = _node } - else if (_node.type == "annotation") - new AnnotationNode { node = _node } - else if (_node.type == "parameter") - new ParameterNode { node = _node } - else if (_node.type == "type_parameter") - new TypeParameterNode { node = _node } - else if (_node.type == "doc_comment") - new DocCommentNode { node = _node } - else - null +function wrap(_node: Node): SyntaxNode? = constructors.getOrNull(_node.type)?.apply(_node) class Node { type: NodeType @@ -129,6 +95,24 @@ class Node { text: String? @ConvertSpan span: Span + + /// The first child of type [t], or `null` if there is none. + function findChild(t: NodeType): Node? = children.findOrNull((c) -> c.type == t) + + /// All children of type [t]. + function findChildren(t: NodeType): List = children.filter((c) -> c.type == t) + + /// The first child that is an expression, or `null` if there is none. + function findExprChild(): Node? = children.findOrNull((c) -> isExprType(c.type)) + + /// All children that are expressions. + function findExprChildren(): List = children.filter((c) -> isExprType(c.type)) + + /// The first child that is a type node, or `null` if there is none. + function findTypeChild(): Node? = children.findOrNull((c) -> isTypeType(c.type)) + + /// All children that are type nodes. + function findTypeChildren(): List = children.filter((c) -> isTypeType(c.type)) } class Span { @@ -278,30 +262,6 @@ typealias NodeType = | "constrained_type_constraint" | "constrained_type_elements" -// Find first child of a given type within a node. -local const function findChild(n: Node?, t: NodeType): Node? = - if (n == null) null else n.children.findOrNull((c) -> c.type == t) - -// Find all children of a given type. -local const function findChildren(n: Node?, t: NodeType): List = - if (n == null) List() else n.children.filter((c) -> c.type == t) - -// Find the first child whose type is one of the expression types. -local const function findExprChild(n: Node?): Node? = - if (n == null) null else n.children.findOrNull((c) -> isExprType(c.type)) - -// Find all children whose type is one of the expression types. -local const function findExprChildren(n: Node?): List = - if (n == null) List() else n.children.filter((c) -> isExprType(c.type)) - -// Find the first child whose type is one of the type node types. -local const function findTypeChild(n: Node?): Node? = - if (n == null) null else n.children.findOrNull((c) -> isTypeType(c.type)) - -// Find all children whose type is one of the type node types. -local const function findTypeChildren(n: Node?): List = - if (n == null) List() else n.children.filter((c) -> isTypeType(c.type)) - // Check if a NodeType represents an expression. local const function isExprType(t: NodeType): Boolean = t == "this_expr" @@ -357,112 +317,83 @@ local const function isObjectMemberType(t: NodeType): Boolean = || t == "when_generator" || t == "for_generator" +// Constructs the typed [SyntaxNode] backing each node type. +local const constructors: Map SyntaxNode> = + Map( + // module structure + "module", (n) -> new ModuleNode { node = n }, + "module_declaration", (n) -> new ModuleDeclarationNode { node = n }, + "import", (n) -> new ImportNode { node = n }, + "class", (n) -> new ClassNode { node = n }, + "typealias", (n) -> new TypeAliasNode { node = n }, + "class_body", (n) -> new ClassBodyNode { node = n }, + "class_property", (n) -> new ClassPropertyNode { node = n }, + "class_method", (n) -> new ClassMethodNode { node = n }, + "object_body", (n) -> new ObjectBodyNode { node = n }, + "annotation", (n) -> new AnnotationNode { node = n }, + "parameter", (n) -> new ParameterNode { node = n }, + "type_parameter", (n) -> new TypeParameterNode { node = n }, + "doc_comment", (n) -> new DocCommentNode { node = n }, + // object members + "object_element", (n) -> new ObjectElementNode { node = n }, + "object_property", (n) -> new ObjectPropertyNode { node = n }, + "object_method", (n) -> new ObjectMethodNode { node = n }, + "member_predicate", (n) -> new MemberPredicateNode { node = n }, + "object_entry", (n) -> new ObjectEntryNode { node = n }, + "object_spread", (n) -> new ObjectSpreadNode { node = n }, + "when_generator", (n) -> new WhenGeneratorNode { node = n }, + "for_generator", (n) -> new ForGeneratorNode { node = n }, + // expressions + "this_expr", (n) -> new ThisExprNode { node = n }, + "outer_expr", (n) -> new OuterExprNode { node = n }, + "module_expr", (n) -> new ModuleExprNode { node = n }, + "null_expr", (n) -> new NullLiteralExprNode { node = n }, + "bool_literal_expr", (n) -> new BoolLiteralExprNode { node = n }, + "int_literal_expr", (n) -> new IntLiteralExprNode { node = n }, + "float_literal_expr", (n) -> new FloatLiteralExprNode { node = n }, + "single_line_string_literal_expr", (n) -> new SingleLineStringLiteralExprNode { node = n }, + "multi_line_string_literal_expr", (n) -> new MultiLineStringLiteralExprNode { node = n }, + "unqualified_access_expr", (n) -> new UnqualifiedAccessExprNode { node = n }, + "qualified_access_expr", (n) -> new QualifiedAccessExprNode { node = n }, + "subscript_expr", (n) -> new SubscriptExprNode { node = n }, + "super_access_expr", (n) -> new SuperAccessExprNode { node = n }, + "super_subscript_expr", (n) -> new SuperSubscriptExprNode { node = n }, + "if_expr", (n) -> new IfExprNode { node = n }, + "let_expr", (n) -> new LetExprNode { node = n }, + "throw_expr", (n) -> new ThrowExprNode { node = n }, + "trace_expr", (n) -> new TraceExprNode { node = n }, + "import_expr", (n) -> new ImportExprNode { node = n }, + "read_expr", (n) -> new ReadExprNode { node = n }, + "new_expr", (n) -> new NewExprNode { node = n }, + "amends_expr", (n) -> new AmendsExprNode { node = n }, + "binary_op_expr", (n) -> new BinaryOpExprNode { node = n }, + "unary_minus_expr", (n) -> new UnaryMinusExprNode { node = n }, + "logical_not_expr", (n) -> new LogicalNotExprNode { node = n }, + "non_null_expr", (n) -> new NonNullExprNode { node = n }, + "function_literal_expr", (n) -> new FunctionLiteralExprNode { node = n }, + "parenthesized_expr", (n) -> new ParenthesizedExprNode { node = n }, + // types + "unknown_type", (n) -> new UnknownTypeNode { node = n }, + "nothing_type", (n) -> new NothingTypeNode { node = n }, + "module_type", (n) -> new ModuleTypeNode { node = n }, + "declared_type", (n) -> new DeclaredTypeNode { node = n }, + "nullable_type", (n) -> new NullableTypeNode { node = n }, + "union_type", (n) -> new UnionTypeNode { node = n }, + "function_type", (n) -> new FunctionTypeNode { node = n }, + "constrained_type", (n) -> new ConstrainedTypeNode { node = n }, + "parenthesized_type", (n) -> new ParenthesizedTypeNode { node = n }, + "string_constant_type", (n) -> new StringConstantTypeNode { node = n }, + ) + // Wrap a raw Node into the appropriate Expr subclass. -local const function wrapExpr(n: Node): Expr = - if (n.type == "this_expr") - new ThisExprNode { node = n } - else if (n.type == "outer_expr") - new OuterExprNode { node = n } - else if (n.type == "module_expr") - new ModuleExprNode { node = n } - else if (n.type == "null_expr") - new NullLiteralExprNode { node = n } - else if (n.type == "bool_literal_expr") - new BoolLiteralExprNode { node = n } - else if (n.type == "int_literal_expr") - new IntLiteralExprNode { node = n } - else if (n.type == "float_literal_expr") - new FloatLiteralExprNode { node = n } - else if (n.type == "single_line_string_literal_expr") - new SingleLineStringLiteralExprNode { node = n } - else if (n.type == "multi_line_string_literal_expr") - new MultiLineStringLiteralExprNode { node = n } - else if (n.type == "unqualified_access_expr") - new UnqualifiedAccessExprNode { node = n } - else if (n.type == "qualified_access_expr") - new QualifiedAccessExprNode { node = n } - else if (n.type == "subscript_expr") - new SubscriptExprNode { node = n } - else if (n.type == "super_access_expr") - new SuperAccessExprNode { node = n } - else if (n.type == "super_subscript_expr") - new SuperSubscriptExprNode { node = n } - else if (n.type == "if_expr") - new IfExprNode { node = n } - else if (n.type == "let_expr") - new LetExprNode { node = n } - else if (n.type == "throw_expr") - new ThrowExprNode { node = n } - else if (n.type == "trace_expr") - new TraceExprNode { node = n } - else if (n.type == "import_expr") - new ImportExprNode { node = n } - else if (n.type == "read_expr") - new ReadExprNode { node = n } - else if (n.type == "new_expr") - new NewExprNode { node = n } - else if (n.type == "amends_expr") - new AmendsExprNode { node = n } - else if (n.type == "binary_op_expr") - new BinaryOpExprNode { node = n } - else if (n.type == "unary_minus_expr") - new UnaryMinusExprNode { node = n } - else if (n.type == "logical_not_expr") - new LogicalNotExprNode { node = n } - else if (n.type == "non_null_expr") - new NonNullExprNode { node = n } - else if (n.type == "function_literal_expr") - new FunctionLiteralExprNode { node = n } - else if (n.type == "parenthesized_expr") - new ParenthesizedExprNode { node = n } - else - throw("Unknown expression type: \(n.type)") +local const function wrapExpr(n: Node): Expr = constructors[n.type].apply(n) as Expr // Wrap a raw Node into the appropriate TypeNode subclass. -local const function wrapTypeNode(n: Node): TypeNode = - if (n.type == "unknown_type") - new UnknownTypeNode { node = n } - else if (n.type == "nothing_type") - new NothingTypeNode { node = n } - else if (n.type == "module_type") - new ModuleTypeNode { node = n } - else if (n.type == "declared_type") - new DeclaredTypeNode { node = n } - else if (n.type == "nullable_type") - new NullableTypeNode { node = n } - else if (n.type == "union_type") - new UnionTypeNode { node = n } - else if (n.type == "function_type") - new FunctionTypeNode { node = n } - else if (n.type == "constrained_type") - new ConstrainedTypeNode { node = n } - else if (n.type == "parenthesized_type") - new ParenthesizedTypeNode { node = n } - else if (n.type == "string_constant_type") - new StringConstantTypeNode { node = n } - else - throw("Unknown type node type: \(n.type)") +local const function wrapTypeNode(n: Node): TypeNode = constructors[n.type].apply(n) as TypeNode // Wrap a raw Node into the appropriate ObjectMemberNode subclass. local const function wrapObjectMember(n: Node): ObjectMemberNode = - if (n.type == "object_element") - new ObjectElementNode { node = n } - else if (n.type == "object_property") - new ObjectPropertyNode { node = n } - else if (n.type == "object_method") - new ObjectMethodNode { node = n } - else if (n.type == "member_predicate") - new MemberPredicateNode { node = n } - else if (n.type == "object_entry") - new ObjectEntryNode { node = n } - else if (n.type == "object_spread") - new ObjectSpreadNode { node = n } - else if (n.type == "when_generator") - new WhenGeneratorNode { node = n } - else if (n.type == "for_generator") - new ForGeneratorNode { node = n } - else - throw("Unknown object member type: \(n.type)") + constructors[n.type].apply(n) as ObjectMemberNode // Extract the string constant from a STRING_CHARS node. local const function extractStringConstant(n: Node?): String = @@ -474,19 +405,19 @@ local const function extractStringConstant(n: Node?): String = // Find and extract the string_chars of a node. local const function getStringChars(node: Node?): String = - extractStringConstant(findChild(node, "string_chars")) + extractStringConstant(node?.findChild("string_chars")) // The dotted name of a `qualified_identifier` node (e.g. "a.b.c"). local const function qualifiedName(qid: Node?): String = - findChildren(qid, "identifier").map((n) -> n.text ?? "").join(".") + (qid?.findChildren("identifier") ?? List()).map((n) -> n.text ?? "").join(".") // The identifier text of the first `identifier` child of `node`. -local const function identifierText(node: Node?): String = findChild(node, "identifier")?.text ?? "" +local const function identifierText(node: Node?): String = node?.findChild("identifier")?.text ?? "" // The modifier keywords of a `modifier_list` child of `node`. local const function modifiersOf(node: Node?): List = - let (ml = findChild(node, "modifier_list")) - if (ml == null) List() else findChildren(ml, "modifier").map((n) -> n.text ?? "") + let (ml = node?.findChild("modifier_list")) + if (ml == null) List() else ml.findChildren("modifier").map((n) -> n.text ?? "") /// Base class for all typed syntax nodes. /// @@ -531,31 +462,32 @@ abstract class ObjectMemberNode extends SyntaxNode class ModuleNode extends SyntaxNode { /// The module declaration, if present. declaration: ModuleDeclarationNode? = - let (n = findChild(node, "module_declaration")) + let (n = node?.findChild("module_declaration")) if (n == null) null else new ModuleDeclarationNode { node = n } /// All imports in this module. imports: List = - let (importList = findChild(node, "import_list")) + let (importList = node?.findChild("import_list")) if (importList == null) List() else - findChildren(importList, "import").map((n) -> new ImportNode { node = n }) + importList.findChildren("import").map((n) -> new ImportNode { node = n }) /// All class declarations in this module. - classes: List = findChildren(node, "class").map((n) -> new ClassNode { node = n }) + classes: List = + node?.findChildren("class")?.map((n) -> new ClassNode { node = n }) ?? List() /// All typealias declarations in this module. typeAliases: List = - findChildren(node, "typealias").map((n) -> new TypeAliasNode { node = n }) + node?.findChildren("typealias")?.map((n) -> new TypeAliasNode { node = n }) ?? List() /// All top-level properties in this module. properties: List = - findChildren(node, "class_property").map((n) -> new ClassPropertyNode { node = n }) + node?.findChildren("class_property")?.map((n) -> new ClassPropertyNode { node = n }) ?? List() /// All top-level methods in this module. methods: List = - findChildren(node, "class_method").map((n) -> new ClassMethodNode { node = n }) + node?.findChildren("class_method")?.map((n) -> new ClassMethodNode { node = n }) ?? List() fixed builtNode = let (self = this) @@ -581,16 +513,16 @@ class ModuleNode extends SyntaxNode { /// A module declaration (including doc comment, annotations, modifiers, name, amends/extends). class ModuleDeclarationNode extends SyntaxNode { - local moduleDefinition: Node? = findChild(node, "module_definition") + local moduleDefinition: Node? = node?.findChild("module_definition") /// The doc comment on the module declaration, if present. docComment: DocCommentNode? = - let (n = findChild(node, "doc_comment")) + let (n = node?.findChild("doc_comment")) if (n == null) null else new DocCommentNode { node = n } /// Annotations on the module declaration. annotations: List = - findChildren(node, "annotation").map((n) -> new AnnotationNode { node = n }) + node?.findChildren("annotation")?.map((n) -> new AnnotationNode { node = n }) ?? List() /// The modifiers on the module declaration. modifiers: List = if (moduleDefinition == null) List() else modifiersOf(moduleDefinition) @@ -600,17 +532,17 @@ class ModuleDeclarationNode extends SyntaxNode { if (moduleDefinition == null) null else - let (n = findChild(moduleDefinition, "qualified_identifier")) + let (n = moduleDefinition?.findChild("qualified_identifier")) if (n == null) null else qualifiedName(n) /// The URI string of the amended module, if any. Mutually exclusive with [extendsUri]. amendsUri: String? = - let (n = findChild(node, "amends_clause")) + let (n = node?.findChild("amends_clause")) if (n == null) null else getStringChars(n) /// The URI string of the extended module, if any. Mutually exclusive with [amendsUri]. extendsUri: String? = - let (n = findChild(node, "extends_clause")) + let (n = node?.findChild("extends_clause")) if (n == null) null else getStringChars(n) fixed builtNode = @@ -664,7 +596,7 @@ class ImportNode extends SyntaxNode { /// The alias for this import, if present. alias: String? = - let (aliasNode = findChild(node, "import_alias")) + let (aliasNode = node?.findChild("import_alias")) if (aliasNode == null) null else identifierText(aliasNode) fixed builtNode = @@ -691,16 +623,16 @@ class ImportNode extends SyntaxNode { /// A class declaration. class ClassNode extends SyntaxNode { - local header: Node? = findChild(node, "class_header") + local header: Node? = node?.findChild("class_header") /// The doc comment, if present. docComment: DocCommentNode? = - let (n = findChild(node, "doc_comment")) + let (n = node?.findChild("doc_comment")) if (n == null) null else new DocCommentNode { node = n } /// Annotations on the class. annotations: List = - findChildren(node, "annotation").map((n) -> new AnnotationNode { node = n }) + node?.findChildren("annotation")?.map((n) -> new AnnotationNode { node = n }) ?? List() /// The modifiers on the class. modifiers: List = modifiersOf(header) @@ -710,28 +642,28 @@ class ClassNode extends SyntaxNode { /// The type parameters. typeParameters: List = - let (tpl = findChild(header, "type_parameter_list")) + let (tpl = header?.findChild("type_parameter_list")) if (tpl == null) List() else - let (elems = findChild(tpl, "type_parameter_list_elements")) + let (elems = tpl.findChild("type_parameter_list_elements")) if (elems == null) List() else - findChildren(elems, "type_parameter").map((n) -> new TypeParameterNode { node = n }) + elems.findChildren("type_parameter").map((n) -> new TypeParameterNode { node = n }) /// The supertype this class extends, if present. extendsType: TypeNode? = - let (ext = findChild(header, "class_header_extends")) + let (ext = header?.findChild("class_header_extends")) if (ext == null) null else - let (t = findTypeChild(ext)) + let (t = ext.findTypeChild()) if (t == null) null else wrapTypeNode(t) /// The class body, if present. body: ClassBodyNode? = - let (n = findChild(node, "class_body")) + let (n = node?.findChild("class_body")) if (n == null) null else new ClassBodyNode { node = n } fixed builtNode = @@ -766,16 +698,16 @@ class ClassNode extends SyntaxNode { /// A typealias declaration. class TypeAliasNode extends SyntaxNode { - local header: Node? = findChild(node, "typealias_header") + local header: Node? = node?.findChild("typealias_header") /// The doc comment, if present. docComment: DocCommentNode? = - let (n = findChild(node, "doc_comment")) + let (n = node?.findChild("doc_comment")) if (n == null) null else new DocCommentNode { node = n } /// Annotations on the typealias. annotations: List = - findChildren(node, "annotation").map((n) -> new AnnotationNode { node = n }) + node?.findChildren("annotation")?.map((n) -> new AnnotationNode { node = n }) ?? List() /// The modifiers on the typealias. modifiers: List = modifiersOf(header) @@ -785,20 +717,20 @@ class TypeAliasNode extends SyntaxNode { /// The type parameters. typeParameters: List = - let (tpl = findChild(header, "type_parameter_list")) + let (tpl = header?.findChild("type_parameter_list")) if (tpl == null) List() else - let (elems = findChild(tpl, "type_parameter_list_elements")) + let (elems = tpl.findChild("type_parameter_list_elements")) if (elems == null) List() else - findChildren(elems, "type_parameter").map((n) -> new TypeParameterNode { node = n }) + elems.findChildren("type_parameter").map((n) -> new TypeParameterNode { node = n }) /// The type that this alias resolves to. type: TypeNode = - let (body = findChild(node, "typealias_body")) - let (t = findTypeChild(body!!)) + let (body = node?.findChild("typealias_body")) + let (t = body!!.findTypeChild()) wrapTypeNode(t!!) fixed builtNode = @@ -828,21 +760,21 @@ class TypeAliasNode extends SyntaxNode { /// A class body delimited by braces. class ClassBodyNode extends SyntaxNode { - local elements: Node? = findChild(node, "class_body_elements") + local elements: Node? = node?.findChild("class_body_elements") /// Properties declared in this class body. properties: List = if (elements == null) List() else - findChildren(elements, "class_property").map((n) -> new ClassPropertyNode { node = n }) + elements.findChildren("class_property").map((n) -> new ClassPropertyNode { node = n }) /// Methods declared in this class body. methods: List = if (elements == null) List() else - findChildren(elements, "class_method").map((n) -> new ClassMethodNode { node = n }) + elements.findChildren("class_method").map((n) -> new ClassMethodNode { node = n }) fixed builtNode = let (self = this) @@ -866,17 +798,17 @@ class ClassBodyNode extends SyntaxNode { /// A class property declaration. class ClassPropertyNode extends SyntaxNode { - local propHeader: Node? = findChild(node, "class_property_header") - local headerBegin: Node? = findChild(propHeader, "class_property_header_begin") + local propHeader: Node? = node?.findChild("class_property_header") + local headerBegin: Node? = propHeader?.findChild("class_property_header_begin") /// The doc comment, if present. docComment: DocCommentNode? = - let (n = findChild(node, "doc_comment")) + let (n = node?.findChild("doc_comment")) if (n == null) null else new DocCommentNode { node = n } /// Annotations on the property. annotations: List = - findChildren(node, "annotation").map((n) -> new AnnotationNode { node = n }) + node?.findChildren("annotation")?.map((n) -> new AnnotationNode { node = n }) ?? List() /// The modifiers on the property. modifiers: List = modifiersOf(headerBegin) @@ -886,21 +818,21 @@ class ClassPropertyNode extends SyntaxNode { /// The type annotation, if present. typeAnnotation: TypeNode? = - let (n = findChild(propHeader, "type_annotation")) - if (n == null) null else wrapTypeNode(findTypeChild(n)!!) + let (n = propHeader?.findChild("type_annotation")) + if (n == null) null else wrapTypeNode(n.findTypeChild()!!) /// The value expression, if present (from `= expr`). value: Expr? = - let (body = findChild(node, "class_property_body")) + let (body = node?.findChild("class_property_body")) if (body == null) null else - let (e = findExprChild(body)) + let (e = body.findExprChild()) if (e == null) null else wrapExpr(e) /// Object bodies for amending (from `{ ... }` blocks). objectBodies: List = - findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) + node?.findChildren("object_body")?.map((n) -> new ObjectBodyNode { node = n }) ?? List() fixed builtNode = let (self = this) @@ -937,16 +869,16 @@ class ClassPropertyNode extends SyntaxNode { /// A class method declaration. class ClassMethodNode extends SyntaxNode { - local methodHeader: Node? = findChild(node, "class_method_header") + local methodHeader: Node? = node?.findChild("class_method_header") /// The doc comment, if present. docComment: DocCommentNode? = - let (n = findChild(node, "doc_comment")) + let (n = node?.findChild("doc_comment")) if (n == null) null else new DocCommentNode { node = n } /// Annotations on the method. annotations: List = - findChildren(node, "annotation").map((n) -> new AnnotationNode { node = n }) + node?.findChildren("annotation")?.map((n) -> new AnnotationNode { node = n }) ?? List() /// The modifiers on the method. modifiers: List = modifiersOf(methodHeader) @@ -956,33 +888,33 @@ class ClassMethodNode extends SyntaxNode { /// The type parameters. typeParameters: List = - let (tpl = findChild(node, "type_parameter_list")) + let (tpl = node?.findChild("type_parameter_list")) if (tpl == null) List() else - let (elems = findChild(tpl, "type_parameter_list_elements")) + let (elems = tpl.findChild("type_parameter_list_elements")) if (elems == null) List() else - findChildren(elems, "type_parameter").map((n) -> new TypeParameterNode { node = n }) + elems.findChildren("type_parameter").map((n) -> new TypeParameterNode { node = n }) /// The parameters. parameters: List = - let (pl = findChild(node, "parameter_list")) + let (pl = node?.findChild("parameter_list")) parametersOf(pl!!) /// The return type annotation, if present. returnType: TypeNode? = - let (n = findChild(node, "type_annotation")) - if (n == null) null else wrapTypeNode(findTypeChild(n)!!) + let (n = node?.findChild("type_annotation")) + if (n == null) null else wrapTypeNode(n.findTypeChild()!!) /// The method body expression, if present. Null for abstract methods. body: Expr? = - let (bodyNode = findChild(node, "class_method_body")) + let (bodyNode = node?.findChild("class_method_body")) if (bodyNode == null) null else - let (e = findExprChild(bodyNode)) + let (e = bodyNode?.findExprChild()) if (e == null) null else wrapExpr(e) fixed builtNode = @@ -1021,15 +953,15 @@ class ClassMethodNode extends SyntaxNode { /// An object body delimited by braces. class ObjectBodyNode extends SyntaxNode { - local paramList: Node? = findChild(node, "object_parameter_list") - local memberList: Node? = findChild(node, "object_member_list") + local paramList: Node? = node?.findChild("object_parameter_list") + local memberList: Node? = node?.findChild("object_member_list") /// Parameters for this object body (e.g., `{ x, y -> ... }`). parameters: List = if (paramList == null) List() else - findChildren(paramList, "parameter").map((n) -> new ParameterNode { node = n }) + paramList.findChildren("parameter").map((n) -> new ParameterNode { node = n }) /// All object members (properties, methods, elements, entries, spreads, generators). members: List = @@ -1072,8 +1004,8 @@ class ObjectBodyNode extends SyntaxNode { /// An object property declaration. class ObjectPropertyNode extends ObjectMemberNode { - local propHeader: Node? = findChild(node, "object_property_header") - local headerBegin: Node? = findChild(propHeader, "object_property_header_begin") + local propHeader: Node? = node?.findChild("object_property_header") + local headerBegin: Node? = propHeader?.findChild("object_property_header_begin") /// The modifiers on the property. modifiers: List = modifiersOf(headerBegin) @@ -1083,21 +1015,21 @@ class ObjectPropertyNode extends ObjectMemberNode { /// The type annotation, if present. typeAnnotation: TypeNode? = - let (n = findChild(propHeader, "type_annotation")) - if (n == null) null else wrapTypeNode(findTypeChild(n)!!) + let (n = propHeader?.findChild("type_annotation")) + if (n == null) null else wrapTypeNode(n.findTypeChild()!!) /// The value expression, if present (from `= expr`). value: Expr? = - let (body = findChild(node, "object_property_body")) + let (body = node?.findChild("object_property_body")) if (body == null) null else - let (e = findExprChild(body)) + let (e = body.findExprChild()) if (e == null) null else wrapExpr(e) /// Object bodies for amending. objectBodies: List = - findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) + node?.findChildren("object_body")?.map((n) -> new ObjectBodyNode { node = n }) ?? List() fixed builtNode = let (self = this) @@ -1132,7 +1064,7 @@ class ObjectPropertyNode extends ObjectMemberNode { /// An object method declaration. class ObjectMethodNode extends ObjectMemberNode { - local methodHeader: Node? = findChild(node, "class_method_header") + local methodHeader: Node? = node?.findChild("class_method_header") /// The modifiers on the method. modifiers: List = modifiersOf(methodHeader) @@ -1142,30 +1074,30 @@ class ObjectMethodNode extends ObjectMemberNode { /// The type parameters. typeParameters: List = - let (tpl = findChild(node, "type_parameter_list")) + let (tpl = node?.findChild("type_parameter_list")) if (tpl == null) List() else - let (elems = findChild(tpl, "type_parameter_list_elements")) + let (elems = tpl.findChild("type_parameter_list_elements")) if (elems == null) List() else - findChildren(elems, "type_parameter").map((n) -> new TypeParameterNode { node = n }) + elems.findChildren("type_parameter").map((n) -> new TypeParameterNode { node = n }) /// The parameters. parameters: List = - let (pl = findChild(node, "parameter_list")) + let (pl = node?.findChild("parameter_list")) parametersOf(pl!!) /// The return type annotation, if present. returnType: TypeNode? = - let (n = findChild(node, "type_annotation")) - if (n == null) null else wrapTypeNode(findTypeChild(n)!!) + let (n = node?.findChild("type_annotation")) + if (n == null) null else wrapTypeNode(n.findTypeChild()!!) /// The method body expression. body: Expr = - let (bodyNode = findChild(node, "class_method_body")) - wrapExpr(findExprChild(bodyNode!!)!!) + let (bodyNode = node?.findChild("class_method_body")) + wrapExpr(bodyNode!!.findExprChild()!!) fixed builtNode = let (self = this) @@ -1197,7 +1129,7 @@ class ObjectMethodNode extends ObjectMemberNode { /// An object element (a positional expression in an object body). class ObjectElementNode extends ObjectMemberNode { /// The expression value. - expression: Expr = wrapExpr(findExprChild(node)!!) + expression: Expr = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1209,19 +1141,19 @@ class ObjectElementNode extends ObjectMemberNode { /// An object entry (`[key] = value` or `[key] { ... }`). class ObjectEntryNode extends ObjectMemberNode { - local entryHeader: Node? = findChild(node, "object_entry_header") + local entryHeader: Node? = node?.findChild("object_entry_header") /// The key expression. - key: Expr = wrapExpr(findExprChild(entryHeader)!!) + key: Expr = wrapExpr(entryHeader?.findExprChild()!!) /// The value expression, if present (from `[key] = value`). value: Expr? = - let (e = findExprChildren(node).findOrNull((c) -> c != findExprChild(entryHeader))) + let (e = node?.findExprChildren()?.findOrNull((c) -> c != entryHeader?.findExprChild())) if (e == null) null else wrapExpr(e) /// Object bodies for amending. objectBodies: List = - findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) + node?.findChildren("object_body")?.map((n) -> new ObjectBodyNode { node = n }) ?? List() fixed builtNode = let (self = this) @@ -1253,7 +1185,7 @@ class ObjectSpreadNode extends ObjectMemberNode { isNullable: Boolean = terminals.firstOrNull?.text == "...?" /// The spread expression. - expression: Expr = wrapExpr(findExprChild(node)!!) + expression: Expr = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1269,7 +1201,7 @@ class ObjectSpreadNode extends ObjectMemberNode { /// A member predicate (`[[condition]] = value` or `[[condition]] { ... }`). class MemberPredicateNode extends ObjectMemberNode { - local exprs: List = findExprChildren(node) + local exprs: List = node?.findExprChildren() ?? List() /// The condition expression. condition: Expr = wrapExpr(exprs.first) @@ -1279,7 +1211,7 @@ class MemberPredicateNode extends ObjectMemberNode { /// Object bodies for amending. objectBodies: List = - findChildren(node, "object_body").map((n) -> new ObjectBodyNode { node = n }) + node?.findChildren("object_body")?.map((n) -> new ObjectBodyNode { node = n }) ?? List() fixed builtNode = let (self = this) @@ -1303,10 +1235,10 @@ class MemberPredicateNode extends ObjectMemberNode { /// A `for (param in iterable) { ... }` generator. class ForGeneratorNode extends ObjectMemberNode { - local forHeader: Node? = findChild(node, "for_generator_header") - local forDef: Node? = findChild(forHeader, "for_generator_header_definition") - local forDefHeader: Node? = findChild(forDef, "for_generator_header_definition_header") - local paramNodes: List = findChildren(forDefHeader, "parameter") + local forHeader: Node? = node?.findChild("for_generator_header") + local forDef: Node? = forHeader?.findChild("for_generator_header_definition") + local forDefHeader: Node? = forDef?.findChild("for_generator_header_definition_header") + local paramNodes: List = forDefHeader?.findChildren("parameter") ?? List() /// The key parameter (first parameter when two are present), if present. keyParameter: ParameterNode? = @@ -1316,11 +1248,11 @@ class ForGeneratorNode extends ObjectMemberNode { valueParameter: ParameterNode = new ParameterNode { node = paramNodes.last } /// The iterable expression. - iterable: Expr = wrapExpr(findExprChild(forDef)!!) + iterable: Expr = wrapExpr(forDef?.findExprChild()!!) /// The body. body: ObjectBodyNode = - let (n = findChild(node, "object_body")) + let (n = node?.findChild("object_body")) new ObjectBodyNode { node = n!! } fixed builtNode = @@ -1367,11 +1299,11 @@ class ForGeneratorNode extends ObjectMemberNode { /// A `when (condition) { ... }` generator. class WhenGeneratorNode extends ObjectMemberNode { - local whenHeader: Node? = findChild(node, "when_generator_header") - local bodyNodes: List = findChildren(node, "object_body") + local whenHeader: Node? = node?.findChild("when_generator_header") + local bodyNodes: List = node?.findChildren("object_body") ?? List() /// The condition expression. - condition: Expr = wrapExpr(findExprChild(whenHeader)!!) + condition: Expr = wrapExpr(whenHeader?.findExprChild()!!) /// The "then" body. thenBody: ObjectBodyNode = new ObjectBodyNode { node = bodyNodes.first } @@ -1504,7 +1436,7 @@ class UnqualifiedAccessExprNode extends Expr { /// The arguments, if this is a function call. Null for a plain identifier access. arguments: List? = - let (n = findChild(node, "argument_list")) + let (n = node?.findChild("argument_list")) if (n == null) null else argumentsOf(n) fixed builtNode = @@ -1521,20 +1453,20 @@ class UnqualifiedAccessExprNode extends Expr { /// optionally with arguments for method calls). class QualifiedAccessExprNode extends Expr { /// The receiver expression. - receiver: Expr = wrapExpr(findExprChildren(node).first) + receiver: Expr = wrapExpr((node?.findExprChildren() ?? List()).first) /// Whether this is a null-safe access (`?.`). - isNullSafe: Boolean = findChild(node, "operator")?.text == "?." + isNullSafe: Boolean = node?.findChild("operator")?.text == "?." /// The accessed member name. member: String = - let (m = findChildren(node, "unqualified_access_expr").last) + let (m = (node?.findChildren("unqualified_access_expr") ?? List()).last) identifierText(m) /// The arguments, if this is a method call. Null for a property access. arguments: List? = - let (m = findChildren(node, "unqualified_access_expr").last) - let (n = findChild(m, "argument_list")) + let (m = (node?.findChildren("unqualified_access_expr") ?? List()).last) + let (n = m.findChild("argument_list")) if (n == null) null else argumentsOf(n) fixed builtNode = @@ -1560,10 +1492,10 @@ class QualifiedAccessExprNode extends Expr { /// A subscript expression (`receiver[index]`). class SubscriptExprNode extends Expr { /// The receiver expression. - receiver: Expr = wrapExpr(findExprChildren(node).first) + receiver: Expr = wrapExpr(node?.findExprChildren().first) /// The index expression. - index: Expr = wrapExpr(findExprChildren(node)[1]) + index: Expr = wrapExpr(node?.findExprChildren().getOrNull(1)!!) fixed builtNode = let (self = this) @@ -1595,22 +1527,22 @@ class SuperSubscriptExprNode extends Expr { /// An `if (condition) thenExpr else elseExpr` expression. class IfExprNode extends Expr { - local ifHeader: Node = findChild(node, "if_header")!! - local ifCondition: Node = findChild(ifHeader, "if_condition")!! - local ifConditionExpr: Node = findChild(ifCondition, "if_condition_expr")!! + local ifHeader: Node = node?.findChild("if_header")!! + local ifCondition: Node = ifHeader.findChild("if_condition")!! + local ifConditionExpr: Node = ifCondition.findChild("if_condition_expr")!! /// The condition expression. - condition: Expr = wrapExpr(findExprChild(ifConditionExpr)!!) + condition: Expr = wrapExpr(ifConditionExpr.findExprChild()!!) /// The then-branch expression. thenExpr: Expr = - let (thenNode = findChild(node, "if_then_expr")) - wrapExpr(findExprChild(thenNode!!)!!) + let (thenNode = node?.findChild("if_then_expr")) + wrapExpr(thenNode!!.findExprChild()!!) /// The else-branch expression. elseExpr: Expr = - let (elseNode = findChild(node, "if_else_expr")) - wrapExpr(findExprChild(elseNode!!)!!) + let (elseNode = node?.findChild("if_else_expr")) + wrapExpr(elseNode!!.findExprChild()!!) fixed builtNode = let (self = this) @@ -1652,19 +1584,19 @@ class IfExprNode extends Expr { /// A `let (param = value) body` expression. class LetExprNode extends Expr { - local letParamDef: Node = findChild(node, "let_parameter_definition")!! - local letParam: Node = findChild(letParamDef, "let_parameter")!! + local letParamDef: Node = node?.findChild("let_parameter_definition")!! + local letParam: Node = letParamDef.findChild("let_parameter")!! /// The let-binding parameter. parameter: ParameterNode = - let (p = findChild(letParam, "parameter")) + let (p = letParam.findChild("parameter")) new ParameterNode { node = p!! } /// The binding value expression. - bindingValue: Expr = wrapExpr(findExprChild(letParam)!!) + bindingValue: Expr = wrapExpr(letParam.findExprChild()!!) /// The body expression. - body: Expr = wrapExpr(findExprChild(node)!!) + body: Expr = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1698,7 +1630,7 @@ class LetExprNode extends Expr { /// A `throw(expr)` expression. class ThrowExprNode extends Expr { /// The expression being thrown. - expression: Expr = wrapExpr(findExprChild(node)!!) + expression: Expr = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1717,7 +1649,7 @@ class ThrowExprNode extends Expr { /// A `trace(expr)` expression. class TraceExprNode extends Expr { /// The expression being traced. - expression: Expr = wrapExpr(findExprChild(node)!!) + expression: Expr = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1762,7 +1694,7 @@ class ReadExprNode extends Expr { (terminals.firstOrNull?.text ?? "read") as "read" | "read?" | "read*" /// The expression to be read. - expression: Expr = wrapExpr(findExprChild(node)!!) + expression: Expr = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1780,16 +1712,16 @@ class ReadExprNode extends Expr { /// A `new Type { ... }` expression. class NewExprNode extends Expr { - local newHeader: Node = findChild(node, "new_header")!! + local newHeader: Node = node?.findChild("new_header")!! /// The type being constructed, if present. type: TypeNode? = - let (t = findTypeChild(newHeader)) + let (t = newHeader.findTypeChild()) if (t == null) null else wrapTypeNode(t) /// The object body. body: ObjectBodyNode = - let (n = findChild(node, "object_body")) + let (n = node?.findChild("object_body")) new ObjectBodyNode { node = n!! } fixed builtNode = @@ -1814,11 +1746,11 @@ class NewExprNode extends Expr { /// An `(expr) { ... }` amends expression. class AmendsExprNode extends Expr { /// The expression being amended. - parentExpr: Expr = wrapExpr(findExprChildren(node).first) + parentExpr: Expr = wrapExpr((node?.findExprChildren() ?? List()).first) /// The object body. body: ObjectBodyNode = - let (n = findChild(node, "object_body")) + let (n = node?.findChild("object_body")) new ObjectBodyNode { node = n!! } fixed builtNode = @@ -1831,10 +1763,10 @@ class AmendsExprNode extends Expr { /// A binary operator expression (`left op right`), including `is`/`as`. class BinaryOpExprNode extends Expr { - local exprs: List = findExprChildren(node) + local exprs: List = node?.findExprChildren() ?? List() /// The operator string. - operator: String = findChild(node, "operator")?.text ?? "" + operator: String = node?.findChild("operator")?.text ?? "" /// The left-hand expression. left: Expr = wrapExpr(exprs.first) @@ -1844,7 +1776,7 @@ class BinaryOpExprNode extends Expr { /// The right-hand type, if this is an `is` or `as` operation. rightType: TypeNode? = - let (t = findTypeChild(node)) + let (t = node?.findTypeChild()) if (t == null) null else wrapTypeNode(t) fixed builtNode = @@ -1870,7 +1802,7 @@ class BinaryOpExprNode extends Expr { /// A unary minus expression (`-expr`). class UnaryMinusExprNode extends Expr { /// The operand expression. - operand: Expr = wrapExpr(findExprChild(node)!!) + operand: Expr = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1883,7 +1815,7 @@ class UnaryMinusExprNode extends Expr { /// A logical not expression (`!expr`). class LogicalNotExprNode extends Expr { /// The operand expression. - operand: Expr = wrapExpr(findExprChild(node)!!) + operand: Expr = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1896,7 +1828,7 @@ class LogicalNotExprNode extends Expr { /// A non-null assertion expression (`expr!!`). class NonNullExprNode extends Expr { /// The operand expression. - operand: Expr = wrapExpr(findExprChild(node)!!) + operand: Expr = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1910,13 +1842,13 @@ class NonNullExprNode extends Expr { class FunctionLiteralExprNode extends Expr { /// The parameters. parameters: List = - let (pl = findChild(node, "parameter_list")) + let (pl = node?.findChild("parameter_list")) parametersOf(pl!!) /// The body expression. body: Expr = - let (bodyNode = findChild(node, "function_literal_body")) - wrapExpr(findExprChild(bodyNode!!)!!) + let (bodyNode = node?.findChild("function_literal_body")) + wrapExpr(bodyNode!!.findExprChild()!!) fixed builtNode = let (self = this) @@ -1938,11 +1870,11 @@ class FunctionLiteralExprNode extends Expr { class ParenthesizedExprNode extends Expr { /// The inner expression, if present (may be empty for `()`). expression: Expr? = - let (elems = findChild(node, "parenthesized_expr_elements")) + let (elems = node?.findChild("parenthesized_expr_elements")) if (elems == null) null else - let (e = findExprChild(elems)) + let (e = elems.findExprChild()) if (e == null) null else wrapExpr(e) fixed builtNode = @@ -1980,17 +1912,17 @@ class ModuleTypeNode extends TypeNode { class DeclaredTypeNode extends TypeNode { /// The type name (dotted, e.g. `"List"` or `"foo.Bar"`). name: String = - let (n = findChild(node, "qualified_identifier")) + let (n = node?.findChild("qualified_identifier")) qualifiedName(n!!) /// The type arguments. typeArguments: List = - let (tal = findChild(node, "type_argument_list")) + let (tal = node?.findChild("type_argument_list")) if (tal == null) List() else - let (elems = findChild(tal, "type_argument_list_elements")) - if (elems == null) List() else findTypeChildren(elems).map((n) -> wrapTypeNode(n)) + let (elems = tal.findChild("type_argument_list_elements")) + if (elems == null) List() else elems.findTypeChildren().map((n) -> wrapTypeNode(n)) fixed builtNode = let (self = this) @@ -2018,7 +1950,7 @@ class DeclaredTypeNode extends TypeNode { /// A nullable type (`Type?`). class NullableTypeNode extends TypeNode { /// The base type. - baseType: TypeNode = wrapTypeNode(findTypeChild(node)!!) + baseType: TypeNode = wrapTypeNode(node?.findTypeChild()!!) fixed builtNode = let (self = this) @@ -2031,7 +1963,7 @@ class NullableTypeNode extends TypeNode { /// A union type (`TypeA|TypeB|TypeC`). class UnionTypeNode extends TypeNode { /// The member types. - members: List = findTypeChildren(node).map((n) -> wrapTypeNode(n)) + members: List = node?.findTypeChildren()?.map((n) -> wrapTypeNode(n)) ?? List() fixed builtNode = let (self = this) @@ -2048,15 +1980,15 @@ class UnionTypeNode extends TypeNode { /// A function type (`(ParamTypes) -> ReturnType`). class FunctionTypeNode extends TypeNode { - local params: Node = findChild(node, "function_type_parameters")!! - local paramElems: Node? = findChild(params, "parenthesized_type_elements") + local params: Node = node?.findChild("function_type_parameters")!! + local paramElems: Node? = params.findChild("parenthesized_type_elements") /// The parameter types. parameterTypes: List = - if (paramElems == null) List() else findTypeChildren(paramElems).map((n) -> wrapTypeNode(n)) + if (paramElems == null) List() else paramElems.findTypeChildren().map((n) -> wrapTypeNode(n)) /// The return type. - returnType: TypeNode = wrapTypeNode(findTypeChildren(node).last) + returnType: TypeNode = wrapTypeNode((node?.findTypeChildren() ?? List()).last) fixed builtNode = let (self = this) @@ -2088,13 +2020,13 @@ class FunctionTypeNode extends TypeNode { /// A constrained type (`Type(constraint)`). class ConstrainedTypeNode extends TypeNode { /// The base type. - baseType: TypeNode = wrapTypeNode(findTypeChild(node)!!) + baseType: TypeNode = wrapTypeNode(node?.findTypeChild()!!) - local constraint: Node = findChild(node, "constrained_type_constraint")!! - local constraintElems: Node = findChild(constraint, "constrained_type_elements")!! + local constraint: Node = node?.findChild("constrained_type_constraint")!! + local constraintElems: Node = constraint.findChild("constrained_type_elements")!! /// The constraint expressions. - constraints: List = findExprChildren(constraintElems).map((n) -> wrapExpr(n)) + constraints: List = constraintElems.findExprChildren().map((n) -> wrapExpr(n)) fixed builtNode = let (self = this) @@ -2120,11 +2052,11 @@ class ConstrainedTypeNode extends TypeNode { class ParenthesizedTypeNode extends TypeNode { /// The inner type, if present. type: TypeNode? = - let (elems = findChild(node, "parenthesized_type_elements")) + let (elems = node?.findChild("parenthesized_type_elements")) if (elems == null) null else - let (t = findTypeChild(elems)) + let (t = elems.findTypeChild()) if (t == null) null else wrapTypeNode(t) fixed builtNode = @@ -2159,11 +2091,11 @@ class StringConstantTypeNode extends TypeNode { /// An annotation (`@Type { ... }`). class AnnotationNode extends SyntaxNode { /// The annotation type. - type: TypeNode = wrapTypeNode(findTypeChild(node)!!) + type: TypeNode = wrapTypeNode(node?.findTypeChild()!!) /// The annotation body, if present. body: ObjectBodyNode? = - let (n = findChild(node, "object_body")) + let (n = node?.findChild("object_body")) if (n == null) null else new ObjectBodyNode { node = n } fixed builtNode = @@ -2183,7 +2115,7 @@ class ParameterNode extends SyntaxNode { /// The parameter name. Use `"_"` for a wildcard parameter. name: String = - let (id = findChild(node, "identifier")) + let (id = node?.findChild("identifier")) if (id != null) id.text ?? "" else if (children.findOrNull((c) -> c.type == "terminal" && c.text == "_") != null) @@ -2193,8 +2125,8 @@ class ParameterNode extends SyntaxNode { /// The type annotation, if present. typeAnnotation: TypeNode? = - let (n = findChild(node, "type_annotation")) - if (n == null) null else wrapTypeNode(findTypeChild(n)!!) + let (n = node?.findChild("type_annotation")) + if (n == null) null else wrapTypeNode(n.findTypeChild()!!) fixed builtNode = let (self = this) @@ -2236,11 +2168,10 @@ class TypeParameterNode extends SyntaxNode { class DocCommentNode extends SyntaxNode { /// The body text of each line, without the leading `///`. lines: List = - findChildren(node, "doc_comment_line") - .map((n) -> - let (l = n.text ?? "") - if (l.startsWith("///")) l.drop(3) else l - ) + (node?.findChildren("doc_comment_line") ?? List()).map((n) -> + let (l = n.text ?? "") + if (l.startsWith("///")) l.drop(3) else l + ) fixed builtNode = let (self = this) @@ -2417,16 +2348,16 @@ local const function stringCharsNode(value: String): Node = new Node { // Read the parameters of a `parameter_list` node. local const function parametersOf(pl: Node?): List = - let (elems = findChild(pl, "parameter_list_elements")) + let (elems = pl?.findChild("parameter_list_elements")) if (elems == null) List() else - findChildren(elems, "parameter").map((n) -> new ParameterNode { node = n }) + elems.findChildren("parameter").map((n) -> new ParameterNode { node = n }) // Read the argument expressions of an `argument_list` node. local const function argumentsOf(al: Node?): List = - let (elems = findChild(al, "argument_list_elements")) - if (elems == null) List() else findExprChildren(elems).map((n) -> wrapExpr(n)) + let (elems = al?.findChild("argument_list_elements")) + if (elems == null) List() else elems.findExprChildren().map((n) -> wrapExpr(n)) // Build a `parameter_list` node from typed parameters. local const function parameterListNode(parameters: List): Node = new Node { From cc99159618c80ad15b808423f18024a064e9803c Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Tue, 14 Jul 2026 17:43:57 +0200 Subject: [PATCH 10/49] Refactor list joins --- stdlib/syntax.pkl | 223 +++++++++++++++++++++++----------------------- 1 file changed, 112 insertions(+), 111 deletions(-) diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 1d5fa8204..59ef7210f 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -494,16 +494,16 @@ class ModuleNode extends SyntaxNode { new Node { type = "module" children = - (if (self.declaration == null) List() else List(self.declaration.builtNode)) - + ( - if (self.imports.isEmpty) - List() - else - List(new Node { - type = "import_list" - children = self.imports.map((i) -> i.builtNode) - }) - ) + List( + self.declaration?.builtNode, + if (self.imports.isEmpty) + null + else + new Node { + type = "import_list" + children = self.imports.map((i) -> i.builtNode) + }, + ).filterNonNull() + self.classes.map((c) -> c.builtNode) + self.typeAliases.map((t) -> t.builtNode) + self.properties.map((p) -> p.builtNode) @@ -552,37 +552,35 @@ class ModuleDeclarationNode extends SyntaxNode { children = (if (self.docComment == null) List() else List(self.docComment.builtNode)) + self.annotations.map((a) -> a.builtNode) - + ( + + List( if (self.name != null) - List(new Node { + new Node { type = "module_definition" children = - (if (self.modifiers.isEmpty) List() else List(modifierListNode(self.modifiers))) - + List( - (terminal) { text = "module" }, - qualifiedIdentifierNode(self.name!!), - ) - }) + List( + if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), + (terminal) { text = "module" }, + qualifiedIdentifierNode(self.name!!), + ).filterNonNull() + } else if (!self.modifiers.isEmpty) - List(modifierListNode(self.modifiers)) + modifierListNode(self.modifiers) else - List() - ) - + ( + null, if (self.amendsUri != null) - List(new Node { + new Node { type = "amends_clause" children = List((terminal) { text = "amends" }, stringCharsNode(self.amendsUri!!)) - }) + } else if (self.extendsUri != null) - List(new Node { + new Node { type = "extends_clause" children = List((terminal) { text = "extends" }, stringCharsNode(self.extendsUri!!)) - }) + } else - List() - ) + null, + ).filterNonNull() } } @@ -607,17 +605,15 @@ class ImportNode extends SyntaxNode { List( (terminal) { text = if (self.isGlob) "import*" else "import" }, stringCharsNode(self.uri), - ) - + ( - if (self.alias == null) - List() - else - List(new Node { - type = "import_alias" - children = - List((terminal) { text = "as" }, (identifierLeaf) { text = self.alias!! }) - }) - ) + if (self.alias == null) + null + else + new Node { + type = "import_alias" + children = + List((terminal) { text = "as" }, (identifierLeaf) { text = self.alias!! }) + }, + ).filterNonNull() } } @@ -673,26 +669,29 @@ class ClassNode extends SyntaxNode { children = (if (self.docComment == null) List() else List(self.docComment.builtNode)) + self.annotations.map((a) -> a.builtNode) - + List(new Node { - type = "class_header" - children = - (if (self.modifiers.isEmpty) List() else List(modifierListNode(self.modifiers))) - + List( + + List( + new Node { + type = "class_header" + children = + List( + if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), (terminal) { text = "class" }, (identifierLeaf) { text = self.name }, - ) - + typeParameterListNodes(self.typeParameters) - + ( - if (self.extendsType == null) - List() - else - List(new Node { - type = "class_header_extends" - children = List((terminal) { text = "extends" }, self.extendsType.builtNode) - }) - ) - }) - + (if (self.body == null) List() else List(self.body.builtNode)) + ).filterNonNull() + + typeParameterListNodes(self.typeParameters) + + ( + if (self.extendsType == null) + List() + else + List(new Node { + type = "class_header_extends" + children = + List((terminal) { text = "extends" }, self.extendsType.builtNode) + }) + ) + }, + self.body?.builtNode, + ).filterNonNull() } } @@ -743,11 +742,11 @@ class TypeAliasNode extends SyntaxNode { + List(new Node { type = "typealias_header" children = - (if (self.modifiers.isEmpty) List() else List(modifierListNode(self.modifiers))) - + List( - (terminal) { text = "typealias" }, - (identifierLeaf) { text = self.name }, - ) + List( + if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), + (terminal) { text = "typealias" }, + (identifierLeaf) { text = self.name }, + ).filterNonNull() + typeParameterListNodes(self.typeParameters) + List((terminal) { text = "=" }) }) @@ -781,18 +780,18 @@ class ClassBodyNode extends SyntaxNode { new Node { type = "class_body" children = - List((terminal) { text = "{" }) - + ( - let ( - members = - self.properties.map((p) -> p.builtNode) + self.methods.map((m) -> m.builtNode) - ) - if (members.isEmpty) - List() - else - List(new Node { type = "class_body_elements"; children = members }) + List( + (terminal) { text = "{" }, + let ( + members = + self.properties.map((p) -> p.builtNode) + self.methods.map((m) -> m.builtNode) ) - + List((terminal) { text = "}" }) + if (members.isEmpty) + null + else + new Node { type = "class_body_elements"; children = members }, + (terminal) { text = "}" }, + ).filterNonNull() } } @@ -847,8 +846,9 @@ class ClassPropertyNode extends SyntaxNode { List(new Node { type = "class_property_header_begin" children = - (if (self.modifiers.isEmpty) List() else List(modifierListNode(self.modifiers))) - + List((identifierLeaf) { text = self.name }) + List(if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), ( + identifierLeaf + ) { text = self.name }).filterNonNull() }) + typeAnnotationNodes(self.typeAnnotation) }) @@ -927,11 +927,11 @@ class ClassMethodNode extends SyntaxNode { + List(new Node { type = "class_method_header" children = - (if (self.modifiers.isEmpty) List() else List(modifierListNode(self.modifiers))) - + List( - (terminal) { text = "function" }, - (identifierLeaf) { text = self.name }, - ) + List( + if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), + (terminal) { text = "function" }, + (identifierLeaf) { text = self.name }, + ).filterNonNull() }) + typeParameterListNodes(self.typeParameters) + List(parameterListNode(self.parameters)) @@ -977,28 +977,26 @@ class ObjectBodyNode extends SyntaxNode { new Node { type = "object_body" children = - List((terminal) { text = "{" }) - + ( - if (self.parameters.isEmpty) - List() - else - List(new Node { - type = "object_parameter_list" - children = - commaSeparate(self.parameters.map((p) -> p.builtNode)) - .add((terminal) { text = "->" }) - }) - ) - + ( - if (self.members.isEmpty) - List() - else - List(new Node { - type = "object_member_list" - children = self.members.map((m) -> m.builtNode) - }) - ) - + List((terminal) { text = "}" }) + List( + (terminal) { text = "{" }, + if (self.parameters.isEmpty) + null + else + new Node { + type = "object_parameter_list" + children = + commaSeparate(self.parameters.map((p) -> p.builtNode)) + .add((terminal) { text = "->" }) + }, + if (self.members.isEmpty) + null + else + new Node { + type = "object_member_list" + children = self.members.map((m) -> m.builtNode) + }, + (terminal) { text = "}" }, + ).filterNonNull() } } @@ -1107,11 +1105,11 @@ class ObjectMethodNode extends ObjectMemberNode { List(new Node { type = "class_method_header" children = - (if (self.modifiers.isEmpty) List() else List(modifierListNode(self.modifiers))) - + List( - (terminal) { text = "function" }, - (identifierLeaf) { text = self.name }, - ) + List( + if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), + (terminal) { text = "function" }, + (identifierLeaf) { text = self.name }, + ).filterNonNull() }) + typeParameterListNodes(self.typeParameters) + List(parameterListNode(self.parameters)) @@ -1167,8 +1165,8 @@ class ObjectEntryNode extends ObjectMemberNode { (terminal) { text = "[" }, self.key.builtNode, (terminal) { text = "]" }, - ) - + (if (self.value != null) List((terminal) { text = "=" }) else List()) + if (self.value != null) (terminal) { text = "=" } else null, + ).filterNonNull() }) + ( if (self.value != null) @@ -2103,8 +2101,11 @@ class AnnotationNode extends SyntaxNode { new Node { type = "annotation" children = - List((terminal) { text = "@" }, self.type.builtNode) - + (if (self.body == null) List() else List(self.body.builtNode)) + List( + (terminal) { text = "@" }, + self.type.builtNode, + self.body?.builtNode, + ).filterNonNull() } } From f47be50377ec5287c48a941b41663b07f69505b8 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Thu, 16 Jul 2026 14:57:54 +0200 Subject: [PATCH 11/49] Move parsing methods to Parser class --- .../org/pkl/core/runtime/SyntaxModule.java | 8 +- .../pkl/core/stdlib/syntax/ParserNodes.java | 148 ++++++++++++++++ .../pkl/core/stdlib/syntax/SyntaxNodes.java | 61 ------- .../org/pkl/core/errorMessages.properties | 3 + .../input/syntax/expressions.pkl | 5 +- .../input/syntax/format.pkl | 2 +- .../input/syntax/moduleStructure.pkl | 29 ++-- .../input/syntax/objectMembers.pkl | 5 +- .../input/syntax/traversal.pkl | 2 +- .../input/syntax/types.pkl | 9 +- .../input/syntax/walk.pkl | 2 +- .../output/syntax/moduleStructure.pcf | 2 - stdlib/syntax.pkl | 163 +++++++++--------- 13 files changed, 258 insertions(+), 181 deletions(-) create mode 100644 pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java index 923831e0a..6b09c764b 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java @@ -37,8 +37,8 @@ public static VmClass getSpanClass() { return SpanClass.instance; } - public static VmClass getParserErrorClass() { - return ParserErrorClass.instance; + public static VmClass getModuleNodeClass() { + return ModuleNodeClass.instance; } private static final class NodeClass { @@ -49,8 +49,8 @@ private static final class SpanClass { static final VmClass instance = loadClass("Span"); } - private static final class ParserErrorClass { - static final VmClass instance = loadClass("ParserError"); + private static final class ModuleNodeClass { + static final VmClass instance = loadClass("ModuleNode"); } @CompilerDirectives.TruffleBoundary diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java new file mode 100644 index 000000000..87127482a --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -0,0 +1,148 @@ +/* + * Copyright © 2026 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.stdlib.syntax; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.Specialization; +import java.util.ArrayList; +import java.util.Locale; +import org.pkl.core.runtime.Identifier; +import org.pkl.core.runtime.SyntaxModule; +import org.pkl.core.runtime.VmExceptionBuilder; +import org.pkl.core.runtime.VmList; +import org.pkl.core.runtime.VmNull; +import org.pkl.core.runtime.VmTyped; +import org.pkl.core.runtime.VmUtils; +import org.pkl.core.stdlib.ExternalMethod1Node; +import org.pkl.core.stdlib.VmObjectFactory; +import org.pkl.core.stdlib.syntax.SyntaxNodes.NodeData; +import org.pkl.parser.GenericParser; +import org.pkl.parser.GenericParserError; +import org.pkl.parser.syntax.generic.FullSpan; +import org.pkl.parser.syntax.generic.Node; +import org.pkl.parser.syntax.generic.NodeType; + +public class ParserNodes { + private ParserNodes() {} + + private static final VmObjectFactory spanFactory = + new VmObjectFactory(SyntaxModule::getSpanClass) + .addIntProperty("lineStart", FullSpan::lineBegin) + .addIntProperty("colStart", FullSpan::colBegin) + .addIntProperty("lineEnd", FullSpan::lineEnd) + .addIntProperty("colEnd", FullSpan::colEnd); + + private static final VmObjectFactory nodeFactory = + new VmObjectFactory(SyntaxModule::getNodeClass) + .addStringProperty("type", nd -> nd.node.type.name().toLowerCase(Locale.ROOT)) + .addListProperty("children", nd -> nd.childrenVm) + .addProperty("parent", nd -> VmNull.lift(nd.parentVm)) + .addProperty( + "text", + nd -> + nd.node.children.isEmpty() || nd.node.type == NodeType.STRING_CHARS + ? nd.node.text(nd.source) + : VmNull.withoutDefault()) + .addTypedProperty("span", nd -> nd.spanVm); + + private static final VmObjectFactory moduleNodeFactory = + new VmObjectFactory(SyntaxModule::getModuleNodeClass).addProperty("node", vm -> vm); + + public abstract static class parseModule extends ExternalMethod1Node { + @Specialization + @TruffleBoundary + protected Object evalString(@SuppressWarnings("unused") VmTyped self, String source) { + return doParse(source); + } + + @Specialization + @TruffleBoundary + protected Object evalResource(@SuppressWarnings("unused") VmTyped self, VmTyped source) { + // `source` is a `pkl.base#Resource` + var text = (String) VmUtils.readMember(source, Identifier.TEXT); + return doParse(text); + } + } + + public abstract static class parseModuleOrNull extends ExternalMethod1Node { + @Specialization + @TruffleBoundary + protected Object evalString(@SuppressWarnings("unused") VmTyped self, String source) { + return doParseOrNull(source); + } + + @Specialization + @TruffleBoundary + protected Object evalResource( + @SuppressWarnings("unused") VmTyped self, VmTyped source) { + // `source` is a `pkl.base#Resource` + var text = (String) VmUtils.readMember(source, Identifier.TEXT); + return doParseOrNull(text); + } + } + + private static Object doParse(String src) { + var sourceChars = src.toCharArray(); + try { + var parser = new GenericParser(); + var root = parser.parseModule(src); + var genericNode = convertNode(root, sourceChars); + return moduleNodeFactory.create(genericNode); + } catch (GenericParserError e) { + throw new VmExceptionBuilder().evalError("parserError").withHint(e.toString()).build(); + } + } + + private static Object doParseOrNull(String src) { + var sourceChars = src.toCharArray(); + try { + var parser = new GenericParser(); + var root = parser.parseModule(src); + var genericNode = convertNode(root, sourceChars); + return moduleNodeFactory.create(genericNode); + } catch (GenericParserError e) { + return VmNull.withoutDefault(); + } + } + + private static VmTyped convertNode(Node genericNode, char[] sourceChars) { + // convert children recursively + var childrenList = new ArrayList(genericNode.children.size()); + for (var child : genericNode.children) { + childrenList.add(convertNode(child, sourceChars)); + } + + // materialize text now so that nodes reused verbatim by `walk`/`format` are + // self-contained + if (genericNode.children.isEmpty() || genericNode.type == NodeType.STRING_CHARS) { + genericNode.text(sourceChars); + } + + var childrenVm = VmList.create(childrenList.toArray()); + var spanVm = spanFactory.create(genericNode.span); + var data = new NodeData(genericNode, sourceChars, childrenVm, spanVm); + + var result = nodeFactory.create(data); + + // set parent back-reference on each child + for (var childVm : childrenList) { + var childData = (NodeData) childVm.getExtraStorage(); + childData.parentVm = result; + } + + return result; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java index 003fa70c8..46b6e1960 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java @@ -30,13 +30,10 @@ import org.pkl.core.runtime.VmPair; import org.pkl.core.runtime.VmTyped; import org.pkl.core.runtime.VmUtils; -import org.pkl.core.stdlib.ExternalMethod1Node; import org.pkl.core.stdlib.ExternalMethod2Node; import org.pkl.core.stdlib.VmObjectFactory; import org.pkl.formatter.Formatter; import org.pkl.formatter.GrammarVersion; -import org.pkl.parser.GenericParser; -import org.pkl.parser.GenericParserError; import org.pkl.parser.syntax.generic.FullSpan; import org.pkl.parser.syntax.generic.Node; import org.pkl.parser.syntax.generic.NodeType; @@ -74,13 +71,6 @@ static final class ErrorData { } } - private static final VmObjectFactory spanFactory = - new VmObjectFactory(SyntaxModule::getSpanClass) - .addIntProperty("lineStart", FullSpan::lineBegin) - .addIntProperty("colStart", FullSpan::colBegin) - .addIntProperty("lineEnd", FullSpan::lineEnd) - .addIntProperty("colEnd", FullSpan::colEnd); - private static final VmObjectFactory nodeFactory = new VmObjectFactory(SyntaxModule::getNodeClass) .addStringProperty("type", nd -> nd.node.type.name().toLowerCase(Locale.ROOT)) @@ -94,57 +84,6 @@ static final class ErrorData { : VmNull.withoutDefault()) .addTypedProperty("span", nd -> nd.spanVm); - private static final VmObjectFactory parserErrorFactory = - new VmObjectFactory(SyntaxModule::getParserErrorClass) - .addStringProperty("text", ed -> ed.text) - .addTypedProperty("span", ed -> ed.spanVm); - - public abstract static class parseNodes extends ExternalMethod1Node { - @Specialization - @TruffleBoundary - protected Object eval(VmTyped self, String source) { - var sourceChars = source.toCharArray(); - - try { - var parser = new GenericParser(); - var root = parser.parseModule(source); - return convertNode(root, sourceChars); - } catch (GenericParserError e) { - var errorSpanVm = spanFactory.create(e.getSpan()); - var text = e.getMessage() != null ? e.getMessage() : "Parse error"; - return parserErrorFactory.create(new ErrorData(text, errorSpanVm)); - } - } - - private static VmTyped convertNode(Node genericNode, char[] sourceChars) { - // convert children recursively - var childrenList = new ArrayList(genericNode.children.size()); - for (var child : genericNode.children) { - childrenList.add(convertNode(child, sourceChars)); - } - - // materialize text now so that nodes reused verbatim by `walk`/`format` are - // self-contained - if (genericNode.children.isEmpty() || genericNode.type == NodeType.STRING_CHARS) { - genericNode.text(sourceChars); - } - - var childrenVm = VmList.create(childrenList.toArray()); - var spanVm = spanFactory.create(genericNode.span); - var data = new NodeData(genericNode, sourceChars, childrenVm, spanVm); - - var result = nodeFactory.create(data); - - // set parent back-reference on each child - for (var childVm : childrenList) { - var childData = (NodeData) childVm.getExtraStorage(); - childData.parentVm = result; - } - - return result; - } - } - public abstract static class formatToString extends ExternalMethod2Node { @Specialization @TruffleBoundary 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 b1e52a977..08b95965c 100644 --- a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties +++ b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties @@ -1206,5 +1206,8 @@ Redirected to: `{1}` invalidReferenceTypeAnnotationWithConstraint=\ `Reference` referent type argument may not include type constraints. +parserError=\ +Could not parse Pkl source. + cannotInstallPackageWithNoCache=\ Cannot install package to module cache dir when module cache is disabled. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl index 353034e45..993ee5775 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl @@ -2,11 +2,8 @@ amends "../snippetTest.pkl" import "pkl:syntax" -local function parse(source: String) = syntax.parse(source) - local function expr(source: String) = - let (result = parse("x = \(source)")) - (result as syntax.ModuleNode).properties.first.value + new syntax.Parser {}.parseModule("x = \(source)").properties.first.value facts { ["literals"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/format.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/format.pkl index 7df9fd902..ed4ebc37b 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/format.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/format.pkl @@ -4,7 +4,7 @@ import "pkl:syntax" local function roundTrip(source: String) = syntax.format(parseNode(source)) -local function parseNode(source: String) = syntax.parse(source).node +local function parseNode(source: String) = new syntax.Parser {}.parseModule(source).node local function replaceLeaf( node: syntax.Node, diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl index 0a928e5c0..6e067ef75 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl @@ -2,7 +2,9 @@ amends "../snippetTest.pkl" import "pkl:syntax" -local function parse(source: String) = syntax.parse(source) +local parser = new syntax.Parser {} + +local function parse(source: String) = parser.parseModule(source) facts { ["module declaration"] { @@ -174,32 +176,29 @@ facts { params[0].name == "x" params[0].typeAnnotation != null - params[0].isWildcard == false + params[0].isBlankIdentifier == false params[1].name == "_" - params[1].isWildcard == true + params[1].isBlankIdentifier == true params[1].typeAnnotation == null params[2].name == "y" params[2].typeAnnotation == null - params[2].isWildcard == false + params[2].isBlankIdentifier == false } ["parser error"] { - local result = parse("x = {{{") - result is syntax.ParserError - (result as syntax.ParserError).text.length > 0 + local result = new syntax.Parser {}.parseModuleOrNull("x = {{{") + result == null } ["empty module"] { local result = parse("") - result is syntax.ModuleNode - local mod = result as syntax.ModuleNode - mod.declaration == null - mod.imports.length == 0 - mod.classes.length == 0 - mod.typeAliases.length == 0 - mod.properties.length == 0 - mod.methods.length == 0 + result.declaration == null + result.imports.length == 0 + result.classes.length == 0 + result.typeAliases.length == 0 + result.properties.length == 0 + result.methods.length == 0 } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl index b649d3a38..4af869629 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl @@ -2,12 +2,11 @@ amends "../snippetTest.pkl" import "pkl:syntax" -local function parse(source: String) = syntax.parse(source) +local function parse(source: String) = new syntax.Parser {}.parseModule(source) local function body(source: String) = let (result = parse("x { \(source) }")) - let (mod = result as syntax.ModuleNode) - mod.properties.first.objectBodies.first + result.properties.first.objectBodies.first facts { ["object property"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl index 834ae0bd6..9d7aed84d 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl @@ -2,7 +2,7 @@ amends "../snippetTest.pkl" import "pkl:syntax" -local function mod(source: String): syntax.ModuleNode = syntax.parse(source) as syntax.ModuleNode +local function mod(source: String): syntax.ModuleNode = new syntax.Parser {}.parseModule(source) local sample: syntax.Node = mod( diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl index 2e5011241..23dd52316 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl @@ -2,11 +2,10 @@ amends "../snippetTest.pkl" import "pkl:syntax" -local function parse(source: String) = syntax.parse(source) +local function parse(source: String): syntax.ModuleNode = new syntax.Parser {}.parseModule(source) local function typeOf(typeSource: String) = - let (result = parse("x: \(typeSource) = 0")) - (result as syntax.ModuleNode).properties.first.typeAnnotation + parse("x: \(typeSource) = 0").properties.first.typeAnnotation facts { ["simple types"] { @@ -82,7 +81,7 @@ facts { ["string constant type"] { local result = parse(#"typealias Foo = "bar"|"baz""#) - local ta = (result as syntax.ModuleNode).typeAliases.first + local ta = result.typeAliases.first ta.type is syntax.UnionTypeNode local members = (ta.type as syntax.UnionTypeNode).members members.length == 2 @@ -94,7 +93,7 @@ facts { ["type annotation"] { local result = parse("x: String = \"hello\"") - local prop = (result as syntax.ModuleNode).properties.first + local prop = result.properties.first prop.typeAnnotation != null prop.typeAnnotation!! is syntax.DeclaredTypeNode } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl index 68103f7ae..cc9f990c1 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl @@ -2,7 +2,7 @@ amends "../snippetTest.pkl" import "pkl:syntax" -local function mod(source: String): syntax.ModuleNode = syntax.parse(source) as syntax.ModuleNode +local function mod(source: String): syntax.ModuleNode = new syntax.Parser {}.parseModule(source) local function fmt(source: String): String = syntax.format(mod(source).node) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf index ec5e36c0c..fb09ff624 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf @@ -105,7 +105,6 @@ facts { } ["parser error"] { true - true } ["empty module"] { true @@ -114,6 +113,5 @@ facts { true true true - true } } diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 59ef7210f..cc1d0b963 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -15,19 +15,19 @@ //===----------------------------------------------------------------------===// /// Utilities for managing Pkl source code -@ModuleInfo { minPklVersion = "0.32.0" } +@ModuleInfo { minPklVersion = "0.33.0" } module pkl.syntax -/// Parse the string or resource as a Pkl module, returning either a typed AST node or an error. -function parse(source: String | Resource): ModuleNode | ParserError = - let (src = if (source is String) source else source.text) - let (result = parseNodes(src)) - if (result is ParserError) - result - else - new ModuleNode { node = result } +/// Utilities to parse Pkl source code to a typed AST. +class Parser { + /// Parse the string or resource as a Pkl module, returning either a typed AST node or + /// throwing an error. + external function parseModule(source: String | Resource): ModuleNode -external local function parseNodes(source: String): Node | ParserError + /// Parse the string or resource as a Pkl module, returning either a typed AST node or null + /// in case of an error. + external function parseModuleOrNull(source: String | Resource): ModuleNode? +} /// Format a syntax node back to Pkl source code. function format(node: Node): String = formatToString(node, "V2") @@ -129,11 +129,6 @@ class ConvertSpan extends ConvertProperty { Pair(property.key, "\(span.lineStart):\(span.colStart)-\(span.lineEnd):\(span.colEnd)") } -class ParserError { - text: String - span: Span -} - typealias NodeType = // terminals and affixes "terminal" @@ -386,7 +381,7 @@ local const constructors: Map SyntaxNode> = ) // Wrap a raw Node into the appropriate Expr subclass. -local const function wrapExpr(n: Node): Expr = constructors[n.type].apply(n) as Expr +local const function wrapExpr(n: Node): ExprNode = constructors[n.type].apply(n) as ExprNode // Wrap a raw Node into the appropriate TypeNode subclass. local const function wrapTypeNode(n: Node): TypeNode = constructors[n.type].apply(n) as TypeNode @@ -450,7 +445,7 @@ abstract class SyntaxNode { } /// Base class for expression nodes. -abstract class Expr extends SyntaxNode +abstract class ExprNode extends SyntaxNode /// Base class for type nodes. abstract class TypeNode extends SyntaxNode @@ -821,7 +816,7 @@ class ClassPropertyNode extends SyntaxNode { if (n == null) null else wrapTypeNode(n.findTypeChild()!!) /// The value expression, if present (from `= expr`). - value: Expr? = + value: ExprNode? = let (body = node?.findChild("class_property_body")) if (body == null) null @@ -909,7 +904,7 @@ class ClassMethodNode extends SyntaxNode { if (n == null) null else wrapTypeNode(n.findTypeChild()!!) /// The method body expression, if present. Null for abstract methods. - body: Expr? = + body: ExprNode? = let (bodyNode = node?.findChild("class_method_body")) if (bodyNode == null) null @@ -1017,7 +1012,7 @@ class ObjectPropertyNode extends ObjectMemberNode { if (n == null) null else wrapTypeNode(n.findTypeChild()!!) /// The value expression, if present (from `= expr`). - value: Expr? = + value: ExprNode? = let (body = node?.findChild("object_property_body")) if (body == null) null @@ -1093,7 +1088,7 @@ class ObjectMethodNode extends ObjectMemberNode { if (n == null) null else wrapTypeNode(n.findTypeChild()!!) /// The method body expression. - body: Expr = + body: ExprNode = let (bodyNode = node?.findChild("class_method_body")) wrapExpr(bodyNode!!.findExprChild()!!) @@ -1127,7 +1122,7 @@ class ObjectMethodNode extends ObjectMemberNode { /// An object element (a positional expression in an object body). class ObjectElementNode extends ObjectMemberNode { /// The expression value. - expression: Expr = wrapExpr(node?.findExprChild()!!) + expression: ExprNode = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1142,10 +1137,10 @@ class ObjectEntryNode extends ObjectMemberNode { local entryHeader: Node? = node?.findChild("object_entry_header") /// The key expression. - key: Expr = wrapExpr(entryHeader?.findExprChild()!!) + key: ExprNode = wrapExpr(entryHeader?.findExprChild()!!) /// The value expression, if present (from `[key] = value`). - value: Expr? = + value: ExprNode? = let (e = node?.findExprChildren()?.findOrNull((c) -> c != entryHeader?.findExprChild())) if (e == null) null else wrapExpr(e) @@ -1183,7 +1178,7 @@ class ObjectSpreadNode extends ObjectMemberNode { isNullable: Boolean = terminals.firstOrNull?.text == "...?" /// The spread expression. - expression: Expr = wrapExpr(node?.findExprChild()!!) + expression: ExprNode = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1202,10 +1197,10 @@ class MemberPredicateNode extends ObjectMemberNode { local exprs: List = node?.findExprChildren() ?? List() /// The condition expression. - condition: Expr = wrapExpr(exprs.first) + condition: ExprNode = wrapExpr(exprs.first) /// The value expression, if present. - value: Expr? = if (exprs.length < 2) null else wrapExpr(exprs[1]) + value: ExprNode? = if (exprs.length < 2) null else wrapExpr(exprs[1]) /// Object bodies for amending. objectBodies: List = @@ -1246,7 +1241,7 @@ class ForGeneratorNode extends ObjectMemberNode { valueParameter: ParameterNode = new ParameterNode { node = paramNodes.last } /// The iterable expression. - iterable: Expr = wrapExpr(forDef?.findExprChild()!!) + iterable: ExprNode = wrapExpr(forDef?.findExprChild()!!) /// The body. body: ObjectBodyNode = @@ -1301,7 +1296,7 @@ class WhenGeneratorNode extends ObjectMemberNode { local bodyNodes: List = node?.findChildren("object_body") ?? List() /// The condition expression. - condition: Expr = wrapExpr(whenHeader?.findExprChild()!!) + condition: ExprNode = wrapExpr(whenHeader?.findExprChild()!!) /// The "then" body. thenBody: ObjectBodyNode = new ObjectBodyNode { node = bodyNodes.first } @@ -1338,27 +1333,27 @@ class WhenGeneratorNode extends ObjectMemberNode { } /// The `this` expression. -class ThisExprNode extends Expr { +class ThisExprNode extends ExprNode { fixed builtNode = new Node { type = "this_expr"; text = "this" } } /// The `outer` expression. -class OuterExprNode extends Expr { +class OuterExprNode extends ExprNode { fixed builtNode = new Node { type = "outer_expr"; text = "outer" } } /// The `module` expression. -class ModuleExprNode extends Expr { +class ModuleExprNode extends ExprNode { fixed builtNode = new Node { type = "module_expr"; text = "module" } } /// A `null` literal expression. -class NullLiteralExprNode extends Expr { +class NullLiteralExprNode extends ExprNode { fixed builtNode = new Node { type = "null_expr"; text = "null" } } /// A boolean literal expression (`true` or `false`). -class BoolLiteralExprNode extends Expr { +class BoolLiteralExprNode extends ExprNode { /// The boolean value. value: Boolean = node?.text == "true" @@ -1368,7 +1363,7 @@ class BoolLiteralExprNode extends Expr { } /// An integer literal expression. -class IntLiteralExprNode extends Expr { +class IntLiteralExprNode extends ExprNode { /// The integer literal (e.g. `42`, `"0xFF"`). value: Int | String = node?.text ?? "" @@ -1378,7 +1373,7 @@ class IntLiteralExprNode extends Expr { } /// A float literal expression. -class FloatLiteralExprNode extends Expr { +class FloatLiteralExprNode extends ExprNode { /// The float literal (e.g. `3.14`, `"1.0e10"`). value: Float | String = node?.text ?? "" @@ -1388,7 +1383,7 @@ class FloatLiteralExprNode extends Expr { } /// A single-line string literal expression. -class SingleLineStringLiteralExprNode extends Expr { +class SingleLineStringLiteralExprNode extends ExprNode { /// The string parts (chars, escapes, interpolations). parts: List = buildStringParts(children) @@ -1406,7 +1401,7 @@ class SingleLineStringLiteralExprNode extends Expr { /// A multi-line string literal expression. /// /// Use [StringNewlineNode] entries in [parts] to separate lines. -class MultiLineStringLiteralExprNode extends Expr { +class MultiLineStringLiteralExprNode extends ExprNode { /// The string parts (chars, escapes, newlines, interpolations). parts: List = buildStringParts(children) @@ -1428,12 +1423,12 @@ class MultiLineStringLiteralExprNode extends Expr { } /// An unqualified access expression (`name` or `name(args)`). -class UnqualifiedAccessExprNode extends Expr { +class UnqualifiedAccessExprNode extends ExprNode { /// The identifier being accessed. identifier: String = identifierText(node!!) /// The arguments, if this is a function call. Null for a plain identifier access. - arguments: List? = + arguments: List? = let (n = node?.findChild("argument_list")) if (n == null) null else argumentsOf(n) @@ -1449,9 +1444,9 @@ class UnqualifiedAccessExprNode extends Expr { /// A qualified access expression (`receiver.member` or `receiver?.member`, /// optionally with arguments for method calls). -class QualifiedAccessExprNode extends Expr { +class QualifiedAccessExprNode extends ExprNode { /// The receiver expression. - receiver: Expr = wrapExpr((node?.findExprChildren() ?? List()).first) + receiver: ExprNode = wrapExpr((node?.findExprChildren() ?? List()).first) /// Whether this is a null-safe access (`?.`). isNullSafe: Boolean = node?.findChild("operator")?.text == "?." @@ -1462,7 +1457,7 @@ class QualifiedAccessExprNode extends Expr { identifierText(m) /// The arguments, if this is a method call. Null for a property access. - arguments: List? = + arguments: List? = let (m = (node?.findChildren("unqualified_access_expr") ?? List()).last) let (n = m.findChild("argument_list")) if (n == null) null else argumentsOf(n) @@ -1488,12 +1483,12 @@ class QualifiedAccessExprNode extends Expr { } /// A subscript expression (`receiver[index]`). -class SubscriptExprNode extends Expr { +class SubscriptExprNode extends ExprNode { /// The receiver expression. - receiver: Expr = wrapExpr(node?.findExprChildren().first) + receiver: ExprNode = wrapExpr(node?.findExprChildren().first) /// The index expression. - index: Expr = wrapExpr(node?.findExprChildren().getOrNull(1)!!) + index: ExprNode = wrapExpr(node?.findExprChildren().getOrNull(1)!!) fixed builtNode = let (self = this) @@ -1512,33 +1507,33 @@ class SubscriptExprNode extends Expr { /// A `super.member` access expression. /// /// Read-only: this node has no builder and cannot be constructed from scratch. -class SuperAccessExprNode extends Expr { +class SuperAccessExprNode extends ExprNode { fixed builtNode = node!! } /// A `super[index]` subscript expression. /// /// Read-only: this node has no builder and cannot be constructed from scratch. -class SuperSubscriptExprNode extends Expr { +class SuperSubscriptExprNode extends ExprNode { fixed builtNode = node!! } /// An `if (condition) thenExpr else elseExpr` expression. -class IfExprNode extends Expr { +class IfExprNode extends ExprNode { local ifHeader: Node = node?.findChild("if_header")!! local ifCondition: Node = ifHeader.findChild("if_condition")!! local ifConditionExpr: Node = ifCondition.findChild("if_condition_expr")!! /// The condition expression. - condition: Expr = wrapExpr(ifConditionExpr.findExprChild()!!) + condition: ExprNode = wrapExpr(ifConditionExpr.findExprChild()!!) /// The then-branch expression. - thenExpr: Expr = + thenExpr: ExprNode = let (thenNode = node?.findChild("if_then_expr")) wrapExpr(thenNode!!.findExprChild()!!) /// The else-branch expression. - elseExpr: Expr = + elseExpr: ExprNode = let (elseNode = node?.findChild("if_else_expr")) wrapExpr(elseNode!!.findExprChild()!!) @@ -1581,7 +1576,7 @@ class IfExprNode extends Expr { } /// A `let (param = value) body` expression. -class LetExprNode extends Expr { +class LetExprNode extends ExprNode { local letParamDef: Node = node?.findChild("let_parameter_definition")!! local letParam: Node = letParamDef.findChild("let_parameter")!! @@ -1591,10 +1586,10 @@ class LetExprNode extends Expr { new ParameterNode { node = p!! } /// The binding value expression. - bindingValue: Expr = wrapExpr(letParam.findExprChild()!!) + bindingValue: ExprNode = wrapExpr(letParam.findExprChild()!!) /// The body expression. - body: Expr = wrapExpr(node?.findExprChild()!!) + body: ExprNode = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1626,9 +1621,9 @@ class LetExprNode extends Expr { } /// A `throw(expr)` expression. -class ThrowExprNode extends Expr { +class ThrowExprNode extends ExprNode { /// The expression being thrown. - expression: Expr = wrapExpr(node?.findExprChild()!!) + expression: ExprNode = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1645,9 +1640,9 @@ class ThrowExprNode extends Expr { } /// A `trace(expr)` expression. -class TraceExprNode extends Expr { +class TraceExprNode extends ExprNode { /// The expression being traced. - expression: Expr = wrapExpr(node?.findExprChild()!!) + expression: ExprNode = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1664,7 +1659,7 @@ class TraceExprNode extends Expr { } /// An `import("uri")` or `import*("uri")` expression. -class ImportExprNode extends Expr { +class ImportExprNode extends ExprNode { /// Whether this is a glob import expression (`import*`). isGlob: Boolean = terminals.firstOrNull?.text == "import*" @@ -1686,13 +1681,13 @@ class ImportExprNode extends Expr { } /// A `read(expr)`, `read*(expr)`, or `read?(expr)` expression. -class ReadExprNode extends Expr { +class ReadExprNode extends ExprNode { /// The keyword used (`"read"`, `"read?"`, or `"read*"`). keyword: "read" | "read?" | "read*" = (terminals.firstOrNull?.text ?? "read") as "read" | "read?" | "read*" /// The expression to be read. - expression: Expr = wrapExpr(node?.findExprChild()!!) + expression: ExprNode = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1709,7 +1704,7 @@ class ReadExprNode extends Expr { } /// A `new Type { ... }` expression. -class NewExprNode extends Expr { +class NewExprNode extends ExprNode { local newHeader: Node = node?.findChild("new_header")!! /// The type being constructed, if present. @@ -1742,9 +1737,9 @@ class NewExprNode extends Expr { } /// An `(expr) { ... }` amends expression. -class AmendsExprNode extends Expr { +class AmendsExprNode extends ExprNode { /// The expression being amended. - parentExpr: Expr = wrapExpr((node?.findExprChildren() ?? List()).first) + parentExpr: ExprNode = wrapExpr((node?.findExprChildren() ?? List()).first) /// The object body. body: ObjectBodyNode = @@ -1760,17 +1755,17 @@ class AmendsExprNode extends Expr { } /// A binary operator expression (`left op right`), including `is`/`as`. -class BinaryOpExprNode extends Expr { +class BinaryOpExprNode extends ExprNode { local exprs: List = node?.findExprChildren() ?? List() /// The operator string. operator: String = node?.findChild("operator")?.text ?? "" /// The left-hand expression. - left: Expr = wrapExpr(exprs.first) + left: ExprNode = wrapExpr(exprs.first) /// The right-hand expression, if present (not present for `is`/`as` which use [rightType]). - right: Expr? = if (exprs.length < 2) null else wrapExpr(exprs[1]) + right: ExprNode? = if (exprs.length < 2) null else wrapExpr(exprs[1]) /// The right-hand type, if this is an `is` or `as` operation. rightType: TypeNode? = @@ -1798,9 +1793,9 @@ class BinaryOpExprNode extends Expr { } /// A unary minus expression (`-expr`). -class UnaryMinusExprNode extends Expr { +class UnaryMinusExprNode extends ExprNode { /// The operand expression. - operand: Expr = wrapExpr(node?.findExprChild()!!) + operand: ExprNode = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1811,9 +1806,9 @@ class UnaryMinusExprNode extends Expr { } /// A logical not expression (`!expr`). -class LogicalNotExprNode extends Expr { +class LogicalNotExprNode extends ExprNode { /// The operand expression. - operand: Expr = wrapExpr(node?.findExprChild()!!) + operand: ExprNode = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1824,9 +1819,9 @@ class LogicalNotExprNode extends Expr { } /// A non-null assertion expression (`expr!!`). -class NonNullExprNode extends Expr { +class NonNullExprNode extends ExprNode { /// The operand expression. - operand: Expr = wrapExpr(node?.findExprChild()!!) + operand: ExprNode = wrapExpr(node?.findExprChild()!!) fixed builtNode = let (self = this) @@ -1837,14 +1832,14 @@ class NonNullExprNode extends Expr { } /// A function literal expression (`(params) -> body`). -class FunctionLiteralExprNode extends Expr { +class FunctionLiteralExprNode extends ExprNode { /// The parameters. parameters: List = let (pl = node?.findChild("parameter_list")) parametersOf(pl!!) /// The body expression. - body: Expr = + body: ExprNode = let (bodyNode = node?.findChild("function_literal_body")) wrapExpr(bodyNode!!.findExprChild()!!) @@ -1865,9 +1860,9 @@ class FunctionLiteralExprNode extends Expr { } /// A parenthesized expression (`(expr)`). -class ParenthesizedExprNode extends Expr { +class ParenthesizedExprNode extends ExprNode { /// The inner expression, if present (may be empty for `()`). - expression: Expr? = + expression: ExprNode? = let (elems = node?.findChild("parenthesized_expr_elements")) if (elems == null) null @@ -2024,7 +2019,7 @@ class ConstrainedTypeNode extends TypeNode { local constraintElems: Node = constraint.findChild("constrained_type_elements")!! /// The constraint expressions. - constraints: List = constraintElems.findExprChildren().map((n) -> wrapExpr(n)) + constraints: List = constraintElems.findExprChildren().map((n) -> wrapExpr(n)) fixed builtNode = let (self = this) @@ -2111,8 +2106,8 @@ class AnnotationNode extends SyntaxNode { /// A parameter declaration (`name`, `name: Type`, or `_`). class ParameterNode extends SyntaxNode { - /// Whether this is a wildcard parameter (`_`). - isWildcard: Boolean = name == "_" + /// Whether this is a blank identifier parameter (`_`). + isBlankIdentifier: Boolean = name == "_" /// The parameter name. Use `"_"` for a wildcard parameter. name: String = @@ -2219,7 +2214,7 @@ class StringNewlineNode extends StringPartNode { /// An interpolation in a string literal (`\(expr)`). class StringInterpolationNode extends StringPartNode { /// The interpolated expression. - expression: Expr = wrapExpr(node!!) + expression: ExprNode = wrapExpr(node!!) function toNodes(): List = let (self = this) @@ -2356,7 +2351,7 @@ local const function parametersOf(pl: Node?): List = elems.findChildren("parameter").map((n) -> new ParameterNode { node = n }) // Read the argument expressions of an `argument_list` node. -local const function argumentsOf(al: Node?): List = +local const function argumentsOf(al: Node?): List = let (elems = al?.findChild("argument_list_elements")) if (elems == null) List() else elems.findExprChildren().map((n) -> wrapExpr(n)) @@ -2378,7 +2373,7 @@ local const function parameterListNode(parameters: List): Node = } // Build an `argument_list` node from typed argument expressions. -local const function argumentListNode(arguments: List): Node = new Node { +local const function argumentListNode(arguments: List): Node = new Node { type = "argument_list" children = if (arguments.isEmpty) From acbbaad865c0fb633ff2bbe9ac242cc52b461108 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Mon, 20 Jul 2026 18:40:25 +0200 Subject: [PATCH 12/49] Move node building to Java --- .../org/pkl/core/runtime/SyntaxModule.java | 512 ++++++ .../pkl/core/stdlib/syntax/ParserNodes.java | 1412 ++++++++++++++++- .../pkl/core/stdlib/syntax/SyntaxNodes.java | 11 - .../input/syntax/expressions.pkl | 63 +- .../input/syntax/moduleStructure.pkl | 30 +- .../input/syntax/objectMembers.pkl | 18 +- .../input/syntax/traversal.pkl | 37 - .../input/syntax/types.pkl | 4 +- .../input/syntax/walk.pkl | 32 - .../output/syntax/expressions.pcf | 29 + .../output/syntax/traversal.pcf | 19 - .../output/syntax/walk.pcf | 12 - stdlib/syntax.pkl | 1019 +++--------- 13 files changed, 2219 insertions(+), 979 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java index 6b09c764b..65a8d8b1a 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java @@ -41,6 +41,262 @@ public static VmClass getModuleNodeClass() { return ModuleNodeClass.instance; } + public static VmClass getModuleDeclarationNodeClass() { + return ModuleDeclarationNodeClass.instance; + } + + public static VmClass getImportNodeClass() { + return ImportNodeClass.instance; + } + + public static VmClass getIdentifierNodeClass() { + return IdentifierNodeClass.instance; + } + + public static VmClass getQualifiedIdentifierNodeClass() { + return QualifiedIdentifierNodeClass.instance; + } + + public static VmClass getDocCommentNodeClass() { + return DocCommentNodeClass.instance; + } + + public static VmClass getAnnotationNodeClass() { + return AnnotationNodeClass.instance; + } + + public static VmClass getClassNodeClass() { + return ClassNodeClass.instance; + } + + public static VmClass getTypeAliasNodeClass() { + return TypeAliasNodeClass.instance; + } + + public static VmClass getClassBodyNodeClass() { + return ClassBodyNodeClass.instance; + } + + public static VmClass getClassPropertyNodeClass() { + return ClassPropertyNodeClass.instance; + } + + public static VmClass getClassMethodNodeClass() { + return ClassMethodNodeClass.instance; + } + + public static VmClass getObjectBodyNodeClass() { + return ObjectBodyNodeClass.instance; + } + + public static VmClass getParameterNodeClass() { + return ParameterNodeClass.instance; + } + + public static VmClass getObjectElementNodeClass() { + return ObjectElementNodeClass.instance; + } + + public static VmClass getObjectPropertyNodeClass() { + return ObjectPropertyNodeClass.instance; + } + + public static VmClass getObjectMethodNodeClass() { + return ObjectMethodNodeClass.instance; + } + + public static VmClass getMemberPredicateNodeClass() { + return MemberPredicateNodeClass.instance; + } + + public static VmClass getObjectEntryNodeClass() { + return ObjectEntryNodeClass.instance; + } + + public static VmClass getObjectSpreadNodeClass() { + return ObjectSpreadNodeClass.instance; + } + + public static VmClass getWhenGeneratorNodeClass() { + return WhenGeneratorNodeClass.instance; + } + + public static VmClass getForGeneratorNodeClass() { + return ForGeneratorNodeClass.instance; + } + + public static VmClass getStringCharsNodeClass() { + return StringCharsNodeClass.instance; + } + + public static VmClass getStringEscapeNodeClass() { + return StringEscapeNodeClass.instance; + } + + public static VmClass getStringNewlineNodeClass() { + return StringNewlineNodeClass.instance; + } + + public static VmClass getStringInterpolationNodeClass() { + return StringInterpolationNodeClass.instance; + } + + public static VmClass getTypeParameterNodeClass() { + return TypeParameterNodeClass.instance; + } + + public static VmClass getUnknownTypeNodeClass() { + return UnknownTypeNodeClass.instance; + } + + public static VmClass getNothingTypeNodeClass() { + return NothingTypeNodeClass.instance; + } + + public static VmClass getModuleTypeNodeClass() { + return ModuleTypeNodeClass.instance; + } + + public static VmClass getDeclaredTypeNodeClass() { + return DeclaredTypeNodeClass.instance; + } + + public static VmClass getNullableTypeNodeClass() { + return NullableTypeNodeClass.instance; + } + + public static VmClass getUnionTypeNodeClass() { + return UnionTypeNodeClass.instance; + } + + public static VmClass getFunctionTypeNodeClass() { + return FunctionTypeNodeClass.instance; + } + + public static VmClass getConstrainedTypeNodeClass() { + return ConstrainedTypeNodeClass.instance; + } + + public static VmClass getParenthesizedTypeNodeClass() { + return ParenthesizedTypeNodeClass.instance; + } + + public static VmClass getStringConstantTypeNodeClass() { + return StringConstantTypeNodeClass.instance; + } + + public static VmClass getThisExprNodeClass() { + return ThisExprNodeClass.instance; + } + + public static VmClass getOuterExprNodeClass() { + return OuterExprNodeClass.instance; + } + + public static VmClass getModuleExprNodeClass() { + return ModuleExprNodeClass.instance; + } + + public static VmClass getNullLiteralExprNodeClass() { + return NullLiteralExprNodeClass.instance; + } + + public static VmClass getBoolLiteralExprNodeClass() { + return BoolLiteralExprNodeClass.instance; + } + + public static VmClass getIntLiteralExprNodeClass() { + return IntLiteralExprNodeClass.instance; + } + + public static VmClass getFloatLiteralExprNodeClass() { + return FloatLiteralExprNodeClass.instance; + } + + public static VmClass getSingleLineStringLiteralExprNodeClass() { + return SingleLineStringLiteralExprNodeClass.instance; + } + + public static VmClass getMultiLineStringLiteralExprNodeClass() { + return MultiLineStringLiteralExprNodeClass.instance; + } + + public static VmClass getUnqualifiedAccessExprNodeClass() { + return UnqualifiedAccessExprNodeClass.instance; + } + + public static VmClass getQualifiedAccessExprNodeClass() { + return QualifiedAccessExprNodeClass.instance; + } + + public static VmClass getSubscriptExprNodeClass() { + return SubscriptExprNodeClass.instance; + } + + public static VmClass getSuperAccessExprNodeClass() { + return SuperAccessExprNodeClass.instance; + } + + public static VmClass getSuperSubscriptExprNodeClass() { + return SuperSubscriptExprNodeClass.instance; + } + + public static VmClass getIfExprNodeClass() { + return IfExprNodeClass.instance; + } + + public static VmClass getLetExprNodeClass() { + return LetExprNodeClass.instance; + } + + public static VmClass getThrowExprNodeClass() { + return ThrowExprNodeClass.instance; + } + + public static VmClass getTraceExprNodeClass() { + return TraceExprNodeClass.instance; + } + + public static VmClass getImportExprNodeClass() { + return ImportExprNodeClass.instance; + } + + public static VmClass getReadExprNodeClass() { + return ReadExprNodeClass.instance; + } + + public static VmClass getNewExprNodeClass() { + return NewExprNodeClass.instance; + } + + public static VmClass getAmendsExprNodeClass() { + return AmendsExprNodeClass.instance; + } + + public static VmClass getBinaryOpExprNodeClass() { + return BinaryOpExprNodeClass.instance; + } + + public static VmClass getUnaryMinusExprNodeClass() { + return UnaryMinusExprNodeClass.instance; + } + + public static VmClass getLogicalNotExprNodeClass() { + return LogicalNotExprNodeClass.instance; + } + + public static VmClass getNonNullExprNodeClass() { + return NonNullExprNodeClass.instance; + } + + public static VmClass getFunctionLiteralExprNodeClass() { + return FunctionLiteralExprNodeClass.instance; + } + + public static VmClass getParenthesizedExprNodeClass() { + return ParenthesizedExprNodeClass.instance; + } + private static final class NodeClass { static final VmClass instance = loadClass("Node"); } @@ -53,6 +309,262 @@ private static final class ModuleNodeClass { static final VmClass instance = loadClass("ModuleNode"); } + private static final class ModuleDeclarationNodeClass { + static final VmClass instance = loadClass("ModuleDeclarationNode"); + } + + private static final class ImportNodeClass { + static final VmClass instance = loadClass("ImportNode"); + } + + private static final class IdentifierNodeClass { + static final VmClass instance = loadClass("IdentifierNode"); + } + + private static final class QualifiedIdentifierNodeClass { + static final VmClass instance = loadClass("QualifiedIdentifierNode"); + } + + private static final class DocCommentNodeClass { + static final VmClass instance = loadClass("DocCommentNode"); + } + + private static final class AnnotationNodeClass { + static final VmClass instance = loadClass("AnnotationNode"); + } + + private static final class ClassNodeClass { + static final VmClass instance = loadClass("ClassNode"); + } + + private static final class TypeAliasNodeClass { + static final VmClass instance = loadClass("TypeAliasNode"); + } + + private static final class ClassBodyNodeClass { + static final VmClass instance = loadClass("ClassBodyNode"); + } + + private static final class ClassPropertyNodeClass { + static final VmClass instance = loadClass("ClassPropertyNode"); + } + + private static final class ClassMethodNodeClass { + static final VmClass instance = loadClass("ClassMethodNode"); + } + + private static final class ObjectBodyNodeClass { + static final VmClass instance = loadClass("ObjectBodyNode"); + } + + private static final class ParameterNodeClass { + static final VmClass instance = loadClass("ParameterNode"); + } + + private static final class ObjectElementNodeClass { + static final VmClass instance = loadClass("ObjectElementNode"); + } + + private static final class ObjectPropertyNodeClass { + static final VmClass instance = loadClass("ObjectPropertyNode"); + } + + private static final class ObjectMethodNodeClass { + static final VmClass instance = loadClass("ObjectMethodNode"); + } + + private static final class MemberPredicateNodeClass { + static final VmClass instance = loadClass("MemberPredicateNode"); + } + + private static final class ObjectEntryNodeClass { + static final VmClass instance = loadClass("ObjectEntryNode"); + } + + private static final class ObjectSpreadNodeClass { + static final VmClass instance = loadClass("ObjectSpreadNode"); + } + + private static final class WhenGeneratorNodeClass { + static final VmClass instance = loadClass("WhenGeneratorNode"); + } + + private static final class ForGeneratorNodeClass { + static final VmClass instance = loadClass("ForGeneratorNode"); + } + + private static final class StringCharsNodeClass { + static final VmClass instance = loadClass("StringCharsNode"); + } + + private static final class StringEscapeNodeClass { + static final VmClass instance = loadClass("StringEscapeNode"); + } + + private static final class StringNewlineNodeClass { + static final VmClass instance = loadClass("StringNewlineNode"); + } + + private static final class StringInterpolationNodeClass { + static final VmClass instance = loadClass("StringInterpolationNode"); + } + + private static final class TypeParameterNodeClass { + static final VmClass instance = loadClass("TypeParameterNode"); + } + + private static final class UnknownTypeNodeClass { + static final VmClass instance = loadClass("UnknownTypeNode"); + } + + private static final class NothingTypeNodeClass { + static final VmClass instance = loadClass("NothingTypeNode"); + } + + private static final class ModuleTypeNodeClass { + static final VmClass instance = loadClass("ModuleTypeNode"); + } + + private static final class DeclaredTypeNodeClass { + static final VmClass instance = loadClass("DeclaredTypeNode"); + } + + private static final class NullableTypeNodeClass { + static final VmClass instance = loadClass("NullableTypeNode"); + } + + private static final class UnionTypeNodeClass { + static final VmClass instance = loadClass("UnionTypeNode"); + } + + private static final class FunctionTypeNodeClass { + static final VmClass instance = loadClass("FunctionTypeNode"); + } + + private static final class ConstrainedTypeNodeClass { + static final VmClass instance = loadClass("ConstrainedTypeNode"); + } + + private static final class ParenthesizedTypeNodeClass { + static final VmClass instance = loadClass("ParenthesizedTypeNode"); + } + + private static final class StringConstantTypeNodeClass { + static final VmClass instance = loadClass("StringConstantTypeNode"); + } + + private static final class ThisExprNodeClass { + static final VmClass instance = loadClass("ThisExprNode"); + } + + private static final class OuterExprNodeClass { + static final VmClass instance = loadClass("OuterExprNode"); + } + + private static final class ModuleExprNodeClass { + static final VmClass instance = loadClass("ModuleExprNode"); + } + + private static final class NullLiteralExprNodeClass { + static final VmClass instance = loadClass("NullLiteralExprNode"); + } + + private static final class BoolLiteralExprNodeClass { + static final VmClass instance = loadClass("BoolLiteralExprNode"); + } + + private static final class IntLiteralExprNodeClass { + static final VmClass instance = loadClass("IntLiteralExprNode"); + } + + private static final class FloatLiteralExprNodeClass { + static final VmClass instance = loadClass("FloatLiteralExprNode"); + } + + private static final class SingleLineStringLiteralExprNodeClass { + static final VmClass instance = loadClass("SingleLineStringLiteralExprNode"); + } + + private static final class MultiLineStringLiteralExprNodeClass { + static final VmClass instance = loadClass("MultiLineStringLiteralExprNode"); + } + + private static final class UnqualifiedAccessExprNodeClass { + static final VmClass instance = loadClass("UnqualifiedAccessExprNode"); + } + + private static final class QualifiedAccessExprNodeClass { + static final VmClass instance = loadClass("QualifiedAccessExprNode"); + } + + private static final class SubscriptExprNodeClass { + static final VmClass instance = loadClass("SubscriptExprNode"); + } + + private static final class SuperAccessExprNodeClass { + static final VmClass instance = loadClass("SuperAccessExprNode"); + } + + private static final class SuperSubscriptExprNodeClass { + static final VmClass instance = loadClass("SuperSubscriptExprNode"); + } + + private static final class IfExprNodeClass { + static final VmClass instance = loadClass("IfExprNode"); + } + + private static final class LetExprNodeClass { + static final VmClass instance = loadClass("LetExprNode"); + } + + private static final class ThrowExprNodeClass { + static final VmClass instance = loadClass("ThrowExprNode"); + } + + private static final class TraceExprNodeClass { + static final VmClass instance = loadClass("TraceExprNode"); + } + + private static final class ImportExprNodeClass { + static final VmClass instance = loadClass("ImportExprNode"); + } + + private static final class ReadExprNodeClass { + static final VmClass instance = loadClass("ReadExprNode"); + } + + private static final class NewExprNodeClass { + static final VmClass instance = loadClass("NewExprNode"); + } + + private static final class AmendsExprNodeClass { + static final VmClass instance = loadClass("AmendsExprNode"); + } + + private static final class BinaryOpExprNodeClass { + static final VmClass instance = loadClass("BinaryOpExprNode"); + } + + private static final class UnaryMinusExprNodeClass { + static final VmClass instance = loadClass("UnaryMinusExprNode"); + } + + private static final class LogicalNotExprNodeClass { + static final VmClass instance = loadClass("LogicalNotExprNode"); + } + + private static final class NonNullExprNodeClass { + static final VmClass instance = loadClass("NonNullExprNode"); + } + + private static final class FunctionLiteralExprNodeClass { + static final VmClass instance = loadClass("FunctionLiteralExprNode"); + } + + private static final class ParenthesizedExprNodeClass { + static final VmClass instance = loadClass("ParenthesizedExprNode"); + } + @CompilerDirectives.TruffleBoundary private static VmClass loadClass(String className) { var theModule = getModule(); diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 87127482a..7dafe90e7 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -18,9 +18,13 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Specialization; import java.util.ArrayList; +import java.util.List; import java.util.Locale; +import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.SyntaxModule; +import org.pkl.core.runtime.VmClass; import org.pkl.core.runtime.VmExceptionBuilder; import org.pkl.core.runtime.VmList; import org.pkl.core.runtime.VmNull; @@ -58,8 +62,1411 @@ private ParserNodes() {} : VmNull.withoutDefault()) .addTypedProperty("span", nd -> nd.spanVm); + private static final VmObjectFactory identifierNodeFactory = + new VmObjectFactory(SyntaxModule::getIdentifierNodeClass) + .addProperty("node", vm -> vm) + .addStringProperty("value", ParserNodes::identifierValue); + + private static VmObjectFactory nodeOnlyFactory(Supplier classSupplier) { + return new VmObjectFactory(classSupplier).addProperty("node", vm -> vm); + } + + private static final VmObjectFactory qualifiedIdentifierNodeFactory = + new VmObjectFactory(SyntaxModule::getQualifiedIdentifierNodeClass) + .addProperty("node", vm -> vm) + .addListProperty("identifiers", ParserNodes::qualifiedIdentifierIdentifiers) + .addStringProperty("value", ParserNodes::qualifiedIdentifierValue); + private static final VmObjectFactory docCommentNodeFactory = + new VmObjectFactory(SyntaxModule::getDocCommentNodeClass) + .addProperty("node", vm -> vm) + .addListProperty("lines", ParserNodes::docCommentLines); + private static final VmObjectFactory annotationNodeFactory = + new VmObjectFactory(SyntaxModule::getAnnotationNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("type", ParserNodes::annotationType) + .addProperty("body", ParserNodes::annotationBody); + private static final VmObjectFactory typeParameterNodeFactory = + new VmObjectFactory(SyntaxModule::getTypeParameterNodeClass) + .addProperty("node", vm -> vm) + .addProperty("variance", ParserNodes::typeParameterVariance) + .addTypedProperty("identifier", ParserNodes::identifierNodeOf); + private static final VmObjectFactory objectBodyNodeFactory = + new VmObjectFactory(SyntaxModule::getObjectBodyNodeClass) + .addProperty("node", vm -> vm) + .addListProperty("parameters", ParserNodes::objectBodyParameters) + .addListProperty("members", ParserNodes::objectBodyMembers); + private static final VmObjectFactory parameterNodeFactory = + new VmObjectFactory(SyntaxModule::getParameterNodeClass) + .addProperty("node", vm -> vm) + .addBooleanProperty("isBlankIdentifier", ParserNodes::parameterIsBlankIdentifier) + .addProperty("identifier", ParserNodes::parameterIdentifier) + .addProperty("typeAnnotation", ParserNodes::parameterTypeAnnotation); + + private static final VmObjectFactory objectElementNodeFactory = + new VmObjectFactory(SyntaxModule::getObjectElementNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("expression", ParserNodes::soleExpr); + private static final VmObjectFactory objectPropertyNodeFactory = + new VmObjectFactory(SyntaxModule::getObjectPropertyNodeClass) + .addProperty("node", vm -> vm) + .addListProperty("modifiers", ParserNodes::objectPropertyModifiers) + .addTypedProperty("identifier", ParserNodes::objectPropertyIdentifier) + .addProperty("typeAnnotation", ParserNodes::objectPropertyTypeAnnotation) + .addProperty("value", ParserNodes::objectPropertyValue) + .addListProperty("objectBodies", ParserNodes::objectPropertyObjectBodies); + private static final VmObjectFactory objectMethodNodeFactory = + new VmObjectFactory(SyntaxModule::getObjectMethodNodeClass) + .addProperty("node", vm -> vm) + .addListProperty("modifiers", ParserNodes::classMethodModifiers) + .addTypedProperty("identifier", ParserNodes::classMethodIdentifier) + .addListProperty("typeParameters", ParserNodes::classMethodTypeParameters) + .addListProperty("parameters", ParserNodes::classMethodParameters) + .addProperty("returnType", ParserNodes::classMethodReturnType) + .addTypedProperty("body", ParserNodes::objectMethodBody); + private static final VmObjectFactory memberPredicateNodeFactory = + new VmObjectFactory(SyntaxModule::getMemberPredicateNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("condition", ParserNodes::memberPredicateCondition) + .addProperty("value", ParserNodes::memberPredicateValue) + .addListProperty("objectBodies", ParserNodes::memberPredicateObjectBodies); + private static final VmObjectFactory objectEntryNodeFactory = + new VmObjectFactory(SyntaxModule::getObjectEntryNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("key", ParserNodes::objectEntryKey) + .addProperty("value", ParserNodes::objectEntryValue) + .addListProperty("objectBodies", ParserNodes::objectEntryObjectBodies); + private static final VmObjectFactory objectSpreadNodeFactory = + new VmObjectFactory(SyntaxModule::getObjectSpreadNodeClass) + .addProperty("node", vm -> vm) + .addBooleanProperty("isNullable", ParserNodes::objectSpreadIsNullable) + .addTypedProperty("expression", ParserNodes::soleExpr); + private static final VmObjectFactory whenGeneratorNodeFactory = + new VmObjectFactory(SyntaxModule::getWhenGeneratorNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("condition", ParserNodes::whenCondition) + .addTypedProperty("thenBody", ParserNodes::whenThenBody) + .addProperty("elseBody", ParserNodes::whenElseBody); + private static final VmObjectFactory forGeneratorNodeFactory = + new VmObjectFactory(SyntaxModule::getForGeneratorNodeClass) + .addProperty("node", vm -> vm) + .addProperty("keyParameter", ParserNodes::forKeyParameter) + .addTypedProperty("valueParameter", ParserNodes::forValueParameter) + .addTypedProperty("iterable", ParserNodes::forIterable) + .addTypedProperty("body", ParserNodes::forBody); + + private static final VmObjectFactory unknownTypeNodeFactory = + nodeOnlyFactory(SyntaxModule::getUnknownTypeNodeClass); + private static final VmObjectFactory nothingTypeNodeFactory = + nodeOnlyFactory(SyntaxModule::getNothingTypeNodeClass); + private static final VmObjectFactory moduleTypeNodeFactory = + nodeOnlyFactory(SyntaxModule::getModuleTypeNodeClass); + private static final VmObjectFactory declaredTypeNodeFactory = + new VmObjectFactory(SyntaxModule::getDeclaredTypeNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("name", ParserNodes::declaredTypeName) + .addListProperty("typeArguments", ParserNodes::declaredTypeArguments); + private static final VmObjectFactory nullableTypeNodeFactory = + new VmObjectFactory(SyntaxModule::getNullableTypeNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("baseType", ParserNodes::nullableTypeBaseType); + private static final VmObjectFactory unionTypeNodeFactory = + new VmObjectFactory(SyntaxModule::getUnionTypeNodeClass) + .addProperty("node", vm -> vm) + .addListProperty("members", ParserNodes::unionTypeMembers); + private static final VmObjectFactory functionTypeNodeFactory = + new VmObjectFactory(SyntaxModule::getFunctionTypeNodeClass) + .addProperty("node", vm -> vm) + .addListProperty("parameterTypes", ParserNodes::functionTypeParameterTypes) + .addTypedProperty("returnType", ParserNodes::functionTypeReturnType); + private static final VmObjectFactory constrainedTypeNodeFactory = + new VmObjectFactory(SyntaxModule::getConstrainedTypeNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("baseType", ParserNodes::constrainedTypeBaseType) + .addListProperty("constraints", ParserNodes::constrainedTypeConstraints); + private static final VmObjectFactory parenthesizedTypeNodeFactory = + new VmObjectFactory(SyntaxModule::getParenthesizedTypeNodeClass) + .addProperty("node", vm -> vm) + .addProperty("type", ParserNodes::parenthesizedTypeType); + private static final VmObjectFactory stringConstantTypeNodeFactory = + new VmObjectFactory(SyntaxModule::getStringConstantTypeNodeClass) + .addProperty("node", vm -> vm) + .addStringProperty("value", ParserNodes::stringConstantTypeValue); + + private static final VmObjectFactory thisExprNodeFactory = + nodeOnlyFactory(SyntaxModule::getThisExprNodeClass); + private static final VmObjectFactory outerExprNodeFactory = + nodeOnlyFactory(SyntaxModule::getOuterExprNodeClass); + private static final VmObjectFactory moduleExprNodeFactory = + nodeOnlyFactory(SyntaxModule::getModuleExprNodeClass); + private static final VmObjectFactory nullLiteralExprNodeFactory = + nodeOnlyFactory(SyntaxModule::getNullLiteralExprNodeClass); + private static final VmObjectFactory boolLiteralExprNodeFactory = + new VmObjectFactory(SyntaxModule::getBoolLiteralExprNodeClass) + .addProperty("node", vm -> vm) + .addBooleanProperty("value", ParserNodes::boolLiteralValue); + private static final VmObjectFactory intLiteralExprNodeFactory = + new VmObjectFactory(SyntaxModule::getIntLiteralExprNodeClass) + .addProperty("node", vm -> vm) + .addProperty("value", ParserNodes::literalText); + private static final VmObjectFactory floatLiteralExprNodeFactory = + new VmObjectFactory(SyntaxModule::getFloatLiteralExprNodeClass) + .addProperty("node", vm -> vm) + .addProperty("value", ParserNodes::literalText); + private static final VmObjectFactory singleLineStringLiteralExprNodeFactory = + new VmObjectFactory(SyntaxModule::getSingleLineStringLiteralExprNodeClass) + .addProperty("node", vm -> vm) + .addListProperty("parts", ParserNodes::buildStringParts); + private static final VmObjectFactory multiLineStringLiteralExprNodeFactory = + new VmObjectFactory(SyntaxModule::getMultiLineStringLiteralExprNodeClass) + .addProperty("node", vm -> vm) + .addListProperty("parts", ParserNodes::buildStringParts); + private static final VmObjectFactory unqualifiedAccessExprNodeFactory = + new VmObjectFactory(SyntaxModule::getUnqualifiedAccessExprNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("identifier", ParserNodes::identifierNodeOf) + .addProperty("arguments", ParserNodes::unqualifiedAccessArguments); + private static final VmObjectFactory qualifiedAccessExprNodeFactory = + new VmObjectFactory(SyntaxModule::getQualifiedAccessExprNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("receiver", ParserNodes::qualifiedAccessReceiver) + .addBooleanProperty("isNullSafe", ParserNodes::qualifiedAccessIsNullSafe) + .addTypedProperty("identifier", ParserNodes::qualifiedAccessIdentifier) + .addProperty("arguments", ParserNodes::qualifiedAccessArguments); + private static final VmObjectFactory subscriptExprNodeFactory = + new VmObjectFactory(SyntaxModule::getSubscriptExprNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("receiver", ParserNodes::subscriptReceiver) + .addTypedProperty("index", ParserNodes::subscriptIndex); + private static final VmObjectFactory superAccessExprNodeFactory = + nodeOnlyFactory(SyntaxModule::getSuperAccessExprNodeClass); + private static final VmObjectFactory superSubscriptExprNodeFactory = + nodeOnlyFactory(SyntaxModule::getSuperSubscriptExprNodeClass); + private static final VmObjectFactory ifExprNodeFactory = + new VmObjectFactory(SyntaxModule::getIfExprNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("condition", ParserNodes::ifCondition) + .addTypedProperty("thenExpr", ParserNodes::ifThenExpr) + .addTypedProperty("elseExpr", ParserNodes::ifElseExpr); + private static final VmObjectFactory letExprNodeFactory = + new VmObjectFactory(SyntaxModule::getLetExprNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("parameter", ParserNodes::letParameter) + .addTypedProperty("bindingValue", ParserNodes::letBindingValue) + .addTypedProperty("body", ParserNodes::letBody); + private static final VmObjectFactory throwExprNodeFactory = + new VmObjectFactory(SyntaxModule::getThrowExprNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("expression", ParserNodes::soleExpr); + private static final VmObjectFactory traceExprNodeFactory = + new VmObjectFactory(SyntaxModule::getTraceExprNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("expression", ParserNodes::soleExpr); + private static final VmObjectFactory importExprNodeFactory = + new VmObjectFactory(SyntaxModule::getImportExprNodeClass) + .addProperty("node", vm -> vm) + .addBooleanProperty("isGlob", ParserNodes::importIsGlob) + .addStringProperty("uri", ParserNodes::importUri); + private static final VmObjectFactory readExprNodeFactory = + new VmObjectFactory(SyntaxModule::getReadExprNodeClass) + .addProperty("node", vm -> vm) + .addStringProperty("keyword", ParserNodes::readKeyword) + .addTypedProperty("expression", ParserNodes::soleExpr); + private static final VmObjectFactory newExprNodeFactory = + new VmObjectFactory(SyntaxModule::getNewExprNodeClass) + .addProperty("node", vm -> vm) + .addProperty("type", ParserNodes::newExprType) + .addTypedProperty("body", ParserNodes::newExprBody); + private static final VmObjectFactory amendsExprNodeFactory = + new VmObjectFactory(SyntaxModule::getAmendsExprNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("parentExpr", ParserNodes::amendsParentExpr) + .addTypedProperty("body", ParserNodes::amendsBody); + private static final VmObjectFactory binaryOpExprNodeFactory = + new VmObjectFactory(SyntaxModule::getBinaryOpExprNodeClass) + .addProperty("node", vm -> vm) + .addStringProperty("operator", ParserNodes::binaryOpOperator) + .addTypedProperty("left", ParserNodes::binaryOpLeft) + .addProperty("right", ParserNodes::binaryOpRight) + .addProperty("rightType", ParserNodes::binaryOpRightType); + private static final VmObjectFactory unaryMinusExprNodeFactory = + new VmObjectFactory(SyntaxModule::getUnaryMinusExprNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("operand", ParserNodes::soleExpr); + private static final VmObjectFactory logicalNotExprNodeFactory = + new VmObjectFactory(SyntaxModule::getLogicalNotExprNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("operand", ParserNodes::soleExpr); + private static final VmObjectFactory nonNullExprNodeFactory = + new VmObjectFactory(SyntaxModule::getNonNullExprNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("operand", ParserNodes::soleExpr); + private static final VmObjectFactory functionLiteralExprNodeFactory = + new VmObjectFactory(SyntaxModule::getFunctionLiteralExprNodeClass) + .addProperty("node", vm -> vm) + .addListProperty("parameters", ParserNodes::functionLiteralParameters) + .addTypedProperty("body", ParserNodes::functionLiteralBody); + private static final VmObjectFactory parenthesizedExprNodeFactory = + new VmObjectFactory(SyntaxModule::getParenthesizedExprNodeClass) + .addProperty("node", vm -> vm) + .addProperty("expression", ParserNodes::parenthesizedExpression); + + // String-part factories, produced by `buildStringParts`. `StringPartNode` is not a `SyntaxNode`, + // but it also carries a hidden `node`, so the same `node`-property shape applies. + private static final VmObjectFactory stringCharsNodeFactory = + new VmObjectFactory(SyntaxModule::getStringCharsNodeClass) + .addProperty("node", vm -> vm) + .addStringProperty("value", ParserNodes::literalText); + private static final VmObjectFactory stringEscapeNodeFactory = + new VmObjectFactory(SyntaxModule::getStringEscapeNodeClass) + .addProperty("node", vm -> vm) + .addStringProperty("value", ParserNodes::literalText); + private static final VmObjectFactory stringNewlineNodeFactory = + new VmObjectFactory(SyntaxModule::getStringNewlineNodeClass) + .addProperty("node", vm -> vm); + private static final VmObjectFactory stringInterpolationNodeFactory = + new VmObjectFactory(SyntaxModule::getStringInterpolationNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("expression", ParserNodes::wrapExpr); + + private static final VmObjectFactory importNodeFactory = + new VmObjectFactory(SyntaxModule::getImportNodeClass) + .addProperty("node", vm -> vm) + .addBooleanProperty("isGlob", ParserNodes::importIsGlob) + .addStringProperty("uri", ParserNodes::importUri) + .addProperty("alias", ParserNodes::importAlias); + + private static final VmObjectFactory moduleDeclarationNodeFactory = + new VmObjectFactory(SyntaxModule::getModuleDeclarationNodeClass) + .addProperty("node", vm -> vm) + .addProperty("docComment", ParserNodes::docCommentOf) + .addListProperty("annotations", ParserNodes::annotationsOf) + .addListProperty("modifiers", ParserNodes::moduleDeclModifiers) + .addProperty("name", ParserNodes::moduleDeclName) + .addProperty("amendsUri", ParserNodes::moduleDeclAmendsUri) + .addProperty("extendsUri", ParserNodes::moduleDeclExtendsUri); + + private static final VmObjectFactory classNodeFactory = + new VmObjectFactory(SyntaxModule::getClassNodeClass) + .addProperty("node", vm -> vm) + .addProperty("docComment", ParserNodes::docCommentOf) + .addListProperty("annotations", ParserNodes::annotationsOf) + .addListProperty("modifiers", ParserNodes::classModifiers) + .addTypedProperty("identifier", ParserNodes::classIdentifier) + .addListProperty("typeParameters", ParserNodes::classTypeParameters) + .addProperty("extendsType", ParserNodes::classExtendsType) + .addProperty("body", ParserNodes::classBody); + + private static final VmObjectFactory typeAliasNodeFactory = + new VmObjectFactory(SyntaxModule::getTypeAliasNodeClass) + .addProperty("node", vm -> vm) + .addProperty("docComment", ParserNodes::docCommentOf) + .addListProperty("annotations", ParserNodes::annotationsOf) + .addListProperty("modifiers", ParserNodes::typeAliasModifiers) + .addTypedProperty("identifier", ParserNodes::typeAliasIdentifier) + .addListProperty("typeParameters", ParserNodes::typeAliasTypeParameters) + .addTypedProperty("type", ParserNodes::typeAliasType); + + private static final VmObjectFactory classPropertyNodeFactory = + new VmObjectFactory(SyntaxModule::getClassPropertyNodeClass) + .addProperty("node", vm -> vm) + .addProperty("docComment", ParserNodes::docCommentOf) + .addListProperty("annotations", ParserNodes::annotationsOf) + .addListProperty("modifiers", ParserNodes::classPropertyModifiers) + .addTypedProperty("identifier", ParserNodes::classPropertyIdentifier) + .addProperty("typeAnnotation", ParserNodes::classPropertyTypeAnnotation) + .addProperty("value", ParserNodes::classPropertyValue) + .addListProperty("objectBodies", ParserNodes::classPropertyObjectBodies); + + private static final VmObjectFactory classMethodNodeFactory = + new VmObjectFactory(SyntaxModule::getClassMethodNodeClass) + .addProperty("node", vm -> vm) + .addProperty("docComment", ParserNodes::docCommentOf) + .addListProperty("annotations", ParserNodes::annotationsOf) + .addListProperty("modifiers", ParserNodes::classMethodModifiers) + .addTypedProperty("identifier", ParserNodes::classMethodIdentifier) + .addListProperty("typeParameters", ParserNodes::classMethodTypeParameters) + .addListProperty("parameters", ParserNodes::classMethodParameters) + .addProperty("returnType", ParserNodes::classMethodReturnType) + .addProperty("body", ParserNodes::classMethodBody); + + private static final VmObjectFactory classBodyNodeFactory = + new VmObjectFactory(SyntaxModule::getClassBodyNodeClass) + .addProperty("node", vm -> vm) + .addListProperty("properties", ParserNodes::classBodyProperties) + .addListProperty("methods", ParserNodes::classBodyMethods); + private static final VmObjectFactory moduleNodeFactory = - new VmObjectFactory(SyntaxModule::getModuleNodeClass).addProperty("node", vm -> vm); + new VmObjectFactory(SyntaxModule::getModuleNodeClass) + .addProperty("node", vm -> vm) + .addProperty("declaration", ParserNodes::moduleDeclaration) + .addListProperty("imports", ParserNodes::moduleImports) + .addListProperty("classes", ParserNodes::moduleClasses) + .addListProperty("typeAliases", ParserNodes::moduleTypeAliases) + .addListProperty("properties", ParserNodes::moduleProperties) + .addListProperty("methods", ParserNodes::moduleMethods); + + private static Object moduleDeclaration(VmTyped moduleVm) { + var declVm = findChildVm(moduleVm, NodeType.MODULE_DECLARATION); + return declVm == null ? VmNull.withoutDefault() : moduleDeclarationNodeFactory.create(declVm); + } + + private static Object docCommentOf(VmTyped ownerVm) { + var dc = findChildVm(ownerVm, NodeType.DOC_COMMENT); + return dc == null ? VmNull.withoutDefault() : docCommentNodeFactory.create(dc); + } + + private static VmList annotationsOf(VmTyped ownerVm) { + return wrapAll(findChildrenVm(ownerVm, NodeType.ANNOTATION), annotationNodeFactory); + } + + private static VmTyped annotationType(VmTyped annotationVm) { + var type = findTypeChildVm(annotationVm); + if (type == null) { + throw new VmExceptionBuilder().bug("An annotation always has a type.").build(); + } + return wrapType(type); + } + + private static Object annotationBody(VmTyped annotationVm) { + var body = findChildVm(annotationVm, NodeType.OBJECT_BODY); + return body == null ? VmNull.withoutDefault() : objectBodyNodeFactory.create(body); + } + + private static VmList docCommentLines(VmTyped docCommentVm) { + var lineVms = findChildrenVm(docCommentVm, NodeType.DOC_COMMENT_LINE); + var result = new Object[lineVms.size()]; + for (var i = 0; i < lineVms.size(); i++) { + var data = (NodeData) lineVms.get(i).getExtraStorage(); + var text = data.node.text(data.source); + if (text.startsWith("/// ")) { + result[i] = text.substring(4); + } else if (text.startsWith("///")) { + result[i] = text.substring(3); + } else { + result[i] = text; + } + } + return VmList.create(result); + } + + private static VmTyped declaredTypeName(VmTyped typeVm) { + var name = findChildVm(typeVm, NodeType.QUALIFIED_IDENTIFIER); + if (name == null) { + throw new VmExceptionBuilder() + .bug("A declared type always has a qualified identifier.") + .build(); + } + return qualifiedIdentifierNodeFactory.create(name); + } + + private static VmList declaredTypeArguments(VmTyped typeVm) { + var list = findChildVm(typeVm, NodeType.TYPE_ARGUMENT_LIST); + if (list == null) { + return VmList.EMPTY; + } + var elems = findChildVm(list, NodeType.TYPE_ARGUMENT_LIST_ELEMENTS); + if (elems == null) { + return VmList.EMPTY; + } + return wrapTypes(findTypeChildrenVm(elems)); + } + + private static VmTyped nullableTypeBaseType(VmTyped typeVm) { + return wrapType(requireTypeChild(typeVm)); + } + + private static VmList unionTypeMembers(VmTyped typeVm) { + return wrapTypes(findTypeChildrenVm(typeVm)); + } + + private static VmList functionTypeParameterTypes(VmTyped typeVm) { + var params = findChildVm(typeVm, NodeType.FUNCTION_TYPE_PARAMETERS); + if (params == null) { + return VmList.EMPTY; + } + var elems = findChildVm(params, NodeType.PARENTHESIZED_TYPE_ELEMENTS); + if (elems == null) { + return VmList.EMPTY; + } + return wrapTypes(findTypeChildrenVm(elems)); + } + + private static VmTyped functionTypeReturnType(VmTyped typeVm) { + var types = findTypeChildrenVm(typeVm); + if (types.isEmpty()) { + throw new VmExceptionBuilder().bug("A function type always has a return type.").build(); + } + return wrapType(types.get(types.size() - 1)); + } + + private static VmTyped constrainedTypeBaseType(VmTyped typeVm) { + return wrapType(requireTypeChild(typeVm)); + } + + private static VmList constrainedTypeConstraints(VmTyped typeVm) { + var constraint = findChildVm(typeVm, NodeType.CONSTRAINED_TYPE_CONSTRAINT); + if (constraint == null) { + return VmList.EMPTY; + } + var elems = findChildVm(constraint, NodeType.CONSTRAINED_TYPE_ELEMENTS); + if (elems == null) { + return VmList.EMPTY; + } + return wrapExprs(findExprChildrenVm(elems)); + } + + private static Object parenthesizedTypeType(VmTyped typeVm) { + var elems = findChildVm(typeVm, NodeType.PARENTHESIZED_TYPE_ELEMENTS); + if (elems == null) { + return VmNull.withoutDefault(); + } + var type = findTypeChildVm(elems); + return type == null ? VmNull.withoutDefault() : wrapType(type); + } + + private static String stringConstantTypeValue(VmTyped typeVm) { + var data = (NodeData) typeVm.getExtraStorage(); + return extractStringChars(data.node, data.source); + } + + private static VmTyped requireTypeChild(VmTyped genericVm) { + var type = findTypeChildVm(genericVm); + if (type == null) { + throw new VmExceptionBuilder().bug("Expected a type-node child.").build(); + } + return type; + } + + private static String literalText(VmTyped exprVm) { + var text = nodeText((NodeData) exprVm.getExtraStorage()); + return text == null ? "" : text; + } + + private static boolean boolLiteralValue(VmTyped exprVm) { + return "true".equals(nodeText((NodeData) exprVm.getExtraStorage())); + } + + private static Object unqualifiedAccessArguments(VmTyped exprVm) { + return argumentsOrNull(exprVm); + } + + private static VmTyped qualifiedAccessReceiver(VmTyped exprVm) { + return wrapExpr(requireExprChild(exprVm)); + } + + private static boolean qualifiedAccessIsNullSafe(VmTyped exprVm) { + var op = findChildVm(exprVm, NodeType.OPERATOR); + if (op == null) { + return false; + } + var data = (NodeData) op.getExtraStorage(); + return "?.".equals(data.node.text(data.source)); + } + + private static VmTyped qualifiedAccessIdentifier(VmTyped exprVm) { + return identifierNodeOf(lastChildVm(exprVm, NodeType.UNQUALIFIED_ACCESS_EXPR)); + } + + private static Object qualifiedAccessArguments(VmTyped exprVm) { + var member = lastChildVm(exprVm, NodeType.UNQUALIFIED_ACCESS_EXPR); + return member == null ? VmNull.withoutDefault() : argumentsOrNull(member); + } + + private static VmTyped subscriptReceiver(VmTyped exprVm) { + return wrapExpr(findExprChildrenVm(exprVm).get(0)); + } + + private static VmTyped subscriptIndex(VmTyped exprVm) { + return wrapExpr(findExprChildrenVm(exprVm).get(1)); + } + + private static VmTyped ifCondition(VmTyped exprVm) { + var header = requireChild(exprVm, NodeType.IF_HEADER); + var condition = requireChild(header, NodeType.IF_CONDITION); + var conditionExpr = requireChild(condition, NodeType.IF_CONDITION_EXPR); + return wrapExpr(requireExprChild(conditionExpr)); + } + + private static VmTyped ifThenExpr(VmTyped exprVm) { + return wrapExpr(requireExprChild(requireChild(exprVm, NodeType.IF_THEN_EXPR))); + } + + private static VmTyped ifElseExpr(VmTyped exprVm) { + return wrapExpr(requireExprChild(requireChild(exprVm, NodeType.IF_ELSE_EXPR))); + } + + private static VmTyped letParamNode(VmTyped exprVm) { + return requireChild( + requireChild(exprVm, NodeType.LET_PARAMETER_DEFINITION), NodeType.LET_PARAMETER); + } + + private static VmTyped letParameter(VmTyped exprVm) { + return parameterNodeFactory.create(requireChild(letParamNode(exprVm), NodeType.PARAMETER)); + } + + private static VmTyped letBindingValue(VmTyped exprVm) { + return wrapExpr(requireExprChild(letParamNode(exprVm))); + } + + private static VmTyped letBody(VmTyped exprVm) { + return wrapExpr(requireExprChild(exprVm)); + } + + // The sole expression child of `exprVm` (throw/trace/unary-minus/logical-not/non-null operand). + private static VmTyped soleExpr(VmTyped exprVm) { + return wrapExpr(requireExprChild(exprVm)); + } + + private static String readKeyword(VmTyped exprVm) { + var data = (NodeData) exprVm.getExtraStorage(); + for (var child : data.node.children) { + if (child.type == NodeType.TERMINAL) { + return child.text(data.source); + } + } + return "read"; + } + + private static Object newExprType(VmTyped exprVm) { + var header = requireChild(exprVm, NodeType.NEW_HEADER); + var type = findTypeChildVm(header); + return type == null ? VmNull.withoutDefault() : wrapType(type); + } + + private static VmTyped newExprBody(VmTyped exprVm) { + return objectBodyNodeFactory.create(requireChild(exprVm, NodeType.OBJECT_BODY)); + } + + private static VmTyped amendsParentExpr(VmTyped exprVm) { + return wrapExpr(requireExprChild(exprVm)); + } + + private static VmTyped amendsBody(VmTyped exprVm) { + return objectBodyNodeFactory.create(requireChild(exprVm, NodeType.OBJECT_BODY)); + } + + private static String binaryOpOperator(VmTyped exprVm) { + var op = findChildVm(exprVm, NodeType.OPERATOR); + if (op == null) { + return ""; + } + var data = (NodeData) op.getExtraStorage(); + return data.node.text(data.source); + } + + private static VmTyped binaryOpLeft(VmTyped exprVm) { + return wrapExpr(findExprChildrenVm(exprVm).get(0)); + } + + private static Object binaryOpRight(VmTyped exprVm) { + var exprs = findExprChildrenVm(exprVm); + return exprs.size() < 2 ? VmNull.withoutDefault() : wrapExpr(exprs.get(1)); + } + + private static Object binaryOpRightType(VmTyped exprVm) { + var type = findTypeChildVm(exprVm); + return type == null ? VmNull.withoutDefault() : wrapType(type); + } + + private static VmList functionLiteralParameters(VmTyped exprVm) { + return parametersOf(exprVm); + } + + private static VmTyped functionLiteralBody(VmTyped exprVm) { + return wrapExpr(requireExprChild(requireChild(exprVm, NodeType.FUNCTION_LITERAL_BODY))); + } + + private static Object parenthesizedExpression(VmTyped exprVm) { + var elems = findChildVm(exprVm, NodeType.PARENTHESIZED_EXPR_ELEMENTS); + if (elems == null) { + return VmNull.withoutDefault(); + } + var expr = findExprChildVm(elems); + return expr == null ? VmNull.withoutDefault() : wrapExpr(expr); + } + + private static Object argumentsOrNull(VmTyped ownerVm) { + var argList = findChildVm(ownerVm, NodeType.ARGUMENT_LIST); + return argList == null ? VmNull.withoutDefault() : argumentsOf(argList); + } + + private static VmList argumentsOf(VmTyped argListVm) { + var elems = findChildVm(argListVm, NodeType.ARGUMENT_LIST_ELEMENTS); + if (elems == null) { + return VmList.EMPTY; + } + return wrapExprs(findExprChildrenVm(elems)); + } + + private static VmTyped requireChild(VmTyped genericVm, NodeType type) { + var child = findChildVm(genericVm, type); + if (child == null) { + throw new VmExceptionBuilder().bug("Expected a `" + type + "` child.").build(); + } + return child; + } + + private static VmTyped requireExprChild(VmTyped genericVm) { + var expr = findExprChildVm(genericVm); + if (expr == null) { + throw new VmExceptionBuilder().bug("Expected an expression child.").build(); + } + return expr; + } + + private static @Nullable VmTyped lastChildVm(VmTyped genericVm, NodeType type) { + var data = (NodeData) genericVm.getExtraStorage(); + var children = data.node.children; + VmTyped result = null; + for (var i = 0; i < children.size(); i++) { + if (children.get(i).type == type) { + result = (VmTyped) data.childrenVm.get(i); + } + } + return result; + } + + private static VmList objectBodyParameters(VmTyped bodyVm) { + var paramList = findChildVm(bodyVm, NodeType.OBJECT_PARAMETER_LIST); + if (paramList == null) { + return VmList.EMPTY; + } + return wrapAll(findChildrenVm(paramList, NodeType.PARAMETER), parameterNodeFactory); + } + + private static VmList objectBodyMembers(VmTyped bodyVm) { + var memberList = findChildVm(bodyVm, NodeType.OBJECT_MEMBER_LIST); + if (memberList == null) { + return VmList.EMPTY; + } + var data = (NodeData) memberList.getExtraStorage(); + var children = data.node.children; + var result = new ArrayList<>(); + for (var i = 0; i < children.size(); i++) { + if (isObjectMemberType(children.get(i).type)) { + result.add(wrapObjectMember((VmTyped) data.childrenVm.get(i))); + } + } + return VmList.create(result.toArray()); + } + + private static @Nullable VmTyped objectPropertyHeaderBegin(VmTyped propertyVm) { + var header = findChildVm(propertyVm, NodeType.OBJECT_PROPERTY_HEADER); + return header == null ? null : findChildVm(header, NodeType.OBJECT_PROPERTY_HEADER_BEGIN); + } + + private static VmList objectPropertyModifiers(VmTyped propertyVm) { + var headerBegin = objectPropertyHeaderBegin(propertyVm); + return headerBegin == null ? VmList.EMPTY : modifiersOf(headerBegin); + } + + private static VmTyped objectPropertyIdentifier(VmTyped propertyVm) { + return identifierNodeOf(objectPropertyHeaderBegin(propertyVm)); + } + + private static Object objectPropertyTypeAnnotation(VmTyped propertyVm) { + return typeAnnotationOf(findChildVm(propertyVm, NodeType.OBJECT_PROPERTY_HEADER)); + } + + private static Object objectPropertyValue(VmTyped propertyVm) { + return exprInChild(propertyVm, NodeType.OBJECT_PROPERTY_BODY); + } + + private static VmList objectPropertyObjectBodies(VmTyped propertyVm) { + return objectBodiesOf(propertyVm); + } + + private static VmTyped objectMethodBody(VmTyped methodVm) { + return wrapExpr(requireExprChild(requireChild(methodVm, NodeType.CLASS_METHOD_BODY))); + } + + private static VmTyped memberPredicateCondition(VmTyped predicateVm) { + return wrapExpr(findExprChildrenVm(predicateVm).get(0)); + } + + private static Object memberPredicateValue(VmTyped predicateVm) { + var exprs = findExprChildrenVm(predicateVm); + return exprs.size() < 2 ? VmNull.withoutDefault() : wrapExpr(exprs.get(1)); + } + + private static VmList memberPredicateObjectBodies(VmTyped predicateVm) { + return objectBodiesOf(predicateVm); + } + + private static VmTyped objectEntryKey(VmTyped entryVm) { + var header = requireChild(entryVm, NodeType.OBJECT_ENTRY_HEADER); + return wrapExpr(requireExprChild(header)); + } + + private static Object objectEntryValue(VmTyped entryVm) { + var expr = findExprChildVm(entryVm); + return expr == null ? VmNull.withoutDefault() : wrapExpr(expr); + } + + private static VmList objectEntryObjectBodies(VmTyped entryVm) { + return objectBodiesOf(entryVm); + } + + private static boolean objectSpreadIsNullable(VmTyped spreadVm) { + var data = (NodeData) spreadVm.getExtraStorage(); + for (var child : data.node.children) { + if (child.type == NodeType.TERMINAL) { + return "...?".equals(child.text(data.source)); + } + } + return false; + } + + private static VmTyped whenCondition(VmTyped whenVm) { + var header = requireChild(whenVm, NodeType.WHEN_GENERATOR_HEADER); + return wrapExpr(requireExprChild(header)); + } + + private static VmTyped whenThenBody(VmTyped whenVm) { + return objectBodyNodeFactory.create(findChildrenVm(whenVm, NodeType.OBJECT_BODY).get(0)); + } + + private static Object whenElseBody(VmTyped whenVm) { + var bodies = findChildrenVm(whenVm, NodeType.OBJECT_BODY); + return bodies.size() < 2 + ? VmNull.withoutDefault() + : objectBodyNodeFactory.create(bodies.get(1)); + } + + private static List forGeneratorParams(VmTyped forVm) { + var header = findChildVm(forVm, NodeType.FOR_GENERATOR_HEADER); + var def = header == null ? null : findChildVm(header, NodeType.FOR_GENERATOR_HEADER_DEFINITION); + var defHeader = + def == null ? null : findChildVm(def, NodeType.FOR_GENERATOR_HEADER_DEFINITION_HEADER); + return defHeader == null ? new ArrayList<>() : findChildrenVm(defHeader, NodeType.PARAMETER); + } + + private static Object forKeyParameter(VmTyped forVm) { + var params = forGeneratorParams(forVm); + return params.size() < 2 ? VmNull.withoutDefault() : parameterNodeFactory.create(params.get(0)); + } + + private static VmTyped forValueParameter(VmTyped forVm) { + var params = forGeneratorParams(forVm); + return parameterNodeFactory.create(params.get(params.size() - 1)); + } + + private static VmTyped forIterable(VmTyped forVm) { + var header = requireChild(forVm, NodeType.FOR_GENERATOR_HEADER); + var def = requireChild(header, NodeType.FOR_GENERATOR_HEADER_DEFINITION); + return wrapExpr(requireExprChild(def)); + } + + private static VmTyped forBody(VmTyped forVm) { + return objectBodyNodeFactory.create(requireChild(forVm, NodeType.OBJECT_BODY)); + } + + private static VmList qualifiedIdentifierIdentifiers(VmTyped qualifiedVm) { + return wrapAll(findChildrenVm(qualifiedVm, NodeType.IDENTIFIER), identifierNodeFactory); + } + + private static String qualifiedIdentifierValue(VmTyped qualifiedVm) { + var idVms = findChildrenVm(qualifiedVm, NodeType.IDENTIFIER); + var builder = new StringBuilder(); + for (var i = 0; i < idVms.size(); i++) { + if (i > 0) { + builder.append('.'); + } + var text = nodeText((NodeData) idVms.get(i).getExtraStorage()); + builder.append(text == null ? "" : text); + } + return builder.toString(); + } + + private static Object typeParameterVariance(VmTyped typeParameterVm) { + var data = (NodeData) typeParameterVm.getExtraStorage(); + for (var child : data.node.children) { + if (child.type == NodeType.TERMINAL) { + var text = child.text(data.source); + if ("in".equals(text) || "out".equals(text)) { + return text; + } + } + } + return VmNull.withoutDefault(); + } + + private static boolean parameterIsBlankIdentifier(VmTyped parameterVm) { + return findChildVm(parameterVm, NodeType.IDENTIFIER) == null; + } + + private static Object parameterIdentifier(VmTyped parameterVm) { + var id = findChildVm(parameterVm, NodeType.IDENTIFIER); + return id == null ? VmNull.withoutDefault() : identifierNodeFactory.create(id); + } + + private static Object parameterTypeAnnotation(VmTyped parameterVm) { + return typeAnnotationOf(parameterVm); + } + + private static VmList buildStringParts(VmTyped stringVm) { + var data = (NodeData) stringVm.getExtraStorage(); + var children = data.node.children; + var childrenVm = data.childrenVm; + var parts = new ArrayList<>(); + // skip the opening and closing quote terminals + var end = children.size() - 1; + var i = 1; + while (i < end) { + var child = children.get(i); + switch (child.type) { + case STRING_CHARS -> { + parts.add(stringCharsNodeFactory.create((VmTyped) childrenVm.get(i))); + i++; + } + case STRING_ESCAPE -> { + parts.add(stringEscapeNodeFactory.create((VmTyped) childrenVm.get(i))); + i++; + } + case STRING_NEWLINE -> { + parts.add(stringNewlineNodeFactory.create((VmTyped) childrenVm.get(i))); + i++; + } + case TERMINAL -> { + if (isInterpolationStart(child, data.source)) { + var exprIdx = nextNonAffix(children, i + 1, end); + if (exprIdx >= end) { + i = end; + } else { + parts.add(stringInterpolationNodeFactory.create((VmTyped) childrenVm.get(exprIdx))); + i = nextNonAffix(children, exprIdx + 1, end) + 1; + } + } else { + i++; + } + } + default -> i++; // affixes and stray terminals + } + } + return VmList.create(parts.toArray()); + } + + private static boolean isInterpolationStart(Node terminal, char[] source) { + var text = terminal.text(source); + return text.endsWith("(") && (text.startsWith("\\") || text.startsWith("#")); + } + + // The next index in `[start, end)` whose node is not an affix (comment/semicolon), else `end`. + private static int nextNonAffix(List children, int start, int end) { + var i = start; + while (i < end) { + var type = children.get(i).type; + if (type == NodeType.LINE_COMMENT + || type == NodeType.BLOCK_COMMENT + || type == NodeType.SEMICOLON) { + i++; + } else { + return i; + } + } + return i; + } + + private static VmList moduleDeclModifiers(VmTyped declVm) { + var moduleDefinition = findChildVm(declVm, NodeType.MODULE_DEFINITION); + return moduleDefinition == null ? VmList.EMPTY : modifiersOf(moduleDefinition); + } + + private static Object moduleDeclName(VmTyped declVm) { + var moduleDefinition = findChildVm(declVm, NodeType.MODULE_DEFINITION); + if (moduleDefinition == null) { + return VmNull.withoutDefault(); + } + var name = findChildVm(moduleDefinition, NodeType.QUALIFIED_IDENTIFIER); + return name == null ? VmNull.withoutDefault() : qualifiedIdentifierNodeFactory.create(name); + } + + private static Object moduleDeclAmendsUri(VmTyped declVm) { + var clause = findChildVm(declVm, NodeType.AMENDS_CLAUSE); + return clause == null ? VmNull.withoutDefault() : stringCharsOf(clause); + } + + private static Object moduleDeclExtendsUri(VmTyped declVm) { + var clause = findChildVm(declVm, NodeType.EXTENDS_CLAUSE); + return clause == null ? VmNull.withoutDefault() : stringCharsOf(clause); + } + + private static VmList moduleClasses(VmTyped moduleVm) { + return wrapAll(findChildrenVm(moduleVm, NodeType.CLASS), classNodeFactory); + } + + private static VmList classModifiers(VmTyped classVm) { + var header = findChildVm(classVm, NodeType.CLASS_HEADER); + return header == null ? VmList.EMPTY : modifiersOf(header); + } + + private static VmTyped classIdentifier(VmTyped classVm) { + return identifierNodeOf(findChildVm(classVm, NodeType.CLASS_HEADER)); + } + + private static VmList classTypeParameters(VmTyped classVm) { + return typeParametersOf(findChildVm(classVm, NodeType.CLASS_HEADER)); + } + + private static Object classExtendsType(VmTyped classVm) { + var header = findChildVm(classVm, NodeType.CLASS_HEADER); + if (header == null) { + return VmNull.withoutDefault(); + } + var ext = findChildVm(header, NodeType.CLASS_HEADER_EXTENDS); + if (ext == null) { + return VmNull.withoutDefault(); + } + var type = findTypeChildVm(ext); + return type == null ? VmNull.withoutDefault() : wrapType(type); + } + + private static Object classBody(VmTyped classVm) { + var body = findChildVm(classVm, NodeType.CLASS_BODY); + return body == null ? VmNull.withoutDefault() : classBodyNodeFactory.create(body); + } + + private static VmList moduleTypeAliases(VmTyped moduleVm) { + return wrapAll(findChildrenVm(moduleVm, NodeType.TYPEALIAS), typeAliasNodeFactory); + } + + private static VmList typeAliasModifiers(VmTyped typeAliasVm) { + var header = findChildVm(typeAliasVm, NodeType.TYPEALIAS_HEADER); + return header == null ? VmList.EMPTY : modifiersOf(header); + } + + private static VmTyped typeAliasIdentifier(VmTyped typeAliasVm) { + return identifierNodeOf(findChildVm(typeAliasVm, NodeType.TYPEALIAS_HEADER)); + } + + private static VmList typeAliasTypeParameters(VmTyped typeAliasVm) { + return typeParametersOf(findChildVm(typeAliasVm, NodeType.TYPEALIAS_HEADER)); + } + + private static VmTyped typeAliasType(VmTyped typeAliasVm) { + var body = findChildVm(typeAliasVm, NodeType.TYPEALIAS_BODY); + var type = body == null ? null : findTypeChildVm(body); + if (type == null) { + throw new VmExceptionBuilder().bug("A parsed `typealias` always has a body type.").build(); + } + return wrapType(type); + } + + private static VmList moduleProperties(VmTyped moduleVm) { + return wrapAll(findChildrenVm(moduleVm, NodeType.CLASS_PROPERTY), classPropertyNodeFactory); + } + + private static VmList moduleMethods(VmTyped moduleVm) { + return wrapAll(findChildrenVm(moduleVm, NodeType.CLASS_METHOD), classMethodNodeFactory); + } + + private static @Nullable VmTyped classPropertyHeaderBegin(VmTyped propertyVm) { + var propHeader = findChildVm(propertyVm, NodeType.CLASS_PROPERTY_HEADER); + return propHeader == null + ? null + : findChildVm(propHeader, NodeType.CLASS_PROPERTY_HEADER_BEGIN); + } + + private static VmList classPropertyModifiers(VmTyped propertyVm) { + var headerBegin = classPropertyHeaderBegin(propertyVm); + return headerBegin == null ? VmList.EMPTY : modifiersOf(headerBegin); + } + + private static VmTyped classPropertyIdentifier(VmTyped propertyVm) { + return identifierNodeOf(classPropertyHeaderBegin(propertyVm)); + } + + private static Object classPropertyTypeAnnotation(VmTyped propertyVm) { + return typeAnnotationOf(findChildVm(propertyVm, NodeType.CLASS_PROPERTY_HEADER)); + } + + private static Object classPropertyValue(VmTyped propertyVm) { + return exprInChild(propertyVm, NodeType.CLASS_PROPERTY_BODY); + } + + private static VmList classPropertyObjectBodies(VmTyped propertyVm) { + return objectBodiesOf(propertyVm); + } + + private static VmList classMethodModifiers(VmTyped methodVm) { + var header = findChildVm(methodVm, NodeType.CLASS_METHOD_HEADER); + return header == null ? VmList.EMPTY : modifiersOf(header); + } + + private static VmTyped classMethodIdentifier(VmTyped methodVm) { + return identifierNodeOf(findChildVm(methodVm, NodeType.CLASS_METHOD_HEADER)); + } + + private static VmList classMethodTypeParameters(VmTyped methodVm) { + return typeParametersOf(methodVm); + } + + private static VmList classMethodParameters(VmTyped methodVm) { + return parametersOf(methodVm); + } + + private static Object classMethodReturnType(VmTyped methodVm) { + return typeAnnotationOf(methodVm); + } + + private static Object classMethodBody(VmTyped methodVm) { + return exprInChild(methodVm, NodeType.CLASS_METHOD_BODY); + } + + private static VmList classBodyProperties(VmTyped classBodyVm) { + var elements = findChildVm(classBodyVm, NodeType.CLASS_BODY_ELEMENTS); + if (elements == null) { + return VmList.EMPTY; + } + return wrapAll(findChildrenVm(elements, NodeType.CLASS_PROPERTY), classPropertyNodeFactory); + } + + private static VmList classBodyMethods(VmTyped classBodyVm) { + var elements = findChildVm(classBodyVm, NodeType.CLASS_BODY_ELEMENTS); + if (elements == null) { + return VmList.EMPTY; + } + return wrapAll(findChildrenVm(elements, NodeType.CLASS_METHOD), classMethodNodeFactory); + } + + private static VmList moduleImports(VmTyped moduleVm) { + var importListVm = findChildVm(moduleVm, NodeType.IMPORT_LIST); + if (importListVm == null) { + return VmList.EMPTY; + } + return wrapAll(findChildrenVm(importListVm, NodeType.IMPORT), importNodeFactory); + } + + private static boolean importIsGlob(VmTyped importVm) { + var data = (NodeData) importVm.getExtraStorage(); + for (var child : data.node.children) { + if (child.type == NodeType.TERMINAL) { + return "import*".equals(child.text(data.source)); + } + } + return false; + } + + private static String importUri(VmTyped importVm) { + var data = (NodeData) importVm.getExtraStorage(); + return extractStringChars(data.node, data.source); + } + + private static Object importAlias(VmTyped importVm) { + var aliasVm = findChildVm(importVm, NodeType.IMPORT_ALIAS); + if (aliasVm == null) { + return VmNull.withoutDefault(); + } + // an `import_alias` node always contains an `identifier` + return identifierNodeFactory.create(findChildVm(aliasVm, NodeType.IDENTIFIER)); + } + + private static String identifierValue(VmTyped identifierVm) { + var text = nodeText((NodeData) identifierVm.getExtraStorage()); + return text == null ? "" : text; + } + + private static @Nullable String nodeText(NodeData data) { + return data.node.children.isEmpty() || data.node.type == NodeType.STRING_CHARS + ? data.node.text(data.source) + : null; + } + + // Extract the string constant from a node's `string_chars` child (dropping the enclosing quotes) + private static String extractStringChars(Node node, char[] source) { + var stringChars = node.findChildByType(NodeType.STRING_CHARS); + if (stringChars == null) { + return ""; + } + var terminals = new ArrayList(); + for (var child : stringChars.children) { + if (child.type == NodeType.TERMINAL) { + terminals.add(child); + } + } + var builder = new StringBuilder(); + for (var i = 1; i < terminals.size() - 1; i++) { + var text = terminals.get(i).text(source); + builder.append(text); + } + return builder.toString(); + } + + private static VmList modifiersOf(VmTyped ownerVm) { + var modifierList = findChildVm(ownerVm, NodeType.MODIFIER_LIST); + if (modifierList == null) { + return VmList.EMPTY; + } + var modifierVms = findChildrenVm(modifierList, NodeType.MODIFIER); + var result = new Object[modifierVms.size()]; + for (var i = 0; i < modifierVms.size(); i++) { + var data = (NodeData) modifierVms.get(i).getExtraStorage(); + result[i] = data.node.text(data.source); + } + return VmList.create(result); + } + + private static String stringCharsOf(VmTyped clauseVm) { + var data = (NodeData) clauseVm.getExtraStorage(); + return extractStringChars(data.node, data.source); + } + + // The first child of `genericVm` with the given type, as a generic-node `VmTyped`, or null. + private static @Nullable VmTyped findChildVm(VmTyped genericVm, NodeType type) { + var data = (NodeData) genericVm.getExtraStorage(); + var children = data.node.children; + for (var i = 0; i < children.size(); i++) { + if (children.get(i).type == type) { + return (VmTyped) data.childrenVm.get(i); + } + } + return null; + } + + // The first type-node child of `genericVm`, as a generic-node `VmTyped`, or null. + private static @Nullable VmTyped findTypeChildVm(VmTyped genericVm) { + var data = (NodeData) genericVm.getExtraStorage(); + var children = data.node.children; + for (var i = 0; i < children.size(); i++) { + if (children.get(i).type.isType()) { + return (VmTyped) data.childrenVm.get(i); + } + } + return null; + } + + // The first expression-node child of `genericVm`, as a generic-node `VmTyped`, or null. + private static @Nullable VmTyped findExprChildVm(VmTyped genericVm) { + var data = (NodeData) genericVm.getExtraStorage(); + var children = data.node.children; + for (var i = 0; i < children.size(); i++) { + if (children.get(i).type.isExpression()) { + return (VmTyped) data.childrenVm.get(i); + } + } + return null; + } + + // All type-node children of `genericVm`, as generic-node `VmTyped`s. + private static List findTypeChildrenVm(VmTyped genericVm) { + var data = (NodeData) genericVm.getExtraStorage(); + var children = data.node.children; + var result = new ArrayList(); + for (var i = 0; i < children.size(); i++) { + if (children.get(i).type.isType()) { + result.add((VmTyped) data.childrenVm.get(i)); + } + } + return result; + } + + // All expression-node children of `genericVm`, as generic-node `VmTyped`s. + private static List findExprChildrenVm(VmTyped genericVm) { + var data = (NodeData) genericVm.getExtraStorage(); + var children = data.node.children; + var result = new ArrayList(); + for (var i = 0; i < children.size(); i++) { + if (children.get(i).type.isExpression()) { + result.add((VmTyped) data.childrenVm.get(i)); + } + } + return result; + } + + // Wrap each generic type node into its `TypeNode` subclass, as a `VmList`. + private static VmList wrapTypes(List typeVms) { + var result = new Object[typeVms.size()]; + for (var i = 0; i < typeVms.size(); i++) { + result[i] = wrapType(typeVms.get(i)); + } + return VmList.create(result); + } + + // Wrap each generic expression node into its `ExprNode` subclass, as a `VmList`. + private static VmList wrapExprs(List exprVms) { + var result = new Object[exprVms.size()]; + for (var i = 0; i < exprVms.size(); i++) { + result[i] = wrapExpr(exprVms.get(i)); + } + return VmList.create(result); + } + + private static Object typeAnnotationOf(@Nullable VmTyped ownerVm) { + if (ownerVm == null) { + return VmNull.withoutDefault(); + } + var annotation = findChildVm(ownerVm, NodeType.TYPE_ANNOTATION); + if (annotation == null) { + return VmNull.withoutDefault(); + } + var type = findTypeChildVm(annotation); + if (type == null) { + throw new VmExceptionBuilder().bug("A `type_annotation` always contains a type.").build(); + } + return wrapType(type); + } + + // The expression inside a `containerType` child of `ownerVm`, as an ExprNode, or null when + // absent. + private static Object exprInChild(VmTyped ownerVm, NodeType containerType) { + var container = findChildVm(ownerVm, containerType); + if (container == null) { + return VmNull.withoutDefault(); + } + var expr = findExprChildVm(container); + return expr == null ? VmNull.withoutDefault() : wrapExpr(expr); + } + + private static VmList objectBodiesOf(VmTyped ownerVm) { + return wrapAll(findChildrenVm(ownerVm, NodeType.OBJECT_BODY), objectBodyNodeFactory); + } + + private static VmList parametersOf(VmTyped ownerVm) { + var list = findChildVm(ownerVm, NodeType.PARAMETER_LIST); + if (list == null) { + return VmList.EMPTY; + } + var elems = findChildVm(list, NodeType.PARAMETER_LIST_ELEMENTS); + if (elems == null) { + return VmList.EMPTY; + } + return wrapAll(findChildrenVm(elems, NodeType.PARAMETER), parameterNodeFactory); + } + + // Wrap each generic-node `VmTyped` into a typed node via `factory`, as a `VmList`. + private static VmList wrapAll(List genericVms, VmObjectFactory factory) { + var result = new Object[genericVms.size()]; + for (var i = 0; i < genericVms.size(); i++) { + result[i] = factory.create(genericVms.get(i)); + } + return VmList.create(result); + } + + private static VmTyped identifierNodeOf(@Nullable VmTyped ownerVm) { + var id = ownerVm == null ? null : findChildVm(ownerVm, NodeType.IDENTIFIER); + return identifierNodeFactory.create(id); + } + + private static VmList typeParametersOf(@Nullable VmTyped ownerVm) { + if (ownerVm == null) { + return VmList.EMPTY; + } + var list = findChildVm(ownerVm, NodeType.TYPE_PARAMETER_LIST); + if (list == null) { + return VmList.EMPTY; + } + var elems = findChildVm(list, NodeType.TYPE_PARAMETER_LIST_ELEMENTS); + if (elems == null) { + return VmList.EMPTY; + } + return wrapAll(findChildrenVm(elems, NodeType.TYPE_PARAMETER), typeParameterNodeFactory); + } + + // Wrap a generic type node into its specific `TypeNode` subclass + private static VmTyped wrapType(VmTyped typeVm) { + var data = (NodeData) typeVm.getExtraStorage(); + return switch (data.node.type) { + case UNKNOWN_TYPE -> unknownTypeNodeFactory.create(typeVm); + case NOTHING_TYPE -> nothingTypeNodeFactory.create(typeVm); + case MODULE_TYPE -> moduleTypeNodeFactory.create(typeVm); + case DECLARED_TYPE -> declaredTypeNodeFactory.create(typeVm); + case NULLABLE_TYPE -> nullableTypeNodeFactory.create(typeVm); + case UNION_TYPE -> unionTypeNodeFactory.create(typeVm); + case FUNCTION_TYPE -> functionTypeNodeFactory.create(typeVm); + case CONSTRAINED_TYPE -> constrainedTypeNodeFactory.create(typeVm); + case PARENTHESIZED_TYPE -> parenthesizedTypeNodeFactory.create(typeVm); + case STRING_CONSTANT_TYPE -> stringConstantTypeNodeFactory.create(typeVm); + default -> + throw new VmExceptionBuilder().bug("Unexpected type node: " + data.node.type).build(); + }; + } + + // Wrap a generic expression node into its specific `ExprNode` subclass + private static VmTyped wrapExpr(VmTyped exprVm) { + var data = (NodeData) exprVm.getExtraStorage(); + return switch (data.node.type) { + case THIS_EXPR -> thisExprNodeFactory.create(exprVm); + case OUTER_EXPR -> outerExprNodeFactory.create(exprVm); + case MODULE_EXPR -> moduleExprNodeFactory.create(exprVm); + case NULL_EXPR -> nullLiteralExprNodeFactory.create(exprVm); + case BOOL_LITERAL_EXPR -> boolLiteralExprNodeFactory.create(exprVm); + case INT_LITERAL_EXPR -> intLiteralExprNodeFactory.create(exprVm); + case FLOAT_LITERAL_EXPR -> floatLiteralExprNodeFactory.create(exprVm); + case SINGLE_LINE_STRING_LITERAL_EXPR -> singleLineStringLiteralExprNodeFactory.create(exprVm); + case MULTI_LINE_STRING_LITERAL_EXPR -> multiLineStringLiteralExprNodeFactory.create(exprVm); + case UNQUALIFIED_ACCESS_EXPR -> unqualifiedAccessExprNodeFactory.create(exprVm); + case QUALIFIED_ACCESS_EXPR -> qualifiedAccessExprNodeFactory.create(exprVm); + case SUBSCRIPT_EXPR -> subscriptExprNodeFactory.create(exprVm); + case SUPER_ACCESS_EXPR -> superAccessExprNodeFactory.create(exprVm); + case SUPER_SUBSCRIPT_EXPR -> superSubscriptExprNodeFactory.create(exprVm); + case IF_EXPR -> ifExprNodeFactory.create(exprVm); + case LET_EXPR -> letExprNodeFactory.create(exprVm); + case THROW_EXPR -> throwExprNodeFactory.create(exprVm); + case TRACE_EXPR -> traceExprNodeFactory.create(exprVm); + case IMPORT_EXPR -> importExprNodeFactory.create(exprVm); + case READ_EXPR -> readExprNodeFactory.create(exprVm); + case NEW_EXPR -> newExprNodeFactory.create(exprVm); + case AMENDS_EXPR -> amendsExprNodeFactory.create(exprVm); + case BINARY_OP_EXPR -> binaryOpExprNodeFactory.create(exprVm); + case UNARY_MINUS_EXPR -> unaryMinusExprNodeFactory.create(exprVm); + case LOGICAL_NOT_EXPR -> logicalNotExprNodeFactory.create(exprVm); + case NON_NULL_EXPR -> nonNullExprNodeFactory.create(exprVm); + case FUNCTION_LITERAL_EXPR -> functionLiteralExprNodeFactory.create(exprVm); + case PARENTHESIZED_EXPR -> parenthesizedExprNodeFactory.create(exprVm); + default -> + throw new VmExceptionBuilder() + .bug("Unexpected expression node: " + data.node.type) + .build(); + }; + } + + // Wrap a generic object-member node into its `ObjectMemberNode` subclass + private static VmTyped wrapObjectMember(VmTyped memberVm) { + var data = (NodeData) memberVm.getExtraStorage(); + return switch (data.node.type) { + case OBJECT_ELEMENT -> objectElementNodeFactory.create(memberVm); + case OBJECT_PROPERTY -> objectPropertyNodeFactory.create(memberVm); + case OBJECT_METHOD -> objectMethodNodeFactory.create(memberVm); + case MEMBER_PREDICATE -> memberPredicateNodeFactory.create(memberVm); + case OBJECT_ENTRY -> objectEntryNodeFactory.create(memberVm); + case OBJECT_SPREAD -> objectSpreadNodeFactory.create(memberVm); + case WHEN_GENERATOR -> whenGeneratorNodeFactory.create(memberVm); + case FOR_GENERATOR -> forGeneratorNodeFactory.create(memberVm); + default -> + throw new VmExceptionBuilder() + .bug("Unexpected object-member node: " + data.node.type) + .build(); + }; + } + + // Whether `type` is one of the object-member node types + private static boolean isObjectMemberType(NodeType type) { + return switch (type) { + case OBJECT_ELEMENT, + OBJECT_PROPERTY, + OBJECT_METHOD, + MEMBER_PREDICATE, + OBJECT_ENTRY, + OBJECT_SPREAD, + WHEN_GENERATOR, + FOR_GENERATOR -> + true; + default -> false; + }; + } + + // All children of `genericVm` with the given type, as generic-node `VmTyped`s. + private static List findChildrenVm(VmTyped genericVm, NodeType type) { + var data = (NodeData) genericVm.getExtraStorage(); + var children = data.node.children; + var result = new ArrayList(); + for (var i = 0; i < children.size(); i++) { + if (children.get(i).type == type) { + result.add((VmTyped) data.childrenVm.get(i)); + } + } + return result; + } public abstract static class parseModule extends ExternalMethod1Node { @Specialization @@ -86,8 +1493,7 @@ protected Object evalString(@SuppressWarnings("unused") VmTyped self, String sou @Specialization @TruffleBoundary - protected Object evalResource( - @SuppressWarnings("unused") VmTyped self, VmTyped source) { + protected Object evalResource(@SuppressWarnings("unused") VmTyped self, VmTyped source) { // `source` is a `pkl.base#Resource` var text = (String) VmUtils.readMember(source, Identifier.TEXT); return doParseOrNull(text); diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java index 46b6e1960..9e1d8a292 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java @@ -60,17 +60,6 @@ static final class NodeData { } } - /** Extra storage backing a Pkl {@code ParserError} instance. */ - static final class ErrorData { - final String text; - final VmTyped spanVm; - - ErrorData(String text, VmTyped spanVm) { - this.text = text; - this.spanVm = spanVm; - } - } - private static final VmObjectFactory nodeFactory = new VmObjectFactory(SyntaxModule::getNodeClass) .addStringProperty("type", nd -> nd.node.type.name().toLowerCase(Locale.ROOT)) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl index 993ee5775..943a4e4c2 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl @@ -17,15 +17,15 @@ facts { local intLit = expr("42") intLit is syntax.IntLiteralExprNode - (intLit as syntax.IntLiteralExprNode).text == "42" + (intLit as syntax.IntLiteralExprNode).value == "42" local hexLit = expr("0xFF") hexLit is syntax.IntLiteralExprNode - (hexLit as syntax.IntLiteralExprNode).text == "0xFF" + (hexLit as syntax.IntLiteralExprNode).value == "0xFF" local floatLit = expr("3.14") floatLit is syntax.FloatLiteralExprNode - (floatLit as syntax.FloatLiteralExprNode).text == "3.14" + (floatLit as syntax.FloatLiteralExprNode).value == "3.14" local nullLit = expr("null") nullLit is syntax.NullLiteralExprNode @@ -78,12 +78,12 @@ facts { ["access expressions"] { local unqual = expr("foo") unqual is syntax.UnqualifiedAccessExprNode - (unqual as syntax.UnqualifiedAccessExprNode).identifier == "foo" + (unqual as syntax.UnqualifiedAccessExprNode).identifier.value == "foo" (unqual as syntax.UnqualifiedAccessExprNode).arguments == null local withArgs = expr("foo(1, 2)") withArgs is syntax.UnqualifiedAccessExprNode - (withArgs as syntax.UnqualifiedAccessExprNode).identifier == "foo" + (withArgs as syntax.UnqualifiedAccessExprNode).identifier.value == "foo" (withArgs as syntax.UnqualifiedAccessExprNode).arguments != null (withArgs as syntax.UnqualifiedAccessExprNode).arguments.length == 2 @@ -91,7 +91,7 @@ facts { qual is syntax.QualifiedAccessExprNode (qual as syntax.QualifiedAccessExprNode).receiver is syntax.UnqualifiedAccessExprNode (qual as syntax.QualifiedAccessExprNode).isNullSafe == false - (qual as syntax.QualifiedAccessExprNode).member == "bar" + (qual as syntax.QualifiedAccessExprNode).identifier.value == "bar" local nullSafe = expr("foo?.bar") nullSafe is syntax.QualifiedAccessExprNode @@ -158,7 +158,7 @@ facts { ["let expression"] { local letExpr = expr("let (y = 1) y + 1") letExpr is syntax.LetExprNode - (letExpr as syntax.LetExprNode).parameter.name == "y" + (letExpr as syntax.LetExprNode).parameter.identifier!!.value == "y" (letExpr as syntax.LetExprNode).bindingValue is syntax.IntLiteralExprNode (letExpr as syntax.LetExprNode).body is syntax.BinaryOpExprNode } @@ -173,8 +173,8 @@ facts { local fn = expr("(x, y) -> x + y") fn is syntax.FunctionLiteralExprNode (fn as syntax.FunctionLiteralExprNode).parameters.length == 2 - (fn as syntax.FunctionLiteralExprNode).parameters[0].name == "x" - (fn as syntax.FunctionLiteralExprNode).parameters[1].name == "y" + (fn as syntax.FunctionLiteralExprNode).parameters[0].identifier!!.value == "x" + (fn as syntax.FunctionLiteralExprNode).parameters[1].identifier!!.value == "y" (fn as syntax.FunctionLiteralExprNode).body is syntax.BinaryOpExprNode } @@ -219,4 +219,49 @@ facts { readGlob is syntax.ReadExprNode (readGlob as syntax.ReadExprNode).keyword == "read*" } + + ["class declaration"] { + local mod = new syntax.Parser {}.parseModule(""" + /// A person. + @Deprecated { message = "old" } + abstract open class Person extends Being { + name: String + function greet() = "hi" + } + """) + mod.classes.length == 1 + local cls = mod.classes.first + cls.identifier.value == "Person" + cls.modifiers == List("abstract", "open") + cls.docComment != null + cls.docComment!!.lines.length == 1 + cls.annotations.length == 1 + cls.annotations.first.type is syntax.DeclaredTypeNode + cls.extendsType is syntax.DeclaredTypeNode + cls.body != null + cls.body!!.properties.length == 1 + cls.body!!.methods.length == 1 + cls.typeParameters.isEmpty + } + + ["generic class"] { + local mod = new syntax.Parser {}.parseModule("class Box { value: T }") + local cls = mod.classes.first + cls.identifier.value == "Box" + cls.typeParameters.length == 1 + cls.typeParameters.first.identifier.value == "T" + cls.typeParameters.first.variance == "out" + } + + ["minimal class"] { + local mod = new syntax.Parser {}.parseModule("class Empty") + local cls = mod.classes.first + cls.identifier.value == "Empty" + cls.modifiers.isEmpty + cls.docComment == null + cls.annotations.isEmpty + cls.extendsType == null + cls.body == null + cls.typeParameters.isEmpty + } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl index 6e067ef75..5cab8ed88 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl @@ -12,7 +12,7 @@ facts { result is syntax.ModuleNode local mod = result as syntax.ModuleNode mod.declaration != null - mod.declaration!!.name == "my.app" + mod.declaration!!.name!!.value == "my.app" mod.declaration!!.docComment == null mod.declaration!!.annotations.length == 0 mod.declaration!!.modifiers.isEmpty @@ -68,7 +68,7 @@ facts { mod.imports[0].alias == null mod.imports[1].uri == "bar.pkl" - mod.imports[1].alias == "myBar" + mod.imports[1].alias!!.value == "myBar" mod.imports[2].uri == "*.pkl" mod.imports[2].isGlob == true @@ -92,22 +92,22 @@ facts { cls.modifiers != null cls.modifiers == List("abstract") - cls.name == "Bird" + cls.identifier.value == "Bird" cls.typeParameters.isEmpty cls.extendsType == null cls.body != null cls.body!!.properties.length == 1 - cls.body!!.properties.first.name == "name" + cls.body!!.properties.first.identifier.value == "name" cls.body!!.properties.first.typeAnnotation != null cls.body!!.properties.first.typeAnnotation is syntax.DeclaredTypeNode cls.body!!.properties.first.value == null cls.body!!.methods.length == 1 - cls.body!!.methods.first.name == "fly" + cls.body!!.methods.first.identifier.value == "fly" cls.body!!.methods.first.parameters.length == 1 - cls.body!!.methods.first.parameters.first.name == "speed" + cls.body!!.methods.first.parameters.first.identifier!!.value == "speed" cls.body!!.methods.first.returnType != null cls.body!!.methods.first.body != null cls.body!!.methods.first.body is syntax.BoolLiteralExprNode @@ -122,10 +122,10 @@ facts { local mod = result as syntax.ModuleNode local cls = mod.classes.first - cls.name == "Container" + cls.identifier.value == "Container" cls.typeParameters != null cls.typeParameters.length == 1 - cls.typeParameters.first.name == "T" + cls.typeParameters.first.identifier.value == "T" cls.typeParameters.first.variance == null cls.extendsType != null @@ -137,7 +137,7 @@ facts { local mod = result as syntax.ModuleNode mod.typeAliases.length == 1 local ta = mod.typeAliases.first - ta.name == "Positive" + ta.identifier.value == "Positive" ta.typeParameters.isEmpty ta.type is syntax.ConstrainedTypeNode } @@ -151,17 +151,17 @@ facts { local mod = result as syntax.ModuleNode mod.properties.length == 2 - mod.properties[0].name == "name" + mod.properties[0].identifier.value == "name" mod.properties[0].modifiers != null mod.properties[0].modifiers == List("hidden") mod.properties[0].value is syntax.SingleLineStringLiteralExprNode - mod.properties[1].name == "count" + mod.properties[1].identifier.value == "count" mod.properties[1].modifiers != null mod.properties[1].modifiers == List("local") mod.methods.length == 1 - mod.methods.first.name == "greet" + mod.methods.first.identifier.value == "greet" mod.methods.first.parameters.length == 1 mod.methods.first.returnType != null mod.methods.first.body is syntax.SingleLineStringLiteralExprNode @@ -174,15 +174,15 @@ facts { params.length == 3 - params[0].name == "x" + params[0].identifier!!.value == "x" params[0].typeAnnotation != null params[0].isBlankIdentifier == false - params[1].name == "_" + params[1].identifier == null params[1].isBlankIdentifier == true params[1].typeAnnotation == null - params[2].name == "y" + params[2].identifier!!.value == "y" params[2].typeAnnotation == null params[2].isBlankIdentifier == false } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl index 4af869629..621252464 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl @@ -14,7 +14,7 @@ facts { b.members.length == 1 b.members.first is syntax.ObjectPropertyNode local prop = b.members.first as syntax.ObjectPropertyNode - prop.name == "name" + prop.identifier.value == "name" prop.value is syntax.SingleLineStringLiteralExprNode prop.modifiers.isEmpty prop.typeAnnotation == null @@ -34,7 +34,7 @@ facts { local b = body("inner { x = 1 }") b.members.length == 1 local prop = b.members.first as syntax.ObjectPropertyNode - prop.name == "inner" + prop.identifier.value == "inner" prop.value == null prop.objectBodies.length == 1 } @@ -44,9 +44,9 @@ facts { b.members.length == 1 b.members.first is syntax.ObjectMethodNode local method = b.members.first as syntax.ObjectMethodNode - method.name == "greet" + method.identifier.value == "greet" method.parameters.length == 1 - method.parameters.first.name == "who" + method.parameters.first.identifier!!.value == "who" method.returnType != null method.body is syntax.SingleLineStringLiteralExprNode } @@ -112,7 +112,7 @@ facts { b.members.first is syntax.ForGeneratorNode local gen = b.members.first as syntax.ForGeneratorNode gen.keyParameter == null - gen.valueParameter.name == "item" + gen.valueParameter.identifier!!.value == "item" gen.iterable is syntax.UnqualifiedAccessExprNode } @@ -120,8 +120,8 @@ facts { local b = body("for (k, v in items) { v }") local gen = b.members.first as syntax.ForGeneratorNode gen.keyParameter != null - gen.keyParameter!!.name == "k" - gen.valueParameter.name == "v" + gen.keyParameter!!.identifier!!.value == "k" + gen.valueParameter.identifier!!.value == "v" } ["when generator"] { @@ -148,8 +148,8 @@ facts { propVal is syntax.NewExprNode local newBody = (propVal as syntax.NewExprNode).body newBody.parameters.length == 2 - newBody.parameters[0].name == "a" - newBody.parameters[1].name == "b" + newBody.parameters[0].identifier!!.value == "a" + newBody.parameters[1].identifier!!.value == "b" } ["mixed object members"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl index 9d7aed84d..137491d6e 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl @@ -62,41 +62,4 @@ facts { syntax.descendants(sample).filter((n) -> n.type == "class").length == 1 } - - ["descendants + wrap gives type-safe access"] { - syntax - .descendants(sample) - .map((n) -> syntax.wrap(n)) - .filter((s) -> s is syntax.ImportNode) - .map((s) -> (s as syntax.ImportNode).uri) == List("foo.pkl", "bar.pkl") - - syntax - .descendants(sample) - .map((n) -> syntax.wrap(n)) - .filter((s) -> s is syntax.ClassPropertyNode) - .map((s) -> (s as syntax.ClassPropertyNode).name) == List("x", "y", "origin", "scaled") - } - - ["wrap returns typed nodes for known kinds"] { - syntax.wrap(new syntax.Node { type = "if_expr" }) is syntax.IfExprNode - syntax.wrap(new syntax.Node { type = "declared_type" }) is syntax.DeclaredTypeNode - syntax.wrap(new syntax.Node { type = "object_property" }) is syntax.ObjectPropertyNode - syntax.wrap(new syntax.Node { type = "class" }) is syntax.ClassNode - syntax.wrap(new syntax.Node { type = "parameter" }) is syntax.ParameterNode - } - - ["wrap returns null for kinds without a typed form"] { - syntax.wrap(new syntax.Node { type = "terminal" }) == null - syntax.wrap(new syntax.Node { type = "modifier_list" }) == null - syntax.wrap(new syntax.Node { type = "if_header" }) == null - } - - ["fold reads typed fields via wrap"] { - // collect the arithmetic operators used anywhere in the module - syntax.fold(sample, List(), (acc, n) -> - let (s = syntax.wrap(n)) - if (s is syntax.BinaryOpExprNode) acc.add(s.operator) else acc - ) - == List("*") - } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl index 23dd52316..f7cf0ea04 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl @@ -22,12 +22,12 @@ facts { ["declared type"] { local simple = typeOf("String") simple is syntax.DeclaredTypeNode - (simple as syntax.DeclaredTypeNode).name == "String" + (simple as syntax.DeclaredTypeNode).name.value == "String" (simple as syntax.DeclaredTypeNode).typeArguments.isEmpty local withArgs = typeOf("List") withArgs is syntax.DeclaredTypeNode - (withArgs as syntax.DeclaredTypeNode).name == "List" + (withArgs as syntax.DeclaredTypeNode).name.value == "List" (withArgs as syntax.DeclaredTypeNode).typeArguments.length == 1 (withArgs as syntax.DeclaredTypeNode).typeArguments.first is syntax.DeclaredTypeNode diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl index cc9f990c1..99f5dd85f 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl @@ -11,9 +11,6 @@ local function walkFormat( visit: (syntax.Node) -> Pair?, ): String = syntax.format(syntax.walk(mod(source).node, visit)) -local function visitFormat(source: String, visitor: syntax.Visitor): String = - syntax.format(syntax.visit(mod(source).node, visitor)) - facts { ["read-only walk leaves the tree unchanged"] { // returning `null` everywhere keeps every node and keeps descending @@ -87,33 +84,4 @@ facts { result.parent == null result.children.first.parent.type == "module" } - - // ---- typed Visitor ---- - - ["visitor rewrites matched nodes only"] { - visitFormat("x = 1 + 2 * 3", new syntax.Visitor { - visitIntLiteralExpr = (_) -> Pair(new syntax.IntLiteralExprNode { value = 0 }, false) - }) == fmt("x = 0 + 0 * 0") - } - - ["visitor reads typed fields"] { - visitFormat("x = foo + bar", new syntax.Visitor { - visitUnqualifiedAccessExpr = (it) -> - if (it.identifier == "foo") - Pair(new syntax.UnqualifiedAccessExprNode { identifier = "renamed" }, false) - else - null - }) == fmt("x = renamed + bar") - } - - ["visitor can broaden the node kind"] { - visitFormat("x = true", new syntax.Visitor { - visitBoolLiteralExpr = (_) -> - Pair(new syntax.IntLiteralExprNode { value = 1 }, false) - }) == fmt("x = 1") - } - - ["visitor leaves unmatched trees intact"] { - visitFormat("x = if (cond) 1 else 2", new syntax.Visitor {}) == fmt("x = if (cond) 1 else 2") - } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf index d95f2676b..681ed1554 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf @@ -128,4 +128,33 @@ facts { true true } + ["class declaration"] { + true + true + true + true + true + true + true + true + true + true + true + true + } + ["generic class"] { + true + true + true + true + } + ["minimal class"] { + true + true + true + true + true + true + true + } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/traversal.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/traversal.pcf index 7b749467c..5096572ab 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/traversal.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/traversal.pcf @@ -15,23 +15,4 @@ facts { true true } - ["descendants + wrap gives type-safe access"] { - true - true - } - ["wrap returns typed nodes for known kinds"] { - true - true - true - true - true - } - ["wrap returns null for kinds without a typed form"] { - true - true - true - } - ["fold reads typed fields via wrap"] { - true - } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf index 0bbf1e585..da058fdfc 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf @@ -22,16 +22,4 @@ facts { true true } - ["visitor rewrites matched nodes only"] { - true - } - ["visitor reads typed fields"] { - true - } - ["visitor can broaden the node kind"] { - true - } - ["visitor leaves unmatched trees intact"] { - true - } } diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index cc1d0b963..661b9da4b 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -50,18 +50,6 @@ external function formatToString(node: Node, grammarVersion: "V1" | "V2"): Strin /// nodes constructed from scratch retain their given `parent`. external function walk(node: Node, visit: (Node) -> Pair?): Node -/// Walk [node] with a [Visitor]. -/// -/// This is a type-safe wrapper around [walk]: each callback receives the typed -/// node and returns either `null` to leave it unchanged, or -/// `Pair(replacement, descend)` where `replacement` is any [SyntaxNode]. -/// Nodes without a matching callback are traversed and reused unchanged. -/// -/// Because [SyntaxNode.builtNode] always rebuilds from fields, return `null` to keep -/// a node unchanged — never `Pair(it, ...)`, as that would rebuild its subtree -/// (dropping inner comments/spacing). -function visit(node: Node, visitor: Visitor): Node = walk(node, (n) -> dispatch(n, visitor)) - /// Fold [accumulate] over [node] and its descendants, top-down in pre-order. /// /// ``` @@ -73,21 +61,9 @@ function fold(node: Node, initial: Acc, accumulate: (Acc, Node) -> Acc): Ac /// Every node at or below [node], in pre-order. /// -/// A convenience over [fold] for the common case of enumerating nodes. Combine with -/// [wrap] for type-safe access: -/// -/// ``` -/// // every imported URI in a module -/// descendants(module.node) -/// .map((n) -> wrap(n)) -/// .filterIsInstance(ImportNode) -/// .map((s) -> s.uri) -/// ``` +/// A convenience over [fold] for the common case of enumerating nodes. function descendants(node: Node): List = fold(node, List(), (acc, n) -> acc.add(n)) -/// Wrap a raw [node] into its typed [SyntaxNode], or `null` if it has no typed form. -function wrap(_node: Node): SyntaxNode? = constructors.getOrNull(_node.type)?.apply(_node) - class Node { type: NodeType children: List @@ -301,119 +277,6 @@ local const function isTypeType(t: NodeType): Boolean = || t == "string_constant_type" || t == "constrained_type" -// Check if a NodeType represents an object member. -local const function isObjectMemberType(t: NodeType): Boolean = - t == "object_element" - || t == "object_property" - || t == "object_method" - || t == "member_predicate" - || t == "object_entry" - || t == "object_spread" - || t == "when_generator" - || t == "for_generator" - -// Constructs the typed [SyntaxNode] backing each node type. -local const constructors: Map SyntaxNode> = - Map( - // module structure - "module", (n) -> new ModuleNode { node = n }, - "module_declaration", (n) -> new ModuleDeclarationNode { node = n }, - "import", (n) -> new ImportNode { node = n }, - "class", (n) -> new ClassNode { node = n }, - "typealias", (n) -> new TypeAliasNode { node = n }, - "class_body", (n) -> new ClassBodyNode { node = n }, - "class_property", (n) -> new ClassPropertyNode { node = n }, - "class_method", (n) -> new ClassMethodNode { node = n }, - "object_body", (n) -> new ObjectBodyNode { node = n }, - "annotation", (n) -> new AnnotationNode { node = n }, - "parameter", (n) -> new ParameterNode { node = n }, - "type_parameter", (n) -> new TypeParameterNode { node = n }, - "doc_comment", (n) -> new DocCommentNode { node = n }, - // object members - "object_element", (n) -> new ObjectElementNode { node = n }, - "object_property", (n) -> new ObjectPropertyNode { node = n }, - "object_method", (n) -> new ObjectMethodNode { node = n }, - "member_predicate", (n) -> new MemberPredicateNode { node = n }, - "object_entry", (n) -> new ObjectEntryNode { node = n }, - "object_spread", (n) -> new ObjectSpreadNode { node = n }, - "when_generator", (n) -> new WhenGeneratorNode { node = n }, - "for_generator", (n) -> new ForGeneratorNode { node = n }, - // expressions - "this_expr", (n) -> new ThisExprNode { node = n }, - "outer_expr", (n) -> new OuterExprNode { node = n }, - "module_expr", (n) -> new ModuleExprNode { node = n }, - "null_expr", (n) -> new NullLiteralExprNode { node = n }, - "bool_literal_expr", (n) -> new BoolLiteralExprNode { node = n }, - "int_literal_expr", (n) -> new IntLiteralExprNode { node = n }, - "float_literal_expr", (n) -> new FloatLiteralExprNode { node = n }, - "single_line_string_literal_expr", (n) -> new SingleLineStringLiteralExprNode { node = n }, - "multi_line_string_literal_expr", (n) -> new MultiLineStringLiteralExprNode { node = n }, - "unqualified_access_expr", (n) -> new UnqualifiedAccessExprNode { node = n }, - "qualified_access_expr", (n) -> new QualifiedAccessExprNode { node = n }, - "subscript_expr", (n) -> new SubscriptExprNode { node = n }, - "super_access_expr", (n) -> new SuperAccessExprNode { node = n }, - "super_subscript_expr", (n) -> new SuperSubscriptExprNode { node = n }, - "if_expr", (n) -> new IfExprNode { node = n }, - "let_expr", (n) -> new LetExprNode { node = n }, - "throw_expr", (n) -> new ThrowExprNode { node = n }, - "trace_expr", (n) -> new TraceExprNode { node = n }, - "import_expr", (n) -> new ImportExprNode { node = n }, - "read_expr", (n) -> new ReadExprNode { node = n }, - "new_expr", (n) -> new NewExprNode { node = n }, - "amends_expr", (n) -> new AmendsExprNode { node = n }, - "binary_op_expr", (n) -> new BinaryOpExprNode { node = n }, - "unary_minus_expr", (n) -> new UnaryMinusExprNode { node = n }, - "logical_not_expr", (n) -> new LogicalNotExprNode { node = n }, - "non_null_expr", (n) -> new NonNullExprNode { node = n }, - "function_literal_expr", (n) -> new FunctionLiteralExprNode { node = n }, - "parenthesized_expr", (n) -> new ParenthesizedExprNode { node = n }, - // types - "unknown_type", (n) -> new UnknownTypeNode { node = n }, - "nothing_type", (n) -> new NothingTypeNode { node = n }, - "module_type", (n) -> new ModuleTypeNode { node = n }, - "declared_type", (n) -> new DeclaredTypeNode { node = n }, - "nullable_type", (n) -> new NullableTypeNode { node = n }, - "union_type", (n) -> new UnionTypeNode { node = n }, - "function_type", (n) -> new FunctionTypeNode { node = n }, - "constrained_type", (n) -> new ConstrainedTypeNode { node = n }, - "parenthesized_type", (n) -> new ParenthesizedTypeNode { node = n }, - "string_constant_type", (n) -> new StringConstantTypeNode { node = n }, - ) - -// Wrap a raw Node into the appropriate Expr subclass. -local const function wrapExpr(n: Node): ExprNode = constructors[n.type].apply(n) as ExprNode - -// Wrap a raw Node into the appropriate TypeNode subclass. -local const function wrapTypeNode(n: Node): TypeNode = constructors[n.type].apply(n) as TypeNode - -// Wrap a raw Node into the appropriate ObjectMemberNode subclass. -local const function wrapObjectMember(n: Node): ObjectMemberNode = - constructors[n.type].apply(n) as ObjectMemberNode - -// Extract the string constant from a STRING_CHARS node. -local const function extractStringConstant(n: Node?): String = - if (n == null) - "" - else - let (inner = n.children.filter((c) -> c.type == "terminal").drop(1).dropLast(1)) - inner.map((c) -> c.text ?? "").join("") - -// Find and extract the string_chars of a node. -local const function getStringChars(node: Node?): String = - extractStringConstant(node?.findChild("string_chars")) - -// The dotted name of a `qualified_identifier` node (e.g. "a.b.c"). -local const function qualifiedName(qid: Node?): String = - (qid?.findChildren("identifier") ?? List()).map((n) -> n.text ?? "").join(".") - -// The identifier text of the first `identifier` child of `node`. -local const function identifierText(node: Node?): String = node?.findChild("identifier")?.text ?? "" - -// The modifier keywords of a `modifier_list` child of `node`. -local const function modifiersOf(node: Node?): List = - let (ml = node?.findChild("modifier_list")) - if (ml == null) List() else ml.findChildren("modifier").map((n) -> n.text ?? "") - /// Base class for all typed syntax nodes. /// /// A typed node may be *backed* by a parsed [Node] (available via [node]) or @@ -423,21 +286,10 @@ abstract class SyntaxNode { /// The original parsed node, or `null` when this was built from scratch. hidden node: Node? = null - hidden children: List = node?.children ?? List() - - hidden text: String? = node?.text - /// The source span of this node. @ConvertSpan span: Span = node?.span ?? new Span {} - /// All terminal children (keywords, punctuation, operators). - hidden terminals: List = children.filter((n) -> n.type == "terminal") - - /// All comment children. - hidden comments: List = - children.filter((n) -> n.type == "line_comment" || n.type == "block_comment") - /// This node rebuilt into a generic [Node]. /// /// Always constructs a fresh node from this node's fields. @@ -456,33 +308,22 @@ abstract class ObjectMemberNode extends SyntaxNode /// The top-level module node. class ModuleNode extends SyntaxNode { /// The module declaration, if present. - declaration: ModuleDeclarationNode? = - let (n = node?.findChild("module_declaration")) - if (n == null) null else new ModuleDeclarationNode { node = n } + declaration: ModuleDeclarationNode? /// All imports in this module. - imports: List = - let (importList = node?.findChild("import_list")) - if (importList == null) - List() - else - importList.findChildren("import").map((n) -> new ImportNode { node = n }) + imports: List /// All class declarations in this module. - classes: List = - node?.findChildren("class")?.map((n) -> new ClassNode { node = n }) ?? List() + classes: List /// All typealias declarations in this module. - typeAliases: List = - node?.findChildren("typealias")?.map((n) -> new TypeAliasNode { node = n }) ?? List() + typeAliases: List /// All top-level properties in this module. - properties: List = - node?.findChildren("class_property")?.map((n) -> new ClassPropertyNode { node = n }) ?? List() + properties: List /// All top-level methods in this module. - methods: List = - node?.findChildren("class_method")?.map((n) -> new ClassMethodNode { node = n }) ?? List() + methods: List fixed builtNode = let (self = this) @@ -508,37 +349,23 @@ class ModuleNode extends SyntaxNode { /// A module declaration (including doc comment, annotations, modifiers, name, amends/extends). class ModuleDeclarationNode extends SyntaxNode { - local moduleDefinition: Node? = node?.findChild("module_definition") - /// The doc comment on the module declaration, if present. - docComment: DocCommentNode? = - let (n = node?.findChild("doc_comment")) - if (n == null) null else new DocCommentNode { node = n } + docComment: DocCommentNode? /// Annotations on the module declaration. - annotations: List = - node?.findChildren("annotation")?.map((n) -> new AnnotationNode { node = n }) ?? List() + annotations: List /// The modifiers on the module declaration. - modifiers: List = if (moduleDefinition == null) List() else modifiersOf(moduleDefinition) + modifiers: List /// The qualified name of the module, if present. - name: String? = - if (moduleDefinition == null) - null - else - let (n = moduleDefinition?.findChild("qualified_identifier")) - if (n == null) null else qualifiedName(n) + name: QualifiedIdentifierNode? /// The URI string of the amended module, if any. Mutually exclusive with [extendsUri]. - amendsUri: String? = - let (n = node?.findChild("amends_clause")) - if (n == null) null else getStringChars(n) + amendsUri: String? /// The URI string of the extended module, if any. Mutually exclusive with [amendsUri]. - extendsUri: String? = - let (n = node?.findChild("extends_clause")) - if (n == null) null else getStringChars(n) + extendsUri: String? fixed builtNode = let (self = this) @@ -555,7 +382,7 @@ class ModuleDeclarationNode extends SyntaxNode { List( if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), (terminal) { text = "module" }, - qualifiedIdentifierNode(self.name!!), + self.name.builtNode, ).filterNonNull() } else if (!self.modifiers.isEmpty) @@ -582,15 +409,13 @@ class ModuleDeclarationNode extends SyntaxNode { /// An import declaration. class ImportNode extends SyntaxNode { /// Whether this is a glob import (`import*`). - isGlob: Boolean = terminals.firstOrNull?.text == "import*" + isGlob: Boolean /// The URI string of the import. - uri: String = getStringChars(node) + uri: String /// The alias for this import, if present. - alias: String? = - let (aliasNode = node?.findChild("import_alias")) - if (aliasNode == null) null else identifierText(aliasNode) + alias: IdentifierNode? fixed builtNode = let (self = this) @@ -605,8 +430,7 @@ class ImportNode extends SyntaxNode { else new Node { type = "import_alias" - children = - List((terminal) { text = "as" }, (identifierLeaf) { text = self.alias!! }) + children = List((terminal) { text = "as" }, self.alias.builtNode) }, ).filterNonNull() } @@ -614,48 +438,26 @@ class ImportNode extends SyntaxNode { /// A class declaration. class ClassNode extends SyntaxNode { - local header: Node? = node?.findChild("class_header") - /// The doc comment, if present. - docComment: DocCommentNode? = - let (n = node?.findChild("doc_comment")) - if (n == null) null else new DocCommentNode { node = n } + docComment: DocCommentNode? /// Annotations on the class. - annotations: List = - node?.findChildren("annotation")?.map((n) -> new AnnotationNode { node = n }) ?? List() + annotations: List /// The modifiers on the class. - modifiers: List = modifiersOf(header) + modifiers: List /// The class name. - name: String = identifierText(header) + identifier: IdentifierNode /// The type parameters. - typeParameters: List = - let (tpl = header?.findChild("type_parameter_list")) - if (tpl == null) - List() - else - let (elems = tpl.findChild("type_parameter_list_elements")) - if (elems == null) - List() - else - elems.findChildren("type_parameter").map((n) -> new TypeParameterNode { node = n }) + typeParameters: List /// The supertype this class extends, if present. - extendsType: TypeNode? = - let (ext = header?.findChild("class_header_extends")) - if (ext == null) - null - else - let (t = ext.findTypeChild()) - if (t == null) null else wrapTypeNode(t) + extendsType: TypeNode? /// The class body, if present. - body: ClassBodyNode? = - let (n = node?.findChild("class_body")) - if (n == null) null else new ClassBodyNode { node = n } + body: ClassBodyNode? fixed builtNode = let (self = this) @@ -671,7 +473,7 @@ class ClassNode extends SyntaxNode { List( if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), (terminal) { text = "class" }, - (identifierLeaf) { text = self.name }, + self.identifier.builtNode, ).filterNonNull() + typeParameterListNodes(self.typeParameters) + ( @@ -692,40 +494,23 @@ class ClassNode extends SyntaxNode { /// A typealias declaration. class TypeAliasNode extends SyntaxNode { - local header: Node? = node?.findChild("typealias_header") - /// The doc comment, if present. - docComment: DocCommentNode? = - let (n = node?.findChild("doc_comment")) - if (n == null) null else new DocCommentNode { node = n } + docComment: DocCommentNode? /// Annotations on the typealias. - annotations: List = - node?.findChildren("annotation")?.map((n) -> new AnnotationNode { node = n }) ?? List() + annotations: List /// The modifiers on the typealias. - modifiers: List = modifiersOf(header) + modifiers: List /// The typealias name. - name: String = identifierText(header) + identifier: IdentifierNode /// The type parameters. - typeParameters: List = - let (tpl = header?.findChild("type_parameter_list")) - if (tpl == null) - List() - else - let (elems = tpl.findChild("type_parameter_list_elements")) - if (elems == null) - List() - else - elems.findChildren("type_parameter").map((n) -> new TypeParameterNode { node = n }) + typeParameters: List /// The type that this alias resolves to. - type: TypeNode = - let (body = node?.findChild("typealias_body")) - let (t = body!!.findTypeChild()) - wrapTypeNode(t!!) + type: TypeNode fixed builtNode = let (self = this) @@ -740,7 +525,7 @@ class TypeAliasNode extends SyntaxNode { List( if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), (terminal) { text = "typealias" }, - (identifierLeaf) { text = self.name }, + self.identifier.builtNode, ).filterNonNull() + typeParameterListNodes(self.typeParameters) + List((terminal) { text = "=" }) @@ -754,21 +539,11 @@ class TypeAliasNode extends SyntaxNode { /// A class body delimited by braces. class ClassBodyNode extends SyntaxNode { - local elements: Node? = node?.findChild("class_body_elements") - /// Properties declared in this class body. - properties: List = - if (elements == null) - List() - else - elements.findChildren("class_property").map((n) -> new ClassPropertyNode { node = n }) + properties: List /// Methods declared in this class body. - methods: List = - if (elements == null) - List() - else - elements.findChildren("class_method").map((n) -> new ClassMethodNode { node = n }) + methods: List fixed builtNode = let (self = this) @@ -792,41 +567,26 @@ class ClassBodyNode extends SyntaxNode { /// A class property declaration. class ClassPropertyNode extends SyntaxNode { - local propHeader: Node? = node?.findChild("class_property_header") - local headerBegin: Node? = propHeader?.findChild("class_property_header_begin") - /// The doc comment, if present. - docComment: DocCommentNode? = - let (n = node?.findChild("doc_comment")) - if (n == null) null else new DocCommentNode { node = n } + docComment: DocCommentNode? /// Annotations on the property. - annotations: List = - node?.findChildren("annotation")?.map((n) -> new AnnotationNode { node = n }) ?? List() + annotations: List /// The modifiers on the property. - modifiers: List = modifiersOf(headerBegin) + modifiers: List /// The property name. - name: String = identifierText(headerBegin) + identifier: IdentifierNode /// The type annotation, if present. - typeAnnotation: TypeNode? = - let (n = propHeader?.findChild("type_annotation")) - if (n == null) null else wrapTypeNode(n.findTypeChild()!!) + typeAnnotation: TypeNode? /// The value expression, if present (from `= expr`). - value: ExprNode? = - let (body = node?.findChild("class_property_body")) - if (body == null) - null - else - let (e = body.findExprChild()) - if (e == null) null else wrapExpr(e) + value: ExprNode? /// Object bodies for amending (from `{ ... }` blocks). - objectBodies: List = - node?.findChildren("object_body")?.map((n) -> new ObjectBodyNode { node = n }) ?? List() + objectBodies: List fixed builtNode = let (self = this) @@ -841,9 +601,10 @@ class ClassPropertyNode extends SyntaxNode { List(new Node { type = "class_property_header_begin" children = - List(if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), ( - identifierLeaf - ) { text = self.name }).filterNonNull() + List( + if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), + self.identifier.builtNode, + ).filterNonNull() }) + typeAnnotationNodes(self.typeAnnotation) }) @@ -864,53 +625,29 @@ class ClassPropertyNode extends SyntaxNode { /// A class method declaration. class ClassMethodNode extends SyntaxNode { - local methodHeader: Node? = node?.findChild("class_method_header") - /// The doc comment, if present. - docComment: DocCommentNode? = - let (n = node?.findChild("doc_comment")) - if (n == null) null else new DocCommentNode { node = n } + docComment: DocCommentNode? /// Annotations on the method. - annotations: List = - node?.findChildren("annotation")?.map((n) -> new AnnotationNode { node = n }) ?? List() + annotations: List /// The modifiers on the method. - modifiers: List = modifiersOf(methodHeader) + modifiers: List /// The method name. - name: String = identifierText(methodHeader) + identifier: IdentifierNode /// The type parameters. - typeParameters: List = - let (tpl = node?.findChild("type_parameter_list")) - if (tpl == null) - List() - else - let (elems = tpl.findChild("type_parameter_list_elements")) - if (elems == null) - List() - else - elems.findChildren("type_parameter").map((n) -> new TypeParameterNode { node = n }) + typeParameters: List /// The parameters. - parameters: List = - let (pl = node?.findChild("parameter_list")) - parametersOf(pl!!) + parameters: List /// The return type annotation, if present. - returnType: TypeNode? = - let (n = node?.findChild("type_annotation")) - if (n == null) null else wrapTypeNode(n.findTypeChild()!!) + returnType: TypeNode? /// The method body expression, if present. Null for abstract methods. - body: ExprNode? = - let (bodyNode = node?.findChild("class_method_body")) - if (bodyNode == null) - null - else - let (e = bodyNode?.findExprChild()) - if (e == null) null else wrapExpr(e) + body: ExprNode? fixed builtNode = let (self = this) @@ -925,7 +662,7 @@ class ClassMethodNode extends SyntaxNode { List( if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), (terminal) { text = "function" }, - (identifierLeaf) { text = self.name }, + self.identifier.builtNode, ).filterNonNull() }) + typeParameterListNodes(self.typeParameters) @@ -948,24 +685,11 @@ class ClassMethodNode extends SyntaxNode { /// An object body delimited by braces. class ObjectBodyNode extends SyntaxNode { - local paramList: Node? = node?.findChild("object_parameter_list") - local memberList: Node? = node?.findChild("object_member_list") - /// Parameters for this object body (e.g., `{ x, y -> ... }`). - parameters: List = - if (paramList == null) - List() - else - paramList.findChildren("parameter").map((n) -> new ParameterNode { node = n }) + parameters: List /// All object members (properties, methods, elements, entries, spreads, generators). - members: List = - if (memberList == null) - List() - else - memberList.children - .filter((c) -> isObjectMemberType(c.type)) - .map((n) -> wrapObjectMember(n)) + members: List fixed builtNode = let (self = this) @@ -997,32 +721,20 @@ class ObjectBodyNode extends SyntaxNode { /// An object property declaration. class ObjectPropertyNode extends ObjectMemberNode { - local propHeader: Node? = node?.findChild("object_property_header") - local headerBegin: Node? = propHeader?.findChild("object_property_header_begin") - /// The modifiers on the property. - modifiers: List = modifiersOf(headerBegin) + modifiers: List /// The property name. - name: String = identifierText(headerBegin) + identifier: IdentifierNode /// The type annotation, if present. - typeAnnotation: TypeNode? = - let (n = propHeader?.findChild("type_annotation")) - if (n == null) null else wrapTypeNode(n.findTypeChild()!!) + typeAnnotation: TypeNode? /// The value expression, if present (from `= expr`). - value: ExprNode? = - let (body = node?.findChild("object_property_body")) - if (body == null) - null - else - let (e = body.findExprChild()) - if (e == null) null else wrapExpr(e) + value: ExprNode? /// Object bodies for amending. - objectBodies: List = - node?.findChildren("object_body")?.map((n) -> new ObjectBodyNode { node = n }) ?? List() + objectBodies: List fixed builtNode = let (self = this) @@ -1036,7 +748,7 @@ class ObjectPropertyNode extends ObjectMemberNode { type = "object_property_header_begin" children = (if (self.modifiers.isEmpty) List() else List(modifierListNode(self.modifiers))) - + List((identifierLeaf) { text = self.name }) + + List(self.identifier.builtNode) }) + typeAnnotationNodes(self.typeAnnotation) }) @@ -1057,40 +769,23 @@ class ObjectPropertyNode extends ObjectMemberNode { /// An object method declaration. class ObjectMethodNode extends ObjectMemberNode { - local methodHeader: Node? = node?.findChild("class_method_header") - /// The modifiers on the method. - modifiers: List = modifiersOf(methodHeader) + modifiers: List /// The method name. - name: String = identifierText(methodHeader) + identifier: IdentifierNode /// The type parameters. - typeParameters: List = - let (tpl = node?.findChild("type_parameter_list")) - if (tpl == null) - List() - else - let (elems = tpl.findChild("type_parameter_list_elements")) - if (elems == null) - List() - else - elems.findChildren("type_parameter").map((n) -> new TypeParameterNode { node = n }) + typeParameters: List /// The parameters. - parameters: List = - let (pl = node?.findChild("parameter_list")) - parametersOf(pl!!) + parameters: List /// The return type annotation, if present. - returnType: TypeNode? = - let (n = node?.findChild("type_annotation")) - if (n == null) null else wrapTypeNode(n.findTypeChild()!!) + returnType: TypeNode? /// The method body expression. - body: ExprNode = - let (bodyNode = node?.findChild("class_method_body")) - wrapExpr(bodyNode!!.findExprChild()!!) + body: ExprNode fixed builtNode = let (self = this) @@ -1103,7 +798,7 @@ class ObjectMethodNode extends ObjectMemberNode { List( if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), (terminal) { text = "function" }, - (identifierLeaf) { text = self.name }, + self.identifier.builtNode, ).filterNonNull() }) + typeParameterListNodes(self.typeParameters) @@ -1122,7 +817,7 @@ class ObjectMethodNode extends ObjectMemberNode { /// An object element (a positional expression in an object body). class ObjectElementNode extends ObjectMemberNode { /// The expression value. - expression: ExprNode = wrapExpr(node?.findExprChild()!!) + expression: ExprNode fixed builtNode = let (self = this) @@ -1134,19 +829,14 @@ class ObjectElementNode extends ObjectMemberNode { /// An object entry (`[key] = value` or `[key] { ... }`). class ObjectEntryNode extends ObjectMemberNode { - local entryHeader: Node? = node?.findChild("object_entry_header") - /// The key expression. - key: ExprNode = wrapExpr(entryHeader?.findExprChild()!!) + key: ExprNode /// The value expression, if present (from `[key] = value`). - value: ExprNode? = - let (e = node?.findExprChildren()?.findOrNull((c) -> c != entryHeader?.findExprChild())) - if (e == null) null else wrapExpr(e) + value: ExprNode? /// Object bodies for amending. - objectBodies: List = - node?.findChildren("object_body")?.map((n) -> new ObjectBodyNode { node = n }) ?? List() + objectBodies: List fixed builtNode = let (self = this) @@ -1175,10 +865,10 @@ class ObjectEntryNode extends ObjectMemberNode { /// An object spread (`...expr` or `...?expr`). class ObjectSpreadNode extends ObjectMemberNode { /// Whether this is a nullable spread (`...?`). - isNullable: Boolean = terminals.firstOrNull?.text == "...?" + isNullable: Boolean /// The spread expression. - expression: ExprNode = wrapExpr(node?.findExprChild()!!) + expression: ExprNode fixed builtNode = let (self = this) @@ -1194,17 +884,14 @@ class ObjectSpreadNode extends ObjectMemberNode { /// A member predicate (`[[condition]] = value` or `[[condition]] { ... }`). class MemberPredicateNode extends ObjectMemberNode { - local exprs: List = node?.findExprChildren() ?? List() - /// The condition expression. - condition: ExprNode = wrapExpr(exprs.first) + condition: ExprNode /// The value expression, if present. - value: ExprNode? = if (exprs.length < 2) null else wrapExpr(exprs[1]) + value: ExprNode? /// Object bodies for amending. - objectBodies: List = - node?.findChildren("object_body")?.map((n) -> new ObjectBodyNode { node = n }) ?? List() + objectBodies: List fixed builtNode = let (self = this) @@ -1228,25 +915,17 @@ class MemberPredicateNode extends ObjectMemberNode { /// A `for (param in iterable) { ... }` generator. class ForGeneratorNode extends ObjectMemberNode { - local forHeader: Node? = node?.findChild("for_generator_header") - local forDef: Node? = forHeader?.findChild("for_generator_header_definition") - local forDefHeader: Node? = forDef?.findChild("for_generator_header_definition_header") - local paramNodes: List = forDefHeader?.findChildren("parameter") ?? List() - /// The key parameter (first parameter when two are present), if present. - keyParameter: ParameterNode? = - if (paramNodes.length < 2) null else new ParameterNode { node = paramNodes.first } + keyParameter: ParameterNode? /// The value parameter (or the only parameter when just one is present). - valueParameter: ParameterNode = new ParameterNode { node = paramNodes.last } + valueParameter: ParameterNode /// The iterable expression. - iterable: ExprNode = wrapExpr(forDef?.findExprChild()!!) + iterable: ExprNode /// The body. - body: ObjectBodyNode = - let (n = node?.findChild("object_body")) - new ObjectBodyNode { node = n!! } + body: ObjectBodyNode fixed builtNode = let (self = this) @@ -1292,18 +971,14 @@ class ForGeneratorNode extends ObjectMemberNode { /// A `when (condition) { ... }` generator. class WhenGeneratorNode extends ObjectMemberNode { - local whenHeader: Node? = node?.findChild("when_generator_header") - local bodyNodes: List = node?.findChildren("object_body") ?? List() - /// The condition expression. - condition: ExprNode = wrapExpr(whenHeader?.findExprChild()!!) + condition: ExprNode /// The "then" body. - thenBody: ObjectBodyNode = new ObjectBodyNode { node = bodyNodes.first } + thenBody: ObjectBodyNode /// The "else" body, if present. - elseBody: ObjectBodyNode? = - if (bodyNodes.length < 2) null else new ObjectBodyNode { node = bodyNodes[1] } + elseBody: ObjectBodyNode? fixed builtNode = let (self = this) @@ -1355,7 +1030,7 @@ class NullLiteralExprNode extends ExprNode { /// A boolean literal expression (`true` or `false`). class BoolLiteralExprNode extends ExprNode { /// The boolean value. - value: Boolean = node?.text == "true" + value: Boolean fixed builtNode = let (self = this) @@ -1365,7 +1040,7 @@ class BoolLiteralExprNode extends ExprNode { /// An integer literal expression. class IntLiteralExprNode extends ExprNode { /// The integer literal (e.g. `42`, `"0xFF"`). - value: Int | String = node?.text ?? "" + value: Int | String fixed builtNode = let (self = this) @@ -1375,7 +1050,7 @@ class IntLiteralExprNode extends ExprNode { /// A float literal expression. class FloatLiteralExprNode extends ExprNode { /// The float literal (e.g. `3.14`, `"1.0e10"`). - value: Float | String = node?.text ?? "" + value: Float | String fixed builtNode = let (self = this) @@ -1385,7 +1060,7 @@ class FloatLiteralExprNode extends ExprNode { /// A single-line string literal expression. class SingleLineStringLiteralExprNode extends ExprNode { /// The string parts (chars, escapes, interpolations). - parts: List = buildStringParts(children) + parts: List fixed builtNode = let (self = this) @@ -1403,7 +1078,7 @@ class SingleLineStringLiteralExprNode extends ExprNode { /// Use [StringNewlineNode] entries in [parts] to separate lines. class MultiLineStringLiteralExprNode extends ExprNode { /// The string parts (chars, escapes, newlines, interpolations). - parts: List = buildStringParts(children) + parts: List fixed builtNode = let (self = this) @@ -1425,19 +1100,17 @@ class MultiLineStringLiteralExprNode extends ExprNode { /// An unqualified access expression (`name` or `name(args)`). class UnqualifiedAccessExprNode extends ExprNode { /// The identifier being accessed. - identifier: String = identifierText(node!!) + identifier: IdentifierNode /// The arguments, if this is a function call. Null for a plain identifier access. - arguments: List? = - let (n = node?.findChild("argument_list")) - if (n == null) null else argumentsOf(n) + arguments: List? fixed builtNode = let (self = this) new Node { type = "unqualified_access_expr" children = - List((identifierLeaf) { text = self.identifier }) + List(self.identifier.builtNode) + (if (self.arguments == null) List() else List(argumentListNode(self.arguments!!))) } } @@ -1446,21 +1119,16 @@ class UnqualifiedAccessExprNode extends ExprNode { /// optionally with arguments for method calls). class QualifiedAccessExprNode extends ExprNode { /// The receiver expression. - receiver: ExprNode = wrapExpr((node?.findExprChildren() ?? List()).first) + receiver: ExprNode /// Whether this is a null-safe access (`?.`). - isNullSafe: Boolean = node?.findChild("operator")?.text == "?." + isNullSafe: Boolean /// The accessed member name. - member: String = - let (m = (node?.findChildren("unqualified_access_expr") ?? List()).last) - identifierText(m) + identifier: IdentifierNode /// The arguments, if this is a method call. Null for a property access. - arguments: List? = - let (m = (node?.findChildren("unqualified_access_expr") ?? List()).last) - let (n = m.findChild("argument_list")) - if (n == null) null else argumentsOf(n) + arguments: List? fixed builtNode = let (self = this) @@ -1473,7 +1141,7 @@ class QualifiedAccessExprNode extends ExprNode { new Node { type = "unqualified_access_expr" children = - List((identifierLeaf) { text = self.member }) + List(self.identifier.builtNode) + ( if (self.arguments == null) List() else List(argumentListNode(self.arguments!!)) ) @@ -1485,10 +1153,10 @@ class QualifiedAccessExprNode extends ExprNode { /// A subscript expression (`receiver[index]`). class SubscriptExprNode extends ExprNode { /// The receiver expression. - receiver: ExprNode = wrapExpr(node?.findExprChildren().first) + receiver: ExprNode /// The index expression. - index: ExprNode = wrapExpr(node?.findExprChildren().getOrNull(1)!!) + index: ExprNode fixed builtNode = let (self = this) @@ -1520,22 +1188,14 @@ class SuperSubscriptExprNode extends ExprNode { /// An `if (condition) thenExpr else elseExpr` expression. class IfExprNode extends ExprNode { - local ifHeader: Node = node?.findChild("if_header")!! - local ifCondition: Node = ifHeader.findChild("if_condition")!! - local ifConditionExpr: Node = ifCondition.findChild("if_condition_expr")!! - /// The condition expression. - condition: ExprNode = wrapExpr(ifConditionExpr.findExprChild()!!) + condition: ExprNode /// The then-branch expression. - thenExpr: ExprNode = - let (thenNode = node?.findChild("if_then_expr")) - wrapExpr(thenNode!!.findExprChild()!!) + thenExpr: ExprNode /// The else-branch expression. - elseExpr: ExprNode = - let (elseNode = node?.findChild("if_else_expr")) - wrapExpr(elseNode!!.findExprChild()!!) + elseExpr: ExprNode fixed builtNode = let (self = this) @@ -1577,19 +1237,14 @@ class IfExprNode extends ExprNode { /// A `let (param = value) body` expression. class LetExprNode extends ExprNode { - local letParamDef: Node = node?.findChild("let_parameter_definition")!! - local letParam: Node = letParamDef.findChild("let_parameter")!! - /// The let-binding parameter. - parameter: ParameterNode = - let (p = letParam.findChild("parameter")) - new ParameterNode { node = p!! } + parameter: ParameterNode /// The binding value expression. - bindingValue: ExprNode = wrapExpr(letParam.findExprChild()!!) + bindingValue: ExprNode /// The body expression. - body: ExprNode = wrapExpr(node?.findExprChild()!!) + body: ExprNode fixed builtNode = let (self = this) @@ -1623,7 +1278,7 @@ class LetExprNode extends ExprNode { /// A `throw(expr)` expression. class ThrowExprNode extends ExprNode { /// The expression being thrown. - expression: ExprNode = wrapExpr(node?.findExprChild()!!) + expression: ExprNode fixed builtNode = let (self = this) @@ -1642,7 +1297,7 @@ class ThrowExprNode extends ExprNode { /// A `trace(expr)` expression. class TraceExprNode extends ExprNode { /// The expression being traced. - expression: ExprNode = wrapExpr(node?.findExprChild()!!) + expression: ExprNode fixed builtNode = let (self = this) @@ -1661,10 +1316,10 @@ class TraceExprNode extends ExprNode { /// An `import("uri")` or `import*("uri")` expression. class ImportExprNode extends ExprNode { /// Whether this is a glob import expression (`import*`). - isGlob: Boolean = terminals.firstOrNull?.text == "import*" + isGlob: Boolean /// The import URI string. - uri: String = getStringChars(node) + uri: String fixed builtNode = let (self = this) @@ -1683,11 +1338,10 @@ class ImportExprNode extends ExprNode { /// A `read(expr)`, `read*(expr)`, or `read?(expr)` expression. class ReadExprNode extends ExprNode { /// The keyword used (`"read"`, `"read?"`, or `"read*"`). - keyword: "read" | "read?" | "read*" = - (terminals.firstOrNull?.text ?? "read") as "read" | "read?" | "read*" + keyword: "read" | "read?" | "read*" /// The expression to be read. - expression: ExprNode = wrapExpr(node?.findExprChild()!!) + expression: ExprNode fixed builtNode = let (self = this) @@ -1705,17 +1359,11 @@ class ReadExprNode extends ExprNode { /// A `new Type { ... }` expression. class NewExprNode extends ExprNode { - local newHeader: Node = node?.findChild("new_header")!! - /// The type being constructed, if present. - type: TypeNode? = - let (t = newHeader.findTypeChild()) - if (t == null) null else wrapTypeNode(t) + type: TypeNode? /// The object body. - body: ObjectBodyNode = - let (n = node?.findChild("object_body")) - new ObjectBodyNode { node = n!! } + body: ObjectBodyNode fixed builtNode = let (self = this) @@ -1739,12 +1387,10 @@ class NewExprNode extends ExprNode { /// An `(expr) { ... }` amends expression. class AmendsExprNode extends ExprNode { /// The expression being amended. - parentExpr: ExprNode = wrapExpr((node?.findExprChildren() ?? List()).first) + parentExpr: ExprNode /// The object body. - body: ObjectBodyNode = - let (n = node?.findChild("object_body")) - new ObjectBodyNode { node = n!! } + body: ObjectBodyNode fixed builtNode = let (self = this) @@ -1756,21 +1402,17 @@ class AmendsExprNode extends ExprNode { /// A binary operator expression (`left op right`), including `is`/`as`. class BinaryOpExprNode extends ExprNode { - local exprs: List = node?.findExprChildren() ?? List() - /// The operator string. - operator: String = node?.findChild("operator")?.text ?? "" + operator: String /// The left-hand expression. - left: ExprNode = wrapExpr(exprs.first) + left: ExprNode /// The right-hand expression, if present (not present for `is`/`as` which use [rightType]). - right: ExprNode? = if (exprs.length < 2) null else wrapExpr(exprs[1]) + right: ExprNode? /// The right-hand type, if this is an `is` or `as` operation. - rightType: TypeNode? = - let (t = node?.findTypeChild()) - if (t == null) null else wrapTypeNode(t) + rightType: TypeNode? fixed builtNode = let (self = this) @@ -1795,7 +1437,7 @@ class BinaryOpExprNode extends ExprNode { /// A unary minus expression (`-expr`). class UnaryMinusExprNode extends ExprNode { /// The operand expression. - operand: ExprNode = wrapExpr(node?.findExprChild()!!) + operand: ExprNode fixed builtNode = let (self = this) @@ -1808,7 +1450,7 @@ class UnaryMinusExprNode extends ExprNode { /// A logical not expression (`!expr`). class LogicalNotExprNode extends ExprNode { /// The operand expression. - operand: ExprNode = wrapExpr(node?.findExprChild()!!) + operand: ExprNode fixed builtNode = let (self = this) @@ -1821,7 +1463,7 @@ class LogicalNotExprNode extends ExprNode { /// A non-null assertion expression (`expr!!`). class NonNullExprNode extends ExprNode { /// The operand expression. - operand: ExprNode = wrapExpr(node?.findExprChild()!!) + operand: ExprNode fixed builtNode = let (self = this) @@ -1834,14 +1476,10 @@ class NonNullExprNode extends ExprNode { /// A function literal expression (`(params) -> body`). class FunctionLiteralExprNode extends ExprNode { /// The parameters. - parameters: List = - let (pl = node?.findChild("parameter_list")) - parametersOf(pl!!) + parameters: List /// The body expression. - body: ExprNode = - let (bodyNode = node?.findChild("function_literal_body")) - wrapExpr(bodyNode!!.findExprChild()!!) + body: ExprNode fixed builtNode = let (self = this) @@ -1862,13 +1500,7 @@ class FunctionLiteralExprNode extends ExprNode { /// A parenthesized expression (`(expr)`). class ParenthesizedExprNode extends ExprNode { /// The inner expression, if present (may be empty for `()`). - expression: ExprNode? = - let (elems = node?.findChild("parenthesized_expr_elements")) - if (elems == null) - null - else - let (e = elems.findExprChild()) - if (e == null) null else wrapExpr(e) + expression: ExprNode? fixed builtNode = let (self = this) @@ -1903,19 +1535,11 @@ class ModuleTypeNode extends TypeNode { /// A declared type (e.g., `String`, `List`). class DeclaredTypeNode extends TypeNode { - /// The type name (dotted, e.g. `"List"` or `"foo.Bar"`). - name: String = - let (n = node?.findChild("qualified_identifier")) - qualifiedName(n!!) + /// The type name (dotted, e.g. `List` or `foo.Bar`). + name: QualifiedIdentifierNode /// The type arguments. - typeArguments: List = - let (tal = node?.findChild("type_argument_list")) - if (tal == null) - List() - else - let (elems = tal.findChild("type_argument_list_elements")) - if (elems == null) List() else elems.findTypeChildren().map((n) -> wrapTypeNode(n)) + typeArguments: List fixed builtNode = let (self = this) @@ -1923,9 +1547,9 @@ class DeclaredTypeNode extends TypeNode { type = "declared_type" children = if (self.typeArguments.isEmpty) - List(qualifiedIdentifierNode(self.name)) + List(self.name.builtNode) else - List(qualifiedIdentifierNode(self.name), new Node { + List(self.name.builtNode, new Node { type = "type_argument_list" children = List( @@ -1943,7 +1567,7 @@ class DeclaredTypeNode extends TypeNode { /// A nullable type (`Type?`). class NullableTypeNode extends TypeNode { /// The base type. - baseType: TypeNode = wrapTypeNode(node?.findTypeChild()!!) + baseType: TypeNode fixed builtNode = let (self = this) @@ -1956,7 +1580,7 @@ class NullableTypeNode extends TypeNode { /// A union type (`TypeA|TypeB|TypeC`). class UnionTypeNode extends TypeNode { /// The member types. - members: List = node?.findTypeChildren()?.map((n) -> wrapTypeNode(n)) ?? List() + members: List fixed builtNode = let (self = this) @@ -1973,15 +1597,11 @@ class UnionTypeNode extends TypeNode { /// A function type (`(ParamTypes) -> ReturnType`). class FunctionTypeNode extends TypeNode { - local params: Node = node?.findChild("function_type_parameters")!! - local paramElems: Node? = params.findChild("parenthesized_type_elements") - /// The parameter types. - parameterTypes: List = - if (paramElems == null) List() else paramElems.findTypeChildren().map((n) -> wrapTypeNode(n)) + parameterTypes: List /// The return type. - returnType: TypeNode = wrapTypeNode((node?.findTypeChildren() ?? List()).last) + returnType: TypeNode fixed builtNode = let (self = this) @@ -2013,13 +1633,10 @@ class FunctionTypeNode extends TypeNode { /// A constrained type (`Type(constraint)`). class ConstrainedTypeNode extends TypeNode { /// The base type. - baseType: TypeNode = wrapTypeNode(node?.findTypeChild()!!) - - local constraint: Node = node?.findChild("constrained_type_constraint")!! - local constraintElems: Node = constraint.findChild("constrained_type_elements")!! + baseType: TypeNode /// The constraint expressions. - constraints: List = constraintElems.findExprChildren().map((n) -> wrapExpr(n)) + constraints: List fixed builtNode = let (self = this) @@ -2044,13 +1661,7 @@ class ConstrainedTypeNode extends TypeNode { /// A parenthesized type (`(Type)`). class ParenthesizedTypeNode extends TypeNode { /// The inner type, if present. - type: TypeNode? = - let (elems = node?.findChild("parenthesized_type_elements")) - if (elems == null) - null - else - let (t = elems.findTypeChild()) - if (t == null) null else wrapTypeNode(t) + type: TypeNode? fixed builtNode = let (self = this) @@ -2071,7 +1682,7 @@ class ParenthesizedTypeNode extends TypeNode { /// A string constant type (e.g., `"foo"`). class StringConstantTypeNode extends TypeNode { /// The string value. - value: String = getStringChars(node) + value: String fixed builtNode = let (self = this) @@ -2084,12 +1695,10 @@ class StringConstantTypeNode extends TypeNode { /// An annotation (`@Type { ... }`). class AnnotationNode extends SyntaxNode { /// The annotation type. - type: TypeNode = wrapTypeNode(node?.findTypeChild()!!) + type: TypeNode /// The annotation body, if present. - body: ObjectBodyNode? = - let (n = node?.findChild("object_body")) - if (n == null) null else new ObjectBodyNode { node = n } + body: ObjectBodyNode? fixed builtNode = let (self = this) @@ -2107,34 +1716,25 @@ class AnnotationNode extends SyntaxNode { /// A parameter declaration (`name`, `name: Type`, or `_`). class ParameterNode extends SyntaxNode { /// Whether this is a blank identifier parameter (`_`). - isBlankIdentifier: Boolean = name == "_" - - /// The parameter name. Use `"_"` for a wildcard parameter. - name: String = - let (id = node?.findChild("identifier")) - if (id != null) - id.text ?? "" - else if (children.findOrNull((c) -> c.type == "terminal" && c.text == "_") != null) - "_" - else - "" + isBlankIdentifier: Boolean + + /// The parameter name, or `null` for a wildcard parameter (`_`). + identifier: IdentifierNode? /// The type annotation, if present. - typeAnnotation: TypeNode? = - let (n = node?.findChild("type_annotation")) - if (n == null) null else wrapTypeNode(n.findTypeChild()!!) + typeAnnotation: TypeNode? fixed builtNode = let (self = this) new Node { type = "parameter" children = - if (self.name == "_") + if (self.identifier == null) List((terminal) { text = "_" }) else if (self.typeAnnotation == null) - List((identifierLeaf) { text = self.name }) + List(self.identifier.builtNode) else - List((identifierLeaf) { text = self.name }) + List(self.identifier.builtNode) + typeAnnotationNodes(self.typeAnnotation) } } @@ -2142,11 +1742,10 @@ class ParameterNode extends SyntaxNode { /// A type parameter declaration (`T`, `in T`, or `out T`). class TypeParameterNode extends SyntaxNode { /// The variance modifier (`"in"`, `"out"`, or null). - variance: ("in" | "out")? = - terminals.findOrNull((t) -> t.text == "in" || t.text == "out")?.text as ("in" | "out")? + variance: ("in" | "out")? /// The type parameter name. - name: String = identifierText(node!!) + identifier: IdentifierNode fixed builtNode = let (self = this) @@ -2154,20 +1753,47 @@ class TypeParameterNode extends SyntaxNode { type = "type_parameter" children = if (self.variance == null) - List((identifierLeaf) { text = self.name }) + List(self.identifier.builtNode) else - List((terminal) { text = self.variance!! }, (identifierLeaf) { text = self.name }) + List((terminal) { text = self.variance!! }, self.identifier.builtNode) + } +} + +/// An identifier (a name occurring in the source, e.g. a property or class name). +class IdentifierNode extends SyntaxNode { + /// The identifier text. + value: String + + fixed builtNode = + let (self = this) + new Node { type = "identifier"; text = self.value } +} + +/// A qualified (dotted) identifier, e.g. `foo.bar.baz`. +class QualifiedIdentifierNode extends SyntaxNode { + /// The parts of the qualified identifier. + identifiers: List + + /// The dotted name (e.g. `"foo.bar.baz"`). + value: String + + fixed builtNode = + let (self = this) + new Node { + type = "qualified_identifier" + children = + self.identifiers + .map((i) -> i.builtNode) + .fold(List(), (acc: List, item: Node) -> + if (acc.isEmpty) List(item) else acc.add((terminal) { text = "." }).add(item) + ) } } /// A doc comment. class DocCommentNode extends SyntaxNode { /// The body text of each line, without the leading `///`. - lines: List = - (node?.findChildren("doc_comment_line") ?? List()).map((n) -> - let (l = n.text ?? "") - if (l.startsWith("///")) l.drop(3) else l - ) + lines: List fixed builtNode = let (self = this) @@ -2189,7 +1815,7 @@ abstract class StringPartNode { /// A plain text part of a string literal. class StringCharsNode extends StringPartNode { /// The text content. - value: String = node?.text ?? "" + value: String function toNodes(): List = let (self = this) @@ -2199,7 +1825,7 @@ class StringCharsNode extends StringPartNode { /// An escape sequence in a string literal (e.g., `"\\n"`, `"\\t"`). class StringEscapeNode extends StringPartNode { /// The escape sequence text including the leading backslash. - value: String = node?.text ?? "" + value: String function toNodes(): List = let (self = this) @@ -2214,7 +1840,7 @@ class StringNewlineNode extends StringPartNode { /// An interpolation in a string literal (`\(expr)`). class StringInterpolationNode extends StringPartNode { /// The interpolated expression. - expression: ExprNode = wrapExpr(node!!) + expression: ExprNode function toNodes(): List = let (self = this) @@ -2225,80 +1851,12 @@ class StringInterpolationNode extends StringPartNode { ) } -/// Build string parts from the children of a string literal node. -local const function buildStringParts(cs: List): List = - // skip opening and closing terminals - let (inner = cs.drop(1).dropLast(1)) - buildStringPartsInner(inner, 0, List()) - -local const function buildStringPartsInner( - cs: List, - i: Int, - acc: List, -): List = - if (i >= cs.length) - acc - else - let (c = cs[i]) - if (c.type == "string_chars") - buildStringPartsInner(cs, i + 1, acc.add(new StringCharsNode { node = c })) - else if (c.type == "string_escape") - buildStringPartsInner(cs, i + 1, acc.add(new StringEscapeNode { node = c })) - else if (c.type == "string_newline") - buildStringPartsInner(cs, i + 1, acc.add(new StringNewlineNode { node = c })) - else if (c.type == "terminal" && isInterpolationStart(c)) - let (exprAndClose = findInterpolationExpr(cs, i + 1)) - buildStringPartsInner( - cs, - exprAndClose.second, - acc.add(new StringInterpolationNode { node = exprAndClose.first }), - ) - else if (c.type == "line_comment" || c.type == "block_comment" || c.type == "semicolon") - // skip affixes - buildStringPartsInner(cs, i + 1, acc) - else - // skip other terminals (shouldn't normally happen) - buildStringPartsInner(cs, i + 1, acc) - -local const function isInterpolationStart(n: Node): Boolean = - let (t = n.text) - if (t == null) - false - else - t.endsWith("(") && (t.startsWith("\\") || t.startsWith("#")) - -/// Find the interpolation expression and return it along with the index past the closing paren. -local const function findInterpolationExpr(cs: List, startIdx: Int): Pair = - // walk forward to find the expression node (skip affixes) - let (exprIdx = findNextNonAffix(cs, startIdx)) - if (exprIdx >= cs.length) - Pair(cs[startIdx - 1], cs.length) - else - let (exprNode = cs[exprIdx]) - // next should be the closing terminal ")" - let (closeIdx = findNextNonAffix(cs, exprIdx + 1)) - Pair(exprNode, closeIdx + 1) - -local const function findNextNonAffix(cs: List, startIdx: Int): Int = - if (startIdx >= cs.length) - startIdx - else if ( - cs[startIdx].type == "line_comment" - || cs[startIdx].type == "block_comment" - || cs[startIdx].type == "semicolon" - ) - findNextNonAffix(cs, startIdx + 1) - else - startIdx - // =============== // Node construction helpers // =============== local const terminal: Node = new Node { type = "terminal" } -local const identifierLeaf: Node = new Node { type = "identifier" } - local const operatorLeaf: Node = new Node { type = "operator" } local const commaTerminal: Node = new Node { type = "terminal"; text = "," } @@ -2315,20 +1873,6 @@ local const function modifierListNode(mods: List): Node = new Node { children = mods.map((m) -> new Node { type = "modifier"; text = m }) } -// Build a `qualified_identifier` node from a dotted name like `"a.b.c"`. -local const function qualifiedIdentifierNode(qname: String): Node = new Node { - type = "qualified_identifier" - children = - qname - .split(".") - .fold(List(), (acc: List, part: String) -> - if (acc.isEmpty) - List((identifierLeaf) { text = part }) - else - acc.add((terminal) { text = "." }).add((identifierLeaf) { text = part }) - ) -} - // Build a quoted `string_chars` node for a string constant like `"foo"`. // Parsed nodes derive `text` from source, built nodes must set it themselves. local const function stringCharsNode(value: String): Node = new Node { @@ -2342,19 +1886,6 @@ local const function stringCharsNode(value: String): Node = new Node { ) } -// Read the parameters of a `parameter_list` node. -local const function parametersOf(pl: Node?): List = - let (elems = pl?.findChild("parameter_list_elements")) - if (elems == null) - List() - else - elems.findChildren("parameter").map((n) -> new ParameterNode { node = n }) - -// Read the argument expressions of an `argument_list` node. -local const function argumentsOf(al: Node?): List = - let (elems = al?.findChild("argument_list_elements")) - if (elems == null) List() else elems.findExprChildren().map((n) -> wrapExpr(n)) - // Build a `parameter_list` node from typed parameters. local const function parameterListNode(parameters: List): Node = new Node { type = "parameter_list" @@ -2416,175 +1947,3 @@ local const function typeAnnotationNodes(_type: TypeNode?): List = type = "type_annotation" children = List((terminal) { text = ":" }, _type.builtNode) }) - -// =============== -// Visitor -// =============== - -/// A type-safe tree visitor for use with [visit]. -/// -/// See [visit] for the descent and rebuild semantics. -class Visitor { - // module structure - visitModule: (ModuleNode) -> Pair? = (_) -> null - visitModuleDeclaration: (ModuleDeclarationNode) -> Pair? = (_) -> null - visitImport: (ImportNode) -> Pair? = (_) -> null - visitClass: (ClassNode) -> Pair? = (_) -> null - visitTypeAlias: (TypeAliasNode) -> Pair? = (_) -> null - visitClassBody: (ClassBodyNode) -> Pair? = (_) -> null - visitClassProperty: (ClassPropertyNode) -> Pair? = (_) -> null - visitClassMethod: (ClassMethodNode) -> Pair? = (_) -> null - visitObjectBody: (ObjectBodyNode) -> Pair? = (_) -> null - visitAnnotation: (AnnotationNode) -> Pair? = (_) -> null - visitParameter: (ParameterNode) -> Pair? = (_) -> null - visitTypeParameter: (TypeParameterNode) -> Pair? = (_) -> null - visitDocComment: (DocCommentNode) -> Pair? = (_) -> null - - // object members - visitObjectProperty: (ObjectPropertyNode) -> Pair? = (_) -> null - visitObjectMethod: (ObjectMethodNode) -> Pair? = (_) -> null - visitObjectElement: (ObjectElementNode) -> Pair? = (_) -> null - visitObjectEntry: (ObjectEntryNode) -> Pair? = (_) -> null - visitObjectSpread: (ObjectSpreadNode) -> Pair? = (_) -> null - visitMemberPredicate: (MemberPredicateNode) -> Pair? = (_) -> null - visitForGenerator: (ForGeneratorNode) -> Pair? = (_) -> null - visitWhenGenerator: (WhenGeneratorNode) -> Pair? = (_) -> null - - // expressions - visitThisExpr: (ThisExprNode) -> Pair? = (_) -> null - visitOuterExpr: (OuterExprNode) -> Pair? = (_) -> null - visitModuleExpr: (ModuleExprNode) -> Pair? = (_) -> null - visitNullLiteralExpr: (NullLiteralExprNode) -> Pair? = (_) -> null - visitBoolLiteralExpr: (BoolLiteralExprNode) -> Pair? = (_) -> null - visitIntLiteralExpr: (IntLiteralExprNode) -> Pair? = (_) -> null - visitFloatLiteralExpr: (FloatLiteralExprNode) -> Pair? = (_) -> null - visitSingleLineStringLiteralExpr: (SingleLineStringLiteralExprNode) -> Pair? = ( - _, - ) -> null - visitMultiLineStringLiteralExpr: (MultiLineStringLiteralExprNode) -> Pair? = ( - _, - ) -> null - visitUnqualifiedAccessExpr: (UnqualifiedAccessExprNode) -> Pair? = (_) -> - null - visitQualifiedAccessExpr: (QualifiedAccessExprNode) -> Pair? = (_) -> null - visitSubscriptExpr: (SubscriptExprNode) -> Pair? = (_) -> null - visitSuperAccessExpr: (SuperAccessExprNode) -> Pair? = (_) -> null - visitSuperSubscriptExpr: (SuperSubscriptExprNode) -> Pair? = (_) -> null - visitIfExpr: (IfExprNode) -> Pair? = (_) -> null - visitLetExpr: (LetExprNode) -> Pair? = (_) -> null - visitThrowExpr: (ThrowExprNode) -> Pair? = (_) -> null - visitTraceExpr: (TraceExprNode) -> Pair? = (_) -> null - visitImportExpr: (ImportExprNode) -> Pair? = (_) -> null - visitReadExpr: (ReadExprNode) -> Pair? = (_) -> null - visitNewExpr: (NewExprNode) -> Pair? = (_) -> null - visitAmendsExpr: (AmendsExprNode) -> Pair? = (_) -> null - visitBinaryOpExpr: (BinaryOpExprNode) -> Pair? = (_) -> null - visitUnaryMinusExpr: (UnaryMinusExprNode) -> Pair? = (_) -> null - visitLogicalNotExpr: (LogicalNotExprNode) -> Pair? = (_) -> null - visitNonNullExpr: (NonNullExprNode) -> Pair? = (_) -> null - visitFunctionLiteralExpr: (FunctionLiteralExprNode) -> Pair? = (_) -> null - visitParenthesizedExpr: (ParenthesizedExprNode) -> Pair? = (_) -> null - - // types - visitUnknownType: (UnknownTypeNode) -> Pair? = (_) -> null - visitNothingType: (NothingTypeNode) -> Pair? = (_) -> null - visitModuleType: (ModuleTypeNode) -> Pair? = (_) -> null - visitDeclaredType: (DeclaredTypeNode) -> Pair? = (_) -> null - visitNullableType: (NullableTypeNode) -> Pair? = (_) -> null - visitUnionType: (UnionTypeNode) -> Pair? = (_) -> null - visitFunctionType: (FunctionTypeNode) -> Pair? = (_) -> null - visitConstrainedType: (ConstrainedTypeNode) -> Pair? = (_) -> null - visitParenthesizedType: (ParenthesizedTypeNode) -> Pair? = (_) -> null - visitStringConstantType: (StringConstantTypeNode) -> Pair? = (_) -> null -} - -// Map a typed visitor result into the raw-node result that `walk` consumes. -local const function toWalkResult(r: Pair?): Pair? = - if (r == null) null else Pair(r.first.builtNode, r.second) - -// Dispatch a raw node to the matching visitor callback (or `null` if none applies). -local function dispatch(n: Node, v: Visitor): Pair? = - let (call = dispatchers.getOrNull(n.type)) - if (call == null) null else toWalkResult(call.apply(n, v)) - -local dispatchers: Map Pair?> = - Map( - // module structure - "module", (n, v) -> v.visitModule.apply(new ModuleNode { node = n }), - "module_declaration", - (n, v) -> v.visitModuleDeclaration.apply(new ModuleDeclarationNode { node = n }), - "import", (n, v) -> v.visitImport.apply(new ImportNode { node = n }), - "class", (n, v) -> v.visitClass.apply(new ClassNode { node = n }), - "typealias", (n, v) -> v.visitTypeAlias.apply(new TypeAliasNode { node = n }), - "class_body", (n, v) -> v.visitClassBody.apply(new ClassBodyNode { node = n }), - "class_property", (n, v) -> v.visitClassProperty.apply(new ClassPropertyNode { node = n }), - "class_method", (n, v) -> v.visitClassMethod.apply(new ClassMethodNode { node = n }), - "object_body", (n, v) -> v.visitObjectBody.apply(new ObjectBodyNode { node = n }), - "annotation", (n, v) -> v.visitAnnotation.apply(new AnnotationNode { node = n }), - "parameter", (n, v) -> v.visitParameter.apply(new ParameterNode { node = n }), - "type_parameter", (n, v) -> v.visitTypeParameter.apply(new TypeParameterNode { node = n }), - "doc_comment", (n, v) -> v.visitDocComment.apply(new DocCommentNode { node = n }), - // object members - "object_property", (n, v) -> v.visitObjectProperty.apply(new ObjectPropertyNode { node = n }), - "object_method", (n, v) -> v.visitObjectMethod.apply(new ObjectMethodNode { node = n }), - "object_element", (n, v) -> v.visitObjectElement.apply(new ObjectElementNode { node = n }), - "object_entry", (n, v) -> v.visitObjectEntry.apply(new ObjectEntryNode { node = n }), - "object_spread", (n, v) -> v.visitObjectSpread.apply(new ObjectSpreadNode { node = n }), - "member_predicate", (n, v) -> v.visitMemberPredicate.apply(new MemberPredicateNode { node = n }), - "for_generator", (n, v) -> v.visitForGenerator.apply(new ForGeneratorNode { node = n }), - "when_generator", (n, v) -> v.visitWhenGenerator.apply(new WhenGeneratorNode { node = n }), - // expressions - "this_expr", (n, v) -> v.visitThisExpr.apply(new ThisExprNode { node = n }), - "outer_expr", (n, v) -> v.visitOuterExpr.apply(new OuterExprNode { node = n }), - "module_expr", (n, v) -> v.visitModuleExpr.apply(new ModuleExprNode { node = n }), - "null_expr", (n, v) -> v.visitNullLiteralExpr.apply(new NullLiteralExprNode { node = n }), - "bool_literal_expr", - (n, v) -> v.visitBoolLiteralExpr.apply(new BoolLiteralExprNode { node = n }), - "int_literal_expr", (n, v) -> v.visitIntLiteralExpr.apply(new IntLiteralExprNode { node = n }), - "float_literal_expr", - (n, v) -> v.visitFloatLiteralExpr.apply(new FloatLiteralExprNode { node = n }), - "single_line_string_literal_expr", - (n, v) -> - v.visitSingleLineStringLiteralExpr.apply(new SingleLineStringLiteralExprNode { node = n }), - "multi_line_string_literal_expr", - (n, v) -> - v.visitMultiLineStringLiteralExpr.apply(new MultiLineStringLiteralExprNode { node = n }), - "unqualified_access_expr", - (n, v) -> v.visitUnqualifiedAccessExpr.apply(new UnqualifiedAccessExprNode { node = n }), - "qualified_access_expr", - (n, v) -> v.visitQualifiedAccessExpr.apply(new QualifiedAccessExprNode { node = n }), - "subscript_expr", (n, v) -> v.visitSubscriptExpr.apply(new SubscriptExprNode { node = n }), - "super_access_expr", - (n, v) -> v.visitSuperAccessExpr.apply(new SuperAccessExprNode { node = n }), - "super_subscript_expr", - (n, v) -> v.visitSuperSubscriptExpr.apply(new SuperSubscriptExprNode { node = n }), - "if_expr", (n, v) -> v.visitIfExpr.apply(new IfExprNode { node = n }), - "let_expr", (n, v) -> v.visitLetExpr.apply(new LetExprNode { node = n }), - "throw_expr", (n, v) -> v.visitThrowExpr.apply(new ThrowExprNode { node = n }), - "trace_expr", (n, v) -> v.visitTraceExpr.apply(new TraceExprNode { node = n }), - "import_expr", (n, v) -> v.visitImportExpr.apply(new ImportExprNode { node = n }), - "read_expr", (n, v) -> v.visitReadExpr.apply(new ReadExprNode { node = n }), - "new_expr", (n, v) -> v.visitNewExpr.apply(new NewExprNode { node = n }), - "amends_expr", (n, v) -> v.visitAmendsExpr.apply(new AmendsExprNode { node = n }), - "binary_op_expr", (n, v) -> v.visitBinaryOpExpr.apply(new BinaryOpExprNode { node = n }), - "unary_minus_expr", (n, v) -> v.visitUnaryMinusExpr.apply(new UnaryMinusExprNode { node = n }), - "logical_not_expr", (n, v) -> v.visitLogicalNotExpr.apply(new LogicalNotExprNode { node = n }), - "non_null_expr", (n, v) -> v.visitNonNullExpr.apply(new NonNullExprNode { node = n }), - "function_literal_expr", - (n, v) -> v.visitFunctionLiteralExpr.apply(new FunctionLiteralExprNode { node = n }), - "parenthesized_expr", - (n, v) -> v.visitParenthesizedExpr.apply(new ParenthesizedExprNode { node = n }), - // types - "unknown_type", (n, v) -> v.visitUnknownType.apply(new UnknownTypeNode { node = n }), - "nothing_type", (n, v) -> v.visitNothingType.apply(new NothingTypeNode { node = n }), - "module_type", (n, v) -> v.visitModuleType.apply(new ModuleTypeNode { node = n }), - "declared_type", (n, v) -> v.visitDeclaredType.apply(new DeclaredTypeNode { node = n }), - "nullable_type", (n, v) -> v.visitNullableType.apply(new NullableTypeNode { node = n }), - "union_type", (n, v) -> v.visitUnionType.apply(new UnionTypeNode { node = n }), - "function_type", (n, v) -> v.visitFunctionType.apply(new FunctionTypeNode { node = n }), - "constrained_type", (n, v) -> v.visitConstrainedType.apply(new ConstrainedTypeNode { node = n }), - "parenthesized_type", - (n, v) -> v.visitParenthesizedType.apply(new ParenthesizedTypeNode { node = n }), - "string_constant_type", - (n, v) -> v.visitStringConstantType.apply(new StringConstantTypeNode { node = n }), - ) From 83344ed760d1af95963be84ba7e63e345c2ffd9e Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Wed, 22 Jul 2026 14:03:03 +0200 Subject: [PATCH 13/49] Move builtNode to Java --- .../pkl/core/stdlib/syntax/ParserNodes.java | 9 +- .../core/stdlib/syntax/SyntaxNodeNodes.java | 887 ++++++++++++++ .../input/syntax/walk.pkl | 42 +- .../output/syntax/walk.pcf | 5 +- stdlib/syntax.pkl | 1034 +---------------- 5 files changed, 952 insertions(+), 1025 deletions(-) create mode 100644 pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 7dafe90e7..9a5a097b1 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -238,9 +238,14 @@ private static VmObjectFactory nodeOnlyFactory(Supplier classS .addTypedProperty("receiver", ParserNodes::subscriptReceiver) .addTypedProperty("index", ParserNodes::subscriptIndex); private static final VmObjectFactory superAccessExprNodeFactory = - nodeOnlyFactory(SyntaxModule::getSuperAccessExprNodeClass); + new VmObjectFactory(SyntaxModule::getSuperAccessExprNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("identifier", ParserNodes::identifierNodeOf) + .addProperty("arguments", ParserNodes::argumentsOrNull); private static final VmObjectFactory superSubscriptExprNodeFactory = - nodeOnlyFactory(SyntaxModule::getSuperSubscriptExprNodeClass); + new VmObjectFactory(SyntaxModule::getSuperSubscriptExprNodeClass) + .addProperty("node", vm -> vm) + .addTypedProperty("index", ParserNodes::soleExpr); private static final VmObjectFactory ifExprNodeFactory = new VmObjectFactory(SyntaxModule::getIfExprNodeClass) .addProperty("node", vm -> vm) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java new file mode 100644 index 000000000..0e3395640 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java @@ -0,0 +1,887 @@ +/* + * Copyright © 2026 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.stdlib.syntax; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.Specialization; +import java.util.ArrayList; +import java.util.List; +import org.jspecify.annotations.Nullable; +import org.pkl.core.runtime.Identifier; +import org.pkl.core.runtime.SyntaxModule; +import org.pkl.core.runtime.VmExceptionBuilder; +import org.pkl.core.runtime.VmList; +import org.pkl.core.runtime.VmObjectBuilder; +import org.pkl.core.runtime.VmTyped; +import org.pkl.core.runtime.VmUtils; +import org.pkl.core.stdlib.ExternalPropertyNode; +import org.pkl.core.stdlib.PklName; + +/** + * Backs {@code pkl.syntax#SyntaxNode.builtNode}. + * + *

Reconstructs a generic {@code pkl.syntax#Node} tree from a typed syntax node's own fields, + * ignoring the parse-time {@code node} it may be backed by. + * + *

The nodes produced here are storage-less. + */ +public final class SyntaxNodeNodes { + private SyntaxNodeNodes() {} + + @PklName("builtNode") + public abstract static class builtNode extends ExternalPropertyNode { + @Specialization + @TruffleBoundary + protected Object eval(VmTyped self) { + return build(self); + } + } + + private static VmTyped build(VmTyped self) { + return switch (self.getVmClass().getSimpleName()) { + case "ModuleNode" -> buildModule(self); + case "ModuleDeclarationNode" -> buildModuleDeclaration(self); + case "ImportNode" -> buildImport(self); + case "ClassNode" -> buildClass(self); + case "TypeAliasNode" -> buildTypeAlias(self); + case "ClassBodyNode" -> buildClassBody(self); + case "ClassPropertyNode" -> buildClassProperty(self); + case "ClassMethodNode" -> buildClassMethod(self); + case "ObjectBodyNode" -> buildObjectBody(self); + case "ObjectPropertyNode" -> buildObjectProperty(self); + case "ObjectMethodNode" -> buildObjectMethod(self); + case "ObjectElementNode" -> + branch("object_element", List.of(build(reqNode(self, "expression")))); + case "ObjectEntryNode" -> buildObjectEntry(self); + case "ObjectSpreadNode" -> + branch( + "object_spread", + List.of( + terminal(bool(self, "isNullable") ? "...?" : "..."), + build(reqNode(self, "expression")))); + case "MemberPredicateNode" -> buildMemberPredicate(self); + case "ForGeneratorNode" -> buildForGenerator(self); + case "WhenGeneratorNode" -> buildWhenGenerator(self); + case "ThisExprNode" -> leaf("this_expr", "this"); + case "OuterExprNode" -> leaf("outer_expr", "outer"); + case "ModuleExprNode" -> leaf("module_expr", "module"); + case "NullLiteralExprNode" -> leaf("null_expr", "null"); + case "BoolLiteralExprNode" -> + leaf("bool_literal_expr", Boolean.toString(bool(self, "value"))); + case "IntLiteralExprNode" -> leaf("int_literal_expr", numText(member(self, "value"))); + case "FloatLiteralExprNode" -> leaf("float_literal_expr", numText(member(self, "value"))); + case "SingleLineStringLiteralExprNode" -> buildSingleLineString(self); + case "MultiLineStringLiteralExprNode" -> buildMultiLineString(self); + case "UnqualifiedAccessExprNode" -> buildUnqualifiedAccess(self); + case "QualifiedAccessExprNode" -> buildQualifiedAccess(self); + case "SubscriptExprNode" -> + branch( + "subscript_expr", + List.of( + build(reqNode(self, "receiver")), + operatorLeaf("["), + build(reqNode(self, "index")), + terminal("]"))); + case "SuperAccessExprNode" -> buildSuperAccess(self); + case "SuperSubscriptExprNode" -> + branch( + "super_subscript_expr", + List.of( + terminal("super"), terminal("["), build(reqNode(self, "index")), terminal("]"))); + case "IfExprNode" -> buildIf(self); + case "LetExprNode" -> buildLet(self); + case "ThrowExprNode" -> buildCall("throw_expr", "throw", build(reqNode(self, "expression"))); + case "TraceExprNode" -> buildCall("trace_expr", "trace", build(reqNode(self, "expression"))); + case "ImportExprNode" -> + buildCall( + "import_expr", + bool(self, "isGlob") ? "import*" : "import", + stringCharsNode(str(self, "uri"))); + case "ReadExprNode" -> + buildCall("read_expr", str(self, "keyword"), build(reqNode(self, "expression"))); + case "NewExprNode" -> buildNew(self); + case "AmendsExprNode" -> + branch( + "amends_expr", + List.of(build(reqNode(self, "parentExpr")), build(reqNode(self, "body")))); + case "BinaryOpExprNode" -> buildBinaryOp(self); + case "UnaryMinusExprNode" -> + branch("unary_minus_expr", List.of(terminal("-"), build(reqNode(self, "operand")))); + case "LogicalNotExprNode" -> + branch("logical_not_expr", List.of(terminal("!"), build(reqNode(self, "operand")))); + case "NonNullExprNode" -> + branch("non_null_expr", List.of(build(reqNode(self, "operand")), operatorLeaf("!!"))); + case "FunctionLiteralExprNode" -> buildFunctionLiteral(self); + case "ParenthesizedExprNode" -> + branch( + "parenthesized_expr", + List.of( + terminal("("), + branch( + "parenthesized_expr_elements", + List.of(build(nonNull(optNode(self, "expression"))))), + terminal(")"))); + case "UnknownTypeNode" -> leaf("unknown_type", "unknown"); + case "NothingTypeNode" -> leaf("nothing_type", "nothing"); + case "ModuleTypeNode" -> leaf("module_type", "module"); + case "DeclaredTypeNode" -> buildDeclaredType(self); + case "NullableTypeNode" -> + branch("nullable_type", List.of(build(reqNode(self, "baseType")), terminal("?"))); + case "UnionTypeNode" -> + branch( + "union_type", interleave(buildAll(listMember(self, "members")), () -> terminal("|"))); + case "FunctionTypeNode" -> buildFunctionType(self); + case "ConstrainedTypeNode" -> buildConstrainedType(self); + case "ParenthesizedTypeNode" -> + branch( + "parenthesized_type", + List.of( + terminal("("), + branch( + "parenthesized_type_elements", + List.of(build(nonNull(optNode(self, "type"))))), + terminal(")"))); + case "StringConstantTypeNode" -> + branch("string_constant_type", List.of(stringCharsNode(str(self, "value")))); + case "AnnotationNode" -> buildAnnotation(self); + case "ParameterNode" -> buildParameter(self); + case "TypeParameterNode" -> buildTypeParameter(self); + case "IdentifierNode" -> leaf("identifier", str(self, "value")); + case "QualifiedIdentifierNode" -> + branch( + "qualified_identifier", + interleave(buildAll(listMember(self, "identifiers")), () -> terminal("."))); + case "DocCommentNode" -> buildDocComment(self); + default -> + throw new VmExceptionBuilder() + .bug("Unexpected syntax node: " + self.getVmClass().getSimpleName()) + .build(); + }; + } + + private static VmTyped buildModule(VmTyped self) { + var children = new ArrayList<>(); + var declaration = optNode(self, "declaration"); + if (declaration != null) { + children.add(build(declaration)); + } + var imports = listMember(self, "imports"); + if (imports.getLength() > 0) { + children.add(branch("import_list", buildAll(imports))); + } + children.addAll(buildAll(listMember(self, "classes"))); + children.addAll(buildAll(listMember(self, "typeAliases"))); + children.addAll(buildAll(listMember(self, "properties"))); + children.addAll(buildAll(listMember(self, "methods"))); + return branch("module", children); + } + + private static VmTyped buildModuleDeclaration(VmTyped self) { + var children = docAndAnnotations(self); + var name = optNode(self, "name"); + var modifiers = listMember(self, "modifiers"); + if (name != null) { + var definition = new ArrayList<>(); + if (modifiers.getLength() > 0) { + definition.add(modifierListNode(modifiers)); + } + definition.add(terminal("module")); + definition.add(build(name)); + children.add(branch("module_definition", definition)); + } else if (modifiers.getLength() > 0) { + children.add(modifierListNode(modifiers)); + } + var amendsUri = member(self, "amendsUri"); + var extendsUri = member(self, "extendsUri"); + if (amendsUri instanceof String uri) { + children.add(branch("amends_clause", List.of(terminal("amends"), stringCharsNode(uri)))); + } else if (extendsUri instanceof String uri) { + children.add(branch("extends_clause", List.of(terminal("extends"), stringCharsNode(uri)))); + } + return branch("module_declaration", children); + } + + private static VmTyped buildImport(VmTyped self) { + var children = new ArrayList<>(); + children.add(terminal(bool(self, "isGlob") ? "import*" : "import")); + children.add(stringCharsNode(str(self, "uri"))); + var alias = optNode(self, "alias"); + if (alias != null) { + children.add(branch("import_alias", List.of(terminal("as"), build(alias)))); + } + return branch("import", children); + } + + private static VmTyped buildClass(VmTyped self) { + var children = docAndAnnotations(self); + var header = new ArrayList<>(); + var modifiers = listMember(self, "modifiers"); + if (modifiers.getLength() > 0) { + header.add(modifierListNode(modifiers)); + } + header.add(terminal("class")); + header.add(build(reqNode(self, "identifier"))); + header.addAll(typeParameterListNodes(listMember(self, "typeParameters"))); + var extendsType = optNode(self, "extendsType"); + if (extendsType != null) { + header.add(branch("class_header_extends", List.of(terminal("extends"), build(extendsType)))); + } + children.add(branch("class_header", header)); + var body = optNode(self, "body"); + if (body != null) { + children.add(build(body)); + } + return branch("class", children); + } + + private static VmTyped buildTypeAlias(VmTyped self) { + var children = docAndAnnotations(self); + var header = new ArrayList<>(); + var modifiers = listMember(self, "modifiers"); + if (modifiers.getLength() > 0) { + header.add(modifierListNode(modifiers)); + } + header.add(terminal("typealias")); + header.add(build(reqNode(self, "identifier"))); + header.addAll(typeParameterListNodes(listMember(self, "typeParameters"))); + header.add(terminal("=")); + children.add(branch("typealias_header", header)); + children.add(branch("typealias_body", List.of(build(reqNode(self, "type"))))); + return branch("typealias", children); + } + + private static VmTyped buildClassBody(VmTyped self) { + var members = new ArrayList<>(); + members.addAll(buildAll(listMember(self, "properties"))); + members.addAll(buildAll(listMember(self, "methods"))); + var children = new ArrayList<>(); + children.add(terminal("{")); + if (!members.isEmpty()) { + children.add(branch("class_body_elements", members)); + } + children.add(terminal("}")); + return branch("class_body", children); + } + + private static VmTyped buildClassProperty(VmTyped self) { + var children = docAndAnnotations(self); + var headerBegin = new ArrayList<>(); + var modifiers = listMember(self, "modifiers"); + if (modifiers.getLength() > 0) { + headerBegin.add(modifierListNode(modifiers)); + } + headerBegin.add(build(reqNode(self, "identifier"))); + var header = new ArrayList<>(); + header.add(branch("class_property_header_begin", headerBegin)); + header.addAll(typeAnnotationNodes(optNode(self, "typeAnnotation"))); + children.add(branch("class_property_header", header)); + var value = optNode(self, "value"); + if (value != null) { + children.add(terminal("=")); + children.add(branch("class_property_body", List.of(build(value)))); + } else { + children.addAll(buildAll(listMember(self, "objectBodies"))); + } + return branch("class_property", children); + } + + private static VmTyped buildClassMethod(VmTyped self) { + var children = docAndAnnotations(self); + var header = new ArrayList<>(); + var modifiers = listMember(self, "modifiers"); + if (modifiers.getLength() > 0) { + header.add(modifierListNode(modifiers)); + } + header.add(terminal("function")); + header.add(build(reqNode(self, "identifier"))); + children.add(branch("class_method_header", header)); + children.addAll(typeParameterListNodes(listMember(self, "typeParameters"))); + children.add(parameterListNode(listMember(self, "parameters"))); + children.addAll(typeAnnotationNodes(optNode(self, "returnType"))); + var body = optNode(self, "body"); + if (body != null) { + children.add(terminal("=")); + children.add(branch("class_method_body", List.of(build(body)))); + } + return branch("class_method", children); + } + + private static VmTyped buildObjectBody(VmTyped self) { + var children = new ArrayList<>(); + children.add(terminal("{")); + var parameters = listMember(self, "parameters"); + if (parameters.getLength() > 0) { + var elements = interleave(buildAll(parameters), SyntaxNodeNodes::comma); + elements.add(terminal("->")); + children.add(branch("object_parameter_list", elements)); + } + var members = listMember(self, "members"); + if (members.getLength() > 0) { + children.add(branch("object_member_list", buildAll(members))); + } + children.add(terminal("}")); + return branch("object_body", children); + } + + private static VmTyped buildObjectProperty(VmTyped self) { + var headerBegin = new ArrayList<>(); + var modifiers = listMember(self, "modifiers"); + if (modifiers.getLength() > 0) { + headerBegin.add(modifierListNode(modifiers)); + } + headerBegin.add(build(reqNode(self, "identifier"))); + var header = new ArrayList<>(); + header.add(branch("object_property_header_begin", headerBegin)); + header.addAll(typeAnnotationNodes(optNode(self, "typeAnnotation"))); + var children = new ArrayList<>(); + children.add(branch("object_property_header", header)); + var value = optNode(self, "value"); + if (value != null) { + children.add(terminal("=")); + children.add(branch("object_property_body", List.of(build(value)))); + } else { + children.addAll(buildAll(listMember(self, "objectBodies"))); + } + return branch("object_property", children); + } + + private static VmTyped buildObjectMethod(VmTyped self) { + var header = new ArrayList<>(); + var modifiers = listMember(self, "modifiers"); + if (modifiers.getLength() > 0) { + header.add(modifierListNode(modifiers)); + } + header.add(terminal("function")); + header.add(build(reqNode(self, "identifier"))); + var children = new ArrayList<>(); + children.add(branch("class_method_header", header)); + children.addAll(typeParameterListNodes(listMember(self, "typeParameters"))); + children.add(parameterListNode(listMember(self, "parameters"))); + children.addAll(typeAnnotationNodes(optNode(self, "returnType"))); + children.add(terminal("=")); + children.add(branch("class_method_body", List.of(build(reqNode(self, "body"))))); + return branch("object_method", children); + } + + private static VmTyped buildObjectEntry(VmTyped self) { + var header = new ArrayList<>(); + header.add(terminal("[")); + header.add(build(reqNode(self, "key"))); + header.add(terminal("]")); + var value = optNode(self, "value"); + if (value != null) { + header.add(terminal("=")); + } + var children = new ArrayList<>(); + children.add(branch("object_entry_header", header)); + if (value != null) { + children.add(build(value)); + } else { + children.addAll(buildAll(listMember(self, "objectBodies"))); + } + return branch("object_entry", children); + } + + private static VmTyped buildMemberPredicate(VmTyped self) { + var children = new ArrayList<>(); + children.add(terminal("[[")); + children.add(build(reqNode(self, "condition"))); + children.add(terminal("]")); + children.add(terminal("]")); + var value = optNode(self, "value"); + if (value != null) { + children.add(terminal("=")); + children.add(build(value)); + } else { + children.addAll(buildAll(listMember(self, "objectBodies"))); + } + return branch("member_predicate", children); + } + + private static VmTyped buildForGenerator(VmTyped self) { + var definitionHeader = new ArrayList<>(); + var keyParameter = optNode(self, "keyParameter"); + if (keyParameter == null) { + definitionHeader.add(build(reqNode(self, "valueParameter"))); + } else { + definitionHeader.add(build(keyParameter)); + definitionHeader.add(terminal(",")); + definitionHeader.add(build(reqNode(self, "valueParameter"))); + } + definitionHeader.add(terminal("in")); + List definition = + List.of( + branch("for_generator_header_definition_header", definitionHeader), + build(reqNode(self, "iterable"))); + List header = + List.of( + terminal("("), branch("for_generator_header_definition", definition), terminal(")")); + return branch( + "for_generator", + List.of( + terminal("for"), branch("for_generator_header", header), build(reqNode(self, "body")))); + } + + private static VmTyped buildWhenGenerator(VmTyped self) { + var children = new ArrayList<>(); + children.add(terminal("when")); + children.add( + branch( + "when_generator_header", + List.of(terminal("("), build(reqNode(self, "condition")), terminal(")")))); + children.add(build(reqNode(self, "thenBody"))); + var elseBody = optNode(self, "elseBody"); + if (elseBody != null) { + children.add(terminal("else")); + children.add(build(elseBody)); + } + return branch("when_generator", children); + } + + private static VmTyped buildSingleLineString(VmTyped self) { + var children = new ArrayList<>(); + children.add(terminal("\"")); + children.addAll(buildStringParts(listMember(self, "parts"))); + children.add(terminal("\"")); + return branch("single_line_string_literal_expr", children); + } + + private static VmTyped buildMultiLineString(VmTyped self) { + var children = new ArrayList<>(); + children.add(terminal("\"\"\"")); + children.addAll(buildStringParts(listMember(self, "parts"))); + // The formatter uses colStart of the closing `"""` to determine the indentation to strip from + // each content line. + var closingSpan = new VmObjectBuilder(1).addProperty(Identifier.COL_START, 1L); + children.add( + makeNode("terminal", null, "\"\"\"", closingSpan.toTyped(SyntaxModule.getSpanClass()))); + return branch("multi_line_string_literal_expr", children); + } + + private static List buildStringParts(VmList parts) { + var result = new ArrayList<>(); + for (var i = 0; i < parts.getLength(); i++) { + result.addAll(buildStringPart((VmTyped) parts.get(i))); + } + return result; + } + + // Mirrors `StringPartNode.toNodes` for each part kind. `StringPartNode` is not a `SyntaxNode`, so + // it is handled here rather than through `build`. + private static List buildStringPart(VmTyped part) { + return switch (part.getVmClass().getSimpleName()) { + case "StringCharsNode" -> List.of(leaf("string_chars", str(part, "value"))); + case "StringEscapeNode" -> List.of(leaf("string_escape", str(part, "value"))); + case "StringNewlineNode" -> List.of(typeOnly("string_newline")); + case "StringInterpolationNode" -> + List.of(terminal("\\("), build(reqNode(part, "expression")), terminal(")")); + default -> + throw new VmExceptionBuilder() + .bug("Unexpected string-part node: " + part.getVmClass().getSimpleName()) + .build(); + }; + } + + private static VmTyped buildUnqualifiedAccess(VmTyped self) { + var children = new ArrayList<>(); + children.add(build(reqNode(self, "identifier"))); + var arguments = optList(self, "arguments"); + if (arguments != null) { + children.add(argumentListNode(arguments)); + } + return branch("unqualified_access_expr", children); + } + + private static VmTyped buildQualifiedAccess(VmTyped self) { + var member = new ArrayList<>(); + member.add(build(reqNode(self, "identifier"))); + var arguments = optList(self, "arguments"); + if (arguments != null) { + member.add(argumentListNode(arguments)); + } + return branch( + "qualified_access_expr", + List.of( + build(reqNode(self, "receiver")), + operatorLeaf(bool(self, "isNullSafe") ? "?." : "."), + branch("unqualified_access_expr", member))); + } + + private static VmTyped buildSuperAccess(VmTyped self) { + var children = new ArrayList<>(); + children.add(terminal("super")); + children.add(terminal(".")); + children.add(build(reqNode(self, "identifier"))); + var arguments = optList(self, "arguments"); + if (arguments != null) { + children.add(argumentListNode(arguments)); + } + return branch("super_access_expr", children); + } + + private static VmTyped buildIf(VmTyped self) { + return branch( + "if_expr", + List.of( + branch( + "if_header", + List.of( + terminal("if"), + branch( + "if_condition", + List.of( + terminal("("), + branch("if_condition_expr", List.of(build(reqNode(self, "condition")))), + terminal(")"))))), + branch("if_then_expr", List.of(build(reqNode(self, "thenExpr")))), + terminal("else"), + branch("if_else_expr", List.of(build(reqNode(self, "elseExpr")))))); + } + + private static VmTyped buildLet(VmTyped self) { + return branch( + "let_expr", + List.of( + terminal("let"), + branch( + "let_parameter_definition", + List.of( + terminal("("), + branch( + "let_parameter", + List.of( + build(reqNode(self, "parameter")), + terminal("="), + build(reqNode(self, "bindingValue")))), + terminal(")"))), + build(reqNode(self, "body")))); + } + + private static VmTyped buildNew(VmTyped self) { + var type = optNode(self, "type"); + var header = + type == null + ? List.of(terminal("new")) + : List.of(terminal("new"), build(type)); + return branch("new_expr", List.of(branch("new_header", header), build(reqNode(self, "body")))); + } + + private static VmTyped buildBinaryOp(VmTyped self) { + var operator = str(self, "operator"); + var right = + operator.equals("is") || operator.equals("as") + ? build(nonNull(optNode(self, "rightType"))) + : build(nonNull(optNode(self, "right"))); + return branch( + "binary_op_expr", List.of(build(reqNode(self, "left")), operatorLeaf(operator), right)); + } + + private static VmTyped buildFunctionLiteral(VmTyped self) { + return branch( + "function_literal_expr", + List.of( + parameterListNode(listMember(self, "parameters")), + terminal("->"), + branch("function_literal_body", List.of(build(reqNode(self, "body")))))); + } + + private static VmTyped buildDeclaredType(VmTyped self) { + var name = build(reqNode(self, "name")); + var typeArguments = listMember(self, "typeArguments"); + if (typeArguments.getLength() == 0) { + return branch("declared_type", List.of(name)); + } + return branch( + "declared_type", + List.of( + name, + branch( + "type_argument_list", + List.of( + terminal("<"), + branch( + "type_argument_list_elements", + interleave(buildAll(typeArguments), SyntaxNodeNodes::comma)), + terminal(">"))))); + } + + private static VmTyped buildFunctionType(VmTyped self) { + var parameterTypes = listMember(self, "parameterTypes"); + var parameters = + parameterTypes.getLength() == 0 + ? List.of(terminal("("), terminal(")")) + : List.of( + terminal("("), + branch( + "parenthesized_type_elements", + interleave(buildAll(parameterTypes), SyntaxNodeNodes::comma)), + terminal(")")); + return branch( + "function_type", + List.of( + branch("function_type_parameters", parameters), + terminal("->"), + build(reqNode(self, "returnType")))); + } + + private static VmTyped buildConstrainedType(VmTyped self) { + return branch( + "constrained_type", + List.of( + build(reqNode(self, "baseType")), + branch( + "constrained_type_constraint", + List.of( + terminal("("), + branch( + "constrained_type_elements", + interleave( + buildAll(listMember(self, "constraints")), SyntaxNodeNodes::comma)), + terminal(")"))))); + } + + private static VmTyped buildAnnotation(VmTyped self) { + var children = new ArrayList<>(); + children.add(terminal("@")); + children.add(build(reqNode(self, "type"))); + var body = optNode(self, "body"); + if (body != null) { + children.add(build(body)); + } + return branch("annotation", children); + } + + private static VmTyped buildParameter(VmTyped self) { + var identifier = optNode(self, "identifier"); + if (identifier == null) { + return branch("parameter", List.of(terminal("_"))); + } + var typeAnnotation = optNode(self, "typeAnnotation"); + if (typeAnnotation == null) { + return branch("parameter", List.of(build(identifier))); + } + var children = new ArrayList<>(); + children.add(build(identifier)); + children.addAll(typeAnnotationNodes(typeAnnotation)); + return branch("parameter", children); + } + + private static VmTyped buildTypeParameter(VmTyped self) { + var variance = member(self, "variance"); + if (variance instanceof String v) { + return branch("type_parameter", List.of(terminal(v), build(reqNode(self, "identifier")))); + } + return branch("type_parameter", List.of(build(reqNode(self, "identifier")))); + } + + private static VmTyped buildDocComment(VmTyped self) { + var lines = listMember(self, "lines"); + var children = new ArrayList<>(); + for (var i = 0; i < lines.getLength(); i++) { + children.add(leaf("doc_comment_line", "/// " + lines.get(i))); + } + return branch("doc_comment", children); + } + + private static VmTyped buildCall(String type, String keyword, VmTyped inner) { + return branch(type, List.of(terminal(keyword), terminal("("), inner, terminal(")"))); + } + + // The doc comment (if any) followed by the annotations of a declaration. + private static List docAndAnnotations(VmTyped self) { + var result = new ArrayList<>(); + var docComment = optNode(self, "docComment"); + if (docComment != null) { + result.add(build(docComment)); + } + result.addAll(buildAll(listMember(self, "annotations"))); + return result; + } + + // Node construction helpers + + private static VmTyped modifierListNode(VmList modifiers) { + var children = new ArrayList<>(); + for (var i = 0; i < modifiers.getLength(); i++) { + children.add(leaf("modifier", (String) modifiers.get(i))); + } + return branch("modifier_list", children); + } + + // A quoted `string_chars` node for a string constant like `"foo"`. + private static VmTyped stringCharsNode(String value) { + return makeNode( + "string_chars", + List.of(terminal("\""), terminal(value), terminal("\"")), + "\"" + value + "\"", + null); + } + + private static VmTyped parameterListNode(VmList parameters) { + if (parameters.getLength() == 0) { + return branch("parameter_list", List.of(terminal("("), terminal(")"))); + } + return branch( + "parameter_list", + List.of( + terminal("("), + branch( + "parameter_list_elements", + interleave(buildAll(parameters), SyntaxNodeNodes::comma)), + terminal(")"))); + } + + private static VmTyped argumentListNode(VmList arguments) { + if (arguments.getLength() == 0) { + return branch("argument_list", List.of(terminal("("), terminal(")"))); + } + return branch( + "argument_list", + List.of( + terminal("("), + branch( + "argument_list_elements", interleave(buildAll(arguments), SyntaxNodeNodes::comma)), + terminal(")"))); + } + + private static List typeParameterListNodes(VmList typeParameters) { + if (typeParameters.getLength() == 0) { + return List.of(); + } + return List.of( + branch( + "type_parameter_list", + List.of( + terminal("<"), + branch( + "type_parameter_list_elements", + interleave(buildAll(typeParameters), SyntaxNodeNodes::comma)), + terminal(">")))); + } + + private static List typeAnnotationNodes(@Nullable VmTyped type) { + if (type == null) { + return List.of(); + } + return List.of(branch("type_annotation", List.of(terminal(":"), build(type)))); + } + + private static VmTyped comma() { + return terminal(","); + } + + // Interleave `items` with fresh separators. + private static ArrayList interleave( + List items, java.util.function.Supplier separator) { + var result = new ArrayList<>(items.isEmpty() ? 0 : items.size() * 2 - 1); + for (var item : items) { + if (!result.isEmpty()) { + result.add(separator.get()); + } + result.add(item); + } + return result; + } + + private static VmTyped terminal(String text) { + return leaf("terminal", text); + } + + private static VmTyped operatorLeaf(String text) { + return leaf("operator", text); + } + + private static VmTyped branch(String type, List children) { + return makeNode(type, children, null, null); + } + + private static VmTyped leaf(String type, String text) { + return makeNode(type, null, text, null); + } + + private static VmTyped typeOnly(String type) { + return makeNode(type, null, null, null); + } + + // Build a generic `Node`, setting only the members that differ from the class + // defaults (`children` defaults to empty, `text` to null, `span`/`parent` to their defaults). + private static VmTyped makeNode( + String type, @Nullable List children, @Nullable String text, @Nullable VmTyped span) { + var builder = new VmObjectBuilder(4); + builder.addProperty(Identifier.TYPE, type); + if (children != null) { + builder.addProperty(Identifier.CHILDREN, VmList.create(children.toArray())); + } + if (text != null) { + builder.addProperty(Identifier.TEXT, text); + } + if (span != null) { + builder.addProperty(Identifier.SPAN, span); + } + return builder.toTyped(SyntaxModule.getNodeClass()); + } + + // =============== + // Member readers + // =============== + + private static List buildAll(VmList nodes) { + var result = new ArrayList<>(); + for (var i = 0; i < nodes.getLength(); i++) { + result.add(build((VmTyped) nodes.get(i))); + } + return result; + } + + private static Object member(VmTyped self, String name) { + return VmUtils.readMember(self, Identifier.get(name)); + } + + private static VmTyped reqNode(VmTyped self, String name) { + return (VmTyped) member(self, name); + } + + private static @Nullable VmTyped optNode(VmTyped self, String name) { + return member(self, name) instanceof VmTyped node ? node : null; + } + + private static VmList listMember(VmTyped self, String name) { + return (VmList) member(self, name); + } + + private static @Nullable VmList optList(VmTyped self, String name) { + return member(self, name) instanceof VmList list ? list : null; + } + + private static String str(VmTyped self, String name) { + return (String) member(self, name); + } + + private static boolean bool(VmTyped self, String name) { + return (Boolean) member(self, name); + } + + private static VmTyped nonNull(@Nullable VmTyped value) { + if (value == null) { + throw new VmExceptionBuilder().evalError("expectedNonNullValue").build(); + } + return value; + } + + private static String numText(Object value) { + return value instanceof String s ? s : value.toString(); + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl index 99f5dd85f..4b1acc735 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl @@ -74,14 +74,42 @@ facts { null) == fmt("x = (1)") } - ["parent back-references on the result tree"] { - local result = syntax.walk(mod("x = 41").node, (n) -> + ["build super access from scratch"] { + walkFormat("x = 0", (n) -> if (n.type == "int_literal_expr") - Pair(new syntax.IntLiteralExprNode { value = 42 }.builtNode, false) + Pair( + new syntax.SuperAccessExprNode { + identifier = new syntax.IdentifierNode { value = "foo" } + arguments = null + }.builtNode, + false, + ) + else + null) == fmt("x = super.foo") + + walkFormat("x = 0", (n) -> + if (n.type == "int_literal_expr") + Pair( + new syntax.SuperAccessExprNode { + identifier = new syntax.IdentifierNode { value = "foo" } + arguments = List(new syntax.IntLiteralExprNode { value = 1 }) + }.builtNode, + false, + ) + else + null) == fmt("x = super.foo(1)") + } + + ["build super subscript from scratch"] { + walkFormat("x = 0", (n) -> + if (n.type == "int_literal_expr") + Pair( + new syntax.SuperSubscriptExprNode { + index = new syntax.IntLiteralExprNode { value = 0 } + }.builtNode, + false, + ) else - null) - // the root of the returned tree has no parent - result.parent == null - result.children.first.parent.type == "module" + null) == fmt("x = super[0]") } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf index da058fdfc..19291ccb2 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf @@ -18,8 +18,11 @@ facts { ["descend = false leaves the emitted subtree untouched"] { true } - ["parent back-references on the result tree"] { + ["build super access from scratch"] { true true } + ["build super subscript from scratch"] { + true + } } diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 661b9da4b..eac8dbf35 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -293,7 +293,7 @@ abstract class SyntaxNode { /// This node rebuilt into a generic [Node]. /// /// Always constructs a fresh node from this node's fields. - fixed builtNode: Node + external fixed builtNode: Node } /// Base class for expression nodes. @@ -324,27 +324,6 @@ class ModuleNode extends SyntaxNode { /// All top-level methods in this module. methods: List - - fixed builtNode = - let (self = this) - new Node { - type = "module" - children = - List( - self.declaration?.builtNode, - if (self.imports.isEmpty) - null - else - new Node { - type = "import_list" - children = self.imports.map((i) -> i.builtNode) - }, - ).filterNonNull() - + self.classes.map((c) -> c.builtNode) - + self.typeAliases.map((t) -> t.builtNode) - + self.properties.map((p) -> p.builtNode) - + self.methods.map((m) -> m.builtNode) - } } /// A module declaration (including doc comment, annotations, modifiers, name, amends/extends). @@ -366,44 +345,6 @@ class ModuleDeclarationNode extends SyntaxNode { /// The URI string of the extended module, if any. Mutually exclusive with [amendsUri]. extendsUri: String? - - fixed builtNode = - let (self = this) - new Node { - type = "module_declaration" - children = - (if (self.docComment == null) List() else List(self.docComment.builtNode)) - + self.annotations.map((a) -> a.builtNode) - + List( - if (self.name != null) - new Node { - type = "module_definition" - children = - List( - if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), - (terminal) { text = "module" }, - self.name.builtNode, - ).filterNonNull() - } - else if (!self.modifiers.isEmpty) - modifierListNode(self.modifiers) - else - null, - if (self.amendsUri != null) - new Node { - type = "amends_clause" - children = List((terminal) { text = "amends" }, stringCharsNode(self.amendsUri!!)) - } - else if (self.extendsUri != null) - new Node { - type = "extends_clause" - children = - List((terminal) { text = "extends" }, stringCharsNode(self.extendsUri!!)) - } - else - null, - ).filterNonNull() - } } /// An import declaration. @@ -416,24 +357,6 @@ class ImportNode extends SyntaxNode { /// The alias for this import, if present. alias: IdentifierNode? - - fixed builtNode = - let (self = this) - new Node { - type = "import" - children = - List( - (terminal) { text = if (self.isGlob) "import*" else "import" }, - stringCharsNode(self.uri), - if (self.alias == null) - null - else - new Node { - type = "import_alias" - children = List((terminal) { text = "as" }, self.alias.builtNode) - }, - ).filterNonNull() - } } /// A class declaration. @@ -458,38 +381,6 @@ class ClassNode extends SyntaxNode { /// The class body, if present. body: ClassBodyNode? - - fixed builtNode = - let (self = this) - new Node { - type = "class" - children = - (if (self.docComment == null) List() else List(self.docComment.builtNode)) - + self.annotations.map((a) -> a.builtNode) - + List( - new Node { - type = "class_header" - children = - List( - if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), - (terminal) { text = "class" }, - self.identifier.builtNode, - ).filterNonNull() - + typeParameterListNodes(self.typeParameters) - + ( - if (self.extendsType == null) - List() - else - List(new Node { - type = "class_header_extends" - children = - List((terminal) { text = "extends" }, self.extendsType.builtNode) - }) - ) - }, - self.body?.builtNode, - ).filterNonNull() - } } /// A typealias declaration. @@ -511,30 +402,6 @@ class TypeAliasNode extends SyntaxNode { /// The type that this alias resolves to. type: TypeNode - - fixed builtNode = - let (self = this) - new Node { - type = "typealias" - children = - (if (self.docComment == null) List() else List(self.docComment.builtNode)) - + self.annotations.map((a) -> a.builtNode) - + List(new Node { - type = "typealias_header" - children = - List( - if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), - (terminal) { text = "typealias" }, - self.identifier.builtNode, - ).filterNonNull() - + typeParameterListNodes(self.typeParameters) - + List((terminal) { text = "=" }) - }) - + List(new Node { - type = "typealias_body" - children = List(self.type.builtNode) - }) - } } /// A class body delimited by braces. @@ -544,25 +411,6 @@ class ClassBodyNode extends SyntaxNode { /// Methods declared in this class body. methods: List - - fixed builtNode = - let (self = this) - new Node { - type = "class_body" - children = - List( - (terminal) { text = "{" }, - let ( - members = - self.properties.map((p) -> p.builtNode) + self.methods.map((m) -> m.builtNode) - ) - if (members.isEmpty) - null - else - new Node { type = "class_body_elements"; children = members }, - (terminal) { text = "}" }, - ).filterNonNull() - } } /// A class property declaration. @@ -587,40 +435,6 @@ class ClassPropertyNode extends SyntaxNode { /// Object bodies for amending (from `{ ... }` blocks). objectBodies: List - - fixed builtNode = - let (self = this) - new Node { - type = "class_property" - children = - (if (self.docComment == null) List() else List(self.docComment.builtNode)) - + self.annotations.map((a) -> a.builtNode) - + List(new Node { - type = "class_property_header" - children = - List(new Node { - type = "class_property_header_begin" - children = - List( - if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), - self.identifier.builtNode, - ).filterNonNull() - }) - + typeAnnotationNodes(self.typeAnnotation) - }) - + ( - if (self.value != null) - List( - (terminal) { text = "=" }, - new Node { - type = "class_property_body" - children = List(self.value.builtNode) - }, - ) - else - self.objectBodies.map((b) -> b.builtNode) - ) - } } /// A class method declaration. @@ -648,39 +462,6 @@ class ClassMethodNode extends SyntaxNode { /// The method body expression, if present. Null for abstract methods. body: ExprNode? - - fixed builtNode = - let (self = this) - new Node { - type = "class_method" - children = - (if (self.docComment == null) List() else List(self.docComment.builtNode)) - + self.annotations.map((a) -> a.builtNode) - + List(new Node { - type = "class_method_header" - children = - List( - if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), - (terminal) { text = "function" }, - self.identifier.builtNode, - ).filterNonNull() - }) - + typeParameterListNodes(self.typeParameters) - + List(parameterListNode(self.parameters)) - + typeAnnotationNodes(self.returnType) - + ( - if (self.body == null) - List() - else - List( - (terminal) { text = "=" }, - new Node { - type = "class_method_body" - children = List(self.body.builtNode) - }, - ) - ) - } } /// An object body delimited by braces. @@ -690,33 +471,6 @@ class ObjectBodyNode extends SyntaxNode { /// All object members (properties, methods, elements, entries, spreads, generators). members: List - - fixed builtNode = - let (self = this) - new Node { - type = "object_body" - children = - List( - (terminal) { text = "{" }, - if (self.parameters.isEmpty) - null - else - new Node { - type = "object_parameter_list" - children = - commaSeparate(self.parameters.map((p) -> p.builtNode)) - .add((terminal) { text = "->" }) - }, - if (self.members.isEmpty) - null - else - new Node { - type = "object_member_list" - children = self.members.map((m) -> m.builtNode) - }, - (terminal) { text = "}" }, - ).filterNonNull() - } } /// An object property declaration. @@ -735,36 +489,6 @@ class ObjectPropertyNode extends ObjectMemberNode { /// Object bodies for amending. objectBodies: List - - fixed builtNode = - let (self = this) - new Node { - type = "object_property" - children = - List(new Node { - type = "object_property_header" - children = - List(new Node { - type = "object_property_header_begin" - children = - (if (self.modifiers.isEmpty) List() else List(modifierListNode(self.modifiers))) - + List(self.identifier.builtNode) - }) - + typeAnnotationNodes(self.typeAnnotation) - }) - + ( - if (self.value != null) - List( - (terminal) { text = "=" }, - new Node { - type = "object_property_body" - children = List(self.value.builtNode) - }, - ) - else - self.objectBodies.map((b) -> b.builtNode) - ) - } } /// An object method declaration. @@ -786,45 +510,12 @@ class ObjectMethodNode extends ObjectMemberNode { /// The method body expression. body: ExprNode - - fixed builtNode = - let (self = this) - new Node { - type = "object_method" - children = - List(new Node { - type = "class_method_header" - children = - List( - if (self.modifiers.isEmpty) null else modifierListNode(self.modifiers), - (terminal) { text = "function" }, - self.identifier.builtNode, - ).filterNonNull() - }) - + typeParameterListNodes(self.typeParameters) - + List(parameterListNode(self.parameters)) - + typeAnnotationNodes(self.returnType) - + List( - (terminal) { text = "=" }, - new Node { - type = "class_method_body" - children = List(self.body.builtNode) - }, - ) - } } /// An object element (a positional expression in an object body). class ObjectElementNode extends ObjectMemberNode { /// The expression value. expression: ExprNode - - fixed builtNode = - let (self = this) - new Node { - type = "object_element" - children = List(self.expression.builtNode) - } } /// An object entry (`[key] = value` or `[key] { ... }`). @@ -837,29 +528,6 @@ class ObjectEntryNode extends ObjectMemberNode { /// Object bodies for amending. objectBodies: List - - fixed builtNode = - let (self = this) - new Node { - type = "object_entry" - children = - List(new Node { - type = "object_entry_header" - children = - List( - (terminal) { text = "[" }, - self.key.builtNode, - (terminal) { text = "]" }, - if (self.value != null) (terminal) { text = "=" } else null, - ).filterNonNull() - }) - + ( - if (self.value != null) - List(self.value.builtNode) - else - self.objectBodies.map((b) -> b.builtNode) - ) - } } /// An object spread (`...expr` or `...?expr`). @@ -869,17 +537,6 @@ class ObjectSpreadNode extends ObjectMemberNode { /// The spread expression. expression: ExprNode - - fixed builtNode = - let (self = this) - new Node { - type = "object_spread" - children = - List( - (terminal) { text = if (self.isNullable) "...?" else "..." }, - self.expression.builtNode, - ) - } } /// A member predicate (`[[condition]] = value` or `[[condition]] { ... }`). @@ -892,25 +549,6 @@ class MemberPredicateNode extends ObjectMemberNode { /// Object bodies for amending. objectBodies: List - - fixed builtNode = - let (self = this) - new Node { - type = "member_predicate" - children = - List( - (terminal) { text = "[[" }, - self.condition.builtNode, - (terminal) { text = "]" }, - (terminal) { text = "]" }, - ) - + ( - if (self.value != null) - List((terminal) { text = "=" }, self.value.builtNode) - else - self.objectBodies.map((b) -> b.builtNode) - ) - } } /// A `for (param in iterable) { ... }` generator. @@ -926,47 +564,6 @@ class ForGeneratorNode extends ObjectMemberNode { /// The body. body: ObjectBodyNode - - fixed builtNode = - let (self = this) - new Node { - type = "for_generator" - children = - List( - (terminal) { text = "for" }, - new Node { - type = "for_generator_header" - children = - List( - (terminal) { text = "(" }, - new Node { - type = "for_generator_header_definition" - children = - List( - new Node { - type = "for_generator_header_definition_header" - children = - ( - if (self.keyParameter == null) - List(self.valueParameter.builtNode) - else - List( - self.keyParameter.builtNode, - (terminal) { text = "," }, - self.valueParameter.builtNode, - ) - ) - + List((terminal) { text = "in" }) - }, - self.iterable.builtNode, - ) - }, - (terminal) { text = ")" }, - ) - }, - self.body.builtNode, - ) - } } /// A `when (condition) { ... }` generator. @@ -979,98 +576,42 @@ class WhenGeneratorNode extends ObjectMemberNode { /// The "else" body, if present. elseBody: ObjectBodyNode? - - fixed builtNode = - let (self = this) - new Node { - type = "when_generator" - children = - List( - (terminal) { text = "when" }, - new Node { - type = "when_generator_header" - children = - List( - (terminal) { text = "(" }, - self.condition.builtNode, - (terminal) { text = ")" }, - ) - }, - self.thenBody.builtNode, - ) - + ( - if (self.elseBody == null) - List() - else - List((terminal) { text = "else" }, self.elseBody.builtNode) - ) - } } /// The `this` expression. -class ThisExprNode extends ExprNode { - fixed builtNode = new Node { type = "this_expr"; text = "this" } -} +class ThisExprNode extends ExprNode {} /// The `outer` expression. -class OuterExprNode extends ExprNode { - fixed builtNode = new Node { type = "outer_expr"; text = "outer" } -} +class OuterExprNode extends ExprNode {} /// The `module` expression. -class ModuleExprNode extends ExprNode { - fixed builtNode = new Node { type = "module_expr"; text = "module" } -} +class ModuleExprNode extends ExprNode {} /// A `null` literal expression. -class NullLiteralExprNode extends ExprNode { - fixed builtNode = new Node { type = "null_expr"; text = "null" } -} +class NullLiteralExprNode extends ExprNode {} /// A boolean literal expression (`true` or `false`). class BoolLiteralExprNode extends ExprNode { /// The boolean value. value: Boolean - - fixed builtNode = - let (self = this) - new Node { type = "bool_literal_expr"; text = if (self.value) "true" else "false" } } /// An integer literal expression. class IntLiteralExprNode extends ExprNode { /// The integer literal (e.g. `42`, `"0xFF"`). value: Int | String - - fixed builtNode = - let (self = this) - new Node { type = "int_literal_expr"; text = self.value.toString() } } /// A float literal expression. class FloatLiteralExprNode extends ExprNode { /// The float literal (e.g. `3.14`, `"1.0e10"`). value: Float | String - - fixed builtNode = - let (self = this) - new Node { type = "float_literal_expr"; text = self.value.toString() } } /// A single-line string literal expression. class SingleLineStringLiteralExprNode extends ExprNode { /// The string parts (chars, escapes, interpolations). parts: List - - fixed builtNode = - let (self = this) - new Node { - type = "single_line_string_literal_expr" - children = - List((terminal) { text = "\"" }) - + self.parts.flatMap((p) -> p.toNodes()) - + List((terminal) { text = "\"" }) - } } /// A multi-line string literal expression. @@ -1079,22 +620,6 @@ class SingleLineStringLiteralExprNode extends ExprNode { class MultiLineStringLiteralExprNode extends ExprNode { /// The string parts (chars, escapes, newlines, interpolations). parts: List - - fixed builtNode = - let (self = this) - new Node { - type = "multi_line_string_literal_expr" - children = - List((terminal) { text = "\"\"\"" }) - + self.parts.flatMap((p) -> p.toNodes()) - + List(new Node { - type = "terminal" - text = "\"\"\"" - // formatter uses span.colStart of the closing `"""` to determine the - // indentation to strip from each content line. - span = new Span { colStart = 1 } - }) - } } /// An unqualified access expression (`name` or `name(args)`). @@ -1104,15 +629,6 @@ class UnqualifiedAccessExprNode extends ExprNode { /// The arguments, if this is a function call. Null for a plain identifier access. arguments: List? - - fixed builtNode = - let (self = this) - new Node { - type = "unqualified_access_expr" - children = - List(self.identifier.builtNode) - + (if (self.arguments == null) List() else List(argumentListNode(self.arguments!!))) - } } /// A qualified access expression (`receiver.member` or `receiver?.member`, @@ -1129,25 +645,6 @@ class QualifiedAccessExprNode extends ExprNode { /// The arguments, if this is a method call. Null for a property access. arguments: List? - - fixed builtNode = - let (self = this) - new Node { - type = "qualified_access_expr" - children = - List( - self.receiver.builtNode, - (operatorLeaf) { text = if (self.isNullSafe) "?." else "." }, - new Node { - type = "unqualified_access_expr" - children = - List(self.identifier.builtNode) - + ( - if (self.arguments == null) List() else List(argumentListNode(self.arguments!!)) - ) - }, - ) - } } /// A subscript expression (`receiver[index]`). @@ -1157,33 +654,21 @@ class SubscriptExprNode extends ExprNode { /// The index expression. index: ExprNode +} - fixed builtNode = - let (self = this) - new Node { - type = "subscript_expr" - children = - List( - self.receiver.builtNode, - (operatorLeaf) { text = "[" }, - self.index.builtNode, - (terminal) { text = "]" }, - ) - } -} - -/// A `super.member` access expression. -/// -/// Read-only: this node has no builder and cannot be constructed from scratch. +/// A `super.member` access expression (`super.member` or `super.member(args)`). class SuperAccessExprNode extends ExprNode { - fixed builtNode = node!! + /// The accessed member name. + identifier: IdentifierNode + + /// The arguments, if this is a method call. Null for a property access. + arguments: List? } /// A `super[index]` subscript expression. -/// -/// Read-only: this node has no builder and cannot be constructed from scratch. class SuperSubscriptExprNode extends ExprNode { - fixed builtNode = node!! + /// The index expression. + index: ExprNode } /// An `if (condition) thenExpr else elseExpr` expression. @@ -1196,43 +681,6 @@ class IfExprNode extends ExprNode { /// The else-branch expression. elseExpr: ExprNode - - fixed builtNode = - let (self = this) - new Node { - type = "if_expr" - children = - List( - new Node { - type = "if_header" - children = - List( - (terminal) { text = "if" }, - new Node { - type = "if_condition" - children = - List( - (terminal) { text = "(" }, - new Node { - type = "if_condition_expr" - children = List(self.condition.builtNode) - }, - (terminal) { text = ")" }, - ) - }, - ) - }, - new Node { - type = "if_then_expr" - children = List(self.thenExpr.builtNode) - }, - (terminal) { text = "else" }, - new Node { - type = "if_else_expr" - children = List(self.elseExpr.builtNode) - }, - ) - } } /// A `let (param = value) body` expression. @@ -1245,72 +693,18 @@ class LetExprNode extends ExprNode { /// The body expression. body: ExprNode - - fixed builtNode = - let (self = this) - new Node { - type = "let_expr" - children = - List( - (terminal) { text = "let" }, - new Node { - type = "let_parameter_definition" - children = - List( - (terminal) { text = "(" }, - new Node { - type = "let_parameter" - children = - List( - self.parameter.builtNode, - (terminal) { text = "=" }, - self.bindingValue.builtNode, - ) - }, - (terminal) { text = ")" }, - ) - }, - self.body.builtNode, - ) - } } /// A `throw(expr)` expression. class ThrowExprNode extends ExprNode { /// The expression being thrown. expression: ExprNode - - fixed builtNode = - let (self = this) - new Node { - type = "throw_expr" - children = - List( - (terminal) { text = "throw" }, - (terminal) { text = "(" }, - self.expression.builtNode, - (terminal) { text = ")" }, - ) - } } /// A `trace(expr)` expression. class TraceExprNode extends ExprNode { /// The expression being traced. expression: ExprNode - - fixed builtNode = - let (self = this) - new Node { - type = "trace_expr" - children = - List( - (terminal) { text = "trace" }, - (terminal) { text = "(" }, - self.expression.builtNode, - (terminal) { text = ")" }, - ) - } } /// An `import("uri")` or `import*("uri")` expression. @@ -1320,19 +714,6 @@ class ImportExprNode extends ExprNode { /// The import URI string. uri: String - - fixed builtNode = - let (self = this) - new Node { - type = "import_expr" - children = - List( - (terminal) { text = if (self.isGlob) "import*" else "import" }, - (terminal) { text = "(" }, - stringCharsNode(self.uri), - (terminal) { text = ")" }, - ) - } } /// A `read(expr)`, `read*(expr)`, or `read?(expr)` expression. @@ -1342,19 +723,6 @@ class ReadExprNode extends ExprNode { /// The expression to be read. expression: ExprNode - - fixed builtNode = - let (self = this) - new Node { - type = "read_expr" - children = - List( - (terminal) { text = self.keyword }, - (terminal) { text = "(" }, - self.expression.builtNode, - (terminal) { text = ")" }, - ) - } } /// A `new Type { ... }` expression. @@ -1364,24 +732,6 @@ class NewExprNode extends ExprNode { /// The object body. body: ObjectBodyNode - - fixed builtNode = - let (self = this) - new Node { - type = "new_expr" - children = - List( - new Node { - type = "new_header" - children = - if (self.type == null) - List((terminal) { text = "new" }) - else - List((terminal) { text = "new" }, self.type.builtNode) - }, - self.body.builtNode, - ) - } } /// An `(expr) { ... }` amends expression. @@ -1391,13 +741,6 @@ class AmendsExprNode extends ExprNode { /// The object body. body: ObjectBodyNode - - fixed builtNode = - let (self = this) - new Node { - type = "amends_expr" - children = List(self.parentExpr.builtNode, self.body.builtNode) - } } /// A binary operator expression (`left op right`), including `is`/`as`. @@ -1413,64 +756,24 @@ class BinaryOpExprNode extends ExprNode { /// The right-hand type, if this is an `is` or `as` operation. rightType: TypeNode? - - fixed builtNode = - let (self = this) - new Node { - type = "binary_op_expr" - children = - if (self.operator == "is" || self.operator == "as") - List( - self.left.builtNode, - (operatorLeaf) { text = self.operator }, - self.rightType!!.builtNode, - ) - else - List( - self.left.builtNode, - (operatorLeaf) { text = self.operator }, - self.right!!.builtNode, - ) - } } /// A unary minus expression (`-expr`). class UnaryMinusExprNode extends ExprNode { /// The operand expression. operand: ExprNode - - fixed builtNode = - let (self = this) - new Node { - type = "unary_minus_expr" - children = List((terminal) { text = "-" }, self.operand.builtNode) - } } /// A logical not expression (`!expr`). class LogicalNotExprNode extends ExprNode { /// The operand expression. operand: ExprNode - - fixed builtNode = - let (self = this) - new Node { - type = "logical_not_expr" - children = List((terminal) { text = "!" }, self.operand.builtNode) - } } /// A non-null assertion expression (`expr!!`). class NonNullExprNode extends ExprNode { /// The operand expression. operand: ExprNode - - fixed builtNode = - let (self = this) - new Node { - type = "non_null_expr" - children = List(self.operand.builtNode, (operatorLeaf) { text = "!!" }) - } } /// A function literal expression (`(params) -> body`). @@ -1480,58 +783,22 @@ class FunctionLiteralExprNode extends ExprNode { /// The body expression. body: ExprNode - - fixed builtNode = - let (self = this) - new Node { - type = "function_literal_expr" - children = - List( - parameterListNode(self.parameters), - (terminal) { text = "->" }, - new Node { - type = "function_literal_body" - children = List(self.body.builtNode) - }, - ) - } } /// A parenthesized expression (`(expr)`). class ParenthesizedExprNode extends ExprNode { /// The inner expression, if present (may be empty for `()`). expression: ExprNode? - - fixed builtNode = - let (self = this) - new Node { - type = "parenthesized_expr" - children = - List( - (terminal) { text = "(" }, - new Node { - type = "parenthesized_expr_elements" - children = List(self.expression!!.builtNode) - }, - (terminal) { text = ")" }, - ) - } } /// The `unknown` type. -class UnknownTypeNode extends TypeNode { - fixed builtNode = new Node { type = "unknown_type"; text = "unknown" } -} +class UnknownTypeNode extends TypeNode {} /// The `nothing` type. -class NothingTypeNode extends TypeNode { - fixed builtNode = new Node { type = "nothing_type"; text = "nothing" } -} +class NothingTypeNode extends TypeNode {} /// The `module` type. -class ModuleTypeNode extends TypeNode { - fixed builtNode = new Node { type = "module_type"; text = "module" } -} +class ModuleTypeNode extends TypeNode {} /// A declared type (e.g., `String`, `List`). class DeclaredTypeNode extends TypeNode { @@ -1540,59 +807,18 @@ class DeclaredTypeNode extends TypeNode { /// The type arguments. typeArguments: List - - fixed builtNode = - let (self = this) - new Node { - type = "declared_type" - children = - if (self.typeArguments.isEmpty) - List(self.name.builtNode) - else - List(self.name.builtNode, new Node { - type = "type_argument_list" - children = - List( - (terminal) { text = "<" }, - new Node { - type = "type_argument_list_elements" - children = commaSeparate(self.typeArguments.map((t) -> t.builtNode)) - }, - (terminal) { text = ">" }, - ) - }) - } } /// A nullable type (`Type?`). class NullableTypeNode extends TypeNode { /// The base type. baseType: TypeNode - - fixed builtNode = - let (self = this) - new Node { - type = "nullable_type" - children = List(self.baseType.builtNode, (terminal) { text = "?" }) - } } /// A union type (`TypeA|TypeB|TypeC`). class UnionTypeNode extends TypeNode { /// The member types. members: List - - fixed builtNode = - let (self = this) - new Node { - type = "union_type" - children = - self.members - .map((m) -> m.builtNode) - .fold(List(), (acc: List, item: Node) -> - if (acc.isEmpty) List(item) else acc.add((terminal) { text = "|" }).add(item) - ) - } } /// A function type (`(ParamTypes) -> ReturnType`). @@ -1602,32 +828,6 @@ class FunctionTypeNode extends TypeNode { /// The return type. returnType: TypeNode - - fixed builtNode = - let (self = this) - new Node { - type = "function_type" - children = - List( - new Node { - type = "function_type_parameters" - children = - if (self.parameterTypes.isEmpty) - List((terminal) { text = "(" }, (terminal) { text = ")" }) - else - List( - (terminal) { text = "(" }, - new Node { - type = "parenthesized_type_elements" - children = commaSeparate(self.parameterTypes.map((t) -> t.builtNode)) - }, - (terminal) { text = ")" }, - ) - }, - (terminal) { text = "->" }, - self.returnType.builtNode, - ) - } } /// A constrained type (`Type(constraint)`). @@ -1637,59 +837,18 @@ class ConstrainedTypeNode extends TypeNode { /// The constraint expressions. constraints: List - - fixed builtNode = - let (self = this) - new Node { - type = "constrained_type" - children = - List(self.baseType.builtNode, new Node { - type = "constrained_type_constraint" - children = - List( - (terminal) { text = "(" }, - new Node { - type = "constrained_type_elements" - children = commaSeparate(self.constraints.map((c) -> c.builtNode)) - }, - (terminal) { text = ")" }, - ) - }) - } } /// A parenthesized type (`(Type)`). class ParenthesizedTypeNode extends TypeNode { /// The inner type, if present. type: TypeNode? - - fixed builtNode = - let (self = this) - new Node { - type = "parenthesized_type" - children = - List( - (terminal) { text = "(" }, - new Node { - type = "parenthesized_type_elements" - children = List(self.type!!.builtNode) - }, - (terminal) { text = ")" }, - ) - } } /// A string constant type (e.g., `"foo"`). class StringConstantTypeNode extends TypeNode { /// The string value. value: String - - fixed builtNode = - let (self = this) - new Node { - type = "string_constant_type" - children = List(stringCharsNode(self.value)) - } } /// An annotation (`@Type { ... }`). @@ -1699,18 +858,6 @@ class AnnotationNode extends SyntaxNode { /// The annotation body, if present. body: ObjectBodyNode? - - fixed builtNode = - let (self = this) - new Node { - type = "annotation" - children = - List( - (terminal) { text = "@" }, - self.type.builtNode, - self.body?.builtNode, - ).filterNonNull() - } } /// A parameter declaration (`name`, `name: Type`, or `_`). @@ -1723,20 +870,6 @@ class ParameterNode extends SyntaxNode { /// The type annotation, if present. typeAnnotation: TypeNode? - - fixed builtNode = - let (self = this) - new Node { - type = "parameter" - children = - if (self.identifier == null) - List((terminal) { text = "_" }) - else if (self.typeAnnotation == null) - List(self.identifier.builtNode) - else - List(self.identifier.builtNode) - + typeAnnotationNodes(self.typeAnnotation) - } } /// A type parameter declaration (`T`, `in T`, or `out T`). @@ -1746,27 +879,12 @@ class TypeParameterNode extends SyntaxNode { /// The type parameter name. identifier: IdentifierNode - - fixed builtNode = - let (self = this) - new Node { - type = "type_parameter" - children = - if (self.variance == null) - List(self.identifier.builtNode) - else - List((terminal) { text = self.variance!! }, self.identifier.builtNode) - } } /// An identifier (a name occurring in the source, e.g. a property or class name). class IdentifierNode extends SyntaxNode { /// The identifier text. value: String - - fixed builtNode = - let (self = this) - new Node { type = "identifier"; text = self.value } } /// A qualified (dotted) identifier, e.g. `foo.bar.baz`. @@ -1776,31 +894,12 @@ class QualifiedIdentifierNode extends SyntaxNode { /// The dotted name (e.g. `"foo.bar.baz"`). value: String - - fixed builtNode = - let (self = this) - new Node { - type = "qualified_identifier" - children = - self.identifiers - .map((i) -> i.builtNode) - .fold(List(), (acc: List, item: Node) -> - if (acc.isEmpty) List(item) else acc.add((terminal) { text = "." }).add(item) - ) - } } /// A doc comment. class DocCommentNode extends SyntaxNode { /// The body text of each line, without the leading `///`. lines: List - - fixed builtNode = - let (self = this) - new Node { - type = "doc_comment" - children = self.lines.map((l) -> new Node { type = "doc_comment_line"; text = "///" + l }) - } } /// Base class for parts of a string literal (text, escapes, newlines, interpolations). @@ -1842,6 +941,8 @@ class StringInterpolationNode extends StringPartNode { /// The interpolated expression. expression: ExprNode + local const terminal: Node = new Node { type = "terminal" } + function toNodes(): List = let (self = this) List( @@ -1850,100 +951,3 @@ class StringInterpolationNode extends StringPartNode { (terminal) { text = ")" }, ) } - -// =============== -// Node construction helpers -// =============== - -local const terminal: Node = new Node { type = "terminal" } - -local const operatorLeaf: Node = new Node { type = "operator" } - -local const commaTerminal: Node = new Node { type = "terminal"; text = "," } - -// Interleave nodes with commas, producing `[a, ",", b, ",", c]` for `[a, b, c]`. -local const function commaSeparate(items: List): List = - items.fold(List(), (acc: List, item: Node) -> - if (acc.isEmpty) List(item) else acc.add(commaTerminal).add(item) - ) - -// Build a `modifier_list` node from a list of modifier strings. -local const function modifierListNode(mods: List): Node = new Node { - type = "modifier_list" - children = mods.map((m) -> new Node { type = "modifier"; text = m }) -} - -// Build a quoted `string_chars` node for a string constant like `"foo"`. -// Parsed nodes derive `text` from source, built nodes must set it themselves. -local const function stringCharsNode(value: String): Node = new Node { - type = "string_chars" - text = "\"" + value + "\"" - children = - List( - (terminal) { text = "\"" }, - (terminal) { text = value }, - (terminal) { text = "\"" }, - ) -} - -// Build a `parameter_list` node from typed parameters. -local const function parameterListNode(parameters: List): Node = new Node { - type = "parameter_list" - children = - if (parameters.isEmpty) - List((terminal) { text = "(" }, (terminal) { text = ")" }) - else - List( - (terminal) { text = "(" }, - new Node { - type = "parameter_list_elements" - children = commaSeparate(parameters.map((p) -> p.builtNode)) - }, - (terminal) { text = ")" }, - ) -} - -// Build an `argument_list` node from typed argument expressions. -local const function argumentListNode(arguments: List): Node = new Node { - type = "argument_list" - children = - if (arguments.isEmpty) - List((terminal) { text = "(" }, (terminal) { text = ")" }) - else - List( - (terminal) { text = "(" }, - new Node { - type = "argument_list_elements" - children = commaSeparate(arguments.map((a) -> a.builtNode)) - }, - (terminal) { text = ")" }, - ) -} - -// Build a `type_parameter_list` node from typed type parameters (empty list → no node). -local const function typeParameterListNodes(typeParameters: List): List = - if (typeParameters.isEmpty) - List() - else - List(new Node { - type = "type_parameter_list" - children = - List( - (terminal) { text = "<" }, - new Node { - type = "type_parameter_list_elements" - children = commaSeparate(typeParameters.map((t) -> t.builtNode)) - }, - (terminal) { text = ">" }, - ) - }) - -// Build a `type_annotation` node from an optional type (null → no node). -local const function typeAnnotationNodes(_type: TypeNode?): List = - if (_type == null) - List() - else - List(new Node { - type = "type_annotation" - children = List((terminal) { text = ":" }, _type.builtNode) - }) From 7e17090e7b22d98bdafd5e8e758a6649a22dffd0 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Wed, 22 Jul 2026 14:17:20 +0200 Subject: [PATCH 14/49] Remove unused methods --- stdlib/syntax.pkl | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index eac8dbf35..2e25c3715 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -71,24 +71,6 @@ class Node { text: String? @ConvertSpan span: Span - - /// The first child of type [t], or `null` if there is none. - function findChild(t: NodeType): Node? = children.findOrNull((c) -> c.type == t) - - /// All children of type [t]. - function findChildren(t: NodeType): List = children.filter((c) -> c.type == t) - - /// The first child that is an expression, or `null` if there is none. - function findExprChild(): Node? = children.findOrNull((c) -> isExprType(c.type)) - - /// All children that are expressions. - function findExprChildren(): List = children.filter((c) -> isExprType(c.type)) - - /// The first child that is a type node, or `null` if there is none. - function findTypeChild(): Node? = children.findOrNull((c) -> isTypeType(c.type)) - - /// All children that are type nodes. - function findTypeChildren(): List = children.filter((c) -> isTypeType(c.type)) } class Span { From f2e6542f7191557883b11ad742a0d2a3ecf7d4c7 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Wed, 22 Jul 2026 14:51:30 +0200 Subject: [PATCH 15/49] Improve doc comment node --- .../org/pkl/core/stdlib/syntax/ParserNodes.java | 17 ++++++++++------- .../pkl/core/stdlib/syntax/SyntaxNodeNodes.java | 6 +++--- .../input/syntax/expressions.pkl | 2 +- .../input/syntax/moduleStructure.pkl | 4 ++-- stdlib/syntax.pkl | 5 ++--- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 9a5a097b1..fa2c5aa06 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -79,7 +79,7 @@ private static VmObjectFactory nodeOnlyFactory(Supplier classS private static final VmObjectFactory docCommentNodeFactory = new VmObjectFactory(SyntaxModule::getDocCommentNodeClass) .addProperty("node", vm -> vm) - .addListProperty("lines", ParserNodes::docCommentLines); + .addStringProperty("value", ParserNodes::docCommentValue); private static final VmObjectFactory annotationNodeFactory = new VmObjectFactory(SyntaxModule::getAnnotationNodeClass) .addProperty("node", vm -> vm) @@ -437,21 +437,24 @@ private static Object annotationBody(VmTyped annotationVm) { return body == null ? VmNull.withoutDefault() : objectBodyNodeFactory.create(body); } - private static VmList docCommentLines(VmTyped docCommentVm) { + private static String docCommentValue(VmTyped docCommentVm) { var lineVms = findChildrenVm(docCommentVm, NodeType.DOC_COMMENT_LINE); - var result = new Object[lineVms.size()]; + var builder = new StringBuilder(); for (var i = 0; i < lineVms.size(); i++) { + if (i > 0) { + builder.append('\n'); + } var data = (NodeData) lineVms.get(i).getExtraStorage(); var text = data.node.text(data.source); if (text.startsWith("/// ")) { - result[i] = text.substring(4); + builder.append(text, 4, text.length()); } else if (text.startsWith("///")) { - result[i] = text.substring(3); + builder.append(text, 3, text.length()); } else { - result[i] = text; + builder.append(text); } } - return VmList.create(result); + return builder.toString(); } private static VmTyped declaredTypeName(VmTyped typeVm) { diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java index 0e3395640..71142b98a 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java @@ -688,10 +688,10 @@ private static VmTyped buildTypeParameter(VmTyped self) { } private static VmTyped buildDocComment(VmTyped self) { - var lines = listMember(self, "lines"); + var value = str(self, "value"); var children = new ArrayList<>(); - for (var i = 0; i < lines.getLength(); i++) { - children.add(leaf("doc_comment_line", "/// " + lines.get(i))); + for (var line : value.split("\n", -1)) { + children.add(leaf("doc_comment_line", "/// " + line)); } return branch("doc_comment", children); } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl index 943a4e4c2..c1ab18d7a 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl @@ -234,7 +234,7 @@ facts { cls.identifier.value == "Person" cls.modifiers == List("abstract", "open") cls.docComment != null - cls.docComment!!.lines.length == 1 + cls.docComment!!.value == "A person." cls.annotations.length == 1 cls.annotations.first.type is syntax.DeclaredTypeNode cls.extendsType is syntax.DeclaredTypeNode diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl index 5cab8ed88..a83b338f8 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl @@ -30,7 +30,7 @@ facts { mod.declaration != null mod.declaration!!.docComment != null mod.declaration!!.docComment!! is syntax.DocCommentNode - mod.declaration!!.docComment!!.lines.length == 1 + mod.declaration!!.docComment!!.value == "This is my module." mod.declaration!!.annotations.length == 1 mod.declaration!!.annotations.first is syntax.AnnotationNode @@ -87,7 +87,7 @@ facts { local cls = mod.classes.first cls.docComment != null - cls.docComment!!.lines.length == 1 + cls.docComment!!.value == "A bird class." cls.modifiers != null cls.modifiers == List("abstract") diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 2e25c3715..f61ffc65c 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -269,7 +269,6 @@ abstract class SyntaxNode { hidden node: Node? = null /// The source span of this node. - @ConvertSpan span: Span = node?.span ?? new Span {} /// This node rebuilt into a generic [Node]. @@ -880,8 +879,8 @@ class QualifiedIdentifierNode extends SyntaxNode { /// A doc comment. class DocCommentNode extends SyntaxNode { - /// The body text of each line, without the leading `///`. - lines: List + /// The body text of the comment, with the leading `///` stripped from each line. + value: String } /// Base class for parts of a string literal (text, escapes, newlines, interpolations). From 81d3f5a7511452cdfc51eb30db858fc816c1d3f2 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Wed, 22 Jul 2026 15:11:10 +0200 Subject: [PATCH 16/49] Make parenthesized type non-nullable --- .../java/org/pkl/core/stdlib/syntax/ParserNodes.java | 12 ++++++------ .../org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java | 4 +--- stdlib/syntax.pkl | 4 ++-- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index fa2c5aa06..9ba3dac1f 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -186,7 +186,7 @@ private static VmObjectFactory nodeOnlyFactory(Supplier classS private static final VmObjectFactory parenthesizedTypeNodeFactory = new VmObjectFactory(SyntaxModule::getParenthesizedTypeNodeClass) .addProperty("node", vm -> vm) - .addProperty("type", ParserNodes::parenthesizedTypeType); + .addTypedProperty("type", ParserNodes::parenthesizedTypeType); private static final VmObjectFactory stringConstantTypeNodeFactory = new VmObjectFactory(SyntaxModule::getStringConstantTypeNodeClass) .addProperty("node", vm -> vm) @@ -523,13 +523,13 @@ private static VmList constrainedTypeConstraints(VmTyped typeVm) { return wrapExprs(findExprChildrenVm(elems)); } - private static Object parenthesizedTypeType(VmTyped typeVm) { + private static VmTyped parenthesizedTypeType(VmTyped typeVm) { var elems = findChildVm(typeVm, NodeType.PARENTHESIZED_TYPE_ELEMENTS); - if (elems == null) { - return VmNull.withoutDefault(); + var type = elems == null ? null : findTypeChildVm(elems); + if (type == null) { + throw new VmExceptionBuilder().bug("A parenthesized type always has an inner type.").build(); } - var type = findTypeChildVm(elems); - return type == null ? VmNull.withoutDefault() : wrapType(type); + return wrapType(type); } private static String stringConstantTypeValue(VmTyped typeVm) { diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java index 71142b98a..94a5113fe 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java @@ -150,9 +150,7 @@ private static VmTyped build(VmTyped self) { "parenthesized_type", List.of( terminal("("), - branch( - "parenthesized_type_elements", - List.of(build(nonNull(optNode(self, "type"))))), + branch("parenthesized_type_elements", List.of(build(reqNode(self, "type")))), terminal(")"))); case "StringConstantTypeNode" -> branch("string_constant_type", List.of(stringCharsNode(str(self, "value")))); diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index f61ffc65c..69231ff6f 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -822,8 +822,8 @@ class ConstrainedTypeNode extends TypeNode { /// A parenthesized type (`(Type)`). class ParenthesizedTypeNode extends TypeNode { - /// The inner type, if present. - type: TypeNode? + /// The inner type. + type: TypeNode } /// A string constant type (e.g., `"foo"`). From c378d82841d6188a50612db0ad2c60681753c184 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Wed, 22 Jul 2026 15:20:58 +0200 Subject: [PATCH 17/49] Add docs to Node and Span --- stdlib/syntax.pkl | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 69231ff6f..56e82511e 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -64,19 +64,40 @@ function fold(node: Node, initial: Acc, accumulate: (Acc, Node) -> Acc): Ac /// A convenience over [fold] for the common case of enumerating nodes. function descendants(node: Node): List = fold(node, List(), (acc, n) -> acc.add(n)) +/// A generic, untyped node in the Pkl syntax tree. +/// +/// A node is either a *leaf*, carrying source [text] and no +/// [children], or a *branch*, carrying [children] and no [text]. class Node { + /// The kind of this node. type: NodeType + + /// The child nodes, in source order. Empty for leaf nodes. children: List + + /// The parent node, or `null` for the root and for nodes not attached to a tree. hidden parent: Node? + + /// The verbatim source text of a leaf node (identifier, literal, terminal, etc.), + /// or `null` for a branch node whose content is its [children]. text: String? - @ConvertSpan + + /// The source location of this node. span: Span } +/// A source location, given as 1-based line and column positions. class Span { + /// The line of the first character. lineStart: UInt = 0 + + /// The column of the first character. colStart: UInt = 0 + + /// The line of the character following the span. lineEnd: UInt = 0 + + /// The column of the character following the span. colEnd: UInt = 0 } From 59412bfeeafc399836191f7ddb3669b62c59a09a Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Wed, 22 Jul 2026 16:53:10 +0200 Subject: [PATCH 18/49] Move format function to Renderer class --- .../java/org/pkl/core/runtime/Identifier.java | 3 ++ .../pkl/core/stdlib/syntax/RendererNodes.java | 39 ++++++++++++++++ .../pkl/core/stdlib/syntax/SyntaxNodes.java | 15 +------ .../input/syntax/{format.pkl => render.pkl} | 45 ++++++++++++------- .../input/syntax/walk.pkl | 4 +- .../output/syntax/{format.pcf => render.pcf} | 7 +++ stdlib/syntax.pkl | 15 +++++-- 7 files changed, 94 insertions(+), 34 deletions(-) create mode 100644 pkl-core/src/main/java/org/pkl/core/stdlib/syntax/RendererNodes.java rename pkl-core/src/test/files/LanguageSnippetTests/input/syntax/{format.pkl => render.pkl} (80%) rename pkl-core/src/test/files/LanguageSnippetTests/output/syntax/{format.pcf => render.pcf} (92%) diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/Identifier.java b/pkl-core/src/main/java/org/pkl/core/runtime/Identifier.java index 57c83d444..6022fcb9e 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/Identifier.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/Identifier.java @@ -181,6 +181,9 @@ public final class Identifier implements Comparable { public static final Identifier LINE_END = get("lineEnd"); public static final Identifier COL_END = get("colEnd"); + // members of pkl.syntax#Renderer + public static final Identifier GRAMMAR_VERSION = get("grammarVersion"); + private final String name; private Identifier(String name) { diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/RendererNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/RendererNodes.java new file mode 100644 index 000000000..5ef65b8b2 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/RendererNodes.java @@ -0,0 +1,39 @@ +/* + * Copyright © 2026 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.stdlib.syntax; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.Specialization; +import org.pkl.core.runtime.Identifier; +import org.pkl.core.runtime.VmTyped; +import org.pkl.core.runtime.VmUtils; +import org.pkl.core.stdlib.ExternalMethod1Node; +import org.pkl.formatter.Formatter; +import org.pkl.formatter.GrammarVersion; + +public final class RendererNodes { + private RendererNodes() {} + + public abstract static class render extends ExternalMethod1Node { + @Specialization + @TruffleBoundary + protected String eval(VmTyped self, VmTyped nodeVm) { + var grammarVersion = (String) VmUtils.readMember(self, Identifier.GRAMMAR_VERSION); + var node = SyntaxNodes.convertVmToNode(nodeVm, SyntaxNodes.ZERO_SPAN); + return new Formatter(GrammarVersion.valueOf(grammarVersion)).format(node); + } + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java index 9e1d8a292..0f1c90d38 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java @@ -32,8 +32,6 @@ import org.pkl.core.runtime.VmUtils; import org.pkl.core.stdlib.ExternalMethod2Node; import org.pkl.core.stdlib.VmObjectFactory; -import org.pkl.formatter.Formatter; -import org.pkl.formatter.GrammarVersion; import org.pkl.parser.syntax.generic.FullSpan; import org.pkl.parser.syntax.generic.Node; import org.pkl.parser.syntax.generic.NodeType; @@ -42,7 +40,7 @@ public final class SyntaxNodes { private SyntaxNodes() {} private static final char[] EMPTY_SOURCE = new char[0]; - private static final FullSpan ZERO_SPAN = new FullSpan(0, 0, 0, 0, 0, 0); + static final FullSpan ZERO_SPAN = new FullSpan(0, 0, 0, 0, 0, 0); /** Extra storage backing a Pkl {@code Node} instance. */ static final class NodeData { @@ -73,15 +71,6 @@ static final class NodeData { : VmNull.withoutDefault()) .addTypedProperty("span", nd -> nd.spanVm); - public abstract static class formatToString extends ExternalMethod2Node { - @Specialization - @TruffleBoundary - protected String eval(VmTyped self, VmTyped nodeVm, String grammarVersion) { - var node = convertVmToNode(nodeVm, ZERO_SPAN); - return new Formatter(GrammarVersion.valueOf(grammarVersion)).format(node); - } - } - public abstract static class walk extends ExternalMethod2Node { @Child private ApplyVmFunction1Node applyVisit = ApplyVmFunction1Node.create(); @@ -170,7 +159,7 @@ private static VmTyped rebuild(VmTyped template, Object[] newChildrenVm) { * meaningful span of their own, so that a subtree spliced into reused siblings lines up with * them. */ - private static Node convertVmToNode(VmTyped nodeVm, FullSpan fallbackSpan) { + static Node convertVmToNode(VmTyped nodeVm, FullSpan fallbackSpan) { // a node still carrying its parse-time storage is verbatim from `parse`: reuse it wholesale if (nodeVm.hasExtraStorage()) { return ((NodeData) nodeVm.getExtraStorage()).node; diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/format.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl similarity index 80% rename from pkl-core/src/test/files/LanguageSnippetTests/input/syntax/format.pkl rename to pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl index ed4ebc37b..486642274 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/format.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl @@ -2,7 +2,7 @@ amends "../snippetTest.pkl" import "pkl:syntax" -local function roundTrip(source: String) = syntax.format(parseNode(source)) +local function roundTrip(source: String) = syntax.render(parseNode(source)!!) local function parseNode(source: String) = new syntax.Parser {}.parseModule(source).node @@ -314,38 +314,38 @@ facts { ["modify identifier"] { local root = parseNode("x = 1") - local modified = replaceLeaf(root, "identifier", "x", "y") - syntax.format(modified) == "y = 1\n" + local modified = replaceLeaf(root!!, "identifier", "x", "y") + syntax.render(modified) == "y = 1\n" } ["modify modifier"] { local root = parseNode("hidden x = 1") - local modified = replaceLeaf(root, "modifier", "hidden", "local") - syntax.format(modified) == "local x = 1\n" + local modified = replaceLeaf(root!!, "modifier", "hidden", "local") + syntax.render(modified) == "local x = 1\n" } ["modify string content"] { local root = parseNode(#"x = "hello""#) - local modified = replaceLeaf(root, "string_chars", "hello", "world") - syntax.format(modified) == #"x = "world"\#n"# + local modified = replaceLeaf(root!!, "string_chars", "hello", "world") + syntax.render(modified) == #"x = "world"\#n"# } ["modify int literal"] { local root = parseNode("x = 42") - local modified = replaceLeaf(root, "int_literal_expr", "42", "99") - syntax.format(modified) == "x = 99\n" + local modified = replaceLeaf(root!!, "int_literal_expr", "42", "99") + syntax.render(modified) == "x = 99\n" } ["modify boolean literal"] { local root = parseNode("x = true") - local modified = replaceLeaf(root, "bool_literal_expr", "true", "false") - syntax.format(modified) == "x = false\n" + local modified = replaceLeaf(root!!, "bool_literal_expr", "true", "false") + syntax.render(modified) == "x = false\n" } ["modify float literal"] { local root = parseNode("x = 3.14") - local modified = replaceLeaf(root, "float_literal_expr", "3.14", "2.72") - syntax.format(modified) == "x = 2.72\n" + local modified = replaceLeaf(root!!, "float_literal_expr", "3.14", "2.72") + syntax.render(modified) == "x = 2.72\n" } ["add new modifier"] { @@ -355,13 +355,26 @@ facts { text = "const" } local modified = - transformFirst(root, "modifier_list", (n) -> (n) { + transformFirst(root!!, "modifier_list", (n) -> (n) { children = List(constModifier) + n.children }) // modifier order is switched by the formatter - syntax.format(modified) == """ + syntax.render(modified) == """ local const x = 1 - + """ } + + ["format delegates to a default Renderer"] { + local node = parseNode("x = 1") + syntax.render(node!!) == new syntax.Renderer {}.render(node!!) + new syntax.Renderer {}.render(node!!) == new syntax.Renderer { grammarVersion = "V2" }.render(node!!) + } + + ["renderer honors the grammar version"] { + local node = + parseNode("x = fn(argument1, argument2, argument3, argument4, argument5, argument6, argument7, argument8, argument9)") + new syntax.Renderer { grammarVersion = "V1" }.render(node!!) + != new syntax.Renderer { grammarVersion = "V2" }.render(node!!) + } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl index 4b1acc735..ecc8116a3 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl @@ -4,12 +4,12 @@ import "pkl:syntax" local function mod(source: String): syntax.ModuleNode = new syntax.Parser {}.parseModule(source) -local function fmt(source: String): String = syntax.format(mod(source).node) +local function fmt(source: String): String = syntax.render(mod(source).node!!) local function walkFormat( source: String, visit: (syntax.Node) -> Pair?, -): String = syntax.format(syntax.walk(mod(source).node, visit)) +): String = syntax.render(syntax.walk(mod(source).node!!, visit)) facts { ["read-only walk leaves the tree unchanged"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/format.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/render.pcf similarity index 92% rename from pkl-core/src/test/files/LanguageSnippetTests/output/syntax/format.pcf rename to pkl-core/src/test/files/LanguageSnippetTests/output/syntax/render.pcf index 4b8ab0fbd..73e1220b4 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/format.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/render.pcf @@ -127,4 +127,11 @@ facts { ["add new modifier"] { true } + ["format delegates to a default Renderer"] { + true + true + } + ["renderer honors the grammar version"] { + true + } } diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 56e82511e..d846afb21 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -29,10 +29,19 @@ class Parser { external function parseModuleOrNull(source: String | Resource): ModuleNode? } -/// Format a syntax node back to Pkl source code. -function format(node: Node): String = formatToString(node, "V2") +/// Renders a syntax [Node] back to Pkl source code. +class Renderer { + /// The grammar version to target. + /// + /// `"V1"` matches Pkl 0.25 - 0.29, `"V2"` matches Pkl 0.30+. + grammarVersion: "V1" | "V2" = "V2" + + /// Render [node] as Pkl source code. + external function render(node: Node): String +} -external function formatToString(node: Node, grammarVersion: "V1" | "V2"): String +/// Render a syntax node back to Pkl source code using default settings. +function render(node: Node): String = new Renderer {}.render(node) /// Walk [node] and its descendants top-down, applying [visit] to each node and /// returning the (possibly rewritten) tree. From d5e5f9f2fcc114a737f7feaa525b61b9e3ed6d02 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Mon, 27 Jul 2026 14:22:43 +0200 Subject: [PATCH 19/49] Add ExtendsOrAmendsClauseNode to CST --- .../org/pkl/core/runtime/SyntaxModule.java | 8 ++++ .../pkl/core/stdlib/syntax/ParserNodes.java | 24 +++++++--- .../core/stdlib/syntax/SyntaxNodeNodes.java | 16 ++++--- .../input/syntax/moduleStructure.pkl | 44 +++++++------------ .../input/syntax/objectMembers.pkl | 3 +- .../output/syntax/moduleStructure.pcf | 8 ++-- stdlib/syntax.pkl | 14 ++++-- 7 files changed, 67 insertions(+), 50 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java index 65a8d8b1a..82bc8a524 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java @@ -45,6 +45,10 @@ public static VmClass getModuleDeclarationNodeClass() { return ModuleDeclarationNodeClass.instance; } + public static VmClass getExtendsOrAmendsClauseNodeClass() { + return ExtendsOrAmendsClauseNodeClass.instance; + } + public static VmClass getImportNodeClass() { return ImportNodeClass.instance; } @@ -313,6 +317,10 @@ private static final class ModuleDeclarationNodeClass { static final VmClass instance = loadClass("ModuleDeclarationNode"); } + private static final class ExtendsOrAmendsClauseNodeClass { + static final VmClass instance = loadClass("ExtendsOrAmendsClauseNode"); + } + private static final class ImportNodeClass { static final VmClass instance = loadClass("ImportNode"); } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 9ba3dac1f..68de57318 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -347,8 +347,13 @@ private static VmObjectFactory nodeOnlyFactory(Supplier classS .addListProperty("annotations", ParserNodes::annotationsOf) .addListProperty("modifiers", ParserNodes::moduleDeclModifiers) .addProperty("name", ParserNodes::moduleDeclName) - .addProperty("amendsUri", ParserNodes::moduleDeclAmendsUri) - .addProperty("extendsUri", ParserNodes::moduleDeclExtendsUri); + .addProperty("extendsOrAmendsClause", ParserNodes::moduleDeclExtendsOrAmendsClause); + + private static final VmObjectFactory extendsOrAmendsClauseNodeFactory = + new VmObjectFactory(SyntaxModule::getExtendsOrAmendsClauseNodeClass) + .addProperty("node", vm -> vm) + .addBooleanProperty("isAmend", ParserNodes::extendsOrAmendsClauseIsAmend) + .addStringProperty("uri", ParserNodes::stringCharsOf); private static final VmObjectFactory classNodeFactory = new VmObjectFactory(SyntaxModule::getClassNodeClass) @@ -989,14 +994,19 @@ private static Object moduleDeclName(VmTyped declVm) { return name == null ? VmNull.withoutDefault() : qualifiedIdentifierNodeFactory.create(name); } - private static Object moduleDeclAmendsUri(VmTyped declVm) { + private static Object moduleDeclExtendsOrAmendsClause(VmTyped declVm) { var clause = findChildVm(declVm, NodeType.AMENDS_CLAUSE); - return clause == null ? VmNull.withoutDefault() : stringCharsOf(clause); + if (clause == null) { + clause = findChildVm(declVm, NodeType.EXTENDS_CLAUSE); + } + return clause == null + ? VmNull.withoutDefault() + : extendsOrAmendsClauseNodeFactory.create(clause); } - private static Object moduleDeclExtendsUri(VmTyped declVm) { - var clause = findChildVm(declVm, NodeType.EXTENDS_CLAUSE); - return clause == null ? VmNull.withoutDefault() : stringCharsOf(clause); + private static boolean extendsOrAmendsClauseIsAmend(VmTyped clauseVm) { + var data = (NodeData) clauseVm.getExtraStorage(); + return data.node.type == NodeType.AMENDS_CLAUSE; } private static VmList moduleClasses(VmTyped moduleVm) { diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java index 94a5113fe..424d5980f 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java @@ -54,6 +54,13 @@ private static VmTyped build(VmTyped self) { return switch (self.getVmClass().getSimpleName()) { case "ModuleNode" -> buildModule(self); case "ModuleDeclarationNode" -> buildModuleDeclaration(self); + case "ExtendsOrAmendsClauseNode" -> + bool(self, "isAmend") + ? branch( + "amends_clause", List.of(terminal("amends"), stringCharsNode(str(self, "uri")))) + : branch( + "extends_clause", + List.of(terminal("extends"), stringCharsNode(str(self, "uri")))); case "ImportNode" -> buildImport(self); case "ClassNode" -> buildClass(self); case "TypeAliasNode" -> buildTypeAlias(self); @@ -202,12 +209,9 @@ private static VmTyped buildModuleDeclaration(VmTyped self) { } else if (modifiers.getLength() > 0) { children.add(modifierListNode(modifiers)); } - var amendsUri = member(self, "amendsUri"); - var extendsUri = member(self, "extendsUri"); - if (amendsUri instanceof String uri) { - children.add(branch("amends_clause", List.of(terminal("amends"), stringCharsNode(uri)))); - } else if (extendsUri instanceof String uri) { - children.add(branch("extends_clause", List.of(terminal("extends"), stringCharsNode(uri)))); + var clause = optNode(self, "extendsOrAmendsClause"); + if (clause != null) { + children.add(build(clause)); } return branch("module_declaration", children); } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl index a83b338f8..ab636bc22 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl @@ -8,32 +8,26 @@ local function parse(source: String) = parser.parseModule(source) facts { ["module declaration"] { - local result = parse("module my.app") - result is syntax.ModuleNode - local mod = result as syntax.ModuleNode + local mod = parse("module my.app") mod.declaration != null mod.declaration!!.name!!.value == "my.app" mod.declaration!!.docComment == null mod.declaration!!.annotations.length == 0 mod.declaration!!.modifiers.isEmpty - mod.declaration!!.amendsUri == null - mod.declaration!!.extendsUri == null + mod.declaration!!.extendsOrAmendsClause == null } ["module with modifiers and doc comment"] { - local result = parse(""" + local mod = parse(""" /// This is my module. @Deprecated { message = "use other" } open module my.mod """) - local mod = result as syntax.ModuleNode mod.declaration != null mod.declaration!!.docComment != null - mod.declaration!!.docComment!! is syntax.DocCommentNode mod.declaration!!.docComment!!.value == "This is my module." mod.declaration!!.annotations.length == 1 - mod.declaration!!.annotations.first is syntax.AnnotationNode mod.declaration!!.annotations.first.type is syntax.DeclaredTypeNode mod.declaration!!.modifiers != null @@ -41,26 +35,27 @@ facts { } ["amends clause"] { - local result = parse(#"amends "base.pkl""#) - local mod = result as syntax.ModuleNode + local mod = parse(#"amends "base.pkl""#) mod.declaration != null - mod.declaration!!.amendsUri == "base.pkl" + mod.declaration!!.extendsOrAmendsClause != null + mod.declaration!!.extendsOrAmendsClause!!.isAmend == true + mod.declaration!!.extendsOrAmendsClause!!.uri == "base.pkl" } ["extends clause"] { - local result = parse(#"extends "base.pkl""#) - local mod = result as syntax.ModuleNode + local mod = parse(#"extends "base.pkl""#) mod.declaration != null - mod.declaration!!.extendsUri == "base.pkl" + mod.declaration!!.extendsOrAmendsClause != null + mod.declaration!!.extendsOrAmendsClause!!.isAmend == false + mod.declaration!!.extendsOrAmendsClause!!.uri == "base.pkl" } ["imports"] { - local result = parse(""" + local mod = parse(""" import "foo.pkl" import "bar.pkl" as myBar import* "*.pkl" """) - local mod = result as syntax.ModuleNode mod.imports.length == 3 mod.imports[0].uri == "foo.pkl" @@ -75,14 +70,13 @@ facts { } ["class declaration"] { - local result = parse(""" + local mod = parse(""" /// A bird class. abstract class Bird { name: String function fly(speed: Int): Boolean = true } """) - local mod = result as syntax.ModuleNode mod.classes.length == 1 local cls = mod.classes.first @@ -114,12 +108,11 @@ facts { } ["class with extends and type parameters"] { - local result = parse(""" + local mod = parse(""" class Container extends Base { item: T } """) - local mod = result as syntax.ModuleNode local cls = mod.classes.first cls.identifier.value == "Container" @@ -133,8 +126,7 @@ facts { } ["typealias"] { - local result = parse("typealias Positive = Int(this > 0)") - local mod = result as syntax.ModuleNode + local mod = parse("typealias Positive = Int(this > 0)") mod.typeAliases.length == 1 local ta = mod.typeAliases.first ta.identifier.value == "Positive" @@ -143,12 +135,11 @@ facts { } ["top-level properties and methods"] { - local result = parse(""" + local mod = parse(""" hidden name: String = "pkl" local count: Int = 42 function greet(who: String): String = "hi" """) - local mod = result as syntax.ModuleNode mod.properties.length == 2 mod.properties[0].identifier.value == "name" @@ -168,8 +159,7 @@ facts { } ["parameter variations"] { - local result = parse("function f(x: Int, _, y): Boolean = true") - local mod = result as syntax.ModuleNode + local mod = parse("function f(x: Int, _, y): Boolean = true") local params = mod.methods.first.parameters params.length == 3 diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl index 621252464..7785bfbbb 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl @@ -142,8 +142,7 @@ facts { } ["object body with parameters"] { - local result = parse("x = new Listing { a, b -> a }") - local mod = result as syntax.ModuleNode + local mod = parse("x = new Listing { a, b -> a }") local propVal = mod.properties.first.value propVal is syntax.NewExprNode local newBody = (propVal as syntax.NewExprNode).body diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf index fb09ff624..cfceecf27 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf @@ -6,8 +6,6 @@ facts { true true true - true - true } ["module with modifiers and doc comment"] { true @@ -17,16 +15,18 @@ facts { true true true - true - true } ["amends clause"] { true true + true + true } ["extends clause"] { true true + true + true } ["imports"] { true diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index d846afb21..059a387bd 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -351,11 +351,17 @@ class ModuleDeclarationNode extends SyntaxNode { /// The qualified name of the module, if present. name: QualifiedIdentifierNode? - /// The URI string of the amended module, if any. Mutually exclusive with [extendsUri]. - amendsUri: String? + /// The `extends` or `amends` clause, if present. + extendsOrAmendsClause: ExtendsOrAmendsClauseNode? +} + +/// The `extends` or `amends` clause of a module declaration. +class ExtendsOrAmendsClauseNode extends SyntaxNode { + /// Whether this is an `amends` clause. When `false`, this is an `extends` clause. + isAmend: Boolean - /// The URI string of the extended module, if any. Mutually exclusive with [amendsUri]. - extendsUri: String? + /// The URI string of the amended or extended module. + uri: String } /// An import declaration. From 9b456dcf522646bec0ed7815297316d852625de9 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Mon, 27 Jul 2026 14:50:16 +0200 Subject: [PATCH 20/49] Rename extendsType to superType --- .../main/java/org/pkl/core/stdlib/syntax/ParserNodes.java | 4 ++-- .../java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java | 6 +++--- .../files/LanguageSnippetTests/input/syntax/expressions.pkl | 4 ++-- .../LanguageSnippetTests/input/syntax/moduleStructure.pkl | 6 +++--- stdlib/syntax.pkl | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 68de57318..dfd03238f 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -363,7 +363,7 @@ private static VmObjectFactory nodeOnlyFactory(Supplier classS .addListProperty("modifiers", ParserNodes::classModifiers) .addTypedProperty("identifier", ParserNodes::classIdentifier) .addListProperty("typeParameters", ParserNodes::classTypeParameters) - .addProperty("extendsType", ParserNodes::classExtendsType) + .addProperty("superType", ParserNodes::classSuperType) .addProperty("body", ParserNodes::classBody); private static final VmObjectFactory typeAliasNodeFactory = @@ -1026,7 +1026,7 @@ private static VmList classTypeParameters(VmTyped classVm) { return typeParametersOf(findChildVm(classVm, NodeType.CLASS_HEADER)); } - private static Object classExtendsType(VmTyped classVm) { + private static Object classSuperType(VmTyped classVm) { var header = findChildVm(classVm, NodeType.CLASS_HEADER); if (header == null) { return VmNull.withoutDefault(); diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java index 424d5980f..9d530e403 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java @@ -237,9 +237,9 @@ private static VmTyped buildClass(VmTyped self) { header.add(terminal("class")); header.add(build(reqNode(self, "identifier"))); header.addAll(typeParameterListNodes(listMember(self, "typeParameters"))); - var extendsType = optNode(self, "extendsType"); - if (extendsType != null) { - header.add(branch("class_header_extends", List.of(terminal("extends"), build(extendsType)))); + var superType = optNode(self, "superType"); + if (superType != null) { + header.add(branch("class_header_extends", List.of(terminal("extends"), build(superType)))); } children.add(branch("class_header", header)); var body = optNode(self, "body"); diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl index c1ab18d7a..e23882c0a 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl @@ -237,7 +237,7 @@ facts { cls.docComment!!.value == "A person." cls.annotations.length == 1 cls.annotations.first.type is syntax.DeclaredTypeNode - cls.extendsType is syntax.DeclaredTypeNode + cls.superType is syntax.DeclaredTypeNode cls.body != null cls.body!!.properties.length == 1 cls.body!!.methods.length == 1 @@ -260,7 +260,7 @@ facts { cls.modifiers.isEmpty cls.docComment == null cls.annotations.isEmpty - cls.extendsType == null + cls.superType == null cls.body == null cls.typeParameters.isEmpty } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl index ab636bc22..351623c26 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl @@ -89,7 +89,7 @@ facts { cls.identifier.value == "Bird" cls.typeParameters.isEmpty - cls.extendsType == null + cls.superType == null cls.body != null cls.body!!.properties.length == 1 @@ -121,8 +121,8 @@ facts { cls.typeParameters.first.identifier.value == "T" cls.typeParameters.first.variance == null - cls.extendsType != null - cls.extendsType is syntax.DeclaredTypeNode + cls.superType != null + cls.superType is syntax.DeclaredTypeNode } ["typealias"] { diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 059a387bd..564d53f67 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -394,7 +394,7 @@ class ClassNode extends SyntaxNode { typeParameters: List /// The supertype this class extends, if present. - extendsType: TypeNode? + superType: TypeNode? /// The class body, if present. body: ClassBodyNode? From 83c2c624c7b5a8a9d5efa8d12bfa66555bd219e5 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Mon, 27 Jul 2026 15:35:36 +0200 Subject: [PATCH 21/49] Move top level functions to Node --- .../org/pkl/core/stdlib/syntax/NodeNodes.java | 82 ++++++++++++++++++ .../pkl/core/stdlib/syntax/SyntaxNodes.java | 58 +------------ .../input/syntax/render.pkl | 18 ++-- .../input/syntax/traversal.pkl | 16 ++-- .../input/syntax/walk.pkl | 4 +- stdlib/syntax.pkl | 85 +++++++++---------- 6 files changed, 142 insertions(+), 121 deletions(-) create mode 100644 pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java new file mode 100644 index 000000000..43178de96 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java @@ -0,0 +1,82 @@ +/* + * Copyright © 2026 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.stdlib.syntax; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.Specialization; +import org.pkl.core.ast.lambda.ApplyVmFunction1Node; +import org.pkl.core.runtime.Identifier; +import org.pkl.core.runtime.VmFunction; +import org.pkl.core.runtime.VmList; +import org.pkl.core.runtime.VmPair; +import org.pkl.core.runtime.VmTyped; +import org.pkl.core.runtime.VmUtils; +import org.pkl.core.stdlib.ExternalMethod1Node; +import org.pkl.core.stdlib.syntax.SyntaxNodes.NodeData; + +public final class NodeNodes { + private NodeNodes() {} + + public abstract static class walk extends ExternalMethod1Node { + @Child private ApplyVmFunction1Node applyVisit = ApplyVmFunction1Node.create(); + + @Specialization + @TruffleBoundary + protected VmTyped eval(VmTyped self, VmFunction visit) { + var result = walkNode(self, visit); + // the root of the returned tree has no parent + if (result.hasExtraStorage()) { + ((NodeData) result.getExtraStorage()).parentVm = null; + } + return result; + } + + private VmTyped walkNode(VmTyped nodeVm, VmFunction visit) { + var visited = applyVisit.execute(visit, nodeVm); + + VmTyped node; + boolean descend; + if (visited instanceof VmPair pair) { + node = (VmTyped) pair.getFirst(); + descend = (Boolean) pair.getSecond(); + } else { + // `null`: leave this node unchanged and keep descending + node = nodeVm; + descend = true; + } + if (!descend) { + return node; + } + + var childrenVm = (VmList) VmUtils.readMember(node, Identifier.CHILDREN); + var length = childrenVm.getLength(); + if (length == 0) { + return node; + } + + var newChildren = new Object[length]; + var changed = false; + for (var i = 0; i < length; i++) { + var child = (VmTyped) childrenVm.get(i); + var newChild = walkNode(child, visit); + newChildren[i] = newChild; + changed |= newChild != child; + } + // reuse the node (and its extra storage) untouched when nothing below changed + return changed ? SyntaxNodes.rebuild(node, newChildren) : node; + } + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java index 0f1c90d38..d16398504 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java @@ -15,22 +15,16 @@ */ package org.pkl.core.stdlib.syntax; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.dsl.Specialization; import java.util.ArrayList; import java.util.List; import java.util.Locale; import org.jspecify.annotations.Nullable; -import org.pkl.core.ast.lambda.ApplyVmFunction1Node; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.SyntaxModule; -import org.pkl.core.runtime.VmFunction; import org.pkl.core.runtime.VmList; import org.pkl.core.runtime.VmNull; -import org.pkl.core.runtime.VmPair; import org.pkl.core.runtime.VmTyped; import org.pkl.core.runtime.VmUtils; -import org.pkl.core.stdlib.ExternalMethod2Node; import org.pkl.core.stdlib.VmObjectFactory; import org.pkl.parser.syntax.generic.FullSpan; import org.pkl.parser.syntax.generic.Node; @@ -71,58 +65,8 @@ static final class NodeData { : VmNull.withoutDefault()) .addTypedProperty("span", nd -> nd.spanVm); - public abstract static class walk extends ExternalMethod2Node { - @Child private ApplyVmFunction1Node applyVisit = ApplyVmFunction1Node.create(); - - @Specialization - @TruffleBoundary - protected VmTyped eval(VmTyped self, VmTyped node, VmFunction visit) { - var result = walkNode(node, visit); - // the root of the returned tree has no parent - if (result.hasExtraStorage()) { - ((NodeData) result.getExtraStorage()).parentVm = null; - } - return result; - } - - private VmTyped walkNode(VmTyped nodeVm, VmFunction visit) { - var visited = applyVisit.execute(visit, nodeVm); - - VmTyped node; - boolean descend; - if (visited instanceof VmPair pair) { - node = (VmTyped) pair.getFirst(); - descend = (Boolean) pair.getSecond(); - } else { - // `null`: leave this node unchanged and keep descending - node = nodeVm; - descend = true; - } - if (!descend) { - return node; - } - - var childrenVm = (VmList) VmUtils.readMember(node, Identifier.CHILDREN); - var length = childrenVm.getLength(); - if (length == 0) { - return node; - } - - var newChildren = new Object[length]; - var changed = false; - for (var i = 0; i < length; i++) { - var child = (VmTyped) childrenVm.get(i); - var newChild = walkNode(child, visit); - newChildren[i] = newChild; - changed |= newChild != child; - } - // reuse the node (and its extra storage) untouched when nothing below changed - return changed ? rebuild(node, newChildren) : node; - } - } - /** Rebuild a node from {@code template} (its type, span, text) with new children. */ - private static VmTyped rebuild(VmTyped template, Object[] newChildrenVm) { + static VmTyped rebuild(VmTyped template, Object[] newChildrenVm) { var nodeType = NodeType.valueOf( ((String) VmUtils.readMember(template, Identifier.TYPE)).toUpperCase(Locale.ROOT)); diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl index 486642274..f1a9db1a3 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl @@ -2,7 +2,7 @@ amends "../snippetTest.pkl" import "pkl:syntax" -local function roundTrip(source: String) = syntax.render(parseNode(source)!!) +local function roundTrip(source: String) = parseNode(source)!!.render() local function parseNode(source: String) = new syntax.Parser {}.parseModule(source).node @@ -315,37 +315,37 @@ facts { ["modify identifier"] { local root = parseNode("x = 1") local modified = replaceLeaf(root!!, "identifier", "x", "y") - syntax.render(modified) == "y = 1\n" + modified.render() == "y = 1\n" } ["modify modifier"] { local root = parseNode("hidden x = 1") local modified = replaceLeaf(root!!, "modifier", "hidden", "local") - syntax.render(modified) == "local x = 1\n" + modified.render() == "local x = 1\n" } ["modify string content"] { local root = parseNode(#"x = "hello""#) local modified = replaceLeaf(root!!, "string_chars", "hello", "world") - syntax.render(modified) == #"x = "world"\#n"# + modified.render() == #"x = "world"\#n"# } ["modify int literal"] { local root = parseNode("x = 42") local modified = replaceLeaf(root!!, "int_literal_expr", "42", "99") - syntax.render(modified) == "x = 99\n" + modified.render() == "x = 99\n" } ["modify boolean literal"] { local root = parseNode("x = true") local modified = replaceLeaf(root!!, "bool_literal_expr", "true", "false") - syntax.render(modified) == "x = false\n" + modified.render() == "x = false\n" } ["modify float literal"] { local root = parseNode("x = 3.14") local modified = replaceLeaf(root!!, "float_literal_expr", "3.14", "2.72") - syntax.render(modified) == "x = 2.72\n" + modified.render() == "x = 2.72\n" } ["add new modifier"] { @@ -359,7 +359,7 @@ facts { children = List(constModifier) + n.children }) // modifier order is switched by the formatter - syntax.render(modified) == """ + modified.render() == """ local const x = 1 """ @@ -367,7 +367,7 @@ facts { ["format delegates to a default Renderer"] { local node = parseNode("x = 1") - syntax.render(node!!) == new syntax.Renderer {}.render(node!!) + node!!.render() == new syntax.Renderer {}.render(node!!) new syntax.Renderer {}.render(node!!) == new syntax.Renderer { grammarVersion = "V2" }.render(node!!) } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl index 137491d6e..82cad1664 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl @@ -23,15 +23,15 @@ local sample: syntax.Node = facts { ["fold counts nodes by predicate"] { - syntax.fold(sample, 0, (acc, n) -> if (n.type == "import") acc + 1 else acc) == 2 + sample.fold(0, (acc, n) -> if (n.type == "import") acc + 1 else acc) == 2 - syntax.fold(sample, 0, (acc, n) -> if (n.type == "if_expr") acc + 1 else acc) == 1 + sample.fold(0, (acc, n) -> if (n.type == "if_expr") acc + 1 else acc) == 1 - syntax.fold(sample, 0, (acc, n) -> if (n.type == "when_generator") acc + 1 else acc) == 0 + sample.fold(0, (acc, n) -> if (n.type == "when_generator") acc + 1 else acc) == 0 } ["fold accumulates into a collection"] { - syntax.fold(sample, List(), (acc, n) -> if (n.type == "identifier") acc.add(n.text) else acc) + sample.fold(List(), (acc, n) -> if (n.type == "identifier") acc.add(n.text) else acc) == List( "baz", "Point", @@ -52,14 +52,14 @@ facts { ["fold visits a node before its children (pre-order)"] { // the module node is visited first - syntax.fold(sample, List(), (acc, n) -> acc.add(n.type)).first == "module" + sample.fold(List(), (acc, n) -> acc.add(n.type)).first == "module" } ["descendants enumerates the whole tree"] { - syntax.descendants(sample).first == sample + sample.descendants().first == sample - syntax.descendants(sample) == syntax.fold(sample, List(), (acc, n) -> acc.add(n)) + sample.descendants() == sample.fold(List(), (acc, n) -> acc.add(n)) - syntax.descendants(sample).filter((n) -> n.type == "class").length == 1 + sample.descendants().filter((n) -> n.type == "class").length == 1 } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl index ecc8116a3..e3c24ec2d 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl @@ -4,12 +4,12 @@ import "pkl:syntax" local function mod(source: String): syntax.ModuleNode = new syntax.Parser {}.parseModule(source) -local function fmt(source: String): String = syntax.render(mod(source).node!!) +local function fmt(source: String): String = mod(source).node!!.render() local function walkFormat( source: String, visit: (syntax.Node) -> Pair?, -): String = syntax.render(syntax.walk(mod(source).node!!, visit)) +): String = mod(source).node!!.walk(visit).render() facts { ["read-only walk leaves the tree unchanged"] { diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 564d53f67..f89ad9c1a 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -40,39 +40,6 @@ class Renderer { external function render(node: Node): String } -/// Render a syntax node back to Pkl source code using default settings. -function render(node: Node): String = new Renderer {}.render(node) - -/// Walk [node] and its descendants top-down, applying [visit] to each node and -/// returning the (possibly rewritten) tree. -/// -/// For each node, [visit] returns either: -/// - `null` to leave the node unchanged and continue walking into its children. -/// - `Pair(replacement, descend)` to replace the node with `replacement`. When -/// `descend` is `true`, the children of `replacement` are visited in turn, so -/// nodes emitted by a rewrite are themselves processed further down. When -/// `descend` is `false`, `replacement` and its subtree are left as-is. -/// -/// Constructed nodes must set their own [Node.text] where it applies. -/// [Node.span] is carried through unchanged unless set explicitly. -/// [Node.parent] is populated on the returned tree for nodes originating from [parse]; -/// nodes constructed from scratch retain their given `parent`. -external function walk(node: Node, visit: (Node) -> Pair?): Node - -/// Fold [accumulate] over [node] and its descendants, top-down in pre-order. -/// -/// ``` -/// // count the if-expressions in a module -/// fold(module.node, 0, (acc, n) -> if (n.type == "if_expr") acc + 1 else acc) -/// ``` -function fold(node: Node, initial: Acc, accumulate: (Acc, Node) -> Acc): Acc = - node.children.fold(accumulate.apply(initial, node), (acc, child) -> fold(child, acc, accumulate)) - -/// Every node at or below [node], in pre-order. -/// -/// A convenience over [fold] for the common case of enumerating nodes. -function descendants(node: Node): List = fold(node, List(), (acc, n) -> acc.add(n)) - /// A generic, untyped node in the Pkl syntax tree. /// /// A node is either a *leaf*, carrying source [text] and no @@ -93,6 +60,39 @@ class Node { /// The source location of this node. span: Span + + /// Render this node back to Pkl source code using default settings. + function render(): String = new Renderer {}.render(this) + + /// Walk this node and its descendants top-down, applying [visit] to each node and + /// returning the (possibly rewritten) tree. + /// + /// For each node, [visit] returns either: + /// - `null` to leave the node unchanged and continue walking into its children. + /// - `Pair(replacement, descend)` to replace the node with `replacement`. When + /// `descend` is `true`, the children of `replacement` are visited in turn, so + /// nodes emitted by a rewrite are themselves processed further down. When + /// `descend` is `false`, `replacement` and its subtree are left as-is. + /// + /// Constructed nodes must set their own [text] where it applies. + /// [span] is carried through unchanged unless set explicitly. + /// [parent] is populated on the returned tree for nodes originating from [Parser.parseModule]; + /// nodes constructed from scratch retain their given `parent`. + external function walk(visit: (Node) -> Pair?): Node + + /// Fold [accumulate] over this node and its descendants, top-down in pre-order. + /// + /// ``` + /// // count the if-expressions in a module + /// module.node.fold(0, (acc, n) -> if (n.type == "if_expr") acc + 1 else acc) + /// ``` + function fold(initial: Acc, accumulate: (Acc, Node) -> Acc): Acc = + children.fold(accumulate.apply(initial, this), (acc, child) -> child.fold(acc, accumulate)) + + /// Every node at or below this node, in pre-order. + /// + /// A convenience over [fold()] for the common case of enumerating nodes. + function descendants(): List = fold(List(), (acc, n) -> acc.add(n)) } /// A source location, given as 1-based line and column positions. @@ -933,9 +933,7 @@ class StringCharsNode extends StringPartNode { /// The text content. value: String - function toNodes(): List = - let (self = this) - List(new Node { type = "string_chars"; text = self.value }) + function toNodes(): List = List(new Node { type = "string_chars"; text = outer.value }) } /// An escape sequence in a string literal (e.g., `"\\n"`, `"\\t"`). @@ -943,9 +941,7 @@ class StringEscapeNode extends StringPartNode { /// The escape sequence text including the leading backslash. value: String - function toNodes(): List = - let (self = this) - List(new Node { type = "string_escape"; text = self.value }) + function toNodes(): List = List(new Node { type = "string_escape"; text = outer.value }) } /// A newline in a multi-line string literal. @@ -961,10 +957,9 @@ class StringInterpolationNode extends StringPartNode { local const terminal: Node = new Node { type = "terminal" } function toNodes(): List = - let (self = this) - List( - (terminal) { text = "\\(" }, - self.expression.builtNode, - (terminal) { text = ")" }, - ) + List( + (terminal) { text = "\\(" }, + outer.expression.builtNode, + (terminal) { text = ")" }, + ) } From fd4d48ad41c385cdc92f9477b47e12e58086e585 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Fri, 31 Jul 2026 14:27:24 +0200 Subject: [PATCH 22/49] Fix some review remarks --- .../org/pkl/core/stdlib/syntax/NodeNodes.java | 25 ++++++++ .../input/syntax/traversal.pkl | 8 --- .../output/syntax/traversal.pcf | 5 -- stdlib/syntax.pkl | 61 +------------------ 4 files changed, 27 insertions(+), 72 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java index 43178de96..b083b22d6 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java @@ -17,7 +17,10 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Specialization; +import java.util.ArrayDeque; import org.pkl.core.ast.lambda.ApplyVmFunction1Node; +import org.pkl.core.ast.lambda.ApplyVmFunction2Node; +import org.pkl.core.ast.lambda.ApplyVmFunction2NodeGen; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.VmFunction; import org.pkl.core.runtime.VmList; @@ -25,11 +28,33 @@ import org.pkl.core.runtime.VmTyped; import org.pkl.core.runtime.VmUtils; import org.pkl.core.stdlib.ExternalMethod1Node; +import org.pkl.core.stdlib.ExternalMethod2Node; import org.pkl.core.stdlib.syntax.SyntaxNodes.NodeData; public final class NodeNodes { private NodeNodes() {} + public abstract static class fold extends ExternalMethod2Node { + @Child private ApplyVmFunction2Node applyAccumulate = ApplyVmFunction2NodeGen.create(); + + @Specialization + @TruffleBoundary + protected Object eval(VmTyped self, Object initial, VmFunction operator) { + var pending = new ArrayDeque(); + pending.push(self); + var result = initial; + while (!pending.isEmpty()) { + var node = pending.pop(); + result = applyAccumulate.execute(operator, result, node); + var children = (VmList) VmUtils.readMember(node, Identifier.CHILDREN); + for (var i = children.getLength() - 1; i >= 0; i--) { + pending.push((VmTyped) children.get(i)); + } + } + return result; + } + } + public abstract static class walk extends ExternalMethod1Node { @Child private ApplyVmFunction1Node applyVisit = ApplyVmFunction1Node.create(); diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl index 82cad1664..d1dde89be 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl @@ -54,12 +54,4 @@ facts { // the module node is visited first sample.fold(List(), (acc, n) -> acc.add(n.type)).first == "module" } - - ["descendants enumerates the whole tree"] { - sample.descendants().first == sample - - sample.descendants() == sample.fold(List(), (acc, n) -> acc.add(n)) - - sample.descendants().filter((n) -> n.type == "class").length == 1 - } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/traversal.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/traversal.pcf index 5096572ab..a8b02f8ba 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/traversal.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/traversal.pcf @@ -10,9 +10,4 @@ facts { ["fold visits a node before its children (pre-order)"] { true } - ["descendants enumerates the whole tree"] { - true - true - true - } } diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index f89ad9c1a..7bfd52423 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -80,19 +80,13 @@ class Node { /// nodes constructed from scratch retain their given `parent`. external function walk(visit: (Node) -> Pair?): Node - /// Fold [accumulate] over this node and its descendants, top-down in pre-order. + /// Fold [operator] over this node and its descendants, top-down in pre-order. /// /// ``` /// // count the if-expressions in a module /// module.node.fold(0, (acc, n) -> if (n.type == "if_expr") acc + 1 else acc) /// ``` - function fold(initial: Acc, accumulate: (Acc, Node) -> Acc): Acc = - children.fold(accumulate.apply(initial, this), (acc, child) -> child.fold(acc, accumulate)) - - /// Every node at or below this node, in pre-order. - /// - /// A convenience over [fold()] for the common case of enumerating nodes. - function descendants(): List = fold(List(), (acc, n) -> acc.add(n)) + external function fold(initial: Result, operator: (Result, Node) -> Result): Result } /// A source location, given as 1-based line and column positions. @@ -110,13 +104,6 @@ class Span { colEnd: UInt = 0 } -// noinspection TypeMismatch -class ConvertSpan extends ConvertProperty { - render = (property: Pair, _) -> - let (span = property.value) - Pair(property.key, "\(span.lineStart):\(span.colStart)-\(span.lineEnd):\(span.colEnd)") -} - typealias NodeType = // terminals and affixes "terminal" @@ -245,50 +232,6 @@ typealias NodeType = | "constrained_type_constraint" | "constrained_type_elements" -// Check if a NodeType represents an expression. -local const function isExprType(t: NodeType): Boolean = - t == "this_expr" - || t == "outer_expr" - || t == "module_expr" - || t == "null_expr" - || t == "throw_expr" - || t == "trace_expr" - || t == "import_expr" - || t == "read_expr" - || t == "new_expr" - || t == "unary_minus_expr" - || t == "logical_not_expr" - || t == "function_literal_expr" - || t == "parenthesized_expr" - || t == "super_subscript_expr" - || t == "super_access_expr" - || t == "subscript_expr" - || t == "qualified_access_expr" - || t == "if_expr" - || t == "let_expr" - || t == "bool_literal_expr" - || t == "int_literal_expr" - || t == "float_literal_expr" - || t == "single_line_string_literal_expr" - || t == "multi_line_string_literal_expr" - || t == "unqualified_access_expr" - || t == "non_null_expr" - || t == "amends_expr" - || t == "binary_op_expr" - -// Check if a NodeType represents a type node. -local const function isTypeType(t: NodeType): Boolean = - t == "unknown_type" - || t == "nothing_type" - || t == "module_type" - || t == "union_type" - || t == "function_type" - || t == "parenthesized_type" - || t == "declared_type" - || t == "nullable_type" - || t == "string_constant_type" - || t == "constrained_type" - /// Base class for all typed syntax nodes. /// /// A typed node may be *backed* by a parsed [Node] (available via [node]) or From 2456208c0417895b89eef5ed8daa8171e77d7a9e Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Fri, 31 Jul 2026 15:40:01 +0200 Subject: [PATCH 23/49] Add display url to spans and source locations --- .../java/org/pkl/core/runtime/Identifier.java | 10 +- .../org/pkl/core/runtime/SyntaxModule.java | 8 ++ .../pkl/core/stdlib/syntax/ParserNodes.java | 38 ++++---- .../core/stdlib/syntax/SyntaxNodeNodes.java | 12 ++- .../pkl/core/stdlib/syntax/SyntaxNodes.java | 79 ++++++++++++--- .../input-helper/syntax/spans/sample.pkl | 5 + .../input/syntax/spans.pkl | 84 ++++++++++++++++ .../output/syntax/spans.pcf | 95 +++++++++++++++++++ stdlib/syntax.pkl | 45 ++++++--- 9 files changed, 322 insertions(+), 54 deletions(-) create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input-helper/syntax/spans/sample.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/syntax/spans.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/syntax/spans.pcf diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/Identifier.java b/pkl-core/src/main/java/org/pkl/core/runtime/Identifier.java index 6022fcb9e..1c799e8b4 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/Identifier.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/Identifier.java @@ -172,14 +172,14 @@ public final class Identifier implements Comparable { // common in lambdas etc public static final Identifier IT = get("it"); - // members of pkl.syntax#Node and pkl.syntax#Span + // members of pkl.syntax#Node, pkl.syntax#Span and pkl.syntax#SourceLocation public static final Identifier TYPE = get("type"); public static final Identifier CHILDREN = get("children"); public static final Identifier SPAN = get("span"); - public static final Identifier LINE_START = get("lineStart"); - public static final Identifier COL_START = get("colStart"); - public static final Identifier LINE_END = get("lineEnd"); - public static final Identifier COL_END = get("colEnd"); + public static final Identifier START = get("start"); + public static final Identifier END = get("end"); + public static final Identifier LINE = get("line"); + public static final Identifier COLUMN = get("column"); // members of pkl.syntax#Renderer public static final Identifier GRAMMAR_VERSION = get("grammarVersion"); diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java index 82bc8a524..7515eb7f4 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java @@ -37,6 +37,10 @@ public static VmClass getSpanClass() { return SpanClass.instance; } + public static VmClass getSourceLocationClass() { + return SourceLocationClass.instance; + } + public static VmClass getModuleNodeClass() { return ModuleNodeClass.instance; } @@ -309,6 +313,10 @@ private static final class SpanClass { static final VmClass instance = loadClass("Span"); } + private static final class SourceLocationClass { + static final VmClass instance = loadClass("SourceLocation"); + } + private static final class ModuleNodeClass { static final VmClass instance = loadClass("ModuleNode"); } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index dfd03238f..42ed754f6 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -33,22 +33,15 @@ import org.pkl.core.stdlib.ExternalMethod1Node; import org.pkl.core.stdlib.VmObjectFactory; import org.pkl.core.stdlib.syntax.SyntaxNodes.NodeData; +import org.pkl.core.stdlib.syntax.SyntaxNodes.SpanData; import org.pkl.parser.GenericParser; import org.pkl.parser.GenericParserError; -import org.pkl.parser.syntax.generic.FullSpan; import org.pkl.parser.syntax.generic.Node; import org.pkl.parser.syntax.generic.NodeType; public class ParserNodes { private ParserNodes() {} - private static final VmObjectFactory spanFactory = - new VmObjectFactory(SyntaxModule::getSpanClass) - .addIntProperty("lineStart", FullSpan::lineBegin) - .addIntProperty("colStart", FullSpan::colBegin) - .addIntProperty("lineEnd", FullSpan::lineEnd) - .addIntProperty("colEnd", FullSpan::colEnd); - private static final VmObjectFactory nodeFactory = new VmObjectFactory(SyntaxModule::getNodeClass) .addStringProperty("type", nd -> nd.node.type.name().toLowerCase(Locale.ROOT)) @@ -60,7 +53,7 @@ private ParserNodes() {} nd.node.children.isEmpty() || nd.node.type == NodeType.STRING_CHARS ? nd.node.text(nd.source) : VmNull.withoutDefault()) - .addTypedProperty("span", nd -> nd.spanVm); + .addProperty("span", nd -> VmNull.lift(nd.spanVm)); private static final VmObjectFactory identifierNodeFactory = new VmObjectFactory(SyntaxModule::getIdentifierNodeClass) @@ -1490,7 +1483,7 @@ public abstract static class parseModule extends ExternalMethod1Node { @Specialization @TruffleBoundary protected Object evalString(@SuppressWarnings("unused") VmTyped self, String source) { - return doParse(source); + return doParse(source, null); } @Specialization @@ -1498,7 +1491,7 @@ protected Object evalString(@SuppressWarnings("unused") VmTyped self, String sou protected Object evalResource(@SuppressWarnings("unused") VmTyped self, VmTyped source) { // `source` is a `pkl.base#Resource` var text = (String) VmUtils.readMember(source, Identifier.TEXT); - return doParse(text); + return doParse(text, sourceUri(source)); } } @@ -1506,7 +1499,7 @@ public abstract static class parseModuleOrNull extends ExternalMethod1Node { @Specialization @TruffleBoundary protected Object evalString(@SuppressWarnings("unused") VmTyped self, String source) { - return doParseOrNull(source); + return doParseOrNull(source, null); } @Specialization @@ -1514,39 +1507,44 @@ protected Object evalString(@SuppressWarnings("unused") VmTyped self, String sou protected Object evalResource(@SuppressWarnings("unused") VmTyped self, VmTyped source) { // `source` is a `pkl.base#Resource` var text = (String) VmUtils.readMember(source, Identifier.TEXT); - return doParseOrNull(text); + return doParseOrNull(text, sourceUri(source)); } } - private static Object doParse(String src) { + private static String sourceUri(VmTyped resource) { + return VmUtils.readMember(resource, Identifier.URI).toString(); + } + + private static Object doParse(String src, @Nullable String sourceUri) { var sourceChars = src.toCharArray(); try { var parser = new GenericParser(); var root = parser.parseModule(src); - var genericNode = convertNode(root, sourceChars); + var genericNode = convertNode(root, sourceChars, sourceUri); return moduleNodeFactory.create(genericNode); } catch (GenericParserError e) { throw new VmExceptionBuilder().evalError("parserError").withHint(e.toString()).build(); } } - private static Object doParseOrNull(String src) { + private static Object doParseOrNull(String src, @Nullable String sourceUri) { var sourceChars = src.toCharArray(); try { var parser = new GenericParser(); var root = parser.parseModule(src); - var genericNode = convertNode(root, sourceChars); + var genericNode = convertNode(root, sourceChars, sourceUri); return moduleNodeFactory.create(genericNode); } catch (GenericParserError e) { return VmNull.withoutDefault(); } } - private static VmTyped convertNode(Node genericNode, char[] sourceChars) { + private static VmTyped convertNode( + Node genericNode, char[] sourceChars, @Nullable String sourceUri) { // convert children recursively var childrenList = new ArrayList(genericNode.children.size()); for (var child : genericNode.children) { - childrenList.add(convertNode(child, sourceChars)); + childrenList.add(convertNode(child, sourceChars, sourceUri)); } // materialize text now so that nodes reused verbatim by `walk`/`format` are @@ -1556,7 +1554,7 @@ private static VmTyped convertNode(Node genericNode, char[] sourceChars) { } var childrenVm = VmList.create(childrenList.toArray()); - var spanVm = spanFactory.create(genericNode.span); + var spanVm = SyntaxNodes.spanFactory.create(new SpanData(genericNode.span, sourceUri)); var data = new NodeData(genericNode, sourceChars, childrenVm, spanVm); var result = nodeFactory.create(data); diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java index 9d530e403..a2cf51fad 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java @@ -29,6 +29,8 @@ import org.pkl.core.runtime.VmUtils; import org.pkl.core.stdlib.ExternalPropertyNode; import org.pkl.core.stdlib.PklName; +import org.pkl.core.stdlib.syntax.SyntaxNodes.SpanData; +import org.pkl.parser.syntax.generic.FullSpan; /** * Backs {@code pkl.syntax#SyntaxNode.builtNode}. @@ -465,11 +467,11 @@ private static VmTyped buildMultiLineString(VmTyped self) { var children = new ArrayList<>(); children.add(terminal("\"\"\"")); children.addAll(buildStringParts(listMember(self, "parts"))); - // The formatter uses colStart of the closing `"""` to determine the indentation to strip from - // each content line. - var closingSpan = new VmObjectBuilder(1).addProperty(Identifier.COL_START, 1L); - children.add( - makeNode("terminal", null, "\"\"\"", closingSpan.toTyped(SyntaxModule.getSpanClass()))); + // The formatter uses the start column of the closing `"""` to determine the indentation to + // strip from each content line. + var closingSpan = + SyntaxNodes.spanFactory.create(new SpanData(new FullSpan(0, 0, 0, 1, 0, 0), null)); + children.add(makeNode("terminal", null, "\"\"\"", closingSpan)); return branch("multi_line_string_literal_expr", children); } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java index d16398504..cb6f0d85a 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java @@ -36,15 +36,57 @@ private SyntaxNodes() {} private static final char[] EMPTY_SOURCE = new char[0]; static final FullSpan ZERO_SPAN = new FullSpan(0, 0, 0, 0, 0, 0); + record SpanData(FullSpan span, @Nullable String sourceUri) {} + + private record SourceLocationData(int line, int column, @Nullable String sourceUri) {} + + private static final VmObjectFactory sourceLocationFactory = + new VmObjectFactory(SyntaxModule::getSourceLocationClass) + .addIntProperty("line", SourceLocationData::line) + .addIntProperty("column", SourceLocationData::column) + .addStringProperty( + "displayUri", sl -> displayUri(sl.sourceUri(), position(sl.line(), sl.column()))); + + static final VmObjectFactory spanFactory = + new VmObjectFactory(SyntaxModule::getSpanClass) + .addTypedProperty( + "start", + sd -> + sourceLocationFactory.create( + new SourceLocationData( + sd.span().lineBegin(), sd.span().colBegin(), sd.sourceUri()))) + .addTypedProperty( + "end", + sd -> + sourceLocationFactory.create( + new SourceLocationData( + sd.span().lineEnd(), sd.span().colEnd(), sd.sourceUri()))) + .addStringProperty( + "displayUri", + sd -> + displayUri( + sd.sourceUri(), + position(sd.span().lineBegin(), sd.span().colBegin()) + + "-" + + position(sd.span().lineEnd(), sd.span().colEnd()))); + + private static String position(int line, int column) { + return line + ":" + column; + } + + private static String displayUri(@Nullable String sourceUri, String position) { + return sourceUri == null ? position : sourceUri + "#" + position; + } + /** Extra storage backing a Pkl {@code Node} instance. */ static final class NodeData { final Node node; final char[] source; @Nullable VmTyped parentVm; VmList childrenVm; - VmTyped spanVm; + @Nullable VmTyped spanVm; - NodeData(Node node, char[] source, VmList childrenVm, VmTyped spanVm) { + NodeData(Node node, char[] source, VmList childrenVm, @Nullable VmTyped spanVm) { this.node = node; this.source = source; this.childrenVm = childrenVm; @@ -63,14 +105,14 @@ static final class NodeData { nd.node.children.isEmpty() || nd.node.type == NodeType.STRING_CHARS ? nd.node.text(nd.source) : VmNull.withoutDefault()) - .addTypedProperty("span", nd -> nd.spanVm); + .addProperty("span", nd -> VmNull.lift(nd.spanVm)); /** Rebuild a node from {@code template} (its type, span, text) with new children. */ static VmTyped rebuild(VmTyped template, Object[] newChildrenVm) { var nodeType = NodeType.valueOf( ((String) VmUtils.readMember(template, Identifier.TYPE)).toUpperCase(Locale.ROOT)); - var spanVm = (VmTyped) VmUtils.readMember(template, Identifier.SPAN); + var spanVm = optSpan(template); var span = readSpan(spanVm); var childJavaNodes = new ArrayList(newChildrenVm.length); @@ -112,7 +154,7 @@ static Node convertVmToNode(VmTyped nodeVm, FullSpan fallbackSpan) { var typeStr = (String) VmUtils.readMember(nodeVm, Identifier.TYPE); var nodeType = NodeType.valueOf(typeStr.toUpperCase(Locale.ROOT)); - var ownSpan = readSpan((VmTyped) VmUtils.readMember(nodeVm, Identifier.SPAN)); + var ownSpan = readSpan(optSpan(nodeVm)); // a constructed node that did not set its own span inherits the insertion point's span var span = ownSpan.equals(ZERO_SPAN) ? fallbackSpan : ownSpan; @@ -125,12 +167,27 @@ static Node convertVmToNode(VmTyped nodeVm, FullSpan fallbackSpan) { return makeJavaNode(nodeType, span, children, VmUtils.readMember(nodeVm, Identifier.TEXT)); } - private static FullSpan readSpan(VmTyped spanVm) { - var lineStart = ((Long) VmUtils.readMember(spanVm, Identifier.LINE_START)).intValue(); - var colStart = ((Long) VmUtils.readMember(spanVm, Identifier.COL_START)).intValue(); - var lineEnd = ((Long) VmUtils.readMember(spanVm, Identifier.LINE_END)).intValue(); - var colEnd = ((Long) VmUtils.readMember(spanVm, Identifier.COL_END)).intValue(); - return new FullSpan(0, 0, lineStart, colStart, lineEnd, colEnd); + private static @Nullable VmTyped optSpan(VmTyped nodeVm) { + return VmUtils.readMember(nodeVm, Identifier.SPAN) instanceof VmTyped spanVm ? spanVm : null; + } + + private static FullSpan readSpan(@Nullable VmTyped spanVm) { + if (spanVm == null) { + return ZERO_SPAN; + } + var start = (VmTyped) VmUtils.readMember(spanVm, Identifier.START); + var end = (VmTyped) VmUtils.readMember(spanVm, Identifier.END); + return new FullSpan( + 0, + 0, + readPosition(start, Identifier.LINE), + readPosition(start, Identifier.COLUMN), + readPosition(end, Identifier.LINE), + readPosition(end, Identifier.COLUMN)); + } + + private static int readPosition(VmTyped sourceLocationVm, Identifier name) { + return ((Long) VmUtils.readMember(sourceLocationVm, name)).intValue(); } private static Node makeJavaNode( diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input-helper/syntax/spans/sample.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/syntax/spans/sample.pkl new file mode 100644 index 000000000..1cd9df9f5 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/syntax/spans/sample.pkl @@ -0,0 +1,5 @@ +x = 1 + +foo { + bar = "baz" +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/spans.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/spans.pkl new file mode 100644 index 000000000..4fd969a76 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/spans.pkl @@ -0,0 +1,84 @@ +amends "../snippetTest.pkl" + +import "pkl:syntax" + +local parser = new syntax.Parser {} + +local stringModule = parser.parseModule(""" + x = 1 + + foo { + bar = "baz" + } + """) + +local resourceModule = parser.parseModule(read(".../input-helper/syntax/spans/sample.pkl")) + +local function nestedProperty(mod: syntax.ModuleNode): syntax.Node = + mod.node!! + .fold(List(), (acc: List, n) -> if (n.type == "object_property") acc.add(n) else acc) + .last + +examples { + ["span of a module parsed from a string"] { + stringModule.span + } + + ["span of a leaf parsed from a string"] { + nestedProperty(stringModule).span + } + + ["span of a module parsed from a resource carries the resource URI"] { + resourceModule.span + } + + ["span of a leaf parsed from a resource carries the resource URI"] { + nestedProperty(resourceModule).span + } +} + +facts { + ["span positions are 1-based"] { + stringModule.span!!.start.line == 1 + stringModule.span!!.start.column == 1 + } + + ["end is exclusive"] { + local span = nestedProperty(stringModule).span!! + span.start.line == 4 + span.start.column == 3 + span.end.line == 4 + span.end.column == 14 + } + + ["a span's displayUri combines those of its start and end"] { + local span = nestedProperty(stringModule).span!! + span.displayUri == "\(span.start.displayUri)-\(span.end.displayUri)" + } + + ["a child's span is contained in its parent's"] { + local child = nestedProperty(stringModule) + local parent = child.parent!! + parent.span!!.start.line <= child.span!!.start.line + parent.span!!.end.line >= child.span!!.end.line + } + + ["nodes constructed from scratch have no span"] { + new syntax.IntLiteralExprNode { value = 42 }.span == null + new syntax.IntLiteralExprNode { value = 42 }.builtNode.span == null + } + + ["a spanless constructed node still renders"] { + new syntax.IntLiteralExprNode { value = 42 }.builtNode.render().trim() == "42" + } + + ["parsing the same text from a string and a resource differs only in displayUri"] { + local fromString = stringModule.span!! + local fromResource = resourceModule.span!! + fromString.start.line == fromResource.start.line + fromString.start.column == fromResource.start.column + fromString.end.line == fromResource.end.line + fromString.end.column == fromResource.end.column + fromString.displayUri != fromResource.displayUri + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/spans.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/spans.pcf new file mode 100644 index 000000000..eda558e88 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/spans.pcf @@ -0,0 +1,95 @@ +facts { + ["span positions are 1-based"] { + true + true + } + ["end is exclusive"] { + true + true + true + true + } + ["a span's displayUri combines those of its start and end"] { + true + } + ["a child's span is contained in its parent's"] { + true + true + } + ["nodes constructed from scratch have no span"] { + true + true + } + ["a spanless constructed node still renders"] { + true + } + ["parsing the same text from a string and a resource differs only in displayUri"] { + true + true + true + true + true + } +} +examples { + ["span of a module parsed from a string"] { + new { + start { + line = X + column = 1 + displayUri = "1:1" + } + end { + line = X + column = 2 + displayUri = "5:2" + } + displayUri = "1:1-5:2" + } + } + ["span of a leaf parsed from a string"] { + new { + start { + line = X + column = 3 + displayUri = "4:3" + } + end { + line = X + column = 14 + displayUri = "4:14" + } + displayUri = "4:3-4:14" + } + } + ["span of a module parsed from a resource carries the resource URI"] { + new { + start { + line = X + column = 1 + displayUri = "file:///$snippetsDir/input-helper/syntax/spans/sample.pkl#1:1" + } + end { + line = X + column = 2 + displayUri = "file:///$snippetsDir/input-helper/syntax/spans/sample.pkl#5:2" + } + displayUri = "file:///$snippetsDir/input-helper/syntax/spans/sample.pkl#1:1-5:2" + } + } + ["span of a leaf parsed from a resource carries the resource URI"] { + new { + start { + line = X + column = 3 + displayUri = "file:///$snippetsDir/input-helper/syntax/spans/sample.pkl#4:3" + } + end { + line = X + column = 14 + displayUri = "file:///$snippetsDir/input-helper/syntax/spans/sample.pkl#4:14" + } + displayUri = "file:///$snippetsDir/input-helper/syntax/spans/sample.pkl#4:3-4:14" + } + } +} diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 7bfd52423..9d72387e5 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -58,8 +58,8 @@ class Node { /// or `null` for a branch node whose content is its [children]. text: String? - /// The source location of this node. - span: Span + /// The source location of this node or `null`. + span: Span? /// Render this node back to Pkl source code using default settings. function render(): String = new Renderer {}.render(this) @@ -89,19 +89,38 @@ class Node { external function fold(initial: Result, operator: (Result, Node) -> Result): Result } -/// A source location, given as 1-based line and column positions. +/// A range of source code, spanning [start] (inclusive) to [end] (exclusive). class Span { - /// The line of the first character. - lineStart: UInt = 0 + /// The start of this span. + start: SourceLocation - /// The column of the first character. - colStart: UInt = 0 + /// The end of this span. + end: SourceLocation - /// The line of the character following the span. - lineEnd: UInt = 0 + /// The display URI of this span. + /// + /// For nodes parsed from a [Resource], this is the resource's URI with the span appended + /// as a fragment, for example `file:///foo/bar.pkl#3:5-3:12`. + /// For nodes parsed from a [String], the URI of the source is unknown, so this is just the + /// span itself, for example `3:5-3:12`. + displayUri: String +} + +/// A position in source code, given as a 1-based line and column. +class SourceLocation { + /// The 1-based line number of this source location. + line: UInt - /// The column of the character following the span. - colEnd: UInt = 0 + /// The 1-based column number of this source location. + column: UInt + + /// The display URI of this source location. + /// + /// For nodes parsed from a [Resource], this is the resource's URI with the location appended + /// as a fragment, for example `file:///foo/bar.pkl#3:5`. + /// For nodes parsed from a [String], the URI of the source is unknown, so this is just the + /// location itself, for example `3:5`. + displayUri: String } typealias NodeType = @@ -241,8 +260,8 @@ abstract class SyntaxNode { /// The original parsed node, or `null` when this was built from scratch. hidden node: Node? = null - /// The source span of this node. - span: Span = node?.span ?? new Span {} + /// The source span of this node or `null`. + span: Span? = node?.span /// This node rebuilt into a generic [Node]. /// From 66071d3f604d7b221d1d4d0e541d1e8d0975ad77 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Fri, 31 Jul 2026 15:50:46 +0200 Subject: [PATCH 24/49] Remove span from SyntaxNode --- .../LanguageSnippetTests/input/syntax/spans.pkl | 14 +++++++------- stdlib/syntax.pkl | 3 --- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/spans.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/spans.pkl index 4fd969a76..d4f261669 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/spans.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/spans.pkl @@ -21,7 +21,7 @@ local function nestedProperty(mod: syntax.ModuleNode): syntax.Node = examples { ["span of a module parsed from a string"] { - stringModule.span + stringModule.node.span } ["span of a leaf parsed from a string"] { @@ -29,7 +29,7 @@ examples { } ["span of a module parsed from a resource carries the resource URI"] { - resourceModule.span + resourceModule.node.span } ["span of a leaf parsed from a resource carries the resource URI"] { @@ -39,8 +39,8 @@ examples { facts { ["span positions are 1-based"] { - stringModule.span!!.start.line == 1 - stringModule.span!!.start.column == 1 + stringModule.node.span!!.start.line == 1 + stringModule.node.span!!.start.column == 1 } ["end is exclusive"] { @@ -64,7 +64,7 @@ facts { } ["nodes constructed from scratch have no span"] { - new syntax.IntLiteralExprNode { value = 42 }.span == null + new syntax.IntLiteralExprNode { value = 42 }?.node?.span == null new syntax.IntLiteralExprNode { value = 42 }.builtNode.span == null } @@ -73,8 +73,8 @@ facts { } ["parsing the same text from a string and a resource differs only in displayUri"] { - local fromString = stringModule.span!! - local fromResource = resourceModule.span!! + local fromString = stringModule.node.span!! + local fromResource = resourceModule.node.span!! fromString.start.line == fromResource.start.line fromString.start.column == fromResource.start.column fromString.end.line == fromResource.end.line diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 9d72387e5..ae5b70fee 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -260,9 +260,6 @@ abstract class SyntaxNode { /// The original parsed node, or `null` when this was built from scratch. hidden node: Node? = null - /// The source span of this node or `null`. - span: Span? = node?.span - /// This node rebuilt into a generic [Node]. /// /// Always constructs a fresh node from this node's fields. From be92f1d738e648cba9c3534f732f5185edff69e2 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Fri, 31 Jul 2026 17:32:00 +0200 Subject: [PATCH 25/49] Make ObjectMemberNode a sum type --- .../pkl/core/stdlib/syntax/ParserNodes.java | 2 +- stdlib/syntax.pkl | 28 ++++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 42ed754f6..0fccf2ff6 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -1431,7 +1431,7 @@ private static VmTyped wrapExpr(VmTyped exprVm) { }; } - // Wrap a generic object-member node into its `ObjectMemberNode` subclass + // Wrap a generic object-member node into the matching `ObjectMemberNode` alternative private static VmTyped wrapObjectMember(VmTyped memberVm) { var data = (NodeData) memberVm.getExtraStorage(); return switch (data.node.type) { diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index ae5b70fee..695047c39 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -272,8 +272,16 @@ abstract class ExprNode extends SyntaxNode /// Base class for type nodes. abstract class TypeNode extends SyntaxNode -/// Base class for object member nodes. -abstract class ObjectMemberNode extends SyntaxNode +/// A member of an [ObjectBodyNode]. +typealias ObjectMemberNode = + ObjectPropertyNode + | ObjectMethodNode + | ObjectElementNode + | ObjectEntryNode + | ObjectSpreadNode + | MemberPredicateNode + | ForGeneratorNode + | WhenGeneratorNode /// The top-level module node. class ModuleNode extends SyntaxNode { @@ -450,7 +458,7 @@ class ObjectBodyNode extends SyntaxNode { } /// An object property declaration. -class ObjectPropertyNode extends ObjectMemberNode { +class ObjectPropertyNode extends SyntaxNode { /// The modifiers on the property. modifiers: List @@ -468,7 +476,7 @@ class ObjectPropertyNode extends ObjectMemberNode { } /// An object method declaration. -class ObjectMethodNode extends ObjectMemberNode { +class ObjectMethodNode extends SyntaxNode { /// The modifiers on the method. modifiers: List @@ -489,13 +497,13 @@ class ObjectMethodNode extends ObjectMemberNode { } /// An object element (a positional expression in an object body). -class ObjectElementNode extends ObjectMemberNode { +class ObjectElementNode extends SyntaxNode { /// The expression value. expression: ExprNode } /// An object entry (`[key] = value` or `[key] { ... }`). -class ObjectEntryNode extends ObjectMemberNode { +class ObjectEntryNode extends SyntaxNode { /// The key expression. key: ExprNode @@ -507,7 +515,7 @@ class ObjectEntryNode extends ObjectMemberNode { } /// An object spread (`...expr` or `...?expr`). -class ObjectSpreadNode extends ObjectMemberNode { +class ObjectSpreadNode extends SyntaxNode { /// Whether this is a nullable spread (`...?`). isNullable: Boolean @@ -516,7 +524,7 @@ class ObjectSpreadNode extends ObjectMemberNode { } /// A member predicate (`[[condition]] = value` or `[[condition]] { ... }`). -class MemberPredicateNode extends ObjectMemberNode { +class MemberPredicateNode extends SyntaxNode { /// The condition expression. condition: ExprNode @@ -528,7 +536,7 @@ class MemberPredicateNode extends ObjectMemberNode { } /// A `for (param in iterable) { ... }` generator. -class ForGeneratorNode extends ObjectMemberNode { +class ForGeneratorNode extends SyntaxNode { /// The key parameter (first parameter when two are present), if present. keyParameter: ParameterNode? @@ -543,7 +551,7 @@ class ForGeneratorNode extends ObjectMemberNode { } /// A `when (condition) { ... }` generator. -class WhenGeneratorNode extends ObjectMemberNode { +class WhenGeneratorNode extends SyntaxNode { /// The condition expression. condition: ExprNode From aad10d14b08f9d050c67ce7159ffd918e1d19409 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Fri, 31 Jul 2026 17:54:58 +0200 Subject: [PATCH 26/49] Explode object members --- .../pkl/core/stdlib/syntax/ParserNodes.java | 89 +++++++++---------- .../core/stdlib/syntax/SyntaxNodeNodes.java | 14 ++- .../input/syntax/objectMembers.pkl | 83 ++++++++--------- .../output/syntax/objectMembers.pcf | 9 -- stdlib/syntax.pkl | 25 +++++- 5 files changed, 114 insertions(+), 106 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 0fccf2ff6..42702ebfa 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -87,7 +87,14 @@ private static VmObjectFactory nodeOnlyFactory(Supplier classS new VmObjectFactory(SyntaxModule::getObjectBodyNodeClass) .addProperty("node", vm -> vm) .addListProperty("parameters", ParserNodes::objectBodyParameters) - .addListProperty("members", ParserNodes::objectBodyMembers); + .addListProperty("properties", ParserNodes::objectBodyProperties) + .addListProperty("methods", ParserNodes::objectBodyMethods) + .addListProperty("elements", ParserNodes::objectBodyElements) + .addListProperty("entries", ParserNodes::objectBodyEntries) + .addListProperty("spreads", ParserNodes::objectBodySpreads) + .addListProperty("memberPredicates", ParserNodes::objectBodyMemberPredicates) + .addListProperty("forGenerators", ParserNodes::objectBodyForGenerators) + .addListProperty("whenGenerators", ParserNodes::objectBodyWhenGenerators); private static final VmObjectFactory parameterNodeFactory = new VmObjectFactory(SyntaxModule::getParameterNodeClass) .addProperty("node", vm -> vm) @@ -740,20 +747,45 @@ private static VmList objectBodyParameters(VmTyped bodyVm) { return wrapAll(findChildrenVm(paramList, NodeType.PARAMETER), parameterNodeFactory); } - private static VmList objectBodyMembers(VmTyped bodyVm) { + private static VmList objectBodyMembers( + VmTyped bodyVm, NodeType memberType, VmObjectFactory factory) { var memberList = findChildVm(bodyVm, NodeType.OBJECT_MEMBER_LIST); if (memberList == null) { return VmList.EMPTY; } - var data = (NodeData) memberList.getExtraStorage(); - var children = data.node.children; - var result = new ArrayList<>(); - for (var i = 0; i < children.size(); i++) { - if (isObjectMemberType(children.get(i).type)) { - result.add(wrapObjectMember((VmTyped) data.childrenVm.get(i))); - } - } - return VmList.create(result.toArray()); + return wrapAll(findChildrenVm(memberList, memberType), factory); + } + + private static VmList objectBodyProperties(VmTyped bodyVm) { + return objectBodyMembers(bodyVm, NodeType.OBJECT_PROPERTY, objectPropertyNodeFactory); + } + + private static VmList objectBodyMethods(VmTyped bodyVm) { + return objectBodyMembers(bodyVm, NodeType.OBJECT_METHOD, objectMethodNodeFactory); + } + + private static VmList objectBodyElements(VmTyped bodyVm) { + return objectBodyMembers(bodyVm, NodeType.OBJECT_ELEMENT, objectElementNodeFactory); + } + + private static VmList objectBodyEntries(VmTyped bodyVm) { + return objectBodyMembers(bodyVm, NodeType.OBJECT_ENTRY, objectEntryNodeFactory); + } + + private static VmList objectBodySpreads(VmTyped bodyVm) { + return objectBodyMembers(bodyVm, NodeType.OBJECT_SPREAD, objectSpreadNodeFactory); + } + + private static VmList objectBodyMemberPredicates(VmTyped bodyVm) { + return objectBodyMembers(bodyVm, NodeType.MEMBER_PREDICATE, memberPredicateNodeFactory); + } + + private static VmList objectBodyForGenerators(VmTyped bodyVm) { + return objectBodyMembers(bodyVm, NodeType.FOR_GENERATOR, forGeneratorNodeFactory); + } + + private static VmList objectBodyWhenGenerators(VmTyped bodyVm) { + return objectBodyMembers(bodyVm, NodeType.WHEN_GENERATOR, whenGeneratorNodeFactory); } private static @Nullable VmTyped objectPropertyHeaderBegin(VmTyped propertyVm) { @@ -1431,41 +1463,6 @@ private static VmTyped wrapExpr(VmTyped exprVm) { }; } - // Wrap a generic object-member node into the matching `ObjectMemberNode` alternative - private static VmTyped wrapObjectMember(VmTyped memberVm) { - var data = (NodeData) memberVm.getExtraStorage(); - return switch (data.node.type) { - case OBJECT_ELEMENT -> objectElementNodeFactory.create(memberVm); - case OBJECT_PROPERTY -> objectPropertyNodeFactory.create(memberVm); - case OBJECT_METHOD -> objectMethodNodeFactory.create(memberVm); - case MEMBER_PREDICATE -> memberPredicateNodeFactory.create(memberVm); - case OBJECT_ENTRY -> objectEntryNodeFactory.create(memberVm); - case OBJECT_SPREAD -> objectSpreadNodeFactory.create(memberVm); - case WHEN_GENERATOR -> whenGeneratorNodeFactory.create(memberVm); - case FOR_GENERATOR -> forGeneratorNodeFactory.create(memberVm); - default -> - throw new VmExceptionBuilder() - .bug("Unexpected object-member node: " + data.node.type) - .build(); - }; - } - - // Whether `type` is one of the object-member node types - private static boolean isObjectMemberType(NodeType type) { - return switch (type) { - case OBJECT_ELEMENT, - OBJECT_PROPERTY, - OBJECT_METHOD, - MEMBER_PREDICATE, - OBJECT_ENTRY, - OBJECT_SPREAD, - WHEN_GENERATOR, - FOR_GENERATOR -> - true; - default -> false; - }; - } - // All children of `genericVm` with the given type, as generic-node `VmTyped`s. private static List findChildrenVm(VmTyped genericVm, NodeType type) { var data = (NodeData) genericVm.getExtraStorage(); diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java index a2cf51fad..656bbaed6 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java @@ -332,9 +332,17 @@ private static VmTyped buildObjectBody(VmTyped self) { elements.add(terminal("->")); children.add(branch("object_parameter_list", elements)); } - var members = listMember(self, "members"); - if (members.getLength() > 0) { - children.add(branch("object_member_list", buildAll(members))); + var members = new ArrayList<>(); + members.addAll(buildAll(listMember(self, "properties"))); + members.addAll(buildAll(listMember(self, "methods"))); + members.addAll(buildAll(listMember(self, "elements"))); + members.addAll(buildAll(listMember(self, "entries"))); + members.addAll(buildAll(listMember(self, "spreads"))); + members.addAll(buildAll(listMember(self, "memberPredicates"))); + members.addAll(buildAll(listMember(self, "forGenerators"))); + members.addAll(buildAll(listMember(self, "whenGenerators"))); + if (!members.isEmpty()) { + children.add(branch("object_member_list", members)); } children.add(terminal("}")); return branch("object_body", children); diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl index 7785bfbbb..77a189e3f 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl @@ -11,9 +11,8 @@ local function body(source: String) = facts { ["object property"] { local b = body("name = \"hello\"") - b.members.length == 1 - b.members.first is syntax.ObjectPropertyNode - local prop = b.members.first as syntax.ObjectPropertyNode + b.properties.length == 1 + local prop = b.properties.first prop.identifier.value == "name" prop.value is syntax.SingleLineStringLiteralExprNode prop.modifiers.isEmpty @@ -23,8 +22,8 @@ facts { ["object property with type and modifiers"] { local b = body("hidden name: String = \"hello\"") - b.members.length == 1 - local prop = b.members.first as syntax.ObjectPropertyNode + b.properties.length == 1 + local prop = b.properties.first prop.modifiers == List("hidden") prop.typeAnnotation != null prop.typeAnnotation is syntax.DeclaredTypeNode @@ -32,8 +31,8 @@ facts { ["object property with amending body"] { local b = body("inner { x = 1 }") - b.members.length == 1 - local prop = b.members.first as syntax.ObjectPropertyNode + b.properties.length == 1 + local prop = b.properties.first prop.identifier.value == "inner" prop.value == null prop.objectBodies.length == 1 @@ -41,9 +40,8 @@ facts { ["object method"] { local b = body("function greet(who: String): String = \"hi\"") - b.members.length == 1 - b.members.first is syntax.ObjectMethodNode - local method = b.members.first as syntax.ObjectMethodNode + b.methods.length == 1 + local method = b.methods.first method.identifier.value == "greet" method.parameters.length == 1 method.parameters.first.identifier!!.value == "who" @@ -53,54 +51,49 @@ facts { ["object element"] { local b = body("1\n 2\n 3") - b.members.length == 3 - b.members[0] is syntax.ObjectElementNode - b.members[0].expression is syntax.IntLiteralExprNode - b.members[1].expression is syntax.IntLiteralExprNode - b.members[2].expression is syntax.IntLiteralExprNode + b.elements.length == 3 + b.elements[0].expression is syntax.IntLiteralExprNode + b.elements[1].expression is syntax.IntLiteralExprNode + b.elements[2].expression is syntax.IntLiteralExprNode } ["object entry"] { local b = body("[\"key\"] = 42") - b.members.length == 1 - b.members.first is syntax.ObjectEntryNode - b.members.first.key is syntax.SingleLineStringLiteralExprNode - b.members.first.value is syntax.IntLiteralExprNode + b.entries.length == 1 + b.entries.first.key is syntax.SingleLineStringLiteralExprNode + b.entries.first.value is syntax.IntLiteralExprNode } ["object entry with amending body"] { local b = body("[\"key\"] { x = 1 }") - b.members.length == 1 - b.members.first.key is syntax.SingleLineStringLiteralExprNode - b.members.first.objectBodies.length == 1 + b.entries.length == 1 + b.entries.first.key is syntax.SingleLineStringLiteralExprNode + b.entries.first.objectBodies.length == 1 } ["object spread"] { local b = body("...other") - b.members.length == 1 - b.members.first is syntax.ObjectSpreadNode - (b.members.first as syntax.ObjectSpreadNode).isNullable == false - (b.members.first as syntax.ObjectSpreadNode).expression is syntax.UnqualifiedAccessExprNode + b.spreads.length == 1 + b.spreads.first.isNullable == false + b.spreads.first.expression is syntax.UnqualifiedAccessExprNode } ["nullable object spread"] { local b = body("...?other") - b.members.length == 1 - b.members.first is syntax.ObjectSpreadNode - (b.members.first as syntax.ObjectSpreadNode).isNullable == true + b.spreads.length == 1 + b.spreads.first.isNullable == true } ["member predicate"] { local b = body("[[name == \"foo\"]] = 1") - b.members.length == 1 - b.members.first is syntax.MemberPredicateNode - (b.members.first as syntax.MemberPredicateNode).condition is syntax.BinaryOpExprNode - (b.members.first as syntax.MemberPredicateNode).value is syntax.IntLiteralExprNode + b.memberPredicates.length == 1 + b.memberPredicates.first.condition is syntax.BinaryOpExprNode + b.memberPredicates.first.value is syntax.IntLiteralExprNode } ["member predicate with amending body"] { local b = body("[[name == \"foo\"]] { x = 1 }") - local pred = b.members.first as syntax.MemberPredicateNode + local pred = b.memberPredicates.first pred.condition is syntax.BinaryOpExprNode pred.value == null pred.objectBodies.length == 1 @@ -108,9 +101,8 @@ facts { ["for generator"] { local b = body("for (item in items) { item }") - b.members.length == 1 - b.members.first is syntax.ForGeneratorNode - local gen = b.members.first as syntax.ForGeneratorNode + b.forGenerators.length == 1 + local gen = b.forGenerators.first gen.keyParameter == null gen.valueParameter.identifier!!.value == "item" gen.iterable is syntax.UnqualifiedAccessExprNode @@ -118,7 +110,7 @@ facts { ["for generator with key"] { local b = body("for (k, v in items) { v }") - local gen = b.members.first as syntax.ForGeneratorNode + local gen = b.forGenerators.first gen.keyParameter != null gen.keyParameter!!.identifier!!.value == "k" gen.valueParameter.identifier!!.value == "v" @@ -126,16 +118,15 @@ facts { ["when generator"] { local b = body("when (flag) { 1 }") - b.members.length == 1 - b.members.first is syntax.WhenGeneratorNode - local gen = b.members.first as syntax.WhenGeneratorNode + b.whenGenerators.length == 1 + local gen = b.whenGenerators.first gen.condition is syntax.UnqualifiedAccessExprNode gen.elseBody == null } ["when generator with else"] { local b = body("when (flag) { 1 } else { 2 }") - local gen = b.members.first as syntax.WhenGeneratorNode + local gen = b.whenGenerators.first gen.condition is syntax.UnqualifiedAccessExprNode gen.elseBody != null gen.elseBody is syntax.ObjectBodyNode @@ -158,9 +149,9 @@ facts { ["key"] = 2 function f() = 3 """) - b.members.length == 4 - b.members.filterIsInstance(syntax.ObjectPropertyNode).length == 1 - b.members.filterIsInstance(syntax.ObjectEntryNode).length == 1 - b.members.filterIsInstance(syntax.ObjectMethodNode).length == 1 + b.properties.length == 1 + b.elements.length == 1 + b.entries.length == 1 + b.methods.length == 1 } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf index e861c01d9..746e4545e 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf @@ -6,7 +6,6 @@ facts { true true true - true } ["object property with type and modifiers"] { true @@ -27,20 +26,17 @@ facts { true true true - true } ["object element"] { true true true true - true } ["object entry"] { true true true - true } ["object entry with amending body"] { true @@ -51,18 +47,15 @@ facts { true true true - true } ["nullable object spread"] { true true - true } ["member predicate"] { true true true - true } ["member predicate with amending body"] { true @@ -74,7 +67,6 @@ facts { true true true - true } ["for generator with key"] { true @@ -85,7 +77,6 @@ facts { true true true - true } ["when generator with else"] { true diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 695047c39..c28ff1c25 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -453,8 +453,29 @@ class ObjectBodyNode extends SyntaxNode { /// Parameters for this object body (e.g., `{ x, y -> ... }`). parameters: List - /// All object members (properties, methods, elements, entries, spreads, generators). - members: List + /// Properties declared in this object body. + properties: List + + /// Methods declared in this object body. + methods: List + + /// Elements declared in this object body. + elements: List + + /// Entries declared in this object body. + entries: List + + /// Spreads declared in this object body. + spreads: List + + /// Member predicates declared in this object body. + memberPredicates: List + + /// `for` generators declared in this object body. + forGenerators: List + + /// `when` generators declared in this object body. + whenGenerators: List } /// An object property declaration. From 8b9473c723c13f97e7897d850227bb0529d68b97 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Mon, 3 Aug 2026 11:22:04 +0200 Subject: [PATCH 27/49] Refine modifier list types --- .../input/syntax/objectMembers.pkl | 4 +- stdlib/syntax.pkl | 72 ++++++++++--------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl index 77a189e3f..795a9afcd 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl @@ -21,10 +21,10 @@ facts { } ["object property with type and modifiers"] { - local b = body("hidden name: String = \"hello\"") + local b = body("local name: String = \"hello\"") b.properties.length == 1 local prop = b.properties.first - prop.modifiers == List("hidden") + prop.modifiers == List("local") prop.typeAnnotation != null prop.typeAnnotation is syntax.DeclaredTypeNode } diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index c28ff1c25..da34e72e4 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -285,7 +285,7 @@ typealias ObjectMemberNode = /// The top-level module node. class ModuleNode extends SyntaxNode { - /// The module declaration, if present. + /// The module declaration. declaration: ModuleDeclarationNode? /// All imports in this module. @@ -306,19 +306,21 @@ class ModuleNode extends SyntaxNode { /// A module declaration (including doc comment, annotations, modifiers, name, amends/extends). class ModuleDeclarationNode extends SyntaxNode { - /// The doc comment on the module declaration, if present. + /// The doc comment on the module declaration. docComment: DocCommentNode? /// Annotations on the module declaration. annotations: List /// The modifiers on the module declaration. - modifiers: List + /// + /// An amending module cannot have any modifiers. + modifiers: List<"abstract" | "open">(isDistinct) - /// The qualified name of the module, if present. + /// The qualified name of the module. name: QualifiedIdentifierNode? - /// The `extends` or `amends` clause, if present. + /// The `extends` or `amends` clause. extendsOrAmendsClause: ExtendsOrAmendsClauseNode? } @@ -339,20 +341,20 @@ class ImportNode extends SyntaxNode { /// The URI string of the import. uri: String - /// The alias for this import, if present. + /// The alias for this import. alias: IdentifierNode? } /// A class declaration. class ClassNode extends SyntaxNode { - /// The doc comment, if present. + /// The doc comment. docComment: DocCommentNode? /// Annotations on the class. annotations: List /// The modifiers on the class. - modifiers: List + modifiers: List<"abstract" | "open" | "local" | "external">(isDistinct) /// The class name. identifier: IdentifierNode @@ -360,23 +362,23 @@ class ClassNode extends SyntaxNode { /// The type parameters. typeParameters: List - /// The supertype this class extends, if present. + /// The supertype this class extends. superType: TypeNode? - /// The class body, if present. + /// The class body. body: ClassBodyNode? } /// A typealias declaration. class TypeAliasNode extends SyntaxNode { - /// The doc comment, if present. + /// The doc comment. docComment: DocCommentNode? /// Annotations on the typealias. annotations: List /// The modifiers on the typealias. - modifiers: List + modifiers: List<"local" | "external">(isDistinct) /// The typealias name. identifier: IdentifierNode @@ -399,22 +401,24 @@ class ClassBodyNode extends SyntaxNode { /// A class property declaration. class ClassPropertyNode extends SyntaxNode { - /// The doc comment, if present. + /// The doc comment. docComment: DocCommentNode? /// Annotations on the property. annotations: List /// The modifiers on the property. - modifiers: List + /// + /// The `abstract` modifier is accepted for backwards compatibility, but has no effect. + modifiers: List<"abstract" | "local" | "hidden" | "external" | "fixed" | "const">(isDistinct) /// The property name. identifier: IdentifierNode - /// The type annotation, if present. + /// The type annotation. typeAnnotation: TypeNode? - /// The value expression, if present (from `= expr`). + /// The value expression (from `= expr`). value: ExprNode? /// Object bodies for amending (from `{ ... }` blocks). @@ -423,14 +427,14 @@ class ClassPropertyNode extends SyntaxNode { /// A class method declaration. class ClassMethodNode extends SyntaxNode { - /// The doc comment, if present. + /// The doc comment. docComment: DocCommentNode? /// Annotations on the method. annotations: List /// The modifiers on the method. - modifiers: List + modifiers: List<"abstract" | "local" | "external" | "const">(isDistinct) /// The method name. identifier: IdentifierNode @@ -441,10 +445,10 @@ class ClassMethodNode extends SyntaxNode { /// The parameters. parameters: List - /// The return type annotation, if present. + /// The return type annotation. returnType: TypeNode? - /// The method body expression, if present. Null for abstract methods. + /// The method body expression. Null for abstract methods. body: ExprNode? } @@ -481,15 +485,15 @@ class ObjectBodyNode extends SyntaxNode { /// An object property declaration. class ObjectPropertyNode extends SyntaxNode { /// The modifiers on the property. - modifiers: List + modifiers: List<"local" | "const">(isDistinct) /// The property name. identifier: IdentifierNode - /// The type annotation, if present. + /// The type annotation. typeAnnotation: TypeNode? - /// The value expression, if present (from `= expr`). + /// The value expression (from `= expr`). value: ExprNode? /// Object bodies for amending. @@ -499,7 +503,7 @@ class ObjectPropertyNode extends SyntaxNode { /// An object method declaration. class ObjectMethodNode extends SyntaxNode { /// The modifiers on the method. - modifiers: List + modifiers: List<"local" | "const">(isDistinct) /// The method name. identifier: IdentifierNode @@ -510,7 +514,7 @@ class ObjectMethodNode extends SyntaxNode { /// The parameters. parameters: List - /// The return type annotation, if present. + /// The return type annotation. returnType: TypeNode? /// The method body expression. @@ -528,7 +532,7 @@ class ObjectEntryNode extends SyntaxNode { /// The key expression. key: ExprNode - /// The value expression, if present (from `[key] = value`). + /// The value expression (from `[key] = value`). value: ExprNode? /// Object bodies for amending. @@ -549,7 +553,7 @@ class MemberPredicateNode extends SyntaxNode { /// The condition expression. condition: ExprNode - /// The value expression, if present. + /// The value expression. value: ExprNode? /// Object bodies for amending. @@ -558,7 +562,7 @@ class MemberPredicateNode extends SyntaxNode { /// A `for (param in iterable) { ... }` generator. class ForGeneratorNode extends SyntaxNode { - /// The key parameter (first parameter when two are present), if present. + /// The key parameter (first parameter when two are present). keyParameter: ParameterNode? /// The value parameter (or the only parameter when just one is present). @@ -579,7 +583,7 @@ class WhenGeneratorNode extends SyntaxNode { /// The "then" body. thenBody: ObjectBodyNode - /// The "else" body, if present. + /// The "else" body. elseBody: ObjectBodyNode? } @@ -732,7 +736,7 @@ class ReadExprNode extends ExprNode { /// A `new Type { ... }` expression. class NewExprNode extends ExprNode { - /// The type being constructed, if present. + /// The type being constructed. type: TypeNode? /// The object body. @@ -756,7 +760,7 @@ class BinaryOpExprNode extends ExprNode { /// The left-hand expression. left: ExprNode - /// The right-hand expression, if present (not present for `is`/`as` which use [rightType]). + /// The right-hand expression (not present for `is`/`as` which use [rightType]). right: ExprNode? /// The right-hand type, if this is an `is` or `as` operation. @@ -792,7 +796,7 @@ class FunctionLiteralExprNode extends ExprNode { /// A parenthesized expression (`(expr)`). class ParenthesizedExprNode extends ExprNode { - /// The inner expression, if present (may be empty for `()`). + /// The inner expression (may be empty for `()`). expression: ExprNode? } @@ -861,7 +865,7 @@ class AnnotationNode extends SyntaxNode { /// The annotation type. type: TypeNode - /// The annotation body, if present. + /// The annotation body. body: ObjectBodyNode? } @@ -873,7 +877,7 @@ class ParameterNode extends SyntaxNode { /// The parameter name, or `null` for a wildcard parameter (`_`). identifier: IdentifierNode? - /// The type annotation, if present. + /// The type annotation. typeAnnotation: TypeNode? } From b08054deaf7c84f392ef6dd752f1bbba1304d37d Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Mon, 3 Aug 2026 14:18:14 +0200 Subject: [PATCH 28/49] Change some properties to `keyword` for consistency --- .../pkl/core/stdlib/syntax/ParserNodes.java | 51 ++++++++----------- .../core/stdlib/syntax/SyntaxNodeNodes.java | 24 ++++----- .../input/syntax/expressions.pkl | 4 +- .../input/syntax/moduleStructure.pkl | 8 +-- .../input/syntax/objectMembers.pkl | 4 +- stdlib/syntax.pkl | 16 +++--- 6 files changed, 47 insertions(+), 60 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 42702ebfa..301afad64 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -138,7 +138,7 @@ private static VmObjectFactory nodeOnlyFactory(Supplier classS private static final VmObjectFactory objectSpreadNodeFactory = new VmObjectFactory(SyntaxModule::getObjectSpreadNodeClass) .addProperty("node", vm -> vm) - .addBooleanProperty("isNullable", ParserNodes::objectSpreadIsNullable) + .addStringProperty("keyword", ParserNodes::objectSpreadKeyword) .addTypedProperty("expression", ParserNodes::soleExpr); private static final VmObjectFactory whenGeneratorNodeFactory = new VmObjectFactory(SyntaxModule::getWhenGeneratorNodeClass) @@ -269,7 +269,7 @@ private static VmObjectFactory nodeOnlyFactory(Supplier classS private static final VmObjectFactory importExprNodeFactory = new VmObjectFactory(SyntaxModule::getImportExprNodeClass) .addProperty("node", vm -> vm) - .addBooleanProperty("isGlob", ParserNodes::importIsGlob) + .addStringProperty("keyword", ParserNodes::importKeyword) .addStringProperty("uri", ParserNodes::importUri); private static final VmObjectFactory readExprNodeFactory = new VmObjectFactory(SyntaxModule::getReadExprNodeClass) @@ -336,7 +336,7 @@ private static VmObjectFactory nodeOnlyFactory(Supplier classS private static final VmObjectFactory importNodeFactory = new VmObjectFactory(SyntaxModule::getImportNodeClass) .addProperty("node", vm -> vm) - .addBooleanProperty("isGlob", ParserNodes::importIsGlob) + .addStringProperty("keyword", ParserNodes::importKeyword) .addStringProperty("uri", ParserNodes::importUri) .addProperty("alias", ParserNodes::importAlias); @@ -352,7 +352,7 @@ private static VmObjectFactory nodeOnlyFactory(Supplier classS private static final VmObjectFactory extendsOrAmendsClauseNodeFactory = new VmObjectFactory(SyntaxModule::getExtendsOrAmendsClauseNodeClass) .addProperty("node", vm -> vm) - .addBooleanProperty("isAmend", ParserNodes::extendsOrAmendsClauseIsAmend) + .addStringProperty("keyword", ParserNodes::extendsOrAmendsClauseKeyword) .addStringProperty("uri", ParserNodes::stringCharsOf); private static final VmObjectFactory classNodeFactory = @@ -631,13 +631,7 @@ private static VmTyped soleExpr(VmTyped exprVm) { } private static String readKeyword(VmTyped exprVm) { - var data = (NodeData) exprVm.getExtraStorage(); - for (var child : data.node.children) { - if (child.type == NodeType.TERMINAL) { - return child.text(data.source); - } - } - return "read"; + return firstTerminalText(exprVm, "read"); } private static Object newExprType(VmTyped exprVm) { @@ -845,14 +839,8 @@ private static VmList objectEntryObjectBodies(VmTyped entryVm) { return objectBodiesOf(entryVm); } - private static boolean objectSpreadIsNullable(VmTyped spreadVm) { - var data = (NodeData) spreadVm.getExtraStorage(); - for (var child : data.node.children) { - if (child.type == NodeType.TERMINAL) { - return "...?".equals(child.text(data.source)); - } - } - return false; + private static String objectSpreadKeyword(VmTyped spreadVm) { + return firstTerminalText(spreadVm, "..."); } private static VmTyped whenCondition(VmTyped whenVm) { @@ -1029,9 +1017,9 @@ private static Object moduleDeclExtendsOrAmendsClause(VmTyped declVm) { : extendsOrAmendsClauseNodeFactory.create(clause); } - private static boolean extendsOrAmendsClauseIsAmend(VmTyped clauseVm) { + private static String extendsOrAmendsClauseKeyword(VmTyped clauseVm) { var data = (NodeData) clauseVm.getExtraStorage(); - return data.node.type == NodeType.AMENDS_CLAUSE; + return data.node.type == NodeType.AMENDS_CLAUSE ? "amends" : "extends"; } private static VmList moduleClasses(VmTyped moduleVm) { @@ -1180,14 +1168,8 @@ private static VmList moduleImports(VmTyped moduleVm) { return wrapAll(findChildrenVm(importListVm, NodeType.IMPORT), importNodeFactory); } - private static boolean importIsGlob(VmTyped importVm) { - var data = (NodeData) importVm.getExtraStorage(); - for (var child : data.node.children) { - if (child.type == NodeType.TERMINAL) { - return "import*".equals(child.text(data.source)); - } - } - return false; + private static String importKeyword(VmTyped importVm) { + return firstTerminalText(importVm, "import"); } private static String importUri(VmTyped importVm) { @@ -1215,6 +1197,17 @@ private static String identifierValue(VmTyped identifierVm) { : null; } + // The text of the node's first terminal child, or `fallback` if it has none + private static String firstTerminalText(VmTyped nodeVm, String fallback) { + var data = (NodeData) nodeVm.getExtraStorage(); + for (var child : data.node.children) { + if (child.type == NodeType.TERMINAL) { + return child.text(data.source); + } + } + return fallback; + } + // Extract the string constant from a node's `string_chars` child (dropping the enclosing quotes) private static String extractStringChars(Node node, char[] source) { var stringChars = node.findChildByType(NodeType.STRING_CHARS); diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java index 656bbaed6..ba7807695 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java @@ -56,13 +56,12 @@ private static VmTyped build(VmTyped self) { return switch (self.getVmClass().getSimpleName()) { case "ModuleNode" -> buildModule(self); case "ModuleDeclarationNode" -> buildModuleDeclaration(self); - case "ExtendsOrAmendsClauseNode" -> - bool(self, "isAmend") - ? branch( - "amends_clause", List.of(terminal("amends"), stringCharsNode(str(self, "uri")))) - : branch( - "extends_clause", - List.of(terminal("extends"), stringCharsNode(str(self, "uri")))); + case "ExtendsOrAmendsClauseNode" -> { + var keyword = str(self, "keyword"); + yield branch( + keyword.equals("amends") ? "amends_clause" : "extends_clause", + List.of(terminal(keyword), stringCharsNode(str(self, "uri")))); + } case "ImportNode" -> buildImport(self); case "ClassNode" -> buildClass(self); case "TypeAliasNode" -> buildTypeAlias(self); @@ -78,9 +77,7 @@ private static VmTyped build(VmTyped self) { case "ObjectSpreadNode" -> branch( "object_spread", - List.of( - terminal(bool(self, "isNullable") ? "...?" : "..."), - build(reqNode(self, "expression")))); + List.of(terminal(str(self, "keyword")), build(reqNode(self, "expression")))); case "MemberPredicateNode" -> buildMemberPredicate(self); case "ForGeneratorNode" -> buildForGenerator(self); case "WhenGeneratorNode" -> buildWhenGenerator(self); @@ -115,10 +112,7 @@ private static VmTyped build(VmTyped self) { case "ThrowExprNode" -> buildCall("throw_expr", "throw", build(reqNode(self, "expression"))); case "TraceExprNode" -> buildCall("trace_expr", "trace", build(reqNode(self, "expression"))); case "ImportExprNode" -> - buildCall( - "import_expr", - bool(self, "isGlob") ? "import*" : "import", - stringCharsNode(str(self, "uri"))); + buildCall("import_expr", str(self, "keyword"), stringCharsNode(str(self, "uri"))); case "ReadExprNode" -> buildCall("read_expr", str(self, "keyword"), build(reqNode(self, "expression"))); case "NewExprNode" -> buildNew(self); @@ -220,7 +214,7 @@ private static VmTyped buildModuleDeclaration(VmTyped self) { private static VmTyped buildImport(VmTyped self) { var children = new ArrayList<>(); - children.add(terminal(bool(self, "isGlob") ? "import*" : "import")); + children.add(terminal(str(self, "keyword"))); children.add(stringCharsNode(str(self, "uri"))); var alias = optNode(self, "alias"); if (alias != null) { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl index e23882c0a..39b93b285 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl @@ -197,12 +197,12 @@ facts { ["import expression"] { local impExpr = expr(#"import("foo.pkl")"#) impExpr is syntax.ImportExprNode - (impExpr as syntax.ImportExprNode).isGlob == false + (impExpr as syntax.ImportExprNode).keyword == "import" (impExpr as syntax.ImportExprNode).uri == "foo.pkl" local impGlob = expr(#"import*("*.pkl")"#) impGlob is syntax.ImportExprNode - (impGlob as syntax.ImportExprNode).isGlob == true + (impGlob as syntax.ImportExprNode).keyword == "import*" (impGlob as syntax.ImportExprNode).uri == "*.pkl" } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl index 351623c26..fc541a3c5 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl @@ -38,7 +38,7 @@ facts { local mod = parse(#"amends "base.pkl""#) mod.declaration != null mod.declaration!!.extendsOrAmendsClause != null - mod.declaration!!.extendsOrAmendsClause!!.isAmend == true + mod.declaration!!.extendsOrAmendsClause!!.keyword == "amends" mod.declaration!!.extendsOrAmendsClause!!.uri == "base.pkl" } @@ -46,7 +46,7 @@ facts { local mod = parse(#"extends "base.pkl""#) mod.declaration != null mod.declaration!!.extendsOrAmendsClause != null - mod.declaration!!.extendsOrAmendsClause!!.isAmend == false + mod.declaration!!.extendsOrAmendsClause!!.keyword == "extends" mod.declaration!!.extendsOrAmendsClause!!.uri == "base.pkl" } @@ -59,14 +59,14 @@ facts { mod.imports.length == 3 mod.imports[0].uri == "foo.pkl" - mod.imports[0].isGlob == false + mod.imports[0].keyword == "import" mod.imports[0].alias == null mod.imports[1].uri == "bar.pkl" mod.imports[1].alias!!.value == "myBar" mod.imports[2].uri == "*.pkl" - mod.imports[2].isGlob == true + mod.imports[2].keyword == "import*" } ["class declaration"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl index 795a9afcd..da71e739d 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl @@ -74,14 +74,14 @@ facts { ["object spread"] { local b = body("...other") b.spreads.length == 1 - b.spreads.first.isNullable == false + b.spreads.first.keyword == "..." b.spreads.first.expression is syntax.UnqualifiedAccessExprNode } ["nullable object spread"] { local b = body("...?other") b.spreads.length == 1 - b.spreads.first.isNullable == true + b.spreads.first.keyword == "...?" } ["member predicate"] { diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index da34e72e4..d1e30e0da 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -326,8 +326,8 @@ class ModuleDeclarationNode extends SyntaxNode { /// The `extends` or `amends` clause of a module declaration. class ExtendsOrAmendsClauseNode extends SyntaxNode { - /// Whether this is an `amends` clause. When `false`, this is an `extends` clause. - isAmend: Boolean + /// The keyword used (`"extends"` or `"amends"`). + keyword: "extends" | "amends" /// The URI string of the amended or extended module. uri: String @@ -335,8 +335,8 @@ class ExtendsOrAmendsClauseNode extends SyntaxNode { /// An import declaration. class ImportNode extends SyntaxNode { - /// Whether this is a glob import (`import*`). - isGlob: Boolean + /// The keyword used (`"import"` or `"import*"`). + keyword: "import" | "import*" /// The URI string of the import. uri: String @@ -541,8 +541,8 @@ class ObjectEntryNode extends SyntaxNode { /// An object spread (`...expr` or `...?expr`). class ObjectSpreadNode extends SyntaxNode { - /// Whether this is a nullable spread (`...?`). - isNullable: Boolean + /// The keyword used (`"..."` or `"...?"`). + keyword: "..." | "...?" /// The spread expression. expression: ExprNode @@ -718,8 +718,8 @@ class TraceExprNode extends ExprNode { /// An `import("uri")` or `import*("uri")` expression. class ImportExprNode extends ExprNode { - /// Whether this is a glob import expression (`import*`). - isGlob: Boolean + /// The keyword used (`"import"` or `"import*"`). + keyword: "import" | "import*" /// The import URI string. uri: String From 4d91dba724aa55b926877f2ffb964d33abe34fc3 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Mon, 3 Aug 2026 15:02:09 +0200 Subject: [PATCH 29/49] Fix some remarks --- .../main/java/org/pkl/core/runtime/SyntaxModule.java | 8 ++++---- .../java/org/pkl/core/stdlib/syntax/ParserNodes.java | 10 +++++----- .../org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java | 2 +- .../input/syntax/expressions.pkl | 8 ++++---- .../input/syntax/moduleStructure.pkl | 2 +- stdlib/syntax.pkl | 12 ++++++------ 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java index 7515eb7f4..f772758df 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java @@ -209,8 +209,8 @@ public static VmClass getNullLiteralExprNodeClass() { return NullLiteralExprNodeClass.instance; } - public static VmClass getBoolLiteralExprNodeClass() { - return BoolLiteralExprNodeClass.instance; + public static VmClass getBooleanLiteralExprNodeClass() { + return BooleanLiteralExprNodeClass.instance; } public static VmClass getIntLiteralExprNodeClass() { @@ -485,8 +485,8 @@ private static final class NullLiteralExprNodeClass { static final VmClass instance = loadClass("NullLiteralExprNode"); } - private static final class BoolLiteralExprNodeClass { - static final VmClass instance = loadClass("BoolLiteralExprNode"); + private static final class BooleanLiteralExprNodeClass { + static final VmClass instance = loadClass("BooleanLiteralExprNode"); } private static final class IntLiteralExprNodeClass { diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 301afad64..073002877 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -200,10 +200,10 @@ private static VmObjectFactory nodeOnlyFactory(Supplier classS nodeOnlyFactory(SyntaxModule::getModuleExprNodeClass); private static final VmObjectFactory nullLiteralExprNodeFactory = nodeOnlyFactory(SyntaxModule::getNullLiteralExprNodeClass); - private static final VmObjectFactory boolLiteralExprNodeFactory = - new VmObjectFactory(SyntaxModule::getBoolLiteralExprNodeClass) + private static final VmObjectFactory booleanLiteralExprNodeFactory = + new VmObjectFactory(SyntaxModule::getBooleanLiteralExprNodeClass) .addProperty("node", vm -> vm) - .addBooleanProperty("value", ParserNodes::boolLiteralValue); + .addBooleanProperty("value", ParserNodes::booleanLiteralValue); private static final VmObjectFactory intLiteralExprNodeFactory = new VmObjectFactory(SyntaxModule::getIntLiteralExprNodeClass) .addProperty("node", vm -> vm) @@ -555,7 +555,7 @@ private static String literalText(VmTyped exprVm) { return text == null ? "" : text; } - private static boolean boolLiteralValue(VmTyped exprVm) { + private static boolean booleanLiteralValue(VmTyped exprVm) { return "true".equals(nodeText((NodeData) exprVm.getExtraStorage())); } @@ -1425,7 +1425,7 @@ private static VmTyped wrapExpr(VmTyped exprVm) { case OUTER_EXPR -> outerExprNodeFactory.create(exprVm); case MODULE_EXPR -> moduleExprNodeFactory.create(exprVm); case NULL_EXPR -> nullLiteralExprNodeFactory.create(exprVm); - case BOOL_LITERAL_EXPR -> boolLiteralExprNodeFactory.create(exprVm); + case BOOL_LITERAL_EXPR -> booleanLiteralExprNodeFactory.create(exprVm); case INT_LITERAL_EXPR -> intLiteralExprNodeFactory.create(exprVm); case FLOAT_LITERAL_EXPR -> floatLiteralExprNodeFactory.create(exprVm); case SINGLE_LINE_STRING_LITERAL_EXPR -> singleLineStringLiteralExprNodeFactory.create(exprVm); diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java index ba7807695..70c8079da 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java @@ -85,7 +85,7 @@ yield branch( case "OuterExprNode" -> leaf("outer_expr", "outer"); case "ModuleExprNode" -> leaf("module_expr", "module"); case "NullLiteralExprNode" -> leaf("null_expr", "null"); - case "BoolLiteralExprNode" -> + case "BooleanLiteralExprNode" -> leaf("bool_literal_expr", Boolean.toString(bool(self, "value"))); case "IntLiteralExprNode" -> leaf("int_literal_expr", numText(member(self, "value"))); case "FloatLiteralExprNode" -> leaf("float_literal_expr", numText(member(self, "value"))); diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl index 39b93b285..678f44535 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl @@ -8,12 +8,12 @@ local function expr(source: String) = facts { ["literals"] { local boolTrue = expr("true") - boolTrue is syntax.BoolLiteralExprNode - (boolTrue as syntax.BoolLiteralExprNode).value == true + boolTrue is syntax.BooleanLiteralExprNode + (boolTrue as syntax.BooleanLiteralExprNode).value == true local boolFalse = expr("false") - boolFalse is syntax.BoolLiteralExprNode - (boolFalse as syntax.BoolLiteralExprNode).value == false + boolFalse is syntax.BooleanLiteralExprNode + (boolFalse as syntax.BooleanLiteralExprNode).value == false local intLit = expr("42") intLit is syntax.IntLiteralExprNode diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl index fc541a3c5..4e8e1e918 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl @@ -104,7 +104,7 @@ facts { cls.body!!.methods.first.parameters.first.identifier!!.value == "speed" cls.body!!.methods.first.returnType != null cls.body!!.methods.first.body != null - cls.body!!.methods.first.body is syntax.BoolLiteralExprNode + cls.body!!.methods.first.body is syntax.BooleanLiteralExprNode } ["class with extends and type parameters"] { diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index d1e30e0da..edb5b3c44 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -554,7 +554,7 @@ class MemberPredicateNode extends SyntaxNode { condition: ExprNode /// The value expression. - value: ExprNode? + value: ExprNode(objectBodies.isEmpty)? /// Object bodies for amending. objectBodies: List @@ -588,19 +588,19 @@ class WhenGeneratorNode extends SyntaxNode { } /// The `this` expression. -class ThisExprNode extends ExprNode {} +class ThisExprNode extends ExprNode /// The `outer` expression. -class OuterExprNode extends ExprNode {} +class OuterExprNode extends ExprNode /// The `module` expression. -class ModuleExprNode extends ExprNode {} +class ModuleExprNode extends ExprNode /// A `null` literal expression. -class NullLiteralExprNode extends ExprNode {} +class NullLiteralExprNode extends ExprNode /// A boolean literal expression (`true` or `false`). -class BoolLiteralExprNode extends ExprNode { +class BooleanLiteralExprNode extends ExprNode { /// The boolean value. value: Boolean } From b75640660fd28767a5b0eb7bb4661894c0c3bfdc Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Mon, 3 Aug 2026 16:00:46 +0200 Subject: [PATCH 30/49] Change binary operator to have their own classes --- .../org/pkl/core/runtime/SyntaxModule.java | 152 +++++++++++++++++- .../pkl/core/stdlib/syntax/ParserNodes.java | 99 ++++++++++-- .../core/stdlib/syntax/SyntaxNodeNodes.java | 40 ++++- .../input/syntax/expressions.pkl | 54 ++++--- .../input/syntax/objectMembers.pkl | 4 +- .../input/syntax/types.pkl | 2 +- .../output/syntax/expressions.pcf | 9 ++ stdlib/syntax.pkl | 81 ++++++++-- 8 files changed, 378 insertions(+), 63 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java index f772758df..2f14e10bc 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java @@ -281,8 +281,80 @@ public static VmClass getAmendsExprNodeClass() { return AmendsExprNodeClass.instance; } - public static VmClass getBinaryOpExprNodeClass() { - return BinaryOpExprNodeClass.instance; + public static VmClass getExponentiationExprNodeClass() { + return ExponentiationExprNodeClass.instance; + } + + public static VmClass getMultiplicationExprNodeClass() { + return MultiplicationExprNodeClass.instance; + } + + public static VmClass getDivisionExprNodeClass() { + return DivisionExprNodeClass.instance; + } + + public static VmClass getIntegerDivisionExprNodeClass() { + return IntegerDivisionExprNodeClass.instance; + } + + public static VmClass getRemainderExprNodeClass() { + return RemainderExprNodeClass.instance; + } + + public static VmClass getAdditionExprNodeClass() { + return AdditionExprNodeClass.instance; + } + + public static VmClass getSubtractionExprNodeClass() { + return SubtractionExprNodeClass.instance; + } + + public static VmClass getLessThanExprNodeClass() { + return LessThanExprNodeClass.instance; + } + + public static VmClass getLessThanOrEqualExprNodeClass() { + return LessThanOrEqualExprNodeClass.instance; + } + + public static VmClass getGreaterThanExprNodeClass() { + return GreaterThanExprNodeClass.instance; + } + + public static VmClass getGreaterThanOrEqualExprNodeClass() { + return GreaterThanOrEqualExprNodeClass.instance; + } + + public static VmClass getEqualExprNodeClass() { + return EqualExprNodeClass.instance; + } + + public static VmClass getNotEqualExprNodeClass() { + return NotEqualExprNodeClass.instance; + } + + public static VmClass getLogicalAndExprNodeClass() { + return LogicalAndExprNodeClass.instance; + } + + public static VmClass getLogicalOrExprNodeClass() { + return LogicalOrExprNodeClass.instance; + } + + public static VmClass getPipeExprNodeClass() { + return PipeExprNodeClass.instance; + } + + public static VmClass getNullCoalescingExprNodeClass() { + return NullCoalescingExprNodeClass.instance; + } + + public static VmClass getTypeCheckExprNodeClass() { + return TypeCheckExprNodeClass.instance; + } + + public static VmClass getTypeCastExprNodeClass() { + return TypeCastExprNodeClass.instance; } public static VmClass getUnaryMinusExprNodeClass() { @@ -557,8 +629,80 @@ private static final class AmendsExprNodeClass { static final VmClass instance = loadClass("AmendsExprNode"); } - private static final class BinaryOpExprNodeClass { - static final VmClass instance = loadClass("BinaryOpExprNode"); + private static final class ExponentiationExprNodeClass { + static final VmClass instance = loadClass("ExponentiationExprNode"); + } + + private static final class MultiplicationExprNodeClass { + static final VmClass instance = loadClass("MultiplicationExprNode"); + } + + private static final class DivisionExprNodeClass { + static final VmClass instance = loadClass("DivisionExprNode"); + } + + private static final class IntegerDivisionExprNodeClass { + static final VmClass instance = loadClass("IntegerDivisionExprNode"); + } + + private static final class RemainderExprNodeClass { + static final VmClass instance = loadClass("RemainderExprNode"); + } + + private static final class AdditionExprNodeClass { + static final VmClass instance = loadClass("AdditionExprNode"); + } + + private static final class SubtractionExprNodeClass { + static final VmClass instance = loadClass("SubtractionExprNode"); + } + + private static final class LessThanExprNodeClass { + static final VmClass instance = loadClass("LessThanExprNode"); + } + + private static final class LessThanOrEqualExprNodeClass { + static final VmClass instance = loadClass("LessThanOrEqualExprNode"); + } + + private static final class GreaterThanExprNodeClass { + static final VmClass instance = loadClass("GreaterThanExprNode"); + } + + private static final class GreaterThanOrEqualExprNodeClass { + static final VmClass instance = loadClass("GreaterThanOrEqualExprNode"); + } + + private static final class EqualExprNodeClass { + static final VmClass instance = loadClass("EqualExprNode"); + } + + private static final class NotEqualExprNodeClass { + static final VmClass instance = loadClass("NotEqualExprNode"); + } + + private static final class LogicalAndExprNodeClass { + static final VmClass instance = loadClass("LogicalAndExprNode"); + } + + private static final class LogicalOrExprNodeClass { + static final VmClass instance = loadClass("LogicalOrExprNode"); + } + + private static final class PipeExprNodeClass { + static final VmClass instance = loadClass("PipeExprNode"); + } + + private static final class NullCoalescingExprNodeClass { + static final VmClass instance = loadClass("NullCoalescingExprNode"); + } + + private static final class TypeCheckExprNodeClass { + static final VmClass instance = loadClass("TypeCheckExprNode"); + } + + private static final class TypeCastExprNodeClass { + static final VmClass instance = loadClass("TypeCastExprNode"); } private static final class UnaryMinusExprNodeClass { diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 073002877..3673ddc3e 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -286,13 +286,59 @@ private static VmObjectFactory nodeOnlyFactory(Supplier classS .addProperty("node", vm -> vm) .addTypedProperty("parentExpr", ParserNodes::amendsParentExpr) .addTypedProperty("body", ParserNodes::amendsBody); - private static final VmObjectFactory binaryOpExprNodeFactory = - new VmObjectFactory(SyntaxModule::getBinaryOpExprNodeClass) - .addProperty("node", vm -> vm) - .addStringProperty("operator", ParserNodes::binaryOpOperator) - .addTypedProperty("left", ParserNodes::binaryOpLeft) - .addProperty("right", ParserNodes::binaryOpRight) - .addProperty("rightType", ParserNodes::binaryOpRightType); + + private static VmObjectFactory binaryOpExprNodeFactory(Supplier classSupplier) { + return new VmObjectFactory(classSupplier) + .addProperty("node", vm -> vm) + .addTypedProperty("left", ParserNodes::binaryOpLeft) + .addTypedProperty("right", ParserNodes::binaryOpRight); + } + + private static VmObjectFactory typeOpExprNodeFactory(Supplier classSupplier) { + return new VmObjectFactory(classSupplier) + .addProperty("node", vm -> vm) + .addTypedProperty("expression", ParserNodes::binaryOpLeft) + .addTypedProperty("type", ParserNodes::typeOpType); + } + + private static final VmObjectFactory exponentiationExprNodeFactory = + binaryOpExprNodeFactory(SyntaxModule::getExponentiationExprNodeClass); + private static final VmObjectFactory multiplicationExprNodeFactory = + binaryOpExprNodeFactory(SyntaxModule::getMultiplicationExprNodeClass); + private static final VmObjectFactory divisionExprNodeFactory = + binaryOpExprNodeFactory(SyntaxModule::getDivisionExprNodeClass); + private static final VmObjectFactory integerDivisionExprNodeFactory = + binaryOpExprNodeFactory(SyntaxModule::getIntegerDivisionExprNodeClass); + private static final VmObjectFactory remainderExprNodeFactory = + binaryOpExprNodeFactory(SyntaxModule::getRemainderExprNodeClass); + private static final VmObjectFactory additionExprNodeFactory = + binaryOpExprNodeFactory(SyntaxModule::getAdditionExprNodeClass); + private static final VmObjectFactory subtractionExprNodeFactory = + binaryOpExprNodeFactory(SyntaxModule::getSubtractionExprNodeClass); + private static final VmObjectFactory lessThanExprNodeFactory = + binaryOpExprNodeFactory(SyntaxModule::getLessThanExprNodeClass); + private static final VmObjectFactory lessThanOrEqualExprNodeFactory = + binaryOpExprNodeFactory(SyntaxModule::getLessThanOrEqualExprNodeClass); + private static final VmObjectFactory greaterThanExprNodeFactory = + binaryOpExprNodeFactory(SyntaxModule::getGreaterThanExprNodeClass); + private static final VmObjectFactory greaterThanOrEqualExprNodeFactory = + binaryOpExprNodeFactory(SyntaxModule::getGreaterThanOrEqualExprNodeClass); + private static final VmObjectFactory equalExprNodeFactory = + binaryOpExprNodeFactory(SyntaxModule::getEqualExprNodeClass); + private static final VmObjectFactory notEqualExprNodeFactory = + binaryOpExprNodeFactory(SyntaxModule::getNotEqualExprNodeClass); + private static final VmObjectFactory logicalAndExprNodeFactory = + binaryOpExprNodeFactory(SyntaxModule::getLogicalAndExprNodeClass); + private static final VmObjectFactory logicalOrExprNodeFactory = + binaryOpExprNodeFactory(SyntaxModule::getLogicalOrExprNodeClass); + private static final VmObjectFactory pipeExprNodeFactory = + binaryOpExprNodeFactory(SyntaxModule::getPipeExprNodeClass); + private static final VmObjectFactory nullCoalescingExprNodeFactory = + binaryOpExprNodeFactory(SyntaxModule::getNullCoalescingExprNodeClass); + private static final VmObjectFactory typeCheckExprNodeFactory = + typeOpExprNodeFactory(SyntaxModule::getTypeCheckExprNodeClass); + private static final VmObjectFactory typeCastExprNodeFactory = + typeOpExprNodeFactory(SyntaxModule::getTypeCastExprNodeClass); private static final VmObjectFactory unaryMinusExprNodeFactory = new VmObjectFactory(SyntaxModule::getUnaryMinusExprNodeClass) .addProperty("node", vm -> vm) @@ -665,14 +711,39 @@ private static VmTyped binaryOpLeft(VmTyped exprVm) { return wrapExpr(findExprChildrenVm(exprVm).get(0)); } - private static Object binaryOpRight(VmTyped exprVm) { - var exprs = findExprChildrenVm(exprVm); - return exprs.size() < 2 ? VmNull.withoutDefault() : wrapExpr(exprs.get(1)); + private static VmTyped binaryOpRight(VmTyped exprVm) { + return wrapExpr(findExprChildrenVm(exprVm).get(1)); } - private static Object binaryOpRightType(VmTyped exprVm) { - var type = findTypeChildVm(exprVm); - return type == null ? VmNull.withoutDefault() : wrapType(type); + private static VmTyped typeOpType(VmTyped exprVm) { + return wrapType(requireTypeChild(exprVm)); + } + + private static VmObjectFactory binaryOpFactory(VmTyped exprVm) { + var operator = binaryOpOperator(exprVm); + return switch (operator) { + case "**" -> exponentiationExprNodeFactory; + case "*" -> multiplicationExprNodeFactory; + case "/" -> divisionExprNodeFactory; + case "~/" -> integerDivisionExprNodeFactory; + case "%" -> remainderExprNodeFactory; + case "+" -> additionExprNodeFactory; + case "-" -> subtractionExprNodeFactory; + case "<" -> lessThanExprNodeFactory; + case "<=" -> lessThanOrEqualExprNodeFactory; + case ">" -> greaterThanExprNodeFactory; + case ">=" -> greaterThanOrEqualExprNodeFactory; + case "==" -> equalExprNodeFactory; + case "!=" -> notEqualExprNodeFactory; + case "&&" -> logicalAndExprNodeFactory; + case "||" -> logicalOrExprNodeFactory; + case "|>" -> pipeExprNodeFactory; + case "??" -> nullCoalescingExprNodeFactory; + case "is" -> typeCheckExprNodeFactory; + case "as" -> typeCastExprNodeFactory; + default -> + throw new VmExceptionBuilder().bug("Unexpected binary operator: " + operator).build(); + }; } private static VmList functionLiteralParameters(VmTyped exprVm) { @@ -1443,7 +1514,7 @@ private static VmTyped wrapExpr(VmTyped exprVm) { case READ_EXPR -> readExprNodeFactory.create(exprVm); case NEW_EXPR -> newExprNodeFactory.create(exprVm); case AMENDS_EXPR -> amendsExprNodeFactory.create(exprVm); - case BINARY_OP_EXPR -> binaryOpExprNodeFactory.create(exprVm); + case BINARY_OP_EXPR -> binaryOpFactory(exprVm).create(exprVm); case UNARY_MINUS_EXPR -> unaryMinusExprNodeFactory.create(exprVm); case LOGICAL_NOT_EXPR -> logicalNotExprNodeFactory.create(exprVm); case NON_NULL_EXPR -> nonNullExprNodeFactory.create(exprVm); diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java index 70c8079da..dd4f41d55 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java @@ -120,7 +120,25 @@ yield branch( branch( "amends_expr", List.of(build(reqNode(self, "parentExpr")), build(reqNode(self, "body")))); - case "BinaryOpExprNode" -> buildBinaryOp(self); + case "ExponentiationExprNode" -> buildBinaryOp(self, "**"); + case "MultiplicationExprNode" -> buildBinaryOp(self, "*"); + case "DivisionExprNode" -> buildBinaryOp(self, "/"); + case "IntegerDivisionExprNode" -> buildBinaryOp(self, "~/"); + case "RemainderExprNode" -> buildBinaryOp(self, "%"); + case "AdditionExprNode" -> buildBinaryOp(self, "+"); + case "SubtractionExprNode" -> buildBinaryOp(self, "-"); + case "LessThanExprNode" -> buildBinaryOp(self, "<"); + case "LessThanOrEqualExprNode" -> buildBinaryOp(self, "<="); + case "GreaterThanExprNode" -> buildBinaryOp(self, ">"); + case "GreaterThanOrEqualExprNode" -> buildBinaryOp(self, ">="); + case "EqualExprNode" -> buildBinaryOp(self, "=="); + case "NotEqualExprNode" -> buildBinaryOp(self, "!="); + case "LogicalAndExprNode" -> buildBinaryOp(self, "&&"); + case "LogicalOrExprNode" -> buildBinaryOp(self, "||"); + case "PipeExprNode" -> buildBinaryOp(self, "|>"); + case "NullCoalescingExprNode" -> buildBinaryOp(self, "??"); + case "TypeCheckExprNode" -> buildTypeOp(self, "is"); + case "TypeCastExprNode" -> buildTypeOp(self, "as"); case "UnaryMinusExprNode" -> branch("unary_minus_expr", List.of(terminal("-"), build(reqNode(self, "operand")))); case "LogicalNotExprNode" -> @@ -585,14 +603,20 @@ private static VmTyped buildNew(VmTyped self) { return branch("new_expr", List.of(branch("new_header", header), build(reqNode(self, "body")))); } - private static VmTyped buildBinaryOp(VmTyped self) { - var operator = str(self, "operator"); - var right = - operator.equals("is") || operator.equals("as") - ? build(nonNull(optNode(self, "rightType"))) - : build(nonNull(optNode(self, "right"))); + private static VmTyped buildBinaryOp(VmTyped self, String operator) { return branch( - "binary_op_expr", List.of(build(reqNode(self, "left")), operatorLeaf(operator), right)); + "binary_op_expr", + List.of( + build(reqNode(self, "left")), operatorLeaf(operator), build(reqNode(self, "right")))); + } + + private static VmTyped buildTypeOp(VmTyped self, String operator) { + return branch( + "binary_op_expr", + List.of( + build(reqNode(self, "expression")), + operatorLeaf(operator), + build(reqNode(self, "type")))); } private static VmTyped buildFunctionLiteral(VmTyped self) { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl index 678f44535..d9fb9285f 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl @@ -105,32 +105,36 @@ facts { ["binary operators"] { local add = expr("1 + 2") - add is syntax.BinaryOpExprNode - (add as syntax.BinaryOpExprNode).operator == "+" - (add as syntax.BinaryOpExprNode).left is syntax.IntLiteralExprNode - (add as syntax.BinaryOpExprNode).right is syntax.IntLiteralExprNode - - local eq = expr("a == b") - eq is syntax.BinaryOpExprNode - (eq as syntax.BinaryOpExprNode).operator == "==" - - local pipeline = expr("a |> b") - pipeline is syntax.BinaryOpExprNode - (pipeline as syntax.BinaryOpExprNode).operator == "|>" - - local nullCoalesce = expr("a ?? b") - nullCoalesce is syntax.BinaryOpExprNode - (nullCoalesce as syntax.BinaryOpExprNode).operator == "??" + add is syntax.AdditionExprNode + (add as syntax.AdditionExprNode).left is syntax.IntLiteralExprNode + (add as syntax.AdditionExprNode).right is syntax.IntLiteralExprNode + + expr("1 ** 2") is syntax.ExponentiationExprNode + expr("1 * 2") is syntax.MultiplicationExprNode + expr("1 / 2") is syntax.DivisionExprNode + expr("1 ~/ 2") is syntax.IntegerDivisionExprNode + expr("1 % 2") is syntax.RemainderExprNode + expr("1 - 2") is syntax.SubtractionExprNode + expr("a < b") is syntax.LessThanExprNode + expr("a <= b") is syntax.LessThanOrEqualExprNode + expr("a > b") is syntax.GreaterThanExprNode + expr("a >= b") is syntax.GreaterThanOrEqualExprNode + expr("a == b") is syntax.EqualExprNode + expr("a != b") is syntax.NotEqualExprNode + expr("a && b") is syntax.LogicalAndExprNode + expr("a || b") is syntax.LogicalOrExprNode + expr("a |> b") is syntax.PipeExprNode + expr("a ?? b") is syntax.NullCoalescingExprNode local isOp = expr("a is String") - isOp is syntax.BinaryOpExprNode - (isOp as syntax.BinaryOpExprNode).operator == "is" - (isOp as syntax.BinaryOpExprNode).rightType is syntax.DeclaredTypeNode + isOp is syntax.TypeCheckExprNode + (isOp as syntax.TypeCheckExprNode).expression is syntax.UnqualifiedAccessExprNode + (isOp as syntax.TypeCheckExprNode).type is syntax.DeclaredTypeNode local asOp = expr("a as String") - asOp is syntax.BinaryOpExprNode - (asOp as syntax.BinaryOpExprNode).operator == "as" - (asOp as syntax.BinaryOpExprNode).rightType is syntax.DeclaredTypeNode + asOp is syntax.TypeCastExprNode + (asOp as syntax.TypeCastExprNode).expression is syntax.UnqualifiedAccessExprNode + (asOp as syntax.TypeCastExprNode).type is syntax.DeclaredTypeNode } ["unary operators"] { @@ -160,7 +164,7 @@ facts { letExpr is syntax.LetExprNode (letExpr as syntax.LetExprNode).parameter.identifier!!.value == "y" (letExpr as syntax.LetExprNode).bindingValue is syntax.IntLiteralExprNode - (letExpr as syntax.LetExprNode).body is syntax.BinaryOpExprNode + (letExpr as syntax.LetExprNode).body is syntax.AdditionExprNode } ["new expression"] { @@ -175,13 +179,13 @@ facts { (fn as syntax.FunctionLiteralExprNode).parameters.length == 2 (fn as syntax.FunctionLiteralExprNode).parameters[0].identifier!!.value == "x" (fn as syntax.FunctionLiteralExprNode).parameters[1].identifier!!.value == "y" - (fn as syntax.FunctionLiteralExprNode).body is syntax.BinaryOpExprNode + (fn as syntax.FunctionLiteralExprNode).body is syntax.AdditionExprNode } ["parenthesized expression"] { local paren = expr("(1 + 2)") paren is syntax.ParenthesizedExprNode - (paren as syntax.ParenthesizedExprNode).expression is syntax.BinaryOpExprNode + (paren as syntax.ParenthesizedExprNode).expression is syntax.AdditionExprNode } ["throw and trace"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl index da71e739d..f4b49add4 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl @@ -87,14 +87,14 @@ facts { ["member predicate"] { local b = body("[[name == \"foo\"]] = 1") b.memberPredicates.length == 1 - b.memberPredicates.first.condition is syntax.BinaryOpExprNode + b.memberPredicates.first.condition is syntax.EqualExprNode b.memberPredicates.first.value is syntax.IntLiteralExprNode } ["member predicate with amending body"] { local b = body("[[name == \"foo\"]] { x = 1 }") local pred = b.memberPredicates.first - pred.condition is syntax.BinaryOpExprNode + pred.condition is syntax.EqualExprNode pred.value == null pred.objectBodies.length == 1 } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl index f7cf0ea04..8a5579bc8 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl @@ -70,7 +70,7 @@ facts { constrained is syntax.ConstrainedTypeNode (constrained as syntax.ConstrainedTypeNode).baseType is syntax.DeclaredTypeNode (constrained as syntax.ConstrainedTypeNode).constraints.length == 1 - (constrained as syntax.ConstrainedTypeNode).constraints.first is syntax.BinaryOpExprNode + (constrained as syntax.ConstrainedTypeNode).constraints.first is syntax.GreaterThanOrEqualExprNode } ["parenthesized type"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf index 681ed1554..8b0d966d3 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf @@ -70,6 +70,15 @@ facts { true true true + true + true + true + true + true + true + true + true + true } ["unary operators"] { true diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index edb5b3c44..2fdf946e4 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -752,19 +752,82 @@ class AmendsExprNode extends ExprNode { body: ObjectBodyNode } -/// A binary operator expression (`left op right`), including `is`/`as`. -class BinaryOpExprNode extends ExprNode { - /// The operator string. - operator: String - +/// Base class for binary operator expressions (`left op right`). +abstract class BinaryOpExprNode extends ExprNode { /// The left-hand expression. left: ExprNode - /// The right-hand expression (not present for `is`/`as` which use [rightType]). - right: ExprNode? + /// The right-hand expression. + right: ExprNode +} + +/// An exponentiation expression (`left ** right`). +class ExponentiationExprNode extends BinaryOpExprNode + +/// A multiplication expression (`left * right`). +class MultiplicationExprNode extends BinaryOpExprNode + +/// A division expression (`left / right`). +class DivisionExprNode extends BinaryOpExprNode + +/// An integer division expression (`left ~/ right`). +class IntegerDivisionExprNode extends BinaryOpExprNode + +/// A remainder expression (`left % right`). +class RemainderExprNode extends BinaryOpExprNode + +/// An addition expression (`left + right`). +class AdditionExprNode extends BinaryOpExprNode + +/// A subtraction expression (`left - right`). +class SubtractionExprNode extends BinaryOpExprNode + +/// A less-than comparison expression (`left < right`). +class LessThanExprNode extends BinaryOpExprNode + +/// A less-than-or-equal comparison expression (`left <= right`). +class LessThanOrEqualExprNode extends BinaryOpExprNode + +/// A greater-than comparison expression (`left > right`). +class GreaterThanExprNode extends BinaryOpExprNode + +/// A greater-than-or-equal comparison expression (`left >= right`). +class GreaterThanOrEqualExprNode extends BinaryOpExprNode + +/// An equality expression (`left == right`). +class EqualExprNode extends BinaryOpExprNode + +/// An inequality expression (`left != right`). +class NotEqualExprNode extends BinaryOpExprNode - /// The right-hand type, if this is an `is` or `as` operation. - rightType: TypeNode? +/// A logical and expression (`left && right`). +class LogicalAndExprNode extends BinaryOpExprNode + +/// A logical or expression (`left || right`). +class LogicalOrExprNode extends BinaryOpExprNode + +/// A pipe expression (`left |> right`). +class PipeExprNode extends BinaryOpExprNode + +/// A null coalescing expression (`left ?? right`). +class NullCoalescingExprNode extends BinaryOpExprNode + +/// A type check expression (`expression is Type`). +class TypeCheckExprNode extends ExprNode { + /// The left-hand expression. + expression: ExprNode + + /// The right-hand type. + type: TypeNode +} + +/// A type cast expression (`expression as Type`). +class TypeCastExprNode extends ExprNode { + /// The left-hand expression. + expression: ExprNode + + /// The right-hand type. + type: TypeNode } /// A unary minus expression (`-expr`). From 39c13ad5a557d2617f5b3be2d7c633f1e508de45 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Mon, 3 Aug 2026 16:56:10 +0200 Subject: [PATCH 31/49] Fix bug in generic parser and syntax node --- .../java/org/pkl/core/stdlib/syntax/ParserNodes.java | 12 ++++-------- .../org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java | 10 +--------- .../src/main/java/org/pkl/formatter/Builder.java | 1 - .../main/java/org/pkl/parser/GenericParserImpl.java | 9 +++------ stdlib/syntax.pkl | 4 ++-- 5 files changed, 10 insertions(+), 26 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 3673ddc3e..6c4422147 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -359,7 +359,7 @@ private static VmObjectFactory typeOpExprNodeFactory(Supplier private static final VmObjectFactory parenthesizedExprNodeFactory = new VmObjectFactory(SyntaxModule::getParenthesizedExprNodeClass) .addProperty("node", vm -> vm) - .addProperty("expression", ParserNodes::parenthesizedExpression); + .addTypedProperty("expression", ParserNodes::parenthesizedExpression); // String-part factories, produced by `buildStringParts`. `StringPartNode` is not a `SyntaxNode`, // but it also carries a hidden `node`, so the same `node`-property shape applies. @@ -754,13 +754,9 @@ private static VmTyped functionLiteralBody(VmTyped exprVm) { return wrapExpr(requireExprChild(requireChild(exprVm, NodeType.FUNCTION_LITERAL_BODY))); } - private static Object parenthesizedExpression(VmTyped exprVm) { - var elems = findChildVm(exprVm, NodeType.PARENTHESIZED_EXPR_ELEMENTS); - if (elems == null) { - return VmNull.withoutDefault(); - } - var expr = findExprChildVm(elems); - return expr == null ? VmNull.withoutDefault() : wrapExpr(expr); + private static VmTyped parenthesizedExpression(VmTyped exprVm) { + var elems = requireChild(exprVm, NodeType.PARENTHESIZED_EXPR_ELEMENTS); + return wrapExpr(requireExprChild(elems)); } private static Object argumentsOrNull(VmTyped ownerVm) { diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java index dd4f41d55..ff955ef99 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java @@ -152,8 +152,7 @@ yield branch( List.of( terminal("("), branch( - "parenthesized_expr_elements", - List.of(build(nonNull(optNode(self, "expression"))))), + "parenthesized_expr_elements", List.of(build(reqNode(self, "expression")))), terminal(")"))); case "UnknownTypeNode" -> leaf("unknown_type", "unknown"); case "NothingTypeNode" -> leaf("nothing_type", "nothing"); @@ -904,13 +903,6 @@ private static boolean bool(VmTyped self, String name) { return (Boolean) member(self, name); } - private static VmTyped nonNull(@Nullable VmTyped value) { - if (value == null) { - throw new VmExceptionBuilder().evalError("expectedNonNullValue").build(); - } - return value; - } - private static String numText(Object value) { return value instanceof String s ? s : value.toString(); } diff --git a/pkl-formatter/src/main/java/org/pkl/formatter/Builder.java b/pkl-formatter/src/main/java/org/pkl/formatter/Builder.java index 247ee8a04..c7b1f60ae 100644 --- a/pkl-formatter/src/main/java/org/pkl/formatter/Builder.java +++ b/pkl-formatter/src/main/java/org/pkl/formatter/Builder.java @@ -1028,7 +1028,6 @@ private FormatNode formatNewHeader(Node node) { } private FormatNode formatParenthesizedExpr(Node node) { - if (node.children.size() == 2) return new Text("()"); var nodes = formatGenericWithGen( node.children, diff --git a/pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java b/pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java index 0dd9b9a65..40ab6cf99 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java +++ b/pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java @@ -937,7 +937,9 @@ private boolean isFunctionLiteral() { var token = next().token; ff(); if (token == Token.RPAREN) { - return lookahead == Token.ARROW; + // `()` is only valid as an empty parameter list + // let `parseFunctionLiteral` report the missing `->` + return true; } if (token == Token.UNDERSCORE) { return true; @@ -1072,11 +1074,6 @@ private void validateStringIndentation(List nodes) { private Node parseParenthesizedExpr() { var children = new ArrayList(); expect(Token.LPAREN, children, "unexpectedToken", "("); - if (lookahead() == Token.RPAREN) { - ff(children); - children.add(makeTerminal(next())); - return new Node(NodeType.PARENTHESIZED_EXPR, children); - } var elements = new ArrayList(); ff(elements); elements.add(parseExpr(")")); diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 2fdf946e4..bf1799989 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -859,8 +859,8 @@ class FunctionLiteralExprNode extends ExprNode { /// A parenthesized expression (`(expr)`). class ParenthesizedExprNode extends ExprNode { - /// The inner expression (may be empty for `()`). - expression: ExprNode? + /// The inner expression. + expression: ExprNode } /// The `unknown` type. From 6e4a52bfbfc8d962bf8737d880a523147efe8f43 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Mon, 3 Aug 2026 17:26:51 +0200 Subject: [PATCH 32/49] Add tests for quoted identifiers --- .../input/syntax/moduleStructure.pkl | 46 +++++++++++++++++++ .../input/syntax/render.pkl | 21 +++++++++ .../output/syntax/moduleStructure.pcf | 18 ++++++++ .../output/syntax/render.pcf | 7 +++ 4 files changed, 92 insertions(+) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl index 4e8e1e918..833c82923 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl @@ -177,6 +177,52 @@ facts { params[2].isBlankIdentifier == false } + // `value` carries the identifier verbatim, so quoted identifiers keep their backticks + ["quoted identifiers"] { + local mod = parse(""" + module `my mod`.`sub pkg` + + import "foo.pkl" as `my alias` + + class `My Class` { + `a prop`: String + function `do it`(`the arg`: Int) = `the arg` + } + + typealias `My Alias` = `My Class` + + const `my prop` = 0 + """) + + mod.declaration!!.name!!.value == "`my mod`.`sub pkg`" + mod.declaration!!.name!!.identifiers.map((i) -> i.value) == List("`my mod`", "`sub pkg`") + + mod.imports.first.alias!!.value == "`my alias`" + + local cls = mod.classes.first + cls.identifier.value == "`My Class`" + cls.body!!.properties.first.identifier.value == "`a prop`" + cls.body!!.methods.first.identifier.value == "`do it`" + cls.body!!.methods.first.parameters.first.identifier!!.value == "`the arg`" + + local ta = mod.typeAliases.first + ta.identifier.value == "`My Alias`" + (ta.type as syntax.DeclaredTypeNode).name.value == "`My Class`" + + mod.properties.first.identifier.value == "`my prop`" + mod.properties.first.modifiers == List("const") + } + + ["quoted identifiers in object bodies and accesses"] { + local mod = parse("x { `inner prop` = `some`.`path` }") + local prop = mod.properties.first.objectBodies.first.properties.first + prop.identifier.value == "`inner prop`" + + local access = prop.value as syntax.QualifiedAccessExprNode + access.identifier.value == "`path`" + (access.receiver as syntax.UnqualifiedAccessExprNode).identifier.value == "`some`" + } + ["parser error"] { local result = new syntax.Parser {}.parseModuleOrNull("x = {{{") result == null diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl index f1a9db1a3..d7f032957 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl @@ -312,6 +312,27 @@ facts { roundTrip("x = (1 + 2)") == "x = (1 + 2)\n" } + ["quoted identifiers"] { + roundTrip("const `my prop` = 0") == "const `my prop` = 0\n" + roundTrip("x = `some`.`path`") == "x = `some`.`path`\n" + roundTrip("module `my mod`.`sub pkg`") == "module `my mod`.`sub pkg`\n" + roundTrip("function `do it`(`the arg`: Int) = `the arg`") + == "function `do it`(`the arg`: Int) = `the arg`\n" + roundTrip( + """ + class `My Class` { + `a prop`: String + } + """, + ) + == """ + class `My Class` { + `a prop`: String + } + + """ + } + ["modify identifier"] { local root = parseNode("x = 1") local modified = replaceLeaf(root!!, "identifier", "x", "y") diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf index cfceecf27..13299c0f2 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf @@ -103,6 +103,24 @@ facts { true true } + ["quoted identifiers"] { + true + true + true + true + true + true + true + true + true + true + true + } + ["quoted identifiers in object bodies and accesses"] { + true + true + true + } ["parser error"] { true } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/render.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/render.pcf index 73e1220b4..23295e95b 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/render.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/render.pcf @@ -106,6 +106,13 @@ facts { ["parenthesized expression"] { true } + ["quoted identifiers"] { + true + true + true + true + true + } ["modify identifier"] { true } From a9b7fac725eeafd4ceba2c878ffcf620b7a9ea3d Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Mon, 3 Aug 2026 17:40:27 +0200 Subject: [PATCH 33/49] Remove toNode --- .../input/syntax/expressions.pkl | 9 ++++++++- .../output/syntax/expressions.pcf | 5 +++++ stdlib/syntax.pkl | 20 +------------------ 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl index d9fb9285f..ef83d756e 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl @@ -57,11 +57,18 @@ facts { local multiLine = expr(#""" """ hello + world """ """#) multiLine is syntax.MultiLineStringLiteralExprNode local mlParts = (multiLine as syntax.MultiLineStringLiteralExprNode).parts - mlParts.length >= 1 + // each content line is preceded by a newline part + mlParts.length == 5 + mlParts[0] is syntax.StringNewlineNode + (mlParts[1] as syntax.StringCharsNode).value == "hello" + mlParts[2] is syntax.StringNewlineNode + (mlParts[3] as syntax.StringCharsNode).value == "world" + mlParts[4] is syntax.StringNewlineNode } ["keyword expressions"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf index 8b0d966d3..4d309931d 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/expressions.pcf @@ -29,6 +29,11 @@ facts { true true true + true + true + true + true + true } ["keyword expressions"] { true diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index bf1799989..30d08edf5 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -978,43 +978,25 @@ class DocCommentNode extends SyntaxNode { abstract class StringPartNode { /// The original parsed node, or `null` when built from scratch. hidden node: Node? = null - - /// Build the list of [Node] parts for this string part. - abstract function toNodes(): List } /// A plain text part of a string literal. class StringCharsNode extends StringPartNode { /// The text content. value: String - - function toNodes(): List = List(new Node { type = "string_chars"; text = outer.value }) } /// An escape sequence in a string literal (e.g., `"\\n"`, `"\\t"`). class StringEscapeNode extends StringPartNode { /// The escape sequence text including the leading backslash. value: String - - function toNodes(): List = List(new Node { type = "string_escape"; text = outer.value }) } /// A newline in a multi-line string literal. -class StringNewlineNode extends StringPartNode { - function toNodes(): List = List(new Node { type = "string_newline" }) -} +class StringNewlineNode extends StringPartNode /// An interpolation in a string literal (`\(expr)`). class StringInterpolationNode extends StringPartNode { /// The interpolated expression. expression: ExprNode - - local const terminal: Node = new Node { type = "terminal" } - - function toNodes(): List = - List( - (terminal) { text = "\\(" }, - outer.expression.builtNode, - (terminal) { text = ")" }, - ) } From 52e9204e62697b3ab7693e14f56f7fbb23838c8a Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Tue, 4 Aug 2026 13:48:45 +0200 Subject: [PATCH 34/49] Rename Node to GenricNode and SyntaxNode to Node --- .../org/pkl/core/runtime/SyntaxModule.java | 8 +- .../core/stdlib/syntax/GenericNodeNodes.java | 108 +++ .../org/pkl/core/stdlib/syntax/NodeNodes.java | 917 ++++++++++++++++-- .../pkl/core/stdlib/syntax/ParserNodes.java | 204 ++-- .../core/stdlib/syntax/SyntaxNodeNodes.java | 909 ----------------- .../pkl/core/stdlib/syntax/SyntaxNodes.java | 20 +- .../input/syntax/render.pkl | 14 +- .../input/syntax/spans.pkl | 20 +- .../input/syntax/traversal.pkl | 4 +- .../input/syntax/walk.pkl | 6 +- stdlib/syntax.pkl | 84 +- 11 files changed, 1142 insertions(+), 1152 deletions(-) create mode 100644 pkl-core/src/main/java/org/pkl/core/stdlib/syntax/GenericNodeNodes.java delete mode 100644 pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java index 2f14e10bc..9353eac10 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java @@ -29,8 +29,8 @@ public static VmTyped getModule() { return instance; } - public static VmClass getNodeClass() { - return NodeClass.instance; + public static VmClass getGenericNodeClass() { + return GenericNodeClass.instance; } public static VmClass getSpanClass() { @@ -377,8 +377,8 @@ public static VmClass getParenthesizedExprNodeClass() { return ParenthesizedExprNodeClass.instance; } - private static final class NodeClass { - static final VmClass instance = loadClass("Node"); + private static final class GenericNodeClass { + static final VmClass instance = loadClass("GenericNode"); } private static final class SpanClass { diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/GenericNodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/GenericNodeNodes.java new file mode 100644 index 000000000..ab0dc7caf --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/GenericNodeNodes.java @@ -0,0 +1,108 @@ +/* + * Copyright © 2026 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.stdlib.syntax; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.Specialization; +import java.util.ArrayDeque; +import org.pkl.core.ast.lambda.ApplyVmFunction1Node; +import org.pkl.core.ast.lambda.ApplyVmFunction2Node; +import org.pkl.core.ast.lambda.ApplyVmFunction2NodeGen; +import org.pkl.core.runtime.Identifier; +import org.pkl.core.runtime.VmFunction; +import org.pkl.core.runtime.VmList; +import org.pkl.core.runtime.VmPair; +import org.pkl.core.runtime.VmTyped; +import org.pkl.core.runtime.VmUtils; +import org.pkl.core.stdlib.ExternalMethod1Node; +import org.pkl.core.stdlib.ExternalMethod2Node; +import org.pkl.core.stdlib.syntax.SyntaxNodes.GenericNodeData; + +/** Backs {@code pkl.syntax#GenericNode.fold} and {@code pkl.syntax#GenericNode.walk}. */ +public final class GenericNodeNodes { + private GenericNodeNodes() {} + + public abstract static class fold extends ExternalMethod2Node { + @Child private ApplyVmFunction2Node applyAccumulate = ApplyVmFunction2NodeGen.create(); + + @Specialization + @TruffleBoundary + protected Object eval(VmTyped self, Object initial, VmFunction operator) { + var pending = new ArrayDeque(); + pending.push(self); + var result = initial; + while (!pending.isEmpty()) { + var node = pending.pop(); + result = applyAccumulate.execute(operator, result, node); + var children = (VmList) VmUtils.readMember(node, Identifier.CHILDREN); + for (var i = children.getLength() - 1; i >= 0; i--) { + pending.push((VmTyped) children.get(i)); + } + } + return result; + } + } + + public abstract static class walk extends ExternalMethod1Node { + @Child private ApplyVmFunction1Node applyVisit = ApplyVmFunction1Node.create(); + + @Specialization + @TruffleBoundary + protected VmTyped eval(VmTyped self, VmFunction visit) { + var result = walkNode(self, visit); + // the root of the returned tree has no parent + if (result.hasExtraStorage()) { + ((GenericNodeData) result.getExtraStorage()).parentVm = null; + } + return result; + } + + private VmTyped walkNode(VmTyped nodeVm, VmFunction visit) { + var visited = applyVisit.execute(visit, nodeVm); + + VmTyped node; + boolean descend; + if (visited instanceof VmPair pair) { + node = (VmTyped) pair.getFirst(); + descend = (Boolean) pair.getSecond(); + } else { + // `null`: leave this node unchanged and keep descending + node = nodeVm; + descend = true; + } + if (!descend) { + return node; + } + + var childrenVm = (VmList) VmUtils.readMember(node, Identifier.CHILDREN); + var length = childrenVm.getLength(); + if (length == 0) { + return node; + } + + var newChildren = new Object[length]; + var changed = false; + for (var i = 0; i < length; i++) { + var child = (VmTyped) childrenVm.get(i); + var newChild = walkNode(child, visit); + newChildren[i] = newChild; + changed |= newChild != child; + } + // reuse the node (and its extra storage) untouched when nothing below changed + return changed ? SyntaxNodes.rebuild(node, newChildren) : node; + } + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java index b083b22d6..4b015f3e3 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java @@ -17,91 +17,880 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Specialization; -import java.util.ArrayDeque; -import org.pkl.core.ast.lambda.ApplyVmFunction1Node; -import org.pkl.core.ast.lambda.ApplyVmFunction2Node; -import org.pkl.core.ast.lambda.ApplyVmFunction2NodeGen; +import java.util.ArrayList; +import java.util.List; +import org.jspecify.annotations.Nullable; import org.pkl.core.runtime.Identifier; -import org.pkl.core.runtime.VmFunction; +import org.pkl.core.runtime.SyntaxModule; +import org.pkl.core.runtime.VmExceptionBuilder; import org.pkl.core.runtime.VmList; -import org.pkl.core.runtime.VmPair; +import org.pkl.core.runtime.VmObjectBuilder; import org.pkl.core.runtime.VmTyped; import org.pkl.core.runtime.VmUtils; -import org.pkl.core.stdlib.ExternalMethod1Node; -import org.pkl.core.stdlib.ExternalMethod2Node; -import org.pkl.core.stdlib.syntax.SyntaxNodes.NodeData; +import org.pkl.core.stdlib.ExternalPropertyNode; +import org.pkl.core.stdlib.PklName; +import org.pkl.core.stdlib.syntax.SyntaxNodes.SpanData; +import org.pkl.parser.syntax.generic.FullSpan; public final class NodeNodes { private NodeNodes() {} - public abstract static class fold extends ExternalMethod2Node { - @Child private ApplyVmFunction2Node applyAccumulate = ApplyVmFunction2NodeGen.create(); - + @PklName("builtNode") + public abstract static class builtNode extends ExternalPropertyNode { @Specialization @TruffleBoundary - protected Object eval(VmTyped self, Object initial, VmFunction operator) { - var pending = new ArrayDeque(); - pending.push(self); - var result = initial; - while (!pending.isEmpty()) { - var node = pending.pop(); - result = applyAccumulate.execute(operator, result, node); - var children = (VmList) VmUtils.readMember(node, Identifier.CHILDREN); - for (var i = children.getLength() - 1; i >= 0; i--) { - pending.push((VmTyped) children.get(i)); - } - } - return result; + protected Object eval(VmTyped self) { + return build(self); } } - public abstract static class walk extends ExternalMethod1Node { - @Child private ApplyVmFunction1Node applyVisit = ApplyVmFunction1Node.create(); + private static VmTyped build(VmTyped self) { + return switch (self.getVmClass().getSimpleName()) { + case "ModuleNode" -> buildModule(self); + case "ModuleDeclarationNode" -> buildModuleDeclaration(self); + case "ExtendsOrAmendsClauseNode" -> { + var keyword = str(self, "keyword"); + yield branch( + keyword.equals("amends") ? "amends_clause" : "extends_clause", + List.of(terminal(keyword), stringCharsNode(str(self, "uri")))); + } + case "ImportNode" -> buildImport(self); + case "ClassNode" -> buildClass(self); + case "TypeAliasNode" -> buildTypeAlias(self); + case "ClassBodyNode" -> buildClassBody(self); + case "ClassPropertyNode" -> buildClassProperty(self); + case "ClassMethodNode" -> buildClassMethod(self); + case "ObjectBodyNode" -> buildObjectBody(self); + case "ObjectPropertyNode" -> buildObjectProperty(self); + case "ObjectMethodNode" -> buildObjectMethod(self); + case "ObjectElementNode" -> + branch("object_element", List.of(build(reqNode(self, "expression")))); + case "ObjectEntryNode" -> buildObjectEntry(self); + case "ObjectSpreadNode" -> + branch( + "object_spread", + List.of(terminal(str(self, "keyword")), build(reqNode(self, "expression")))); + case "MemberPredicateNode" -> buildMemberPredicate(self); + case "ForGeneratorNode" -> buildForGenerator(self); + case "WhenGeneratorNode" -> buildWhenGenerator(self); + case "ThisExprNode" -> leaf("this_expr", "this"); + case "OuterExprNode" -> leaf("outer_expr", "outer"); + case "ModuleExprNode" -> leaf("module_expr", "module"); + case "NullLiteralExprNode" -> leaf("null_expr", "null"); + case "BooleanLiteralExprNode" -> + leaf("bool_literal_expr", Boolean.toString(bool(self, "value"))); + case "IntLiteralExprNode" -> leaf("int_literal_expr", numText(member(self, "value"))); + case "FloatLiteralExprNode" -> leaf("float_literal_expr", numText(member(self, "value"))); + case "SingleLineStringLiteralExprNode" -> buildSingleLineString(self); + case "MultiLineStringLiteralExprNode" -> buildMultiLineString(self); + case "UnqualifiedAccessExprNode" -> buildUnqualifiedAccess(self); + case "QualifiedAccessExprNode" -> buildQualifiedAccess(self); + case "SubscriptExprNode" -> + branch( + "subscript_expr", + List.of( + build(reqNode(self, "receiver")), + operatorLeaf("["), + build(reqNode(self, "index")), + terminal("]"))); + case "SuperAccessExprNode" -> buildSuperAccess(self); + case "SuperSubscriptExprNode" -> + branch( + "super_subscript_expr", + List.of( + terminal("super"), terminal("["), build(reqNode(self, "index")), terminal("]"))); + case "IfExprNode" -> buildIf(self); + case "LetExprNode" -> buildLet(self); + case "ThrowExprNode" -> buildCall("throw_expr", "throw", build(reqNode(self, "expression"))); + case "TraceExprNode" -> buildCall("trace_expr", "trace", build(reqNode(self, "expression"))); + case "ImportExprNode" -> + buildCall("import_expr", str(self, "keyword"), stringCharsNode(str(self, "uri"))); + case "ReadExprNode" -> + buildCall("read_expr", str(self, "keyword"), build(reqNode(self, "expression"))); + case "NewExprNode" -> buildNew(self); + case "AmendsExprNode" -> + branch( + "amends_expr", + List.of(build(reqNode(self, "parentExpr")), build(reqNode(self, "body")))); + case "ExponentiationExprNode" -> buildBinaryOp(self, "**"); + case "MultiplicationExprNode" -> buildBinaryOp(self, "*"); + case "DivisionExprNode" -> buildBinaryOp(self, "/"); + case "IntegerDivisionExprNode" -> buildBinaryOp(self, "~/"); + case "RemainderExprNode" -> buildBinaryOp(self, "%"); + case "AdditionExprNode" -> buildBinaryOp(self, "+"); + case "SubtractionExprNode" -> buildBinaryOp(self, "-"); + case "LessThanExprNode" -> buildBinaryOp(self, "<"); + case "LessThanOrEqualExprNode" -> buildBinaryOp(self, "<="); + case "GreaterThanExprNode" -> buildBinaryOp(self, ">"); + case "GreaterThanOrEqualExprNode" -> buildBinaryOp(self, ">="); + case "EqualExprNode" -> buildBinaryOp(self, "=="); + case "NotEqualExprNode" -> buildBinaryOp(self, "!="); + case "LogicalAndExprNode" -> buildBinaryOp(self, "&&"); + case "LogicalOrExprNode" -> buildBinaryOp(self, "||"); + case "PipeExprNode" -> buildBinaryOp(self, "|>"); + case "NullCoalescingExprNode" -> buildBinaryOp(self, "??"); + case "TypeCheckExprNode" -> buildTypeOp(self, "is"); + case "TypeCastExprNode" -> buildTypeOp(self, "as"); + case "UnaryMinusExprNode" -> + branch("unary_minus_expr", List.of(terminal("-"), build(reqNode(self, "operand")))); + case "LogicalNotExprNode" -> + branch("logical_not_expr", List.of(terminal("!"), build(reqNode(self, "operand")))); + case "NonNullExprNode" -> + branch("non_null_expr", List.of(build(reqNode(self, "operand")), operatorLeaf("!!"))); + case "FunctionLiteralExprNode" -> buildFunctionLiteral(self); + case "ParenthesizedExprNode" -> + branch( + "parenthesized_expr", + List.of( + terminal("("), + branch( + "parenthesized_expr_elements", List.of(build(reqNode(self, "expression")))), + terminal(")"))); + case "UnknownTypeNode" -> leaf("unknown_type", "unknown"); + case "NothingTypeNode" -> leaf("nothing_type", "nothing"); + case "ModuleTypeNode" -> leaf("module_type", "module"); + case "DeclaredTypeNode" -> buildDeclaredType(self); + case "NullableTypeNode" -> + branch("nullable_type", List.of(build(reqNode(self, "baseType")), terminal("?"))); + case "UnionTypeNode" -> + branch( + "union_type", interleave(buildAll(listMember(self, "members")), () -> terminal("|"))); + case "FunctionTypeNode" -> buildFunctionType(self); + case "ConstrainedTypeNode" -> buildConstrainedType(self); + case "ParenthesizedTypeNode" -> + branch( + "parenthesized_type", + List.of( + terminal("("), + branch("parenthesized_type_elements", List.of(build(reqNode(self, "type")))), + terminal(")"))); + case "StringConstantTypeNode" -> + branch("string_constant_type", List.of(stringCharsNode(str(self, "value")))); + case "AnnotationNode" -> buildAnnotation(self); + case "ParameterNode" -> buildParameter(self); + case "TypeParameterNode" -> buildTypeParameter(self); + case "IdentifierNode" -> leaf("identifier", str(self, "value")); + case "QualifiedIdentifierNode" -> + branch( + "qualified_identifier", + interleave(buildAll(listMember(self, "identifiers")), () -> terminal("."))); + case "DocCommentNode" -> buildDocComment(self); + default -> + throw new VmExceptionBuilder() + .bug("Unexpected syntax node: " + self.getVmClass().getSimpleName()) + .build(); + }; + } - @Specialization - @TruffleBoundary - protected VmTyped eval(VmTyped self, VmFunction visit) { - var result = walkNode(self, visit); - // the root of the returned tree has no parent - if (result.hasExtraStorage()) { - ((NodeData) result.getExtraStorage()).parentVm = null; + private static VmTyped buildModule(VmTyped self) { + var children = new ArrayList<>(); + var declaration = optNode(self, "declaration"); + if (declaration != null) { + children.add(build(declaration)); + } + var imports = listMember(self, "imports"); + if (imports.getLength() > 0) { + children.add(branch("import_list", buildAll(imports))); + } + children.addAll(buildAll(listMember(self, "classes"))); + children.addAll(buildAll(listMember(self, "typeAliases"))); + children.addAll(buildAll(listMember(self, "properties"))); + children.addAll(buildAll(listMember(self, "methods"))); + return branch("module", children); + } + + private static VmTyped buildModuleDeclaration(VmTyped self) { + var children = docAndAnnotations(self); + var name = optNode(self, "name"); + var modifiers = listMember(self, "modifiers"); + if (name != null) { + var definition = new ArrayList<>(); + if (modifiers.getLength() > 0) { + definition.add(modifierListNode(modifiers)); } - return result; + definition.add(terminal("module")); + definition.add(build(name)); + children.add(branch("module_definition", definition)); + } else if (modifiers.getLength() > 0) { + children.add(modifierListNode(modifiers)); + } + var clause = optNode(self, "extendsOrAmendsClause"); + if (clause != null) { + children.add(build(clause)); } + return branch("module_declaration", children); + } - private VmTyped walkNode(VmTyped nodeVm, VmFunction visit) { - var visited = applyVisit.execute(visit, nodeVm); + private static VmTyped buildImport(VmTyped self) { + var children = new ArrayList<>(); + children.add(terminal(str(self, "keyword"))); + children.add(stringCharsNode(str(self, "uri"))); + var alias = optNode(self, "alias"); + if (alias != null) { + children.add(branch("import_alias", List.of(terminal("as"), build(alias)))); + } + return branch("import", children); + } - VmTyped node; - boolean descend; - if (visited instanceof VmPair pair) { - node = (VmTyped) pair.getFirst(); - descend = (Boolean) pair.getSecond(); - } else { - // `null`: leave this node unchanged and keep descending - node = nodeVm; - descend = true; - } - if (!descend) { - return node; - } + private static VmTyped buildClass(VmTyped self) { + var children = docAndAnnotations(self); + var header = new ArrayList<>(); + var modifiers = listMember(self, "modifiers"); + if (modifiers.getLength() > 0) { + header.add(modifierListNode(modifiers)); + } + header.add(terminal("class")); + header.add(build(reqNode(self, "identifier"))); + header.addAll(typeParameterListNodes(listMember(self, "typeParameters"))); + var superType = optNode(self, "superType"); + if (superType != null) { + header.add(branch("class_header_extends", List.of(terminal("extends"), build(superType)))); + } + children.add(branch("class_header", header)); + var body = optNode(self, "body"); + if (body != null) { + children.add(build(body)); + } + return branch("class", children); + } - var childrenVm = (VmList) VmUtils.readMember(node, Identifier.CHILDREN); - var length = childrenVm.getLength(); - if (length == 0) { - return node; - } + private static VmTyped buildTypeAlias(VmTyped self) { + var children = docAndAnnotations(self); + var header = new ArrayList<>(); + var modifiers = listMember(self, "modifiers"); + if (modifiers.getLength() > 0) { + header.add(modifierListNode(modifiers)); + } + header.add(terminal("typealias")); + header.add(build(reqNode(self, "identifier"))); + header.addAll(typeParameterListNodes(listMember(self, "typeParameters"))); + header.add(terminal("=")); + children.add(branch("typealias_header", header)); + children.add(branch("typealias_body", List.of(build(reqNode(self, "type"))))); + return branch("typealias", children); + } + + private static VmTyped buildClassBody(VmTyped self) { + var members = new ArrayList<>(); + members.addAll(buildAll(listMember(self, "properties"))); + members.addAll(buildAll(listMember(self, "methods"))); + var children = new ArrayList<>(); + children.add(terminal("{")); + if (!members.isEmpty()) { + children.add(branch("class_body_elements", members)); + } + children.add(terminal("}")); + return branch("class_body", children); + } + + private static VmTyped buildClassProperty(VmTyped self) { + var children = docAndAnnotations(self); + var headerBegin = new ArrayList<>(); + var modifiers = listMember(self, "modifiers"); + if (modifiers.getLength() > 0) { + headerBegin.add(modifierListNode(modifiers)); + } + headerBegin.add(build(reqNode(self, "identifier"))); + var header = new ArrayList<>(); + header.add(branch("class_property_header_begin", headerBegin)); + header.addAll(typeAnnotationNodes(optNode(self, "typeAnnotation"))); + children.add(branch("class_property_header", header)); + var value = optNode(self, "value"); + if (value != null) { + children.add(terminal("=")); + children.add(branch("class_property_body", List.of(build(value)))); + } else { + children.addAll(buildAll(listMember(self, "objectBodies"))); + } + return branch("class_property", children); + } + + private static VmTyped buildClassMethod(VmTyped self) { + var children = docAndAnnotations(self); + var header = new ArrayList<>(); + var modifiers = listMember(self, "modifiers"); + if (modifiers.getLength() > 0) { + header.add(modifierListNode(modifiers)); + } + header.add(terminal("function")); + header.add(build(reqNode(self, "identifier"))); + children.add(branch("class_method_header", header)); + children.addAll(typeParameterListNodes(listMember(self, "typeParameters"))); + children.add(parameterListNode(listMember(self, "parameters"))); + children.addAll(typeAnnotationNodes(optNode(self, "returnType"))); + var body = optNode(self, "body"); + if (body != null) { + children.add(terminal("=")); + children.add(branch("class_method_body", List.of(build(body)))); + } + return branch("class_method", children); + } + + private static VmTyped buildObjectBody(VmTyped self) { + var children = new ArrayList<>(); + children.add(terminal("{")); + var parameters = listMember(self, "parameters"); + if (parameters.getLength() > 0) { + var elements = interleave(buildAll(parameters), NodeNodes::comma); + elements.add(terminal("->")); + children.add(branch("object_parameter_list", elements)); + } + var members = new ArrayList<>(); + members.addAll(buildAll(listMember(self, "properties"))); + members.addAll(buildAll(listMember(self, "methods"))); + members.addAll(buildAll(listMember(self, "elements"))); + members.addAll(buildAll(listMember(self, "entries"))); + members.addAll(buildAll(listMember(self, "spreads"))); + members.addAll(buildAll(listMember(self, "memberPredicates"))); + members.addAll(buildAll(listMember(self, "forGenerators"))); + members.addAll(buildAll(listMember(self, "whenGenerators"))); + if (!members.isEmpty()) { + children.add(branch("object_member_list", members)); + } + children.add(terminal("}")); + return branch("object_body", children); + } + + private static VmTyped buildObjectProperty(VmTyped self) { + var headerBegin = new ArrayList<>(); + var modifiers = listMember(self, "modifiers"); + if (modifiers.getLength() > 0) { + headerBegin.add(modifierListNode(modifiers)); + } + headerBegin.add(build(reqNode(self, "identifier"))); + var header = new ArrayList<>(); + header.add(branch("object_property_header_begin", headerBegin)); + header.addAll(typeAnnotationNodes(optNode(self, "typeAnnotation"))); + var children = new ArrayList<>(); + children.add(branch("object_property_header", header)); + var value = optNode(self, "value"); + if (value != null) { + children.add(terminal("=")); + children.add(branch("object_property_body", List.of(build(value)))); + } else { + children.addAll(buildAll(listMember(self, "objectBodies"))); + } + return branch("object_property", children); + } + + private static VmTyped buildObjectMethod(VmTyped self) { + var header = new ArrayList<>(); + var modifiers = listMember(self, "modifiers"); + if (modifiers.getLength() > 0) { + header.add(modifierListNode(modifiers)); + } + header.add(terminal("function")); + header.add(build(reqNode(self, "identifier"))); + var children = new ArrayList<>(); + children.add(branch("class_method_header", header)); + children.addAll(typeParameterListNodes(listMember(self, "typeParameters"))); + children.add(parameterListNode(listMember(self, "parameters"))); + children.addAll(typeAnnotationNodes(optNode(self, "returnType"))); + children.add(terminal("=")); + children.add(branch("class_method_body", List.of(build(reqNode(self, "body"))))); + return branch("object_method", children); + } + + private static VmTyped buildObjectEntry(VmTyped self) { + var header = new ArrayList<>(); + header.add(terminal("[")); + header.add(build(reqNode(self, "key"))); + header.add(terminal("]")); + var value = optNode(self, "value"); + if (value != null) { + header.add(terminal("=")); + } + var children = new ArrayList<>(); + children.add(branch("object_entry_header", header)); + if (value != null) { + children.add(build(value)); + } else { + children.addAll(buildAll(listMember(self, "objectBodies"))); + } + return branch("object_entry", children); + } + + private static VmTyped buildMemberPredicate(VmTyped self) { + var children = new ArrayList<>(); + children.add(terminal("[[")); + children.add(build(reqNode(self, "condition"))); + children.add(terminal("]")); + children.add(terminal("]")); + var value = optNode(self, "value"); + if (value != null) { + children.add(terminal("=")); + children.add(build(value)); + } else { + children.addAll(buildAll(listMember(self, "objectBodies"))); + } + return branch("member_predicate", children); + } + + private static VmTyped buildForGenerator(VmTyped self) { + var definitionHeader = new ArrayList<>(); + var keyParameter = optNode(self, "keyParameter"); + if (keyParameter == null) { + definitionHeader.add(build(reqNode(self, "valueParameter"))); + } else { + definitionHeader.add(build(keyParameter)); + definitionHeader.add(terminal(",")); + definitionHeader.add(build(reqNode(self, "valueParameter"))); + } + definitionHeader.add(terminal("in")); + List definition = + List.of( + branch("for_generator_header_definition_header", definitionHeader), + build(reqNode(self, "iterable"))); + List header = + List.of( + terminal("("), branch("for_generator_header_definition", definition), terminal(")")); + return branch( + "for_generator", + List.of( + terminal("for"), branch("for_generator_header", header), build(reqNode(self, "body")))); + } + + private static VmTyped buildWhenGenerator(VmTyped self) { + var children = new ArrayList<>(); + children.add(terminal("when")); + children.add( + branch( + "when_generator_header", + List.of(terminal("("), build(reqNode(self, "condition")), terminal(")")))); + children.add(build(reqNode(self, "thenBody"))); + var elseBody = optNode(self, "elseBody"); + if (elseBody != null) { + children.add(terminal("else")); + children.add(build(elseBody)); + } + return branch("when_generator", children); + } + + private static VmTyped buildSingleLineString(VmTyped self) { + var children = new ArrayList<>(); + children.add(terminal("\"")); + children.addAll(buildStringParts(listMember(self, "parts"))); + children.add(terminal("\"")); + return branch("single_line_string_literal_expr", children); + } + + private static VmTyped buildMultiLineString(VmTyped self) { + var children = new ArrayList<>(); + children.add(terminal("\"\"\"")); + children.addAll(buildStringParts(listMember(self, "parts"))); + // The formatter uses the start column of the closing `"""` to determine the indentation to + // strip from each content line. + var closingSpan = + SyntaxNodes.spanFactory.create(new SpanData(new FullSpan(0, 0, 0, 1, 0, 0), null)); + children.add(makeNode("terminal", null, "\"\"\"", closingSpan)); + return branch("multi_line_string_literal_expr", children); + } + + private static List buildStringParts(VmList parts) { + var result = new ArrayList<>(); + for (var i = 0; i < parts.getLength(); i++) { + result.addAll(buildStringPart((VmTyped) parts.get(i))); + } + return result; + } + + // `StringPartNode` is not a `Node`, so it is handled here rather than through `build`. + private static List buildStringPart(VmTyped part) { + return switch (part.getVmClass().getSimpleName()) { + case "StringCharsNode" -> List.of(leaf("string_chars", str(part, "value"))); + case "StringEscapeNode" -> List.of(leaf("string_escape", str(part, "value"))); + case "StringNewlineNode" -> List.of(typeOnly("string_newline")); + case "StringInterpolationNode" -> + List.of(terminal("\\("), build(reqNode(part, "expression")), terminal(")")); + default -> + throw new VmExceptionBuilder() + .bug("Unexpected string-part node: " + part.getVmClass().getSimpleName()) + .build(); + }; + } + + private static VmTyped buildUnqualifiedAccess(VmTyped self) { + var children = new ArrayList<>(); + children.add(build(reqNode(self, "identifier"))); + var arguments = optList(self, "arguments"); + if (arguments != null) { + children.add(argumentListNode(arguments)); + } + return branch("unqualified_access_expr", children); + } + + private static VmTyped buildQualifiedAccess(VmTyped self) { + var member = new ArrayList<>(); + member.add(build(reqNode(self, "identifier"))); + var arguments = optList(self, "arguments"); + if (arguments != null) { + member.add(argumentListNode(arguments)); + } + return branch( + "qualified_access_expr", + List.of( + build(reqNode(self, "receiver")), + operatorLeaf(bool(self, "isNullSafe") ? "?." : "."), + branch("unqualified_access_expr", member))); + } + + private static VmTyped buildSuperAccess(VmTyped self) { + var children = new ArrayList<>(); + children.add(terminal("super")); + children.add(terminal(".")); + children.add(build(reqNode(self, "identifier"))); + var arguments = optList(self, "arguments"); + if (arguments != null) { + children.add(argumentListNode(arguments)); + } + return branch("super_access_expr", children); + } + + private static VmTyped buildIf(VmTyped self) { + return branch( + "if_expr", + List.of( + branch( + "if_header", + List.of( + terminal("if"), + branch( + "if_condition", + List.of( + terminal("("), + branch("if_condition_expr", List.of(build(reqNode(self, "condition")))), + terminal(")"))))), + branch("if_then_expr", List.of(build(reqNode(self, "thenExpr")))), + terminal("else"), + branch("if_else_expr", List.of(build(reqNode(self, "elseExpr")))))); + } + + private static VmTyped buildLet(VmTyped self) { + return branch( + "let_expr", + List.of( + terminal("let"), + branch( + "let_parameter_definition", + List.of( + terminal("("), + branch( + "let_parameter", + List.of( + build(reqNode(self, "parameter")), + terminal("="), + build(reqNode(self, "bindingValue")))), + terminal(")"))), + build(reqNode(self, "body")))); + } + + private static VmTyped buildNew(VmTyped self) { + var type = optNode(self, "type"); + var header = + type == null + ? List.of(terminal("new")) + : List.of(terminal("new"), build(type)); + return branch("new_expr", List.of(branch("new_header", header), build(reqNode(self, "body")))); + } + + private static VmTyped buildBinaryOp(VmTyped self, String operator) { + return branch( + "binary_op_expr", + List.of( + build(reqNode(self, "left")), operatorLeaf(operator), build(reqNode(self, "right")))); + } - var newChildren = new Object[length]; - var changed = false; - for (var i = 0; i < length; i++) { - var child = (VmTyped) childrenVm.get(i); - var newChild = walkNode(child, visit); - newChildren[i] = newChild; - changed |= newChild != child; + private static VmTyped buildTypeOp(VmTyped self, String operator) { + return branch( + "binary_op_expr", + List.of( + build(reqNode(self, "expression")), + operatorLeaf(operator), + build(reqNode(self, "type")))); + } + + private static VmTyped buildFunctionLiteral(VmTyped self) { + return branch( + "function_literal_expr", + List.of( + parameterListNode(listMember(self, "parameters")), + terminal("->"), + branch("function_literal_body", List.of(build(reqNode(self, "body")))))); + } + + private static VmTyped buildDeclaredType(VmTyped self) { + var name = build(reqNode(self, "name")); + var typeArguments = listMember(self, "typeArguments"); + if (typeArguments.getLength() == 0) { + return branch("declared_type", List.of(name)); + } + return branch( + "declared_type", + List.of( + name, + branch( + "type_argument_list", + List.of( + terminal("<"), + branch( + "type_argument_list_elements", + interleave(buildAll(typeArguments), NodeNodes::comma)), + terminal(">"))))); + } + + private static VmTyped buildFunctionType(VmTyped self) { + var parameterTypes = listMember(self, "parameterTypes"); + var parameters = + parameterTypes.getLength() == 0 + ? List.of(terminal("("), terminal(")")) + : List.of( + terminal("("), + branch( + "parenthesized_type_elements", + interleave(buildAll(parameterTypes), NodeNodes::comma)), + terminal(")")); + return branch( + "function_type", + List.of( + branch("function_type_parameters", parameters), + terminal("->"), + build(reqNode(self, "returnType")))); + } + + private static VmTyped buildConstrainedType(VmTyped self) { + return branch( + "constrained_type", + List.of( + build(reqNode(self, "baseType")), + branch( + "constrained_type_constraint", + List.of( + terminal("("), + branch( + "constrained_type_elements", + interleave(buildAll(listMember(self, "constraints")), NodeNodes::comma)), + terminal(")"))))); + } + + private static VmTyped buildAnnotation(VmTyped self) { + var children = new ArrayList<>(); + children.add(terminal("@")); + children.add(build(reqNode(self, "type"))); + var body = optNode(self, "body"); + if (body != null) { + children.add(build(body)); + } + return branch("annotation", children); + } + + private static VmTyped buildParameter(VmTyped self) { + var identifier = optNode(self, "identifier"); + if (identifier == null) { + return branch("parameter", List.of(terminal("_"))); + } + var typeAnnotation = optNode(self, "typeAnnotation"); + if (typeAnnotation == null) { + return branch("parameter", List.of(build(identifier))); + } + var children = new ArrayList<>(); + children.add(build(identifier)); + children.addAll(typeAnnotationNodes(typeAnnotation)); + return branch("parameter", children); + } + + private static VmTyped buildTypeParameter(VmTyped self) { + var variance = member(self, "variance"); + if (variance instanceof String v) { + return branch("type_parameter", List.of(terminal(v), build(reqNode(self, "identifier")))); + } + return branch("type_parameter", List.of(build(reqNode(self, "identifier")))); + } + + private static VmTyped buildDocComment(VmTyped self) { + var value = str(self, "value"); + var children = new ArrayList<>(); + for (var line : value.split("\n", -1)) { + children.add(leaf("doc_comment_line", "/// " + line)); + } + return branch("doc_comment", children); + } + + private static VmTyped buildCall(String type, String keyword, VmTyped inner) { + return branch(type, List.of(terminal(keyword), terminal("("), inner, terminal(")"))); + } + + // The doc comment (if any) followed by the annotations of a declaration. + private static List docAndAnnotations(VmTyped self) { + var result = new ArrayList<>(); + var docComment = optNode(self, "docComment"); + if (docComment != null) { + result.add(build(docComment)); + } + result.addAll(buildAll(listMember(self, "annotations"))); + return result; + } + + // Node construction helpers + + private static VmTyped modifierListNode(VmList modifiers) { + var children = new ArrayList<>(); + for (var i = 0; i < modifiers.getLength(); i++) { + children.add(leaf("modifier", (String) modifiers.get(i))); + } + return branch("modifier_list", children); + } + + // A quoted `string_chars` node for a string constant like `"foo"`. + private static VmTyped stringCharsNode(String value) { + return makeNode( + "string_chars", + List.of(terminal("\""), terminal(value), terminal("\"")), + "\"" + value + "\"", + null); + } + + private static VmTyped parameterListNode(VmList parameters) { + if (parameters.getLength() == 0) { + return branch("parameter_list", List.of(terminal("("), terminal(")"))); + } + return branch( + "parameter_list", + List.of( + terminal("("), + branch("parameter_list_elements", interleave(buildAll(parameters), NodeNodes::comma)), + terminal(")"))); + } + + private static VmTyped argumentListNode(VmList arguments) { + if (arguments.getLength() == 0) { + return branch("argument_list", List.of(terminal("("), terminal(")"))); + } + return branch( + "argument_list", + List.of( + terminal("("), + branch("argument_list_elements", interleave(buildAll(arguments), NodeNodes::comma)), + terminal(")"))); + } + + private static List typeParameterListNodes(VmList typeParameters) { + if (typeParameters.getLength() == 0) { + return List.of(); + } + return List.of( + branch( + "type_parameter_list", + List.of( + terminal("<"), + branch( + "type_parameter_list_elements", + interleave(buildAll(typeParameters), NodeNodes::comma)), + terminal(">")))); + } + + private static List typeAnnotationNodes(@Nullable VmTyped type) { + if (type == null) { + return List.of(); + } + return List.of(branch("type_annotation", List.of(terminal(":"), build(type)))); + } + + private static VmTyped comma() { + return terminal(","); + } + + // Interleave `items` with fresh separators. + private static ArrayList interleave( + List items, java.util.function.Supplier separator) { + var result = new ArrayList<>(items.isEmpty() ? 0 : items.size() * 2 - 1); + for (var item : items) { + if (!result.isEmpty()) { + result.add(separator.get()); } - // reuse the node (and its extra storage) untouched when nothing below changed - return changed ? SyntaxNodes.rebuild(node, newChildren) : node; + result.add(item); + } + return result; + } + + private static VmTyped terminal(String text) { + return leaf("terminal", text); + } + + private static VmTyped operatorLeaf(String text) { + return leaf("operator", text); + } + + private static VmTyped branch(String type, List children) { + return makeNode(type, children, null, null); + } + + private static VmTyped leaf(String type, String text) { + return makeNode(type, null, text, null); + } + + private static VmTyped typeOnly(String type) { + return makeNode(type, null, null, null); + } + + // Build a `GenericNode`, setting only the members that differ from the class + // defaults (`children` defaults to empty, `text` to null, `span`/`parent` to their defaults). + private static VmTyped makeNode( + String type, @Nullable List children, @Nullable String text, @Nullable VmTyped span) { + var builder = new VmObjectBuilder(4); + builder.addProperty(Identifier.TYPE, type); + if (children != null) { + builder.addProperty(Identifier.CHILDREN, VmList.create(children.toArray())); + } + if (text != null) { + builder.addProperty(Identifier.TEXT, text); } + if (span != null) { + builder.addProperty(Identifier.SPAN, span); + } + return builder.toTyped(SyntaxModule.getGenericNodeClass()); + } + + // =============== + // Member readers + // =============== + + private static List buildAll(VmList nodes) { + var result = new ArrayList<>(); + for (var i = 0; i < nodes.getLength(); i++) { + result.add(build((VmTyped) nodes.get(i))); + } + return result; + } + + private static Object member(VmTyped self, String name) { + return VmUtils.readMember(self, Identifier.get(name)); + } + + private static VmTyped reqNode(VmTyped self, String name) { + return (VmTyped) member(self, name); + } + + private static @Nullable VmTyped optNode(VmTyped self, String name) { + return member(self, name) instanceof VmTyped node ? node : null; + } + + private static VmList listMember(VmTyped self, String name) { + return (VmList) member(self, name); + } + + private static @Nullable VmList optList(VmTyped self, String name) { + return member(self, name) instanceof VmList list ? list : null; + } + + private static String str(VmTyped self, String name) { + return (String) member(self, name); + } + + private static boolean bool(VmTyped self, String name) { + return (Boolean) member(self, name); + } + + private static String numText(Object value) { + return value instanceof String s ? s : value.toString(); } } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 6c4422147..84109f691 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -32,7 +32,7 @@ import org.pkl.core.runtime.VmUtils; import org.pkl.core.stdlib.ExternalMethod1Node; import org.pkl.core.stdlib.VmObjectFactory; -import org.pkl.core.stdlib.syntax.SyntaxNodes.NodeData; +import org.pkl.core.stdlib.syntax.SyntaxNodes.GenericNodeData; import org.pkl.core.stdlib.syntax.SyntaxNodes.SpanData; import org.pkl.parser.GenericParser; import org.pkl.parser.GenericParserError; @@ -42,8 +42,8 @@ public class ParserNodes { private ParserNodes() {} - private static final VmObjectFactory nodeFactory = - new VmObjectFactory(SyntaxModule::getNodeClass) + private static final VmObjectFactory genericNodeFactory = + new VmObjectFactory(SyntaxModule::getGenericNodeClass) .addStringProperty("type", nd -> nd.node.type.name().toLowerCase(Locale.ROOT)) .addListProperty("children", nd -> nd.childrenVm) .addProperty("parent", nd -> VmNull.lift(nd.parentVm)) @@ -57,35 +57,35 @@ private ParserNodes() {} private static final VmObjectFactory identifierNodeFactory = new VmObjectFactory(SyntaxModule::getIdentifierNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addStringProperty("value", ParserNodes::identifierValue); - private static VmObjectFactory nodeOnlyFactory(Supplier classSupplier) { - return new VmObjectFactory(classSupplier).addProperty("node", vm -> vm); + private static VmObjectFactory genericNodeOnlyFactory(Supplier classSupplier) { + return new VmObjectFactory(classSupplier).addProperty("genericNode", vm -> vm); } private static final VmObjectFactory qualifiedIdentifierNodeFactory = new VmObjectFactory(SyntaxModule::getQualifiedIdentifierNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addListProperty("identifiers", ParserNodes::qualifiedIdentifierIdentifiers) .addStringProperty("value", ParserNodes::qualifiedIdentifierValue); private static final VmObjectFactory docCommentNodeFactory = new VmObjectFactory(SyntaxModule::getDocCommentNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addStringProperty("value", ParserNodes::docCommentValue); private static final VmObjectFactory annotationNodeFactory = new VmObjectFactory(SyntaxModule::getAnnotationNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("type", ParserNodes::annotationType) .addProperty("body", ParserNodes::annotationBody); private static final VmObjectFactory typeParameterNodeFactory = new VmObjectFactory(SyntaxModule::getTypeParameterNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addProperty("variance", ParserNodes::typeParameterVariance) .addTypedProperty("identifier", ParserNodes::identifierNodeOf); private static final VmObjectFactory objectBodyNodeFactory = new VmObjectFactory(SyntaxModule::getObjectBodyNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addListProperty("parameters", ParserNodes::objectBodyParameters) .addListProperty("properties", ParserNodes::objectBodyProperties) .addListProperty("methods", ParserNodes::objectBodyMethods) @@ -97,18 +97,18 @@ private static VmObjectFactory nodeOnlyFactory(Supplier classS .addListProperty("whenGenerators", ParserNodes::objectBodyWhenGenerators); private static final VmObjectFactory parameterNodeFactory = new VmObjectFactory(SyntaxModule::getParameterNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addBooleanProperty("isBlankIdentifier", ParserNodes::parameterIsBlankIdentifier) .addProperty("identifier", ParserNodes::parameterIdentifier) .addProperty("typeAnnotation", ParserNodes::parameterTypeAnnotation); private static final VmObjectFactory objectElementNodeFactory = new VmObjectFactory(SyntaxModule::getObjectElementNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("expression", ParserNodes::soleExpr); private static final VmObjectFactory objectPropertyNodeFactory = new VmObjectFactory(SyntaxModule::getObjectPropertyNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addListProperty("modifiers", ParserNodes::objectPropertyModifiers) .addTypedProperty("identifier", ParserNodes::objectPropertyIdentifier) .addProperty("typeAnnotation", ParserNodes::objectPropertyTypeAnnotation) @@ -116,7 +116,7 @@ private static VmObjectFactory nodeOnlyFactory(Supplier classS .addListProperty("objectBodies", ParserNodes::objectPropertyObjectBodies); private static final VmObjectFactory objectMethodNodeFactory = new VmObjectFactory(SyntaxModule::getObjectMethodNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addListProperty("modifiers", ParserNodes::classMethodModifiers) .addTypedProperty("identifier", ParserNodes::classMethodIdentifier) .addListProperty("typeParameters", ParserNodes::classMethodTypeParameters) @@ -125,178 +125,178 @@ private static VmObjectFactory nodeOnlyFactory(Supplier classS .addTypedProperty("body", ParserNodes::objectMethodBody); private static final VmObjectFactory memberPredicateNodeFactory = new VmObjectFactory(SyntaxModule::getMemberPredicateNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("condition", ParserNodes::memberPredicateCondition) .addProperty("value", ParserNodes::memberPredicateValue) .addListProperty("objectBodies", ParserNodes::memberPredicateObjectBodies); private static final VmObjectFactory objectEntryNodeFactory = new VmObjectFactory(SyntaxModule::getObjectEntryNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("key", ParserNodes::objectEntryKey) .addProperty("value", ParserNodes::objectEntryValue) .addListProperty("objectBodies", ParserNodes::objectEntryObjectBodies); private static final VmObjectFactory objectSpreadNodeFactory = new VmObjectFactory(SyntaxModule::getObjectSpreadNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addStringProperty("keyword", ParserNodes::objectSpreadKeyword) .addTypedProperty("expression", ParserNodes::soleExpr); private static final VmObjectFactory whenGeneratorNodeFactory = new VmObjectFactory(SyntaxModule::getWhenGeneratorNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("condition", ParserNodes::whenCondition) .addTypedProperty("thenBody", ParserNodes::whenThenBody) .addProperty("elseBody", ParserNodes::whenElseBody); private static final VmObjectFactory forGeneratorNodeFactory = new VmObjectFactory(SyntaxModule::getForGeneratorNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addProperty("keyParameter", ParserNodes::forKeyParameter) .addTypedProperty("valueParameter", ParserNodes::forValueParameter) .addTypedProperty("iterable", ParserNodes::forIterable) .addTypedProperty("body", ParserNodes::forBody); private static final VmObjectFactory unknownTypeNodeFactory = - nodeOnlyFactory(SyntaxModule::getUnknownTypeNodeClass); + genericNodeOnlyFactory(SyntaxModule::getUnknownTypeNodeClass); private static final VmObjectFactory nothingTypeNodeFactory = - nodeOnlyFactory(SyntaxModule::getNothingTypeNodeClass); + genericNodeOnlyFactory(SyntaxModule::getNothingTypeNodeClass); private static final VmObjectFactory moduleTypeNodeFactory = - nodeOnlyFactory(SyntaxModule::getModuleTypeNodeClass); + genericNodeOnlyFactory(SyntaxModule::getModuleTypeNodeClass); private static final VmObjectFactory declaredTypeNodeFactory = new VmObjectFactory(SyntaxModule::getDeclaredTypeNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("name", ParserNodes::declaredTypeName) .addListProperty("typeArguments", ParserNodes::declaredTypeArguments); private static final VmObjectFactory nullableTypeNodeFactory = new VmObjectFactory(SyntaxModule::getNullableTypeNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("baseType", ParserNodes::nullableTypeBaseType); private static final VmObjectFactory unionTypeNodeFactory = new VmObjectFactory(SyntaxModule::getUnionTypeNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addListProperty("members", ParserNodes::unionTypeMembers); private static final VmObjectFactory functionTypeNodeFactory = new VmObjectFactory(SyntaxModule::getFunctionTypeNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addListProperty("parameterTypes", ParserNodes::functionTypeParameterTypes) .addTypedProperty("returnType", ParserNodes::functionTypeReturnType); private static final VmObjectFactory constrainedTypeNodeFactory = new VmObjectFactory(SyntaxModule::getConstrainedTypeNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("baseType", ParserNodes::constrainedTypeBaseType) .addListProperty("constraints", ParserNodes::constrainedTypeConstraints); private static final VmObjectFactory parenthesizedTypeNodeFactory = new VmObjectFactory(SyntaxModule::getParenthesizedTypeNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("type", ParserNodes::parenthesizedTypeType); private static final VmObjectFactory stringConstantTypeNodeFactory = new VmObjectFactory(SyntaxModule::getStringConstantTypeNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addStringProperty("value", ParserNodes::stringConstantTypeValue); private static final VmObjectFactory thisExprNodeFactory = - nodeOnlyFactory(SyntaxModule::getThisExprNodeClass); + genericNodeOnlyFactory(SyntaxModule::getThisExprNodeClass); private static final VmObjectFactory outerExprNodeFactory = - nodeOnlyFactory(SyntaxModule::getOuterExprNodeClass); + genericNodeOnlyFactory(SyntaxModule::getOuterExprNodeClass); private static final VmObjectFactory moduleExprNodeFactory = - nodeOnlyFactory(SyntaxModule::getModuleExprNodeClass); + genericNodeOnlyFactory(SyntaxModule::getModuleExprNodeClass); private static final VmObjectFactory nullLiteralExprNodeFactory = - nodeOnlyFactory(SyntaxModule::getNullLiteralExprNodeClass); + genericNodeOnlyFactory(SyntaxModule::getNullLiteralExprNodeClass); private static final VmObjectFactory booleanLiteralExprNodeFactory = new VmObjectFactory(SyntaxModule::getBooleanLiteralExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addBooleanProperty("value", ParserNodes::booleanLiteralValue); private static final VmObjectFactory intLiteralExprNodeFactory = new VmObjectFactory(SyntaxModule::getIntLiteralExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addProperty("value", ParserNodes::literalText); private static final VmObjectFactory floatLiteralExprNodeFactory = new VmObjectFactory(SyntaxModule::getFloatLiteralExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addProperty("value", ParserNodes::literalText); private static final VmObjectFactory singleLineStringLiteralExprNodeFactory = new VmObjectFactory(SyntaxModule::getSingleLineStringLiteralExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addListProperty("parts", ParserNodes::buildStringParts); private static final VmObjectFactory multiLineStringLiteralExprNodeFactory = new VmObjectFactory(SyntaxModule::getMultiLineStringLiteralExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addListProperty("parts", ParserNodes::buildStringParts); private static final VmObjectFactory unqualifiedAccessExprNodeFactory = new VmObjectFactory(SyntaxModule::getUnqualifiedAccessExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("identifier", ParserNodes::identifierNodeOf) .addProperty("arguments", ParserNodes::unqualifiedAccessArguments); private static final VmObjectFactory qualifiedAccessExprNodeFactory = new VmObjectFactory(SyntaxModule::getQualifiedAccessExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("receiver", ParserNodes::qualifiedAccessReceiver) .addBooleanProperty("isNullSafe", ParserNodes::qualifiedAccessIsNullSafe) .addTypedProperty("identifier", ParserNodes::qualifiedAccessIdentifier) .addProperty("arguments", ParserNodes::qualifiedAccessArguments); private static final VmObjectFactory subscriptExprNodeFactory = new VmObjectFactory(SyntaxModule::getSubscriptExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("receiver", ParserNodes::subscriptReceiver) .addTypedProperty("index", ParserNodes::subscriptIndex); private static final VmObjectFactory superAccessExprNodeFactory = new VmObjectFactory(SyntaxModule::getSuperAccessExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("identifier", ParserNodes::identifierNodeOf) .addProperty("arguments", ParserNodes::argumentsOrNull); private static final VmObjectFactory superSubscriptExprNodeFactory = new VmObjectFactory(SyntaxModule::getSuperSubscriptExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("index", ParserNodes::soleExpr); private static final VmObjectFactory ifExprNodeFactory = new VmObjectFactory(SyntaxModule::getIfExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("condition", ParserNodes::ifCondition) .addTypedProperty("thenExpr", ParserNodes::ifThenExpr) .addTypedProperty("elseExpr", ParserNodes::ifElseExpr); private static final VmObjectFactory letExprNodeFactory = new VmObjectFactory(SyntaxModule::getLetExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("parameter", ParserNodes::letParameter) .addTypedProperty("bindingValue", ParserNodes::letBindingValue) .addTypedProperty("body", ParserNodes::letBody); private static final VmObjectFactory throwExprNodeFactory = new VmObjectFactory(SyntaxModule::getThrowExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("expression", ParserNodes::soleExpr); private static final VmObjectFactory traceExprNodeFactory = new VmObjectFactory(SyntaxModule::getTraceExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("expression", ParserNodes::soleExpr); private static final VmObjectFactory importExprNodeFactory = new VmObjectFactory(SyntaxModule::getImportExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addStringProperty("keyword", ParserNodes::importKeyword) .addStringProperty("uri", ParserNodes::importUri); private static final VmObjectFactory readExprNodeFactory = new VmObjectFactory(SyntaxModule::getReadExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addStringProperty("keyword", ParserNodes::readKeyword) .addTypedProperty("expression", ParserNodes::soleExpr); private static final VmObjectFactory newExprNodeFactory = new VmObjectFactory(SyntaxModule::getNewExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addProperty("type", ParserNodes::newExprType) .addTypedProperty("body", ParserNodes::newExprBody); private static final VmObjectFactory amendsExprNodeFactory = new VmObjectFactory(SyntaxModule::getAmendsExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("parentExpr", ParserNodes::amendsParentExpr) .addTypedProperty("body", ParserNodes::amendsBody); private static VmObjectFactory binaryOpExprNodeFactory(Supplier classSupplier) { return new VmObjectFactory(classSupplier) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("left", ParserNodes::binaryOpLeft) .addTypedProperty("right", ParserNodes::binaryOpRight); } private static VmObjectFactory typeOpExprNodeFactory(Supplier classSupplier) { return new VmObjectFactory(classSupplier) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("expression", ParserNodes::binaryOpLeft) .addTypedProperty("type", ParserNodes::typeOpType); } @@ -341,54 +341,54 @@ private static VmObjectFactory typeOpExprNodeFactory(Supplier typeOpExprNodeFactory(SyntaxModule::getTypeCastExprNodeClass); private static final VmObjectFactory unaryMinusExprNodeFactory = new VmObjectFactory(SyntaxModule::getUnaryMinusExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("operand", ParserNodes::soleExpr); private static final VmObjectFactory logicalNotExprNodeFactory = new VmObjectFactory(SyntaxModule::getLogicalNotExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("operand", ParserNodes::soleExpr); private static final VmObjectFactory nonNullExprNodeFactory = new VmObjectFactory(SyntaxModule::getNonNullExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("operand", ParserNodes::soleExpr); private static final VmObjectFactory functionLiteralExprNodeFactory = new VmObjectFactory(SyntaxModule::getFunctionLiteralExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addListProperty("parameters", ParserNodes::functionLiteralParameters) .addTypedProperty("body", ParserNodes::functionLiteralBody); private static final VmObjectFactory parenthesizedExprNodeFactory = new VmObjectFactory(SyntaxModule::getParenthesizedExprNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("expression", ParserNodes::parenthesizedExpression); - // String-part factories, produced by `buildStringParts`. `StringPartNode` is not a `SyntaxNode`, - // but it also carries a hidden `node`, so the same `node`-property shape applies. + // String-part factories, produced by `buildStringParts`. `StringPartNode` is not a `Node`, + // but it also carries a hidden `genericNode`, so the same property shape applies. private static final VmObjectFactory stringCharsNodeFactory = new VmObjectFactory(SyntaxModule::getStringCharsNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addStringProperty("value", ParserNodes::literalText); private static final VmObjectFactory stringEscapeNodeFactory = new VmObjectFactory(SyntaxModule::getStringEscapeNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addStringProperty("value", ParserNodes::literalText); private static final VmObjectFactory stringNewlineNodeFactory = new VmObjectFactory(SyntaxModule::getStringNewlineNodeClass) - .addProperty("node", vm -> vm); + .addProperty("genericNode", vm -> vm); private static final VmObjectFactory stringInterpolationNodeFactory = new VmObjectFactory(SyntaxModule::getStringInterpolationNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addTypedProperty("expression", ParserNodes::wrapExpr); private static final VmObjectFactory importNodeFactory = new VmObjectFactory(SyntaxModule::getImportNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addStringProperty("keyword", ParserNodes::importKeyword) .addStringProperty("uri", ParserNodes::importUri) .addProperty("alias", ParserNodes::importAlias); private static final VmObjectFactory moduleDeclarationNodeFactory = new VmObjectFactory(SyntaxModule::getModuleDeclarationNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addProperty("docComment", ParserNodes::docCommentOf) .addListProperty("annotations", ParserNodes::annotationsOf) .addListProperty("modifiers", ParserNodes::moduleDeclModifiers) @@ -397,13 +397,13 @@ private static VmObjectFactory typeOpExprNodeFactory(Supplier private static final VmObjectFactory extendsOrAmendsClauseNodeFactory = new VmObjectFactory(SyntaxModule::getExtendsOrAmendsClauseNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addStringProperty("keyword", ParserNodes::extendsOrAmendsClauseKeyword) .addStringProperty("uri", ParserNodes::stringCharsOf); private static final VmObjectFactory classNodeFactory = new VmObjectFactory(SyntaxModule::getClassNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addProperty("docComment", ParserNodes::docCommentOf) .addListProperty("annotations", ParserNodes::annotationsOf) .addListProperty("modifiers", ParserNodes::classModifiers) @@ -414,7 +414,7 @@ private static VmObjectFactory typeOpExprNodeFactory(Supplier private static final VmObjectFactory typeAliasNodeFactory = new VmObjectFactory(SyntaxModule::getTypeAliasNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addProperty("docComment", ParserNodes::docCommentOf) .addListProperty("annotations", ParserNodes::annotationsOf) .addListProperty("modifiers", ParserNodes::typeAliasModifiers) @@ -424,7 +424,7 @@ private static VmObjectFactory typeOpExprNodeFactory(Supplier private static final VmObjectFactory classPropertyNodeFactory = new VmObjectFactory(SyntaxModule::getClassPropertyNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addProperty("docComment", ParserNodes::docCommentOf) .addListProperty("annotations", ParserNodes::annotationsOf) .addListProperty("modifiers", ParserNodes::classPropertyModifiers) @@ -435,7 +435,7 @@ private static VmObjectFactory typeOpExprNodeFactory(Supplier private static final VmObjectFactory classMethodNodeFactory = new VmObjectFactory(SyntaxModule::getClassMethodNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addProperty("docComment", ParserNodes::docCommentOf) .addListProperty("annotations", ParserNodes::annotationsOf) .addListProperty("modifiers", ParserNodes::classMethodModifiers) @@ -447,13 +447,13 @@ private static VmObjectFactory typeOpExprNodeFactory(Supplier private static final VmObjectFactory classBodyNodeFactory = new VmObjectFactory(SyntaxModule::getClassBodyNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addListProperty("properties", ParserNodes::classBodyProperties) .addListProperty("methods", ParserNodes::classBodyMethods); private static final VmObjectFactory moduleNodeFactory = new VmObjectFactory(SyntaxModule::getModuleNodeClass) - .addProperty("node", vm -> vm) + .addProperty("genericNode", vm -> vm) .addProperty("declaration", ParserNodes::moduleDeclaration) .addListProperty("imports", ParserNodes::moduleImports) .addListProperty("classes", ParserNodes::moduleClasses) @@ -495,7 +495,7 @@ private static String docCommentValue(VmTyped docCommentVm) { if (i > 0) { builder.append('\n'); } - var data = (NodeData) lineVms.get(i).getExtraStorage(); + var data = (GenericNodeData) lineVms.get(i).getExtraStorage(); var text = data.node.text(data.source); if (text.startsWith("/// ")) { builder.append(text, 4, text.length()); @@ -584,7 +584,7 @@ private static VmTyped parenthesizedTypeType(VmTyped typeVm) { } private static String stringConstantTypeValue(VmTyped typeVm) { - var data = (NodeData) typeVm.getExtraStorage(); + var data = (GenericNodeData) typeVm.getExtraStorage(); return extractStringChars(data.node, data.source); } @@ -597,12 +597,12 @@ private static VmTyped requireTypeChild(VmTyped genericVm) { } private static String literalText(VmTyped exprVm) { - var text = nodeText((NodeData) exprVm.getExtraStorage()); + var text = nodeText((GenericNodeData) exprVm.getExtraStorage()); return text == null ? "" : text; } private static boolean booleanLiteralValue(VmTyped exprVm) { - return "true".equals(nodeText((NodeData) exprVm.getExtraStorage())); + return "true".equals(nodeText((GenericNodeData) exprVm.getExtraStorage())); } private static Object unqualifiedAccessArguments(VmTyped exprVm) { @@ -618,7 +618,7 @@ private static boolean qualifiedAccessIsNullSafe(VmTyped exprVm) { if (op == null) { return false; } - var data = (NodeData) op.getExtraStorage(); + var data = (GenericNodeData) op.getExtraStorage(); return "?.".equals(data.node.text(data.source)); } @@ -703,7 +703,7 @@ private static String binaryOpOperator(VmTyped exprVm) { if (op == null) { return ""; } - var data = (NodeData) op.getExtraStorage(); + var data = (GenericNodeData) op.getExtraStorage(); return data.node.text(data.source); } @@ -789,7 +789,7 @@ private static VmTyped requireExprChild(VmTyped genericVm) { } private static @Nullable VmTyped lastChildVm(VmTyped genericVm, NodeType type) { - var data = (NodeData) genericVm.getExtraStorage(); + var data = (GenericNodeData) genericVm.getExtraStorage(); var children = data.node.children; VmTyped result = null; for (var i = 0; i < children.size(); i++) { @@ -965,14 +965,14 @@ private static String qualifiedIdentifierValue(VmTyped qualifiedVm) { if (i > 0) { builder.append('.'); } - var text = nodeText((NodeData) idVms.get(i).getExtraStorage()); + var text = nodeText((GenericNodeData) idVms.get(i).getExtraStorage()); builder.append(text == null ? "" : text); } return builder.toString(); } private static Object typeParameterVariance(VmTyped typeParameterVm) { - var data = (NodeData) typeParameterVm.getExtraStorage(); + var data = (GenericNodeData) typeParameterVm.getExtraStorage(); for (var child : data.node.children) { if (child.type == NodeType.TERMINAL) { var text = child.text(data.source); @@ -998,7 +998,7 @@ private static Object parameterTypeAnnotation(VmTyped parameterVm) { } private static VmList buildStringParts(VmTyped stringVm) { - var data = (NodeData) stringVm.getExtraStorage(); + var data = (GenericNodeData) stringVm.getExtraStorage(); var children = data.node.children; var childrenVm = data.childrenVm; var parts = new ArrayList<>(); @@ -1085,7 +1085,7 @@ private static Object moduleDeclExtendsOrAmendsClause(VmTyped declVm) { } private static String extendsOrAmendsClauseKeyword(VmTyped clauseVm) { - var data = (NodeData) clauseVm.getExtraStorage(); + var data = (GenericNodeData) clauseVm.getExtraStorage(); return data.node.type == NodeType.AMENDS_CLAUSE ? "amends" : "extends"; } @@ -1240,7 +1240,7 @@ private static String importKeyword(VmTyped importVm) { } private static String importUri(VmTyped importVm) { - var data = (NodeData) importVm.getExtraStorage(); + var data = (GenericNodeData) importVm.getExtraStorage(); return extractStringChars(data.node, data.source); } @@ -1254,11 +1254,11 @@ private static Object importAlias(VmTyped importVm) { } private static String identifierValue(VmTyped identifierVm) { - var text = nodeText((NodeData) identifierVm.getExtraStorage()); + var text = nodeText((GenericNodeData) identifierVm.getExtraStorage()); return text == null ? "" : text; } - private static @Nullable String nodeText(NodeData data) { + private static @Nullable String nodeText(GenericNodeData data) { return data.node.children.isEmpty() || data.node.type == NodeType.STRING_CHARS ? data.node.text(data.source) : null; @@ -1266,7 +1266,7 @@ private static String identifierValue(VmTyped identifierVm) { // The text of the node's first terminal child, or `fallback` if it has none private static String firstTerminalText(VmTyped nodeVm, String fallback) { - var data = (NodeData) nodeVm.getExtraStorage(); + var data = (GenericNodeData) nodeVm.getExtraStorage(); for (var child : data.node.children) { if (child.type == NodeType.TERMINAL) { return child.text(data.source); @@ -1303,20 +1303,20 @@ private static VmList modifiersOf(VmTyped ownerVm) { var modifierVms = findChildrenVm(modifierList, NodeType.MODIFIER); var result = new Object[modifierVms.size()]; for (var i = 0; i < modifierVms.size(); i++) { - var data = (NodeData) modifierVms.get(i).getExtraStorage(); + var data = (GenericNodeData) modifierVms.get(i).getExtraStorage(); result[i] = data.node.text(data.source); } return VmList.create(result); } private static String stringCharsOf(VmTyped clauseVm) { - var data = (NodeData) clauseVm.getExtraStorage(); + var data = (GenericNodeData) clauseVm.getExtraStorage(); return extractStringChars(data.node, data.source); } // The first child of `genericVm` with the given type, as a generic-node `VmTyped`, or null. private static @Nullable VmTyped findChildVm(VmTyped genericVm, NodeType type) { - var data = (NodeData) genericVm.getExtraStorage(); + var data = (GenericNodeData) genericVm.getExtraStorage(); var children = data.node.children; for (var i = 0; i < children.size(); i++) { if (children.get(i).type == type) { @@ -1328,7 +1328,7 @@ private static String stringCharsOf(VmTyped clauseVm) { // The first type-node child of `genericVm`, as a generic-node `VmTyped`, or null. private static @Nullable VmTyped findTypeChildVm(VmTyped genericVm) { - var data = (NodeData) genericVm.getExtraStorage(); + var data = (GenericNodeData) genericVm.getExtraStorage(); var children = data.node.children; for (var i = 0; i < children.size(); i++) { if (children.get(i).type.isType()) { @@ -1340,7 +1340,7 @@ private static String stringCharsOf(VmTyped clauseVm) { // The first expression-node child of `genericVm`, as a generic-node `VmTyped`, or null. private static @Nullable VmTyped findExprChildVm(VmTyped genericVm) { - var data = (NodeData) genericVm.getExtraStorage(); + var data = (GenericNodeData) genericVm.getExtraStorage(); var children = data.node.children; for (var i = 0; i < children.size(); i++) { if (children.get(i).type.isExpression()) { @@ -1352,7 +1352,7 @@ private static String stringCharsOf(VmTyped clauseVm) { // All type-node children of `genericVm`, as generic-node `VmTyped`s. private static List findTypeChildrenVm(VmTyped genericVm) { - var data = (NodeData) genericVm.getExtraStorage(); + var data = (GenericNodeData) genericVm.getExtraStorage(); var children = data.node.children; var result = new ArrayList(); for (var i = 0; i < children.size(); i++) { @@ -1365,7 +1365,7 @@ private static List findTypeChildrenVm(VmTyped genericVm) { // All expression-node children of `genericVm`, as generic-node `VmTyped`s. private static List findExprChildrenVm(VmTyped genericVm) { - var data = (NodeData) genericVm.getExtraStorage(); + var data = (GenericNodeData) genericVm.getExtraStorage(); var children = data.node.children; var result = new ArrayList(); for (var i = 0; i < children.size(); i++) { @@ -1467,7 +1467,7 @@ private static VmList typeParametersOf(@Nullable VmTyped ownerVm) { // Wrap a generic type node into its specific `TypeNode` subclass private static VmTyped wrapType(VmTyped typeVm) { - var data = (NodeData) typeVm.getExtraStorage(); + var data = (GenericNodeData) typeVm.getExtraStorage(); return switch (data.node.type) { case UNKNOWN_TYPE -> unknownTypeNodeFactory.create(typeVm); case NOTHING_TYPE -> nothingTypeNodeFactory.create(typeVm); @@ -1486,7 +1486,7 @@ private static VmTyped wrapType(VmTyped typeVm) { // Wrap a generic expression node into its specific `ExprNode` subclass private static VmTyped wrapExpr(VmTyped exprVm) { - var data = (NodeData) exprVm.getExtraStorage(); + var data = (GenericNodeData) exprVm.getExtraStorage(); return switch (data.node.type) { case THIS_EXPR -> thisExprNodeFactory.create(exprVm); case OUTER_EXPR -> outerExprNodeFactory.create(exprVm); @@ -1525,7 +1525,7 @@ private static VmTyped wrapExpr(VmTyped exprVm) { // All children of `genericVm` with the given type, as generic-node `VmTyped`s. private static List findChildrenVm(VmTyped genericVm, NodeType type) { - var data = (NodeData) genericVm.getExtraStorage(); + var data = (GenericNodeData) genericVm.getExtraStorage(); var children = data.node.children; var result = new ArrayList(); for (var i = 0; i < children.size(); i++) { @@ -1612,13 +1612,13 @@ private static VmTyped convertNode( var childrenVm = VmList.create(childrenList.toArray()); var spanVm = SyntaxNodes.spanFactory.create(new SpanData(genericNode.span, sourceUri)); - var data = new NodeData(genericNode, sourceChars, childrenVm, spanVm); + var data = new GenericNodeData(genericNode, sourceChars, childrenVm, spanVm); - var result = nodeFactory.create(data); + var result = genericNodeFactory.create(data); // set parent back-reference on each child for (var childVm : childrenList) { - var childData = (NodeData) childVm.getExtraStorage(); + var childData = (GenericNodeData) childVm.getExtraStorage(); childData.parentVm = result; } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java deleted file mode 100644 index ff955ef99..000000000 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodeNodes.java +++ /dev/null @@ -1,909 +0,0 @@ -/* - * Copyright © 2026 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.stdlib.syntax; - -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.dsl.Specialization; -import java.util.ArrayList; -import java.util.List; -import org.jspecify.annotations.Nullable; -import org.pkl.core.runtime.Identifier; -import org.pkl.core.runtime.SyntaxModule; -import org.pkl.core.runtime.VmExceptionBuilder; -import org.pkl.core.runtime.VmList; -import org.pkl.core.runtime.VmObjectBuilder; -import org.pkl.core.runtime.VmTyped; -import org.pkl.core.runtime.VmUtils; -import org.pkl.core.stdlib.ExternalPropertyNode; -import org.pkl.core.stdlib.PklName; -import org.pkl.core.stdlib.syntax.SyntaxNodes.SpanData; -import org.pkl.parser.syntax.generic.FullSpan; - -/** - * Backs {@code pkl.syntax#SyntaxNode.builtNode}. - * - *

Reconstructs a generic {@code pkl.syntax#Node} tree from a typed syntax node's own fields, - * ignoring the parse-time {@code node} it may be backed by. - * - *

The nodes produced here are storage-less. - */ -public final class SyntaxNodeNodes { - private SyntaxNodeNodes() {} - - @PklName("builtNode") - public abstract static class builtNode extends ExternalPropertyNode { - @Specialization - @TruffleBoundary - protected Object eval(VmTyped self) { - return build(self); - } - } - - private static VmTyped build(VmTyped self) { - return switch (self.getVmClass().getSimpleName()) { - case "ModuleNode" -> buildModule(self); - case "ModuleDeclarationNode" -> buildModuleDeclaration(self); - case "ExtendsOrAmendsClauseNode" -> { - var keyword = str(self, "keyword"); - yield branch( - keyword.equals("amends") ? "amends_clause" : "extends_clause", - List.of(terminal(keyword), stringCharsNode(str(self, "uri")))); - } - case "ImportNode" -> buildImport(self); - case "ClassNode" -> buildClass(self); - case "TypeAliasNode" -> buildTypeAlias(self); - case "ClassBodyNode" -> buildClassBody(self); - case "ClassPropertyNode" -> buildClassProperty(self); - case "ClassMethodNode" -> buildClassMethod(self); - case "ObjectBodyNode" -> buildObjectBody(self); - case "ObjectPropertyNode" -> buildObjectProperty(self); - case "ObjectMethodNode" -> buildObjectMethod(self); - case "ObjectElementNode" -> - branch("object_element", List.of(build(reqNode(self, "expression")))); - case "ObjectEntryNode" -> buildObjectEntry(self); - case "ObjectSpreadNode" -> - branch( - "object_spread", - List.of(terminal(str(self, "keyword")), build(reqNode(self, "expression")))); - case "MemberPredicateNode" -> buildMemberPredicate(self); - case "ForGeneratorNode" -> buildForGenerator(self); - case "WhenGeneratorNode" -> buildWhenGenerator(self); - case "ThisExprNode" -> leaf("this_expr", "this"); - case "OuterExprNode" -> leaf("outer_expr", "outer"); - case "ModuleExprNode" -> leaf("module_expr", "module"); - case "NullLiteralExprNode" -> leaf("null_expr", "null"); - case "BooleanLiteralExprNode" -> - leaf("bool_literal_expr", Boolean.toString(bool(self, "value"))); - case "IntLiteralExprNode" -> leaf("int_literal_expr", numText(member(self, "value"))); - case "FloatLiteralExprNode" -> leaf("float_literal_expr", numText(member(self, "value"))); - case "SingleLineStringLiteralExprNode" -> buildSingleLineString(self); - case "MultiLineStringLiteralExprNode" -> buildMultiLineString(self); - case "UnqualifiedAccessExprNode" -> buildUnqualifiedAccess(self); - case "QualifiedAccessExprNode" -> buildQualifiedAccess(self); - case "SubscriptExprNode" -> - branch( - "subscript_expr", - List.of( - build(reqNode(self, "receiver")), - operatorLeaf("["), - build(reqNode(self, "index")), - terminal("]"))); - case "SuperAccessExprNode" -> buildSuperAccess(self); - case "SuperSubscriptExprNode" -> - branch( - "super_subscript_expr", - List.of( - terminal("super"), terminal("["), build(reqNode(self, "index")), terminal("]"))); - case "IfExprNode" -> buildIf(self); - case "LetExprNode" -> buildLet(self); - case "ThrowExprNode" -> buildCall("throw_expr", "throw", build(reqNode(self, "expression"))); - case "TraceExprNode" -> buildCall("trace_expr", "trace", build(reqNode(self, "expression"))); - case "ImportExprNode" -> - buildCall("import_expr", str(self, "keyword"), stringCharsNode(str(self, "uri"))); - case "ReadExprNode" -> - buildCall("read_expr", str(self, "keyword"), build(reqNode(self, "expression"))); - case "NewExprNode" -> buildNew(self); - case "AmendsExprNode" -> - branch( - "amends_expr", - List.of(build(reqNode(self, "parentExpr")), build(reqNode(self, "body")))); - case "ExponentiationExprNode" -> buildBinaryOp(self, "**"); - case "MultiplicationExprNode" -> buildBinaryOp(self, "*"); - case "DivisionExprNode" -> buildBinaryOp(self, "/"); - case "IntegerDivisionExprNode" -> buildBinaryOp(self, "~/"); - case "RemainderExprNode" -> buildBinaryOp(self, "%"); - case "AdditionExprNode" -> buildBinaryOp(self, "+"); - case "SubtractionExprNode" -> buildBinaryOp(self, "-"); - case "LessThanExprNode" -> buildBinaryOp(self, "<"); - case "LessThanOrEqualExprNode" -> buildBinaryOp(self, "<="); - case "GreaterThanExprNode" -> buildBinaryOp(self, ">"); - case "GreaterThanOrEqualExprNode" -> buildBinaryOp(self, ">="); - case "EqualExprNode" -> buildBinaryOp(self, "=="); - case "NotEqualExprNode" -> buildBinaryOp(self, "!="); - case "LogicalAndExprNode" -> buildBinaryOp(self, "&&"); - case "LogicalOrExprNode" -> buildBinaryOp(self, "||"); - case "PipeExprNode" -> buildBinaryOp(self, "|>"); - case "NullCoalescingExprNode" -> buildBinaryOp(self, "??"); - case "TypeCheckExprNode" -> buildTypeOp(self, "is"); - case "TypeCastExprNode" -> buildTypeOp(self, "as"); - case "UnaryMinusExprNode" -> - branch("unary_minus_expr", List.of(terminal("-"), build(reqNode(self, "operand")))); - case "LogicalNotExprNode" -> - branch("logical_not_expr", List.of(terminal("!"), build(reqNode(self, "operand")))); - case "NonNullExprNode" -> - branch("non_null_expr", List.of(build(reqNode(self, "operand")), operatorLeaf("!!"))); - case "FunctionLiteralExprNode" -> buildFunctionLiteral(self); - case "ParenthesizedExprNode" -> - branch( - "parenthesized_expr", - List.of( - terminal("("), - branch( - "parenthesized_expr_elements", List.of(build(reqNode(self, "expression")))), - terminal(")"))); - case "UnknownTypeNode" -> leaf("unknown_type", "unknown"); - case "NothingTypeNode" -> leaf("nothing_type", "nothing"); - case "ModuleTypeNode" -> leaf("module_type", "module"); - case "DeclaredTypeNode" -> buildDeclaredType(self); - case "NullableTypeNode" -> - branch("nullable_type", List.of(build(reqNode(self, "baseType")), terminal("?"))); - case "UnionTypeNode" -> - branch( - "union_type", interleave(buildAll(listMember(self, "members")), () -> terminal("|"))); - case "FunctionTypeNode" -> buildFunctionType(self); - case "ConstrainedTypeNode" -> buildConstrainedType(self); - case "ParenthesizedTypeNode" -> - branch( - "parenthesized_type", - List.of( - terminal("("), - branch("parenthesized_type_elements", List.of(build(reqNode(self, "type")))), - terminal(")"))); - case "StringConstantTypeNode" -> - branch("string_constant_type", List.of(stringCharsNode(str(self, "value")))); - case "AnnotationNode" -> buildAnnotation(self); - case "ParameterNode" -> buildParameter(self); - case "TypeParameterNode" -> buildTypeParameter(self); - case "IdentifierNode" -> leaf("identifier", str(self, "value")); - case "QualifiedIdentifierNode" -> - branch( - "qualified_identifier", - interleave(buildAll(listMember(self, "identifiers")), () -> terminal("."))); - case "DocCommentNode" -> buildDocComment(self); - default -> - throw new VmExceptionBuilder() - .bug("Unexpected syntax node: " + self.getVmClass().getSimpleName()) - .build(); - }; - } - - private static VmTyped buildModule(VmTyped self) { - var children = new ArrayList<>(); - var declaration = optNode(self, "declaration"); - if (declaration != null) { - children.add(build(declaration)); - } - var imports = listMember(self, "imports"); - if (imports.getLength() > 0) { - children.add(branch("import_list", buildAll(imports))); - } - children.addAll(buildAll(listMember(self, "classes"))); - children.addAll(buildAll(listMember(self, "typeAliases"))); - children.addAll(buildAll(listMember(self, "properties"))); - children.addAll(buildAll(listMember(self, "methods"))); - return branch("module", children); - } - - private static VmTyped buildModuleDeclaration(VmTyped self) { - var children = docAndAnnotations(self); - var name = optNode(self, "name"); - var modifiers = listMember(self, "modifiers"); - if (name != null) { - var definition = new ArrayList<>(); - if (modifiers.getLength() > 0) { - definition.add(modifierListNode(modifiers)); - } - definition.add(terminal("module")); - definition.add(build(name)); - children.add(branch("module_definition", definition)); - } else if (modifiers.getLength() > 0) { - children.add(modifierListNode(modifiers)); - } - var clause = optNode(self, "extendsOrAmendsClause"); - if (clause != null) { - children.add(build(clause)); - } - return branch("module_declaration", children); - } - - private static VmTyped buildImport(VmTyped self) { - var children = new ArrayList<>(); - children.add(terminal(str(self, "keyword"))); - children.add(stringCharsNode(str(self, "uri"))); - var alias = optNode(self, "alias"); - if (alias != null) { - children.add(branch("import_alias", List.of(terminal("as"), build(alias)))); - } - return branch("import", children); - } - - private static VmTyped buildClass(VmTyped self) { - var children = docAndAnnotations(self); - var header = new ArrayList<>(); - var modifiers = listMember(self, "modifiers"); - if (modifiers.getLength() > 0) { - header.add(modifierListNode(modifiers)); - } - header.add(terminal("class")); - header.add(build(reqNode(self, "identifier"))); - header.addAll(typeParameterListNodes(listMember(self, "typeParameters"))); - var superType = optNode(self, "superType"); - if (superType != null) { - header.add(branch("class_header_extends", List.of(terminal("extends"), build(superType)))); - } - children.add(branch("class_header", header)); - var body = optNode(self, "body"); - if (body != null) { - children.add(build(body)); - } - return branch("class", children); - } - - private static VmTyped buildTypeAlias(VmTyped self) { - var children = docAndAnnotations(self); - var header = new ArrayList<>(); - var modifiers = listMember(self, "modifiers"); - if (modifiers.getLength() > 0) { - header.add(modifierListNode(modifiers)); - } - header.add(terminal("typealias")); - header.add(build(reqNode(self, "identifier"))); - header.addAll(typeParameterListNodes(listMember(self, "typeParameters"))); - header.add(terminal("=")); - children.add(branch("typealias_header", header)); - children.add(branch("typealias_body", List.of(build(reqNode(self, "type"))))); - return branch("typealias", children); - } - - private static VmTyped buildClassBody(VmTyped self) { - var members = new ArrayList<>(); - members.addAll(buildAll(listMember(self, "properties"))); - members.addAll(buildAll(listMember(self, "methods"))); - var children = new ArrayList<>(); - children.add(terminal("{")); - if (!members.isEmpty()) { - children.add(branch("class_body_elements", members)); - } - children.add(terminal("}")); - return branch("class_body", children); - } - - private static VmTyped buildClassProperty(VmTyped self) { - var children = docAndAnnotations(self); - var headerBegin = new ArrayList<>(); - var modifiers = listMember(self, "modifiers"); - if (modifiers.getLength() > 0) { - headerBegin.add(modifierListNode(modifiers)); - } - headerBegin.add(build(reqNode(self, "identifier"))); - var header = new ArrayList<>(); - header.add(branch("class_property_header_begin", headerBegin)); - header.addAll(typeAnnotationNodes(optNode(self, "typeAnnotation"))); - children.add(branch("class_property_header", header)); - var value = optNode(self, "value"); - if (value != null) { - children.add(terminal("=")); - children.add(branch("class_property_body", List.of(build(value)))); - } else { - children.addAll(buildAll(listMember(self, "objectBodies"))); - } - return branch("class_property", children); - } - - private static VmTyped buildClassMethod(VmTyped self) { - var children = docAndAnnotations(self); - var header = new ArrayList<>(); - var modifiers = listMember(self, "modifiers"); - if (modifiers.getLength() > 0) { - header.add(modifierListNode(modifiers)); - } - header.add(terminal("function")); - header.add(build(reqNode(self, "identifier"))); - children.add(branch("class_method_header", header)); - children.addAll(typeParameterListNodes(listMember(self, "typeParameters"))); - children.add(parameterListNode(listMember(self, "parameters"))); - children.addAll(typeAnnotationNodes(optNode(self, "returnType"))); - var body = optNode(self, "body"); - if (body != null) { - children.add(terminal("=")); - children.add(branch("class_method_body", List.of(build(body)))); - } - return branch("class_method", children); - } - - private static VmTyped buildObjectBody(VmTyped self) { - var children = new ArrayList<>(); - children.add(terminal("{")); - var parameters = listMember(self, "parameters"); - if (parameters.getLength() > 0) { - var elements = interleave(buildAll(parameters), SyntaxNodeNodes::comma); - elements.add(terminal("->")); - children.add(branch("object_parameter_list", elements)); - } - var members = new ArrayList<>(); - members.addAll(buildAll(listMember(self, "properties"))); - members.addAll(buildAll(listMember(self, "methods"))); - members.addAll(buildAll(listMember(self, "elements"))); - members.addAll(buildAll(listMember(self, "entries"))); - members.addAll(buildAll(listMember(self, "spreads"))); - members.addAll(buildAll(listMember(self, "memberPredicates"))); - members.addAll(buildAll(listMember(self, "forGenerators"))); - members.addAll(buildAll(listMember(self, "whenGenerators"))); - if (!members.isEmpty()) { - children.add(branch("object_member_list", members)); - } - children.add(terminal("}")); - return branch("object_body", children); - } - - private static VmTyped buildObjectProperty(VmTyped self) { - var headerBegin = new ArrayList<>(); - var modifiers = listMember(self, "modifiers"); - if (modifiers.getLength() > 0) { - headerBegin.add(modifierListNode(modifiers)); - } - headerBegin.add(build(reqNode(self, "identifier"))); - var header = new ArrayList<>(); - header.add(branch("object_property_header_begin", headerBegin)); - header.addAll(typeAnnotationNodes(optNode(self, "typeAnnotation"))); - var children = new ArrayList<>(); - children.add(branch("object_property_header", header)); - var value = optNode(self, "value"); - if (value != null) { - children.add(terminal("=")); - children.add(branch("object_property_body", List.of(build(value)))); - } else { - children.addAll(buildAll(listMember(self, "objectBodies"))); - } - return branch("object_property", children); - } - - private static VmTyped buildObjectMethod(VmTyped self) { - var header = new ArrayList<>(); - var modifiers = listMember(self, "modifiers"); - if (modifiers.getLength() > 0) { - header.add(modifierListNode(modifiers)); - } - header.add(terminal("function")); - header.add(build(reqNode(self, "identifier"))); - var children = new ArrayList<>(); - children.add(branch("class_method_header", header)); - children.addAll(typeParameterListNodes(listMember(self, "typeParameters"))); - children.add(parameterListNode(listMember(self, "parameters"))); - children.addAll(typeAnnotationNodes(optNode(self, "returnType"))); - children.add(terminal("=")); - children.add(branch("class_method_body", List.of(build(reqNode(self, "body"))))); - return branch("object_method", children); - } - - private static VmTyped buildObjectEntry(VmTyped self) { - var header = new ArrayList<>(); - header.add(terminal("[")); - header.add(build(reqNode(self, "key"))); - header.add(terminal("]")); - var value = optNode(self, "value"); - if (value != null) { - header.add(terminal("=")); - } - var children = new ArrayList<>(); - children.add(branch("object_entry_header", header)); - if (value != null) { - children.add(build(value)); - } else { - children.addAll(buildAll(listMember(self, "objectBodies"))); - } - return branch("object_entry", children); - } - - private static VmTyped buildMemberPredicate(VmTyped self) { - var children = new ArrayList<>(); - children.add(terminal("[[")); - children.add(build(reqNode(self, "condition"))); - children.add(terminal("]")); - children.add(terminal("]")); - var value = optNode(self, "value"); - if (value != null) { - children.add(terminal("=")); - children.add(build(value)); - } else { - children.addAll(buildAll(listMember(self, "objectBodies"))); - } - return branch("member_predicate", children); - } - - private static VmTyped buildForGenerator(VmTyped self) { - var definitionHeader = new ArrayList<>(); - var keyParameter = optNode(self, "keyParameter"); - if (keyParameter == null) { - definitionHeader.add(build(reqNode(self, "valueParameter"))); - } else { - definitionHeader.add(build(keyParameter)); - definitionHeader.add(terminal(",")); - definitionHeader.add(build(reqNode(self, "valueParameter"))); - } - definitionHeader.add(terminal("in")); - List definition = - List.of( - branch("for_generator_header_definition_header", definitionHeader), - build(reqNode(self, "iterable"))); - List header = - List.of( - terminal("("), branch("for_generator_header_definition", definition), terminal(")")); - return branch( - "for_generator", - List.of( - terminal("for"), branch("for_generator_header", header), build(reqNode(self, "body")))); - } - - private static VmTyped buildWhenGenerator(VmTyped self) { - var children = new ArrayList<>(); - children.add(terminal("when")); - children.add( - branch( - "when_generator_header", - List.of(terminal("("), build(reqNode(self, "condition")), terminal(")")))); - children.add(build(reqNode(self, "thenBody"))); - var elseBody = optNode(self, "elseBody"); - if (elseBody != null) { - children.add(terminal("else")); - children.add(build(elseBody)); - } - return branch("when_generator", children); - } - - private static VmTyped buildSingleLineString(VmTyped self) { - var children = new ArrayList<>(); - children.add(terminal("\"")); - children.addAll(buildStringParts(listMember(self, "parts"))); - children.add(terminal("\"")); - return branch("single_line_string_literal_expr", children); - } - - private static VmTyped buildMultiLineString(VmTyped self) { - var children = new ArrayList<>(); - children.add(terminal("\"\"\"")); - children.addAll(buildStringParts(listMember(self, "parts"))); - // The formatter uses the start column of the closing `"""` to determine the indentation to - // strip from each content line. - var closingSpan = - SyntaxNodes.spanFactory.create(new SpanData(new FullSpan(0, 0, 0, 1, 0, 0), null)); - children.add(makeNode("terminal", null, "\"\"\"", closingSpan)); - return branch("multi_line_string_literal_expr", children); - } - - private static List buildStringParts(VmList parts) { - var result = new ArrayList<>(); - for (var i = 0; i < parts.getLength(); i++) { - result.addAll(buildStringPart((VmTyped) parts.get(i))); - } - return result; - } - - // Mirrors `StringPartNode.toNodes` for each part kind. `StringPartNode` is not a `SyntaxNode`, so - // it is handled here rather than through `build`. - private static List buildStringPart(VmTyped part) { - return switch (part.getVmClass().getSimpleName()) { - case "StringCharsNode" -> List.of(leaf("string_chars", str(part, "value"))); - case "StringEscapeNode" -> List.of(leaf("string_escape", str(part, "value"))); - case "StringNewlineNode" -> List.of(typeOnly("string_newline")); - case "StringInterpolationNode" -> - List.of(terminal("\\("), build(reqNode(part, "expression")), terminal(")")); - default -> - throw new VmExceptionBuilder() - .bug("Unexpected string-part node: " + part.getVmClass().getSimpleName()) - .build(); - }; - } - - private static VmTyped buildUnqualifiedAccess(VmTyped self) { - var children = new ArrayList<>(); - children.add(build(reqNode(self, "identifier"))); - var arguments = optList(self, "arguments"); - if (arguments != null) { - children.add(argumentListNode(arguments)); - } - return branch("unqualified_access_expr", children); - } - - private static VmTyped buildQualifiedAccess(VmTyped self) { - var member = new ArrayList<>(); - member.add(build(reqNode(self, "identifier"))); - var arguments = optList(self, "arguments"); - if (arguments != null) { - member.add(argumentListNode(arguments)); - } - return branch( - "qualified_access_expr", - List.of( - build(reqNode(self, "receiver")), - operatorLeaf(bool(self, "isNullSafe") ? "?." : "."), - branch("unqualified_access_expr", member))); - } - - private static VmTyped buildSuperAccess(VmTyped self) { - var children = new ArrayList<>(); - children.add(terminal("super")); - children.add(terminal(".")); - children.add(build(reqNode(self, "identifier"))); - var arguments = optList(self, "arguments"); - if (arguments != null) { - children.add(argumentListNode(arguments)); - } - return branch("super_access_expr", children); - } - - private static VmTyped buildIf(VmTyped self) { - return branch( - "if_expr", - List.of( - branch( - "if_header", - List.of( - terminal("if"), - branch( - "if_condition", - List.of( - terminal("("), - branch("if_condition_expr", List.of(build(reqNode(self, "condition")))), - terminal(")"))))), - branch("if_then_expr", List.of(build(reqNode(self, "thenExpr")))), - terminal("else"), - branch("if_else_expr", List.of(build(reqNode(self, "elseExpr")))))); - } - - private static VmTyped buildLet(VmTyped self) { - return branch( - "let_expr", - List.of( - terminal("let"), - branch( - "let_parameter_definition", - List.of( - terminal("("), - branch( - "let_parameter", - List.of( - build(reqNode(self, "parameter")), - terminal("="), - build(reqNode(self, "bindingValue")))), - terminal(")"))), - build(reqNode(self, "body")))); - } - - private static VmTyped buildNew(VmTyped self) { - var type = optNode(self, "type"); - var header = - type == null - ? List.of(terminal("new")) - : List.of(terminal("new"), build(type)); - return branch("new_expr", List.of(branch("new_header", header), build(reqNode(self, "body")))); - } - - private static VmTyped buildBinaryOp(VmTyped self, String operator) { - return branch( - "binary_op_expr", - List.of( - build(reqNode(self, "left")), operatorLeaf(operator), build(reqNode(self, "right")))); - } - - private static VmTyped buildTypeOp(VmTyped self, String operator) { - return branch( - "binary_op_expr", - List.of( - build(reqNode(self, "expression")), - operatorLeaf(operator), - build(reqNode(self, "type")))); - } - - private static VmTyped buildFunctionLiteral(VmTyped self) { - return branch( - "function_literal_expr", - List.of( - parameterListNode(listMember(self, "parameters")), - terminal("->"), - branch("function_literal_body", List.of(build(reqNode(self, "body")))))); - } - - private static VmTyped buildDeclaredType(VmTyped self) { - var name = build(reqNode(self, "name")); - var typeArguments = listMember(self, "typeArguments"); - if (typeArguments.getLength() == 0) { - return branch("declared_type", List.of(name)); - } - return branch( - "declared_type", - List.of( - name, - branch( - "type_argument_list", - List.of( - terminal("<"), - branch( - "type_argument_list_elements", - interleave(buildAll(typeArguments), SyntaxNodeNodes::comma)), - terminal(">"))))); - } - - private static VmTyped buildFunctionType(VmTyped self) { - var parameterTypes = listMember(self, "parameterTypes"); - var parameters = - parameterTypes.getLength() == 0 - ? List.of(terminal("("), terminal(")")) - : List.of( - terminal("("), - branch( - "parenthesized_type_elements", - interleave(buildAll(parameterTypes), SyntaxNodeNodes::comma)), - terminal(")")); - return branch( - "function_type", - List.of( - branch("function_type_parameters", parameters), - terminal("->"), - build(reqNode(self, "returnType")))); - } - - private static VmTyped buildConstrainedType(VmTyped self) { - return branch( - "constrained_type", - List.of( - build(reqNode(self, "baseType")), - branch( - "constrained_type_constraint", - List.of( - terminal("("), - branch( - "constrained_type_elements", - interleave( - buildAll(listMember(self, "constraints")), SyntaxNodeNodes::comma)), - terminal(")"))))); - } - - private static VmTyped buildAnnotation(VmTyped self) { - var children = new ArrayList<>(); - children.add(terminal("@")); - children.add(build(reqNode(self, "type"))); - var body = optNode(self, "body"); - if (body != null) { - children.add(build(body)); - } - return branch("annotation", children); - } - - private static VmTyped buildParameter(VmTyped self) { - var identifier = optNode(self, "identifier"); - if (identifier == null) { - return branch("parameter", List.of(terminal("_"))); - } - var typeAnnotation = optNode(self, "typeAnnotation"); - if (typeAnnotation == null) { - return branch("parameter", List.of(build(identifier))); - } - var children = new ArrayList<>(); - children.add(build(identifier)); - children.addAll(typeAnnotationNodes(typeAnnotation)); - return branch("parameter", children); - } - - private static VmTyped buildTypeParameter(VmTyped self) { - var variance = member(self, "variance"); - if (variance instanceof String v) { - return branch("type_parameter", List.of(terminal(v), build(reqNode(self, "identifier")))); - } - return branch("type_parameter", List.of(build(reqNode(self, "identifier")))); - } - - private static VmTyped buildDocComment(VmTyped self) { - var value = str(self, "value"); - var children = new ArrayList<>(); - for (var line : value.split("\n", -1)) { - children.add(leaf("doc_comment_line", "/// " + line)); - } - return branch("doc_comment", children); - } - - private static VmTyped buildCall(String type, String keyword, VmTyped inner) { - return branch(type, List.of(terminal(keyword), terminal("("), inner, terminal(")"))); - } - - // The doc comment (if any) followed by the annotations of a declaration. - private static List docAndAnnotations(VmTyped self) { - var result = new ArrayList<>(); - var docComment = optNode(self, "docComment"); - if (docComment != null) { - result.add(build(docComment)); - } - result.addAll(buildAll(listMember(self, "annotations"))); - return result; - } - - // Node construction helpers - - private static VmTyped modifierListNode(VmList modifiers) { - var children = new ArrayList<>(); - for (var i = 0; i < modifiers.getLength(); i++) { - children.add(leaf("modifier", (String) modifiers.get(i))); - } - return branch("modifier_list", children); - } - - // A quoted `string_chars` node for a string constant like `"foo"`. - private static VmTyped stringCharsNode(String value) { - return makeNode( - "string_chars", - List.of(terminal("\""), terminal(value), terminal("\"")), - "\"" + value + "\"", - null); - } - - private static VmTyped parameterListNode(VmList parameters) { - if (parameters.getLength() == 0) { - return branch("parameter_list", List.of(terminal("("), terminal(")"))); - } - return branch( - "parameter_list", - List.of( - terminal("("), - branch( - "parameter_list_elements", - interleave(buildAll(parameters), SyntaxNodeNodes::comma)), - terminal(")"))); - } - - private static VmTyped argumentListNode(VmList arguments) { - if (arguments.getLength() == 0) { - return branch("argument_list", List.of(terminal("("), terminal(")"))); - } - return branch( - "argument_list", - List.of( - terminal("("), - branch( - "argument_list_elements", interleave(buildAll(arguments), SyntaxNodeNodes::comma)), - terminal(")"))); - } - - private static List typeParameterListNodes(VmList typeParameters) { - if (typeParameters.getLength() == 0) { - return List.of(); - } - return List.of( - branch( - "type_parameter_list", - List.of( - terminal("<"), - branch( - "type_parameter_list_elements", - interleave(buildAll(typeParameters), SyntaxNodeNodes::comma)), - terminal(">")))); - } - - private static List typeAnnotationNodes(@Nullable VmTyped type) { - if (type == null) { - return List.of(); - } - return List.of(branch("type_annotation", List.of(terminal(":"), build(type)))); - } - - private static VmTyped comma() { - return terminal(","); - } - - // Interleave `items` with fresh separators. - private static ArrayList interleave( - List items, java.util.function.Supplier separator) { - var result = new ArrayList<>(items.isEmpty() ? 0 : items.size() * 2 - 1); - for (var item : items) { - if (!result.isEmpty()) { - result.add(separator.get()); - } - result.add(item); - } - return result; - } - - private static VmTyped terminal(String text) { - return leaf("terminal", text); - } - - private static VmTyped operatorLeaf(String text) { - return leaf("operator", text); - } - - private static VmTyped branch(String type, List children) { - return makeNode(type, children, null, null); - } - - private static VmTyped leaf(String type, String text) { - return makeNode(type, null, text, null); - } - - private static VmTyped typeOnly(String type) { - return makeNode(type, null, null, null); - } - - // Build a generic `Node`, setting only the members that differ from the class - // defaults (`children` defaults to empty, `text` to null, `span`/`parent` to their defaults). - private static VmTyped makeNode( - String type, @Nullable List children, @Nullable String text, @Nullable VmTyped span) { - var builder = new VmObjectBuilder(4); - builder.addProperty(Identifier.TYPE, type); - if (children != null) { - builder.addProperty(Identifier.CHILDREN, VmList.create(children.toArray())); - } - if (text != null) { - builder.addProperty(Identifier.TEXT, text); - } - if (span != null) { - builder.addProperty(Identifier.SPAN, span); - } - return builder.toTyped(SyntaxModule.getNodeClass()); - } - - // =============== - // Member readers - // =============== - - private static List buildAll(VmList nodes) { - var result = new ArrayList<>(); - for (var i = 0; i < nodes.getLength(); i++) { - result.add(build((VmTyped) nodes.get(i))); - } - return result; - } - - private static Object member(VmTyped self, String name) { - return VmUtils.readMember(self, Identifier.get(name)); - } - - private static VmTyped reqNode(VmTyped self, String name) { - return (VmTyped) member(self, name); - } - - private static @Nullable VmTyped optNode(VmTyped self, String name) { - return member(self, name) instanceof VmTyped node ? node : null; - } - - private static VmList listMember(VmTyped self, String name) { - return (VmList) member(self, name); - } - - private static @Nullable VmList optList(VmTyped self, String name) { - return member(self, name) instanceof VmList list ? list : null; - } - - private static String str(VmTyped self, String name) { - return (String) member(self, name); - } - - private static boolean bool(VmTyped self, String name) { - return (Boolean) member(self, name); - } - - private static String numText(Object value) { - return value instanceof String s ? s : value.toString(); - } -} diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java index cb6f0d85a..d139a31f9 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java @@ -78,15 +78,15 @@ private static String displayUri(@Nullable String sourceUri, String position) { return sourceUri == null ? position : sourceUri + "#" + position; } - /** Extra storage backing a Pkl {@code Node} instance. */ - static final class NodeData { + /** Extra storage backing a Pkl {@code GenericNode} instance. */ + static final class GenericNodeData { final Node node; final char[] source; @Nullable VmTyped parentVm; VmList childrenVm; @Nullable VmTyped spanVm; - NodeData(Node node, char[] source, VmList childrenVm, @Nullable VmTyped spanVm) { + GenericNodeData(Node node, char[] source, VmList childrenVm, @Nullable VmTyped spanVm) { this.node = node; this.source = source; this.childrenVm = childrenVm; @@ -94,8 +94,8 @@ static final class NodeData { } } - private static final VmObjectFactory nodeFactory = - new VmObjectFactory(SyntaxModule::getNodeClass) + private static final VmObjectFactory genericNodeFactory = + new VmObjectFactory(SyntaxModule::getGenericNodeClass) .addStringProperty("type", nd -> nd.node.type.name().toLowerCase(Locale.ROOT)) .addListProperty("children", nd -> nd.childrenVm) .addProperty("parent", nd -> VmNull.lift(nd.parentVm)) @@ -126,20 +126,22 @@ static VmTyped rebuild(VmTyped template, Object[] newChildrenVm) { makeJavaNode(nodeType, span, childJavaNodes, VmUtils.readMember(template, Identifier.TEXT)); var childrenVm = VmList.create(newChildrenVm); - var result = nodeFactory.create(new NodeData(javaNode, EMPTY_SOURCE, childrenVm, spanVm)); + var result = + genericNodeFactory.create(new GenericNodeData(javaNode, EMPTY_SOURCE, childrenVm, spanVm)); // wire up the parent back-reference for (var child : newChildrenVm) { var childVm = (VmTyped) child; if (childVm.hasExtraStorage()) { - ((NodeData) childVm.getExtraStorage()).parentVm = result; + ((GenericNodeData) childVm.getExtraStorage()).parentVm = result; } } return result; } /** - * Convert a Pkl node to a generic {@link Node}, reusing the parse-time node when present. + * Convert a Pkl {@code GenericNode} to a generic {@link Node}, reusing the parse-time node when + * present. * *

{@code fallbackSpan} is used for constructed nodes (and their descendants) that carry no * meaningful span of their own, so that a subtree spliced into reused siblings lines up with @@ -148,7 +150,7 @@ static VmTyped rebuild(VmTyped template, Object[] newChildrenVm) { static Node convertVmToNode(VmTyped nodeVm, FullSpan fallbackSpan) { // a node still carrying its parse-time storage is verbatim from `parse`: reuse it wholesale if (nodeVm.hasExtraStorage()) { - return ((NodeData) nodeVm.getExtraStorage()).node; + return ((GenericNodeData) nodeVm.getExtraStorage()).node; } var typeStr = (String) VmUtils.readMember(nodeVm, Identifier.TYPE); diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl index d7f032957..e6339b3f6 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl @@ -4,14 +4,14 @@ import "pkl:syntax" local function roundTrip(source: String) = parseNode(source)!!.render() -local function parseNode(source: String) = new syntax.Parser {}.parseModule(source).node +local function parseNode(source: String) = new syntax.Parser {}.parseModule(source).genericNode local function replaceLeaf( - node: syntax.Node, + node: syntax.GenericNode, targetType: syntax.NodeType, oldText: String, newText: String, -): syntax.Node = +): syntax.GenericNode = if (node.type == targetType && node.text == oldText) (node) { text = newText } else @@ -20,10 +20,10 @@ local function replaceLeaf( } local function transformFirst( - node: syntax.Node, + node: syntax.GenericNode, targetType: syntax.NodeType, - transform: (syntax.Node) -> syntax.Node, -): syntax.Node = + transform: (syntax.GenericNode) -> syntax.GenericNode, +): syntax.GenericNode = if (node.type == targetType) transform.apply(node) else @@ -371,7 +371,7 @@ facts { ["add new modifier"] { local root = parseNode("local x = 1") - local constModifier = new syntax.Node { + local constModifier = new syntax.GenericNode { type = "modifier" text = "const" } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/spans.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/spans.pkl index d4f261669..15b7850a1 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/spans.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/spans.pkl @@ -14,14 +14,14 @@ local stringModule = parser.parseModule(""" local resourceModule = parser.parseModule(read(".../input-helper/syntax/spans/sample.pkl")) -local function nestedProperty(mod: syntax.ModuleNode): syntax.Node = - mod.node!! - .fold(List(), (acc: List, n) -> if (n.type == "object_property") acc.add(n) else acc) +local function nestedProperty(mod: syntax.ModuleNode): syntax.GenericNode = + mod.genericNode!! + .fold(List(), (acc: List, n) -> if (n.type == "object_property") acc.add(n) else acc) .last examples { ["span of a module parsed from a string"] { - stringModule.node.span + stringModule.genericNode.span } ["span of a leaf parsed from a string"] { @@ -29,7 +29,7 @@ examples { } ["span of a module parsed from a resource carries the resource URI"] { - resourceModule.node.span + resourceModule.genericNode.span } ["span of a leaf parsed from a resource carries the resource URI"] { @@ -39,8 +39,8 @@ examples { facts { ["span positions are 1-based"] { - stringModule.node.span!!.start.line == 1 - stringModule.node.span!!.start.column == 1 + stringModule.genericNode.span!!.start.line == 1 + stringModule.genericNode.span!!.start.column == 1 } ["end is exclusive"] { @@ -64,7 +64,7 @@ facts { } ["nodes constructed from scratch have no span"] { - new syntax.IntLiteralExprNode { value = 42 }?.node?.span == null + new syntax.IntLiteralExprNode { value = 42 }?.genericNode?.span == null new syntax.IntLiteralExprNode { value = 42 }.builtNode.span == null } @@ -73,8 +73,8 @@ facts { } ["parsing the same text from a string and a resource differs only in displayUri"] { - local fromString = stringModule.node.span!! - local fromResource = resourceModule.node.span!! + local fromString = stringModule.genericNode.span!! + local fromResource = resourceModule.genericNode.span!! fromString.start.line == fromResource.start.line fromString.start.column == fromResource.start.column fromString.end.line == fromResource.end.line diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl index d1dde89be..3d6f04564 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/traversal.pkl @@ -4,7 +4,7 @@ import "pkl:syntax" local function mod(source: String): syntax.ModuleNode = new syntax.Parser {}.parseModule(source) -local sample: syntax.Node = +local sample: syntax.GenericNode = mod( """ import "foo.pkl" @@ -19,7 +19,7 @@ local sample: syntax.Node = scaled = origin.x * 2 """, - ).node!! + ).genericNode!! facts { ["fold counts nodes by predicate"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl index e3c24ec2d..1ef391866 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl @@ -4,12 +4,12 @@ import "pkl:syntax" local function mod(source: String): syntax.ModuleNode = new syntax.Parser {}.parseModule(source) -local function fmt(source: String): String = mod(source).node!!.render() +local function fmt(source: String): String = mod(source).genericNode!!.render() local function walkFormat( source: String, - visit: (syntax.Node) -> Pair?, -): String = mod(source).node!!.walk(visit).render() + visit: (syntax.GenericNode) -> Pair?, +): String = mod(source).genericNode!!.walk(visit).render() facts { ["read-only walk leaves the tree unchanged"] { diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 30d08edf5..8c42290ce 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -29,7 +29,7 @@ class Parser { external function parseModuleOrNull(source: String | Resource): ModuleNode? } -/// Renders a syntax [Node] back to Pkl source code. +/// Renders a [GenericNode] back to Pkl source code. class Renderer { /// The grammar version to target. /// @@ -37,22 +37,22 @@ class Renderer { grammarVersion: "V1" | "V2" = "V2" /// Render [node] as Pkl source code. - external function render(node: Node): String + external function render(node: GenericNode): String } /// A generic, untyped node in the Pkl syntax tree. /// /// A node is either a *leaf*, carrying source [text] and no /// [children], or a *branch*, carrying [children] and no [text]. -class Node { +class GenericNode { /// The kind of this node. type: NodeType /// The child nodes, in source order. Empty for leaf nodes. - children: List + children: List /// The parent node, or `null` for the root and for nodes not attached to a tree. - hidden parent: Node? + hidden parent: GenericNode? /// The verbatim source text of a leaf node (identifier, literal, terminal, etc.), /// or `null` for a branch node whose content is its [children]. @@ -78,15 +78,15 @@ class Node { /// [span] is carried through unchanged unless set explicitly. /// [parent] is populated on the returned tree for nodes originating from [Parser.parseModule]; /// nodes constructed from scratch retain their given `parent`. - external function walk(visit: (Node) -> Pair?): Node + external function walk(visit: (GenericNode) -> Pair?): GenericNode /// Fold [operator] over this node and its descendants, top-down in pre-order. /// /// ``` /// // count the if-expressions in a module - /// module.node.fold(0, (acc, n) -> if (n.type == "if_expr") acc + 1 else acc) + /// module.genericNode.fold(0, (acc, n) -> if (n.type == "if_expr") acc + 1 else acc) /// ``` - external function fold(initial: Result, operator: (Result, Node) -> Result): Result + external function fold(initial: Result, operator: (Result, GenericNode) -> Result): Result } /// A range of source code, spanning [start] (inclusive) to [end] (exclusive). @@ -253,24 +253,24 @@ typealias NodeType = /// Base class for all typed syntax nodes. /// -/// A typed node may be *backed* by a parsed [Node] (available via [node]) or -/// constructed from scratch (in which case [node] is `null`). Its typed fields -/// are read from [node] when backed, or supplied directly when constructed. -abstract class SyntaxNode { +/// A typed node may be *backed* by a parsed [GenericNode] (available via [genericNode]) or +/// constructed from scratch (in which case [genericNode] is `null`). Its typed fields +/// are read from [genericNode] when backed, or supplied directly when constructed. +abstract class Node { /// The original parsed node, or `null` when this was built from scratch. - hidden node: Node? = null + hidden genericNode: GenericNode? = null - /// This node rebuilt into a generic [Node]. + /// This node rebuilt into a [GenericNode]. /// /// Always constructs a fresh node from this node's fields. - external fixed builtNode: Node + external fixed builtNode: GenericNode } /// Base class for expression nodes. -abstract class ExprNode extends SyntaxNode +abstract class ExprNode extends Node /// Base class for type nodes. -abstract class TypeNode extends SyntaxNode +abstract class TypeNode extends Node /// A member of an [ObjectBodyNode]. typealias ObjectMemberNode = @@ -284,7 +284,7 @@ typealias ObjectMemberNode = | WhenGeneratorNode /// The top-level module node. -class ModuleNode extends SyntaxNode { +class ModuleNode extends Node { /// The module declaration. declaration: ModuleDeclarationNode? @@ -305,7 +305,7 @@ class ModuleNode extends SyntaxNode { } /// A module declaration (including doc comment, annotations, modifiers, name, amends/extends). -class ModuleDeclarationNode extends SyntaxNode { +class ModuleDeclarationNode extends Node { /// The doc comment on the module declaration. docComment: DocCommentNode? @@ -325,7 +325,7 @@ class ModuleDeclarationNode extends SyntaxNode { } /// The `extends` or `amends` clause of a module declaration. -class ExtendsOrAmendsClauseNode extends SyntaxNode { +class ExtendsOrAmendsClauseNode extends Node { /// The keyword used (`"extends"` or `"amends"`). keyword: "extends" | "amends" @@ -334,7 +334,7 @@ class ExtendsOrAmendsClauseNode extends SyntaxNode { } /// An import declaration. -class ImportNode extends SyntaxNode { +class ImportNode extends Node { /// The keyword used (`"import"` or `"import*"`). keyword: "import" | "import*" @@ -346,7 +346,7 @@ class ImportNode extends SyntaxNode { } /// A class declaration. -class ClassNode extends SyntaxNode { +class ClassNode extends Node { /// The doc comment. docComment: DocCommentNode? @@ -370,7 +370,7 @@ class ClassNode extends SyntaxNode { } /// A typealias declaration. -class TypeAliasNode extends SyntaxNode { +class TypeAliasNode extends Node { /// The doc comment. docComment: DocCommentNode? @@ -391,7 +391,7 @@ class TypeAliasNode extends SyntaxNode { } /// A class body delimited by braces. -class ClassBodyNode extends SyntaxNode { +class ClassBodyNode extends Node { /// Properties declared in this class body. properties: List @@ -400,7 +400,7 @@ class ClassBodyNode extends SyntaxNode { } /// A class property declaration. -class ClassPropertyNode extends SyntaxNode { +class ClassPropertyNode extends Node { /// The doc comment. docComment: DocCommentNode? @@ -426,7 +426,7 @@ class ClassPropertyNode extends SyntaxNode { } /// A class method declaration. -class ClassMethodNode extends SyntaxNode { +class ClassMethodNode extends Node { /// The doc comment. docComment: DocCommentNode? @@ -453,7 +453,7 @@ class ClassMethodNode extends SyntaxNode { } /// An object body delimited by braces. -class ObjectBodyNode extends SyntaxNode { +class ObjectBodyNode extends Node { /// Parameters for this object body (e.g., `{ x, y -> ... }`). parameters: List @@ -483,7 +483,7 @@ class ObjectBodyNode extends SyntaxNode { } /// An object property declaration. -class ObjectPropertyNode extends SyntaxNode { +class ObjectPropertyNode extends Node { /// The modifiers on the property. modifiers: List<"local" | "const">(isDistinct) @@ -501,7 +501,7 @@ class ObjectPropertyNode extends SyntaxNode { } /// An object method declaration. -class ObjectMethodNode extends SyntaxNode { +class ObjectMethodNode extends Node { /// The modifiers on the method. modifiers: List<"local" | "const">(isDistinct) @@ -522,13 +522,13 @@ class ObjectMethodNode extends SyntaxNode { } /// An object element (a positional expression in an object body). -class ObjectElementNode extends SyntaxNode { +class ObjectElementNode extends Node { /// The expression value. expression: ExprNode } /// An object entry (`[key] = value` or `[key] { ... }`). -class ObjectEntryNode extends SyntaxNode { +class ObjectEntryNode extends Node { /// The key expression. key: ExprNode @@ -540,7 +540,7 @@ class ObjectEntryNode extends SyntaxNode { } /// An object spread (`...expr` or `...?expr`). -class ObjectSpreadNode extends SyntaxNode { +class ObjectSpreadNode extends Node { /// The keyword used (`"..."` or `"...?"`). keyword: "..." | "...?" @@ -549,7 +549,7 @@ class ObjectSpreadNode extends SyntaxNode { } /// A member predicate (`[[condition]] = value` or `[[condition]] { ... }`). -class MemberPredicateNode extends SyntaxNode { +class MemberPredicateNode extends Node { /// The condition expression. condition: ExprNode @@ -561,7 +561,7 @@ class MemberPredicateNode extends SyntaxNode { } /// A `for (param in iterable) { ... }` generator. -class ForGeneratorNode extends SyntaxNode { +class ForGeneratorNode extends Node { /// The key parameter (first parameter when two are present). keyParameter: ParameterNode? @@ -576,7 +576,7 @@ class ForGeneratorNode extends SyntaxNode { } /// A `when (condition) { ... }` generator. -class WhenGeneratorNode extends SyntaxNode { +class WhenGeneratorNode extends Node { /// The condition expression. condition: ExprNode @@ -924,7 +924,7 @@ class StringConstantTypeNode extends TypeNode { } /// An annotation (`@Type { ... }`). -class AnnotationNode extends SyntaxNode { +class AnnotationNode extends Node { /// The annotation type. type: TypeNode @@ -933,7 +933,7 @@ class AnnotationNode extends SyntaxNode { } /// A parameter declaration (`name`, `name: Type`, or `_`). -class ParameterNode extends SyntaxNode { +class ParameterNode extends Node { /// Whether this is a blank identifier parameter (`_`). isBlankIdentifier: Boolean @@ -945,7 +945,7 @@ class ParameterNode extends SyntaxNode { } /// A type parameter declaration (`T`, `in T`, or `out T`). -class TypeParameterNode extends SyntaxNode { +class TypeParameterNode extends Node { /// The variance modifier (`"in"`, `"out"`, or null). variance: ("in" | "out")? @@ -954,13 +954,13 @@ class TypeParameterNode extends SyntaxNode { } /// An identifier (a name occurring in the source, e.g. a property or class name). -class IdentifierNode extends SyntaxNode { +class IdentifierNode extends Node { /// The identifier text. value: String } /// A qualified (dotted) identifier, e.g. `foo.bar.baz`. -class QualifiedIdentifierNode extends SyntaxNode { +class QualifiedIdentifierNode extends Node { /// The parts of the qualified identifier. identifiers: List @@ -969,7 +969,7 @@ class QualifiedIdentifierNode extends SyntaxNode { } /// A doc comment. -class DocCommentNode extends SyntaxNode { +class DocCommentNode extends Node { /// The body text of the comment, with the leading `///` stripped from each line. value: String } @@ -977,7 +977,7 @@ class DocCommentNode extends SyntaxNode { /// Base class for parts of a string literal (text, escapes, newlines, interpolations). abstract class StringPartNode { /// The original parsed node, or `null` when built from scratch. - hidden node: Node? = null + hidden genericNode: GenericNode? = null } /// A plain text part of a string literal. From d8704a53da092ece371bcbb06c51c917b90622a2 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Tue, 4 Aug 2026 14:13:29 +0200 Subject: [PATCH 35/49] Change List to Listing for Node properties --- .../org/pkl/core/stdlib/VmObjectFactory.java | 4 + .../org/pkl/core/stdlib/syntax/NodeNodes.java | 72 ++--- .../pkl/core/stdlib/syntax/ParserNodes.java | 256 +++++++++--------- .../input/syntax/expressions.pkl | 44 +-- .../input/syntax/moduleStructure.pkl | 12 +- .../input/syntax/objectMembers.pkl | 2 +- .../input/syntax/walk.pkl | 26 +- stdlib/syntax.pkl | 98 +++---- 8 files changed, 275 insertions(+), 239 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/VmObjectFactory.java b/pkl-core/src/main/java/org/pkl/core/stdlib/VmObjectFactory.java index 377a86709..dadb8e0ae 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/VmObjectFactory.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/VmObjectFactory.java @@ -68,6 +68,10 @@ public VmObjectFactory addListProperty(String name, Property impl) return doAddProperty(name, new PropertyNode<>(impl)); } + public VmObjectFactory addListingProperty(String name, Property impl) { + return doAddProperty(name, new PropertyNode<>(impl)); + } + public VmObjectFactory addSetProperty(String name, Property impl) { return doAddProperty(name, new PropertyNode<>(impl)); } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java index 4b015f3e3..3935b133f 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java @@ -24,6 +24,7 @@ import org.pkl.core.runtime.SyntaxModule; import org.pkl.core.runtime.VmExceptionBuilder; import org.pkl.core.runtime.VmList; +import org.pkl.core.runtime.VmListing; import org.pkl.core.runtime.VmObjectBuilder; import org.pkl.core.runtime.VmTyped; import org.pkl.core.runtime.VmUtils; @@ -189,7 +190,7 @@ private static VmTyped buildModule(VmTyped self) { children.add(build(declaration)); } var imports = listMember(self, "imports"); - if (imports.getLength() > 0) { + if (!imports.isEmpty()) { children.add(branch("import_list", buildAll(imports))); } children.addAll(buildAll(listMember(self, "classes"))); @@ -205,13 +206,13 @@ private static VmTyped buildModuleDeclaration(VmTyped self) { var modifiers = listMember(self, "modifiers"); if (name != null) { var definition = new ArrayList<>(); - if (modifiers.getLength() > 0) { + if (!modifiers.isEmpty()) { definition.add(modifierListNode(modifiers)); } definition.add(terminal("module")); definition.add(build(name)); children.add(branch("module_definition", definition)); - } else if (modifiers.getLength() > 0) { + } else if (!modifiers.isEmpty()) { children.add(modifierListNode(modifiers)); } var clause = optNode(self, "extendsOrAmendsClause"); @@ -236,7 +237,7 @@ private static VmTyped buildClass(VmTyped self) { var children = docAndAnnotations(self); var header = new ArrayList<>(); var modifiers = listMember(self, "modifiers"); - if (modifiers.getLength() > 0) { + if (!modifiers.isEmpty()) { header.add(modifierListNode(modifiers)); } header.add(terminal("class")); @@ -258,7 +259,7 @@ private static VmTyped buildTypeAlias(VmTyped self) { var children = docAndAnnotations(self); var header = new ArrayList<>(); var modifiers = listMember(self, "modifiers"); - if (modifiers.getLength() > 0) { + if (!modifiers.isEmpty()) { header.add(modifierListNode(modifiers)); } header.add(terminal("typealias")); @@ -287,7 +288,7 @@ private static VmTyped buildClassProperty(VmTyped self) { var children = docAndAnnotations(self); var headerBegin = new ArrayList<>(); var modifiers = listMember(self, "modifiers"); - if (modifiers.getLength() > 0) { + if (!modifiers.isEmpty()) { headerBegin.add(modifierListNode(modifiers)); } headerBegin.add(build(reqNode(self, "identifier"))); @@ -309,7 +310,7 @@ private static VmTyped buildClassMethod(VmTyped self) { var children = docAndAnnotations(self); var header = new ArrayList<>(); var modifiers = listMember(self, "modifiers"); - if (modifiers.getLength() > 0) { + if (!modifiers.isEmpty()) { header.add(modifierListNode(modifiers)); } header.add(terminal("function")); @@ -330,7 +331,7 @@ private static VmTyped buildObjectBody(VmTyped self) { var children = new ArrayList<>(); children.add(terminal("{")); var parameters = listMember(self, "parameters"); - if (parameters.getLength() > 0) { + if (!parameters.isEmpty()) { var elements = interleave(buildAll(parameters), NodeNodes::comma); elements.add(terminal("->")); children.add(branch("object_parameter_list", elements)); @@ -354,7 +355,7 @@ private static VmTyped buildObjectBody(VmTyped self) { private static VmTyped buildObjectProperty(VmTyped self) { var headerBegin = new ArrayList<>(); var modifiers = listMember(self, "modifiers"); - if (modifiers.getLength() > 0) { + if (!modifiers.isEmpty()) { headerBegin.add(modifierListNode(modifiers)); } headerBegin.add(build(reqNode(self, "identifier"))); @@ -376,7 +377,7 @@ private static VmTyped buildObjectProperty(VmTyped self) { private static VmTyped buildObjectMethod(VmTyped self) { var header = new ArrayList<>(); var modifiers = listMember(self, "modifiers"); - if (modifiers.getLength() > 0) { + if (!modifiers.isEmpty()) { header.add(modifierListNode(modifiers)); } header.add(terminal("function")); @@ -486,10 +487,10 @@ private static VmTyped buildMultiLineString(VmTyped self) { return branch("multi_line_string_literal_expr", children); } - private static List buildStringParts(VmList parts) { + private static List buildStringParts(List parts) { var result = new ArrayList<>(); - for (var i = 0; i < parts.getLength(); i++) { - result.addAll(buildStringPart((VmTyped) parts.get(i))); + for (var part : parts) { + result.addAll(buildStringPart((VmTyped) part)); } return result; } @@ -621,7 +622,7 @@ private static VmTyped buildFunctionLiteral(VmTyped self) { private static VmTyped buildDeclaredType(VmTyped self) { var name = build(reqNode(self, "name")); var typeArguments = listMember(self, "typeArguments"); - if (typeArguments.getLength() == 0) { + if (typeArguments.isEmpty()) { return branch("declared_type", List.of(name)); } return branch( @@ -641,7 +642,7 @@ private static VmTyped buildDeclaredType(VmTyped self) { private static VmTyped buildFunctionType(VmTyped self) { var parameterTypes = listMember(self, "parameterTypes"); var parameters = - parameterTypes.getLength() == 0 + parameterTypes.isEmpty() ? List.of(terminal("("), terminal(")")) : List.of( terminal("("), @@ -732,10 +733,10 @@ private static List docAndAnnotations(VmTyped self) { // Node construction helpers - private static VmTyped modifierListNode(VmList modifiers) { + private static VmTyped modifierListNode(List modifiers) { var children = new ArrayList<>(); - for (var i = 0; i < modifiers.getLength(); i++) { - children.add(leaf("modifier", (String) modifiers.get(i))); + for (var modifier : modifiers) { + children.add(leaf("modifier", (String) modifier)); } return branch("modifier_list", children); } @@ -749,8 +750,8 @@ private static VmTyped stringCharsNode(String value) { null); } - private static VmTyped parameterListNode(VmList parameters) { - if (parameters.getLength() == 0) { + private static VmTyped parameterListNode(List parameters) { + if (parameters.isEmpty()) { return branch("parameter_list", List.of(terminal("("), terminal(")"))); } return branch( @@ -761,8 +762,8 @@ private static VmTyped parameterListNode(VmList parameters) { terminal(")"))); } - private static VmTyped argumentListNode(VmList arguments) { - if (arguments.getLength() == 0) { + private static VmTyped argumentListNode(List arguments) { + if (arguments.isEmpty()) { return branch("argument_list", List.of(terminal("("), terminal(")"))); } return branch( @@ -773,8 +774,8 @@ private static VmTyped argumentListNode(VmList arguments) { terminal(")"))); } - private static List typeParameterListNodes(VmList typeParameters) { - if (typeParameters.getLength() == 0) { + private static List typeParameterListNodes(List typeParameters) { + if (typeParameters.isEmpty()) { return List.of(); } return List.of( @@ -854,10 +855,10 @@ private static VmTyped makeNode( // Member readers // =============== - private static List buildAll(VmList nodes) { + private static List buildAll(List nodes) { var result = new ArrayList<>(); - for (var i = 0; i < nodes.getLength(); i++) { - result.add(build((VmTyped) nodes.get(i))); + for (var node : nodes) { + result.add(build((VmTyped) node)); } return result; } @@ -874,12 +875,21 @@ private static VmTyped reqNode(VmTyped self, String name) { return member(self, name) instanceof VmTyped node ? node : null; } - private static VmList listMember(VmTyped self, String name) { - return (VmList) member(self, name); + private static List listMember(VmTyped self, String name) { + return elementsOf((VmListing) member(self, name)); } - private static @Nullable VmList optList(VmTyped self, String name) { - return member(self, name) instanceof VmList list ? list : null; + private static @Nullable List optList(VmTyped self, String name) { + return member(self, name) instanceof VmListing listing ? elementsOf(listing) : null; + } + + // The elements of `listing`, in order. + private static List elementsOf(VmListing listing) { + var result = new ArrayList<>(listing.getLength()); + for (var i = 0; i < listing.getLength(); i++) { + result.add(VmUtils.readMember(listing, (long) i)); + } + return result; } private static String str(VmTyped self, String name) { diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 84109f691..6a985620a 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -27,6 +27,7 @@ import org.pkl.core.runtime.VmClass; import org.pkl.core.runtime.VmExceptionBuilder; import org.pkl.core.runtime.VmList; +import org.pkl.core.runtime.VmListing; import org.pkl.core.runtime.VmNull; import org.pkl.core.runtime.VmTyped; import org.pkl.core.runtime.VmUtils; @@ -67,7 +68,7 @@ private static VmObjectFactory genericNodeOnlyFactory(Supplier private static final VmObjectFactory qualifiedIdentifierNodeFactory = new VmObjectFactory(SyntaxModule::getQualifiedIdentifierNodeClass) .addProperty("genericNode", vm -> vm) - .addListProperty("identifiers", ParserNodes::qualifiedIdentifierIdentifiers) + .addListingProperty("identifiers", ParserNodes::qualifiedIdentifierIdentifiers) .addStringProperty("value", ParserNodes::qualifiedIdentifierValue); private static final VmObjectFactory docCommentNodeFactory = new VmObjectFactory(SyntaxModule::getDocCommentNodeClass) @@ -86,15 +87,15 @@ private static VmObjectFactory genericNodeOnlyFactory(Supplier private static final VmObjectFactory objectBodyNodeFactory = new VmObjectFactory(SyntaxModule::getObjectBodyNodeClass) .addProperty("genericNode", vm -> vm) - .addListProperty("parameters", ParserNodes::objectBodyParameters) - .addListProperty("properties", ParserNodes::objectBodyProperties) - .addListProperty("methods", ParserNodes::objectBodyMethods) - .addListProperty("elements", ParserNodes::objectBodyElements) - .addListProperty("entries", ParserNodes::objectBodyEntries) - .addListProperty("spreads", ParserNodes::objectBodySpreads) - .addListProperty("memberPredicates", ParserNodes::objectBodyMemberPredicates) - .addListProperty("forGenerators", ParserNodes::objectBodyForGenerators) - .addListProperty("whenGenerators", ParserNodes::objectBodyWhenGenerators); + .addListingProperty("parameters", ParserNodes::objectBodyParameters) + .addListingProperty("properties", ParserNodes::objectBodyProperties) + .addListingProperty("methods", ParserNodes::objectBodyMethods) + .addListingProperty("elements", ParserNodes::objectBodyElements) + .addListingProperty("entries", ParserNodes::objectBodyEntries) + .addListingProperty("spreads", ParserNodes::objectBodySpreads) + .addListingProperty("memberPredicates", ParserNodes::objectBodyMemberPredicates) + .addListingProperty("forGenerators", ParserNodes::objectBodyForGenerators) + .addListingProperty("whenGenerators", ParserNodes::objectBodyWhenGenerators); private static final VmObjectFactory parameterNodeFactory = new VmObjectFactory(SyntaxModule::getParameterNodeClass) .addProperty("genericNode", vm -> vm) @@ -109,18 +110,18 @@ private static VmObjectFactory genericNodeOnlyFactory(Supplier private static final VmObjectFactory objectPropertyNodeFactory = new VmObjectFactory(SyntaxModule::getObjectPropertyNodeClass) .addProperty("genericNode", vm -> vm) - .addListProperty("modifiers", ParserNodes::objectPropertyModifiers) + .addListingProperty("modifiers", ParserNodes::objectPropertyModifiers) .addTypedProperty("identifier", ParserNodes::objectPropertyIdentifier) .addProperty("typeAnnotation", ParserNodes::objectPropertyTypeAnnotation) .addProperty("value", ParserNodes::objectPropertyValue) - .addListProperty("objectBodies", ParserNodes::objectPropertyObjectBodies); + .addListingProperty("objectBodies", ParserNodes::objectPropertyObjectBodies); private static final VmObjectFactory objectMethodNodeFactory = new VmObjectFactory(SyntaxModule::getObjectMethodNodeClass) .addProperty("genericNode", vm -> vm) - .addListProperty("modifiers", ParserNodes::classMethodModifiers) + .addListingProperty("modifiers", ParserNodes::classMethodModifiers) .addTypedProperty("identifier", ParserNodes::classMethodIdentifier) - .addListProperty("typeParameters", ParserNodes::classMethodTypeParameters) - .addListProperty("parameters", ParserNodes::classMethodParameters) + .addListingProperty("typeParameters", ParserNodes::classMethodTypeParameters) + .addListingProperty("parameters", ParserNodes::classMethodParameters) .addProperty("returnType", ParserNodes::classMethodReturnType) .addTypedProperty("body", ParserNodes::objectMethodBody); private static final VmObjectFactory memberPredicateNodeFactory = @@ -128,13 +129,13 @@ private static VmObjectFactory genericNodeOnlyFactory(Supplier .addProperty("genericNode", vm -> vm) .addTypedProperty("condition", ParserNodes::memberPredicateCondition) .addProperty("value", ParserNodes::memberPredicateValue) - .addListProperty("objectBodies", ParserNodes::memberPredicateObjectBodies); + .addListingProperty("objectBodies", ParserNodes::memberPredicateObjectBodies); private static final VmObjectFactory objectEntryNodeFactory = new VmObjectFactory(SyntaxModule::getObjectEntryNodeClass) .addProperty("genericNode", vm -> vm) .addTypedProperty("key", ParserNodes::objectEntryKey) .addProperty("value", ParserNodes::objectEntryValue) - .addListProperty("objectBodies", ParserNodes::objectEntryObjectBodies); + .addListingProperty("objectBodies", ParserNodes::objectEntryObjectBodies); private static final VmObjectFactory objectSpreadNodeFactory = new VmObjectFactory(SyntaxModule::getObjectSpreadNodeClass) .addProperty("genericNode", vm -> vm) @@ -164,7 +165,7 @@ private static VmObjectFactory genericNodeOnlyFactory(Supplier new VmObjectFactory(SyntaxModule::getDeclaredTypeNodeClass) .addProperty("genericNode", vm -> vm) .addTypedProperty("name", ParserNodes::declaredTypeName) - .addListProperty("typeArguments", ParserNodes::declaredTypeArguments); + .addListingProperty("typeArguments", ParserNodes::declaredTypeArguments); private static final VmObjectFactory nullableTypeNodeFactory = new VmObjectFactory(SyntaxModule::getNullableTypeNodeClass) .addProperty("genericNode", vm -> vm) @@ -172,17 +173,17 @@ private static VmObjectFactory genericNodeOnlyFactory(Supplier private static final VmObjectFactory unionTypeNodeFactory = new VmObjectFactory(SyntaxModule::getUnionTypeNodeClass) .addProperty("genericNode", vm -> vm) - .addListProperty("members", ParserNodes::unionTypeMembers); + .addListingProperty("members", ParserNodes::unionTypeMembers); private static final VmObjectFactory functionTypeNodeFactory = new VmObjectFactory(SyntaxModule::getFunctionTypeNodeClass) .addProperty("genericNode", vm -> vm) - .addListProperty("parameterTypes", ParserNodes::functionTypeParameterTypes) + .addListingProperty("parameterTypes", ParserNodes::functionTypeParameterTypes) .addTypedProperty("returnType", ParserNodes::functionTypeReturnType); private static final VmObjectFactory constrainedTypeNodeFactory = new VmObjectFactory(SyntaxModule::getConstrainedTypeNodeClass) .addProperty("genericNode", vm -> vm) .addTypedProperty("baseType", ParserNodes::constrainedTypeBaseType) - .addListProperty("constraints", ParserNodes::constrainedTypeConstraints); + .addListingProperty("constraints", ParserNodes::constrainedTypeConstraints); private static final VmObjectFactory parenthesizedTypeNodeFactory = new VmObjectFactory(SyntaxModule::getParenthesizedTypeNodeClass) .addProperty("genericNode", vm -> vm) @@ -215,11 +216,11 @@ private static VmObjectFactory genericNodeOnlyFactory(Supplier private static final VmObjectFactory singleLineStringLiteralExprNodeFactory = new VmObjectFactory(SyntaxModule::getSingleLineStringLiteralExprNodeClass) .addProperty("genericNode", vm -> vm) - .addListProperty("parts", ParserNodes::buildStringParts); + .addListingProperty("parts", ParserNodes::buildStringParts); private static final VmObjectFactory multiLineStringLiteralExprNodeFactory = new VmObjectFactory(SyntaxModule::getMultiLineStringLiteralExprNodeClass) .addProperty("genericNode", vm -> vm) - .addListProperty("parts", ParserNodes::buildStringParts); + .addListingProperty("parts", ParserNodes::buildStringParts); private static final VmObjectFactory unqualifiedAccessExprNodeFactory = new VmObjectFactory(SyntaxModule::getUnqualifiedAccessExprNodeClass) .addProperty("genericNode", vm -> vm) @@ -354,7 +355,7 @@ private static VmObjectFactory typeOpExprNodeFactory(Supplier private static final VmObjectFactory functionLiteralExprNodeFactory = new VmObjectFactory(SyntaxModule::getFunctionLiteralExprNodeClass) .addProperty("genericNode", vm -> vm) - .addListProperty("parameters", ParserNodes::functionLiteralParameters) + .addListingProperty("parameters", ParserNodes::functionLiteralParameters) .addTypedProperty("body", ParserNodes::functionLiteralBody); private static final VmObjectFactory parenthesizedExprNodeFactory = new VmObjectFactory(SyntaxModule::getParenthesizedExprNodeClass) @@ -390,8 +391,8 @@ private static VmObjectFactory typeOpExprNodeFactory(Supplier new VmObjectFactory(SyntaxModule::getModuleDeclarationNodeClass) .addProperty("genericNode", vm -> vm) .addProperty("docComment", ParserNodes::docCommentOf) - .addListProperty("annotations", ParserNodes::annotationsOf) - .addListProperty("modifiers", ParserNodes::moduleDeclModifiers) + .addListingProperty("annotations", ParserNodes::annotationsOf) + .addListingProperty("modifiers", ParserNodes::moduleDeclModifiers) .addProperty("name", ParserNodes::moduleDeclName) .addProperty("extendsOrAmendsClause", ParserNodes::moduleDeclExtendsOrAmendsClause); @@ -405,10 +406,10 @@ private static VmObjectFactory typeOpExprNodeFactory(Supplier new VmObjectFactory(SyntaxModule::getClassNodeClass) .addProperty("genericNode", vm -> vm) .addProperty("docComment", ParserNodes::docCommentOf) - .addListProperty("annotations", ParserNodes::annotationsOf) - .addListProperty("modifiers", ParserNodes::classModifiers) + .addListingProperty("annotations", ParserNodes::annotationsOf) + .addListingProperty("modifiers", ParserNodes::classModifiers) .addTypedProperty("identifier", ParserNodes::classIdentifier) - .addListProperty("typeParameters", ParserNodes::classTypeParameters) + .addListingProperty("typeParameters", ParserNodes::classTypeParameters) .addProperty("superType", ParserNodes::classSuperType) .addProperty("body", ParserNodes::classBody); @@ -416,50 +417,50 @@ private static VmObjectFactory typeOpExprNodeFactory(Supplier new VmObjectFactory(SyntaxModule::getTypeAliasNodeClass) .addProperty("genericNode", vm -> vm) .addProperty("docComment", ParserNodes::docCommentOf) - .addListProperty("annotations", ParserNodes::annotationsOf) - .addListProperty("modifiers", ParserNodes::typeAliasModifiers) + .addListingProperty("annotations", ParserNodes::annotationsOf) + .addListingProperty("modifiers", ParserNodes::typeAliasModifiers) .addTypedProperty("identifier", ParserNodes::typeAliasIdentifier) - .addListProperty("typeParameters", ParserNodes::typeAliasTypeParameters) + .addListingProperty("typeParameters", ParserNodes::typeAliasTypeParameters) .addTypedProperty("type", ParserNodes::typeAliasType); private static final VmObjectFactory classPropertyNodeFactory = new VmObjectFactory(SyntaxModule::getClassPropertyNodeClass) .addProperty("genericNode", vm -> vm) .addProperty("docComment", ParserNodes::docCommentOf) - .addListProperty("annotations", ParserNodes::annotationsOf) - .addListProperty("modifiers", ParserNodes::classPropertyModifiers) + .addListingProperty("annotations", ParserNodes::annotationsOf) + .addListingProperty("modifiers", ParserNodes::classPropertyModifiers) .addTypedProperty("identifier", ParserNodes::classPropertyIdentifier) .addProperty("typeAnnotation", ParserNodes::classPropertyTypeAnnotation) .addProperty("value", ParserNodes::classPropertyValue) - .addListProperty("objectBodies", ParserNodes::classPropertyObjectBodies); + .addListingProperty("objectBodies", ParserNodes::classPropertyObjectBodies); private static final VmObjectFactory classMethodNodeFactory = new VmObjectFactory(SyntaxModule::getClassMethodNodeClass) .addProperty("genericNode", vm -> vm) .addProperty("docComment", ParserNodes::docCommentOf) - .addListProperty("annotations", ParserNodes::annotationsOf) - .addListProperty("modifiers", ParserNodes::classMethodModifiers) + .addListingProperty("annotations", ParserNodes::annotationsOf) + .addListingProperty("modifiers", ParserNodes::classMethodModifiers) .addTypedProperty("identifier", ParserNodes::classMethodIdentifier) - .addListProperty("typeParameters", ParserNodes::classMethodTypeParameters) - .addListProperty("parameters", ParserNodes::classMethodParameters) + .addListingProperty("typeParameters", ParserNodes::classMethodTypeParameters) + .addListingProperty("parameters", ParserNodes::classMethodParameters) .addProperty("returnType", ParserNodes::classMethodReturnType) .addProperty("body", ParserNodes::classMethodBody); private static final VmObjectFactory classBodyNodeFactory = new VmObjectFactory(SyntaxModule::getClassBodyNodeClass) .addProperty("genericNode", vm -> vm) - .addListProperty("properties", ParserNodes::classBodyProperties) - .addListProperty("methods", ParserNodes::classBodyMethods); + .addListingProperty("properties", ParserNodes::classBodyProperties) + .addListingProperty("methods", ParserNodes::classBodyMethods); private static final VmObjectFactory moduleNodeFactory = new VmObjectFactory(SyntaxModule::getModuleNodeClass) .addProperty("genericNode", vm -> vm) .addProperty("declaration", ParserNodes::moduleDeclaration) - .addListProperty("imports", ParserNodes::moduleImports) - .addListProperty("classes", ParserNodes::moduleClasses) - .addListProperty("typeAliases", ParserNodes::moduleTypeAliases) - .addListProperty("properties", ParserNodes::moduleProperties) - .addListProperty("methods", ParserNodes::moduleMethods); + .addListingProperty("imports", ParserNodes::moduleImports) + .addListingProperty("classes", ParserNodes::moduleClasses) + .addListingProperty("typeAliases", ParserNodes::moduleTypeAliases) + .addListingProperty("properties", ParserNodes::moduleProperties) + .addListingProperty("methods", ParserNodes::moduleMethods); private static Object moduleDeclaration(VmTyped moduleVm) { var declVm = findChildVm(moduleVm, NodeType.MODULE_DECLARATION); @@ -471,7 +472,7 @@ private static Object docCommentOf(VmTyped ownerVm) { return dc == null ? VmNull.withoutDefault() : docCommentNodeFactory.create(dc); } - private static VmList annotationsOf(VmTyped ownerVm) { + private static VmListing annotationsOf(VmTyped ownerVm) { return wrapAll(findChildrenVm(ownerVm, NodeType.ANNOTATION), annotationNodeFactory); } @@ -518,14 +519,14 @@ private static VmTyped declaredTypeName(VmTyped typeVm) { return qualifiedIdentifierNodeFactory.create(name); } - private static VmList declaredTypeArguments(VmTyped typeVm) { + private static VmListing declaredTypeArguments(VmTyped typeVm) { var list = findChildVm(typeVm, NodeType.TYPE_ARGUMENT_LIST); if (list == null) { - return VmList.EMPTY; + return VmListing.empty(); } var elems = findChildVm(list, NodeType.TYPE_ARGUMENT_LIST_ELEMENTS); if (elems == null) { - return VmList.EMPTY; + return VmListing.empty(); } return wrapTypes(findTypeChildrenVm(elems)); } @@ -534,18 +535,18 @@ private static VmTyped nullableTypeBaseType(VmTyped typeVm) { return wrapType(requireTypeChild(typeVm)); } - private static VmList unionTypeMembers(VmTyped typeVm) { + private static VmListing unionTypeMembers(VmTyped typeVm) { return wrapTypes(findTypeChildrenVm(typeVm)); } - private static VmList functionTypeParameterTypes(VmTyped typeVm) { + private static VmListing functionTypeParameterTypes(VmTyped typeVm) { var params = findChildVm(typeVm, NodeType.FUNCTION_TYPE_PARAMETERS); if (params == null) { - return VmList.EMPTY; + return VmListing.empty(); } var elems = findChildVm(params, NodeType.PARENTHESIZED_TYPE_ELEMENTS); if (elems == null) { - return VmList.EMPTY; + return VmListing.empty(); } return wrapTypes(findTypeChildrenVm(elems)); } @@ -562,14 +563,14 @@ private static VmTyped constrainedTypeBaseType(VmTyped typeVm) { return wrapType(requireTypeChild(typeVm)); } - private static VmList constrainedTypeConstraints(VmTyped typeVm) { + private static VmListing constrainedTypeConstraints(VmTyped typeVm) { var constraint = findChildVm(typeVm, NodeType.CONSTRAINED_TYPE_CONSTRAINT); if (constraint == null) { - return VmList.EMPTY; + return VmListing.empty(); } var elems = findChildVm(constraint, NodeType.CONSTRAINED_TYPE_ELEMENTS); if (elems == null) { - return VmList.EMPTY; + return VmListing.empty(); } return wrapExprs(findExprChildrenVm(elems)); } @@ -746,7 +747,7 @@ private static VmObjectFactory binaryOpFactory(VmTyped exprVm) { }; } - private static VmList functionLiteralParameters(VmTyped exprVm) { + private static VmListing functionLiteralParameters(VmTyped exprVm) { return parametersOf(exprVm); } @@ -764,10 +765,10 @@ private static Object argumentsOrNull(VmTyped ownerVm) { return argList == null ? VmNull.withoutDefault() : argumentsOf(argList); } - private static VmList argumentsOf(VmTyped argListVm) { + private static VmListing argumentsOf(VmTyped argListVm) { var elems = findChildVm(argListVm, NodeType.ARGUMENT_LIST_ELEMENTS); if (elems == null) { - return VmList.EMPTY; + return VmListing.empty(); } return wrapExprs(findExprChildrenVm(elems)); } @@ -800,52 +801,52 @@ private static VmTyped requireExprChild(VmTyped genericVm) { return result; } - private static VmList objectBodyParameters(VmTyped bodyVm) { + private static VmListing objectBodyParameters(VmTyped bodyVm) { var paramList = findChildVm(bodyVm, NodeType.OBJECT_PARAMETER_LIST); if (paramList == null) { - return VmList.EMPTY; + return VmListing.empty(); } return wrapAll(findChildrenVm(paramList, NodeType.PARAMETER), parameterNodeFactory); } - private static VmList objectBodyMembers( + private static VmListing objectBodyMembers( VmTyped bodyVm, NodeType memberType, VmObjectFactory factory) { var memberList = findChildVm(bodyVm, NodeType.OBJECT_MEMBER_LIST); if (memberList == null) { - return VmList.EMPTY; + return VmListing.empty(); } return wrapAll(findChildrenVm(memberList, memberType), factory); } - private static VmList objectBodyProperties(VmTyped bodyVm) { + private static VmListing objectBodyProperties(VmTyped bodyVm) { return objectBodyMembers(bodyVm, NodeType.OBJECT_PROPERTY, objectPropertyNodeFactory); } - private static VmList objectBodyMethods(VmTyped bodyVm) { + private static VmListing objectBodyMethods(VmTyped bodyVm) { return objectBodyMembers(bodyVm, NodeType.OBJECT_METHOD, objectMethodNodeFactory); } - private static VmList objectBodyElements(VmTyped bodyVm) { + private static VmListing objectBodyElements(VmTyped bodyVm) { return objectBodyMembers(bodyVm, NodeType.OBJECT_ELEMENT, objectElementNodeFactory); } - private static VmList objectBodyEntries(VmTyped bodyVm) { + private static VmListing objectBodyEntries(VmTyped bodyVm) { return objectBodyMembers(bodyVm, NodeType.OBJECT_ENTRY, objectEntryNodeFactory); } - private static VmList objectBodySpreads(VmTyped bodyVm) { + private static VmListing objectBodySpreads(VmTyped bodyVm) { return objectBodyMembers(bodyVm, NodeType.OBJECT_SPREAD, objectSpreadNodeFactory); } - private static VmList objectBodyMemberPredicates(VmTyped bodyVm) { + private static VmListing objectBodyMemberPredicates(VmTyped bodyVm) { return objectBodyMembers(bodyVm, NodeType.MEMBER_PREDICATE, memberPredicateNodeFactory); } - private static VmList objectBodyForGenerators(VmTyped bodyVm) { + private static VmListing objectBodyForGenerators(VmTyped bodyVm) { return objectBodyMembers(bodyVm, NodeType.FOR_GENERATOR, forGeneratorNodeFactory); } - private static VmList objectBodyWhenGenerators(VmTyped bodyVm) { + private static VmListing objectBodyWhenGenerators(VmTyped bodyVm) { return objectBodyMembers(bodyVm, NodeType.WHEN_GENERATOR, whenGeneratorNodeFactory); } @@ -854,9 +855,9 @@ private static VmList objectBodyWhenGenerators(VmTyped bodyVm) { return header == null ? null : findChildVm(header, NodeType.OBJECT_PROPERTY_HEADER_BEGIN); } - private static VmList objectPropertyModifiers(VmTyped propertyVm) { + private static VmListing objectPropertyModifiers(VmTyped propertyVm) { var headerBegin = objectPropertyHeaderBegin(propertyVm); - return headerBegin == null ? VmList.EMPTY : modifiersOf(headerBegin); + return headerBegin == null ? VmListing.empty() : modifiersOf(headerBegin); } private static VmTyped objectPropertyIdentifier(VmTyped propertyVm) { @@ -871,7 +872,7 @@ private static Object objectPropertyValue(VmTyped propertyVm) { return exprInChild(propertyVm, NodeType.OBJECT_PROPERTY_BODY); } - private static VmList objectPropertyObjectBodies(VmTyped propertyVm) { + private static VmListing objectPropertyObjectBodies(VmTyped propertyVm) { return objectBodiesOf(propertyVm); } @@ -888,7 +889,7 @@ private static Object memberPredicateValue(VmTyped predicateVm) { return exprs.size() < 2 ? VmNull.withoutDefault() : wrapExpr(exprs.get(1)); } - private static VmList memberPredicateObjectBodies(VmTyped predicateVm) { + private static VmListing memberPredicateObjectBodies(VmTyped predicateVm) { return objectBodiesOf(predicateVm); } @@ -902,7 +903,7 @@ private static Object objectEntryValue(VmTyped entryVm) { return expr == null ? VmNull.withoutDefault() : wrapExpr(expr); } - private static VmList objectEntryObjectBodies(VmTyped entryVm) { + private static VmListing objectEntryObjectBodies(VmTyped entryVm) { return objectBodiesOf(entryVm); } @@ -954,7 +955,7 @@ private static VmTyped forBody(VmTyped forVm) { return objectBodyNodeFactory.create(requireChild(forVm, NodeType.OBJECT_BODY)); } - private static VmList qualifiedIdentifierIdentifiers(VmTyped qualifiedVm) { + private static VmListing qualifiedIdentifierIdentifiers(VmTyped qualifiedVm) { return wrapAll(findChildrenVm(qualifiedVm, NodeType.IDENTIFIER), identifierNodeFactory); } @@ -997,7 +998,7 @@ private static Object parameterTypeAnnotation(VmTyped parameterVm) { return typeAnnotationOf(parameterVm); } - private static VmList buildStringParts(VmTyped stringVm) { + private static VmListing buildStringParts(VmTyped stringVm) { var data = (GenericNodeData) stringVm.getExtraStorage(); var children = data.node.children; var childrenVm = data.childrenVm; @@ -1036,7 +1037,7 @@ private static VmList buildStringParts(VmTyped stringVm) { default -> i++; // affixes and stray terminals } } - return VmList.create(parts.toArray()); + return listingOf(parts.toArray()); } private static boolean isInterpolationStart(Node terminal, char[] source) { @@ -1060,9 +1061,9 @@ private static int nextNonAffix(List children, int start, int end) { return i; } - private static VmList moduleDeclModifiers(VmTyped declVm) { + private static VmListing moduleDeclModifiers(VmTyped declVm) { var moduleDefinition = findChildVm(declVm, NodeType.MODULE_DEFINITION); - return moduleDefinition == null ? VmList.EMPTY : modifiersOf(moduleDefinition); + return moduleDefinition == null ? VmListing.empty() : modifiersOf(moduleDefinition); } private static Object moduleDeclName(VmTyped declVm) { @@ -1089,20 +1090,20 @@ private static String extendsOrAmendsClauseKeyword(VmTyped clauseVm) { return data.node.type == NodeType.AMENDS_CLAUSE ? "amends" : "extends"; } - private static VmList moduleClasses(VmTyped moduleVm) { + private static VmListing moduleClasses(VmTyped moduleVm) { return wrapAll(findChildrenVm(moduleVm, NodeType.CLASS), classNodeFactory); } - private static VmList classModifiers(VmTyped classVm) { + private static VmListing classModifiers(VmTyped classVm) { var header = findChildVm(classVm, NodeType.CLASS_HEADER); - return header == null ? VmList.EMPTY : modifiersOf(header); + return header == null ? VmListing.empty() : modifiersOf(header); } private static VmTyped classIdentifier(VmTyped classVm) { return identifierNodeOf(findChildVm(classVm, NodeType.CLASS_HEADER)); } - private static VmList classTypeParameters(VmTyped classVm) { + private static VmListing classTypeParameters(VmTyped classVm) { return typeParametersOf(findChildVm(classVm, NodeType.CLASS_HEADER)); } @@ -1124,20 +1125,20 @@ private static Object classBody(VmTyped classVm) { return body == null ? VmNull.withoutDefault() : classBodyNodeFactory.create(body); } - private static VmList moduleTypeAliases(VmTyped moduleVm) { + private static VmListing moduleTypeAliases(VmTyped moduleVm) { return wrapAll(findChildrenVm(moduleVm, NodeType.TYPEALIAS), typeAliasNodeFactory); } - private static VmList typeAliasModifiers(VmTyped typeAliasVm) { + private static VmListing typeAliasModifiers(VmTyped typeAliasVm) { var header = findChildVm(typeAliasVm, NodeType.TYPEALIAS_HEADER); - return header == null ? VmList.EMPTY : modifiersOf(header); + return header == null ? VmListing.empty() : modifiersOf(header); } private static VmTyped typeAliasIdentifier(VmTyped typeAliasVm) { return identifierNodeOf(findChildVm(typeAliasVm, NodeType.TYPEALIAS_HEADER)); } - private static VmList typeAliasTypeParameters(VmTyped typeAliasVm) { + private static VmListing typeAliasTypeParameters(VmTyped typeAliasVm) { return typeParametersOf(findChildVm(typeAliasVm, NodeType.TYPEALIAS_HEADER)); } @@ -1150,11 +1151,11 @@ private static VmTyped typeAliasType(VmTyped typeAliasVm) { return wrapType(type); } - private static VmList moduleProperties(VmTyped moduleVm) { + private static VmListing moduleProperties(VmTyped moduleVm) { return wrapAll(findChildrenVm(moduleVm, NodeType.CLASS_PROPERTY), classPropertyNodeFactory); } - private static VmList moduleMethods(VmTyped moduleVm) { + private static VmListing moduleMethods(VmTyped moduleVm) { return wrapAll(findChildrenVm(moduleVm, NodeType.CLASS_METHOD), classMethodNodeFactory); } @@ -1165,9 +1166,9 @@ private static VmList moduleMethods(VmTyped moduleVm) { : findChildVm(propHeader, NodeType.CLASS_PROPERTY_HEADER_BEGIN); } - private static VmList classPropertyModifiers(VmTyped propertyVm) { + private static VmListing classPropertyModifiers(VmTyped propertyVm) { var headerBegin = classPropertyHeaderBegin(propertyVm); - return headerBegin == null ? VmList.EMPTY : modifiersOf(headerBegin); + return headerBegin == null ? VmListing.empty() : modifiersOf(headerBegin); } private static VmTyped classPropertyIdentifier(VmTyped propertyVm) { @@ -1182,24 +1183,24 @@ private static Object classPropertyValue(VmTyped propertyVm) { return exprInChild(propertyVm, NodeType.CLASS_PROPERTY_BODY); } - private static VmList classPropertyObjectBodies(VmTyped propertyVm) { + private static VmListing classPropertyObjectBodies(VmTyped propertyVm) { return objectBodiesOf(propertyVm); } - private static VmList classMethodModifiers(VmTyped methodVm) { + private static VmListing classMethodModifiers(VmTyped methodVm) { var header = findChildVm(methodVm, NodeType.CLASS_METHOD_HEADER); - return header == null ? VmList.EMPTY : modifiersOf(header); + return header == null ? VmListing.empty() : modifiersOf(header); } private static VmTyped classMethodIdentifier(VmTyped methodVm) { return identifierNodeOf(findChildVm(methodVm, NodeType.CLASS_METHOD_HEADER)); } - private static VmList classMethodTypeParameters(VmTyped methodVm) { + private static VmListing classMethodTypeParameters(VmTyped methodVm) { return typeParametersOf(methodVm); } - private static VmList classMethodParameters(VmTyped methodVm) { + private static VmListing classMethodParameters(VmTyped methodVm) { return parametersOf(methodVm); } @@ -1211,26 +1212,26 @@ private static Object classMethodBody(VmTyped methodVm) { return exprInChild(methodVm, NodeType.CLASS_METHOD_BODY); } - private static VmList classBodyProperties(VmTyped classBodyVm) { + private static VmListing classBodyProperties(VmTyped classBodyVm) { var elements = findChildVm(classBodyVm, NodeType.CLASS_BODY_ELEMENTS); if (elements == null) { - return VmList.EMPTY; + return VmListing.empty(); } return wrapAll(findChildrenVm(elements, NodeType.CLASS_PROPERTY), classPropertyNodeFactory); } - private static VmList classBodyMethods(VmTyped classBodyVm) { + private static VmListing classBodyMethods(VmTyped classBodyVm) { var elements = findChildVm(classBodyVm, NodeType.CLASS_BODY_ELEMENTS); if (elements == null) { - return VmList.EMPTY; + return VmListing.empty(); } return wrapAll(findChildrenVm(elements, NodeType.CLASS_METHOD), classMethodNodeFactory); } - private static VmList moduleImports(VmTyped moduleVm) { + private static VmListing moduleImports(VmTyped moduleVm) { var importListVm = findChildVm(moduleVm, NodeType.IMPORT_LIST); if (importListVm == null) { - return VmList.EMPTY; + return VmListing.empty(); } return wrapAll(findChildrenVm(importListVm, NodeType.IMPORT), importNodeFactory); } @@ -1295,10 +1296,15 @@ private static String extractStringChars(Node node, char[] source) { return builder.toString(); } - private static VmList modifiersOf(VmTyped ownerVm) { + // A `Listing` holding `elements`, in order. + private static VmListing listingOf(Object[] elements) { + return VmList.create(elements).toListing(); + } + + private static VmListing modifiersOf(VmTyped ownerVm) { var modifierList = findChildVm(ownerVm, NodeType.MODIFIER_LIST); if (modifierList == null) { - return VmList.EMPTY; + return VmListing.empty(); } var modifierVms = findChildrenVm(modifierList, NodeType.MODIFIER); var result = new Object[modifierVms.size()]; @@ -1306,7 +1312,7 @@ private static VmList modifiersOf(VmTyped ownerVm) { var data = (GenericNodeData) modifierVms.get(i).getExtraStorage(); result[i] = data.node.text(data.source); } - return VmList.create(result); + return listingOf(result); } private static String stringCharsOf(VmTyped clauseVm) { @@ -1376,22 +1382,22 @@ private static List findExprChildrenVm(VmTyped genericVm) { return result; } - // Wrap each generic type node into its `TypeNode` subclass, as a `VmList`. - private static VmList wrapTypes(List typeVms) { + // Wrap each generic type node into its `TypeNode` subclass, as a `VmListing`. + private static VmListing wrapTypes(List typeVms) { var result = new Object[typeVms.size()]; for (var i = 0; i < typeVms.size(); i++) { result[i] = wrapType(typeVms.get(i)); } - return VmList.create(result); + return listingOf(result); } - // Wrap each generic expression node into its `ExprNode` subclass, as a `VmList`. - private static VmList wrapExprs(List exprVms) { + // Wrap each generic expression node into its `ExprNode` subclass, as a `VmListing`. + private static VmListing wrapExprs(List exprVms) { var result = new Object[exprVms.size()]; for (var i = 0; i < exprVms.size(); i++) { result[i] = wrapExpr(exprVms.get(i)); } - return VmList.create(result); + return listingOf(result); } private static Object typeAnnotationOf(@Nullable VmTyped ownerVm) { @@ -1420,29 +1426,29 @@ private static Object exprInChild(VmTyped ownerVm, NodeType containerType) { return expr == null ? VmNull.withoutDefault() : wrapExpr(expr); } - private static VmList objectBodiesOf(VmTyped ownerVm) { + private static VmListing objectBodiesOf(VmTyped ownerVm) { return wrapAll(findChildrenVm(ownerVm, NodeType.OBJECT_BODY), objectBodyNodeFactory); } - private static VmList parametersOf(VmTyped ownerVm) { + private static VmListing parametersOf(VmTyped ownerVm) { var list = findChildVm(ownerVm, NodeType.PARAMETER_LIST); if (list == null) { - return VmList.EMPTY; + return VmListing.empty(); } var elems = findChildVm(list, NodeType.PARAMETER_LIST_ELEMENTS); if (elems == null) { - return VmList.EMPTY; + return VmListing.empty(); } return wrapAll(findChildrenVm(elems, NodeType.PARAMETER), parameterNodeFactory); } - // Wrap each generic-node `VmTyped` into a typed node via `factory`, as a `VmList`. - private static VmList wrapAll(List genericVms, VmObjectFactory factory) { + // Wrap each generic-node `VmTyped` into a typed node via `factory`, as a `VmListing`. + private static VmListing wrapAll(List genericVms, VmObjectFactory factory) { var result = new Object[genericVms.size()]; for (var i = 0; i < genericVms.size(); i++) { result[i] = factory.create(genericVms.get(i)); } - return VmList.create(result); + return listingOf(result); } private static VmTyped identifierNodeOf(@Nullable VmTyped ownerVm) { @@ -1450,17 +1456,17 @@ private static VmTyped identifierNodeOf(@Nullable VmTyped ownerVm) { return identifierNodeFactory.create(id); } - private static VmList typeParametersOf(@Nullable VmTyped ownerVm) { + private static VmListing typeParametersOf(@Nullable VmTyped ownerVm) { if (ownerVm == null) { - return VmList.EMPTY; + return VmListing.empty(); } var list = findChildVm(ownerVm, NodeType.TYPE_PARAMETER_LIST); if (list == null) { - return VmList.EMPTY; + return VmListing.empty(); } var elems = findChildVm(list, NodeType.TYPE_PARAMETER_LIST_ELEMENTS); if (elems == null) { - return VmList.EMPTY; + return VmListing.empty(); } return wrapAll(findChildrenVm(elems, NodeType.TYPE_PARAMETER), typeParameterNodeFactory); } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl index ef83d756e..2384f650c 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl @@ -36,7 +36,8 @@ facts { simple is syntax.SingleLineStringLiteralExprNode (simple as syntax.SingleLineStringLiteralExprNode).parts.length == 1 (simple as syntax.SingleLineStringLiteralExprNode).parts.first is syntax.StringCharsNode - ((simple as syntax.SingleLineStringLiteralExprNode).parts.first as syntax.StringCharsNode).value == "hello" + ((simple as syntax.SingleLineStringLiteralExprNode).parts.first as syntax.StringCharsNode).value + == "hello" local withEscape = expr(#""hello\nworld""#) withEscape is syntax.SingleLineStringLiteralExprNode @@ -52,14 +53,18 @@ facts { interpParts.length == 2 interpParts[0] is syntax.StringCharsNode interpParts[1] is syntax.StringInterpolationNode - (interpParts[1] as syntax.StringInterpolationNode).expression is syntax.UnqualifiedAccessExprNode - - local multiLine = expr(#""" - """ - hello - world - """ - """#) + (interpParts[1] as syntax.StringInterpolationNode).expression + is syntax.UnqualifiedAccessExprNode + + local multiLine = + expr( + #""" + """ + hello + world + """ + """#, + ) multiLine is syntax.MultiLineStringLiteralExprNode local mlParts = (multiLine as syntax.MultiLineStringLiteralExprNode).parts // each content line is preceded by a newline part @@ -232,18 +237,21 @@ facts { } ["class declaration"] { - local mod = new syntax.Parser {}.parseModule(""" - /// A person. - @Deprecated { message = "old" } - abstract open class Person extends Being { - name: String - function greet() = "hi" - } - """) + local mod = + new syntax.Parser {}.parseModule( + """ + /// A person. + @Deprecated { message = "old" } + abstract open class Person extends Being { + name: String + function greet() = "hi" + } + """, + ) mod.classes.length == 1 local cls = mod.classes.first cls.identifier.value == "Person" - cls.modifiers == List("abstract", "open") + cls.modifiers == new Listing { "abstract"; "open" } cls.docComment != null cls.docComment!!.value == "A person." cls.annotations.length == 1 diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl index 833c82923..d85c53d39 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl @@ -31,7 +31,7 @@ facts { mod.declaration!!.annotations.first.type is syntax.DeclaredTypeNode mod.declaration!!.modifiers != null - mod.declaration!!.modifiers == List("open") + mod.declaration!!.modifiers == new Listing { "open" } } ["amends clause"] { @@ -84,7 +84,7 @@ facts { cls.docComment!!.value == "A bird class." cls.modifiers != null - cls.modifiers == List("abstract") + cls.modifiers == new Listing { "abstract" } cls.identifier.value == "Bird" @@ -144,12 +144,12 @@ facts { mod.properties.length == 2 mod.properties[0].identifier.value == "name" mod.properties[0].modifiers != null - mod.properties[0].modifiers == List("hidden") + mod.properties[0].modifiers == new Listing { "hidden" } mod.properties[0].value is syntax.SingleLineStringLiteralExprNode mod.properties[1].identifier.value == "count" mod.properties[1].modifiers != null - mod.properties[1].modifiers == List("local") + mod.properties[1].modifiers == new Listing { "local" } mod.methods.length == 1 mod.methods.first.identifier.value == "greet" @@ -195,7 +195,7 @@ facts { """) mod.declaration!!.name!!.value == "`my mod`.`sub pkg`" - mod.declaration!!.name!!.identifiers.map((i) -> i.value) == List("`my mod`", "`sub pkg`") + mod.declaration!!.name!!.identifiers.toList().map((i) -> i.value) == List("`my mod`", "`sub pkg`") mod.imports.first.alias!!.value == "`my alias`" @@ -210,7 +210,7 @@ facts { (ta.type as syntax.DeclaredTypeNode).name.value == "`My Class`" mod.properties.first.identifier.value == "`my prop`" - mod.properties.first.modifiers == List("const") + mod.properties.first.modifiers == new Listing { "const" } } ["quoted identifiers in object bodies and accesses"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl index f4b49add4..c6b22c4c4 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl @@ -24,7 +24,7 @@ facts { local b = body("local name: String = \"hello\"") b.properties.length == 1 local prop = b.properties.first - prop.modifiers == List("local") + prop.modifiers == new Listing { "local" } prop.typeAnnotation != null prop.typeAnnotation is syntax.DeclaredTypeNode } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl index 1ef391866..9839a7b5c 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl @@ -23,7 +23,8 @@ facts { if (n.type == "identifier" && n.text == "foo") Pair((n) { text = "bar" }, true) else - null) == fmt("bar = bar + 1") + null + ) == fmt("bar = bar + 1") } ["replace a leaf via a typed node"] { @@ -31,7 +32,8 @@ facts { if (n.type == "int_literal_expr" && n.text == "0") Pair(new syntax.IntLiteralExprNode { value = 100 }.builtNode, false) else - null) == fmt("x = 100") + null + ) == fmt("x = 100") } ["rebuilds ancestors of a changed node"] { @@ -41,7 +43,8 @@ facts { else if (n.type == "int_literal_expr" && n.text == "0") Pair(new syntax.IntLiteralExprNode { value = 100 }.builtNode, false) else - null) == fmt("x = if (cond) 42 else 100") + null + ) == fmt("x = if (cond) 42 else 100") } ["descend reprocesses emitted nodes"] { @@ -56,7 +59,8 @@ facts { else if (n.type == "int_literal_expr" && n.text == "1") Pair(new syntax.IntLiteralExprNode { value = 2 }.builtNode, true) else - null) == fmt("x = (2)") + null + ) == fmt("x = (2)") } ["descend = false leaves the emitted subtree untouched"] { @@ -71,7 +75,8 @@ facts { else if (n.type == "int_literal_expr" && n.text == "1") Pair(new syntax.IntLiteralExprNode { value = 2 }.builtNode, true) else - null) == fmt("x = (1)") + null + ) == fmt("x = (1)") } ["build super access from scratch"] { @@ -85,19 +90,21 @@ facts { false, ) else - null) == fmt("x = super.foo") + null + ) == fmt("x = super.foo") walkFormat("x = 0", (n) -> if (n.type == "int_literal_expr") Pair( new syntax.SuperAccessExprNode { identifier = new syntax.IdentifierNode { value = "foo" } - arguments = List(new syntax.IntLiteralExprNode { value = 1 }) + arguments { new syntax.IntLiteralExprNode { value = 1 } } }.builtNode, false, ) else - null) == fmt("x = super.foo(1)") + null + ) == fmt("x = super.foo(1)") } ["build super subscript from scratch"] { @@ -110,6 +117,7 @@ facts { false, ) else - null) == fmt("x = super[0]") + null + ) == fmt("x = super[0]") } } diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 8c42290ce..166bfca0b 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -289,19 +289,19 @@ class ModuleNode extends Node { declaration: ModuleDeclarationNode? /// All imports in this module. - imports: List + imports: Listing /// All class declarations in this module. - classes: List + classes: Listing /// All typealias declarations in this module. - typeAliases: List + typeAliases: Listing /// All top-level properties in this module. - properties: List + properties: Listing /// All top-level methods in this module. - methods: List + methods: Listing } /// A module declaration (including doc comment, annotations, modifiers, name, amends/extends). @@ -310,12 +310,12 @@ class ModuleDeclarationNode extends Node { docComment: DocCommentNode? /// Annotations on the module declaration. - annotations: List + annotations: Listing /// The modifiers on the module declaration. /// /// An amending module cannot have any modifiers. - modifiers: List<"abstract" | "open">(isDistinct) + modifiers: Listing<"abstract" | "open">(isDistinct) /// The qualified name of the module. name: QualifiedIdentifierNode? @@ -351,16 +351,16 @@ class ClassNode extends Node { docComment: DocCommentNode? /// Annotations on the class. - annotations: List + annotations: Listing /// The modifiers on the class. - modifiers: List<"abstract" | "open" | "local" | "external">(isDistinct) + modifiers: Listing<"abstract" | "open" | "local" | "external">(isDistinct) /// The class name. identifier: IdentifierNode /// The type parameters. - typeParameters: List + typeParameters: Listing /// The supertype this class extends. superType: TypeNode? @@ -375,16 +375,16 @@ class TypeAliasNode extends Node { docComment: DocCommentNode? /// Annotations on the typealias. - annotations: List + annotations: Listing /// The modifiers on the typealias. - modifiers: List<"local" | "external">(isDistinct) + modifiers: Listing<"local" | "external">(isDistinct) /// The typealias name. identifier: IdentifierNode /// The type parameters. - typeParameters: List + typeParameters: Listing /// The type that this alias resolves to. type: TypeNode @@ -393,10 +393,10 @@ class TypeAliasNode extends Node { /// A class body delimited by braces. class ClassBodyNode extends Node { /// Properties declared in this class body. - properties: List + properties: Listing /// Methods declared in this class body. - methods: List + methods: Listing } /// A class property declaration. @@ -405,12 +405,12 @@ class ClassPropertyNode extends Node { docComment: DocCommentNode? /// Annotations on the property. - annotations: List + annotations: Listing /// The modifiers on the property. /// /// The `abstract` modifier is accepted for backwards compatibility, but has no effect. - modifiers: List<"abstract" | "local" | "hidden" | "external" | "fixed" | "const">(isDistinct) + modifiers: Listing<"abstract" | "local" | "hidden" | "external" | "fixed" | "const">(isDistinct) /// The property name. identifier: IdentifierNode @@ -422,7 +422,7 @@ class ClassPropertyNode extends Node { value: ExprNode? /// Object bodies for amending (from `{ ... }` blocks). - objectBodies: List + objectBodies: Listing } /// A class method declaration. @@ -431,19 +431,19 @@ class ClassMethodNode extends Node { docComment: DocCommentNode? /// Annotations on the method. - annotations: List + annotations: Listing /// The modifiers on the method. - modifiers: List<"abstract" | "local" | "external" | "const">(isDistinct) + modifiers: Listing<"abstract" | "local" | "external" | "const">(isDistinct) /// The method name. identifier: IdentifierNode /// The type parameters. - typeParameters: List + typeParameters: Listing /// The parameters. - parameters: List + parameters: Listing /// The return type annotation. returnType: TypeNode? @@ -455,37 +455,37 @@ class ClassMethodNode extends Node { /// An object body delimited by braces. class ObjectBodyNode extends Node { /// Parameters for this object body (e.g., `{ x, y -> ... }`). - parameters: List + parameters: Listing /// Properties declared in this object body. - properties: List + properties: Listing /// Methods declared in this object body. - methods: List + methods: Listing /// Elements declared in this object body. - elements: List + elements: Listing /// Entries declared in this object body. - entries: List + entries: Listing /// Spreads declared in this object body. - spreads: List + spreads: Listing /// Member predicates declared in this object body. - memberPredicates: List + memberPredicates: Listing /// `for` generators declared in this object body. - forGenerators: List + forGenerators: Listing /// `when` generators declared in this object body. - whenGenerators: List + whenGenerators: Listing } /// An object property declaration. class ObjectPropertyNode extends Node { /// The modifiers on the property. - modifiers: List<"local" | "const">(isDistinct) + modifiers: Listing<"local" | "const">(isDistinct) /// The property name. identifier: IdentifierNode @@ -497,22 +497,22 @@ class ObjectPropertyNode extends Node { value: ExprNode? /// Object bodies for amending. - objectBodies: List + objectBodies: Listing } /// An object method declaration. class ObjectMethodNode extends Node { /// The modifiers on the method. - modifiers: List<"local" | "const">(isDistinct) + modifiers: Listing<"local" | "const">(isDistinct) /// The method name. identifier: IdentifierNode /// The type parameters. - typeParameters: List + typeParameters: Listing /// The parameters. - parameters: List + parameters: Listing /// The return type annotation. returnType: TypeNode? @@ -536,7 +536,7 @@ class ObjectEntryNode extends Node { value: ExprNode? /// Object bodies for amending. - objectBodies: List + objectBodies: Listing } /// An object spread (`...expr` or `...?expr`). @@ -557,7 +557,7 @@ class MemberPredicateNode extends Node { value: ExprNode(objectBodies.isEmpty)? /// Object bodies for amending. - objectBodies: List + objectBodies: Listing } /// A `for (param in iterable) { ... }` generator. @@ -620,7 +620,7 @@ class FloatLiteralExprNode extends ExprNode { /// A single-line string literal expression. class SingleLineStringLiteralExprNode extends ExprNode { /// The string parts (chars, escapes, interpolations). - parts: List + parts: Listing } /// A multi-line string literal expression. @@ -628,7 +628,7 @@ class SingleLineStringLiteralExprNode extends ExprNode { /// Use [StringNewlineNode] entries in [parts] to separate lines. class MultiLineStringLiteralExprNode extends ExprNode { /// The string parts (chars, escapes, newlines, interpolations). - parts: List + parts: Listing } /// An unqualified access expression (`name` or `name(args)`). @@ -637,7 +637,7 @@ class UnqualifiedAccessExprNode extends ExprNode { identifier: IdentifierNode /// The arguments, if this is a function call. Null for a plain identifier access. - arguments: List? + arguments: Listing? } /// A qualified access expression (`receiver.member` or `receiver?.member`, @@ -653,7 +653,7 @@ class QualifiedAccessExprNode extends ExprNode { identifier: IdentifierNode /// The arguments, if this is a method call. Null for a property access. - arguments: List? + arguments: Listing? } /// A subscript expression (`receiver[index]`). @@ -671,7 +671,7 @@ class SuperAccessExprNode extends ExprNode { identifier: IdentifierNode /// The arguments, if this is a method call. Null for a property access. - arguments: List? + arguments: Listing? } /// A `super[index]` subscript expression. @@ -851,7 +851,7 @@ class NonNullExprNode extends ExprNode { /// A function literal expression (`(params) -> body`). class FunctionLiteralExprNode extends ExprNode { /// The parameters. - parameters: List + parameters: Listing /// The body expression. body: ExprNode @@ -878,7 +878,7 @@ class DeclaredTypeNode extends TypeNode { name: QualifiedIdentifierNode /// The type arguments. - typeArguments: List + typeArguments: Listing } /// A nullable type (`Type?`). @@ -890,13 +890,13 @@ class NullableTypeNode extends TypeNode { /// A union type (`TypeA|TypeB|TypeC`). class UnionTypeNode extends TypeNode { /// The member types. - members: List + members: Listing } /// A function type (`(ParamTypes) -> ReturnType`). class FunctionTypeNode extends TypeNode { /// The parameter types. - parameterTypes: List + parameterTypes: Listing /// The return type. returnType: TypeNode @@ -908,7 +908,7 @@ class ConstrainedTypeNode extends TypeNode { baseType: TypeNode /// The constraint expressions. - constraints: List + constraints: Listing } /// A parenthesized type (`(Type)`). @@ -962,7 +962,7 @@ class IdentifierNode extends Node { /// A qualified (dotted) identifier, e.g. `foo.bar.baz`. class QualifiedIdentifierNode extends Node { /// The parts of the qualified identifier. - identifiers: List + identifiers: Listing /// The dotted name (e.g. `"foo.bar.baz"`). value: String From c6bf5cd0923589686c92254d0117467efb6fcfbe Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Tue, 4 Aug 2026 16:12:12 +0200 Subject: [PATCH 36/49] Add members property to ModuleNode --- .../org/pkl/core/stdlib/syntax/NodeNodes.java | 5 +- .../pkl/core/stdlib/syntax/ParserNodes.java | 38 ++-- .../input/syntax/expressions.pkl | 8 +- .../input/syntax/moduleStructure.pkl | 185 ++++++++++++------ .../input/syntax/objectMembers.pkl | 4 +- .../input/syntax/types.pkl | 6 +- .../output/syntax/moduleStructure.pcf | 16 ++ stdlib/syntax.pkl | 30 ++- 8 files changed, 195 insertions(+), 97 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java index 3935b133f..23e6db37e 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java @@ -193,10 +193,7 @@ private static VmTyped buildModule(VmTyped self) { if (!imports.isEmpty()) { children.add(branch("import_list", buildAll(imports))); } - children.addAll(buildAll(listMember(self, "classes"))); - children.addAll(buildAll(listMember(self, "typeAliases"))); - children.addAll(buildAll(listMember(self, "properties"))); - children.addAll(buildAll(listMember(self, "methods"))); + children.addAll(buildAll(listMember(self, "members"))); return branch("module", children); } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 6a985620a..608e105a1 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -457,10 +457,7 @@ private static VmObjectFactory typeOpExprNodeFactory(Supplier .addProperty("genericNode", vm -> vm) .addProperty("declaration", ParserNodes::moduleDeclaration) .addListingProperty("imports", ParserNodes::moduleImports) - .addListingProperty("classes", ParserNodes::moduleClasses) - .addListingProperty("typeAliases", ParserNodes::moduleTypeAliases) - .addListingProperty("properties", ParserNodes::moduleProperties) - .addListingProperty("methods", ParserNodes::moduleMethods); + .addListingProperty("members", ParserNodes::moduleMembers); private static Object moduleDeclaration(VmTyped moduleVm) { var declVm = findChildVm(moduleVm, NodeType.MODULE_DECLARATION); @@ -1090,8 +1087,25 @@ private static String extendsOrAmendsClauseKeyword(VmTyped clauseVm) { return data.node.type == NodeType.AMENDS_CLAUSE ? "amends" : "extends"; } - private static VmListing moduleClasses(VmTyped moduleVm) { - return wrapAll(findChildrenVm(moduleVm, NodeType.CLASS), classNodeFactory); + // The module's classes, typealiases, properties, and methods, in source order. + private static VmListing moduleMembers(VmTyped moduleVm) { + var data = (GenericNodeData) moduleVm.getExtraStorage(); + var children = data.node.children; + var result = new ArrayList<>(); + for (var i = 0; i < children.size(); i++) { + var factory = + switch (children.get(i).type) { + case CLASS -> classNodeFactory; + case TYPEALIAS -> typeAliasNodeFactory; + case CLASS_PROPERTY -> classPropertyNodeFactory; + case CLASS_METHOD -> classMethodNodeFactory; + default -> null; + }; + if (factory != null) { + result.add(factory.create((VmTyped) data.childrenVm.get(i))); + } + } + return listingOf(result.toArray()); } private static VmListing classModifiers(VmTyped classVm) { @@ -1125,10 +1139,6 @@ private static Object classBody(VmTyped classVm) { return body == null ? VmNull.withoutDefault() : classBodyNodeFactory.create(body); } - private static VmListing moduleTypeAliases(VmTyped moduleVm) { - return wrapAll(findChildrenVm(moduleVm, NodeType.TYPEALIAS), typeAliasNodeFactory); - } - private static VmListing typeAliasModifiers(VmTyped typeAliasVm) { var header = findChildVm(typeAliasVm, NodeType.TYPEALIAS_HEADER); return header == null ? VmListing.empty() : modifiersOf(header); @@ -1151,14 +1161,6 @@ private static VmTyped typeAliasType(VmTyped typeAliasVm) { return wrapType(type); } - private static VmListing moduleProperties(VmTyped moduleVm) { - return wrapAll(findChildrenVm(moduleVm, NodeType.CLASS_PROPERTY), classPropertyNodeFactory); - } - - private static VmListing moduleMethods(VmTyped moduleVm) { - return wrapAll(findChildrenVm(moduleVm, NodeType.CLASS_METHOD), classMethodNodeFactory); - } - private static @Nullable VmTyped classPropertyHeaderBegin(VmTyped propertyVm) { var propHeader = findChildVm(propertyVm, NodeType.CLASS_PROPERTY_HEADER); return propHeader == null diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl index 2384f650c..ae0bb9640 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl @@ -3,7 +3,7 @@ amends "../snippetTest.pkl" import "pkl:syntax" local function expr(source: String) = - new syntax.Parser {}.parseModule("x = \(source)").properties.first.value + new syntax.Parser {}.parseModule("x = \(source)").properties["x"].value facts { ["literals"] { @@ -249,7 +249,7 @@ facts { """, ) mod.classes.length == 1 - local cls = mod.classes.first + local cls = mod.classes["Person"] cls.identifier.value == "Person" cls.modifiers == new Listing { "abstract"; "open" } cls.docComment != null @@ -265,7 +265,7 @@ facts { ["generic class"] { local mod = new syntax.Parser {}.parseModule("class Box { value: T }") - local cls = mod.classes.first + local cls = mod.classes["Box"] cls.identifier.value == "Box" cls.typeParameters.length == 1 cls.typeParameters.first.identifier.value == "T" @@ -274,7 +274,7 @@ facts { ["minimal class"] { local mod = new syntax.Parser {}.parseModule("class Empty") - local cls = mod.classes.first + local cls = mod.classes["Empty"] cls.identifier.value == "Empty" cls.modifiers.isEmpty cls.docComment == null diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl index d85c53d39..e27401767 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl @@ -18,11 +18,14 @@ facts { } ["module with modifiers and doc comment"] { - local mod = parse(""" - /// This is my module. - @Deprecated { message = "use other" } - open module my.mod - """) + local mod = + parse( + """ + /// This is my module. + @Deprecated { message = "use other" } + open module my.mod + """, + ) mod.declaration != null mod.declaration!!.docComment != null mod.declaration!!.docComment!!.value == "This is my module." @@ -51,11 +54,14 @@ facts { } ["imports"] { - local mod = parse(""" - import "foo.pkl" - import "bar.pkl" as myBar - import* "*.pkl" - """) + local mod = + parse( + """ + import "foo.pkl" + import "bar.pkl" as myBar + import* "*.pkl" + """, + ) mod.imports.length == 3 mod.imports[0].uri == "foo.pkl" @@ -70,15 +76,18 @@ facts { } ["class declaration"] { - local mod = parse(""" - /// A bird class. - abstract class Bird { - name: String - function fly(speed: Int): Boolean = true - } - """) + local mod = + parse( + """ + /// A bird class. + abstract class Bird { + name: String + function fly(speed: Int): Boolean = true + } + """, + ) mod.classes.length == 1 - local cls = mod.classes.first + local cls = mod.classes["Bird"] cls.docComment != null cls.docComment!!.value == "A bird class." @@ -108,12 +117,15 @@ facts { } ["class with extends and type parameters"] { - local mod = parse(""" - class Container extends Base { - item: T - } - """) - local cls = mod.classes.first + local mod = + parse( + """ + class Container extends Base { + item: T + } + """, + ) + local cls = mod.classes["Container"] cls.identifier.value == "Container" cls.typeParameters != null @@ -128,39 +140,42 @@ facts { ["typealias"] { local mod = parse("typealias Positive = Int(this > 0)") mod.typeAliases.length == 1 - local ta = mod.typeAliases.first + local ta = mod.typeAliases["Positive"] ta.identifier.value == "Positive" ta.typeParameters.isEmpty ta.type is syntax.ConstrainedTypeNode } ["top-level properties and methods"] { - local mod = parse(""" - hidden name: String = "pkl" - local count: Int = 42 - function greet(who: String): String = "hi" - """) + local mod = + parse( + """ + hidden name: String = "pkl" + local count: Int = 42 + function greet(who: String): String = "hi" + """, + ) mod.properties.length == 2 - mod.properties[0].identifier.value == "name" - mod.properties[0].modifiers != null - mod.properties[0].modifiers == new Listing { "hidden" } - mod.properties[0].value is syntax.SingleLineStringLiteralExprNode + mod.properties["name"].identifier.value == "name" + mod.properties["name"].modifiers != null + mod.properties["name"].modifiers == new Listing { "hidden" } + mod.properties["name"].value is syntax.SingleLineStringLiteralExprNode - mod.properties[1].identifier.value == "count" - mod.properties[1].modifiers != null - mod.properties[1].modifiers == new Listing { "local" } + mod.properties["count"].identifier.value == "count" + mod.properties["count"].modifiers != null + mod.properties["count"].modifiers == new Listing { "local" } mod.methods.length == 1 - mod.methods.first.identifier.value == "greet" - mod.methods.first.parameters.length == 1 - mod.methods.first.returnType != null - mod.methods.first.body is syntax.SingleLineStringLiteralExprNode + mod.methods["greet"].identifier.value == "greet" + mod.methods["greet"].parameters.length == 1 + mod.methods["greet"].returnType != null + mod.methods["greet"].body is syntax.SingleLineStringLiteralExprNode } ["parameter variations"] { local mod = parse("function f(x: Int, _, y): Boolean = true") - local params = mod.methods.first.parameters + local params = mod.methods["f"].parameters params.length == 3 @@ -179,43 +194,47 @@ facts { // `value` carries the identifier verbatim, so quoted identifiers keep their backticks ["quoted identifiers"] { - local mod = parse(""" - module `my mod`.`sub pkg` + local mod = + parse( + """ + module `my mod`.`sub pkg` - import "foo.pkl" as `my alias` + import "foo.pkl" as `my alias` - class `My Class` { - `a prop`: String - function `do it`(`the arg`: Int) = `the arg` - } + class `My Class` { + `a prop`: String + function `do it`(`the arg`: Int) = `the arg` + } - typealias `My Alias` = `My Class` + typealias `My Alias` = `My Class` - const `my prop` = 0 - """) + const `my prop` = 0 + """, + ) mod.declaration!!.name!!.value == "`my mod`.`sub pkg`" - mod.declaration!!.name!!.identifiers.toList().map((i) -> i.value) == List("`my mod`", "`sub pkg`") + mod.declaration!!.name!!.identifiers.toList().map((i) -> i.value) + == List("`my mod`", "`sub pkg`") mod.imports.first.alias!!.value == "`my alias`" - local cls = mod.classes.first + local cls = mod.classes["`My Class`"] cls.identifier.value == "`My Class`" cls.body!!.properties.first.identifier.value == "`a prop`" cls.body!!.methods.first.identifier.value == "`do it`" cls.body!!.methods.first.parameters.first.identifier!!.value == "`the arg`" - local ta = mod.typeAliases.first + local ta = mod.typeAliases["`My Alias`"] ta.identifier.value == "`My Alias`" (ta.type as syntax.DeclaredTypeNode).name.value == "`My Class`" - mod.properties.first.identifier.value == "`my prop`" - mod.properties.first.modifiers == new Listing { "const" } + mod.properties["`my prop`"].identifier.value == "`my prop`" + mod.properties["`my prop`"].modifiers == new Listing { "const" } } ["quoted identifiers in object bodies and accesses"] { local mod = parse("x { `inner prop` = `some`.`path` }") - local prop = mod.properties.first.objectBodies.first.properties.first + local prop = mod.properties["x"].objectBodies.first.properties.first prop.identifier.value == "`inner prop`" local access = prop.value as syntax.QualifiedAccessExprNode @@ -228,6 +247,56 @@ facts { result == null } + ["members keep source order"] { + local mod = + parse( + """ + a = 1 + typealias A = Int + function f() = 0 + class C { + z: Int + } + b = 2 + """, + ) + + mod.members.length == 5 + mod.members[0] is syntax.ClassPropertyNode + mod.members[1] is syntax.TypeAliasNode + mod.members[2] is syntax.ClassMethodNode + mod.members[3] is syntax.ClassNode + mod.members[4] is syntax.ClassPropertyNode + + // the specific-member views index `members` by name + mod.properties.keys == Set("a", "b") + mod.typeAliases.keys == Set("A") + mod.methods.keys == Set("f") + mod.classes.keys == Set("C") + mod.classes["C"] == mod.members[3] + } + + ["building a module keeps member order"] { + local mod = + parse( + """ + a = 1 + typealias A = Int + function f() = 0 + b = 2 + """, + ) + + mod.builtNode.render() + == """ + a = 1 + typealias A = Int + function f() = 0 + b = 2 + + """ + } + ["empty module"] { local result = parse("") result.declaration == null diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl index c6b22c4c4..b0991ab34 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl @@ -6,7 +6,7 @@ local function parse(source: String) = new syntax.Parser {}.parseModule(source) local function body(source: String) = let (result = parse("x { \(source) }")) - result.properties.first.objectBodies.first + result.properties["x"].objectBodies.first facts { ["object property"] { @@ -134,7 +134,7 @@ facts { ["object body with parameters"] { local mod = parse("x = new Listing { a, b -> a }") - local propVal = mod.properties.first.value + local propVal = mod.properties["x"].value propVal is syntax.NewExprNode local newBody = (propVal as syntax.NewExprNode).body newBody.parameters.length == 2 diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl index 8a5579bc8..e7b9cb406 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl @@ -5,7 +5,7 @@ import "pkl:syntax" local function parse(source: String): syntax.ModuleNode = new syntax.Parser {}.parseModule(source) local function typeOf(typeSource: String) = - parse("x: \(typeSource) = 0").properties.first.typeAnnotation + parse("x: \(typeSource) = 0").properties["x"].typeAnnotation facts { ["simple types"] { @@ -81,7 +81,7 @@ facts { ["string constant type"] { local result = parse(#"typealias Foo = "bar"|"baz""#) - local ta = result.typeAliases.first + local ta = result.typeAliases["Foo"] ta.type is syntax.UnionTypeNode local members = (ta.type as syntax.UnionTypeNode).members members.length == 2 @@ -93,7 +93,7 @@ facts { ["type annotation"] { local result = parse("x: String = \"hello\"") - local prop = result.properties.first + local prop = result.properties["x"] prop.typeAnnotation != null prop.typeAnnotation!! is syntax.DeclaredTypeNode } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf index 13299c0f2..4661e0bac 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf @@ -124,6 +124,22 @@ facts { ["parser error"] { true } + ["members keep source order"] { + true + true + true + true + true + true + true + true + true + true + true + } + ["building a module keeps member order"] { + true + } ["empty module"] { true true diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 166bfca0b..f6643973f 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -283,6 +283,10 @@ typealias ObjectMemberNode = | ForGeneratorNode | WhenGeneratorNode +/// A member of a [ModuleNode]. +typealias ModuleMember = ClassNode | TypeAliasNode | ClassPropertyNode | ClassMethodNode + +// noinspection TypeMismatch /// The top-level module node. class ModuleNode extends Node { /// The module declaration. @@ -291,17 +295,27 @@ class ModuleNode extends Node { /// All imports in this module. imports: Listing - /// All class declarations in this module. - classes: Listing + /// The members of this module, in source order. + members: Listing - /// All typealias declarations in this module. - typeAliases: Listing + local const function byIdentifier(members: List) = + members.toMap((it) -> it.identifier.value, (it) -> it).toMapping() - /// All top-level properties in this module. - properties: Listing + /// All class declarations in this module, keyed by class name. + fixed classes: Mapping = + byIdentifier(members.toList().filterIsInstance(ClassNode)) - /// All top-level methods in this module. - methods: Listing + /// All typealias declarations in this module, keyed by typealias name. + fixed typeAliases: Mapping = + byIdentifier(members.toList().filterIsInstance(TypeAliasNode)) + + /// All top-level properties in this module, keyed by property name. + fixed properties: Mapping = + byIdentifier(members.toList().filterIsInstance(ClassPropertyNode)) + + /// All top-level methods in this module, keyed by method name. + fixed methods: Mapping = + byIdentifier(members.toList().filterIsInstance(ClassMethodNode)) } /// A module declaration (including doc comment, annotations, modifiers, name, amends/extends). From 31e951bd64f7c1887af683153e37944c361c6944 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Tue, 4 Aug 2026 16:47:26 +0200 Subject: [PATCH 37/49] Add members property to ClassBodyNode --- .../org/pkl/core/stdlib/syntax/NodeNodes.java | 4 +-- .../pkl/core/stdlib/syntax/ParserNodes.java | 24 +++++-------- .../input/syntax/moduleStructure.pkl | 34 +++++++++---------- stdlib/syntax.pkl | 28 +++++++++++---- 4 files changed, 47 insertions(+), 43 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java index 23e6db37e..c9006c36e 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java @@ -269,9 +269,7 @@ private static VmTyped buildTypeAlias(VmTyped self) { } private static VmTyped buildClassBody(VmTyped self) { - var members = new ArrayList<>(); - members.addAll(buildAll(listMember(self, "properties"))); - members.addAll(buildAll(listMember(self, "methods"))); + var members = buildAll(listMember(self, "members")); var children = new ArrayList<>(); children.add(terminal("{")); if (!members.isEmpty()) { diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 608e105a1..9cca80744 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -449,15 +449,14 @@ private static VmObjectFactory typeOpExprNodeFactory(Supplier private static final VmObjectFactory classBodyNodeFactory = new VmObjectFactory(SyntaxModule::getClassBodyNodeClass) .addProperty("genericNode", vm -> vm) - .addListingProperty("properties", ParserNodes::classBodyProperties) - .addListingProperty("methods", ParserNodes::classBodyMethods); + .addListingProperty("members", ParserNodes::classBodyMembers); private static final VmObjectFactory moduleNodeFactory = new VmObjectFactory(SyntaxModule::getModuleNodeClass) .addProperty("genericNode", vm -> vm) .addProperty("declaration", ParserNodes::moduleDeclaration) .addListingProperty("imports", ParserNodes::moduleImports) - .addListingProperty("members", ParserNodes::moduleMembers); + .addListingProperty("members", ParserNodes::memberChildren); private static Object moduleDeclaration(VmTyped moduleVm) { var declVm = findChildVm(moduleVm, NodeType.MODULE_DECLARATION); @@ -1087,9 +1086,10 @@ private static String extendsOrAmendsClauseKeyword(VmTyped clauseVm) { return data.node.type == NodeType.AMENDS_CLAUSE ? "amends" : "extends"; } - // The module's classes, typealiases, properties, and methods, in source order. - private static VmListing moduleMembers(VmTyped moduleVm) { - var data = (GenericNodeData) moduleVm.getExtraStorage(); + // The typed classes, typealiases, properties, and methods among `ownerVm`'s children, + // in source order. + private static VmListing memberChildren(VmTyped ownerVm) { + var data = (GenericNodeData) ownerVm.getExtraStorage(); var children = data.node.children; var result = new ArrayList<>(); for (var i = 0; i < children.size(); i++) { @@ -1214,20 +1214,12 @@ private static Object classMethodBody(VmTyped methodVm) { return exprInChild(methodVm, NodeType.CLASS_METHOD_BODY); } - private static VmListing classBodyProperties(VmTyped classBodyVm) { + private static VmListing classBodyMembers(VmTyped classBodyVm) { var elements = findChildVm(classBodyVm, NodeType.CLASS_BODY_ELEMENTS); if (elements == null) { return VmListing.empty(); } - return wrapAll(findChildrenVm(elements, NodeType.CLASS_PROPERTY), classPropertyNodeFactory); - } - - private static VmListing classBodyMethods(VmTyped classBodyVm) { - var elements = findChildVm(classBodyVm, NodeType.CLASS_BODY_ELEMENTS); - if (elements == null) { - return VmListing.empty(); - } - return wrapAll(findChildrenVm(elements, NodeType.CLASS_METHOD), classMethodNodeFactory); + return memberChildren(elements); } private static VmListing moduleImports(VmTyped moduleVm) { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl index e27401767..52ffee8f6 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl @@ -102,18 +102,18 @@ facts { cls.body != null cls.body!!.properties.length == 1 - cls.body!!.properties.first.identifier.value == "name" - cls.body!!.properties.first.typeAnnotation != null - cls.body!!.properties.first.typeAnnotation is syntax.DeclaredTypeNode - cls.body!!.properties.first.value == null + cls.body!!.properties["name"].identifier.value == "name" + cls.body!!.properties["name"].typeAnnotation != null + cls.body!!.properties["name"].typeAnnotation is syntax.DeclaredTypeNode + cls.body!!.properties["name"].value == null cls.body!!.methods.length == 1 - cls.body!!.methods.first.identifier.value == "fly" - cls.body!!.methods.first.parameters.length == 1 - cls.body!!.methods.first.parameters.first.identifier!!.value == "speed" - cls.body!!.methods.first.returnType != null - cls.body!!.methods.first.body != null - cls.body!!.methods.first.body is syntax.BooleanLiteralExprNode + cls.body!!.methods["fly"].identifier.value == "fly" + cls.body!!.methods["fly"].parameters.length == 1 + cls.body!!.methods["fly"].parameters.first.identifier!!.value == "speed" + cls.body!!.methods["fly"].returnType != null + cls.body!!.methods["fly"].body != null + cls.body!!.methods["fly"].body is syntax.BooleanLiteralExprNode } ["class with extends and type parameters"] { @@ -218,18 +218,18 @@ facts { mod.imports.first.alias!!.value == "`my alias`" - local cls = mod.classes["`My Class`"] + local cls = mod.classes["My Class"] cls.identifier.value == "`My Class`" - cls.body!!.properties.first.identifier.value == "`a prop`" - cls.body!!.methods.first.identifier.value == "`do it`" - cls.body!!.methods.first.parameters.first.identifier!!.value == "`the arg`" + cls.body!!.properties["a prop"].identifier.value == "`a prop`" + cls.body!!.methods["do it"].identifier.value == "`do it`" + cls.body!!.methods["do it"].parameters.first.identifier!!.value == "`the arg`" - local ta = mod.typeAliases["`My Alias`"] + local ta = mod.typeAliases["My Alias"] ta.identifier.value == "`My Alias`" (ta.type as syntax.DeclaredTypeNode).name.value == "`My Class`" - mod.properties["`my prop`"].identifier.value == "`my prop`" - mod.properties["`my prop`"].modifiers == new Listing { "const" } + mod.properties["my prop"].identifier.value == "`my prop`" + mod.properties["my prop"].modifiers == new Listing { "const" } } ["quoted identifiers in object bodies and accesses"] { diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index f6643973f..088d23685 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -286,6 +286,14 @@ typealias ObjectMemberNode = /// A member of a [ModuleNode]. typealias ModuleMember = ClassNode | TypeAliasNode | ClassPropertyNode | ClassMethodNode +/// Indexes [members] by name, stripping the enclosing backticks of quoted identifiers. +local const function byIdentifier(members: List) = + members.toMap((it) -> unquote(it.identifier.value), (it) -> it).toMapping() + +/// Strips the enclosing backticks of a quoted identifier. +local const function unquote(identifier: String): String = + if (identifier.startsWith("`")) identifier.drop(1).dropLast(1) else identifier + // noinspection TypeMismatch /// The top-level module node. class ModuleNode extends Node { @@ -298,9 +306,6 @@ class ModuleNode extends Node { /// The members of this module, in source order. members: Listing - local const function byIdentifier(members: List) = - members.toMap((it) -> it.identifier.value, (it) -> it).toMapping() - /// All class declarations in this module, keyed by class name. fixed classes: Mapping = byIdentifier(members.toList().filterIsInstance(ClassNode)) @@ -404,13 +409,22 @@ class TypeAliasNode extends Node { type: TypeNode } +/// A member of a [ClassBodyNode]. +typealias ClassMember = ClassPropertyNode | ClassMethodNode + +// noinspection TypeMismatch /// A class body delimited by braces. class ClassBodyNode extends Node { - /// Properties declared in this class body. - properties: Listing + /// The members of this class body, in source order. + members: Listing - /// Methods declared in this class body. - methods: Listing + /// Properties declared in this class body, keyed by property name. + fixed properties: Mapping = + byIdentifier(members.toList().filterIsInstance(ClassPropertyNode)) + + /// Methods declared in this class body, keyed by method name. + fixed methods: Mapping = + byIdentifier(members.toList().filterIsInstance(ClassMethodNode)) } /// A class property declaration. From 817614b23ec12c26e833f1ad952dbe0cbd5be227 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Tue, 4 Aug 2026 16:54:20 +0200 Subject: [PATCH 38/49] Rename StringConstantTypeNode to StringLiteralTypeNode --- .../main/java/org/pkl/core/runtime/SyntaxModule.java | 8 ++++---- .../java/org/pkl/core/stdlib/syntax/NodeNodes.java | 2 +- .../java/org/pkl/core/stdlib/syntax/ParserNodes.java | 10 +++++----- .../files/LanguageSnippetTests/input/syntax/types.pkl | 8 ++++---- stdlib/syntax.pkl | 6 +++--- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java index 9353eac10..186556cf4 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java @@ -189,8 +189,8 @@ public static VmClass getParenthesizedTypeNodeClass() { return ParenthesizedTypeNodeClass.instance; } - public static VmClass getStringConstantTypeNodeClass() { - return StringConstantTypeNodeClass.instance; + public static VmClass getStringLiteralTypeNodeClass() { + return StringLiteralTypeNodeClass.instance; } public static VmClass getThisExprNodeClass() { @@ -537,8 +537,8 @@ private static final class ParenthesizedTypeNodeClass { static final VmClass instance = loadClass("ParenthesizedTypeNode"); } - private static final class StringConstantTypeNodeClass { - static final VmClass instance = loadClass("StringConstantTypeNode"); + private static final class StringLiteralTypeNodeClass { + static final VmClass instance = loadClass("StringLiteralTypeNode"); } private static final class ThisExprNodeClass { diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java index c9006c36e..b4d32a30a 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java @@ -165,7 +165,7 @@ yield branch( terminal("("), branch("parenthesized_type_elements", List.of(build(reqNode(self, "type")))), terminal(")"))); - case "StringConstantTypeNode" -> + case "StringLiteralTypeNode" -> branch("string_constant_type", List.of(stringCharsNode(str(self, "value")))); case "AnnotationNode" -> buildAnnotation(self); case "ParameterNode" -> buildParameter(self); diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 9cca80744..69761e8cb 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -188,10 +188,10 @@ private static VmObjectFactory genericNodeOnlyFactory(Supplier new VmObjectFactory(SyntaxModule::getParenthesizedTypeNodeClass) .addProperty("genericNode", vm -> vm) .addTypedProperty("type", ParserNodes::parenthesizedTypeType); - private static final VmObjectFactory stringConstantTypeNodeFactory = - new VmObjectFactory(SyntaxModule::getStringConstantTypeNodeClass) + private static final VmObjectFactory stringLiteralTypeNodeFactory = + new VmObjectFactory(SyntaxModule::getStringLiteralTypeNodeClass) .addProperty("genericNode", vm -> vm) - .addStringProperty("value", ParserNodes::stringConstantTypeValue); + .addStringProperty("value", ParserNodes::stringLiteralTypeValue); private static final VmObjectFactory thisExprNodeFactory = genericNodeOnlyFactory(SyntaxModule::getThisExprNodeClass); @@ -580,7 +580,7 @@ private static VmTyped parenthesizedTypeType(VmTyped typeVm) { return wrapType(type); } - private static String stringConstantTypeValue(VmTyped typeVm) { + private static String stringLiteralTypeValue(VmTyped typeVm) { var data = (GenericNodeData) typeVm.getExtraStorage(); return extractStringChars(data.node, data.source); } @@ -1478,7 +1478,7 @@ private static VmTyped wrapType(VmTyped typeVm) { case FUNCTION_TYPE -> functionTypeNodeFactory.create(typeVm); case CONSTRAINED_TYPE -> constrainedTypeNodeFactory.create(typeVm); case PARENTHESIZED_TYPE -> parenthesizedTypeNodeFactory.create(typeVm); - case STRING_CONSTANT_TYPE -> stringConstantTypeNodeFactory.create(typeVm); + case STRING_CONSTANT_TYPE -> stringLiteralTypeNodeFactory.create(typeVm); default -> throw new VmExceptionBuilder().bug("Unexpected type node: " + data.node.type).build(); }; diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl index e7b9cb406..be0a15762 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl @@ -85,10 +85,10 @@ facts { ta.type is syntax.UnionTypeNode local members = (ta.type as syntax.UnionTypeNode).members members.length == 2 - members[0] is syntax.StringConstantTypeNode - (members[0] as syntax.StringConstantTypeNode).value == "bar" - members[1] is syntax.StringConstantTypeNode - (members[1] as syntax.StringConstantTypeNode).value == "baz" + members[0] is syntax.StringLiteralTypeNode + (members[0] as syntax.StringLiteralTypeNode).value == "bar" + members[1] is syntax.StringLiteralTypeNode + (members[1] as syntax.StringLiteralTypeNode).value == "baz" } ["type annotation"] { diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 088d23685..b434ad00d 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -903,7 +903,7 @@ class ModuleTypeNode extends TypeNode {} /// A declared type (e.g., `String`, `List`). class DeclaredTypeNode extends TypeNode { /// The type name (dotted, e.g. `List` or `foo.Bar`). - name: QualifiedIdentifierNode + name: QualifiedIdentifierNode(identifiers.length.isBetween(1, 2)) /// The type arguments. typeArguments: Listing @@ -945,8 +945,8 @@ class ParenthesizedTypeNode extends TypeNode { type: TypeNode } -/// A string constant type (e.g., `"foo"`). -class StringConstantTypeNode extends TypeNode { +/// A string literal type (e.g., `"foo"`). +class StringLiteralTypeNode extends TypeNode { /// The string value. value: String } From b610c43295e02b2177bad18765ef7adff67f746e Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Tue, 4 Aug 2026 17:27:22 +0200 Subject: [PATCH 39/49] Reuse extra storage for building nodes --- .../src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java | 3 +++ stdlib/syntax.pkl | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java index b4d32a30a..8124cd5af 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java @@ -46,6 +46,9 @@ protected Object eval(VmTyped self) { } private static VmTyped build(VmTyped self) { + if (self.hasExtraStorage() && self.getExtraStorage() instanceof VmTyped genericNode) { + return genericNode; + } return switch (self.getVmClass().getSimpleName()) { case "ModuleNode" -> buildModule(self); case "ModuleDeclarationNode" -> buildModuleDeclaration(self); diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index b434ad00d..a50ab2c5c 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -262,7 +262,9 @@ abstract class Node { /// This node rebuilt into a [GenericNode]. /// - /// Always constructs a fresh node from this node's fields. + /// A node that is verbatim from [Parser.parseModule()] yields its [genericNode] unchanged, as + /// does any part of this node's subtree that was not modified. Modified and constructed nodes are + /// rebuilt from their fields. external fixed builtNode: GenericNode } From dde51521ffc839155a3fbd904ee983c4c5fe1b2c Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Tue, 4 Aug 2026 17:33:24 +0200 Subject: [PATCH 40/49] Remove render function from GenericNode --- stdlib/syntax.pkl | 3 --- 1 file changed, 3 deletions(-) diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index a50ab2c5c..78f2f1f6b 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -61,9 +61,6 @@ class GenericNode { /// The source location of this node or `null`. span: Span? - /// Render this node back to Pkl source code using default settings. - function render(): String = new Renderer {}.render(this) - /// Walk this node and its descendants top-down, applying [visit] to each node and /// returning the (possibly rewritten) tree. /// From af30e870ac377e1f1e150915f88b50132a1d69cd Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Thu, 6 Aug 2026 14:09:06 +0200 Subject: [PATCH 41/49] Add `name` property to identifiers --- .../org/pkl/core/stdlib/syntax/NodeNodes.java | 7 ++- .../pkl/core/stdlib/syntax/ParserNodes.java | 4 +- .../input/syntax/expressions.pkl | 20 +++---- .../input/syntax/moduleStructure.pkl | 55 ++++++++++--------- .../input/syntax/objectMembers.pkl | 18 +++--- .../input/syntax/render.pkl | 23 ++++---- .../input/syntax/spans.pkl | 3 +- .../input/syntax/walk.pkl | 40 ++++++++++++-- .../output/syntax/moduleStructure.pcf | 4 ++ .../output/syntax/render.pcf | 3 +- .../output/syntax/walk.pcf | 7 +++ stdlib/syntax.pkl | 32 +++++++---- 12 files changed, 140 insertions(+), 76 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java index 8124cd5af..0a2ff1ea4 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java @@ -31,6 +31,7 @@ import org.pkl.core.stdlib.ExternalPropertyNode; import org.pkl.core.stdlib.PklName; import org.pkl.core.stdlib.syntax.SyntaxNodes.SpanData; +import org.pkl.parser.Lexer; import org.pkl.parser.syntax.generic.FullSpan; public final class NodeNodes { @@ -173,7 +174,7 @@ yield branch( case "AnnotationNode" -> buildAnnotation(self); case "ParameterNode" -> buildParameter(self); case "TypeParameterNode" -> buildTypeParameter(self); - case "IdentifierNode" -> leaf("identifier", str(self, "value")); + case "IdentifierNode" -> leaf("identifier", identifierText(self)); case "QualifiedIdentifierNode" -> branch( "qualified_identifier", @@ -718,6 +719,10 @@ private static VmTyped buildCall(String type, String keyword, VmTyped inner) { return branch(type, List.of(terminal(keyword), terminal("("), inner, terminal(")"))); } + private static String identifierText(VmTyped self) { + return Lexer.maybeQuoteIdentifier(str(self, "name")); + } + // The doc comment (if any) followed by the annotations of a declaration. private static List docAndAnnotations(VmTyped self) { var result = new ArrayList<>(); diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 69761e8cb..02fdd3500 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -59,7 +59,7 @@ private ParserNodes() {} private static final VmObjectFactory identifierNodeFactory = new VmObjectFactory(SyntaxModule::getIdentifierNodeClass) .addProperty("genericNode", vm -> vm) - .addStringProperty("value", ParserNodes::identifierValue); + .addStringProperty("text", ParserNodes::identifierText); private static VmObjectFactory genericNodeOnlyFactory(Supplier classSupplier) { return new VmObjectFactory(classSupplier).addProperty("genericNode", vm -> vm); @@ -1248,7 +1248,7 @@ private static Object importAlias(VmTyped importVm) { return identifierNodeFactory.create(findChildVm(aliasVm, NodeType.IDENTIFIER)); } - private static String identifierValue(VmTyped identifierVm) { + private static String identifierText(VmTyped identifierVm) { var text = nodeText((GenericNodeData) identifierVm.getExtraStorage()); return text == null ? "" : text; } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl index ae0bb9640..fdad4dacf 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl @@ -90,12 +90,12 @@ facts { ["access expressions"] { local unqual = expr("foo") unqual is syntax.UnqualifiedAccessExprNode - (unqual as syntax.UnqualifiedAccessExprNode).identifier.value == "foo" + (unqual as syntax.UnqualifiedAccessExprNode).identifier.name == "foo" (unqual as syntax.UnqualifiedAccessExprNode).arguments == null local withArgs = expr("foo(1, 2)") withArgs is syntax.UnqualifiedAccessExprNode - (withArgs as syntax.UnqualifiedAccessExprNode).identifier.value == "foo" + (withArgs as syntax.UnqualifiedAccessExprNode).identifier.name == "foo" (withArgs as syntax.UnqualifiedAccessExprNode).arguments != null (withArgs as syntax.UnqualifiedAccessExprNode).arguments.length == 2 @@ -103,7 +103,7 @@ facts { qual is syntax.QualifiedAccessExprNode (qual as syntax.QualifiedAccessExprNode).receiver is syntax.UnqualifiedAccessExprNode (qual as syntax.QualifiedAccessExprNode).isNullSafe == false - (qual as syntax.QualifiedAccessExprNode).identifier.value == "bar" + (qual as syntax.QualifiedAccessExprNode).identifier.name == "bar" local nullSafe = expr("foo?.bar") nullSafe is syntax.QualifiedAccessExprNode @@ -174,7 +174,7 @@ facts { ["let expression"] { local letExpr = expr("let (y = 1) y + 1") letExpr is syntax.LetExprNode - (letExpr as syntax.LetExprNode).parameter.identifier!!.value == "y" + (letExpr as syntax.LetExprNode).parameter.identifier!!.name == "y" (letExpr as syntax.LetExprNode).bindingValue is syntax.IntLiteralExprNode (letExpr as syntax.LetExprNode).body is syntax.AdditionExprNode } @@ -189,8 +189,8 @@ facts { local fn = expr("(x, y) -> x + y") fn is syntax.FunctionLiteralExprNode (fn as syntax.FunctionLiteralExprNode).parameters.length == 2 - (fn as syntax.FunctionLiteralExprNode).parameters[0].identifier!!.value == "x" - (fn as syntax.FunctionLiteralExprNode).parameters[1].identifier!!.value == "y" + (fn as syntax.FunctionLiteralExprNode).parameters[0].identifier!!.name == "x" + (fn as syntax.FunctionLiteralExprNode).parameters[1].identifier!!.name == "y" (fn as syntax.FunctionLiteralExprNode).body is syntax.AdditionExprNode } @@ -250,7 +250,7 @@ facts { ) mod.classes.length == 1 local cls = mod.classes["Person"] - cls.identifier.value == "Person" + cls.identifier.name == "Person" cls.modifiers == new Listing { "abstract"; "open" } cls.docComment != null cls.docComment!!.value == "A person." @@ -266,16 +266,16 @@ facts { ["generic class"] { local mod = new syntax.Parser {}.parseModule("class Box { value: T }") local cls = mod.classes["Box"] - cls.identifier.value == "Box" + cls.identifier.name == "Box" cls.typeParameters.length == 1 - cls.typeParameters.first.identifier.value == "T" + cls.typeParameters.first.identifier.name == "T" cls.typeParameters.first.variance == "out" } ["minimal class"] { local mod = new syntax.Parser {}.parseModule("class Empty") local cls = mod.classes["Empty"] - cls.identifier.value == "Empty" + cls.identifier.name == "Empty" cls.modifiers.isEmpty cls.docComment == null cls.annotations.isEmpty diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl index 52ffee8f6..834f1beb7 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl @@ -69,7 +69,7 @@ facts { mod.imports[0].alias == null mod.imports[1].uri == "bar.pkl" - mod.imports[1].alias!!.value == "myBar" + mod.imports[1].alias!!.name == "myBar" mod.imports[2].uri == "*.pkl" mod.imports[2].keyword == "import*" @@ -95,22 +95,22 @@ facts { cls.modifiers != null cls.modifiers == new Listing { "abstract" } - cls.identifier.value == "Bird" + cls.identifier.name == "Bird" cls.typeParameters.isEmpty cls.superType == null cls.body != null cls.body!!.properties.length == 1 - cls.body!!.properties["name"].identifier.value == "name" + cls.body!!.properties["name"].identifier.name == "name" cls.body!!.properties["name"].typeAnnotation != null cls.body!!.properties["name"].typeAnnotation is syntax.DeclaredTypeNode cls.body!!.properties["name"].value == null cls.body!!.methods.length == 1 - cls.body!!.methods["fly"].identifier.value == "fly" + cls.body!!.methods["fly"].identifier.name == "fly" cls.body!!.methods["fly"].parameters.length == 1 - cls.body!!.methods["fly"].parameters.first.identifier!!.value == "speed" + cls.body!!.methods["fly"].parameters.first.identifier!!.name == "speed" cls.body!!.methods["fly"].returnType != null cls.body!!.methods["fly"].body != null cls.body!!.methods["fly"].body is syntax.BooleanLiteralExprNode @@ -127,10 +127,10 @@ facts { ) local cls = mod.classes["Container"] - cls.identifier.value == "Container" + cls.identifier.name == "Container" cls.typeParameters != null cls.typeParameters.length == 1 - cls.typeParameters.first.identifier.value == "T" + cls.typeParameters.first.identifier.name == "T" cls.typeParameters.first.variance == null cls.superType != null @@ -141,7 +141,7 @@ facts { local mod = parse("typealias Positive = Int(this > 0)") mod.typeAliases.length == 1 local ta = mod.typeAliases["Positive"] - ta.identifier.value == "Positive" + ta.identifier.name == "Positive" ta.typeParameters.isEmpty ta.type is syntax.ConstrainedTypeNode } @@ -157,17 +157,17 @@ facts { ) mod.properties.length == 2 - mod.properties["name"].identifier.value == "name" + mod.properties["name"].identifier.name == "name" mod.properties["name"].modifiers != null mod.properties["name"].modifiers == new Listing { "hidden" } mod.properties["name"].value is syntax.SingleLineStringLiteralExprNode - mod.properties["count"].identifier.value == "count" + mod.properties["count"].identifier.name == "count" mod.properties["count"].modifiers != null mod.properties["count"].modifiers == new Listing { "local" } mod.methods.length == 1 - mod.methods["greet"].identifier.value == "greet" + mod.methods["greet"].identifier.name == "greet" mod.methods["greet"].parameters.length == 1 mod.methods["greet"].returnType != null mod.methods["greet"].body is syntax.SingleLineStringLiteralExprNode @@ -179,7 +179,7 @@ facts { params.length == 3 - params[0].identifier!!.value == "x" + params[0].identifier!!.name == "x" params[0].typeAnnotation != null params[0].isBlankIdentifier == false @@ -187,7 +187,7 @@ facts { params[1].isBlankIdentifier == true params[1].typeAnnotation == null - params[2].identifier!!.value == "y" + params[2].identifier!!.name == "y" params[2].typeAnnotation == null params[2].isBlankIdentifier == false } @@ -213,33 +213,38 @@ facts { ) mod.declaration!!.name!!.value == "`my mod`.`sub pkg`" - mod.declaration!!.name!!.identifiers.toList().map((i) -> i.value) + mod.declaration!!.name!!.identifiers.toList().map((i) -> i.text) == List("`my mod`", "`sub pkg`") + mod.declaration!!.name!!.identifiers.toList().map((i) -> i.name) + == List("my mod", "sub pkg") - mod.imports.first.alias!!.value == "`my alias`" + mod.imports.first.alias!!.text == "`my alias`" + mod.imports.first.alias!!.name == "my alias" local cls = mod.classes["My Class"] - cls.identifier.value == "`My Class`" - cls.body!!.properties["a prop"].identifier.value == "`a prop`" - cls.body!!.methods["do it"].identifier.value == "`do it`" - cls.body!!.methods["do it"].parameters.first.identifier!!.value == "`the arg`" + cls.identifier.text == "`My Class`" + cls.identifier.name == "My Class" + cls.body!!.properties["a prop"].identifier.name == "a prop" + cls.body!!.methods["do it"].identifier.name == "do it" + cls.body!!.methods["do it"].parameters.first.identifier!!.name == "the arg" local ta = mod.typeAliases["My Alias"] - ta.identifier.value == "`My Alias`" + ta.identifier.name == "My Alias" (ta.type as syntax.DeclaredTypeNode).name.value == "`My Class`" - mod.properties["my prop"].identifier.value == "`my prop`" + mod.properties["my prop"].identifier.name == "my prop" mod.properties["my prop"].modifiers == new Listing { "const" } } ["quoted identifiers in object bodies and accesses"] { local mod = parse("x { `inner prop` = `some`.`path` }") local prop = mod.properties["x"].objectBodies.first.properties.first - prop.identifier.value == "`inner prop`" + prop.identifier.text == "`inner prop`" + prop.identifier.name == "inner prop" local access = prop.value as syntax.QualifiedAccessExprNode - access.identifier.value == "`path`" - (access.receiver as syntax.UnqualifiedAccessExprNode).identifier.value == "`some`" + access.identifier.name == "path" + (access.receiver as syntax.UnqualifiedAccessExprNode).identifier.name == "some" } ["parser error"] { @@ -287,7 +292,7 @@ facts { """, ) - mod.builtNode.render() + new syntax.Renderer {}.render(mod.builtNode) == """ a = 1 typealias A = Int diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl index b0991ab34..e989bb8cb 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl @@ -13,7 +13,7 @@ facts { local b = body("name = \"hello\"") b.properties.length == 1 local prop = b.properties.first - prop.identifier.value == "name" + prop.identifier.name == "name" prop.value is syntax.SingleLineStringLiteralExprNode prop.modifiers.isEmpty prop.typeAnnotation == null @@ -33,7 +33,7 @@ facts { local b = body("inner { x = 1 }") b.properties.length == 1 local prop = b.properties.first - prop.identifier.value == "inner" + prop.identifier.name == "inner" prop.value == null prop.objectBodies.length == 1 } @@ -42,9 +42,9 @@ facts { local b = body("function greet(who: String): String = \"hi\"") b.methods.length == 1 local method = b.methods.first - method.identifier.value == "greet" + method.identifier.name == "greet" method.parameters.length == 1 - method.parameters.first.identifier!!.value == "who" + method.parameters.first.identifier!!.name == "who" method.returnType != null method.body is syntax.SingleLineStringLiteralExprNode } @@ -104,7 +104,7 @@ facts { b.forGenerators.length == 1 local gen = b.forGenerators.first gen.keyParameter == null - gen.valueParameter.identifier!!.value == "item" + gen.valueParameter.identifier!!.name == "item" gen.iterable is syntax.UnqualifiedAccessExprNode } @@ -112,8 +112,8 @@ facts { local b = body("for (k, v in items) { v }") local gen = b.forGenerators.first gen.keyParameter != null - gen.keyParameter!!.identifier!!.value == "k" - gen.valueParameter.identifier!!.value == "v" + gen.keyParameter!!.identifier!!.name == "k" + gen.valueParameter.identifier!!.name == "v" } ["when generator"] { @@ -138,8 +138,8 @@ facts { propVal is syntax.NewExprNode local newBody = (propVal as syntax.NewExprNode).body newBody.parameters.length == 2 - newBody.parameters[0].identifier!!.value == "a" - newBody.parameters[1].identifier!!.value == "b" + newBody.parameters[0].identifier!!.name == "a" + newBody.parameters[1].identifier!!.name == "b" } ["mixed object members"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl index e6339b3f6..679b71484 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl @@ -2,7 +2,9 @@ amends "../snippetTest.pkl" import "pkl:syntax" -local function roundTrip(source: String) = parseNode(source)!!.render() +local renderer = new syntax.Renderer {} + +local function roundTrip(source: String) = renderer.render(parseNode(source)!!) local function parseNode(source: String) = new syntax.Parser {}.parseModule(source).genericNode @@ -336,37 +338,37 @@ facts { ["modify identifier"] { local root = parseNode("x = 1") local modified = replaceLeaf(root!!, "identifier", "x", "y") - modified.render() == "y = 1\n" + renderer.render(modified) == "y = 1\n" } ["modify modifier"] { local root = parseNode("hidden x = 1") local modified = replaceLeaf(root!!, "modifier", "hidden", "local") - modified.render() == "local x = 1\n" + renderer.render(modified) == "local x = 1\n" } ["modify string content"] { local root = parseNode(#"x = "hello""#) local modified = replaceLeaf(root!!, "string_chars", "hello", "world") - modified.render() == #"x = "world"\#n"# + renderer.render(modified) == #"x = "world"\#n"# } ["modify int literal"] { local root = parseNode("x = 42") local modified = replaceLeaf(root!!, "int_literal_expr", "42", "99") - modified.render() == "x = 99\n" + renderer.render(modified) == "x = 99\n" } ["modify boolean literal"] { local root = parseNode("x = true") local modified = replaceLeaf(root!!, "bool_literal_expr", "true", "false") - modified.render() == "x = false\n" + renderer.render(modified) == "x = false\n" } ["modify float literal"] { local root = parseNode("x = 3.14") local modified = replaceLeaf(root!!, "float_literal_expr", "3.14", "2.72") - modified.render() == "x = 2.72\n" + renderer.render(modified) == "x = 2.72\n" } ["add new modifier"] { @@ -380,16 +382,15 @@ facts { children = List(constModifier) + n.children }) // modifier order is switched by the formatter - modified.render() == """ + renderer.render(modified) == """ local const x = 1 """ } - ["format delegates to a default Renderer"] { + ["a default renderer targets the V2 grammar"] { local node = parseNode("x = 1") - node!!.render() == new syntax.Renderer {}.render(node!!) - new syntax.Renderer {}.render(node!!) == new syntax.Renderer { grammarVersion = "V2" }.render(node!!) + renderer.render(node!!) == new syntax.Renderer { grammarVersion = "V2" }.render(node!!) } ["renderer honors the grammar version"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/spans.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/spans.pkl index 15b7850a1..50c175f8a 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/spans.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/spans.pkl @@ -69,7 +69,8 @@ facts { } ["a spanless constructed node still renders"] { - new syntax.IntLiteralExprNode { value = 42 }.builtNode.render().trim() == "42" + new syntax.Renderer {}.render(new syntax.IntLiteralExprNode { value = 42 }.builtNode).trim() + == "42" } ["parsing the same text from a string and a resource differs only in displayUri"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl index 9839a7b5c..07bd70557 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl @@ -4,12 +4,36 @@ import "pkl:syntax" local function mod(source: String): syntax.ModuleNode = new syntax.Parser {}.parseModule(source) -local function fmt(source: String): String = mod(source).genericNode!!.render() +local renderer = new syntax.Renderer {} + +local function fmt(source: String): String = renderer.render(mod(source).genericNode!!) local function walkFormat( source: String, visit: (syntax.GenericNode) -> Pair?, -): String = mod(source).genericNode!!.walk(visit).render() +): String = renderer.render(mod(source).genericNode!!.walk(visit)) + +local function renameIdentifier(source: String, oldText: String, newName: String): String = + walkFormat(source, (n) -> + if (n.type == "identifier" && n.text == oldText) + Pair(new syntax.IdentifierNode { name = newName }.builtNode, false) + else + null + ) + +local function accessNamed(newName: String): String = + walkFormat("x = 0", (n) -> + if (n.type == "int_literal_expr") + Pair( + new syntax.UnqualifiedAccessExprNode { + identifier = new syntax.IdentifierNode { name = newName } + arguments = null + }.builtNode, + false, + ) + else + null + ) facts { ["read-only walk leaves the tree unchanged"] { @@ -84,7 +108,7 @@ facts { if (n.type == "int_literal_expr") Pair( new syntax.SuperAccessExprNode { - identifier = new syntax.IdentifierNode { value = "foo" } + identifier = new syntax.IdentifierNode { name = "foo" } arguments = null }.builtNode, false, @@ -97,7 +121,7 @@ facts { if (n.type == "int_literal_expr") Pair( new syntax.SuperAccessExprNode { - identifier = new syntax.IdentifierNode { value = "foo" } + identifier = new syntax.IdentifierNode { name = "foo" } arguments { new syntax.IntLiteralExprNode { value = 1 } } }.builtNode, false, @@ -120,4 +144,12 @@ facts { null ) == fmt("x = super[0]") } + + ["an identifier name is quoted as needed"] { + accessNamed("foo") == fmt("x = foo") + accessNamed("my ## foo") == fmt("x = `my ## foo`") + accessNamed("class") == fmt("x = `class`") + renameIdentifier("x = someProp", "someProp", "other prop") == fmt("x = `other prop`") + renameIdentifier("x = `some prop`", "`some prop`", "otherProp") == fmt("x = otherProp") + } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf index 4661e0bac..814770cf6 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/moduleStructure.pcf @@ -115,11 +115,15 @@ facts { true true true + true + true + true } ["quoted identifiers in object bodies and accesses"] { true true true + true } ["parser error"] { true diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/render.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/render.pcf index 23295e95b..577b15dbc 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/render.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/render.pcf @@ -134,8 +134,7 @@ facts { ["add new modifier"] { true } - ["format delegates to a default Renderer"] { - true + ["a default renderer targets the V2 grammar"] { true } ["renderer honors the grammar version"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf index 19291ccb2..2e1c4cfa3 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf @@ -25,4 +25,11 @@ facts { ["build super subscript from scratch"] { true } + ["an identifier name is quoted as needed"] { + true + true + true + true + true + } } diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 78f2f1f6b..091f34433 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -285,13 +285,9 @@ typealias ObjectMemberNode = /// A member of a [ModuleNode]. typealias ModuleMember = ClassNode | TypeAliasNode | ClassPropertyNode | ClassMethodNode -/// Indexes [members] by name, stripping the enclosing backticks of quoted identifiers. +/// Indexes [members] by [IdentifierNode.name]. local const function byIdentifier(members: List) = - members.toMap((it) -> unquote(it.identifier.value), (it) -> it).toMapping() - -/// Strips the enclosing backticks of a quoted identifier. -local const function unquote(identifier: String): String = - if (identifier.startsWith("`")) identifier.drop(1).dropLast(1) else identifier + members.toMap((it) -> it.identifier.name, (it) -> it).toMapping() // noinspection TypeMismatch /// The top-level module node. @@ -891,13 +887,13 @@ class ParenthesizedExprNode extends ExprNode { } /// The `unknown` type. -class UnknownTypeNode extends TypeNode {} +class UnknownTypeNode extends TypeNode /// The `nothing` type. -class NothingTypeNode extends TypeNode {} +class NothingTypeNode extends TypeNode /// The `module` type. -class ModuleTypeNode extends TypeNode {} +class ModuleTypeNode extends TypeNode /// A declared type (e.g., `String`, `List`). class DeclaredTypeNode extends TypeNode { @@ -980,10 +976,24 @@ class TypeParameterNode extends Node { identifier: IdentifierNode } +/// Strips the enclosing backticks of a quoted identifier. +local const function unquote(identifier: String): String = + if (identifier.startsWith("`")) identifier.drop(1).dropLast(1) else identifier + /// An identifier (a name occurring in the source, e.g. a property or class name). class IdentifierNode extends Node { - /// The identifier text. - value: String + /// The verbatim identifier text, including the enclosing backticks of a quoted identifier. + /// + /// For example, the text of `` `my ## foo` `` is ``"`my ## foo`"``. + text: String + + /// The identifier name, with the enclosing backticks of a quoted identifier stripped. + /// + /// For example, the name of `` `my ## foo` `` is `"my ## foo"`. + /// + /// This is the name used when rendering this node back to source code; it is quoted again + /// if it is not a regular Pkl identifier. + name: String = unquote(text) } /// A qualified (dotted) identifier, e.g. `foo.bar.baz`. From 79af24c0509d1359565622d373bc818975816ca1 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Thu, 6 Aug 2026 14:16:29 +0200 Subject: [PATCH 42/49] Add constraint to expr bodies --- stdlib/syntax.pkl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 091f34433..6fe936e41 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -442,7 +442,7 @@ class ClassPropertyNode extends Node { typeAnnotation: TypeNode? /// The value expression (from `= expr`). - value: ExprNode? + value: ExprNode(objectBodies.isEmpty)? /// Object bodies for amending (from `{ ... }` blocks). objectBodies: Listing @@ -517,7 +517,7 @@ class ObjectPropertyNode extends Node { typeAnnotation: TypeNode? /// The value expression (from `= expr`). - value: ExprNode? + value: ExprNode(objectBodies.isEmpty)? /// Object bodies for amending. objectBodies: Listing @@ -556,7 +556,7 @@ class ObjectEntryNode extends Node { key: ExprNode /// The value expression (from `[key] = value`). - value: ExprNode? + value: ExprNode(objectBodies.isEmpty)? /// Object bodies for amending. objectBodies: Listing From 058dba79fd9b9b5141b5550fcc5cf7ba13ca7111 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Thu, 6 Aug 2026 14:30:57 +0200 Subject: [PATCH 43/49] Change variance to modifiers --- .../java/org/pkl/core/stdlib/syntax/NodeNodes.java | 10 ++++++---- .../java/org/pkl/core/stdlib/syntax/ParserNodes.java | 10 ++++++---- .../LanguageSnippetTests/input/syntax/expressions.pkl | 2 +- .../input/syntax/moduleStructure.pkl | 2 +- stdlib/syntax.pkl | 4 ++-- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java index 0a2ff1ea4..44595c699 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java @@ -699,11 +699,13 @@ private static VmTyped buildParameter(VmTyped self) { } private static VmTyped buildTypeParameter(VmTyped self) { - var variance = member(self, "variance"); - if (variance instanceof String v) { - return branch("type_parameter", List.of(terminal(v), build(reqNode(self, "identifier")))); + var children = new ArrayList<>(); + // Unlike other modifiers, variance modifiers are plain terminals rather than a `modifier_list`. + for (var modifier : listMember(self, "modifiers")) { + children.add(terminal((String) modifier)); } - return branch("type_parameter", List.of(build(reqNode(self, "identifier")))); + children.add(build(reqNode(self, "identifier"))); + return branch("type_parameter", children); } private static VmTyped buildDocComment(VmTyped self) { diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 02fdd3500..dfa54f228 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -82,7 +82,7 @@ private static VmObjectFactory genericNodeOnlyFactory(Supplier private static final VmObjectFactory typeParameterNodeFactory = new VmObjectFactory(SyntaxModule::getTypeParameterNodeClass) .addProperty("genericNode", vm -> vm) - .addProperty("variance", ParserNodes::typeParameterVariance) + .addListingProperty("modifiers", ParserNodes::typeParameterModifiers) .addTypedProperty("identifier", ParserNodes::identifierNodeOf); private static final VmObjectFactory objectBodyNodeFactory = new VmObjectFactory(SyntaxModule::getObjectBodyNodeClass) @@ -968,17 +968,19 @@ private static String qualifiedIdentifierValue(VmTyped qualifiedVm) { return builder.toString(); } - private static Object typeParameterVariance(VmTyped typeParameterVm) { + // Unlike other modifiers, variance modifiers are plain terminals rather than `modifier` nodes. + private static VmListing typeParameterModifiers(VmTyped typeParameterVm) { var data = (GenericNodeData) typeParameterVm.getExtraStorage(); + var result = new ArrayList<>(); for (var child : data.node.children) { if (child.type == NodeType.TERMINAL) { var text = child.text(data.source); if ("in".equals(text) || "out".equals(text)) { - return text; + result.add(text); } } } - return VmNull.withoutDefault(); + return listingOf(result.toArray()); } private static boolean parameterIsBlankIdentifier(VmTyped parameterVm) { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl index fdad4dacf..4d400ac32 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl @@ -269,7 +269,7 @@ facts { cls.identifier.name == "Box" cls.typeParameters.length == 1 cls.typeParameters.first.identifier.name == "T" - cls.typeParameters.first.variance == "out" + cls.typeParameters.first.modifiers == new Listing { "out" } } ["minimal class"] { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl index 834f1beb7..bbe76daae 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/moduleStructure.pkl @@ -131,7 +131,7 @@ facts { cls.typeParameters != null cls.typeParameters.length == 1 cls.typeParameters.first.identifier.name == "T" - cls.typeParameters.first.variance == null + cls.typeParameters.first.modifiers.isEmpty cls.superType != null cls.superType is syntax.DeclaredTypeNode diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 6fe936e41..fc7c47912 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -969,8 +969,8 @@ class ParameterNode extends Node { /// A type parameter declaration (`T`, `in T`, or `out T`). class TypeParameterNode extends Node { - /// The variance modifier (`"in"`, `"out"`, or null). - variance: ("in" | "out")? + /// The variance modifiers on the type parameter. + modifiers: Listing<"in" | "out">(isDistinct) /// The type parameter name. identifier: IdentifierNode From 3bcca770c3c37d7c54d9c37bea8b60a9011b966b Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Thu, 6 Aug 2026 15:06:05 +0200 Subject: [PATCH 44/49] Add members to ObjectBodyNode --- .../org/pkl/core/runtime/SyntaxModule.java | 16 ++--- .../org/pkl/core/stdlib/syntax/NodeNodes.java | 20 +++--- .../pkl/core/stdlib/syntax/ParserNodes.java | 72 +++++++------------ .../input/syntax/objectMembers.pkl | 19 ++++- .../output/syntax/objectMembers.pcf | 9 +++ stdlib/syntax.pkl | 72 ++++++++++--------- 6 files changed, 107 insertions(+), 101 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java index 186556cf4..3b4e4a4e3 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/SyntaxModule.java @@ -101,10 +101,6 @@ public static VmClass getParameterNodeClass() { return ParameterNodeClass.instance; } - public static VmClass getObjectElementNodeClass() { - return ObjectElementNodeClass.instance; - } - public static VmClass getObjectPropertyNodeClass() { return ObjectPropertyNodeClass.instance; } @@ -193,6 +189,10 @@ public static VmClass getStringLiteralTypeNodeClass() { return StringLiteralTypeNodeClass.instance; } + public static VmClass getExprNodeClass() { + return ExprNodeClass.instance; + } + public static VmClass getThisExprNodeClass() { return ThisExprNodeClass.instance; } @@ -449,10 +449,6 @@ private static final class ParameterNodeClass { static final VmClass instance = loadClass("ParameterNode"); } - private static final class ObjectElementNodeClass { - static final VmClass instance = loadClass("ObjectElementNode"); - } - private static final class ObjectPropertyNodeClass { static final VmClass instance = loadClass("ObjectPropertyNode"); } @@ -541,6 +537,10 @@ private static final class StringLiteralTypeNodeClass { static final VmClass instance = loadClass("StringLiteralTypeNode"); } + private static final class ExprNodeClass { + static final VmClass instance = loadClass("ExprNode"); + } + private static final class ThisExprNodeClass { static final VmClass instance = loadClass("ThisExprNode"); } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java index 44595c699..65d21953c 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java @@ -68,8 +68,6 @@ yield branch( case "ObjectBodyNode" -> buildObjectBody(self); case "ObjectPropertyNode" -> buildObjectProperty(self); case "ObjectMethodNode" -> buildObjectMethod(self); - case "ObjectElementNode" -> - branch("object_element", List.of(build(reqNode(self, "expression")))); case "ObjectEntryNode" -> buildObjectEntry(self); case "ObjectSpreadNode" -> branch( @@ -336,14 +334,9 @@ private static VmTyped buildObjectBody(VmTyped self) { children.add(branch("object_parameter_list", elements)); } var members = new ArrayList<>(); - members.addAll(buildAll(listMember(self, "properties"))); - members.addAll(buildAll(listMember(self, "methods"))); - members.addAll(buildAll(listMember(self, "elements"))); - members.addAll(buildAll(listMember(self, "entries"))); - members.addAll(buildAll(listMember(self, "spreads"))); - members.addAll(buildAll(listMember(self, "memberPredicates"))); - members.addAll(buildAll(listMember(self, "forGenerators"))); - members.addAll(buildAll(listMember(self, "whenGenerators"))); + for (var member : listMember(self, "members")) { + members.add(buildObjectMember((VmTyped) member)); + } if (!members.isEmpty()) { children.add(branch("object_member_list", members)); } @@ -351,6 +344,13 @@ private static VmTyped buildObjectBody(VmTyped self) { return branch("object_body", children); } + private static VmTyped buildObjectMember(VmTyped member) { + var built = build(member); + return member.getVmClass().isSubclassOf(SyntaxModule.getExprNodeClass()) + ? branch("object_element", List.of(built)) + : built; + } + private static VmTyped buildObjectProperty(VmTyped self) { var headerBegin = new ArrayList<>(); var modifiers = listMember(self, "modifiers"); diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index dfa54f228..8b79422bf 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -88,14 +88,7 @@ private static VmObjectFactory genericNodeOnlyFactory(Supplier new VmObjectFactory(SyntaxModule::getObjectBodyNodeClass) .addProperty("genericNode", vm -> vm) .addListingProperty("parameters", ParserNodes::objectBodyParameters) - .addListingProperty("properties", ParserNodes::objectBodyProperties) - .addListingProperty("methods", ParserNodes::objectBodyMethods) - .addListingProperty("elements", ParserNodes::objectBodyElements) - .addListingProperty("entries", ParserNodes::objectBodyEntries) - .addListingProperty("spreads", ParserNodes::objectBodySpreads) - .addListingProperty("memberPredicates", ParserNodes::objectBodyMemberPredicates) - .addListingProperty("forGenerators", ParserNodes::objectBodyForGenerators) - .addListingProperty("whenGenerators", ParserNodes::objectBodyWhenGenerators); + .addListingProperty("members", ParserNodes::objectBodyMembers); private static final VmObjectFactory parameterNodeFactory = new VmObjectFactory(SyntaxModule::getParameterNodeClass) .addProperty("genericNode", vm -> vm) @@ -103,10 +96,6 @@ private static VmObjectFactory genericNodeOnlyFactory(Supplier .addProperty("identifier", ParserNodes::parameterIdentifier) .addProperty("typeAnnotation", ParserNodes::parameterTypeAnnotation); - private static final VmObjectFactory objectElementNodeFactory = - new VmObjectFactory(SyntaxModule::getObjectElementNodeClass) - .addProperty("genericNode", vm -> vm) - .addTypedProperty("expression", ParserNodes::soleExpr); private static final VmObjectFactory objectPropertyNodeFactory = new VmObjectFactory(SyntaxModule::getObjectPropertyNodeClass) .addProperty("genericNode", vm -> vm) @@ -805,45 +794,34 @@ private static VmListing objectBodyParameters(VmTyped bodyVm) { return wrapAll(findChildrenVm(paramList, NodeType.PARAMETER), parameterNodeFactory); } - private static VmListing objectBodyMembers( - VmTyped bodyVm, NodeType memberType, VmObjectFactory factory) { + private static VmListing objectBodyMembers(VmTyped bodyVm) { var memberList = findChildVm(bodyVm, NodeType.OBJECT_MEMBER_LIST); if (memberList == null) { return VmListing.empty(); } - return wrapAll(findChildrenVm(memberList, memberType), factory); - } - - private static VmListing objectBodyProperties(VmTyped bodyVm) { - return objectBodyMembers(bodyVm, NodeType.OBJECT_PROPERTY, objectPropertyNodeFactory); - } - - private static VmListing objectBodyMethods(VmTyped bodyVm) { - return objectBodyMembers(bodyVm, NodeType.OBJECT_METHOD, objectMethodNodeFactory); - } - - private static VmListing objectBodyElements(VmTyped bodyVm) { - return objectBodyMembers(bodyVm, NodeType.OBJECT_ELEMENT, objectElementNodeFactory); - } - - private static VmListing objectBodyEntries(VmTyped bodyVm) { - return objectBodyMembers(bodyVm, NodeType.OBJECT_ENTRY, objectEntryNodeFactory); - } - - private static VmListing objectBodySpreads(VmTyped bodyVm) { - return objectBodyMembers(bodyVm, NodeType.OBJECT_SPREAD, objectSpreadNodeFactory); - } - - private static VmListing objectBodyMemberPredicates(VmTyped bodyVm) { - return objectBodyMembers(bodyVm, NodeType.MEMBER_PREDICATE, memberPredicateNodeFactory); - } - - private static VmListing objectBodyForGenerators(VmTyped bodyVm) { - return objectBodyMembers(bodyVm, NodeType.FOR_GENERATOR, forGeneratorNodeFactory); - } - - private static VmListing objectBodyWhenGenerators(VmTyped bodyVm) { - return objectBodyMembers(bodyVm, NodeType.WHEN_GENERATOR, whenGeneratorNodeFactory); + var data = (GenericNodeData) memberList.getExtraStorage(); + var children = data.node.children; + var result = new ArrayList<>(); + for (var i = 0; i < children.size(); i++) { + var childVm = (VmTyped) data.childrenVm.get(i); + var member = + switch (children.get(i).type) { + case OBJECT_PROPERTY -> objectPropertyNodeFactory.create(childVm); + case OBJECT_METHOD -> objectMethodNodeFactory.create(childVm); + // an element is represented by its expression + case OBJECT_ELEMENT -> soleExpr(childVm); + case OBJECT_ENTRY -> objectEntryNodeFactory.create(childVm); + case OBJECT_SPREAD -> objectSpreadNodeFactory.create(childVm); + case MEMBER_PREDICATE -> memberPredicateNodeFactory.create(childVm); + case FOR_GENERATOR -> forGeneratorNodeFactory.create(childVm); + case WHEN_GENERATOR -> whenGeneratorNodeFactory.create(childVm); + default -> null; + }; + if (member != null) { + result.add(member); + } + } + return listingOf(result.toArray()); } private static @Nullable VmTyped objectPropertyHeaderBegin(VmTyped propertyVm) { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl index e989bb8cb..117f7090d 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/objectMembers.pkl @@ -52,9 +52,9 @@ facts { ["object element"] { local b = body("1\n 2\n 3") b.elements.length == 3 - b.elements[0].expression is syntax.IntLiteralExprNode - b.elements[1].expression is syntax.IntLiteralExprNode - b.elements[2].expression is syntax.IntLiteralExprNode + b.elements[0] is syntax.IntLiteralExprNode + b.elements[1] is syntax.IntLiteralExprNode + b.elements[2] is syntax.IntLiteralExprNode } ["object entry"] { @@ -149,9 +149,22 @@ facts { ["key"] = 2 function f() = 3 """) + + // `members` keeps source order across member kinds + b.members.length == 4 + b.members[0] is syntax.ObjectPropertyNode + b.members[1] is syntax.IntLiteralExprNode + b.members[2] is syntax.ObjectEntryNode + b.members[3] is syntax.ObjectMethodNode + + // the specific-member views are derived from `members` b.properties.length == 1 + b.properties.first == b.members[0] b.elements.length == 1 + b.elements.first == b.members[1] b.entries.length == 1 + b.entries.first == b.members[2] b.methods.length == 1 + b.methods.first == b.members[3] } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf index 746e4545e..680894e8c 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/objectMembers.pcf @@ -94,5 +94,14 @@ facts { true true true + true + true + true + true + true + true + true + true + true } } diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index fc7c47912..1f2f56c67 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -271,17 +271,6 @@ abstract class ExprNode extends Node /// Base class for type nodes. abstract class TypeNode extends Node -/// A member of an [ObjectBodyNode]. -typealias ObjectMemberNode = - ObjectPropertyNode - | ObjectMethodNode - | ObjectElementNode - | ObjectEntryNode - | ObjectSpreadNode - | MemberPredicateNode - | ForGeneratorNode - | WhenGeneratorNode - /// A member of a [ModuleNode]. typealias ModuleMember = ClassNode | TypeAliasNode | ClassPropertyNode | ClassMethodNode @@ -475,34 +464,57 @@ class ClassMethodNode extends Node { body: ExprNode? } +/// A member of an [ObjectBodyNode]. +/// +/// An [ExprNode] member is an *element*, that is, a positional value such as the `1` in `{ 1 }`. +typealias ObjectMemberNode = + ObjectPropertyNode + | ObjectMethodNode + | ExprNode + | ObjectEntryNode + | ObjectSpreadNode + | MemberPredicateNode + | ForGeneratorNode + | WhenGeneratorNode + /// An object body delimited by braces. class ObjectBodyNode extends Node { /// Parameters for this object body (e.g., `{ x, y -> ... }`). parameters: Listing - /// Properties declared in this object body. - properties: Listing + /// The members of this object body, in source order. + members: Listing + + /// Properties declared in this object body, in source order. + fixed properties: Listing = + members.toList().filterIsInstance(ObjectPropertyNode).toListing() - /// Methods declared in this object body. - methods: Listing + /// Methods declared in this object body, in source order. + fixed methods: Listing = + members.toList().filterIsInstance(ObjectMethodNode).toListing() - /// Elements declared in this object body. - elements: Listing + /// Elements declared in this object body, in source order. + fixed elements: Listing = members.toList().filterIsInstance(ExprNode).toListing() - /// Entries declared in this object body. - entries: Listing + /// Entries declared in this object body, in source order. + fixed entries: Listing = + members.toList().filterIsInstance(ObjectEntryNode).toListing() - /// Spreads declared in this object body. - spreads: Listing + /// Spreads declared in this object body, in source order. + fixed spreads: Listing = + members.toList().filterIsInstance(ObjectSpreadNode).toListing() - /// Member predicates declared in this object body. - memberPredicates: Listing + /// Member predicates declared in this object body, in source order. + fixed memberPredicates: Listing = + members.toList().filterIsInstance(MemberPredicateNode).toListing() - /// `for` generators declared in this object body. - forGenerators: Listing + /// `for` generators declared in this object body, in source order. + fixed forGenerators: Listing = + members.toList().filterIsInstance(ForGeneratorNode).toListing() - /// `when` generators declared in this object body. - whenGenerators: Listing + /// `when` generators declared in this object body, in source order. + fixed whenGenerators: Listing = + members.toList().filterIsInstance(WhenGeneratorNode).toListing() } /// An object property declaration. @@ -544,12 +556,6 @@ class ObjectMethodNode extends Node { body: ExprNode } -/// An object element (a positional expression in an object body). -class ObjectElementNode extends Node { - /// The expression value. - expression: ExprNode -} - /// An object entry (`[key] = value` or `[key] { ... }`). class ObjectEntryNode extends Node { /// The key expression. From 119b66728d37397aae3a9afef42346e6507cd8b5 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Thu, 6 Aug 2026 15:21:06 +0200 Subject: [PATCH 45/49] Fix review remarks --- stdlib/syntax.pkl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 1f2f56c67..0be5c704c 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -461,7 +461,7 @@ class ClassMethodNode extends Node { returnType: TypeNode? /// The method body expression. Null for abstract methods. - body: ExprNode? + body: ExprNode?((this == null) == modifiers.contains("abstract")) } /// A member of an [ObjectBodyNode]. @@ -643,7 +643,7 @@ class IntLiteralExprNode extends ExprNode { /// A float literal expression. class FloatLiteralExprNode extends ExprNode { /// The float literal (e.g. `3.14`, `"1.0e10"`). - value: Float | String + value: Float | String(toFloatOrNull() != null) } /// A single-line string literal expression. @@ -775,7 +775,12 @@ class NewExprNode extends ExprNode { /// An `(expr) { ... }` amends expression. class AmendsExprNode extends ExprNode { /// The expression being amended. - parentExpr: ExprNode + /// + /// Takes one of a few forms: + /// * [ParenthesizedExprNode]: `(expr) { ... }` + /// * [AmendsExprNode]: `(expr) { ... } { ... }` (where `(expr) { ... }` is the parent) + /// * [NewExprNode]: `new { ... } { ... }` (where `new { ... }` is the parent) + parentExpr: ParenthesizedExprNode | AmendsExprNode | NewExprNode /// The object body. body: ObjectBodyNode @@ -964,7 +969,7 @@ class AnnotationNode extends Node { /// A parameter declaration (`name`, `name: Type`, or `_`). class ParameterNode extends Node { /// Whether this is a blank identifier parameter (`_`). - isBlankIdentifier: Boolean + isBlankIdentifier: Boolean(this == (identifier == null), implies(typeAnnotation == null)) /// The parameter name, or `null` for a wildcard parameter (`_`). identifier: IdentifierNode? From d64c48a03d0e0dd66ec069b7049deb542e7b82ab Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Thu, 6 Aug 2026 17:01:59 +0200 Subject: [PATCH 46/49] Add helper to build strings --- .../input/syntax/render.pkl | 55 ++++++++++++++++++ .../output/syntax/render.pcf | 18 ++++++ stdlib/syntax.pkl | 57 +++++++++++++++++++ 3 files changed, 130 insertions(+) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl index 679b71484..4d377ca1a 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/render.pkl @@ -8,6 +8,8 @@ local function roundTrip(source: String) = renderer.render(parseNode(source)!!) local function parseNode(source: String) = new syntax.Parser {}.parseModule(source).genericNode +local function renderExpr(node: syntax.ExprNode) = renderer.render(node.builtNode).trim() + local function replaceLeaf( node: syntax.GenericNode, targetType: syntax.NodeType, @@ -81,6 +83,59 @@ facts { """# } + ["single-line string literal from a value"] { + renderExpr(syntax.singleLineStringLiteral("hello")) == #""hello""# + renderExpr(syntax.singleLineStringLiteral("")) == "\"\"" + renderExpr(syntax.singleLineStringLiteral("a\\b")) == #""a\\b""# + renderExpr(syntax.singleLineStringLiteral(#"he said "hi""#)) == #""he said \"hi\"""# + renderExpr(syntax.singleLineStringLiteral("a\nb\tc\rd")) == #""a\nb\tc\rd""# + renderExpr(syntax.singleLineStringLiteral(#"\(foo)"#)) == #""\\(foo)""# + } + + ["multi-line string literal from a value"] { + renderExpr(syntax.multiLineStringLiteral("hello\nworld")) == #""" + """ + hello + world + """ + """# + renderExpr(syntax.multiLineStringLiteral("")) == "\"\"\"\n\n\"\"\"" + renderExpr(syntax.multiLineStringLiteral("foo\n")) == #""" + """ + foo + + """ + """# + renderExpr(syntax.multiLineStringLiteral(" indented")) == #""" + """ + indented + """ + """# + renderExpr(syntax.multiLineStringLiteral("a\n\nb")) == #""" + """ + a + + b + """ + """# + renderExpr(syntax.multiLineStringLiteral("a\\b\tc\rd")) == #""" + """ + a\\b\tc\rd + """ + """# + renderExpr(syntax.multiLineStringLiteral(#"say """ now"#)) == #""" + """ + say \""" now + """ + """# + // in a longer run, every quote that would start a `"""` sequence is escaped + renderExpr(syntax.multiLineStringLiteral(#"quad """" quotes"#)) == #""" + """ + quad \"\""" quotes + """ + """# + } + ["comments"] { roundTrip( """ diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/render.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/render.pcf index 577b15dbc..3133d0670 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/render.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/render.pcf @@ -16,6 +16,24 @@ facts { ["multiline string"] { true } + ["single-line string literal from a value"] { + true + true + true + true + true + true + } + ["multi-line string literal from a value"] { + true + true + true + true + true + true + true + true + } ["comments"] { true } diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 0be5c704c..7b0effa77 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -660,6 +660,63 @@ class MultiLineStringLiteralExprNode extends ExprNode { parts: Listing } +/// Splits a string into runs of characters that can appear verbatim in a single-line string +/// literal (group 1), and single characters that must be escaped (group 2). +local const singleLineStringChunks = Regex(#"([^\\"\n\r\t]+)|([\s\S])"#) + +/// Same as [singleLineStringChunks], but for a multi-line string literal. +local const multiLineStringChunks = Regex(#"((?:(?!""")[^\\\n\r\t])+)|([\s\S])"#) + +/// The escape sequence for each character that cannot appear verbatim in a string literal. +local const stringEscapes: Map = + Map( + "\\", #"\\"#, + "\"", #"\""#, + "\n", #"\n"#, + "\r", #"\r"#, + "\t", #"\t"#, + ) + +/// Turns a chunk matched by [singleLineStringChunks] or [multiLineStringChunks] into a string +/// literal part. +local const function stringPart(match: RegexMatch): StringPartNode = + if (match.groups[1] != null) + new StringCharsNode { value = match.value } + else + new StringEscapeNode { value = stringEscapes[match.value] } + +/// Same as [stringPart()], but turns a newline into a [StringNewlineNode]. +local const function multiLineStringPart(match: RegexMatch): StringPartNode = + if (match.value == "\n") new StringNewlineNode {} else stringPart(match) + +/// Creates a single-line string literal expression that evaluates to [value]. +/// +/// Characters that cannot appear verbatim in a single-line string literal +/// (`\`, `"`, newline, carriage return, and tab) become [StringEscapeNode] parts. +/// +/// To interpolate expressions, set [SingleLineStringLiteralExprNode.parts] directly, +/// using [StringInterpolationNode] parts. +const function singleLineStringLiteral(value: String): SingleLineStringLiteralExprNode = new { + parts { ...singleLineStringChunks.findMatchesIn(value).map((match) -> stringPart(match)) } +} + +/// Creates a multi-line string literal expression that evaluates to [value]. +/// +/// Newlines in [value] become [StringNewlineNode] parts. +/// Characters that cannot appear verbatim in a multi-line string literal +/// (`\`, carriage return, tab, and quotes that would otherwise form `"""`) +/// become [StringEscapeNode] parts. +/// +/// To interpolate expressions, set [MultiLineStringLiteralExprNode.parts] directly, +/// using [StringInterpolationNode] parts. +const function multiLineStringLiteral(value: String): MultiLineStringLiteralExprNode = new { + parts { + new StringNewlineNode {} + ...multiLineStringChunks.findMatchesIn(value).map((match) -> multiLineStringPart(match)) + new StringNewlineNode {} + } +} + /// An unqualified access expression (`name` or `name(args)`). class UnqualifiedAccessExprNode extends ExprNode { /// The identifier being accessed. From bae41f44cd41108bd34802c43ffa0fbc3045c427 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Thu, 6 Aug 2026 17:17:43 +0200 Subject: [PATCH 47/49] Improve string parts --- .../org/pkl/core/stdlib/syntax/NodeNodes.java | 2 +- .../pkl/core/stdlib/syntax/ParserNodes.java | 10 ++----- stdlib/syntax.pkl | 29 ++++++++++--------- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java index 65d21953c..10e04ad52 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/NodeNodes.java @@ -494,7 +494,7 @@ private static List buildStringParts(List parts) { return result; } - // `StringPartNode` is not a `Node`, so it is handled here rather than through `build`. + // String parts are not `Node`s, so they are handled here rather than through `build`. private static List buildStringPart(VmTyped part) { return switch (part.getVmClass().getSimpleName()) { case "StringCharsNode" -> List.of(leaf("string_chars", str(part, "value"))); diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 8b79422bf..636dee774 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -351,22 +351,18 @@ private static VmObjectFactory typeOpExprNodeFactory(Supplier .addProperty("genericNode", vm -> vm) .addTypedProperty("expression", ParserNodes::parenthesizedExpression); - // String-part factories, produced by `buildStringParts`. `StringPartNode` is not a `Node`, - // but it also carries a hidden `genericNode`, so the same property shape applies. + // String-part factories, produced by `buildStringParts`. String parts are not `Node`s and don't + // carry a `genericNode`, but the generic node is still their factory input. private static final VmObjectFactory stringCharsNodeFactory = new VmObjectFactory(SyntaxModule::getStringCharsNodeClass) - .addProperty("genericNode", vm -> vm) .addStringProperty("value", ParserNodes::literalText); private static final VmObjectFactory stringEscapeNodeFactory = new VmObjectFactory(SyntaxModule::getStringEscapeNodeClass) - .addProperty("genericNode", vm -> vm) .addStringProperty("value", ParserNodes::literalText); private static final VmObjectFactory stringNewlineNodeFactory = - new VmObjectFactory(SyntaxModule::getStringNewlineNodeClass) - .addProperty("genericNode", vm -> vm); + new VmObjectFactory(SyntaxModule::getStringNewlineNodeClass); private static final VmObjectFactory stringInterpolationNodeFactory = new VmObjectFactory(SyntaxModule::getStringInterpolationNodeClass) - .addProperty("genericNode", vm -> vm) .addTypedProperty("expression", ParserNodes::wrapExpr); private static final VmObjectFactory importNodeFactory = diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 7b0effa77..aea051769 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -646,10 +646,17 @@ class FloatLiteralExprNode extends ExprNode { value: Float | String(toFloatOrNull() != null) } +/// A part of a single-line string literal. +typealias SingleLineStringPartNode = StringCharsNode | StringEscapeNode | StringInterpolationNode + +/// A part of a multi-line string literal. +typealias MultiLineStringPartNode = + StringCharsNode | StringEscapeNode | StringInterpolationNode | StringNewlineNode + /// A single-line string literal expression. class SingleLineStringLiteralExprNode extends ExprNode { /// The string parts (chars, escapes, interpolations). - parts: Listing + parts: Listing } /// A multi-line string literal expression. @@ -657,7 +664,7 @@ class SingleLineStringLiteralExprNode extends ExprNode { /// Use [StringNewlineNode] entries in [parts] to separate lines. class MultiLineStringLiteralExprNode extends ExprNode { /// The string parts (chars, escapes, newlines, interpolations). - parts: Listing + parts: Listing } /// Splits a string into runs of characters that can appear verbatim in a single-line string @@ -679,14 +686,14 @@ local const stringEscapes: Map = /// Turns a chunk matched by [singleLineStringChunks] or [multiLineStringChunks] into a string /// literal part. -local const function stringPart(match: RegexMatch): StringPartNode = +local const function stringPart(match: RegexMatch): SingleLineStringPartNode = if (match.groups[1] != null) new StringCharsNode { value = match.value } else new StringEscapeNode { value = stringEscapes[match.value] } /// Same as [stringPart()], but turns a newline into a [StringNewlineNode]. -local const function multiLineStringPart(match: RegexMatch): StringPartNode = +local const function multiLineStringPart(match: RegexMatch): MultiLineStringPartNode = if (match.value == "\n") new StringNewlineNode {} else stringPart(match) /// Creates a single-line string literal expression that evaluates to [value]. @@ -1079,29 +1086,23 @@ class DocCommentNode extends Node { value: String } -/// Base class for parts of a string literal (text, escapes, newlines, interpolations). -abstract class StringPartNode { - /// The original parsed node, or `null` when built from scratch. - hidden genericNode: GenericNode? = null -} - /// A plain text part of a string literal. -class StringCharsNode extends StringPartNode { +class StringCharsNode { /// The text content. value: String } /// An escape sequence in a string literal (e.g., `"\\n"`, `"\\t"`). -class StringEscapeNode extends StringPartNode { +class StringEscapeNode { /// The escape sequence text including the leading backslash. value: String } /// A newline in a multi-line string literal. -class StringNewlineNode extends StringPartNode +class StringNewlineNode /// An interpolation in a string literal (`\(expr)`). -class StringInterpolationNode extends StringPartNode { +class StringInterpolationNode { /// The interpolated expression. expression: ExprNode } From 7f67e51e49fba2d849566b65330d17770ccea9c9 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Thu, 6 Aug 2026 19:11:15 +0200 Subject: [PATCH 48/49] Rename walk to transform --- .../core/stdlib/syntax/GenericNodeNodes.java | 18 +++++------ .../pkl/core/stdlib/syntax/ParserNodes.java | 2 +- .../input/syntax/{walk.pkl => transform.pkl} | 32 +++++++++---------- .../output/syntax/{walk.pcf => transform.pcf} | 2 +- stdlib/syntax.pkl | 6 ++-- 5 files changed, 30 insertions(+), 30 deletions(-) rename pkl-core/src/test/files/LanguageSnippetTests/input/syntax/{walk.pkl => transform.pkl} (84%) rename pkl-core/src/test/files/LanguageSnippetTests/output/syntax/{walk.pcf => transform.pcf} (90%) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/GenericNodeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/GenericNodeNodes.java index ab0dc7caf..d8f214f7d 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/GenericNodeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/GenericNodeNodes.java @@ -31,7 +31,7 @@ import org.pkl.core.stdlib.ExternalMethod2Node; import org.pkl.core.stdlib.syntax.SyntaxNodes.GenericNodeData; -/** Backs {@code pkl.syntax#GenericNode.fold} and {@code pkl.syntax#GenericNode.walk}. */ +/** Backs {@code pkl.syntax#GenericNode.fold} and {@code pkl.syntax#GenericNode.transform}. */ public final class GenericNodeNodes { private GenericNodeNodes() {} @@ -56,13 +56,13 @@ protected Object eval(VmTyped self, Object initial, VmFunction operator) { } } - public abstract static class walk extends ExternalMethod1Node { - @Child private ApplyVmFunction1Node applyVisit = ApplyVmFunction1Node.create(); + public abstract static class transform extends ExternalMethod1Node { + @Child private ApplyVmFunction1Node applyOperator = ApplyVmFunction1Node.create(); @Specialization @TruffleBoundary - protected VmTyped eval(VmTyped self, VmFunction visit) { - var result = walkNode(self, visit); + protected VmTyped eval(VmTyped self, VmFunction operator) { + var result = transformNode(self, operator); // the root of the returned tree has no parent if (result.hasExtraStorage()) { ((GenericNodeData) result.getExtraStorage()).parentVm = null; @@ -70,12 +70,12 @@ protected VmTyped eval(VmTyped self, VmFunction visit) { return result; } - private VmTyped walkNode(VmTyped nodeVm, VmFunction visit) { - var visited = applyVisit.execute(visit, nodeVm); + private VmTyped transformNode(VmTyped nodeVm, VmFunction operator) { + var transformed = applyOperator.execute(operator, nodeVm); VmTyped node; boolean descend; - if (visited instanceof VmPair pair) { + if (transformed instanceof VmPair pair) { node = (VmTyped) pair.getFirst(); descend = (Boolean) pair.getSecond(); } else { @@ -97,7 +97,7 @@ private VmTyped walkNode(VmTyped nodeVm, VmFunction visit) { var changed = false; for (var i = 0; i < length; i++) { var child = (VmTyped) childrenVm.get(i); - var newChild = walkNode(child, visit); + var newChild = transformNode(child, operator); newChildren[i] = newChild; changed |= newChild != child; } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 636dee774..6039ae819 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -1580,7 +1580,7 @@ private static VmTyped convertNode( childrenList.add(convertNode(child, sourceChars, sourceUri)); } - // materialize text now so that nodes reused verbatim by `walk`/`format` are + // materialize text now so that nodes reused verbatim by `transform`/`format` are // self-contained if (genericNode.children.isEmpty() || genericNode.type == NodeType.STRING_CHARS) { genericNode.text(sourceChars); diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/transform.pkl similarity index 84% rename from pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl rename to pkl-core/src/test/files/LanguageSnippetTests/input/syntax/transform.pkl index 07bd70557..298c168ef 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/walk.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/transform.pkl @@ -8,13 +8,13 @@ local renderer = new syntax.Renderer {} local function fmt(source: String): String = renderer.render(mod(source).genericNode!!) -local function walkFormat( +local function transformFormat( source: String, - visit: (syntax.GenericNode) -> Pair?, -): String = renderer.render(mod(source).genericNode!!.walk(visit)) + operator: (syntax.GenericNode) -> Pair?, +): String = renderer.render(mod(source).genericNode!!.transform(operator)) local function renameIdentifier(source: String, oldText: String, newName: String): String = - walkFormat(source, (n) -> + transformFormat(source, (n) -> if (n.type == "identifier" && n.text == oldText) Pair(new syntax.IdentifierNode { name = newName }.builtNode, false) else @@ -22,7 +22,7 @@ local function renameIdentifier(source: String, oldText: String, newName: String ) local function accessNamed(newName: String): String = - walkFormat("x = 0", (n) -> + transformFormat("x = 0", (n) -> if (n.type == "int_literal_expr") Pair( new syntax.UnqualifiedAccessExprNode { @@ -36,14 +36,14 @@ local function accessNamed(newName: String): String = ) facts { - ["read-only walk leaves the tree unchanged"] { + ["read-only transform leaves the tree unchanged"] { // returning `null` everywhere keeps every node and keeps descending - walkFormat("x = 1", (_) -> null) == fmt("x = 1") - walkFormat("x = if (cond) 1 else 2", (_) -> null) == fmt("x = if (cond) 1 else 2") + transformFormat("x = 1", (_) -> null) == fmt("x = 1") + transformFormat("x = if (cond) 1 else 2", (_) -> null) == fmt("x = if (cond) 1 else 2") } ["rename identifiers everywhere"] { - walkFormat("foo = foo + 1", (n) -> + transformFormat("foo = foo + 1", (n) -> if (n.type == "identifier" && n.text == "foo") Pair((n) { text = "bar" }, true) else @@ -52,7 +52,7 @@ facts { } ["replace a leaf via a typed node"] { - walkFormat("x = 0", (n) -> + transformFormat("x = 0", (n) -> if (n.type == "int_literal_expr" && n.text == "0") Pair(new syntax.IntLiteralExprNode { value = 100 }.builtNode, false) else @@ -61,7 +61,7 @@ facts { } ["rebuilds ancestors of a changed node"] { - walkFormat("x = if (cond) 41 else 0", (n) -> + transformFormat("x = if (cond) 41 else 0", (n) -> if (n.type == "int_literal_expr" && n.text == "41") Pair(new syntax.IntLiteralExprNode { value = 42 }.builtNode, false) else if (n.type == "int_literal_expr" && n.text == "0") @@ -72,7 +72,7 @@ facts { } ["descend reprocesses emitted nodes"] { - walkFormat("x = 0", (n) -> + transformFormat("x = 0", (n) -> if (n.type == "int_literal_expr" && n.text == "0") Pair( new syntax.ParenthesizedExprNode { @@ -88,7 +88,7 @@ facts { } ["descend = false leaves the emitted subtree untouched"] { - walkFormat("x = 0", (n) -> + transformFormat("x = 0", (n) -> if (n.type == "int_literal_expr" && n.text == "0") Pair( new syntax.ParenthesizedExprNode { @@ -104,7 +104,7 @@ facts { } ["build super access from scratch"] { - walkFormat("x = 0", (n) -> + transformFormat("x = 0", (n) -> if (n.type == "int_literal_expr") Pair( new syntax.SuperAccessExprNode { @@ -117,7 +117,7 @@ facts { null ) == fmt("x = super.foo") - walkFormat("x = 0", (n) -> + transformFormat("x = 0", (n) -> if (n.type == "int_literal_expr") Pair( new syntax.SuperAccessExprNode { @@ -132,7 +132,7 @@ facts { } ["build super subscript from scratch"] { - walkFormat("x = 0", (n) -> + transformFormat("x = 0", (n) -> if (n.type == "int_literal_expr") Pair( new syntax.SuperSubscriptExprNode { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/transform.pcf similarity index 90% rename from pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf rename to pkl-core/src/test/files/LanguageSnippetTests/output/syntax/transform.pcf index 2e1c4cfa3..595098b1c 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/walk.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/transform.pcf @@ -1,5 +1,5 @@ facts { - ["read-only walk leaves the tree unchanged"] { + ["read-only transform leaves the tree unchanged"] { true true } diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index aea051769..0a70d785c 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -61,10 +61,10 @@ class GenericNode { /// The source location of this node or `null`. span: Span? - /// Walk this node and its descendants top-down, applying [visit] to each node and + /// Walk this node and its descendants top-down, applying [operator] to each node and /// returning the (possibly rewritten) tree. /// - /// For each node, [visit] returns either: + /// For each node, [operator] returns either: /// - `null` to leave the node unchanged and continue walking into its children. /// - `Pair(replacement, descend)` to replace the node with `replacement`. When /// `descend` is `true`, the children of `replacement` are visited in turn, so @@ -75,7 +75,7 @@ class GenericNode { /// [span] is carried through unchanged unless set explicitly. /// [parent] is populated on the returned tree for nodes originating from [Parser.parseModule]; /// nodes constructed from scratch retain their given `parent`. - external function walk(visit: (GenericNode) -> Pair?): GenericNode + external function transform(operator: (GenericNode) -> Pair?): GenericNode /// Fold [operator] over this node and its descendants, top-down in pre-order. /// From 57253e69122f8f7efe8f8c832b2d3164b4626657 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Fri, 7 Aug 2026 14:57:59 +0200 Subject: [PATCH 49/49] Add parseExpression to the Parser --- .../pkl/core/stdlib/syntax/ParserNodes.java | 54 +++++++++++++++++++ .../input/syntax/expressions.pkl | 3 +- .../java/org/pkl/parser/GenericParser.java | 4 ++ .../org/pkl/parser/GenericParserImpl.java | 10 ++++ stdlib/syntax.pkl | 8 +++ 5 files changed, 77 insertions(+), 2 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java index 6039ae819..96363519b 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/syntax/ParserNodes.java @@ -1544,6 +1544,38 @@ protected Object evalResource(@SuppressWarnings("unused") VmTyped self, VmTyped } } + public abstract static class parseExpression extends ExternalMethod1Node { + @Specialization + @TruffleBoundary + protected Object evalString(@SuppressWarnings("unused") VmTyped self, String source) { + return doParseExpression(source, null); + } + + @Specialization + @TruffleBoundary + protected Object evalResource(@SuppressWarnings("unused") VmTyped self, VmTyped source) { + // `source` is a `pkl.base#Resource` + var text = (String) VmUtils.readMember(source, Identifier.TEXT); + return doParseExpression(text, sourceUri(source)); + } + } + + public abstract static class parseExpressionOrNull extends ExternalMethod1Node { + @Specialization + @TruffleBoundary + protected Object evalString(@SuppressWarnings("unused") VmTyped self, String source) { + return doParseExpressionOrNull(source, null); + } + + @Specialization + @TruffleBoundary + protected Object evalResource(@SuppressWarnings("unused") VmTyped self, VmTyped source) { + // `source` is a `pkl.base#Resource` + var text = (String) VmUtils.readMember(source, Identifier.TEXT); + return doParseExpressionOrNull(text, sourceUri(source)); + } + } + private static String sourceUri(VmTyped resource) { return VmUtils.readMember(resource, Identifier.URI).toString(); } @@ -1572,6 +1604,28 @@ private static Object doParseOrNull(String src, @Nullable String sourceUri) { } } + private static Object doParseExpression(String src, @Nullable String sourceUri) { + var sourceChars = src.toCharArray(); + try { + var parser = new GenericParser(); + var root = parser.parseExpressionInput(src); + return wrapExpr(convertNode(root, sourceChars, sourceUri)); + } catch (GenericParserError e) { + throw new VmExceptionBuilder().evalError("parserError").withHint(e.toString()).build(); + } + } + + private static Object doParseExpressionOrNull(String src, @Nullable String sourceUri) { + var sourceChars = src.toCharArray(); + try { + var parser = new GenericParser(); + var root = parser.parseExpressionInput(src); + return wrapExpr(convertNode(root, sourceChars, sourceUri)); + } catch (GenericParserError e) { + return VmNull.withoutDefault(); + } + } + private static VmTyped convertNode( Node genericNode, char[] sourceChars, @Nullable String sourceUri) { // convert children recursively diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl index 4d400ac32..4b32a6ac7 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/expressions.pkl @@ -2,8 +2,7 @@ amends "../snippetTest.pkl" import "pkl:syntax" -local function expr(source: String) = - new syntax.Parser {}.parseModule("x = \(source)").properties["x"].value +local function expr(source: String) = new syntax.Parser {}.parseExpression(source) facts { ["literals"] { diff --git a/pkl-parser/src/main/java/org/pkl/parser/GenericParser.java b/pkl-parser/src/main/java/org/pkl/parser/GenericParser.java index 3cb01dc64..a770c6a47 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/GenericParser.java +++ b/pkl-parser/src/main/java/org/pkl/parser/GenericParser.java @@ -21,4 +21,8 @@ public final class GenericParser { public Node parseModule(String source) { return new GenericParserImpl(source).parseModule(); } + + public Node parseExpressionInput(String source) { + return new GenericParserImpl(source).parseExpressionInput(); + } } diff --git a/pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java b/pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java index 40ab6cf99..7b6fa5699 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java +++ b/pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java @@ -102,6 +102,16 @@ Node parseModule() { return new Node(NodeType.MODULE, nodes); } + Node parseExpressionInput() { + ff(); + var expr = parseExpr(); + ff(); + if (lookahead != Token.EOF) { + throw parserError("unexpectedToken", _lookahead.text(lexer), "end of file"); + } + return expr; + } + private Node parseModuleDecl(List preChildren) { var headerParts = getHeaderParts(preChildren); var children = new ArrayList<>(headerParts.preffixes); diff --git a/stdlib/syntax.pkl b/stdlib/syntax.pkl index 0a70d785c..1dca78806 100644 --- a/stdlib/syntax.pkl +++ b/stdlib/syntax.pkl @@ -27,6 +27,14 @@ class Parser { /// Parse the string or resource as a Pkl module, returning either a typed AST node or null /// in case of an error. external function parseModuleOrNull(source: String | Resource): ModuleNode? + + /// Parse the string or resource as a Pkl expression, returning either a typed AST node or + /// throwing an error. + external function parseExpression(source: String | Resource): ExprNode + + /// Parse the string or resource as a Pkl expression, returning either a typed AST node or null + /// in case of an error. + external function parseExpressionOrNull(source: String | Resource): ExprNode? } /// Renders a [GenericNode] back to Pkl source code.