Add an s-exp renderer to pkl:syntax (#1955)
This commit is contained in:
1 parent
85b4a4669a
commit
fc5d658aa3
3 files changed
+223
No files matched your search
@@ -0,0 +1,58 @@
|
||||
amends "../snippetTest.pkl"
|
||||
|
||||
import "pkl:syntax"
|
||||
|
||||
local renderer = new syntax.SExpressionRenderer { renderTerminalText = false }
|
||||
local textRenderer = new syntax.SExpressionRenderer {}
|
||||
|
||||
local function parseExpr(source: String): syntax.Node =
|
||||
new syntax.Parser {}.parseExpression(source)
|
||||
|
||||
local function parseModule(source: String): syntax.Node =
|
||||
new syntax.Parser {}.parseModule(source)
|
||||
|
||||
facts {
|
||||
["terminal"] {
|
||||
renderer.renderValue(parseExpr("5")) == "(int_literal_expr)"
|
||||
}
|
||||
|
||||
["terminal with text"] {
|
||||
textRenderer.renderValue(parseExpr("5")) == #"(int_literal_expr "5")"#
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
examples {
|
||||
["nested nodes are indented"] {
|
||||
renderer.renderValue(parseExpr("1 + 2"))
|
||||
}
|
||||
|
||||
["nested nodes with terminal text"] {
|
||||
textRenderer.renderValue(parseExpr("1 + 2"))
|
||||
}
|
||||
|
||||
["keywords and punctuation are rendered without text"] {
|
||||
textRenderer.renderValue(parseExpr("if (true) x else 0"))
|
||||
}
|
||||
|
||||
["terminal text is escaped"] {
|
||||
textRenderer.renderValue(parseExpr(#""a \"b\"\n""#))
|
||||
}
|
||||
|
||||
["module"] {
|
||||
renderer.renderValue(parseModule("x = 1"))
|
||||
textRenderer.renderValue(parseModule("x = 1"))
|
||||
}
|
||||
|
||||
["class"] {
|
||||
renderer.renderValue(
|
||||
parseModule(
|
||||
"""
|
||||
class Bird {
|
||||
name: String
|
||||
}
|
||||
""",
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
facts {
|
||||
["terminal"] {
|
||||
true
|
||||
}
|
||||
["terminal with text"] {
|
||||
true
|
||||
}
|
||||
}
|
||||
examples {
|
||||
["nested nodes are indented"] {
|
||||
"""
|
||||
(binary_op_expr
|
||||
(int_literal_expr)
|
||||
(operator)
|
||||
(int_literal_expr))
|
||||
"""
|
||||
}
|
||||
["nested nodes with terminal text"] {
|
||||
"""
|
||||
(binary_op_expr
|
||||
(int_literal_expr "1")
|
||||
(operator "+")
|
||||
(int_literal_expr "2"))
|
||||
"""
|
||||
}
|
||||
["keywords and punctuation are rendered without text"] {
|
||||
"""
|
||||
(if_expr
|
||||
(if_header
|
||||
(if_keyword)
|
||||
(if_condition
|
||||
(lparen)
|
||||
(if_condition_expr
|
||||
(bool_literal_expr "true"))
|
||||
(rparen)))
|
||||
(if_then_expr
|
||||
(unqualified_access_expr
|
||||
(identifier "x")))
|
||||
(else_keyword)
|
||||
(if_else_expr
|
||||
(int_literal_expr "0")))
|
||||
"""
|
||||
}
|
||||
["terminal text is escaped"] {
|
||||
"""
|
||||
(single_line_string_literal_expr
|
||||
(string_start "\\"")
|
||||
(string_chars "a ")
|
||||
(string_escape "\\\\\\"")
|
||||
(string_chars "b")
|
||||
(string_escape "\\\\\\"")
|
||||
(string_escape "\\\\n")
|
||||
(string_end "\\""))
|
||||
"""
|
||||
}
|
||||
["module"] {
|
||||
"""
|
||||
(module
|
||||
(class_property
|
||||
(class_property_header
|
||||
(class_property_header_begin
|
||||
(identifier)))
|
||||
(assign)
|
||||
(class_property_body
|
||||
(int_literal_expr))))
|
||||
"""
|
||||
"""
|
||||
(module
|
||||
(class_property
|
||||
(class_property_header
|
||||
(class_property_header_begin
|
||||
(identifier "x")))
|
||||
(assign)
|
||||
(class_property_body
|
||||
(int_literal_expr "1"))))
|
||||
"""
|
||||
}
|
||||
["class"] {
|
||||
"""
|
||||
(module
|
||||
(class
|
||||
(class_header
|
||||
(class_keyword)
|
||||
(identifier))
|
||||
(class_body
|
||||
(lbrace)
|
||||
(class_body_elements
|
||||
(class_property
|
||||
(class_property_header
|
||||
(class_property_header_begin
|
||||
(identifier)))
|
||||
(type_annotation
|
||||
(colon)
|
||||
(declared_type
|
||||
(qualified_identifier
|
||||
(identifier))))))
|
||||
(rbrace))))
|
||||
"""
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,71 @@ class FormattingRenderer {
|
||||
external function renderValue(node: Node): String
|
||||
}
|
||||
|
||||
/// Renders a [Node] as an [S-expression](https://en.wikipedia.org/wiki/S-expression).
|
||||
///
|
||||
/// Each node is rendered as its [type][Node.type] in parentheses, followed by its
|
||||
/// [children][Node.children], each on its own line and indented by two spaces.
|
||||
/// Nodes without children are rendered on a single line.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// new SExpressionRenderer {}.renderValue(new Parser {}.parseExpression("1 + 2"))
|
||||
/// ```
|
||||
///
|
||||
/// The above renders:
|
||||
///
|
||||
/// ```
|
||||
/// (binary_op_expr
|
||||
/// (int_literal_expr "1")
|
||||
/// (operator "+")
|
||||
/// (int_literal_expr "2"))
|
||||
/// ```
|
||||
class SExpressionRenderer {
|
||||
/// Whether to render the [Node.text] of terminal nodes whose text is not implied by their
|
||||
/// [Node.type].
|
||||
///
|
||||
/// If [true], the text follows the type as a quoted, escaped string, as in
|
||||
/// `(int_literal_expr "5")`.
|
||||
/// If [false], only the type is rendered.
|
||||
renderTerminalText: Boolean = true
|
||||
|
||||
/// The types of the terminal nodes that [renderTerminalText] applies to.
|
||||
local textTypes: Set<TerminalNodeType> =
|
||||
Set(
|
||||
"identifier",
|
||||
"modifier",
|
||||
"operator",
|
||||
"bool_literal_expr",
|
||||
"int_literal_expr",
|
||||
"float_literal_expr",
|
||||
"string_start",
|
||||
"string_multi_start",
|
||||
"string_end",
|
||||
"interpolation_start",
|
||||
"string_chars",
|
||||
"string_escape",
|
||||
"string_continuation",
|
||||
"shebang",
|
||||
"line_comment",
|
||||
"block_comment",
|
||||
"doc_comment_line",
|
||||
)
|
||||
|
||||
/// Renders [value] as a S-expression.
|
||||
function renderValue(value: Node): String = renderIndented(value, "")
|
||||
|
||||
local function renderIndented(node: Node, indent: String): String =
|
||||
if (node.children.isEmpty)
|
||||
if (renderTerminalText && textTypes.contains(node.type))
|
||||
"\(indent)(\(node.type) \(new PcfRenderer {}.renderValue(node.text)))"
|
||||
else
|
||||
"\(indent)(\(node.type))"
|
||||
else
|
||||
"\(indent)(\(node.type)\n"
|
||||
+ node.children.map((child) -> renderIndented(child, indent + " ")).join("\n")
|
||||
+ ")"
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
||||
Reference in new issue
Block a user