Files
pkl/stdlib/syntax.pkl
T
Daniel Chao a9b7fe3a33 Switch type argument syntax (#1940)
This changes type argument syntax to "standard" style, and away from turbofish.

The motivation here is that turbofish-style type arguments are hard to read, and developers who are not already deeply knowledgeable about Pkl might struggle to understand such code.
In most languages, the grammar for "method call with type args" don't introduce any additional symbols between the identifier and the type argument list (e.g. C++, TypeScript, C#, Scala, Go).
2026-11-03 05:09:40 -08:00

423 lines
13 KiB
Plaintext

//===----------------------------------------------------------------------===//
// 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.
@ModuleInfo { minPklVersion = "0.33.0" }
@Since { version = "0.33.0" }
module pkl.syntax
/// Utilities to parse Pkl source code to a syntax tree.
class Parser {
/// Parse the string or resource as a Pkl module, returning either a syntax node or
/// throwing an error.
external function parseModule(source: String | Resource): Node
/// Parse the string or resource as a Pkl module, returning either a syntax node or null
/// in case of an error.
external function parseModuleOrNull(source: String | Resource): Node?
/// Parse the string or resource as a Pkl expression, returning either a syntax node or
/// throwing an error.
external function parseExpression(source: String | Resource): Node
/// Parse the string or resource as a Pkl expression, returning either a syntax node or null
/// in case of an error.
external function parseExpressionOrNull(source: String | Resource): Node?
}
/// Renders a [Node] back to Pkl source code, using the canonical Pkl formatter.
class FormattingRenderer {
/// 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
}
/// A generic, untyped node in the Pkl syntax tree.
///
/// A node is either a *leaf*, carrying no [children], or a *branch*, whose content is its
/// [children]. Either way, [text] is the source code the node spans.
class Node {
/// The kind of this node.
type: NodeType
/// The child nodes, in source order. Empty for leaf nodes.
children: List<Node>(isTerminal.implies(isEmpty))
/// The parent node, or [null] for the root and for nodes not attached to a tree.
hidden parent: Node?
/// The verbatim source text for this node.
///
/// [FormattingRenderer] only renders this text if this is a [terminal][isTerminal] node.
text: String?(isTerminal.implies(this != null))
/// The source location of this node.
span: Span?
/// Tells if this is a terminal node; i.e. a node that spans a single token of the grammar.
///
/// A terminal never has [children], but not every node without [children] is a terminal.
fixed isTerminal: Boolean = type is TerminalNodeType
/// Fold [operator] over this node and its descendants, top-down in pre-order.
///
/// Example:
/// ```
/// // count the if-expressions in a module
/// module.fold(0, (acc, n) -> if (n.type == "if_expr") acc + 1 else acc)
/// ```
external function fold<Result>(initial: Result, operator: (Result, Node) -> Result): Result
/// Returns the first child matching [predicate], searching depth-first in pre-order.
///
/// *Child* means any node below this one, at any depth.
/// This node itself is not considered.
/// The search stops at the first match.
///
/// Throws if no child matches [predicate].
///
/// Example:
/// ```
/// // the name of the first class declared in a module
/// module.findChild((n) -> n.type == "class").findChildOfType("identifier").text
/// ```
external function findChild(predicate: (Node) -> Boolean): Node
/// Same as [findChild()] but returns [null] if no child matches [predicate].
external function findChildOrNull(predicate: (Node) -> Boolean): Node?
/// Returns every child matching [predicate], in depth-first pre-order.
///
/// *Child* means any node below this one, at any depth.
/// This node itself is not considered.
/// A matching node is searched for further matches, so a match may contain another.
external function findChildren(predicate: (Node) -> Boolean): List<Node>
/// Returns the first child whose [type] is [type].
///
/// Throws if no child has this type.
external function findChildOfType(type: NodeType): Node
/// Same as [findChildOfType()] but returns [null] if no child has this type.
external function findChildOfTypeOrNull(type: NodeType): Node?
/// Returns every child whose [type] is [type], in depth-first pre-order.
external function findChildrenOfType(type: NodeType): List<Node>
/// Returns this node with the first child matching [predicate] replaced by [replacer]'s result.
///
/// The child replaced is the one [findChild()] returns. If there is none, the tree is
/// returned unchanged.
/// The replacement is left as it is, so it is not searched for further matches.
external function replaceChild(predicate: (Node) -> Boolean, replacer: (Node) -> Node): Node
/// Returns this node with every child matching [predicate] replaced by [replacer]'s result.
///
/// The children replaced are those [findChildren()] returns, except that a replacement is left
/// as it is, so a match nested in another match is left to [replacer].
external function replaceChildren(predicate: (Node) -> Boolean, replacer: (Node) -> Node): Node
/// Returns this node with the first child whose [type] is [type] replaced by [replacer]'s result.
///
/// If there is no such child, the tree is returned unchanged.
external function replaceChildOfType(type: NodeType, replacer: (Node) -> Node): Node
/// Returns this node with every child whose [type] is [type] replaced by [replacer]'s result.
external function replaceChildrenOfType(type: NodeType, replacer: (Node) -> Node): Node
/// Returns the nearest parent matching [predicate].
///
/// Throws if no parent matches [predicate].
external function findParent(predicate: (Node) -> Boolean): Node
/// Same as [findParent()] but returns [null] if no parent matches [predicate].
external function findParentOrNull(predicate: (Node) -> Boolean): Node?
/// Returns the nearest parent whose [type] is [type].
///
/// Throws if no parent has this type.
external function findParentOfType(type: NodeType): Node
/// Same as [findParentOfType()] but returns [null] if no parent has this type.
external function findParentOfTypeOrNull(type: NodeType): Node?
/// Returns every parent matching [predicate], nearest first.
external function findParents(predicate: (Node) -> Boolean): List<Node>
/// Tells whether any parent matches [predicate].
external function hasParent(predicate: (Node) -> Boolean): Boolean
/// Tells whether any parent's [type] is [type].
external function hasParentOfType(type: NodeType): Boolean
}
/// A range of source code, spanning [start] (inclusive) to [end] (exclusive).
class Span {
/// The start of this span.
start: SourceLocation
/// The end of this span.
end: SourceLocation
/// The display URI of this span.
///
/// Rendered for the editor configured in the Pkl settings file, the same way a location is
/// rendered in a stack trace.
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 1-based column number of this source location.
column: UInt
/// A URI identifying this source location, suitable for display.
///
/// The exact format depends on where this location came from and on the editor configured in the
/// Pkl settings file.
displayUri: String
}
/// The kinds of node in the Pkl syntax tree.
typealias NodeType =
TerminalNodeType
| ExprNodeType
| TypeNodeType
| ObjectMemberNodeType
| ClassMemberNodeType
| ModuleMemberNodeType
// module structure
| "module"
| "doc_comment"
| "modifier_list"
| "amends_clause"
| "extends_clause"
| "module_declaration"
| "module_definition"
| "annotation"
| "qualified_identifier"
| "import"
| "import_alias"
| "import_list"
| "typealias_header"
| "typealias_body"
| "class_header"
| "class_header_extends"
| "class_body"
| "class_body_elements"
| "method_header"
| "method_body"
| "class_property_header"
| "class_property_header_begin"
| "class_property_body"
| "object_body"
| "object_member_elements"
| "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_constant"
// parts of members
| "object_property_header"
| "object_property_header_begin"
| "object_property_body"
| "object_entry_header"
| "member_predicate_header"
| "when_generator_header"
| "for_generator_header"
| "for_generator_header_definition"
| "for_generator_header_definition_header"
// parts of expressions
| "new_header"
| "function_literal_body"
| "if_header"
| "if_condition"
| "if_condition_expr"
| "if_then_expr"
| "if_else_expr"
| "let_parameter_definition"
| "let_parameter"
// parts of types
| "function_type_parameters"
| "parenthesized_type_elements"
| "constrained_type_constraint"
| "constrained_type_elements"
/// The [NodeType]s that describe a terminal: a node that spans a single token of the grammar, such
/// as a keyword, a punctuation character, an identifier or a literal.
///
/// A terminal never has [Node.children].
typealias TerminalNodeType =
// keywords
"amends_keyword"
| "as_keyword"
| "class_keyword"
| "else_keyword"
| "extends_keyword"
| "for_keyword"
| "function_keyword"
| "if_keyword"
| "import_keyword"
| "import_star_keyword"
| "in_keyword"
| "let_keyword"
| "module_keyword"
| "new_keyword"
| "out_keyword"
| "read_keyword"
| "read_question_keyword"
| "read_star_keyword"
| "super_keyword"
| "throw_keyword"
| "trace_keyword"
| "typealias_keyword"
| "when_keyword"
// punctuation
| "arrow"
| "assign"
| "at"
| "colon"
| "comma"
| "dot"
| "gt"
| "lbrace"
| "lbrack"
| "lparen"
| "lpred"
| "lt"
| "minus"
| "not"
| "qspread"
| "question"
| "rbrace"
| "rbrack"
| "rparen"
| "spread"
| "star"
| "underscore"
| "union"
// string delimiters
| "interpolation_start"
| "string_start"
| "string_multi_start"
| "string_end"
// string content
| "string_chars"
| "string_newline"
| "string_continuation"
| "string_escape"
// affixes
| "shebang"
| "line_comment"
| "block_comment"
| "semicolon"
| "doc_comment_line"
// names, modifiers and operators
| "identifier"
| "modifier"
| "operator"
// literals
| "bool_literal_expr"
| "int_literal_expr"
| "float_literal_expr"
// expressions without operands
| "this_expr"
| "outer_expr"
| "module_expr"
| "null_expr"
// types without operands
| "unknown_type"
| "nothing_type"
| "module_type"
| "this_type"
/// The [NodeType]s that describe an expression.
typealias ExprNodeType =
"this_expr"
| "outer_expr"
| "module_expr"
| "null_expr"
| "throw_expr"
| "trace_expr"
| "import_expr"
| "read_expr"
| "new_expr"
| "unary_minus_expr"
| "logical_not_expr"
| "function_literal_expr"
| "parenthesized_expr"
| "super_subscript_expr"
| "super_access_expr"
| "subscript_expr"
| "qualified_access_expr"
| "if_expr"
| "let_expr"
| "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"
/// The [NodeType]s that describe a type.
typealias TypeNodeType =
"unknown_type"
| "nothing_type"
| "module_type"
| "this_type"
| "union_type"
| "function_type"
| "parenthesized_type"
| "declared_type"
| "nullable_type"
| "string_literal_type"
| "constrained_type"
/// The [NodeType]s that describe a member of an object body.
typealias ObjectMemberNodeType =
"object_element"
| "object_property"
| "object_method"
| "member_predicate"
| "object_entry"
| "object_spread"
| "when_generator"
| "for_generator"
/// The [NodeType]s that describe a member of a class body.
typealias ClassMemberNodeType = "class_property" | "class_method"
/// The [NodeType]s that describe a member of a module.
typealias ModuleMemberNodeType = "class_property" | "class_method" | "class" | "typealias"