From f74e98e70e04f9a98432f0c1b971b6e94cfca3cc Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 3 Aug 2022 23:09:03 -0700 Subject: [PATCH 01/50] Operator-precedence parsing for sequence expressions Implement the sequence "fold" operation that takes a sequence expression (i.e., something like a + b * c) and turns it into a structured syntax tree that reflects the relative precedence of the operators involved (here, * has higher precedence than +). The core of this algorithm is a direct port of the C++ implementation of operator-precedence parsing. There are several pieces to it: * PrecedenceGroup and Operator, which are semantic representations of precedence groups and operator declarations. * PrecedenceGraph, which keeps track of a set of precedence groups and their relationships to each other, and is able to answer questions like "how to the precedences of two different groups compare?". * Syntactic -> semantic translation to go from PrecedenceGroupDeclSyntax and OperatorDeclSyntax nodes to PrecedenceGroup and Operator, by walking the child syntax. * OperatorPrecedence, which contains a PrecedenceGraph that can be created by processing a source file to pick out the various operator and precedence-group declarations. It provides the core "fold" operation that turns a SequenceExprSyntax node into one that is fully-structured to show the order of operations. The parsing infrastructure and basic algorithm work (modulo lots of testing), but there are a lot of missing pieces: * Right now we either throw an error or outright ignore pretty much any problem that shows up. Neither of those is an acceptable answer, so we'll need to decide how to * There are default precedence groups for certain expression kinds (e.g., '=', 'as?', '->') that we will need to implement by looking at the syntax node kind for the "operator" and handling each of the cases. This is easy but tedious to do/test. * There is no syntax node for "infix binary expression" in the grammar, so we cannot actually represent the result the way we want to. For testing purposes now, we create a new TupleExprSyntax instead, so we can see the full parenthesization in the output. * We do not have primitives for merging precedence graphs when combining multiple source files, modules, etc. We'll need to define those rules. * We don't make any attempt at caching repeated queries, which could matter later on. * We probably want to provide some pre-canned OperatorPrecedence instances for, e.g., the logical operators (&&, ||, !) used when evaluating preprocessor conditionals (#if x || y) and possibly the standard library itself. The biggest question of all is whether any of this even makes sense to do as part of the syntax library, or whether it belongs with the main Swift compiler. --- .../OperatorPrecedence+Parsing.swift | 330 ++++++++++++++++++ Sources/SwiftParser/OperatorPrecedence.swift | 305 ++++++++++++++++ .../SwiftParserTest/OperatorPrecedence.swift | 63 ++++ 3 files changed, 698 insertions(+) create mode 100644 Sources/SwiftParser/OperatorPrecedence+Parsing.swift create mode 100644 Sources/SwiftParser/OperatorPrecedence.swift create mode 100644 Tests/SwiftParserTest/OperatorPrecedence.swift diff --git a/Sources/SwiftParser/OperatorPrecedence+Parsing.swift b/Sources/SwiftParser/OperatorPrecedence+Parsing.swift new file mode 100644 index 00000000000..a940c9e33ff --- /dev/null +++ b/Sources/SwiftParser/OperatorPrecedence+Parsing.swift @@ -0,0 +1,330 @@ +//===-------------- OperatorPrecedenceParsing.swift -----------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +import SwiftSyntax + +extension PrecedenceGroup { + /// Form the semantic definition of a precedence group given its syntax. + /// + /// TODO: This ignores all semantic errors. + init(from syntax: PrecedenceGroupDeclSyntax) { + self.name = syntax.identifier.text + + for attr in syntax.groupAttributes { + // Relation (lowerThan, higherThan) + if let relation = attr.as(PrecedenceGroupRelationSyntax.self) { + let isLowerThan = relation.higherThanOrLowerThan.text == "lowerThan" + for otherGroup in relation.otherNames { + let otherGroupName = otherGroup.name.text + self.relations.append(isLowerThan ? .lowerThan(otherGroupName) + : .higherThan(otherGroupName)) + } + + continue + } + + // Assignment + if let assignment = attr.as(PrecedenceGroupAssignmentSyntax.self) { + self.assignment = assignment.flag.text == "true" + continue + } + + // Associativity + if let associativity = attr.as(PrecedenceGroupAssociativitySyntax.self) { + switch associativity.value.text { + case "left": + self.associativity = .left + + case "right": + self.associativity = .right + + case "none": + self.associativity = .none + + default: + break + } + } + } + } +} + +extension Operator { + /// Form the semantic definition of an operator given its syntax. + /// + /// TODO: This ignores all semantic errors. + init(from syntax: OperatorDeclSyntax) { + let kindModifier = syntax.modifiers?.first { modifier in + OperatorKind(rawValue: modifier.name.text) != nil + } + + if let kindModifier = kindModifier { + kind = OperatorKind(rawValue: kindModifier.name.text)! + } else { + kind = .infix + } + + name = syntax.identifier.text + + if let groupSyntax = syntax.operatorPrecedenceAndTypes? + .precedenceGroupAndDesignatedTypes { + precedenceGroup = groupSyntax.firstToken?.text + } else { + precedenceGroup = nil + } + } +} + +extension OperatorPrecedence { + /// Integrate the operator and precedence group declarations from the given + /// source file into the operator precedence tables. + public mutating func addSourceFile(_ sourceFile: SourceFileSyntax) throws { + for stmt in sourceFile.statements { + if let operatorSyntax = stmt.item.as(OperatorDeclSyntax.self) { + try record(Operator(from: operatorSyntax)) + continue + } + + if let precedenceGroupSyntax = stmt.item.as(PrecedenceGroupDeclSyntax.self) { + try record(PrecedenceGroup(from: precedenceGroupSyntax)) + continue + } + } + } +} + +extension OperatorPrecedence { + private struct PrecedenceBound { + let groupName: PrecedenceGroupName? + let isStrict: Bool + } + + /// Determine whether we should consider an operator in the given group + /// based on the specified bound. + private func shouldConsiderOperator( + fromGroup groupName: PrecedenceGroupName?, in bound: PrecedenceBound + ) throws -> Bool { + guard let boundGroupName = bound.groupName else { + return true + } + + guard let groupName = groupName else { + return false + } + + if groupName == boundGroupName { + return !bound.isStrict + } + + return try precedence(relating: groupName, to: boundGroupName) != .lowerThan + } + + /// Look up the precedence group for the given expression syntax. + private func lookupPrecedence(of expr: ExprSyntax) -> PrecedenceGroupName? { + if let binaryExpr = expr.as(BinaryOperatorExprSyntax.self) { + guard let op = operators[binaryExpr.operatorToken.text] else { + // FIXME: Report unknown operator. + return nil + } + + return op.precedenceGroup + } + + // FIXME: Handle all of the language-defined precedence relationships. + return nil + } + + /// Form a binary operation expression for, e.g., a + b. + private func makeBinaryOperationExpr( + lhs: ExprSyntax, op: ExprSyntax, rhs: ExprSyntax + ) -> ExprSyntax { + // FIXME: As a short-term hack, form a parenthesized sequence expression + // so we can see the resulting structure without having to introduce new + // nodes. + ExprSyntax( + SyntaxFactory.makeTupleExpr( + leftParen: SyntaxFactory.makeToken(.leftParen, presence: .present), + elementList: SyntaxFactory.makeTupleExprElementList([ + SyntaxFactory.makeTupleExprElement( + label: nil, colon: nil, + expression: ExprSyntax( + SyntaxFactory.makeSequenceExpr( + elements: SyntaxFactory.makeExprList([lhs, op, rhs]) + )), + trailingComma: nil) + ]), + rightParen: SyntaxFactory.makeToken(.rightParen, presence: .present)) + ) + } + + /// Determine the associativity between two precedence groups. + private func associativity( + firstGroup: PrecedenceGroupName?, secondGroup: PrecedenceGroupName? + ) throws -> Associativity { + guard let firstGroup = firstGroup, let secondGroup = secondGroup else { + return .none + } + + // If we have the same group, query its associativity. + if firstGroup == secondGroup { + return try precedenceGraph.lookupGroup(firstGroup).associativity + } + + if try precedence(relating: firstGroup, to: secondGroup) == .higherThan { + return .left + } + + if try precedence(relating: secondGroup, to: firstGroup) == .higherThan { + return .right + } + + return .none + } + + /// "Fold" an expression sequence where the left-hand side has been broken + /// out and (potentially) folded somewhat, and the "rest" of the sequence is + /// consumed along the way + private func fold( + _ lhs: ExprSyntax, rest: inout Slice, + bound: PrecedenceBound + ) throws -> ExprSyntax { + if rest.isEmpty { return lhs } + + // We mutate the left-hand side in place as we fold the sequence. + var lhs = lhs + + /// Get the operator, if appropriate to this pass. + func getNextOperator() throws -> (ExprSyntax, PrecedenceGroupName?)? { + let op = rest.first! + + // If the operator's precedence is lower than the minimum, stop here. + let opPrecedence = lookupPrecedence(of: op) + if try !shouldConsiderOperator(fromGroup: opPrecedence, in: bound) { + return nil + } + + return (op, opPrecedence) + } + + // Extract out the first operator. + guard var (op1, op1Precedence) = try getNextOperator() else { + return lhs + } + + // We will definitely be consuming at least one operator. + // Pull out the prospective RHS and slice off the first two elements. + rest = rest.dropFirst() + var rhs = rest.first! + rest = rest.dropFirst() + + while !rest.isEmpty { + assert( + try! shouldConsiderOperator(fromGroup: op1Precedence, in: bound) + ) + + #if compiler(>=10.0) && false + // If the operator is a cast operator, the RHS can't extend past the type + // that's part of the cast production. + if (isa(op1.op)) { + LHS = makeBinOp(Ctx, op1.op, LHS, RHS, op1.precedence, S.empty()); + op1 = getNextOperator(); + if (!op1) return LHS; + RHS = S[1]; + S = S.slice(2); + continue; + } + #endif + + // Pull out the next binary operator. + let op2 = rest.first! + let op2Precedence = lookupPrecedence(of: op2) + + // If the second operator's precedence is lower than the + // precedence bound, break out of the loop. + if try !shouldConsiderOperator(fromGroup: op2Precedence, in: bound) { + break + } + + let associativity = try associativity( + firstGroup: op1Precedence, secondGroup: op2Precedence + ) + + switch associativity { + case .left: + // Apply left-associativity immediately by folding the first two + // operands. + lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) + op1 = op2 + op1Precedence = op2Precedence + rest = rest.dropFirst() + rhs = rest.first! + rest = rest.dropFirst() + + case .right where op1Precedence != op2Precedence: + // If the first operator's precedence is lower than the second + // operator's precedence, recursively fold all such + // higher-precedence operators starting from this point, then + // repeat. + rhs = try fold( + rhs, rest: &rest, + bound: PrecedenceBound(groupName: op1Precedence, isStrict: true)) + + case .right: + // Apply right-associativity by recursively folding operators + // starting from this point, then immediately folding the LHS and RHS. + rhs = try fold( + rhs, rest: &rest, + bound: PrecedenceBound(groupName: op1Precedence, isStrict: false) + ) + + lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) + + // If we've drained the entire sequence, we're done. + if rest.isEmpty { + return lhs + } + + // Otherwise, start all over with our new LHS. + return try fold(lhs, rest: &rest, bound: bound) + + case .none: + // If we ended up here, it's because we're either: + // - missing precedence groups, + // - have unordered precedence groups, or + // - have the same precedence group with no associativity. + if let op1Precedence = op1Precedence, + let op2Precedence = op2Precedence { + if op1Precedence == op2Precedence { + // FIXME: Must be a nonassociative group, diagnose + } else { + // FIXME: Diagnose unrelated groups + } + } + + // Recover by folding arbitrarily at this operator, then continuing. + lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) + return try fold(lhs, rest: &rest, bound: bound) + } + } + + // Fold LHS and RHS together and declare completion. + return makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) + } + + /// "Fold" an expression sequence into a structured syntax tree. + public func fold(_ sequence: SequenceExprSyntax) throws -> ExprSyntax { + let lhs = sequence.elements.first! + var rest = sequence.elements.dropFirst() + return try fold( + lhs, rest: &rest, bound: PrecedenceBound(groupName: nil, isStrict: false) + ) + } +} diff --git a/Sources/SwiftParser/OperatorPrecedence.swift b/Sources/SwiftParser/OperatorPrecedence.swift new file mode 100644 index 00000000000..ee8820ce928 --- /dev/null +++ b/Sources/SwiftParser/OperatorPrecedence.swift @@ -0,0 +1,305 @@ +//===------------------ OperatorPrecedence.swift --------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// Names a precedence group. +/// +/// TODO: For now, we'll use strings, but we likely want to move this to +/// a general notion of an Identifier. +public typealias PrecedenceGroupName = String + +/// Names an operator. +/// +/// TODO: For now, we'll use strings, but we likely want to move this to a +/// general notion of an Identifier. +public typealias OperatorName = String + +/// The associativity of a precedence group. +public enum Associativity: String { + /// The precedence group is nonassociative, meaning that one must + /// parenthesize when there are multiple operators in a sequence, e.g., + /// if ^ was nonassociative, a ^ b ^ c would need to be disambiguated as + /// either (a ^ b ) ^ c or a ^ (b ^ c). + case none + + /// The precedence group is left-associative, meaning that multiple operators + /// in the same sequence will be parenthesized from the left. This is typical + /// for arithmetic operators, such that a + b - c is treated as (a + b) - c. + case left + + /// The precedence group is right-associative, meaning that multiple operators + /// in the same sequence will be parenthesized from the right. This is used + /// for assignments, where a = b = c is treated as a = (b = c). + case right +} + +/// Describes the relationship between two different precedence groups. +public enum PrecedenceRelation { + /// The precedence group storing this relation has higher precedence than + /// the named group. + case higherThan(PrecedenceGroupName) + + /// The precedence group storing this relation has lower precedence than + /// the named group. + case lowerThan(PrecedenceGroupName) +} + +/// Precedence groups are used for parsing sequences of expressions in Swift +/// source code. Each precedence group defines the associativity of the +/// operator and its precedence relative to other precedence groups: +/// +/// precedencegroup MultiplicativePrecedence { +/// associativity: left +/// higherThan: AdditivePrecedence +/// } +/// +/// Operator declarations then specify which precedence group describes their +/// precedence, e.g., +/// +/// infix operator *: MultiplicationPrecedence +public struct PrecedenceGroup { + /// The name of the group, which must be unique. + public var name: PrecedenceGroupName + + /// The associativity for the group. + public var associativity: Associativity = .none + + /// Whether the operators in this precedence group are considered to be + /// assignment operators. + public var assignment: Bool = false + + /// The set of relations to other precedence groups that are defined by + /// this precedence group. + public var relations: [PrecedenceRelation] = [] + + public init( + name: PrecedenceGroupName, associativity: Associativity, assignment: Bool, + relations: [PrecedenceRelation] + ) { + self.name = name + self.associativity = associativity + self.assignment = assignment + self.relations = relations + } +} + +/// Describes errors that can occur when working with operator precedence graphs. +public enum OperatorPrecedenceError: Error { + /// Error produced when a given precedence group already exists in the + /// precedence graph. + case groupAlreadyExists(existing: PrecedenceGroup, new: PrecedenceGroup) + + /// The named precedence group is missing from the precedence graph. + case missingGroup(PrecedenceGroupName) + + /// Error produced when a given operator already exists. + case operatorAlreadyExists(existing: Operator, new: Operator) + + /// The named operator is missing from the precedence graph. + case missingOperator(OperatorName) +} + +/// Describes the relative precedence of two groups. +public enum Precedence { + case unrelated + case higherThan + case lowerThan +} + +/// A graph formed from a set of precedence groups, which can be used to +/// determine the relative precedence of two precedence groups. +public struct PrecedenceGraph { + /// The known set of precedence groups, found by name. + public var precedenceGroups: [PrecedenceGroupName : PrecedenceGroup] = [:] + + /// Add a new precedence group + /// + /// - throws: If there is already a precedence group with the given name, + /// throws PrecedenceGraphError.groupAlreadyExists. + public mutating func add(_ group: PrecedenceGroup) throws { + if let existing = precedenceGroups[group.name] { + throw OperatorPrecedenceError.groupAlreadyExists( + existing: existing, new: group) + } + + precedenceGroups[group.name] = group + } + + /// Look for the precedence group with the given name, or produce an error + /// if such a group is not known. + public func lookupGroup( + _ groupName: PrecedenceGroupName + ) throws -> PrecedenceGroup { + guard let group = precedenceGroups[groupName] else { + throw OperatorPrecedenceError.missingGroup(groupName) + } + + return group + } + + /// Determine the precedence relationship between two precedence groups. + /// + /// Follow the precedence relationships among the precedence groups to + /// determine the precedence of the start group relative to the end group. + public func precedence( + relating startGroupName: PrecedenceGroupName, + to endGroupName: PrecedenceGroupName + ) throws -> Precedence { + if startGroupName == endGroupName { + return .unrelated + } + + // Keep track of all of the groups we have seen during our exploration of + // the graph. This detects cycles and prevents extraneous work. + var groupsSeen: Set = [] + + // Walk all of the lower-than relationships from the end group. If we + // reach the start group, the start has lower precedence than the end. + var stack: [PrecedenceGroupName] = [endGroupName] + while let currentGroupName = stack.popLast() { + let currentGroup = try lookupGroup(currentGroupName) + + for relation in currentGroup.relations { + if case let .lowerThan(otherGroupName) = relation { + // If we hit our start group, we're done. + if otherGroupName == startGroupName { + return .lowerThan + } + + if !groupsSeen.insert(otherGroupName).inserted { + stack.append(otherGroupName) + } + } + } + } + + // Walk all of the higher-than relationships from the start group. If we + // reach the end group, the start has higher precedence than the end. + assert(stack.isEmpty) + groupsSeen.removeAll() + stack.append(startGroupName) + while let currentGroupName = stack.popLast() { + let currentGroup = try lookupGroup(currentGroupName) + + for relation in currentGroup.relations { + if case let .higherThan(otherGroupName) = relation { + // If we hit our end group, we're done. + if otherGroupName == endGroupName { + return .higherThan + } + + if !groupsSeen.insert(otherGroupName).inserted { + stack.append(otherGroupName) + } + } + } + } + + // The two are incomparable. + return .unrelated + } +} + +/// Describes the kind of an operator. +public enum OperatorKind: String { + /// Infix operator such as the + in a + b. + case infix + + /// Prefix operator such as the - in -x. + case prefix + + /// Postfix operator such as the ! in x!. + case postfix +} + +/// Describes an operator. +public struct Operator { + public let kind: OperatorKind + public let name: OperatorName + public let precedenceGroup: PrecedenceGroupName? + + public init( + kind: OperatorKind, name: OperatorName, + precedenceGroup: PrecedenceGroupName? + ) { + self.kind = kind + self.name = name + self.precedenceGroup = precedenceGroup + } +} + +/// Maintains information about operators and their relative precedence, +/// providing the core operations for "folding" sequence expression syntax into +/// a structured expression syntax tree. +public struct OperatorPrecedence { + var precedenceGraph: PrecedenceGraph = .init() + var operators: [OperatorName : Operator] = [:] + + public init() { } + + /// Record the operator, if it matters. + /// FIXME: Terrible API used only for tests + public mutating func record(_ op: Operator) throws { + if op.kind != .infix { return } + + if let existing = operators[op.name] { + throw OperatorPrecedenceError.operatorAlreadyExists( + existing: existing, new: op) + } + + operators[op.name] = op + } + + /// Record the precedence group. + /// FIXME: Terrible API used only for tests + public mutating func record(_ group: PrecedenceGroup) throws { + try precedenceGraph.add(group) + } +} + +extension OperatorPrecedence { + /// Look for the precedence group corresponding to the given operator. + public func lookupOperatorPrecedenceGroupName( + _ operatorName: OperatorName + ) throws -> PrecedenceGroupName? { + guard let op = operators[operatorName] else { + throw OperatorPrecedenceError.missingOperator(operatorName) + } + + return op.precedenceGroup + } + + /// Look for the precedence group corresponding to the given operator. + public func lookupOperatorPrecedenceGroup( + _ operatorName: OperatorName + ) throws -> PrecedenceGroup? { + guard let groupName = try lookupOperatorPrecedenceGroupName(operatorName) + else { + return nil + } + return try precedenceGraph.lookupGroup(groupName) + } + + /// Determine the relative precedence between two precedence groups. + public func precedence( + relating startGroupName: PrecedenceGroupName?, + to endGroupName: PrecedenceGroupName? + ) throws -> Precedence { + guard let startGroupName = startGroupName, let endGroupName = endGroupName + else { + return .unrelated + } + + return try precedenceGraph.precedence( + relating: startGroupName, to: endGroupName + ) + } +} diff --git a/Tests/SwiftParserTest/OperatorPrecedence.swift b/Tests/SwiftParserTest/OperatorPrecedence.swift new file mode 100644 index 00000000000..e50ac0e28d4 --- /dev/null +++ b/Tests/SwiftParserTest/OperatorPrecedence.swift @@ -0,0 +1,63 @@ +import XCTest +import SwiftSyntax +import SwiftParser + +public class OperatorPrecedenceTests: XCTestCase { + func testLogicalExprs() throws { + var opPrecedence = OperatorPrecedence() + + try opPrecedence.record( + Operator(kind: .infix, name: "&&", + precedenceGroup: "LogicalConjunctionPrecedence")) + try opPrecedence.record( + Operator(kind: .infix, name: "||", + precedenceGroup: "LogicalDisjunctionPrecedence")) + try opPrecedence.record( + PrecedenceGroup(name: "LogicalConjunctionPrecedence", + associativity: .left, assignment: false, + relations: [.higherThan("LogicalDisjunctionPrecedence")])) + try opPrecedence.record( + PrecedenceGroup(name: "LogicalDisjunctionPrecedence", + associativity: .left, assignment: false, + relations: [])) + + let parsed = try Parser.parse(source: "x && y || w && v || z") + let sequenceExpr = + parsed.statements.first!.item.as(SequenceExprSyntax.self)! + let foldedExpr = try opPrecedence.fold(sequenceExpr) + XCTAssertEqual("\(foldedExpr)", "(((x && y )|| (w && v ))|| z)") + } + + func testParsedLogicalExprs() throws { + let logicalOperatorSources = + """ + precedencegroup LogicalDisjunctionPrecedence { + associativity: left + } + + precedencegroup LogicalConjunctionPrecedence { + associativity: left + higherThan: LogicalDisjunctionPrecedence + } + + // "Conjunctive" + + infix operator &&: LogicalConjunctionPrecedence + + // "Disjunctive" + + infix operator ||: LogicalDisjunctionPrecedence + """ + + let parsedOperatorPrecedence = try Parser.parse(source: logicalOperatorSources) + var opPrecedence = OperatorPrecedence() + try opPrecedence.addSourceFile(parsedOperatorPrecedence) + + let parsed = try Parser.parse(source: "x && y || w && v || z") + let sequenceExpr = + parsed.statements.first!.item.as(SequenceExprSyntax.self)! + let foldedExpr = try opPrecedence.fold(sequenceExpr) + XCTAssertEqual("\(foldedExpr)", "(((x && y )|| (w && v ))|| z)") + + } +} From 679ad36098e927d812f37f2c88878dc54a8fe51b Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 5 Aug 2022 22:22:15 -0700 Subject: [PATCH 02/50] Use InfixOperatorExpr --- .../OperatorPrecedence+Parsing.swift | 228 ------------------ Sources/SwiftParser/OperatorPrecedence.swift | 217 +++++++++++++++++ .../SwiftParserTest/OperatorPrecedence.swift | 7 +- 3 files changed, 221 insertions(+), 231 deletions(-) diff --git a/Sources/SwiftParser/OperatorPrecedence+Parsing.swift b/Sources/SwiftParser/OperatorPrecedence+Parsing.swift index a940c9e33ff..c23beaa7431 100644 --- a/Sources/SwiftParser/OperatorPrecedence+Parsing.swift +++ b/Sources/SwiftParser/OperatorPrecedence+Parsing.swift @@ -100,231 +100,3 @@ extension OperatorPrecedence { } } } - -extension OperatorPrecedence { - private struct PrecedenceBound { - let groupName: PrecedenceGroupName? - let isStrict: Bool - } - - /// Determine whether we should consider an operator in the given group - /// based on the specified bound. - private func shouldConsiderOperator( - fromGroup groupName: PrecedenceGroupName?, in bound: PrecedenceBound - ) throws -> Bool { - guard let boundGroupName = bound.groupName else { - return true - } - - guard let groupName = groupName else { - return false - } - - if groupName == boundGroupName { - return !bound.isStrict - } - - return try precedence(relating: groupName, to: boundGroupName) != .lowerThan - } - - /// Look up the precedence group for the given expression syntax. - private func lookupPrecedence(of expr: ExprSyntax) -> PrecedenceGroupName? { - if let binaryExpr = expr.as(BinaryOperatorExprSyntax.self) { - guard let op = operators[binaryExpr.operatorToken.text] else { - // FIXME: Report unknown operator. - return nil - } - - return op.precedenceGroup - } - - // FIXME: Handle all of the language-defined precedence relationships. - return nil - } - - /// Form a binary operation expression for, e.g., a + b. - private func makeBinaryOperationExpr( - lhs: ExprSyntax, op: ExprSyntax, rhs: ExprSyntax - ) -> ExprSyntax { - // FIXME: As a short-term hack, form a parenthesized sequence expression - // so we can see the resulting structure without having to introduce new - // nodes. - ExprSyntax( - SyntaxFactory.makeTupleExpr( - leftParen: SyntaxFactory.makeToken(.leftParen, presence: .present), - elementList: SyntaxFactory.makeTupleExprElementList([ - SyntaxFactory.makeTupleExprElement( - label: nil, colon: nil, - expression: ExprSyntax( - SyntaxFactory.makeSequenceExpr( - elements: SyntaxFactory.makeExprList([lhs, op, rhs]) - )), - trailingComma: nil) - ]), - rightParen: SyntaxFactory.makeToken(.rightParen, presence: .present)) - ) - } - - /// Determine the associativity between two precedence groups. - private func associativity( - firstGroup: PrecedenceGroupName?, secondGroup: PrecedenceGroupName? - ) throws -> Associativity { - guard let firstGroup = firstGroup, let secondGroup = secondGroup else { - return .none - } - - // If we have the same group, query its associativity. - if firstGroup == secondGroup { - return try precedenceGraph.lookupGroup(firstGroup).associativity - } - - if try precedence(relating: firstGroup, to: secondGroup) == .higherThan { - return .left - } - - if try precedence(relating: secondGroup, to: firstGroup) == .higherThan { - return .right - } - - return .none - } - - /// "Fold" an expression sequence where the left-hand side has been broken - /// out and (potentially) folded somewhat, and the "rest" of the sequence is - /// consumed along the way - private func fold( - _ lhs: ExprSyntax, rest: inout Slice, - bound: PrecedenceBound - ) throws -> ExprSyntax { - if rest.isEmpty { return lhs } - - // We mutate the left-hand side in place as we fold the sequence. - var lhs = lhs - - /// Get the operator, if appropriate to this pass. - func getNextOperator() throws -> (ExprSyntax, PrecedenceGroupName?)? { - let op = rest.first! - - // If the operator's precedence is lower than the minimum, stop here. - let opPrecedence = lookupPrecedence(of: op) - if try !shouldConsiderOperator(fromGroup: opPrecedence, in: bound) { - return nil - } - - return (op, opPrecedence) - } - - // Extract out the first operator. - guard var (op1, op1Precedence) = try getNextOperator() else { - return lhs - } - - // We will definitely be consuming at least one operator. - // Pull out the prospective RHS and slice off the first two elements. - rest = rest.dropFirst() - var rhs = rest.first! - rest = rest.dropFirst() - - while !rest.isEmpty { - assert( - try! shouldConsiderOperator(fromGroup: op1Precedence, in: bound) - ) - - #if compiler(>=10.0) && false - // If the operator is a cast operator, the RHS can't extend past the type - // that's part of the cast production. - if (isa(op1.op)) { - LHS = makeBinOp(Ctx, op1.op, LHS, RHS, op1.precedence, S.empty()); - op1 = getNextOperator(); - if (!op1) return LHS; - RHS = S[1]; - S = S.slice(2); - continue; - } - #endif - - // Pull out the next binary operator. - let op2 = rest.first! - let op2Precedence = lookupPrecedence(of: op2) - - // If the second operator's precedence is lower than the - // precedence bound, break out of the loop. - if try !shouldConsiderOperator(fromGroup: op2Precedence, in: bound) { - break - } - - let associativity = try associativity( - firstGroup: op1Precedence, secondGroup: op2Precedence - ) - - switch associativity { - case .left: - // Apply left-associativity immediately by folding the first two - // operands. - lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) - op1 = op2 - op1Precedence = op2Precedence - rest = rest.dropFirst() - rhs = rest.first! - rest = rest.dropFirst() - - case .right where op1Precedence != op2Precedence: - // If the first operator's precedence is lower than the second - // operator's precedence, recursively fold all such - // higher-precedence operators starting from this point, then - // repeat. - rhs = try fold( - rhs, rest: &rest, - bound: PrecedenceBound(groupName: op1Precedence, isStrict: true)) - - case .right: - // Apply right-associativity by recursively folding operators - // starting from this point, then immediately folding the LHS and RHS. - rhs = try fold( - rhs, rest: &rest, - bound: PrecedenceBound(groupName: op1Precedence, isStrict: false) - ) - - lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) - - // If we've drained the entire sequence, we're done. - if rest.isEmpty { - return lhs - } - - // Otherwise, start all over with our new LHS. - return try fold(lhs, rest: &rest, bound: bound) - - case .none: - // If we ended up here, it's because we're either: - // - missing precedence groups, - // - have unordered precedence groups, or - // - have the same precedence group with no associativity. - if let op1Precedence = op1Precedence, - let op2Precedence = op2Precedence { - if op1Precedence == op2Precedence { - // FIXME: Must be a nonassociative group, diagnose - } else { - // FIXME: Diagnose unrelated groups - } - } - - // Recover by folding arbitrarily at this operator, then continuing. - lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) - return try fold(lhs, rest: &rest, bound: bound) - } - } - - // Fold LHS and RHS together and declare completion. - return makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) - } - - /// "Fold" an expression sequence into a structured syntax tree. - public func fold(_ sequence: SequenceExprSyntax) throws -> ExprSyntax { - let lhs = sequence.elements.first! - var rest = sequence.elements.dropFirst() - return try fold( - lhs, rest: &rest, bound: PrecedenceBound(groupName: nil, isStrict: false) - ) - } -} diff --git a/Sources/SwiftParser/OperatorPrecedence.swift b/Sources/SwiftParser/OperatorPrecedence.swift index ee8820ce928..c96d07eea10 100644 --- a/Sources/SwiftParser/OperatorPrecedence.swift +++ b/Sources/SwiftParser/OperatorPrecedence.swift @@ -10,6 +10,8 @@ // //===----------------------------------------------------------------------===// +import SwiftSyntax + /// Names a precedence group. /// /// TODO: For now, we'll use strings, but we likely want to move this to @@ -303,3 +305,218 @@ extension OperatorPrecedence { ) } } + +extension OperatorPrecedence { + private struct PrecedenceBound { + let groupName: PrecedenceGroupName? + let isStrict: Bool + } + + /// Determine whether we should consider an operator in the given group + /// based on the specified bound. + private func shouldConsiderOperator( + fromGroup groupName: PrecedenceGroupName?, in bound: PrecedenceBound + ) throws -> Bool { + guard let boundGroupName = bound.groupName else { + return true + } + + guard let groupName = groupName else { + return false + } + + if groupName == boundGroupName { + return !bound.isStrict + } + + return try precedence(relating: groupName, to: boundGroupName) != .lowerThan + } + + /// Look up the precedence group for the given expression syntax. + private func lookupPrecedence(of expr: ExprSyntax) -> PrecedenceGroupName? { + if let binaryExpr = expr.as(BinaryOperatorExprSyntax.self) { + guard let op = operators[binaryExpr.operatorToken.text] else { + // FIXME: Report unknown operator. + return nil + } + + return op.precedenceGroup + } + + // FIXME: Handle all of the language-defined precedence relationships. + return nil + } + + /// Form a binary operation expression for, e.g., a + b. + private func makeBinaryOperationExpr( + lhs: ExprSyntax, op: ExprSyntax, rhs: ExprSyntax + ) -> ExprSyntax { + ExprSyntax( + SyntaxFactory.makeInfixOperatorExpr( + leftOperand: lhs, operatorOperand: op, rightOperand: rhs) + ) + } + + /// Determine the associativity between two precedence groups. + private func associativity( + firstGroup: PrecedenceGroupName?, secondGroup: PrecedenceGroupName? + ) throws -> Associativity { + guard let firstGroup = firstGroup, let secondGroup = secondGroup else { + return .none + } + + // If we have the same group, query its associativity. + if firstGroup == secondGroup { + return try precedenceGraph.lookupGroup(firstGroup).associativity + } + + if try precedence(relating: firstGroup, to: secondGroup) == .higherThan { + return .left + } + + if try precedence(relating: secondGroup, to: firstGroup) == .higherThan { + return .right + } + + return .none + } + + /// "Fold" an expression sequence where the left-hand side has been broken + /// out and (potentially) folded somewhat, and the "rest" of the sequence is + /// consumed along the way + private func fold( + _ lhs: ExprSyntax, rest: inout Slice, + bound: PrecedenceBound + ) throws -> ExprSyntax { + if rest.isEmpty { return lhs } + + // We mutate the left-hand side in place as we fold the sequence. + var lhs = lhs + + /// Get the operator, if appropriate to this pass. + func getNextOperator() throws -> (ExprSyntax, PrecedenceGroupName?)? { + let op = rest.first! + + // If the operator's precedence is lower than the minimum, stop here. + let opPrecedence = lookupPrecedence(of: op) + if try !shouldConsiderOperator(fromGroup: opPrecedence, in: bound) { + return nil + } + + return (op, opPrecedence) + } + + // Extract out the first operator. + guard var (op1, op1Precedence) = try getNextOperator() else { + return lhs + } + + // We will definitely be consuming at least one operator. + // Pull out the prospective RHS and slice off the first two elements. + rest = rest.dropFirst() + var rhs = rest.first! + rest = rest.dropFirst() + + while !rest.isEmpty { + assert( + try! shouldConsiderOperator(fromGroup: op1Precedence, in: bound) + ) + + #if compiler(>=10.0) && false + // If the operator is a cast operator, the RHS can't extend past the type + // that's part of the cast production. + if (isa(op1.op)) { + LHS = makeBinOp(Ctx, op1.op, LHS, RHS, op1.precedence, S.empty()); + op1 = getNextOperator(); + if (!op1) return LHS; + RHS = S[1]; + S = S.slice(2); + continue; + } + #endif + + // Pull out the next binary operator. + let op2 = rest.first! + let op2Precedence = lookupPrecedence(of: op2) + + // If the second operator's precedence is lower than the + // precedence bound, break out of the loop. + if try !shouldConsiderOperator(fromGroup: op2Precedence, in: bound) { + break + } + + let associativity = try associativity( + firstGroup: op1Precedence, secondGroup: op2Precedence + ) + + switch associativity { + case .left: + // Apply left-associativity immediately by folding the first two + // operands. + lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) + op1 = op2 + op1Precedence = op2Precedence + rest = rest.dropFirst() + rhs = rest.first! + rest = rest.dropFirst() + + case .right where op1Precedence != op2Precedence: + // If the first operator's precedence is lower than the second + // operator's precedence, recursively fold all such + // higher-precedence operators starting from this point, then + // repeat. + rhs = try fold( + rhs, rest: &rest, + bound: PrecedenceBound(groupName: op1Precedence, isStrict: true)) + + case .right: + // Apply right-associativity by recursively folding operators + // starting from this point, then immediately folding the LHS and RHS. + rhs = try fold( + rhs, rest: &rest, + bound: PrecedenceBound(groupName: op1Precedence, isStrict: false) + ) + + lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) + + // If we've drained the entire sequence, we're done. + if rest.isEmpty { + return lhs + } + + // Otherwise, start all over with our new LHS. + return try fold(lhs, rest: &rest, bound: bound) + + case .none: + // If we ended up here, it's because we're either: + // - missing precedence groups, + // - have unordered precedence groups, or + // - have the same precedence group with no associativity. + if let op1Precedence = op1Precedence, + let op2Precedence = op2Precedence { + if op1Precedence == op2Precedence { + // FIXME: Must be a nonassociative group, diagnose + } else { + // FIXME: Diagnose unrelated groups + } + } + + // Recover by folding arbitrarily at this operator, then continuing. + lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) + return try fold(lhs, rest: &rest, bound: bound) + } + } + + // Fold LHS and RHS together and declare completion. + return makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) + } + + /// "Fold" an expression sequence into a structured syntax tree. + public func fold(_ sequence: SequenceExprSyntax) throws -> ExprSyntax { + let lhs = sequence.elements.first! + var rest = sequence.elements.dropFirst() + return try fold( + lhs, rest: &rest, bound: PrecedenceBound(groupName: nil, isStrict: false) + ) + } +} diff --git a/Tests/SwiftParserTest/OperatorPrecedence.swift b/Tests/SwiftParserTest/OperatorPrecedence.swift index e50ac0e28d4..c01c2ebe210 100644 --- a/Tests/SwiftParserTest/OperatorPrecedence.swift +++ b/Tests/SwiftParserTest/OperatorPrecedence.swift @@ -25,7 +25,8 @@ public class OperatorPrecedenceTests: XCTestCase { let sequenceExpr = parsed.statements.first!.item.as(SequenceExprSyntax.self)! let foldedExpr = try opPrecedence.fold(sequenceExpr) - XCTAssertEqual("\(foldedExpr)", "(((x && y )|| (w && v ))|| z)") + XCTAssertEqual("\(foldedExpr)", "x && y || w && v || z") + XCTAssertNil(foldedExpr.as(SequenceExprSyntax.self)) } func testParsedLogicalExprs() throws { @@ -57,7 +58,7 @@ public class OperatorPrecedenceTests: XCTestCase { let sequenceExpr = parsed.statements.first!.item.as(SequenceExprSyntax.self)! let foldedExpr = try opPrecedence.fold(sequenceExpr) - XCTAssertEqual("\(foldedExpr)", "(((x && y )|| (w && v ))|| z)") - + XCTAssertEqual("\(foldedExpr)", "x && y || w && v || z") + XCTAssertNil(foldedExpr.as(SequenceExprSyntax.self)) } } From f4a47d3661e9090be0beaefda76fd99ec74085c6 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 5 Aug 2022 22:25:21 -0700 Subject: [PATCH 03/50] Restrict access to a number of operator-precedence-related APIs --- Sources/SwiftParser/OperatorPrecedence.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftParser/OperatorPrecedence.swift b/Sources/SwiftParser/OperatorPrecedence.swift index c96d07eea10..126532062ea 100644 --- a/Sources/SwiftParser/OperatorPrecedence.swift +++ b/Sources/SwiftParser/OperatorPrecedence.swift @@ -118,15 +118,15 @@ public enum Precedence { /// A graph formed from a set of precedence groups, which can be used to /// determine the relative precedence of two precedence groups. -public struct PrecedenceGraph { +struct PrecedenceGraph { /// The known set of precedence groups, found by name. - public var precedenceGroups: [PrecedenceGroupName : PrecedenceGroup] = [:] + var precedenceGroups: [PrecedenceGroupName : PrecedenceGroup] = [:] /// Add a new precedence group /// /// - throws: If there is already a precedence group with the given name, /// throws PrecedenceGraphError.groupAlreadyExists. - public mutating func add(_ group: PrecedenceGroup) throws { + mutating func add(_ group: PrecedenceGroup) throws { if let existing = precedenceGroups[group.name] { throw OperatorPrecedenceError.groupAlreadyExists( existing: existing, new: group) @@ -137,7 +137,7 @@ public struct PrecedenceGraph { /// Look for the precedence group with the given name, or produce an error /// if such a group is not known. - public func lookupGroup( + func lookupGroup( _ groupName: PrecedenceGroupName ) throws -> PrecedenceGroup { guard let group = precedenceGroups[groupName] else { @@ -151,7 +151,7 @@ public struct PrecedenceGraph { /// /// Follow the precedence relationships among the precedence groups to /// determine the precedence of the start group relative to the end group. - public func precedence( + func precedence( relating startGroupName: PrecedenceGroupName, to endGroupName: PrecedenceGroupName ) throws -> Precedence { @@ -269,7 +269,7 @@ public struct OperatorPrecedence { extension OperatorPrecedence { /// Look for the precedence group corresponding to the given operator. - public func lookupOperatorPrecedenceGroupName( + func lookupOperatorPrecedenceGroupName( _ operatorName: OperatorName ) throws -> PrecedenceGroupName? { guard let op = operators[operatorName] else { @@ -280,7 +280,7 @@ extension OperatorPrecedence { } /// Look for the precedence group corresponding to the given operator. - public func lookupOperatorPrecedenceGroup( + func lookupOperatorPrecedenceGroup( _ operatorName: OperatorName ) throws -> PrecedenceGroup? { guard let groupName = try lookupOperatorPrecedenceGroupName(operatorName) @@ -291,7 +291,7 @@ extension OperatorPrecedence { } /// Determine the relative precedence between two precedence groups. - public func precedence( + func precedence( relating startGroupName: PrecedenceGroupName?, to endGroupName: PrecedenceGroupName? ) throws -> Precedence { From b838b33f4c581057d9784c90fae091cd3c656db6 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 5 Aug 2022 22:50:01 -0700 Subject: [PATCH 04/50] Add standard and logical operator definitions --- Sources/SwiftParser/OperatorPrecedence.swift | 473 +++++++++++++++++- .../SwiftParserTest/OperatorPrecedence.swift | 28 +- 2 files changed, 482 insertions(+), 19 deletions(-) diff --git a/Sources/SwiftParser/OperatorPrecedence.swift b/Sources/SwiftParser/OperatorPrecedence.swift index 126532062ea..782a45d24bd 100644 --- a/Sources/SwiftParser/OperatorPrecedence.swift +++ b/Sources/SwiftParser/OperatorPrecedence.swift @@ -83,8 +83,10 @@ public struct PrecedenceGroup { public var relations: [PrecedenceRelation] = [] public init( - name: PrecedenceGroupName, associativity: Associativity, assignment: Bool, - relations: [PrecedenceRelation] + name: PrecedenceGroupName, + associativity: Associativity = .none, + assignment: Bool = false, + relations: [PrecedenceRelation] = [] ) { self.name = name self.associativity = associativity @@ -267,6 +269,473 @@ public struct OperatorPrecedence { } } +/// Prefabricated operator precedence graphs. +extension OperatorPrecedence { + /// Operator precedence graph for the logical operators '&&' and '||', for + /// example as it is used in `#if` processing. + public static var logicalOperators: OperatorPrecedence { + var opPrecedence = OperatorPrecedence() + + try! opPrecedence.record( + Operator(kind: .infix, name: "&&", + precedenceGroup: "LogicalConjunctionPrecedence")) + try! opPrecedence.record( + Operator(kind: .infix, name: "||", + precedenceGroup: "LogicalDisjunctionPrecedence")) + try! opPrecedence.record( + PrecedenceGroup(name: "LogicalConjunctionPrecedence", + associativity: .left, assignment: false, + relations: [.higherThan("LogicalDisjunctionPrecedence")])) + try! opPrecedence.record( + PrecedenceGroup(name: "LogicalDisjunctionPrecedence", + associativity: .left, assignment: false, + relations: [])) + return opPrecedence + } + + /// Operator precedence graph for the Swift standard library. + /// + /// This describes the operators within the Swift standard library at the + /// type of this writing. It can be used to approximate the behavior one + /// would get from parsing the actual Swift standard library's operators + /// without requiring access to the standard library source code. However, + /// because it does not incorporate user-defined operators, it will only + /// ever be useful for a quick approximation. + public static var standardOperators: OperatorPrecedence { + var opPrecedence = OperatorPrecedence() + + try! opPrecedence.record( + PrecedenceGroup( + name: "AssignmentPrecedence", + associativity: .right, + assignment: true + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "FunctionArrowPrecedence", + associativity: .right, + relations: [.higherThan("AssignmentPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "TernaryPrecedence", + associativity: .right, + relations: [.higherThan("FunctionArrowPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "DefaultPrecedence", + relations: [.higherThan("TernaryPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "LogicalDisjunctionPrecedence", + associativity: .left, + relations: [.higherThan("TernaryPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "LogicalConjunctionPrecedence", + associativity: .left, + relations: [.higherThan("LogicalDisjunctionPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "ComparisonPrecedence", + relations: [.higherThan("LogicalConjunctionPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "NilCoalescingPrecedence", + associativity: .right, + relations: [.higherThan("ComparisonPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "CastingPrecedence", + relations: [.higherThan("NilCoalescingPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "RangeFormationPrecedence", + relations: [.higherThan("CastingPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "AdditionPrecedence", + associativity: .left, + relations: [.higherThan("RangeFormationPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "MultiplicationPrecedence", + associativity: .left, + relations: [.higherThan("AdditionPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "BitwiseShiftPrecedence", + relations: [.higherThan("MultiplicationPrecedence")] + ) + ) + + // "Exponentiative" + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "<<", + precedenceGroup: "BitwiseShiftPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&<<", + precedenceGroup: "BitwiseShiftPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: ">>", + precedenceGroup: "BitwiseShiftPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&>>", + precedenceGroup: "BitwiseShiftPrecedence" + ) + ) + + // "Multiplicative" + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "*", + precedenceGroup: "MultiplicationPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&*", + precedenceGroup: "MultiplicationPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "/", + precedenceGroup: "MultiplicationPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "%", + precedenceGroup: "MultiplicationPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&", + precedenceGroup: "MultiplicationPrecedence" + ) + ) + + // "Additive" + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "+", + precedenceGroup: "AdditionPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&+", + precedenceGroup: "AdditionPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "-", + precedenceGroup: "AdditionPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&-", + precedenceGroup: "AdditionPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "|", + precedenceGroup: "AdditionPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "^", + precedenceGroup: "AdditionPrecedence" + ) + ) + + // FIXME: is this the right precedence level for "..."? + try! opPrecedence.record( + Operator( + kind: .infix, + name: "...", + precedenceGroup: "RangeFormationPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "..<", + precedenceGroup: "RangeFormationPrecedence" + ) + ) + + // "Coalescing" + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "??", + precedenceGroup: "NilCoalescingPrecedence" + ) + ) + + // "Comparative" + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "<", + precedenceGroup: "ComparisonPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "<=", + precedenceGroup: "ComparisonPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: ">", + precedenceGroup: "ComparisonPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: ">=", + precedenceGroup: "ComparisonPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "==", + precedenceGroup: "ComparisonPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "!=", + precedenceGroup: "ComparisonPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "===", + precedenceGroup: "ComparisonPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "!==", + precedenceGroup: "ComparisonPrecedence" + ) + ) + // FIXME: ~= will be built into the compiler. + try! opPrecedence.record( + Operator( + kind: .infix, + name: "~=", + precedenceGroup: "ComparisonPrecedence" + ) + ) + + // "Conjunctive" + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&&", + precedenceGroup: "LogicalConjunctionPrecedence" + ) + ) + + // "Disjunctive" + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "||", + precedenceGroup: "LogicalDisjunctionPrecedence" + ) + ) + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "*=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&*=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "/=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "%=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "+=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&+=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "-=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&-=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "<<=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&<<=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: ">>=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&>>=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "^=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "|=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "~>", + precedenceGroup: nil + ) + ) + + return opPrecedence + } +} + extension OperatorPrecedence { /// Look for the precedence group corresponding to the given operator. func lookupOperatorPrecedenceGroupName( diff --git a/Tests/SwiftParserTest/OperatorPrecedence.swift b/Tests/SwiftParserTest/OperatorPrecedence.swift index c01c2ebe210..67840e16c20 100644 --- a/Tests/SwiftParserTest/OperatorPrecedence.swift +++ b/Tests/SwiftParserTest/OperatorPrecedence.swift @@ -4,23 +4,7 @@ import SwiftParser public class OperatorPrecedenceTests: XCTestCase { func testLogicalExprs() throws { - var opPrecedence = OperatorPrecedence() - - try opPrecedence.record( - Operator(kind: .infix, name: "&&", - precedenceGroup: "LogicalConjunctionPrecedence")) - try opPrecedence.record( - Operator(kind: .infix, name: "||", - precedenceGroup: "LogicalDisjunctionPrecedence")) - try opPrecedence.record( - PrecedenceGroup(name: "LogicalConjunctionPrecedence", - associativity: .left, assignment: false, - relations: [.higherThan("LogicalDisjunctionPrecedence")])) - try opPrecedence.record( - PrecedenceGroup(name: "LogicalDisjunctionPrecedence", - associativity: .left, assignment: false, - relations: [])) - + let opPrecedence = OperatorPrecedence.logicalOperators let parsed = try Parser.parse(source: "x && y || w && v || z") let sequenceExpr = parsed.statements.first!.item.as(SequenceExprSyntax.self)! @@ -29,6 +13,16 @@ public class OperatorPrecedenceTests: XCTestCase { XCTAssertNil(foldedExpr.as(SequenceExprSyntax.self)) } + func testSwiftExprs() throws { + let opPrecedence = OperatorPrecedence.standardOperators + let parsed = try Parser.parse(source: "(x + y > 17) && x && y || w && v || z") + let sequenceExpr = + parsed.statements.first!.item.as(SequenceExprSyntax.self)! + let foldedExpr = try opPrecedence.fold(sequenceExpr) + XCTAssertEqual("\(foldedExpr)", "(x + y > 17) && x && y || w && v || z") + XCTAssertNil(foldedExpr.as(SequenceExprSyntax.self)) + } + func testParsedLogicalExprs() throws { let logicalOperatorSources = """ From 7240dd5907def39ebc86f2f98a3acd5151d88f6c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 5 Aug 2022 23:03:02 -0700 Subject: [PATCH 05/50] Use a syntax visitor for adding operators and prededence groups from source file Once the parser switches over to building raw syntax syntax trees, this will allow us to realize fewer nodes. --- .../OperatorPrecedence+Parsing.swift | 65 +++++++++++++++++-- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/Sources/SwiftParser/OperatorPrecedence+Parsing.swift b/Sources/SwiftParser/OperatorPrecedence+Parsing.swift index c23beaa7431..be448b792d1 100644 --- a/Sources/SwiftParser/OperatorPrecedence+Parsing.swift +++ b/Sources/SwiftParser/OperatorPrecedence+Parsing.swift @@ -87,16 +87,67 @@ extension OperatorPrecedence { /// Integrate the operator and precedence group declarations from the given /// source file into the operator precedence tables. public mutating func addSourceFile(_ sourceFile: SourceFileSyntax) throws { - for stmt in sourceFile.statements { - if let operatorSyntax = stmt.item.as(OperatorDeclSyntax.self) { - try record(Operator(from: operatorSyntax)) - continue + class OperatorAndGroupVisitor : SyntaxAnyVisitor { + var opPrecedence: OperatorPrecedence + var errors: [Error] = [] + + init(opPrecedence: OperatorPrecedence) { + self.opPrecedence = opPrecedence + super.init(viewMode: .fixedUp) } - if let precedenceGroupSyntax = stmt.item.as(PrecedenceGroupDeclSyntax.self) { - try record(PrecedenceGroup(from: precedenceGroupSyntax)) - continue + override func visit( + _ node: OperatorDeclSyntax + ) -> SyntaxVisitorContinueKind { + do { + try opPrecedence.record(Operator(from: node)) + } catch { + errors.append(error) + } + return .skipChildren + } + + override func visit( + _ node: PrecedenceGroupDeclSyntax + ) -> SyntaxVisitorContinueKind { + do { + try opPrecedence.record(PrecedenceGroup(from: node)) + } catch { + errors.append(error) + } + return .skipChildren + } + + // Only visit top-level entities to find operators and precedence groups. + override func visit( + _ node: SourceFileSyntax + ) -> SyntaxVisitorContinueKind { + return .visitChildren } + + override func visit( + _ node: CodeBlockItemListSyntax + ) -> SyntaxVisitorContinueKind { + return .visitChildren + } + + override func visit( + _ node: CodeBlockItemSyntax + ) -> SyntaxVisitorContinueKind { + return .visitChildren + } + + // Everything else stops the visitation. + override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind{ + return .skipChildren + } + } + + let visitor = OperatorAndGroupVisitor(opPrecedence: self) + _ = visitor.visit(sourceFile) + if let firstError = visitor.errors.first { + throw firstError } + self = visitor.opPrecedence } } From 495006895a24005b6d734749a39bf68cc0368632 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 16 Aug 2022 22:02:29 -0700 Subject: [PATCH 06/50] Don't use deprecated factory method --- Sources/SwiftParser/OperatorPrecedence.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftParser/OperatorPrecedence.swift b/Sources/SwiftParser/OperatorPrecedence.swift index 782a45d24bd..4f27d582126 100644 --- a/Sources/SwiftParser/OperatorPrecedence.swift +++ b/Sources/SwiftParser/OperatorPrecedence.swift @@ -821,7 +821,7 @@ extension OperatorPrecedence { lhs: ExprSyntax, op: ExprSyntax, rhs: ExprSyntax ) -> ExprSyntax { ExprSyntax( - SyntaxFactory.makeInfixOperatorExpr( + InfixOperatorExprSyntax( leftOperand: lhs, operatorOperand: op, rightOperand: rhs) ) } From a4336b1f2f7e5dff1baa4a2a464deecf598c0847 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 16 Aug 2022 22:15:38 -0700 Subject: [PATCH 07/50] Start tracking syntax nodes for operator and precedence group declarations --- .../OperatorPrecedence+Parsing.swift | 10 ++- Sources/SwiftParser/OperatorPrecedence.swift | 62 +++++++++++++++---- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/Sources/SwiftParser/OperatorPrecedence+Parsing.swift b/Sources/SwiftParser/OperatorPrecedence+Parsing.swift index be448b792d1..1ca3b5b2da5 100644 --- a/Sources/SwiftParser/OperatorPrecedence+Parsing.swift +++ b/Sources/SwiftParser/OperatorPrecedence+Parsing.swift @@ -17,6 +17,7 @@ extension PrecedenceGroup { /// TODO: This ignores all semantic errors. init(from syntax: PrecedenceGroupDeclSyntax) { self.name = syntax.identifier.text + self.syntax = syntax for attr in syntax.groupAttributes { // Relation (lowerThan, higherThan) @@ -24,8 +25,11 @@ extension PrecedenceGroup { let isLowerThan = relation.higherThanOrLowerThan.text == "lowerThan" for otherGroup in relation.otherNames { let otherGroupName = otherGroup.name.text - self.relations.append(isLowerThan ? .lowerThan(otherGroupName) - : .higherThan(otherGroupName)) + let relationKind: PrecedenceRelation.Kind = isLowerThan ? .lowerThan + : .higherThan + let relation = PrecedenceRelation( + kind: relationKind, groupName: otherGroupName, syntax: otherGroup) + self.relations.append(relation) } continue @@ -62,6 +66,8 @@ extension Operator { /// /// TODO: This ignores all semantic errors. init(from syntax: OperatorDeclSyntax) { + self.syntax = syntax + let kindModifier = syntax.modifiers?.first { modifier in OperatorKind(rawValue: modifier.name.text) != nil } diff --git a/Sources/SwiftParser/OperatorPrecedence.swift b/Sources/SwiftParser/OperatorPrecedence.swift index 4f27d582126..d6a842ab62d 100644 --- a/Sources/SwiftParser/OperatorPrecedence.swift +++ b/Sources/SwiftParser/OperatorPrecedence.swift @@ -43,15 +43,41 @@ public enum Associativity: String { case right } -/// Describes the relationship between two different precedence groups. -public enum PrecedenceRelation { - /// The precedence group storing this relation has higher precedence than - /// the named group. - case higherThan(PrecedenceGroupName) - - /// The precedence group storing this relation has lower precedence than - /// the named group. - case lowerThan(PrecedenceGroupName) +/// Describes the relationship of a precedence group to another precedence +/// group. +public struct PrecedenceRelation { + /// Describes the kind of a precedence relation. + public enum Kind { + case higherThan + case lowerThan + } + + /// The relationship to the other group. + public var kind: Kind + + /// The group name. + public var groupName: PrecedenceGroupName + + /// The syntax that provides the relation. This specifically refers to the + /// group name itself, but one can follow the parent pointer to find its + /// position. + public var syntax: PrecedenceGroupNameElementSyntax? + + /// Return a higher-than precedence relation. + public static func higherThan( + _ groupName: PrecedenceGroupName, + syntax: PrecedenceGroupNameElementSyntax? = nil + ) -> PrecedenceRelation { + return .init(kind: .higherThan, groupName: groupName, syntax: syntax) + } + + /// Return a lower-than precedence relation. + public static func lowerThan( + _ groupName: PrecedenceGroupName, + syntax: PrecedenceGroupNameElementSyntax? = nil + ) -> PrecedenceRelation { + return .init(kind: .lowerThan, groupName: groupName, syntax: syntax) + } } /// Precedence groups are used for parsing sequences of expressions in Swift @@ -82,16 +108,21 @@ public struct PrecedenceGroup { /// this precedence group. public var relations: [PrecedenceRelation] = [] + /// The syntax node that describes this precedence group. + public var syntax: PrecedenceGroupDeclSyntax? = nil + public init( name: PrecedenceGroupName, associativity: Associativity = .none, assignment: Bool = false, - relations: [PrecedenceRelation] = [] + relations: [PrecedenceRelation] = [], + syntax: PrecedenceGroupDeclSyntax? = nil ) { self.name = name self.associativity = associativity self.assignment = assignment self.relations = relations + self.syntax = syntax } } @@ -172,8 +203,9 @@ struct PrecedenceGraph { let currentGroup = try lookupGroup(currentGroupName) for relation in currentGroup.relations { - if case let .lowerThan(otherGroupName) = relation { + if relation.kind == .lowerThan { // If we hit our start group, we're done. + let otherGroupName = relation.groupName if otherGroupName == startGroupName { return .lowerThan } @@ -194,8 +226,9 @@ struct PrecedenceGraph { let currentGroup = try lookupGroup(currentGroupName) for relation in currentGroup.relations { - if case let .higherThan(otherGroupName) = relation { + if relation.kind == .higherThan { // If we hit our end group, we're done. + let otherGroupName = relation.groupName if otherGroupName == endGroupName { return .higherThan } @@ -229,14 +262,17 @@ public struct Operator { public let kind: OperatorKind public let name: OperatorName public let precedenceGroup: PrecedenceGroupName? + public let syntax: OperatorDeclSyntax? public init( kind: OperatorKind, name: OperatorName, - precedenceGroup: PrecedenceGroupName? + precedenceGroup: PrecedenceGroupName?, + syntax: OperatorDeclSyntax? = nil ) { self.kind = kind self.name = name self.precedenceGroup = precedenceGroup + self.syntax = syntax } } From 284d90afa3fdf402945ee1c45834643e5db99bec Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 16 Aug 2022 23:35:31 -0700 Subject: [PATCH 08/50] Report errors through an optionally-throwing error handler. When the operator-precedence parser encounters errors at any point, provide the errors to an error handler and then keep going. This makes all of the entrypoints rethrowing, so clients can bail out early if they wish (the default handler just throws the error) or can record/report the errors and continue with some default behavior. --- .../OperatorPrecedence+Parsing.swift | 31 ++- Sources/SwiftParser/OperatorPrecedence.swift | 249 ++++++++++++------ .../SwiftParserTest/OperatorPrecedence.swift | 25 ++ 3 files changed, 213 insertions(+), 92 deletions(-) diff --git a/Sources/SwiftParser/OperatorPrecedence+Parsing.swift b/Sources/SwiftParser/OperatorPrecedence+Parsing.swift index 1ca3b5b2da5..b17e2faf7e5 100644 --- a/Sources/SwiftParser/OperatorPrecedence+Parsing.swift +++ b/Sources/SwiftParser/OperatorPrecedence+Parsing.swift @@ -92,35 +92,36 @@ extension Operator { extension OperatorPrecedence { /// Integrate the operator and precedence group declarations from the given /// source file into the operator precedence tables. - public mutating func addSourceFile(_ sourceFile: SourceFileSyntax) throws { + public mutating func addSourceFile( + _ sourceFile: SourceFileSyntax, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows { class OperatorAndGroupVisitor : SyntaxAnyVisitor { var opPrecedence: OperatorPrecedence - var errors: [Error] = [] + var errors: [OperatorPrecedenceError] = [] init(opPrecedence: OperatorPrecedence) { self.opPrecedence = opPrecedence super.init(viewMode: .fixedUp) } + private func errorHandler(error: OperatorPrecedenceError) { + errors.append(error) + } + override func visit( _ node: OperatorDeclSyntax ) -> SyntaxVisitorContinueKind { - do { - try opPrecedence.record(Operator(from: node)) - } catch { - errors.append(error) - } + opPrecedence.record( + Operator(from: node), errorHandler: errorHandler) return .skipChildren } override func visit( _ node: PrecedenceGroupDeclSyntax ) -> SyntaxVisitorContinueKind { - do { - try opPrecedence.record(PrecedenceGroup(from: node)) - } catch { - errors.append(error) - } + opPrecedence.record( + PrecedenceGroup(from: node), errorHandler: errorHandler) return .skipChildren } @@ -150,10 +151,8 @@ extension OperatorPrecedence { } let visitor = OperatorAndGroupVisitor(opPrecedence: self) - _ = visitor.visit(sourceFile) - if let firstError = visitor.errors.first { - throw firstError - } + visitor.walk(sourceFile) + try visitor.errors.forEach(errorHandler) self = visitor.opPrecedence } } diff --git a/Sources/SwiftParser/OperatorPrecedence.swift b/Sources/SwiftParser/OperatorPrecedence.swift index d6a842ab62d..8a224858ee1 100644 --- a/Sources/SwiftParser/OperatorPrecedence.swift +++ b/Sources/SwiftParser/OperatorPrecedence.swift @@ -133,15 +133,26 @@ public enum OperatorPrecedenceError: Error { case groupAlreadyExists(existing: PrecedenceGroup, new: PrecedenceGroup) /// The named precedence group is missing from the precedence graph. - case missingGroup(PrecedenceGroupName) + case missingGroup(PrecedenceGroupName, referencedFrom: SyntaxProtocol?) /// Error produced when a given operator already exists. case operatorAlreadyExists(existing: Operator, new: Operator) /// The named operator is missing from the precedence graph. - case missingOperator(OperatorName) + case missingOperator(OperatorName, referencedFrom: SyntaxProtocol?) } +/// A function that receives an operator precedence error and may do with it +/// whatever it likes. +/// +/// Operator precedence error handlers are passed into each function in the +/// operator-precedence parser that can produce a failure. The handler +/// may choose to throw (in which case the error will propagate outward) or +/// may separately record/drop the error and return without throwing (in +/// which case the operator-precedence parser will recover). +public typealias OperatorPrecedenceErrorHandler = + (OperatorPrecedenceError) throws -> Void + /// Describes the relative precedence of two groups. public enum Precedence { case unrelated @@ -159,25 +170,23 @@ struct PrecedenceGraph { /// /// - throws: If there is already a precedence group with the given name, /// throws PrecedenceGraphError.groupAlreadyExists. - mutating func add(_ group: PrecedenceGroup) throws { + mutating func add( + _ group: PrecedenceGroup, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows { if let existing = precedenceGroups[group.name] { - throw OperatorPrecedenceError.groupAlreadyExists( - existing: existing, new: group) + try errorHandler( + OperatorPrecedenceError.groupAlreadyExists( + existing: existing, new: group)) + } else { + precedenceGroups[group.name] = group } - - precedenceGroups[group.name] = group } - /// Look for the precedence group with the given name, or produce an error - /// if such a group is not known. - func lookupGroup( - _ groupName: PrecedenceGroupName - ) throws -> PrecedenceGroup { - guard let group = precedenceGroups[groupName] else { - throw OperatorPrecedenceError.missingGroup(groupName) - } - - return group + /// Look for the precedence group with the given name, or return nil if + /// no such group is known. + func lookupGroup(_ groupName: PrecedenceGroupName) -> PrecedenceGroup? { + return precedenceGroups[groupName] } /// Determine the precedence relationship between two precedence groups. @@ -186,8 +195,11 @@ struct PrecedenceGraph { /// determine the precedence of the start group relative to the end group. func precedence( relating startGroupName: PrecedenceGroupName, - to endGroupName: PrecedenceGroupName - ) throws -> Precedence { + to endGroupName: PrecedenceGroupName, + startSyntax: SyntaxProtocol?, + endSyntax: SyntaxProtocol?, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows -> Precedence { if startGroupName == endGroupName { return .unrelated } @@ -198,9 +210,14 @@ struct PrecedenceGraph { // Walk all of the lower-than relationships from the end group. If we // reach the start group, the start has lower precedence than the end. - var stack: [PrecedenceGroupName] = [endGroupName] - while let currentGroupName = stack.popLast() { - let currentGroup = try lookupGroup(currentGroupName) + var stack: [(PrecedenceGroupName, SyntaxProtocol?)] = + [(endGroupName, endSyntax)] + while let (currentGroupName, currentGroupSyntax) = stack.popLast() { + guard let currentGroup = lookupGroup(currentGroupName) else { + try errorHandler( + .missingGroup(currentGroupName, referencedFrom: currentGroupSyntax)) + continue + } for relation in currentGroup.relations { if relation.kind == .lowerThan { @@ -211,7 +228,7 @@ struct PrecedenceGraph { } if !groupsSeen.insert(otherGroupName).inserted { - stack.append(otherGroupName) + stack.append((otherGroupName, relation.syntax)) } } } @@ -221,9 +238,13 @@ struct PrecedenceGraph { // reach the end group, the start has higher precedence than the end. assert(stack.isEmpty) groupsSeen.removeAll() - stack.append(startGroupName) - while let currentGroupName = stack.popLast() { - let currentGroup = try lookupGroup(currentGroupName) + stack.append((startGroupName, startSyntax)) + while let (currentGroupName, currentGroupSyntax) = stack.popLast() { + guard let currentGroup = lookupGroup(currentGroupName) else { + try errorHandler( + .missingGroup(currentGroupName, referencedFrom: currentGroupSyntax)) + continue + } for relation in currentGroup.relations { if relation.kind == .higherThan { @@ -234,7 +255,7 @@ struct PrecedenceGraph { } if !groupsSeen.insert(otherGroupName).inserted { - stack.append(otherGroupName) + stack.append((otherGroupName, relation.syntax)) } } } @@ -287,21 +308,28 @@ public struct OperatorPrecedence { /// Record the operator, if it matters. /// FIXME: Terrible API used only for tests - public mutating func record(_ op: Operator) throws { + public mutating func record( + _ op: Operator, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows { + // FIXME: Could do operator-already-exists checking for prefix/postfix + // operators as well, since we parse them. if op.kind != .infix { return } if let existing = operators[op.name] { - throw OperatorPrecedenceError.operatorAlreadyExists( - existing: existing, new: op) + try errorHandler(.operatorAlreadyExists(existing: existing, new: op)) + } else { + operators[op.name] = op } - - operators[op.name] = op } /// Record the precedence group. /// FIXME: Terrible API used only for tests - public mutating func record(_ group: PrecedenceGroup) throws { - try precedenceGraph.add(group) + public mutating func record( + _ group: PrecedenceGroup, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows { + try precedenceGraph.add(group, errorHandler: errorHandler) } } @@ -775,10 +803,14 @@ extension OperatorPrecedence { extension OperatorPrecedence { /// Look for the precedence group corresponding to the given operator. func lookupOperatorPrecedenceGroupName( - _ operatorName: OperatorName - ) throws -> PrecedenceGroupName? { + _ operatorName: OperatorName, + referencedFrom syntax: SyntaxProtocol?, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows -> PrecedenceGroupName? { guard let op = operators[operatorName] else { - throw OperatorPrecedenceError.missingOperator(operatorName) + try errorHandler( + .missingOperator(operatorName, referencedFrom: syntax)) + return nil } return op.precedenceGroup @@ -786,27 +818,41 @@ extension OperatorPrecedence { /// Look for the precedence group corresponding to the given operator. func lookupOperatorPrecedenceGroup( - _ operatorName: OperatorName - ) throws -> PrecedenceGroup? { - guard let groupName = try lookupOperatorPrecedenceGroupName(operatorName) + _ operatorName: OperatorName, + referencedFrom syntax: SyntaxProtocol?, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows -> PrecedenceGroup? { + guard let groupName = try lookupOperatorPrecedenceGroupName( + operatorName, referencedFrom: syntax, errorHandler: errorHandler) else { return nil } - return try precedenceGraph.lookupGroup(groupName) + + guard let group = precedenceGraph.lookupGroup(groupName) else { + try errorHandler(.missingGroup(groupName, referencedFrom: syntax)) + return nil + } + + return group } /// Determine the relative precedence between two precedence groups. func precedence( relating startGroupName: PrecedenceGroupName?, - to endGroupName: PrecedenceGroupName? - ) throws -> Precedence { + to endGroupName: PrecedenceGroupName?, + startSyntax: SyntaxProtocol?, + endSyntax: SyntaxProtocol?, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows -> Precedence { guard let startGroupName = startGroupName, let endGroupName = endGroupName else { return .unrelated } return try precedenceGraph.precedence( - relating: startGroupName, to: endGroupName + relating: startGroupName, to: endGroupName, + startSyntax: startSyntax, endSyntax: endSyntax, + errorHandler: errorHandler ) } } @@ -815,13 +861,17 @@ extension OperatorPrecedence { private struct PrecedenceBound { let groupName: PrecedenceGroupName? let isStrict: Bool + let syntax: SyntaxProtocol? } /// Determine whether we should consider an operator in the given group /// based on the specified bound. private func shouldConsiderOperator( - fromGroup groupName: PrecedenceGroupName?, in bound: PrecedenceBound - ) throws -> Bool { + fromGroup groupName: PrecedenceGroupName?, + in bound: PrecedenceBound, + fromGroupSyntax: SyntaxProtocol?, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows -> Bool { guard let boundGroupName = bound.groupName else { return true } @@ -834,18 +884,25 @@ extension OperatorPrecedence { return !bound.isStrict } - return try precedence(relating: groupName, to: boundGroupName) != .lowerThan + return try precedence( + relating: groupName, to: boundGroupName, + startSyntax: fromGroupSyntax, endSyntax: bound.syntax, + errorHandler: errorHandler + ) != .lowerThan } /// Look up the precedence group for the given expression syntax. - private func lookupPrecedence(of expr: ExprSyntax) -> PrecedenceGroupName? { + private func lookupPrecedence( + of expr: ExprSyntax, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows -> PrecedenceGroupName? { + // A binary operator. if let binaryExpr = expr.as(BinaryOperatorExprSyntax.self) { - guard let op = operators[binaryExpr.operatorToken.text] else { - // FIXME: Report unknown operator. - return nil - } - - return op.precedenceGroup + let operatorName = binaryExpr.operatorToken.text + return try lookupOperatorPrecedenceGroupName( + operatorName, referencedFrom: binaryExpr.operatorToken, + errorHandler: errorHandler + ) } // FIXME: Handle all of the language-defined precedence relationships. @@ -864,22 +921,40 @@ extension OperatorPrecedence { /// Determine the associativity between two precedence groups. private func associativity( - firstGroup: PrecedenceGroupName?, secondGroup: PrecedenceGroupName? - ) throws -> Associativity { + firstGroup: PrecedenceGroupName?, + firstGroupSyntax: SyntaxProtocol?, + secondGroup: PrecedenceGroupName?, + secondGroupSyntax: SyntaxProtocol?, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows -> Associativity { guard let firstGroup = firstGroup, let secondGroup = secondGroup else { return .none } // If we have the same group, query its associativity. if firstGroup == secondGroup { - return try precedenceGraph.lookupGroup(firstGroup).associativity + guard let group = precedenceGraph.lookupGroup(firstGroup) else { + try errorHandler( + .missingGroup(firstGroup, referencedFrom: firstGroupSyntax)) + return .none + } + + return group.associativity } - if try precedence(relating: firstGroup, to: secondGroup) == .higherThan { + if try precedence( + relating: firstGroup, to: secondGroup, + startSyntax: firstGroupSyntax, endSyntax: secondGroupSyntax, + errorHandler: errorHandler + ) == .higherThan { return .left } - if try precedence(relating: secondGroup, to: firstGroup) == .higherThan { + if try precedence( + relating: secondGroup, to: firstGroup, + startSyntax: secondGroupSyntax, endSyntax: firstGroupSyntax, + errorHandler: errorHandler + ) == .higherThan { return .right } @@ -891,8 +966,9 @@ extension OperatorPrecedence { /// consumed along the way private func fold( _ lhs: ExprSyntax, rest: inout Slice, - bound: PrecedenceBound - ) throws -> ExprSyntax { + bound: PrecedenceBound, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows -> ExprSyntax { if rest.isEmpty { return lhs } // We mutate the left-hand side in place as we fold the sequence. @@ -903,8 +979,11 @@ extension OperatorPrecedence { let op = rest.first! // If the operator's precedence is lower than the minimum, stop here. - let opPrecedence = lookupPrecedence(of: op) - if try !shouldConsiderOperator(fromGroup: opPrecedence, in: bound) { + let opPrecedence = try lookupPrecedence( + of: op, errorHandler: errorHandler) + if try !shouldConsiderOperator( + fromGroup: opPrecedence, in: bound, fromGroupSyntax: op + ) { return nil } @@ -923,10 +1002,6 @@ extension OperatorPrecedence { rest = rest.dropFirst() while !rest.isEmpty { - assert( - try! shouldConsiderOperator(fromGroup: op1Precedence, in: bound) - ) - #if compiler(>=10.0) && false // If the operator is a cast operator, the RHS can't extend past the type // that's part of the cast production. @@ -942,16 +1017,24 @@ extension OperatorPrecedence { // Pull out the next binary operator. let op2 = rest.first! - let op2Precedence = lookupPrecedence(of: op2) + let op2Precedence = try lookupPrecedence( + of: op2, errorHandler: errorHandler) // If the second operator's precedence is lower than the // precedence bound, break out of the loop. - if try !shouldConsiderOperator(fromGroup: op2Precedence, in: bound) { + if try !shouldConsiderOperator( + fromGroup: op2Precedence, in: bound, fromGroupSyntax: op1, + errorHandler: errorHandler + ) { break } let associativity = try associativity( - firstGroup: op1Precedence, secondGroup: op2Precedence + firstGroup: op1Precedence, + firstGroupSyntax: op1, + secondGroup: op2Precedence, + secondGroupSyntax: op2, + errorHandler: errorHandler ) switch associativity { @@ -972,14 +1055,21 @@ extension OperatorPrecedence { // repeat. rhs = try fold( rhs, rest: &rest, - bound: PrecedenceBound(groupName: op1Precedence, isStrict: true)) + bound: PrecedenceBound( + groupName: op1Precedence, isStrict: true, syntax: op1 + ), + errorHandler: errorHandler + ) case .right: // Apply right-associativity by recursively folding operators // starting from this point, then immediately folding the LHS and RHS. rhs = try fold( rhs, rest: &rest, - bound: PrecedenceBound(groupName: op1Precedence, isStrict: false) + bound: PrecedenceBound( + groupName: op1Precedence, isStrict: false, syntax: op1 + ), + errorHandler: errorHandler ) lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) @@ -990,7 +1080,9 @@ extension OperatorPrecedence { } // Otherwise, start all over with our new LHS. - return try fold(lhs, rest: &rest, bound: bound) + return try fold( + lhs, rest: &rest, bound: bound, errorHandler: errorHandler + ) case .none: // If we ended up here, it's because we're either: @@ -1008,7 +1100,7 @@ extension OperatorPrecedence { // Recover by folding arbitrarily at this operator, then continuing. lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) - return try fold(lhs, rest: &rest, bound: bound) + return try fold(lhs, rest: &rest, bound: bound, errorHandler: errorHandler) } } @@ -1017,11 +1109,16 @@ extension OperatorPrecedence { } /// "Fold" an expression sequence into a structured syntax tree. - public func fold(_ sequence: SequenceExprSyntax) throws -> ExprSyntax { + public func fold( + _ sequence: SequenceExprSyntax, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows -> ExprSyntax { let lhs = sequence.elements.first! var rest = sequence.elements.dropFirst() return try fold( - lhs, rest: &rest, bound: PrecedenceBound(groupName: nil, isStrict: false) + lhs, rest: &rest, + bound: PrecedenceBound(groupName: nil, isStrict: false, syntax: nil), + errorHandler: errorHandler ) } } diff --git a/Tests/SwiftParserTest/OperatorPrecedence.swift b/Tests/SwiftParserTest/OperatorPrecedence.swift index 67840e16c20..fbde85074c5 100644 --- a/Tests/SwiftParserTest/OperatorPrecedence.swift +++ b/Tests/SwiftParserTest/OperatorPrecedence.swift @@ -55,4 +55,29 @@ public class OperatorPrecedenceTests: XCTestCase { XCTAssertEqual("\(foldedExpr)", "x && y || w && v || z") XCTAssertNil(foldedExpr.as(SequenceExprSyntax.self)) } + + func testParseErrors() throws { + let sources = + """ + infix operator + + infix operator + + """ + + let parsedOperatorPrecedence = try Parser.parse(source: sources) + + var opPrecedence = OperatorPrecedence() + var errors: [OperatorPrecedenceError] = [] + opPrecedence.addSourceFile(parsedOperatorPrecedence) { error in + errors.append(error) + } + + XCTAssertEqual(errors.count, 1) + guard case let .operatorAlreadyExists(existing, new) = errors[0] else { + XCTFail("expected an 'operator already exists' error") + return + } + + _ = existing + _ = new + } } From 1c3d0ecaa57085a11bfe4a73c409c0871c371a7e Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 17 Aug 2022 22:25:24 -0700 Subject: [PATCH 09/50] Add tests for the various kinds of operator-precedence errors --- Sources/SwiftParser/OperatorPrecedence.swift | 4 +- .../SwiftParserTest/OperatorPrecedence.swift | 77 ++++++++++++++++++- 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftParser/OperatorPrecedence.swift b/Sources/SwiftParser/OperatorPrecedence.swift index 8a224858ee1..3238de61584 100644 --- a/Sources/SwiftParser/OperatorPrecedence.swift +++ b/Sources/SwiftParser/OperatorPrecedence.swift @@ -227,7 +227,7 @@ struct PrecedenceGraph { return .lowerThan } - if !groupsSeen.insert(otherGroupName).inserted { + if groupsSeen.insert(otherGroupName).inserted { stack.append((otherGroupName, relation.syntax)) } } @@ -254,7 +254,7 @@ struct PrecedenceGraph { return .higherThan } - if !groupsSeen.insert(otherGroupName).inserted { + if groupsSeen.insert(otherGroupName).inserted { stack.append((otherGroupName, relation.syntax)) } } diff --git a/Tests/SwiftParserTest/OperatorPrecedence.swift b/Tests/SwiftParserTest/OperatorPrecedence.swift index fbde85074c5..36940b582f1 100644 --- a/Tests/SwiftParserTest/OperatorPrecedence.swift +++ b/Tests/SwiftParserTest/OperatorPrecedence.swift @@ -61,6 +61,16 @@ public class OperatorPrecedenceTests: XCTestCase { """ infix operator + infix operator + + + precedencegroup A { + associativity: none + higherThan: B + } + + precedencegroup A { + associativity: none + higherThan: B + } """ let parsedOperatorPrecedence = try Parser.parse(source: sources) @@ -71,7 +81,7 @@ public class OperatorPrecedenceTests: XCTestCase { errors.append(error) } - XCTAssertEqual(errors.count, 1) + XCTAssertEqual(errors.count, 2) guard case let .operatorAlreadyExists(existing, new) = errors[0] else { XCTFail("expected an 'operator already exists' error") return @@ -79,5 +89,70 @@ public class OperatorPrecedenceTests: XCTestCase { _ = existing _ = new + + guard case let .groupAlreadyExists(existingGroup, newGroup) = errors[1] else { + XCTFail("expected a 'group already exists' error") + return + } + _ = newGroup + _ = existingGroup + } + + func testFoldErrors() throws { + let parsedOperatorPrecedence = try Parser.parse(source: + """ + precedencegroup A { + associativity: none + } + + precedencegroup C { + associativity: none + lowerThan: B + } + + infix operator +: A + infix operator -: A + + infix operator *: C + """) + + var opPrecedence = OperatorPrecedence() + try opPrecedence.addSourceFile(parsedOperatorPrecedence) + + do { + var errors: [OperatorPrecedenceError] = [] + let parsed = try Parser.parse(source: "a + b * c") + let sequenceExpr = + parsed.statements.first!.item.as(SequenceExprSyntax.self)! + _ = opPrecedence.fold(sequenceExpr) { error in + errors.append(error) + } + + XCTAssertEqual(errors.count, 1) + guard case let .missingGroup(groupName, location) = errors[0] else { + XCTFail("expected a 'missing group' error") + return + } + XCTAssertEqual(groupName, "B") + _ = location + } + + do { + var errors: [OperatorPrecedenceError] = [] + let parsed = try Parser.parse(source: "a / c") + let sequenceExpr = + parsed.statements.first!.item.as(SequenceExprSyntax.self)! + _ = opPrecedence.fold(sequenceExpr) { error in + errors.append(error) + } + + XCTAssertEqual(errors.count, 1) + guard case let .missingOperator(operatorName, location) = errors[0] else { + XCTFail("expected a 'missing operator' error") + return + } + XCTAssertEqual(operatorName, "/") + _ = location + } } } From 2dcafeda054d14ef3d740ce48cf15240aa9e8005 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 17 Aug 2022 22:40:52 -0700 Subject: [PATCH 10/50] Diagnose incomparable operators without parentheses --- Sources/SwiftParser/OperatorPrecedence.swift | 17 +++++-- .../SwiftParserTest/OperatorPrecedence.swift | 46 ++++++++++++++++++- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftParser/OperatorPrecedence.swift b/Sources/SwiftParser/OperatorPrecedence.swift index 3238de61584..ba1e43046d7 100644 --- a/Sources/SwiftParser/OperatorPrecedence.swift +++ b/Sources/SwiftParser/OperatorPrecedence.swift @@ -140,6 +140,12 @@ public enum OperatorPrecedenceError: Error { /// The named operator is missing from the precedence graph. case missingOperator(OperatorName, referencedFrom: SyntaxProtocol?) + + /// No associativity relationship between operators. + case incomparableOperators( + leftOperator: ExprSyntax, leftPrecedenceGroup: PrecedenceGroupName, + rightOperator: ExprSyntax, rightPrecedenceGroup: PrecedenceGroupName + ) } /// A function that receives an operator precedence error and may do with it @@ -1091,11 +1097,12 @@ extension OperatorPrecedence { // - have the same precedence group with no associativity. if let op1Precedence = op1Precedence, let op2Precedence = op2Precedence { - if op1Precedence == op2Precedence { - // FIXME: Must be a nonassociative group, diagnose - } else { - // FIXME: Diagnose unrelated groups - } + try errorHandler( + .incomparableOperators( + leftOperator: op1, leftPrecedenceGroup: op1Precedence, + rightOperator: op2, rightPrecedenceGroup: op2Precedence + ) + ) } // Recover by folding arbitrarily at this operator, then continuing. diff --git a/Tests/SwiftParserTest/OperatorPrecedence.swift b/Tests/SwiftParserTest/OperatorPrecedence.swift index 36940b582f1..ca7d29596bb 100644 --- a/Tests/SwiftParserTest/OperatorPrecedence.swift +++ b/Tests/SwiftParserTest/OperatorPrecedence.swift @@ -110,10 +110,16 @@ public class OperatorPrecedenceTests: XCTestCase { lowerThan: B } + precedencegroup D { + associativity: none + } + infix operator +: A infix operator -: A infix operator *: C + + infix operator ++: D """) var opPrecedence = OperatorPrecedence() @@ -128,7 +134,7 @@ public class OperatorPrecedenceTests: XCTestCase { errors.append(error) } - XCTAssertEqual(errors.count, 1) + XCTAssertEqual(errors.count, 2) guard case let .missingGroup(groupName, location) = errors[0] else { XCTFail("expected a 'missing group' error") return @@ -154,5 +160,43 @@ public class OperatorPrecedenceTests: XCTestCase { XCTAssertEqual(operatorName, "/") _ = location } + + do { + var errors: [OperatorPrecedenceError] = [] + let parsed = try Parser.parse(source: "a + b - c") + let sequenceExpr = + parsed.statements.first!.item.as(SequenceExprSyntax.self)! + _ = opPrecedence.fold(sequenceExpr) { error in + errors.append(error) + } + + XCTAssertEqual(errors.count, 1) + guard case let .incomparableOperators(_, leftGroup, _, rightGroup) = + errors[0] else { + XCTFail("expected an 'incomparable operator' error") + return + } + XCTAssertEqual(leftGroup, "A") + XCTAssertEqual(rightGroup, "A") + } + + do { + var errors: [OperatorPrecedenceError] = [] + let parsed = try Parser.parse(source: "a ++ b - d") + let sequenceExpr = + parsed.statements.first!.item.as(SequenceExprSyntax.self)! + _ = opPrecedence.fold(sequenceExpr) { error in + errors.append(error) + } + + XCTAssertEqual(errors.count, 1) + guard case let .incomparableOperators(_, leftGroup, _, rightGroup) = + errors[0] else { + XCTFail("expected an 'incomparable operator' error") + return + } + XCTAssertEqual(leftGroup, "D") + XCTAssertEqual(rightGroup, "A") + } } } From e30281a91cfc2dfefbf89f1b6c06c579135c5d6e Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 18 Aug 2022 22:51:01 -0700 Subject: [PATCH 11/50] Move operator-precedence parsing into its own module --- Package.swift | 10 ++++++++++ .../OperatorPrecedence+Parsing.swift | 0 .../OperatorPrecedence.swift | 0 .../OperatorPrecedence.swift | 1 + 4 files changed, 11 insertions(+) rename Sources/{SwiftParser => SwiftOperatorPrecedence}/OperatorPrecedence+Parsing.swift (100%) rename Sources/{SwiftParser => SwiftOperatorPrecedence}/OperatorPrecedence.swift (100%) rename Tests/{SwiftParserTest => SwiftOperatorPrecedenceTest}/OperatorPrecedence.swift (99%) diff --git a/Package.swift b/Package.swift index bd5577202bb..a4e535bd648 100644 --- a/Package.swift +++ b/Package.swift @@ -45,6 +45,8 @@ let package = Package( .macCatalyst(.v13), ], products: [ + .library(name: "SwiftOperatorPrecedence", type: .static, + targets: ["SwiftOperatorPrecedence"]), .library(name: "SwiftParser", type: .static, targets: ["SwiftParser"]), .library(name: "SwiftSyntax", type: .static, targets: ["SwiftSyntax"]), .library(name: "SwiftSyntaxParser", type: .static, targets: ["SwiftSyntaxParser"]), @@ -118,6 +120,10 @@ let package = Package( "DeclarationAttribute.swift.gyb", ] ), + .target( + name: "SwiftOperatorPrecedence", + dependencies: ["SwiftSyntax", "SwiftParser"] + ), .executableTarget( name: "lit-test-helper", dependencies: ["SwiftSyntax", "SwiftSyntaxParser"] @@ -173,6 +179,10 @@ let package = Package( name: "SwiftParserTest", dependencies: ["SwiftDiagnostics", "SwiftParser", "_SwiftSyntaxTestSupport"] ), + .testTarget( + name: "SwiftOperatorPrecedenceTest", + dependencies: ["SwiftOperatorPrecedence", "_SwiftSyntaxTestSupport"] + ), ] ) diff --git a/Sources/SwiftParser/OperatorPrecedence+Parsing.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Parsing.swift similarity index 100% rename from Sources/SwiftParser/OperatorPrecedence+Parsing.swift rename to Sources/SwiftOperatorPrecedence/OperatorPrecedence+Parsing.swift diff --git a/Sources/SwiftParser/OperatorPrecedence.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift similarity index 100% rename from Sources/SwiftParser/OperatorPrecedence.swift rename to Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift diff --git a/Tests/SwiftParserTest/OperatorPrecedence.swift b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift similarity index 99% rename from Tests/SwiftParserTest/OperatorPrecedence.swift rename to Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift index ca7d29596bb..2f81ac9be6c 100644 --- a/Tests/SwiftParserTest/OperatorPrecedence.swift +++ b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift @@ -1,6 +1,7 @@ import XCTest import SwiftSyntax import SwiftParser +import SwiftOperatorPrecedence public class OperatorPrecedenceTests: XCTestCase { func testLogicalExprs() throws { From 8e5c944b49927cd17316853052e9f4e5f76e1413 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 18 Aug 2022 23:03:52 -0700 Subject: [PATCH 12/50] Split the operator-precedence module into many more files --- Package.swift | 5 +- .../SwiftOperatorPrecedence/Operator.swift | 50 + .../OperatorPrecedence+Defaults.swift | 479 ++++++++ .../OperatorPrecedence+Folding.swift | 279 +++++ ...ift => OperatorPrecedence+Semantics.swift} | 2 +- .../OperatorPrecedence.swift | 1026 ----------------- .../OperatorPrecedenceError.swift | 45 + .../PrecedenceGraph.swift | 121 ++ .../PrecedenceGroup.swift | 120 ++ 9 files changed, 1098 insertions(+), 1029 deletions(-) create mode 100644 Sources/SwiftOperatorPrecedence/Operator.swift create mode 100644 Sources/SwiftOperatorPrecedence/OperatorPrecedence+Defaults.swift create mode 100644 Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift rename Sources/SwiftOperatorPrecedence/{OperatorPrecedence+Parsing.swift => OperatorPrecedence+Semantics.swift} (98%) create mode 100644 Sources/SwiftOperatorPrecedence/OperatorPrecedenceError.swift create mode 100644 Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift create mode 100644 Sources/SwiftOperatorPrecedence/PrecedenceGroup.swift diff --git a/Package.swift b/Package.swift index a4e535bd648..577fdee32f0 100644 --- a/Package.swift +++ b/Package.swift @@ -122,7 +122,7 @@ let package = Package( ), .target( name: "SwiftOperatorPrecedence", - dependencies: ["SwiftSyntax", "SwiftParser"] + dependencies: ["SwiftSyntax"] ), .executableTarget( name: "lit-test-helper", @@ -181,7 +181,8 @@ let package = Package( ), .testTarget( name: "SwiftOperatorPrecedenceTest", - dependencies: ["SwiftOperatorPrecedence", "_SwiftSyntaxTestSupport"] + dependencies: ["SwiftOperatorPrecedence", "_SwiftSyntaxTestSupport", + "SwiftParser"] ), ] ) diff --git a/Sources/SwiftOperatorPrecedence/Operator.swift b/Sources/SwiftOperatorPrecedence/Operator.swift new file mode 100644 index 00000000000..8c60e7a659c --- /dev/null +++ b/Sources/SwiftOperatorPrecedence/Operator.swift @@ -0,0 +1,50 @@ +//===------------------ Operator.swift ------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +/// Names an operator. +/// +/// TODO: For now, we'll use strings, but we likely want to move this to a +/// general notion of an Identifier. +public typealias OperatorName = String + +/// Describes the kind of an operator. +public enum OperatorKind: String { + /// Infix operator such as the + in a + b. + case infix + + /// Prefix operator such as the - in -x. + case prefix + + /// Postfix operator such as the ! in x!. + case postfix +} + +/// Describes an operator. +public struct Operator { + public let kind: OperatorKind + public let name: OperatorName + public let precedenceGroup: PrecedenceGroupName? + public let syntax: OperatorDeclSyntax? + + public init( + kind: OperatorKind, name: OperatorName, + precedenceGroup: PrecedenceGroupName?, + syntax: OperatorDeclSyntax? = nil + ) { + self.kind = kind + self.name = name + self.precedenceGroup = precedenceGroup + self.syntax = syntax + } +} diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Defaults.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Defaults.swift new file mode 100644 index 00000000000..d31ad77b9e7 --- /dev/null +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Defaults.swift @@ -0,0 +1,479 @@ +//===------------------ OperatorPrecedence+Defaults.swift -----------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +import SwiftSyntax + +/// Prefabricated operator precedence graphs. +extension OperatorPrecedence { + /// Operator precedence graph for the logical operators '&&' and '||', for + /// example as it is used in `#if` processing. + public static var logicalOperators: OperatorPrecedence { + var opPrecedence = OperatorPrecedence() + + try! opPrecedence.record( + Operator(kind: .infix, name: "&&", + precedenceGroup: "LogicalConjunctionPrecedence")) + try! opPrecedence.record( + Operator(kind: .infix, name: "||", + precedenceGroup: "LogicalDisjunctionPrecedence")) + try! opPrecedence.record( + PrecedenceGroup(name: "LogicalConjunctionPrecedence", + associativity: .left, assignment: false, + relations: [.higherThan("LogicalDisjunctionPrecedence")])) + try! opPrecedence.record( + PrecedenceGroup(name: "LogicalDisjunctionPrecedence", + associativity: .left, assignment: false, + relations: [])) + return opPrecedence + } + + /// Operator precedence graph for the Swift standard library. + /// + /// This describes the operators within the Swift standard library at the + /// type of this writing. It can be used to approximate the behavior one + /// would get from parsing the actual Swift standard library's operators + /// without requiring access to the standard library source code. However, + /// because it does not incorporate user-defined operators, it will only + /// ever be useful for a quick approximation. + public static var standardOperators: OperatorPrecedence { + var opPrecedence = OperatorPrecedence() + + try! opPrecedence.record( + PrecedenceGroup( + name: "AssignmentPrecedence", + associativity: .right, + assignment: true + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "FunctionArrowPrecedence", + associativity: .right, + relations: [.higherThan("AssignmentPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "TernaryPrecedence", + associativity: .right, + relations: [.higherThan("FunctionArrowPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "DefaultPrecedence", + relations: [.higherThan("TernaryPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "LogicalDisjunctionPrecedence", + associativity: .left, + relations: [.higherThan("TernaryPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "LogicalConjunctionPrecedence", + associativity: .left, + relations: [.higherThan("LogicalDisjunctionPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "ComparisonPrecedence", + relations: [.higherThan("LogicalConjunctionPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "NilCoalescingPrecedence", + associativity: .right, + relations: [.higherThan("ComparisonPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "CastingPrecedence", + relations: [.higherThan("NilCoalescingPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "RangeFormationPrecedence", + relations: [.higherThan("CastingPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "AdditionPrecedence", + associativity: .left, + relations: [.higherThan("RangeFormationPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "MultiplicationPrecedence", + associativity: .left, + relations: [.higherThan("AdditionPrecedence")] + ) + ) + try! opPrecedence.record( + PrecedenceGroup( + name: "BitwiseShiftPrecedence", + relations: [.higherThan("MultiplicationPrecedence")] + ) + ) + + // "Exponentiative" + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "<<", + precedenceGroup: "BitwiseShiftPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&<<", + precedenceGroup: "BitwiseShiftPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: ">>", + precedenceGroup: "BitwiseShiftPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&>>", + precedenceGroup: "BitwiseShiftPrecedence" + ) + ) + + // "Multiplicative" + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "*", + precedenceGroup: "MultiplicationPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&*", + precedenceGroup: "MultiplicationPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "/", + precedenceGroup: "MultiplicationPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "%", + precedenceGroup: "MultiplicationPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&", + precedenceGroup: "MultiplicationPrecedence" + ) + ) + + // "Additive" + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "+", + precedenceGroup: "AdditionPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&+", + precedenceGroup: "AdditionPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "-", + precedenceGroup: "AdditionPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&-", + precedenceGroup: "AdditionPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "|", + precedenceGroup: "AdditionPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "^", + precedenceGroup: "AdditionPrecedence" + ) + ) + + // FIXME: is this the right precedence level for "..."? + try! opPrecedence.record( + Operator( + kind: .infix, + name: "...", + precedenceGroup: "RangeFormationPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "..<", + precedenceGroup: "RangeFormationPrecedence" + ) + ) + + // "Coalescing" + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "??", + precedenceGroup: "NilCoalescingPrecedence" + ) + ) + + // "Comparative" + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "<", + precedenceGroup: "ComparisonPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "<=", + precedenceGroup: "ComparisonPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: ">", + precedenceGroup: "ComparisonPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: ">=", + precedenceGroup: "ComparisonPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "==", + precedenceGroup: "ComparisonPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "!=", + precedenceGroup: "ComparisonPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "===", + precedenceGroup: "ComparisonPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "!==", + precedenceGroup: "ComparisonPrecedence" + ) + ) + // FIXME: ~= will be built into the compiler. + try! opPrecedence.record( + Operator( + kind: .infix, + name: "~=", + precedenceGroup: "ComparisonPrecedence" + ) + ) + + // "Conjunctive" + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&&", + precedenceGroup: "LogicalConjunctionPrecedence" + ) + ) + + // "Disjunctive" + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "||", + precedenceGroup: "LogicalDisjunctionPrecedence" + ) + ) + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "*=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&*=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "/=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "%=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "+=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&+=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "-=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&-=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "<<=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&<<=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: ">>=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&>>=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "&=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "^=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + try! opPrecedence.record( + Operator( + kind: .infix, + name: "|=", + precedenceGroup: "AssignmentPrecedence" + ) + ) + + try! opPrecedence.record( + Operator( + kind: .infix, + name: "~>", + precedenceGroup: nil + ) + ) + + return opPrecedence + } +} diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift new file mode 100644 index 00000000000..d95a6300299 --- /dev/null +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift @@ -0,0 +1,279 @@ +//===------------------ OperatorPrecedence+Folding.swift ------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +import SwiftSyntax + +extension OperatorPrecedence { + private struct PrecedenceBound { + let groupName: PrecedenceGroupName? + let isStrict: Bool + let syntax: SyntaxProtocol? + } + + /// Determine whether we should consider an operator in the given group + /// based on the specified bound. + private func shouldConsiderOperator( + fromGroup groupName: PrecedenceGroupName?, + in bound: PrecedenceBound, + fromGroupSyntax: SyntaxProtocol?, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows -> Bool { + guard let boundGroupName = bound.groupName else { + return true + } + + guard let groupName = groupName else { + return false + } + + if groupName == boundGroupName { + return !bound.isStrict + } + + return try precedence( + relating: groupName, to: boundGroupName, + startSyntax: fromGroupSyntax, endSyntax: bound.syntax, + errorHandler: errorHandler + ) != .lowerThan + } + + /// Look up the precedence group for the given expression syntax. + private func lookupPrecedence( + of expr: ExprSyntax, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows -> PrecedenceGroupName? { + // A binary operator. + if let binaryExpr = expr.as(BinaryOperatorExprSyntax.self) { + let operatorName = binaryExpr.operatorToken.text + return try lookupOperatorPrecedenceGroupName( + operatorName, referencedFrom: binaryExpr.operatorToken, + errorHandler: errorHandler + ) + } + + // FIXME: Handle all of the language-defined precedence relationships. + return nil + } + + /// Form a binary operation expression for, e.g., a + b. + private func makeBinaryOperationExpr( + lhs: ExprSyntax, op: ExprSyntax, rhs: ExprSyntax + ) -> ExprSyntax { + ExprSyntax( + InfixOperatorExprSyntax( + leftOperand: lhs, operatorOperand: op, rightOperand: rhs) + ) + } + + /// Determine the associativity between two precedence groups. + private func associativity( + firstGroup: PrecedenceGroupName?, + firstGroupSyntax: SyntaxProtocol?, + secondGroup: PrecedenceGroupName?, + secondGroupSyntax: SyntaxProtocol?, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows -> Associativity { + guard let firstGroup = firstGroup, let secondGroup = secondGroup else { + return .none + } + + // If we have the same group, query its associativity. + if firstGroup == secondGroup { + guard let group = precedenceGraph.lookupGroup(firstGroup) else { + try errorHandler( + .missingGroup(firstGroup, referencedFrom: firstGroupSyntax)) + return .none + } + + return group.associativity + } + + if try precedence( + relating: firstGroup, to: secondGroup, + startSyntax: firstGroupSyntax, endSyntax: secondGroupSyntax, + errorHandler: errorHandler + ) == .higherThan { + return .left + } + + if try precedence( + relating: secondGroup, to: firstGroup, + startSyntax: secondGroupSyntax, endSyntax: firstGroupSyntax, + errorHandler: errorHandler + ) == .higherThan { + return .right + } + + return .none + } + + /// "Fold" an expression sequence where the left-hand side has been broken + /// out and (potentially) folded somewhat, and the "rest" of the sequence is + /// consumed along the way + private func fold( + _ lhs: ExprSyntax, rest: inout Slice, + bound: PrecedenceBound, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows -> ExprSyntax { + if rest.isEmpty { return lhs } + + // We mutate the left-hand side in place as we fold the sequence. + var lhs = lhs + + /// Get the operator, if appropriate to this pass. + func getNextOperator() throws -> (ExprSyntax, PrecedenceGroupName?)? { + let op = rest.first! + + // If the operator's precedence is lower than the minimum, stop here. + let opPrecedence = try lookupPrecedence( + of: op, errorHandler: errorHandler) + if try !shouldConsiderOperator( + fromGroup: opPrecedence, in: bound, fromGroupSyntax: op + ) { + return nil + } + + return (op, opPrecedence) + } + + // Extract out the first operator. + guard var (op1, op1Precedence) = try getNextOperator() else { + return lhs + } + + // We will definitely be consuming at least one operator. + // Pull out the prospective RHS and slice off the first two elements. + rest = rest.dropFirst() + var rhs = rest.first! + rest = rest.dropFirst() + + while !rest.isEmpty { + #if compiler(>=10.0) && false + // If the operator is a cast operator, the RHS can't extend past the type + // that's part of the cast production. + if (isa(op1.op)) { + LHS = makeBinOp(Ctx, op1.op, LHS, RHS, op1.precedence, S.empty()); + op1 = getNextOperator(); + if (!op1) return LHS; + RHS = S[1]; + S = S.slice(2); + continue; + } + #endif + + // Pull out the next binary operator. + let op2 = rest.first! + let op2Precedence = try lookupPrecedence( + of: op2, errorHandler: errorHandler) + + // If the second operator's precedence is lower than the + // precedence bound, break out of the loop. + if try !shouldConsiderOperator( + fromGroup: op2Precedence, in: bound, fromGroupSyntax: op1, + errorHandler: errorHandler + ) { + break + } + + let associativity = try associativity( + firstGroup: op1Precedence, + firstGroupSyntax: op1, + secondGroup: op2Precedence, + secondGroupSyntax: op2, + errorHandler: errorHandler + ) + + switch associativity { + case .left: + // Apply left-associativity immediately by folding the first two + // operands. + lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) + op1 = op2 + op1Precedence = op2Precedence + rest = rest.dropFirst() + rhs = rest.first! + rest = rest.dropFirst() + + case .right where op1Precedence != op2Precedence: + // If the first operator's precedence is lower than the second + // operator's precedence, recursively fold all such + // higher-precedence operators starting from this point, then + // repeat. + rhs = try fold( + rhs, rest: &rest, + bound: PrecedenceBound( + groupName: op1Precedence, isStrict: true, syntax: op1 + ), + errorHandler: errorHandler + ) + + case .right: + // Apply right-associativity by recursively folding operators + // starting from this point, then immediately folding the LHS and RHS. + rhs = try fold( + rhs, rest: &rest, + bound: PrecedenceBound( + groupName: op1Precedence, isStrict: false, syntax: op1 + ), + errorHandler: errorHandler + ) + + lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) + + // If we've drained the entire sequence, we're done. + if rest.isEmpty { + return lhs + } + + // Otherwise, start all over with our new LHS. + return try fold( + lhs, rest: &rest, bound: bound, errorHandler: errorHandler + ) + + case .none: + // If we ended up here, it's because we're either: + // - missing precedence groups, + // - have unordered precedence groups, or + // - have the same precedence group with no associativity. + if let op1Precedence = op1Precedence, + let op2Precedence = op2Precedence { + try errorHandler( + .incomparableOperators( + leftOperator: op1, leftPrecedenceGroup: op1Precedence, + rightOperator: op2, rightPrecedenceGroup: op2Precedence + ) + ) + } + + // Recover by folding arbitrarily at this operator, then continuing. + lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) + return try fold(lhs, rest: &rest, bound: bound, errorHandler: errorHandler) + } + } + + // Fold LHS and RHS together and declare completion. + return makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) + } + + /// "Fold" an expression sequence into a structured syntax tree. + public func fold( + _ sequence: SequenceExprSyntax, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows -> ExprSyntax { + let lhs = sequence.elements.first! + var rest = sequence.elements.dropFirst() + return try fold( + lhs, rest: &rest, + bound: PrecedenceBound(groupName: nil, isStrict: false, syntax: nil), + errorHandler: errorHandler + ) + } +} diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Parsing.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Semantics.swift similarity index 98% rename from Sources/SwiftOperatorPrecedence/OperatorPrecedence+Parsing.swift rename to Sources/SwiftOperatorPrecedence/OperatorPrecedence+Semantics.swift index b17e2faf7e5..f71d7824adb 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Parsing.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Semantics.swift @@ -1,4 +1,4 @@ -//===-------------- OperatorPrecedenceParsing.swift -----------------------===// +//===-------------- OperatorPrecedence+Semantics.swift --------------------===// // // This source file is part of the Swift.org open source project // diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift index ba1e43046d7..1168874b4d7 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift @@ -9,300 +9,8 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// - import SwiftSyntax -/// Names a precedence group. -/// -/// TODO: For now, we'll use strings, but we likely want to move this to -/// a general notion of an Identifier. -public typealias PrecedenceGroupName = String - -/// Names an operator. -/// -/// TODO: For now, we'll use strings, but we likely want to move this to a -/// general notion of an Identifier. -public typealias OperatorName = String - -/// The associativity of a precedence group. -public enum Associativity: String { - /// The precedence group is nonassociative, meaning that one must - /// parenthesize when there are multiple operators in a sequence, e.g., - /// if ^ was nonassociative, a ^ b ^ c would need to be disambiguated as - /// either (a ^ b ) ^ c or a ^ (b ^ c). - case none - - /// The precedence group is left-associative, meaning that multiple operators - /// in the same sequence will be parenthesized from the left. This is typical - /// for arithmetic operators, such that a + b - c is treated as (a + b) - c. - case left - - /// The precedence group is right-associative, meaning that multiple operators - /// in the same sequence will be parenthesized from the right. This is used - /// for assignments, where a = b = c is treated as a = (b = c). - case right -} - -/// Describes the relationship of a precedence group to another precedence -/// group. -public struct PrecedenceRelation { - /// Describes the kind of a precedence relation. - public enum Kind { - case higherThan - case lowerThan - } - - /// The relationship to the other group. - public var kind: Kind - - /// The group name. - public var groupName: PrecedenceGroupName - - /// The syntax that provides the relation. This specifically refers to the - /// group name itself, but one can follow the parent pointer to find its - /// position. - public var syntax: PrecedenceGroupNameElementSyntax? - - /// Return a higher-than precedence relation. - public static func higherThan( - _ groupName: PrecedenceGroupName, - syntax: PrecedenceGroupNameElementSyntax? = nil - ) -> PrecedenceRelation { - return .init(kind: .higherThan, groupName: groupName, syntax: syntax) - } - - /// Return a lower-than precedence relation. - public static func lowerThan( - _ groupName: PrecedenceGroupName, - syntax: PrecedenceGroupNameElementSyntax? = nil - ) -> PrecedenceRelation { - return .init(kind: .lowerThan, groupName: groupName, syntax: syntax) - } -} - -/// Precedence groups are used for parsing sequences of expressions in Swift -/// source code. Each precedence group defines the associativity of the -/// operator and its precedence relative to other precedence groups: -/// -/// precedencegroup MultiplicativePrecedence { -/// associativity: left -/// higherThan: AdditivePrecedence -/// } -/// -/// Operator declarations then specify which precedence group describes their -/// precedence, e.g., -/// -/// infix operator *: MultiplicationPrecedence -public struct PrecedenceGroup { - /// The name of the group, which must be unique. - public var name: PrecedenceGroupName - - /// The associativity for the group. - public var associativity: Associativity = .none - - /// Whether the operators in this precedence group are considered to be - /// assignment operators. - public var assignment: Bool = false - - /// The set of relations to other precedence groups that are defined by - /// this precedence group. - public var relations: [PrecedenceRelation] = [] - - /// The syntax node that describes this precedence group. - public var syntax: PrecedenceGroupDeclSyntax? = nil - - public init( - name: PrecedenceGroupName, - associativity: Associativity = .none, - assignment: Bool = false, - relations: [PrecedenceRelation] = [], - syntax: PrecedenceGroupDeclSyntax? = nil - ) { - self.name = name - self.associativity = associativity - self.assignment = assignment - self.relations = relations - self.syntax = syntax - } -} - -/// Describes errors that can occur when working with operator precedence graphs. -public enum OperatorPrecedenceError: Error { - /// Error produced when a given precedence group already exists in the - /// precedence graph. - case groupAlreadyExists(existing: PrecedenceGroup, new: PrecedenceGroup) - - /// The named precedence group is missing from the precedence graph. - case missingGroup(PrecedenceGroupName, referencedFrom: SyntaxProtocol?) - - /// Error produced when a given operator already exists. - case operatorAlreadyExists(existing: Operator, new: Operator) - - /// The named operator is missing from the precedence graph. - case missingOperator(OperatorName, referencedFrom: SyntaxProtocol?) - - /// No associativity relationship between operators. - case incomparableOperators( - leftOperator: ExprSyntax, leftPrecedenceGroup: PrecedenceGroupName, - rightOperator: ExprSyntax, rightPrecedenceGroup: PrecedenceGroupName - ) -} - -/// A function that receives an operator precedence error and may do with it -/// whatever it likes. -/// -/// Operator precedence error handlers are passed into each function in the -/// operator-precedence parser that can produce a failure. The handler -/// may choose to throw (in which case the error will propagate outward) or -/// may separately record/drop the error and return without throwing (in -/// which case the operator-precedence parser will recover). -public typealias OperatorPrecedenceErrorHandler = - (OperatorPrecedenceError) throws -> Void - -/// Describes the relative precedence of two groups. -public enum Precedence { - case unrelated - case higherThan - case lowerThan -} - -/// A graph formed from a set of precedence groups, which can be used to -/// determine the relative precedence of two precedence groups. -struct PrecedenceGraph { - /// The known set of precedence groups, found by name. - var precedenceGroups: [PrecedenceGroupName : PrecedenceGroup] = [:] - - /// Add a new precedence group - /// - /// - throws: If there is already a precedence group with the given name, - /// throws PrecedenceGraphError.groupAlreadyExists. - mutating func add( - _ group: PrecedenceGroup, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } - ) rethrows { - if let existing = precedenceGroups[group.name] { - try errorHandler( - OperatorPrecedenceError.groupAlreadyExists( - existing: existing, new: group)) - } else { - precedenceGroups[group.name] = group - } - } - - /// Look for the precedence group with the given name, or return nil if - /// no such group is known. - func lookupGroup(_ groupName: PrecedenceGroupName) -> PrecedenceGroup? { - return precedenceGroups[groupName] - } - - /// Determine the precedence relationship between two precedence groups. - /// - /// Follow the precedence relationships among the precedence groups to - /// determine the precedence of the start group relative to the end group. - func precedence( - relating startGroupName: PrecedenceGroupName, - to endGroupName: PrecedenceGroupName, - startSyntax: SyntaxProtocol?, - endSyntax: SyntaxProtocol?, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } - ) rethrows -> Precedence { - if startGroupName == endGroupName { - return .unrelated - } - - // Keep track of all of the groups we have seen during our exploration of - // the graph. This detects cycles and prevents extraneous work. - var groupsSeen: Set = [] - - // Walk all of the lower-than relationships from the end group. If we - // reach the start group, the start has lower precedence than the end. - var stack: [(PrecedenceGroupName, SyntaxProtocol?)] = - [(endGroupName, endSyntax)] - while let (currentGroupName, currentGroupSyntax) = stack.popLast() { - guard let currentGroup = lookupGroup(currentGroupName) else { - try errorHandler( - .missingGroup(currentGroupName, referencedFrom: currentGroupSyntax)) - continue - } - - for relation in currentGroup.relations { - if relation.kind == .lowerThan { - // If we hit our start group, we're done. - let otherGroupName = relation.groupName - if otherGroupName == startGroupName { - return .lowerThan - } - - if groupsSeen.insert(otherGroupName).inserted { - stack.append((otherGroupName, relation.syntax)) - } - } - } - } - - // Walk all of the higher-than relationships from the start group. If we - // reach the end group, the start has higher precedence than the end. - assert(stack.isEmpty) - groupsSeen.removeAll() - stack.append((startGroupName, startSyntax)) - while let (currentGroupName, currentGroupSyntax) = stack.popLast() { - guard let currentGroup = lookupGroup(currentGroupName) else { - try errorHandler( - .missingGroup(currentGroupName, referencedFrom: currentGroupSyntax)) - continue - } - - for relation in currentGroup.relations { - if relation.kind == .higherThan { - // If we hit our end group, we're done. - let otherGroupName = relation.groupName - if otherGroupName == endGroupName { - return .higherThan - } - - if groupsSeen.insert(otherGroupName).inserted { - stack.append((otherGroupName, relation.syntax)) - } - } - } - } - - // The two are incomparable. - return .unrelated - } -} - -/// Describes the kind of an operator. -public enum OperatorKind: String { - /// Infix operator such as the + in a + b. - case infix - - /// Prefix operator such as the - in -x. - case prefix - - /// Postfix operator such as the ! in x!. - case postfix -} - -/// Describes an operator. -public struct Operator { - public let kind: OperatorKind - public let name: OperatorName - public let precedenceGroup: PrecedenceGroupName? - public let syntax: OperatorDeclSyntax? - - public init( - kind: OperatorKind, name: OperatorName, - precedenceGroup: PrecedenceGroupName?, - syntax: OperatorDeclSyntax? = nil - ) { - self.kind = kind - self.name = name - self.precedenceGroup = precedenceGroup - self.syntax = syntax - } -} - /// Maintains information about operators and their relative precedence, /// providing the core operations for "folding" sequence expression syntax into /// a structured expression syntax tree. @@ -339,473 +47,6 @@ public struct OperatorPrecedence { } } -/// Prefabricated operator precedence graphs. -extension OperatorPrecedence { - /// Operator precedence graph for the logical operators '&&' and '||', for - /// example as it is used in `#if` processing. - public static var logicalOperators: OperatorPrecedence { - var opPrecedence = OperatorPrecedence() - - try! opPrecedence.record( - Operator(kind: .infix, name: "&&", - precedenceGroup: "LogicalConjunctionPrecedence")) - try! opPrecedence.record( - Operator(kind: .infix, name: "||", - precedenceGroup: "LogicalDisjunctionPrecedence")) - try! opPrecedence.record( - PrecedenceGroup(name: "LogicalConjunctionPrecedence", - associativity: .left, assignment: false, - relations: [.higherThan("LogicalDisjunctionPrecedence")])) - try! opPrecedence.record( - PrecedenceGroup(name: "LogicalDisjunctionPrecedence", - associativity: .left, assignment: false, - relations: [])) - return opPrecedence - } - - /// Operator precedence graph for the Swift standard library. - /// - /// This describes the operators within the Swift standard library at the - /// type of this writing. It can be used to approximate the behavior one - /// would get from parsing the actual Swift standard library's operators - /// without requiring access to the standard library source code. However, - /// because it does not incorporate user-defined operators, it will only - /// ever be useful for a quick approximation. - public static var standardOperators: OperatorPrecedence { - var opPrecedence = OperatorPrecedence() - - try! opPrecedence.record( - PrecedenceGroup( - name: "AssignmentPrecedence", - associativity: .right, - assignment: true - ) - ) - try! opPrecedence.record( - PrecedenceGroup( - name: "FunctionArrowPrecedence", - associativity: .right, - relations: [.higherThan("AssignmentPrecedence")] - ) - ) - try! opPrecedence.record( - PrecedenceGroup( - name: "TernaryPrecedence", - associativity: .right, - relations: [.higherThan("FunctionArrowPrecedence")] - ) - ) - try! opPrecedence.record( - PrecedenceGroup( - name: "DefaultPrecedence", - relations: [.higherThan("TernaryPrecedence")] - ) - ) - try! opPrecedence.record( - PrecedenceGroup( - name: "LogicalDisjunctionPrecedence", - associativity: .left, - relations: [.higherThan("TernaryPrecedence")] - ) - ) - try! opPrecedence.record( - PrecedenceGroup( - name: "LogicalConjunctionPrecedence", - associativity: .left, - relations: [.higherThan("LogicalDisjunctionPrecedence")] - ) - ) - try! opPrecedence.record( - PrecedenceGroup( - name: "ComparisonPrecedence", - relations: [.higherThan("LogicalConjunctionPrecedence")] - ) - ) - try! opPrecedence.record( - PrecedenceGroup( - name: "NilCoalescingPrecedence", - associativity: .right, - relations: [.higherThan("ComparisonPrecedence")] - ) - ) - try! opPrecedence.record( - PrecedenceGroup( - name: "CastingPrecedence", - relations: [.higherThan("NilCoalescingPrecedence")] - ) - ) - try! opPrecedence.record( - PrecedenceGroup( - name: "RangeFormationPrecedence", - relations: [.higherThan("CastingPrecedence")] - ) - ) - try! opPrecedence.record( - PrecedenceGroup( - name: "AdditionPrecedence", - associativity: .left, - relations: [.higherThan("RangeFormationPrecedence")] - ) - ) - try! opPrecedence.record( - PrecedenceGroup( - name: "MultiplicationPrecedence", - associativity: .left, - relations: [.higherThan("AdditionPrecedence")] - ) - ) - try! opPrecedence.record( - PrecedenceGroup( - name: "BitwiseShiftPrecedence", - relations: [.higherThan("MultiplicationPrecedence")] - ) - ) - - // "Exponentiative" - - try! opPrecedence.record( - Operator( - kind: .infix, - name: "<<", - precedenceGroup: "BitwiseShiftPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "&<<", - precedenceGroup: "BitwiseShiftPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: ">>", - precedenceGroup: "BitwiseShiftPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "&>>", - precedenceGroup: "BitwiseShiftPrecedence" - ) - ) - - // "Multiplicative" - - try! opPrecedence.record( - Operator( - kind: .infix, - name: "*", - precedenceGroup: "MultiplicationPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "&*", - precedenceGroup: "MultiplicationPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "/", - precedenceGroup: "MultiplicationPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "%", - precedenceGroup: "MultiplicationPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "&", - precedenceGroup: "MultiplicationPrecedence" - ) - ) - - // "Additive" - - try! opPrecedence.record( - Operator( - kind: .infix, - name: "+", - precedenceGroup: "AdditionPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "&+", - precedenceGroup: "AdditionPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "-", - precedenceGroup: "AdditionPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "&-", - precedenceGroup: "AdditionPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "|", - precedenceGroup: "AdditionPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "^", - precedenceGroup: "AdditionPrecedence" - ) - ) - - // FIXME: is this the right precedence level for "..."? - try! opPrecedence.record( - Operator( - kind: .infix, - name: "...", - precedenceGroup: "RangeFormationPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "..<", - precedenceGroup: "RangeFormationPrecedence" - ) - ) - - // "Coalescing" - - try! opPrecedence.record( - Operator( - kind: .infix, - name: "??", - precedenceGroup: "NilCoalescingPrecedence" - ) - ) - - // "Comparative" - - try! opPrecedence.record( - Operator( - kind: .infix, - name: "<", - precedenceGroup: "ComparisonPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "<=", - precedenceGroup: "ComparisonPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: ">", - precedenceGroup: "ComparisonPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: ">=", - precedenceGroup: "ComparisonPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "==", - precedenceGroup: "ComparisonPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "!=", - precedenceGroup: "ComparisonPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "===", - precedenceGroup: "ComparisonPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "!==", - precedenceGroup: "ComparisonPrecedence" - ) - ) - // FIXME: ~= will be built into the compiler. - try! opPrecedence.record( - Operator( - kind: .infix, - name: "~=", - precedenceGroup: "ComparisonPrecedence" - ) - ) - - // "Conjunctive" - - try! opPrecedence.record( - Operator( - kind: .infix, - name: "&&", - precedenceGroup: "LogicalConjunctionPrecedence" - ) - ) - - // "Disjunctive" - - try! opPrecedence.record( - Operator( - kind: .infix, - name: "||", - precedenceGroup: "LogicalDisjunctionPrecedence" - ) - ) - - try! opPrecedence.record( - Operator( - kind: .infix, - name: "*=", - precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "&*=", - precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "/=", - precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "%=", - precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "+=", - precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "&+=", - precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "-=", - precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "&-=", - precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "<<=", - precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "&<<=", - precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: ">>=", - precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "&>>=", - precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "&=", - precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "^=", - precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( - Operator( - kind: .infix, - name: "|=", - precedenceGroup: "AssignmentPrecedence" - ) - ) - - try! opPrecedence.record( - Operator( - kind: .infix, - name: "~>", - precedenceGroup: nil - ) - ) - - return opPrecedence - } -} - extension OperatorPrecedence { /// Look for the precedence group corresponding to the given operator. func lookupOperatorPrecedenceGroupName( @@ -862,270 +103,3 @@ extension OperatorPrecedence { ) } } - -extension OperatorPrecedence { - private struct PrecedenceBound { - let groupName: PrecedenceGroupName? - let isStrict: Bool - let syntax: SyntaxProtocol? - } - - /// Determine whether we should consider an operator in the given group - /// based on the specified bound. - private func shouldConsiderOperator( - fromGroup groupName: PrecedenceGroupName?, - in bound: PrecedenceBound, - fromGroupSyntax: SyntaxProtocol?, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } - ) rethrows -> Bool { - guard let boundGroupName = bound.groupName else { - return true - } - - guard let groupName = groupName else { - return false - } - - if groupName == boundGroupName { - return !bound.isStrict - } - - return try precedence( - relating: groupName, to: boundGroupName, - startSyntax: fromGroupSyntax, endSyntax: bound.syntax, - errorHandler: errorHandler - ) != .lowerThan - } - - /// Look up the precedence group for the given expression syntax. - private func lookupPrecedence( - of expr: ExprSyntax, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } - ) rethrows -> PrecedenceGroupName? { - // A binary operator. - if let binaryExpr = expr.as(BinaryOperatorExprSyntax.self) { - let operatorName = binaryExpr.operatorToken.text - return try lookupOperatorPrecedenceGroupName( - operatorName, referencedFrom: binaryExpr.operatorToken, - errorHandler: errorHandler - ) - } - - // FIXME: Handle all of the language-defined precedence relationships. - return nil - } - - /// Form a binary operation expression for, e.g., a + b. - private func makeBinaryOperationExpr( - lhs: ExprSyntax, op: ExprSyntax, rhs: ExprSyntax - ) -> ExprSyntax { - ExprSyntax( - InfixOperatorExprSyntax( - leftOperand: lhs, operatorOperand: op, rightOperand: rhs) - ) - } - - /// Determine the associativity between two precedence groups. - private func associativity( - firstGroup: PrecedenceGroupName?, - firstGroupSyntax: SyntaxProtocol?, - secondGroup: PrecedenceGroupName?, - secondGroupSyntax: SyntaxProtocol?, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } - ) rethrows -> Associativity { - guard let firstGroup = firstGroup, let secondGroup = secondGroup else { - return .none - } - - // If we have the same group, query its associativity. - if firstGroup == secondGroup { - guard let group = precedenceGraph.lookupGroup(firstGroup) else { - try errorHandler( - .missingGroup(firstGroup, referencedFrom: firstGroupSyntax)) - return .none - } - - return group.associativity - } - - if try precedence( - relating: firstGroup, to: secondGroup, - startSyntax: firstGroupSyntax, endSyntax: secondGroupSyntax, - errorHandler: errorHandler - ) == .higherThan { - return .left - } - - if try precedence( - relating: secondGroup, to: firstGroup, - startSyntax: secondGroupSyntax, endSyntax: firstGroupSyntax, - errorHandler: errorHandler - ) == .higherThan { - return .right - } - - return .none - } - - /// "Fold" an expression sequence where the left-hand side has been broken - /// out and (potentially) folded somewhat, and the "rest" of the sequence is - /// consumed along the way - private func fold( - _ lhs: ExprSyntax, rest: inout Slice, - bound: PrecedenceBound, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } - ) rethrows -> ExprSyntax { - if rest.isEmpty { return lhs } - - // We mutate the left-hand side in place as we fold the sequence. - var lhs = lhs - - /// Get the operator, if appropriate to this pass. - func getNextOperator() throws -> (ExprSyntax, PrecedenceGroupName?)? { - let op = rest.first! - - // If the operator's precedence is lower than the minimum, stop here. - let opPrecedence = try lookupPrecedence( - of: op, errorHandler: errorHandler) - if try !shouldConsiderOperator( - fromGroup: opPrecedence, in: bound, fromGroupSyntax: op - ) { - return nil - } - - return (op, opPrecedence) - } - - // Extract out the first operator. - guard var (op1, op1Precedence) = try getNextOperator() else { - return lhs - } - - // We will definitely be consuming at least one operator. - // Pull out the prospective RHS and slice off the first two elements. - rest = rest.dropFirst() - var rhs = rest.first! - rest = rest.dropFirst() - - while !rest.isEmpty { - #if compiler(>=10.0) && false - // If the operator is a cast operator, the RHS can't extend past the type - // that's part of the cast production. - if (isa(op1.op)) { - LHS = makeBinOp(Ctx, op1.op, LHS, RHS, op1.precedence, S.empty()); - op1 = getNextOperator(); - if (!op1) return LHS; - RHS = S[1]; - S = S.slice(2); - continue; - } - #endif - - // Pull out the next binary operator. - let op2 = rest.first! - let op2Precedence = try lookupPrecedence( - of: op2, errorHandler: errorHandler) - - // If the second operator's precedence is lower than the - // precedence bound, break out of the loop. - if try !shouldConsiderOperator( - fromGroup: op2Precedence, in: bound, fromGroupSyntax: op1, - errorHandler: errorHandler - ) { - break - } - - let associativity = try associativity( - firstGroup: op1Precedence, - firstGroupSyntax: op1, - secondGroup: op2Precedence, - secondGroupSyntax: op2, - errorHandler: errorHandler - ) - - switch associativity { - case .left: - // Apply left-associativity immediately by folding the first two - // operands. - lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) - op1 = op2 - op1Precedence = op2Precedence - rest = rest.dropFirst() - rhs = rest.first! - rest = rest.dropFirst() - - case .right where op1Precedence != op2Precedence: - // If the first operator's precedence is lower than the second - // operator's precedence, recursively fold all such - // higher-precedence operators starting from this point, then - // repeat. - rhs = try fold( - rhs, rest: &rest, - bound: PrecedenceBound( - groupName: op1Precedence, isStrict: true, syntax: op1 - ), - errorHandler: errorHandler - ) - - case .right: - // Apply right-associativity by recursively folding operators - // starting from this point, then immediately folding the LHS and RHS. - rhs = try fold( - rhs, rest: &rest, - bound: PrecedenceBound( - groupName: op1Precedence, isStrict: false, syntax: op1 - ), - errorHandler: errorHandler - ) - - lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) - - // If we've drained the entire sequence, we're done. - if rest.isEmpty { - return lhs - } - - // Otherwise, start all over with our new LHS. - return try fold( - lhs, rest: &rest, bound: bound, errorHandler: errorHandler - ) - - case .none: - // If we ended up here, it's because we're either: - // - missing precedence groups, - // - have unordered precedence groups, or - // - have the same precedence group with no associativity. - if let op1Precedence = op1Precedence, - let op2Precedence = op2Precedence { - try errorHandler( - .incomparableOperators( - leftOperator: op1, leftPrecedenceGroup: op1Precedence, - rightOperator: op2, rightPrecedenceGroup: op2Precedence - ) - ) - } - - // Recover by folding arbitrarily at this operator, then continuing. - lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) - return try fold(lhs, rest: &rest, bound: bound, errorHandler: errorHandler) - } - } - - // Fold LHS and RHS together and declare completion. - return makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) - } - - /// "Fold" an expression sequence into a structured syntax tree. - public func fold( - _ sequence: SequenceExprSyntax, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } - ) rethrows -> ExprSyntax { - let lhs = sequence.elements.first! - var rest = sequence.elements.dropFirst() - return try fold( - lhs, rest: &rest, - bound: PrecedenceBound(groupName: nil, isStrict: false, syntax: nil), - errorHandler: errorHandler - ) - } -} diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError.swift new file mode 100644 index 00000000000..02e1fcebd6d --- /dev/null +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError.swift @@ -0,0 +1,45 @@ +//===------------------ OperatorPrecedenceError.swift ---------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +import SwiftSyntax + +/// Describes errors that can occur when working with operator precedence graphs. +public enum OperatorPrecedenceError: Error { + /// Error produced when a given precedence group already exists in the + /// precedence graph. + case groupAlreadyExists(existing: PrecedenceGroup, new: PrecedenceGroup) + + /// The named precedence group is missing from the precedence graph. + case missingGroup(PrecedenceGroupName, referencedFrom: SyntaxProtocol?) + + /// Error produced when a given operator already exists. + case operatorAlreadyExists(existing: Operator, new: Operator) + + /// The named operator is missing from the precedence graph. + case missingOperator(OperatorName, referencedFrom: SyntaxProtocol?) + + /// No associativity relationship between operators. + case incomparableOperators( + leftOperator: ExprSyntax, leftPrecedenceGroup: PrecedenceGroupName, + rightOperator: ExprSyntax, rightPrecedenceGroup: PrecedenceGroupName + ) +} + +/// A function that receives an operator precedence error and may do with it +/// whatever it likes. +/// +/// Operator precedence error handlers are passed into each function in the +/// operator-precedence parser that can produce a failure. The handler +/// may choose to throw (in which case the error will propagate outward) or +/// may separately record/drop the error and return without throwing (in +/// which case the operator-precedence parser will recover). +public typealias OperatorPrecedenceErrorHandler = + (OperatorPrecedenceError) throws -> Void diff --git a/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift b/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift new file mode 100644 index 00000000000..423e4ab0a0b --- /dev/null +++ b/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift @@ -0,0 +1,121 @@ +// +// File.swift +// +// +// Created by Doug Gregor on 8/18/22. +// + +import SwiftSyntax + +/// Describes the relative precedence of two groups. +enum Precedence { + case unrelated + case higherThan + case lowerThan +} + +/// A graph formed from a set of precedence groups, which can be used to +/// determine the relative precedence of two precedence groups. +struct PrecedenceGraph { + /// The known set of precedence groups, found by name. + var precedenceGroups: [PrecedenceGroupName : PrecedenceGroup] = [:] + + /// Add a new precedence group + /// + /// - throws: If there is already a precedence group with the given name, + /// throws PrecedenceGraphError.groupAlreadyExists. + mutating func add( + _ group: PrecedenceGroup, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows { + if let existing = precedenceGroups[group.name] { + try errorHandler( + OperatorPrecedenceError.groupAlreadyExists( + existing: existing, new: group)) + } else { + precedenceGroups[group.name] = group + } + } + + /// Look for the precedence group with the given name, or return nil if + /// no such group is known. + func lookupGroup(_ groupName: PrecedenceGroupName) -> PrecedenceGroup? { + return precedenceGroups[groupName] + } + + /// Determine the precedence relationship between two precedence groups. + /// + /// Follow the precedence relationships among the precedence groups to + /// determine the precedence of the start group relative to the end group. + func precedence( + relating startGroupName: PrecedenceGroupName, + to endGroupName: PrecedenceGroupName, + startSyntax: SyntaxProtocol?, + endSyntax: SyntaxProtocol?, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows -> Precedence { + if startGroupName == endGroupName { + return .unrelated + } + + // Keep track of all of the groups we have seen during our exploration of + // the graph. This detects cycles and prevents extraneous work. + var groupsSeen: Set = [] + + // Walk all of the lower-than relationships from the end group. If we + // reach the start group, the start has lower precedence than the end. + var stack: [(PrecedenceGroupName, SyntaxProtocol?)] = + [(endGroupName, endSyntax)] + while let (currentGroupName, currentGroupSyntax) = stack.popLast() { + guard let currentGroup = lookupGroup(currentGroupName) else { + try errorHandler( + .missingGroup(currentGroupName, referencedFrom: currentGroupSyntax)) + continue + } + + for relation in currentGroup.relations { + if relation.kind == .lowerThan { + // If we hit our start group, we're done. + let otherGroupName = relation.groupName + if otherGroupName == startGroupName { + return .lowerThan + } + + if groupsSeen.insert(otherGroupName).inserted { + stack.append((otherGroupName, relation.syntax)) + } + } + } + } + + // Walk all of the higher-than relationships from the start group. If we + // reach the end group, the start has higher precedence than the end. + assert(stack.isEmpty) + groupsSeen.removeAll() + stack.append((startGroupName, startSyntax)) + while let (currentGroupName, currentGroupSyntax) = stack.popLast() { + guard let currentGroup = lookupGroup(currentGroupName) else { + try errorHandler( + .missingGroup(currentGroupName, referencedFrom: currentGroupSyntax)) + continue + } + + for relation in currentGroup.relations { + if relation.kind == .higherThan { + // If we hit our end group, we're done. + let otherGroupName = relation.groupName + if otherGroupName == endGroupName { + return .higherThan + } + + if groupsSeen.insert(otherGroupName).inserted { + stack.append((otherGroupName, relation.syntax)) + } + } + } + } + + // The two are incomparable. + return .unrelated + } +} diff --git a/Sources/SwiftOperatorPrecedence/PrecedenceGroup.swift b/Sources/SwiftOperatorPrecedence/PrecedenceGroup.swift new file mode 100644 index 00000000000..1ebd4c99f4c --- /dev/null +++ b/Sources/SwiftOperatorPrecedence/PrecedenceGroup.swift @@ -0,0 +1,120 @@ +//===------------------ PrecedenceGroup.swift -----------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +import SwiftSyntax + +/// Names a precedence group. +/// +/// TODO: For now, we'll use strings, but we likely want to move this to +/// a general notion of an Identifier. +public typealias PrecedenceGroupName = String + +/// The associativity of a precedence group. +public enum Associativity: String { + /// The precedence group is nonassociative, meaning that one must + /// parenthesize when there are multiple operators in a sequence, e.g., + /// if ^ was nonassociative, a ^ b ^ c would need to be disambiguated as + /// either (a ^ b ) ^ c or a ^ (b ^ c). + case none + + /// The precedence group is left-associative, meaning that multiple operators + /// in the same sequence will be parenthesized from the left. This is typical + /// for arithmetic operators, such that a + b - c is treated as (a + b) - c. + case left + + /// The precedence group is right-associative, meaning that multiple operators + /// in the same sequence will be parenthesized from the right. This is used + /// for assignments, where a = b = c is treated as a = (b = c). + case right +} + +/// Describes the relationship of a precedence group to another precedence +/// group. +public struct PrecedenceRelation { + /// Describes the kind of a precedence relation. + public enum Kind { + case higherThan + case lowerThan + } + + /// The relationship to the other group. + public var kind: Kind + + /// The group name. + public var groupName: PrecedenceGroupName + + /// The syntax that provides the relation. This specifically refers to the + /// group name itself, but one can follow the parent pointer to find its + /// position. + public var syntax: PrecedenceGroupNameElementSyntax? + + /// Return a higher-than precedence relation. + public static func higherThan( + _ groupName: PrecedenceGroupName, + syntax: PrecedenceGroupNameElementSyntax? = nil + ) -> PrecedenceRelation { + return .init(kind: .higherThan, groupName: groupName, syntax: syntax) + } + + /// Return a lower-than precedence relation. + public static func lowerThan( + _ groupName: PrecedenceGroupName, + syntax: PrecedenceGroupNameElementSyntax? = nil + ) -> PrecedenceRelation { + return .init(kind: .lowerThan, groupName: groupName, syntax: syntax) + } +} + +/// Precedence groups are used for parsing sequences of expressions in Swift +/// source code. Each precedence group defines the associativity of the +/// operator and its precedence relative to other precedence groups: +/// +/// precedencegroup MultiplicativePrecedence { +/// associativity: left +/// higherThan: AdditivePrecedence +/// } +/// +/// Operator declarations then specify which precedence group describes their +/// precedence, e.g., +/// +/// infix operator *: MultiplicationPrecedence +public struct PrecedenceGroup { + /// The name of the group, which must be unique. + public var name: PrecedenceGroupName + + /// The associativity for the group. + public var associativity: Associativity = .none + + /// Whether the operators in this precedence group are considered to be + /// assignment operators. + public var assignment: Bool = false + + /// The set of relations to other precedence groups that are defined by + /// this precedence group. + public var relations: [PrecedenceRelation] = [] + + /// The syntax node that describes this precedence group. + public var syntax: PrecedenceGroupDeclSyntax? = nil + + public init( + name: PrecedenceGroupName, + associativity: Associativity = .none, + assignment: Bool = false, + relations: [PrecedenceRelation] = [], + syntax: PrecedenceGroupDeclSyntax? = nil + ) { + self.name = name + self.associativity = associativity + self.assignment = assignment + self.relations = relations + self.syntax = syntax + } +} From 112b3142719abe7b0eb50328fa3f0e01354206d6 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 18 Aug 2022 23:52:43 -0700 Subject: [PATCH 13/50] Add DocC documentation for the SwiftOperatorPrecedence library --- .../SwiftOperatorPrecedence.docc/Info.plist | 38 ++++++++ .../SwiftOperatorPrecedence.md | 93 +++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/Info.plist create mode 100644 Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md diff --git a/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/Info.plist b/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/Info.plist new file mode 100644 index 00000000000..2e9532240b1 --- /dev/null +++ b/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleName + SwiftOperatorPrecedence + CFBundleDisplayName + SwiftOperatorPrecedence + CFBundleIdentifier + com.apple.swift-operator-precedence + CFBundleDevelopmentRegion + en + CFBundleIconFile + DocumentationIcon + CFBundleIconName + DocumentationIcon + CFBundlePackageType + DOCS + CFBundleShortVersionString + 0.1.0 + CDDefaultCodeListingLanguage + swift + CFBundleVersion + 0.1.0 + CDAppleDefaultAvailability + + SwiftOperatorPrecedence + + + name + macOS + version + 10.15 + + + + + diff --git a/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md b/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md new file mode 100644 index 00000000000..e736381df04 --- /dev/null +++ b/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md @@ -0,0 +1,93 @@ +# ``SwiftOperatorPrecedence`` + + + +An implementation of Swift's operator precedence semantics for Swift syntax trees. + + + +## Overview + +Swift allows developers to define new operators to use in expressions. For example, the infix `+` and `*` operators are defined by the Swift standard library with [operator declarations](https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_operator-declaration) that look like this: + +```swift +infix operator +: AdditionPrecedence +infix operator *: MultiplicationPrecedence +``` + +The define the associativity and relative precedence of these operators is defined via a [precedence group declaration](https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_precedence-group-declaration). For example, the precedence groups used for `+` and `*` are defined as follows: + +```swift +precedencegroup AdditionPrecedence { + associativity: left +} +precedencegroup MultiplicationPrecedence { + associativity: left + higherThan: AdditionPrecedence +} +``` + +The Swift parser itself does not reason about the semantics of operators or precedence groups. Instead, an expression such as `x + y * z` will be parsed into a `SequenceExprSyntax` node whose children are `x`, a `BinaryOperatorExprSyntax` node for `+`, `y`, a `BinaryOperatorExprSyntax` node for `*`, and `z`. This is all the structure that is possible to parse for a Swift program without semantic information about operators and precedence groups. + +The `SwiftOperatorPrecedence` module interprets operator and precedence group declarations to provide those semantics. Its primary operation is to "fold" a `SequenceExprSyntax` node into an equivalent syntax tree that fully expresses the order of operations: in our example case, this means that the resulting syntax node will be an `InfixOperatorExprSyntax` whose left-hand side is `x` and operator is `+`, and whose right-hand side is another `InfixOperatorExprSyntax` node representing `y * z`. The resulting syntax tree will still accurately represent the original source input, but will be completely describe the order of evaluation and be suitable for structured editing or later semantic passes, such as type checking. + + + +## Quickstart + +The `SwiftOperatorPrecedence` library is typically used to take a raw parse of Swift code and apply the operator-precedence transformation to it to replace all `SequenceExprSyntax` nodes with more structured syntax nodes. For example, we can use this library's representation of the Swift standard library operators to provide a structured syntax tree for the expression `x + y * z`: + +```swift +import SwiftSyntax +import SwiftParser +import SwiftOperatorPrecedence + +var opPrecedence = OperatorPrecedence.standardOperators // Use the Swift standard library operators +let parsed = try Parser.parse(source: "x + y * z") +let sequenceExpr = parsed.statements.first!.item.as(SequenceExprSyntax.self)! +dump(sequenceExpr) // SequenceExprSyntax(x, +, y, *, z) +let foldedExpr = try opPrecedence.fold(sequenceExpr) +dump(foldedExpr) // InfixOperatorExpr(x, +, InfixOperatorExpr(y, *, z)) +``` + +The type maintains the table of known operators and precedence groups, and is the primary way in which one interacts with this library. The standard operators are provided as a static variable of this type, which will work to fold most Swift code, such as in the example above that folds `x + y * z`. + +If your Swift code involves operator and precedence group declarations, they can be parsed into another source file and then added to the `OperatorPrecedence` instance using `addSourceFile`: + +```swift +let moreOperators = + """ + precedencegroup ExponentiationPrecedence { + associativity: right + higherThan: MultiplicationPrecedence + } + + infix operator **: ExponentiationPrecedence + """ +let parsedOperators = try Parser.parse(source: moreOperators) + +// Adds **, ExponentiationPrecedence to the set of known operators and precedence groups. +try opPrecedence.addSourceFile(parsedOperators) + +let parsed2 = try Parser.parse(source: "b ** c ** d") +let sequenceExpr2 = parsed2.statements.first!.item.as(SequenceExprSyntax.self)! +dump(sequenceExpr2) // SequenceExprSyntax(b, **, c, **, d) +let foldedExpr2 = try opPrecedence.fold(sequenceExpr2) +dump(foldedExpr2) // InfixOperatorExpr(b, **, InfixOperatorExpr(c, **, d)) +``` + + + +## Error handling + +By default, any of the operations that can produce an error, whether folding a sequence or parsing a source file's operators and precedence groups into a table, will throw an instance of . However, each entry point takes an optional error handler (of type ) that will be provided with each error that occurs. For example, we can capture errors like this: + +```swift +var errors: [OperatorPrecedenceError] = [] +let foldedExpr2e = opPrecedence.fold(sequenceExpr2) { error in + errors.append(error) +} +``` + +As indicated by the lack of `try`, the folding operation will continue even in the presence of errors, and produce a structured syntax tree. That structured syntax tree will have had some fallback behavior applied (e.g., bias toward left-associative when operators cannot be compared), but the sequence expression will have been removed. + From faf0c013d133fb51743e868d9168c831f596c6a9 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 19 Aug 2022 22:38:36 -0700 Subject: [PATCH 14/50] Implement a recursive "foldAll" operation to fold all sequences. --- .../OperatorPrecedence+Folding.swift | 89 ++++++++++++++++++- .../SwiftOperatorPrecedence.md | 22 ++--- .../OperatorPrecedence.swift | 49 ++++++++-- 3 files changed, 137 insertions(+), 23 deletions(-) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift index d95a6300299..b3b26304060 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift @@ -263,8 +263,16 @@ extension OperatorPrecedence { return makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) } - /// "Fold" an expression sequence into a structured syntax tree. - public func fold( + /// "Fold" a sequence expression into a structured syntax tree. + /// + /// A sequence expression results from parsing an expression involving + /// infix operators, such as `x + y * z`. Swift's grammar does not + /// involve operator precedence, so a sequence expression is a flat list + /// of all of the terms `x`, `+`, `y`, `*`, and `z`. This operation folds + /// a single sequence expression into a structured syntax tree that + /// represents the same source code, but describes the order of operations + /// as if the expression has been parenthesized `x + (y * z)`. + public func foldSingle( _ sequence: SequenceExprSyntax, errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } ) rethrows -> ExprSyntax { @@ -276,4 +284,81 @@ extension OperatorPrecedence { errorHandler: errorHandler ) } + + /// Syntax rewriter that folds all of the sequence expressions it + /// encounters. + private class SequenceFolder : SyntaxRewriter { + /// The first operator precedecence that caused the error handler to + /// also throw. + var firstFatalError: OperatorPrecedenceError? = nil + + let opPrecedence: OperatorPrecedence + let errorHandler: OperatorPrecedenceErrorHandler + + init( + opPrecedence: OperatorPrecedence, + errorHandler: @escaping OperatorPrecedenceErrorHandler + ) { + self.opPrecedence = opPrecedence + self.errorHandler = errorHandler + } + + override func visit(_ node: SequenceExprSyntax) -> ExprSyntax { + // If the error handler threw in response to an error, don't + // continue folding. + if firstFatalError != nil { + return ExprSyntax(node) + } + + let newNode = super.visit(node).as(SequenceExprSyntax.self)! + + // If the error handler threw in response to an error, don't + // continue folding. + if firstFatalError != nil { + return ExprSyntax(newNode) + } + + // Try to fold this sequence expression. + do { + return try opPrecedence.foldSingle(newNode) { origError in + do { + try errorHandler(origError) + } catch { + firstFatalError = origError + throw error + } + } + } catch { + return ExprSyntax(newNode) + } + } + } + + /// Fold all sequence expressions within the given syntax tree into a + /// structured syntax tree. + /// + /// This operation replaces all sequence expressions in the given syntax + /// tree with structured syntax trees, by walking the tree and invoking + /// `foldSingle` on each sequence expression it encounters. Use this to + /// provide structure to an entire tree. + public func foldAll( + _ node: Node, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows -> SyntaxProtocol { + return try withoutActuallyEscaping(errorHandler) { errorHandler in + let folder = SequenceFolder( + opPrecedence: self, errorHandler: errorHandler + ) + let result = folder.visit(Syntax(node)) + + // If the sequence folder encountered an error that caused the error + // handler to throw, invoke the error handler again with the original + // error. + if let origFatalError = folder.firstFatalError { + try errorHandler(origFatalError) + } + + return result + } + } } diff --git a/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md b/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md index e736381df04..65db38b0bcb 100644 --- a/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md +++ b/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md @@ -44,10 +44,9 @@ import SwiftOperatorPrecedence var opPrecedence = OperatorPrecedence.standardOperators // Use the Swift standard library operators let parsed = try Parser.parse(source: "x + y * z") -let sequenceExpr = parsed.statements.first!.item.as(SequenceExprSyntax.self)! -dump(sequenceExpr) // SequenceExprSyntax(x, +, y, *, z) -let foldedExpr = try opPrecedence.fold(sequenceExpr) -dump(foldedExpr) // InfixOperatorExpr(x, +, InfixOperatorExpr(y, *, z)) +dump(parsed) // contains SequenceExprSyntax(x, +, y, *, z) +let folded = try opPrecedence.foldAll(parsed) +dump(folded) // contains InfixOperatorExpr(x, +, InfixOperatorExpr(y, *, z)) ``` The type maintains the table of known operators and precedence groups, and is the primary way in which one interacts with this library. The standard operators are provided as a static variable of this type, which will work to fold most Swift code, such as in the example above that folds `x + y * z`. @@ -70,24 +69,21 @@ let parsedOperators = try Parser.parse(source: moreOperators) try opPrecedence.addSourceFile(parsedOperators) let parsed2 = try Parser.parse(source: "b ** c ** d") -let sequenceExpr2 = parsed2.statements.first!.item.as(SequenceExprSyntax.self)! -dump(sequenceExpr2) // SequenceExprSyntax(b, **, c, **, d) -let foldedExpr2 = try opPrecedence.fold(sequenceExpr2) -dump(foldedExpr2) // InfixOperatorExpr(b, **, InfixOperatorExpr(c, **, d)) +dump(parsed2) // contains SequenceExprSyntax(b, **, c, **, d) +let folded2 = try opPrecedence.foldAll(parsed2) +dump(folded2) // contains InfixOperatorExpr(b, **, InfixOperatorExpr(c, **, d)) ``` - - ## Error handling -By default, any of the operations that can produce an error, whether folding a sequence or parsing a source file's operators and precedence groups into a table, will throw an instance of . However, each entry point takes an optional error handler (of type ) that will be provided with each error that occurs. For example, we can capture errors like this: +By default, any of the operations that can produce an error, whether folding a sequence or parsing a source file's operators and precedence groups into a table, will throw an instance of . However, each entry point takes an optional error handler (of type ) that will be called with each error that occurs. For example, we can capture errors like this: ```swift var errors: [OperatorPrecedenceError] = [] -let foldedExpr2e = opPrecedence.fold(sequenceExpr2) { error in +let foldedExpr2e = opPrecedence.foldSingle(sequenceExpr2) { error in errors.append(error) } ``` -As indicated by the lack of `try`, the folding operation will continue even in the presence of errors, and produce a structured syntax tree. That structured syntax tree will have had some fallback behavior applied (e.g., bias toward left-associative when operators cannot be compared), but the sequence expression will have been removed. +As indicated by the lack of `try`, the folding operation will continue even in the presence of errors, and produce a structured syntax tree. That structured syntax tree will have had some fallback behavior applied (e.g., bias toward left-associative when operators cannot be compared), but the sequence expression(s) will have been replaced in the resulting tree. diff --git a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift index 2f81ac9be6c..7cbd8fffdaf 100644 --- a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift +++ b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift @@ -3,13 +3,38 @@ import SwiftSyntax import SwiftParser import SwiftOperatorPrecedence +/// Visitor that looks for ExprSequenceSyntax nodes. +private class ExprSequenceSearcher: SyntaxAnyVisitor { + var foundSequenceExpr = false + + override func visit( + _ node: SequenceExprSyntax + ) -> SyntaxVisitorContinueKind { + foundSequenceExpr = true + return .skipChildren + } + + override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind{ + return foundSequenceExpr ? .skipChildren : .visitChildren + } +} + +extension SyntaxProtocol { + /// Determine whether the given syntax contains an ExprSequence anywhere. + var containsExprSequence: Bool { + let searcher = ExprSequenceSearcher(viewMode: .sourceAccurate) + searcher.walk(self) + return searcher.foundSequenceExpr + } +} + public class OperatorPrecedenceTests: XCTestCase { func testLogicalExprs() throws { let opPrecedence = OperatorPrecedence.logicalOperators let parsed = try Parser.parse(source: "x && y || w && v || z") let sequenceExpr = parsed.statements.first!.item.as(SequenceExprSyntax.self)! - let foldedExpr = try opPrecedence.fold(sequenceExpr) + let foldedExpr = try opPrecedence.foldSingle(sequenceExpr) XCTAssertEqual("\(foldedExpr)", "x && y || w && v || z") XCTAssertNil(foldedExpr.as(SequenceExprSyntax.self)) } @@ -18,12 +43,20 @@ public class OperatorPrecedenceTests: XCTestCase { let opPrecedence = OperatorPrecedence.standardOperators let parsed = try Parser.parse(source: "(x + y > 17) && x && y || w && v || z") let sequenceExpr = - parsed.statements.first!.item.as(SequenceExprSyntax.self)! - let foldedExpr = try opPrecedence.fold(sequenceExpr) + parsed.statements.first!.item.as(SequenceExprSyntax.self)! + let foldedExpr = try opPrecedence.foldSingle(sequenceExpr) XCTAssertEqual("\(foldedExpr)", "(x + y > 17) && x && y || w && v || z") XCTAssertNil(foldedExpr.as(SequenceExprSyntax.self)) } + func testNestedSwiftExprs() throws { + let opPrecedence = OperatorPrecedence.standardOperators + let parsed = try Parser.parse(source: "(x + y > 17) && x && y || w && v || z") + let foldedAll = try opPrecedence.foldAll(parsed) + XCTAssertEqual("\(foldedAll)", "(x + y > 17) && x && y || w && v || z") + XCTAssertFalse(foldedAll.containsExprSequence) + } + func testParsedLogicalExprs() throws { let logicalOperatorSources = """ @@ -52,7 +85,7 @@ public class OperatorPrecedenceTests: XCTestCase { let parsed = try Parser.parse(source: "x && y || w && v || z") let sequenceExpr = parsed.statements.first!.item.as(SequenceExprSyntax.self)! - let foldedExpr = try opPrecedence.fold(sequenceExpr) + let foldedExpr = try opPrecedence.foldSingle(sequenceExpr) XCTAssertEqual("\(foldedExpr)", "x && y || w && v || z") XCTAssertNil(foldedExpr.as(SequenceExprSyntax.self)) } @@ -131,7 +164,7 @@ public class OperatorPrecedenceTests: XCTestCase { let parsed = try Parser.parse(source: "a + b * c") let sequenceExpr = parsed.statements.first!.item.as(SequenceExprSyntax.self)! - _ = opPrecedence.fold(sequenceExpr) { error in + _ = opPrecedence.foldSingle(sequenceExpr) { error in errors.append(error) } @@ -149,7 +182,7 @@ public class OperatorPrecedenceTests: XCTestCase { let parsed = try Parser.parse(source: "a / c") let sequenceExpr = parsed.statements.first!.item.as(SequenceExprSyntax.self)! - _ = opPrecedence.fold(sequenceExpr) { error in + _ = opPrecedence.foldSingle(sequenceExpr) { error in errors.append(error) } @@ -167,7 +200,7 @@ public class OperatorPrecedenceTests: XCTestCase { let parsed = try Parser.parse(source: "a + b - c") let sequenceExpr = parsed.statements.first!.item.as(SequenceExprSyntax.self)! - _ = opPrecedence.fold(sequenceExpr) { error in + _ = opPrecedence.foldSingle(sequenceExpr) { error in errors.append(error) } @@ -186,7 +219,7 @@ public class OperatorPrecedenceTests: XCTestCase { let parsed = try Parser.parse(source: "a ++ b - d") let sequenceExpr = parsed.statements.first!.item.as(SequenceExprSyntax.self)! - _ = opPrecedence.fold(sequenceExpr) { error in + _ = opPrecedence.foldSingle(sequenceExpr) { error in errors.append(error) } From 3c2745395e283ab281521604818a39d7b916603d Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 19 Aug 2022 23:16:44 -0700 Subject: [PATCH 15/50] Make an OperatorPrecedenceError into a DiagnosticMessage. --- Package.swift | 2 +- .../OperatorPrecedenceError+Diagnostics.swift | 98 +++++++++++++++++++ .../OperatorPrecedence.swift | 10 ++ 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift diff --git a/Package.swift b/Package.swift index 577fdee32f0..fca660c9bab 100644 --- a/Package.swift +++ b/Package.swift @@ -122,7 +122,7 @@ let package = Package( ), .target( name: "SwiftOperatorPrecedence", - dependencies: ["SwiftSyntax"] + dependencies: ["SwiftSyntax", "SwiftParser"] ), .executableTarget( name: "lit-test-helper", diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift new file mode 100644 index 00000000000..aa589ecd8a5 --- /dev/null +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift @@ -0,0 +1,98 @@ +//===------------------ OperatorPrecedenceError.swift ---------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftParser +import SwiftSyntax + +extension OperatorPrecedenceError : DiagnosticMessage { + public var message: String { + switch self { + case .groupAlreadyExists(let existing, _): + return "redefinition of precedence group '\(existing.name)'" + + case .missingGroup(let groupName, _): + return "unknown precedence group '\(groupName)'" + + case .operatorAlreadyExists(let existing, _): + return "redefinition of infix operator '\(existing.name)'" + + case .missingOperator(let operatorName, _): + return "unknown infix operator '\(operatorName)'" + + case .incomparableOperators(_, let leftGroup, _, let rightGroup): + if leftGroup == rightGroup { + return "adjacent operators are in non-associative precedence group '\(leftGroup)'" + } + + return "adjacent operators are in unordered precedence groups '\(leftGroup)' and '\(rightGroup)'" + } + } + + /// A string representation of each case. + private var diagnosticCaseID: String { + switch self { + case .incomparableOperators: + return "incomparable_operators" + + case .operatorAlreadyExists: + return "operator_already_exists" + + case .missingOperator: + return "missing_operator" + + case .missingGroup: + return "missing_group" + + case .groupAlreadyExists: + return "group_already_exists" + } + } + + public var diagnosticID: DiagnosticMessageID { + DiagnosticMessageID("SwiftOperatorPrecedence/\(diagnosticCaseID)") + } +} + +extension OperatorPrecedenceError { + private func fixupDiagnosticDisplayNode(_ node: SyntaxProtocol?) -> Syntax { + if let node = node { + return Syntax(node) + } + + return Syntax(MissingDeclSyntax(attributes: nil, modifiers: nil)) + } + + /// Produce the syntax node at which a diagnostic should be displayed. + var diagnosticDisplayNode: Syntax { + switch self { + case .incomparableOperators(let leftOperator, _, _, _): + return Syntax(leftOperator) + + case .missingOperator(_, let node): + return fixupDiagnosticDisplayNode(node) + + case .operatorAlreadyExists(_, let newOperator): + return fixupDiagnosticDisplayNode(newOperator.syntax) + + case .missingGroup(_, let node): + return fixupDiagnosticDisplayNode(node) + + case .groupAlreadyExists(_, let newGroup): + return fixupDiagnosticDisplayNode(newGroup.syntax) + } + } + + /// Produce a diagnostic for a given operator-precedence error. + public var asDiagnostic: Diagnostic { + .init(node: diagnosticDisplayNode, message: self) + } +} diff --git a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift index 7cbd8fffdaf..55cc5fd87e7 100644 --- a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift +++ b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift @@ -121,6 +121,7 @@ public class OperatorPrecedenceTests: XCTestCase { return } + XCTAssertEqual(errors[0].message, "redefinition of infix operator '+'") _ = existing _ = new @@ -128,6 +129,7 @@ public class OperatorPrecedenceTests: XCTestCase { XCTFail("expected a 'group already exists' error") return } + XCTAssertEqual(errors[1].message, "redefinition of precedence group 'A'") _ = newGroup _ = existingGroup } @@ -174,6 +176,7 @@ public class OperatorPrecedenceTests: XCTestCase { return } XCTAssertEqual(groupName, "B") + XCTAssertEqual(errors[0].message, "unknown precedence group 'B'") _ = location } @@ -192,6 +195,7 @@ public class OperatorPrecedenceTests: XCTestCase { return } XCTAssertEqual(operatorName, "/") + XCTAssertEqual(errors[0].message, "unknown infix operator '/'") _ = location } @@ -212,6 +216,9 @@ public class OperatorPrecedenceTests: XCTestCase { } XCTAssertEqual(leftGroup, "A") XCTAssertEqual(rightGroup, "A") + XCTAssertEqual( + errors[0].message, + "adjacent operators are in non-associative precedence group 'A'") } do { @@ -231,6 +238,9 @@ public class OperatorPrecedenceTests: XCTestCase { } XCTAssertEqual(leftGroup, "D") XCTAssertEqual(rightGroup, "A") + XCTAssertEqual( + errors[0].message, + "adjacent operators are in unordered precedence groups 'D' and 'A'") } } } From 297c45e6fbe5986df163b88f4ca07e9ad7ce5490 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 21 Aug 2022 11:29:46 -0700 Subject: [PATCH 16/50] Add infrastructure to make it easy to test the result of sequence folding. Introduce an assertion operation `assertExpectedFold` that takes a string containing the source code to be folded along with a fully-parenthesized version of the same source code. The former will go through the sequence folding operation, while the latter will go through a separate pass that identifiers parenthesized sequence expressions for a binary operator and map them to infix expression syntax. If it fails, we'll get a syntax tree diff. Example usage: let opPrecedence = OperatorPrecedence.logicalOperators try opPrecedence.assertExpectedFold("x || y && w", "(x || (y && w))") --- .../OperatorPrecedence.swift | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift index 55cc5fd87e7..cfc841c893a 100644 --- a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift +++ b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift @@ -2,6 +2,7 @@ import XCTest import SwiftSyntax import SwiftParser import SwiftOperatorPrecedence +import _SwiftSyntaxTestSupport /// Visitor that looks for ExprSequenceSyntax nodes. private class ExprSequenceSearcher: SyntaxAnyVisitor { @@ -28,8 +29,64 @@ extension SyntaxProtocol { } } +/// A syntax rewriter that folds explicitly-parenthesized sequence expressions +/// into a structured syntax tree. +class ExplicitParenFolder : SyntaxRewriter { + override func visit(_ node: TupleExprSyntax) -> ExprSyntax { + // Identify syntax nodes of the form (x + y), which is a + // TupleExprSyntax(SequenceExpr(x, BinaryOperatorExprSyntax, y))./ + guard node.elementList.count == 1, + let firstNode = node.elementList.first, + firstNode.label == nil, + let sequenceExpr = firstNode.expression.as(SequenceExprSyntax.self), + sequenceExpr.elements.count == 3, + let leftOperand = sequenceExpr.elements.first, + let middleExpr = sequenceExpr.elements.removingFirst().first, + let operatorExpr = middleExpr.as(BinaryOperatorExprSyntax.self), + let rightOperand = + sequenceExpr.elements.removingFirst().removingFirst().first + else { + return ExprSyntax(node) + } + + return ExprSyntax( + InfixOperatorExprSyntax( + leftOperand: visit(Syntax(leftOperand)).as(ExprSyntax.self)!, + operatorOperand: ExprSyntax(operatorExpr), + rightOperand: visit(Syntax(rightOperand)).as(ExprSyntax.self)!) + ) + } +} + +extension OperatorPrecedence { + /// Assert that parsing and folding the given "unfolded" source code + /// produces the same syntax tree as the fully-parenthesized version of + /// the same source. + /// + /// The `expectedSource` should be a fully-parenthesized expression, e.g., + /// `(a + (b * c))` that expresses how the initial code should have been + /// folded. + func assertExpectedFold( + _ source: String, + _ fullyParenthesizedSource: String + ) throws { + // Parse and fold the source we're testing. + let parsed = try Parser.parse(source: source) + let foldedSyntax = try foldAll(parsed) + XCTAssertFalse(foldedSyntax.containsExprSequence) + + // Parse and "fold" the parenthesized version. + let parenthesizedParsed = try Parser.parse(source: fullyParenthesizedSource) + let parenthesizedSyntax = ExplicitParenFolder().visit(parenthesizedParsed) + XCTAssertFalse(parenthesizedSyntax.containsExprSequence) + + // Make sure the two have the same structure. + XCTAssertSameStructure(foldedSyntax, parenthesizedSyntax) + } +} + public class OperatorPrecedenceTests: XCTestCase { - func testLogicalExprs() throws { + func testLogicalExprsSingle() throws { let opPrecedence = OperatorPrecedence.logicalOperators let parsed = try Parser.parse(source: "x && y || w && v || z") let sequenceExpr = @@ -39,6 +96,12 @@ public class OperatorPrecedenceTests: XCTestCase { XCTAssertNil(foldedExpr.as(SequenceExprSyntax.self)) } + func testLogicalExprs() throws { + let opPrecedence = OperatorPrecedence.logicalOperators + try opPrecedence.assertExpectedFold("x && y || w", "((x && y) || w)") + try opPrecedence.assertExpectedFold("x || y && w", "(x || (y && w))") + } + func testSwiftExprs() throws { let opPrecedence = OperatorPrecedence.standardOperators let parsed = try Parser.parse(source: "(x + y > 17) && x && y || w && v || z") From 472020ed6c73953644d4dc479c5c0829dd2d08ce Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 27 Aug 2022 23:33:10 -0700 Subject: [PATCH 17/50] Adapt to upstream changes in diagnostics handling --- Package.swift | 2 +- .../OperatorPrecedenceError+Diagnostics.swift | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index fca660c9bab..0f81845dcc2 100644 --- a/Package.swift +++ b/Package.swift @@ -122,7 +122,7 @@ let package = Package( ), .target( name: "SwiftOperatorPrecedence", - dependencies: ["SwiftSyntax", "SwiftParser"] + dependencies: ["SwiftSyntax", "SwiftParser", "SwiftDiagnostics"] ), .executableTarget( name: "lit-test-helper", diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift index aa589ecd8a5..cf175061727 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift @@ -10,10 +10,15 @@ // //===----------------------------------------------------------------------===// +import SwiftDiagnostics import SwiftParser import SwiftSyntax extension OperatorPrecedenceError : DiagnosticMessage { + public var severity: DiagnosticSeverity { + .error + } + public var message: String { switch self { case .groupAlreadyExists(let existing, _): @@ -57,8 +62,8 @@ extension OperatorPrecedenceError : DiagnosticMessage { } } - public var diagnosticID: DiagnosticMessageID { - DiagnosticMessageID("SwiftOperatorPrecedence/\(diagnosticCaseID)") + public var diagnosticID: MessageID { + MessageID(domain: "SwiftOperatorPrecedence", id: diagnosticCaseID) } } From 188bd7c4531bd7abe32dffee97361125c0a4a6bb Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 27 Aug 2022 23:42:44 -0700 Subject: [PATCH 18/50] Fold the ternary operator a ? b : c. --- .../OperatorPrecedence+Folding.swift | 49 ++++++++++++++++--- .../OperatorPrecedence.swift | 25 ++++++---- 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift index b3b26304060..01c664094c3 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift @@ -59,18 +59,51 @@ extension OperatorPrecedence { ) } + // The ternary operator has a fixed precedence group name. + if let ternaryExpr = expr.as(UnresolvedTernaryExprSyntax.self) { + return "TernaryPrecedence" + } + // FIXME: Handle all of the language-defined precedence relationships. return nil } /// Form a binary operation expression for, e.g., a + b. - private func makeBinaryOperationExpr( + @_spi(Testing) + public static func makeBinaryOperationExpr( lhs: ExprSyntax, op: ExprSyntax, rhs: ExprSyntax ) -> ExprSyntax { - ExprSyntax( - InfixOperatorExprSyntax( - leftOperand: lhs, operatorOperand: op, rightOperand: rhs) + // The form of the binary operation depends on the operator itself, + // which will be one of the unresolved infix operators. + + // An operator such as '+'. + if let binaryOperatorExpr = op.as(BinaryOperatorExprSyntax.self) { + return ExprSyntax( + InfixOperatorExprSyntax( + leftOperand: lhs, + binaryOperatorExpr.unexpectedBeforeOperatorToken, + operatorOperand: op, + rightOperand: rhs) ) + } + + // A ternary operator x ? y : z. + if let ternaryExpr = op.as(UnresolvedTernaryExprSyntax.self) { + return ExprSyntax( + TernaryExprSyntax( + conditionExpression: lhs, + ternaryExpr.unexpectedBeforeQuestionMark, + questionMark: ternaryExpr.questionMark, + ternaryExpr.unexpectedBetweenQuestionMarkAndFirstChoice, + firstChoice: ternaryExpr.firstChoice, + ternaryExpr.unexpectedBetweenFirstChoiceAndColonMark, + colonMark: ternaryExpr.colonMark, + secondChoice: rhs) + ) + } + + // FIXME: Fallback that we should never need + fatalError("Unknown binary operator") } /// Determine the associativity between two precedence groups. @@ -195,7 +228,7 @@ extension OperatorPrecedence { case .left: // Apply left-associativity immediately by folding the first two // operands. - lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) + lhs = Self.makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) op1 = op2 op1Precedence = op2Precedence rest = rest.dropFirst() @@ -226,7 +259,7 @@ extension OperatorPrecedence { errorHandler: errorHandler ) - lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) + lhs = Self.makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) // If we've drained the entire sequence, we're done. if rest.isEmpty { @@ -254,13 +287,13 @@ extension OperatorPrecedence { } // Recover by folding arbitrarily at this operator, then continuing. - lhs = makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) + lhs = Self.makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) return try fold(lhs, rest: &rest, bound: bound, errorHandler: errorHandler) } } // Fold LHS and RHS together and declare completion. - return makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) + return Self.makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) } /// "Fold" a sequence expression into a structured syntax tree. diff --git a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift index cfc841c893a..6f90a2d32e8 100644 --- a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift +++ b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift @@ -1,7 +1,7 @@ import XCTest import SwiftSyntax import SwiftParser -import SwiftOperatorPrecedence +@_spi(Testing) import SwiftOperatorPrecedence import _SwiftSyntaxTestSupport /// Visitor that looks for ExprSequenceSyntax nodes. @@ -33,8 +33,8 @@ extension SyntaxProtocol { /// into a structured syntax tree. class ExplicitParenFolder : SyntaxRewriter { override func visit(_ node: TupleExprSyntax) -> ExprSyntax { - // Identify syntax nodes of the form (x + y), which is a - // TupleExprSyntax(SequenceExpr(x, BinaryOperatorExprSyntax, y))./ + // Identify syntax nodes of the form (x (op) y), which is a + // TupleExprSyntax(SequenceExpr(x, (op), y)). guard node.elementList.count == 1, let firstNode = node.elementList.first, firstNode.label == nil, @@ -42,19 +42,17 @@ class ExplicitParenFolder : SyntaxRewriter { sequenceExpr.elements.count == 3, let leftOperand = sequenceExpr.elements.first, let middleExpr = sequenceExpr.elements.removingFirst().first, - let operatorExpr = middleExpr.as(BinaryOperatorExprSyntax.self), let rightOperand = sequenceExpr.elements.removingFirst().removingFirst().first else { return ExprSyntax(node) } - return ExprSyntax( - InfixOperatorExprSyntax( - leftOperand: visit(Syntax(leftOperand)).as(ExprSyntax.self)!, - operatorOperand: ExprSyntax(operatorExpr), - rightOperand: visit(Syntax(rightOperand)).as(ExprSyntax.self)!) - ) + return OperatorPrecedence.makeBinaryOperationExpr( + lhs: visit(Syntax(leftOperand)).as(ExprSyntax.self)!, + op: visit(Syntax(middleExpr)).as(ExprSyntax.self)!, + rhs: visit(Syntax(rightOperand)).as(ExprSyntax.self)! + ) } } @@ -306,4 +304,11 @@ public class OperatorPrecedenceTests: XCTestCase { "adjacent operators are in unordered precedence groups 'D' and 'A'") } } + + func testTernaryExpr() throws { + let opPrecedence = OperatorPrecedence.standardOperators + try opPrecedence.assertExpectedFold( + "b + c ? y : z ? z2 : z3", + "((b + c) ? y : (z ? z2 : z3))") + } } From 7ff59a4bb75aead30203087a67329d52efe9d4fb Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 27 Aug 2022 23:47:20 -0700 Subject: [PATCH 19/50] Add sequence folding for the assignment operator. --- .../OperatorPrecedence+Folding.swift | 20 ++++++++++++++++--- .../OperatorPrecedence.swift | 6 ++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift index 01c664094c3..9d743aeb8a0 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift @@ -60,10 +60,15 @@ extension OperatorPrecedence { } // The ternary operator has a fixed precedence group name. - if let ternaryExpr = expr.as(UnresolvedTernaryExprSyntax.self) { + if expr.is(UnresolvedTernaryExprSyntax.self) { return "TernaryPrecedence" } + // An assignment operator has fixed precedence. + if expr.is(AssignmentExprSyntax.self) { + return "AssignmentPrecedence" + } + // FIXME: Handle all of the language-defined precedence relationships. return nil } @@ -81,8 +86,7 @@ extension OperatorPrecedence { return ExprSyntax( InfixOperatorExprSyntax( leftOperand: lhs, - binaryOperatorExpr.unexpectedBeforeOperatorToken, - operatorOperand: op, + operatorOperand: ExprSyntax(binaryOperatorExpr), rightOperand: rhs) ) } @@ -102,6 +106,16 @@ extension OperatorPrecedence { ) } + // An assignment operator x = y. + if let assignExpr = op.as(AssignmentExprSyntax.self) { + return ExprSyntax( + InfixOperatorExprSyntax( + leftOperand: lhs, + operatorOperand: ExprSyntax(assignExpr), + rightOperand: rhs) + ) + } + // FIXME: Fallback that we should never need fatalError("Unknown binary operator") } diff --git a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift index 6f90a2d32e8..3dee58de945 100644 --- a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift +++ b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift @@ -118,6 +118,12 @@ public class OperatorPrecedenceTests: XCTestCase { XCTAssertFalse(foldedAll.containsExprSequence) } + func testAssignExprs() throws { + let opPrecedence = OperatorPrecedence.standardOperators + try opPrecedence.assertExpectedFold("a = b + c", "(a = (b + c))") + try opPrecedence.assertExpectedFold("a = b = c", "(a = (b = c))") + } + func testParsedLogicalExprs() throws { let logicalOperatorSources = """ From 1beb8417610a013cfbac7eb408135823bc617a7d Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 28 Aug 2022 00:05:54 -0700 Subject: [PATCH 20/50] Fold explicit casts --- .../OperatorPrecedence+Folding.swift | 61 ++++++++++++++++--- .../OperatorPrecedence.swift | 6 ++ 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift index 9d743aeb8a0..3afd54adde6 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift @@ -11,6 +11,12 @@ //===----------------------------------------------------------------------===// import SwiftSyntax +extension ExprSyntax { + // Is this an unresolved explicit cast? + fileprivate var isUnresolvedExplicitCast: Bool { + self.is(UnresolvedIsExprSyntax.self) || self.is(UnresolvedAsExprSyntax.self) + } +} extension OperatorPrecedence { private struct PrecedenceBound { let groupName: PrecedenceGroupName? @@ -69,6 +75,11 @@ extension OperatorPrecedence { return "AssignmentPrecedence" } + // Cast operators have fixed precedence. + if expr.isUnresolvedExplicitCast { + return "CastingPrecedence" + } + // FIXME: Handle all of the language-defined precedence relationships. return nil } @@ -116,6 +127,34 @@ extension OperatorPrecedence { ) } + // An "is" type check. + if let isExpr = op.as(UnresolvedIsExprSyntax.self) { + // FIXME: Do we actually have a guarantee that the right-hand side is a + // type expression here? + return ExprSyntax( + IsExprSyntax( + expression: lhs, + isExpr.unexpectedBeforeIsTok, + isTok: isExpr.isTok, + typeName: rhs.as(TypeExprSyntax.self)!.type) + ) + } + + // An "as" cast. + if let asExpr = op.as(UnresolvedAsExprSyntax.self) { + // FIXME: Do we actually have a guarantee that the right-hand side is a + // type expression here? + return ExprSyntax( + AsExprSyntax( + expression: lhs, + asExpr.unexpectedBeforeAsTok, + asTok: asExpr.asTok, + asExpr.unexpectedBetweenAsTokAndQuestionOrExclamationMark, + questionOrExclamationMark: asExpr.questionOrExclamationMark, + typeName: rhs.as(TypeExprSyntax.self)!.type) + ) + } + // FIXME: Fallback that we should never need fatalError("Unknown binary operator") } @@ -203,18 +242,22 @@ extension OperatorPrecedence { rest = rest.dropFirst() while !rest.isEmpty { - #if compiler(>=10.0) && false // If the operator is a cast operator, the RHS can't extend past the type // that's part of the cast production. - if (isa(op1.op)) { - LHS = makeBinOp(Ctx, op1.op, LHS, RHS, op1.precedence, S.empty()); - op1 = getNextOperator(); - if (!op1) return LHS; - RHS = S[1]; - S = S.slice(2); - continue; + if op1.isUnresolvedExplicitCast { + lhs = Self.makeBinaryOperationExpr(lhs: lhs, op: op1, rhs: rhs) + guard let (newOp1, newOp1Precedence) = try getNextOperator() else { + return lhs + } + + op1 = newOp1 + op1Precedence = newOp1Precedence + + rest = rest.dropFirst() + rhs = rest.first! + rest = rest.dropFirst() + continue } - #endif // Pull out the next binary operator. let op2 = rest.first! diff --git a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift index 3dee58de945..3169aa9efbe 100644 --- a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift +++ b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift @@ -124,6 +124,12 @@ public class OperatorPrecedenceTests: XCTestCase { try opPrecedence.assertExpectedFold("a = b = c", "(a = (b = c))") } + func testCastExprs() throws { + let opPrecedence = OperatorPrecedence.standardOperators + try opPrecedence.assertExpectedFold("a is (b)", "(a is (b))") + try opPrecedence.assertExpectedFold("a as c == nil", "((a as c) == nil)") + } + func testParsedLogicalExprs() throws { let logicalOperatorSources = """ From 96295ad559829f0276b975587bd707b4a85abe57 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 28 Aug 2022 00:14:37 -0700 Subject: [PATCH 21/50] Add folding for the arrow operator --- .../OperatorPrecedence+Folding.swift | 16 +++++++++++++++- .../OperatorPrecedence.swift | 8 ++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift index 3afd54adde6..d549cfdceb9 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift @@ -80,7 +80,11 @@ extension OperatorPrecedence { return "CastingPrecedence" } - // FIXME: Handle all of the language-defined precedence relationships. + // The arrow operator has fixed precedence. + if expr.is(ArrowExprSyntax.self) { + return "FunctionArrowPrecedence" + } + return nil } @@ -155,6 +159,16 @@ extension OperatorPrecedence { ) } + // An arrow expression (->). + if let arrowExpr = op.as(ArrowExprSyntax.self) { + return ExprSyntax( + InfixOperatorExprSyntax( + leftOperand: lhs, + operatorOperand: ExprSyntax(arrowExpr), + rightOperand: rhs) + ) + } + // FIXME: Fallback that we should never need fatalError("Unknown binary operator") } diff --git a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift index 3169aa9efbe..1b79d3a6156 100644 --- a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift +++ b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift @@ -130,6 +130,14 @@ public class OperatorPrecedenceTests: XCTestCase { try opPrecedence.assertExpectedFold("a as c == nil", "((a as c) == nil)") } + func testArrowExpr() throws { + let opPrecedence = OperatorPrecedence.standardOperators + try opPrecedence.assertExpectedFold( + "a = b -> c -> d", + "(a = (b -> (c -> d)))" + ) + } + func testParsedLogicalExprs() throws { let logicalOperatorSources = """ From 67635c49b0a94fa77e5b52da3d17d0412e0b592b Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 1 Sep 2022 14:39:04 -0700 Subject: [PATCH 22/50] Account for the removal of XCTAssertSameStructure --- Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift index 1b79d3a6156..d3cdbc1dffd 100644 --- a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift +++ b/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift @@ -79,7 +79,12 @@ extension OperatorPrecedence { XCTAssertFalse(parenthesizedSyntax.containsExprSequence) // Make sure the two have the same structure. - XCTAssertSameStructure(foldedSyntax, parenthesizedSyntax) + let subtreeMatcher = SubtreeMatcher(Syntax(foldedSyntax), markers: [:]) + do { + try subtreeMatcher.assertSameStructure(Syntax(parenthesizedSyntax)) + } catch { + XCTFail("Matching for a subtree failed with error: \(error)") + } } } From 867e812ef43799cdff370f1e4cc8cae356539bd7 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 1 Sep 2022 14:51:48 -0700 Subject: [PATCH 23/50] Make the declaration of the default operator precedence properties simpler. --- .../OperatorPrecedence+Defaults.swift | 335 +++++++----------- .../OperatorPrecedence.swift | 15 + 2 files changed, 146 insertions(+), 204 deletions(-) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Defaults.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Defaults.swift index d31ad77b9e7..f19fb2d3cc1 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Defaults.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Defaults.swift @@ -16,23 +16,24 @@ extension OperatorPrecedence { /// Operator precedence graph for the logical operators '&&' and '||', for /// example as it is used in `#if` processing. public static var logicalOperators: OperatorPrecedence { - var opPrecedence = OperatorPrecedence() - - try! opPrecedence.record( - Operator(kind: .infix, name: "&&", - precedenceGroup: "LogicalConjunctionPrecedence")) - try! opPrecedence.record( - Operator(kind: .infix, name: "||", - precedenceGroup: "LogicalDisjunctionPrecedence")) - try! opPrecedence.record( + let precedenceGroups: [PrecedenceGroup] = [ PrecedenceGroup(name: "LogicalConjunctionPrecedence", associativity: .left, assignment: false, - relations: [.higherThan("LogicalDisjunctionPrecedence")])) - try! opPrecedence.record( + relations: [.higherThan("LogicalDisjunctionPrecedence")]), PrecedenceGroup(name: "LogicalDisjunctionPrecedence", associativity: .left, assignment: false, - relations: [])) - return opPrecedence + relations: []) + ] + + let operators: [Operator] = [ + Operator(kind: .infix, name: "&&", + precedenceGroup: "LogicalConjunctionPrecedence"), + Operator(kind: .infix, name: "||", + precedenceGroup: "LogicalDisjunctionPrecedence") + ] + + return try! OperatorPrecedence( + precedenceGroups: precedenceGroups, operators: operators) } /// Operator precedence graph for the Swift standard library. @@ -44,436 +45,362 @@ extension OperatorPrecedence { /// because it does not incorporate user-defined operators, it will only /// ever be useful for a quick approximation. public static var standardOperators: OperatorPrecedence { - var opPrecedence = OperatorPrecedence() - - try! opPrecedence.record( + let precedenceGroups: [PrecedenceGroup] = [ PrecedenceGroup( name: "AssignmentPrecedence", associativity: .right, assignment: true - ) - ) - try! opPrecedence.record( + ), + PrecedenceGroup( name: "FunctionArrowPrecedence", associativity: .right, relations: [.higherThan("AssignmentPrecedence")] - ) - ) - try! opPrecedence.record( + ), + PrecedenceGroup( name: "TernaryPrecedence", associativity: .right, relations: [.higherThan("FunctionArrowPrecedence")] - ) - ) - try! opPrecedence.record( + ), + PrecedenceGroup( name: "DefaultPrecedence", relations: [.higherThan("TernaryPrecedence")] - ) - ) - try! opPrecedence.record( + ), + PrecedenceGroup( name: "LogicalDisjunctionPrecedence", associativity: .left, relations: [.higherThan("TernaryPrecedence")] - ) - ) - try! opPrecedence.record( + ), + PrecedenceGroup( name: "LogicalConjunctionPrecedence", associativity: .left, relations: [.higherThan("LogicalDisjunctionPrecedence")] - ) - ) - try! opPrecedence.record( + ), + PrecedenceGroup( name: "ComparisonPrecedence", relations: [.higherThan("LogicalConjunctionPrecedence")] - ) - ) - try! opPrecedence.record( + ), + PrecedenceGroup( name: "NilCoalescingPrecedence", associativity: .right, relations: [.higherThan("ComparisonPrecedence")] - ) - ) - try! opPrecedence.record( + ), + PrecedenceGroup( name: "CastingPrecedence", relations: [.higherThan("NilCoalescingPrecedence")] - ) - ) - try! opPrecedence.record( + ), + PrecedenceGroup( name: "RangeFormationPrecedence", relations: [.higherThan("CastingPrecedence")] - ) - ) - try! opPrecedence.record( + ), + PrecedenceGroup( name: "AdditionPrecedence", associativity: .left, relations: [.higherThan("RangeFormationPrecedence")] - ) - ) - try! opPrecedence.record( + ), + PrecedenceGroup( name: "MultiplicationPrecedence", associativity: .left, relations: [.higherThan("AdditionPrecedence")] - ) - ) - try! opPrecedence.record( + ), + PrecedenceGroup( name: "BitwiseShiftPrecedence", relations: [.higherThan("MultiplicationPrecedence")] ) - ) + ] - // "Exponentiative" - - try! opPrecedence.record( + let operators: [Operator] = [ + // "Exponentiative" Operator( kind: .infix, name: "<<", precedenceGroup: "BitwiseShiftPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "&<<", precedenceGroup: "BitwiseShiftPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: ">>", precedenceGroup: "BitwiseShiftPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "&>>", precedenceGroup: "BitwiseShiftPrecedence" - ) - ) + ), - // "Multiplicative" - - try! opPrecedence.record( + // "Multiplicative" Operator( kind: .infix, name: "*", precedenceGroup: "MultiplicationPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "&*", precedenceGroup: "MultiplicationPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "/", precedenceGroup: "MultiplicationPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "%", precedenceGroup: "MultiplicationPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "&", precedenceGroup: "MultiplicationPrecedence" - ) - ) - - // "Additive" + ), - try! opPrecedence.record( + // "Additive" Operator( kind: .infix, name: "+", precedenceGroup: "AdditionPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "&+", precedenceGroup: "AdditionPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "-", precedenceGroup: "AdditionPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "&-", precedenceGroup: "AdditionPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "|", precedenceGroup: "AdditionPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "^", precedenceGroup: "AdditionPrecedence" - ) - ) + ), - // FIXME: is this the right precedence level for "..."? - try! opPrecedence.record( Operator( kind: .infix, name: "...", precedenceGroup: "RangeFormationPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "..<", precedenceGroup: "RangeFormationPrecedence" - ) - ) - - // "Coalescing" + ), - try! opPrecedence.record( + // "Coalescing" Operator( kind: .infix, name: "??", precedenceGroup: "NilCoalescingPrecedence" - ) - ) - - // "Comparative" + ), - try! opPrecedence.record( + // "Comparative" Operator( kind: .infix, name: "<", precedenceGroup: "ComparisonPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "<=", precedenceGroup: "ComparisonPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: ">", precedenceGroup: "ComparisonPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: ">=", precedenceGroup: "ComparisonPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "==", precedenceGroup: "ComparisonPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "!=", precedenceGroup: "ComparisonPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "===", precedenceGroup: "ComparisonPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "!==", precedenceGroup: "ComparisonPrecedence" - ) - ) - // FIXME: ~= will be built into the compiler. - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "~=", precedenceGroup: "ComparisonPrecedence" - ) - ) + ), - // "Conjunctive" - - try! opPrecedence.record( + // "Conjunctive" Operator( kind: .infix, name: "&&", precedenceGroup: "LogicalConjunctionPrecedence" - ) - ) + ), - // "Disjunctive" - - try! opPrecedence.record( + // "Disjunctive" Operator( kind: .infix, name: "||", precedenceGroup: "LogicalDisjunctionPrecedence" - ) - ) + ), + - try! opPrecedence.record( Operator( kind: .infix, name: "*=", precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "&*=", precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "/=", precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "%=", precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "+=", precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "&+=", precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "-=", precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "&-=", precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "<<=", precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "&<<=", precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: ">>=", precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "&>>=", precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "&=", precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "^=", precedenceGroup: "AssignmentPrecedence" - ) - ) - try! opPrecedence.record( + ), + Operator( kind: .infix, name: "|=", precedenceGroup: "AssignmentPrecedence" - ) - ) + ), - try! opPrecedence.record( Operator( kind: .infix, name: "~>", precedenceGroup: nil ) - ) + ] - return opPrecedence + return try! OperatorPrecedence( + precedenceGroups: precedenceGroups, operators: operators) } } diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift index 1168874b4d7..5756f7ab003 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift @@ -20,6 +20,21 @@ public struct OperatorPrecedence { public init() { } + /// Initialize the operator precedence instance with a given set of + /// operators and precedence groups. + public init( + precedenceGroups: [PrecedenceGroup], + operators: [Operator], + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows { + for group in precedenceGroups { + try record(group, errorHandler: errorHandler) + } + for op in operators { + try record(op, errorHandler: errorHandler) + } + } + /// Record the operator, if it matters. /// FIXME: Terrible API used only for tests public mutating func record( From 498bde9c62af4e384d2c1eecb59ffa11da5d69a8 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 1 Sep 2022 14:54:47 -0700 Subject: [PATCH 24/50] Hide the `OperatorPrecedence.record` APIs. We don't need them. --- Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift index 5756f7ab003..c1df883303c 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift @@ -36,8 +36,7 @@ public struct OperatorPrecedence { } /// Record the operator, if it matters. - /// FIXME: Terrible API used only for tests - public mutating func record( + mutating func record( _ op: Operator, errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } ) rethrows { @@ -53,8 +52,7 @@ public struct OperatorPrecedence { } /// Record the precedence group. - /// FIXME: Terrible API used only for tests - public mutating func record( + mutating func record( _ group: PrecedenceGroup, errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } ) rethrows { From fb2e36a688fae67376a3d14c4d39fa53677d1547 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 1 Sep 2022 16:35:34 -0700 Subject: [PATCH 25/50] Factor out the precedence relation search into a separate function --- .../PrecedenceGraph.swift | 94 +++++++++---------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift b/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift index 423e4ab0a0b..4a9531c8a19 100644 --- a/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift +++ b/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift @@ -43,29 +43,21 @@ struct PrecedenceGraph { return precedenceGroups[groupName] } - /// Determine the precedence relationship between two precedence groups. - /// - /// Follow the precedence relationships among the precedence groups to - /// determine the precedence of the start group relative to the end group. - func precedence( - relating startGroupName: PrecedenceGroupName, - to endGroupName: PrecedenceGroupName, - startSyntax: SyntaxProtocol?, - endSyntax: SyntaxProtocol?, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } - ) rethrows -> Precedence { - if startGroupName == endGroupName { - return .unrelated - } - + /// Search the precedence-group relationships, starting at the given + /// (fromGroup, fromSyntax) and following precedence groups in the + /// specified direction. + private func searchRelationships( + initialGroupName: PrecedenceGroupName, initialSyntax: SyntaxProtocol?, + targetGroupName: PrecedenceGroupName, + direction: PrecedenceRelation.Kind, + errorHandler: OperatorPrecedenceErrorHandler + ) rethrows -> Precedence? { // Keep track of all of the groups we have seen during our exploration of // the graph. This detects cycles and prevents extraneous work. var groupsSeen: Set = [] - // Walk all of the lower-than relationships from the end group. If we - // reach the start group, the start has lower precedence than the end. var stack: [(PrecedenceGroupName, SyntaxProtocol?)] = - [(endGroupName, endSyntax)] + [(initialGroupName, initialSyntax)] while let (currentGroupName, currentGroupSyntax) = stack.popLast() { guard let currentGroup = lookupGroup(currentGroupName) else { try errorHandler( @@ -74,11 +66,17 @@ struct PrecedenceGraph { } for relation in currentGroup.relations { - if relation.kind == .lowerThan { - // If we hit our start group, we're done. + if relation.kind == direction { + // If we hit our initial group, we're done. let otherGroupName = relation.groupName - if otherGroupName == startGroupName { - return .lowerThan + if otherGroupName == targetGroupName { + switch direction { + case .lowerThan: + return .lowerThan + + case .higherThan: + return .higherThan + } } if groupsSeen.insert(otherGroupName).inserted { @@ -88,34 +86,34 @@ struct PrecedenceGraph { } } - // Walk all of the higher-than relationships from the start group. If we - // reach the end group, the start has higher precedence than the end. - assert(stack.isEmpty) - groupsSeen.removeAll() - stack.append((startGroupName, startSyntax)) - while let (currentGroupName, currentGroupSyntax) = stack.popLast() { - guard let currentGroup = lookupGroup(currentGroupName) else { - try errorHandler( - .missingGroup(currentGroupName, referencedFrom: currentGroupSyntax)) - continue - } - - for relation in currentGroup.relations { - if relation.kind == .higherThan { - // If we hit our end group, we're done. - let otherGroupName = relation.groupName - if otherGroupName == endGroupName { - return .higherThan - } + return nil + } - if groupsSeen.insert(otherGroupName).inserted { - stack.append((otherGroupName, relation.syntax)) - } - } - } + /// Determine the precedence relationship between two precedence groups. + /// + /// Follow the precedence relationships among the precedence groups to + /// determine the precedence of the start group relative to the end group. + func precedence( + relating startGroupName: PrecedenceGroupName, + to endGroupName: PrecedenceGroupName, + startSyntax: SyntaxProtocol?, + endSyntax: SyntaxProtocol?, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows -> Precedence { + if startGroupName == endGroupName { + return .unrelated } - // The two are incomparable. - return .unrelated + // Walk all of the relationships from the end down, then from the beginning + // up, to determine whether there is a relation between the two groups. + return try searchRelationships( + initialGroupName: endGroupName, initialSyntax: endSyntax, + targetGroupName: startGroupName, direction: .lowerThan, + errorHandler: errorHandler + ) ?? searchRelationships( + initialGroupName: startGroupName, initialSyntax: startSyntax, + targetGroupName: endGroupName, direction: .higherThan, + errorHandler: errorHandler + ) ?? .unrelated } } From dabf779ce178e8d4583c473a9af8a1887987e38a Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 1 Sep 2022 18:20:38 -0700 Subject: [PATCH 26/50] Teach precedence.precedence(...) to look in both directions --- .../OperatorPrecedence+Folding.swift | 19 ++++++------- .../PrecedenceGraph.swift | 28 ++++++++++++++++++- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift index d549cfdceb9..bffd0755053 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift @@ -196,23 +196,22 @@ extension OperatorPrecedence { return group.associativity } - if try precedence( + let prec = try precedence( relating: firstGroup, to: secondGroup, startSyntax: firstGroupSyntax, endSyntax: secondGroupSyntax, errorHandler: errorHandler - ) == .higherThan { + ) + + switch prec { + case .higherThan: return .left - } - if try precedence( - relating: secondGroup, to: firstGroup, - startSyntax: secondGroupSyntax, endSyntax: firstGroupSyntax, - errorHandler: errorHandler - ) == .higherThan { + case .lowerThan: return .right - } - return .none + case .unrelated: + return .none + } } /// "Fold" an expression sequence where the left-hand side has been broken diff --git a/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift b/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift index 4a9531c8a19..2ea2b48d083 100644 --- a/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift +++ b/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift @@ -12,6 +12,20 @@ enum Precedence { case unrelated case higherThan case lowerThan + + /// Flip the precedence order around. + var flipped: Precedence { + switch self { + case .unrelated: + return .unrelated + + case .higherThan: + return .lowerThan + + case .lowerThan: + return .higherThan + } + } } /// A graph formed from a set of precedence groups, which can be used to @@ -114,6 +128,18 @@ struct PrecedenceGraph { initialGroupName: startGroupName, initialSyntax: startSyntax, targetGroupName: endGroupName, direction: .higherThan, errorHandler: errorHandler - ) ?? .unrelated + ) ?? searchRelationships( + initialGroupName: startGroupName, initialSyntax: startSyntax, + targetGroupName: endGroupName, direction: .lowerThan, + errorHandler: errorHandler + ).map { + $0.flipped + } ?? searchRelationships( + initialGroupName: endGroupName, initialSyntax: endSyntax, + targetGroupName: startGroupName, direction: .higherThan, + errorHandler: errorHandler + ).map { + $0.flipped + } ?? .unrelated } } From 230287fa99868add094a31ffe05751254f807072 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 1 Sep 2022 18:30:41 -0700 Subject: [PATCH 27/50] Remove unused function --- .../OperatorPrecedence.swift | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift index c1df883303c..e972e44af5e 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift @@ -76,26 +76,6 @@ extension OperatorPrecedence { return op.precedenceGroup } - /// Look for the precedence group corresponding to the given operator. - func lookupOperatorPrecedenceGroup( - _ operatorName: OperatorName, - referencedFrom syntax: SyntaxProtocol?, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } - ) rethrows -> PrecedenceGroup? { - guard let groupName = try lookupOperatorPrecedenceGroupName( - operatorName, referencedFrom: syntax, errorHandler: errorHandler) - else { - return nil - } - - guard let group = precedenceGraph.lookupGroup(groupName) else { - try errorHandler(.missingGroup(groupName, referencedFrom: syntax)) - return nil - } - - return group - } - /// Determine the relative precedence between two precedence groups. func precedence( relating startGroupName: PrecedenceGroupName?, From 76c71db82a57e42e867638da46c9b7bde9899015 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 1 Sep 2022 21:38:55 -0700 Subject: [PATCH 28/50] Improve naming and documentation for internal operations. Plus, remove an unnecessary function whose optionality was unused. --- .../OperatorPrecedence+Folding.swift | 12 +++++------ .../OperatorPrecedence.swift | 20 ------------------- .../PrecedenceGraph.swift | 4 ++++ 3 files changed, 10 insertions(+), 26 deletions(-) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift index bffd0755053..36fc8c17bfe 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift @@ -29,7 +29,7 @@ extension OperatorPrecedence { private func shouldConsiderOperator( fromGroup groupName: PrecedenceGroupName?, in bound: PrecedenceBound, - fromGroupSyntax: SyntaxProtocol?, + operatorSyntax: SyntaxProtocol?, errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } ) rethrows -> Bool { guard let boundGroupName = bound.groupName else { @@ -44,9 +44,9 @@ extension OperatorPrecedence { return !bound.isStrict } - return try precedence( + return try precedenceGraph.precedence( relating: groupName, to: boundGroupName, - startSyntax: fromGroupSyntax, endSyntax: bound.syntax, + startSyntax: operatorSyntax, endSyntax: bound.syntax, errorHandler: errorHandler ) != .lowerThan } @@ -196,7 +196,7 @@ extension OperatorPrecedence { return group.associativity } - let prec = try precedence( + let prec = try precedenceGraph.precedence( relating: firstGroup, to: secondGroup, startSyntax: firstGroupSyntax, endSyntax: secondGroupSyntax, errorHandler: errorHandler @@ -235,7 +235,7 @@ extension OperatorPrecedence { let opPrecedence = try lookupPrecedence( of: op, errorHandler: errorHandler) if try !shouldConsiderOperator( - fromGroup: opPrecedence, in: bound, fromGroupSyntax: op + fromGroup: opPrecedence, in: bound, operatorSyntax: op ) { return nil } @@ -280,7 +280,7 @@ extension OperatorPrecedence { // If the second operator's precedence is lower than the // precedence bound, break out of the loop. if try !shouldConsiderOperator( - fromGroup: op2Precedence, in: bound, fromGroupSyntax: op1, + fromGroup: op2Precedence, in: bound, operatorSyntax: op1, errorHandler: errorHandler ) { break diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift index e972e44af5e..a0f901966ee 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift @@ -75,24 +75,4 @@ extension OperatorPrecedence { return op.precedenceGroup } - - /// Determine the relative precedence between two precedence groups. - func precedence( - relating startGroupName: PrecedenceGroupName?, - to endGroupName: PrecedenceGroupName?, - startSyntax: SyntaxProtocol?, - endSyntax: SyntaxProtocol?, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } - ) rethrows -> Precedence { - guard let startGroupName = startGroupName, let endGroupName = endGroupName - else { - return .unrelated - } - - return try precedenceGraph.precedence( - relating: startGroupName, to: endGroupName, - startSyntax: startSyntax, endSyntax: endSyntax, - errorHandler: errorHandler - ) - } } diff --git a/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift b/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift index 2ea2b48d083..4f4de2554c5 100644 --- a/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift +++ b/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift @@ -107,6 +107,10 @@ struct PrecedenceGraph { /// /// Follow the precedence relationships among the precedence groups to /// determine the precedence of the start group relative to the end group. + /// + /// - Returns: Precedence.lowerThan if startGroupName has lower precedence + /// than endGroupName, Precedence.higherThan if startGroupName has higher + /// precedence than endGroup name, and Precedence.unrelated otherwise. func precedence( relating startGroupName: PrecedenceGroupName, to endGroupName: PrecedenceGroupName, From aaef6e70d07b233685b952ec52ea17a65fb9305c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 1 Sep 2022 21:47:27 -0700 Subject: [PATCH 29/50] Use a switch over SyntaxEnum to clean up the semantic check --- .../OperatorPrecedence+Semantics.swift | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Semantics.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Semantics.swift index f71d7824adb..9ffb2d15263 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Semantics.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Semantics.swift @@ -20,8 +20,9 @@ extension PrecedenceGroup { self.syntax = syntax for attr in syntax.groupAttributes { + switch attr.as(SyntaxEnum.self) { // Relation (lowerThan, higherThan) - if let relation = attr.as(PrecedenceGroupRelationSyntax.self) { + case .precedenceGroupRelation(let relation): let isLowerThan = relation.higherThanOrLowerThan.text == "lowerThan" for otherGroup in relation.otherNames { let otherGroupName = otherGroup.name.text @@ -32,17 +33,12 @@ extension PrecedenceGroup { self.relations.append(relation) } - continue - } - // Assignment - if let assignment = attr.as(PrecedenceGroupAssignmentSyntax.self) { + case .precedenceGroupAssignment(let assignment): self.assignment = assignment.flag.text == "true" - continue - } // Associativity - if let associativity = attr.as(PrecedenceGroupAssociativitySyntax.self) { + case .precedenceGroupAssociativity(let associativity): switch associativity.value.text { case "left": self.associativity = .left @@ -56,6 +52,9 @@ extension PrecedenceGroup { default: break } + + default: + break } } } @@ -68,15 +67,9 @@ extension Operator { init(from syntax: OperatorDeclSyntax) { self.syntax = syntax - let kindModifier = syntax.modifiers?.first { modifier in - OperatorKind(rawValue: modifier.name.text) != nil - } - - if let kindModifier = kindModifier { - kind = OperatorKind(rawValue: kindModifier.name.text)! - } else { - kind = .infix - } + kind = syntax.modifiers?.compactMap { + OperatorKind(rawValue: $0.name.text) + }.first ?? .infix name = syntax.identifier.text From 9b7148038f3e6f0eaf29312578fb4cf36950409b Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 1 Sep 2022 21:49:47 -0700 Subject: [PATCH 30/50] Minor cleanup --- .../SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md b/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md index 65db38b0bcb..4e114881292 100644 --- a/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md +++ b/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md @@ -81,7 +81,7 @@ By default, any of the operations that can produce an error, whether folding a s ```swift var errors: [OperatorPrecedenceError] = [] let foldedExpr2e = opPrecedence.foldSingle(sequenceExpr2) { error in - errors.append(error) + errors.append(error) } ``` From 39ef90c2ce917a4c5168b7381d1deeb44f6eb38b Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 1 Sep 2022 22:22:52 -0700 Subject: [PATCH 31/50] Eliminate the use of the existential SyntaxProtocol --- .../OperatorPrecedence+Folding.swift | 22 +++++++++---------- .../OperatorPrecedence.swift | 2 +- .../OperatorPrecedenceError+Diagnostics.swift | 4 +++- .../OperatorPrecedenceError.swift | 4 ++-- .../PrecedenceGraph.swift | 10 ++++----- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift index 36fc8c17bfe..1eca91d65db 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift @@ -21,7 +21,7 @@ extension OperatorPrecedence { private struct PrecedenceBound { let groupName: PrecedenceGroupName? let isStrict: Bool - let syntax: SyntaxProtocol? + let syntax: Syntax? } /// Determine whether we should consider an operator in the given group @@ -29,7 +29,7 @@ extension OperatorPrecedence { private func shouldConsiderOperator( fromGroup groupName: PrecedenceGroupName?, in bound: PrecedenceBound, - operatorSyntax: SyntaxProtocol?, + operatorSyntax: Syntax, errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } ) rethrows -> Bool { guard let boundGroupName = bound.groupName else { @@ -60,7 +60,7 @@ extension OperatorPrecedence { if let binaryExpr = expr.as(BinaryOperatorExprSyntax.self) { let operatorName = binaryExpr.operatorToken.text return try lookupOperatorPrecedenceGroupName( - operatorName, referencedFrom: binaryExpr.operatorToken, + operatorName, referencedFrom: Syntax(binaryExpr.operatorToken), errorHandler: errorHandler ) } @@ -176,9 +176,9 @@ extension OperatorPrecedence { /// Determine the associativity between two precedence groups. private func associativity( firstGroup: PrecedenceGroupName?, - firstGroupSyntax: SyntaxProtocol?, + firstGroupSyntax: Syntax?, secondGroup: PrecedenceGroupName?, - secondGroupSyntax: SyntaxProtocol?, + secondGroupSyntax: Syntax?, errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } ) rethrows -> Associativity { guard let firstGroup = firstGroup, let secondGroup = secondGroup else { @@ -235,7 +235,7 @@ extension OperatorPrecedence { let opPrecedence = try lookupPrecedence( of: op, errorHandler: errorHandler) if try !shouldConsiderOperator( - fromGroup: opPrecedence, in: bound, operatorSyntax: op + fromGroup: opPrecedence, in: bound, operatorSyntax: Syntax(op) ) { return nil } @@ -280,7 +280,7 @@ extension OperatorPrecedence { // If the second operator's precedence is lower than the // precedence bound, break out of the loop. if try !shouldConsiderOperator( - fromGroup: op2Precedence, in: bound, operatorSyntax: op1, + fromGroup: op2Precedence, in: bound, operatorSyntax: Syntax(op1), errorHandler: errorHandler ) { break @@ -288,9 +288,9 @@ extension OperatorPrecedence { let associativity = try associativity( firstGroup: op1Precedence, - firstGroupSyntax: op1, + firstGroupSyntax: Syntax(op1), secondGroup: op2Precedence, - secondGroupSyntax: op2, + secondGroupSyntax: Syntax(op2), errorHandler: errorHandler ) @@ -313,7 +313,7 @@ extension OperatorPrecedence { rhs = try fold( rhs, rest: &rest, bound: PrecedenceBound( - groupName: op1Precedence, isStrict: true, syntax: op1 + groupName: op1Precedence, isStrict: true, syntax: Syntax(op1) ), errorHandler: errorHandler ) @@ -324,7 +324,7 @@ extension OperatorPrecedence { rhs = try fold( rhs, rest: &rest, bound: PrecedenceBound( - groupName: op1Precedence, isStrict: false, syntax: op1 + groupName: op1Precedence, isStrict: false, syntax: Syntax(op1) ), errorHandler: errorHandler ) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift index a0f901966ee..603fc5b2525 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift @@ -64,7 +64,7 @@ extension OperatorPrecedence { /// Look for the precedence group corresponding to the given operator. func lookupOperatorPrecedenceGroupName( _ operatorName: OperatorName, - referencedFrom syntax: SyntaxProtocol?, + referencedFrom syntax: Syntax?, errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } ) rethrows -> PrecedenceGroupName? { guard let op = operators[operatorName] else { diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift index cf175061727..c4bcd4a9850 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift @@ -68,7 +68,9 @@ extension OperatorPrecedenceError : DiagnosticMessage { } extension OperatorPrecedenceError { - private func fixupDiagnosticDisplayNode(_ node: SyntaxProtocol?) -> Syntax { + private func fixupDiagnosticDisplayNode( + _ node: Node? + ) -> Syntax { if let node = node { return Syntax(node) } diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError.swift index 02e1fcebd6d..48b8419bc27 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError.swift @@ -18,13 +18,13 @@ public enum OperatorPrecedenceError: Error { case groupAlreadyExists(existing: PrecedenceGroup, new: PrecedenceGroup) /// The named precedence group is missing from the precedence graph. - case missingGroup(PrecedenceGroupName, referencedFrom: SyntaxProtocol?) + case missingGroup(PrecedenceGroupName, referencedFrom: Syntax?) /// Error produced when a given operator already exists. case operatorAlreadyExists(existing: Operator, new: Operator) /// The named operator is missing from the precedence graph. - case missingOperator(OperatorName, referencedFrom: SyntaxProtocol?) + case missingOperator(OperatorName, referencedFrom: Syntax?) /// No associativity relationship between operators. case incomparableOperators( diff --git a/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift b/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift index 4f4de2554c5..87a689670f2 100644 --- a/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift +++ b/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift @@ -61,7 +61,7 @@ struct PrecedenceGraph { /// (fromGroup, fromSyntax) and following precedence groups in the /// specified direction. private func searchRelationships( - initialGroupName: PrecedenceGroupName, initialSyntax: SyntaxProtocol?, + initialGroupName: PrecedenceGroupName, initialSyntax: Syntax?, targetGroupName: PrecedenceGroupName, direction: PrecedenceRelation.Kind, errorHandler: OperatorPrecedenceErrorHandler @@ -70,7 +70,7 @@ struct PrecedenceGraph { // the graph. This detects cycles and prevents extraneous work. var groupsSeen: Set = [] - var stack: [(PrecedenceGroupName, SyntaxProtocol?)] = + var stack: [(PrecedenceGroupName, Syntax?)] = [(initialGroupName, initialSyntax)] while let (currentGroupName, currentGroupSyntax) = stack.popLast() { guard let currentGroup = lookupGroup(currentGroupName) else { @@ -94,7 +94,7 @@ struct PrecedenceGraph { } if groupsSeen.insert(otherGroupName).inserted { - stack.append((otherGroupName, relation.syntax)) + stack.append((otherGroupName, relation.syntax.map { Syntax($0) })) } } } @@ -114,8 +114,8 @@ struct PrecedenceGraph { func precedence( relating startGroupName: PrecedenceGroupName, to endGroupName: PrecedenceGroupName, - startSyntax: SyntaxProtocol?, - endSyntax: SyntaxProtocol?, + startSyntax: Syntax?, + endSyntax: Syntax?, errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } ) rethrows -> Precedence { if startGroupName == endGroupName { From 4076969201d70f0f72ebdb0033c19a8afb7ddb0c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 5 Sep 2022 21:39:01 -0700 Subject: [PATCH 32/50] Document foldAll behavior that calls error handler twice. --- .../OperatorPrecedence+Folding.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift index 1eca91d65db..e4cd7323018 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift @@ -444,6 +444,11 @@ extension OperatorPrecedence { /// tree with structured syntax trees, by walking the tree and invoking /// `foldSingle` on each sequence expression it encounters. Use this to /// provide structure to an entire tree. + /// + /// Due to the inability to express the implementation of this rethrowing + /// function, a throwing error handler will end up being called twice with + /// the first error that causes it to be thrown. The first call will stop + /// the operation, then the second must also throw. public func foldAll( _ node: Node, errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } @@ -459,6 +464,7 @@ extension OperatorPrecedence { // error. if let origFatalError = folder.firstFatalError { try errorHandler(origFatalError) + fatalError("error handler did not throw again after \(origFatalError)") } return result From 28d2918f622a2a3d8332e9252069b75be6c23141 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 5 Sep 2022 21:41:39 -0700 Subject: [PATCH 33/50] Rename strange-sounding "group syntax" to "operator syntax". --- .../OperatorPrecedence+Folding.swift | 12 ++++++------ .../OperatorPrecedence+Semantics.swift | 4 ++-- .../SwiftOperatorPrecedence/PrecedenceGraph.swift | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift index e4cd7323018..8edfccb78d2 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift @@ -176,9 +176,9 @@ extension OperatorPrecedence { /// Determine the associativity between two precedence groups. private func associativity( firstGroup: PrecedenceGroupName?, - firstGroupSyntax: Syntax?, + firstOperatorSyntax: Syntax?, secondGroup: PrecedenceGroupName?, - secondGroupSyntax: Syntax?, + secondOperatorSyntax: Syntax?, errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } ) rethrows -> Associativity { guard let firstGroup = firstGroup, let secondGroup = secondGroup else { @@ -189,7 +189,7 @@ extension OperatorPrecedence { if firstGroup == secondGroup { guard let group = precedenceGraph.lookupGroup(firstGroup) else { try errorHandler( - .missingGroup(firstGroup, referencedFrom: firstGroupSyntax)) + .missingGroup(firstGroup, referencedFrom: firstOperatorSyntax)) return .none } @@ -198,7 +198,7 @@ extension OperatorPrecedence { let prec = try precedenceGraph.precedence( relating: firstGroup, to: secondGroup, - startSyntax: firstGroupSyntax, endSyntax: secondGroupSyntax, + startSyntax: firstOperatorSyntax, endSyntax: secondOperatorSyntax, errorHandler: errorHandler ) @@ -288,9 +288,9 @@ extension OperatorPrecedence { let associativity = try associativity( firstGroup: op1Precedence, - firstGroupSyntax: Syntax(op1), + firstOperatorSyntax: Syntax(op1), secondGroup: op2Precedence, - secondGroupSyntax: Syntax(op2), + secondOperatorSyntax: Syntax(op2), errorHandler: errorHandler ) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Semantics.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Semantics.swift index 9ffb2d15263..be2613acd1f 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Semantics.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Semantics.swift @@ -73,9 +73,9 @@ extension Operator { name = syntax.identifier.text - if let groupSyntax = syntax.operatorPrecedenceAndTypes? + if let operatorSyntax = syntax.operatorPrecedenceAndTypes? .precedenceGroupAndDesignatedTypes { - precedenceGroup = groupSyntax.firstToken?.text + precedenceGroup = operatorSyntax.firstToken?.text } else { precedenceGroup = nil } diff --git a/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift b/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift index 87a689670f2..2099bf8bc60 100644 --- a/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift +++ b/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift @@ -72,10 +72,10 @@ struct PrecedenceGraph { var stack: [(PrecedenceGroupName, Syntax?)] = [(initialGroupName, initialSyntax)] - while let (currentGroupName, currentGroupSyntax) = stack.popLast() { + while let (currentGroupName, currentOperatorSyntax) = stack.popLast() { guard let currentGroup = lookupGroup(currentGroupName) else { try errorHandler( - .missingGroup(currentGroupName, referencedFrom: currentGroupSyntax)) + .missingGroup(currentGroupName, referencedFrom: currentOperatorSyntax)) continue } From b3c3bd1ebab09b09be3bb0919eba034a45785d1e Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 5 Sep 2022 21:55:48 -0700 Subject: [PATCH 34/50] Rename OperatorPrecedence to OperatorTable and improve its documentation. --- .../OperatorPrecedence+Defaults.swift | 10 +++---- .../OperatorPrecedence+Folding.swift | 6 ++--- .../OperatorPrecedence+Semantics.swift | 6 ++--- ...orPrecedence.swift => OperatorTable.swift} | 15 +++++++---- ...orPrecedence.swift => OperatorTable.swift} | 26 +++++++++---------- 5 files changed, 34 insertions(+), 29 deletions(-) rename Sources/SwiftOperatorPrecedence/{OperatorPrecedence.swift => OperatorTable.swift} (81%) rename Tests/SwiftOperatorPrecedenceTest/{OperatorPrecedence.swift => OperatorTable.swift} (93%) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Defaults.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Defaults.swift index f19fb2d3cc1..5a4a1aaa34b 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Defaults.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Defaults.swift @@ -12,10 +12,10 @@ import SwiftSyntax /// Prefabricated operator precedence graphs. -extension OperatorPrecedence { +extension OperatorTable { /// Operator precedence graph for the logical operators '&&' and '||', for /// example as it is used in `#if` processing. - public static var logicalOperators: OperatorPrecedence { + public static var logicalOperators: OperatorTable { let precedenceGroups: [PrecedenceGroup] = [ PrecedenceGroup(name: "LogicalConjunctionPrecedence", associativity: .left, assignment: false, @@ -32,7 +32,7 @@ extension OperatorPrecedence { precedenceGroup: "LogicalDisjunctionPrecedence") ] - return try! OperatorPrecedence( + return try! OperatorTable( precedenceGroups: precedenceGroups, operators: operators) } @@ -44,7 +44,7 @@ extension OperatorPrecedence { /// without requiring access to the standard library source code. However, /// because it does not incorporate user-defined operators, it will only /// ever be useful for a quick approximation. - public static var standardOperators: OperatorPrecedence { + public static var standardOperators: OperatorTable { let precedenceGroups: [PrecedenceGroup] = [ PrecedenceGroup( name: "AssignmentPrecedence", @@ -400,7 +400,7 @@ extension OperatorPrecedence { ) ] - return try! OperatorPrecedence( + return try! OperatorTable( precedenceGroups: precedenceGroups, operators: operators) } } diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift index 8edfccb78d2..9c5703d04ab 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift @@ -17,7 +17,7 @@ extension ExprSyntax { self.is(UnresolvedIsExprSyntax.self) || self.is(UnresolvedAsExprSyntax.self) } } -extension OperatorPrecedence { +extension OperatorTable { private struct PrecedenceBound { let groupName: PrecedenceGroupName? let isStrict: Bool @@ -395,11 +395,11 @@ extension OperatorPrecedence { /// also throw. var firstFatalError: OperatorPrecedenceError? = nil - let opPrecedence: OperatorPrecedence + let opPrecedence: OperatorTable let errorHandler: OperatorPrecedenceErrorHandler init( - opPrecedence: OperatorPrecedence, + opPrecedence: OperatorTable, errorHandler: @escaping OperatorPrecedenceErrorHandler ) { self.opPrecedence = opPrecedence diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Semantics.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Semantics.swift index be2613acd1f..8e9b3f0def9 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Semantics.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Semantics.swift @@ -82,7 +82,7 @@ extension Operator { } } -extension OperatorPrecedence { +extension OperatorTable { /// Integrate the operator and precedence group declarations from the given /// source file into the operator precedence tables. public mutating func addSourceFile( @@ -90,10 +90,10 @@ extension OperatorPrecedence { errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } ) rethrows { class OperatorAndGroupVisitor : SyntaxAnyVisitor { - var opPrecedence: OperatorPrecedence + var opPrecedence: OperatorTable var errors: [OperatorPrecedenceError] = [] - init(opPrecedence: OperatorPrecedence) { + init(opPrecedence: OperatorTable) { self.opPrecedence = opPrecedence super.init(viewMode: .fixedUp) } diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift b/Sources/SwiftOperatorPrecedence/OperatorTable.swift similarity index 81% rename from Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift rename to Sources/SwiftOperatorPrecedence/OperatorTable.swift index 603fc5b2525..44fb8fcf19e 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorTable.swift @@ -11,10 +11,15 @@ //===----------------------------------------------------------------------===// import SwiftSyntax -/// Maintains information about operators and their relative precedence, -/// providing the core operations for "folding" sequence expression syntax into -/// a structured expression syntax tree. -public struct OperatorPrecedence { +/// Maintains and validates information about all operators in a Swift program. +/// +/// The operator table keep track of the various operator and precedence group +/// declarations within a program. Its core operations involve processing the +/// operator and precedence group declarations from a source tree into a +/// semantic representation, validating the correctness of those declarations, +/// and "folding" sequence expression syntax into a structured expression +/// syntax tree. +public struct OperatorTable { var precedenceGraph: PrecedenceGraph = .init() var operators: [OperatorName : Operator] = [:] @@ -60,7 +65,7 @@ public struct OperatorPrecedence { } } -extension OperatorPrecedence { +extension OperatorTable { /// Look for the precedence group corresponding to the given operator. func lookupOperatorPrecedenceGroupName( _ operatorName: OperatorName, diff --git a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift b/Tests/SwiftOperatorPrecedenceTest/OperatorTable.swift similarity index 93% rename from Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift rename to Tests/SwiftOperatorPrecedenceTest/OperatorTable.swift index d3cdbc1dffd..7918cdb2886 100644 --- a/Tests/SwiftOperatorPrecedenceTest/OperatorPrecedence.swift +++ b/Tests/SwiftOperatorPrecedenceTest/OperatorTable.swift @@ -48,7 +48,7 @@ class ExplicitParenFolder : SyntaxRewriter { return ExprSyntax(node) } - return OperatorPrecedence.makeBinaryOperationExpr( + return OperatorTable.makeBinaryOperationExpr( lhs: visit(Syntax(leftOperand)).as(ExprSyntax.self)!, op: visit(Syntax(middleExpr)).as(ExprSyntax.self)!, rhs: visit(Syntax(rightOperand)).as(ExprSyntax.self)! @@ -56,7 +56,7 @@ class ExplicitParenFolder : SyntaxRewriter { } } -extension OperatorPrecedence { +extension OperatorTable { /// Assert that parsing and folding the given "unfolded" source code /// produces the same syntax tree as the fully-parenthesized version of /// the same source. @@ -90,7 +90,7 @@ extension OperatorPrecedence { public class OperatorPrecedenceTests: XCTestCase { func testLogicalExprsSingle() throws { - let opPrecedence = OperatorPrecedence.logicalOperators + let opPrecedence = OperatorTable.logicalOperators let parsed = try Parser.parse(source: "x && y || w && v || z") let sequenceExpr = parsed.statements.first!.item.as(SequenceExprSyntax.self)! @@ -100,13 +100,13 @@ public class OperatorPrecedenceTests: XCTestCase { } func testLogicalExprs() throws { - let opPrecedence = OperatorPrecedence.logicalOperators + let opPrecedence = OperatorTable.logicalOperators try opPrecedence.assertExpectedFold("x && y || w", "((x && y) || w)") try opPrecedence.assertExpectedFold("x || y && w", "(x || (y && w))") } func testSwiftExprs() throws { - let opPrecedence = OperatorPrecedence.standardOperators + let opPrecedence = OperatorTable.standardOperators let parsed = try Parser.parse(source: "(x + y > 17) && x && y || w && v || z") let sequenceExpr = parsed.statements.first!.item.as(SequenceExprSyntax.self)! @@ -116,7 +116,7 @@ public class OperatorPrecedenceTests: XCTestCase { } func testNestedSwiftExprs() throws { - let opPrecedence = OperatorPrecedence.standardOperators + let opPrecedence = OperatorTable.standardOperators let parsed = try Parser.parse(source: "(x + y > 17) && x && y || w && v || z") let foldedAll = try opPrecedence.foldAll(parsed) XCTAssertEqual("\(foldedAll)", "(x + y > 17) && x && y || w && v || z") @@ -124,19 +124,19 @@ public class OperatorPrecedenceTests: XCTestCase { } func testAssignExprs() throws { - let opPrecedence = OperatorPrecedence.standardOperators + let opPrecedence = OperatorTable.standardOperators try opPrecedence.assertExpectedFold("a = b + c", "(a = (b + c))") try opPrecedence.assertExpectedFold("a = b = c", "(a = (b = c))") } func testCastExprs() throws { - let opPrecedence = OperatorPrecedence.standardOperators + let opPrecedence = OperatorTable.standardOperators try opPrecedence.assertExpectedFold("a is (b)", "(a is (b))") try opPrecedence.assertExpectedFold("a as c == nil", "((a as c) == nil)") } func testArrowExpr() throws { - let opPrecedence = OperatorPrecedence.standardOperators + let opPrecedence = OperatorTable.standardOperators try opPrecedence.assertExpectedFold( "a = b -> c -> d", "(a = (b -> (c -> d)))" @@ -165,7 +165,7 @@ public class OperatorPrecedenceTests: XCTestCase { """ let parsedOperatorPrecedence = try Parser.parse(source: logicalOperatorSources) - var opPrecedence = OperatorPrecedence() + var opPrecedence = OperatorTable() try opPrecedence.addSourceFile(parsedOperatorPrecedence) let parsed = try Parser.parse(source: "x && y || w && v || z") @@ -195,7 +195,7 @@ public class OperatorPrecedenceTests: XCTestCase { let parsedOperatorPrecedence = try Parser.parse(source: sources) - var opPrecedence = OperatorPrecedence() + var opPrecedence = OperatorTable() var errors: [OperatorPrecedenceError] = [] opPrecedence.addSourceFile(parsedOperatorPrecedence) { error in errors.append(error) @@ -244,7 +244,7 @@ public class OperatorPrecedenceTests: XCTestCase { infix operator ++: D """) - var opPrecedence = OperatorPrecedence() + var opPrecedence = OperatorTable() try opPrecedence.addSourceFile(parsedOperatorPrecedence) do { @@ -331,7 +331,7 @@ public class OperatorPrecedenceTests: XCTestCase { } func testTernaryExpr() throws { - let opPrecedence = OperatorPrecedence.standardOperators + let opPrecedence = OperatorTable.standardOperators try opPrecedence.assertExpectedFold( "b + c ? y : z ? z2 : z3", "((b + c) ? y : (z ? z2 : z3))") From 5e7b0a6f5fd895d7805cd51f385590ec37c842bb Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 5 Sep 2022 22:04:09 -0700 Subject: [PATCH 35/50] Record prefix and postfix operators in the operator table. At present, this only ends up checking for duplicate definitions. --- .../OperatorPrecedenceError+Diagnostics.swift | 2 +- .../OperatorTable.swift | 36 +++++++++++++------ .../OperatorTable.swift | 34 ++++++++++++++++++ 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift index c4bcd4a9850..4773a239088 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift @@ -28,7 +28,7 @@ extension OperatorPrecedenceError : DiagnosticMessage { return "unknown precedence group '\(groupName)'" case .operatorAlreadyExists(let existing, _): - return "redefinition of infix operator '\(existing.name)'" + return "redefinition of \(existing.kind) operator '\(existing.name)'" case .missingOperator(let operatorName, _): return "unknown infix operator '\(operatorName)'" diff --git a/Sources/SwiftOperatorPrecedence/OperatorTable.swift b/Sources/SwiftOperatorPrecedence/OperatorTable.swift index 44fb8fcf19e..cb185b22ca6 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorTable.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorTable.swift @@ -21,7 +21,9 @@ import SwiftSyntax /// syntax tree. public struct OperatorTable { var precedenceGraph: PrecedenceGraph = .init() - var operators: [OperatorName : Operator] = [:] + var infixOperators: [OperatorName : Operator] = [:] + var prefixOperators: [OperatorName : Operator] = [:] + var postfixOperators: [OperatorName : Operator] = [:] public init() { } @@ -40,19 +42,33 @@ public struct OperatorTable { } } - /// Record the operator, if it matters. - mutating func record( + /// Record the operator in the given operator array. + private func record( _ op: Operator, + in table: inout [OperatorName : Operator], errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } ) rethrows { - // FIXME: Could do operator-already-exists checking for prefix/postfix - // operators as well, since we parse them. - if op.kind != .infix { return } - - if let existing = operators[op.name] { + if let existing = table[op.name] { try errorHandler(.operatorAlreadyExists(existing: existing, new: op)) } else { - operators[op.name] = op + table[op.name] = op + } + } + + /// Record the operator. + mutating func record( + _ op: Operator, + errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + ) rethrows { + switch op.kind { + case .infix: + return try record(op, in: &infixOperators, errorHandler: errorHandler) + + case .prefix: + return try record(op, in: &prefixOperators, errorHandler: errorHandler) + + case .postfix: + return try record(op, in: &postfixOperators, errorHandler: errorHandler) } } @@ -72,7 +88,7 @@ extension OperatorTable { referencedFrom syntax: Syntax?, errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } ) rethrows -> PrecedenceGroupName? { - guard let op = operators[operatorName] else { + guard let op = infixOperators[operatorName] else { try errorHandler( .missingOperator(operatorName, referencedFrom: syntax)) return nil diff --git a/Tests/SwiftOperatorPrecedenceTest/OperatorTable.swift b/Tests/SwiftOperatorPrecedenceTest/OperatorTable.swift index 7918cdb2886..e2eb4773c98 100644 --- a/Tests/SwiftOperatorPrecedenceTest/OperatorTable.swift +++ b/Tests/SwiftOperatorPrecedenceTest/OperatorTable.swift @@ -220,6 +220,40 @@ public class OperatorPrecedenceTests: XCTestCase { _ = existingGroup } + func testUnaryErrors() throws { + let sources = + """ + prefix operator + + prefix operator + + + postfix operator - + prefix operator - + + postfix operator* + postfix operator* + """ + + let parsedOperatorPrecedence = try Parser.parse(source: sources) + + var opPrecedence = OperatorTable() + var errors: [OperatorPrecedenceError] = [] + opPrecedence.addSourceFile(parsedOperatorPrecedence) { error in + errors.append(error) + } + + XCTAssertEqual(errors.count, 2) + guard case let .operatorAlreadyExists(existing, new) = errors[0] else { + XCTFail("expected an 'operator already exists' error") + return + } + + XCTAssertEqual(errors[0].message, "redefinition of prefix operator '+'") + + XCTAssertEqual(errors[1].message, "redefinition of postfix operator '*'") + _ = existing + _ = new + } + func testFoldErrors() throws { let parsedOperatorPrecedence = try Parser.parse(source: """ From 45b0b8a4cdd1de95a252b9d540ccf3bcbdf596ac Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 5 Sep 2022 22:19:35 -0700 Subject: [PATCH 36/50] Rename several source files to reflect the new "OperatorTable" name --- ...torPrecedence+Defaults.swift => OperatorTable+Defaults.swift} | 0 ...ratorPrecedence+Folding.swift => OperatorTable+Folding.swift} | 1 + ...rPrecedence+Semantics.swift => OperatorTable+Semantics.swift} | 0 3 files changed, 1 insertion(+) rename Sources/SwiftOperatorPrecedence/{OperatorPrecedence+Defaults.swift => OperatorTable+Defaults.swift} (100%) rename Sources/SwiftOperatorPrecedence/{OperatorPrecedence+Folding.swift => OperatorTable+Folding.swift} (99%) rename Sources/SwiftOperatorPrecedence/{OperatorPrecedence+Semantics.swift => OperatorTable+Semantics.swift} (100%) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Defaults.swift b/Sources/SwiftOperatorPrecedence/OperatorTable+Defaults.swift similarity index 100% rename from Sources/SwiftOperatorPrecedence/OperatorPrecedence+Defaults.swift rename to Sources/SwiftOperatorPrecedence/OperatorTable+Defaults.swift diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift b/Sources/SwiftOperatorPrecedence/OperatorTable+Folding.swift similarity index 99% rename from Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift rename to Sources/SwiftOperatorPrecedence/OperatorTable+Folding.swift index 9c5703d04ab..5594a2fe6a0 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Folding.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorTable+Folding.swift @@ -17,6 +17,7 @@ extension ExprSyntax { self.is(UnresolvedIsExprSyntax.self) || self.is(UnresolvedAsExprSyntax.self) } } + extension OperatorTable { private struct PrecedenceBound { let groupName: PrecedenceGroupName? diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedence+Semantics.swift b/Sources/SwiftOperatorPrecedence/OperatorTable+Semantics.swift similarity index 100% rename from Sources/SwiftOperatorPrecedence/OperatorPrecedence+Semantics.swift rename to Sources/SwiftOperatorPrecedence/OperatorTable+Semantics.swift From b94546cbb6f940f6fe8ef400da7f61444faae537 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 5 Sep 2022 22:23:22 -0700 Subject: [PATCH 37/50] Refer to OperatorTable in the documentation --- .../SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md b/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md index 4e114881292..a803297c602 100644 --- a/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md +++ b/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md @@ -42,16 +42,16 @@ import SwiftSyntax import SwiftParser import SwiftOperatorPrecedence -var opPrecedence = OperatorPrecedence.standardOperators // Use the Swift standard library operators +var opPrecedence = OperatorTable.standardOperators // Use the Swift standard library operators let parsed = try Parser.parse(source: "x + y * z") dump(parsed) // contains SequenceExprSyntax(x, +, y, *, z) let folded = try opPrecedence.foldAll(parsed) dump(folded) // contains InfixOperatorExpr(x, +, InfixOperatorExpr(y, *, z)) ``` -The type maintains the table of known operators and precedence groups, and is the primary way in which one interacts with this library. The standard operators are provided as a static variable of this type, which will work to fold most Swift code, such as in the example above that folds `x + y * z`. +The type maintains the table of known operators and precedence groups, and is the primary way in which one interacts with this library. The standard operators are provided as a static variable of this type, which will work to fold most Swift code, such as in the example above that folds `x + y * z`. -If your Swift code involves operator and precedence group declarations, they can be parsed into another source file and then added to the `OperatorPrecedence` instance using `addSourceFile`: +If your Swift code involves operator and precedence group declarations, they can be parsed into another source file and then added to the `OperatorTable` instance using `addSourceFile`: ```swift let moreOperators = From f6207632c91d54eda92ecb76e9aa3774fb4eb72f Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 5 Sep 2022 22:24:58 -0700 Subject: [PATCH 38/50] Use reflection to derive diagnostic IDs --- .../OperatorPrecedenceError+Diagnostics.swift | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift b/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift index 4773a239088..c48d58b1176 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift +++ b/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift @@ -42,28 +42,8 @@ extension OperatorPrecedenceError : DiagnosticMessage { } } - /// A string representation of each case. - private var diagnosticCaseID: String { - switch self { - case .incomparableOperators: - return "incomparable_operators" - - case .operatorAlreadyExists: - return "operator_already_exists" - - case .missingOperator: - return "missing_operator" - - case .missingGroup: - return "missing_group" - - case .groupAlreadyExists: - return "group_already_exists" - } - } - public var diagnosticID: MessageID { - MessageID(domain: "SwiftOperatorPrecedence", id: diagnosticCaseID) + MessageID(domain: "SwiftOperatorPrecedence", id: "\(self)") } } From 9e4c0ce5364e03a41e4a8659bf76438b2f0f4a5e Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 5 Sep 2022 22:34:19 -0700 Subject: [PATCH 39/50] Rename SwiftOperatorPrecedence -> SwiftOperators This module has been generalized (slightly) to handle everything involving Swift's user-defined operators and precedence groups, so rename it accordingly. --- Package.swift | 10 +++++----- .../Operator.swift | 0 .../OperatorPrecedenceError+Diagnostics.swift | 2 +- .../OperatorPrecedenceError.swift | 0 .../OperatorTable+Defaults.swift | 0 .../OperatorTable+Folding.swift | 0 .../OperatorTable+Semantics.swift | 0 .../OperatorTable.swift | 0 .../PrecedenceGraph.swift | 0 .../PrecedenceGroup.swift | 0 .../SwiftOperators.docc}/Info.plist | 8 ++++---- .../SwiftOperators.docc/SwiftOperators.md} | 14 ++++++++------ .../OperatorTable.swift | 2 +- 13 files changed, 19 insertions(+), 17 deletions(-) rename Sources/{SwiftOperatorPrecedence => SwiftOperators}/Operator.swift (100%) rename Sources/{SwiftOperatorPrecedence => SwiftOperators}/OperatorPrecedenceError+Diagnostics.swift (97%) rename Sources/{SwiftOperatorPrecedence => SwiftOperators}/OperatorPrecedenceError.swift (100%) rename Sources/{SwiftOperatorPrecedence => SwiftOperators}/OperatorTable+Defaults.swift (100%) rename Sources/{SwiftOperatorPrecedence => SwiftOperators}/OperatorTable+Folding.swift (100%) rename Sources/{SwiftOperatorPrecedence => SwiftOperators}/OperatorTable+Semantics.swift (100%) rename Sources/{SwiftOperatorPrecedence => SwiftOperators}/OperatorTable.swift (100%) rename Sources/{SwiftOperatorPrecedence => SwiftOperators}/PrecedenceGraph.swift (100%) rename Sources/{SwiftOperatorPrecedence => SwiftOperators}/PrecedenceGroup.swift (100%) rename Sources/{SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc => SwiftOperators/SwiftOperators.docc}/Info.plist (83%) rename Sources/{SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md => SwiftOperators/SwiftOperators.docc/SwiftOperators.md} (68%) rename Tests/{SwiftOperatorPrecedenceTest => SwiftOperatorsTest}/OperatorTable.swift (99%) diff --git a/Package.swift b/Package.swift index 0f81845dcc2..1e16b74c5b0 100644 --- a/Package.swift +++ b/Package.swift @@ -45,8 +45,8 @@ let package = Package( .macCatalyst(.v13), ], products: [ - .library(name: "SwiftOperatorPrecedence", type: .static, - targets: ["SwiftOperatorPrecedence"]), + .library(name: "SwiftOperators", type: .static, + targets: ["SwiftOperators"]), .library(name: "SwiftParser", type: .static, targets: ["SwiftParser"]), .library(name: "SwiftSyntax", type: .static, targets: ["SwiftSyntax"]), .library(name: "SwiftSyntaxParser", type: .static, targets: ["SwiftSyntaxParser"]), @@ -121,7 +121,7 @@ let package = Package( ] ), .target( - name: "SwiftOperatorPrecedence", + name: "SwiftOperators", dependencies: ["SwiftSyntax", "SwiftParser", "SwiftDiagnostics"] ), .executableTarget( @@ -180,8 +180,8 @@ let package = Package( dependencies: ["SwiftDiagnostics", "SwiftParser", "_SwiftSyntaxTestSupport"] ), .testTarget( - name: "SwiftOperatorPrecedenceTest", - dependencies: ["SwiftOperatorPrecedence", "_SwiftSyntaxTestSupport", + name: "SwiftOperatorsTest", + dependencies: ["SwiftOperators", "_SwiftSyntaxTestSupport", "SwiftParser"] ), ] diff --git a/Sources/SwiftOperatorPrecedence/Operator.swift b/Sources/SwiftOperators/Operator.swift similarity index 100% rename from Sources/SwiftOperatorPrecedence/Operator.swift rename to Sources/SwiftOperators/Operator.swift diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift b/Sources/SwiftOperators/OperatorPrecedenceError+Diagnostics.swift similarity index 97% rename from Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift rename to Sources/SwiftOperators/OperatorPrecedenceError+Diagnostics.swift index c48d58b1176..585a9245756 100644 --- a/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError+Diagnostics.swift +++ b/Sources/SwiftOperators/OperatorPrecedenceError+Diagnostics.swift @@ -43,7 +43,7 @@ extension OperatorPrecedenceError : DiagnosticMessage { } public var diagnosticID: MessageID { - MessageID(domain: "SwiftOperatorPrecedence", id: "\(self)") + MessageID(domain: "SwiftOperators", id: "\(self)") } } diff --git a/Sources/SwiftOperatorPrecedence/OperatorPrecedenceError.swift b/Sources/SwiftOperators/OperatorPrecedenceError.swift similarity index 100% rename from Sources/SwiftOperatorPrecedence/OperatorPrecedenceError.swift rename to Sources/SwiftOperators/OperatorPrecedenceError.swift diff --git a/Sources/SwiftOperatorPrecedence/OperatorTable+Defaults.swift b/Sources/SwiftOperators/OperatorTable+Defaults.swift similarity index 100% rename from Sources/SwiftOperatorPrecedence/OperatorTable+Defaults.swift rename to Sources/SwiftOperators/OperatorTable+Defaults.swift diff --git a/Sources/SwiftOperatorPrecedence/OperatorTable+Folding.swift b/Sources/SwiftOperators/OperatorTable+Folding.swift similarity index 100% rename from Sources/SwiftOperatorPrecedence/OperatorTable+Folding.swift rename to Sources/SwiftOperators/OperatorTable+Folding.swift diff --git a/Sources/SwiftOperatorPrecedence/OperatorTable+Semantics.swift b/Sources/SwiftOperators/OperatorTable+Semantics.swift similarity index 100% rename from Sources/SwiftOperatorPrecedence/OperatorTable+Semantics.swift rename to Sources/SwiftOperators/OperatorTable+Semantics.swift diff --git a/Sources/SwiftOperatorPrecedence/OperatorTable.swift b/Sources/SwiftOperators/OperatorTable.swift similarity index 100% rename from Sources/SwiftOperatorPrecedence/OperatorTable.swift rename to Sources/SwiftOperators/OperatorTable.swift diff --git a/Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift b/Sources/SwiftOperators/PrecedenceGraph.swift similarity index 100% rename from Sources/SwiftOperatorPrecedence/PrecedenceGraph.swift rename to Sources/SwiftOperators/PrecedenceGraph.swift diff --git a/Sources/SwiftOperatorPrecedence/PrecedenceGroup.swift b/Sources/SwiftOperators/PrecedenceGroup.swift similarity index 100% rename from Sources/SwiftOperatorPrecedence/PrecedenceGroup.swift rename to Sources/SwiftOperators/PrecedenceGroup.swift diff --git a/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/Info.plist b/Sources/SwiftOperators/SwiftOperators.docc/Info.plist similarity index 83% rename from Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/Info.plist rename to Sources/SwiftOperators/SwiftOperators.docc/Info.plist index 2e9532240b1..41dc5d9d9b7 100644 --- a/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/Info.plist +++ b/Sources/SwiftOperators/SwiftOperators.docc/Info.plist @@ -3,11 +3,11 @@ CFBundleName - SwiftOperatorPrecedence + SwiftOperators CFBundleDisplayName - SwiftOperatorPrecedence + SwiftOperators CFBundleIdentifier - com.apple.swift-operator-precedence + com.apple.swift-operators CFBundleDevelopmentRegion en CFBundleIconFile @@ -24,7 +24,7 @@ 0.1.0 CDAppleDefaultAvailability - SwiftOperatorPrecedence + SwiftOperators name diff --git a/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md b/Sources/SwiftOperators/SwiftOperators.docc/SwiftOperators.md similarity index 68% rename from Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md rename to Sources/SwiftOperators/SwiftOperators.docc/SwiftOperators.md index a803297c602..2fb2f711bbb 100644 --- a/Sources/SwiftOperatorPrecedence/SwiftOperatorPrecedence.docc/SwiftOperatorPrecedence.md +++ b/Sources/SwiftOperators/SwiftOperators.docc/SwiftOperators.md @@ -1,8 +1,10 @@ -# ``SwiftOperatorPrecedence`` +# ``SwiftOperators`` -An implementation of Swift's operator precedence semantics for Swift syntax trees. +An implementation of Swift's user-defined operator declarations and precedence +groups, allowing a program to reason about the relative precedence of +infix operations and transform syntax trees to describe the order of operations. @@ -15,7 +17,7 @@ infix operator +: AdditionPrecedence infix operator *: MultiplicationPrecedence ``` -The define the associativity and relative precedence of these operators is defined via a [precedence group declaration](https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_precedence-group-declaration). For example, the precedence groups used for `+` and `*` are defined as follows: +The associativity and relative precedence of these operators is defined via a [precedence group declaration](https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_precedence-group-declaration). For example, the precedence groups used for `+` and `*` are defined as follows: ```swift precedencegroup AdditionPrecedence { @@ -29,18 +31,18 @@ precedencegroup MultiplicationPrecedence { The Swift parser itself does not reason about the semantics of operators or precedence groups. Instead, an expression such as `x + y * z` will be parsed into a `SequenceExprSyntax` node whose children are `x`, a `BinaryOperatorExprSyntax` node for `+`, `y`, a `BinaryOperatorExprSyntax` node for `*`, and `z`. This is all the structure that is possible to parse for a Swift program without semantic information about operators and precedence groups. -The `SwiftOperatorPrecedence` module interprets operator and precedence group declarations to provide those semantics. Its primary operation is to "fold" a `SequenceExprSyntax` node into an equivalent syntax tree that fully expresses the order of operations: in our example case, this means that the resulting syntax node will be an `InfixOperatorExprSyntax` whose left-hand side is `x` and operator is `+`, and whose right-hand side is another `InfixOperatorExprSyntax` node representing `y * z`. The resulting syntax tree will still accurately represent the original source input, but will be completely describe the order of evaluation and be suitable for structured editing or later semantic passes, such as type checking. +The `SwiftOperators` module interprets operator and precedence group declarations to provide those semantics. Its primary operation is to "fold" a `SequenceExprSyntax` node into an equivalent syntax tree that fully expresses the order of operations: in our example case, this means that the resulting syntax node will be an `InfixOperatorExprSyntax` whose left-hand side is `x` and operator is `+`, and whose right-hand side is another `InfixOperatorExprSyntax` node representing `y * z`. The resulting syntax tree will still accurately represent the original source input, but will be completely describe the order of evaluation and be suitable for structured editing or later semantic passes, such as type checking. ## Quickstart -The `SwiftOperatorPrecedence` library is typically used to take a raw parse of Swift code and apply the operator-precedence transformation to it to replace all `SequenceExprSyntax` nodes with more structured syntax nodes. For example, we can use this library's representation of the Swift standard library operators to provide a structured syntax tree for the expression `x + y * z`: +The `SwiftOperators` library is typically used to take a raw parse of Swift code and apply the operator-precedence transformation to it to replace all `SequenceExprSyntax` nodes with more structured syntax nodes. For example, we can use this library's representation of the Swift standard library operators to provide a structured syntax tree for the expression `x + y * z`: ```swift import SwiftSyntax import SwiftParser -import SwiftOperatorPrecedence +import SwiftOperators var opPrecedence = OperatorTable.standardOperators // Use the Swift standard library operators let parsed = try Parser.parse(source: "x + y * z") diff --git a/Tests/SwiftOperatorPrecedenceTest/OperatorTable.swift b/Tests/SwiftOperatorsTest/OperatorTable.swift similarity index 99% rename from Tests/SwiftOperatorPrecedenceTest/OperatorTable.swift rename to Tests/SwiftOperatorsTest/OperatorTable.swift index e2eb4773c98..02a17a39ac2 100644 --- a/Tests/SwiftOperatorPrecedenceTest/OperatorTable.swift +++ b/Tests/SwiftOperatorsTest/OperatorTable.swift @@ -1,7 +1,7 @@ import XCTest import SwiftSyntax import SwiftParser -@_spi(Testing) import SwiftOperatorPrecedence +@_spi(Testing) import SwiftOperators import _SwiftSyntaxTestSupport /// Visitor that looks for ExprSequenceSyntax nodes. From 4dc0f3d941b492db3f2f754dc52842a3ebcd0b01 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 5 Sep 2022 22:36:24 -0700 Subject: [PATCH 40/50] Rename OperatorPrecedenceError -> OperatorError. More renaming for generalization. --- ...s.swift => OperatorError+Diagnostics.swift} | 6 +++--- ...ecedenceError.swift => OperatorError.swift} | 10 +++++----- .../SwiftOperators/OperatorTable+Folding.swift | 18 +++++++++--------- .../OperatorTable+Semantics.swift | 6 +++--- Sources/SwiftOperators/OperatorTable.swift | 10 +++++----- Sources/SwiftOperators/PrecedenceGraph.swift | 8 ++++---- .../SwiftOperators.docc/SwiftOperators.md | 4 ++-- Tests/SwiftOperatorsTest/OperatorTable.swift | 12 ++++++------ 8 files changed, 37 insertions(+), 37 deletions(-) rename Sources/SwiftOperators/{OperatorPrecedenceError+Diagnostics.swift => OperatorError+Diagnostics.swift} (93%) rename Sources/SwiftOperators/{OperatorPrecedenceError.swift => OperatorError.swift} (85%) diff --git a/Sources/SwiftOperators/OperatorPrecedenceError+Diagnostics.swift b/Sources/SwiftOperators/OperatorError+Diagnostics.swift similarity index 93% rename from Sources/SwiftOperators/OperatorPrecedenceError+Diagnostics.swift rename to Sources/SwiftOperators/OperatorError+Diagnostics.swift index 585a9245756..f97cfd2decb 100644 --- a/Sources/SwiftOperators/OperatorPrecedenceError+Diagnostics.swift +++ b/Sources/SwiftOperators/OperatorError+Diagnostics.swift @@ -1,4 +1,4 @@ -//===------------------ OperatorPrecedenceError.swift ---------------------===// +//===------------------ OperatorError.swift ---------------------===// // // This source file is part of the Swift.org open source project // @@ -14,7 +14,7 @@ import SwiftDiagnostics import SwiftParser import SwiftSyntax -extension OperatorPrecedenceError : DiagnosticMessage { +extension OperatorError : DiagnosticMessage { public var severity: DiagnosticSeverity { .error } @@ -47,7 +47,7 @@ extension OperatorPrecedenceError : DiagnosticMessage { } } -extension OperatorPrecedenceError { +extension OperatorError { private func fixupDiagnosticDisplayNode( _ node: Node? ) -> Syntax { diff --git a/Sources/SwiftOperators/OperatorPrecedenceError.swift b/Sources/SwiftOperators/OperatorError.swift similarity index 85% rename from Sources/SwiftOperators/OperatorPrecedenceError.swift rename to Sources/SwiftOperators/OperatorError.swift index 48b8419bc27..2f177cdd231 100644 --- a/Sources/SwiftOperators/OperatorPrecedenceError.swift +++ b/Sources/SwiftOperators/OperatorError.swift @@ -1,4 +1,4 @@ -//===------------------ OperatorPrecedenceError.swift ---------------------===// +//===------------------ OperatorError.swift ---------------------===// // // This source file is part of the Swift.org open source project // @@ -11,8 +11,8 @@ //===----------------------------------------------------------------------===// import SwiftSyntax -/// Describes errors that can occur when working with operator precedence graphs. -public enum OperatorPrecedenceError: Error { +/// Describes errors that can occur when working with user-defined operators. +public enum OperatorError: Error { /// Error produced when a given precedence group already exists in the /// precedence graph. case groupAlreadyExists(existing: PrecedenceGroup, new: PrecedenceGroup) @@ -41,5 +41,5 @@ public enum OperatorPrecedenceError: Error { /// may choose to throw (in which case the error will propagate outward) or /// may separately record/drop the error and return without throwing (in /// which case the operator-precedence parser will recover). -public typealias OperatorPrecedenceErrorHandler = - (OperatorPrecedenceError) throws -> Void +public typealias OperatorErrorHandler = + (OperatorError) throws -> Void diff --git a/Sources/SwiftOperators/OperatorTable+Folding.swift b/Sources/SwiftOperators/OperatorTable+Folding.swift index 5594a2fe6a0..5f20d51ac0b 100644 --- a/Sources/SwiftOperators/OperatorTable+Folding.swift +++ b/Sources/SwiftOperators/OperatorTable+Folding.swift @@ -31,7 +31,7 @@ extension OperatorTable { fromGroup groupName: PrecedenceGroupName?, in bound: PrecedenceBound, operatorSyntax: Syntax, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + errorHandler: OperatorErrorHandler = { throw $0 } ) rethrows -> Bool { guard let boundGroupName = bound.groupName else { return true @@ -55,7 +55,7 @@ extension OperatorTable { /// Look up the precedence group for the given expression syntax. private func lookupPrecedence( of expr: ExprSyntax, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + errorHandler: OperatorErrorHandler = { throw $0 } ) rethrows -> PrecedenceGroupName? { // A binary operator. if let binaryExpr = expr.as(BinaryOperatorExprSyntax.self) { @@ -180,7 +180,7 @@ extension OperatorTable { firstOperatorSyntax: Syntax?, secondGroup: PrecedenceGroupName?, secondOperatorSyntax: Syntax?, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + errorHandler: OperatorErrorHandler = { throw $0 } ) rethrows -> Associativity { guard let firstGroup = firstGroup, let secondGroup = secondGroup else { return .none @@ -221,7 +221,7 @@ extension OperatorTable { private func fold( _ lhs: ExprSyntax, rest: inout Slice, bound: PrecedenceBound, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + errorHandler: OperatorErrorHandler = { throw $0 } ) rethrows -> ExprSyntax { if rest.isEmpty { return lhs } @@ -378,7 +378,7 @@ extension OperatorTable { /// as if the expression has been parenthesized `x + (y * z)`. public func foldSingle( _ sequence: SequenceExprSyntax, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + errorHandler: OperatorErrorHandler = { throw $0 } ) rethrows -> ExprSyntax { let lhs = sequence.elements.first! var rest = sequence.elements.dropFirst() @@ -394,14 +394,14 @@ extension OperatorTable { private class SequenceFolder : SyntaxRewriter { /// The first operator precedecence that caused the error handler to /// also throw. - var firstFatalError: OperatorPrecedenceError? = nil + var firstFatalError: OperatorError? = nil let opPrecedence: OperatorTable - let errorHandler: OperatorPrecedenceErrorHandler + let errorHandler: OperatorErrorHandler init( opPrecedence: OperatorTable, - errorHandler: @escaping OperatorPrecedenceErrorHandler + errorHandler: @escaping OperatorErrorHandler ) { self.opPrecedence = opPrecedence self.errorHandler = errorHandler @@ -452,7 +452,7 @@ extension OperatorTable { /// the operation, then the second must also throw. public func foldAll( _ node: Node, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + errorHandler: OperatorErrorHandler = { throw $0 } ) rethrows -> SyntaxProtocol { return try withoutActuallyEscaping(errorHandler) { errorHandler in let folder = SequenceFolder( diff --git a/Sources/SwiftOperators/OperatorTable+Semantics.swift b/Sources/SwiftOperators/OperatorTable+Semantics.swift index 8e9b3f0def9..77d55be7878 100644 --- a/Sources/SwiftOperators/OperatorTable+Semantics.swift +++ b/Sources/SwiftOperators/OperatorTable+Semantics.swift @@ -87,18 +87,18 @@ extension OperatorTable { /// source file into the operator precedence tables. public mutating func addSourceFile( _ sourceFile: SourceFileSyntax, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + errorHandler: OperatorErrorHandler = { throw $0 } ) rethrows { class OperatorAndGroupVisitor : SyntaxAnyVisitor { var opPrecedence: OperatorTable - var errors: [OperatorPrecedenceError] = [] + var errors: [OperatorError] = [] init(opPrecedence: OperatorTable) { self.opPrecedence = opPrecedence super.init(viewMode: .fixedUp) } - private func errorHandler(error: OperatorPrecedenceError) { + private func errorHandler(error: OperatorError) { errors.append(error) } diff --git a/Sources/SwiftOperators/OperatorTable.swift b/Sources/SwiftOperators/OperatorTable.swift index cb185b22ca6..9fb54248c3f 100644 --- a/Sources/SwiftOperators/OperatorTable.swift +++ b/Sources/SwiftOperators/OperatorTable.swift @@ -32,7 +32,7 @@ public struct OperatorTable { public init( precedenceGroups: [PrecedenceGroup], operators: [Operator], - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + errorHandler: OperatorErrorHandler = { throw $0 } ) rethrows { for group in precedenceGroups { try record(group, errorHandler: errorHandler) @@ -46,7 +46,7 @@ public struct OperatorTable { private func record( _ op: Operator, in table: inout [OperatorName : Operator], - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + errorHandler: OperatorErrorHandler = { throw $0 } ) rethrows { if let existing = table[op.name] { try errorHandler(.operatorAlreadyExists(existing: existing, new: op)) @@ -58,7 +58,7 @@ public struct OperatorTable { /// Record the operator. mutating func record( _ op: Operator, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + errorHandler: OperatorErrorHandler = { throw $0 } ) rethrows { switch op.kind { case .infix: @@ -75,7 +75,7 @@ public struct OperatorTable { /// Record the precedence group. mutating func record( _ group: PrecedenceGroup, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + errorHandler: OperatorErrorHandler = { throw $0 } ) rethrows { try precedenceGraph.add(group, errorHandler: errorHandler) } @@ -86,7 +86,7 @@ extension OperatorTable { func lookupOperatorPrecedenceGroupName( _ operatorName: OperatorName, referencedFrom syntax: Syntax?, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + errorHandler: OperatorErrorHandler = { throw $0 } ) rethrows -> PrecedenceGroupName? { guard let op = infixOperators[operatorName] else { try errorHandler( diff --git a/Sources/SwiftOperators/PrecedenceGraph.swift b/Sources/SwiftOperators/PrecedenceGraph.swift index 2099bf8bc60..e3c3eb460a0 100644 --- a/Sources/SwiftOperators/PrecedenceGraph.swift +++ b/Sources/SwiftOperators/PrecedenceGraph.swift @@ -40,11 +40,11 @@ struct PrecedenceGraph { /// throws PrecedenceGraphError.groupAlreadyExists. mutating func add( _ group: PrecedenceGroup, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + errorHandler: OperatorErrorHandler = { throw $0 } ) rethrows { if let existing = precedenceGroups[group.name] { try errorHandler( - OperatorPrecedenceError.groupAlreadyExists( + OperatorError.groupAlreadyExists( existing: existing, new: group)) } else { precedenceGroups[group.name] = group @@ -64,7 +64,7 @@ struct PrecedenceGraph { initialGroupName: PrecedenceGroupName, initialSyntax: Syntax?, targetGroupName: PrecedenceGroupName, direction: PrecedenceRelation.Kind, - errorHandler: OperatorPrecedenceErrorHandler + errorHandler: OperatorErrorHandler ) rethrows -> Precedence? { // Keep track of all of the groups we have seen during our exploration of // the graph. This detects cycles and prevents extraneous work. @@ -116,7 +116,7 @@ struct PrecedenceGraph { to endGroupName: PrecedenceGroupName, startSyntax: Syntax?, endSyntax: Syntax?, - errorHandler: OperatorPrecedenceErrorHandler = { throw $0 } + errorHandler: OperatorErrorHandler = { throw $0 } ) rethrows -> Precedence { if startGroupName == endGroupName { return .unrelated diff --git a/Sources/SwiftOperators/SwiftOperators.docc/SwiftOperators.md b/Sources/SwiftOperators/SwiftOperators.docc/SwiftOperators.md index 2fb2f711bbb..872d477537d 100644 --- a/Sources/SwiftOperators/SwiftOperators.docc/SwiftOperators.md +++ b/Sources/SwiftOperators/SwiftOperators.docc/SwiftOperators.md @@ -78,10 +78,10 @@ dump(folded2) // contains InfixOperatorExpr(b, **, InfixOperatorExpr(c, **, d)) ## Error handling -By default, any of the operations that can produce an error, whether folding a sequence or parsing a source file's operators and precedence groups into a table, will throw an instance of . However, each entry point takes an optional error handler (of type ) that will be called with each error that occurs. For example, we can capture errors like this: +By default, any of the operations that can produce an error, whether folding a sequence or parsing a source file's operators and precedence groups into a table, will throw an instance of . However, each entry point takes an optional error handler (of type ) that will be called with each error that occurs. For example, we can capture errors like this: ```swift -var errors: [OperatorPrecedenceError] = [] +var errors: [OperatorError] = [] let foldedExpr2e = opPrecedence.foldSingle(sequenceExpr2) { error in errors.append(error) } diff --git a/Tests/SwiftOperatorsTest/OperatorTable.swift b/Tests/SwiftOperatorsTest/OperatorTable.swift index 02a17a39ac2..86b41ba196d 100644 --- a/Tests/SwiftOperatorsTest/OperatorTable.swift +++ b/Tests/SwiftOperatorsTest/OperatorTable.swift @@ -196,7 +196,7 @@ public class OperatorPrecedenceTests: XCTestCase { let parsedOperatorPrecedence = try Parser.parse(source: sources) var opPrecedence = OperatorTable() - var errors: [OperatorPrecedenceError] = [] + var errors: [OperatorError] = [] opPrecedence.addSourceFile(parsedOperatorPrecedence) { error in errors.append(error) } @@ -236,7 +236,7 @@ public class OperatorPrecedenceTests: XCTestCase { let parsedOperatorPrecedence = try Parser.parse(source: sources) var opPrecedence = OperatorTable() - var errors: [OperatorPrecedenceError] = [] + var errors: [OperatorError] = [] opPrecedence.addSourceFile(parsedOperatorPrecedence) { error in errors.append(error) } @@ -282,7 +282,7 @@ public class OperatorPrecedenceTests: XCTestCase { try opPrecedence.addSourceFile(parsedOperatorPrecedence) do { - var errors: [OperatorPrecedenceError] = [] + var errors: [OperatorError] = [] let parsed = try Parser.parse(source: "a + b * c") let sequenceExpr = parsed.statements.first!.item.as(SequenceExprSyntax.self)! @@ -301,7 +301,7 @@ public class OperatorPrecedenceTests: XCTestCase { } do { - var errors: [OperatorPrecedenceError] = [] + var errors: [OperatorError] = [] let parsed = try Parser.parse(source: "a / c") let sequenceExpr = parsed.statements.first!.item.as(SequenceExprSyntax.self)! @@ -320,7 +320,7 @@ public class OperatorPrecedenceTests: XCTestCase { } do { - var errors: [OperatorPrecedenceError] = [] + var errors: [OperatorError] = [] let parsed = try Parser.parse(source: "a + b - c") let sequenceExpr = parsed.statements.first!.item.as(SequenceExprSyntax.self)! @@ -342,7 +342,7 @@ public class OperatorPrecedenceTests: XCTestCase { } do { - var errors: [OperatorPrecedenceError] = [] + var errors: [OperatorError] = [] let parsed = try Parser.parse(source: "a ++ b - d") let sequenceExpr = parsed.statements.first!.item.as(SequenceExprSyntax.self)! From 2dd5885ecb9bbec3d6d6d66d49e06323d8cfe9f9 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 5 Sep 2022 23:10:44 -0700 Subject: [PATCH 41/50] Add the ability to synthesize syntax for an operator --- Sources/SwiftOperators/SyntaxSynthesis.swift | 40 +++++++++++++++++++ .../SyntaxSynthesisTests.swift | 24 +++++++++++ 2 files changed, 64 insertions(+) create mode 100644 Sources/SwiftOperators/SyntaxSynthesis.swift create mode 100644 Tests/SwiftOperatorsTest/SyntaxSynthesisTests.swift diff --git a/Sources/SwiftOperators/SyntaxSynthesis.swift b/Sources/SwiftOperators/SyntaxSynthesis.swift new file mode 100644 index 00000000000..9fb2d69c35d --- /dev/null +++ b/Sources/SwiftOperators/SyntaxSynthesis.swift @@ -0,0 +1,40 @@ +//===------------------ SyntaxSynthesis.swift -----------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +extension Operator { + /// Synthesize a syntactic representation of this operator based on its + /// semantic definition. + public func synthesizedSyntax() -> OperatorDeclSyntax { + let modifiers = ModifierListSyntax( + [DeclModifierSyntax(name: .identifier("\(kind)"), detail: nil)] + ) + let operatorKeyword = TokenSyntax.operatorKeyword(leadingTrivia: .spaces(1)) + let identifierSyntax = + TokenSyntax.identifier(name, leadingTrivia: .spaces(1)) + let precedenceGroupSyntax = precedenceGroup.map { groupName in + OperatorPrecedenceAndTypesSyntax( + colon: .colonToken(), + precedenceGroupAndDesignatedTypes: IdentifierListSyntax( + [.identifier(groupName, leadingTrivia: .spaces(1))] + ) + ) + } + + return OperatorDeclSyntax( + attributes: nil, modifiers: modifiers, operatorKeyword: operatorKeyword, + identifier: identifierSyntax, + operatorPrecedenceAndTypes: precedenceGroupSyntax + ) + } +} diff --git a/Tests/SwiftOperatorsTest/SyntaxSynthesisTests.swift b/Tests/SwiftOperatorsTest/SyntaxSynthesisTests.swift new file mode 100644 index 00000000000..ac8acfe88ba --- /dev/null +++ b/Tests/SwiftOperatorsTest/SyntaxSynthesisTests.swift @@ -0,0 +1,24 @@ +//===------------------ SyntaxSynthesisTests.swift ------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +import XCTest +import SwiftSyntax +import SwiftOperators + +public class SyntaxSynthesisTests: XCTestCase { + func testInfixOperator() { + let plus = Operator( + kind: .infix, name: "+", precedenceGroup: "AdditivePrecedence") + let plusSyntax = plus.synthesizedSyntax() + XCTAssertEqual( + plusSyntax.description, "infix operator +: AdditivePrecedence") + } +} From 7d43bb17adb73a0da415ada43a9aa2af287d2c21 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 6 Sep 2022 22:19:42 -0700 Subject: [PATCH 42/50] Synthesize syntax nodes for operator and precedence groups Introduce `synthesizeSyntax()` operations for `Operator` and `PrecedenceGroup` to produce syntax nodes describing the semantics. --- Sources/SwiftOperators/SyntaxSynthesis.swift | 91 ++++++++++++++++++- .../SyntaxSynthesisTests.swift | 18 ++++ 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftOperators/SyntaxSynthesis.swift b/Sources/SwiftOperators/SyntaxSynthesis.swift index 9fb2d69c35d..f67abc649b5 100644 --- a/Sources/SwiftOperators/SyntaxSynthesis.swift +++ b/Sources/SwiftOperators/SyntaxSynthesis.swift @@ -19,14 +19,14 @@ extension Operator { let modifiers = ModifierListSyntax( [DeclModifierSyntax(name: .identifier("\(kind)"), detail: nil)] ) - let operatorKeyword = TokenSyntax.operatorKeyword(leadingTrivia: .spaces(1)) + let operatorKeyword = TokenSyntax.operatorKeyword(leadingTrivia: .space) let identifierSyntax = - TokenSyntax.identifier(name, leadingTrivia: .spaces(1)) + TokenSyntax.identifier(name, leadingTrivia: .space) let precedenceGroupSyntax = precedenceGroup.map { groupName in OperatorPrecedenceAndTypesSyntax( colon: .colonToken(), precedenceGroupAndDesignatedTypes: IdentifierListSyntax( - [.identifier(groupName, leadingTrivia: .spaces(1))] + [.identifier(groupName, leadingTrivia: .space)] ) ) } @@ -38,3 +38,88 @@ extension Operator { ) } } + +extension PrecedenceGroup { + /// Synthesize a syntactic representation of this precedence group based on + /// its semantic definition. + public func synthesizedSyntax( + indentation: Int = 4 + ) -> PrecedenceGroupDeclSyntax { + let precedencegroupKeyword = TokenSyntax.precedencegroupKeyword() + let identifierSyntax = + TokenSyntax.identifier(name, leadingTrivia: .space) + let leftBrace = TokenSyntax.leftBraceToken(leadingTrivia: .space) + + var groupAttributes: [Syntax] = [] + + switch associativity { + case .left, .right: + groupAttributes.append( + Syntax( + PrecedenceGroupAssociativitySyntax( + associativityKeyword: + .identifier( + "associativity", + leadingTrivia: [ .newlines(1), .spaces(indentation) ] + ), + colon: .colonToken(), + value: .identifier("\(associativity)", leadingTrivia: .space) + ) + ) + ) + + case .none: + // None is the default associativity. + break + } + + if assignment { + groupAttributes.append( + Syntax( + PrecedenceGroupAssignmentSyntax( + assignmentKeyword: + .identifier( + "assignment", + leadingTrivia: [ .newlines(1), .spaces(indentation) ] + ), + colon: .colonToken(), + flag: .trueKeyword(leadingTrivia: .space) + ) + ) + ) + } + + for relation in relations { + groupAttributes.append( + Syntax( + PrecedenceGroupRelationSyntax( + higherThanOrLowerThan: .contextualKeyword( + "\(relation.kind)", + leadingTrivia: [ .newlines(1), .spaces(indentation) ] + ), + colon: .colonToken(), + otherNames: PrecedenceGroupNameListSyntax( + [ + PrecedenceGroupNameElementSyntax( + name: .identifier(relation.groupName, leadingTrivia: .space), + trailingComma: nil) + ] + ) + ) + ) + ) + } + + let rightBrace = TokenSyntax.rightBraceToken( + leadingTrivia: groupAttributes.isEmpty ? .space : .newline + ) + + return PrecedenceGroupDeclSyntax( + attributes: nil, modifiers: nil, + precedencegroupKeyword: precedencegroupKeyword, + identifier: identifierSyntax, leftBrace: leftBrace, + groupAttributes: PrecedenceGroupAttributeListSyntax(groupAttributes), + rightBrace: rightBrace + ) + } +} diff --git a/Tests/SwiftOperatorsTest/SyntaxSynthesisTests.swift b/Tests/SwiftOperatorsTest/SyntaxSynthesisTests.swift index ac8acfe88ba..67f36e66746 100644 --- a/Tests/SwiftOperatorsTest/SyntaxSynthesisTests.swift +++ b/Tests/SwiftOperatorsTest/SyntaxSynthesisTests.swift @@ -21,4 +21,22 @@ public class SyntaxSynthesisTests: XCTestCase { XCTAssertEqual( plusSyntax.description, "infix operator +: AdditivePrecedence") } + + func testPrecedenceGroup() { + let group = PrecedenceGroup( + name: "MyGroup", associativity: .right, assignment: true, + relations: [ .lowerThan("BetterGroup"), .higherThan("WorseGroup")] + ) + let groupSyntax = group.synthesizedSyntax() + XCTAssertEqual( + groupSyntax.description, + """ + precedencegroup MyGroup { + associativity: right + assignment: true + lowerThan: BetterGroup + higherThan: WorseGroup + } + """) + } } From b67fdaa1897c9ff067ab7e1452d65d304cbf1e9d Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 6 Sep 2022 22:38:48 -0700 Subject: [PATCH 43/50] Make Operator, PrecedenceGroup, and OperatorTable CustomStringConvertible. Make the string representation of the main data types be their syntactic form. This makes it easier to inspect and reason about their values. --- Sources/SwiftOperators/Operator.swift | 7 +++++ Sources/SwiftOperators/OperatorTable.swift | 28 +++++++++++++++++++ Sources/SwiftOperators/PrecedenceGroup.swift | 7 +++++ .../SyntaxSynthesisTests.swift | 19 +++++++++++++ 4 files changed, 61 insertions(+) diff --git a/Sources/SwiftOperators/Operator.swift b/Sources/SwiftOperators/Operator.swift index 8c60e7a659c..1edb78012a1 100644 --- a/Sources/SwiftOperators/Operator.swift +++ b/Sources/SwiftOperators/Operator.swift @@ -48,3 +48,10 @@ public struct Operator { self.syntax = syntax } } + +extension Operator: CustomStringConvertible { + /// The description of an operator is the source code that produces it. + public var description: String { + (syntax ?? synthesizedSyntax()).description + } +} diff --git a/Sources/SwiftOperators/OperatorTable.swift b/Sources/SwiftOperators/OperatorTable.swift index 9fb54248c3f..1e8515edb6b 100644 --- a/Sources/SwiftOperators/OperatorTable.swift +++ b/Sources/SwiftOperators/OperatorTable.swift @@ -97,3 +97,31 @@ extension OperatorTable { return op.precedenceGroup } } + +extension OperatorTable: CustomStringConvertible { + /// The description of an operator table is the source code that produces it. + public var description: String { + var result = "" + + // Turn all of the dictionary values into their string representations. + func add( + _ dict: [Key : Value] + ) { + if dict.isEmpty { + return + } + + result.append(contentsOf: dict.sorted { $0.key < $1.key } + .map { $0.value.description } + .joined(separator: "\n")) + + result += "\n" + } + + add(precedenceGraph.precedenceGroups) + add(prefixOperators) + add(postfixOperators) + add(infixOperators) + return result + } +} diff --git a/Sources/SwiftOperators/PrecedenceGroup.swift b/Sources/SwiftOperators/PrecedenceGroup.swift index 1ebd4c99f4c..ca503e2ccbf 100644 --- a/Sources/SwiftOperators/PrecedenceGroup.swift +++ b/Sources/SwiftOperators/PrecedenceGroup.swift @@ -118,3 +118,10 @@ public struct PrecedenceGroup { self.syntax = syntax } } + +extension PrecedenceGroup: CustomStringConvertible { + /// The description of a precedence group is the source code that produces it. + public var description: String { + (syntax ?? synthesizedSyntax()).description + } +} diff --git a/Tests/SwiftOperatorsTest/SyntaxSynthesisTests.swift b/Tests/SwiftOperatorsTest/SyntaxSynthesisTests.swift index 67f36e66746..c6087a393d5 100644 --- a/Tests/SwiftOperatorsTest/SyntaxSynthesisTests.swift +++ b/Tests/SwiftOperatorsTest/SyntaxSynthesisTests.swift @@ -39,4 +39,23 @@ public class SyntaxSynthesisTests: XCTestCase { } """) } + + func testLogicalOperatorTable() { + let table = OperatorTable.logicalOperators + XCTAssertEqual( + table.description, + """ + precedencegroup LogicalConjunctionPrecedence { + associativity: left + higherThan: LogicalDisjunctionPrecedence + } + precedencegroup LogicalDisjunctionPrecedence { + associativity: left + } + infix operator &&: LogicalConjunctionPrecedence + infix operator ||: LogicalDisjunctionPrecedence + + """ + ) + } } From c22124e5473d0f19b294e24819c25e2386fdaf1c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 6 Sep 2022 22:45:13 -0700 Subject: [PATCH 44/50] Make all Syntax non-optional in OperatorError cases. Always provide syntax nodes, even if they are synthesized, when forming an OperatorError. This eliminates the need for clients to do this synthesis. --- .../OperatorError+Diagnostics.swift | 18 ++------- Sources/SwiftOperators/OperatorError.swift | 4 +- .../OperatorTable+Folding.swift | 6 +-- Sources/SwiftOperators/OperatorTable.swift | 2 +- Sources/SwiftOperators/PrecedenceGraph.swift | 17 +++++--- Sources/SwiftOperators/SyntaxSynthesis.swift | 40 ++++++++++++------- 6 files changed, 48 insertions(+), 39 deletions(-) diff --git a/Sources/SwiftOperators/OperatorError+Diagnostics.swift b/Sources/SwiftOperators/OperatorError+Diagnostics.swift index f97cfd2decb..ce8fb3a7854 100644 --- a/Sources/SwiftOperators/OperatorError+Diagnostics.swift +++ b/Sources/SwiftOperators/OperatorError+Diagnostics.swift @@ -48,16 +48,6 @@ extension OperatorError : DiagnosticMessage { } extension OperatorError { - private func fixupDiagnosticDisplayNode( - _ node: Node? - ) -> Syntax { - if let node = node { - return Syntax(node) - } - - return Syntax(MissingDeclSyntax(attributes: nil, modifiers: nil)) - } - /// Produce the syntax node at which a diagnostic should be displayed. var diagnosticDisplayNode: Syntax { switch self { @@ -65,16 +55,16 @@ extension OperatorError { return Syntax(leftOperator) case .missingOperator(_, let node): - return fixupDiagnosticDisplayNode(node) + return node case .operatorAlreadyExists(_, let newOperator): - return fixupDiagnosticDisplayNode(newOperator.syntax) + return Syntax(newOperator.syntax ?? newOperator.synthesizedSyntax()) case .missingGroup(_, let node): - return fixupDiagnosticDisplayNode(node) + return node case .groupAlreadyExists(_, let newGroup): - return fixupDiagnosticDisplayNode(newGroup.syntax) + return Syntax(newGroup.syntax ?? newGroup.synthesizedSyntax()) } } diff --git a/Sources/SwiftOperators/OperatorError.swift b/Sources/SwiftOperators/OperatorError.swift index 2f177cdd231..5dfb8746c40 100644 --- a/Sources/SwiftOperators/OperatorError.swift +++ b/Sources/SwiftOperators/OperatorError.swift @@ -18,13 +18,13 @@ public enum OperatorError: Error { case groupAlreadyExists(existing: PrecedenceGroup, new: PrecedenceGroup) /// The named precedence group is missing from the precedence graph. - case missingGroup(PrecedenceGroupName, referencedFrom: Syntax?) + case missingGroup(PrecedenceGroupName, referencedFrom: Syntax) /// Error produced when a given operator already exists. case operatorAlreadyExists(existing: Operator, new: Operator) /// The named operator is missing from the precedence graph. - case missingOperator(OperatorName, referencedFrom: Syntax?) + case missingOperator(OperatorName, referencedFrom: Syntax) /// No associativity relationship between operators. case incomparableOperators( diff --git a/Sources/SwiftOperators/OperatorTable+Folding.swift b/Sources/SwiftOperators/OperatorTable+Folding.swift index 5f20d51ac0b..aef712797c7 100644 --- a/Sources/SwiftOperators/OperatorTable+Folding.swift +++ b/Sources/SwiftOperators/OperatorTable+Folding.swift @@ -47,7 +47,7 @@ extension OperatorTable { return try precedenceGraph.precedence( relating: groupName, to: boundGroupName, - startSyntax: operatorSyntax, endSyntax: bound.syntax, + startSyntax: operatorSyntax, endSyntax: bound.syntax!, errorHandler: errorHandler ) != .lowerThan } @@ -177,9 +177,9 @@ extension OperatorTable { /// Determine the associativity between two precedence groups. private func associativity( firstGroup: PrecedenceGroupName?, - firstOperatorSyntax: Syntax?, + firstOperatorSyntax: Syntax, secondGroup: PrecedenceGroupName?, - secondOperatorSyntax: Syntax?, + secondOperatorSyntax: Syntax, errorHandler: OperatorErrorHandler = { throw $0 } ) rethrows -> Associativity { guard let firstGroup = firstGroup, let secondGroup = secondGroup else { diff --git a/Sources/SwiftOperators/OperatorTable.swift b/Sources/SwiftOperators/OperatorTable.swift index 1e8515edb6b..c67a99cc518 100644 --- a/Sources/SwiftOperators/OperatorTable.swift +++ b/Sources/SwiftOperators/OperatorTable.swift @@ -85,7 +85,7 @@ extension OperatorTable { /// Look for the precedence group corresponding to the given operator. func lookupOperatorPrecedenceGroupName( _ operatorName: OperatorName, - referencedFrom syntax: Syntax?, + referencedFrom syntax: Syntax, errorHandler: OperatorErrorHandler = { throw $0 } ) rethrows -> PrecedenceGroupName? { guard let op = infixOperators[operatorName] else { diff --git a/Sources/SwiftOperators/PrecedenceGraph.swift b/Sources/SwiftOperators/PrecedenceGraph.swift index e3c3eb460a0..acb14936581 100644 --- a/Sources/SwiftOperators/PrecedenceGraph.swift +++ b/Sources/SwiftOperators/PrecedenceGraph.swift @@ -61,7 +61,7 @@ struct PrecedenceGraph { /// (fromGroup, fromSyntax) and following precedence groups in the /// specified direction. private func searchRelationships( - initialGroupName: PrecedenceGroupName, initialSyntax: Syntax?, + initialGroupName: PrecedenceGroupName, initialSyntax: Syntax, targetGroupName: PrecedenceGroupName, direction: PrecedenceRelation.Kind, errorHandler: OperatorErrorHandler @@ -70,7 +70,7 @@ struct PrecedenceGraph { // the graph. This detects cycles and prevents extraneous work. var groupsSeen: Set = [] - var stack: [(PrecedenceGroupName, Syntax?)] = + var stack: [(PrecedenceGroupName, Syntax)] = [(initialGroupName, initialSyntax)] while let (currentGroupName, currentOperatorSyntax) = stack.popLast() { guard let currentGroup = lookupGroup(currentGroupName) else { @@ -94,7 +94,14 @@ struct PrecedenceGraph { } if groupsSeen.insert(otherGroupName).inserted { - stack.append((otherGroupName, relation.syntax.map { Syntax($0) })) + let relationSyntax: Syntax + if let knownSyntax = relation.syntax { + relationSyntax = Syntax(knownSyntax) + } else { + relationSyntax = + Syntax(relation.synthesizedSyntax().otherNames.first!.name) + } + stack.append((otherGroupName, relationSyntax)) } } } @@ -114,8 +121,8 @@ struct PrecedenceGraph { func precedence( relating startGroupName: PrecedenceGroupName, to endGroupName: PrecedenceGroupName, - startSyntax: Syntax?, - endSyntax: Syntax?, + startSyntax: Syntax, + endSyntax: Syntax, errorHandler: OperatorErrorHandler = { throw $0 } ) rethrows -> Precedence { if startGroupName == endGroupName { diff --git a/Sources/SwiftOperators/SyntaxSynthesis.swift b/Sources/SwiftOperators/SyntaxSynthesis.swift index f67abc649b5..1461f517ab7 100644 --- a/Sources/SwiftOperators/SyntaxSynthesis.swift +++ b/Sources/SwiftOperators/SyntaxSynthesis.swift @@ -39,6 +39,31 @@ extension Operator { } } +extension PrecedenceRelation { + /// Synthesize a syntactic representation of this precedence relation based on + /// its semantic definition. + /// + /// We only use this internally to synthesize syntactic locations. + func synthesizedSyntax( + indentation: Int = 4 + ) -> PrecedenceGroupRelationSyntax { + PrecedenceGroupRelationSyntax( + higherThanOrLowerThan: .contextualKeyword( + "\(kind)", + leadingTrivia: [ .newlines(1), .spaces(indentation) ] + ), + colon: .colonToken(), + otherNames: PrecedenceGroupNameListSyntax( + [ + PrecedenceGroupNameElementSyntax( + name: .identifier(groupName, leadingTrivia: .space), + trailingComma: nil) + ] + ) + ) + } +} + extension PrecedenceGroup { /// Synthesize a syntactic representation of this precedence group based on /// its semantic definition. @@ -92,20 +117,7 @@ extension PrecedenceGroup { for relation in relations { groupAttributes.append( Syntax( - PrecedenceGroupRelationSyntax( - higherThanOrLowerThan: .contextualKeyword( - "\(relation.kind)", - leadingTrivia: [ .newlines(1), .spaces(indentation) ] - ), - colon: .colonToken(), - otherNames: PrecedenceGroupNameListSyntax( - [ - PrecedenceGroupNameElementSyntax( - name: .identifier(relation.groupName, leadingTrivia: .space), - trailingComma: nil) - ] - ) - ) + relation.synthesizedSyntax() ) ) } From 95461b570f2644e9cbdc8a6731a5985d022c8868 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 6 Sep 2022 23:03:09 -0700 Subject: [PATCH 45/50] Fix file name and add license header --- .../{OperatorTable.swift => OperatorTableTests.swift} | 11 +++++++++++ 1 file changed, 11 insertions(+) rename Tests/SwiftOperatorsTest/{OperatorTable.swift => OperatorTableTests.swift} (95%) diff --git a/Tests/SwiftOperatorsTest/OperatorTable.swift b/Tests/SwiftOperatorsTest/OperatorTableTests.swift similarity index 95% rename from Tests/SwiftOperatorsTest/OperatorTable.swift rename to Tests/SwiftOperatorsTest/OperatorTableTests.swift index 86b41ba196d..b4265c01f65 100644 --- a/Tests/SwiftOperatorsTest/OperatorTable.swift +++ b/Tests/SwiftOperatorsTest/OperatorTableTests.swift @@ -1,3 +1,14 @@ +//===------------------ OperatorTableTests.swift --------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// import XCTest import SwiftSyntax import SwiftParser From 280bf2439dc5bd5669214eca1429d2e1e1bd86ee Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 6 Sep 2022 23:26:24 -0700 Subject: [PATCH 46/50] Add `--fold-sequences` operation to swift-parser-test Use the `SwiftOperators` library to add support for sequence folding into `swift-parser-test`. Each of the subcommands now supports the `--fold-sequences` operation, which applies sequence folding using the standard library operators and any additional precedence groups and operators defines within the provided source file. The resulting behavior for each of the subcommands is: * `verify-round-trip`: ensure that, after folding, the resulting tree still reproduces the input source. * `print-diags`: also prints any diagnostics from sequence folding, e.g., a ** b ** c where ** is non-associative will produce an error. * `dump-tree`: prints the folded tree, which (for example) will no longer have any SequenceExprSyntax nodes. * `reduce`: passes `--fold-sequences` down for reduction. --- Package.swift | 7 +- .../swift-parser-test/swift-parser-test.swift | 69 +++++++++++++++++-- 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/Package.swift b/Package.swift index 1e16b74c5b0..954ac9d9d72 100644 --- a/Package.swift +++ b/Package.swift @@ -130,7 +130,9 @@ let package = Package( ), .executableTarget( name: "swift-parser-test", - dependencies: ["SwiftDiagnostics", "SwiftSyntax", "SwiftParser", .product(name: "ArgumentParser", package: "swift-argument-parser")] + dependencies: ["SwiftDiagnostics", "SwiftSyntax", "SwiftParser", + "SwiftOperators", + .product(name: "ArgumentParser", package: "swift-argument-parser")] ), .executableTarget( name: "generate-swift-syntax-builder", @@ -177,7 +179,8 @@ let package = Package( ), .testTarget( name: "SwiftParserTest", - dependencies: ["SwiftDiagnostics", "SwiftParser", "_SwiftSyntaxTestSupport"] + dependencies: ["SwiftDiagnostics", "SwiftOperators", "SwiftParser", + "_SwiftSyntaxTestSupport"] ), .testTarget( name: "SwiftOperatorsTest", diff --git a/Sources/swift-parser-test/swift-parser-test.swift b/Sources/swift-parser-test/swift-parser-test.swift index 8ba95ab1074..3596ba0f940 100644 --- a/Sources/swift-parser-test/swift-parser-test.swift +++ b/Sources/swift-parser-test/swift-parser-test.swift @@ -14,6 +14,7 @@ import SwiftDiagnostics import SwiftSyntax import SwiftParser +import SwiftOperators import Foundation import ArgumentParser #if os(Windows) @@ -56,6 +57,19 @@ class SwiftParserTest: ParsableCommand { ) } +/// Fold all of the sequences in the given source file. +func foldAllSequences(_ tree: SourceFileSyntax) -> (Syntax, [Diagnostic]) { + var diags: [Diagnostic] = [] + + let recordOperatorError: (OperatorError) -> Void = { error in + diags.append(error.asDiagnostic) + } + var operatorTable = OperatorTable.standardOperators + operatorTable.addSourceFile(tree, errorHandler: recordOperatorError) + let resultTree = operatorTable.foldAll(tree, errorHandler: recordOperatorError) + return (Syntax(resultTree), diags) +} + class VerifyRoundTrip: ParsableCommand { required init() {} @@ -74,6 +88,10 @@ class VerifyRoundTrip: ParsableCommand { @Option(name: .long, help: "Enable or disable the use of forward slash regular-expression literal syntax") var enableBareSlashRegex: Bool? + @Flag(name: .long, + help: "Perform sequence folding with the standard operators") + var foldSequences: Bool = false + enum Error: Swift.Error, CustomStringConvertible { case roundTripFailed @@ -91,21 +109,30 @@ class VerifyRoundTrip: ParsableCommand { try source.withUnsafeBufferPointer { sourceBuffer in try Self.run( source: sourceBuffer, swiftVersion: swiftVersion, - enableBareSlashRegex: enableBareSlashRegex + enableBareSlashRegex: enableBareSlashRegex, + foldSequences: foldSequences ) } } static func run( source: UnsafeBufferPointer, swiftVersion: String?, - enableBareSlashRegex: Bool? + enableBareSlashRegex: Bool?, foldSequences: Bool ) throws { let tree = try Parser.parse( source: source, languageVersion: swiftVersion, enableBareSlashRegexLiteral: enableBareSlashRegex ) - if tree.syntaxTextBytes != [UInt8](source) { + + let resultTree: Syntax + if foldSequences { + resultTree = foldAllSequences(tree).0 + } else { + resultTree = Syntax(tree) + } + + if resultTree.syntaxTextBytes != [UInt8](source) { throw Error.roundTripFailed } } @@ -123,6 +150,10 @@ class PrintDiags: ParsableCommand { @Option(name: .long, help: "Enable or disable the use of forward slash regular-expression literal syntax") var enableBareSlashRegex: Bool? + @Flag(name: .long, + help: "Perform sequence folding with the standard operators") + var foldSequences: Bool = false + func run() throws { let source = try getContentsOfSourceFile(at: sourceFile) @@ -132,7 +163,12 @@ class PrintDiags: ParsableCommand { languageVersion: swiftVersion, enableBareSlashRegexLiteral: enableBareSlashRegex ) - let diags = ParseDiagnosticsGenerator.diagnostics(for: tree) + var diags = ParseDiagnosticsGenerator.diagnostics(for: tree) + + if foldSequences { + diags += foldAllSequences(tree).1 + } + if diags.isEmpty { print("No diagnostics produced") } @@ -155,6 +191,10 @@ class DumpTree: ParsableCommand { @Option(name: .long, help: "Enable or disable the use of forward slash regular-expression literal syntax") var enableBareSlashRegex: Bool? + @Flag(name: .long, + help: "Perform sequence folding with the standard operators") + var foldSequences: Bool = false + func run() throws { let source = try getContentsOfSourceFile(at: sourceFile) @@ -164,7 +204,15 @@ class DumpTree: ParsableCommand { languageVersion: swiftVersion, enableBareSlashRegexLiteral: enableBareSlashRegex ) - print(tree.recursiveDescription) + + let resultTree: Syntax + if foldSequences { + resultTree = foldAllSequences(tree).0 + } else { + resultTree = Syntax(tree) + } + + print(resultTree.recursiveDescription) } } } @@ -181,6 +229,10 @@ class Reduce: ParsableCommand { @Option(name: .long, help: "Enable or disable the use of forward slash regular-expression literal syntax") var enableBareSlashRegex: Bool? + @Flag(name: .long, + help: "Perform sequence folding with the standard operators") + var foldSequences: Bool = false + @Flag(help: "Print status updates while reducing the test case") var verbose: Bool = false @@ -225,6 +277,10 @@ class Reduce: ParsableCommand { "--swift-version", swiftVersion ] } + if foldSequences { + process.arguments! += [ "--fold-sequences" ] + } + let sema = DispatchSemaphore(value: 0) process.standardOutput = FileHandle.nullDevice process.standardError = FileHandle.nullDevice @@ -257,7 +313,8 @@ class Reduce: ParsableCommand { private func runVerifyRoundTripInCurrentProcess(source: [UInt8]) throws -> Bool { do { try source.withUnsafeBufferPointer { sourceBuffer in - try VerifyRoundTrip.run(source: sourceBuffer, swiftVersion: self.swiftVersion, enableBareSlashRegex: self.enableBareSlashRegex) + try VerifyRoundTrip.run(source: sourceBuffer, swiftVersion: self.swiftVersion, enableBareSlashRegex: self.enableBareSlashRegex, + foldSequences: foldSequences) } } catch { return false From 1c17c419ffb6dc3f629ea5e170a3ae67d15d3ace Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 6 Sep 2022 23:33:13 -0700 Subject: [PATCH 47/50] Return `Syntax`, rather than `SyntaxProtocol`, from `foldAll` --- Sources/SwiftOperators/OperatorTable+Folding.swift | 2 +- Sources/swift-parser-test/swift-parser-test.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftOperators/OperatorTable+Folding.swift b/Sources/SwiftOperators/OperatorTable+Folding.swift index aef712797c7..cc62f32a594 100644 --- a/Sources/SwiftOperators/OperatorTable+Folding.swift +++ b/Sources/SwiftOperators/OperatorTable+Folding.swift @@ -453,7 +453,7 @@ extension OperatorTable { public func foldAll( _ node: Node, errorHandler: OperatorErrorHandler = { throw $0 } - ) rethrows -> SyntaxProtocol { + ) rethrows -> Syntax { return try withoutActuallyEscaping(errorHandler) { errorHandler in let folder = SequenceFolder( opPrecedence: self, errorHandler: errorHandler diff --git a/Sources/swift-parser-test/swift-parser-test.swift b/Sources/swift-parser-test/swift-parser-test.swift index 3596ba0f940..d14f70250d1 100644 --- a/Sources/swift-parser-test/swift-parser-test.swift +++ b/Sources/swift-parser-test/swift-parser-test.swift @@ -67,7 +67,7 @@ func foldAllSequences(_ tree: SourceFileSyntax) -> (Syntax, [Diagnostic]) { var operatorTable = OperatorTable.standardOperators operatorTable.addSourceFile(tree, errorHandler: recordOperatorError) let resultTree = operatorTable.foldAll(tree, errorHandler: recordOperatorError) - return (Syntax(resultTree), diags) + return (resultTree, diags) } class VerifyRoundTrip: ParsableCommand { From 5efa052f870a4d4229388660fa2a8c9618e0ee48 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 11 Sep 2022 21:40:58 -0700 Subject: [PATCH 48/50] Hoist "try" and "await" expressions during folding. When folding an expression such as `try x + y`, instead of producing `(try x) + y`, hoist the `try` up to produce `try (x + y)`. --- .../OperatorTable+Folding.swift | 28 +++++++++++++++++++ .../OperatorTableTests.swift | 7 +++++ 2 files changed, 35 insertions(+) diff --git a/Sources/SwiftOperators/OperatorTable+Folding.swift b/Sources/SwiftOperators/OperatorTable+Folding.swift index cc62f32a594..e25bd602142 100644 --- a/Sources/SwiftOperators/OperatorTable+Folding.swift +++ b/Sources/SwiftOperators/OperatorTable+Folding.swift @@ -94,6 +94,34 @@ extension OperatorTable { public static func makeBinaryOperationExpr( lhs: ExprSyntax, op: ExprSyntax, rhs: ExprSyntax ) -> ExprSyntax { + // If the left-hand side is a "try" or "await", hoist it up to encompass + // the right-hand side as well. + if let tryExpr = lhs.as(TryExprSyntax.self) { + return ExprSyntax( + TryExprSyntax( + tryExpr.unexpectedBeforeTryKeyword, + tryKeyword: tryExpr.tryKeyword, + tryExpr.unexpectedBetweenTryKeywordAndQuestionOrExclamationMark, + questionOrExclamationMark: tryExpr.questionOrExclamationMark, + tryExpr.unexpectedBetweenQuestionOrExclamationMarkAndExpression, + expression: makeBinaryOperationExpr( + lhs: tryExpr.expression, op: op, rhs: rhs) + ) + ) + } + + if let awaitExpr = lhs.as(AwaitExprSyntax.self) { + return ExprSyntax( + AwaitExprSyntax( + awaitExpr.unexpectedBeforeAwaitKeyword, + awaitKeyword: awaitExpr.awaitKeyword, + awaitExpr.unexpectedBetweenAwaitKeywordAndExpression, + expression: makeBinaryOperationExpr( + lhs: awaitExpr.expression, op: op, rhs: rhs) + ) + ) + } + // The form of the binary operation depends on the operator itself, // which will be one of the unresolved infix operators. diff --git a/Tests/SwiftOperatorsTest/OperatorTableTests.swift b/Tests/SwiftOperatorsTest/OperatorTableTests.swift index b4265c01f65..790fb69c699 100644 --- a/Tests/SwiftOperatorsTest/OperatorTableTests.swift +++ b/Tests/SwiftOperatorsTest/OperatorTableTests.swift @@ -381,4 +381,11 @@ public class OperatorPrecedenceTests: XCTestCase { "b + c ? y : z ? z2 : z3", "((b + c) ? y : (z ? z2 : z3))") } + + func testTryAwait() throws { + let opPrecedence = OperatorTable.standardOperators + try opPrecedence.assertExpectedFold("try x + y", "try (x + y)") + try opPrecedence.assertExpectedFold( + "await x + y + z", "await ((x + y) + z)") + } } From 7c4b3fef7a51296704bd56552df5dc29854b7c14 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 16 Sep 2022 14:12:02 -0700 Subject: [PATCH 49/50] Work around debug-info assertion in optimized builds --- Sources/SwiftOperators/OperatorTable.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/SwiftOperators/OperatorTable.swift b/Sources/SwiftOperators/OperatorTable.swift index c67a99cc518..85888c6b638 100644 --- a/Sources/SwiftOperators/OperatorTable.swift +++ b/Sources/SwiftOperators/OperatorTable.swift @@ -29,6 +29,7 @@ public struct OperatorTable { /// Initialize the operator precedence instance with a given set of /// operators and precedence groups. + @_optimize(none) public init( precedenceGroups: [PrecedenceGroup], operators: [Operator], From 7bcba760d546cac4b2ef7cfe2e3ddf45e7f86872 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 16 Sep 2022 15:36:19 -0700 Subject: [PATCH 50/50] Adjust for refactoring of precedence group declarations --- Sources/SwiftOperators/OperatorTable+Semantics.swift | 7 +------ Sources/SwiftOperators/SyntaxSynthesis.swift | 5 ++--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftOperators/OperatorTable+Semantics.swift b/Sources/SwiftOperators/OperatorTable+Semantics.swift index 77d55be7878..b05fe4caeb4 100644 --- a/Sources/SwiftOperators/OperatorTable+Semantics.swift +++ b/Sources/SwiftOperators/OperatorTable+Semantics.swift @@ -73,12 +73,7 @@ extension Operator { name = syntax.identifier.text - if let operatorSyntax = syntax.operatorPrecedenceAndTypes? - .precedenceGroupAndDesignatedTypes { - precedenceGroup = operatorSyntax.firstToken?.text - } else { - precedenceGroup = nil - } + precedenceGroup = syntax.operatorPrecedenceAndTypes?.precedenceGroup.text } } diff --git a/Sources/SwiftOperators/SyntaxSynthesis.swift b/Sources/SwiftOperators/SyntaxSynthesis.swift index 1461f517ab7..36d732cd765 100644 --- a/Sources/SwiftOperators/SyntaxSynthesis.swift +++ b/Sources/SwiftOperators/SyntaxSynthesis.swift @@ -25,9 +25,8 @@ extension Operator { let precedenceGroupSyntax = precedenceGroup.map { groupName in OperatorPrecedenceAndTypesSyntax( colon: .colonToken(), - precedenceGroupAndDesignatedTypes: IdentifierListSyntax( - [.identifier(groupName, leadingTrivia: .space)] - ) + precedenceGroup: .identifier(groupName, leadingTrivia: .space), + designatedTypes: DesignatedTypeListSyntax([]) ) }