-
Notifications
You must be signed in to change notification settings - Fork 440
Allow combining SwiftParser and SwiftSyntaxBuilder to generate source code #848
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow combining SwiftParser and SwiftSyntaxBuilder to generate source code #848
Conversation
@swift-ci Please test |
f77ae29
to
f357bf8
Compare
@swift-ci Please test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, this looks very interesting! I haven't dug too deep into this and SwiftParser
in general, so I can't comment on the details, but some minor notes below.
SwitchStmt(expression: "data", cases: SwitchCaseList([ | ||
SwitchCase( | ||
label: SwitchCaseLabel( | ||
caseItems: CaseItem( | ||
pattern: ExpressionPattern( | ||
expression: FunctionCallExpr(MemberAccessExpr(name: "buildable")) { | ||
TupleExprElement( | ||
expression: UnresolvedPatternExpr( | ||
pattern: ValueBindingPattern( | ||
letOrVarKeyword: .let, | ||
valuePattern: IdentifierPattern("buildableData") | ||
) | ||
) | ||
) | ||
} | ||
) | ||
) | ||
), | ||
statements: CodeBlockItemList { | ||
CodeBlockItem( | ||
item: ReturnStmt(expression: SequenceExpr { | ||
MemberAccessExpr(base: "buildableData", name: "trailingComma") | ||
BinaryOperatorExpr("!=") | ||
NilLiteralExpr() | ||
}) | ||
) | ||
} | ||
), | ||
SwitchCase( | ||
label: SwitchCaseLabel( | ||
caseItems: CaseItem( | ||
pattern: ExpressionPattern( | ||
expression: FunctionCallExpr(MemberAccessExpr(name: "constructed")) { | ||
TupleExprElement( | ||
expression: UnresolvedPatternExpr( | ||
pattern: ValueBindingPattern( | ||
letOrVarKeyword: .let, | ||
valuePattern: IdentifierPattern("node") | ||
) | ||
) | ||
) | ||
} | ||
) | ||
) | ||
), | ||
statements: CodeBlockItemList { | ||
CodeBlockItem( | ||
item: ReturnStmt(expression: SequenceExpr { | ||
MemberAccessExpr(base: "node", name: "trailingComma") | ||
BinaryOperatorExpr("!=") | ||
NilLiteralExpr() | ||
}) | ||
) | ||
} | ||
) | ||
])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These switch statements can be written a bit more concisely using the builder-based initializers. I usually also keep the label/caseItems/pattern/expression
boilerplate on a single line, but perhaps there is some other way too to avoid the pyramid of doom here.
SwitchStmt(expression: "data", cases: SwitchCaseList([ | |
SwitchCase( | |
label: SwitchCaseLabel( | |
caseItems: CaseItem( | |
pattern: ExpressionPattern( | |
expression: FunctionCallExpr(MemberAccessExpr(name: "buildable")) { | |
TupleExprElement( | |
expression: UnresolvedPatternExpr( | |
pattern: ValueBindingPattern( | |
letOrVarKeyword: .let, | |
valuePattern: IdentifierPattern("buildableData") | |
) | |
) | |
) | |
} | |
) | |
) | |
), | |
statements: CodeBlockItemList { | |
CodeBlockItem( | |
item: ReturnStmt(expression: SequenceExpr { | |
MemberAccessExpr(base: "buildableData", name: "trailingComma") | |
BinaryOperatorExpr("!=") | |
NilLiteralExpr() | |
}) | |
) | |
} | |
), | |
SwitchCase( | |
label: SwitchCaseLabel( | |
caseItems: CaseItem( | |
pattern: ExpressionPattern( | |
expression: FunctionCallExpr(MemberAccessExpr(name: "constructed")) { | |
TupleExprElement( | |
expression: UnresolvedPatternExpr( | |
pattern: ValueBindingPattern( | |
letOrVarKeyword: .let, | |
valuePattern: IdentifierPattern("node") | |
) | |
) | |
) | |
} | |
) | |
) | |
), | |
statements: CodeBlockItemList { | |
CodeBlockItem( | |
item: ReturnStmt(expression: SequenceExpr { | |
MemberAccessExpr(base: "node", name: "trailingComma") | |
BinaryOperatorExpr("!=") | |
NilLiteralExpr() | |
}) | |
) | |
} | |
) | |
])) | |
SwitchStmt(expression: "data") { | |
SwitchCase( | |
label: SwitchCaseLabel( | |
caseItems: CaseItem( | |
pattern: ExpressionPattern( | |
expression: FunctionCallExpr(MemberAccessExpr(name: "buildable")) { | |
TupleExprElement( | |
expression: UnresolvedPatternExpr( | |
pattern: ValueBindingPattern( | |
letOrVarKeyword: .let, | |
valuePattern: IdentifierPattern("buildableData") | |
) | |
) | |
) | |
} | |
) | |
) | |
) | |
) { | |
ReturnStmt(expression: SequenceExpr { | |
MemberAccessExpr(base: "buildableData", name: "trailingComma") | |
BinaryOperatorExpr("!=") | |
NilLiteralExpr() | |
}) | |
} | |
SwitchCase( | |
label: SwitchCaseLabel( | |
caseItems: CaseItem( | |
pattern: ExpressionPattern( | |
expression: FunctionCallExpr(MemberAccessExpr(name: "constructed")) { | |
TupleExprElement( | |
expression: UnresolvedPatternExpr( | |
pattern: ValueBindingPattern( | |
letOrVarKeyword: .let, | |
valuePattern: IdentifierPattern("node") | |
) | |
) | |
) | |
} | |
) | |
) | |
) | |
) { | |
ReturnStmt(expression: SequenceExpr { | |
MemberAccessExpr(base: "node", name: "trailingComma") | |
BinaryOperatorExpr("!=") | |
NilLiteralExpr() | |
}) | |
} | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. That’s a little cleaner. I’ll probably migrate all of this to be parser-based in a follow-up PR.
TupleExprElement( | ||
label: child.isUnexpectedNodes ? nil : child.swiftName, | ||
expression: child.generateExprBuildSyntaxNode(varName: child.swiftName, formatName: "format") | ||
SwitchStmt(expression: "data", cases: SwitchCaseList([ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly for these switch statements
@@ -1,6 +1,43 @@ | |||
@_spi(RawSyntax) | |||
import SwiftSyntax | |||
|
|||
fileprivate class Indenter: SyntaxRewriter { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not that familiar with SwiftParser
yet, so apologies if I'm missing something. Why do we need an indentation rewriter within the parser? Don't Swift's multi-line string literals already handle indentation properly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that if you have
let nested = """
first
second
"""
let wrapping = """
\(nested)
"""
print(wrapping)
you get
first
second
where second
is not indented. Basically all I’m doing here is indenting all lines in the string interpolation to the current indentation level.
… code This is a proof-of-concept how the parser can be used to generate SwiftSyntax trees for statically known boilerplate while using SwiftSyntaxBuilder for repetitive or dynamic constructs. Once this is merged, I will try to migrate generate-swiftsyntaxbuilder to this scheme to make it easier to read. Also ignore how messy BuildableNodes.swift looks by now. I will try to clean it up once generate-swiftsyntaxbuilder has been migrated to use the parser and is thus easier to work with.
f357bf8
to
9ce266f
Compare
@swift-ci Please test |
This is a proof-of-concept how the parser can be used to generate SwiftSyntax trees for statically known boilerplate while using SwiftSyntaxBuilder for repetitive or dynamic constructs.
Once this is merged, I will try to migrate
generate-swiftsyntaxbuilder
to this scheme to make it easier to read.Also ignore how messy BuildableNodes.swift looks by now. I will try to clean it up once
generate-swiftsyntaxbuilder
has been migrated to use the parser and is thus easier to work with.Example
This allows us to generate
by constructing the repetitive cases using SwiftSyntaxBuilder (which can play its strength here)
and all the static boilerplate by invoking the parser with string interpolation to add the cases.