From a3dd8233395a483e00b66271d245fb6bc852c81d Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Fri, 27 Dec 2024 02:09:40 -0800 Subject: [PATCH 1/2] [DNM] Support box-drawing-based blocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There’s a social media post circulating where `#define` is used to write blocks using box-drawing characters in C code. Swift macros aren’t suitable for that kind of thing, but I wanted to see how difficult it would be to implement, just for funsies. This is—and I cannot believe I am about to write this sentence—perhaps not the way we would actually want to implement box-drawing-based blocks, as it works by treating most of the characters as trivia and only assigning a semantic meaning to a few corners. That means it doesn’t actually enforce the validity of the box shapes. Nor is this—and I cannot believe I am about to write *this* sentence, either—a production-quality implementation of box-drawing-based blocks. There’s a lot I haven’t tested, the diagnostics are probably a mess, and there are a number of places where I suspect I made changes that would cost us some speed. However, it’s certainly a fun little hack: ``` assertParse( #""" ╔═════════════ func fizzBuzz() ═════════════╗ ║ ╔═══════════ for i in 0..<100 ══════════╗ ║ ║ ║ ╔═════ if i.isMultiple(of: 15) ═════╗ ║ ║ ║ ║ ║ print("fizzbuzz ") ║ ║ ║ ║ ║ ╠═══ else if i.isMultiple(of: 3) ═══╣ ║ ║ ║ ║ ║ print("fizz ") ║ ║ ║ ║ ║ ╠═══ else if i.isMultiple(of: 5) ═══╣ ║ ║ ║ ║ ║ print("buzz ") ║ ║ ║ ║ ║ ╠══════════════ else ═══════════════╣ ║ ║ ║ ║ ║ print("\(i) ") ║ ║ ║ ║ ║ ╚═══════════════════════════════════╝ ║ ║ ║ ╚═══════════════════════════════════════╝ ║ ╚═══════════════════════════════════════════╝ """# ) ``` --- .../Sources/SyntaxSupport/Child.swift | 19 +- .../Sources/SyntaxSupport/CommonNodes.swift | 12 +- .../Sources/SyntaxSupport/DeclNodes.swift | 36 +- .../Sources/SyntaxSupport/ExprNodes.swift | 24 +- .../SyntaxSupport/GrammarGenerator.swift | 2 +- .../Sources/SyntaxSupport/TokenSpec.swift | 12 + .../Sources/SyntaxSupport/Traits.swift | 12 +- .../Sources/SyntaxSupport/Trivia.swift | 5 + .../Sources/Utils/SyntaxBuildableChild.swift | 4 +- .../swiftparser/ParserTokenSpecSetFile.swift | 2 +- .../swiftsyntax/RawSyntaxValidationFile.swift | 2 +- .../ValidateSyntaxNodes.swift | 8 +- .../SwiftIDEUtils/SyntaxClassification.swift | 8 + .../SwiftIfConfig/ActiveSyntaxRewriter.swift | 2 +- Sources/SwiftParser/Declarations.swift | 31 +- Sources/SwiftParser/Expressions.swift | 44 +- Sources/SwiftParser/Lexer/Cursor.swift | 26 + .../SwiftParser/Lexer/RegexLiteralLexer.swift | 4 +- Sources/SwiftParser/Lookahead.swift | 30 +- Sources/SwiftParser/Nominals.swift | 4 +- Sources/SwiftParser/Parser.swift | 59 +- Sources/SwiftParser/Recovery.swift | 75 +- Sources/SwiftParser/Statements.swift | 18 +- Sources/SwiftParser/TokenConsumer.swift | 60 +- Sources/SwiftParser/TokenPrecedence.swift | 16 +- Sources/SwiftParser/TokenSpec.swift | 8 + Sources/SwiftParser/TokenSpecSet.swift | 6 + Sources/SwiftParser/TopLevel.swift | 6 +- Sources/SwiftParser/TriviaParser.swift | 21 +- .../generated/Parser+TokenSpecSet.swift | 732 ++++++++++++++++++ .../generated/TokenSpecStaticMembers.swift | 16 + .../generated/TokenNameForDiagnostics.swift | 8 + Sources/SwiftSyntax/SourceLocation.swift | 2 + Sources/SwiftSyntax/Trivia.swift | 2 + .../SwiftSyntax/generated/SyntaxTraits.swift | 10 +- Sources/SwiftSyntax/generated/TokenKind.swift | 76 ++ Sources/SwiftSyntax/generated/Tokens.swift | 52 ++ .../SwiftSyntax/generated/TriviaPieces.swift | 22 + .../generated/raw/RawSyntaxValidation.swift | 24 +- .../generated/syntaxNodes/SyntaxNodesAB.swift | 14 +- .../generated/syntaxNodes/SyntaxNodesC.swift | 28 +- .../syntaxNodes/SyntaxNodesJKLMN.swift | 14 +- .../generated/syntaxNodes/SyntaxNodesOP.swift | 14 +- .../syntaxNodes/SyntaxNodesQRS.swift | 14 +- Tests/SwiftParserTest/Assertions.swift | 8 +- Tests/SwiftParserTest/DeclarationTests.swift | 158 ++++ Tests/SwiftParserTest/LexerTests.swift | 22 + Tests/SwiftParserTest/TriviaParserTests.swift | 42 + 48 files changed, 1570 insertions(+), 244 deletions(-) diff --git a/CodeGeneration/Sources/SyntaxSupport/Child.swift b/CodeGeneration/Sources/SyntaxSupport/Child.swift index e486ef1598b..c179516179a 100644 --- a/CodeGeneration/Sources/SyntaxSupport/Child.swift +++ b/CodeGeneration/Sources/SyntaxSupport/Child.swift @@ -52,7 +52,7 @@ public enum ChildKind { /// The child is a token that matches one of the given `choices`. /// If `requiresLeadingSpace` or `requiresTrailingSpace` is not `nil`, it /// overrides the default leading/trailing space behavior of the token. - case token(choices: [TokenChoice], requiresLeadingSpace: Bool? = nil, requiresTrailingSpace: Bool? = nil) + case token(choices: [TokenChoice], requiresLeadingSpace: Bool? = nil, requiresTrailingSpace: Bool? = nil, defaultAt: Int? = nil) public var isNodeChoices: Bool { if case .nodeChoices = self { @@ -108,7 +108,7 @@ public class Child: NodeChoiceConvertible { /// A docc comment describing the child, including the trivia provided when /// initializing the ``Child``, and the list of possible token choices inferred automatically. public var documentation: SwiftSyntax.Trivia { - if case .token(let choices, _, _) = kind { + if case .token(let choices, _, _, _) = kind { let tokenChoicesTrivia = SwiftSyntax.Trivia.docCommentTrivia( from: GrammarGenerator.childTokenChoices(for: choices) ) @@ -230,9 +230,18 @@ public class Child: NodeChoiceConvertible { /// Grab the existing reference to that token from the global list. public var tokenKind: Token? { switch kind { - case .token(let choices, _, _): - if choices.count == 1 { - switch choices.first! { + case .token(let choices, _, _, defaultAt: let defaultIndex): + let defaultToken: TokenChoice? + if let defaultIndex { + defaultToken = choices[defaultIndex] + } else if let onlyToken = choices.only { + defaultToken = onlyToken + } else { + defaultToken = nil + } + + if let defaultToken { + switch defaultToken { case .keyword: return .keyword case .token(let token): return token } diff --git a/CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift b/CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift index 7247e17a909..39d5aeec415 100644 --- a/CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift @@ -64,7 +64,11 @@ public let COMMON_NODES: [Node] = [ children: [ Child( name: "leftBrace", - kind: .token(choices: [.token(.leftBrace)]), + kind: .token(choices: [ + .token(.leftBrace), + .token(.leadingBoxCorner), + .token(.leadingBoxJunction), + ], defaultAt: 0), documentation: "The brace introducing the code block." ), Child( @@ -74,7 +78,11 @@ public let COMMON_NODES: [Node] = [ ), Child( name: "rightBrace", - kind: .token(choices: [.token(.rightBrace)]), + kind: .token(choices: [ + .token(.rightBrace), + .token(.trailingBoxCorner), + .token(.trailingBoxJunction), + ], defaultAt: 0), documentation: "The brace closing the code block." ), ] diff --git a/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift b/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift index 0dea2d63426..2c145699a2a 100644 --- a/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift @@ -57,7 +57,11 @@ public let DECL_NODES: [Node] = [ children: [ Child( name: "leftBrace", - kind: .token(choices: [.token(.leftBrace)]), + kind: .token(choices: [ + .token(.leftBrace), + .token(.leadingBoxCorner), + .token(.leadingBoxJunction), + ], defaultAt: 0), documentation: "The brace introducing the accessor block." ), Child( @@ -75,7 +79,11 @@ public let DECL_NODES: [Node] = [ ), Child( name: "rightBrace", - kind: .token(choices: [.token(.rightBrace)]), + kind: .token(choices: [ + .token(.rightBrace), + .token(.trailingBoxCorner), + .token(.trailingBoxJunction), + ], defaultAt: 0), documentation: "The brace closing the accessor block." ), ] @@ -1579,7 +1587,11 @@ public let DECL_NODES: [Node] = [ children: [ Child( name: "leftBrace", - kind: .token(choices: [.token(.leftBrace)]) + kind: .token(choices: [ + .token(.leftBrace), + .token(.leadingBoxCorner), + .token(.leadingBoxJunction), + ], defaultAt: 0) ), Child( name: "members", @@ -1587,7 +1599,11 @@ public let DECL_NODES: [Node] = [ ), Child( name: "rightBrace", - kind: .token(choices: [.token(.rightBrace)]) + kind: .token(choices: [ + .token(.rightBrace), + .token(.trailingBoxCorner), + .token(.trailingBoxJunction), + ], defaultAt: 0) ), ] ), @@ -1972,7 +1988,11 @@ public let DECL_NODES: [Node] = [ ), Child( name: "leftBrace", - kind: .token(choices: [.token(.leftBrace)]) + kind: .token(choices: [ + .token(.leftBrace), + .token(.leadingBoxCorner), + .token(.leadingBoxJunction), + ], defaultAt: 0) ), Child( name: "groupAttributes", @@ -1981,7 +2001,11 @@ public let DECL_NODES: [Node] = [ ), Child( name: "rightBrace", - kind: .token(choices: [.token(.rightBrace)]) + kind: .token(choices: [ + .token(.rightBrace), + .token(.trailingBoxCorner), + .token(.trailingBoxJunction), + ], defaultAt: 0) ), ], childHistory: [ diff --git a/CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift b/CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift index fdad15af88d..22c72a54250 100644 --- a/CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift @@ -511,7 +511,11 @@ public let EXPR_NODES: [Node] = [ children: [ Child( name: "leftBrace", - kind: .token(choices: [.token(.leftBrace)]) + kind: .token(choices: [ + .token(.leftBrace), + .token(.leadingBoxCorner), + .token(.leadingBoxJunction), + ], defaultAt: 0) ), Child( name: "signature", @@ -524,7 +528,11 @@ public let EXPR_NODES: [Node] = [ ), Child( name: "rightBrace", - kind: .token(choices: [.token(.rightBrace)]) + kind: .token(choices: [ + .token(.rightBrace), + .token(.trailingBoxCorner), + .token(.trailingBoxJunction), + ], defaultAt: 0) ), ] ), @@ -1949,7 +1957,11 @@ public let EXPR_NODES: [Node] = [ ), Child( name: "leftBrace", - kind: .token(choices: [.token(.leftBrace)]), + kind: .token(choices: [ + .token(.leftBrace), + .token(.leadingBoxCorner), + .token(.leadingBoxJunction), + ], defaultAt: 0), documentation: "The brace introducing the switch body." ), Child( @@ -1959,7 +1971,11 @@ public let EXPR_NODES: [Node] = [ ), Child( name: "rightBrace", - kind: .token(choices: [.token(.rightBrace)]), + kind: .token(choices: [ + .token(.rightBrace), + .token(.trailingBoxCorner), + .token(.trailingBoxJunction), + ], defaultAt: 0), documentation: "The brace closing the switch body." ), ], diff --git a/CodeGeneration/Sources/SyntaxSupport/GrammarGenerator.swift b/CodeGeneration/Sources/SyntaxSupport/GrammarGenerator.swift index ae84735dbf9..00a81e2de9f 100644 --- a/CodeGeneration/Sources/SyntaxSupport/GrammarGenerator.swift +++ b/CodeGeneration/Sources/SyntaxSupport/GrammarGenerator.swift @@ -43,7 +43,7 @@ struct GrammarGenerator { return "(\(choicesDescriptions.joined(separator: " | ")))\(optionality)" case .collection(kind: let kind, _, _, _): return "\(kind.doccLink)\(optionality)" - case .token(let choices, _, _): + case .token(let choices, _, _, _): if choices.count == 1 { return "\(grammar(for: choices.first!))\(optionality)" } else { diff --git a/CodeGeneration/Sources/SyntaxSupport/TokenSpec.swift b/CodeGeneration/Sources/SyntaxSupport/TokenSpec.swift index 2abbc28adc8..9cf0a6457d9 100644 --- a/CodeGeneration/Sources/SyntaxSupport/TokenSpec.swift +++ b/CodeGeneration/Sources/SyntaxSupport/TokenSpec.swift @@ -137,6 +137,8 @@ public enum Token: CaseIterable { case infixQuestionMark case integerLiteral case keyword + case leadingBoxCorner + case leadingBoxJunction case leftAngle case leftBrace case leftParen @@ -168,6 +170,8 @@ public enum Token: CaseIterable { case singleQuote case stringQuote case stringSegment + case trailingBoxCorner + case trailingBoxJunction case unknown case wildcard @@ -207,6 +211,10 @@ public enum Token: CaseIterable { return .other(name: "integerLiteral", nameForDiagnostics: "integer literal") case .keyword: return TokenSpec(name: "keyword", nameForDiagnostics: "keyword", text: nil, kind: .keyword) + case .leadingBoxCorner: + return .punctuator(name: "leadingBoxCorner", text: "╗") + case .leadingBoxJunction: + return .punctuator(name: "leadingBoxJunction", text: "╣") case .leftAngle: return .punctuator(name: "leftAngle", text: "<") case .leftBrace: @@ -269,6 +277,10 @@ public enum Token: CaseIterable { return .punctuator(name: "stringQuote", text: "\"") case .stringSegment: return .other(name: "stringSegment", nameForDiagnostics: "string segment") + case .trailingBoxCorner: + return .punctuator(name: "trailingBoxCorner", text: "╚") + case .trailingBoxJunction: + return .punctuator(name: "trailingBoxJunction", text: "╠") case .unknown: return .other(name: "unknown", nameForDiagnostics: "token") case .wildcard: diff --git a/CodeGeneration/Sources/SyntaxSupport/Traits.swift b/CodeGeneration/Sources/SyntaxSupport/Traits.swift index c9eb2828931..850fb4e1bd3 100644 --- a/CodeGeneration/Sources/SyntaxSupport/Traits.swift +++ b/CodeGeneration/Sources/SyntaxSupport/Traits.swift @@ -45,8 +45,16 @@ public let TRAITS: [Trait] = [ Trait( traitName: "Braced", children: [ - Child(name: "leftBrace", kind: .token(choices: [.token(.leftBrace)])), - Child(name: "rightBrace", kind: .token(choices: [.token(.rightBrace)])), + Child(name: "leftBrace", kind: .token(choices: [ + .token(.leftBrace), + .token(.leadingBoxCorner), + .token(.leadingBoxJunction), + ], defaultAt: 0)), + Child(name: "rightBrace", kind: .token(choices: [ + .token(.rightBrace), + .token(.trailingBoxCorner), + .token(.trailingBoxJunction), + ], defaultAt: 0)), ] ), Trait( diff --git a/CodeGeneration/Sources/SyntaxSupport/Trivia.swift b/CodeGeneration/Sources/SyntaxSupport/Trivia.swift index b1d9aa96269..da9c426c332 100644 --- a/CodeGeneration/Sources/SyntaxSupport/Trivia.swift +++ b/CodeGeneration/Sources/SyntaxSupport/Trivia.swift @@ -232,4 +232,9 @@ public let TRIVIAS: [Trivia] = [ Character("\u{2B7F}") ] ), + + Trivia( + name: "BoxDrawing", + comment: #"Box-drawing characters which have no syntactic significance."# + ), ] diff --git a/CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift b/CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift index fe73c60d237..0794a3f9a2a 100644 --- a/CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift +++ b/CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift @@ -74,7 +74,7 @@ extension Child { if token.text != nil { return ExprSyntax(".\(token.identifier)Token()") } - if case .token(let choices, _, _) = kind, + if case .token(let choices, _, _, _) = kind, case .keyword(let keyword) = choices.only { return ExprSyntax(".\(token.memberCallName)(.\(keyword.spec.memberCallName))") @@ -100,7 +100,7 @@ extension Child { /// `precondition` statement that verifies the variable with name var_name and of type /// ``TokenSyntax`` contains one of the supported text options. Otherwise return `nil`. public func generateAssertStmtTextChoices(varName: String) -> FunctionCallExprSyntax? { - guard case .token(choices: let choices, requiresLeadingSpace: _, requiresTrailingSpace: _) = kind else { + guard case .token(choices: let choices, requiresLeadingSpace: _, requiresTrailingSpace: _, defaultAt: _) = kind else { return nil } diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/ParserTokenSpecSetFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/ParserTokenSpecSetFile.swift index 934018885cf..d96217d9c09 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/ParserTokenSpecSetFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/ParserTokenSpecSetFile.swift @@ -39,7 +39,7 @@ let parserTokenSpecSetFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { for layoutNode in SYNTAX_NODES.compactMap(\.layoutNode) { for child in layoutNode.children { - if case let .token(choices, _, _) = child.kind, choices.count > 1 { + if case let .token(choices, _, _, _) = child.kind, choices.count > 1 { try! ExtensionDeclSyntax("extension \(layoutNode.kind.syntaxType)") { try EnumDeclSyntax( """ diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/RawSyntaxValidationFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/RawSyntaxValidationFile.swift index 15ea4eacba3..9e4814e541e 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/RawSyntaxValidationFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/RawSyntaxValidationFile.swift @@ -209,7 +209,7 @@ let rawSyntaxValidationFile = try! SourceFileSyntax(leadingTrivia: copyrightHead } ExprSyntax("assertAnyHasNoError(kind, \(raw: index), \(verifiedChoices))") - case .token(choices: let choices, requiresLeadingSpace: _, requiresTrailingSpace: _): + case .token(choices: let choices, requiresLeadingSpace: _, requiresTrailingSpace: _, defaultAt: _): let choices = ArrayExprSyntax { for choice in choices { switch choice { diff --git a/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift b/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift index 5148bf09142..143dfc8d4e9 100644 --- a/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift +++ b/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift @@ -57,7 +57,7 @@ fileprivate extension ChildKind { return choices.count == otherChoices.count && zip(choices, otherChoices).allSatisfy { $0.hasSameType(as: $1) } case (.collection(kind: let kind, _, _, _), .collection(kind: let otherKind, _, _, _)): return kind == otherKind - case (.token(let choices, _, _), .token(let otherChoices, _, _)): + case (.token(let choices, _, _, _), .token(let otherChoices, _, _, _)): return choices == otherChoices case (.node(let kind), .collection(kind: let otherKind, _, _, _)): return kind == otherKind @@ -92,7 +92,7 @@ fileprivate extension Child { guard childIndex + 2 < node.children.count else { return false } - if case .token(choices: [.token(.colon)], _, _) = node.children[childIndex + 2].kind { + if case .token(choices: [.token(.colon)], _, _, _) = node.children[childIndex + 2].kind { return true } else { return false @@ -173,7 +173,7 @@ class ValidateSyntaxNodes: XCTestCase { /// /// - Returns: A failure message if validation failed, otherwise `nil` private func validateSingleTokenChoiceChild(child: Child, in node: LayoutNode) -> String? { - guard case .token(choices: let tokenChoices, _, _) = child.kind, let choice = tokenChoices.only else { + guard case .token(choices: let tokenChoices, _, _, _) = child.kind, let choice = tokenChoices.only else { return nil } switch choice { @@ -369,7 +369,7 @@ class ValidateSyntaxNodes: XCTestCase { var failures: [ValidationFailure] = [] for node in SYNTAX_NODES.compactMap(\.layoutNode) { for child in node.children { - guard case .token(choices: let tokenChoices, _, _) = child.kind, + guard case .token(choices: let tokenChoices, _, _, _) = child.kind, tokenChoices.count > 1, tokenChoices.allSatisfy({ $0.isKeyword }) else { diff --git a/Sources/SwiftIDEUtils/SyntaxClassification.swift b/Sources/SwiftIDEUtils/SyntaxClassification.swift index 1979483c777..ed40e0dafb9 100644 --- a/Sources/SwiftIDEUtils/SyntaxClassification.swift +++ b/Sources/SwiftIDEUtils/SyntaxClassification.swift @@ -136,6 +136,10 @@ extension RawTokenKind { return .integerLiteral case .keyword: return .keyword + case .leadingBoxCorner: + return .none + case .leadingBoxJunction: + return .none case .leftAngle: return .none case .leftBrace: @@ -198,6 +202,10 @@ extension RawTokenKind { return .stringLiteral case .stringSegment: return .stringLiteral + case .trailingBoxCorner: + return .none + case .trailingBoxJunction: + return .none case .unknown: return .none case .wildcard: diff --git a/Sources/SwiftIfConfig/ActiveSyntaxRewriter.swift b/Sources/SwiftIfConfig/ActiveSyntaxRewriter.swift index f4ac3b4c695..5ffa7784ed4 100644 --- a/Sources/SwiftIfConfig/ActiveSyntaxRewriter.swift +++ b/Sources/SwiftIfConfig/ActiveSyntaxRewriter.swift @@ -478,7 +478,7 @@ extension Trivia { for piece in pieces { switch piece { case .backslashes, .carriageReturnLineFeeds, .carriageReturns, .formfeeds, .newlines, .pounds, .spaces, .tabs, - .unexpectedText, .verticalTabs: + .unexpectedText, .verticalTabs, .boxDrawing: piece.write(to: &stream) case .blockComment(let text), .docBlockComment(let text), .docLineComment(let text), .lineComment(let text): diff --git a/Sources/SwiftParser/Declarations.swift b/Sources/SwiftParser/Declarations.swift index d186a4b0a7d..4dad0b48c80 100644 --- a/Sources/SwiftParser/Declarations.swift +++ b/Sources/SwiftParser/Declarations.swift @@ -824,7 +824,7 @@ extension Parser { } mutating func atWhereClauseListTerminator() -> Bool { - return self.at(.leftBrace) + return self.at(anyIn: CodeBlockSyntax.LeftBraceOptions.self) != nil } } @@ -908,7 +908,7 @@ extension Parser { /// If the left brace is missing, its indentation will be used to judge whether a following `}` was /// indented to close this code block or a surrounding context. See `expectRightBrace`. mutating func parseMemberBlock(introducer: RawTokenSyntax? = nil) -> RawMemberBlockSyntax { - let (unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace) + let (unexpectedBeforeLBrace, lbrace) = self.expect(anyIn: MemberBlockSyntax.LeftBraceOptions.self, default: .leftBrace) let members = parseMemberDeclList() let (unexpectedBeforeRBrace, rbrace) = self.expectRightBrace(leftBrace: lbrace, introducer: introducer) @@ -1337,7 +1337,7 @@ extension Parser { // Parse getter and setter. let accessor: RawAccessorBlockSyntax? - if self.at(.leftBrace) || self.at(anyIn: AccessorDeclSyntax.AccessorSpecifierOptions.self) != nil { + if self.at(anyIn: AccessorBlockSyntax.LeftBraceOptions.self) != nil || self.at(anyIn: AccessorDeclSyntax.AccessorSpecifierOptions.self) != nil { accessor = self.parseAccessorBlock() } else { accessor = nil @@ -1449,7 +1449,7 @@ extension Parser { value: initExpr, arena: self.arena ) - } else if self.atStartOfExpression(), !self.at(.leftBrace), !self.atStartOfLine { + } else if self.atStartOfExpression(), self.at(anyIn: AccessorBlockSyntax.LeftBraceOptions.self) == nil, !self.atStartOfLine { let missingEqual = RawTokenSyntax(missing: .equal, arena: self.arena) let expr = self.parseExpression(flavor: .basic, pattern: .none) initializer = RawInitializerClauseSyntax( @@ -1462,7 +1462,7 @@ extension Parser { } let accessors: RawAccessorBlockSyntax? - if (self.at(.leftBrace) + if (self.at(anyIn: AccessorBlockSyntax.LeftBraceOptions.self) != nil && (initializer == nil || !self.currentToken.isAtStartOfLine || self.withLookahead({ $0.atStartOfGetSetAccessor() }))) || (context.requiresDecl && self.at(anyIn: AccessorDeclSyntax.AccessorSpecifierOptions.self) != nil @@ -1620,17 +1620,18 @@ extension Parser { unexpectedBeforeLBrace = nil lbrace = missingToken(.leftBrace) } else { - (unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace) + (unexpectedBeforeLBrace, lbrace) = self.expect(anyIn: AccessorBlockSyntax.LeftBraceOptions.self, default: .leftBrace) } + let expectedRBrace = rightBrace(for: lbrace) let accessorList = parseAccessorList() // There can only be an implicit getter if no other accessors were // seen before this one. guard let accessorList else { - let body = parseCodeBlockItemList(until: { $0.at(.rightBrace) }) + let body = parseCodeBlockItemList(until: { $0.at(expectedRBrace) }) - let (unexpectedBeforeRBrace, rbrace) = self.expect(.rightBrace) + let (unexpectedBeforeRBrace, rbrace) = self.expect(expectedRBrace) return RawAccessorBlockSyntax( unexpectedBeforeLBrace, leftBrace: lbrace, @@ -1641,7 +1642,7 @@ extension Parser { ) } - let (unexpectedBeforeRBrace, rbrace) = self.expect(.rightBrace) + let (unexpectedBeforeRBrace, rbrace) = self.expect(expectedRBrace) return RawAccessorBlockSyntax( unexpectedBeforeLBrace, leftBrace: lbrace, @@ -1813,7 +1814,7 @@ extension Parser { var loopProgress = LoopProgressCondition() while (identifiersAfterOperatorName.last ?? name).trailingTriviaByteLength == 0, self.currentToken.leadingTriviaByteLength == 0, - !self.at(.colon, .leftBrace, .endOfFile), + !self.at(.colon, .leftBrace, .leadingBoxCorner, .leadingBoxJunction, .endOfFile), self.hasProgressed(&loopProgress) { identifiersAfterOperatorName.append(consumeAnyToken()) @@ -1855,9 +1856,9 @@ extension Parser { precedenceAndTypes = nil } let unexpectedAtEnd: RawUnexpectedNodesSyntax? - if let leftBrace = self.consume(if: .leftBrace) { + if let leftBrace = self.consume(if: .leftBrace, .leadingBoxCorner, .leadingBoxJunction) { let attributeList = self.parsePrecedenceGroupAttributeListSyntax() - let rightBrace = self.consume(if: .rightBrace) + let rightBrace = self.consume(if: .rightBrace, .trailingBoxCorner, .trailingBoxJunction) unexpectedAtEnd = RawUnexpectedNodesSyntax( elements: [ RawSyntax(leftBrace), @@ -1890,11 +1891,11 @@ extension Parser { ) -> RawPrecedenceGroupDeclSyntax { let (unexpectedBeforeGroup, group) = self.eat(handle) let (unexpectedBeforeName, name) = self.expectIdentifier(allowSelfOrCapitalSelfAsIdentifier: true) - let (unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace) + let (unexpectedBeforeLBrace, lbrace) = self.expect(anyIn: PrecedenceGroupDeclSyntax.LeftBraceOptions.self, default: .leftBrace) let groupAttributes = self.parsePrecedenceGroupAttributeListSyntax() - let (unexpectedBeforeRBrace, rbrace) = self.expect(.rightBrace) + let (unexpectedBeforeRBrace, rbrace) = self.expect(rightBrace(for: lbrace)) return RawPrecedenceGroupDeclSyntax( attributes: attrs.attributes, modifiers: attrs.modifiers, @@ -2149,7 +2150,7 @@ extension Parser { // Parse the optional trailing closures. let trailingClosure: RawClosureExprSyntax? let additionalTrailingClosures: RawMultipleTrailingClosureElementListSyntax - if self.at(.leftBrace), + if self.at(anyIn: ClosureExprSyntax.LeftBraceOptions.self) != nil, self.withLookahead({ $0.atValidTrailingClosure(flavor: .basic) }) { (trailingClosure, additionalTrailingClosures) = diff --git a/Sources/SwiftParser/Expressions.swift b/Sources/SwiftParser/Expressions.swift index 620912bcaea..5651060de58 100644 --- a/Sources/SwiftParser/Expressions.swift +++ b/Sources/SwiftParser/Expressions.swift @@ -59,7 +59,10 @@ extension TokenConsumer { // whether the '{' is the start of a closure expression or a // brace statement for 'repeat { ... } while' let lookahead = self.lookahead() - return lookahead.peek().rawTokenKind != .leftBrace + return CodeBlockSyntax.LeftBraceOptions( + lexeme: lookahead.peek(), + experimentalFeatures: self.experimentalFeatures + ) == nil } return false @@ -763,7 +766,7 @@ extension Parser { // If we can parse trailing closures, do so. let trailingClosure: RawClosureExprSyntax? let additionalTrailingClosures: RawMultipleTrailingClosureElementListSyntax - if case .basic = flavor, self.at(.leftBrace), self.withLookahead({ $0.atValidTrailingClosure(flavor: flavor) }) + if case .basic = flavor, self.at(anyIn: ClosureExprSyntax.LeftBraceOptions.self) != nil, self.withLookahead({ $0.atValidTrailingClosure(flavor: flavor) }) { (trailingClosure, additionalTrailingClosures) = self.parseTrailingClosures(flavor: flavor) } else { @@ -803,7 +806,7 @@ extension Parser { // If we can parse trailing closures, do so. let trailingClosure: RawClosureExprSyntax? let additionalTrailingClosures: RawMultipleTrailingClosureElementListSyntax - if case .basic = flavor, self.at(.leftBrace), self.withLookahead({ $0.atValidTrailingClosure(flavor: flavor) }) + if case .basic = flavor, self.at(anyIn: ClosureExprSyntax.LeftBraceOptions.self) != nil, self.withLookahead({ $0.atValidTrailingClosure(flavor: flavor) }) { (trailingClosure, additionalTrailingClosures) = self.parseTrailingClosures(flavor: flavor) } else { @@ -827,7 +830,7 @@ extension Parser { } // Check for a trailing closure, if allowed. - if self.at(.leftBrace) && !leadingExpr.raw.kind.isLiteral + if self.at(anyIn: ClosureExprSyntax.LeftBraceOptions.self) != nil && !leadingExpr.raw.kind.isLiteral && self.withLookahead({ $0.atValidTrailingClosure(flavor: flavor) }) { // Add dummy blank argument list to the call expression syntax. @@ -848,7 +851,7 @@ extension Parser { // We only allow a single trailing closure on a call. This could be // generalized in the future, but needs further design. - if self.at(.leftBrace) { + if self.at(anyIn: ClosureExprSyntax.LeftBraceOptions.self) != nil { break } continue @@ -1219,7 +1222,7 @@ extension Parser { arena: self.arena ) ) - case (.leftBrace, _)?: // expr-closure + case (.leadingBoxCorner, _)?, (.leadingBoxJunction, _)?, (.leftBrace, _)?: // expr-closure return RawExprSyntax(self.parseClosureExpression()) case (.period, let handle)?: // .foo let period = self.eat(handle) @@ -1356,7 +1359,7 @@ extension Parser { // Parse the optional trailing closures. let trailingClosure: RawClosureExprSyntax? let additionalTrailingClosures: RawMultipleTrailingClosureElementListSyntax - if case .basic = flavor, self.at(.leftBrace), self.withLookahead({ $0.atValidTrailingClosure(flavor: flavor) }) { + if case .basic = flavor, self.at(anyIn: ClosureExprSyntax.LeftBraceOptions.self) != nil, self.withLookahead({ $0.atValidTrailingClosure(flavor: flavor) }) { (trailingClosure, additionalTrailingClosures) = self.parseTrailingClosures(flavor: flavor) } else { trailingClosure = nil @@ -1683,15 +1686,15 @@ extension Parser { /// Parse a closure expression. mutating func parseClosureExpression() -> RawClosureExprSyntax { // Parse the opening left brace. - let (unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace) + let (unexpectedBeforeLBrace, lbrace) = self.expect(anyIn: ClosureExprSyntax.LeftBraceOptions.self, default: .leftBrace) // Parse the closure-signature, if present. let signature = self.parseClosureSignatureIfPresent() // Parse the body. - let elements = parseCodeBlockItemList(until: { $0.at(.rightBrace) }) + let elements = parseCodeBlockItemList(until: { $0.at(rightBrace(for: lbrace)) }) // Parse the closing '}'. - let (unexpectedBeforeRBrace, rbrace) = self.expect(.rightBrace) + let (unexpectedBeforeRBrace, rbrace) = self.expect(rightBrace(for: lbrace)) return RawClosureExprSyntax( unexpectedBeforeLBrace, leftBrace: lbrace, @@ -2019,7 +2022,7 @@ extension Parser.Lookahead { // `label: switch x { ... }`. var lookahead = self.lookahead() lookahead.consumeAnyToken() - if lookahead.peek().rawTokenKind == .leftBrace { + if SwitchExprSyntax.LeftBraceOptions(lexeme: lookahead.peek(), experimentalFeatures: experimentalFeatures) != nil { return true } if lookahead.peek().isEditorPlaceholder { @@ -2035,7 +2038,7 @@ extension Parser.Lookahead { /// handle this by doing some lookahead in common situations. And later, Sema /// will emit a diagnostic with a fixit to add wrapping parens. mutating func atValidTrailingClosure(flavor: Parser.ExprFlavor) -> Bool { - precondition(self.at(.leftBrace), "Couldn't be a trailing closure") + precondition(self.at(anyIn: ClosureExprSyntax.LeftBraceOptions.self) != nil, "Couldn't be a trailing closure") // If this is the start of a get/set accessor, then it isn't a trailing // closure. @@ -2075,21 +2078,24 @@ extension Parser.Lookahead { // to see if it is immediately followed by a token which indicates we should // consider it part of the preceding expression var lookahead = self.lookahead() - lookahead.eat(.leftBrace) + let lbrace = lookahead.at(anyIn: ClosureExprSyntax.LeftBraceOptions.self)!.spec + lookahead.eat(lbrace.spec) var loopProgress = LoopProgressCondition() - while !lookahead.at(.endOfFile, .rightBrace) + while !lookahead.at([.endOfFile] + rightBrace(for: lbrace.spec)) && !lookahead.at(.poundEndif, .poundElse, .poundElseif) && lookahead.hasProgressed(&loopProgress) { lookahead.skipSingle() } - guard lookahead.consume(if: .rightBrace) != nil else { + guard lookahead.consume(if: rightBrace(for: lbrace.spec)) != nil else { return false } switch lookahead.currentToken { - case TokenSpec(.leftBrace), + case TokenSpec(.leadingBoxCorner), + TokenSpec(.leadingBoxJunction), + TokenSpec(.leftBrace), TokenSpec(.where), TokenSpec(.comma): return true @@ -2148,7 +2154,7 @@ extension Parser { let conditions: RawConditionElementListSyntax - if self.at(.leftBrace) { + if self.at(anyIn: CodeBlockSyntax.LeftBraceOptions.self) != nil { conditions = RawConditionElementListSyntax( elements: [ RawConditionElementSyntax( @@ -2205,13 +2211,13 @@ extension Parser { // a ``RawClosureExprSyntax`` in the condition, which is most likely not what the user meant. // Create a missing condition instead and use the `{` for the start of the body. let subject: RawExprSyntax - if self.at(.leftBrace) { + if self.at(anyIn: SwitchExprSyntax.LeftBraceOptions.self) != nil { subject = RawExprSyntax(RawMissingExprSyntax(arena: self.arena)) } else { subject = self.parseExpression(flavor: .stmtCondition, pattern: .none) } - let (unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace) + let (unexpectedBeforeLBrace, lbrace) = self.expect(anyIn: SwitchExprSyntax.LeftBraceOptions.self, default: .leftBrace) let cases = self.parseSwitchCases(allowStandaloneStmtRecovery: !lbrace.isMissing) diff --git a/Sources/SwiftParser/Lexer/Cursor.swift b/Sources/SwiftParser/Lexer/Cursor.swift index 28ed57f8947..031d5f20bb2 100644 --- a/Sources/SwiftParser/Lexer/Cursor.swift +++ b/Sources/SwiftParser/Lexer/Cursor.swift @@ -1005,6 +1005,15 @@ extension Lexer.Cursor { case nil: return Lexer.Result(.endOfFile) default: + // Syntax that requires Unicode. + switch self.peekScalar() { + case "╗": _ = self.advanceValidatingUTF8Character(); return Lexer.Result(.leadingBoxCorner) + case "╣": _ = self.advanceValidatingUTF8Character(); return Lexer.Result(.leadingBoxJunction) + case "╚": _ = self.advanceValidatingUTF8Character(); return Lexer.Result(.trailingBoxCorner) + case "╠": _ = self.advanceValidatingUTF8Character(); return Lexer.Result(.trailingBoxJunction) + default: break + } + var tmp = self if tmp.advance(if: { $0.isValidIdentifierStartCodePoint }) { return self.lexIdentifier() @@ -1172,6 +1181,23 @@ extension Lexer.Cursor { while true { let start = self + // Check for first UTF-8 byte of box drawing characters + if self.peek() == 0xE2 { + // Eat all inert box-drawing characters + self.advance(while: { char in + switch char { + case "╔", "╝", "║", "═": + return true; + default: + return false + } + }) + + if self.hasProgressed(comparedTo: start) { + continue + } + } + switch self.advance() { // 'continue' - the character is a part of the trivia. // 'break' - the character should a part of token text. diff --git a/Sources/SwiftParser/Lexer/RegexLiteralLexer.swift b/Sources/SwiftParser/Lexer/RegexLiteralLexer.swift index c5c565405cd..6b8d4f511af 100644 --- a/Sources/SwiftParser/Lexer/RegexLiteralLexer.swift +++ b/Sources/SwiftParser/Lexer/RegexLiteralLexer.swift @@ -606,7 +606,7 @@ extension Lexer.Cursor { return false // Prefix grammar that appears before an expression. - case .leftAngle, .leftBrace, .leftParen, .leftSquare, .prefixOperator, .prefixAmpersand: + case .leadingBoxCorner, .leadingBoxJunction, .leftAngle, .leftBrace, .leftParen, .leftSquare, .prefixOperator, .prefixAmpersand: return true // Binary operators sequence expressions. @@ -618,7 +618,7 @@ extension Lexer.Cursor { return true // Postfix grammar would expect a binary operator next. - case .postfixOperator, .exclamationMark, .postfixQuestionMark, .rightAngle, .rightBrace, .rightParen, .rightSquare: + case .postfixOperator, .exclamationMark, .postfixQuestionMark, .rightAngle, .rightBrace, .rightParen, .rightSquare, .trailingBoxCorner, .trailingBoxJunction: return false // Punctuation that does not sequence expressions. diff --git a/Sources/SwiftParser/Lookahead.swift b/Sources/SwiftParser/Lookahead.swift index 9aa7afe8c4b..a73cb671a9d 100644 --- a/Sources/SwiftParser/Lookahead.swift +++ b/Sources/SwiftParser/Lookahead.swift @@ -90,8 +90,19 @@ extension Parser.Lookahead: TokenConsumer { /// /// - Parameter kind: The kind of token to consume. /// - Returns: A token of the given kind. - mutating func eat(_ spec: TokenSpec) -> Token { - return self.consume(if: spec)! + mutating func eat(_ specs: [TokenSpec]) -> Token { + return self.consume(if: specs)! + } + + /// Consumes the current token, and asserts that it matches `spec`. + /// + /// If the token kind did not match, this function will abort. It is useful + /// to insert structural invariants during parsing. + /// + /// - Parameter kind: The kind of token to consume. + /// - Returns: A token of the given kind. + mutating func eat(_ specs: TokenSpec...) -> Token { + return self.eat(specs) } #if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION @@ -269,7 +280,7 @@ extension Parser.Lookahead { extension Parser.Lookahead { mutating func atStartOfGetSetAccessor() -> Bool { - precondition(self.at(.leftBrace), "not checking a brace?") + precondition(self.at(anyIn: AccessorBlockSyntax.LeftBraceOptions.self) != nil, "not checking a brace?") // The only case this can happen is if the accessor label is immediately after // a brace (possibly preceded by attributes). "get" is implicit, so it can't @@ -291,7 +302,7 @@ extension Parser.Lookahead { // Eat the "{". var lookahead = self.lookahead() - lookahead.eat(.leftBrace) + lookahead.consume(ifAnyIn: CodeBlockSyntax.LeftBraceOptions.self)! // Eat attributes, if present. while lookahead.consume(if: .atSign) != nil { @@ -334,6 +345,8 @@ extension Parser.Lookahead { // - String interpolation contains parentheses, so it automatically skips // until the closing parenthesis. private enum BracketedTokens: TokenSpecSet { + case leadingBoxCorner + case leadingBoxJunction case leftParen case leftBrace case leftSquare @@ -343,6 +356,8 @@ extension Parser.Lookahead { init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { switch lexeme.rawTokenKind { + case .leadingBoxCorner: self = .leadingBoxCorner + case .leadingBoxJunction: self = .leadingBoxJunction case .leftParen: self = .leftParen case .leftBrace: self = .leftBrace case .leftSquare: self = .leftSquare @@ -355,6 +370,8 @@ extension Parser.Lookahead { var spec: TokenSpec { switch self { + case .leadingBoxCorner: return .leadingBoxCorner + case .leadingBoxJunction: return .leadingBoxJunction case .leftParen: return .leftParen case .leftBrace: return .leftBrace case .leftSquare: return .leftSquare @@ -384,6 +401,9 @@ extension Parser.Lookahead { case .skipSingle: let t = self.at(anyIn: BracketedTokens.self) switch t { + case (.leadingBoxCorner, let handle)?, (.leadingBoxJunction, let handle)?: + self.eat(handle) + stack += [.skipSinglePost(start: t!.spec), .skipUntil(.trailingBoxCorner, .trailingBoxJunction)] case (.leftParen, let handle)?: self.eat(handle) stack += [.skipSinglePost(start: .leftParen), .skipUntil(.rightParen, .rightBrace)] @@ -404,6 +424,8 @@ extension Parser.Lookahead { } case .skipSinglePost(start: let start): switch start { + case .leadingBoxCorner, .leadingBoxJunction: + self.consume(if: .trailingBoxJunction, .trailingBoxJunction) case .leftParen: self.consume(if: .rightParen) case .leftBrace: diff --git a/Sources/SwiftParser/Nominals.swift b/Sources/SwiftParser/Nominals.swift index 8929833e516..8d7d7b7b0df 100644 --- a/Sources/SwiftParser/Nominals.swift +++ b/Sources/SwiftParser/Nominals.swift @@ -340,7 +340,7 @@ extension Parser { } mutating func atInheritanceListTerminator() -> Bool { - return self.experimentalFeatures.contains(.trailingComma) && (self.at(.leftBrace) || self.at(.keyword(.where))) + return self.experimentalFeatures.contains(.trailingComma) && (self.at(anyIn: MemberBlockSyntax.LeftBraceOptions.self) != nil || self.at(.keyword(.where))) } mutating func parsePrimaryAssociatedTypes() -> RawPrimaryAssociatedTypeClauseSyntax { @@ -379,7 +379,7 @@ extension Parser { return self.withLookahead { $0.consume(if: .leftParen) guard $0.canParseType() else { return false } - return $0.at(.rightParen, .keyword(.where), .leftBrace) || $0.at(.endOfFile) + return $0.at(.rightParen, .keyword(.where), .leftBrace, .leadingBoxCorner, .leadingBoxJunction) || $0.at(.endOfFile) } } } diff --git a/Sources/SwiftParser/Parser.swift b/Sources/SwiftParser/Parser.swift index 89d21612a18..8d2b031874a 100644 --- a/Sources/SwiftParser/Parser.swift +++ b/Sources/SwiftParser/Parser.swift @@ -399,9 +399,9 @@ public struct Parser { private mutating func adjustNestingLevel(for tokenKind: RawTokenKind) { switch tokenKind { - case .leftAngle, .leftBrace, .leftParen, .leftSquare, .poundIf: + case .leadingBoxCorner, .leadingBoxJunction, .leftAngle, .leftBrace, .leftParen, .leftSquare, .poundIf: nestingLevel += 1 - case .rightAngle, .rightBrace, .rightParen, .rightSquare, .poundEndif: + case .rightAngle, .rightBrace, .rightParen, .rightSquare, .poundEndif, .trailingBoxCorner, .trailingBoxJunction: nestingLevel -= 1 default: break @@ -557,6 +557,25 @@ extension Parser { // MARK: Expecting Tokens with Recovery +func rightBrace(for leftBrace: RawTokenKind) -> [TokenSpec] { + switch leftBrace { + case .leftBrace: + return [.rightBrace] + case .leadingBoxCorner, .leadingBoxJunction: + return [.trailingBoxCorner, .trailingBoxJunction] + default: + preconditionFailure("Passed in \(leftBrace), not a left brace") + } +} + +func rightBrace(for leftBrace: RawTokenSyntax) -> [TokenSpec] { + return rightBrace(for: leftBrace.tokenKind) +} + +func rightBrace(for leftBrace: TokenSpec) -> [TokenSpec] { + return rightBrace(for: leftBrace.synthesizedTokenKind.decomposeToRaw().rawKind) +} + extension Parser { /// Implements the paradigm shared across all `expect` methods. @inline(__always) @@ -602,14 +621,13 @@ extension Parser { /// a missing token of `defaultKind`. @inline(__always) mutating func expect( - _ spec1: TokenSpec, - _ spec2: TokenSpec, - default defaultKind: TokenSpec + _ specs: [TokenSpec], + default defaultKind: TokenSpec? = nil ) -> (unexpected: RawUnexpectedNodesSyntax?, token: RawTokenSyntax) { return expectImpl( - consume: { $0.consume(if: spec1, spec2) }, - canRecoverTo: { $0.canRecoverTo(spec1, spec2) }, - makeMissing: { $0.missingToken(defaultKind) } + consume: { $0.consume(if: specs) }, + canRecoverTo: { $0.canRecoverTo(specs) }, + makeMissing: { $0.missingToken(defaultKind ?? specs.first!) } ) } @@ -622,16 +640,10 @@ extension Parser { /// a missing token of `defaultKind`. @inline(__always) mutating func expect( - _ spec1: TokenSpec, - _ spec2: TokenSpec, - _ spec3: TokenSpec, + _ specs: TokenSpec..., default defaultKind: TokenSpec ) -> (unexpected: RawUnexpectedNodesSyntax?, token: RawTokenSyntax) { - return expectImpl( - consume: { $0.consume(if: spec1, spec2, spec3) }, - canRecoverTo: { $0.canRecoverTo(spec1, spec2, spec3) }, - makeMissing: { $0.missingToken(defaultKind) } - ) + return expect(specs, default: defaultKind) } @inline(__always) @@ -746,15 +758,16 @@ extension Parser { } } + let rightBraces = rightBrace(for: leftBrace) guard leftBrace.isMissing, let introducer = introducer else { // Fast path for correct parses: If leftBrace is not missing, just `expect`. - return self.expect(.rightBrace) + return self.expect(rightBraces) } var lookahead = self.lookahead() - guard let recoveryHandle = lookahead.canRecoverTo(.rightBrace) else { + guard let recoveryHandle = lookahead.canRecoverTo(rightBraces) else { // We can't recover to '}'. Synthesize it. - return (nil, self.missingToken(.rightBrace)) + return (nil, self.missingToken(rightBraces.first!)) } // We can recover to a '}'. Decide whether we want to eat it based on its indentation. @@ -762,13 +775,13 @@ extension Parser { switch (indentation(introducer.leadingTriviaPieces), indentation(rightBraceTrivia)) { // Catch cases where the brace has known indentation that is less than that of `introducer`, in which case we don't want to consume it. case (.spaces(let introducerSpaces), .spaces(let rightBraceSpaces)) where rightBraceSpaces < introducerSpaces: - return (nil, self.missingToken(.rightBrace)) + return (nil, self.missingToken(rightBraces.first!)) case (.tabs(let introducerTabs), .tabs(let rightBraceTabs)) where rightBraceTabs < introducerTabs: - return (nil, self.missingToken(.rightBrace)) + return (nil, self.missingToken(rightBraces.first!)) case (.spaces, .tabs(0)): - return (nil, self.missingToken(.rightBrace)) + return (nil, self.missingToken(rightBraces.first!)) case (.tabs, .spaces(0)): - return (nil, self.missingToken(.rightBrace)) + return (nil, self.missingToken(rightBraces.first!)) default: return self.eat(recoveryHandle) } diff --git a/Sources/SwiftParser/Recovery.swift b/Sources/SwiftParser/Recovery.swift index 955497ca1f3..0a0552ad976 100644 --- a/Sources/SwiftParser/Recovery.swift +++ b/Sources/SwiftParser/Recovery.swift @@ -59,24 +59,7 @@ struct RecoveryConsumptionHandle { } extension Parser.Lookahead { - /// See `canRecoverTo` that takes 3 specs. - mutating func canRecoverTo( - _ spec: TokenSpec, - recursionDepth: Int = 1 - ) -> RecoveryConsumptionHandle? { - return canRecoverTo(spec, spec, spec, recursionDepth: recursionDepth) - } - - /// See `canRecoverTo` that takes 3 specs. - mutating func canRecoverTo( - _ spec1: TokenSpec, - _ spec2: TokenSpec, - recursionDepth: Int = 1 - ) -> RecoveryConsumptionHandle? { - return canRecoverTo(spec1, spec2, spec1, recursionDepth: recursionDepth) - } - - /// Tries eating tokens until it finds a token that matches `spec1`, `spec2` or `spec3` + /// Tries eating tokens until it finds a token that matches one of `specs` /// without skipping tokens that have a precedence that's higher than the /// lowest precedence in the expected kinds. If it found a token in this way, /// returns `true`, otherwise `false`. @@ -84,9 +67,7 @@ extension Parser.Lookahead { /// tokens this lookahead skipped over to find `kind` by consuming /// `lookahead.tokensConsumed` as unexpected. mutating func canRecoverTo( - _ spec1: TokenSpec, - _ spec2: TokenSpec, - _ spec3: TokenSpec, + _ specs: [TokenSpec], recursionDepth: Int = 1 ) -> RecoveryConsumptionHandle? { if recursionDepth > 10 { @@ -100,30 +81,20 @@ extension Parser.Lookahead { } #if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION if shouldRecordAlternativeTokenChoices { - recordAlternativeTokenChoice(for: self.currentToken, choices: [spec1, spec2, spec3]) + recordAlternativeTokenChoice(for: self.currentToken, choices: specs) } #endif let initialTokensConsumed = self.tokensConsumed - let recoveryPrecedence = min(spec1.recoveryPrecedence, spec2.recoveryPrecedence, spec3.recoveryPrecedence) - let shouldSkipOverNewlines = - recoveryPrecedence.shouldSkipOverNewlines && spec1.allowAtStartOfLine && spec2.allowAtStartOfLine - && spec3.allowAtStartOfLine + let recoveryPrecedence = specs.map(\.recoveryPrecedence).min()! + let shouldSkipOverNewlines = recoveryPrecedence.shouldSkipOverNewlines && specs.allSatisfy(\.allowAtStartOfLine) while !self.at(.endOfFile) { if !shouldSkipOverNewlines, self.atStartOfLine { break } - let matchedSpec: TokenSpec? - switch self.currentToken { - case spec1: - matchedSpec = spec1 - case spec2: - matchedSpec = spec2 - case spec3: - matchedSpec = spec3 - default: - matchedSpec = nil + let matchedSpec = specs.first { spec in + spec ~= self.currentToken } if let matchedSpec { return RecoveryConsumptionHandle( @@ -135,13 +106,13 @@ extension Parser.Lookahead { if currentTokenPrecedence >= recoveryPrecedence { break } - if let closingDelimiter = currentTokenPrecedence.closingTokenKind { - let closingDelimiterSpec = TokenSpec(closingDelimiter) + if !currentTokenPrecedence.closingTokenKinds.isEmpty { + let closingDelimiterSpecs = currentTokenPrecedence.closingTokenKinds.map { TokenSpec($0) } let canCloseAtSameLine: Int? = self.withLookahead { lookahead in var tokensToSkip = 0 while !lookahead.at(.endOfFile), !lookahead.currentToken.isAtStartOfLine { tokensToSkip += 1 - if lookahead.at(closingDelimiterSpec) { + if lookahead.at(closingDelimiterSpecs) { return tokensToSkip } else { lookahead.consumeAnyToken() @@ -156,10 +127,10 @@ extension Parser.Lookahead { continue } self.consumeAnyToken() - guard self.canRecoverTo(closingDelimiterSpec, recursionDepth: recursionDepth + 1) != nil else { + guard self.canRecoverTo(closingDelimiterSpecs, recursionDepth: recursionDepth + 1) != nil else { continue } - self.eat(closingDelimiterSpec) + self.eat(closingDelimiterSpecs) } else { self.consumeAnyToken() } @@ -168,6 +139,20 @@ extension Parser.Lookahead { return nil } + /// Tries eating tokens until it finds a token that matches one of `specs` + /// without skipping tokens that have a precedence that's higher than the + /// lowest precedence in the expected kinds. If it found a token in this way, + /// returns `true`, otherwise `false`. + /// If this method returns `true`, the parser probably wants to consume the + /// tokens this lookahead skipped over to find `kind` by consuming + /// `lookahead.tokensConsumed` as unexpected. + mutating func canRecoverTo( + _ specs: TokenSpec..., + recursionDepth: Int = 1 + ) -> RecoveryConsumptionHandle? { + return self.canRecoverTo(specs, recursionDepth: recursionDepth) + } + /// Checks if we can reach a token in `subset` by skipping tokens that have /// a precedence that have a lower ``TokenPrecedence`` than the minimum /// precedence of a token in that subset. @@ -211,12 +196,12 @@ extension Parser.Lookahead { break } self.consumeAnyToken() - if let closingDelimiter = currentTokenPrecedence.closingTokenKind { - let closingDelimiterSpec = TokenSpec(closingDelimiter) - guard self.canRecoverTo(closingDelimiterSpec) != nil else { + if !currentTokenPrecedence.closingTokenKinds.isEmpty { + let closingDelimiterSpecs = currentTokenPrecedence.closingTokenKinds.map { TokenSpec($0) } + guard self.canRecoverTo(closingDelimiterSpecs) != nil else { break } - self.eat(closingDelimiterSpec) + self.eat(closingDelimiterSpecs) } } diff --git a/Sources/SwiftParser/Statements.swift b/Sources/SwiftParser/Statements.swift index e58e1694174..e42ed420ccc 100644 --- a/Sources/SwiftParser/Statements.swift +++ b/Sources/SwiftParser/Statements.swift @@ -202,7 +202,7 @@ extension Parser { } // Condition terminator is start of statement body for `if` or `while` statements. // Missing `else` is a common mistake for `guard` statements so we fall back to lookahead for a body. - return self.at(.leftBrace) && withLookahead({ $0.atStartOfConditionalStatementBody() }) + return self.at(anyIn: CodeBlockSyntax.LeftBraceOptions.self) != nil && withLookahead({ $0.atStartOfConditionalStatementBody() }) } /// Parse a condition element. @@ -454,7 +454,7 @@ extension Parser { mutating func parseCatchClause() -> RawCatchClauseSyntax { let (unexpectedBeforeCatchKeyword, catchKeyword) = self.expect(.keyword(.catch)) var catchItems = [RawCatchItemSyntax]() - if !self.at(.leftBrace) { + if self.at(anyIn: CodeBlockSyntax.LeftBraceOptions.self) == nil { var keepGoing: RawTokenSyntax? = nil var loopProgress = LoopProgressCondition() repeat { @@ -486,7 +486,7 @@ extension Parser { // If this is a 'catch' clause and we have "catch {" or "catch where...", // then we get an implicit "let error" pattern. let pattern: RawPatternSyntax? - if self.at(.leftBrace, .keyword(.where)) { + if self.at(.keyword(.where)) || self.at(anyIn: CodeBlockSyntax.LeftBraceOptions.self) != nil { pattern = nil } else { pattern = self.parseMatchingPattern(context: .matching) @@ -515,7 +515,7 @@ extension Parser { mutating func parseWhileStatement(whileHandle: RecoveryConsumptionHandle) -> RawWhileStmtSyntax { let (unexpectedBeforeWhileKeyword, whileKeyword) = self.eat(whileHandle) let conditions: RawConditionElementListSyntax - if self.at(.leftBrace) { + if self.at(anyIn: CodeBlockSyntax.LeftBraceOptions.self) != nil { conditions = RawConditionElementListSyntax( elements: [ RawConditionElementSyntax( @@ -598,7 +598,7 @@ extension Parser { // a `RawClosureExprSyntax` in the condition, which is most likely not what the user meant. // Create a missing condition instead and use the `{` for the start of the body. let expr: RawExprSyntax - if self.at(.leftBrace) { + if self.at(anyIn: CodeBlockSyntax.LeftBraceOptions.self) != nil { expr = RawExprSyntax(RawMissingExprSyntax(arena: self.arena)) } else { expr = self.parseExpression(flavor: .stmtCondition, pattern: .none) @@ -937,7 +937,7 @@ extension Parser.Lookahead { // is a pack expansion expression. // FIXME: 'repeat' followed by '{' could be a pack expansion // with a closure pattern. - return self.peek().rawTokenKind == .leftBrace + return CodeBlockSyntax.LeftBraceOptions(lexeme: self.peek(), experimentalFeatures: self.experimentalFeatures) != nil case .yield?: switch self.peek().rawTokenKind { case .prefixAmpersand: @@ -1008,7 +1008,7 @@ extension Parser.Lookahead { // expr. return false - case .leftBrace: + case .trailingBoxCorner, .trailingBoxJunction, .leftBrace: // This is a trailing closure. return false @@ -1074,7 +1074,7 @@ extension Parser.Lookahead { /// Returns `true` if the current token represents the start of an `if` or `while` statement body. mutating func atStartOfConditionalStatementBody() -> Bool { - guard at(.leftBrace) else { + guard let lbrace = at(anyIn: CodeBlockSyntax.LeftBraceOptions.self)?.spec else { // Statement bodies always start with a '{'. If there is no '{', we can't be at the statement body. return false } @@ -1091,7 +1091,7 @@ extension Parser.Lookahead { // If the current token is an `else` keyword, this must be the statement body of an `if` statement since conditions can't be followed by `else`. return true } - if self.at(.rightBrace, .rightParen) { + if self.at(rightBrace(for: lbrace.spec) + [.rightParen]) { // A right brace or parenthesis cannot start a statement body, nor can the condition list continue afterwards. So, this must be the statement body. // This covers cases like `if true, { if true, { } }` or `( if true, { print(0) } )`. While the latter is not valid code, it improves diagnostics. return true diff --git a/Sources/SwiftParser/TokenConsumer.swift b/Sources/SwiftParser/TokenConsumer.swift index 2a603b77ae1..7af91b7ccc5 100644 --- a/Sources/SwiftParser/TokenConsumer.swift +++ b/Sources/SwiftParser/TokenConsumer.swift @@ -90,42 +90,25 @@ extension TokenConsumer { return spec ~= self.currentToken } - /// Returns whether the current token matches one of the two specs. + /// Returns whether the current token matches one of the specs. @inline(__always) mutating func at( - _ spec1: TokenSpec, - _ spec2: TokenSpec + _ specs: [TokenSpec] ) -> Bool { #if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION if shouldRecordAlternativeTokenChoices { - recordAlternativeTokenChoice(for: self.currentToken, choices: [spec1, spec2]) + recordAlternativeTokenChoice(for: self.currentToken, choices: specs) } #endif - switch self.currentToken { - case spec1: return true - case spec2: return true - default: return false - } + return specs.contains(where: { $0 ~= self.currentToken }) } - /// Returns whether the current token matches one of the three specs. + /// Returns whether the current token matches one of the specs. @inline(__always) mutating func at( - _ spec1: TokenSpec, - _ spec2: TokenSpec, - _ spec3: TokenSpec + _ specs: TokenSpec... ) -> Bool { - #if SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION - if shouldRecordAlternativeTokenChoices { - recordAlternativeTokenChoice(for: self.currentToken, choices: [spec1, spec2, spec3]) - } - #endif - switch self.currentToken { - case spec1: return true - case spec2: return true - case spec3: return true - default: return false - } + return at(specs) } /// Returns whether the current token is an operator with the given `name`. @@ -263,35 +246,24 @@ extension TokenConsumer { /// Otherwise return `nil`. @inline(__always) mutating func consume( - if spec1: TokenSpec, - _ spec2: TokenSpec + if specs: [TokenSpec] ) -> Token? { - if let token = consume(if: spec1) { - return token - } else if let token = consume(if: spec2) { - return token - } else { - return nil + for spec in specs { + if let token = consume(if: spec) { + return token + } } + return nil } + /// If the current token matches one of the specs, consume the token and return it. /// Otherwise return `nil`. @inline(__always) mutating func consume( - if spec1: TokenSpec, - _ spec2: TokenSpec, - _ spec3: TokenSpec + if specs: TokenSpec... ) -> Token? { - if let token = consume(if: spec1) { - return token - } else if let token = consume(if: spec2) { - return token - } else if let token = consume(if: spec3) { - return token - } else { - return nil - } + return self.consume(if: specs) } /// Consumes and returns the current token is an operator with the given `name`. diff --git a/Sources/SwiftParser/TokenPrecedence.swift b/Sources/SwiftParser/TokenPrecedence.swift index 7a7e84d6bb4..b615aed567e 100644 --- a/Sources/SwiftParser/TokenPrecedence.swift +++ b/Sources/SwiftParser/TokenPrecedence.swift @@ -53,16 +53,18 @@ enum TokenPrecedence: Comparable { case closingPoundIf /// If the precedence is `weakBracketed` or `strongBracketed`, the closing delimiter of the bracketed group. - var closingTokenKind: RawTokenKind? { + var closingTokenKinds: [RawTokenKind] { switch self { case .weakBracketed(closingDelimiter: let closingDelimiter): - return closingDelimiter + return [closingDelimiter] + case .openingBrace(closingDelimiter: .trailingBoxCorner): + return [.trailingBoxCorner, .trailingBoxJunction] case .openingBrace(closingDelimiter: let closingDelimiter): - return closingDelimiter + return [closingDelimiter] case .openingPoundIf: - return .poundEndif + return [.poundEndif] default: - return nil + return [] } } @@ -166,6 +168,8 @@ enum TokenPrecedence: Comparable { self = .weakBracketClose // MARK: Strong bracketed + case .leadingBoxCorner, .leadingBoxJunction: + self = .openingBrace(closingDelimiter: .trailingBoxCorner) case .leftBrace: self = .openingBrace(closingDelimiter: .rightBrace) case .poundElseif, .poundElse, .poundIf: @@ -181,7 +185,7 @@ enum TokenPrecedence: Comparable { self = .strongPunctuator // MARK: Strong bracket close - case .rightBrace: + case .rightBrace, .trailingBoxCorner, .trailingBoxJunction: self = .closingBrace case .poundEndif: self = .closingPoundIf diff --git a/Sources/SwiftParser/TokenSpec.swift b/Sources/SwiftParser/TokenSpec.swift index d7dbc5d4656..55dbce7ac51 100644 --- a/Sources/SwiftParser/TokenSpec.swift +++ b/Sources/SwiftParser/TokenSpec.swift @@ -192,6 +192,14 @@ public struct TokenSpec: Sendable { default: return TokenKind.fromRaw(kind: rawTokenKind, text: "") } } + + func disallowedAtStartOfLine() -> TokenSpec { + if let keyword { + return .init(keyword, remapping: remapping, recoveryPrecedence: recoveryPrecedence, allowAtStartOfLine: false) + } + return .init(rawTokenKind, remapping: remapping, recoveryPrecedence: recoveryPrecedence, allowAtStartOfLine: false) + + } } extension TokenConsumer { diff --git a/Sources/SwiftParser/TokenSpecSet.swift b/Sources/SwiftParser/TokenSpecSet.swift index e3eb5126ffa..ead3e406cd9 100644 --- a/Sources/SwiftParser/TokenSpecSet.swift +++ b/Sources/SwiftParser/TokenSpecSet.swift @@ -836,6 +836,8 @@ enum PrimaryExpressionStart: TokenSpecSet { case identifier case `init` case integerLiteral + case leadingBoxCorner + case leadingBoxJunction case leftBrace case leftParen case leftSquare @@ -868,6 +870,8 @@ enum PrimaryExpressionStart: TokenSpecSet { case TokenSpec(.identifier): self = .identifier case TokenSpec(.`init`): self = .`init` case TokenSpec(.integerLiteral): self = .integerLiteral + case TokenSpec(.leadingBoxCorner): self = .leadingBoxCorner + case TokenSpec(.leadingBoxJunction): self = .leadingBoxJunction case TokenSpec(.leftBrace): self = .leftBrace case TokenSpec(.leftParen): self = .leftParen case TokenSpec(.leftSquare): self = .leftSquare @@ -903,6 +907,8 @@ enum PrimaryExpressionStart: TokenSpecSet { case .identifier: return .identifier case .`init`: return .keyword(.`init`) case .integerLiteral: return .integerLiteral + case .leadingBoxCorner: return .leadingBoxCorner + case .leadingBoxJunction: return .leadingBoxJunction case .leftBrace: return .leftBrace case .leftParen: return .leftParen case .leftSquare: return .leftSquare diff --git a/Sources/SwiftParser/TopLevel.swift b/Sources/SwiftParser/TopLevel.swift index 5ee166a7c95..e07d792e6d3 100644 --- a/Sources/SwiftParser/TopLevel.swift +++ b/Sources/SwiftParser/TopLevel.swift @@ -97,7 +97,7 @@ extension Parser { /// This function is used when parsing places where function bodies are /// optional - like the function requirements in protocol declarations. mutating func parseOptionalCodeBlock(allowInitDecl: Bool = true) -> RawCodeBlockSyntax? { - guard self.at(.leftBrace) || self.canRecoverTo(TokenSpec(.leftBrace, allowAtStartOfLine: false)) != nil else { + guard self.at(anyIn: CodeBlockSyntax.LeftBraceOptions.self) != nil || self.canRecoverTo(anyIn: CodeBlockSyntax.LeftBraceOptions.self) != nil else { return nil } return self.parseCodeBlock(allowInitDecl: allowInitDecl) @@ -109,8 +109,8 @@ extension Parser { /// If the left brace is missing, its indentation will be used to judge whether a following `}` was /// indented to close this code block or a surrounding context. See `expectRightBrace`. mutating func parseCodeBlock(introducer: RawTokenSyntax? = nil, allowInitDecl: Bool = true) -> RawCodeBlockSyntax { - let (unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace) - let itemList = parseCodeBlockItemList(allowInitDecl: allowInitDecl, until: { $0.at(.rightBrace) }) + let (unexpectedBeforeLBrace, lbrace) = self.expect(anyIn: CodeBlockSyntax.LeftBraceOptions.self, default: .leftBrace) + let itemList = parseCodeBlockItemList(allowInitDecl: allowInitDecl, until: { $0.at(rightBrace(for: lbrace)) }) let (unexpectedBeforeRBrace, rbrace) = self.expectRightBrace(leftBrace: lbrace, introducer: introducer) return .init( diff --git a/Sources/SwiftParser/TriviaParser.swift b/Sources/SwiftParser/TriviaParser.swift index e6fe482c8d8..5f70e3ac406 100644 --- a/Sources/SwiftParser/TriviaParser.swift +++ b/Sources/SwiftParser/TriviaParser.swift @@ -30,6 +30,25 @@ public struct TriviaParser { while true { let start = cursor + + // Check for first UTF-8 byte of box drawing characters + if cursor.peek() == 0xE2 { + // Eat all inert box-drawing characters + cursor.advance(while: { char in + switch char { + case "╔", "╝", "║", "═": + return true; + default: + return false + } + }) + + if cursor.hasProgressed(comparedTo: start) { + pieces.append(.boxDrawing(start.text(upTo: cursor))) + continue + } + } + switch cursor.advance() { case nil: // Finished. @@ -112,7 +131,7 @@ public struct TriviaParser { // piece start. cursor.advance(while: { char in switch char { - case " ", "\n", "\r", "\t", "\u{000B}", "\u{000C}", "/", "#", "<", ">": + case " ", "\n", "\r", "\t", "\u{000B}", "\u{000C}", "/", "#", "<", ">", "╔", "╝", "║", "═": return false default: return true diff --git a/Sources/SwiftParser/generated/Parser+TokenSpecSet.swift b/Sources/SwiftParser/generated/Parser+TokenSpecSet.swift index 0b4ac402f2e..c2051134892 100644 --- a/Sources/SwiftParser/generated/Parser+TokenSpecSet.swift +++ b/Sources/SwiftParser/generated/Parser+TokenSpecSet.swift @@ -18,6 +18,128 @@ @_spi(RawSyntax) @_spi(ExperimentalLanguageFeatures) import SwiftSyntax #endif +extension AccessorBlockSyntax { + @_spi(Diagnostics) + public enum LeftBraceOptions: TokenSpecSet { + case leftBrace + case leadingBoxCorner + case leadingBoxJunction + + init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { + switch PrepareForKeywordMatch(lexeme) { + case TokenSpec(.leftBrace): + self = .leftBrace + case TokenSpec(.leadingBoxCorner): + self = .leadingBoxCorner + case TokenSpec(.leadingBoxJunction): + self = .leadingBoxJunction + default: + return nil + } + } + + public init?(token: TokenSyntax) { + switch token { + case TokenSpec(.leftBrace): + self = .leftBrace + case TokenSpec(.leadingBoxCorner): + self = .leadingBoxCorner + case TokenSpec(.leadingBoxJunction): + self = .leadingBoxJunction + default: + return nil + } + } + + var spec: TokenSpec { + switch self { + case .leftBrace: + return .leftBrace + case .leadingBoxCorner: + return .leadingBoxCorner + case .leadingBoxJunction: + return .leadingBoxJunction + } + } + + /// Returns a token that satisfies the `TokenSpec` of this case. + /// + /// If the token kind of this spec has variable text, e.g. for an identifier, this returns a token with empty text. + @_spi(Diagnostics) + public var tokenSyntax: TokenSyntax { + switch self { + case .leftBrace: + return .leftBraceToken() + case .leadingBoxCorner: + return .leadingBoxCornerToken() + case .leadingBoxJunction: + return .leadingBoxJunctionToken() + } + } + } +} + +extension AccessorBlockSyntax { + @_spi(Diagnostics) + public enum RightBraceOptions: TokenSpecSet { + case rightBrace + case trailingBoxCorner + case trailingBoxJunction + + init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { + switch PrepareForKeywordMatch(lexeme) { + case TokenSpec(.rightBrace): + self = .rightBrace + case TokenSpec(.trailingBoxCorner): + self = .trailingBoxCorner + case TokenSpec(.trailingBoxJunction): + self = .trailingBoxJunction + default: + return nil + } + } + + public init?(token: TokenSyntax) { + switch token { + case TokenSpec(.rightBrace): + self = .rightBrace + case TokenSpec(.trailingBoxCorner): + self = .trailingBoxCorner + case TokenSpec(.trailingBoxJunction): + self = .trailingBoxJunction + default: + return nil + } + } + + var spec: TokenSpec { + switch self { + case .rightBrace: + return .rightBrace + case .trailingBoxCorner: + return .trailingBoxCorner + case .trailingBoxJunction: + return .trailingBoxJunction + } + } + + /// Returns a token that satisfies the `TokenSpec` of this case. + /// + /// If the token kind of this spec has variable text, e.g. for an identifier, this returns a token with empty text. + @_spi(Diagnostics) + public var tokenSyntax: TokenSyntax { + switch self { + case .rightBrace: + return .rightBraceToken() + case .trailingBoxCorner: + return .trailingBoxCornerToken() + case .trailingBoxJunction: + return .trailingBoxJunctionToken() + } + } + } +} + extension AccessorDeclSyntax { @_spi(Diagnostics) public enum AccessorSpecifierOptions: TokenSpecSet { @@ -688,6 +810,128 @@ extension ClosureCaptureSyntax { } } +extension ClosureExprSyntax { + @_spi(Diagnostics) + public enum LeftBraceOptions: TokenSpecSet { + case leftBrace + case leadingBoxCorner + case leadingBoxJunction + + init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { + switch PrepareForKeywordMatch(lexeme) { + case TokenSpec(.leftBrace): + self = .leftBrace + case TokenSpec(.leadingBoxCorner): + self = .leadingBoxCorner + case TokenSpec(.leadingBoxJunction): + self = .leadingBoxJunction + default: + return nil + } + } + + public init?(token: TokenSyntax) { + switch token { + case TokenSpec(.leftBrace): + self = .leftBrace + case TokenSpec(.leadingBoxCorner): + self = .leadingBoxCorner + case TokenSpec(.leadingBoxJunction): + self = .leadingBoxJunction + default: + return nil + } + } + + var spec: TokenSpec { + switch self { + case .leftBrace: + return .leftBrace + case .leadingBoxCorner: + return .leadingBoxCorner + case .leadingBoxJunction: + return .leadingBoxJunction + } + } + + /// Returns a token that satisfies the `TokenSpec` of this case. + /// + /// If the token kind of this spec has variable text, e.g. for an identifier, this returns a token with empty text. + @_spi(Diagnostics) + public var tokenSyntax: TokenSyntax { + switch self { + case .leftBrace: + return .leftBraceToken() + case .leadingBoxCorner: + return .leadingBoxCornerToken() + case .leadingBoxJunction: + return .leadingBoxJunctionToken() + } + } + } +} + +extension ClosureExprSyntax { + @_spi(Diagnostics) + public enum RightBraceOptions: TokenSpecSet { + case rightBrace + case trailingBoxCorner + case trailingBoxJunction + + init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { + switch PrepareForKeywordMatch(lexeme) { + case TokenSpec(.rightBrace): + self = .rightBrace + case TokenSpec(.trailingBoxCorner): + self = .trailingBoxCorner + case TokenSpec(.trailingBoxJunction): + self = .trailingBoxJunction + default: + return nil + } + } + + public init?(token: TokenSyntax) { + switch token { + case TokenSpec(.rightBrace): + self = .rightBrace + case TokenSpec(.trailingBoxCorner): + self = .trailingBoxCorner + case TokenSpec(.trailingBoxJunction): + self = .trailingBoxJunction + default: + return nil + } + } + + var spec: TokenSpec { + switch self { + case .rightBrace: + return .rightBrace + case .trailingBoxCorner: + return .trailingBoxCorner + case .trailingBoxJunction: + return .trailingBoxJunction + } + } + + /// Returns a token that satisfies the `TokenSpec` of this case. + /// + /// If the token kind of this spec has variable text, e.g. for an identifier, this returns a token with empty text. + @_spi(Diagnostics) + public var tokenSyntax: TokenSyntax { + switch self { + case .rightBrace: + return .rightBraceToken() + case .trailingBoxCorner: + return .trailingBoxCornerToken() + case .trailingBoxJunction: + return .trailingBoxJunctionToken() + } + } + } +} + extension ClosureParameterSyntax { @_spi(Diagnostics) public enum FirstNameOptions: TokenSpecSet { @@ -844,6 +1088,128 @@ extension ClosureShorthandParameterSyntax { } } +extension CodeBlockSyntax { + @_spi(Diagnostics) + public enum LeftBraceOptions: TokenSpecSet { + case leftBrace + case leadingBoxCorner + case leadingBoxJunction + + init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { + switch PrepareForKeywordMatch(lexeme) { + case TokenSpec(.leftBrace): + self = .leftBrace + case TokenSpec(.leadingBoxCorner): + self = .leadingBoxCorner + case TokenSpec(.leadingBoxJunction): + self = .leadingBoxJunction + default: + return nil + } + } + + public init?(token: TokenSyntax) { + switch token { + case TokenSpec(.leftBrace): + self = .leftBrace + case TokenSpec(.leadingBoxCorner): + self = .leadingBoxCorner + case TokenSpec(.leadingBoxJunction): + self = .leadingBoxJunction + default: + return nil + } + } + + var spec: TokenSpec { + switch self { + case .leftBrace: + return .leftBrace + case .leadingBoxCorner: + return .leadingBoxCorner + case .leadingBoxJunction: + return .leadingBoxJunction + } + } + + /// Returns a token that satisfies the `TokenSpec` of this case. + /// + /// If the token kind of this spec has variable text, e.g. for an identifier, this returns a token with empty text. + @_spi(Diagnostics) + public var tokenSyntax: TokenSyntax { + switch self { + case .leftBrace: + return .leftBraceToken() + case .leadingBoxCorner: + return .leadingBoxCornerToken() + case .leadingBoxJunction: + return .leadingBoxJunctionToken() + } + } + } +} + +extension CodeBlockSyntax { + @_spi(Diagnostics) + public enum RightBraceOptions: TokenSpecSet { + case rightBrace + case trailingBoxCorner + case trailingBoxJunction + + init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { + switch PrepareForKeywordMatch(lexeme) { + case TokenSpec(.rightBrace): + self = .rightBrace + case TokenSpec(.trailingBoxCorner): + self = .trailingBoxCorner + case TokenSpec(.trailingBoxJunction): + self = .trailingBoxJunction + default: + return nil + } + } + + public init?(token: TokenSyntax) { + switch token { + case TokenSpec(.rightBrace): + self = .rightBrace + case TokenSpec(.trailingBoxCorner): + self = .trailingBoxCorner + case TokenSpec(.trailingBoxJunction): + self = .trailingBoxJunction + default: + return nil + } + } + + var spec: TokenSpec { + switch self { + case .rightBrace: + return .rightBrace + case .trailingBoxCorner: + return .trailingBoxCorner + case .trailingBoxJunction: + return .trailingBoxJunction + } + } + + /// Returns a token that satisfies the `TokenSpec` of this case. + /// + /// If the token kind of this spec has variable text, e.g. for an identifier, this returns a token with empty text. + @_spi(Diagnostics) + public var tokenSyntax: TokenSyntax { + switch self { + case .rightBrace: + return .rightBraceToken() + case .trailingBoxCorner: + return .trailingBoxCornerToken() + case .trailingBoxJunction: + return .trailingBoxJunctionToken() + } + } + } +} + extension ConsumeExprSyntax { @_spi(Diagnostics) public enum ConsumeKeywordOptions: TokenSpecSet { @@ -2792,6 +3158,128 @@ extension LifetimeSpecifierArgumentSyntax { } } +extension MemberBlockSyntax { + @_spi(Diagnostics) + public enum LeftBraceOptions: TokenSpecSet { + case leftBrace + case leadingBoxCorner + case leadingBoxJunction + + init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { + switch PrepareForKeywordMatch(lexeme) { + case TokenSpec(.leftBrace): + self = .leftBrace + case TokenSpec(.leadingBoxCorner): + self = .leadingBoxCorner + case TokenSpec(.leadingBoxJunction): + self = .leadingBoxJunction + default: + return nil + } + } + + public init?(token: TokenSyntax) { + switch token { + case TokenSpec(.leftBrace): + self = .leftBrace + case TokenSpec(.leadingBoxCorner): + self = .leadingBoxCorner + case TokenSpec(.leadingBoxJunction): + self = .leadingBoxJunction + default: + return nil + } + } + + var spec: TokenSpec { + switch self { + case .leftBrace: + return .leftBrace + case .leadingBoxCorner: + return .leadingBoxCorner + case .leadingBoxJunction: + return .leadingBoxJunction + } + } + + /// Returns a token that satisfies the `TokenSpec` of this case. + /// + /// If the token kind of this spec has variable text, e.g. for an identifier, this returns a token with empty text. + @_spi(Diagnostics) + public var tokenSyntax: TokenSyntax { + switch self { + case .leftBrace: + return .leftBraceToken() + case .leadingBoxCorner: + return .leadingBoxCornerToken() + case .leadingBoxJunction: + return .leadingBoxJunctionToken() + } + } + } +} + +extension MemberBlockSyntax { + @_spi(Diagnostics) + public enum RightBraceOptions: TokenSpecSet { + case rightBrace + case trailingBoxCorner + case trailingBoxJunction + + init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { + switch PrepareForKeywordMatch(lexeme) { + case TokenSpec(.rightBrace): + self = .rightBrace + case TokenSpec(.trailingBoxCorner): + self = .trailingBoxCorner + case TokenSpec(.trailingBoxJunction): + self = .trailingBoxJunction + default: + return nil + } + } + + public init?(token: TokenSyntax) { + switch token { + case TokenSpec(.rightBrace): + self = .rightBrace + case TokenSpec(.trailingBoxCorner): + self = .trailingBoxCorner + case TokenSpec(.trailingBoxJunction): + self = .trailingBoxJunction + default: + return nil + } + } + + var spec: TokenSpec { + switch self { + case .rightBrace: + return .rightBrace + case .trailingBoxCorner: + return .trailingBoxCorner + case .trailingBoxJunction: + return .trailingBoxJunction + } + } + + /// Returns a token that satisfies the `TokenSpec` of this case. + /// + /// If the token kind of this spec has variable text, e.g. for an identifier, this returns a token with empty text. + @_spi(Diagnostics) + public var tokenSyntax: TokenSyntax { + switch self { + case .rightBrace: + return .rightBraceToken() + case .trailingBoxCorner: + return .trailingBoxCornerToken() + case .trailingBoxJunction: + return .trailingBoxJunctionToken() + } + } + } +} + extension MemberTypeSyntax { @_spi(Diagnostics) public enum NameOptions: TokenSpecSet { @@ -3277,6 +3765,128 @@ extension PrecedenceGroupAssociativitySyntax { } } +extension PrecedenceGroupDeclSyntax { + @_spi(Diagnostics) + public enum LeftBraceOptions: TokenSpecSet { + case leftBrace + case leadingBoxCorner + case leadingBoxJunction + + init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { + switch PrepareForKeywordMatch(lexeme) { + case TokenSpec(.leftBrace): + self = .leftBrace + case TokenSpec(.leadingBoxCorner): + self = .leadingBoxCorner + case TokenSpec(.leadingBoxJunction): + self = .leadingBoxJunction + default: + return nil + } + } + + public init?(token: TokenSyntax) { + switch token { + case TokenSpec(.leftBrace): + self = .leftBrace + case TokenSpec(.leadingBoxCorner): + self = .leadingBoxCorner + case TokenSpec(.leadingBoxJunction): + self = .leadingBoxJunction + default: + return nil + } + } + + var spec: TokenSpec { + switch self { + case .leftBrace: + return .leftBrace + case .leadingBoxCorner: + return .leadingBoxCorner + case .leadingBoxJunction: + return .leadingBoxJunction + } + } + + /// Returns a token that satisfies the `TokenSpec` of this case. + /// + /// If the token kind of this spec has variable text, e.g. for an identifier, this returns a token with empty text. + @_spi(Diagnostics) + public var tokenSyntax: TokenSyntax { + switch self { + case .leftBrace: + return .leftBraceToken() + case .leadingBoxCorner: + return .leadingBoxCornerToken() + case .leadingBoxJunction: + return .leadingBoxJunctionToken() + } + } + } +} + +extension PrecedenceGroupDeclSyntax { + @_spi(Diagnostics) + public enum RightBraceOptions: TokenSpecSet { + case rightBrace + case trailingBoxCorner + case trailingBoxJunction + + init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { + switch PrepareForKeywordMatch(lexeme) { + case TokenSpec(.rightBrace): + self = .rightBrace + case TokenSpec(.trailingBoxCorner): + self = .trailingBoxCorner + case TokenSpec(.trailingBoxJunction): + self = .trailingBoxJunction + default: + return nil + } + } + + public init?(token: TokenSyntax) { + switch token { + case TokenSpec(.rightBrace): + self = .rightBrace + case TokenSpec(.trailingBoxCorner): + self = .trailingBoxCorner + case TokenSpec(.trailingBoxJunction): + self = .trailingBoxJunction + default: + return nil + } + } + + var spec: TokenSpec { + switch self { + case .rightBrace: + return .rightBrace + case .trailingBoxCorner: + return .trailingBoxCorner + case .trailingBoxJunction: + return .trailingBoxJunction + } + } + + /// Returns a token that satisfies the `TokenSpec` of this case. + /// + /// If the token kind of this spec has variable text, e.g. for an identifier, this returns a token with empty text. + @_spi(Diagnostics) + public var tokenSyntax: TokenSyntax { + switch self { + case .rightBrace: + return .rightBraceToken() + case .trailingBoxCorner: + return .trailingBoxCornerToken() + case .trailingBoxJunction: + return .trailingBoxJunctionToken() + } + } + } +} + extension PrecedenceGroupRelationSyntax { @_spi(Diagnostics) public enum HigherThanOrLowerThanLabelOptions: TokenSpecSet { @@ -3774,6 +4384,128 @@ extension StringLiteralExprSyntax { } } +extension SwitchExprSyntax { + @_spi(Diagnostics) + public enum LeftBraceOptions: TokenSpecSet { + case leftBrace + case leadingBoxCorner + case leadingBoxJunction + + init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { + switch PrepareForKeywordMatch(lexeme) { + case TokenSpec(.leftBrace): + self = .leftBrace + case TokenSpec(.leadingBoxCorner): + self = .leadingBoxCorner + case TokenSpec(.leadingBoxJunction): + self = .leadingBoxJunction + default: + return nil + } + } + + public init?(token: TokenSyntax) { + switch token { + case TokenSpec(.leftBrace): + self = .leftBrace + case TokenSpec(.leadingBoxCorner): + self = .leadingBoxCorner + case TokenSpec(.leadingBoxJunction): + self = .leadingBoxJunction + default: + return nil + } + } + + var spec: TokenSpec { + switch self { + case .leftBrace: + return .leftBrace + case .leadingBoxCorner: + return .leadingBoxCorner + case .leadingBoxJunction: + return .leadingBoxJunction + } + } + + /// Returns a token that satisfies the `TokenSpec` of this case. + /// + /// If the token kind of this spec has variable text, e.g. for an identifier, this returns a token with empty text. + @_spi(Diagnostics) + public var tokenSyntax: TokenSyntax { + switch self { + case .leftBrace: + return .leftBraceToken() + case .leadingBoxCorner: + return .leadingBoxCornerToken() + case .leadingBoxJunction: + return .leadingBoxJunctionToken() + } + } + } +} + +extension SwitchExprSyntax { + @_spi(Diagnostics) + public enum RightBraceOptions: TokenSpecSet { + case rightBrace + case trailingBoxCorner + case trailingBoxJunction + + init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { + switch PrepareForKeywordMatch(lexeme) { + case TokenSpec(.rightBrace): + self = .rightBrace + case TokenSpec(.trailingBoxCorner): + self = .trailingBoxCorner + case TokenSpec(.trailingBoxJunction): + self = .trailingBoxJunction + default: + return nil + } + } + + public init?(token: TokenSyntax) { + switch token { + case TokenSpec(.rightBrace): + self = .rightBrace + case TokenSpec(.trailingBoxCorner): + self = .trailingBoxCorner + case TokenSpec(.trailingBoxJunction): + self = .trailingBoxJunction + default: + return nil + } + } + + var spec: TokenSpec { + switch self { + case .rightBrace: + return .rightBrace + case .trailingBoxCorner: + return .trailingBoxCorner + case .trailingBoxJunction: + return .trailingBoxJunction + } + } + + /// Returns a token that satisfies the `TokenSpec` of this case. + /// + /// If the token kind of this spec has variable text, e.g. for an identifier, this returns a token with empty text. + @_spi(Diagnostics) + public var tokenSyntax: TokenSyntax { + switch self { + case .rightBrace: + return .rightBraceToken() + case .trailingBoxCorner: + return .trailingBoxCornerToken() + case .trailingBoxJunction: + return .trailingBoxJunctionToken() + } + } + } +} + extension ThrowsClauseSyntax { @_spi(Diagnostics) public enum ThrowsSpecifierOptions: TokenSpecSet { diff --git a/Sources/SwiftParser/generated/TokenSpecStaticMembers.swift b/Sources/SwiftParser/generated/TokenSpecStaticMembers.swift index 8a4aed50302..577e890a2fa 100644 --- a/Sources/SwiftParser/generated/TokenSpecStaticMembers.swift +++ b/Sources/SwiftParser/generated/TokenSpecStaticMembers.swift @@ -83,6 +83,14 @@ extension TokenSpec { return TokenSpec(.integerLiteral) } + static var leadingBoxCorner: TokenSpec { + return TokenSpec(.leadingBoxCorner) + } + + static var leadingBoxJunction: TokenSpec { + return TokenSpec(.leadingBoxJunction) + } + static var leftAngle: TokenSpec { return TokenSpec(.leftAngle) } @@ -207,6 +215,14 @@ extension TokenSpec { return TokenSpec(.stringSegment) } + static var trailingBoxCorner: TokenSpec { + return TokenSpec(.trailingBoxCorner) + } + + static var trailingBoxJunction: TokenSpec { + return TokenSpec(.trailingBoxJunction) + } + static var unknown: TokenSpec { return TokenSpec(.unknown) } diff --git a/Sources/SwiftParserDiagnostics/generated/TokenNameForDiagnostics.swift b/Sources/SwiftParserDiagnostics/generated/TokenNameForDiagnostics.swift index bea05d8e9d1..39d435a73ec 100644 --- a/Sources/SwiftParserDiagnostics/generated/TokenNameForDiagnostics.swift +++ b/Sources/SwiftParserDiagnostics/generated/TokenNameForDiagnostics.swift @@ -53,6 +53,10 @@ extension TokenKind { return "?" case .integerLiteral: return "integer literal" + case .leadingBoxCorner: + return "╗" + case .leadingBoxJunction: + return "╣" case .leftAngle: return "<" case .leftBrace: @@ -115,6 +119,10 @@ extension TokenKind { return #"""# case .stringSegment: return "string segment" + case .trailingBoxCorner: + return "╚" + case .trailingBoxJunction: + return "╠" case .unknown: return "token" case .wildcard: diff --git a/Sources/SwiftSyntax/SourceLocation.swift b/Sources/SwiftSyntax/SourceLocation.swift index dcca9d2a74f..bbb3c65d4d8 100644 --- a/Sources/SwiftSyntax/SourceLocation.swift +++ b/Sources/SwiftSyntax/SourceLocation.swift @@ -767,6 +767,8 @@ fileprivate extension RawTriviaPiece { position = position.advanced(by: 2) body(position) } + case let .boxDrawing(text): + position = position.advanced(by: text.count) case let .lineComment(text), let .docLineComment(text): // Line comments are not supposed to contain newlines. diff --git a/Sources/SwiftSyntax/Trivia.swift b/Sources/SwiftSyntax/Trivia.swift index f45f8a781f9..097ceb2f984 100644 --- a/Sources/SwiftSyntax/Trivia.swift +++ b/Sources/SwiftSyntax/Trivia.swift @@ -192,6 +192,8 @@ extension Trivia { return Array(repeating: TriviaPiece.carriageReturns(1), count: count) case .carriageReturnLineFeeds(let count): return Array(repeating: TriviaPiece.carriageReturnLineFeeds(1), count: count) + case .boxDrawing(let text): + return text.map { TriviaPiece.boxDrawing(String($0)) } case .lineComment, .blockComment, .docLineComment, .docBlockComment, .unexpectedText: return [piece] } diff --git a/Sources/SwiftSyntax/generated/SyntaxTraits.swift b/Sources/SwiftSyntax/generated/SyntaxTraits.swift index 68f8ad515c9..4c805ae5070 100644 --- a/Sources/SwiftSyntax/generated/SyntaxTraits.swift +++ b/Sources/SwiftSyntax/generated/SyntaxTraits.swift @@ -17,7 +17,10 @@ public protocol BracedSyntax: SyntaxProtocol { /// ### Tokens /// - /// For syntax trees generated by the parser, this is guaranteed to be `{`. + /// For syntax trees generated by the parser, this is guaranteed to be one of the following kinds: + /// - `{` + /// - `╗` + /// - `╣` var leftBrace: TokenSyntax { get set @@ -25,7 +28,10 @@ public protocol BracedSyntax: SyntaxProtocol { /// ### Tokens /// - /// For syntax trees generated by the parser, this is guaranteed to be `}`. + /// For syntax trees generated by the parser, this is guaranteed to be one of the following kinds: + /// - `}` + /// - `╚` + /// - `╠` var rightBrace: TokenSyntax { get set diff --git a/Sources/SwiftSyntax/generated/TokenKind.swift b/Sources/SwiftSyntax/generated/TokenKind.swift index 2d42fa828f8..278d8f618a1 100644 --- a/Sources/SwiftSyntax/generated/TokenKind.swift +++ b/Sources/SwiftSyntax/generated/TokenKind.swift @@ -31,6 +31,8 @@ public enum TokenKind: Hashable, Sendable { case infixQuestionMark case integerLiteral(String) case keyword(Keyword) + case leadingBoxCorner + case leadingBoxJunction case leftAngle case leftBrace case leftParen @@ -62,6 +64,8 @@ public enum TokenKind: Hashable, Sendable { case singleQuote case stringQuote case stringSegment(String) + case trailingBoxCorner + case trailingBoxJunction case unknown(String) case wildcard @@ -103,6 +107,10 @@ public enum TokenKind: Hashable, Sendable { return text case .keyword(let assoc): return String(syntaxText: assoc.defaultText) + case .leadingBoxCorner: + return "╗" + case .leadingBoxJunction: + return "╣" case .leftAngle: return "<" case .leftBrace: @@ -165,6 +173,10 @@ public enum TokenKind: Hashable, Sendable { return #"""# case .stringSegment(let text): return text + case .trailingBoxCorner: + return "╚" + case .trailingBoxJunction: + return "╠" case .unknown(let text): return text case .wildcard: @@ -200,6 +212,10 @@ public enum TokenKind: Hashable, Sendable { return "?" case .keyword(let assoc): return assoc.defaultText + case .leadingBoxCorner: + return "╗" + case .leadingBoxJunction: + return "╣" case .leftAngle: return "<" case .leftBrace: @@ -248,6 +264,10 @@ public enum TokenKind: Hashable, Sendable { return "'" case .stringQuote: return #"""# + case .trailingBoxCorner: + return "╚" + case .trailingBoxJunction: + return "╠" case .wildcard: return "_" default: @@ -296,6 +316,10 @@ public enum TokenKind: Hashable, Sendable { return false case .keyword: return false + case .leadingBoxCorner: + return true + case .leadingBoxJunction: + return true case .leftAngle: return true case .leftBrace: @@ -358,6 +382,10 @@ public enum TokenKind: Hashable, Sendable { return true case .stringSegment: return false + case .trailingBoxCorner: + return true + case .trailingBoxJunction: + return true case .unknown: return false case .wildcard: @@ -403,6 +431,10 @@ extension TokenKind: Equatable { return lhsText == rhsText case (.keyword(let lhsText), .keyword(let rhsText)): return lhsText == rhsText + case (.leadingBoxCorner, .leadingBoxCorner): + return true + case (.leadingBoxJunction, .leadingBoxJunction): + return true case (.leftAngle, .leftAngle): return true case (.leftBrace, .leftBrace): @@ -465,6 +497,10 @@ extension TokenKind: Equatable { return true case (.stringSegment(let lhsText), .stringSegment(let rhsText)): return lhsText == rhsText + case (.trailingBoxCorner, .trailingBoxCorner): + return true + case (.trailingBoxJunction, .trailingBoxJunction): + return true case (.unknown(let lhsText), .unknown(let rhsText)): return lhsText == rhsText case (.wildcard, .wildcard): @@ -499,6 +535,8 @@ public enum RawTokenKind: UInt8, Equatable, Hashable { case infixQuestionMark case integerLiteral case keyword + case leadingBoxCorner + case leadingBoxJunction case leftAngle case leftBrace case leftParen @@ -530,6 +568,8 @@ public enum RawTokenKind: UInt8, Equatable, Hashable { case singleQuote case stringQuote case stringSegment + case trailingBoxCorner + case trailingBoxJunction case unknown case wildcard @@ -558,6 +598,10 @@ public enum RawTokenKind: UInt8, Equatable, Hashable { return "!" case .infixQuestionMark: return "?" + case .leadingBoxCorner: + return "╗" + case .leadingBoxJunction: + return "╣" case .leftAngle: return "<" case .leftBrace: @@ -606,6 +650,10 @@ public enum RawTokenKind: UInt8, Equatable, Hashable { return "'" case .stringQuote: return #"""# + case .trailingBoxCorner: + return "╚" + case .trailingBoxJunction: + return "╠" case .wildcard: return "_" default: @@ -654,6 +702,10 @@ public enum RawTokenKind: UInt8, Equatable, Hashable { return false case .keyword: return false + case .leadingBoxCorner: + return true + case .leadingBoxJunction: + return true case .leftAngle: return true case .leftBrace: @@ -716,6 +768,10 @@ public enum RawTokenKind: UInt8, Equatable, Hashable { return true case .stringSegment: return false + case .trailingBoxCorner: + return true + case .trailingBoxJunction: + return true case .unknown: return false case .wildcard: @@ -777,6 +833,12 @@ extension TokenKind { return text.withSyntaxText { text in return .keyword(Keyword(text)!) } + case .leadingBoxCorner: + precondition(text.isEmpty || rawKind.defaultText.map(String.init) == text) + return .leadingBoxCorner + case .leadingBoxJunction: + precondition(text.isEmpty || rawKind.defaultText.map(String.init) == text) + return .leadingBoxJunction case .leftAngle: precondition(text.isEmpty || rawKind.defaultText.map(String.init) == text) return .leftAngle @@ -863,6 +925,12 @@ extension TokenKind { return .stringQuote case .stringSegment: return .stringSegment(text) + case .trailingBoxCorner: + precondition(text.isEmpty || rawKind.defaultText.map(String.init) == text) + return .trailingBoxCorner + case .trailingBoxJunction: + precondition(text.isEmpty || rawKind.defaultText.map(String.init) == text) + return .trailingBoxJunction case .unknown: return .unknown(text) case .wildcard: @@ -910,6 +978,10 @@ extension TokenKind { return (.integerLiteral, str) case .keyword(let keyword): return (.keyword, String(syntaxText: keyword.defaultText)) + case .leadingBoxCorner: + return (.leadingBoxCorner, nil) + case .leadingBoxJunction: + return (.leadingBoxJunction, nil) case .leftAngle: return (.leftAngle, nil) case .leftBrace: @@ -972,6 +1044,10 @@ extension TokenKind { return (.stringQuote, nil) case .stringSegment(let str): return (.stringSegment, str) + case .trailingBoxCorner: + return (.trailingBoxCorner, nil) + case .trailingBoxJunction: + return (.trailingBoxJunction, nil) case .unknown(let str): return (.unknown, str) case .wildcard: diff --git a/Sources/SwiftSyntax/generated/Tokens.swift b/Sources/SwiftSyntax/generated/Tokens.swift index a71c447d1f8..41ae95bc063 100644 --- a/Sources/SwiftSyntax/generated/Tokens.swift +++ b/Sources/SwiftSyntax/generated/Tokens.swift @@ -240,6 +240,32 @@ extension TokenSyntax { ) } + public static func leadingBoxCornerToken( + leadingTrivia: Trivia = [], + trailingTrivia: Trivia = [], + presence: SourcePresence = .present + ) -> TokenSyntax { + return TokenSyntax( + .leadingBoxCorner, + leadingTrivia: leadingTrivia, + trailingTrivia: trailingTrivia, + presence: presence + ) + } + + public static func leadingBoxJunctionToken( + leadingTrivia: Trivia = [], + trailingTrivia: Trivia = [], + presence: SourcePresence = .present + ) -> TokenSyntax { + return TokenSyntax( + .leadingBoxJunction, + leadingTrivia: leadingTrivia, + trailingTrivia: trailingTrivia, + presence: presence + ) + } + public static func leftAngleToken( leadingTrivia: Trivia = [], trailingTrivia: Trivia = [], @@ -650,6 +676,32 @@ extension TokenSyntax { ) } + public static func trailingBoxCornerToken( + leadingTrivia: Trivia = [], + trailingTrivia: Trivia = [], + presence: SourcePresence = .present + ) -> TokenSyntax { + return TokenSyntax( + .trailingBoxCorner, + leadingTrivia: leadingTrivia, + trailingTrivia: trailingTrivia, + presence: presence + ) + } + + public static func trailingBoxJunctionToken( + leadingTrivia: Trivia = [], + trailingTrivia: Trivia = [], + presence: SourcePresence = .present + ) -> TokenSyntax { + return TokenSyntax( + .trailingBoxJunction, + leadingTrivia: leadingTrivia, + trailingTrivia: trailingTrivia, + presence: presence + ) + } + public static func unknown( _ text: String, leadingTrivia: Trivia = [], diff --git a/Sources/SwiftSyntax/generated/TriviaPieces.swift b/Sources/SwiftSyntax/generated/TriviaPieces.swift index 201f96ea436..7db0cef8659 100644 --- a/Sources/SwiftSyntax/generated/TriviaPieces.swift +++ b/Sources/SwiftSyntax/generated/TriviaPieces.swift @@ -49,6 +49,8 @@ public enum TriviaPiece: Sendable { case unexpectedText(String) /// A vertical tab '\v' character. case verticalTabs(Int) + /// Box-drawing characters which have no syntactic significance. + case boxDrawing(String) } extension TriviaPiece: TextOutputStreamable { @@ -90,6 +92,8 @@ extension TriviaPiece: TextOutputStreamable { stream.write(text) case let .verticalTabs(count): printRepeated("\u{b}", count: count) + case let .boxDrawing(text): + stream.write(text) } } } @@ -126,6 +130,8 @@ extension TriviaPiece: CustomDebugStringConvertible { return "unexpectedText(\(name.debugDescription))" case .verticalTabs(let data): return "verticalTabs(\(data))" + case .boxDrawing(let name): + return "boxDrawing(\(name.debugDescription))" } } } @@ -245,6 +251,11 @@ extension Trivia { public static var verticalTab: Trivia { return .verticalTabs(1) } + + /// Returns a piece of trivia for BoxDrawing. + public static func boxDrawing(_ text: String) -> Trivia { + return [.boxDrawing(text)] + } } extension TriviaPiece: Equatable {} @@ -280,6 +291,8 @@ extension TriviaPiece { return SourceLength(of: text) case let .verticalTabs(count): return SourceLength(utf8Length: count) + case let .boxDrawing(text): + return SourceLength(of: text) } } } @@ -304,6 +317,7 @@ public enum RawTriviaPiece: Equatable, Sendable { case tabs(Int) case unexpectedText(SyntaxText) case verticalTabs(Int) + case boxDrawing(SyntaxText) static func make(_ piece: TriviaPiece, arena: SyntaxArena) -> RawTriviaPiece { switch piece { @@ -335,6 +349,8 @@ public enum RawTriviaPiece: Equatable, Sendable { return .unexpectedText(arena.intern(text)) case let .verticalTabs(count): return .verticalTabs(count) + case let .boxDrawing(text): + return .boxDrawing(arena.intern(text)) } } } @@ -370,6 +386,8 @@ extension TriviaPiece { self = .unexpectedText(String(syntaxText: text)) case let .verticalTabs(count): self = .verticalTabs(count) + case let .boxDrawing(text): + self = .boxDrawing(String(syntaxText: text)) } } } @@ -405,6 +423,8 @@ extension RawTriviaPiece { return text.count case let .verticalTabs(count): return count + case let .boxDrawing(text): + return text.count } } @@ -438,6 +458,8 @@ extension RawTriviaPiece { return text case .verticalTabs(_): return nil + case .boxDrawing(let text): + return text } } } diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift index 3a04edbea02..8b579b24fde 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift @@ -216,12 +216,12 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { func validateAccessorBlockSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { assert(layout.count == 7) assertNoError(kind, 0, verify(layout[0], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 1, verify(layout[1], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.leftBrace)])) + assertNoError(kind, 1, verify(layout[1], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.leftBrace), .tokenKind(.leadingBoxCorner), .tokenKind(.leadingBoxJunction)])) assertNoError(kind, 2, verify(layout[2], as: RawUnexpectedNodesSyntax?.self)) assertAnyHasNoError(kind, 3, [ verify(layout[3], as: RawSyntax.self)]) assertNoError(kind, 4, verify(layout[4], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 5, verify(layout[5], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.rightBrace)])) + assertNoError(kind, 5, verify(layout[5], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.rightBrace), .tokenKind(.trailingBoxCorner), .tokenKind(.trailingBoxJunction)])) assertNoError(kind, 6, verify(layout[6], as: RawUnexpectedNodesSyntax?.self)) } func validateAccessorDeclListSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { @@ -621,13 +621,13 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { func validateClosureExprSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { assert(layout.count == 9) assertNoError(kind, 0, verify(layout[0], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 1, verify(layout[1], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.leftBrace)])) + assertNoError(kind, 1, verify(layout[1], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.leftBrace), .tokenKind(.leadingBoxCorner), .tokenKind(.leadingBoxJunction)])) assertNoError(kind, 2, verify(layout[2], as: RawUnexpectedNodesSyntax?.self)) assertNoError(kind, 3, verify(layout[3], as: RawClosureSignatureSyntax?.self)) assertNoError(kind, 4, verify(layout[4], as: RawUnexpectedNodesSyntax?.self)) assertNoError(kind, 5, verify(layout[5], as: RawCodeBlockItemListSyntax.self)) assertNoError(kind, 6, verify(layout[6], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 7, verify(layout[7], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.rightBrace)])) + assertNoError(kind, 7, verify(layout[7], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.rightBrace), .tokenKind(.trailingBoxCorner), .tokenKind(.trailingBoxJunction)])) assertNoError(kind, 8, verify(layout[8], as: RawUnexpectedNodesSyntax?.self)) } func validateClosureParameterClauseSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { @@ -712,11 +712,11 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { func validateCodeBlockSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { assert(layout.count == 7) assertNoError(kind, 0, verify(layout[0], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 1, verify(layout[1], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.leftBrace)])) + assertNoError(kind, 1, verify(layout[1], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.leftBrace), .tokenKind(.leadingBoxCorner), .tokenKind(.leadingBoxJunction)])) assertNoError(kind, 2, verify(layout[2], as: RawUnexpectedNodesSyntax?.self)) assertNoError(kind, 3, verify(layout[3], as: RawCodeBlockItemListSyntax.self)) assertNoError(kind, 4, verify(layout[4], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 5, verify(layout[5], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.rightBrace)])) + assertNoError(kind, 5, verify(layout[5], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.rightBrace), .tokenKind(.trailingBoxCorner), .tokenKind(.trailingBoxJunction)])) assertNoError(kind, 6, verify(layout[6], as: RawUnexpectedNodesSyntax?.self)) } func validateCompositionTypeElementListSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { @@ -2007,11 +2007,11 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { func validateMemberBlockSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { assert(layout.count == 7) assertNoError(kind, 0, verify(layout[0], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 1, verify(layout[1], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.leftBrace)])) + assertNoError(kind, 1, verify(layout[1], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.leftBrace), .tokenKind(.leadingBoxCorner), .tokenKind(.leadingBoxJunction)])) assertNoError(kind, 2, verify(layout[2], as: RawUnexpectedNodesSyntax?.self)) assertNoError(kind, 3, verify(layout[3], as: RawMemberBlockItemListSyntax.self)) assertNoError(kind, 4, verify(layout[4], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 5, verify(layout[5], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.rightBrace)])) + assertNoError(kind, 5, verify(layout[5], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.rightBrace), .tokenKind(.trailingBoxCorner), .tokenKind(.trailingBoxJunction)])) assertNoError(kind, 6, verify(layout[6], as: RawUnexpectedNodesSyntax?.self)) } func validateMemberTypeSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { @@ -2362,11 +2362,11 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { assertNoError(kind, 6, verify(layout[6], as: RawUnexpectedNodesSyntax?.self)) assertNoError(kind, 7, verify(layout[7], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.identifier)])) assertNoError(kind, 8, verify(layout[8], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 9, verify(layout[9], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.leftBrace)])) + assertNoError(kind, 9, verify(layout[9], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.leftBrace), .tokenKind(.leadingBoxCorner), .tokenKind(.leadingBoxJunction)])) assertNoError(kind, 10, verify(layout[10], as: RawUnexpectedNodesSyntax?.self)) assertNoError(kind, 11, verify(layout[11], as: RawPrecedenceGroupAttributeListSyntax.self)) assertNoError(kind, 12, verify(layout[12], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 13, verify(layout[13], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.rightBrace)])) + assertNoError(kind, 13, verify(layout[13], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.rightBrace), .tokenKind(.trailingBoxCorner), .tokenKind(.trailingBoxJunction)])) assertNoError(kind, 14, verify(layout[14], as: RawUnexpectedNodesSyntax?.self)) } func validatePrecedenceGroupNameListSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { @@ -2740,11 +2740,11 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { assertNoError(kind, 2, verify(layout[2], as: RawUnexpectedNodesSyntax?.self)) assertNoError(kind, 3, verify(layout[3], as: RawExprSyntax.self)) assertNoError(kind, 4, verify(layout[4], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 5, verify(layout[5], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.leftBrace)])) + assertNoError(kind, 5, verify(layout[5], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.leftBrace), .tokenKind(.leadingBoxCorner), .tokenKind(.leadingBoxJunction)])) assertNoError(kind, 6, verify(layout[6], as: RawUnexpectedNodesSyntax?.self)) assertNoError(kind, 7, verify(layout[7], as: RawSwitchCaseListSyntax.self)) assertNoError(kind, 8, verify(layout[8], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 9, verify(layout[9], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.rightBrace)])) + assertNoError(kind, 9, verify(layout[9], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.rightBrace), .tokenKind(.trailingBoxCorner), .tokenKind(.trailingBoxJunction)])) assertNoError(kind, 10, verify(layout[10], as: RawUnexpectedNodesSyntax?.self)) } func validateTernaryExprSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesAB.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesAB.swift index 70af586989d..565e055a97f 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesAB.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesAB.swift @@ -404,9 +404,9 @@ public struct ABIAttributeArgumentsSyntax: SyntaxProtocol, SyntaxHashable, _Leaf /// ### Children /// -/// - `leftBrace`: `{` +/// - `leftBrace`: (`{` | `╗` | `╣`) /// - `accessors`: (``AccessorDeclListSyntax`` | ``CodeBlockItemListSyntax``) -/// - `rightBrace`: `}` +/// - `rightBrace`: (`}` | `╚` | `╠`) /// /// ### Contained in /// @@ -562,7 +562,10 @@ public struct AccessorBlockSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNo /// /// ### Tokens /// - /// For syntax trees generated by the parser, this is guaranteed to be `{`. + /// For syntax trees generated by the parser, this is guaranteed to be one of the following kinds: + /// - `{` + /// - `╗` + /// - `╣` public var leftBrace: TokenSyntax { get { return Syntax(self).child(at: 1)!.cast(TokenSyntax.self) @@ -603,7 +606,10 @@ public struct AccessorBlockSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNo /// /// ### Tokens /// - /// For syntax trees generated by the parser, this is guaranteed to be `}`. + /// For syntax trees generated by the parser, this is guaranteed to be one of the following kinds: + /// - `}` + /// - `╚` + /// - `╠` public var rightBrace: TokenSyntax { get { return Syntax(self).child(at: 5)!.cast(TokenSyntax.self) diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesC.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesC.swift index a80eedea2ac..867af6901ee 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesC.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesC.swift @@ -1686,10 +1686,10 @@ public struct ClosureCaptureSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxN /// ### Children /// -/// - `leftBrace`: `{` +/// - `leftBrace`: (`{` | `╗` | `╣`) /// - `signature`: ``ClosureSignatureSyntax``? /// - `statements`: ``CodeBlockItemListSyntax`` -/// - `rightBrace`: `}` +/// - `rightBrace`: (`}` | `╚` | `╠`) /// /// ### Contained in /// @@ -1770,7 +1770,10 @@ public struct ClosureExprSyntax: ExprSyntaxProtocol, SyntaxHashable, _LeafExprSy /// ### Tokens /// - /// For syntax trees generated by the parser, this is guaranteed to be `{`. + /// For syntax trees generated by the parser, this is guaranteed to be one of the following kinds: + /// - `{` + /// - `╗` + /// - `╣` public var leftBrace: TokenSyntax { get { return Syntax(self).child(at: 1)!.cast(TokenSyntax.self) @@ -1854,7 +1857,10 @@ public struct ClosureExprSyntax: ExprSyntaxProtocol, SyntaxHashable, _LeafExprSy /// ### Tokens /// - /// For syntax trees generated by the parser, this is guaranteed to be `}`. + /// For syntax trees generated by the parser, this is guaranteed to be one of the following kinds: + /// - `}` + /// - `╚` + /// - `╠` public var rightBrace: TokenSyntax { get { return Syntax(self).child(at: 7)!.cast(TokenSyntax.self) @@ -3122,9 +3128,9 @@ public struct CodeBlockItemSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNo /// ### Children /// -/// - `leftBrace`: `{` +/// - `leftBrace`: (`{` | `╗` | `╣`) /// - `statements`: ``CodeBlockItemListSyntax`` -/// - `rightBrace`: `}` +/// - `rightBrace`: (`}` | `╚` | `╠`) /// /// ### Contained in /// @@ -3211,7 +3217,10 @@ public struct CodeBlockSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNodePr /// /// ### Tokens /// - /// For syntax trees generated by the parser, this is guaranteed to be `{`. + /// For syntax trees generated by the parser, this is guaranteed to be one of the following kinds: + /// - `{` + /// - `╗` + /// - `╣` public var leftBrace: TokenSyntax { get { return Syntax(self).child(at: 1)!.cast(TokenSyntax.self) @@ -3279,7 +3288,10 @@ public struct CodeBlockSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNodePr /// /// ### Tokens /// - /// For syntax trees generated by the parser, this is guaranteed to be `}`. + /// For syntax trees generated by the parser, this is guaranteed to be one of the following kinds: + /// - `}` + /// - `╚` + /// - `╠` public var rightBrace: TokenSyntax { get { return Syntax(self).child(at: 5)!.cast(TokenSyntax.self) diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesJKLMN.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesJKLMN.swift index 9f48aab9adf..1e549d933f9 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesJKLMN.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesJKLMN.swift @@ -3561,9 +3561,9 @@ public struct MemberBlockItemSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntax /// ### Children /// -/// - `leftBrace`: `{` +/// - `leftBrace`: (`{` | `╗` | `╣`) /// - `members`: ``MemberBlockItemListSyntax`` -/// - `rightBrace`: `}` +/// - `rightBrace`: (`}` | `╚` | `╠`) /// /// ### Contained in /// @@ -3639,7 +3639,10 @@ public struct MemberBlockSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNode /// ### Tokens /// - /// For syntax trees generated by the parser, this is guaranteed to be `{`. + /// For syntax trees generated by the parser, this is guaranteed to be one of the following kinds: + /// - `{` + /// - `╗` + /// - `╣` public var leftBrace: TokenSyntax { get { return Syntax(self).child(at: 1)!.cast(TokenSyntax.self) @@ -3705,7 +3708,10 @@ public struct MemberBlockSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNode /// ### Tokens /// - /// For syntax trees generated by the parser, this is guaranteed to be `}`. + /// For syntax trees generated by the parser, this is guaranteed to be one of the following kinds: + /// - `}` + /// - `╚` + /// - `╠` public var rightBrace: TokenSyntax { get { return Syntax(self).child(at: 5)!.cast(TokenSyntax.self) diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesOP.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesOP.swift index f137e4e5989..fe296af565c 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesOP.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesOP.swift @@ -3259,9 +3259,9 @@ public struct PrecedenceGroupAssociativitySyntax: SyntaxProtocol, SyntaxHashable /// - `modifiers`: ``DeclModifierListSyntax`` /// - `precedencegroupKeyword`: `precedencegroup` /// - `name`: `` -/// - `leftBrace`: `{` +/// - `leftBrace`: (`{` | `╗` | `╣`) /// - `groupAttributes`: ``PrecedenceGroupAttributeListSyntax`` -/// - `rightBrace`: `}` +/// - `rightBrace`: (`}` | `╚` | `╠`) public struct PrecedenceGroupDeclSyntax: DeclSyntaxProtocol, SyntaxHashable, _LeafDeclSyntaxNodeProtocol { public let _syntaxNode: Syntax @@ -3492,7 +3492,10 @@ public struct PrecedenceGroupDeclSyntax: DeclSyntaxProtocol, SyntaxHashable, _Le /// ### Tokens /// - /// For syntax trees generated by the parser, this is guaranteed to be `{`. + /// For syntax trees generated by the parser, this is guaranteed to be one of the following kinds: + /// - `{` + /// - `╗` + /// - `╣` public var leftBrace: TokenSyntax { get { return Syntax(self).child(at: 9)!.cast(TokenSyntax.self) @@ -3559,7 +3562,10 @@ public struct PrecedenceGroupDeclSyntax: DeclSyntaxProtocol, SyntaxHashable, _Le /// ### Tokens /// - /// For syntax trees generated by the parser, this is guaranteed to be `}`. + /// For syntax trees generated by the parser, this is guaranteed to be one of the following kinds: + /// - `}` + /// - `╚` + /// - `╠` public var rightBrace: TokenSyntax { get { return Syntax(self).child(at: 13)!.cast(TokenSyntax.self) diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesQRS.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesQRS.swift index add1bca1a9f..b47b4765eda 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesQRS.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesQRS.swift @@ -4261,9 +4261,9 @@ public struct SwitchDefaultLabelSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyn /// /// - `switchKeyword`: `switch` /// - `subject`: ``ExprSyntax`` -/// - `leftBrace`: `{` +/// - `leftBrace`: (`{` | `╗` | `╣`) /// - `cases`: ``SwitchCaseListSyntax`` -/// - `rightBrace`: `}` +/// - `rightBrace`: (`}` | `╚` | `╠`) public struct SwitchExprSyntax: ExprSyntaxProtocol, SyntaxHashable, _LeafExprSyntaxNodeProtocol { public let _syntaxNode: Syntax @@ -4391,7 +4391,10 @@ public struct SwitchExprSyntax: ExprSyntaxProtocol, SyntaxHashable, _LeafExprSyn /// /// ### Tokens /// - /// For syntax trees generated by the parser, this is guaranteed to be `{`. + /// For syntax trees generated by the parser, this is guaranteed to be one of the following kinds: + /// - `{` + /// - `╗` + /// - `╣` public var leftBrace: TokenSyntax { get { return Syntax(self).child(at: 5)!.cast(TokenSyntax.self) @@ -4460,7 +4463,10 @@ public struct SwitchExprSyntax: ExprSyntaxProtocol, SyntaxHashable, _LeafExprSyn /// /// ### Tokens /// - /// For syntax trees generated by the parser, this is guaranteed to be `}`. + /// For syntax trees generated by the parser, this is guaranteed to be one of the following kinds: + /// - `}` + /// - `╚` + /// - `╠` public var rightBrace: TokenSyntax { get { return Syntax(self).child(at: 9)!.cast(TokenSyntax.self) diff --git a/Tests/SwiftParserTest/Assertions.swift b/Tests/SwiftParserTest/Assertions.swift index 0d637934486..609b23f3101 100644 --- a/Tests/SwiftParserTest/Assertions.swift +++ b/Tests/SwiftParserTest/Assertions.swift @@ -100,8 +100,8 @@ private func assertTokens( if actualLexeme.leadingTriviaText != expectedLexeme.leadingTrivia { assertStringsEqualWithDiff( - String(syntaxText: actualLexeme.leadingTriviaText), - String(syntaxText: expectedLexeme.leadingTrivia), + String(reflecting: String(syntaxText: actualLexeme.leadingTriviaText)), + String(reflecting: String(syntaxText: expectedLexeme.leadingTrivia)), "Leading trivia does not match", file: expectedLexeme.file, line: expectedLexeme.line @@ -120,8 +120,8 @@ private func assertTokens( if actualLexeme.trailingTriviaText != expectedLexeme.trailingTrivia { assertStringsEqualWithDiff( - String(syntaxText: actualLexeme.trailingTriviaText), - String(syntaxText: expectedLexeme.trailingTrivia), + String(reflecting: String(syntaxText: actualLexeme.trailingTriviaText)), + String(reflecting: String(syntaxText: expectedLexeme.trailingTrivia)), "Trailing trivia does not match", file: expectedLexeme.file, line: expectedLexeme.line diff --git a/Tests/SwiftParserTest/DeclarationTests.swift b/Tests/SwiftParserTest/DeclarationTests.swift index 50a651948b2..6440f8cdb84 100644 --- a/Tests/SwiftParserTest/DeclarationTests.swift +++ b/Tests/SwiftParserTest/DeclarationTests.swift @@ -168,6 +168,164 @@ final class DeclarationTests: ParserTestCase { ) } + func testFuncBoxDrawing() { + func callIsMultiple(of value: Int) -> FunctionCallExprSyntax { + FunctionCallExprSyntax( + calledExpression: MemberAccessExprSyntax( + base: DeclReferenceExprSyntax(baseName: .identifier("i")), + name: .identifier("isMultiple") + ), + leftParen: .leftParenToken(), + arguments: LabeledExprListSyntax { + LabeledExprSyntax( + label: "of", + expression: IntegerLiteralExprSyntax(value) + ) + }, + rightParen: .rightParenToken() + ) + } + + func callPrint(_ literal: StringLiteralExprSyntax) -> FunctionCallExprSyntax { + FunctionCallExprSyntax( + calledExpression: DeclReferenceExprSyntax(baseName: .identifier("print")), + leftParen: .leftParenToken(), + arguments: LabeledExprListSyntax { + LabeledExprSyntax(expression: literal) + }, + rightParen: .rightParenToken() + ) + } + + assertParse( + #""" + ╔═════════════ func fizzBuzz() ═════════════╗ + ║ ╔═══════════ for i in 0..<100 ══════════╗ ║ + ║ ║ ╔═════ if i.isMultiple(of: 15) ═════╗ ║ ║ + ║ ║ ║ print("fizzbuzz ") ║ ║ ║ + ║ ║ ╠═══ else if i.isMultiple(of: 3) ═══╣ ║ ║ + ║ ║ ║ print("fizz ") ║ ║ ║ + ║ ║ ╠═══ else if i.isMultiple(of: 5) ═══╣ ║ ║ + ║ ║ ║ print("buzz ") ║ ║ ║ + ║ ║ ╠══════════════ else ═══════════════╣ ║ ║ + ║ ║ ║ print("\(i) ") ║ ║ ║ + ║ ║ ╚═══════════════════════════════════╝ ║ ║ + ║ ╚═══════════════════════════════════════╝ ║ + ╚═══════════════════════════════════════════╝ + """#, + substructure: Syntax( + FunctionDeclSyntax( + attributes: [], + modifiers: [], + funcKeyword: .keyword(.func), + name: .identifier("fizzBuzz"), + signature: FunctionSignatureSyntax( + parameterClause: FunctionParameterClauseSyntax( + leftParen: .leftParenToken(), + parameters: [], + rightParen: .rightParenToken() + ) + ), + body: CodeBlockSyntax( + leftBrace: .leadingBoxCornerToken(), + statements: CodeBlockItemListSyntax { + ForStmtSyntax( + forKeyword: .keyword(.for), + pattern: IdentifierPatternSyntax( + identifier: .identifier("i") + ), + inKeyword: .keyword(.in), + sequence: SequenceExprSyntax { + IntegerLiteralExprSyntax(0) + BinaryOperatorExprSyntax(text: "..<") + IntegerLiteralExprSyntax(100) + }, + body: CodeBlockSyntax( + leftBrace: .leadingBoxCornerToken(), + statements: CodeBlockItemListSyntax { + ExpressionStmtSyntax( + expression: IfExprSyntax( + ifKeyword: .keyword(.if), + conditions: ConditionElementListSyntax { + callIsMultiple(of: 15) + }, + body: CodeBlockSyntax( + leftBrace: .leadingBoxCornerToken(), + statements: CodeBlockItemListSyntax { + callPrint(StringLiteralExprSyntax(content: "fizzbuzz ")) + }, + rightBrace: .trailingBoxJunctionToken() + ), + elseKeyword: .keyword(.else), + elseBody: IfExprSyntax.ElseBody.ifExpr( + IfExprSyntax( + ifKeyword: .keyword(.if), + conditions: ConditionElementListSyntax { + callIsMultiple(of: 3) + }, + body: CodeBlockSyntax( + leftBrace: .leadingBoxJunctionToken(), + statements: CodeBlockItemListSyntax { + callPrint(StringLiteralExprSyntax(content: "fizz ")) + }, + rightBrace: .trailingBoxJunctionToken() + ), + elseKeyword: .keyword(.else), + elseBody: IfExprSyntax.ElseBody.ifExpr( + IfExprSyntax( + ifKeyword: .keyword(.if), + conditions: ConditionElementListSyntax { + callIsMultiple(of: 5) + }, + body: CodeBlockSyntax( + leftBrace: .leadingBoxJunctionToken(), + statements: CodeBlockItemListSyntax { + callPrint(StringLiteralExprSyntax(content: "buzz ")) + }, + rightBrace: .trailingBoxJunctionToken() + ), + elseKeyword: .keyword(.else), + elseBody: .codeBlock( + CodeBlockSyntax( + leftBrace: .leadingBoxJunctionToken(), + statements: CodeBlockItemListSyntax { + callPrint( + StringLiteralExprSyntax( + openingQuote: .stringQuoteToken(), + segments: StringLiteralSegmentListSyntax { + StringSegmentSyntax(content: .stringSegment("")) + ExpressionSegmentSyntax( + expressions: LabeledExprListSyntax { + LabeledExprSyntax(expression: DeclReferenceExprSyntax(baseName: .identifier("i"))) + } + ) + StringSegmentSyntax(content: .stringSegment(" ")) + }, + closingQuote: .stringQuoteToken() + ) + ) + }, + rightBrace: .trailingBoxCornerToken() + ) + ) + ) + ) + ) + ) + ) + ) + }, + rightBrace: .trailingBoxCornerToken() + ) + ) + }, + rightBrace: .trailingBoxCornerToken() + ) + ) + ) + ) + } + func testClassParsing() { assertParse("class Foo {}") diff --git a/Tests/SwiftParserTest/LexerTests.swift b/Tests/SwiftParserTest/LexerTests.swift index dd9e61bcf12..6f13710e64b 100644 --- a/Tests/SwiftParserTest/LexerTests.swift +++ b/Tests/SwiftParserTest/LexerTests.swift @@ -1721,4 +1721,26 @@ class LexerTests: ParserTestCase { ] ) } + + func testBoxDrawing() { + assertLexemes( + """ + ╔═════ hello ═════╗ + ║ world ║ + ╠═══════foo═══════╣ + ║ bar ║ + ╚═════════════════╝ + """, + lexemes: [ + LexemeSpec(.identifier, leading: "╔═════ ", text: "hello", trailing: " ═════"), + LexemeSpec(.leadingBoxCorner, text: "╗", trailing: ""), + LexemeSpec(.identifier, leading: "\n║ ", text: "world", trailing: " ║", flags: .isAtStartOfLine), + LexemeSpec(.trailingBoxJunction, leading: "\n", text: "╠", trailing: "═══════", flags: .isAtStartOfLine), + LexemeSpec(.identifier, text: "foo", trailing: "═══════"), + LexemeSpec(.leadingBoxJunction, text: "╣"), + LexemeSpec(.identifier, leading: "\n║ ", text: "bar", trailing: " ║", flags:.isAtStartOfLine), + LexemeSpec(.trailingBoxCorner, leading: "\n", text: "╚", trailing: "═════════════════╝", flags: .isAtStartOfLine), + ] + ) + } } diff --git a/Tests/SwiftParserTest/TriviaParserTests.swift b/Tests/SwiftParserTest/TriviaParserTests.swift index 5ff85c8db8a..ff4399fe57c 100644 --- a/Tests/SwiftParserTest/TriviaParserTests.swift +++ b/Tests/SwiftParserTest/TriviaParserTests.swift @@ -150,6 +150,48 @@ final class TriviaParserTests: ParserTestCase { ), ] ) + + XCTAssertEqual( + TriviaParser.parseTrivia( + """ + ╔═════ hello ═════╗ + ║ world ║ + ╠═══════foo═══════╣ + ║ bar ║ + ╚═════════════════╝ + """, + position: .leading + ), + [ + .boxDrawing("╔═════"), + .spaces(1), + .unexpectedText("hello"), + .spaces(1), + .boxDrawing("═════"), + .unexpectedText("╗"), + .newlines(1), + .boxDrawing("║"), + .spaces(6), + .unexpectedText("world"), + .spaces(6), + .boxDrawing("║"), + .newlines(1), + .unexpectedText("╠"), + .boxDrawing("═══════"), + .unexpectedText("foo"), + .boxDrawing("═══════"), + .unexpectedText("╣"), + .newlines(1), + .boxDrawing("║"), + .spaces(7), + .unexpectedText("bar"), + .spaces(7), + .boxDrawing("║"), + .newlines(1), + .unexpectedText("╚"), + .boxDrawing("═════════════════╝"), + ] + ) } func testRawSyntaxLazyTriviaPieces() { From 0609ec61fc604ccd30b98b47553822406cdc9ef5 Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Fri, 27 Dec 2024 12:39:18 -0800 Subject: [PATCH 2/2] Simplify and prettify box drawing test This involves, among other things, introducing new SyntaxNodeWithBody parameters and methods. --- .../SyntaxNodeWithBody.swift | 186 ++++++++++++++++-- Tests/SwiftParserTest/DeclarationTests.swift | 151 ++------------ 2 files changed, 187 insertions(+), 150 deletions(-) diff --git a/Sources/SwiftSyntaxBuilder/SyntaxNodeWithBody.swift b/Sources/SwiftSyntaxBuilder/SyntaxNodeWithBody.swift index 4255b47c593..d04ad9c696d 100644 --- a/Sources/SwiftSyntaxBuilder/SyntaxNodeWithBody.swift +++ b/Sources/SwiftSyntaxBuilder/SyntaxNodeWithBody.swift @@ -12,10 +12,10 @@ #if compiler(>=6) public import SwiftSyntax -internal import SwiftParser +@_spi(Diagnostics) internal import SwiftParser #else import SwiftSyntax -import SwiftParser +@_spi(Diagnostics) import SwiftParser #endif // MARK: - PartialSyntaxNode @@ -46,6 +46,61 @@ extension SyntaxStringInterpolation { } } +public enum CodeBlockStyle { + case braces + case box(leadingJoined: Bool = false, trailingJoined: Bool = false) + + init(leftBrace: TokenSyntax, rightBrace: TokenSyntax) { + switch (leftBrace.tokenKind, rightBrace.tokenKind) { + case (.leadingBoxCorner, .trailingBoxCorner): + self = .box() + case (.leadingBoxCorner, .trailingBoxJunction): + self = .box(trailingJoined: true) + case (.leadingBoxJunction, .trailingBoxCorner): + self = .box(leadingJoined: true) + case (.leadingBoxJunction, .trailingBoxJunction): + self = .box(leadingJoined: true, trailingJoined: true) + + default: + self = .braces + } + } + + var leftBrace: TokenSyntax { + switch self { + case .braces: + return .leftBraceToken() + case .box(leadingJoined: true, trailingJoined: _): + return .leadingBoxJunctionToken() + case .box(leadingJoined: false, trailingJoined: _): + return .leadingBoxCornerToken() + } + } + + var rightBrace: TokenSyntax { + switch self { + case .braces: + return .rightBraceToken() + case .box(leadingJoined: _, trailingJoined: true): + return .trailingBoxJunctionToken() + case .box(leadingJoined: _, trailingJoined: false): + return .trailingBoxCornerToken() + } + } + + func withJoining(leading: Bool = false, trailing: Bool = false) -> CodeBlockStyle { + switch self { + case .braces: + return self + case .box(leadingJoined: let oldLeading, trailingJoined: let oldTrailing): + return .box( + leadingJoined: leading || oldLeading, + trailingJoined: trailing || oldTrailing + ) + } + } +} + // MARK: - HasTrailingCodeBlock public protocol HasTrailingCodeBlock: WithCodeBlockSyntax { @@ -68,12 +123,13 @@ public protocol HasTrailingCodeBlock: WithCodeBlockSyntax { /// ``` /// /// Throws an error if `header` defines a different node type than the type the initializer is called on. E.g. if calling `try IfStmtSyntax("while x < 5") {}` - init(_ header: SyntaxNodeString, @CodeBlockItemListBuilder bodyBuilder: () throws -> CodeBlockItemListSyntax) rethrows + init(_ header: SyntaxNodeString, delimitedBy blockStyle: CodeBlockStyle, @CodeBlockItemListBuilder bodyBuilder: () throws -> CodeBlockItemListSyntax) rethrows } extension HasTrailingCodeBlock where Self: StmtSyntaxProtocol { public init( _ header: SyntaxNodeString, + delimitedBy blockStyle: CodeBlockStyle = .braces, @CodeBlockItemListBuilder bodyBuilder: () throws -> CodeBlockItemListSyntax ) throws { let stmt = StmtSyntax("\(header) {}") @@ -81,17 +137,26 @@ extension HasTrailingCodeBlock where Self: StmtSyntaxProtocol { throw SyntaxStringInterpolationInvalidNodeTypeError(expectedType: Self.self, actualNode: stmt) } self = castedStmt - self.body = try CodeBlockSyntax(statements: bodyBuilder()) + self.body = try CodeBlockSyntax( + leftBrace: blockStyle.leftBrace, + statements: bodyBuilder(), + rightBrace: blockStyle.rightBrace + ) } } extension CatchClauseSyntax: HasTrailingCodeBlock { public init( _ header: SyntaxNodeString, + delimitedBy blockStyle: CodeBlockStyle = .braces, @CodeBlockItemListBuilder bodyBuilder: () throws -> CodeBlockItemListSyntax ) rethrows { self = CatchClauseSyntax("\(header) {}") - self.body = try CodeBlockSyntax(statements: bodyBuilder()) + self.body = try CodeBlockSyntax( + leftBrace: blockStyle.leftBrace, + statements: bodyBuilder(), + rightBrace: blockStyle.rightBrace + ) } } extension DeferStmtSyntax: HasTrailingCodeBlock {} @@ -124,6 +189,7 @@ extension WithOptionalCodeBlockSyntax where Self: DeclSyntaxProtocol { /// Throws an error if `header` defines a different node type than the type the initializer is called on. E.g. if calling `try FunctionDeclSyntax("init") {}` public init( _ header: SyntaxNodeString, + delimitedBy blockStyle: CodeBlockStyle = .braces, @CodeBlockItemListBuilder bodyBuilder: () throws -> CodeBlockItemListSyntax ) throws { // If the type provides a custom `SyntaxParseable` implementation, use that. Otherwise construct it as a @@ -146,7 +212,11 @@ extension WithOptionalCodeBlockSyntax where Self: DeclSyntaxProtocol { throw SyntaxStringInterpolationInvalidNodeTypeError(expectedType: Self.self, actualNode: decl) } self = castedDecl - self.body = try CodeBlockSyntax(statements: bodyBuilder()) + self.body = try CodeBlockSyntax( + leftBrace: blockStyle.leftBrace, + statements: bodyBuilder(), + rightBrace: blockStyle.rightBrace + ) } } @@ -178,6 +248,7 @@ public protocol HasTrailingMemberDeclBlock { /// Throws an error if `header` defines a different node type than the type the initializer is called on. E.g. if calling `try StructDeclSyntax("class MyClass") {}` init( _ header: SyntaxNodeString, + delimitedBy blockStyle: CodeBlockStyle, @MemberBlockItemListBuilder membersBuilder: () throws -> MemberBlockItemListSyntax ) throws } @@ -185,6 +256,7 @@ public protocol HasTrailingMemberDeclBlock { extension HasTrailingMemberDeclBlock where Self: DeclSyntaxProtocol { public init( _ header: SyntaxNodeString, + delimitedBy blockStyle: CodeBlockStyle = .braces, @MemberBlockItemListBuilder membersBuilder: () throws -> MemberBlockItemListSyntax ) throws { // If the type provides a custom `SyntaxParseable` implementation, use that. Otherwise construct it as a @@ -202,7 +274,11 @@ extension HasTrailingMemberDeclBlock where Self: DeclSyntaxProtocol { throw SyntaxStringInterpolationInvalidNodeTypeError(expectedType: Self.self, actualNode: decl) } self = castedDecl - self.memberBlock = try MemberBlockSyntax(members: membersBuilder()) + self.memberBlock = try MemberBlockSyntax( + leftBrace: blockStyle.leftBrace, + members: membersBuilder(), + rightBrace: blockStyle.rightBrace + ) } } @@ -218,6 +294,33 @@ extension StructDeclSyntax: HasTrailingMemberDeclBlock {} // So we cannot conform to `HasTrailingCodeBlock` extension IfExprSyntax { + private static func rewriteBlockStyle( + of ifExpr: IfExprSyntax, + to blockStyle: CodeBlockStyle + ) -> IfExprSyntax { + var ifExpr = ifExpr + + let ifStyle = ifExpr.elseBody != nil ? blockStyle.withJoining(trailing: true) : blockStyle + ifExpr.body.leftBrace = ifStyle.leftBrace + ifExpr.body.rightBrace = ifStyle.rightBrace + + if let elseBody = ifExpr.elseBody { + let elseStyle = blockStyle.withJoining(leading: true) + + switch elseBody { + case .codeBlock(var codeBlock): + codeBlock.leftBrace = elseStyle.leftBrace + codeBlock.rightBrace = elseStyle.rightBrace + ifExpr.elseBody = .codeBlock(codeBlock) + + case .ifExpr(let elseIfExpr): + ifExpr.elseBody = .ifExpr(rewriteBlockStyle(of: elseIfExpr, to: elseStyle)) + } + } + + return ifExpr + } + /// Constructs an `if` expression with an optional `else` block. /// /// `header` specifies the part of the `if` expression before the body’s first brace. @@ -237,17 +340,20 @@ extension IfExprSyntax { /// Throws an error if `header` does not start an `if` expression. E.g. if calling `try IfExprSyntax("while true") {}` public init( _ header: SyntaxNodeString, + delimitedBy blockStyle: CodeBlockStyle = .braces, @CodeBlockItemListBuilder bodyBuilder: () throws -> CodeBlockItemListSyntax, @CodeBlockItemListBuilder `else` elseBuilder: () throws -> CodeBlockItemListSyntax? = { nil } ) throws { let expr = ExprSyntax("\(header) {}") - guard let ifExpr = expr.as(Self.self) else { + guard var ifExpr = expr.as(Self.self) else { throw SyntaxStringInterpolationInvalidNodeTypeError(expectedType: Self.self, actualNode: expr) } - self = ifExpr - self.body = try CodeBlockSyntax(statements: bodyBuilder()) - self.elseBody = try elseBuilder().map { .codeBlock(CodeBlockSyntax(statements: $0)) } - self.elseKeyword = elseBody != nil ? .keyword(.else) : nil + + ifExpr.body = try CodeBlockSyntax(statements: bodyBuilder()) + ifExpr.elseBody = try elseBuilder().map { .codeBlock(CodeBlockSyntax(statements: $0)) } + ifExpr.elseKeyword = ifExpr.elseBody != nil ? .keyword(.else) : nil + + self = Self.rewriteBlockStyle(of: ifExpr, to: blockStyle) } /// Constructs an `if` expression with a following `else if` clause. @@ -286,17 +392,63 @@ extension IfExprSyntax { /// Throws an error if `header` does not start an `if` expression. E.g. if calling `try IfExprSyntax("while true", bodyBuilder: {}, elseIf: {})` public init( _ header: SyntaxNodeString, + delimitedBy blockStyle: CodeBlockStyle = .braces, @CodeBlockItemListBuilder bodyBuilder: () throws -> CodeBlockItemListSyntax, elseIf: IfExprSyntax ) throws { let expr = ExprSyntax("\(header) {}") - guard let ifExpr = expr.as(Self.self) else { + guard var ifExpr = expr.as(Self.self) else { throw SyntaxStringInterpolationInvalidNodeTypeError(expectedType: Self.self, actualNode: expr) } - self = ifExpr - self.body = CodeBlockSyntax(statements: try bodyBuilder()) - self.elseBody = .ifExpr(elseIf) - self.elseKeyword = elseBody != nil ? .keyword(.else) : nil + + ifExpr.body = CodeBlockSyntax(statements: try bodyBuilder()) + ifExpr.elseBody = .ifExpr(elseIf) + ifExpr.elseKeyword = ifExpr.elseBody != nil ? .keyword(.else) : nil + + self = Self.rewriteBlockStyle(of: ifExpr, to: blockStyle) + } + + private var deepestRightBrace: TokenSyntax { + switch elseBody { + case nil: + return body.rightBrace + case .codeBlock(let codeBlock): + return codeBlock.rightBrace + case .ifExpr(let elseIfExpr): + return elseIfExpr.deepestRightBrace + } + } + + private var inferredBlockStyle: CodeBlockStyle { + return CodeBlockStyle(leftBrace: self.body.leftBrace, rightBrace: self.deepestRightBrace) + } + + public func `else`( + if ifHeader: SyntaxNodeString? = nil, + @CodeBlockItemListBuilder elseBuilder: () -> CodeBlockItemListSyntax + ) throws -> IfExprSyntax { + let newElseBody: ElseBody + if let existingElseBody = elseBody { + // We should attach this node to a child. + switch existingElseBody { + case .codeBlock(_): + preconditionFailure("todo: throw an error") + case .ifExpr(let elseIfExpr): + newElseBody = .ifExpr(try elseIfExpr.else(if: ifHeader, elseBuilder: elseBuilder)) + } + } else { + // We should attach this node here. + if let ifHeader { + newElseBody = .ifExpr(try IfExprSyntax(ifHeader, bodyBuilder: elseBuilder)) + } else { + newElseBody = .codeBlock(CodeBlockSyntax(statementsBuilder: elseBuilder)) + } + } + + return Self.rewriteBlockStyle( + of: self.with(\.elseBody, newElseBody).with(\.elseKeyword, .keyword(.else)), + to: self.inferredBlockStyle + ) } } diff --git a/Tests/SwiftParserTest/DeclarationTests.swift b/Tests/SwiftParserTest/DeclarationTests.swift index 6440f8cdb84..2810156bcc3 100644 --- a/Tests/SwiftParserTest/DeclarationTests.swift +++ b/Tests/SwiftParserTest/DeclarationTests.swift @@ -168,33 +168,11 @@ final class DeclarationTests: ParserTestCase { ) } - func testFuncBoxDrawing() { - func callIsMultiple(of value: Int) -> FunctionCallExprSyntax { - FunctionCallExprSyntax( - calledExpression: MemberAccessExprSyntax( - base: DeclReferenceExprSyntax(baseName: .identifier("i")), - name: .identifier("isMultiple") - ), - leftParen: .leftParenToken(), - arguments: LabeledExprListSyntax { - LabeledExprSyntax( - label: "of", - expression: IntegerLiteralExprSyntax(value) - ) - }, - rightParen: .rightParenToken() - ) - } - + func testFuncBoxDrawing() throws { func callPrint(_ literal: StringLiteralExprSyntax) -> FunctionCallExprSyntax { - FunctionCallExprSyntax( - calledExpression: DeclReferenceExprSyntax(baseName: .identifier("print")), - leftParen: .leftParenToken(), - arguments: LabeledExprListSyntax { - LabeledExprSyntax(expression: literal) - }, - rightParen: .rightParenToken() - ) + FunctionCallExprSyntax(callee: DeclReferenceExprSyntax(baseName: "print")) { + LabeledExprSyntax(expression: literal) + } } assertParse( @@ -214,114 +192,21 @@ final class DeclarationTests: ParserTestCase { ╚═══════════════════════════════════════════╝ """#, substructure: Syntax( - FunctionDeclSyntax( - attributes: [], - modifiers: [], - funcKeyword: .keyword(.func), - name: .identifier("fizzBuzz"), - signature: FunctionSignatureSyntax( - parameterClause: FunctionParameterClauseSyntax( - leftParen: .leftParenToken(), - parameters: [], - rightParen: .rightParenToken() + try FunctionDeclSyntax("func fizzBuzz()", delimitedBy: .box()) { + try ForStmtSyntax("for i in 0..<100", delimitedBy: .box()) { + ExpressionStmtSyntax( + expression: try IfExprSyntax("if i.isMultiple(of: 15)", delimitedBy: .box()) { + ExprSyntax(#"print("fizzbuzz ")"#) + }.else(if: "if i.isMultiple(of: 3)") { + ExprSyntax(#"print("fizz ")"#) + }.else(if: "if i.isMultiple(of: 5)") { + ExprSyntax(#"print("buzz ")"#) + }.else { + ExprSyntax(#"print("\(i) ")"#) + } ) - ), - body: CodeBlockSyntax( - leftBrace: .leadingBoxCornerToken(), - statements: CodeBlockItemListSyntax { - ForStmtSyntax( - forKeyword: .keyword(.for), - pattern: IdentifierPatternSyntax( - identifier: .identifier("i") - ), - inKeyword: .keyword(.in), - sequence: SequenceExprSyntax { - IntegerLiteralExprSyntax(0) - BinaryOperatorExprSyntax(text: "..<") - IntegerLiteralExprSyntax(100) - }, - body: CodeBlockSyntax( - leftBrace: .leadingBoxCornerToken(), - statements: CodeBlockItemListSyntax { - ExpressionStmtSyntax( - expression: IfExprSyntax( - ifKeyword: .keyword(.if), - conditions: ConditionElementListSyntax { - callIsMultiple(of: 15) - }, - body: CodeBlockSyntax( - leftBrace: .leadingBoxCornerToken(), - statements: CodeBlockItemListSyntax { - callPrint(StringLiteralExprSyntax(content: "fizzbuzz ")) - }, - rightBrace: .trailingBoxJunctionToken() - ), - elseKeyword: .keyword(.else), - elseBody: IfExprSyntax.ElseBody.ifExpr( - IfExprSyntax( - ifKeyword: .keyword(.if), - conditions: ConditionElementListSyntax { - callIsMultiple(of: 3) - }, - body: CodeBlockSyntax( - leftBrace: .leadingBoxJunctionToken(), - statements: CodeBlockItemListSyntax { - callPrint(StringLiteralExprSyntax(content: "fizz ")) - }, - rightBrace: .trailingBoxJunctionToken() - ), - elseKeyword: .keyword(.else), - elseBody: IfExprSyntax.ElseBody.ifExpr( - IfExprSyntax( - ifKeyword: .keyword(.if), - conditions: ConditionElementListSyntax { - callIsMultiple(of: 5) - }, - body: CodeBlockSyntax( - leftBrace: .leadingBoxJunctionToken(), - statements: CodeBlockItemListSyntax { - callPrint(StringLiteralExprSyntax(content: "buzz ")) - }, - rightBrace: .trailingBoxJunctionToken() - ), - elseKeyword: .keyword(.else), - elseBody: .codeBlock( - CodeBlockSyntax( - leftBrace: .leadingBoxJunctionToken(), - statements: CodeBlockItemListSyntax { - callPrint( - StringLiteralExprSyntax( - openingQuote: .stringQuoteToken(), - segments: StringLiteralSegmentListSyntax { - StringSegmentSyntax(content: .stringSegment("")) - ExpressionSegmentSyntax( - expressions: LabeledExprListSyntax { - LabeledExprSyntax(expression: DeclReferenceExprSyntax(baseName: .identifier("i"))) - } - ) - StringSegmentSyntax(content: .stringSegment(" ")) - }, - closingQuote: .stringQuoteToken() - ) - ) - }, - rightBrace: .trailingBoxCornerToken() - ) - ) - ) - ) - ) - ) - ) - ) - }, - rightBrace: .trailingBoxCornerToken() - ) - ) - }, - rightBrace: .trailingBoxCornerToken() - ) - ) + } + } ) ) }