From 4d9f55d90d527f095cbbd33b6251bb57cdf9b76c Mon Sep 17 00:00:00 2001 From: Joe Newton <> Date: Mon, 2 Mar 2020 01:19:34 -0500 Subject: [PATCH] Added back support for Swift 4.0 and Swift 4.2 --- .gitignore | 1 + ATTRIBUTIONS | 219 +++++ Half.podspec | 2 +- Half.xcodeproj/project.pbxproj | 189 +++++ Package.swift | 2 +- Package@swift-4.0.swift | 20 + Package@swift-4.2.swift | 20 + Sources/CHalf/src/half.c | 30 +- Sources/Half/Half.swift | 67 +- Sources/Half/Half.swift.gyb | 783 ++++++++++++++++++ Sources/Half/Half@swift-4.0.swift | 661 +++++++++++++++ Sources/Half/Half@swift-4.2.swift | 677 ++++++++++++++++ Tests/HalfTests/HalfTests.swift | 4 + Utils/gyb | 3 + Utils/gyb.py | 1260 +++++++++++++++++++++++++++++ 15 files changed, 3906 insertions(+), 32 deletions(-) create mode 100644 Package@swift-4.0.swift create mode 100644 Package@swift-4.2.swift create mode 100644 Sources/Half/Half.swift.gyb create mode 100644 Sources/Half/Half@swift-4.0.swift create mode 100644 Sources/Half/Half@swift-4.2.swift create mode 100755 Utils/gyb create mode 100644 Utils/gyb.py diff --git a/.gitignore b/.gitignore index e64c7e5..3f97df6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ Half.xcodeproj/project.xcworkspace Half.xcodeproj/xcuserdata .swiftpm .build +gyb.pyc diff --git a/ATTRIBUTIONS b/ATTRIBUTIONS index a217ce7..52036b3 100644 --- a/ATTRIBUTIONS +++ b/ATTRIBUTIONS @@ -531,3 +531,222 @@ 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. + + + +gyb and gyb.py are copied from the Swift project. + +https://github.com/apple/swift +-------------------------------------------------- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + + +## Runtime Library Exception to the Apache 2.0 License: ## + + + As an exception, if you use this Software to compile your source code and + portions of this Software are embedded into the binary product as a result, + you may redistribute such product without providing attribution as would + otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. diff --git a/Half.podspec b/Half.podspec index 9c4ddec..c427c73 100644 --- a/Half.podspec +++ b/Half.podspec @@ -19,7 +19,7 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/SomeRandomiOSDev/Half.git", :tag => s.version.to_s } s.source_files = 'Sources/**/*.{swift,h,c}' s.frameworks = 'Foundation' - s.swift_versions = ['5.0'] + s.swift_versions = ['4.0', '4.2', '5.0'] s.cocoapods_version = '>= 1.7.3' end diff --git a/Half.xcodeproj/project.pbxproj b/Half.xcodeproj/project.pbxproj index 9a355b4..cf6883f 100644 --- a/Half.xcodeproj/project.pbxproj +++ b/Half.xcodeproj/project.pbxproj @@ -7,6 +7,17 @@ objects = { /* Begin PBXAggregateTarget section */ + DDB1DF77240B7F4700C20FED /* Generate Sources */ = { + isa = PBXAggregateTarget; + buildConfigurationList = DDB1DF78240B7F4700C20FED /* Build configuration list for PBXAggregateTarget "Generate Sources" */; + buildPhases = ( + DDB1DF7B240B7F5000C20FED /* Generate Sources */, + ); + dependencies = ( + ); + name = "Generate Sources"; + productName = "Generate Sources"; + }; DDFEECA423F1BA0B0096015C /* Run SwiftLint */ = { isa = PBXAggregateTarget; buildConfigurationList = DDFEECA523F1BA0B0096015C /* Build configuration list for PBXAggregateTarget "Run SwiftLint" */; @@ -25,6 +36,14 @@ DD94FBF923F3C5BB0041D4EC /* half.h in Headers */ = {isa = PBXBuildFile; fileRef = DDFEEC8423F0B6FF0096015C /* half.h */; settings = {ATTRIBUTES = (Public, ); }; }; DD94FBFA23F3C5BC0041D4EC /* half.h in Headers */ = {isa = PBXBuildFile; fileRef = DDFEEC8423F0B6FF0096015C /* half.h */; settings = {ATTRIBUTES = (Public, ); }; }; DD94FBFB23F3C5BD0041D4EC /* half.h in Headers */ = {isa = PBXBuildFile; fileRef = DDFEEC8423F0B6FF0096015C /* half.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DDB1DF9A240CCA5A00C20FED /* Half@swift-4.2.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB1DF98240CCA5900C20FED /* Half@swift-4.2.swift */; }; + DDB1DF9B240CCA5A00C20FED /* Half@swift-4.2.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB1DF98240CCA5900C20FED /* Half@swift-4.2.swift */; }; + DDB1DF9C240CCA5A00C20FED /* Half@swift-4.2.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB1DF98240CCA5900C20FED /* Half@swift-4.2.swift */; }; + DDB1DF9D240CCA5A00C20FED /* Half@swift-4.2.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB1DF98240CCA5900C20FED /* Half@swift-4.2.swift */; }; + DDB1DF9E240CCA5A00C20FED /* Half@swift-4.0.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB1DF99240CCA5900C20FED /* Half@swift-4.0.swift */; }; + DDB1DF9F240CCA5A00C20FED /* Half@swift-4.0.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB1DF99240CCA5900C20FED /* Half@swift-4.0.swift */; }; + DDB1DFA0240CCA5A00C20FED /* Half@swift-4.0.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB1DF99240CCA5900C20FED /* Half@swift-4.0.swift */; }; + DDB1DFA1240CCA5A00C20FED /* Half@swift-4.0.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB1DF99240CCA5900C20FED /* Half@swift-4.0.swift */; }; DDB8120323F587890079FEB5 /* CHalfTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB8120223F587890079FEB5 /* CHalfTests.swift */; }; DDB8120423F587890079FEB5 /* CHalfTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB8120223F587890079FEB5 /* CHalfTests.swift */; }; DDB8120523F587890079FEB5 /* CHalfTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB8120223F587890079FEB5 /* CHalfTests.swift */; }; @@ -59,6 +78,55 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + DDB1DF7C240B7FA000C20FED /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DDFEEC2A23EF13900096015C /* Project object */; + proxyType = 1; + remoteGlobalIDString = DDB1DF77240B7F4700C20FED; + remoteInfo = "Generate Sources"; + }; + DDB1DF7E240B7FA400C20FED /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DDFEEC2A23EF13900096015C /* Project object */; + proxyType = 1; + remoteGlobalIDString = DDB1DF77240B7F4700C20FED; + remoteInfo = "Generate Sources"; + }; + DDB1DF80240B7FB100C20FED /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DDFEEC2A23EF13900096015C /* Project object */; + proxyType = 1; + remoteGlobalIDString = DDFEECA423F1BA0B0096015C; + remoteInfo = "Run SwiftLint"; + }; + DDB1DF82240B7FB900C20FED /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DDFEEC2A23EF13900096015C /* Project object */; + proxyType = 1; + remoteGlobalIDString = DDB1DF77240B7F4700C20FED; + remoteInfo = "Generate Sources"; + }; + DDB1DF84240B7FB900C20FED /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DDFEEC2A23EF13900096015C /* Project object */; + proxyType = 1; + remoteGlobalIDString = DDFEECA423F1BA0B0096015C; + remoteInfo = "Run SwiftLint"; + }; + DDB1DF86240B7FBF00C20FED /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DDFEEC2A23EF13900096015C /* Project object */; + proxyType = 1; + remoteGlobalIDString = DDB1DF77240B7F4700C20FED; + remoteInfo = "Generate Sources"; + }; + DDB1DF88240B7FBF00C20FED /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DDFEEC2A23EF13900096015C /* Project object */; + proxyType = 1; + remoteGlobalIDString = DDFEECA423F1BA0B0096015C; + remoteInfo = "Run SwiftLint"; + }; DDFEEC3E23EF13910096015C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DDFEEC2A23EF13900096015C /* Project object */; @@ -93,6 +161,13 @@ DD6F08D124008A7400749359 /* codecov.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = codecov.yml; sourceTree = ""; }; DDB1DF3F240A25D900C20FED /* fp_extend.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = fp_extend.cpp; sourceTree = ""; }; DDB1DF40240A25D900C20FED /* fp_trunc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = fp_trunc.cpp; sourceTree = ""; }; + DDB1DF76240B7EFD00C20FED /* Half.swift.gyb */ = {isa = PBXFileReference; explicitFileType = text.script.python; path = Half.swift.gyb; sourceTree = ""; }; + DDB1DF94240CC4DC00C20FED /* gyb.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = gyb.py; sourceTree = ""; }; + DDB1DF95240CC4DC00C20FED /* gyb */ = {isa = PBXFileReference; lastKnownFileType = text; path = gyb; sourceTree = ""; }; + DDB1DF96240CC5A200C20FED /* Package@swift-4.2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Package@swift-4.2.swift"; sourceTree = ""; }; + DDB1DF97240CC5A200C20FED /* Package@swift-4.0.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Package@swift-4.0.swift"; sourceTree = ""; }; + DDB1DF98240CCA5900C20FED /* Half@swift-4.2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Half@swift-4.2.swift"; sourceTree = ""; }; + DDB1DF99240CCA5900C20FED /* Half@swift-4.0.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Half@swift-4.0.swift"; sourceTree = ""; }; DDB8120223F587890079FEB5 /* CHalfTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CHalfTests.swift; sourceTree = ""; }; DDFEEC3323EF13900096015C /* Half.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Half.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DDFEEC3723EF13900096015C /* Half-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Half-Info.plist"; path = "Plists/Half-Info.plist"; sourceTree = ""; }; @@ -194,6 +269,15 @@ path = CHalfTests; sourceTree = ""; }; + DDB1DF93240CC4DC00C20FED /* Utils */ = { + isa = PBXGroup; + children = ( + DDB1DF94240CC4DC00C20FED /* gyb.py */, + DDB1DF95240CC4DC00C20FED /* gyb */, + ); + path = Utils; + sourceTree = ""; + }; DDFEEC2923EF13900096015C = { isa = PBXGroup; children = ( @@ -222,6 +306,7 @@ DDFEEC8E23F0B9F90096015C /* Sources */, DDFEECAA23F1BA280096015C /* Supporting Files */, DD94FBF723F3C50E0041D4EC /* Tests */, + DDB1DF93240CC4DC00C20FED /* Utils */, ); name = Half; sourceTree = ""; @@ -250,6 +335,8 @@ children = ( DDFEECAB23F1BA550096015C /* Half.podspec */, DDFEECAC23F1BA550096015C /* Package.swift */, + DDB1DF97240CC5A200C20FED /* Package@swift-4.0.swift */, + DDB1DF96240CC5A200C20FED /* Package@swift-4.2.swift */, DD6F08D124008A7400749359 /* codecov.yml */, DDFEECAE23F1BA5E0096015C /* .swiftlint.yml */, DDFEECAD23F1BA5E0096015C /* .travis.yml */, @@ -276,6 +363,9 @@ children = ( DDFEEDA923F3593B0096015C /* Functions.swift */, DDFEEDAA23F3593B0096015C /* Half.swift */, + DDB1DF98240CCA5900C20FED /* Half@swift-4.2.swift */, + DDB1DF99240CCA5900C20FED /* Half@swift-4.0.swift */, + DDB1DF76240B7EFD00C20FED /* Half.swift.gyb */, DDFEEDA823F3593B0096015C /* Half+Coding.swift */, ); path = Half; @@ -350,6 +440,7 @@ buildRules = ( ); dependencies = ( + DDB1DF7D240B7FA000C20FED /* PBXTargetDependency */, DDFEECB323F1BB5B0096015C /* PBXTargetDependency */, ); name = Half; @@ -387,6 +478,8 @@ buildRules = ( ); dependencies = ( + DDB1DF7F240B7FA400C20FED /* PBXTargetDependency */, + DDB1DF81240B7FB100C20FED /* PBXTargetDependency */, ); name = "Half macOS"; productName = "Half macOS"; @@ -423,6 +516,8 @@ buildRules = ( ); dependencies = ( + DDB1DF83240B7FB900C20FED /* PBXTargetDependency */, + DDB1DF85240B7FB900C20FED /* PBXTargetDependency */, ); name = "Half tvOS"; productName = "Half tvOS"; @@ -459,6 +554,8 @@ buildRules = ( ); dependencies = ( + DDB1DF87240B7FBF00C20FED /* PBXTargetDependency */, + DDB1DF89240B7FBF00C20FED /* PBXTargetDependency */, ); name = "Half watchOS"; productName = "Half watchOS"; @@ -475,6 +572,9 @@ LastUpgradeCheck = 1130; ORGANIZATIONNAME = SomeRandomiOSDev; TargetAttributes = { + DDB1DF77240B7F4700C20FED = { + CreatedOnToolsVersion = 11.3.1; + }; DDFEEC3223EF13900096015C = { CreatedOnToolsVersion = 11.3.1; LastSwiftMigration = 1130; @@ -526,6 +626,7 @@ DDFEECD923F2003E0096015C /* Half tvOS */, DDFEECE123F2003E0096015C /* Half tvOS Tests */, DDFEECF523F2004D0096015C /* Half watchOS */, + DDB1DF77240B7F4700C20FED /* Generate Sources */, DDFEECA423F1BA0B0096015C /* Run SwiftLint */, ); }; @@ -584,6 +685,26 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + DDB1DF7B240B7F5000C20FED /* Generate Sources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(SRCROOT)/Sources/Half/Half.swift.gyb", + ); + name = "Generate Sources"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(SRCROOT)/Sources/Half/Half.swift", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "./utils/gyb --line-directive '' -Dswift_version='5.0' -o ./Sources/Half/Half.swift ./Sources/Half/Half.swift.gyb\n./utils/gyb --line-directive '' -Dswift_version='4.2' -o ./Sources/Half/Half@swift-4.2.swift ./Sources/Half/Half.swift.gyb\n./utils/gyb --line-directive '' -Dswift_version='4.0' -o ./Sources/Half/Half@swift-4.0.swift ./Sources/Half/Half.swift.gyb\n"; + }; DDFEECA923F1BA160096015C /* Run SwiftLint */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -609,7 +730,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DDB1DF9A240CCA5A00C20FED /* Half@swift-4.2.swift in Sources */, DDFEEDB323F3593B0096015C /* Half.swift in Sources */, + DDB1DF9E240CCA5A00C20FED /* Half@swift-4.0.swift in Sources */, DDFEED8323F345690096015C /* half.c in Sources */, DDFEEDAB23F3593B0096015C /* Half+Coding.swift in Sources */, DDFEEDAF23F3593B0096015C /* Functions.swift in Sources */, @@ -631,7 +754,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DDB1DF9B240CCA5A00C20FED /* Half@swift-4.2.swift in Sources */, DDFEEDB423F3593B0096015C /* Half.swift in Sources */, + DDB1DF9F240CCA5A00C20FED /* Half@swift-4.0.swift in Sources */, DDFEED8423F345690096015C /* half.c in Sources */, DDFEEDAC23F3593B0096015C /* Half+Coding.swift in Sources */, DDFEEDB023F3593B0096015C /* Functions.swift in Sources */, @@ -653,7 +778,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DDB1DF9C240CCA5A00C20FED /* Half@swift-4.2.swift in Sources */, DDFEEDB523F3593B0096015C /* Half.swift in Sources */, + DDB1DFA0240CCA5A00C20FED /* Half@swift-4.0.swift in Sources */, DDFEED8523F345690096015C /* half.c in Sources */, DDFEEDAD23F3593B0096015C /* Half+Coding.swift in Sources */, DDFEEDB123F3593B0096015C /* Functions.swift in Sources */, @@ -675,7 +802,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DDB1DF9D240CCA5A00C20FED /* Half@swift-4.2.swift in Sources */, DDFEEDB623F3593B0096015C /* Half.swift in Sources */, + DDB1DFA1240CCA5A00C20FED /* Half@swift-4.0.swift in Sources */, DDFEED8623F345690096015C /* half.c in Sources */, DDFEEDAE23F3593B0096015C /* Half+Coding.swift in Sources */, DDFEEDB223F3593B0096015C /* Functions.swift in Sources */, @@ -685,6 +814,41 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + DDB1DF7D240B7FA000C20FED /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DDB1DF77240B7F4700C20FED /* Generate Sources */; + targetProxy = DDB1DF7C240B7FA000C20FED /* PBXContainerItemProxy */; + }; + DDB1DF7F240B7FA400C20FED /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DDB1DF77240B7F4700C20FED /* Generate Sources */; + targetProxy = DDB1DF7E240B7FA400C20FED /* PBXContainerItemProxy */; + }; + DDB1DF81240B7FB100C20FED /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DDFEECA423F1BA0B0096015C /* Run SwiftLint */; + targetProxy = DDB1DF80240B7FB100C20FED /* PBXContainerItemProxy */; + }; + DDB1DF83240B7FB900C20FED /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DDB1DF77240B7F4700C20FED /* Generate Sources */; + targetProxy = DDB1DF82240B7FB900C20FED /* PBXContainerItemProxy */; + }; + DDB1DF85240B7FB900C20FED /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DDFEECA423F1BA0B0096015C /* Run SwiftLint */; + targetProxy = DDB1DF84240B7FB900C20FED /* PBXContainerItemProxy */; + }; + DDB1DF87240B7FBF00C20FED /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DDB1DF77240B7F4700C20FED /* Generate Sources */; + targetProxy = DDB1DF86240B7FBF00C20FED /* PBXContainerItemProxy */; + }; + DDB1DF89240B7FBF00C20FED /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DDFEECA423F1BA0B0096015C /* Run SwiftLint */; + targetProxy = DDB1DF88240B7FBF00C20FED /* PBXContainerItemProxy */; + }; DDFEEC3F23EF13910096015C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DDFEEC3223EF13900096015C /* Half */; @@ -708,6 +872,22 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + DDB1DF79240B7F4700C20FED /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + DDB1DF7A240B7F4700C20FED /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; DDFEEC4523EF13910096015C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1170,6 +1350,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + DDB1DF78240B7F4700C20FED /* Build configuration list for PBXAggregateTarget "Generate Sources" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DDB1DF79240B7F4700C20FED /* Debug */, + DDB1DF7A240B7F4700C20FED /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; DDFEEC2D23EF13900096015C /* Build configuration list for PBXProject "Half" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Package.swift b/Package.swift index 918366d..ea2b83c 100644 --- a/Package.swift +++ b/Package.swift @@ -23,5 +23,5 @@ let package = Package( .testTarget(name: "HalfTests", dependencies: ["Half"]) ], - swiftLanguageVersions: [.version("5")] + swiftLanguageVersions: [.version("4"), .version("4.2"), .version("5")] ) diff --git a/Package@swift-4.0.swift b/Package@swift-4.0.swift new file mode 100644 index 0000000..1474535 --- /dev/null +++ b/Package@swift-4.0.swift @@ -0,0 +1,20 @@ +// swift-tools-version:4.0 +import PackageDescription + +let package = Package( + name: "Half", + + products: [ + .library(name: "Half", targets: ["Half", "CHalf"]) + ], + + targets: [ + .target(name: "CHalf"), + .testTarget(name: "CHalfTests", dependencies: ["CHalf"]), + + .target(name: "Half", dependencies: ["CHalf"]), + .testTarget(name: "HalfTests", dependencies: ["Half"]) + ], + + swiftLanguageVersions: [4] +) diff --git a/Package@swift-4.2.swift b/Package@swift-4.2.swift new file mode 100644 index 0000000..413fb2e --- /dev/null +++ b/Package@swift-4.2.swift @@ -0,0 +1,20 @@ +// swift-tools-version:4.2 +import PackageDescription + +let package = Package( + name: "Half", + + products: [ + .library(name: "Half", targets: ["Half", "CHalf"]) + ], + + targets: [ + .target(name: "CHalf"), + .testTarget(name: "CHalfTests", dependencies: ["CHalf"]), + + .target(name: "Half", dependencies: ["CHalf"]), + .testTarget(name: "HalfTests", dependencies: ["Half"]) + ], + + swiftLanguageVersions: [.v4, .v4_2] +) diff --git a/Sources/CHalf/src/half.c b/Sources/CHalf/src/half.c index 412bd5e..d1ffabd 100644 --- a/Sources/CHalf/src/half.c +++ b/Sources/CHalf/src/half.c @@ -13,6 +13,16 @@ #define HALF_FROM_FP16(x) (half_t){ ._fp = (x) } #define FP16_FROM_HALF(x) x._fp +// On Linux platforms the casting sometimes gets wacky, so we need to first promote +// the incoming value before storing it in the __fp16 type. +#if defined(__linux__) +# define PROMOTE_SIGNED(x) (long long)x +# define PROMOTE_UNSIGNED(x) (unsigned long long)x +#else +# define PROMOTE_SIGNED(x) x +# define PROMOTE_UNSIGNED(x) x +#endif + HALF_FUNC half_t _half_zero(void) { return HALF_FROM_FP16(0.0); } HALF_FUNC half_t _half_epsilon(void) { return HALF_FROM_FP16(0x1p-10); } HALF_FUNC half_t _half_pi(void) { return HALF_FROM_FP16(0x1.92p1); } @@ -23,16 +33,16 @@ HALF_FUNC half_t _half_from_raw(const uint16_t val) { return HALF_FROM_RAW(val); HALF_OFUNC half_t _half_from(const double val) { const __fp16 fpval = (__fp16)val; return HALF_FROM_FP16(fpval); } HALF_OFUNC half_t _half_from(const float val) { const __fp16 fpval = (__fp16)val; return HALF_FROM_FP16(fpval); } -HALF_OFUNC half_t _half_from(const long long val) { const __fp16 fpval = (__fp16)val; return HALF_FROM_FP16(fpval); } -HALF_OFUNC half_t _half_from(const long val) { const __fp16 fpval = (__fp16)val; return HALF_FROM_FP16(fpval); } -HALF_OFUNC half_t _half_from(const int val) { const __fp16 fpval = (__fp16)val; return HALF_FROM_FP16(fpval); } -HALF_OFUNC half_t _half_from(const short val) { const __fp16 fpval = (__fp16)val; return HALF_FROM_FP16(fpval); } -HALF_OFUNC half_t _half_from(const char val) { const __fp16 fpval = (__fp16)val; return HALF_FROM_FP16(fpval); } -HALF_OFUNC half_t _half_from(const unsigned long long val) { const __fp16 fpval = (__fp16)val; return HALF_FROM_FP16(fpval); } -HALF_OFUNC half_t _half_from(const unsigned long val) { const __fp16 fpval = (__fp16)val; return HALF_FROM_FP16(fpval); } -HALF_OFUNC half_t _half_from(const unsigned int val) { const __fp16 fpval = (__fp16)val; return HALF_FROM_FP16(fpval); } -HALF_OFUNC half_t _half_from(const unsigned short val) { const __fp16 fpval = (__fp16)val; return HALF_FROM_FP16(fpval); } -HALF_OFUNC half_t _half_from(const unsigned char val) { const __fp16 fpval = (__fp16)val; return HALF_FROM_FP16(fpval); } +HALF_OFUNC half_t _half_from(const long long val) { const __fp16 fpval = (__fp16)PROMOTE_SIGNED(val); return HALF_FROM_FP16(fpval); } +HALF_OFUNC half_t _half_from(const long val) { const __fp16 fpval = (__fp16)PROMOTE_SIGNED(val); return HALF_FROM_FP16(fpval); } +HALF_OFUNC half_t _half_from(const int val) { const __fp16 fpval = (__fp16)PROMOTE_SIGNED(val); return HALF_FROM_FP16(fpval); } +HALF_OFUNC half_t _half_from(const short val) { const __fp16 fpval = (__fp16)PROMOTE_SIGNED(val); return HALF_FROM_FP16(fpval); } +HALF_OFUNC half_t _half_from(const char val) { const __fp16 fpval = (__fp16)PROMOTE_SIGNED(val); return HALF_FROM_FP16(fpval); } +HALF_OFUNC half_t _half_from(const unsigned long long val) { const __fp16 fpval = (__fp16)PROMOTE_UNSIGNED(val); return HALF_FROM_FP16(fpval); } +HALF_OFUNC half_t _half_from(const unsigned long val) { const __fp16 fpval = (__fp16)PROMOTE_UNSIGNED(val); return HALF_FROM_FP16(fpval); } +HALF_OFUNC half_t _half_from(const unsigned int val) { const __fp16 fpval = (__fp16)PROMOTE_UNSIGNED(val); return HALF_FROM_FP16(fpval); } +HALF_OFUNC half_t _half_from(const unsigned short val) { const __fp16 fpval = (__fp16)PROMOTE_UNSIGNED(val); return HALF_FROM_FP16(fpval); } +HALF_OFUNC half_t _half_from(const unsigned char val) { const __fp16 fpval = (__fp16)PROMOTE_UNSIGNED(val); return HALF_FROM_FP16(fpval); } HALF_FUNC double _half_to_double(const half_t val) { return (double)FP16_FROM_HALF(val); } HALF_FUNC float _half_to_float(const half_t val) { return (float)FP16_FROM_HALF(val); } diff --git a/Sources/Half/Half.swift b/Sources/Half/Half.swift index 60af76e..f4d346d 100644 --- a/Sources/Half/Half.swift +++ b/Sources/Half/Half.swift @@ -10,12 +10,14 @@ import CHalf #endif import Foundation -#if canImport(CoreGraphics) +#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) import CoreGraphics.CGBase -#endif // #if canImport(CoreGraphics) +#endif // #if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) +#if swift(>=5.0) // MARK: - Half Definition +#if swift(>=5.1) @frozen public struct Half { // MARK: Public Properties @@ -34,6 +36,26 @@ import CoreGraphics.CGBase self._value = _value } } +#else +public struct Half { + + // MARK: Public Properties + + public var _value: half_t + + // MARK: Initialization + + @_transparent + public init() { + self._value = _half_zero() + } + + @_transparent + public init(_ _value: half_t) { + self._value = _value + } +} +#endif // MARK: - Half Extension @@ -97,22 +119,22 @@ extension Half { @inlinable @inline(__always) internal static var significandMask: UInt16 { - 1 &<< UInt16(significandBitCount) - 1 + return 1 &<< UInt16(significandBitCount) - 1 } @inlinable @inline(__always) internal static var infinityExponent: UInt { - 1 &<< UInt(exponentBitCount) - 1 + return 1 &<< UInt(exponentBitCount) - 1 } @inlinable @inline(__always) internal static var exponentBias: UInt { - infinityExponent &>> 1 + return infinityExponent &>> 1 } @inlinable @inline(__always) internal static var quietNaNMask: UInt16 { - 1 &<< UInt16(significandBitCount - 1) + return 1 &<< UInt16(significandBitCount - 1) } } @@ -201,17 +223,21 @@ extension Half: BinaryFloatingPoint { } #endif -#if canImport(CoreGraphics) +#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) // Not part of the protocol @inlinable @inline(__always) public init(_ other: CGFloat) { self.init(other.native) } -#endif // #if canImport(CoreGraphics) +#endif // #if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) @inlinable @inline(__always) public init(_ value: Source) where Source: BinaryFloatingPoint { - self.init(Float(value)) + if let half = value as? Half { + self.init(half._value) + } else { + self.init(Float(value)) + } } @inlinable @@ -349,7 +375,7 @@ extension Half: FloatingPoint { let provisional = Int(exponentBitPattern) - Int(Half.exponentBias) if isNormal { return provisional } - let shift = Half.significandBitCount - significandBitPattern._binaryLogarithm() + let shift = Half.significandBitCount - Int(significandBitPattern._binaryLogarithm()) return provisional + 1 - shift } @@ -366,37 +392,37 @@ extension Half: FloatingPoint { @inlinable @inline(__always) public var isFinite: Bool { - exponentBitPattern < Half.infinityExponent + return exponentBitPattern < Half.infinityExponent } @inlinable @inline(__always) public var isInfinite: Bool { - !isFinite && significandBitPattern == 0 + return !isFinite && significandBitPattern == 0 } @inlinable @inline(__always) public var isNaN: Bool { - !isFinite && significandBitPattern != 0 + return !isFinite && significandBitPattern != 0 } @inlinable @inline(__always) public var isNormal: Bool { - exponentBitPattern > 0 && isFinite + return exponentBitPattern > 0 && isFinite } @inlinable @inline(__always) public var isSignalingNaN: Bool { - isNaN && (significandBitPattern & Half.quietNaNMask) == 0 + return isNaN && (significandBitPattern & Half.quietNaNMask) == 0 } @inlinable @inline(__always) public var isSubnormal: Bool { - exponentBitPattern == 0 && significandBitPattern != 0 + return exponentBitPattern == 0 && significandBitPattern != 0 } @inlinable @inline(__always) public var isZero: Bool { - exponentBitPattern == 0 && significandBitPattern == 0 + return exponentBitPattern == 0 && significandBitPattern == 0 } @inlinable @@ -434,7 +460,7 @@ extension Half: FloatingPoint { } if isSubnormal { - let shift = Half.significandBitCount - significandBitPattern._binaryLogarithm() + let shift = Half.significandBitCount - Int(significandBitPattern._binaryLogarithm()) return Half(sign: .plus, exponentBitPattern: Half.exponentBias, significandBitPattern: significandBitPattern &<< shift) } @@ -604,7 +630,7 @@ extension Half: Numeric { @inlinable @inline(__always) public var magnitude: Half { - Half(_half_abs(_value)) + return Half(_half_abs(_value)) } @inlinable @inline(__always) @@ -666,7 +692,7 @@ extension Half: CustomReflectable { @_transparent public var customMirror: Mirror { - Mirror(reflecting: Float(self)) + return Mirror(reflecting: Float(self)) } } @@ -677,3 +703,4 @@ extension Half: CustomPlaygroundDisplayConvertible { return Float(self) } } +#endif // #if swift(>=5.0) diff --git a/Sources/Half/Half.swift.gyb b/Sources/Half/Half.swift.gyb new file mode 100644 index 0000000..4ef1c7e --- /dev/null +++ b/Sources/Half/Half.swift.gyb @@ -0,0 +1,783 @@ +// +// Half.swift.gyb +// Half +// +// Copyright © 2020 SomeRandomiOSDev. All rights reserved. +// + +#if SWIFT_PACKAGE +import CHalf +#endif +import Foundation + +#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) +import CoreGraphics.CGBase +#endif // #if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) + +% inlinable = '' if swift_version == '4.0' else '@inlinable\n ' +% if swift_version == '4.0': +% inline_always = '' +% elif swift_version == '4.2': +% inline_always = '@inlinable\n ' +% else: +% inline_always = '@inlinable @inline(__always)\n ' +% end +% if swift_version == '4.0': +#if swift(>=4.2) +#else // swift(<4.2) +% elif swift_version == '4.2': +#if swift(>=5.0) +#elseif swift(>=4.2) +% else: +#if swift(>=5.0) +% end +// MARK: - Half Definition + +% if swift_version == '5.0': +% for version in ['5.1', '5.0']: +% if version == '5.1': +#if swift(>=5.1) +@frozen public struct Half { +% else: +#else +public struct Half { +% end + + // MARK: Public Properties + + public var _value: half_t + + // MARK: Initialization + + @_transparent + public init() { + self._value = _half_zero() + } + + @_transparent + public init(_ _value: half_t) { + self._value = _value + } +} +% end +#endif +% else: +public struct Half { + + // MARK: Public Properties + + public var _value: half_t + + // MARK: Initialization + + @_transparent + public init() { + self._value = _half_zero() + } + + @_transparent + public init(_ _value: half_t) { + self._value = _value + } +} +% end + +// MARK: - Half Extension + +extension Half { + + ${inlinable}public var bitPattern: UInt16 { + return _half_to_raw(_value) + } + + ${inlinable}public init(bitPattern: UInt16) { + self._value = _half_from_raw(bitPattern) + } + + ${inlinable}public init(nan payload: UInt16, signaling: Bool) { + precondition(payload < (Half.quietNaNMask &>> 1), "NaN payload is not encodable.") + + var significand = payload + significand |= Half.quietNaNMask &>> (signaling ? 1 : 0) + + self.init(sign: .plus, exponentBitPattern: Half.infinityExponent, significandBitPattern: significand) + } +} + +// MARK: - CustomStringConvertible Protocol Conformance + +extension Half: CustomStringConvertible { + + public var description: String { + if isNaN { + return "nan" + } + + return _half_to_float(_value).description + } +} + +// MARK: - CustomStringConvertible Protocol Conformance + +extension Half: CustomDebugStringConvertible { + + public var debugDescription: String { + return _half_to_float(_value).description + } +} + +% if swift_version != '4.0' and swift_version != '4.2': +// MARK: - TextOutputStreamable Protocol Conformance + +extension Half: TextOutputStreamable { + + public func write(to target: inout Target) where Target: TextOutputStream { + _half_to_float(_value).write(to: &target) + } +} + +% end +// MARK: - Internal Constants + +extension Half { + + ${inline_always}internal static var significandMask: UInt16 { + return 1 &<< UInt16(significandBitCount) - 1 + } + + ${inline_always}internal static var infinityExponent: UInt { + return 1 &<< UInt(exponentBitCount) - 1 + } + + ${inline_always}internal static var exponentBias: UInt { + return infinityExponent &>> 1 + } + + ${inline_always}internal static var quietNaNMask: UInt16 { + return 1 &<< UInt16(significandBitCount - 1) + } +} + +// MARK: - BinaryFloatingPoint Protocol Conformance + +extension Half: BinaryFloatingPoint { + + ${inlinable}public static var exponentBitCount: Int { + return 5 + } + + ${inlinable}public static var significandBitCount: Int { + return 10 + } + + ${inlinable}public var exponentBitPattern: UInt { + return UInt(bitPattern &>> UInt16(Half.significandBitCount)) & Half.infinityExponent + } + + ${inlinable}public var significandBitPattern: UInt16 { + return bitPattern & Half.significandMask + } + + // + + ${inlinable}public init(sign: FloatingPointSign, exponentBitPattern: UInt, significandBitPattern: UInt16) { + let signBits: UInt16 = (sign == .minus ? 1 : 0) &<< (Half.exponentBitCount + Half.significandBitCount) + let exponentBits = UInt16((exponentBitPattern & Half.infinityExponent) &<< Half.significandBitCount) + let significandBits = significandBitPattern & Half.significandMask + + self.init(bitPattern: signBits | exponentBits | significandBits) + } + + ${inline_always}public init(_ other: Float) { + if other.isInfinite { + let infinity = Half.infinity + self = Half(sign: other.sign, exponentBitPattern: infinity.exponentBitPattern, significandBitPattern: infinity.significandBitPattern) + } else if other.isNaN { + if other.isSignalingNaN { + self = .signalingNaN + } else { + self = .nan + } + } else { + _value = _half_from(other) + } + } + + ${inline_always}public init(_ other: Double) { + if other.isInfinite { + let infinity = Half.infinity + self = Half(sign: other.sign, exponentBitPattern: infinity.exponentBitPattern, significandBitPattern: infinity.significandBitPattern) + } else if other.isNaN { + if other.isSignalingNaN { + self = .signalingNaN + } else { + self = .nan + } + } else { + _value = _half_from(other) + } + } + +#if !(os(Windows) || os(Android)) && (arch(i386) || arch(x86_64)) + ${inline_always}public init(_ other: Float80) { + if other.isInfinite { + let infinity = Half.infinity + self = Half(sign: other.sign, exponentBitPattern: infinity.exponentBitPattern, significandBitPattern: infinity.significandBitPattern) + } else if other.isNaN { + if other.isSignalingNaN { + self = .signalingNaN + } else { + self = .nan + } + } else { + _value = _half_from(Double(other)) + } + } +#endif + +#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) + // Not part of the protocol + ${inline_always}public init(_ other: CGFloat) { + self.init(other.native) + } +#endif // #if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) + + ${inline_always}public init(_ value: Source) where Source: BinaryFloatingPoint { + if let half = value as? Half { + self.init(half._value) + } else { +% if swift_version == '4.0': + let shift = (Source.significandBitCount - Half.significandBitCount) + let significandBitPattern = UInt16(shift < 0 ? (UInt(value.significandBitPattern) &<< shift) : (UInt(value.significandBitPattern) &>> shift)) + + let exponentBias = Int((1 &<< UInt(Source.exponentBitCount) - 1) &>> 1) + let exponentBitPattern = UInt(truncatingIfNeeded: Int(truncatingIfNeeded: value.exponentBitPattern) - Int(exponentBias) + Int(Half.exponentBias)) + + self.init(sign: value.sign, exponentBitPattern: exponentBitPattern, significandBitPattern: significandBitPattern) +% else: + self.init(Float(value)) +% end + } + } + + ${inlinable}public init?(exactly value: Source) where Source: BinaryFloatingPoint { + self.init(value) + + if isInfinite || value.isInfinite { + if value.isInfinite && (!isInfinite || sign != value.sign) { + // If source is infinite but this isn't or this is but with a different sign + return nil + } else if isInfinite && !value.isInfinite { + // If source isn't infinite but this is + return nil + } + } else if isNaN || value.isNaN { + if value.isNaN && (!isNaN || isSignalingNaN != value.isSignalingNaN) { + // If source is NaN but this isn't or this is but one is signaling while the other isn't + return nil + } else if isNaN && !value.isNaN { + // If source isn't NaN but this is + return nil + } +% if swift_version == '4.0': + } else { + guard self.sign == value.sign else { return nil } + + let shift = (Source.significandBitCount - Half.significandBitCount) + let significandBitPattern = Source.RawSignificand(shift < 0 ? (UInt(value.significandBitPattern) &>> shift) : (UInt(value.significandBitPattern) &<< shift)) + + guard significandBitPattern == value.significandBitPattern else { return nil } + + let exponentBias = Int((1 &<< UInt(Source.exponentBitCount) - 1) &>> 1) + let exponentBitPattern = UInt(truncatingIfNeeded: Int(truncatingIfNeeded: value.exponentBitPattern) - Int(Half.exponentBias) + Int(exponentBias)) + + guard exponentBitPattern == value.exponentBitPattern else { return nil } +% else: + } else if Source(self) != value { + // If casting half back to source isn't equal to original source + return nil +% end + } + } + + // + + ${inlinable}public var binade: Half { + guard isFinite else { return .nan } + + #if !arch(arm) + if isSubnormal { + let bitPattern = (self * 0x1p10).bitPattern & (-Half.infinity).bitPattern + return Half(bitPattern: bitPattern) * .ulpOfOne + } + #endif + + return Half(bitPattern: bitPattern & (-Half.infinity).bitPattern) + } + + ${inlinable}public var significandWidth: Int { + let trailingZeroBits = significandBitPattern.trailingZeroBitCount + if isNormal { + guard significandBitPattern != 0 else { return 0 } + return Half.significandBitCount &- trailingZeroBits + } + if isSubnormal { + let leadingZeroBits = significandBitPattern.leadingZeroBitCount + return UInt16.bitWidth &- (trailingZeroBits &+ leadingZeroBits &+ 1) + } + return -1 + } +} + +// MARK: - ExpressibleByFloatLiteral Protocol Conformance + +extension Half: ExpressibleByFloatLiteral { + + @_transparent + public init(floatLiteral value: Float) { + self.init(value) + } +} + +// MARK: - FloatingPoint Protocol Conformance + +extension Half: FloatingPoint { + + ${inlinable}public init(sign: FloatingPointSign, exponent: Int, significand: Half) { + var result = significand + if sign == .minus { result = -result } + + if significand.isFinite && !significand.isZero { + var clamped = exponent + let leastNormalExponent = 1 - Int(Half.exponentBias) + let greatestFiniteExponent = Int(Half.exponentBias) + + if clamped < leastNormalExponent { + clamped = max(clamped, 3 * leastNormalExponent) + + while clamped < leastNormalExponent { + result *= Half.leastNormalMagnitude + clamped -= leastNormalExponent + } + } else if clamped > greatestFiniteExponent { + let step = Half(sign: .plus, exponentBitPattern: Half.infinityExponent - 1, significandBitPattern: 0) + clamped = min(clamped, 3 * greatestFiniteExponent) + + while clamped > greatestFiniteExponent { + result *= step + clamped -= greatestFiniteExponent + } + } + + let scale = Half(sign: .plus, exponentBitPattern: UInt(Int(Half.exponentBias) + clamped), significandBitPattern: 0) + result *= scale + } + + self = result + } + + @_transparent + public init(_ value: Int) { + _value = _half_from(value) + } + + ${inline_always}public init(_ value: Source) { +% if swift_version == '4.0': + if Source.isSigned { + let asInt = Int(truncatingIfNeeded: value) + self.init(_half_from(asInt)) + } else { + let asUInt = UInt(truncatingIfNeeded: value) + self.init(_half_from(asUInt)) + } +% else: + if value.bitWidth <= MemoryLayout.size * 8 { + if Source.isSigned { + let asInt = Int(truncatingIfNeeded: value) + self.init(_half_from(asInt)) + } else { + let asUInt = UInt(truncatingIfNeeded: value) + self.init(_half_from(asUInt)) + } + } else { + self.init(Float(value)) + } +% end + } + + // + + ${inlinable}public var exponent: Int { + if !isFinite { return .max } + if isZero { return .min } + + let provisional = Int(exponentBitPattern) - Int(Half.exponentBias) + if isNormal { return provisional } + +% if swift_version == '4.0': + let shift = Half.significandBitCount - Int(log2(Double(significandBitPattern))) +% else: + let shift = Half.significandBitCount - Int(significandBitPattern._binaryLogarithm()) +% end + return provisional + 1 - shift + } + + ${inlinable}public var isCanonical: Bool { + #if arch(arm) + if exponentBitPattern == 0 && significandBitPattern != 0 { + return false + } + #endif + + return true + } + + ${inline_always}public var isFinite: Bool { + return exponentBitPattern < Half.infinityExponent + } + + ${inline_always}public var isInfinite: Bool { + return !isFinite && significandBitPattern == 0 + } + + ${inline_always}public var isNaN: Bool { + return !isFinite && significandBitPattern != 0 + } + + ${inline_always}public var isNormal: Bool { + return exponentBitPattern > 0 && isFinite + } + + ${inline_always}public var isSignalingNaN: Bool { + return isNaN && (significandBitPattern & Half.quietNaNMask) == 0 + } + + ${inline_always}public var isSubnormal: Bool { + return exponentBitPattern == 0 && significandBitPattern != 0 + } + + ${inline_always}public var isZero: Bool { + return exponentBitPattern == 0 && significandBitPattern == 0 + } + + ${inlinable}public var nextUp: Half { + let next = self + 0 + + #if arch(arm) + // On arm, treat subnormal values as zero. + if next == 0 { return .leastNonzeroMagnitude } + if next == -.leastNonzeroMagnitude { return -0.0 } + #endif + + if next < .infinity { + let increment = Int16(bitPattern: next.bitPattern) &>> 15 | 1 + let bitPattern = next.bitPattern &+ UInt16(bitPattern: increment) + return Half(bitPattern: bitPattern) + } + + return next + } + + ${inlinable}public var sign: FloatingPointSign { + let shift = Half.significandBitCount + Half.exponentBitCount + //swiftlint:disable force_unwrapping + return FloatingPointSign(rawValue: Int(bitPattern &>> UInt16(shift)))! + //swiftlint:enable force_unwrapping + } + + ${inlinable}public var significand: Half { + if isNaN { return self } + if isNormal { + return Half(sign: .plus, exponentBitPattern: Half.exponentBias, significandBitPattern: significandBitPattern) + } + + if isSubnormal { +% if swift_version == '4.0': + let shift = Half.significandBitCount - Int(log2(Double(significandBitPattern))) +% else: + let shift = Half.significandBitCount - Int(significandBitPattern._binaryLogarithm()) +% end + return Half(sign: .plus, exponentBitPattern: Half.exponentBias, significandBitPattern: significandBitPattern &<< shift) + } + + return Half(sign: .plus, exponentBitPattern: exponentBitPattern, significandBitPattern: 0) + } + + ${inlinable}public var ulp: Half { + guard isFinite else { return .nan } + if isNormal { + let bitPattern = self.bitPattern & Half.infinity.bitPattern + return Half(bitPattern: bitPattern) * .ulpOfOne + } + + return .leastNormalMagnitude * .ulpOfOne + } + + // + + ${inlinable}public static var greatestFiniteMagnitude: Half { + return Half(bitPattern: 0x7BFF) + } + + ${inlinable}public static var infinity: Half { + return Half(bitPattern: 0x7C00) + } + + ${inlinable}public static var leastNonzeroMagnitude: Half { + #if arch(arm) + return leastNormalMagnitude + #else + return Half(sign: .plus, exponentBitPattern: 0, significandBitPattern: 1) + #endif + } + + ${inlinable}public static var leastNormalMagnitude: Half { + return Half(sign: .plus, exponentBitPattern: 1, significandBitPattern: 0) + } + + ${inlinable}public static var nan: Half { + return Half(_half_nan()) + } + + ${inlinable}public static var pi: Half { + return Half(_half_pi()) + } + + ${inlinable}public static var signalingNaN: Half { + return Half(nan: 0, signaling: true) + } + + ${inlinable}public static var ulpOfOne: Half { + return Half(_half_epsilon()) + } + + // + + @_transparent + public mutating func addProduct(_ lhs: Half, _ rhs: Half) { + _value = _half_fma(_value, lhs._value, rhs._value) + } + + ${inline_always}public mutating func formRemainder(dividingBy other: Half) { + self = Half(Float(self).remainder(dividingBy: Float(other))) + } + + @_transparent + public mutating func formSquareRoot() { + _value = _half_sqrt(_value) + } + + ${inline_always}public mutating func formTruncatingRemainder(dividingBy other: Half) { + self = Half(Float(self).truncatingRemainder(dividingBy: Float(other))) + } + + @_transparent + public func isEqual(to other: Half) -> Bool { + return Bool(_half_equal(self._value, other._value)) + } + + @_transparent + public func isLess(than other: Half) -> Bool { + return Bool(_half_lt(self._value, other._value)) + } + + @_transparent + public func isLessThanOrEqualTo(_ other: Half) -> Bool { + return Bool(_half_lte(self._value, other._value)) + } + + @_transparent + public mutating func round(_ rule: FloatingPointRoundingRule) { + self = Half(Float(self).rounded(rule)) + } + + // + + @_transparent + public static func / (lhs: Half, rhs: Half) -> Half { + return Half(_half_div(lhs._value, rhs._value)) + } + + @_transparent + public static func /= (lhs: inout Half, rhs: Half) { + lhs._value = _half_div(lhs._value, rhs._value) + } +} + +// MARK: - Hashable Protocol Conformance + +extension Half: Hashable { + +% if swift_version == '4.0': + //swiftlint:disable legacy_hashing + ${inlinable}public var hashValue: Int { + return Int(bitPattern) + } + //swiftlint:enable legacy_hashing +% else: + ${inlinable}public func hash(into hasher: inout Hasher) { + var value = self + if isZero { + value = 0 // to reconcile -0.0 and +0.0 + } + + hasher.combine(value.bitPattern) + } +% end +} + +// MARK: - Strideable Protocol Conformance + +extension Half: Strideable { + + @_transparent + public func distance(to other: Half) -> Half { + return other - self + } + + @_transparent + public func advanced(by amount: Half) -> Half { + return self + amount + } +} + +// MARK: - SignedNumeric Protocol Conformance + +extension Half: SignedNumeric { + + @_transparent + public mutating func negate() { + _value = _half_neg(_value) + } + + @_transparent + public static prefix func - (value: Half) -> Half { + return Half(_half_neg(value._value)) + } +} + +// MARK: - Numeric Protocol Conformance + +extension Half: Numeric { + + ${inline_always}public var magnitude: Half { + return Half(_half_abs(_value)) + } + + ${inline_always}public init?(exactly value: Source) where Source: BinaryInteger { + self.init(value) + + if isInfinite || isNaN || Source(self) != value { + return nil + } + } + + @_transparent + public static func * (lhs: Half, rhs: Half) -> Half { + return Half(_half_mul(lhs._value, rhs._value)) + } + + @_transparent + public static func *= (lhs: inout Half, rhs: Half) { + lhs._value = _half_mul(lhs._value, rhs._value) + } +} + +// MARK: - ExpressibleByIntegerLiteral Protocol Conformance + +extension Half: ExpressibleByIntegerLiteral { + + @_transparent + public init(integerLiteral value: Int64) { + self = Half(value) + } +} + +% if swift_version == '4.0' or swift_version == '4.2': +// MARK: - Half Extension + +extension Half { +% else: +// MARK: - AdditiveArithmetic Protocol Conformance + +extension Half: AdditiveArithmetic { +% end + + @_transparent + public static func + (lhs: Half, rhs: Half) -> Half { + return Half(_half_add(lhs._value, rhs._value)) + } + + @_transparent + public static func += (lhs: inout Half, rhs: Half) { + lhs._value = _half_add(lhs._value, rhs._value) + } + + @_transparent + public static func - (lhs: Half, rhs: Half) -> Half { + return Half(_half_sub(lhs._value, rhs._value)) + } + + @_transparent + public static func -= (lhs: inout Half, rhs: Half) { + lhs._value = _half_sub(lhs._value, rhs._value) + } +} + +extension Half: CustomReflectable { + + @_transparent + public var customMirror: Mirror { + return Mirror(reflecting: Float(self)) + } +} + +% if swift_version != '4.0': +extension Half: CustomPlaygroundDisplayConvertible { + + @_transparent + public var playgroundDescription: Any { + return Float(self) + } +} +% end +% if swift_version == '4.0': +extension Float { + + ${inline_always}public init(_ other: Half) { + self = _half_to_float(other._value) + } +} + +extension Double { + + ${inline_always}public init(_ other: Half) { + self = _half_to_double(other._value) + } +} + +#if !(os(Windows) || os(Android)) && (arch(i386) || arch(x86_64)) +extension Float80 { + + ${inline_always}public init(_ other: Half) { + self = Float80(_half_to_double(other._value)) + } +} +#endif + +#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) +extension CGFloat { + + ${inline_always}public init(_ other: Half) { + self.init(NativeType(other)) + } +} +#endif // #if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) +% end +% if swift_version == '4.0': +#endif // #if swift(<4.2) +% elif swift_version == '4.2': +#endif // #if swift(>=4.2) && swift(<5.0) +% else: +#endif // #if swift(>=5.0) +% end diff --git a/Sources/Half/Half@swift-4.0.swift b/Sources/Half/Half@swift-4.0.swift new file mode 100644 index 0000000..2a4c89b --- /dev/null +++ b/Sources/Half/Half@swift-4.0.swift @@ -0,0 +1,661 @@ +// +// Half.swift +// Half +// +// Copyright © 2020 SomeRandomiOSDev. All rights reserved. +// + +#if SWIFT_PACKAGE +import CHalf +#endif +import Foundation + +#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) +import CoreGraphics.CGBase +#endif // #if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) + +#if swift(>=4.2) +#else // swift(<4.2) +// MARK: - Half Definition + +public struct Half { + + // MARK: Public Properties + + public var _value: half_t + + // MARK: Initialization + + @_transparent + public init() { + self._value = _half_zero() + } + + @_transparent + public init(_ _value: half_t) { + self._value = _value + } +} + +// MARK: - Half Extension + +extension Half { + + public var bitPattern: UInt16 { + return _half_to_raw(_value) + } + + public init(bitPattern: UInt16) { + self._value = _half_from_raw(bitPattern) + } + + public init(nan payload: UInt16, signaling: Bool) { + precondition(payload < (Half.quietNaNMask &>> 1), "NaN payload is not encodable.") + + var significand = payload + significand |= Half.quietNaNMask &>> (signaling ? 1 : 0) + + self.init(sign: .plus, exponentBitPattern: Half.infinityExponent, significandBitPattern: significand) + } +} + +// MARK: - CustomStringConvertible Protocol Conformance + +extension Half: CustomStringConvertible { + + public var description: String { + if isNaN { + return "nan" + } + + return _half_to_float(_value).description + } +} + +// MARK: - CustomStringConvertible Protocol Conformance + +extension Half: CustomDebugStringConvertible { + + public var debugDescription: String { + return _half_to_float(_value).description + } +} + +// MARK: - Internal Constants + +extension Half { + + internal static var significandMask: UInt16 { + return 1 &<< UInt16(significandBitCount) - 1 + } + + internal static var infinityExponent: UInt { + return 1 &<< UInt(exponentBitCount) - 1 + } + + internal static var exponentBias: UInt { + return infinityExponent &>> 1 + } + + internal static var quietNaNMask: UInt16 { + return 1 &<< UInt16(significandBitCount - 1) + } +} + +// MARK: - BinaryFloatingPoint Protocol Conformance + +extension Half: BinaryFloatingPoint { + + public static var exponentBitCount: Int { + return 5 + } + + public static var significandBitCount: Int { + return 10 + } + + public var exponentBitPattern: UInt { + return UInt(bitPattern &>> UInt16(Half.significandBitCount)) & Half.infinityExponent + } + + public var significandBitPattern: UInt16 { + return bitPattern & Half.significandMask + } + + // + + public init(sign: FloatingPointSign, exponentBitPattern: UInt, significandBitPattern: UInt16) { + let signBits: UInt16 = (sign == .minus ? 1 : 0) &<< (Half.exponentBitCount + Half.significandBitCount) + let exponentBits = UInt16((exponentBitPattern & Half.infinityExponent) &<< Half.significandBitCount) + let significandBits = significandBitPattern & Half.significandMask + + self.init(bitPattern: signBits | exponentBits | significandBits) + } + + public init(_ other: Float) { + if other.isInfinite { + let infinity = Half.infinity + self = Half(sign: other.sign, exponentBitPattern: infinity.exponentBitPattern, significandBitPattern: infinity.significandBitPattern) + } else if other.isNaN { + if other.isSignalingNaN { + self = .signalingNaN + } else { + self = .nan + } + } else { + _value = _half_from(other) + } + } + + public init(_ other: Double) { + if other.isInfinite { + let infinity = Half.infinity + self = Half(sign: other.sign, exponentBitPattern: infinity.exponentBitPattern, significandBitPattern: infinity.significandBitPattern) + } else if other.isNaN { + if other.isSignalingNaN { + self = .signalingNaN + } else { + self = .nan + } + } else { + _value = _half_from(other) + } + } + +#if !(os(Windows) || os(Android)) && (arch(i386) || arch(x86_64)) + public init(_ other: Float80) { + if other.isInfinite { + let infinity = Half.infinity + self = Half(sign: other.sign, exponentBitPattern: infinity.exponentBitPattern, significandBitPattern: infinity.significandBitPattern) + } else if other.isNaN { + if other.isSignalingNaN { + self = .signalingNaN + } else { + self = .nan + } + } else { + _value = _half_from(Double(other)) + } + } +#endif + +#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) + // Not part of the protocol + public init(_ other: CGFloat) { + self.init(other.native) + } +#endif // #if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) + + public init(_ value: Source) where Source: BinaryFloatingPoint { + if let half = value as? Half { + self.init(half._value) + } else { + let shift = (Source.significandBitCount - Half.significandBitCount) + let significandBitPattern = UInt16(shift < 0 ? (UInt(value.significandBitPattern) &<< shift) : (UInt(value.significandBitPattern) &>> shift)) + + let exponentBias = Int((1 &<< UInt(Source.exponentBitCount) - 1) &>> 1) + let exponentBitPattern = UInt(truncatingIfNeeded: Int(truncatingIfNeeded: value.exponentBitPattern) - Int(exponentBias) + Int(Half.exponentBias)) + + self.init(sign: value.sign, exponentBitPattern: exponentBitPattern, significandBitPattern: significandBitPattern) + } + } + + public init?(exactly value: Source) where Source: BinaryFloatingPoint { + self.init(value) + + if isInfinite || value.isInfinite { + if value.isInfinite && (!isInfinite || sign != value.sign) { + // If source is infinite but this isn't or this is but with a different sign + return nil + } else if isInfinite && !value.isInfinite { + // If source isn't infinite but this is + return nil + } + } else if isNaN || value.isNaN { + if value.isNaN && (!isNaN || isSignalingNaN != value.isSignalingNaN) { + // If source is NaN but this isn't or this is but one is signaling while the other isn't + return nil + } else if isNaN && !value.isNaN { + // If source isn't NaN but this is + return nil + } + } else { + guard self.sign == value.sign else { return nil } + + let shift = (Source.significandBitCount - Half.significandBitCount) + let significandBitPattern = Source.RawSignificand(shift < 0 ? (UInt(value.significandBitPattern) &>> shift) : (UInt(value.significandBitPattern) &<< shift)) + + guard significandBitPattern == value.significandBitPattern else { return nil } + + let exponentBias = Int((1 &<< UInt(Source.exponentBitCount) - 1) &>> 1) + let exponentBitPattern = UInt(truncatingIfNeeded: Int(truncatingIfNeeded: value.exponentBitPattern) - Int(Half.exponentBias) + Int(exponentBias)) + + guard exponentBitPattern == value.exponentBitPattern else { return nil } + } + } + + // + + public var binade: Half { + guard isFinite else { return .nan } + + #if !arch(arm) + if isSubnormal { + let bitPattern = (self * 0x1p10).bitPattern & (-Half.infinity).bitPattern + return Half(bitPattern: bitPattern) * .ulpOfOne + } + #endif + + return Half(bitPattern: bitPattern & (-Half.infinity).bitPattern) + } + + public var significandWidth: Int { + let trailingZeroBits = significandBitPattern.trailingZeroBitCount + if isNormal { + guard significandBitPattern != 0 else { return 0 } + return Half.significandBitCount &- trailingZeroBits + } + if isSubnormal { + let leadingZeroBits = significandBitPattern.leadingZeroBitCount + return UInt16.bitWidth &- (trailingZeroBits &+ leadingZeroBits &+ 1) + } + return -1 + } +} + +// MARK: - ExpressibleByFloatLiteral Protocol Conformance + +extension Half: ExpressibleByFloatLiteral { + + @_transparent + public init(floatLiteral value: Float) { + self.init(value) + } +} + +// MARK: - FloatingPoint Protocol Conformance + +extension Half: FloatingPoint { + + public init(sign: FloatingPointSign, exponent: Int, significand: Half) { + var result = significand + if sign == .minus { result = -result } + + if significand.isFinite && !significand.isZero { + var clamped = exponent + let leastNormalExponent = 1 - Int(Half.exponentBias) + let greatestFiniteExponent = Int(Half.exponentBias) + + if clamped < leastNormalExponent { + clamped = max(clamped, 3 * leastNormalExponent) + + while clamped < leastNormalExponent { + result *= Half.leastNormalMagnitude + clamped -= leastNormalExponent + } + } else if clamped > greatestFiniteExponent { + let step = Half(sign: .plus, exponentBitPattern: Half.infinityExponent - 1, significandBitPattern: 0) + clamped = min(clamped, 3 * greatestFiniteExponent) + + while clamped > greatestFiniteExponent { + result *= step + clamped -= greatestFiniteExponent + } + } + + let scale = Half(sign: .plus, exponentBitPattern: UInt(Int(Half.exponentBias) + clamped), significandBitPattern: 0) + result *= scale + } + + self = result + } + + @_transparent + public init(_ value: Int) { + _value = _half_from(value) + } + + public init(_ value: Source) { + if Source.isSigned { + let asInt = Int(truncatingIfNeeded: value) + self.init(_half_from(asInt)) + } else { + let asUInt = UInt(truncatingIfNeeded: value) + self.init(_half_from(asUInt)) + } + } + + // + + public var exponent: Int { + if !isFinite { return .max } + if isZero { return .min } + + let provisional = Int(exponentBitPattern) - Int(Half.exponentBias) + if isNormal { return provisional } + + let shift = Half.significandBitCount - Int(log2(Double(significandBitPattern))) + return provisional + 1 - shift + } + + public var isCanonical: Bool { + #if arch(arm) + if exponentBitPattern == 0 && significandBitPattern != 0 { + return false + } + #endif + + return true + } + + public var isFinite: Bool { + return exponentBitPattern < Half.infinityExponent + } + + public var isInfinite: Bool { + return !isFinite && significandBitPattern == 0 + } + + public var isNaN: Bool { + return !isFinite && significandBitPattern != 0 + } + + public var isNormal: Bool { + return exponentBitPattern > 0 && isFinite + } + + public var isSignalingNaN: Bool { + return isNaN && (significandBitPattern & Half.quietNaNMask) == 0 + } + + public var isSubnormal: Bool { + return exponentBitPattern == 0 && significandBitPattern != 0 + } + + public var isZero: Bool { + return exponentBitPattern == 0 && significandBitPattern == 0 + } + + public var nextUp: Half { + let next = self + 0 + + #if arch(arm) + // On arm, treat subnormal values as zero. + if next == 0 { return .leastNonzeroMagnitude } + if next == -.leastNonzeroMagnitude { return -0.0 } + #endif + + if next < .infinity { + let increment = Int16(bitPattern: next.bitPattern) &>> 15 | 1 + let bitPattern = next.bitPattern &+ UInt16(bitPattern: increment) + return Half(bitPattern: bitPattern) + } + + return next + } + + public var sign: FloatingPointSign { + let shift = Half.significandBitCount + Half.exponentBitCount + //swiftlint:disable force_unwrapping + return FloatingPointSign(rawValue: Int(bitPattern &>> UInt16(shift)))! + //swiftlint:enable force_unwrapping + } + + public var significand: Half { + if isNaN { return self } + if isNormal { + return Half(sign: .plus, exponentBitPattern: Half.exponentBias, significandBitPattern: significandBitPattern) + } + + if isSubnormal { + let shift = Half.significandBitCount - Int(log2(Double(significandBitPattern))) + return Half(sign: .plus, exponentBitPattern: Half.exponentBias, significandBitPattern: significandBitPattern &<< shift) + } + + return Half(sign: .plus, exponentBitPattern: exponentBitPattern, significandBitPattern: 0) + } + + public var ulp: Half { + guard isFinite else { return .nan } + if isNormal { + let bitPattern = self.bitPattern & Half.infinity.bitPattern + return Half(bitPattern: bitPattern) * .ulpOfOne + } + + return .leastNormalMagnitude * .ulpOfOne + } + + // + + public static var greatestFiniteMagnitude: Half { + return Half(bitPattern: 0x7BFF) + } + + public static var infinity: Half { + return Half(bitPattern: 0x7C00) + } + + public static var leastNonzeroMagnitude: Half { + #if arch(arm) + return leastNormalMagnitude + #else + return Half(sign: .plus, exponentBitPattern: 0, significandBitPattern: 1) + #endif + } + + public static var leastNormalMagnitude: Half { + return Half(sign: .plus, exponentBitPattern: 1, significandBitPattern: 0) + } + + public static var nan: Half { + return Half(_half_nan()) + } + + public static var pi: Half { + return Half(_half_pi()) + } + + public static var signalingNaN: Half { + return Half(nan: 0, signaling: true) + } + + public static var ulpOfOne: Half { + return Half(_half_epsilon()) + } + + // + + @_transparent + public mutating func addProduct(_ lhs: Half, _ rhs: Half) { + _value = _half_fma(_value, lhs._value, rhs._value) + } + + public mutating func formRemainder(dividingBy other: Half) { + self = Half(Float(self).remainder(dividingBy: Float(other))) + } + + @_transparent + public mutating func formSquareRoot() { + _value = _half_sqrt(_value) + } + + public mutating func formTruncatingRemainder(dividingBy other: Half) { + self = Half(Float(self).truncatingRemainder(dividingBy: Float(other))) + } + + @_transparent + public func isEqual(to other: Half) -> Bool { + return Bool(_half_equal(self._value, other._value)) + } + + @_transparent + public func isLess(than other: Half) -> Bool { + return Bool(_half_lt(self._value, other._value)) + } + + @_transparent + public func isLessThanOrEqualTo(_ other: Half) -> Bool { + return Bool(_half_lte(self._value, other._value)) + } + + @_transparent + public mutating func round(_ rule: FloatingPointRoundingRule) { + self = Half(Float(self).rounded(rule)) + } + + // + + @_transparent + public static func / (lhs: Half, rhs: Half) -> Half { + return Half(_half_div(lhs._value, rhs._value)) + } + + @_transparent + public static func /= (lhs: inout Half, rhs: Half) { + lhs._value = _half_div(lhs._value, rhs._value) + } +} + +// MARK: - Hashable Protocol Conformance + +extension Half: Hashable { + + //swiftlint:disable legacy_hashing + public var hashValue: Int { + return Int(bitPattern) + } + //swiftlint:enable legacy_hashing +} + +// MARK: - Strideable Protocol Conformance + +extension Half: Strideable { + + @_transparent + public func distance(to other: Half) -> Half { + return other - self + } + + @_transparent + public func advanced(by amount: Half) -> Half { + return self + amount + } +} + +// MARK: - SignedNumeric Protocol Conformance + +extension Half: SignedNumeric { + + @_transparent + public mutating func negate() { + _value = _half_neg(_value) + } + + @_transparent + public static prefix func - (value: Half) -> Half { + return Half(_half_neg(value._value)) + } +} + +// MARK: - Numeric Protocol Conformance + +extension Half: Numeric { + + public var magnitude: Half { + return Half(_half_abs(_value)) + } + + public init?(exactly value: Source) where Source: BinaryInteger { + self.init(value) + + if isInfinite || isNaN || Source(self) != value { + return nil + } + } + + @_transparent + public static func * (lhs: Half, rhs: Half) -> Half { + return Half(_half_mul(lhs._value, rhs._value)) + } + + @_transparent + public static func *= (lhs: inout Half, rhs: Half) { + lhs._value = _half_mul(lhs._value, rhs._value) + } +} + +// MARK: - ExpressibleByIntegerLiteral Protocol Conformance + +extension Half: ExpressibleByIntegerLiteral { + + @_transparent + public init(integerLiteral value: Int64) { + self = Half(value) + } +} + +// MARK: - Half Extension + +extension Half { + + @_transparent + public static func + (lhs: Half, rhs: Half) -> Half { + return Half(_half_add(lhs._value, rhs._value)) + } + + @_transparent + public static func += (lhs: inout Half, rhs: Half) { + lhs._value = _half_add(lhs._value, rhs._value) + } + + @_transparent + public static func - (lhs: Half, rhs: Half) -> Half { + return Half(_half_sub(lhs._value, rhs._value)) + } + + @_transparent + public static func -= (lhs: inout Half, rhs: Half) { + lhs._value = _half_sub(lhs._value, rhs._value) + } +} + +extension Half: CustomReflectable { + + @_transparent + public var customMirror: Mirror { + return Mirror(reflecting: Float(self)) + } +} + +extension Float { + + public init(_ other: Half) { + self = _half_to_float(other._value) + } +} + +extension Double { + + public init(_ other: Half) { + self = _half_to_double(other._value) + } +} + +#if !(os(Windows) || os(Android)) && (arch(i386) || arch(x86_64)) +extension Float80 { + + public init(_ other: Half) { + self = Float80(_half_to_double(other._value)) + } +} +#endif + +#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) +extension CGFloat { + + public init(_ other: Half) { + self.init(NativeType(other)) + } +} +#endif // #if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) +#endif // #if swift(<4.2) diff --git a/Sources/Half/Half@swift-4.2.swift b/Sources/Half/Half@swift-4.2.swift new file mode 100644 index 0000000..5e84d9a --- /dev/null +++ b/Sources/Half/Half@swift-4.2.swift @@ -0,0 +1,677 @@ +// +// Half.swift +// Half +// +// Copyright © 2020 SomeRandomiOSDev. All rights reserved. +// + +#if SWIFT_PACKAGE +import CHalf +#endif +import Foundation + +#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) +import CoreGraphics.CGBase +#endif // #if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) + +#if swift(>=5.0) +#elseif swift(>=4.2) +// MARK: - Half Definition + +public struct Half { + + // MARK: Public Properties + + public var _value: half_t + + // MARK: Initialization + + @_transparent + public init() { + self._value = _half_zero() + } + + @_transparent + public init(_ _value: half_t) { + self._value = _value + } +} + +// MARK: - Half Extension + +extension Half { + + @inlinable + public var bitPattern: UInt16 { + return _half_to_raw(_value) + } + + @inlinable + public init(bitPattern: UInt16) { + self._value = _half_from_raw(bitPattern) + } + + @inlinable + public init(nan payload: UInt16, signaling: Bool) { + precondition(payload < (Half.quietNaNMask &>> 1), "NaN payload is not encodable.") + + var significand = payload + significand |= Half.quietNaNMask &>> (signaling ? 1 : 0) + + self.init(sign: .plus, exponentBitPattern: Half.infinityExponent, significandBitPattern: significand) + } +} + +// MARK: - CustomStringConvertible Protocol Conformance + +extension Half: CustomStringConvertible { + + public var description: String { + if isNaN { + return "nan" + } + + return _half_to_float(_value).description + } +} + +// MARK: - CustomStringConvertible Protocol Conformance + +extension Half: CustomDebugStringConvertible { + + public var debugDescription: String { + return _half_to_float(_value).description + } +} + +// MARK: - Internal Constants + +extension Half { + + @inlinable + internal static var significandMask: UInt16 { + return 1 &<< UInt16(significandBitCount) - 1 + } + + @inlinable + internal static var infinityExponent: UInt { + return 1 &<< UInt(exponentBitCount) - 1 + } + + @inlinable + internal static var exponentBias: UInt { + return infinityExponent &>> 1 + } + + @inlinable + internal static var quietNaNMask: UInt16 { + return 1 &<< UInt16(significandBitCount - 1) + } +} + +// MARK: - BinaryFloatingPoint Protocol Conformance + +extension Half: BinaryFloatingPoint { + + @inlinable + public static var exponentBitCount: Int { + return 5 + } + + @inlinable + public static var significandBitCount: Int { + return 10 + } + + @inlinable + public var exponentBitPattern: UInt { + return UInt(bitPattern &>> UInt16(Half.significandBitCount)) & Half.infinityExponent + } + + @inlinable + public var significandBitPattern: UInt16 { + return bitPattern & Half.significandMask + } + + // + + @inlinable + public init(sign: FloatingPointSign, exponentBitPattern: UInt, significandBitPattern: UInt16) { + let signBits: UInt16 = (sign == .minus ? 1 : 0) &<< (Half.exponentBitCount + Half.significandBitCount) + let exponentBits = UInt16((exponentBitPattern & Half.infinityExponent) &<< Half.significandBitCount) + let significandBits = significandBitPattern & Half.significandMask + + self.init(bitPattern: signBits | exponentBits | significandBits) + } + + @inlinable + public init(_ other: Float) { + if other.isInfinite { + let infinity = Half.infinity + self = Half(sign: other.sign, exponentBitPattern: infinity.exponentBitPattern, significandBitPattern: infinity.significandBitPattern) + } else if other.isNaN { + if other.isSignalingNaN { + self = .signalingNaN + } else { + self = .nan + } + } else { + _value = _half_from(other) + } + } + + @inlinable + public init(_ other: Double) { + if other.isInfinite { + let infinity = Half.infinity + self = Half(sign: other.sign, exponentBitPattern: infinity.exponentBitPattern, significandBitPattern: infinity.significandBitPattern) + } else if other.isNaN { + if other.isSignalingNaN { + self = .signalingNaN + } else { + self = .nan + } + } else { + _value = _half_from(other) + } + } + +#if !(os(Windows) || os(Android)) && (arch(i386) || arch(x86_64)) + @inlinable + public init(_ other: Float80) { + if other.isInfinite { + let infinity = Half.infinity + self = Half(sign: other.sign, exponentBitPattern: infinity.exponentBitPattern, significandBitPattern: infinity.significandBitPattern) + } else if other.isNaN { + if other.isSignalingNaN { + self = .signalingNaN + } else { + self = .nan + } + } else { + _value = _half_from(Double(other)) + } + } +#endif + +#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) + // Not part of the protocol + @inlinable + public init(_ other: CGFloat) { + self.init(other.native) + } +#endif // #if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) + + @inlinable + public init(_ value: Source) where Source: BinaryFloatingPoint { + if let half = value as? Half { + self.init(half._value) + } else { + self.init(Float(value)) + } + } + + @inlinable + public init?(exactly value: Source) where Source: BinaryFloatingPoint { + self.init(value) + + if isInfinite || value.isInfinite { + if value.isInfinite && (!isInfinite || sign != value.sign) { + // If source is infinite but this isn't or this is but with a different sign + return nil + } else if isInfinite && !value.isInfinite { + // If source isn't infinite but this is + return nil + } + } else if isNaN || value.isNaN { + if value.isNaN && (!isNaN || isSignalingNaN != value.isSignalingNaN) { + // If source is NaN but this isn't or this is but one is signaling while the other isn't + return nil + } else if isNaN && !value.isNaN { + // If source isn't NaN but this is + return nil + } + } else if Source(self) != value { + // If casting half back to source isn't equal to original source + return nil + } + } + + // + + @inlinable + public var binade: Half { + guard isFinite else { return .nan } + + #if !arch(arm) + if isSubnormal { + let bitPattern = (self * 0x1p10).bitPattern & (-Half.infinity).bitPattern + return Half(bitPattern: bitPattern) * .ulpOfOne + } + #endif + + return Half(bitPattern: bitPattern & (-Half.infinity).bitPattern) + } + + @inlinable + public var significandWidth: Int { + let trailingZeroBits = significandBitPattern.trailingZeroBitCount + if isNormal { + guard significandBitPattern != 0 else { return 0 } + return Half.significandBitCount &- trailingZeroBits + } + if isSubnormal { + let leadingZeroBits = significandBitPattern.leadingZeroBitCount + return UInt16.bitWidth &- (trailingZeroBits &+ leadingZeroBits &+ 1) + } + return -1 + } +} + +// MARK: - ExpressibleByFloatLiteral Protocol Conformance + +extension Half: ExpressibleByFloatLiteral { + + @_transparent + public init(floatLiteral value: Float) { + self.init(value) + } +} + +// MARK: - FloatingPoint Protocol Conformance + +extension Half: FloatingPoint { + + @inlinable + public init(sign: FloatingPointSign, exponent: Int, significand: Half) { + var result = significand + if sign == .minus { result = -result } + + if significand.isFinite && !significand.isZero { + var clamped = exponent + let leastNormalExponent = 1 - Int(Half.exponentBias) + let greatestFiniteExponent = Int(Half.exponentBias) + + if clamped < leastNormalExponent { + clamped = max(clamped, 3 * leastNormalExponent) + + while clamped < leastNormalExponent { + result *= Half.leastNormalMagnitude + clamped -= leastNormalExponent + } + } else if clamped > greatestFiniteExponent { + let step = Half(sign: .plus, exponentBitPattern: Half.infinityExponent - 1, significandBitPattern: 0) + clamped = min(clamped, 3 * greatestFiniteExponent) + + while clamped > greatestFiniteExponent { + result *= step + clamped -= greatestFiniteExponent + } + } + + let scale = Half(sign: .plus, exponentBitPattern: UInt(Int(Half.exponentBias) + clamped), significandBitPattern: 0) + result *= scale + } + + self = result + } + + @_transparent + public init(_ value: Int) { + _value = _half_from(value) + } + + @inlinable + public init(_ value: Source) { + if value.bitWidth <= MemoryLayout.size * 8 { + if Source.isSigned { + let asInt = Int(truncatingIfNeeded: value) + self.init(_half_from(asInt)) + } else { + let asUInt = UInt(truncatingIfNeeded: value) + self.init(_half_from(asUInt)) + } + } else { + self.init(Float(value)) + } + } + + // + + @inlinable + public var exponent: Int { + if !isFinite { return .max } + if isZero { return .min } + + let provisional = Int(exponentBitPattern) - Int(Half.exponentBias) + if isNormal { return provisional } + + let shift = Half.significandBitCount - Int(significandBitPattern._binaryLogarithm()) + return provisional + 1 - shift + } + + @inlinable + public var isCanonical: Bool { + #if arch(arm) + if exponentBitPattern == 0 && significandBitPattern != 0 { + return false + } + #endif + + return true + } + + @inlinable + public var isFinite: Bool { + return exponentBitPattern < Half.infinityExponent + } + + @inlinable + public var isInfinite: Bool { + return !isFinite && significandBitPattern == 0 + } + + @inlinable + public var isNaN: Bool { + return !isFinite && significandBitPattern != 0 + } + + @inlinable + public var isNormal: Bool { + return exponentBitPattern > 0 && isFinite + } + + @inlinable + public var isSignalingNaN: Bool { + return isNaN && (significandBitPattern & Half.quietNaNMask) == 0 + } + + @inlinable + public var isSubnormal: Bool { + return exponentBitPattern == 0 && significandBitPattern != 0 + } + + @inlinable + public var isZero: Bool { + return exponentBitPattern == 0 && significandBitPattern == 0 + } + + @inlinable + public var nextUp: Half { + let next = self + 0 + + #if arch(arm) + // On arm, treat subnormal values as zero. + if next == 0 { return .leastNonzeroMagnitude } + if next == -.leastNonzeroMagnitude { return -0.0 } + #endif + + if next < .infinity { + let increment = Int16(bitPattern: next.bitPattern) &>> 15 | 1 + let bitPattern = next.bitPattern &+ UInt16(bitPattern: increment) + return Half(bitPattern: bitPattern) + } + + return next + } + + @inlinable + public var sign: FloatingPointSign { + let shift = Half.significandBitCount + Half.exponentBitCount + //swiftlint:disable force_unwrapping + return FloatingPointSign(rawValue: Int(bitPattern &>> UInt16(shift)))! + //swiftlint:enable force_unwrapping + } + + @inlinable + public var significand: Half { + if isNaN { return self } + if isNormal { + return Half(sign: .plus, exponentBitPattern: Half.exponentBias, significandBitPattern: significandBitPattern) + } + + if isSubnormal { + let shift = Half.significandBitCount - Int(significandBitPattern._binaryLogarithm()) + return Half(sign: .plus, exponentBitPattern: Half.exponentBias, significandBitPattern: significandBitPattern &<< shift) + } + + return Half(sign: .plus, exponentBitPattern: exponentBitPattern, significandBitPattern: 0) + } + + @inlinable + public var ulp: Half { + guard isFinite else { return .nan } + if isNormal { + let bitPattern = self.bitPattern & Half.infinity.bitPattern + return Half(bitPattern: bitPattern) * .ulpOfOne + } + + return .leastNormalMagnitude * .ulpOfOne + } + + // + + @inlinable + public static var greatestFiniteMagnitude: Half { + return Half(bitPattern: 0x7BFF) + } + + @inlinable + public static var infinity: Half { + return Half(bitPattern: 0x7C00) + } + + @inlinable + public static var leastNonzeroMagnitude: Half { + #if arch(arm) + return leastNormalMagnitude + #else + return Half(sign: .plus, exponentBitPattern: 0, significandBitPattern: 1) + #endif + } + + @inlinable + public static var leastNormalMagnitude: Half { + return Half(sign: .plus, exponentBitPattern: 1, significandBitPattern: 0) + } + + @inlinable + public static var nan: Half { + return Half(_half_nan()) + } + + @inlinable + public static var pi: Half { + return Half(_half_pi()) + } + + @inlinable + public static var signalingNaN: Half { + return Half(nan: 0, signaling: true) + } + + @inlinable + public static var ulpOfOne: Half { + return Half(_half_epsilon()) + } + + // + + @_transparent + public mutating func addProduct(_ lhs: Half, _ rhs: Half) { + _value = _half_fma(_value, lhs._value, rhs._value) + } + + @inlinable + public mutating func formRemainder(dividingBy other: Half) { + self = Half(Float(self).remainder(dividingBy: Float(other))) + } + + @_transparent + public mutating func formSquareRoot() { + _value = _half_sqrt(_value) + } + + @inlinable + public mutating func formTruncatingRemainder(dividingBy other: Half) { + self = Half(Float(self).truncatingRemainder(dividingBy: Float(other))) + } + + @_transparent + public func isEqual(to other: Half) -> Bool { + return Bool(_half_equal(self._value, other._value)) + } + + @_transparent + public func isLess(than other: Half) -> Bool { + return Bool(_half_lt(self._value, other._value)) + } + + @_transparent + public func isLessThanOrEqualTo(_ other: Half) -> Bool { + return Bool(_half_lte(self._value, other._value)) + } + + @_transparent + public mutating func round(_ rule: FloatingPointRoundingRule) { + self = Half(Float(self).rounded(rule)) + } + + // + + @_transparent + public static func / (lhs: Half, rhs: Half) -> Half { + return Half(_half_div(lhs._value, rhs._value)) + } + + @_transparent + public static func /= (lhs: inout Half, rhs: Half) { + lhs._value = _half_div(lhs._value, rhs._value) + } +} + +// MARK: - Hashable Protocol Conformance + +extension Half: Hashable { + + @inlinable + public func hash(into hasher: inout Hasher) { + var value = self + if isZero { + value = 0 // to reconcile -0.0 and +0.0 + } + + hasher.combine(value.bitPattern) + } +} + +// MARK: - Strideable Protocol Conformance + +extension Half: Strideable { + + @_transparent + public func distance(to other: Half) -> Half { + return other - self + } + + @_transparent + public func advanced(by amount: Half) -> Half { + return self + amount + } +} + +// MARK: - SignedNumeric Protocol Conformance + +extension Half: SignedNumeric { + + @_transparent + public mutating func negate() { + _value = _half_neg(_value) + } + + @_transparent + public static prefix func - (value: Half) -> Half { + return Half(_half_neg(value._value)) + } +} + +// MARK: - Numeric Protocol Conformance + +extension Half: Numeric { + + @inlinable + public var magnitude: Half { + return Half(_half_abs(_value)) + } + + @inlinable + public init?(exactly value: Source) where Source: BinaryInteger { + self.init(value) + + if isInfinite || isNaN || Source(self) != value { + return nil + } + } + + @_transparent + public static func * (lhs: Half, rhs: Half) -> Half { + return Half(_half_mul(lhs._value, rhs._value)) + } + + @_transparent + public static func *= (lhs: inout Half, rhs: Half) { + lhs._value = _half_mul(lhs._value, rhs._value) + } +} + +// MARK: - ExpressibleByIntegerLiteral Protocol Conformance + +extension Half: ExpressibleByIntegerLiteral { + + @_transparent + public init(integerLiteral value: Int64) { + self = Half(value) + } +} + +// MARK: - Half Extension + +extension Half { + + @_transparent + public static func + (lhs: Half, rhs: Half) -> Half { + return Half(_half_add(lhs._value, rhs._value)) + } + + @_transparent + public static func += (lhs: inout Half, rhs: Half) { + lhs._value = _half_add(lhs._value, rhs._value) + } + + @_transparent + public static func - (lhs: Half, rhs: Half) -> Half { + return Half(_half_sub(lhs._value, rhs._value)) + } + + @_transparent + public static func -= (lhs: inout Half, rhs: Half) { + lhs._value = _half_sub(lhs._value, rhs._value) + } +} + +extension Half: CustomReflectable { + + @_transparent + public var customMirror: Mirror { + return Mirror(reflecting: Float(self)) + } +} + +extension Half: CustomPlaygroundDisplayConvertible { + + @_transparent + public var playgroundDescription: Any { + return Float(self) + } +} +#endif // #if swift(>=4.2) && swift(<5.0) diff --git a/Tests/HalfTests/HalfTests.swift b/Tests/HalfTests/HalfTests.swift index 8bb0915..a14bade 100644 --- a/Tests/HalfTests/HalfTests.swift +++ b/Tests/HalfTests/HalfTests.swift @@ -55,6 +55,7 @@ class HalfTests: XCTestCase { } func testConvertFromOtherFloatTypes() { + let half: Half = 3.14 let float: Float = 3.14 let double: Double = 3.14 #if canImport(CoreGraphics) @@ -73,6 +74,7 @@ class HalfTests: XCTestCase { let half4 = Half(float80) #endif + XCTAssertEqual(half, Half(half)) XCTAssertEqual(half1, half2) #if canImport(CoreGraphics) XCTAssertEqual(half2, half3) @@ -440,8 +442,10 @@ class HalfTests: XCTestCase { } func testOutputStreamable() { + #if swift(>=5.0) var tests = self Half(2.5).write(to: &tests) + #endif // #if swift(>=5.0) } // MARK: Private Methods diff --git a/Utils/gyb b/Utils/gyb new file mode 100755 index 0000000..dece788 --- /dev/null +++ b/Utils/gyb @@ -0,0 +1,3 @@ +#!/usr/bin/env python2.7 +import gyb +gyb.main() diff --git a/Utils/gyb.py b/Utils/gyb.py new file mode 100644 index 0000000..2df9a03 --- /dev/null +++ b/Utils/gyb.py @@ -0,0 +1,1260 @@ +#!/usr/bin/env python +# GYB: Generate Your Boilerplate (improved names welcome; at least +# this one's short). See -h output for instructions + +from __future__ import print_function + +import os +import re +import sys +import textwrap +import tokenize +from bisect import bisect + + +try: + from cStringIO import StringIO +except ImportError: + from io import StringIO + + +try: + basestring +except NameError: + basestring = str + + +def get_line_starts(s): + """Return a list containing the start index of each line in s. + + The list also contains a sentinel index for the end of the string, + so there will be one more element in the list than there are lines + in the string + """ + starts = [0] + + for line in s.split('\n'): + starts.append(starts[-1] + len(line) + 1) + + starts[-1] -= 1 + return starts + + +def strip_trailing_nl(s): + """If s ends with a newline, drop it; else return s intact""" + return s[:-1] if s.endswith('\n') else s + + +def split_lines(s): + """Split s into a list of lines, each of which has a trailing newline + + If the lines are later concatenated, the result is s, possibly + with a single appended newline. + """ + return [l + '\n' for l in s.split('\n')] + + +# text on a line up to the first '$$', '${', or '%%' +literalText = r'(?: [^$\n%] | \$(?![${]) | %(?!%) )*' + +# The part of an '%end' line that follows the '%' sign +linesClose = r'[\ \t]* end [\ \t]* (?: \# .* )? $' + +# Note: Where "# Absorb" appears below, the regexp attempts to eat up +# through the end of ${...} and %{...}% constructs. In reality we +# handle this with the Python tokenizer, which avoids mis-detections +# due to nesting, comments and strings. This extra absorption in the +# regexp facilitates testing the regexp on its own, by preventing the +# interior of some of these constructs from being treated as literal +# text. +tokenize_re = re.compile( + r''' +# %-lines and %{...}-blocks + # \n? # absorb one preceding newline + ^ + (?: + (?P + (?P<_indent> [\ \t]* % (?! [{%] ) [\ \t]* ) (?! [\ \t] | ''' + + linesClose + r''' ) .* + ( \n (?P=_indent) (?! ''' + linesClose + r''' ) .* ) * + ) + | (?P [\ \t]* % [ \t]* ''' + linesClose + r''' ) + | [\ \t]* (?P %\{ ) + (?: [^}]| \} (?!%) )* \}% # Absorb + ) + \n? # absorb one trailing newline + +# Substitutions +| (?P \$\{ ) + [^}]* \} # Absorb + +# %% and $$ are literal % and $ respectively +| (?P[$%]) (?P=symbol) + +# Literal text +| (?P ''' + literalText + r''' + (?: + # newline that doesn't precede space+% + (?: \n (?! [\ \t]* %[^%] ) ) + ''' + literalText + r''' + )* + \n? + ) +''', re.VERBOSE | re.MULTILINE) + +gyb_block_close = re.compile(r'\}%[ \t]*\n?') + + +def token_pos_to_index(token_pos, start, line_starts): + """Translate a tokenize (line, column) pair into an absolute + position in source text given the position where we started + tokenizing and a list that maps lines onto their starting + character indexes. + """ + relative_token_line_plus1, token_col = token_pos + + # line number where we started tokenizing + start_line_num = bisect(line_starts, start) - 1 + + # line number of the token in the whole text + abs_token_line = relative_token_line_plus1 - 1 + start_line_num + + # if found in the first line, adjust the end column to account + # for the extra text + if relative_token_line_plus1 == 1: + token_col += start - line_starts[start_line_num] + + # Sometimes tokenizer errors report a line beyond the last one + if abs_token_line >= len(line_starts): + return line_starts[-1] + + return line_starts[abs_token_line] + token_col + + +def tokenize_python_to_unmatched_close_curly(source_text, start, line_starts): + """Apply Python's tokenize to source_text starting at index start + while matching open and close curly braces. When an unmatched + close curly brace is found, return its index. If not found, + return len(source_text). If there's a tokenization error, return + the position of the error. + """ + stream = StringIO(source_text) + stream.seek(start) + nesting = 0 + + try: + for kind, text, token_start, token_end, line_text \ + in tokenize.generate_tokens(stream.readline): + + if text == '{': + nesting += 1 + elif text == '}': + nesting -= 1 + if nesting < 0: + return token_pos_to_index(token_start, start, line_starts) + + except tokenize.TokenError as error: + (message, error_pos) = error.args + return token_pos_to_index(error_pos, start, line_starts) + + return len(source_text) + + +def tokenize_template(template_text): + r"""Given the text of a template, returns an iterator over + (tokenType, token, match) tuples. + + **Note**: this is template syntax tokenization, not Python + tokenization. + + When a non-literal token is matched, a client may call + iter.send(pos) on the iterator to reset the position in + template_text at which scanning will resume. + + This function provides a base level of tokenization which is + then refined by ParseContext.token_generator. + + >>> from pprint import * + >>> pprint(list((kind, text) for kind, text, _ in tokenize_template( + ... '%for x in range(10):\n% print x\n%end\njuicebox'))) + [('gybLines', '%for x in range(10):\n% print x'), + ('gybLinesClose', '%end'), + ('literal', 'juicebox')] + + >>> pprint(list((kind, text) for kind, text, _ in tokenize_template( + ... '''Nothing + ... % if x: + ... % for i in range(3): + ... ${i} + ... % end + ... % else: + ... THIS SHOULD NOT APPEAR IN THE OUTPUT + ... '''))) + [('literal', 'Nothing\n'), + ('gybLines', '% if x:\n% for i in range(3):'), + ('substitutionOpen', '${'), + ('literal', '\n'), + ('gybLinesClose', '% end'), + ('gybLines', '% else:'), + ('literal', 'THIS SHOULD NOT APPEAR IN THE OUTPUT\n')] + + >>> for kind, text, _ in tokenize_template(''' + ... This is $some$ literal stuff containing a ${substitution} + ... followed by a %{...} block: + ... %{ + ... # Python code + ... }% + ... and here $${are} some %-lines: + ... % x = 1 + ... % y = 2 + ... % if z == 3: + ... % print '${hello}' + ... % end + ... % for x in zz: + ... % print x + ... % # different indentation + ... % twice + ... and some lines that literally start with a %% token + ... %% first line + ... %% second line + ... '''): + ... print((kind, text.strip().split('\n',1)[0])) + ('literal', 'This is $some$ literal stuff containing a') + ('substitutionOpen', '${') + ('literal', 'followed by a %{...} block:') + ('gybBlockOpen', '%{') + ('literal', 'and here ${are} some %-lines:') + ('gybLines', '% x = 1') + ('gybLinesClose', '% end') + ('gybLines', '% for x in zz:') + ('gybLines', '% # different indentation') + ('gybLines', '% twice') + ('literal', 'and some lines that literally start with a % token') + """ + pos = 0 + end = len(template_text) + + saved_literal = [] + literal_first_match = None + + while pos < end: + m = tokenize_re.match(template_text, pos, end) + + # pull out the one matched key (ignoring internal patterns starting + # with _) + ((kind, text), ) = ( + (kind, text) for (kind, text) in m.groupdict().items() + if text is not None and kind[0] != '_') + + if kind in ('literal', 'symbol'): + if len(saved_literal) == 0: + literal_first_match = m + # literals and symbols get batched together + saved_literal.append(text) + pos = None + else: + # found a non-literal. First yield any literal we've accumulated + if saved_literal != []: + yield 'literal', ''.join(saved_literal), literal_first_match + saved_literal = [] + + # Then yield the thing we found. If we get a reply, it's + # the place to resume tokenizing + pos = yield kind, text, m + + # If we were not sent a new position by our client, resume + # tokenizing at the end of this match. + if pos is None: + pos = m.end(0) + else: + # Client is not yet ready to process next token + yield + + if saved_literal != []: + yield 'literal', ''.join(saved_literal), literal_first_match + + +def split_gyb_lines(source_lines): + r"""Return a list of lines at which to split the incoming source + + These positions represent the beginnings of python line groups that + will require a matching %end construct if they are to be closed. + + >>> src = split_lines('''\ + ... if x: + ... print x + ... if y: # trailing comment + ... print z + ... if z: # another comment\ + ... ''') + >>> s = split_gyb_lines(src) + >>> len(s) + 2 + >>> src[s[0]] + ' print z\n' + >>> s[1] - len(src) + 0 + + >>> src = split_lines('''\ + ... if x: + ... if y: print 1 + ... if z: + ... print 2 + ... pass\ + ... ''') + >>> s = split_gyb_lines(src) + >>> len(s) + 1 + >>> src[s[0]] + ' if y: print 1\n' + + >>> src = split_lines('''\ + ... if x: + ... if y: + ... print 1 + ... print 2 + ... ''') + >>> s = split_gyb_lines(src) + >>> len(s) + 2 + >>> src[s[0]] + ' if y:\n' + >>> src[s[1]] + ' print 1\n' + """ + last_token_text, last_token_kind = None, None + unmatched_indents = [] + + dedents = 0 + try: + for token_kind, token_text, token_start, \ + (token_end_line, token_end_col), line_text \ + in tokenize.generate_tokens(lambda i=iter(source_lines): + next(i)): + + if token_kind in (tokenize.COMMENT, tokenize.ENDMARKER): + continue + + if token_text == '\n' and last_token_text == ':': + unmatched_indents.append(token_end_line) + + # The tokenizer appends dedents at EOF; don't consider + # those as matching indentations. Instead just save them + # up... + if last_token_kind == tokenize.DEDENT: + dedents += 1 + # And count them later, when we see something real. + if token_kind != tokenize.DEDENT and dedents > 0: + unmatched_indents = unmatched_indents[:-dedents] + dedents = 0 + + last_token_text, last_token_kind = token_text, token_kind + + except tokenize.TokenError: + # Let the later compile() call report the error + return [] + + if last_token_text == ':': + unmatched_indents.append(len(source_lines)) + + return unmatched_indents + + +def code_starts_with_dedent_keyword(source_lines): + r"""Return True iff the incoming Python source_lines begin with "else", + "elif", "except", or "finally". + + Initial comments and whitespace are ignored. + + >>> code_starts_with_dedent_keyword(split_lines('if x in y: pass')) + False + >>> code_starts_with_dedent_keyword(split_lines('except ifSomethingElse:')) + True + >>> code_starts_with_dedent_keyword( + ... split_lines('\n# comment\nelse: # yes')) + True + """ + token_text = None + for token_kind, token_text, _, _, _ \ + in tokenize.generate_tokens(lambda i=iter(source_lines): next(i)): + + if token_kind != tokenize.COMMENT and token_text.strip() != '': + break + + return token_text in ('else', 'elif', 'except', 'finally') + + +class ParseContext(object): + + """State carried through a parse of a template""" + + filename = '' + template = '' + line_starts = [] + code_start_line = -1 + code_text = None + tokens = None # The rest of the tokens + close_lines = False + + def __init__(self, filename, template=None): + self.filename = os.path.abspath(filename) + if sys.platform == 'win32': + self.filename = self.filename.replace('\\', '/') + if template is None: + with open(filename) as f: + self.template = f.read() + else: + self.template = template + self.line_starts = get_line_starts(self.template) + self.tokens = self.token_generator(tokenize_template(self.template)) + self.next_token() + + def pos_to_line(self, pos): + return bisect(self.line_starts, pos) - 1 + + def token_generator(self, base_tokens): + r"""Given an iterator over (kind, text, match) triples (see + tokenize_template above), return a refined iterator over + token_kinds. + + Among other adjustments to the elements found by base_tokens, + this refined iterator tokenizes python code embedded in + template text to help determine its true extent. The + expression "base_tokens.send(pos)" is used to reset the index at + which base_tokens resumes scanning the underlying text. + + >>> ctx = ParseContext('dummy', ''' + ... %for x in y: + ... % print x + ... % end + ... literally + ... ''') + >>> while ctx.token_kind: + ... print((ctx.token_kind, ctx.code_text or ctx.token_text)) + ... ignored = ctx.next_token() + ('literal', '\n') + ('gybLinesOpen', 'for x in y:\n') + ('gybLines', ' print x\n') + ('gybLinesClose', '% end') + ('literal', 'literally\n') + + >>> ctx = ParseContext('dummy', + ... '''Nothing + ... % if x: + ... % for i in range(3): + ... ${i} + ... % end + ... % else: + ... THIS SHOULD NOT APPEAR IN THE OUTPUT + ... ''') + >>> while ctx.token_kind: + ... print((ctx.token_kind, ctx.code_text or ctx.token_text)) + ... ignored = ctx.next_token() + ('literal', 'Nothing\n') + ('gybLinesOpen', 'if x:\n') + ('gybLinesOpen', ' for i in range(3):\n') + ('substitutionOpen', 'i') + ('literal', '\n') + ('gybLinesClose', '% end') + ('gybLinesOpen', 'else:\n') + ('literal', 'THIS SHOULD NOT APPEAR IN THE OUTPUT\n') + + >>> ctx = ParseContext('dummy', + ... '''% for x in [1, 2, 3]: + ... % if x == 1: + ... literal1 + ... % elif x > 1: # add output line here to fix bug + ... % if x == 2: + ... literal2 + ... % end + ... % end + ... % end + ... ''') + >>> while ctx.token_kind: + ... print((ctx.token_kind, ctx.code_text or ctx.token_text)) + ... ignored = ctx.next_token() + ('gybLinesOpen', 'for x in [1, 2, 3]:\n') + ('gybLinesOpen', ' if x == 1:\n') + ('literal', 'literal1\n') + ('gybLinesOpen', 'elif x > 1: # add output line here to fix bug\n') + ('gybLinesOpen', ' if x == 2:\n') + ('literal', 'literal2\n') + ('gybLinesClose', '% end') + ('gybLinesClose', '% end') + ('gybLinesClose', '% end') + """ + for self.token_kind, self.token_text, self.token_match in base_tokens: + kind = self.token_kind + self.code_text = None + + # Do we need to close the current lines? + self.close_lines = kind == 'gybLinesClose' + + # %{...}% and ${...} constructs + if kind.endswith('Open'): + + # Tokenize text that follows as Python up to an unmatched '}' + code_start = self.token_match.end(kind) + self.code_start_line = self.pos_to_line(code_start) + + close_pos = tokenize_python_to_unmatched_close_curly( + self.template, code_start, self.line_starts) + self.code_text = self.template[code_start:close_pos] + yield kind + + if (kind == 'gybBlockOpen'): + # Absorb any '}% \n' + m2 = gyb_block_close.match(self.template, close_pos) + if not m2: + raise ValueError("Invalid block closure") + next_pos = m2.end(0) + else: + assert kind == 'substitutionOpen' + # skip past the closing '}' + next_pos = close_pos + 1 + + # Resume tokenizing after the end of the code. + base_tokens.send(next_pos) + + elif kind == 'gybLines': + + self.code_start_line = self.pos_to_line( + self.token_match.start('gybLines')) + indentation = self.token_match.group('_indent') + + # Strip off the leading indentation and %-sign + source_lines = re.split( + '^' + re.escape(indentation), + self.token_match.group('gybLines') + '\n', + flags=re.MULTILINE)[1:] + + if code_starts_with_dedent_keyword(source_lines): + self.close_lines = True + + last_split = 0 + for line in split_gyb_lines(source_lines): + self.token_kind = 'gybLinesOpen' + self.code_text = ''.join(source_lines[last_split:line]) + yield self.token_kind + last_split = line + self.code_start_line += line - last_split + self.close_lines = False + + self.code_text = ''.join(source_lines[last_split:]) + if self.code_text: + self.token_kind = 'gybLines' + yield self.token_kind + else: + yield self.token_kind + + def next_token(self): + """Move to the next token""" + for kind in self.tokens: + return self.token_kind + + self.token_kind = None + + +_default_line_directive = \ + '// ###sourceLocation(file: "%(file)s", line: %(line)d)' + + +class ExecutionContext(object): + + """State we pass around during execution of a template""" + + def __init__(self, line_directive=_default_line_directive, + **local_bindings): + self.local_bindings = local_bindings + self.line_directive = line_directive + self.local_bindings['__context__'] = self + self.result_text = [] + self.last_file_line = None + + def append_text(self, text, file, line): + # see if we need to inject a line marker + if self.line_directive: + if (file, line) != self.last_file_line: + # We can only insert the line directive at a line break + if len(self.result_text) == 0 \ + or self.result_text[-1].endswith('\n'): + substitutions = {'file': file, 'line': line + 1} + format_str = self.line_directive + '\n' + self.result_text.append(format_str % substitutions) + # But if the new text contains any line breaks, we can create + # one + elif '\n' in text: + i = text.find('\n') + self.result_text.append(text[:i + 1]) + # and try again + self.append_text(text[i + 1:], file, line + 1) + return + + self.result_text.append(text) + self.last_file_line = (file, line + text.count('\n')) + + +class ASTNode(object): + + """Abstract base class for template AST nodes""" + + def __init__(self): + raise NotImplementedError("ASTNode.__init__ is not implemented.") + + def execute(self, context): + raise NotImplementedError("ASTNode.execute is not implemented.") + + def __str__(self, indent=''): + raise NotImplementedError("ASTNode.__str__ is not implemented.") + + def format_children(self, indent): + if not self.children: + return ' []' + + return '\n'.join( + ['', indent + '['] + + [x.__str__(indent + 4 * ' ') for x in self.children] + + [indent + ']']) + + +class Block(ASTNode): + + """A sequence of other AST nodes, to be executed in order""" + + children = [] + + def __init__(self, context): + self.children = [] + + while context.token_kind and not context.close_lines: + if context.token_kind == 'literal': + node = Literal + else: + node = Code + self.children.append(node(context)) + + def execute(self, context): + for x in self.children: + x.execute(context) + + def __str__(self, indent=''): + return indent + 'Block:' + self.format_children(indent) + + +class Literal(ASTNode): + + """An AST node that generates literal text""" + + def __init__(self, context): + self.text = context.token_text + start_position = context.token_match.start(context.token_kind) + self.start_line_number = context.pos_to_line(start_position) + self.filename = context.filename + context.next_token() + + def execute(self, context): + context.append_text(self.text, self.filename, self.start_line_number) + + def __str__(self, indent=''): + return '\n'.join( + [indent + x for x in ['Literal:'] + + strip_trailing_nl(self.text).split('\n')]) + + +class Code(ASTNode): + + """An AST node that is evaluated as Python""" + + code = None + children = () + kind = None + + def __init__(self, context): + + source = '' + source_line_count = 0 + + def accumulate_code(): + s = source + (context.code_start_line - source_line_count) * '\n' \ + + textwrap.dedent(context.code_text) + line_count = context.code_start_line + \ + context.code_text.count('\n') + context.next_token() + return s, line_count + + eval_exec = 'exec' + if context.token_kind.startswith('substitution'): + eval_exec = 'eval' + source, source_line_count = accumulate_code() + source = '(' + source.strip() + ')' + + else: + while context.token_kind == 'gybLinesOpen': + source, source_line_count = accumulate_code() + source += ' __children__[%d].execute(__context__)\n' % len( + self.children) + source_line_count += 1 + + self.children += (Block(context),) + + if context.token_kind == 'gybLinesClose': + context.next_token() + + if context.token_kind == 'gybLines': + source, source_line_count = accumulate_code() + + # Only handle a substitution as part of this code block if + # we don't already have some %-lines. + elif context.token_kind == 'gybBlockOpen': + + # Opening ${...} and %{...}% constructs + source, source_line_count = accumulate_code() + + self.filename = context.filename + self.start_line_number = context.code_start_line + self.code = compile(source, context.filename, eval_exec) + self.source = source + + def execute(self, context): + # Save __children__ from the local bindings + save_children = context.local_bindings.get('__children__') + # Execute the code with our __children__ in scope + context.local_bindings['__children__'] = self.children + context.local_bindings['__file__'] = self.filename + result = eval(self.code, context.local_bindings) + + if context.local_bindings['__children__'] is not self.children: + raise ValueError("The code is not allowed to mutate __children__") + # Restore the bindings + context.local_bindings['__children__'] = save_children + + # If we got a result, the code was an expression, so append + # its value + if result is not None \ + or (isinstance(result, basestring) and result != ''): + from numbers import Number, Integral + result_string = None + if isinstance(result, Number) and not isinstance(result, Integral): + result_string = repr(result) + else: + result_string = str(result) + context.append_text( + result_string, self.filename, self.start_line_number) + + def __str__(self, indent=''): + source_lines = re.sub(r'^\n', '', strip_trailing_nl( + self.source), flags=re.MULTILINE).split('\n') + if len(source_lines) == 1: + s = indent + 'Code: {' + source_lines[0] + '}' + else: + s = indent + 'Code:\n' + indent + '{\n' + '\n'.join( + indent + 4 * ' ' + l for l in source_lines + ) + '\n' + indent + '}' + return s + self.format_children(indent) + + +def expand(filename, line_directive=_default_line_directive, **local_bindings): + r"""Return the contents of the given template file, executed with the given + local bindings. + + >>> from tempfile import NamedTemporaryFile + >>> # On Windows, the name of a NamedTemporaryFile cannot be used to open + >>> # the file for a second time if delete=True. Therefore, we have to + >>> # manually handle closing and deleting this file to allow us to open + >>> # the file by its name across all platforms. + >>> f = NamedTemporaryFile(delete=False) + >>> f.write( + ... r'''--- + ... % for i in range(int(x)): + ... a pox on ${i} for epoxy + ... % end + ... ${120 + + ... + ... 3} + ... abc + ... ${"w\nx\nX\ny"} + ... z + ... ''') + >>> f.flush() + >>> result = expand( + ... f.name, + ... line_directive='//#sourceLocation(file: "%(file)s", ' + \ + ... 'line: %(line)d)', + ... x=2 + ... ).replace( + ... '"%s"' % f.name.replace('\\', '/'), '"dummy.file"') + >>> print(result, end='') + //#sourceLocation(file: "dummy.file", line: 1) + --- + //#sourceLocation(file: "dummy.file", line: 3) + a pox on 0 for epoxy + //#sourceLocation(file: "dummy.file", line: 3) + a pox on 1 for epoxy + //#sourceLocation(file: "dummy.file", line: 5) + 123 + //#sourceLocation(file: "dummy.file", line: 8) + abc + w + x + X + y + //#sourceLocation(file: "dummy.file", line: 10) + z + >>> f.close() + >>> os.remove(f.name) + """ + with open(filename) as f: + t = parse_template(filename, f.read()) + d = os.getcwd() + os.chdir(os.path.dirname(os.path.abspath(filename))) + try: + return execute_template( + t, line_directive=line_directive, **local_bindings) + finally: + os.chdir(d) + + +def parse_template(filename, text=None): + r"""Return an AST corresponding to the given template file. + + If text is supplied, it is assumed to be the contents of the file, + as a string. + + >>> print(parse_template('dummy.file', text= + ... '''% for x in [1, 2, 3]: + ... % if x == 1: + ... literal1 + ... % elif x > 1: # add output line after this line to fix bug + ... % if x == 2: + ... literal2 + ... % end + ... % end + ... % end + ... ''')) + Block: + [ + Code: + { + for x in [1, 2, 3]: + __children__[0].execute(__context__) + } + [ + Block: + [ + Code: + { + if x == 1: + __children__[0].execute(__context__) + elif x > 1: # add output line after this line to fix bug + __children__[1].execute(__context__) + } + [ + Block: + [ + Literal: + literal1 + ] + Block: + [ + Code: + { + if x == 2: + __children__[0].execute(__context__) + } + [ + Block: + [ + Literal: + literal2 + ] + ] + ] + ] + ] + ] + ] + + >>> print(parse_template( + ... 'dummy.file', + ... text='%for x in range(10):\n% print(x)\n%end\njuicebox')) + Block: + [ + Code: + { + for x in range(10): + __children__[0].execute(__context__) + } + [ + Block: + [ + Code: {print(x)} [] + ] + ] + Literal: + juicebox + ] + + >>> print(parse_template('/dummy.file', text= + ... '''Nothing + ... % if x: + ... % for i in range(3): + ... ${i} + ... % end + ... % else: + ... THIS SHOULD NOT APPEAR IN THE OUTPUT + ... ''')) + Block: + [ + Literal: + Nothing + Code: + { + if x: + __children__[0].execute(__context__) + else: + __children__[1].execute(__context__) + } + [ + Block: + [ + Code: + { + for i in range(3): + __children__[0].execute(__context__) + } + [ + Block: + [ + Code: {(i)} [] + Literal: + + ] + ] + ] + Block: + [ + Literal: + THIS SHOULD NOT APPEAR IN THE OUTPUT + ] + ] + ] + + >>> print(parse_template('dummy.file', text='''% + ... %for x in y: + ... % print(y) + ... ''')) + Block: + [ + Code: + { + for x in y: + __children__[0].execute(__context__) + } + [ + Block: + [ + Code: {print(y)} [] + ] + ] + ] + + >>> print(parse_template('dummy.file', text='''% + ... %if x: + ... % print(y) + ... AAAA + ... %else: + ... BBBB + ... ''')) + Block: + [ + Code: + { + if x: + __children__[0].execute(__context__) + else: + __children__[1].execute(__context__) + } + [ + Block: + [ + Code: {print(y)} [] + Literal: + AAAA + ] + Block: + [ + Literal: + BBBB + ] + ] + ] + + >>> print(parse_template('dummy.file', text='''% + ... %if x: + ... % print(y) + ... AAAA + ... %# This is a comment + ... %else: + ... BBBB + ... ''')) + Block: + [ + Code: + { + if x: + __children__[0].execute(__context__) + # This is a comment + else: + __children__[1].execute(__context__) + } + [ + Block: + [ + Code: {print(y)} [] + Literal: + AAAA + ] + Block: + [ + Literal: + BBBB + ] + ] + ] + + >>> print(parse_template('dummy.file', text='''\ + ... %for x in y: + ... AAAA + ... %if x: + ... BBBB + ... %end + ... CCCC + ... ''')) + Block: + [ + Code: + { + for x in y: + __children__[0].execute(__context__) + } + [ + Block: + [ + Literal: + AAAA + Code: + { + if x: + __children__[0].execute(__context__) + } + [ + Block: + [ + Literal: + BBBB + ] + ] + Literal: + CCCC + ] + ] + ] + """ + return Block(ParseContext(filename, text)) + + +def execute_template( + ast, line_directive=_default_line_directive, **local_bindings): + r"""Return the text generated by executing the given template AST. + + Keyword arguments become local variable bindings in the execution context + + >>> root_directory = os.path.abspath('/') + >>> file_name = (root_directory + 'dummy.file').replace('\\', '/') + >>> ast = parse_template(file_name, text= + ... '''Nothing + ... % if x: + ... % for i in range(3): + ... ${i} + ... % end + ... % else: + ... THIS SHOULD NOT APPEAR IN THE OUTPUT + ... ''') + >>> out = execute_template(ast, + ... line_directive='//#sourceLocation(file: "%(file)s", line: %(line)d)', + ... x=1) + >>> out = out.replace(file_name, "DUMMY-FILE") + >>> print(out, end="") + //#sourceLocation(file: "DUMMY-FILE", line: 1) + Nothing + //#sourceLocation(file: "DUMMY-FILE", line: 4) + 0 + //#sourceLocation(file: "DUMMY-FILE", line: 4) + 1 + //#sourceLocation(file: "DUMMY-FILE", line: 4) + 2 + + >>> ast = parse_template(file_name, text= + ... '''Nothing + ... % a = [] + ... % for x in range(3): + ... % a.append(x) + ... % end + ... ${a} + ... ''') + >>> out = execute_template(ast, + ... line_directive='//#sourceLocation(file: "%(file)s", line: %(line)d)', + ... x=1) + >>> out = out.replace(file_name, "DUMMY-FILE") + >>> print(out, end="") + //#sourceLocation(file: "DUMMY-FILE", line: 1) + Nothing + //#sourceLocation(file: "DUMMY-FILE", line: 6) + [0, 1, 2] + + >>> ast = parse_template(file_name, text= + ... '''Nothing + ... % a = [] + ... % for x in range(3): + ... % a.append(x) + ... % end + ... ${a} + ... ''') + >>> out = execute_template(ast, + ... line_directive='#line %(line)d "%(file)s"', x=1) + >>> out = out.replace(file_name, "DUMMY-FILE") + >>> print(out, end="") + #line 1 "DUMMY-FILE" + Nothing + #line 6 "DUMMY-FILE" + [0, 1, 2] + """ + execution_context = ExecutionContext( + line_directive=line_directive, **local_bindings) + ast.execute(execution_context) + return ''.join(execution_context.result_text) + + +def main(): + import argparse + import sys + + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description='Generate Your Boilerplate!', epilog=''' + A GYB template consists of the following elements: + + - Literal text which is inserted directly into the output + + - %% or $$ in literal text, which insert literal '%' and '$' + symbols respectively. + + - Substitutions of the form ${}. The Python + expression is converted to a string and the result is inserted + into the output. + + - Python code delimited by %{...}%. Typically used to inject + definitions (functions, classes, variable bindings) into the + evaluation context of the template. Common indentation is + stripped, so you can add as much indentation to the beginning + of this code as you like + + - Lines beginning with optional whitespace followed by a single + '%' and Python code. %-lines allow you to nest other + constructs inside them. To close a level of nesting, use the + "%end" construct. + + - Lines beginning with optional whitespace and followed by a + single '%' and the token "end", which close open constructs in + %-lines. + + Example template: + + - Hello - + %{ + x = 42 + def succ(a): + return a+1 + }% + + I can assure you that ${x} < ${succ(x)} + + % if int(y) > 7: + % for i in range(3): + y is greater than seven! + % end + % else: + y is less than or equal to seven + % end + + - The End. - + + When run with "gyb -Dy=9", the output is + + - Hello - + + I can assure you that 42 < 43 + + y is greater than seven! + y is greater than seven! + y is greater than seven! + + - The End. - +''' + ) + parser.add_argument( + '-D', action='append', dest='defines', metavar='NAME=VALUE', + default=[], + help='''Bindings to be set in the template's execution context''') + + parser.add_argument( + 'file', type=argparse.FileType(), + help='Path to GYB template file (defaults to stdin)', nargs='?', + default=sys.stdin) + parser.add_argument( + '-o', dest='target', type=argparse.FileType('w'), + help='Output file (defaults to stdout)', default=sys.stdout) + parser.add_argument( + '--test', action='store_true', + default=False, help='Run a self-test') + parser.add_argument( + '--verbose-test', action='store_true', + default=False, help='Run a verbose self-test') + parser.add_argument( + '--dump', action='store_true', + default=False, help='Dump the parsed template to stdout') + parser.add_argument( + '--line-directive', + default=_default_line_directive, + help=''' + Line directive format string, which will be + provided 2 substitutions, `%%(line)d` and `%%(file)s`. + + Example: `#sourceLocation(file: "%%(file)s", line: %%(line)d)` + + The default works automatically with the `line-directive` tool, + which see for more information. + ''') + + args = parser.parse_args(sys.argv[1:]) + + if args.test or args.verbose_test: + import doctest + selfmod = sys.modules[__name__] + if doctest.testmod(selfmod, verbose=args.verbose_test or None).failed: + sys.exit(1) + + bindings = dict(x.split('=', 1) for x in args.defines) + ast = parse_template(args.file.name, args.file.read()) + if args.dump: + print(ast) + # Allow the template to open files and import .py files relative to its own + # directory + os.chdir(os.path.dirname(os.path.abspath(args.file.name))) + sys.path = ['.'] + sys.path + + args.target.write(execute_template(ast, args.line_directive, **bindings)) + + +if __name__ == '__main__': + main()