mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
756 lines
29 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|
|
}
|