Skip to content

Commit

Permalink
Make interpretation of arguments customizable.
Browse files Browse the repository at this point in the history
  • Loading branch information
objecthub committed May 9, 2023
1 parent c97ef6f commit d9bc2be
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 74 deletions.
136 changes: 90 additions & 46 deletions Sources/CLFormat/Arguments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import Foundation
/// the control string. `Argument` objects cannot be reused. They can only be used
/// once to format a string.
///
public class Arguments: CustomStringConvertible {
open class Arguments: CustomStringConvertible {

/// The locale to be used (for locale-specific directives; typically enabled
/// via the `+` modifier.
Expand All @@ -53,11 +53,11 @@ public class Arguments: CustomStringConvertible {
private var firstArg: Int

/// Constructor.
public init(locale: Locale? = nil,
tabsize: Int = 8,
linewidth: Int = 72,
args: [Any?],
numArgumentsLeft: Int? = nil) {
public required init(locale: Locale? = nil,
tabsize: Int = 8,
linewidth: Int = 80,
args: [Any?],
numArgumentsLeft: Int? = nil) {
self.locale = locale
self.tabsize = tabsize
self.linewidth = linewidth
Expand Down Expand Up @@ -105,7 +105,7 @@ public class Arguments: CustomStringConvertible {

/// Returns the next argument to consume; this is, in fact, a lookahead as
/// the next argument is not being consumed by `current`.
public func current() throws -> Any? {
open func current() throws -> Any? {
if self.index < self.args.count {
return self.args[self.index]
} else {
Expand All @@ -114,7 +114,7 @@ public class Arguments: CustomStringConvertible {
}

/// Returns and consumes the next argument.
public func next() throws -> Any? {
open func next() throws -> Any? {
if self.index < self.args.count {
let arg = self.args[self.index]
self.index += 1
Expand All @@ -128,7 +128,7 @@ public class Arguments: CustomStringConvertible {
/// exception if the next argument is not a number.
public func nextAsNumber() throws -> Number {
if let arg = try self.next() {
if let num = Number(arg) {
if let num = self.coerceToNumber(arg) {
return num
} else {
throw CLFormatError.expectedNumberArgument(self.index - 1, arg)
Expand All @@ -142,13 +142,7 @@ public class Arguments: CustomStringConvertible {
/// exception if the next argument cannot be exactly represented as an `Int`.
public func nextAsInt() throws -> Int {
if let arg = try self.next() {
if let num = arg as? Int {
return num
} else if let n = arg as? Int64, let num = Int(exactly: n) {
return num
} else if let n = arg as? UInt, let num = Int(exactly: n) {
return num
} else if let n = arg as? UInt64, let num = Int(exactly: n) {
if let num = self.coerceToInt(arg) {
return num
} else {
throw CLFormatError.expectedNumberArgument(self.index - 1, arg)
Expand All @@ -162,7 +156,7 @@ public class Arguments: CustomStringConvertible {
/// exception if the next argument is not a character.
public func nextAsCharacter() throws -> Character {
if let arg = try self.next() {
if let ch = arg as? Character {
if let ch = self.coerceToCharacter(arg) {
return ch
} else if let str = arg as? String, str.count == 1 {
return str.first!
Expand All @@ -178,12 +172,8 @@ public class Arguments: CustomStringConvertible {
/// exception if the next argument is not a string.
public func nextAsString() throws -> String {
if let arg = try self.next() {
if let str = arg as? String {
if let str = self.coerceToString(arg) {
return str
} else if let str = arg as? NSString {
return str as String
} else if let str = arg as? NSMutableString {
return str as String
} else {
throw CLFormatError.expectedNumberArgument(self.index - 1, arg)
}
Expand All @@ -195,19 +185,10 @@ public class Arguments: CustomStringConvertible {
/// Returns and consumes the next argument as an `Arguments` object. The function throws
/// an exception if the next argument is not a sequence (which is being converted into an
/// `Arguments` object).
public func nextAsArguments(maxArgs: Int = Int.max) throws -> Arguments {
public func nextAsArguments(maxArgs: Int = Int.max) throws -> Self {
if let arg = try self.next() {
if let seq = arg as? any Sequence {
var newargs = [Any?]()
var itercap = maxArgs
var iterator = seq.makeIterator() as (any IteratorProtocol)
while itercap > 0, let next = iterator.next() {
newargs.append(unwrapAny(next))
itercap -= 1
}
return Arguments(locale: self.locale,
tabsize: self.tabsize,
args: newargs)
if let arr = self.coerceToArray(arg, capAt: maxArgs) {
return Self(locale: self.locale, tabsize: self.tabsize, args: arr)
} else {
throw CLFormatError.expectedSequenceArgument(self.index - 1, arg)
}
Expand All @@ -220,18 +201,8 @@ public class Arguments: CustomStringConvertible {
/// exception if the next argument cannot be represented as a parameter.
public func nextAsParameter() throws -> Parameter {
if let arg = try self.next() {
if let num = arg as? Int {
return .number(num)
} else if let n = arg as? Int64, let num = Int(exactly: n) {
return .number(num)
} else if let n = arg as? UInt, let num = Int(exactly: n) {
return .number(num)
} else if let n = arg as? UInt64, let num = Int(exactly: n) {
return .number(num)
} else if let char = arg as? Character {
return .character(char)
} else if let str = arg as? String, str.count < 2 {
return str.count == 0 ? .none : .character(str.first!)
if let param = self.coerceToParameter(arg) {
return param
} else {
throw CLFormatError.cannotUseArgumentAsParameter(self.index - 1, arg)
}
Expand Down Expand Up @@ -263,4 +234,77 @@ public class Arguments: CustomStringConvertible {
}
return res + "]>"
}

/// Coerce object to a Number value, if possible.
open func coerceToNumber(_ obj: Any) -> Number? {
return Number(obj)
}

/// Coerce object to an integer, if possible.
open func coerceToInt(_ obj: Any) -> Int? {
if let num = obj as? Int {
return num
} else if let n = obj as? Int64, let num = Int(exactly: n) {
return num
} else if let n = obj as? UInt, let num = Int(exactly: n) {
return num
} else if let n = obj as? UInt64, let num = Int(exactly: n) {
return num
} else {
return nil
}
}

/// Coerce object to a character, if possible.
open func coerceToCharacter(_ obj: Any) -> Character? {
if let ch = obj as? Character {
return ch
} else if let str = obj as? String, str.count == 1 {
return str.first!
} else {
return nil
}
}

/// Coerce object to a string, if possible.
open func coerceToString(_ obj: Any) -> String? {
if let str = obj as? String {
return str
} else if let str = obj as? NSString {
return str as String
} else if let str = obj as? NSMutableString {
return str as String
} else {
return nil
}
}

/// Coerce object to an array, if possible.
open func coerceToArray(_ obj: Any, capAt: Int) -> [Any?]? {
if let seq = obj as? any Sequence {
var itercap = capAt
var newargs = [Any?]()
var iterator = seq.makeIterator() as (any IteratorProtocol)
while itercap > 0, let next = iterator.next() {
newargs.append(unwrapAny(next))
itercap -= 1
}
return newargs
} else {
return nil
}
}

/// Coerce object to a parameter, if possible.
open func coerceToParameter(_ obj: Any) -> Parameter? {
if let num = self.coerceToInt(obj) {
return .number(num)
} else if let char = self.coerceToCharacter(obj) {
return .character(char)
} else if let str = self.coerceToString(obj), str.count < 2 {
return str.count == 0 ? Parameter.none : Parameter.character(str.first!)
} else {
return nil
}
}
}
16 changes: 8 additions & 8 deletions Sources/CLFormat/CLControl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ public struct CLControl: CustomStringConvertible {
tabsize: Int = 4,
linewidth: Int = 80,
args: Any?...) throws -> String {
return try self.format(with: Arguments(locale: locale,
tabsize: tabsize,
linewidth: linewidth,
args: args),
return try self.format(with: self.config.makeArguments(locale: locale,
tabsize: tabsize,
linewidth: linewidth,
args: args),
in: .root(self.config)).string
}

Expand All @@ -91,10 +91,10 @@ public struct CLControl: CustomStringConvertible {
tabsize: Int = 4,
linewidth: Int = 80,
arguments: [Any?]) throws -> String {
return try self.format(with: Arguments(locale: locale,
tabsize: tabsize,
linewidth: linewidth,
args: arguments),
return try self.format(with: self.config.makeArguments(locale: locale,
tabsize: tabsize,
linewidth: linewidth,
args: arguments),
in: .root(self.config)).string
}

Expand Down
18 changes: 17 additions & 1 deletion Sources/CLFormat/CLControlParserConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,23 @@ import Foundation
/// logic.
///
public struct CLControlParserConfig {
private var directiveParsers: [Character : DirectiveParser] = [:]
private let makeArguments: (Locale?, Int, Int, [Any?], Int?) -> Arguments
private var directiveParsers: [Character : DirectiveParser]

public init(makeArguments: @escaping (Locale?, Int, Int, [Any?], Int?) -> Arguments =
Arguments.init) {
self.makeArguments = makeArguments
self.directiveParsers = [:]
}

/// Return a new arguments object.
public func makeArguments(locale: Locale? = nil,
tabsize: Int = 8,
linewidth: Int = 80,
args: [Any?],
numArgumentsLeft: Int? = nil) -> Arguments {
return self.makeArguments(locale, tabsize, linewidth, args, numArgumentsLeft)
}

/// Add a new directive parser to this control parser configuration for the given
/// array of characters.
Expand Down
34 changes: 15 additions & 19 deletions Sources/CLFormat/StandardDirectiveSpecifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -632,31 +632,27 @@ public enum StandardDirectiveSpecifier: DirectiveSpecifier {
force = false
itercap -= 1
let arg = try iterargs.next()
var subitercap = try parameters.number(1) ?? Int.max
if let seq = arg as? any Sequence {
var newargs = [Any?]()
var iterator = seq.makeIterator() as (any IteratorProtocol)
while subitercap > 0, let next = iterator.next() {
newargs.append(next)
subitercap -= 1
}
let formatted = try control.format(with: Arguments(locale: arguments.locale,
tabsize: arguments.tabsize,
args: newargs,
numArgumentsLeft: iterargs.left),
in: .frame(res, context))
let subitercap = try parameters.number(1) ?? Int.max
if let obj = arg, let arr = arguments.coerceToArray(obj, capAt: subitercap) {
let formatted = try control.format(with:
context.parserConfig.makeArguments(
locale: arguments.locale,
tabsize: arguments.tabsize,
args: arr,
numArgumentsLeft: iterargs.left), in: .frame(res, context))
res += formatted.string
if case .break = formatted {
break
}
} else if subitercap > 0 {
var newargs = [Any?]()
newargs.append(arg)
let formatted = try control.format(with: Arguments(locale: arguments.locale,
tabsize: arguments.tabsize,
args: newargs,
numArgumentsLeft: iterargs.left),
in: .frame(res, context))
let formatted = try control.format(with:
context.parserConfig.makeArguments(
locale: arguments.locale,
tabsize: arguments.tabsize,
args: newargs,
numArgumentsLeft: iterargs.left), in: .frame(res, context))
res += formatted.string
if case .break = formatted {
break
Expand Down Expand Up @@ -744,7 +740,7 @@ public enum StandardDirectiveSpecifier: DirectiveSpecifier {
}
case .indirection:
let control = try CLControl(string: try arguments.nextAsString(),
config: context.parserConfig)
config: context.parserConfig)
if modifiers.contains(.at) {
return .append(try control.format(with: arguments, in: context).string)
} else {
Expand Down

0 comments on commit d9bc2be

Please sign in to comment.