Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.4
// swift-tools-version:5.6
import PackageDescription

let package = Package(
Expand Down
5 changes: 3 additions & 2 deletions Sources/LeafKit/Exports.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import NIOConcurrencyHelpers
/// Various helper identities for convenience
extension Character {

// MARK: - LeafToken specific identities (Internal)
static var tagIndicator: Character = .octothorpe
static let tagIndicator = NIOLockedValueBox(Character.octothorpe)

var isValidInTagName: Bool {
return self.isLowercaseLetter
|| self.isUppercaseLetter
Expand Down
2 changes: 1 addition & 1 deletion Sources/LeafKit/LeafAST.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import NIO

/// `LeafAST` represents a "compiled," grammatically valid Leaf template (which may or may not be fully resolvable or erroring)
public struct LeafAST: Hashable {
public struct LeafAST: Hashable, Sendable {
// MARK: - Public

public func hash(into hasher: inout Hasher) { hasher.combine(name) }
Expand Down
30 changes: 26 additions & 4 deletions Sources/LeafKit/LeafCache/DefaultLeafCache.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
import NIOConcurrencyHelpers
import NIO

public final class DefaultLeafCache: SynchronousLeafCache {
/// `@unchecked Sendable` because uses locks to guarantee Sendability.
public final class DefaultLeafCache: SynchronousLeafCache, @unchecked Sendable {
// MARK: - Public - `LeafCache` Protocol Conformance


var __isEnabled = true
/// Global setting for enabling or disabling the cache
public var isEnabled: Bool = true
public var _isEnabled: Bool {
get {
self.lock.withLock {
self.__isEnabled
}
}
set(newValue) {
self.lock.withLock {
self.__isEnabled = newValue
}
}
}
/// Global setting for enabling or disabling the cache
public var isEnabled: Bool {
get {
self._isEnabled
}
set(newValue) {
self._isEnabled = newValue
}
}
/// Current count of cached documents
Comment on lines 7 to 31
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why two layers of indirection?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i assume compiler will take care of that anyway... two levels to not use self.lock.withLock 2 more times, which is also less code.

public var count: Int { self.lock.withLock { cache.count } }

Expand Down Expand Up @@ -73,7 +95,7 @@ public final class DefaultLeafCache: SynchronousLeafCache {

// MARK: - Internal Only

internal let lock: Lock
internal let lock: NIOLock
internal var cache: [String: LeafAST]

/// Blocking file load behavior
Expand Down
3 changes: 2 additions & 1 deletion Sources/LeafKit/LeafCache/LeafCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import NIO
/// adherents where the cache store itself is not a bottleneck.
///
/// `LeafAST.name` is to be used in all cases as the key for retrieving cached documents.
public protocol LeafCache {
@preconcurrency
public protocol LeafCache: Sendable {
/// Global setting for enabling or disabling the cache
var isEnabled : Bool { get set }
/// Current count of cached documents
Expand Down
83 changes: 43 additions & 40 deletions Sources/LeafKit/LeafConfiguration.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import Foundation
import NIOConcurrencyHelpers

/// General configuration of Leaf
/// - Sets the default View directory where templates will be looked for
/// - Guards setting the global tagIndicator (default `#`).
public struct LeafConfiguration {
public struct LeafConfiguration: Sendable {

/// Initialize Leaf with the default tagIndicator `#` and unfound imports throwing an exception
/// - Parameter rootDirectory: Default directory where templates will be found
Expand All @@ -23,9 +24,9 @@ public struct LeafConfiguration {
/// - Parameter tagIndicator: Unique tagIndicator - may only be set once.
/// - Parameter ignoreUnfoundImports: Ignore unfound imports - may only be set once.
public init(rootDirectory: String, tagIndicator: Character, ignoreUnfoundImports: Bool) {
if !Self.started {
Character.tagIndicator = tagIndicator
Self.started = true
if !Self.started.withLockedValue({ $0 }) {
Character.tagIndicator.withLockedValue { $0 = tagIndicator }
Self.started.withLockedValue { $0 = true }
}
self._rootDirectory = rootDirectory
self._ignoreUnfoundImports = ignoreUnfoundImports
Expand All @@ -42,53 +43,53 @@ public struct LeafConfiguration {
}

public static var encoding: String.Encoding {
get { _encoding }
set { if !Self.running { _encoding = newValue } }
get { _encoding.withLockedValue { $0 } }
set { if !Self.running { _encoding.withLockedValue { $0 = newValue } } }
}

public static var boolFormatter: (Bool) -> String {
get { _boolFormatter }
set { if !Self.running { _boolFormatter = newValue } }
get { _boolFormatter.withLockedValue { $0 } }
set { if !Self.running { _boolFormatter.withLockedValue { $0 = newValue } } }
}

public static var intFormatter: (Int) -> String {
get { _intFormatter }
set { if !Self.running { _intFormatter = newValue } }
get { _intFormatter.withLockedValue { $0 } }
set { if !Self.running { _intFormatter.withLockedValue { $0 = newValue } } }
}

public static var doubleFormatter: (Double) -> String {
get { _doubleFormatter }
set { if !Self.running { _doubleFormatter = newValue } }
get { _doubleFormatter.withLockedValue { $0 } }
set { if !Self.running { _doubleFormatter.withLockedValue { $0 = newValue } } }
}

public static var nilFormatter: () -> String {
get { _nilFormatter }
set { if !Self.running { _nilFormatter = newValue } }
get { _nilFormatter.withLockedValue { $0 } }
set { if !Self.running { _nilFormatter.withLockedValue { $0 = newValue } } }
}

public static var voidFormatter: () -> String {
get { _voidFormatter }
set { if !Self.running { _voidFormatter = newValue } }
get { _voidFormatter.withLockedValue { $0 } }
set { if !Self.running { _voidFormatter.withLockedValue { $0 = newValue } } }
}

public static var stringFormatter: (String) -> String {
get { _stringFormatter }
set { if !Self.running { _stringFormatter = newValue } }
get { _stringFormatter.withLockedValue { $0 } }
set { if !Self.running { _stringFormatter.withLockedValue { $0 = newValue } } }
}

public static var arrayFormatter: ([String]) -> String {
get { _arrayFormatter }
set { if !Self.running { _arrayFormatter = newValue } }
get { _arrayFormatter.withLockedValue { $0 } }
set { if !Self.running { _arrayFormatter.withLockedValue { $0 = newValue } } }
}

public static var dictFormatter: ([String: String]) -> String {
get { _dictFormatter }
set { if !Self.running { _dictFormatter = newValue } }
get { _dictFormatter.withLockedValue { $0 } }
set { if !Self.running { _dictFormatter.withLockedValue { $0 = newValue } } }
}

public static var dataFormatter: (Data) -> String? {
get { _dataFormatter }
set { if !Self.running { _dataFormatter = newValue } }
get { _dataFormatter.withLockedValue { $0 } }
set { if !Self.running { _dataFormatter.withLockedValue { $0 = newValue } } }
}

// MARK: - Internal/Private Only
Expand All @@ -99,27 +100,29 @@ public struct LeafConfiguration {
internal var _ignoreUnfoundImports: Bool {
willSet { assert(!accessed, "Changing property after LeafConfiguration has been read has no effect") }
}
internal static var _encoding: String.Encoding = .utf8
internal static var _boolFormatter: (Bool) -> String = { $0.description }
internal static var _intFormatter: (Int) -> String = { $0.description }
internal static var _doubleFormatter: (Double) -> String = { $0.description }
internal static var _nilFormatter: () -> String = { "" }
internal static var _voidFormatter: () -> String = { "" }
internal static var _stringFormatter: (String) -> String = { $0 }
internal static var _arrayFormatter: ([String]) -> String =

internal static let _encoding = NIOLockedValueBox<String.Encoding>(.utf8)
internal static let _boolFormatter = NIOLockedValueBox<(Bool) -> String>({ $0.description })
internal static let _intFormatter = NIOLockedValueBox<(Int) -> String>({ $0.description })
internal static let _doubleFormatter = NIOLockedValueBox<(Double) -> String>({ $0.description })
internal static let _nilFormatter = NIOLockedValueBox<(() -> String)>({ "" })
internal static let _voidFormatter = NIOLockedValueBox<(() -> String)>({ "" })
internal static let _stringFormatter = NIOLockedValueBox<((String) -> String)>({ $0 })
internal static let _arrayFormatter = NIOLockedValueBox<(([String]) -> String)>(
{ "[\($0.map {"\"\($0)\""}.joined(separator: ", "))]" }
internal static var _dictFormatter: ([String: String]) -> String =
)
internal static let _dictFormatter = NIOLockedValueBox<(([String: String]) -> String)>(
{ "[\($0.map { "\($0): \"\($1)\"" }.joined(separator: ", "))]" }
internal static var _dataFormatter: (Data) -> String? =
{ String(data: $0, encoding: Self._encoding) }

)
internal static let _dataFormatter = NIOLockedValueBox<((Data) -> String?)>(
{ String(data: $0, encoding: Self._encoding.withLockedValue { $0 }) }
)

/// Convenience flag for global write-once
private static var started = false
private static let started = NIOLockedValueBox(false)
private static var running: Bool {
assert(!Self.started, "LeafKit can only be configured prior to instantiating any LeafRenderer")
return Self.started
assert(!Self.started.withLockedValue { $0 }, "LeafKit can only be configured prior to instantiating any LeafRenderer")
return Self.started.withLockedValue { $0 }
}

/// Convenience flag for local lock-after-access
Expand Down
9 changes: 5 additions & 4 deletions Sources/LeafKit/LeafData/LeafData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ public struct LeafData: CustomStringConvertible,
ExpressibleByBooleanLiteral,
ExpressibleByArrayLiteral,
ExpressibleByFloatLiteral,
ExpressibleByNilLiteral {

ExpressibleByNilLiteral,
Sendable {

/// The concrete instantiable object types for a `LeafData`
public enum NaturalType: String, CaseIterable, Hashable {
public enum NaturalType: String, CaseIterable, Hashable, Sendable {
case bool
case string
case int
Expand Down Expand Up @@ -250,7 +251,7 @@ public struct LeafData: CustomStringConvertible,

/// Creates a new `LeafData` from `() -> LeafData` if possible or `nil` if not possible.
/// `returns` must specify a `NaturalType` that the function will return
internal static func lazy(_ lambda: @escaping () -> LeafData,
internal static func lazy(_ lambda: @Sendable @escaping () -> LeafData,
returns type: LeafData.NaturalType,
invariant sideEffects: Bool) throws -> LeafData {
LeafData(.lazy(f: lambda, returns: type, invariant: sideEffects))
Expand Down
5 changes: 3 additions & 2 deletions Sources/LeafKit/LeafData/LeafDataStorage.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import NIO
import Foundation

internal indirect enum LeafDataStorage: Equatable, CustomStringConvertible {
internal indirect enum LeafDataStorage: Equatable, CustomStringConvertible, Sendable {
// MARK: - Cases

// Static values
Expand All @@ -20,7 +20,8 @@ internal indirect enum LeafDataStorage: Equatable, CustomStringConvertible {

// Lazy resolvable function
// Must specify return tuple giving (returnType, invariance)
case lazy(f: () -> (LeafData),
@preconcurrency
case lazy(f: @Sendable () -> (LeafData),
returns: LeafData.NaturalType,
invariant: Bool)

Expand Down
4 changes: 2 additions & 2 deletions Sources/LeafKit/LeafError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
///
public struct LeafError: Error {
/// Possible cases of a LeafError.Reason, with applicable stored values where useful for the type
public enum Reason {
public enum Reason: Sendable {
// MARK: Errors related to loading raw templates
/// Attempted to access a template blocked for security reasons
case illegalAccess(String)
Expand Down Expand Up @@ -109,7 +109,7 @@ public struct LeafError: Error {
public struct LexerError: Error {
// MARK: - Public

public enum Reason {
public enum Reason: Sendable {
// MARK: Errors occuring during Lexing
/// A character not usable in parameters is present when Lexer is not expecting it
case invalidParameterToken(Character)
Expand Down
11 changes: 6 additions & 5 deletions Sources/LeafKit/LeafLexer/LeafLexer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ internal struct LeafLexer {
private mutating func nextToken() throws -> LeafToken? {
// if EOF, return nil - no more to read
guard let current = src.peek() else { return nil }
let isTagID = current == .tagIndicator
let isTagID = current == .tagIndicator.withLockedValue { $0 }
let isTagVal = current.isValidInTagName
let isCol = current == .colon
let next = src.peek(aheadBy: 1)
Expand Down Expand Up @@ -107,10 +107,11 @@ internal struct LeafLexer {
/// Consume all data until hitting an unescaped `tagIndicator` and return a `.raw` token
private mutating func lexRaw() -> LeafToken {
var slice = ""
while let current = src.peek(), current != .tagIndicator {
slice += src.readWhile { $0 != .tagIndicator && $0 != .backSlash }
let tagIndicator = Character.tagIndicator.withLockedValue({ $0 })
while let current = src.peek(), current != tagIndicator {
slice += src.readWhile { $0 != tagIndicator && $0 != .backSlash }
guard let newCurrent = src.peek(), newCurrent == .backSlash else { break }
if let next = src.peek(aheadBy: 1), next == .tagIndicator {
if let next = src.peek(aheadBy: 1), next == tagIndicator {
src.pop()
}
slice += src.pop()!.description
Expand All @@ -129,7 +130,7 @@ internal struct LeafLexer {
return .tagIndicator
} else {
state = .raw
return .raw(Character.tagIndicator.description)
return .raw((Character.tagIndicator.withLockedValue { $0 }).description)
}
}

Expand Down
8 changes: 4 additions & 4 deletions Sources/LeafKit/LeafLexer/LeafParameterTypes.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// MARK: - `Parameter` Token Type

/// An associated value enum holding data, objects or values usable as parameters to a `.tag`
public enum Parameter: Equatable, CustomStringConvertible {
public enum Parameter: Equatable, CustomStringConvertible, Sendable {
case stringLiteral(String)
case constant(Constant)
case variable(name: String)
Expand Down Expand Up @@ -44,7 +44,7 @@ public enum Parameter: Equatable, CustomStringConvertible {
/// `Keyword`s are identifiers which take precedence over syntax/variable names - may potentially have
/// representable state themselves as value when used with operators (eg, `true`, `false` when
/// used with logical operators, `nil` when used with equality operators, and so forth)
public enum LeafKeyword: String, Equatable {
public enum LeafKeyword: String, Equatable, Sendable {
// MARK: Public - Cases

// Eval -> Bool / Other
Expand Down Expand Up @@ -79,7 +79,7 @@ extension LeafKeyword {
// MARK: - Operator Symbols

/// Mathematical and Logical operators
public enum LeafOperator: String, Equatable, CustomStringConvertible, CaseIterable {
public enum LeafOperator: String, Equatable, CustomStringConvertible, CaseIterable, Sendable {
// MARK: Public - Cases

// Operator types: Logic Exist. UnPre Scope
Expand Down Expand Up @@ -157,7 +157,7 @@ public enum LeafOperator: String, Equatable, CustomStringConvertible, CaseIterab
}

/// An integer or double constant value parameter (eg `1_000`, `-42.0`)
public enum Constant: CustomStringConvertible, Equatable {
public enum Constant: CustomStringConvertible, Equatable, Sendable {
case int(Int)
case double(Double)

Expand Down
2 changes: 1 addition & 1 deletion Sources/LeafKit/LeafLexer/LeafToken.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
/// - `.whitespace`: Only generated when not at top-level, and unclear why maintaining it is useful
///

internal enum LeafToken: CustomStringConvertible, Equatable {
internal enum LeafToken: CustomStringConvertible, Equatable, Sendable {
/// Holds a variable-length string of data that will be passed through with no processing
case raw(String)

Expand Down
2 changes: 1 addition & 1 deletion Sources/LeafKit/LeafParser/LeafParameter.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import NIO

public indirect enum ParameterDeclaration: CustomStringConvertible {
public indirect enum ParameterDeclaration: CustomStringConvertible, Sendable {
case parameter(Parameter)
case expression([ParameterDeclaration])
case tag(Syntax.CustomTagDeclaration)
Expand Down
Loading