423 lines
13 KiB
Plaintext
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"
|