From 6256a89e38584c3580f5e00a1ef779bd6d9e358a Mon Sep 17 00:00:00 2001
From: Matthias Zenger <matthias@objecthub.net>
Date: Sat, 13 May 2023 00:19:52 +0200
Subject: [PATCH] Make some more protocols public. Refactor error data types.
 Provide better error descriptions.

---
 CLFormat.xcodeproj/project.pbxproj            |  18 ++-
 Sources/CLFormat/CLControlError.swift         |  66 +++++++++
 Sources/CLFormat/CLControlParser.swift        |  39 ------
 Sources/CLFormat/CLFormat.swift               |  12 +-
 Sources/CLFormat/CLFormatError.swift          | 126 ++++++++++++++++++
 .../CLFormat/StandardDirectiveSpecifier.swift |  70 ----------
 6 files changed, 212 insertions(+), 119 deletions(-)
 create mode 100644 Sources/CLFormat/CLControlError.swift
 create mode 100644 Sources/CLFormat/CLFormatError.swift

diff --git a/CLFormat.xcodeproj/project.pbxproj b/CLFormat.xcodeproj/project.pbxproj
index 849fa2b..7de90d0 100644
--- a/CLFormat.xcodeproj/project.pbxproj
+++ b/CLFormat.xcodeproj/project.pbxproj
@@ -9,6 +9,8 @@
 /* Begin PBXBuildFile section */
 		CC2FF5FB29D8C8F500AD0BC6 /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC2FF5FA29D8C8F500AD0BC6 /* Optional.swift */; };
 		CC4CDDE329E47B8300C5E9C6 /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = CC4CDDE229E47B8300C5E9C6 /* MarkdownKit */; };
+		CC7D4B722A0EE9BB0066BA94 /* CLControlError.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC7D4B712A0EE9BB0066BA94 /* CLControlError.swift */; };
+		CC7D4B742A0EEB010066BA94 /* CLFormatError.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC7D4B732A0EEB010066BA94 /* CLFormatError.swift */; };
 		CCB5F0F629C1455B00587140 /* CLFormat.docc in Sources */ = {isa = PBXBuildFile; fileRef = CCB5F0F529C1455B00587140 /* CLFormat.docc */; };
 		CCB5F0FC29C1455B00587140 /* CLFormat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CCB5F0F229C1455B00587140 /* CLFormat.framework */; };
 		CCB5F10129C1455B00587140 /* CLFormatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCB5F10029C1455B00587140 /* CLFormatTests.swift */; };
@@ -61,6 +63,8 @@
 		CC2FF5FA29D8C8F500AD0BC6 /* Optional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Optional.swift; sourceTree = "<group>"; };
 		CC7D4AF129E9E5980066BA94 /* DIRECTIVES.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = DIRECTIVES.md; sourceTree = "<group>"; };
 		CC7D4B6A2A0B73880066BA94 /* CLFormatTool.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CLFormatTool.entitlements; sourceTree = SOURCE_ROOT; };
+		CC7D4B712A0EE9BB0066BA94 /* CLControlError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLControlError.swift; sourceTree = "<group>"; };
+		CC7D4B732A0EEB010066BA94 /* CLFormatError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLFormatError.swift; sourceTree = "<group>"; };
 		CCB5F0D029C1148900587140 /* Currency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Currency.swift; sourceTree = "<group>"; };
 		CCB5F0D229C114D300587140 /* Number.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Number.swift; sourceTree = "<group>"; };
 		CCB5F0D429C114F600587140 /* NumberFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberFormat.swift; sourceTree = "<group>"; };
@@ -150,8 +154,10 @@
 				CCB5F0DA29C1156400587140 /* Modifiers.swift */,
 				CCB5F0DC29C1157C00587140 /* Arguments.swift */,
 				CCB5F0DE29C115A500587140 /* CLControl.swift */,
+				CC7D4B712A0EE9BB0066BA94 /* CLControlError.swift */,
 				CCB5F0E029C115C400587140 /* CLControlParser.swift */,
 				CCB5F12229C7974B00587140 /* CLFormatConfig.swift */,
+				CC7D4B732A0EEB010066BA94 /* CLFormatError.swift */,
 				CCB5F0E229C115E600587140 /* StandardDirectiveSpecifier.swift */,
 				CCB5F0E429C1160700587140 /* CLFormat.swift */,
 				CCB5F0F429C1455B00587140 /* CLFormat.h */,
@@ -317,11 +323,13 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				CC7D4B742A0EEB010066BA94 /* CLFormatError.swift in Sources */,
 				CCB5F11329C1475F00587140 /* Currency.swift in Sources */,
 				CCB5F11429C1475F00587140 /* Arguments.swift in Sources */,
 				CCB5F12329C7974B00587140 /* CLFormatConfig.swift in Sources */,
 				CC2FF5FB29D8C8F500AD0BC6 /* Optional.swift in Sources */,
 				CCB5F10C29C1475F00587140 /* Parameters.swift in Sources */,
+				CC7D4B722A0EE9BB0066BA94 /* CLControlError.swift in Sources */,
 				CCB5F11029C1475F00587140 /* StandardDirectiveSpecifier.swift in Sources */,
 				CCB5F0F629C1455B00587140 /* CLFormat.docc in Sources */,
 				CCB5F10E29C1475F00587140 /* CLControl.swift in Sources */,
@@ -417,7 +425,8 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				MACOSX_DEPLOYMENT_TARGET = 12.6;
+				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
+				MACOSX_DEPLOYMENT_TARGET = 11.0;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
 				ONLY_ACTIVE_ARCH = YES;
@@ -472,7 +481,8 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				MACOSX_DEPLOYMENT_TARGET = 12.6;
+				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
+				MACOSX_DEPLOYMENT_TARGET = 11.0;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				MTL_FAST_MATH = YES;
 				SDKROOT = macosx;
@@ -497,7 +507,7 @@
 				GENERATE_INFOPLIST_FILE = YES;
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
-				IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"@executable_path/Frameworks",
 					"@loader_path/Frameworks",
@@ -539,7 +549,7 @@
 				GENERATE_INFOPLIST_FILE = YES;
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
-				IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"@executable_path/Frameworks",
 					"@loader_path/Frameworks",
diff --git a/Sources/CLFormat/CLControlError.swift b/Sources/CLFormat/CLControlError.swift
new file mode 100644
index 0000000..3246543
--- /dev/null
+++ b/Sources/CLFormat/CLControlError.swift
@@ -0,0 +1,66 @@
+//
+//  CLControlError.swift
+//  CLFormat
+//
+//  Created by Matthias Zenger on 12/05/2023.
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+//  implied. See the License for the specific language governing
+//  permissions and limitations under the License.
+//
+
+import Foundation
+
+///
+/// Enumeration encapsulating all parsing errors for the built-in directive parsers.
+///
+public enum CLControlError: LocalizedError, CustomStringConvertible {
+  case prematureEndOfControl
+  case duplicateModifier(String)
+  case malformedParameter
+  case malformedNumericParameter(String)
+  case malformedDirectiveSyntax(String, String)
+  case malformedDirective(String)
+  case misplacedDirective(String)
+  case unsupportedDirective(String)
+  case unknownDirective(String)
+  
+  public var description: String {
+    switch self {
+      case .prematureEndOfControl:
+        return "premature end of control"
+      case .duplicateModifier(let mod):
+        return "duplicate modifier \(mod)"
+      case .malformedParameter:
+        return "malformed parameter"
+      case .malformedNumericParameter(let param):
+        return "malformed numeric parameter: \(param)"
+      case .malformedDirectiveSyntax(let name, let dirs):
+        return "malformed \(name) syntax \(dirs)"
+      case .malformedDirective(let dir):
+        return "malformed directive \(dir)"
+      case .misplacedDirective(let dir):
+        return "directive \(dir) in unsupported place"
+      case .unsupportedDirective(let dir):
+        return "unsupported directive \(dir)"
+      case .unknownDirective(let dir):
+        return "unknown directive \(dir)"
+    }
+  }
+  
+  public var errorDescription: String? {
+    return self.description
+  }
+  
+  public var failureReason: String? {
+    return "syntax error in control string"
+  }
+}
diff --git a/Sources/CLFormat/CLControlParser.swift b/Sources/CLFormat/CLControlParser.swift
index 1044eb3..840480a 100644
--- a/Sources/CLFormat/CLControlParser.swift
+++ b/Sources/CLFormat/CLControlParser.swift
@@ -195,42 +195,3 @@ public enum ParseResult {
     return .exit(Directive(parameters: parameters, modifiers: modifiers, specifier: spec))
   }
 }
-
-///
-/// Enumeration encapsulating all parsing errors for the built-in directive
-/// parsers.
-/// 
-public enum CLControlError: Error, CustomStringConvertible {
-  case prematureEndOfControl
-  case duplicateModifier(String)
-  case malformedParameter
-  case malformedNumericParameter(String)
-  case malformedDirectiveSyntax(String, String)
-  case malformedDirective(String)
-  case misplacedDirective(String)
-  case unsupportedDirective(String)
-  case unknownDirective(String)
-  
-  public var description: String {
-    switch self {
-      case .prematureEndOfControl:
-        return "premature end of control"
-      case .duplicateModifier(let mod):
-        return "duplicate modifier \(mod)"
-      case .malformedParameter:
-        return "malformed parameter"
-      case .malformedNumericParameter(let param):
-        return "malformed numeric parameter: \(param)"
-      case .malformedDirectiveSyntax(let name, let dirs):
-        return "malformed \(name) syntax \(dirs)"
-      case .malformedDirective(let dir):
-        return "malformed directive \(dir)"
-      case .misplacedDirective(let dir):
-        return "directive \(dir) in unsupported place"
-      case .unsupportedDirective(let dir):
-        return "unsupported directive \(dir)"
-      case .unknownDirective(let dir):
-        return "unknown directive \(dir)"
-    }
-  }
-}
diff --git a/Sources/CLFormat/CLFormat.swift b/Sources/CLFormat/CLFormat.swift
index 173ba37..1a99922 100644
--- a/Sources/CLFormat/CLFormat.swift
+++ b/Sources/CLFormat/CLFormat.swift
@@ -20,16 +20,16 @@
 
 import Foundation
 
-protocol CLFormatConvertible {
+public protocol CLFormatConvertible {
   var clformatDescription: String { get }
 }
 
-protocol DebugCLFormatConvertible {
+public protocol DebugCLFormatConvertible {
   var clformatDebugDescription: String { get }
 }
 
 extension Array: CLFormatConvertible where Element: CLFormatConvertible {
-  var clformatDescription: String {
+  public var clformatDescription: String {
     var res = "["
     var sep = ""
     for element in self {
@@ -41,7 +41,7 @@ extension Array: CLFormatConvertible where Element: CLFormatConvertible {
 }
 
 extension Array: DebugCLFormatConvertible where Element: DebugCLFormatConvertible {
-  var clformatDebugDescription: String {
+  public var clformatDebugDescription: String {
     var res = "["
     var sep = ""
     for element in self {
@@ -53,7 +53,7 @@ extension Array: DebugCLFormatConvertible where Element: DebugCLFormatConvertibl
 }
 
 extension Optional: CLFormatConvertible, DebugCLFormatConvertible {
-  var clformatDescription: String {
+  public var clformatDescription: String {
     switch self {
       case .none:
         return "nil"
@@ -68,7 +68,7 @@ extension Optional: CLFormatConvertible, DebugCLFormatConvertible {
     }
   }
   
-  var clformatDebugDescription: String {
+  public var clformatDebugDescription: String {
     switch self {
       case .none:
         return "nil"
diff --git a/Sources/CLFormat/CLFormatError.swift b/Sources/CLFormat/CLFormatError.swift
new file mode 100644
index 0000000..47e7f82
--- /dev/null
+++ b/Sources/CLFormat/CLFormatError.swift
@@ -0,0 +1,126 @@
+//
+//  CLFormatError.swift
+//  CLFormat
+//
+//  Created by Matthias Zenger on 12/05/2023.
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+//  implied. See the License for the specific language governing
+//  permissions and limitations under the License.
+//
+
+import Foundation
+
+///
+/// Enumeration encapsulating all formatting errors for the built-in directives.
+///
+public enum CLFormatError: LocalizedError, CustomStringConvertible {
+  case malformedDirective(String)
+  case unsupportedDirective(String)
+  case argumentOutOfRange(Int, Int)
+  case missingArgument
+  case expectedNumberArgument(Int, Any)
+  case expectedSequenceArgument(Int, Any)
+  case cannotUseArgumentAsParameter(Int, Any)
+  case missingNumberParameter(Int)
+  case expectedNumberParameter(Int, Any?)
+  case expectedPositiveNumberParameter(Int, Any?)
+  case missingCharacterParameter(Int)
+  case expectedCharacterParameter(Int, Any?)
+  case missingSequenceParameter(Int)
+  case expectedSequenceParameter(Int, Any?)
+  case cannotRepresentNumber(Number, Int)
+  
+  public var description: String {
+    switch self {
+      case .malformedDirective(let dir):
+        return "malformed directive \(dir) in control string"
+      case .unsupportedDirective(let dir):
+        return "unsupported directive \(dir) in control string"
+      case .argumentOutOfRange(let i, let n):
+        return "cannot access argument \(i); only \(n) arguments in total"
+      case .missingArgument:
+        return "missing argument"
+      case .expectedNumberArgument(let n, let arg):
+        return "expected argument \(n) to be a number; instead it is \(arg)"
+      case .expectedSequenceArgument(let n, let arg):
+        return "expected argument \(n) to be a sequence; instead it is \(arg)"
+      case .cannotUseArgumentAsParameter(let n, let arg):
+        return "cannot use argument \(n) as parameter: \(arg)"
+      case .missingNumberParameter(let n):
+        return "missing number parameter \(n)"
+      case .expectedNumberParameter(let n, let param):
+        if let param = param {
+          return "expected parameter \(n) to be a number, instead it is \(param)"
+        } else {
+          return "expected parameter \(n) to be a number, instead it is nil"
+        }
+      case .expectedPositiveNumberParameter(let n, let param):
+        if let param = param {
+          return "expected parameter \(n) to be a non-negative number, instead it is \(param)"
+        } else {
+          return "expected parameter \(n) to be a non-negative number, instead it is nil"
+        }
+      case .missingCharacterParameter(let n):
+        return "missing character parameter \(n)"
+      case .expectedCharacterParameter(let n, let param):
+        if let param = param {
+          return "expected parameter \(n) to be a character, instead it is \(param)"
+        } else {
+          return "expected parameter \(n) to be a character, instead it is nil"
+        }
+      case .missingSequenceParameter(let n):
+        return "missing sequence parameter \(n)"
+      case .expectedSequenceParameter(let n, let param):
+        if let param = param {
+          return "expected parameter \(n) to be a sequence, instead it is \(param)"
+        } else {
+          return "expected parameter \(n) to be a sequence, instead it is nil"
+        }
+      case .cannotRepresentNumber(let num, let radix):
+        return "cannot represent \(num) with radix \(radix)"
+    }
+  }
+  
+  public var errorDescription: String? {
+    return self.description
+  }
+  
+  public var failureReason: String? {
+    switch self {
+      case .malformedDirective(_):
+        return "illegal combination of directive parameters"
+      case .unsupportedDirective(_):
+        return "implementation limitation"
+      case .argumentOutOfRange(_, _):
+        return "illegal argument"
+      case .missingArgument:
+        return "too few arguments"
+      case .expectedNumberArgument(_, _):
+        return "argument is expected to be a number"
+      case .expectedSequenceArgument(_, _):
+        return "argument is expected to be a sequence"
+      case .cannotUseArgumentAsParameter(_, _):
+        return "illegal argument"
+      case .missingNumberParameter(_),
+           .missingCharacterParameter(_),
+           .missingSequenceParameter(_):
+        return "missing parameter"
+      case .expectedNumberParameter(_, _),
+           .expectedPositiveNumberParameter(_, _),
+           .expectedCharacterParameter(_, _),
+           .expectedSequenceParameter(_, _):
+        return "wrong parameter type"
+      case .cannotRepresentNumber(_, _):
+        return "illegal radix"
+    }
+  }
+}
diff --git a/Sources/CLFormat/StandardDirectiveSpecifier.swift b/Sources/CLFormat/StandardDirectiveSpecifier.swift
index 37299a3..5964cf1 100644
--- a/Sources/CLFormat/StandardDirectiveSpecifier.swift
+++ b/Sources/CLFormat/StandardDirectiveSpecifier.swift
@@ -828,73 +828,3 @@ public enum StandardDirectiveSpecifier: DirectiveSpecifier {
     }
   }
 }
-
-public enum CLFormatError: Error, CustomStringConvertible {
-  case malformedDirective(String)
-  case unsupportedDirective(String)
-  case argumentOutOfRange(Int, Int)
-  case missingArgument
-  case expectedNumberArgument(Int, Any)
-  case expectedSequenceArgument(Int, Any)
-  case cannotUseArgumentAsParameter(Int, Any)
-  case missingNumberParameter(Int)
-  case expectedNumberParameter(Int, Any?)
-  case expectedPositiveNumberParameter(Int, Any?)
-  case missingCharacterParameter(Int)
-  case expectedCharacterParameter(Int, Any?)
-  case missingSequenceParameter(Int)
-  case expectedSequenceParameter(Int, Any?)
-  case cannotRepresentNumber(Number, Int)
-  
-  public var description: String {
-    switch self {
-      case .malformedDirective(let dir):
-        return "malformed directive \(dir) in control string"
-      case .unsupportedDirective(let dir):
-        return "unsupported directive \(dir) in control string"
-      case .argumentOutOfRange(let i, let n):
-        return "cannot access argument \(i); only \(n) arguments in total"
-      case .missingArgument:
-        return "missing argument"
-      case .expectedNumberArgument(let n, let arg):
-        return "expected argument \(n) to be a number; instead it is \(arg)"
-      case .expectedSequenceArgument(let n, let arg):
-        return "expected argument \(n) to be a sequence; instead it is \(arg)"
-      case .cannotUseArgumentAsParameter(let n, let arg):
-        return "cannot use argument \(n) as parameter: \(arg)"
-      case .missingNumberParameter(let n):
-        return "missing number parameter \(n)"
-      case .expectedNumberParameter(let n, let param):
-        if let param = param {
-          return "expected parameter \(n) to be a number, instead it is \(param)"
-        } else {
-          return "expected parameter \(n) to be a number, instead it is nil"
-        }
-      case .expectedPositiveNumberParameter(let n, let param):
-        if let param = param {
-          return "expected parameter \(n) to be a non-negative number, instead it is \(param)"
-        } else {
-          return "expected parameter \(n) to be a non-negative number, instead it is nil"
-        }
-      case .missingCharacterParameter(let n):
-        return "missing character parameter \(n)"
-      case .expectedCharacterParameter(let n, let param):
-        if let param = param {
-          return "expected parameter \(n) to be a character, instead it is \(param)"
-        } else {
-          return "expected parameter \(n) to be a character, instead it is nil"
-        }
-      case .missingSequenceParameter(let n):
-        return "missing sequence parameter \(n)"
-      case .expectedSequenceParameter(let n, let param):
-        if let param = param {
-          return "expected parameter \(n) to be a sequence, instead it is \(param)"
-        } else {
-          return "expected parameter \(n) to be a sequence, instead it is nil"
-        }
-      case .cannotRepresentNumber(let num, let radix):
-        return "cannot represent \(num) with radix \(radix)"
-    }
-  }
-}
-