Files
swift-mirror/SwiftCompilerSources/Sources/SIL/SmallProjectionPath.swift
2023-02-11 08:55:20 +01:00

756 lines
29 KiB
Swift

//===--- SmallProjectionPath.swift - a path of projections ----------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2021 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 Basic
/// A small and very efficient representation of a projection path.
///
/// A `SmallProjectionPath` can be parsed and printed in SIL syntax and parsed from Swift
/// source code - the SIL syntax is more compact than the Swift syntax.
/// In the following, we use the SIL syntax for the examples.
///
/// The `SmallProjectionPath` represents a path of value or address projections.
/// For example, the projection path which represents
///
/// %t = struct_extract %s: $Str, #Str.tupleField // first field in Str
/// %c = tuple_extract %f : $(Int, Class), 4
/// %r = ref_element_addr %c $Class, #Class.classField // 3rd field in Class
///
/// is `s0.4.c2`, where `s0` is the first path component and `c2` is the last component.
///
/// A `SmallProjectionPath` can be a concrete path (like the example above): it only
/// contains concrete field elements, e.g. `s0.c2.e1`
/// Or it can be a pattern path, where one or more path components are wild cards, e.g.
/// `v**.c*` means: any number of value projections (struct, enum, tuple) followed by
/// a single class field projection.
///
/// Internally, a `SmallProjectionPath` is represented as a single 64-bit word.
/// This is very efficient, but it also means that a path cannot exceed a certain length.
/// If too many projections are pushed onto a path, the path is converted to a `**` wildcard,
/// which means: it represents any number of any kind of projections.
/// Though, it's very unlikely that the limit will be reached in real world scenarios.
///
public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflectionChildren {
/// The physical representation of the path. The path components are stored in
/// reverse order: the first path component is stored in the lowest bits (LSB),
/// the last component is stored in the highest bits (MSB).
/// Each path component consists of zero or more "index-overflow" bytes followed
/// by the "index-kind" main byte (from LSB to MSB).
///
/// index overflow byte: bit-nr: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
/// +---+---+---+---+---+---+---+---+
/// content: | index high bits | 1 |
///
/// main byte (small kind): bit-nr: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
/// +---+---+---+---+---+---+---+---+
/// content: | index low bits| kind | 0 |
///
/// "Large" kind values (>= 0x7) don't have an associated index and the main
/// byte looks like:
///
/// main byte (large kind): bit-nr: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
/// +---+---+---+---+---+---+---+---+
/// content: | kind high bits| 1 | 1 | 1 | 0 |
///
private let bytes: UInt64
// TODO: add better support for tail elements by tracking the
// index of `index_addr` instructions.
public enum FieldKind : Int {
case root = 0x0 // The pseudo component denoting the end of the path.
case structField = 0x1 // A concrete struct field: syntax e.g. `s3`
case tupleField = 0x2 // A concrete tuple element: syntax e.g. `2`
case enumCase = 0x3 // A concrete enum case (with payload): syntax e.g. `e4'
case classField = 0x4 // A concrete class field: syntax e.g. `c1`
case tailElements = 0x5 // A tail allocated element of a class: syntax `ct`
case anyValueFields = 0x6 // Any number of any value fields (struct, tuple, enum): syntax `v**`
// "Large" kinds: starting from here the low 3 bits must be 1.
// This and all following kinds (we'll add in the future) cannot have a field index.
case anyClassField = 0x7 // Any class field, including tail elements: syntax `c*`
case anything = 0xf // Any number of any fields: syntax `**`
public var isValueField: Bool {
switch self {
case .anyValueFields, .structField, .tupleField, .enumCase:
return true
case .root, .anything, .anyClassField, .classField, .tailElements:
return false
}
}
public var isClassField: Bool {
switch self {
case .anyClassField, .classField, .tailElements:
return true
case .root, .anything, .anyValueFields, .structField, .tupleField, .enumCase:
return false
}
}
}
public init() { self.bytes = 0 }
/// Creates a new path with an initial element.
public init(_ kind: FieldKind, index: Int = 0) {
self = Self().push(kind, index: index)
}
private init(bytes: UInt64) { self.bytes = bytes }
public var isEmpty: Bool { bytes == 0 }
public var description: String {
let (kind, idx, sp) = pop()
let subPath = sp
let s: String
switch kind {
case .root: return ""
case .structField: s = "s\(idx)"
case .tupleField: s = "\(idx)"
case .enumCase: s = "e\(idx)"
case .classField: s = "c\(idx)"
case .tailElements: s = "ct"
case .anything: s = "**"
case .anyValueFields: s = "v**"
case .anyClassField: s = "c*"
}
if subPath.isEmpty {
return s
}
return "\(s).\(subPath)"
}
/// Returns the top (= the first) path component and the number of its encoding bits.
private var top: (kind: FieldKind, index: Int, numBits: Int) {
var idx = 0
var b = bytes
var numBits = 0
// Parse any index overflow bytes.
while (b & 1) == 1 {
idx = (idx << 7) | Int((b >> 1) & 0x7f)
b >>= 8
numBits = numBits &+ 8
}
var kindVal = (b >> 1) & 0x7
if kindVal == 0x7 {
// A "large" kind - without any index
kindVal = (b >> 1) & 0x7f
assert(idx == 0)
assert(numBits == 0)
} else {
// A "small" kind with an index
idx = (idx << 4) | Int((b >> 4) & 0xf)
}
let k = FieldKind(rawValue: Int(kindVal))!
if k == .anything {
assert((b >> 8) == 0, "'anything' only allowed in last path component")
numBits = 8
} else {
numBits = numBits &+ 8
}
return (k, idx, numBits)
}
/// Pops \p numBits from the path.
private func pop(numBits: Int) -> SmallProjectionPath {
return Self(bytes: bytes &>> numBits)
}
/// Pops and returns the first path component included the resulting path
/// after popping.
///
/// For example, popping from `s0.c3.e1` returns (`s`, 0, `c3.e1`)
public func pop() -> (kind: FieldKind, index: Int, path: SmallProjectionPath) {
let (k, idx, numBits) = top
return (k, idx, pop(numBits: numBits))
}
/// Pushes a new first component to the path and returns the new path.
///
/// For example, pushing `s0` to `c3.e1` returns `s0.c3.e1`.
public func push(_ kind: FieldKind, index: Int = 0) -> SmallProjectionPath {
assert(kind != .anything || bytes == 0, "'anything' only allowed in last path component")
var idx = index
var b = bytes
if (b >> 56) != 0 {
// Overflow
return Self(.anything)
}
b = (b << 8) | UInt64(((idx & 0xf) << 4) | (kind.rawValue << 1))
idx >>= 4
while idx != 0 {
if (b >> 56) != 0 { return Self(.anything) }
b = (b << 8) | UInt64(((idx & 0x7f) << 1) | 1)
idx >>= 7
}
return Self(bytes: b)
}
/// Pops the first path component if it is exactly of kind `kind` - not considering wildcards.
///
/// Returns the index of the component and the new path or - if not matching - returns nil.
public func pop(kind: FieldKind) -> (index: Int, path: SmallProjectionPath)? {
let (k, idx, newPath) = pop()
if k != kind { return nil }
return (idx, newPath)
}
/// Pops the first path component if it matches `kind` and (optionally) `index`.
///
/// For example:
/// popping `s0` from `s0.c3.e1` returns `c3.e1`
/// popping `c2` from `c*.e1` returns `e1`
/// popping `s0` from `v**.c3.e1` return `v**.c3.e1` (because `v**` means _any_ number of value fields)
/// popping `s0` from `c*.e1` returns nil
///
/// Note that if `kind` is a wildcard, also the first path component must be a wildcard to popped.
/// For example:
/// popping `v**` from `s0.c1` returns nil
/// popping `v**` from `v**.c1` returns `v**.c1` (because `v**` means _any_ number of value fields)
/// popping `c*` from `c0.e3` returns nil
/// popping `c*` from `c*.e3` returns `e3`
public func popIfMatches(_ kind: FieldKind, index: Int? = nil) -> SmallProjectionPath? {
let (k, idx, numBits) = top
switch k {
case .anything:
return self
case .anyValueFields:
if kind.isValueField { return self }
return pop(numBits: numBits).popIfMatches(kind, index: index)
case .anyClassField:
if kind.isClassField {
return pop(numBits: numBits)
}
return nil
case kind:
if let i = index {
if i != idx { return nil }
}
return pop(numBits: numBits)
default:
return nil
}
}
/// Returns true if the path has at least one class projection.
/// For example:
/// returns false for `v**`
/// returns true for `v**.c0.s1.v**`
/// returns false for `**` (because '**' can have zero class projections)
public var hasClassProjection: Bool {
var p = self
while true {
let (k, _, numBits) = p.top
if k == .root { return false }
if k.isClassField { return true }
p = p.pop(numBits: numBits)
}
}
/// Returns true if the path may have a class projection.
/// For example:
/// returns false for `v**`
/// returns true for `c0`
/// returns true for `**` (because '**' can have any number of class projections)
public var mayHaveClassProjection: Bool {
return !matches(pattern: Self(.anyValueFields))
}
/// Returns true if the path may have a class projection.
/// For example:
/// returns false for `v**`
/// returns false for `c0`
/// returns true for `c0.c1`
/// returns true for `c0.**` (because '**' can have any number of class projections)
/// returns true for `**` (because '**' can have any number of class projections)
public var mayHaveTwoClassProjections: Bool {
return !matches(pattern: Self(.anyValueFields)) &&
!matches(pattern: Self(.anyValueFields).push(.anyClassField).push(.anyValueFields))
}
/// Pops all value field components from the beginning of the path.
/// For example:
/// `s0.e2.3.c4.s1` -> `c4.s1`
/// `v**.c4.s1` -> `c4.s1`
/// `**` -> `**` (because `**` can also be a class field)
public func popAllValueFields() -> SmallProjectionPath {
var p = self
while true {
let (k, _, numBits) = p.top
if !k.isValueField { return p }
p = p.pop(numBits: numBits)
}
}
/// Pops the last class projection and all following value fields from the tail of the path.
/// For example:
/// `s0.e2.3.c4.s1` -> `s0.e2.3`
/// `v**.c1.c4.s1` -> `v**.c1`
/// `c1.**` -> `c1.**` (because it's unknown how many class projections are in `**`)
public func popLastClassAndValuesFromTail() -> SmallProjectionPath {
var p = self
var totalBits = 0
var neededBits = 0
while true {
let (k, _, numBits) = p.top
if k == .root { break }
if k.isClassField {
neededBits = totalBits
totalBits += numBits
} else {
totalBits += numBits
if !k.isValueField {
// k is `anything`
neededBits = totalBits
}
}
p = p.pop(numBits: numBits)
}
if neededBits == 64 { return self }
return SmallProjectionPath(bytes: bytes & ((1 << neededBits) - 1))
}
/// Returns true if this path matches a pattern path.
///
/// Formally speaking:
/// If this path is a concrete path, returns true if it matches the pattern.
/// If this path is a pattern path itself, returns true if all concrete paths which
/// match this path also match the pattern path.
/// For example:
/// `s0.c3.e1` matches `s0.c3.e1`
/// `s0.c3.e1` matches `v**.c*.e1`
/// `v**.c*.e1` does not match `s0.c3.e1`!
/// Note that matching is not reflexive.
public func matches(pattern: SmallProjectionPath) -> Bool {
let (patternKind, patternIdx, subPattern) = pattern.pop()
switch patternKind {
case .root: return isEmpty
case .anything: return true
case .anyValueFields:
return popAllValueFields().matches(pattern: subPattern)
case .anyClassField:
let (kind, _, subPath) = pop()
if !kind.isClassField { return false }
return subPath.matches(pattern: subPattern)
case .structField, .tupleField, .enumCase, .classField, .tailElements:
let (kind, index, subPath) = pop()
if kind != patternKind || index != patternIdx { return false }
return subPath.matches(pattern: subPattern)
}
}
/// Returns the merged path of this path and `rhs`.
///
/// Merging means that all paths which match this path and `rhs` will also match the result.
/// If `rhs` is not equal to this path, the result is computed by replacing
/// mismatching components by wildcards.
/// For example:
/// `s0.c3.e4` merged with `s0.c1.e4` -> `s0.c*.e4`
/// `s0.s1.c3` merged with `e4.c3` -> `v**.c3`
/// `s0.c1.c2` merged with `s0.c3` -> `s0.**`
public func merge(with rhs: SmallProjectionPath) -> SmallProjectionPath {
if self == rhs { return self }
let (lhsKind, lhsIdx, lhsBits) = top
let (rhsKind, rhsIdx, rhsBits) = rhs.top
if lhsKind == rhsKind && lhsIdx == rhsIdx {
assert(lhsBits == rhsBits)
let subPath = pop(numBits: lhsBits).merge(with: rhs.pop(numBits: rhsBits))
if lhsKind == .anyValueFields && subPath.top.kind == .anyValueFields {
return subPath
}
return subPath.push(lhsKind, index: lhsIdx)
}
if lhsKind.isValueField || rhsKind.isValueField {
let subPath = popAllValueFields().merge(with: rhs.popAllValueFields())
assert(!subPath.top.kind.isValueField)
if subPath.top.kind == .anything {
return subPath
}
return subPath.push(.anyValueFields)
}
if lhsKind.isClassField && rhsKind.isClassField {
let subPath = pop(numBits: lhsBits).merge(with: rhs.pop(numBits: rhsBits))
return subPath.push(.anyClassField)
}
return Self(.anything)
}
/// Returns true if this path may overlap with `rhs`.
///
/// "Overlapping" means that both paths may project the same field.
/// For example:
/// `s0.s1` and `s0.s1` overlap (the paths are identical)
/// `s0.s1` and `s0.s2` don't overlap
/// `s0.s1` and `s0` overlap (the second path is a sub-path of the first one)
/// `s0.v**` and `s0.s1` overlap
public func mayOverlap(with rhs: SmallProjectionPath) -> Bool {
if isEmpty || rhs.isEmpty {
return true
}
let (lhsKind, lhsIdx, lhsBits) = top
let (rhsKind, rhsIdx, rhsBits) = rhs.top
if lhsKind == .anything || rhsKind == .anything {
return true
}
if lhsKind == .anyValueFields || rhsKind == .anyValueFields {
return popAllValueFields().mayOverlap(with: rhs.popAllValueFields())
}
if (lhsKind == rhsKind && lhsIdx == rhsIdx) ||
(lhsKind == .anyClassField && rhsKind.isClassField) ||
(lhsKind.isClassField && rhsKind == .anyClassField) {
return pop(numBits: lhsBits).mayOverlap(with: rhs.pop(numBits: rhsBits))
}
return false
}
}
//===----------------------------------------------------------------------===//
// Parsing
//===----------------------------------------------------------------------===//
extension StringParser {
mutating func parseProjectionPathFromSource(for function: Function, type: Type?) throws -> SmallProjectionPath {
var entries: [(SmallProjectionPath.FieldKind, Int)] = []
var currentTy = type
repeat {
if consume("**") {
entries.append((.anything, 0))
currentTy = nil
} else if consume("class*") {
if let ty = currentTy, !ty.isClass {
try throwError("cannot use 'anyClassField' on a non-class type - add 'anyValueFields' first")
}
entries.append((.anyClassField, 0))
currentTy = nil
} else if consume("value**") {
entries.append((.anyValueFields, 0))
currentTy = nil
} else if let tupleElemIdx = consumeInt() {
guard let ty = currentTy, ty.isTuple else {
try throwError("cannot use a tuple index after 'any' field selection")
}
let tupleElements = ty.tupleElements
if tupleElemIdx >= tupleElements.count {
try throwError("tuple element index too large")
}
entries.append((.tupleField, tupleElemIdx))
currentTy = tupleElements[tupleElemIdx]
} else if let name = consumeIdentifier() {
guard let ty = currentTy else {
try throwError("cannot use field name after 'any' field selection")
}
if !ty.isClass && !ty.isStruct {
try throwError("unknown kind of nominal type")
}
let nominalFields = ty.getNominalFields(in: function)
guard let fieldIdx = nominalFields.getIndexOfField(withName: name) else {
try throwError("field not found")
}
if ty.isClass {
entries.append((.classField, fieldIdx))
} else {
assert(ty.isStruct)
entries.append((.structField, fieldIdx))
}
currentTy = nominalFields[fieldIdx]
} else {
try throwError("expected selection path component")
}
} while consume(".")
if let ty = currentTy, !ty.isClass {
try throwError("the select field is not a class - add 'anyValueFields'")
}
return try createPath(from: entries)
}
mutating func parseProjectionPathFromSIL() throws -> SmallProjectionPath {
var entries: [(SmallProjectionPath.FieldKind, Int)] = []
while true {
if consume("**") {
entries.append((.anything, 0))
} else if consume("c*") {
entries.append((.anyClassField, 0))
} else if consume("v**") {
entries.append((.anyValueFields, 0))
} else if consume("ct") {
entries.append((.tailElements, 0))
} else if consume("c") {
guard let idx = consumeInt(withWhiteSpace: false) else {
try throwError("expected class field index")
}
entries.append((.classField, idx))
} else if consume("e") {
guard let idx = consumeInt(withWhiteSpace: false) else {
try throwError("expected enum case index")
}
entries.append((.enumCase, idx))
} else if consume("s") {
guard let idx = consumeInt(withWhiteSpace: false) else {
try throwError("expected struct field index")
}
entries.append((.structField, idx))
} else if let tupleElemIdx = consumeInt() {
entries.append((.tupleField, tupleElemIdx))
} else if !consume(".") {
return try createPath(from: entries)
}
}
}
private func createPath(from entries: [(SmallProjectionPath.FieldKind, Int)]) throws -> SmallProjectionPath {
var path = SmallProjectionPath()
var first = true
for (kind, idx) in entries.reversed() {
if !first && kind == .anything {
try throwError("'**' only allowed in last path component")
}
path = path.push(kind, index: idx)
// Check for overflow
if !first && path == SmallProjectionPath(.anything) {
try throwError("path is too long")
}
first = false
}
return path
}
}
//===----------------------------------------------------------------------===//
// Unit Tests
//===----------------------------------------------------------------------===//
extension SmallProjectionPath {
public static func runUnitTests() {
basicPushPop()
parsing()
merging()
matching()
overlapping()
predicates()
path2path()
func basicPushPop() {
let p1 = SmallProjectionPath(.structField, index: 3)
.push(.classField, index: 12345678)
let (k2, i2, p2) = p1.pop()
assert(k2 == .classField && i2 == 12345678)
let (k3, i3, p3) = p2.pop()
assert(k3 == .structField && i3 == 3)
assert(p3.isEmpty)
let (k4, i4, _) = p2.push(.enumCase, index: 876).pop()
assert(k4 == .enumCase && i4 == 876)
let p5 = SmallProjectionPath(.anything)
assert(p5.pop().path.isEmpty)
}
func parsing() {
testParse("v**.c*", expect: SmallProjectionPath(.anyClassField)
.push(.anyValueFields))
testParse("s3.c*.v**.s1", expect: SmallProjectionPath(.structField, index: 1)
.push(.anyValueFields)
.push(.anyClassField)
.push(.structField, index: 3))
testParse("2.c*.e6.ct.**", expect: SmallProjectionPath(.anything)
.push(.tailElements)
.push(.enumCase, index: 6)
.push(.anyClassField)
.push(.tupleField, index: 2))
do {
var parser = StringParser("c*.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.s123.s3.**")
_ = try parser.parseProjectionPathFromSIL()
fatalError("too long path not detected")
} catch {
}
do {
var parser = StringParser("**.s0")
_ = try parser.parseProjectionPathFromSIL()
fatalError("wrong '**' not detected")
} catch {
}
}
func testParse(_ pathStr: String, expect: SmallProjectionPath) {
var parser = StringParser(pathStr)
let path = try! parser.parseProjectionPathFromSIL()
assert(path == expect)
let str = path.description
assert(str == pathStr)
}
func merging() {
testMerge("c1.c0", "c0", expect: "c*.**")
testMerge("c2.c1", "c2", expect: "c2.**")
testMerge("s3.c0", "v**.c0", expect: "v**.c0")
testMerge("c0", "s2.c1", expect: "v**.c*")
testMerge("s1.s1.c2", "s1.c2", expect: "s1.v**.c2")
testMerge("s1.s0", "s2.s0", expect: "v**")
testMerge("ct", "c2", expect: "c*")
testMerge("ct.s0.e0.v**.c0", "ct.s0.e0.v**.c0", expect: "ct.s0.e0.v**.c0")
testMerge("ct.s0.s0.c0", "ct.s0.e0.s0.c0", expect: "ct.s0.v**.c0")
}
func testMerge(_ lhsStr: String, _ rhsStr: String,
expect expectStr: String) {
var lhsParser = StringParser(lhsStr)
let lhs = try! lhsParser.parseProjectionPathFromSIL()
var rhsParser = StringParser(rhsStr)
let rhs = try! rhsParser.parseProjectionPathFromSIL()
var expectParser = StringParser(expectStr)
let expect = try! expectParser.parseProjectionPathFromSIL()
let result = lhs.merge(with: rhs)
assert(result == expect)
let result2 = rhs.merge(with: lhs)
assert(result2 == expect)
}
func matching() {
testMatch("ct", "c*", expect: true)
testMatch("c1", "c*", expect: true)
testMatch("s2", "v**", expect: true)
testMatch("1", "v**", expect: true)
testMatch("e1", "v**", expect: true)
testMatch("c*", "c1", expect: false)
testMatch("c*", "ct", expect: false)
testMatch("v**", "s0", expect: false)
testMatch("s0.s1", "s0.s1", expect: true)
testMatch("s0.s2", "s0.s1", expect: false)
testMatch("s0", "s0.v**", expect: true)
testMatch("s0.s1", "s0.v**", expect: true)
testMatch("s0.1.e2", "s0.v**", expect: true)
testMatch("s0.v**.e2", "v**", expect: true)
testMatch("s0.v**", "s0.s1", expect: false)
testMatch("s0.s1.c*", "s0.v**", expect: false)
testMatch("s0.v**", "s0.**", expect: true)
testMatch("s1.v**", "s0.**", expect: false)
testMatch("s0.**", "s0.v**", expect: false)
}
func testMatch(_ lhsStr: String, _ rhsStr: String, expect: Bool) {
var lhsParser = StringParser(lhsStr)
let lhs = try! lhsParser.parseProjectionPathFromSIL()
var rhsParser = StringParser(rhsStr)
let rhs = try! rhsParser.parseProjectionPathFromSIL()
let result = lhs.matches(pattern: rhs)
assert(result == expect)
}
func overlapping() {
testOverlap("s0.s1.s2", "s0.s1.s2", expect: true)
testOverlap("s0.s1.s2", "s0.s2.s2", expect: false)
testOverlap("s0.s1.s2", "s0.e1.s2", expect: false)
testOverlap("s0.s1.s2", "s0.s1", expect: true)
testOverlap("s0.s1.s2", "s1.s2", expect: false)
testOverlap("s0.c*.s2", "s0.ct.s2", expect: true)
testOverlap("s0.c*.s2", "s0.c1.s2", expect: true)
testOverlap("s0.c*.s2", "s0.c1.c2.s2", expect: false)
testOverlap("s0.c*.s2", "s0.s2", expect: false)
testOverlap("s0.v**.s2", "s0.s3", expect: true)
testOverlap("s0.v**.s2.c2", "s0.s3.c1", expect: false)
testOverlap("s0.v**.s2", "s1.s3", expect: false)
testOverlap("s0.v**.s2", "s0.v**.s3", expect: true)
testOverlap("s0.**", "s0.s3.c1", expect: true)
testOverlap("**", "s0.s3.c1", expect: true)
}
func testOverlap(_ lhsStr: String, _ rhsStr: String, expect: Bool) {
var lhsParser = StringParser(lhsStr)
let lhs = try! lhsParser.parseProjectionPathFromSIL()
var rhsParser = StringParser(rhsStr)
let rhs = try! rhsParser.parseProjectionPathFromSIL()
let result = lhs.mayOverlap(with: rhs)
assert(result == expect)
let reversedResult = rhs.mayOverlap(with: lhs)
assert(reversedResult == expect)
}
func predicates() {
testPredicate("v**", \.hasClassProjection, expect: false)
testPredicate("v**.c0.s1.v**", \.hasClassProjection, expect: true)
testPredicate("c0.**", \.hasClassProjection, expect: true)
testPredicate("c0.c1", \.hasClassProjection, expect: true)
testPredicate("ct", \.hasClassProjection, expect: true)
testPredicate("s0", \.hasClassProjection, expect: false)
testPredicate("v**", \.mayHaveClassProjection, expect: false)
testPredicate("c0", \.mayHaveClassProjection, expect: true)
testPredicate("1", \.mayHaveClassProjection, expect: false)
testPredicate("**", \.mayHaveClassProjection, expect: true)
testPredicate("v**", \.mayHaveTwoClassProjections, expect: false)
testPredicate("c0", \.mayHaveTwoClassProjections, expect: false)
testPredicate("**", \.mayHaveTwoClassProjections, expect: true)
testPredicate("v**.c*.s2.1.c0", \.mayHaveTwoClassProjections, expect: true)
testPredicate("c*.s2.1.c0.v**", \.mayHaveTwoClassProjections, expect: true)
testPredicate("v**.c*.**", \.mayHaveTwoClassProjections, expect: true)
}
func testPredicate(_ pathStr: String, _ property: (SmallProjectionPath) -> Bool, expect: Bool) {
var parser = StringParser(pathStr)
let path = try! parser.parseProjectionPathFromSIL()
let result = property(path)
assert(result == expect)
}
func path2path() {
testPath2Path("s0.e2.3.c4.s1", { $0.popAllValueFields() }, expect: "c4.s1")
testPath2Path("v**.c4.s1", { $0.popAllValueFields() }, expect: "c4.s1")
testPath2Path("**", { $0.popAllValueFields() }, expect: "**")
testPath2Path("s0.e2.3.c4.s1.e2.v**.**", { $0.popLastClassAndValuesFromTail() }, expect: "s0.e2.3.c4.s1.e2.v**.**")
testPath2Path("s0.c2.3.c4.s1", { $0.popLastClassAndValuesFromTail() }, expect: "s0.c2.3")
testPath2Path("v**.c*.s1", { $0.popLastClassAndValuesFromTail() }, expect: "v**")
testPath2Path("s1.ct.v**", { $0.popLastClassAndValuesFromTail() }, expect: "s1")
testPath2Path("c0.c1.c2", { $0.popLastClassAndValuesFromTail() }, expect: "c0.c1")
testPath2Path("**", { $0.popLastClassAndValuesFromTail() }, expect: "**")
testPath2Path("v**.c3", { $0.popIfMatches(.anyValueFields) }, expect: "v**.c3")
testPath2Path("**", { $0.popIfMatches(.anyValueFields) }, expect: "**")
testPath2Path("s0.c3", { $0.popIfMatches(.anyValueFields) }, expect: nil)
testPath2Path("c0.s3", { $0.popIfMatches(.anyClassField) }, expect: nil)
testPath2Path("**", { $0.popIfMatches(.anyClassField) }, expect: "**")
testPath2Path("c*.e3", { $0.popIfMatches(.anyClassField) }, expect: "e3")
}
func testPath2Path(_ pathStr: String, _ transform: (SmallProjectionPath) -> SmallProjectionPath?, expect: String?) {
var parser = StringParser(pathStr)
let path = try! parser.parseProjectionPathFromSIL()
let result = transform(path)
if let expect = expect {
var expectParser = StringParser(expect)
let expectPath = try! expectParser.parseProjectionPathFromSIL()
assert(result == expectPath)
} else {
assert(result == nil)
}
}
}
}