Skip to content

Add pkl:syntax module - #1569

Open
stackoverflow wants to merge 48 commits into
apple:mainfrom
stackoverflow:pkl-syntax
Open

Add pkl:syntax module #1569
stackoverflow wants to merge 48 commits into
apple:mainfrom
stackoverflow:pkl-syntax

Conversation

@stackoverflow

Copy link
Copy Markdown
Contributor

This new stdlib module allows for full manipulation of Pkl source code in Pkl: parsing text to a Pkl CST and formatting a Pkl CST back to text.

@odenix

odenix commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Have you considered offering the equivalent Java library? Wrapping that library instead of implementing pkl:syntax in Pkl would also improve (especially cold/warm) performance of pkl:syntax.

@HT154

HT154 commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

The major thing missing from this PR is the ability to whole-cloth construct and then format SyntaxNode instances without laboriously creating the underlying Nodes, especially given how common and important code generation is for the Pkl ecosystem. I'm not opposed to offering a Java API (pklpoet?) for this as long as the SyntaxNode classes remain instantiable (not external).

@stackoverflow

Copy link
Copy Markdown
Contributor Author

The Java library is already Node. We still need the classes to be implemented in Pkl because we don't want to offer a generic API to users. As Jen mentioned, we don't want the classes to be external.

@odenix

odenix commented May 22, 2026

Copy link
Copy Markdown
Contributor

As Jen mentioned, we don't want the classes to be external.

As far as I know, external members of non-external classes can still be implemented in Java.

If this stdlib module is implemented in Pkl, it should not be cached (as it is now), as this is very likely to cause multithreading issues.

@stackoverflow

Copy link
Copy Markdown
Contributor Author

As Jen mentioned, we don't want the classes to be external.

As far as I know, external members of non-external classes can still be implemented in Java.

I don't think there would be much difference performance wise in implementing it in java. All the parsing and formatting is already in java. The Pkl classes are just a sugar layer.

If this stdlib module is implemented in Pkl, it should not be cached (as it is now), as this is very likely to cause multithreading issues.

What do you mean by cached?

@stackoverflow
stackoverflow marked this pull request as ready for review May 27, 2026 15:59
Comment thread pkl-core/src/test/files/LanguageSnippetTests/input/syntax/types.pkl Outdated
Comment thread pkl-core/src/main/java/org/pkl/core/stdlib/syntax/SyntaxNodes.java Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread pkl-core/src/test/files/LanguageSnippetTests/input/syntax/builders.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated

@bioball bioball left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some initial comments

Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl
Comment on lines +152 to +153
| "module_declaration"
| "module_definition"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be called "module_header" and "module_declaration", respectively.

Where, module_header is all of:

module Foo

amends "bar"

and module_declaration is specifically the line that starts with module, e.g.

module Foo

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned some comments above, this is done to keep the Java and Pkl nodes in sync. Diverging them is possible but not simple.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just change this in the Java parser too?

Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
/// [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, Boolean>?): Node

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between (node) -> null and (node) -> Pair(node, true)?

Also, maybe call this method "transform", and call the argument "operator".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pair(node, true) means "I want to visit/process this node, but not its children". null means I don't want to visit/process this node, nor its children.
It's true you can just pass the node unchanged to the pair, but null makes this intent more explicit. Also, most calls to walk only want to process a single or couple things and don't bother about the rest. Accepting null makes this simpler and more performant (no need to allocate a pair for every node visited, just check for null).

Also, maybe call this method "transform", and call the argument "operator".

Done.

Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl
Comment thread stdlib/syntax.pkl
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
grammarVersion: "V1" | "V2" = "V2"

/// Render [node] as Pkl source code.
external function render(node: Node): String

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you render nodes from the typed syntax API? Do users call render(syntaxNode.builtNode)?

I wonder if we should have:

external function render(node: Node | SyntaxNode): String

Where the implementation calls builtNode if given a SyntaxNode. And, if so, I wonder if we even need builtNode as a user-facing API? The underlying implementation would do all the building under the hood.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think builtNode retains utility in cases where you want to transform a parsed module (incl. round-tripping affixes) and add/replace something built from a SyntaxNode.

@bioball bioball Aug 1, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the "transform a parsed module" scenario, you probably should be transforming the raw node. If you go into a typed node and back via builtNode, you'll end up with all sorts of changes to your code that you didn't intend; re-organized members, missing comments, etc.

@HT154 HT154 Aug 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, that wasn't super clear. I don't mean parse -> generic -> typed -> some transformation -> generic -> render, I mean parse -> generic -> walk -> modify a (generic) object body and append a child that was constructed from a typed node -> render. For example: modifying a PklProject file to add a dependency, where the new entry was created as a SyntaxNode.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do users call render(syntaxNode.builtNode)

Yes. It's a lossy operation. To perfectly keep all affixes you have to use GenericNode.

Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl
Comment on lines +606 to +607
/// The integer literal (e.g. `42`, `"0xFF"`).
value: Int | String

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to suggest this

Suggested change
/// The integer literal (e.g. `42`, `"0xFF"`).
value: Int | String
/// The integer literal (e.g. `42`, `"0xFF"`).
value: Int | String(toIntOrNull() != null)

But then I realized we don't actually have any in-language API for parsing the non-decimal int formats supported in Pkl code!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would work if we pick up #1808

Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl
Comment thread stdlib/syntax.pkl Outdated
Comment thread stdlib/syntax.pkl
Comment on lines +617 to +628
class SingleLineStringLiteralExprNode extends ExprNode {
/// The string parts (chars, escapes, interpolations).
parts: List<StringPartNode>
}

/// A multi-line string literal expression.
///
/// Use [StringNewlineNode] entries in [parts] to separate lines.
class MultiLineStringLiteralExprNode extends ExprNode {
/// The string parts (chars, escapes, newlines, interpolations).
parts: List<StringPartNode>
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are missing support for custom string delimiters, which should also influence how escapes/newlines/interpolations are rendered

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants