Skip to content

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

Merged
merged 1 commit into from
Sep 28, 2022

Conversation

ahoppen
Copy link
Member

@ahoppen ahoppen commented Sep 27, 2022

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

func plusOne(base: Int) -> Int {
  switch base {

  case 0:
    return 1
  case 1:
    return 2
  default:
    return -1
  }
}

by constructing the repetitive cases using SwiftSyntaxBuilder (which can play its strength here)

let cases = SwitchCaseList {
  for i in 0..<2 {
    SwitchCase("""
    case \(i):
      return \(i + 1)
    """)
  }
}

and all the static boilerplate by invoking the parser with string interpolation to add the cases.

let plusOne = FunctionDeclSyntax("""
func plusOne(base: Int) -> Int {
  switch base {
  \(cases)
  default:
    return -1
  }
}
""")

@ahoppen ahoppen requested review from DougGregor and fwcd September 27, 2022 18:10
@ahoppen
Copy link
Member Author

ahoppen commented Sep 27, 2022

@swift-ci Please test

@ahoppen ahoppen force-pushed the ahoppen/build-parser-integration branch from f77ae29 to f357bf8 Compare September 27, 2022 19:28
@ahoppen
Copy link
Member Author

ahoppen commented Sep 27, 2022

@swift-ci Please test

Copy link
Member

@fwcd fwcd left a 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.

Comment on lines 103 to 158
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()
})
)
}
)
]))
Copy link
Member

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.

Suggested change
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()
})
}
}
}

Copy link
Member Author

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([
Copy link
Member

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 {
Copy link
Member

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?

Copy link
Member Author

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.
@ahoppen ahoppen force-pushed the ahoppen/build-parser-integration branch from f357bf8 to 9ce266f Compare September 28, 2022 06:31
@ahoppen
Copy link
Member Author

ahoppen commented Sep 28, 2022

@swift-ci Please test

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.

2 participants