Skip to content

Commit 71c34e6

Browse files
committed
Parse SwitchExprSyntax
Update the parser to support parsing SwitchExprSyntax. For now, this only updates the existing statement parsing, and does not attempt to parse in expression position.
1 parent 9ab58a2 commit 71c34e6

File tree

3 files changed

+289
-282
lines changed

3 files changed

+289
-282
lines changed

Sources/SwiftParser/Expressions.swift

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2621,6 +2621,288 @@ extension Parser {
26212621
}
26222622
}
26232623

2624+
// MARK: Switch Expressions
2625+
2626+
extension Parser {
2627+
/// Parse a switch expression.
2628+
///
2629+
/// Grammar
2630+
/// =======
2631+
///
2632+
/// switch-expression → 'switch' expression '{' switch-cases? '}'
2633+
/// switch-cases → switch-case switch-cases?
2634+
@_spi(RawSyntax)
2635+
public mutating func parseSwitchExpression(
2636+
switchHandle: RecoveryConsumptionHandle
2637+
) -> RawSwitchExprSyntax {
2638+
let (unexpectedBeforeSwitchKeyword, switchKeyword) = self.eat(switchHandle)
2639+
2640+
let subject = self.parseExpression(.basic)
2641+
let (unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace)
2642+
2643+
let cases = self.parseSwitchCases(allowStandaloneStmtRecovery: !lbrace.isMissing)
2644+
2645+
let (unexpectedBeforeRBrace, rbrace) = self.expectRightBrace(leftBrace: lbrace, introducer: switchKeyword)
2646+
return RawSwitchExprSyntax(
2647+
unexpectedBeforeSwitchKeyword,
2648+
switchKeyword: switchKeyword,
2649+
expression: subject,
2650+
unexpectedBeforeLBrace,
2651+
leftBrace: lbrace,
2652+
cases: cases,
2653+
unexpectedBeforeRBrace,
2654+
rightBrace: rbrace,
2655+
arena: self.arena
2656+
)
2657+
}
2658+
2659+
/// Parse a list of switch case clauses.
2660+
///
2661+
/// Grammar
2662+
/// =======
2663+
///
2664+
/// switch-cases → switch-case switch-cases?
2665+
///
2666+
/// If `allowStandaloneStmtRecovery` is `true` and we discover a statement that
2667+
/// isn't covered by a case, we assume that the developer forgot to wrote the
2668+
/// `case` and synthesize it. If `allowStandaloneStmtOrDeclRecovery` is `false`,
2669+
/// this recovery is disabled.
2670+
@_spi(RawSyntax)
2671+
public mutating func parseSwitchCases(allowStandaloneStmtRecovery: Bool) -> RawSwitchCaseListSyntax {
2672+
var elements = [RawSwitchCaseListSyntax.Element]()
2673+
var elementsProgress = LoopProgressCondition()
2674+
while !self.at(any: [.eof, .rightBrace, .poundEndifKeyword, .poundElseifKeyword, .poundElseKeyword])
2675+
&& elementsProgress.evaluate(currentToken)
2676+
{
2677+
if self.lookahead().isAtStartOfSwitchCase(allowRecovery: false) {
2678+
elements.append(.switchCase(self.parseSwitchCase()))
2679+
} else if self.canRecoverTo(.poundIfKeyword) != nil {
2680+
// '#if' in 'case' position can enclose zero or more 'case' or 'default'
2681+
// clauses.
2682+
elements.append(
2683+
.ifConfigDecl(
2684+
self.parsePoundIfDirective(
2685+
{ $0.parseSwitchCases(allowStandaloneStmtRecovery: allowStandaloneStmtRecovery) },
2686+
syntax: { parser, cases in
2687+
guard cases.count == 1, let firstCase = cases.first else {
2688+
assert(cases.isEmpty)
2689+
return .switchCases(RawSwitchCaseListSyntax(elements: [], arena: parser.arena))
2690+
}
2691+
return .switchCases(firstCase)
2692+
}
2693+
)
2694+
)
2695+
)
2696+
} else if allowStandaloneStmtRecovery && (self.atStartOfExpression() || self.atStartOfStatement() || self.atStartOfDeclaration()) {
2697+
// Synthesize a label for the stamenent or declaration that isn't coverd by a case right now.
2698+
let statements = parseSwitchCaseBody()
2699+
elements.append(
2700+
.switchCase(
2701+
RawSwitchCaseSyntax(
2702+
unknownAttr: nil,
2703+
label: .case(
2704+
RawSwitchCaseLabelSyntax(
2705+
caseKeyword: missingToken(.keyword(.case), text: nil),
2706+
caseItems: RawCaseItemListSyntax(
2707+
elements: [
2708+
RawCaseItemSyntax(
2709+
pattern: RawPatternSyntax(
2710+
RawIdentifierPatternSyntax(
2711+
identifier: missingToken(.identifier, text: nil),
2712+
arena: self.arena
2713+
)
2714+
),
2715+
whereClause: nil,
2716+
trailingComma: nil,
2717+
arena: self.arena
2718+
)
2719+
],
2720+
arena: self.arena
2721+
),
2722+
colon: missingToken(.colon, text: nil),
2723+
arena: self.arena
2724+
)
2725+
),
2726+
statements: statements,
2727+
arena: self.arena
2728+
)
2729+
)
2730+
)
2731+
} else if self.lookahead().isAtStartOfSwitchCase(allowRecovery: true) {
2732+
elements.append(.switchCase(self.parseSwitchCase()))
2733+
} else {
2734+
break
2735+
}
2736+
}
2737+
return RawSwitchCaseListSyntax(elements: elements, arena: self.arena)
2738+
}
2739+
2740+
mutating func parseSwitchCaseBody() -> RawCodeBlockItemListSyntax {
2741+
var items = [RawCodeBlockItemSyntax]()
2742+
var loopProgress = LoopProgressCondition()
2743+
while !self.at(any: [.rightBrace, .poundEndifKeyword, .poundElseifKeyword, .poundElseKeyword])
2744+
&& !self.lookahead().isStartOfConditionalSwitchCases(),
2745+
let newItem = self.parseCodeBlockItem(),
2746+
loopProgress.evaluate(currentToken)
2747+
{
2748+
items.append(newItem)
2749+
}
2750+
return RawCodeBlockItemListSyntax(elements: items, arena: self.arena)
2751+
}
2752+
2753+
/// Parse a single switch case clause.
2754+
///
2755+
/// Grammar
2756+
/// =======
2757+
///
2758+
/// switch-case → case-label statements
2759+
/// switch-case → default-label statements
2760+
/// switch-case → conditional-switch-case
2761+
@_spi(RawSyntax)
2762+
public mutating func parseSwitchCase() -> RawSwitchCaseSyntax {
2763+
var unknownAttr: RawAttributeSyntax?
2764+
if let at = self.consume(if: .atSign) {
2765+
let (unexpectedBeforeIdent, ident) = self.expectIdentifier()
2766+
2767+
unknownAttr = RawAttributeSyntax(
2768+
atSignToken: at,
2769+
unexpectedBeforeIdent,
2770+
attributeName: RawTypeSyntax(RawSimpleTypeIdentifierSyntax(name: ident, genericArgumentClause: nil, arena: self.arena)),
2771+
leftParen: nil,
2772+
argument: nil,
2773+
rightParen: nil,
2774+
arena: self.arena
2775+
)
2776+
} else {
2777+
unknownAttr = nil
2778+
}
2779+
2780+
let label: RawSwitchCaseSyntax.Label
2781+
switch self.canRecoverTo(anyIn: SwitchCaseStart.self) {
2782+
case (.caseKeyword, let handle)?:
2783+
label = .case(self.parseSwitchCaseLabel(handle))
2784+
case (.defaultKeyword, let handle)?:
2785+
label = .default(self.parseSwitchDefaultLabel(handle))
2786+
case nil:
2787+
label = .case(
2788+
RawSwitchCaseLabelSyntax(
2789+
caseKeyword: missingToken(.keyword(.case)),
2790+
caseItems: RawCaseItemListSyntax(
2791+
elements: [
2792+
RawCaseItemSyntax(
2793+
pattern: RawPatternSyntax(RawIdentifierPatternSyntax(identifier: missingToken(.identifier), arena: self.arena)),
2794+
whereClause: nil,
2795+
trailingComma: nil,
2796+
arena: self.arena
2797+
)
2798+
],
2799+
arena: self.arena
2800+
),
2801+
colon: missingToken(.colon),
2802+
arena: self.arena
2803+
)
2804+
)
2805+
}
2806+
2807+
// Parse the body.
2808+
let statements = parseSwitchCaseBody()
2809+
2810+
return RawSwitchCaseSyntax(
2811+
unknownAttr: unknownAttr,
2812+
label: label,
2813+
statements: statements,
2814+
arena: self.arena
2815+
)
2816+
}
2817+
2818+
/// Parse a switch case with a 'case' label.
2819+
///
2820+
/// Grammar
2821+
/// =======
2822+
///
2823+
/// case-label → attributes? case case-item-list ':'
2824+
/// case-item-list → pattern where-clause? | pattern where-clause? ',' case-item-list
2825+
@_spi(RawSyntax)
2826+
public mutating func parseSwitchCaseLabel(
2827+
_ handle: RecoveryConsumptionHandle
2828+
) -> RawSwitchCaseLabelSyntax {
2829+
let (unexpectedBeforeCaseKeyword, caseKeyword) = self.eat(handle)
2830+
var caseItems = [RawCaseItemSyntax]()
2831+
do {
2832+
var keepGoing: RawTokenSyntax? = nil
2833+
var loopProgress = LoopProgressCondition()
2834+
repeat {
2835+
let (pattern, whereClause) = self.parseGuardedCasePattern()
2836+
keepGoing = self.consume(if: .comma)
2837+
caseItems.append(
2838+
RawCaseItemSyntax(
2839+
pattern: pattern,
2840+
whereClause: whereClause,
2841+
trailingComma: keepGoing,
2842+
arena: self.arena
2843+
)
2844+
)
2845+
} while keepGoing != nil && loopProgress.evaluate(currentToken)
2846+
}
2847+
let (unexpectedBeforeColon, colon) = self.expect(.colon)
2848+
return RawSwitchCaseLabelSyntax(
2849+
unexpectedBeforeCaseKeyword,
2850+
caseKeyword: caseKeyword,
2851+
caseItems: RawCaseItemListSyntax(elements: caseItems, arena: self.arena),
2852+
unexpectedBeforeColon,
2853+
colon: colon,
2854+
arena: self.arena
2855+
)
2856+
}
2857+
2858+
/// Parse a switch case with a 'default' label.
2859+
///
2860+
/// Grammar
2861+
/// =======
2862+
///
2863+
/// default-label → attributes? 'default' ':'
2864+
@_spi(RawSyntax)
2865+
public mutating func parseSwitchDefaultLabel(
2866+
_ handle: RecoveryConsumptionHandle
2867+
) -> RawSwitchDefaultLabelSyntax {
2868+
let (unexpectedBeforeDefaultKeyword, defaultKeyword) = self.eat(handle)
2869+
let (unexpectedBeforeColon, colon) = self.expect(.colon)
2870+
return RawSwitchDefaultLabelSyntax(
2871+
unexpectedBeforeDefaultKeyword,
2872+
defaultKeyword: defaultKeyword,
2873+
unexpectedBeforeColon,
2874+
colon: colon,
2875+
arena: self.arena
2876+
)
2877+
}
2878+
2879+
/// Parse a pattern-matching clause for a case statement,
2880+
/// including the guard expression.
2881+
///
2882+
/// Grammar
2883+
/// =======
2884+
///
2885+
/// case-item → pattern where-clause?
2886+
mutating func parseGuardedCasePattern() -> (RawPatternSyntax, RawWhereClauseSyntax?) {
2887+
let pattern = self.parseMatchingPattern(context: .matching)
2888+
2889+
// Parse the optional 'where' guard, with this particular pattern's bound
2890+
// vars in scope.
2891+
let whereClause: RawWhereClauseSyntax?
2892+
if let whereKeyword = self.consume(if: .keyword(.where)) {
2893+
let guardExpr = self.parseExpression(.trailingClosure)
2894+
whereClause = RawWhereClauseSyntax(
2895+
whereKeyword: whereKeyword,
2896+
guardResult: guardExpr,
2897+
arena: self.arena
2898+
)
2899+
} else {
2900+
whereClause = nil
2901+
}
2902+
return (pattern, whereClause)
2903+
}
2904+
}
2905+
26242906
// MARK: Lookahead
26252907

26262908
extension Parser.Lookahead {

0 commit comments

Comments
 (0)