Add pkl:syntax module - #1569
Conversation
|
Have you considered offering the equivalent Java library? Wrapping that library instead of implementing |
|
The major thing missing from this PR is the ability to whole-cloth construct and then format |
|
The Java library is already |
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. |
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.
What do you mean by cached? |
56827fa to
3d3ae43
Compare
| | "module_declaration" | ||
| | "module_definition" |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Can we just change this in the Java parser too?
d277baa to
c6ba6c2
Compare
| /// [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 |
There was a problem hiding this comment.
What's the difference between (node) -> null and (node) -> Pair(node, true)?
Also, maybe call this method "transform", and call the argument "operator".
There was a problem hiding this comment.
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.
| grammarVersion: "V1" | "V2" = "V2" | ||
|
|
||
| /// Render [node] as Pkl source code. | ||
| external function render(node: Node): String |
There was a problem hiding this comment.
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): StringWhere 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Do users call render(syntaxNode.builtNode)
Yes. It's a lossy operation. To perfectly keep all affixes you have to use GenericNode.
4f7a99c to
fd4d48a
Compare
| /// The integer literal (e.g. `42`, `"0xFF"`). | ||
| value: Int | String |
There was a problem hiding this comment.
I was going to suggest this
| /// 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!
| 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> | ||
| } |
There was a problem hiding this comment.
These are missing support for custom string delimiters, which should also influence how escapes/newlines/interpolations are rendered
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.