mirror of
https://github.com/nicklockwood/SwiftFormat.git
synced 2026-05-17 10:30:35 +00:00
1218 lines
48 KiB
Swift
Executable File
1218 lines
48 KiB
Swift
Executable File
//
|
|
// AnyExpression.swift
|
|
// Expression
|
|
//
|
|
// Version 0.12.12
|
|
//
|
|
// Created by Nick Lockwood on 18/04/2017.
|
|
// Copyright © 2017 Nick Lockwood. All rights reserved.
|
|
//
|
|
// Distributed under the permissive MIT license
|
|
// Get the latest version from here:
|
|
//
|
|
// https://github.com/nicklockwood/Expression
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
// copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// Wrapper for Expression that works with any type of value
|
|
public struct AnyExpression: CustomStringConvertible {
|
|
private let expression: Expression
|
|
private let describer: () -> String
|
|
private let evaluator: () throws -> Any
|
|
|
|
/// Evaluator for individual symbols
|
|
public typealias SymbolEvaluator = (_ args: [Any]) throws -> Any
|
|
|
|
/// Symbols that make up an expression
|
|
public typealias Symbol = Expression.Symbol
|
|
|
|
/// Runtime error when parsing or evaluating an expression
|
|
public typealias Error = Expression.Error
|
|
|
|
/// Options for configuring an expression
|
|
public typealias Options = Expression.Options
|
|
|
|
/// Creates an AnyExpression instance from a string
|
|
/// Optionally accepts some or all of:
|
|
/// - A set of options for configuring expression behavior
|
|
/// - A dictionary of constants for simple static values (including arrays)
|
|
/// - A dictionary of symbols, for implementing custom functions and operators
|
|
public init(
|
|
_ expression: String,
|
|
options: Options = .boolSymbols,
|
|
constants: [String: Any] = [:],
|
|
symbols: [Symbol: SymbolEvaluator] = [:]
|
|
) {
|
|
self.init(
|
|
Expression.parse(expression),
|
|
options: options,
|
|
constants: constants,
|
|
symbols: symbols
|
|
)
|
|
}
|
|
|
|
/// Alternative constructor that accepts a pre-parsed expression
|
|
public init(
|
|
_ expression: ParsedExpression,
|
|
options: Options = [],
|
|
constants: [String: Any] = [:],
|
|
symbols: [Symbol: SymbolEvaluator] = [:]
|
|
) {
|
|
// Options
|
|
let pureSymbols = options.contains(.pureSymbols)
|
|
|
|
self.init(
|
|
expression,
|
|
options: options,
|
|
impureSymbols: { symbol in
|
|
switch symbol {
|
|
case let .variable(name):
|
|
if constants[name] == nil {
|
|
return symbols[symbol]
|
|
}
|
|
case let .array(name):
|
|
// TODO: should we support overloading variables and arrays like this?
|
|
if !(constants[name].map(AnyExpression.isSubscriptable) ?? false) {
|
|
return symbols[symbol]
|
|
}
|
|
default:
|
|
if !pureSymbols {
|
|
return symbols[symbol]
|
|
}
|
|
}
|
|
return nil
|
|
},
|
|
pureSymbols: { symbol in
|
|
switch symbol {
|
|
case let .variable(name):
|
|
return constants[name].map { value in { _ in value } }
|
|
case let .array(name) where constants[name] != nil:
|
|
return nil // Ensure constant array takes precedence over symbols
|
|
default:
|
|
return symbols[symbol]
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
/// Alternative constructor for advanced usage
|
|
/// Allows for dynamic symbol lookup or generation without any performance overhead
|
|
/// Note that standard library symbols are all enabled by default - to disable them
|
|
/// return `{ _ in throw AnyExpression.Error.undefinedSymbol(symbol) }` from your lookup function
|
|
public init(
|
|
_ expression: ParsedExpression,
|
|
impureSymbols: (Symbol) -> SymbolEvaluator?,
|
|
pureSymbols: (Symbol) -> SymbolEvaluator? = { _ in nil }
|
|
) {
|
|
self.init(
|
|
expression,
|
|
options: .boolSymbols,
|
|
impureSymbols: impureSymbols,
|
|
pureSymbols: pureSymbols
|
|
)
|
|
}
|
|
|
|
/// Alternative constructor with only pure symbols
|
|
public init(_ expression: ParsedExpression, pureSymbols: (Symbol) -> SymbolEvaluator?) {
|
|
self.init(expression, impureSymbols: { _ in nil }, pureSymbols: pureSymbols)
|
|
}
|
|
|
|
/// Private initializer implementation
|
|
private init(
|
|
_ expression: ParsedExpression,
|
|
options: Options,
|
|
impureSymbols: (Symbol) -> SymbolEvaluator?,
|
|
pureSymbols: (Symbol) -> SymbolEvaluator?
|
|
) {
|
|
let box = NanBox()
|
|
|
|
func loadNumber(_ arg: Double) -> Double? {
|
|
return box.loadIfStored(arg).map {
|
|
($0 as? NSNumber).map(Double.init(truncating:))
|
|
} ?? arg
|
|
}
|
|
func argsToDouble(_ args: [Double], for symbol: Symbol) throws -> [Double] {
|
|
return try args.map {
|
|
guard let doubleValue = loadNumber($0) else {
|
|
throw Error.typeMismatch(symbol, args.map(box.load))
|
|
}
|
|
return doubleValue
|
|
}
|
|
}
|
|
func equalArgs(_ lhs: Double, _ rhs: Double) throws -> Bool {
|
|
switch (AnyExpression.unwrap(box.load(lhs)), AnyExpression.unwrap(box.load(rhs))) {
|
|
case (nil, nil):
|
|
return true
|
|
case (nil, _),
|
|
(_, nil):
|
|
return false
|
|
case let (lhs as Double, rhs as Double):
|
|
return lhs == rhs
|
|
case let (lhs as AnyHashable, rhs as AnyHashable):
|
|
return lhs == rhs
|
|
case let (lhs as [AnyHashable], rhs as [AnyHashable]):
|
|
return lhs == rhs
|
|
case let (lhs as [AnyHashable: AnyHashable], rhs as [AnyHashable: AnyHashable]):
|
|
return lhs == rhs
|
|
case let (lhs as (AnyHashable, AnyHashable), rhs as (AnyHashable, AnyHashable)):
|
|
return lhs == rhs
|
|
case let (lhs as (AnyHashable, AnyHashable, AnyHashable),
|
|
rhs as (AnyHashable, AnyHashable, AnyHashable)):
|
|
return lhs == rhs
|
|
case let (lhs as (AnyHashable, AnyHashable, AnyHashable, AnyHashable),
|
|
rhs as (AnyHashable, AnyHashable, AnyHashable, AnyHashable)):
|
|
return lhs == rhs
|
|
case let (lhs as (AnyHashable, AnyHashable, AnyHashable, AnyHashable, AnyHashable),
|
|
rhs as (AnyHashable, AnyHashable, AnyHashable, AnyHashable, AnyHashable)):
|
|
return lhs == rhs
|
|
case let (lhs as (AnyHashable, AnyHashable, AnyHashable, AnyHashable, AnyHashable, AnyHashable),
|
|
rhs as (AnyHashable, AnyHashable, AnyHashable, AnyHashable, AnyHashable, AnyHashable)):
|
|
return lhs == rhs
|
|
case let (lhs?, rhs?):
|
|
throw Error.typeMismatch(.infix("=="), [lhs, rhs])
|
|
}
|
|
}
|
|
func unwrapString(_ name: String) -> String? {
|
|
guard name.count >= 2, "'\"".contains(name.first!) else {
|
|
return nil
|
|
}
|
|
return String(name.dropFirst().dropLast())
|
|
}
|
|
func arrayEvaluator(for symbol: Symbol, _ value: Any) -> SymbolEvaluator {
|
|
// TODO: should arrayEvaluator call the `.infix("[]")` implementation,
|
|
// rather than vice-versa?
|
|
switch value {
|
|
case let array as _Array:
|
|
return { args in
|
|
switch args[0] {
|
|
case let index as NSNumber: // TODO: should Bool be explicitly disallowed?
|
|
let values = array.values
|
|
let index = Int(truncating: index) // TODO: should this use Int(exactly:)?
|
|
if (0 ..< values.count).contains(index) {
|
|
return values[index]
|
|
}
|
|
throw Error.arrayBounds(symbol, Double(index))
|
|
case let range as _Range:
|
|
return try range.slice(of: array, for: symbol)
|
|
case let index:
|
|
throw Error.typeMismatch(symbol, [array, index])
|
|
}
|
|
}
|
|
case let dictionary as _Dictionary:
|
|
return { args in
|
|
guard let value = dictionary.value(for: args[0]) else {
|
|
throw Error.typeMismatch(symbol, [dictionary, args[0]])
|
|
}
|
|
return value
|
|
}
|
|
case let string as _String:
|
|
return { args in
|
|
switch args[0] {
|
|
case let offset as NSNumber:
|
|
let substring = string.substring
|
|
let offset = Int(truncating: offset)
|
|
guard (0 ..< substring.count).contains(offset) else {
|
|
throw Error.stringBounds(String(substring), offset)
|
|
}
|
|
return substring[substring.index(substring.startIndex, offsetBy: offset)]
|
|
case let index as String.Index:
|
|
let substring = string.substring
|
|
guard substring.indices.contains(index) else {
|
|
throw Error.stringBounds(substring, index)
|
|
}
|
|
return substring[index]
|
|
case let range as _Range:
|
|
return try range.slice(of: string, for: symbol)
|
|
case let index:
|
|
throw Error.typeMismatch(symbol, [string, index])
|
|
}
|
|
}
|
|
case let value:
|
|
return { throw Error.typeMismatch(symbol, [value] + $0) }
|
|
}
|
|
}
|
|
func funcEvaluator(for symbol: Symbol, _ value: Any) -> Expression.SymbolEvaluator? {
|
|
// TODO: should funcEvaluator call the `.infix("()")` implementation?
|
|
switch value {
|
|
case let fn as SymbolEvaluator:
|
|
return { args in
|
|
try box.store(fn(args.map(box.load)))
|
|
}
|
|
case let fn as Expression.SymbolEvaluator:
|
|
return { args in
|
|
try fn(argsToDouble(args, for: symbol))
|
|
}
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Set description based on the parsed expression, prior to
|
|
// performing optimizations. This avoids issues with inlined
|
|
// constants and string literals being converted to `nan`
|
|
describer = { expression.description }
|
|
|
|
// Options
|
|
let boolSymbols = options.contains(.boolSymbols) ? Expression.boolSymbols : [:]
|
|
let shouldOptimize = !options.contains(.noOptimize)
|
|
|
|
/// Evaluators
|
|
func defaultEvaluator(for symbol: Symbol) -> Expression.SymbolEvaluator? {
|
|
if let fn = AnyExpression.standardSymbols[symbol] {
|
|
return fn
|
|
} else if let fn = Expression.mathSymbols[symbol] {
|
|
switch symbol {
|
|
case .infix("+"):
|
|
return { args in
|
|
switch (box.load(args[0]), box.load(args[1])) {
|
|
case let (lhs as Double, rhs as Double):
|
|
return lhs + rhs
|
|
case let (lhs as _String, any):
|
|
guard let rhs = AnyExpression.unwrap(any) else {
|
|
throw Error.typeMismatch(symbol, [lhs, any])
|
|
}
|
|
return box.store("\(lhs)\(AnyExpression.stringify(rhs))")
|
|
case let (any, rhs as _String):
|
|
guard let lhs = AnyExpression.unwrap(any) else {
|
|
throw Error.typeMismatch(symbol, [any, rhs])
|
|
}
|
|
return box.store("\(AnyExpression.stringify(lhs))\(rhs)")
|
|
case let (lhs as _Array, rhs as _Array):
|
|
return box.store(lhs.values + rhs.values)
|
|
case let (lhs as NSNumber, rhs as NSNumber):
|
|
return Double(truncating: lhs) + Double(truncating: rhs)
|
|
case let (lhs, rhs):
|
|
throw Error.typeMismatch(symbol, [lhs, rhs])
|
|
}
|
|
}
|
|
default:
|
|
return { args in
|
|
// We potentially lose precision by converting all numbers to doubles
|
|
// TODO: find alternative approach that doesn't lose precision
|
|
try fn(argsToDouble(args, for: symbol))
|
|
}
|
|
}
|
|
} else if let fn = boolSymbols[symbol] {
|
|
switch symbol {
|
|
case .infix("=="):
|
|
return { try equalArgs($0[0], $0[1]) ? NanBox.trueValue : NanBox.falseValue }
|
|
case .infix("!="):
|
|
return { try equalArgs($0[0], $0[1]) ? NanBox.falseValue : NanBox.trueValue }
|
|
case .infix("?:"):
|
|
return { args in
|
|
guard args.count == 3 else {
|
|
throw Error.undefinedSymbol(symbol)
|
|
}
|
|
guard let doubleValue = loadNumber(args[0]) else {
|
|
throw Error.typeMismatch(symbol, args.map(box.load))
|
|
}
|
|
return doubleValue != 0 ? args[1] : args[2]
|
|
}
|
|
default:
|
|
return { args in
|
|
try fn(argsToDouble(args, for: symbol)) == 0 ?
|
|
NanBox.falseValue : NanBox.trueValue
|
|
}
|
|
}
|
|
} else {
|
|
switch symbol {
|
|
case .infix("[]"):
|
|
return { args in
|
|
let fn = arrayEvaluator(for: symbol, box.load(args[0]))
|
|
return try box.store(fn([box.load(args[1])]))
|
|
}
|
|
case .infix("..."):
|
|
return { args in
|
|
switch (box.load(args[0]), box.load(args[1])) {
|
|
case let (lhs as NSNumber, rhs as NSNumber):
|
|
let lhs = Int(truncating: lhs), rhs = Int(truncating: rhs)
|
|
guard lhs <= rhs else { throw Error.invalidRange(lhs, rhs) }
|
|
return box.store(lhs ... rhs)
|
|
case let (lhs as String.Index, rhs as String.Index):
|
|
guard lhs <= rhs else { throw Error.invalidRange(lhs, rhs) }
|
|
return box.store(lhs ... rhs)
|
|
case let (lhs, rhs):
|
|
throw Error.typeMismatch(symbol, [lhs, rhs])
|
|
}
|
|
}
|
|
case .postfix("..."):
|
|
return { args in
|
|
switch box.load(args[0]) {
|
|
case let index as NSNumber:
|
|
return box.store(Int(truncating: index)...)
|
|
case let index as String.Index:
|
|
return box.store(index...)
|
|
case let index:
|
|
throw Error.typeMismatch(symbol, [index])
|
|
}
|
|
}
|
|
case .prefix("..."):
|
|
return { args in
|
|
switch box.load(args[0]) {
|
|
case let index as NSNumber:
|
|
return box.store(...Int(truncating: index))
|
|
case let index as String.Index:
|
|
return box.store(...index)
|
|
case let index:
|
|
throw Error.typeMismatch(symbol, [index])
|
|
}
|
|
}
|
|
case .infix("..<"):
|
|
return { args in
|
|
switch (box.load(args[0]), box.load(args[1])) {
|
|
case let (lhs as NSNumber, rhs as NSNumber):
|
|
let lhs = Int(truncating: lhs), rhs = Int(truncating: rhs)
|
|
guard lhs < rhs else { throw Error.invalidRange(lhs, rhs) }
|
|
return box.store(lhs ..< rhs)
|
|
case let (lhs as String.Index, rhs as String.Index):
|
|
guard lhs < rhs else { throw Error.invalidRange(lhs, rhs) }
|
|
return box.store(lhs ..< rhs)
|
|
case let (lhs, rhs):
|
|
throw Error.typeMismatch(symbol, [lhs, rhs])
|
|
}
|
|
}
|
|
case .prefix("..<"):
|
|
return { args in
|
|
switch box.load(args[0]) {
|
|
case let index as NSNumber:
|
|
return box.store(..<Int(truncating: index))
|
|
case let index as String.Index:
|
|
return box.store(..<index)
|
|
case let index:
|
|
throw Error.typeMismatch(symbol, [index])
|
|
}
|
|
}
|
|
case .function("[]", _):
|
|
return { box.store($0.map(box.load)) }
|
|
case let .variable(name):
|
|
guard let string = unwrapString(name) else {
|
|
return { _ in throw Error.undefinedSymbol(symbol) }
|
|
}
|
|
let stringRef = box.store(string)
|
|
return { _ in stringRef }
|
|
case let .array(name):
|
|
guard let string = unwrapString(name) else {
|
|
return { _ in throw Error.undefinedSymbol(symbol) }
|
|
}
|
|
let fn = arrayEvaluator(for: symbol, string)
|
|
return { try box.store(fn([box.load($0[0])])) }
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build Expression
|
|
var _pureSymbols = [Symbol: Expression.SymbolEvaluator]()
|
|
let expression = Expression(
|
|
expression,
|
|
impureSymbols: { symbol in
|
|
if let fn = impureSymbols(symbol) {
|
|
return { try box.store(fn($0.map(box.load))) }
|
|
} else if let fn = pureSymbols(symbol) {
|
|
switch symbol {
|
|
case .variable,
|
|
.function(_, arity: 0):
|
|
do {
|
|
let value = try box.store(fn([]))
|
|
_pureSymbols[symbol] = { _ in value }
|
|
} catch {
|
|
return { _ in throw error }
|
|
}
|
|
default:
|
|
_pureSymbols[symbol] = { try box.store(fn($0.map(box.load))) }
|
|
}
|
|
} else if case let .array(name) = symbol {
|
|
if let fn = impureSymbols(.variable(name)) {
|
|
return { args in
|
|
let fn = try arrayEvaluator(for: symbol, fn([]))
|
|
return try box.store(fn(args.map(box.load)))
|
|
}
|
|
} else if let fn = pureSymbols(.variable(name)) {
|
|
let evaluator: SymbolEvaluator
|
|
do {
|
|
evaluator = try arrayEvaluator(for: symbol, fn([]))
|
|
} catch {
|
|
return { _ in throw error }
|
|
}
|
|
// This is outside do/catch catch in order to fix codecov glitch
|
|
_pureSymbols[symbol] = { try box.store(evaluator($0.map(box.load))) }
|
|
}
|
|
} else if case .infix("()") = symbol {
|
|
// TODO: check for pure `.infix("()")` implementation, and use as
|
|
// fallback if the lhs isn't a SymbolEvaluator?
|
|
return { args in
|
|
switch box.load(args[0]) {
|
|
case let fn as SymbolEvaluator:
|
|
return try box.store(fn(args.dropFirst().map(box.load)))
|
|
case let fn as Expression.SymbolEvaluator:
|
|
return try fn(argsToDouble(Array(args.dropFirst()), for: symbol))
|
|
default:
|
|
throw Error.typeMismatch(symbol, args.map(box.load))
|
|
}
|
|
}
|
|
} else if case let .function(name, _) = symbol {
|
|
if let fn = defaultEvaluator(for: symbol) {
|
|
_pureSymbols[symbol] = fn
|
|
} else if let fn = impureSymbols(.variable(name)) {
|
|
return { args in
|
|
let value = try fn([])
|
|
if let fn = funcEvaluator(for: symbol, value) {
|
|
return try fn(args)
|
|
}
|
|
throw Error.typeMismatch(
|
|
.infix("()"), [value] + [args.map(box.load)]
|
|
)
|
|
}
|
|
} else if let fn = pureSymbols(.variable(name)) {
|
|
do {
|
|
if let fn = try funcEvaluator(for: symbol, fn([])) {
|
|
return fn
|
|
}
|
|
} catch {
|
|
return { _ in throw error }
|
|
}
|
|
}
|
|
}
|
|
if !shouldOptimize {
|
|
return _pureSymbols[symbol] ?? defaultEvaluator(for: symbol)
|
|
}
|
|
return nil
|
|
},
|
|
pureSymbols: { symbol in
|
|
guard let fn = _pureSymbols[symbol] ?? defaultEvaluator(for: symbol) else {
|
|
if case let .function(name, _) = symbol {
|
|
// TODO: check for pure `.infix("()")` implementation?
|
|
for i in 0 ... 10 {
|
|
let symbol = Symbol.function(name, arity: .exactly(i))
|
|
if impureSymbols(symbol) ?? pureSymbols(symbol) != nil {
|
|
return { _ in throw Error.arityMismatch(symbol) }
|
|
}
|
|
}
|
|
if let fn = pureSymbols(.variable(name)) {
|
|
return { args in
|
|
let value = try fn([])
|
|
throw Error.typeMismatch(.infix("()"), [value] + [args.map(box.load)])
|
|
}
|
|
}
|
|
}
|
|
return Expression.errorEvaluator(for: symbol)
|
|
}
|
|
return fn
|
|
}
|
|
)
|
|
|
|
// These are constant values that won't change between evaluations
|
|
// and won't be re-stored, so must not be cleared
|
|
let literals = box.values
|
|
|
|
// Evaluation isn't thread-safe due to shared values
|
|
// so we use objc_sync_enter/exit to prevent re-entrancy
|
|
// Beware that these objc mutexes are not available on Linux
|
|
evaluator = {
|
|
#if !os(Linux)
|
|
objc_sync_enter(box)
|
|
#endif
|
|
defer {
|
|
box.values = literals
|
|
#if !os(Linux)
|
|
objc_sync_exit(box)
|
|
#endif
|
|
}
|
|
let value = try expression.evaluate()
|
|
return box.load(value)
|
|
}
|
|
self.expression = expression
|
|
}
|
|
|
|
/// Evaluate the expression
|
|
public func evaluate<T>() throws -> T {
|
|
let anyValue = try evaluator()
|
|
guard let value: T = AnyExpression.cast(anyValue) else {
|
|
switch T.self {
|
|
case _ where AnyExpression.isNil(anyValue):
|
|
break // Fall through
|
|
case is _String.Type,
|
|
is NSString?.Type,
|
|
is String?.Type,
|
|
is Substring?.Type:
|
|
// TODO: should we stringify any type like this?
|
|
return (AnyExpression.cast(AnyExpression.stringify(anyValue)) as T?)!
|
|
case is Bool.Type,
|
|
is Bool?.Type:
|
|
// TODO: should we boolify numeric types like this?
|
|
if let value = AnyExpression.cast(anyValue) as Double? {
|
|
return (value != 0) as! T
|
|
}
|
|
default:
|
|
// TODO: should we numberify Bool values like this?
|
|
if let boolValue = anyValue as? Bool,
|
|
let value: T = AnyExpression.cast(boolValue ? 1 : 0)
|
|
{
|
|
return value
|
|
}
|
|
}
|
|
throw Error.resultTypeMismatch(T.self, anyValue)
|
|
}
|
|
return value
|
|
}
|
|
|
|
/// All symbols used in the expression
|
|
public var symbols: Set<Symbol> {
|
|
return expression.symbols
|
|
}
|
|
|
|
/// Returns the optmized, pretty-printed expression if it was valid
|
|
/// Otherwise, returns the original (invalid) expression string
|
|
public var description: String {
|
|
return describer()
|
|
}
|
|
}
|
|
|
|
// MARK: Internal API
|
|
|
|
extension AnyExpression.Error {
|
|
/// Standard error message for mismatched argument types
|
|
static func typeMismatch(_ symbol: AnyExpression.Symbol, _ args: [Any]) -> AnyExpression.Error {
|
|
let types = args.map {
|
|
AnyExpression.stringify(AnyExpression.isNil($0) ? $0 : type(of: $0))
|
|
}
|
|
switch symbol {
|
|
case .infix("[]") where types.count == 2:
|
|
if AnyExpression.isSubscriptable(args[0]) {
|
|
return .message("Attempted to subscript \(types[0]) with incompatible index type \(types[1])")
|
|
} else {
|
|
return .message("Attempted to subscript \(types[0]) value")
|
|
}
|
|
case .array where types.count == 2:
|
|
if AnyExpression.isSubscriptable(args[0]) {
|
|
fallthrough
|
|
} else {
|
|
return .message("Attempted to subscript \(types[0]) value \(symbol.escapedName)")
|
|
}
|
|
case .array where !types.isEmpty:
|
|
return .message("Attempted to subscript \(symbol.escapedName) with incompatible index type \(types.last!)")
|
|
case .infix("()") where !types.isEmpty:
|
|
switch type(of: args[0]) {
|
|
case is Expression.SymbolEvaluator.Type,
|
|
is AnyExpression.SymbolEvaluator.Type:
|
|
return .message("Attempted to call function with incompatible arguments (\(types.dropFirst().joined(separator: ", ")))")
|
|
case _ where types[0].contains("->"):
|
|
return .message("Attempted to call non SymbolEvaluator function type \(types[0])")
|
|
default:
|
|
return .message("Attempted to call non function type \(types[0])")
|
|
}
|
|
case .infix("==") where types.count == 2 && types[0] == types[1]:
|
|
return .message("Arguments for \(symbol) must conform to the Hashable protocol")
|
|
case _ where types.count == 1:
|
|
return .message("Argument of type \(types[0]) is not compatible with \(symbol)")
|
|
default:
|
|
return .message("Arguments of type (\(types.joined(separator: ", "))) are not compatible with \(symbol)")
|
|
}
|
|
}
|
|
|
|
/// Standard error message for subscripting outside of a string's bounds
|
|
static func stringBounds(_ string: String, _ index: Int) -> AnyExpression.Error {
|
|
let escapedString = Expression.Symbol.variable("'\(string)'").escapedName
|
|
return .message("Character index \(index) out of bounds for string \(escapedString)")
|
|
}
|
|
|
|
static func stringBounds(_ string: Substring, _ index: String.Index) -> AnyExpression.Error {
|
|
var _string = string
|
|
while index > _string.endIndex {
|
|
// Double the length until it fits
|
|
// TODO: is there a better solution for this?
|
|
_string += _string
|
|
}
|
|
let offset = _string.distance(from: _string.startIndex, to: index)
|
|
return stringBounds(String(string), offset)
|
|
}
|
|
|
|
/// Standard error message for invalid range
|
|
static func invalidRange<T: Comparable>(_ lhs: T, _ rhs: T) -> AnyExpression.Error {
|
|
if lhs > rhs {
|
|
return .message("Cannot form range with lower bound > upper bound")
|
|
}
|
|
return .message("Cannot form half-open range with lower bound == upper bound")
|
|
}
|
|
|
|
/// Standard error message for mismatched return type
|
|
static func resultTypeMismatch(_ type: Any.Type, _ value: Any) -> AnyExpression.Error {
|
|
let valueType = AnyExpression.stringify(AnyExpression.unwrap(value).map { Swift.type(of: $0) } as Any)
|
|
return .message("Result type \(valueType) is not compatible with expected type \(AnyExpression.stringify(type))")
|
|
}
|
|
}
|
|
|
|
extension AnyExpression {
|
|
/// Cast a value to the specified type
|
|
static func cast<T>(_ anyValue: Any) -> T? {
|
|
if let value = anyValue as? T {
|
|
return value
|
|
}
|
|
var type: Any.Type = T.self
|
|
if let optionalType = type as? _Optional.Type {
|
|
type = optionalType.wrappedType
|
|
}
|
|
switch type {
|
|
case let numericType as _Numeric.Type:
|
|
if anyValue is Bool { return nil }
|
|
return (anyValue as? NSNumber).map { numericType.init(truncating: $0) as! T }
|
|
case let arrayType as _SwiftArray.Type:
|
|
return arrayType.cast(anyValue) as? T
|
|
case is String.Type:
|
|
return (anyValue as? _String).map { String($0.substring) as! T }
|
|
case is Substring.Type:
|
|
return (anyValue as? _String)?.substring as! T?
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
/// Convert any value to a printable string
|
|
static func stringify(_ value: Any) -> String {
|
|
switch value {
|
|
case let number as NSNumber:
|
|
// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
|
|
switch UnicodeScalar(UInt8(number.objCType.pointee)) {
|
|
case "c",
|
|
"B":
|
|
return number == 0 ? "false" : "true"
|
|
default:
|
|
break
|
|
}
|
|
if let int = Int64(exactly: number) {
|
|
return "\(int)"
|
|
}
|
|
if let uint = UInt64(exactly: number) {
|
|
return "\(uint)"
|
|
}
|
|
return "\(number)"
|
|
case let array as _Array:
|
|
return "[" + array.values.map(stringify).joined(separator: ", ") + "]"
|
|
case let dictionary as [AnyHashable: Any]:
|
|
return "[" + dictionary.enumerated().map {
|
|
stringify($0.element.key) + ": " + stringify($0.element.value)
|
|
}.joined(separator: ", ") + "]"
|
|
case let range as PartialRangeUpTo<Int>:
|
|
return "..<\(range.upperBound)"
|
|
case let range as PartialRangeThrough<Int>:
|
|
return "...\(range.upperBound)"
|
|
case let range as PartialRangeFrom<Int>:
|
|
return "\(range.lowerBound)..."
|
|
#if !swift(>=3.4) || (swift(>=4) && !swift(>=4.1.5))
|
|
case let range as CountablePartialRangeFrom<Int>:
|
|
return "\(range.lowerBound)..."
|
|
#endif
|
|
case is Any.Type:
|
|
return "\(value)"
|
|
case let value:
|
|
return unwrap(value).map { "\($0)" } ?? "nil"
|
|
}
|
|
}
|
|
|
|
/// Unwraps a potentially optional value
|
|
static func unwrap(_ value: Any) -> Any? {
|
|
switch value {
|
|
case let optional as _Optional:
|
|
guard let value = optional.value else {
|
|
fallthrough
|
|
}
|
|
return unwrap(value)
|
|
case is NSNull:
|
|
return nil
|
|
default:
|
|
return value
|
|
}
|
|
}
|
|
|
|
/// Test if a value is nil
|
|
static func isNil(_ value: Any) -> Bool {
|
|
if let optional = value as? _Optional {
|
|
guard let value = optional.value else {
|
|
return true
|
|
}
|
|
return isNil(value)
|
|
}
|
|
return value is NSNull
|
|
}
|
|
|
|
/// Test if a value supports subscripting
|
|
static func isSubscriptable(_ value: Any) -> Bool {
|
|
return value is _Array || value is _Dictionary || value is _String
|
|
}
|
|
}
|
|
|
|
// MARK: Private API
|
|
|
|
private extension AnyExpression {
|
|
/// Value storage
|
|
final class NanBox {
|
|
private static let mask = (-Double.nan).bitPattern
|
|
private static let indexOffset = 4
|
|
private static let nilBits = bitPattern(for: -1)
|
|
private static let falseBits = bitPattern(for: -2)
|
|
private static let trueBits = bitPattern(for: -3)
|
|
|
|
private static func bitPattern(for index: Int) -> UInt64 {
|
|
assert(index > -indexOffset)
|
|
return UInt64(index + indexOffset) | mask
|
|
}
|
|
|
|
/// Literal values
|
|
static let nilValue = Double(bitPattern: nilBits)
|
|
static let trueValue = Double(bitPattern: trueBits)
|
|
static let falseValue = Double(bitPattern: falseBits)
|
|
|
|
/// The values stored in the box
|
|
var values = [Any]()
|
|
|
|
/// Store a value in the box
|
|
func store(_ value: Any) -> Double {
|
|
switch value {
|
|
case let doubleValue as Double:
|
|
return doubleValue
|
|
case let boolValue as Bool:
|
|
return boolValue ? NanBox.trueValue : NanBox.falseValue
|
|
case let floatValue as Float:
|
|
return Double(floatValue)
|
|
case is Int,
|
|
is UInt,
|
|
is Int32,
|
|
is UInt32:
|
|
return Double(truncating: value as! NSNumber)
|
|
case let uintValue as UInt64:
|
|
if uintValue <= 9007199254740992 as UInt64 {
|
|
return Double(uintValue)
|
|
}
|
|
case let intValue as Int64:
|
|
if intValue <= 9007199254740992 as Int64, intValue >= -9223372036854775808 as Int64 {
|
|
return Double(intValue)
|
|
}
|
|
case let numberValue as NSNumber:
|
|
// Hack to avoid losing type info for UIFont.Weight, etc
|
|
if "\(value)".contains("rawValue") {
|
|
break
|
|
}
|
|
return Double(truncating: numberValue)
|
|
case _ where AnyExpression.isNil(value):
|
|
return NanBox.nilValue
|
|
default:
|
|
break
|
|
}
|
|
values.append(value)
|
|
return Double(bitPattern: NanBox.bitPattern(for: values.count - 1))
|
|
}
|
|
|
|
/// Retrieve a value from the box, if it exists
|
|
func loadIfStored(_ arg: Double) -> Any? {
|
|
switch arg.bitPattern {
|
|
case NanBox.nilBits:
|
|
return nil as Any? as Any
|
|
case NanBox.trueBits:
|
|
return true
|
|
case NanBox.falseBits:
|
|
return false
|
|
case let bits:
|
|
guard var index = Int(exactly: bits ^ NanBox.mask) else {
|
|
return nil
|
|
}
|
|
index -= NanBox.indexOffset
|
|
return values.indices.contains(index) ? values[index] : nil
|
|
}
|
|
}
|
|
|
|
/// Retrieve a value if it exists, else return the argument
|
|
func load(_ arg: Double) -> Any {
|
|
return loadIfStored(arg) ?? arg
|
|
}
|
|
}
|
|
|
|
/// Standard symbols
|
|
static let standardSymbols: [Symbol: Expression.SymbolEvaluator] = [
|
|
// Math symbols
|
|
.variable("pi"): { _ in .pi },
|
|
// Boolean symbols
|
|
.variable("true"): { _ in NanBox.trueValue },
|
|
.variable("false"): { _ in NanBox.falseValue },
|
|
// Optionals
|
|
.variable("nil"): { _ in NanBox.nilValue },
|
|
.infix("??"): { $0[0].bitPattern == NanBox.nilValue.bitPattern ? $0[1] : $0[0] },
|
|
]
|
|
|
|
/// Cast an array
|
|
static func arrayCast<T>(_ anyValue: Any) -> [T]? {
|
|
guard let array = (anyValue as? _Array).map({ $0.values }) else {
|
|
return nil
|
|
}
|
|
var value = [T]()
|
|
for element in array {
|
|
guard let element: T = cast(element) else {
|
|
return nil
|
|
}
|
|
value.append(element)
|
|
}
|
|
return value
|
|
}
|
|
}
|
|
|
|
/// Used for casting numeric values
|
|
private protocol _Numeric {
|
|
init(truncating: NSNumber)
|
|
}
|
|
|
|
extension Int: _Numeric {}
|
|
extension Int8: _Numeric {}
|
|
extension Int16: _Numeric {}
|
|
extension Int32: _Numeric {}
|
|
extension Int64: _Numeric {}
|
|
|
|
extension UInt: _Numeric {}
|
|
extension UInt8: _Numeric {}
|
|
extension UInt16: _Numeric {}
|
|
extension UInt32: _Numeric {}
|
|
extension UInt64: _Numeric {}
|
|
|
|
extension Double: _Numeric {}
|
|
extension Float: _Numeric {}
|
|
|
|
#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS)
|
|
import CoreGraphics
|
|
|
|
extension CGFloat: _Numeric {}
|
|
#endif
|
|
|
|
/// Used for subscripting
|
|
private protocol _Range {
|
|
func slice(of array: _Array, for symbol: Expression.Symbol) throws -> ArraySlice<Any>
|
|
func slice(of string: _String, for symbol: Expression.Symbol) throws -> Substring
|
|
}
|
|
|
|
extension ClosedRange: _Range {
|
|
fileprivate func slice(of array: _Array, for symbol: Expression.Symbol) throws -> ArraySlice<Any> {
|
|
guard let range = self as? ClosedRange<Int> else {
|
|
throw AnyExpression.Error.typeMismatch(symbol, [array, self])
|
|
}
|
|
let values = array.values
|
|
guard values.indices.contains(range.lowerBound) else {
|
|
throw AnyExpression.Error.arrayBounds(symbol, Double(range.lowerBound))
|
|
}
|
|
guard range.upperBound < values.count else {
|
|
throw AnyExpression.Error.arrayBounds(symbol, Double(range.upperBound))
|
|
}
|
|
return array.values[range]
|
|
}
|
|
|
|
fileprivate func slice(of string: _String, for symbol: Expression.Symbol) throws -> Substring {
|
|
let substring = string.substring
|
|
switch self {
|
|
case let range as ClosedRange<Int>:
|
|
guard range.lowerBound >= 0, range.lowerBound < substring.count else {
|
|
throw AnyExpression.Error.stringBounds(String(substring), range.lowerBound)
|
|
}
|
|
guard range.upperBound < substring.count else {
|
|
throw AnyExpression.Error.stringBounds(String(substring), range.upperBound)
|
|
}
|
|
let startIndex = substring.index(substring.startIndex, offsetBy: range.lowerBound)
|
|
let endIndex = substring.index(startIndex, offsetBy: range.count - 1)
|
|
return try (startIndex ... endIndex).slice(of: substring, for: symbol)
|
|
case let range:
|
|
let range = range as! ClosedRange<String.Index>
|
|
guard substring.indices.contains(range.lowerBound) else {
|
|
throw AnyExpression.Error.stringBounds(substring, range.lowerBound)
|
|
}
|
|
guard range.upperBound < substring.endIndex else {
|
|
throw AnyExpression.Error.stringBounds(substring, range.upperBound)
|
|
}
|
|
return substring[range]
|
|
}
|
|
}
|
|
}
|
|
|
|
#if !swift(>=3.4) || (swift(>=4) && !swift(>=4.1.5))
|
|
|
|
extension CountableClosedRange: _Range {
|
|
fileprivate func slice(of array: _Array, for symbol: Expression.Symbol) throws -> ArraySlice<Any> {
|
|
return try ClosedRange(self).slice(of: array, for: symbol)
|
|
}
|
|
|
|
fileprivate func slice(of string: _String, for symbol: Expression.Symbol) throws -> Substring {
|
|
return try ClosedRange(self).slice(of: string, for: symbol)
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
extension Range: _Range {
|
|
fileprivate func slice(of array: _Array, for symbol: Expression.Symbol) throws -> ArraySlice<Any> {
|
|
guard let range = self as? Range<Int> else {
|
|
throw AnyExpression.Error.typeMismatch(symbol, [array, self])
|
|
}
|
|
return try (range.lowerBound ... range.upperBound - 1).slice(of: array, for: symbol)
|
|
}
|
|
|
|
fileprivate func slice(of string: _String, for symbol: Expression.Symbol) throws -> Substring {
|
|
switch self {
|
|
case let range as Range<Int>:
|
|
return try (range.lowerBound ... range.upperBound - 1).slice(of: string, for: symbol)
|
|
case let range:
|
|
let range = range as! Range<String.Index>
|
|
let substring = string.substring
|
|
guard substring.indices.contains(range.lowerBound) else {
|
|
throw AnyExpression.Error.stringBounds(substring, range.lowerBound)
|
|
}
|
|
guard range.upperBound > substring.startIndex, range.upperBound <= substring.endIndex else {
|
|
throw AnyExpression.Error.stringBounds(substring, range.upperBound)
|
|
}
|
|
let endIndex = substring.index(before: range.upperBound)
|
|
return try (range.lowerBound ... endIndex).slice(of: substring, for: symbol)
|
|
}
|
|
}
|
|
}
|
|
|
|
#if !swift(>=3.4) || (swift(>=4) && !swift(>=4.1.5))
|
|
|
|
extension CountableRange: _Range {
|
|
fileprivate func slice(of array: _Array, for symbol: Expression.Symbol) throws -> ArraySlice<Any> {
|
|
return try Range(self).slice(of: array, for: symbol)
|
|
}
|
|
|
|
fileprivate func slice(of string: _String, for symbol: Expression.Symbol) throws -> Substring {
|
|
return try Range(self).slice(of: string, for: symbol)
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
extension PartialRangeThrough: _Range {
|
|
fileprivate func slice(of array: _Array, for symbol: Expression.Symbol) throws -> ArraySlice<Any> {
|
|
guard let range = self as? PartialRangeThrough<Int> else {
|
|
throw AnyExpression.Error.typeMismatch(symbol, [array, self])
|
|
}
|
|
let array = array.values
|
|
guard range.upperBound >= 0 else {
|
|
throw AnyExpression.Error.arrayBounds(symbol, Double(range.upperBound))
|
|
}
|
|
return try Range(0 ... range.upperBound).slice(of: array, for: symbol)
|
|
}
|
|
|
|
fileprivate func slice(of string: _String, for symbol: Expression.Symbol) throws -> Substring {
|
|
let substring = string.substring
|
|
switch self {
|
|
case let range as PartialRangeThrough<Int>:
|
|
guard range.upperBound >= 0 else {
|
|
throw AnyExpression.Error.stringBounds(String(substring), range.upperBound)
|
|
}
|
|
return try (0 ... range.upperBound).slice(of: string, for: symbol)
|
|
case let range:
|
|
let range = range as! PartialRangeThrough<String.Index>
|
|
guard range.upperBound >= substring.startIndex else {
|
|
throw AnyExpression.Error.stringBounds(substring, range.upperBound)
|
|
}
|
|
return try (substring.startIndex ... range.upperBound).slice(of: string, for: symbol)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension PartialRangeUpTo: _Range {
|
|
fileprivate func slice(of array: _Array, for symbol: Expression.Symbol) throws -> ArraySlice<Any> {
|
|
guard let partialRange = self as? PartialRangeUpTo<Int> else {
|
|
throw AnyExpression.Error.typeMismatch(symbol, [array, self])
|
|
}
|
|
let array = array.values
|
|
guard partialRange.upperBound > 0 else {
|
|
throw AnyExpression.Error.arrayBounds(symbol, Double(partialRange.upperBound))
|
|
}
|
|
let range: Range = 0 ..< partialRange.upperBound
|
|
return try range.slice(of: array, for: symbol)
|
|
}
|
|
|
|
fileprivate func slice(of string: _String, for symbol: Expression.Symbol) throws -> Substring {
|
|
let substring = string.substring
|
|
switch self {
|
|
case let range as PartialRangeUpTo<Int>:
|
|
guard range.upperBound > 0 else {
|
|
throw AnyExpression.Error.stringBounds(String(substring), range.upperBound)
|
|
}
|
|
return try (0 ..< range.upperBound).slice(of: string, for: symbol)
|
|
case let range:
|
|
let range = range as! PartialRangeUpTo<String.Index>
|
|
guard range.upperBound > substring.startIndex else {
|
|
throw AnyExpression.Error.stringBounds(substring, range.upperBound)
|
|
}
|
|
return try (substring.startIndex ..< range.upperBound).slice(of: string, for: symbol)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension PartialRangeFrom: _Range {
|
|
fileprivate func slice(of array: _Array, for symbol: Expression.Symbol) throws -> ArraySlice<Any> {
|
|
guard let partialRange = self as? PartialRangeFrom<Int> else {
|
|
throw AnyExpression.Error.typeMismatch(symbol, [array, self])
|
|
}
|
|
let array = array.values
|
|
guard partialRange.lowerBound < array.count else {
|
|
throw AnyExpression.Error.arrayBounds(symbol, Double(partialRange.lowerBound))
|
|
}
|
|
let range = partialRange.lowerBound ..< array.endIndex
|
|
return try range.slice(of: array, for: symbol)
|
|
}
|
|
|
|
fileprivate func slice(of string: _String, for symbol: Expression.Symbol) throws -> Substring {
|
|
let substring = string.substring
|
|
switch self {
|
|
case let range as PartialRangeFrom<Int>:
|
|
guard range.lowerBound < substring.count else {
|
|
throw AnyExpression.Error.stringBounds(String(substring), range.lowerBound)
|
|
}
|
|
return try (range.lowerBound ..< substring.count).slice(of: string, for: symbol)
|
|
case let range:
|
|
let range = range as! PartialRangeFrom<String.Index>
|
|
guard range.lowerBound < substring.endIndex else {
|
|
throw AnyExpression.Error.stringBounds(substring, range.lowerBound)
|
|
}
|
|
return try (range.lowerBound ..< substring.endIndex).slice(of: string, for: symbol)
|
|
}
|
|
}
|
|
}
|
|
|
|
#if !swift(>=3.4) || (swift(>=4) && !swift(>=4.1.5))
|
|
|
|
extension CountablePartialRangeFrom: _Range {
|
|
fileprivate func slice(of array: _Array, for symbol: Expression.Symbol) throws -> ArraySlice<Any> {
|
|
return try PartialRangeFrom(lowerBound).slice(of: array, for: symbol)
|
|
}
|
|
|
|
fileprivate func slice(of string: _String, for symbol: Expression.Symbol) throws -> Substring {
|
|
return try PartialRangeFrom(lowerBound).slice(of: string, for: symbol)
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/// Used for string values
|
|
private protocol _String {
|
|
var substring: Substring { get }
|
|
}
|
|
|
|
extension String: _String {
|
|
var substring: Substring {
|
|
return Substring(self)
|
|
}
|
|
}
|
|
|
|
extension Substring: _String {
|
|
var substring: Substring {
|
|
return self
|
|
}
|
|
}
|
|
|
|
extension NSString: _String {
|
|
var substring: Substring {
|
|
return Substring(self as String)
|
|
}
|
|
}
|
|
|
|
/// Used for array values
|
|
private protocol _Array {
|
|
var values: [Any] { get }
|
|
}
|
|
|
|
private protocol _SwiftArray: _Array {
|
|
static func cast(_ value: Any) -> Any?
|
|
}
|
|
|
|
extension Array: _SwiftArray {
|
|
fileprivate var values: [Any] {
|
|
return self
|
|
}
|
|
|
|
fileprivate static func cast(_ value: Any) -> Any? {
|
|
return AnyExpression.arrayCast(value) as [Element]?
|
|
}
|
|
}
|
|
|
|
extension ArraySlice: _SwiftArray {
|
|
fileprivate var values: [Any] {
|
|
return Array(self)
|
|
}
|
|
|
|
static func cast(_ value: Any) -> Any? {
|
|
return (AnyExpression.arrayCast(value) as [Element]?).map(self.init)
|
|
}
|
|
}
|
|
|
|
extension NSArray: _Array {
|
|
fileprivate var values: [Any] {
|
|
return Array(self)
|
|
}
|
|
}
|
|
|
|
/// Used for dictionary values
|
|
private protocol _Dictionary {
|
|
func value(for key: Any) -> Any?
|
|
}
|
|
|
|
extension Dictionary: _Dictionary {
|
|
fileprivate func value(for key: Any) -> Any? {
|
|
guard let key = AnyExpression.cast(key) as Key? else {
|
|
return nil // Type mismatch
|
|
}
|
|
return self[key] as Any
|
|
}
|
|
}
|
|
|
|
extension NSDictionary: _Dictionary {
|
|
fileprivate func value(for key: Any) -> Any? {
|
|
return self[key] as Any
|
|
}
|
|
}
|
|
|
|
/// Used to test if a value is Optional
|
|
private protocol _Optional {
|
|
var value: Any? { get }
|
|
static var wrappedType: Any.Type { get }
|
|
}
|
|
|
|
extension Optional: _Optional {
|
|
fileprivate var value: Any? {
|
|
return self
|
|
}
|
|
|
|
fileprivate static var wrappedType: Any.Type {
|
|
return Wrapped.self
|
|
}
|
|
}
|
|
|
|
#if !swift(>=3.4) || (swift(>=4) && !swift(>=4.1.5))
|
|
|
|
extension ImplicitlyUnwrappedOptional: _Optional {
|
|
fileprivate var value: Any? {
|
|
return self
|
|
}
|
|
|
|
fileprivate static var wrappedType: Any.Type {
|
|
return Wrapped.self
|
|
}
|
|
}
|
|
|
|
#endif
|