diff --git a/.github/actions/swift-test-docker/Dockerfile b/.github/actions/swift-test-docker/Dockerfile new file mode 100644 index 0000000..8aabf8b --- /dev/null +++ b/.github/actions/swift-test-docker/Dockerfile @@ -0,0 +1,5 @@ +FROM swift:5.4 + +COPY . . + +CMD ["swift", "test", "--enable-test-discovery"] diff --git a/.github/actions/swift-test-docker/action.yml b/.github/actions/swift-test-docker/action.yml new file mode 100644 index 0000000..32b521c --- /dev/null +++ b/.github/actions/swift-test-docker/action.yml @@ -0,0 +1,4 @@ +name: 'swift test' +runs: + using: 'docker' + image: 'Dockerfile' diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml new file mode 100644 index 0000000..4af628b --- /dev/null +++ b/.github/workflows/swift.yml @@ -0,0 +1,56 @@ +name: Swift + +on: + push: + branches: [ develop ] + pull_request: + branches: [ develop ] +env: + DEVELOPER_DIR: /Applications/Xcode_12.app/Contents/Developer + +jobs: + test: + + runs-on: macos-latest + env: + working_directory: ./ + + steps: + - uses: actions/checkout@v2 + - name: Build + run: swift build -v + working-directory: ${{env.working_directory}} + - name: Run tests + run: swift test -v + working-directory: ${{env.working_directory}} + + format: + + runs-on: macos-latest + + steps: + - name: Install Mint + run: brew install mint + - uses: actions/checkout@v2 + - name: Run SwiftFormat lint + run: mint run nicklockwood/SwiftFormat@0.46.2 --lint . + + repl: + + runs-on: macos-latest + env: + working_directory: ./Repl + + steps: + - uses: actions/checkout@v2 + - name: Build + run: swift build -v + working-directory: ${{env.working_directory}} + + test-docker: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: ./.github/actions/swift-test-docker diff --git a/Swona/.gitignore b/.gitignore similarity index 69% rename from Swona/.gitignore rename to .gitignore index 02c0875..98768d6 100644 --- a/Swona/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +.swiftpm + +/.idea + .DS_Store /.build /Packages diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000..d346e2a --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +5.3 diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 0000000..7c824a5 --- /dev/null +++ b/.swiftformat @@ -0,0 +1,36 @@ +--exclude Repl/Sources/Repl/Resources.swift + +--allman false +--binarygrouping 4,8 +--commas always +--comments indent +--decimalgrouping 3,6 +--elseposition same-line +--empty void +--exponentcase lowercase +--exponentgrouping disabled +--fractiongrouping disabled +--header ignore +--hexgrouping 4,8 +--hexliteralcase uppercase +--ifdef indent +--indent 4 +--indentcase false +--importgrouping testable-bottom +--linebreaks lf +--maxwidth none +--octalgrouping 4,8 +--operatorfunc spaced +--patternlet hoist +--ranges spaced +--self remove +--semicolons inline +--stripunusedargs always +--swiftversion 4.2 +--trimwhitespace always +--wraparguments preserve +--wrapcollections preserve +--modifierorder public,override + +--enable isEmpty +--disable redundantRawValues diff --git a/Dockerfile.swift b/Dockerfile similarity index 67% rename from Dockerfile.swift rename to Dockerfile index 2f1e60d..58a5951 100644 --- a/Dockerfile.swift +++ b/Dockerfile @@ -1,10 +1,8 @@ -FROM swift:5.0 +FROM swift:5.4 ENV APP_HOME /app WORKDIR $APP_HOME COPY . . -WORKDIR /app/Repl - CMD ["swift", "build"] diff --git a/Dockerfile.swiftformat b/Dockerfile.swiftformat new file mode 100644 index 0000000..866a4b0 --- /dev/null +++ b/Dockerfile.swiftformat @@ -0,0 +1,32 @@ +# MIT License +# Copyright (c) 2020 Takuhiro Muta +# https://github.com/417-72KI/Docker-Swift-Mint/blob/master/LICENSE + +ARG SWIFT_VERSION=5.4 +FROM swift:${SWIFT_VERSION} + +ARG MINT_REVISION=0.16.0 +ENV MINT_REVISION=${MINT_REVISION} + +ARG SWIFT_FORMAT_REVISION=0.46.2 +ENV SWIFT_FORMAT_REVISION=${SWIFT_FORMAT_REVISION} + +# Install Mint +RUN git clone -b "${MINT_REVISION}" --depth 1 "https://github.com/yonaskolb/Mint.git" ~/Mint && \ + cd ~/Mint && \ + swift build --disable-sandbox -c release && \ + mkdir -p /usr/local/bin && \ + cp -f .build/release/mint /usr/local/bin/mint && \ + cd && \ + rm -rf ~/Mint + +RUN mint install nicklockwood/SwiftFormat@${SWIFT_FORMAT_REVISION} + +ENV APP_HOME /app +WORKDIR $APP_HOME + +COPY . . + +WORKDIR /app/Repl + +CMD ["swiftformat", "."] diff --git a/LICENSE b/LICENSE index f4112d9..bc61f44 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,12 @@ MIT License -Original work Copyright (c) 2016 Juha Komulainen +Original work: +Copyright (c) 2016 Juha Komulainen +Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +Copyright (c) 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors +Copyright (c) 2020 Takuhiro Muta -This Swift port Copyright (c) 2019 Tony Lazenka and others listed in the -source files and in LICENSE-THIRD-PARTY +Modified work Copyright (c) 2019 Tony Lazenka Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -22,3 +25,590 @@ 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. + + +Additional licenses and acknowledgements: + +* Siilinkari - https://github.com/komu/siilinkari/blob/master/LICENSE + +Copyright (c) 2016 Juha Komulainen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +* Swift Package Manager - https://github.com/apple/swift-package-manager/blob/master/LICENSE.txt + +Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +Licensed under Apache License v2.0 with Runtime Library Exception +See http://swift.org/LICENSE.txt for license information +See http://swift.org/CONTRIBUTORS.txt for Swift project authors + + + 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. + + +* Kotlin - https://github.com/JetBrains/kotlin/tree/master/license + +https://github.com/JetBrains/kotlin/blob/master/license/COPYRIGHT.txt + +/* +* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. +* +* 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. +*/ + + +https://github.com/JetBrains/kotlin/blob/master/license/NOTICE.txt + +========================================================================= +== NOTICE file corresponding to the section 4 d of == +== the Apache License, Version 2.0, == +== in this case for the Kotlin Compiler distribution. == +========================================================================= + +Kotlin Compiler +Copyright 2010-2020 JetBrains s.r.o and respective authors and developers + + +https://github.com/JetBrains/kotlin/blob/master/license/LICENSE.txt + + 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 2000-2018 JetBrains s.r.o. + + 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. + + +* Docker-Swift-Mint - https://github.com/417-72KI/Docker-Swift-Mint/blob/master/LICENSE + +MIT License + +Copyright (c) 2020 Takuhiro Muta + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +* Linenoise-Swift - https://github.com/andybest/linenoise-swift/blob/master/LICENSE + +Copyright (c) 2017, Andy Best +Copyright (c) 2010-2014, Salvatore Sanfilippo +Copyright (c) 2010-2013, Pieter Noordhuis + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +* FakeBundle - https://github.com/zweigraf/FakeBundle/blob/master/LICENSE + +Copyright (c) 2017 Luis Reisewitz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +* SwiftFormat - https://github.com/nicklockwood/SwiftFormat/blob/master/LICENSE.md + +MIT License + +Copyright (c) 2016 Nick Lockwood + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSE-THIRD-PARTY b/LICENSE-THIRD-PARTY deleted file mode 100644 index 185ee2b..0000000 --- a/LICENSE-THIRD-PARTY +++ /dev/null @@ -1,468 +0,0 @@ -* Siilinkari - https://github.com/komu/siilinkari - -Copyright (c) 2016 Juha Komulainen - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - -* Swift Package Manager - https://github.com/apple/swift-package-manager - - 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. - - -* Sourcery - https://github.com/krzysztofzablocki/Sourcery - -MIT License - -Copyright (c) 2016 Krzysztof Zabłocki - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - -* Kotlin - https://github.com/JetBrains/kotlin - - - 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 2000-2018 JetBrains s.r.o. - - 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. - diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1b4fac1 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +.PHONY: test +test: + swift test + +.PHONY: format +format: + mint run nicklockwood/SwiftFormat@0.46.2 . + +.PHONY: test-docker +test-docker: + docker-compose run --rm tests + +.PHONY: format-docker +format-docker: + docker-compose run --rm format diff --git a/Swona/Package.swift b/Package.swift similarity index 58% rename from Swona/Package.swift rename to Package.swift index 8b2c911..e7be95e 100644 --- a/Swona/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.0 +// swift-tools-version:5.3 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,18 +6,21 @@ import PackageDescription let package = Package( name: "Swona", products: [ - .library( - name: "Swona", - targets: ["Swona"]), + .library( + name: "Swona", + targets: ["Swona"] + ), ], dependencies: [ ], targets: [ .target( name: "Swona", - dependencies: []), - .testTarget( + dependencies: [] + ), + .testTarget( name: "SwonaTests", - dependencies: ["Swona"]), + dependencies: ["Swona"] + ), ] ) diff --git a/README.md b/README.md index 9932deb..4e28e7d 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,40 @@ A Swift port of the lovely [Siilinkari](https://github.com/komu/siilinkari). +## Launch the REPL + +`cd Repl && swift run && cd -` + +_or_ + +`docker-compose run --rm repl` + +### Example + +``` +var a = stringArrayOfSize(3, "") +var done = false +var i = 0 + +while (!done) { stringArraySet(a, i, "item" + i); i = i + 1; if (i==stringArrayLength(a)) done=true } + +stringArrayGet(a, 2) + +fun pow(a: Int, n: Int): Int = unless (n == 0 ) a * pow(a, n-1) else 1 + +pow(2, 8) +``` + +More in the [Prelude](Resources/prelude.sk). ## Run the tests +`swift test` + +_or_ + `docker-compose run --rm tests` -## Launch the REPL +# Acknowledgements -`docker-compose run --rm repl` +Juha Komulainen and others listed in the [LICENSE](LICENSE). diff --git a/Repl/.gitignore b/Repl/.gitignore index 02c0875..98768d6 100644 --- a/Repl/.gitignore +++ b/Repl/.gitignore @@ -1,3 +1,7 @@ +.swiftpm + +/.idea + .DS_Store /.build /Packages diff --git a/Repl/Package.resolved b/Repl/Package.resolved new file mode 100644 index 0000000..ef3169f --- /dev/null +++ b/Repl/Package.resolved @@ -0,0 +1,25 @@ +{ + "object": { + "pins": [ + { + "package": "LineNoise", + "repositoryURL": "https://github.com/andybest/linenoise-swift.git", + "state": { + "branch": null, + "revision": "afc897c02b030af7333105c4fedcd843a8339238", + "version": "0.0.3" + } + }, + { + "package": "Nimble", + "repositoryURL": "https://github.com/Quick/Nimble.git", + "state": { + "branch": null, + "revision": "e9d769113660769a4d9dd3afb855562c0b7ae7b0", + "version": "7.3.4" + } + } + ] + }, + "version": 1 +} diff --git a/Repl/Package.swift b/Repl/Package.swift index d70f516..087a4e2 100644 --- a/Repl/Package.swift +++ b/Repl/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.0 +// swift-tools-version:5.3 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,11 +6,13 @@ import PackageDescription let package = Package( name: "Repl", dependencies: [ - .package(path: "../Swona"), + .package(name: "Swona", path: "../"), + .package(name: "LineNoise", url: "https://github.com/andybest/linenoise-swift.git", .exact("0.0.3")), ], targets: [ .target( name: "Repl", - dependencies: ["Swona"]), + dependencies: ["Swona", "LineNoise"] + ), ] ) diff --git a/Repl/Sources/Repl/Resources.swift b/Repl/Sources/Repl/Resources.swift new file mode 100644 index 0000000..ecbc028 --- /dev/null +++ b/Repl/Sources/Repl/Resources.swift @@ -0,0 +1,60 @@ +// +// FakeBundle.swift +// +// Generated by FakeBundle +// See https://github.com/zweigraf/FakeBundle +// + +import Foundation + +protocol FileType { + var isDirectory: Bool { get } + var filename: String { get } + func export(to path: String) throws +} +protocol File: FileType { + var contentsBase64: String { get } +} +extension File { + var isDirectory: Bool { + return false + } + var contents: Data? { + return Data(base64Encoded: contentsBase64) + } + + func export(to path: String) throws { + guard let contents = contents else { return } + let originalUrl = URL(fileURLWithPath: path) + let myUrl = originalUrl.appendingPathComponent(filename) + try contents.write(to: myUrl) + } +} +protocol Directory: FileType { + var children: [FileType] { get } +} +extension Directory { + var isDirectory: Bool { + return true + } + func export(to path: String) throws { + let originalUrl = URL(fileURLWithPath: path) + let myUrl = originalUrl.appendingPathComponent(filename) + try FileManager.default.createDirectory(at: myUrl, withIntermediateDirectories: true, attributes: nil) + try children.forEach { try $0.export(to: myUrl.path) } + } +} +class Resources: Directory { + var filename: String = "Resources" + lazy var children: [FileType] = { + return [Prelude_Sk()] + }() + + class Prelude_Sk: File { + var filename: String = "prelude.sk" + lazy var contentsBase64: String = { + return "ZnVuIHNxdWFyZSh4OiBJbnQpID0geCAqIHgKZnVuIGN1YmUoeDogSW50KSA9IHggKiB4ICogeAoKZnVuIGZpYihpOiBJbnQpOiBJbnQgPQogICAgaWYgKGkgPT0gMCB8fCBpID09IDEpCiAgICAgICAgaQogICAgZWxzZQogICAgICAgIGZpYihpLTEpICsgZmliKGktMikK" + }() +} + +} diff --git a/Repl/Sources/Repl/main.swift b/Repl/Sources/Repl/main.swift index 341484a..c8a2930 100644 --- a/Repl/Sources/Repl/main.swift +++ b/Repl/Sources/Repl/main.swift @@ -1,64 +1,133 @@ +import Foundation +import LineNoise import Swona /** * Implementation of Read-Eval-Print loop. */ +extension Bool { + var statusText: String { + if self { + return "on" + } else { + return "off" + } + } +} + +extension Evaluator { + func loadResource(file: String) throws { + let f = Resources().children.compactMap { $0 as? File }.first { $0.filename == file } + + guard let data = f?.contents, let source = String(data: data, encoding: .utf8) else { + print("Error loading file: \(file)") + return + } + + try loadResource(source: source, file: file) + } +} + +// Modified from Kotlin (Apache License, Version 2.0). See LICENSE-THIRD-PARTY in this repo +func measureTimeMillis(block: () throws -> Void) throws -> UInt64 { + let start = DispatchTime.now() + try block() + return (DispatchTime.now().uptimeNanoseconds - start.uptimeNanoseconds) / 1_000_000 +} + +let lineNoise = LineNoise() +lineNoise.preserveHistoryEdits = true + let evaluator = Evaluator() try registerRuntimeFunctions(evaluator: evaluator) +lineNoise.setCompletionCallback { + buffer in + let lowercasedBuffer = buffer.lowercased() + return evaluator.bindingsNames().filter { $0.lowercased().hasPrefix(lowercasedBuffer) } +} + +try evaluator.loadResource(file: "prelude.sk") + print("Welcome to Swona, a Swift port of Siilinkari! Enjoy your stay or type 'exit' to get out.") + +var showElapsedTime = false while true { - print(">>> ", terminator: "") - guard var line = readLine() else { + var line: String + do { + line = try lineNoise.getLine(prompt: ">>> ") + } catch LinenoiseError.CTRL_C { + exit(EXIT_SUCCESS) + } catch { break } + print() if line == "" { continue } if line == "exit" { break } - + + lineNoise.addHistory(line) + + if line == ":trace" { + evaluator.trace = !evaluator.trace + print("trace \(evaluator.trace.statusText)") + continue + } else if line == ":time" { + showElapsedTime = !showElapsedTime + print("time \(showElapsedTime.statusText)") + continue + } else if line == ":optimize" { + evaluator.optimize = !evaluator.optimize + print("optimize \(evaluator.optimize.statusText)") + continue + } + do { - while true { - do { - let evaluation = try evaluator.evaluate(code: line) - let value = evaluation.value - let type = evaluation.type - if evaluation.type != .unit { - print("\(type) = \(value.repr())") - } - break - } - catch { - if error is UnexpectedEndOfInputException { - print("... ", terminator: "") - guard let newLine = readLine() else { - break + if line.hasPrefix(":dump ") { + print(try evaluator.dump(code: String(line.dropFirst(":dump ".count)))) + } else { + while true { + do { + let elapsedTime = try measureTimeMillis { + let evaluation = try evaluator.evaluate(code: line) + let value = evaluation.value + let type = evaluation.type + if evaluation.type != .unit { + print("\(type) = \(value.repr())") + } + } + if showElapsedTime { + print("time: \(elapsedTime)ms") + } + break + } catch { + if error is UnexpectedEndOfInputException { + print("... ", terminator: "") + guard let newLine = readLine() else { + break + } + line = line + "\n" + newLine + } else { + throw error } - line = line + "\n" + newLine - } - else { - throw error } } } - } - catch { + } catch { if let e = error as? SyntaxErrorException { print("Syntax error: \(e.errorMessage)") print(e.sourceLocation.toLongString()) - } - else if let e = error as? TypeCheckException { + } else if let e = error as? TypeCheckException { print("Type checking failed: \(e.errorMessage)") print(e.sourceLocation.toLongString()) - } - else { + } else { print(error) } } - } print("Thank you for visiting Swona, have a nice day!") diff --git a/Resources/prelude.sk b/Resources/prelude.sk new file mode 100644 index 0000000..6734d5e --- /dev/null +++ b/Resources/prelude.sk @@ -0,0 +1,8 @@ +fun square(x: Int) = x * x +fun cube(x: Int) = x * x * x + +fun fib(i: Int): Int = + if (i == 0 || i == 1) + i + else + fib(i-1) + fib(i-2) diff --git a/Swona/Sources/Swona/AST.swift b/Sources/Swona/AST.swift similarity index 89% rename from Swona/Sources/Swona/AST.swift rename to Sources/Swona/AST.swift index 8d693eb..e176d26 100644 --- a/Swona/Sources/Swona/AST.swift +++ b/Sources/Swona/AST.swift @@ -14,41 +14,41 @@ public indirect enum Expression: CustomStringConvertible { /** Reference to a variable. */ case ref(name: String, location: SourceLocation) - + /** Literal value. */ case lit(value: Value, location: SourceLocation) - + /** Logical not. */ case not(exp: Expression, location: SourceLocation) - + /** Function call. */ - case call(func: Expression, args: Array) - + case call(func: Expression, args: [Expression]) + case binary(Binary) - + /** Binary operators. */ public enum Binary: CustomStringConvertible { /** lhs + rhs */ case plus(lhs: Expression, rhs: Expression, location: SourceLocation) - + /** lhs - rhs */ case minus(lhs: Expression, rhs: Expression, location: SourceLocation) - + /** lhs * rhs */ case multiply(lhs: Expression, rhs: Expression, location: SourceLocation) - + /** lhs / rhs */ case divide(lhs: Expression, rhs: Expression, location: SourceLocation) - + /** lhs && rhs */ case and(lhs: Expression, rhs: Expression, location: SourceLocation) - + /** lhs || rhs */ case or(lhs: Expression, rhs: Expression, location: SourceLocation) - + /** =, !=, <, >, <=, >= */ case relational(op: RelationalOp, lhs: Expression, rhs: Expression, location: SourceLocation) - + public var location: SourceLocation { switch self { case let .plus(_, _, location), @@ -61,7 +61,7 @@ public indirect enum Expression: CustomStringConvertible { return location } } - + public var description: String { switch self { case let .plus(lhs, rhs, _): @@ -81,22 +81,22 @@ public indirect enum Expression: CustomStringConvertible { } } } - + /** Assignment to a variable. */ case assign(variable: String, expression: Expression, location: SourceLocation) - + /** Definition of a variable. */ case `var`(variable: String, expression: Expression, mutable: Bool, location: SourceLocation) - + /** If-statement with optional else clause. */ case `if`(condition: Expression, consequent: Expression, alternative: Expression?, location: SourceLocation) - + /** While-statement. */ case `while`(condition: Expression, body: Expression, location: SourceLocation) - + /** List of statements */ - case `expressionList`(expressions: [Expression], location: SourceLocation) - + case expressionList(expressions: [Expression], location: SourceLocation) + public var location: SourceLocation { switch self { case let .assign(_, _, location): @@ -115,9 +115,13 @@ public indirect enum Expression: CustomStringConvertible { return location case let .ref(_, location): return location + case let .var(_, _, _, location): + return location + case let .while(_, _, location): + return location } } - + public var description: String { switch self { case let .ref(name, _): @@ -130,24 +134,23 @@ public indirect enum Expression: CustomStringConvertible { return "[Call \(`func`.description) \(args.description)]" case let .assign(variable, expression, _): return "[Assign \(variable.description) \(expression.description)]" - case let .`if`(condition, consequent, alternative, _): + case let .if(condition, consequent, alternative, _): return "[If \(condition) \(consequent) \(alternative?.description ?? "[]")]" - case let .`while`(condition, body, _): + case let .while(condition, body, _): return "[While \(condition) \(body)]" - case let .`expressionList`(expressions, _): + case let .expressionList(expressions, _): return "[ExpressionList \(expressions)]" - case let .`var`(variable, expression, mutable, _): - return "[\((mutable) ? "Var" : "Val") \(variable) \(expression)]" + case let .var(variable, expression, mutable, _): + return "[\(mutable ? "Var" : "Val") \(variable) \(expression)]" case let .binary(value): return value.description } } - } public struct FunctionDefinition: CustomStringConvertible { let name: String - let args: Array<(String, Type)> + let args: [(String, Type)] let returnType: Type? let body: Expression @@ -155,7 +158,6 @@ public struct FunctionDefinition: CustomStringConvertible { let argsDescription = args.map { "(\($0.0), \($0.1))" }.joined(separator: ", ") return "FunctionDefinition(name=\(name), args=[\(argsDescription)], returnType=\(returnType?.description ?? "null"), body=\(body.description))" } - } public enum RelationalOp: String, CustomStringConvertible { @@ -165,8 +167,6 @@ public enum RelationalOp: String, CustomStringConvertible { case lessThanOrEqual = "<=" case greaterThan = ">" case greaterThanOrEqual = ">=" - - public var description: String { return self.rawValue } -} - + public var description: String { rawValue } +} diff --git a/Sources/Swona/Bridge.swift b/Sources/Swona/Bridge.swift new file mode 100644 index 0000000..57a8990 --- /dev/null +++ b/Sources/Swona/Bridge.swift @@ -0,0 +1,119 @@ +@dynamicMemberLookup +public struct Bridge { + public init() throws { + evaluator = Evaluator(trace: false) + try registerRuntimeFunctions(evaluator: evaluator) + } + + public let evaluator: Evaluator + + public subscript(dynamicMember member: String) -> Value? { + get { + guard let bindingReference = evaluator.globalTypeEnvironment.bindings[member] else { + return nil + } + return evaluator.globalData[bindingReference.binding.index] + } + + nonmutating set { + evaluator.globalTypeEnvironment.unbind(name: member) + if let newValue = newValue { + try! evaluator.bind(name: member, value: newValue) + } + } + } + + public subscript(dynamicMember member: String) -> Expression { + member.ref + } + + public subscript(dynamicMember member: String) -> BridgeRuntimeFunction { + BridgeRuntimeFunction(evaluator: evaluator, name: member) + } +} + +@dynamicCallable +public struct BridgeRuntimeFunction { + let evaluator: Evaluator + let name: String + + public init(evaluator: Evaluator, name: String) { + self.evaluator = evaluator + self.name = name + } + + @discardableResult + public func dynamicallyCall(withArguments arguments: [Expression]) throws -> Value { + let expression = Expression.call(func: .ref(name: name, location: bridgeSourceLocation()), args: arguments) + + let typeChecked = try expression.typeCheck(env: evaluator.globalTypeEnvironment) + let translated = try evaluator.translate(exp: typeChecked) + let evaluated = try evaluator.evaluateSegment(segment: translated) + + return EvaluationResult(value: evaluated, type: typeChecked.type).value + } +} + +extension String { + public var lit: Expression { + Expression.lit(value: value, location: bridgeSourceLocation()) + } + + public var ref: Expression { + Expression.ref(name: self, location: bridgeSourceLocation()) + } +} + +extension Int { + public var lit: Expression { + Expression.lit(value: value, location: bridgeSourceLocation()) + } +} + +func bridgeSourceLocation(file: String = #file) -> SourceLocation { + SourceLocation(file: file, line: 0, column: 0, lineText: "") +} + +extension Value.Function { + public func callAsFunction(_ values: Value...) -> Any { + switch self { + case .compound: + fatalError("Cannot call compound function") + case let .native(`func`, _, _): + guard let result = try? `func`.function(values) else { + fatalError("Could not evaluate function") + } + return result + } + } +} + +extension Value: ExpressibleByStringLiteral { + public init(stringLiteral value: String) { + self = .string(value: value) + } +} + +extension Value: ExpressibleByIntegerLiteral { + public init(integerLiteral value: Int) { + self = .integer(value: value) + } +} + +extension Value: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = .bool(value: value) + } +} + +extension Value: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: Value...) { + let types = Set(elements.map(\.type)) + + guard let type = types.first, types.count == 1 else { + fatalError("Only arrays of a single type are supported") + } + + self = .array(elements: .init(array: elements), elementType: type) + } +} diff --git a/Swona/Sources/Swona/Env.swift b/Sources/Swona/Env.swift similarity index 80% rename from Swona/Sources/Swona/Env.swift rename to Sources/Swona/Env.swift index 1ece05b..a445f7b 100644 --- a/Swona/Sources/Swona/Env.swift +++ b/Sources/Swona/Env.swift @@ -10,7 +10,7 @@ public enum Binding: CustomStringConvertible { case global(name: String, type: Type, index: Int, mutable: Bool) case local(name: String, type: Type, index: Int, mutable: Bool) case argument(name: String, type: Type, index: Int) - + public var index: Int { switch self { case let .global(_, _, index, _), @@ -19,7 +19,7 @@ public enum Binding: CustomStringConvertible { return index } } - + public var mutable: Bool { switch self { case let .global(_, _, _, mutable), @@ -29,7 +29,7 @@ public enum Binding: CustomStringConvertible { return false } } - + public var description: String { switch self { case let .global(name, _, index, _): @@ -40,7 +40,7 @@ public enum Binding: CustomStringConvertible { return "[Argument \(index) (\(name))]" } } - + public var name: String { switch self { case let .global(name, _, _, _), @@ -49,8 +49,8 @@ public enum Binding: CustomStringConvertible { return name } } - - public var type : Type { + + public var type: Type { switch self { case let .argument(_, type, _), let .global(_, type, _, _), @@ -62,23 +62,23 @@ public enum Binding: CustomStringConvertible { public class BindingReference: Hashable, CustomStringConvertible { public static func == (lhs: BindingReference, rhs: BindingReference) -> Bool { - return ObjectIdentifier(lhs) == ObjectIdentifier(rhs) + ObjectIdentifier(lhs) == ObjectIdentifier(rhs) } - + public func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(self).hashValue) } - + let binding: Binding - + init(binding: Binding) { self.binding = binding } - - public var type: Type { return binding.type } - public var name: String { return binding.name } - public var description: String { return binding.description } - public var mutable: Bool { return binding.mutable } + + public var type: Type { binding.type } + public var name: String { binding.name } + public var description: String { binding.description } + public var mutable: Bool { binding.mutable } } /** @@ -90,7 +90,7 @@ struct VariableAlreadyBoundException: Error { let message: String init(name: String) { - self.message = "variable already bound: \(name)" + message = "variable already bound: \(name)" } } @@ -98,16 +98,15 @@ struct VariableAlreadyBoundException: Error { * Mapping from variable names to [Binding]s. */ public protocol StaticEnvironment: AnyObject { - var parent: StaticEnvironment? { get } - - var bindings: Dictionary { get set } - + + var bindings: [String: BindingReference] { get set } + /** * Create a new binding to be installed in this environment. */ func newBinding(name: String, type: Type, mutable: Bool) -> BindingReference - + /** * Returns a new child scope for current environment. * @@ -123,11 +122,9 @@ extension StaticEnvironment { * Returns the binding of given variable. */ subscript(name: String) -> BindingReference? { - get { - return bindings[name] ?? parent?[name] - } + bindings[name] ?? parent?[name] } - + /** * Create a new binding for variable having given [name] and [type]. * For global environment, this creates a new entry in the global mappings. @@ -139,24 +136,22 @@ extension StaticEnvironment { if bindings.keys.contains(name) { throw VariableAlreadyBoundException(name: name) } - + let binding = newBinding(name: name, type: type, mutable: mutable) bindings[name] = binding return binding } - + /** * Removes this binding from global environment, allowing it to be reused. */ func unbind(name: String) { bindings.removeValue(forKey: name) } - - + func bindingNames() -> Set { - return Set(bindings.keys) + Set(bindings.keys) } - } /** @@ -164,28 +159,26 @@ extension StaticEnvironment { */ public class GlobalStaticEnvironment: StaticEnvironment { private var bindingIndexSequence = 0 - - public var parent: StaticEnvironment? { return nil } - - public var bindings: Dictionary = [:] - - public init() { - } - + + public var parent: StaticEnvironment? { nil } + + public var bindings: [String: BindingReference] = [:] + + public init() {} + public func newBinding(name: String, type: Type, mutable: Bool) -> BindingReference { let result = BindingReference(binding: Binding.global(name: name, type: type, index: bindingIndexSequence, mutable: mutable)) bindingIndexSequence += 1 return result } - + public func newScope() -> StaticEnvironment { - return LocalFrameEnvironment(parent: self) + LocalFrameEnvironment(parent: self) } - - public func newScope(args: Array<(String, Type)>) -> StaticEnvironment { - return LocalFrameEnvironment(parent: self, args: args) + + public func newScope(args: [(String, Type)]) -> StaticEnvironment { + LocalFrameEnvironment(parent: self, args: args) } - } /** @@ -196,34 +189,32 @@ public class GlobalStaticEnvironment: StaticEnvironment { */ class LocalFrameEnvironment: StaticEnvironment { var parent: StaticEnvironment? - - var bindings: Dictionary = [:] - + + var bindings: [String: BindingReference] = [:] + private var bindingIndexSequence = 0 - + init(parent: StaticEnvironment) { self.parent = parent } - - init(parent: StaticEnvironment, args: Array<(String, Type)>) { + + init(parent: StaticEnvironment, args: [(String, Type)]) { self.parent = parent args.enumerated().forEach { index, pair in let (name, type) = pair bindings[name] = BindingReference(binding: Binding.argument(name: name, type: type, index: index)) } - } - + func newBinding(name: String, type: Type, mutable: Bool) -> BindingReference { let result = BindingReference(binding: Binding.local(name: name, type: type, index: bindingIndexSequence, mutable: mutable)) bindingIndexSequence += 1 return result } - + func newScope() -> StaticEnvironment { - return LocalFrameChildScope(parent: self, frame: self) + LocalFrameChildScope(parent: self, frame: self) } - } /** @@ -232,23 +223,21 @@ class LocalFrameEnvironment: StaticEnvironment { */ class LocalFrameChildScope: StaticEnvironment { var parent: StaticEnvironment? - - var bindings: Dictionary = [:] - + + var bindings: [String: BindingReference] = [:] + var frame: LocalFrameEnvironment - + init(parent: StaticEnvironment, frame: LocalFrameEnvironment) { self.parent = parent self.frame = frame } - + func newBinding(name: String, type: Type, mutable: Bool) -> BindingReference { - return frame.newBinding(name: name, type: type, mutable: mutable) + frame.newBinding(name: name, type: type, mutable: mutable) } - + func newScope() -> StaticEnvironment { - return LocalFrameChildScope(parent: self, frame: frame) + LocalFrameChildScope(parent: self, frame: frame) } - } - diff --git a/Sources/Swona/Extensions.swift b/Sources/Swona/Extensions.swift new file mode 100644 index 0000000..ab5d3ef --- /dev/null +++ b/Sources/Swona/Extensions.swift @@ -0,0 +1,89 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the + * https://github.com/JetBrains/kotlin/blob/master/license/LICENSE.txt file. + * + * The below code was modified from the original. + */ + +extension String { + // Modified from Kotlin (Apache License, Version 2.0). See LICENSE in this repo + public func lines() -> [Substring] { + split(separator: "\n", omittingEmptySubsequences: false) + } + + // Modified from Kotlin (Apache License, Version 2.0). See LICENSE in this repo + public func substring(startOffset: Int, endOffset: Int) -> String { + let substringStartIndex = index(startIndex, offsetBy: startOffset) + let substringEndIndex = index(startIndex, offsetBy: endOffset) + return String(self[substringStartIndex ..< substringEndIndex]) + } + + // Modified from Kotlin (Apache License, Version 2.0). See LICENSE in this repo + public func padEnd(count: Int, padChar: UnicodeScalar = " ") -> String { + if count <= self.count { + return substring(startOffset: 0, endOffset: self.count) + } + + var result = self + for _ in 1 ... (count - self.count) { + result += String(padChar) + } + return result + } + + // Modified from Kotlin (Apache License, Version 2.0). See LICENSE in this repo + public func padStart(count: Int, padChar: UnicodeScalar = " ") -> String { + if count <= self.count { + return substring(startOffset: 0, endOffset: self.count) + } + var result = "" + for _ in 1 ... (count - self.count) { + result += String(padChar) + } + return result + self + } +} + +extension Array { + // Modified from Kotlin (Apache License, Version 2.0). See LICENSE in this repo + func subList(fromIndex: Int, toIndex: Int) -> Array { + Array(self[fromIndex ..< toIndex]) + } + + // Modified from Kotlin (Apache License, Version 2.0). See LICENSE in this repo + func sumBy(selector: (Element) -> Int) -> Int { + var sum: Int = 0 + for element in self { + sum += selector(element) + } + return sum + } + + // Modified from Kotlin (Apache License, Version 2.0). See LICENSE in this repo + func singleOrNull() -> Element? { + if count == 1 { + return self[0] + } else { + return nil + } + } + + // Modified from Kotlin (Apache License, Version 2.0). See LICENSE in this repo + public func single() -> Element { + switch count { + case 0: + fatalError("List is empty.") + case 1: + return self[0] + default: + fatalError("List has more than one element.") + } + } +} + +extension Int { + public init(_ value: Bool) { + self = value ? 1 : 0 + } +} diff --git a/Swona/Sources/Swona/Lexer.swift b/Sources/Swona/Lexer.swift similarity index 75% rename from Swona/Sources/Swona/Lexer.swift rename to Sources/Swona/Lexer.swift index e4bcbef..f7deea0 100644 --- a/Swona/Sources/Swona/Lexer.swift +++ b/Sources/Swona/Lexer.swift @@ -18,8 +18,8 @@ public struct SourceLocation: Equatable, CustomStringConvertible { /** * Returns single line representation of the location. */ - public var description: String { return "[\(file.description):\(line.description):\(column.description)]" } - + public var description: String { "[\(file.description):\(line.description):\(column.description)]" } + /** * Returns two line representation of the location. */ @@ -37,14 +37,14 @@ public struct SourceLocation: Equatable, CustomStringConvertible { */ open class SyntaxErrorException: Error, CustomStringConvertible { public let errorMessage: String, sourceLocation: SourceLocation - + init(errorMessage: String, sourceLocation: SourceLocation) { self.errorMessage = errorMessage self.sourceLocation = sourceLocation } - + public var description: String { - return "\(errorMessage)\n\(sourceLocation.toLongString())" + "\(errorMessage)\n\(sourceLocation.toLongString())" } } @@ -59,7 +59,6 @@ public class UnexpectedEndOfInputException: SyntaxErrorException { } } - /** * Tokens are the indivisible building blocks of source code. * @@ -72,44 +71,45 @@ public class UnexpectedEndOfInputException: SyntaxErrorException { * * @see TokenInfo */ -public enum Token: CustomStringConvertible { +public enum Token: Equatable, CustomStringConvertible { /** * Identifier such as variable, method or class name. */ case identifier(name: String) - + /** * Literal value, e.g. `42`, `"foo"`, or `true`. */ case literal(value: Value) - + /** * Reserved word in the language. */ case keyword(Keyword) - + /** * Operators. */ case `operator`(Operator) - + /** * General punctuation. */ case punctuation(Punctuation) - - public enum Keyword : String, CustomStringConvertible { + + public enum Keyword: String, CustomStringConvertible { case `else` = "else" case fun = "fun" case `if` = "if" + case unless = "unless" case `var` = "var" case val = "val" case `while` = "while" - - public var description: String { return self.rawValue.description } + + public var description: String { rawValue.description } } - - public enum Operator : String, CaseIterable, CustomStringConvertible { + + public enum Operator: String, CaseIterable, CustomStringConvertible { case plus = "+" case minus = "-" case multiply = "*" @@ -123,11 +123,11 @@ public enum Token: CustomStringConvertible { case greaterThanOrEqual = ">=" case and = "&&" case or = "||" - - public var description: String { return self.rawValue.description } + + public var description: String { rawValue.description } } - - public enum Punctuation : String, CaseIterable, CustomStringConvertible { + + public enum Punctuation: String, CaseIterable, CustomStringConvertible { case leftParen = "(" case rightParen = ")" case leftBrace = "{" @@ -136,9 +136,10 @@ public enum Token: CustomStringConvertible { case colon = ":" case semicolon = ";" case comma = "," - - public var description: String { return "'\(self.rawValue.description)'" } + + public var description: String { "'\(rawValue.description)'" } } + public var description: String { switch self { case let .identifier(name): @@ -153,7 +154,6 @@ public enum Token: CustomStringConvertible { return value.description } } - } /** @@ -162,7 +162,7 @@ public enum Token: CustomStringConvertible { public struct TokenInfo: CustomStringConvertible { public let token: Token public let location: SourceLocation - public var description: String { return "[TokenInfo \(token) \(location)]" } + public var description: String { "[TokenInfo \(token) \(location)]" } } /** @@ -184,115 +184,102 @@ public class Lexer { * by skipping all whitespace after each token whenever a token is read. */ private var position = 0 - + /** Current line number. Used for [SourceLocation]. */ private var line = 1 - + /** Current column number. Used for [SourceLocation]. */ private var column = 1 - - private let source: Array + + private let source: [Character] private let file: String - + /** Lines of the file. Used for [SourceLocation]. */ - private let lines: Array - + private let lines: [Substring] + public init(source: String, file: String = "") throws { self.source = source.unicodeScalars.map { Character(UnicodeScalar($0)) } lines = source.lines() self.file = file - try self.skipWhitespace() + try skipWhitespace() } - + /** * Does the source contain more tokens? */ public var hasMore: Bool { - return position < source.count + position < source.count } - + /** * Read the next [Token] from the source, along with its [SourceLocation]. */ public func readToken() throws -> TokenInfo { let location = currentSourceLocation - + let ch = try peekChar() - + let token: Token = try { - - if (ch.isLetter) { return try readSymbol() } - else if (ch.isWholeNumber) { return try readNumber() } - else if (ch == "\"") { return try readString() } - else if (try readIf(ch: "+")) { return .operator(.plus) } - else if (try readIf(ch: "-")) { return .operator(.minus) } - else if (try readIf(ch: "*")) { return .operator(.multiply) } - else if (try readIf(ch: "/")) { return .operator(.divide) } - else if (try readIf(ch: "(")) { return .punctuation(.leftParen) } - else if (try readIf(ch: ")")) { return .punctuation(.rightParen) } - else if (try readIf(ch: "{")) { return .punctuation(.leftBrace) } - else if (try readIf(ch: "}")) { return .punctuation(.rightBrace) } - else if (try readIf(ch: ":")) { return .punctuation(.colon) } - else if (try readIf(ch: ";")) { return .punctuation(.semicolon) } - else if (try readIf(ch: ",")) { return .punctuation(.comma) } - else if (try readIf(ch: "=")) { - if (try readIf(ch: "=")) { + if ch.isLetter { return try readSymbol() } + else if ch.isWholeNumber { return try readNumber() } + else if ch == "\"" { return try readString() } + else if try readIf(ch: "+") { return .operator(.plus) } + else if try readIf(ch: "-") { return .operator(.minus) } + else if try readIf(ch: "*") { return .operator(.multiply) } + else if try readIf(ch: "/") { return .operator(.divide) } + else if try readIf(ch: "(") { return .punctuation(.leftParen) } + else if try readIf(ch: ")") { return .punctuation(.rightParen) } + else if try readIf(ch: "{") { return .punctuation(.leftBrace) } + else if try readIf(ch: "}") { return .punctuation(.rightBrace) } + else if try readIf(ch: ":") { return .punctuation(.colon) } + else if try readIf(ch: ";") { return .punctuation(.semicolon) } + else if try readIf(ch: ",") { return .punctuation(.comma) } + else if try readIf(ch: "=") { + if try readIf(ch: "=") { return .operator(.equalEqual) - } - else { + } else { return .punctuation(.equal) } - } - else if (try readIf(ch: "!")) { - if (try readIf(ch: "=")) { + } else if try readIf(ch: "!") { + if try readIf(ch: "=") { return .operator(.notEqual) - } - else { + } else { return .operator(.not) } - } - else if (try readIf(ch: "<")) { - if (try readIf(ch: "=")) { + } else if try readIf(ch: "<") { + if try readIf(ch: "=") { return .operator(.lessThanOrEqual) - } - else { + } else { return .operator(.lessThan) } - } - else if (try readIf(ch: ">")) { - if (try readIf(ch: "=")) { + } else if try readIf(ch: ">") { + if try readIf(ch: "=") { return .operator(.greaterThanOrEqual) - } - else { + } else { return .operator(.greaterThan) } - } - else if (try readIf(ch: "&")) { - if (try readIf(ch: "&")) { + } else if try readIf(ch: "&") { + if try readIf(ch: "&") { return .operator(.and) - } - else { + } else { throw fail(message: "got '&', did you mean '&&'?") } - } - else if (try readIf(ch: "|")) { - if (try readIf(ch: "|")) { + } else if try readIf(ch: "|") { + if try readIf(ch: "|") { return .operator(.or) - } - else { + } else { throw fail(message: "got '|', did you mean '||'?") } - } - else { + } else { throw fail(message: "unexpected character '\(ch)'") } - }() - + }() + try skipWhitespace() - + return TokenInfo(token: token, location: location) } - + /** * Reads a symbol. * @@ -303,11 +290,13 @@ public class Lexer { let str = try readWhile { $0.isLetter || $0.isWholeNumber || $0 == "_" } switch str { case "else": - return .keyword(.`else`) + return .keyword(.else) case "fun": return .keyword(.fun) case "if": return .keyword(.if) + case "unless": + return .keyword(.unless) case "var": return .keyword(.var) case "val": @@ -322,7 +311,7 @@ public class Lexer { return .identifier(name: str) } } - + /** * Reads a number literal. * @@ -333,49 +322,46 @@ public class Lexer { guard let value = Int(i) else { throw fail(message: "expected Int, but got '\(i)'") } - + return .literal(value: .integer(value: value)) } - + /** * Reads a string literal. */ private func readString() throws -> Token { var sb = [Character]() var escape = false - + try expect(ch: "\"") - - while (hasMore) { + + while hasMore { let ch = try readChar() - if (escape) { + if escape { sb.append(ch) escape = false - } - else if (ch == "\\") { + } else if ch == "\\" { escape = true - } - else if (ch == "\"") { + } else if ch == "\"" { return .literal(value: .string(value: String(sb))) - } - else { + } else { sb.append(ch) } } - + throw unexpectedEnd() } - + /** * Returns the next character in source code without consuming it. */ private func peekChar() throws -> Character { - if (!hasMore) { + if !hasMore { throw unexpectedEnd() } return source[position] } - + /** * If next character is [ch], consume the character and return true. * Otherwise don't consume the character and return false. @@ -384,21 +370,20 @@ public class Lexer { if try (hasMore && peekChar() == ch) { try readChar() return true - } - else { + } else { return false } } - + /** * Skip characters in input as long as [predicate] returns `true`. */ private func skipWhile(predicate: (Character) -> Bool) throws { - while (hasMore && predicate(source[position])) { + while hasMore, predicate(source[position]) { try readChar() } } - + /** * Read characters in input as long as [predicate] returns `true` * and return the string of read characters. @@ -408,7 +393,7 @@ public class Lexer { try skipWhile(predicate: predicate) return String(source).substring(startOffset: start, endOffset: position) } - + /** * Reads a single character from source code. * @@ -416,62 +401,60 @@ public class Lexer { * this method takes care of adjusting [line] and [column] accordingly. */ @discardableResult private func readChar() throws -> Character { - if (!hasMore) { + if !hasMore { throw unexpectedEnd() } let ch = source[position] position += 1 - - if (ch == "\n") { + + if ch == "\n" { line += 1 column = 1 - } - else { + } else { column += 1 } - + return ch } - + /** * Consume next character if it is [ch]. Otherwise throws [SyntaxErrorException]. */ private func expect(ch: Character) throws { let c = try peekChar() - if (ch == c) { + if ch == c { try readChar() - } - else { + } else { throw fail(message: "expected '\(ch)', but got '\(c)'") } } - + /** * Skips all whitespace. */ - private func skipWhitespace() throws -> Void { + private func skipWhitespace() throws { try skipWhile { $0.isWhitespace } } - + /** * Returns current source location. */ - public var currentSourceLocation : SourceLocation { - return SourceLocation(file: file, line: line, column: column, lineText: String(lines[line - 1])) + public var currentSourceLocation: SourceLocation { + SourceLocation(file: file, line: line, column: column, lineText: String(lines[line - 1])) } - + /** * Throws [SyntaxErrorException] with given [message] and current [SourceLocation]. */ private func fail(message: String) -> SyntaxErrorException { - return SyntaxErrorException(errorMessage: message, sourceLocation: currentSourceLocation) + SyntaxErrorException(errorMessage: message, sourceLocation: currentSourceLocation) } - + /** * Throws [UnexpectedEndOfInputException] with current [SourceLocation]. */ func unexpectedEnd() -> UnexpectedEndOfInputException { - return UnexpectedEndOfInputException(sourceLocation: currentSourceLocation) + UnexpectedEndOfInputException(sourceLocation: currentSourceLocation) } } @@ -487,18 +470,18 @@ public class Lexer { */ public class LookaheadLexer { let lexer: Lexer - + public init(lexer: Lexer) { self.lexer = lexer } - + /** * Convenience constructor that creates the wrapped [Lexer] using given [source]. */ public convenience init(source: String) throws { self.init(lexer: try Lexer(source: source)) } - + /** * The lookahead token. * @@ -507,15 +490,15 @@ public class LookaheadLexer { * * If we are in sync with [lexer], then the lookahead is `null`, */ - var lookahead: TokenInfo? = nil - + var lookahead: TokenInfo? + /** * Are there any more tokens in the input? */ public var hasMore: Bool { - return lookahead != nil || lexer.hasMore + lookahead != nil || lexer.hasMore } - + /** * Consumes and returns the next token. */ @@ -526,7 +509,7 @@ public class LookaheadLexer { self.lookahead = nil return lookahead } - + /** * Returns the next token without consuming it. */ @@ -534,57 +517,54 @@ public class LookaheadLexer { let lookahead: TokenInfo if let l = self.lookahead { lookahead = l - } - else { + } else { lookahead = try lexer.readToken() } self.lookahead = lookahead return lookahead } - + /** * Returns the location of the next token. */ func nextTokenLocation() throws -> SourceLocation { - return try peekToken().location + try peekToken().location } - + /** * Returns true if the next token is [token]. */ func nextTokenIs(token: Token) throws -> Bool { - return try (hasMore && peekToken().token == token) + try (hasMore && peekToken().token == token) } - + /** * If the next token is [token], consume it and return `true`. Otherwise don't * consume the token and return `false`. */ public func readNextIf(token: Token) throws -> Bool { - if (try nextTokenIs(token: token)) { + if try nextTokenIs(token: token) { try readToken() return true - } - else { + } else { return false } } - + /** * If the next token is [expected], consume it and return its location. * Otherwise throw [SyntaxErrorException]. */ @discardableResult public func expect(expected: Token) throws -> SourceLocation { let tokenInfo = try readToken() - if (tokenInfo.token == expected) { + if tokenInfo.token == expected { return tokenInfo.location - } - else { + } else { throw SyntaxErrorException(errorMessage: "expected token \(expected.description), but got \(tokenInfo.token.description)", sourceLocation: tokenInfo.location) } } - - public var currentSourceLocation : SourceLocation { - return self.lexer.currentSourceLocation + + public var currentSourceLocation: SourceLocation { + lexer.currentSourceLocation } } diff --git a/Swona/Sources/Swona/Objects.swift b/Sources/Swona/Objects.swift similarity index 84% rename from Swona/Sources/Swona/Objects.swift rename to Sources/Swona/Objects.swift index cb66af8..60ef617 100644 --- a/Swona/Sources/Swona/Objects.swift +++ b/Sources/Swona/Objects.swift @@ -6,40 +6,40 @@ * Kotlin values we are actually planning to support and the compiler could not * help us with exhaustiveness checks. Therefore we'll model our values explicitly. */ -public enum Value: CustomStringConvertible { +public enum Value: Equatable, CustomStringConvertible { /** * Unit value. */ case unit - + /** * Strings. */ case string(value: String) - + /** * Booleans. */ case bool(value: Bool) - + /** * Integers. */ case integer(value: Int) - + case function(Function) - - public enum Function { + + public enum Function: Equatable { /** * Function whose implementation is byte-code. */ case compound(address: Int, codeSize: Int, name: String, signature: Type.Function) - + /** * Function implemented as native function. */ case native(func: FunctionReference, name: String, signature: Type.Function) - + public var signature: Type.Function { switch self { case let .compound(_, _, _, signature), @@ -47,7 +47,7 @@ public enum Value: CustomStringConvertible { return signature } } - + public var name: String { switch self { case let .compound(_, _, name, _), @@ -56,14 +56,14 @@ public enum Value: CustomStringConvertible { } } } - + case array(elements: ArrayReference, elementType: Type) - + case pointer(value: Int, Pointer) public enum Pointer { case code } - + public func plus(rhs: Value) -> Value { if case let .string(lhs) = self { return Value.string(value: lhs + rhs.description) @@ -73,28 +73,28 @@ public enum Value: CustomStringConvertible { } fatalError("operation 'plus' invalid for \(self) and \(rhs)") } - + public func minus(rhs: Value) -> Value { if case let .integer(lhs) = self, case let .integer(rhs) = rhs { return .integer(value: lhs - rhs) } fatalError("operation 'minus' invalid for \(self) and \(rhs)") } - + public func times(rhs: Value) -> Value { if case let .integer(lhs) = self, case let .integer(rhs) = rhs { return .integer(value: lhs * rhs) } fatalError("operation 'times' invalid for \(self) and \(rhs)") } - + public func div(rhs: Value) -> Value { if case let .integer(lhs) = self, case let .integer(rhs) = rhs { return .integer(value: lhs / rhs) } fatalError("operation 'div' invalid for \(self) and \(rhs)") } - + /** * Returns a string representation of this value that is similar * to the syntax used in source code. Used when printing AST and @@ -105,10 +105,10 @@ public enum Value: CustomStringConvertible { case let .string(value): return "\"" + value.split(separator: "\"").joined(separator: "\\\"") + "\"" default: - return self.description + return description } } - + /** * Returns the [Type] associated with this value. */ @@ -116,21 +116,21 @@ public enum Value: CustomStringConvertible { switch self { case .unit: return Type.unit - case .bool(_): + case .bool: return Type.boolean - case .integer(_): + case .integer: return Type.int - case .string(_): + case .string: return Type.string case let .function(function): return Type.function(function.signature) case let .array(_, elementType): return Type.array(elementType: elementType) - case .pointer(_, _): + case .pointer: fatalError("pointers are internal objects that have no visible type") } } - + /** Are values of this type subject to constant propagation? */ public var mayInline: Bool { switch self { @@ -140,7 +140,7 @@ public enum Value: CustomStringConvertible { return true } } - + public func lessThan(r: Value) -> Bool { switch (self, r) { case let (.string(lhs), .string(rhs)): @@ -153,7 +153,7 @@ public enum Value: CustomStringConvertible { fatalError("< not supported for \(self) and \(r)") } } - + public var description: String { switch self { case .unit: @@ -167,10 +167,10 @@ public enum Value: CustomStringConvertible { case let .function(function): switch function.signature { case let .function(argumentTypes, returnType): - return "fun \(function.name)(\(argumentTypes.map { $0.description }.joined(separator: ", "))): \(returnType)" + return "fun \(function.name)(\(argumentTypes.map(\.description).joined(separator: ", "))): \(returnType)" } case let .array(elements, _): - return "[\(elements.array.map { $0.description }.joined(separator: ", "))]" + return "[\(elements.array.map(\.description).joined(separator: ", "))]" case let .pointer(value, _): return "Pointer.Code(\(value.description))" } @@ -179,32 +179,52 @@ public enum Value: CustomStringConvertible { public class ArrayReference: Hashable { public static func == (lhs: ArrayReference, rhs: ArrayReference) -> Bool { - return ObjectIdentifier(lhs) == ObjectIdentifier(rhs) + ObjectIdentifier(lhs) == ObjectIdentifier(rhs) } - + public func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(self).hashValue) } - - internal var array: Array - - init(array: Array) { + + public internal(set) var array: [Value] + + init(array: [Value]) { self.array = array } } public class FunctionReference: Hashable { public static func == (lhs: FunctionReference, rhs: FunctionReference) -> Bool { - return ObjectIdentifier(lhs) == ObjectIdentifier(rhs) + ObjectIdentifier(lhs) == ObjectIdentifier(rhs) } - + public func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(self).hashValue) } - - let function: (Array) throws -> Value - - init(function: @escaping (Array) throws -> Value) { + + let function: ([Value]) throws -> Value + + init(function: @escaping ([Value]) throws -> Value) { self.function = function } } + +// Helper properties to make values easily out of Kotlin literals. (e.g. "foo".value or 123.value) + +extension String { + public var value: Value { + .string(value: self) + } +} + +extension Int { + public var value: Value { + .integer(value: self) + } +} + +extension Bool { + public var value: Value { + .bool(value: self) + } +} diff --git a/Swona/Sources/Swona/Optimizer.swift b/Sources/Swona/Optimizer.swift similarity index 70% rename from Swona/Sources/Swona/Optimizer.swift rename to Sources/Swona/Optimizer.swift index cc475b7..fe922f1 100644 --- a/Swona/Sources/Swona/Optimizer.swift +++ b/Sources/Swona/Optimizer.swift @@ -1,12 +1,12 @@ extension TypedExpression { public func optimize() -> TypedExpression { - return evaluateConstantExpressions() + evaluateConstantExpressions() } - + public func evaluateConstantExpressions() -> TypedExpression { - return eval(env: ConstantBindingEnv()) + eval(env: ConstantBindingEnv()) } - + fileprivate func eval(env: ConstantBindingEnv) -> TypedExpression { switch self { case let .ref(bindingReference): @@ -25,7 +25,7 @@ extension TypedExpression { guard case let .bool(boolValue) = value else { fatalError("invalid lit value: \(value.description)") } - return .lit(value: .bool(value: !(boolValue)), type: Type.boolean) + return .lit(value: .bool(value: !boolValue), type: Type.boolean) case let .not(exp): return exp default: @@ -39,17 +39,17 @@ extension TypedExpression { if case let .relational(op, _, _) = binary { return .lit(value: Value.bool(value: op.evaluate(lhs: lvalue, rhs: rvalue)), type: .boolean) } - if case .string = lvalue, case .concatString(_, _) = binary { + if case .string = lvalue, case .concatString = binary { return .lit(value: lvalue.plus(rhs: rvalue), type: .string) } - if case .integer(_) = lvalue, case let .integer(rvalueInt) = rvalue { + if case .integer = lvalue, case let .integer(rvalueInt) = rvalue { switch binary { case .plus: return .lit(value: lvalue.plus(rhs: rvalue), type: .int) case .minus: return .lit(value: lvalue.minus(rhs: rvalue), type: .int) case .divide: - if (rvalueInt == 0) { + if rvalueInt == 0 { break } return .lit(value: lvalue.div(rhs: rvalue), type: .int) @@ -59,51 +59,45 @@ extension TypedExpression { #warning("Remodel") fatalError("invalid operation on integers: \(binary)") } - } default: break } - switch self { - case let .binary(binary): - switch binary { - case .plus: - return .binary(.plus(lhs: optLhs, rhs: optRhs, type: type)) - case .minus: - return .binary(.minus(lhs: optLhs, rhs: optRhs, type: type)) - case .multiply: - return .binary(.multiply(lhs: optLhs, rhs: optRhs, type: type)) - case .divide: - return .binary(.divide(lhs: optLhs, rhs: optRhs, type: type)) - case .concatString: - return .binary(.concatString(lhs: optLhs, rhs: optRhs)) - case let .relational(op, _, _): - return .binary(.relational(op: op, lhs: optLhs, rhs: optRhs)) - - } + switch binary { + case .plus: + return .binary(.plus(lhs: optLhs, rhs: optRhs, type: type)) + case .minus: + return .binary(.minus(lhs: optLhs, rhs: optRhs, type: type)) + case .multiply: + return .binary(.multiply(lhs: optLhs, rhs: optRhs, type: type)) + case .divide: + return .binary(.divide(lhs: optLhs, rhs: optRhs, type: type)) + case .concatString: + return .binary(.concatString(lhs: optLhs, rhs: optRhs)) + case let .relational(op, _, _): + return .binary(.relational(op: op, lhs: optLhs, rhs: optRhs)) } case let .assign(variable, expression): return .assign(variable: variable, expression: expression.eval(env: env)) case let .var(variable, expression): let value = expression.eval(env: env) - if case let .lit(v, _) = value, v.mayInline, !(variable.mutable) { + if case let .lit(v, _) = value, v.mayInline, !variable.mutable { env[variable] = v } return .var(variable: variable, expression: value) case let .if(condition, consequent, alternative, type): let optCondition = condition.eval(env: env) - if case let .lit(optCondition) = optCondition, case let .bool(value) = optCondition.value { - if (value) { + if case let .lit(optConditionValue, _) = optCondition, case let .bool(value) = optConditionValue { + if value { return consequent.eval(env: env.child()) - } - else { + } else { return alternative?.eval(env: env.child()) ?? TypedExpression.empty } } - return .if(condition: optCondition, consequent: consequent.eval(env: env.child()), alternative: alternative?.eval(env: env.child()), type: type); + return .if(condition: optCondition, consequent: consequent.eval(env: env.child()), alternative: alternative?.eval(env: env.child()), type: type) case let .while(condition, body): let optCondition = condition.eval(env: env) - if case let .lit(optCondition) = optCondition, case let .bool(value) = optCondition.value, value == false { + if case let .lit(optConditionValue, _) = optCondition, case let .bool(value) = optConditionValue, value == false { return TypedExpression.empty } return .while(condition: optCondition, body: body.eval(env: env.child())) @@ -115,7 +109,6 @@ extension TypedExpression { } extension RelationalOp { - func evaluate(lhs: Value, rhs: Value) -> Bool { switch self { case .equals: @@ -138,25 +131,24 @@ extension RelationalOp { * Environment containing all constant bindings. */ private class ConstantBindingEnv { - private let parent: ConstantBindingEnv? - + init(parent: ConstantBindingEnv? = nil) { self.parent = parent } - - private var constantBindings = [BindingReference:Value]() - + + private var constantBindings = [BindingReference: Value]() + func child() -> ConstantBindingEnv { - return ConstantBindingEnv(parent: self) + ConstantBindingEnv(parent: self) } - + subscript(binding: BindingReference) -> Value? { set(value) { constantBindings[binding] = value } get { - return constantBindings[binding] ?? parent?[binding] + constantBindings[binding] ?? parent?[binding] } } } @@ -171,12 +163,12 @@ extension BasicBlock { repeat { modified = false for optimizer in optimizers { - if (optimizer.optimize(basicBlock: self)) { + if optimizer.optimize(basicBlock: self) { modified = true } } - - } while (modified) + + } while modified } } @@ -193,16 +185,15 @@ private let optimizers: [PeepholeOptimizer] = [ * window of the [BasicBlock] to optimize. */ private protocol PeepholeOptimizer { - var windowSize: Int { get } - + /** * Try to optimize a window of IR-sequence. If the optimizer detects * a pattern it can optimize, it will return a list of instructions * replacing the instructions of the original window. Otherwise it * will return null. */ - func optimizeWindow(window: Array) -> Array? + func optimizeWindow(window: [IR]) -> [IR]? } extension PeepholeOptimizer { @@ -216,15 +207,15 @@ extension PeepholeOptimizer { precondition(windowSize > 0) var modified = false - + var opCodes = basicBlock.opCodes var i = 0 while i < opCodes.count { let end = i + windowSize - let window = opCodes[i.. 1) ? (optimizeWindow(window: Array(window))) : (nil) + let window = opCodes[i ..< min(end, opCodes.count)] + let left = opCodes[0 ..< i] + let right = opCodes[min(end, opCodes.count) ..< opCodes.count] + let optimized = (window.count > 1) ? optimizeWindow(window: Array(window)) : nil opCodes = Array(left) + (optimized ?? Array(window)) + right if optimized != nil { modified = true @@ -232,7 +223,7 @@ extension PeepholeOptimizer { i += 1 } basicBlock.opCodes = opCodes - + return modified } } @@ -240,17 +231,16 @@ extension PeepholeOptimizer { /** * Loading a variable by storing to same variable is a no-op: remove instructions. */ -private struct RedundantLoadStoreOptimizer : PeepholeOptimizer { - var windowSize: Int { return 2 } - - func optimizeWindow(window: Array) -> Array? { +private struct RedundantLoadStoreOptimizer: PeepholeOptimizer { + var windowSize: Int { 2 } + + func optimizeWindow(window: [IR]) -> [IR]? { let first = window[0] let second = window[1] - - if case let .localFrameIR(localIR1) = first, case let .loadLocal(l1) = localIR1, case let .localFrameIR(localIR2) = second, case let .storeLocal(l2) = localIR2, l1.index == l2.index { + + if case let .localFrameIR(localIR1) = first, case let .loadLocal(l1Index, _) = localIR1, case let .localFrameIR(localIR2) = second, case let .storeLocal(l2Index, _) = localIR2, l1Index == l2Index { return [] - } - else { + } else { return nil } } @@ -259,40 +249,35 @@ private struct RedundantLoadStoreOptimizer : PeepholeOptimizer { /** * Storing a variable and then loading the same variable can be replaced by dup + store. */ -private struct RedundantLoadOptimizer : PeepholeOptimizer { - var windowSize: Int { return 2 } - - func optimizeWindow(window: Array) -> Array? { +private struct RedundantLoadOptimizer: PeepholeOptimizer { + var windowSize: Int { 2 } + + func optimizeWindow(window: [IR]) -> [IR]? { let first = window[0] let second = window[1] - - if case let .localFrameIR(localIR1) = first, case let .storeLocal(l1) = localIR1, case let .localFrameIR(localIR2) = second, case let .loadLocal(l2) = localIR2, l1.index == l2.index { + + if case let .localFrameIR(localIR1) = first, case let .storeLocal(l1Index, _) = localIR1, case let .localFrameIR(localIR2) = second, case let .loadLocal(l2Index, _) = localIR2, l1Index == l2Index { return [IR.dup, first] - } - else { + } else { return nil } } - } /** * Removes PushUnit + Pop -combinations */ private struct RedundantPushUnitPopOptimizer: PeepholeOptimizer { - var windowSize: Int { return 2 } - - func optimizeWindow(window: Array) -> Array? { + var windowSize: Int { 2 } + + func optimizeWindow(window: [IR]) -> [IR]? { let first = window[0] let second = window[1] - + if case .pushUnit = first, case .pop = second { return [] - } - else { + } else { return nil } } } - - diff --git a/Sources/Swona/OrderedSet.swift b/Sources/Swona/OrderedSet.swift new file mode 100644 index 0000000..be48b3a --- /dev/null +++ b/Sources/Swona/OrderedSet.swift @@ -0,0 +1,100 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See http://swift.org/LICENSE.txt for license information + See http://swift.org/CONTRIBUTORS.txt for Swift project authors + + The below code was modified from the original. + */ + +// See https://github.com/apple/swift-package-manager/blob/1444b46dbc13477f027959e4d59e520747fc8382/swift-tools-support-core/Sources/TSCBasic/OrderedSet.swift + +/// An ordered set is an ordered collection of instances of `Element` in which +/// uniqueness of the objects is guaranteed. +public struct OrderedSet: Equatable, Collection { + public typealias Element = E + + var array: [Element] + var elementToIndex: [Element: Int] + + /// Creates an empty ordered set. + public init() { + array = [] + elementToIndex = [:] + } + + /// Creates an ordered set with the contents of `array`. + /// + /// If an element occurs more than once in `element`, only the first one + /// will be included. + public init(_ array: [Element]) { + self.init() + for element in array { + append(element) + } + } + + /// Returns the contents of the set as an array. + public var contents: [Element] { array } + + /// Adds an element to the ordered set. + /// + /// If it already contains the element, then the set is unchanged. + /// + /// - returns: True if the item was inserted. + @discardableResult + public mutating func append(_ newElement: Element) -> Bool { + guard elementToIndex[newElement] == nil else { + return false + } + + array.append(newElement) + elementToIndex[newElement] = array.count - 1 + + return true + } + + /// Remove an element. + @discardableResult public mutating func remove(_ element: Element) -> Bool { + guard let index = elementToIndex[element] else { + return false + } + array.remove(at: index) + elementToIndex[element] = nil + for (e, i) in elementToIndex { + guard i >= index else { + continue + } + elementToIndex[e] = i - 1 + } + + return true + } +} + +extension OrderedSet: ExpressibleByArrayLiteral { + /// Create an instance initialized with `elements`. + /// + /// If an element occurs more than once in `element`, only the first one + /// will be included. + public init(arrayLiteral elements: Element...) { + self.init(elements) + } +} + +extension OrderedSet: RandomAccessCollection { + public var startIndex: Int { contents.startIndex } + public var endIndex: Int { contents.endIndex } + public subscript(index: Int) -> Element { + contents[index] + } +} + +public func == (lhs: OrderedSet, rhs: OrderedSet) -> Bool { + lhs.contents == rhs.contents +} + +extension OrderedSet: Hashable where Element: Hashable {} diff --git a/Swona/Sources/Swona/Parser.swift b/Sources/Swona/Parser.swift similarity index 73% rename from Swona/Sources/Swona/Parser.swift rename to Sources/Swona/Parser.swift index bf801c7..04cd33a 100644 --- a/Swona/Sources/Swona/Parser.swift +++ b/Sources/Swona/Parser.swift @@ -1,26 +1,24 @@ - - /** * Parse [code] as [Expression]. * * @throws SyntaxErrorException if parsing fails */ -public func parseExpression(code: String) throws -> Expression { - return try parseComplete(lexer: try Lexer(source: code)) { try $0.parseTopLevelExpression() } +public func parseExpression(code: String) throws -> Expression { + try parseComplete(lexer: try Lexer(source: code)) { try $0.parseTopLevelExpression() } } /** * Parses a function definition. */ public func parseFunctionDefinition(code: String) throws -> FunctionDefinition { - return try parseComplete(lexer: Lexer(source: code)) { try $0.parseFunctionDefinition() } + try parseComplete(lexer: Lexer(source: code)) { try $0.parseFunctionDefinition() } } /** * Parses a function definition. */ public func parseFunctionDefinitions(code: String, file: String) throws -> [FunctionDefinition] { - return try parseComplete(lexer: Lexer(source: code, file: file)) { try $0.parseFunctionDefinitions() } + try parseComplete(lexer: Lexer(source: code, file: file)) { try $0.parseFunctionDefinitions() } } /** @@ -39,22 +37,22 @@ private func parseComplete(lexer: Lexer, callback: (Parser) throws -> T) thro * A simple recursive descent parser. */ public class Parser { - private let lexer: LookaheadLexer; - + private let lexer: LookaheadLexer + init(lexer: Lexer) { self.lexer = LookaheadLexer(lexer: lexer) } - - func parseFunctionDefinitions() throws -> Array { - var result = Array() - - while (lexer.hasMore) { + + func parseFunctionDefinitions() throws -> [FunctionDefinition] { + var result = [FunctionDefinition]() + + while lexer.hasMore { result += [try parseFunctionDefinition()] } - + return result } - + /** * ``` * functionDefinition :== "fun" name "(" args ")" [ ":" type ] "=" expression @@ -64,19 +62,18 @@ public class Parser { try lexer.expect(expected: .keyword(.fun)) let name = try parseName().0 let args = try parseArgumentDefinitionList() - let returnType: Type?; - if (try lexer.readNextIf(token: .punctuation(.colon))) { + let returnType: Type? + if try lexer.readNextIf(token: .punctuation(.colon)) { returnType = try parseType() - } - else { + } else { returnType = nil } try lexer.expect(expected: .punctuation(.equal)) let body = try parseTopLevelExpression() - + return FunctionDefinition(name: name, args: args, returnType: returnType, body: body) } - + /** * Parses an expression. * @@ -88,7 +85,7 @@ public class Parser { * ``` */ func parseTopLevelExpression() throws -> Expression { - switch try lexer.peekToken().token { + switch try lexer.peekToken().token { case let .keyword(keyword): switch keyword { case .var: @@ -109,59 +106,56 @@ public class Parser { let exp = try parseExpression1() if case let .ref(name, _) = exp, try lexer.nextTokenIs(token: .punctuation(.equal)) { return try parseAssignTo(variable: name) - } - else { + } else { return exp } - + default: break } - + return try parseExpression1() } - + /** * ``` - * expression1 ::= expression2 (("||") expression2)* + * expression1 ::= expression2 (("||") expression2) * ``` */ private func parseExpression1() throws -> Expression { var exp = try parseExpression2() - - while (lexer.hasMore) { + + while lexer.hasMore { let location = try lexer.nextTokenLocation() - if (try lexer.readNextIf(token: .operator(.or))) { + if try lexer.readNextIf(token: .operator(.or)) { exp = Expression.binary(.or(lhs: exp, rhs: try parseExpression2(), location: location)) - } - else { + } else { return exp } } return exp } - + /** * ``` - * expression2 ::= expression3 (("&&") expression2)3 + * expression2 ::= expression3 (("&&") expression2) * ``` */ private func parseExpression2() throws -> Expression { var exp = try parseExpression3() - - while (lexer.hasMore) { + + while lexer.hasMore { let location = try lexer.nextTokenLocation() - if (try lexer.readNextIf(token: .operator(.and))) { + if try lexer.readNextIf(token: .operator(.and)) { exp = Expression.binary(.and(lhs: exp, rhs: try parseExpression3(), location: location)) - } - else { + } else { return exp } } - + return exp } - + /** * ``` * expression3 ::= expression4 (("==" | "!=") expression4)* @@ -169,23 +163,21 @@ public class Parser { */ func parseExpression3() throws -> Expression { var exp = try parseExpression4() - - while (lexer.hasMore) { + + while lexer.hasMore { let location = try lexer.nextTokenLocation() - if (try lexer.readNextIf(token: .operator(.equalEqual))) { + if try lexer.readNextIf(token: .operator(.equalEqual)) { exp = Expression.binary(.relational(op: .equals, lhs: exp, rhs: try parseExpression4(), location: location)) - } - else if (try lexer.readNextIf(token: .operator(.notEqual))) { + } else if try lexer.readNextIf(token: .operator(.notEqual)) { exp = Expression.binary(.relational(op: .notEquals, lhs: exp, rhs: try parseExpression4(), location: location)) - } - else { + } else { return exp } } - + return exp } - + /** * ``` * expression4 ::= expression5 (("<" | ">" | "<=" | ">=") expression5)* @@ -193,29 +185,25 @@ public class Parser { */ private func parseExpression4() throws -> Expression { var exp = try parseExpression5() - - while (lexer.hasMore) { + + while lexer.hasMore { let location = try lexer.nextTokenLocation() - if (try lexer.readNextIf(token: .`operator`(.lessThan))) { + if try lexer.readNextIf(token: .operator(.lessThan)) { exp = Expression.binary(.relational(op: .lessThan, lhs: exp, rhs: try parseExpression5(), location: location)) - } - else if (try lexer.readNextIf(token: .`operator`(.lessThanOrEqual))) { + } else if try lexer.readNextIf(token: .operator(.lessThanOrEqual)) { exp = Expression.binary(.relational(op: .lessThanOrEqual, lhs: exp, rhs: try parseExpression5(), location: location)) - } - else if (try lexer.readNextIf(token: .`operator`(.greaterThan))) { + } else if try lexer.readNextIf(token: .operator(.greaterThan)) { exp = Expression.binary(.relational(op: .greaterThan, lhs: exp, rhs: try parseExpression5(), location: location)) - } - else if (try lexer.readNextIf(token: .`operator`(.greaterThanOrEqual))) { + } else if try lexer.readNextIf(token: .operator(.greaterThanOrEqual)) { exp = Expression.binary(.relational(op: .greaterThanOrEqual, lhs: exp, rhs: try parseExpression5(), location: location)) - } - else { + } else { return exp } } - + return exp } - + /** * ``` * expression5 ::= expression6 (("+" | "-") expression6)* @@ -223,47 +211,43 @@ public class Parser { */ private func parseExpression5() throws -> Expression { var exp = try parseExpression6() - - while (lexer.hasMore) { + + while lexer.hasMore { let location = try lexer.nextTokenLocation() - if (try lexer.readNextIf(token: .operator(.plus))) { + if try lexer.readNextIf(token: .operator(.plus)) { exp = Expression.binary(.plus(lhs: exp, rhs: try parseExpression6(), location: location)) - } - else if (try lexer.readNextIf(token: .operator(.minus))) { + } else if try lexer.readNextIf(token: .operator(.minus)) { exp = Expression.binary(.minus(lhs: exp, rhs: try parseExpression6(), location: location)) - } - else { + } else { return exp } } - + return exp } - + /** * ``` - * expression6 ::= expression7 (("*" | "/") expression7)* + * expression6 ::= expression7 (("*" | "/") expression7) * ``` */ private func parseExpression6() throws -> Expression { var exp = try parseExpression7() - - while (lexer.hasMore) { + + while lexer.hasMore { let location = try lexer.nextTokenLocation() - if (try lexer.readNextIf(token: .operator(.multiply))) { + if try lexer.readNextIf(token: .operator(.multiply)) { exp = Expression.binary(.multiply(lhs: exp, rhs: try parseExpression7(), location: location)) - } - else if (try lexer.readNextIf(token: .operator(.divide))) { + } else if try lexer.readNextIf(token: .operator(.divide)) { exp = Expression.binary(.divide(lhs: exp, rhs: try parseExpression7(), location: location)) - } - else { + } else { return exp } } - + return exp } - + /** * ``` * expression7 ::= expression8 [ '(' args ')'] @@ -271,15 +255,14 @@ public class Parser { */ private func parseExpression7() throws -> Expression { let exp = try parseExpression8() - + if try lexer.nextTokenIs(token: .punctuation(.leftParen)) { return Expression.call(func: exp, args: try parseArgumentList()) - } - else { + } else { return exp } } - + /** * ``` * expression8 ::= identifier | literal | not | "(" expression ")" | if | while @@ -306,10 +289,12 @@ public class Parser { default: break } - case let .keyword(`keyword`): - switch `keyword` { + case let .keyword(keyword): + switch keyword { case .if: return try parseIf() + case .unless: + return try parseUnless() case .while: return try parseWhile() default: @@ -318,30 +303,29 @@ public class Parser { } throw fail(location: tokenInfo.location, message: "unexpected token \(tokenInfo.token.description)") } - + private func parseAssignTo(variable: String) throws -> Expression { let location = try lexer.expect(expected: .punctuation(.equal)) let rhs = try parseTopLevelExpression() - + return Expression.assign(variable: variable, expression: rhs, location: location) } - + private func parseVariableDefinition() throws -> Expression { let mutable = try lexer.nextTokenIs(token: .keyword(.var)) let location: SourceLocation if mutable { location = try lexer.expect(expected: .keyword(.var)) - } - else { + } else { location = try lexer.expect(expected: .keyword(.val)) } - + let variable = try parseName().0 try lexer.expect(expected: .punctuation(.equal)) let expression = try parseTopLevelExpression() - return Expression.`var`(variable: variable, expression: expression, mutable: mutable, location: location) + return Expression.var(variable: variable, expression: expression, mutable: mutable, location: location) } - + private func parseIf() throws -> Expression { let location = try lexer.expect(expected: .keyword(.if)) let condition = try inParens { try parseTopLevelExpression() } @@ -349,35 +333,46 @@ public class Parser { let alternative: Expression? if try lexer.readNextIf(token: .keyword(.else)) { alternative = try parseTopLevelExpression() + } else { + alternative = nil } - else { + + return Expression.if(condition: condition, consequent: consequent, alternative: alternative, location: location) + } + + private func parseUnless() throws -> Expression { + let location = try lexer.expect(expected: .keyword(.unless)) + let condition = try inParens { try parseTopLevelExpression() } + let consequent = try parseTopLevelExpression() + let alternative: Expression? + if try lexer.readNextIf(token: .keyword(.else)) { + alternative = try parseTopLevelExpression() + } else { alternative = nil } - - return Expression.`if`(condition: condition, consequent: consequent, alternative: alternative, location: location) + + return Expression.if(condition: .not(exp: condition, location: location), consequent: consequent, alternative: alternative, location: location) } - private func parseWhile() throws -> Expression { let location = try lexer.expect(expected: .keyword(.while)) let condition = try inParens { try parseTopLevelExpression() } let body = try parseTopLevelExpression() - - return Expression.`while`(condition: condition, body: body, location: location) + + return Expression.while(condition: condition, body: body, location: location) } - + private func parseExpressionList() throws -> Expression { let location = try lexer.nextTokenLocation() let result: [Expression] = try inBraces { - if (try lexer.nextTokenIs(token: .punctuation(.rightBrace))) { + if try lexer.nextTokenIs(token: .punctuation(.rightBrace)) { return [] - } - else { + } else { return try separatedBy(separator: .punctuation(.semicolon)) { try parseTopLevelExpression() } } } - - return Expression.`expressionList`(expressions: result, location: location) + + return Expression.expressionList(expressions: result, location: location) } private func parseLiteral() throws -> Expression { @@ -387,42 +382,40 @@ public class Parser { } return Expression.lit(value: value, location: tokenInfo.location) } - + private func parseNot() throws -> Expression { let location = try lexer.expect(expected: .operator(.not)) let exp = try parseExpression7() - + return Expression.not(exp: exp, location: location) } - + private func parseIdentifier() throws -> Expression { let (name, location) = try parseName() - + return Expression.ref(name: name, location: location) } - - private func parseArgumentList() throws -> Array { - return try inParens { - if (try lexer.nextTokenIs(token: .punctuation(.rightParen))) { + + private func parseArgumentList() throws -> [Expression] { + try inParens { + if try lexer.nextTokenIs(token: .punctuation(.rightParen)) { return [] - } - else { - var args = Array() + } else { + var args = [Expression]() repeat { args.append(try parseTopLevelExpression()) - } while (try lexer.readNextIf(token: .punctuation(.comma))) + } while try lexer.readNextIf(token: .punctuation(.comma)) return args } } } - - private func parseArgumentDefinitionList() throws -> Array<(String, Type)> { - return try inParens { - if (try lexer.nextTokenIs(token: .punctuation(.rightParen))) { + + private func parseArgumentDefinitionList() throws -> [(String, Type)] { + try inParens { + if try lexer.nextTokenIs(token: .punctuation(.rightParen)) { return [] - } - else { - var args = Array<(String, Type)>() + } else { + var args = [(String, Type)]() repeat { let name = try parseName().0 try lexer.expect(expected: .punctuation(.colon)) @@ -433,7 +426,7 @@ public class Parser { } } } - + private func parseName() throws -> (String, SourceLocation) { let tokenInfo = try lexer.readToken() guard case let Token.identifier(name) = tokenInfo.token else { @@ -441,7 +434,7 @@ public class Parser { } return (name, tokenInfo.location) } - + private func parseType() throws -> Type { let tokenInfo = try lexer.readToken() guard case let Token.identifier(name) = tokenInfo.token else { @@ -460,42 +453,42 @@ public class Parser { throw fail(location: tokenInfo.location, message: "unknown type name: '\(name)'") } } - + private func inParens(parser: () throws -> T) throws -> T { - return try between(left: .punctuation(.leftParen), right: .punctuation(.rightParen), parser: parser) + try between(left: .punctuation(.leftParen), right: .punctuation(.rightParen), parser: parser) } - + private func inBraces(parser: () throws -> T) throws -> T { - return try between(left: .punctuation(.leftBrace), right: .punctuation(.rightBrace), parser: parser) + try between(left: .punctuation(.leftBrace), right: .punctuation(.rightBrace), parser: parser) } - + private func between(left: Token, right: Token, parser: () throws -> T) throws -> T { try lexer.expect(expected: left) let value = try parser() try lexer.expect(expected: right) return value } - - private func separatedBy (separator: Token, parser: () throws -> T) throws -> Array { - var result = Array() - + + private func separatedBy(separator: Token, parser: () throws -> T) throws -> [T] { + var result = [T]() + repeat { try result.append(parser()) - } while (try lexer.readNextIf(token: separator)) - + } while try lexer.readNextIf(token: separator) + return result } - + private func fail(message: String) -> SyntaxErrorException { - return fail(location: self.lexer.currentSourceLocation, message: message) + fail(location: lexer.currentSourceLocation, message: message) } - + private func fail(location: SourceLocation, message: String) -> SyntaxErrorException { - return SyntaxErrorException(errorMessage: message, sourceLocation: location) + SyntaxErrorException(errorMessage: message, sourceLocation: location) } - + func expectEnd() throws { - if (lexer.hasMore) { + if lexer.hasMore { let tokenInfo = try lexer.peekToken() throw fail(location: tokenInfo.location, message: "expected end, but got \(tokenInfo.token)") } diff --git a/Swona/Sources/Swona/Runtime.swift b/Sources/Swona/Runtime.swift similarity index 72% rename from Swona/Sources/Swona/Runtime.swift rename to Sources/Swona/Runtime.swift index 8ffdd65..f9ccf8d 100644 --- a/Swona/Sources/Swona/Runtime.swift +++ b/Sources/Swona/Runtime.swift @@ -1,35 +1,37 @@ /** * Creates a single argument primitive function. */ -public func fun1(name: String, argType: Type, returnType: Type, `func`: @escaping (Value) throws -> Value) -> Value.Function { +public func fun1(name: String, argType: Type, returnType: Type, func: @escaping (Value) throws -> Value) -> Value.Function { let signature = Type.Function.function(argumentTypes: [argType], returnType: returnType) - return .native(func: FunctionReference(function: { args in try `func`(args.single())}), name: name, signature: signature) + return .native(func: FunctionReference(function: { args in try `func`(args.single()) }), name: name, signature: signature) } -public func fun1(name: String, argType: Type, `func`: @escaping (Value) throws -> Value) -> Value.Function { - return fun1(name: name, argType: argType, returnType: .unit, func: `func`) +public func fun1(name: String, argType: Type, func: @escaping (Value) throws -> Value) -> Value.Function { + fun1(name: name, argType: argType, returnType: .unit, func: `func`) } -public func fun2(name: String, argType1: Type, argType2: Type, returnType: Type, `func`: @escaping (Value, Value) throws -> Value) -> Value.Function { +public func fun2(name: String, argType1: Type, argType2: Type, returnType: Type, func: @escaping (Value, Value) throws -> Value) -> Value.Function { let signature = Type.Function.function(argumentTypes: [argType1, argType2], returnType: returnType) return .native(func: FunctionReference(function: { args in precondition(args.count == 2) - return try `func`(args[0], args[1])}), name: name, signature: signature) + return try `func`(args[0], args[1]) + }), name: name, signature: signature) } -public func fun2(name: String, argType: Type, returnType: Type, `func`: @escaping (Value, Value) throws -> Value) -> Value.Function { - return fun2(name: name, argType1: argType, argType2: argType, returnType: returnType, func: `func`) +public func fun2(name: String, argType: Type, returnType: Type, func: @escaping (Value, Value) throws -> Value) -> Value.Function { + fun2(name: name, argType1: argType, argType2: argType, returnType: returnType, func: `func`) } -public func fun3(name: String, argType1: Type, argType2: Type, argType3: Type, returnType: Type, `func`: @escaping (Value, Value, Value) throws -> Value) -> Value.Function { +public func fun3(name: String, argType1: Type, argType2: Type, argType3: Type, returnType: Type, func: @escaping (Value, Value, Value) throws -> Value) -> Value.Function { let signature = Type.Function.function(argumentTypes: [argType1, argType2, argType3], returnType: returnType) return .native(func: FunctionReference(function: { args in precondition(args.count == 3) - return try `func`(args[0], args[1], args[2])}), name: name, signature: signature) + return try `func`(args[0], args[1], args[2]) + }), name: name, signature: signature) } extension Evaluator { - func bindFunction(`func`: Value.Function) throws { + func bindFunction(func: Value.Function) throws { try bind(name: `func`.name, value: .function(`func`), mutable: false) } } @@ -47,44 +49,43 @@ public func registerRuntimeFunctions(evaluator: Evaluator) throws { print($0.description) return .unit })) - + try evaluator.bindFunction(func: fun1(name: "error", argType: .string, func: { guard case .string = $0 else { fatalError("Expected string, got \($0.description)") } throw RuntimeError(message: $0) })) - + try evaluator.bindFunction(func: fun2(name: "stringArrayOfSize", argType1: .int, argType2: .string, returnType: .array(elementType: .string), func: { - (size, initialValue) in + size, initialValue in guard case let .integer(s) = size, case let .string(i) = initialValue else { fatalError("Expected int size and string initialValue, got \(size) and \(initialValue)") } - return .array(elements: ArrayReference(array: Array(repeating: .string(value: i), count: s)), elementType: .string) + return .array(elements: ArrayReference(array: [Value](repeating: .string(value: i), count: s)), elementType: .string) })) - - try evaluator.bindFunction(func: fun1(name: "stringArrayLength", argType: .array(elementType:.string), returnType: .int, func: { + + try evaluator.bindFunction(func: fun1(name: "stringArrayLength", argType: .array(elementType: .string), returnType: .int, func: { guard case let .array(elements, _) = $0 else { fatalError("Expected array, got \($0.description)") } return .integer(value: elements.array.count) })) - + try evaluator.bindFunction(func: fun2(name: "stringArrayGet", argType1: .array(elementType: .string), argType2: .int, returnType: .string, func: { - (array, index) in + array, index in guard case let .array(elements, elementType) = array, elementType == .string, case let .integer(i) = index else { fatalError("Expected string array and int index, got \(array) and \(index)") } return elements.array[i] })) - + try evaluator.bindFunction(func: fun3(name: "stringArraySet", argType1: .array(elementType: .string), argType2: .int, argType3: .string, returnType: .unit, func: { - (array, index, value) in + array, index, value in guard case let .array(elements, elementType) = array, elementType == .string, case let .integer(i) = index, case .string = value else { fatalError("Expected string array and int index and string value, got \(array) and \(index) and \(value)") } elements.array[i] = value return .unit })) - } diff --git a/Swona/Sources/Swona/Translator.swift b/Sources/Swona/Translator.swift similarity index 85% rename from Swona/Sources/Swona/Translator.swift rename to Sources/Swona/Translator.swift index ff1e7da..cc8bdcc 100644 --- a/Swona/Sources/Swona/Translator.swift +++ b/Sources/Swona/Translator.swift @@ -3,7 +3,7 @@ * but leaves things like addresses and labels still abstract. */ -public enum IR: CustomStringConvertible { +public enum IR: Equatable, CustomStringConvertible { case not case add case subtract @@ -24,25 +24,25 @@ public enum IR: CustomStringConvertible { case loadArgument(index: Int, name: String) case storeGlobal(index: Int, name: String) case localFrameIR(LocalFrameIR) - - public enum LocalFrameIR: CustomStringConvertible { + + public enum LocalFrameIR: Equatable, CustomStringConvertible { case loadLocal(index: Int, name: String) case storeLocal(index: Int, name: String) - + public var stackDelta: Int { switch self { case .loadLocal: return 1 case .storeLocal: return -1 } } - + public var description: String { switch self { - case let .loadLocal(index, name): return "LoadLocal \(index) ; \(name)" + case let .loadLocal(index, name): return "LoadLocal \(index) ; \(name)" case let .storeLocal(index, name): return "StoreLocal \(index) ; \(name)" } } - + public var localFrameOffset: Int { switch self { case let .loadLocal(index, _): @@ -51,26 +51,25 @@ public enum IR: CustomStringConvertible { return index } } - } public var stackDelta: Int { switch self { - case .not : return 0 - case .add : return -1 - case .subtract : return -1 - case .multiply : return -1 - case .divide : return -1 - case .equal : return -1 - case .lessThan : return -1 - case .lessThanOrEqual : return -1 - case .concatString : return -1 - case .pop : return -1 - case .dup : return 1 + case .not: return 0 + case .add: return -1 + case .subtract: return -1 + case .multiply: return -1 + case .divide: return -1 + case .equal: return -1 + case .lessThan: return -1 + case .lessThanOrEqual: return -1 + case .concatString: return -1 + case .pop: return -1 + case .dup: return 1 case let .call(argumentCount): return -argumentCount - case .restoreFrame : return 0 - case .ret : return -1 - case .pushUnit : return 1 + case .restoreFrame: return 0 + case .ret: return -1 + case .pushUnit: return 1 case .push: return 1 case .loadGlobal: return 1 case .loadArgument: return 1 @@ -78,7 +77,7 @@ public enum IR: CustomStringConvertible { case let .localFrameIR(localFrameIR): return localFrameIR.stackDelta } } - + public var description: String { switch self { case .not: return "Not" @@ -92,7 +91,7 @@ public enum IR: CustomStringConvertible { case .concatString: return "ConcatString" case .pop: return "Pop" case .dup: return "Dup" - case .call(_): return "Call" + case .call: return "Call" case .restoreFrame: return "RestoreFrame" case .ret: return "Ret" case .pushUnit: return "PushUnit" @@ -103,7 +102,6 @@ public enum IR: CustomStringConvertible { case let .localFrameIR(localFrameIR): return localFrameIR.description } } - } /** @@ -111,21 +109,20 @@ public enum IR: CustomStringConvertible { */ public class BasicBlock: CustomStringConvertible, Hashable { public static func == (lhs: BasicBlock, rhs: BasicBlock) -> Bool { - return ObjectIdentifier(lhs) == ObjectIdentifier(rhs) + ObjectIdentifier(lhs) == ObjectIdentifier(rhs) } - + public var opCodes = [IR]() - - var next: BlockEnd = BlockEnd.none - - public init() { - } - + + var next = BlockEnd.none + + public init() {} + /** How executing this blocks affects the depth of the stack */ public var stackDelta: Int { - return opCodes.sumBy { $0.stackDelta } + next.stackDelta + opCodes.sumBy { $0.stackDelta } + next.stackDelta } - + public var maxLocalVariableOffset: Int { let p: [Int?] = opCodes.map { switch $0 { @@ -136,14 +133,13 @@ public class BasicBlock: CustomStringConvertible, Hashable { } } return p.compactMap { $0 }.max() ?? -1 - } - + public indirect enum BlockEnd: CustomStringConvertible { case none case jump(basicBlock: BasicBlock) case branch(trueBlock: BasicBlock, falseBlock: BasicBlock) - + var stackDelta: Int { switch self { case .none: @@ -154,7 +150,7 @@ public class BasicBlock: CustomStringConvertible, Hashable { return -1 } } - + public var description: String { switch self { case .none: @@ -165,7 +161,7 @@ public class BasicBlock: CustomStringConvertible, Hashable { return "Branch" } } - + public var blocks: [BasicBlock] { switch self { case .none: @@ -176,38 +172,37 @@ public class BasicBlock: CustomStringConvertible, Hashable { return [trueBlock, falseBlock] } } - } - + /** * Adds a new opcode. */ func plusAssign(op: IR) { opCodes += [op] } - - public static func +=(left: BasicBlock, right: IR) { + + public static func += (left: BasicBlock, right: IR) { left.plusAssign(op: right) } - + func endWithJumpTo(next: BasicBlock) { guard case .none = self.next else { fatalError() } self.next = BlockEnd.jump(basicBlock: next) } - + public func endWithBranch(trueBlock: BasicBlock, falseBlock: BasicBlock) { - guard case .none = self.next else { + guard case .none = next else { fatalError() } - self.next = BlockEnd.branch(trueBlock: trueBlock, falseBlock: falseBlock) + next = BlockEnd.branch(trueBlock: trueBlock, falseBlock: falseBlock) } public var description: String { - return ((opCodes.map { $0.description }) + [next.description]).joined(separator: "; ") + (opCodes.map(\.description) + [next.description]).joined(separator: "; ") } - + public func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(self).hashValue) } @@ -219,7 +214,7 @@ public class BasicBlock: CustomStringConvertible, Hashable { * * It's always a bug in compiler if this is thrown: never an error in user program. */ -public struct InvalidStackUseException : Error { +public struct InvalidStackUseException: Error { let message: String init(_ message: String) { self.message = message @@ -230,27 +225,25 @@ public struct InvalidStackUseException : Error { * Graph of basic blocks for a single function, method etc. */ public struct BasicBlockGraph { - - public init() { - } - + public init() {} + public let start = BasicBlock() - + var end: BasicBlock { - return allBlocksInArbitraryOrder().first { + allBlocksInArbitraryOrder().first { if case BasicBlock.BlockEnd.none = $0.next { return true } return false - }! + }! } - + func optimize() { for block in allBlocksInArbitraryOrder() { block.peepholeOptimize() } } - + /** * Verifies that stack usage of the graph is valid. * @@ -259,41 +252,40 @@ public struct BasicBlockGraph { * blocks are static, we can just verify that each basic block has a consistent * start depth no matter what path we reach it through. */ - public func buildStackDepthMap() throws -> Dictionary { + public func buildStackDepthMap() throws -> [BasicBlock: Int] { var startStackDepths = [start: 0] - + for block in allBlocks() { guard let startDepth = startStackDepths[block] else { fatalError("no depth assigned for \(block)") } let endDepth = startDepth + block.stackDelta - + for next in block.next.blocks { let nextDepth = startStackDepths[next] - if (nextDepth == nil) { + if nextDepth == nil { startStackDepths[next] = endDepth - } - else if (nextDepth != endDepth) { + } else if nextDepth != endDepth { throw InvalidStackUseException("expected \(String(describing: nextDepth)), but got \(endDepth) for \(block) -> \(next)") } } } - + let endDepth = startStackDepths[end]! + end.stackDelta - if (endDepth != 0) { + if endDepth != 0 { throw InvalidStackUseException("invalid end depth for stack: \(endDepth)") } - + return startStackDepths } - + func localVariablesCount() -> Int { - return allBlocksInArbitraryOrder().map { $0.maxLocalVariableOffset + 1 }.max() ?? 0 + allBlocksInArbitraryOrder().map { $0.maxLocalVariableOffset + 1 }.max() ?? 0 } - + func allBlocks() -> OrderedSet { var blocks = allBlocksInArbitraryOrder() - + // move the ending block to be last guard let endBlock = Array(blocks.filter { if case BasicBlock.BlockEnd.none = $0.next { @@ -303,16 +295,15 @@ public struct BasicBlockGraph { }).singleOrNull() else { fatalError("no unique end block") } - + blocks.remove(endBlock) blocks.append(endBlock) return blocks } - - + private func allBlocksInArbitraryOrder() -> OrderedSet { var blocks = OrderedSet() - + func gatherBlocks(block: BasicBlock) { if blocks.append(block) { for b in block.next.blocks { @@ -320,59 +311,58 @@ public struct BasicBlockGraph { } } } - + gatherBlocks(block: start) return blocks } } - extension BasicBlockGraph { func translateToCode(argumentCount: Int) throws -> CodeSegment { - return try OpCodeTranslator(code: self, argumentCount: argumentCount).translate() + try OpCodeTranslator(code: self, argumentCount: argumentCount).translate() } } public class OpCodeTranslator { let code: BasicBlockGraph let argumentCount: Int - + let localCount: Int - + init(code: BasicBlockGraph, argumentCount: Int) { self.code = code self.argumentCount = argumentCount - self.localCount = code.localVariablesCount() + localCount = code.localVariablesCount() } - + func translate() throws -> CodeSegment { let blocks = code.allBlocks() - + let blockAddresses = calculateAddressesForBlocks(blocks: blocks) let stackDepths = try code.buildStackDepthMap() - - var ops = Array() + + var ops = [OpCode]() for block in blocks { ops += try translateBlock(block: block, blockAddresses: blockAddresses, stackDepths: stackDepths) } - + return CodeSegment(opCodes: ops) } - - private func translateBlock(block: BasicBlock, blockAddresses: Dictionary, stackDepths: Dictionary) throws -> Array { - var ops1 = Array() + + private func translateBlock(block: BasicBlock, blockAddresses: [BasicBlock: Int], stackDepths: [BasicBlock: Int]) throws -> [OpCode] { + var ops1 = [OpCode]() let baseStackOffset = argumentCount + localCount // there's a +1 term from return address, but a cancelling -1 term from addressing convention var sp = baseStackOffset + stackDepths[block]! - + for op in block.opCodes { - if (sp < baseStackOffset) { + if sp < baseStackOffset { throw InvalidStackUseException("stack underflow") } - + ops1 += [translate(ir: op, sp: sp)] sp += op.stackDelta } - + let next = block.next switch next { case .none: @@ -383,22 +373,22 @@ public class OpCodeTranslator { ops1 += [OpCode.jumpIfFalse(sp: sp, address: blockAddresses[falseBlock]!)] ops1 += [OpCode.jump(address: blockAddresses[trueBlock]!)] } - + return ops1 } - - private func calculateAddressesForBlocks(blocks: OrderedSet) -> Dictionary { - var blockAddresses = Dictionary() - + + private func calculateAddressesForBlocks(blocks: OrderedSet) -> [BasicBlock: Int] { + var blockAddresses = [BasicBlock: Int]() + var nextFreeAddress = 0 for block in blocks { blockAddresses[block] = nextFreeAddress nextFreeAddress += translatedSize(block: block) } - + return blockAddresses } - + private func translatedSize(block: BasicBlock) -> Int { let c: Int switch block.next { @@ -411,7 +401,7 @@ public class OpCodeTranslator { } return block.opCodes.count + c } - + func translate(ir: IR, sp: Int) -> OpCode { switch ir { case IR.not: return OpCode.not(target: sp, source: sp) @@ -442,17 +432,17 @@ public class OpCodeTranslator { case let IR.storeGlobal(index, name): return OpCode.storeGlobal(targetGlobal: index, source: sp, name: name) } } - + private func argumentOffset(index: Int) -> Int { - return index + index } - + private func returnAddressOffset() -> Int { - return argumentCount + argumentCount } - + private func localOffset(index: Int) -> Int { - return argumentCount + 1 + index + argumentCount + 1 + index } } @@ -461,32 +451,31 @@ public class OpCodeTranslator { */ class FunctionTranslator { let env: GlobalStaticEnvironment - + init(env: GlobalStaticEnvironment) { self.env = env } - + func translateFunction(func: FunctionDefinition, optimize: Bool) throws -> (Type.Function, CodeSegment) { var typedExp = try `func`.body.typeCheck(env: env.newScope(args: `func`.args)) - if (optimize) { + if optimize { typedExp = typedExp.optimize() } - + if let returnType = `func`.returnType { try typedExp.expectAssignableTo(expectedType: returnType, location: `func`.body.location) } - + let basicBlocks = typedExp.translateToIR() - - if (optimize) { + + if optimize { basicBlocks.optimize() } - - return (Type.Function.function(argumentTypes: `func`.args.map { $0.1 }, returnType: typedExp.type), try basicBlocks.translateToCode(argumentCount: `func`.args.count)) + + return (Type.Function.function(argumentTypes: `func`.args.map(\.1), returnType: typedExp.type), try basicBlocks.translateToCode(argumentCount: `func`.args.count)) } } - extension TypedExpression { func translateToIR() -> BasicBlockGraph { let translator = Translator() @@ -497,14 +486,13 @@ extension TypedExpression { } public class Translator { - let basicBlocks = BasicBlockGraph() private var currentBlock: BasicBlock - + init() { currentBlock = basicBlocks.start } - + func emitCode(typedExpression: TypedExpression) { switch typedExpression { case let .ref(bindingReference): @@ -537,62 +525,59 @@ public class Translator { currentBlock += IR.pushUnit case let .if(condition, consequent, alternative, _): emitCode(typedExpression: condition) - + let afterBlock = BasicBlock() - - + if let alternative = alternative { let trueBlock = BasicBlock() let falseBlock = BasicBlock() - + currentBlock.endWithBranch(trueBlock: trueBlock, falseBlock: falseBlock) - + currentBlock = trueBlock emitCode(typedExpression: consequent) currentBlock.endWithJumpTo(next: afterBlock) - + currentBlock = falseBlock emitCode(typedExpression: alternative) - + currentBlock.endWithJumpTo(next: afterBlock) - - } - else { + } else { let trueBlock = BasicBlock() - + currentBlock.endWithBranch(trueBlock: trueBlock, falseBlock: afterBlock) - + currentBlock = trueBlock emitCode(typedExpression: consequent) currentBlock += IR.pop currentBlock.endWithJumpTo(next: afterBlock) - + afterBlock += IR.pushUnit } - + currentBlock = afterBlock case let .while(condition, body): - + let loopHead = BasicBlock() let loopBody = BasicBlock() let afterLoop = BasicBlock() - + currentBlock.endWithJumpTo(next: loopHead) - + currentBlock = loopHead emitCode(typedExpression: condition) currentBlock.endWithBranch(trueBlock: loopBody, falseBlock: afterLoop) - + currentBlock = loopBody emitCode(typedExpression: body) currentBlock += IR.pop currentBlock.endWithJumpTo(next: loopHead) - + currentBlock = afterLoop currentBlock += IR.pushUnit } } - + func emitCode(binary: TypedExpression.Binary) { emitCode(typedExpression: binary.lhs) emitCode(typedExpression: binary.rhs) @@ -609,10 +594,9 @@ public class Translator { currentBlock += IR.concatString case let .relational(op, _, _): emitCode(relationalOp: op) - } } - + func emitCode(relationalOp: RelationalOp) { switch relationalOp { case .equals: @@ -632,7 +616,7 @@ public class Translator { currentBlock += IR.not } } - + func emitStore(binding: Binding) { switch binding { case let .local(name, _, index, _): @@ -643,7 +627,7 @@ public class Translator { fatalError("can't store into arguments") } } - + func emitLoad(binding: Binding) { switch binding { case let .local(name, _, index, _): diff --git a/Swona/Sources/Swona/Types.swift b/Sources/Swona/Types.swift similarity index 90% rename from Swona/Sources/Swona/Types.swift rename to Sources/Swona/Types.swift index 3aed744..67e552d 100644 --- a/Swona/Sources/Swona/Types.swift +++ b/Sources/Swona/Types.swift @@ -1,29 +1,29 @@ /** * Represents the types supported in the language. */ -public indirect enum Type: CustomStringConvertible { +public indirect enum Type: Equatable, Hashable, CustomStringConvertible { case string case int case boolean case unit case function(Function) case array(elementType: Type) - - public enum Function { + + public enum Function: Equatable, Hashable { case function(argumentTypes: [Type], returnType: Type) } - + func supports(op: RelationalOp) -> Bool { switch self { case .unit, .function: return false case .array: - return [.equals, .notEquals].contains(op) + return [.equals, .notEquals].contains(op) default: return true } } - + public var description: String { switch self { case .string: @@ -39,7 +39,7 @@ public indirect enum Type: CustomStringConvertible { case let .function(function): switch function { case let .function(argumentTypes, returnType): - return "(\(argumentTypes.map { $0.description }.joined(separator: ", "))) -> \(returnType)" + return "(\(argumentTypes.map(\.description).joined(separator: ", "))) -> \(returnType)" } } } @@ -60,38 +60,38 @@ public indirect enum Type: CustomStringConvertible { public indirect enum TypedExpression: CustomStringConvertible { /** Reference to a variable. */ case ref(bindingReference: BindingReference) - + /** Literal value */ case lit(value: Value, type: Type) - + /** Logical not. */ case not(exp: TypedExpression) - + /** Function call. */ - case call(func: TypedExpression, args: Array, type: Type) - + case call(func: TypedExpression, args: [TypedExpression], type: Type) + case binary(Binary) - + /** Binary operators. */ public enum Binary: CustomStringConvertible { /** Numeric addition */ case plus(lhs: TypedExpression, rhs: TypedExpression, type: Type) - + /** Numeric subtraction */ case minus(lhs: TypedExpression, rhs: TypedExpression, type: Type) - + /** Numeric multiplication */ case multiply(lhs: TypedExpression, rhs: TypedExpression, type: Type) - + /** Numeric division */ case divide(lhs: TypedExpression, rhs: TypedExpression, type: Type) - + /** String concatenation */ case concatString(lhs: TypedExpression, rhs: TypedExpression) - + /** =, !=, <, >, <=, >= */ case relational(op: RelationalOp, lhs: TypedExpression, rhs: TypedExpression) - + var lhs: TypedExpression { switch self { case let .plus(lhs, _, _), @@ -103,7 +103,7 @@ public indirect enum TypedExpression: CustomStringConvertible { return lhs } } - + var rhs: TypedExpression { switch self { case let .plus(_, rhs, _), @@ -115,8 +115,7 @@ public indirect enum TypedExpression: CustomStringConvertible { return rhs } } - - + var type: Type { switch self { case let .plus(_, _, type): @@ -133,7 +132,7 @@ public indirect enum TypedExpression: CustomStringConvertible { return Type.boolean } } - + public var description: String { switch self { case let .plus(lhs, rhs, _): @@ -148,25 +147,24 @@ public indirect enum TypedExpression: CustomStringConvertible { return "[ConcatString \(lhs) \(rhs)]" case let .relational(op, lhs, rhs): return "[\(op) \(lhs) \(rhs)]" - } } } - + case assign(variable: BindingReference, expression: TypedExpression) - + case `var`(variable: BindingReference, expression: TypedExpression) - + case `if`(condition: TypedExpression, consequent: TypedExpression, alternative: TypedExpression?, type: Type) - + case `while`(condition: TypedExpression, body: TypedExpression) - - case expressionList(expressions: Array, type: Type) - + + case expressionList(expressions: [TypedExpression], type: Type) + static var empty: TypedExpression { - return .expressionList(expressions: [], type: Type.unit) + .expressionList(expressions: [], type: Type.unit) } - + public var type: Type { switch self { case let .ref(bindingReference): @@ -202,9 +200,8 @@ public indirect enum TypedExpression: CustomStringConvertible { case let .binary(binary): return binary.type } - } - + public var description: String { switch self { case let .ref(bindingReference): @@ -227,7 +224,6 @@ public indirect enum TypedExpression: CustomStringConvertible { return "[ExpressionList \(expressions)]" case let .binary(binary): return binary.description - } } } @@ -270,22 +266,22 @@ extension Expression { guard case let .function(function) = typedFunc.type else { throw TypeCheckException(errorMessage: "expected function type for call, but got \(typedFunc.type)", sourceLocation: location) } - + switch function { case let .function(argumentTypes, returnType): let expectedArgTypes = argumentTypes - - if (args.count != expectedArgTypes.count) { + + if args.count != expectedArgTypes.count { throw TypeCheckException(errorMessage: "expected \(expectedArgTypes.count) arguments, but got \(args.count)", sourceLocation: location) } - + let typedArgs = try args.enumerated().map { i, arg in try arg.typeCheckExpected(expectedType: expectedArgTypes[i], env: env) } - + return TypedExpression.call(func: typedFunc, args: typedArgs, type: returnType) } case let .assign(variable, expression, location): let binding = try env.lookupBinding(name: variable, location: location) - if (!binding.mutable) { + if !binding.mutable { throw TypeCheckException(errorMessage: "can't assign to immutable variable \(binding.name)", sourceLocation: location) } let typedLhs = try expression.typeCheckExpected(expectedType: binding.type, env: env) @@ -298,15 +294,14 @@ extension Expression { let typedCondition = try condition.typeCheckExpected(expectedType: Type.boolean, env: env) let typedConsequent = try consequent.typeCheck(env: env) let typedAlternative = try alternative?.typeCheck(env: env) - + let type: Type - if (typedAlternative != nil) && (typedConsequent.type == typedAlternative?.type) { + if typedAlternative != nil, typedConsequent.type == typedAlternative?.type { type = typedConsequent.type - } - else { + } else { type = Type.unit } - + return TypedExpression.if(condition: typedCondition, consequent: typedConsequent, alternative: typedAlternative, type: type) case let .while(condition, body, _): let typedCondition = try condition.typeCheckExpected(expectedType: Type.boolean, env: env) @@ -319,9 +314,9 @@ extension Expression { return TypedExpression.expressionList(expressions: expressions, type: lastType) } } - + func typeCheckExpected(expectedType: Type, env: StaticEnvironment) throws -> TypedExpression { - return try typeCheck(env: env).expectAssignableTo(expectedType: expectedType, location: location) + try typeCheck(env: env).expectAssignableTo(expectedType: expectedType, location: location) } } @@ -330,12 +325,11 @@ extension Expression.Binary { switch self { case let .plus(lhs, rhs, _): let typedLhs = try lhs.typeCheck(env: env) - - if (typedLhs.type == .string) { + + if typedLhs.type == .string { let typedRhs = try rhs.typeCheck(env: env) return .binary(.concatString(lhs: typedLhs, rhs: typedRhs)) - } - else { + } else { let typedLhs2 = try typedLhs.expectAssignableTo(expectedType: Type.int, location: lhs.location) let typedRhs = try rhs.typeCheckExpected(expectedType: Type.int, env: env) return .binary(.plus(lhs: typedLhs2, rhs: typedRhs, type: Type.int)) @@ -362,34 +356,32 @@ extension Expression.Binary { return TypedExpression.if(condition: typedLhs, consequent: TypedExpression.lit(value: Value.bool(value: true), type: .boolean), alternative: typedRhs, type: Type.boolean) case let .relational(op, lhs, rhs, location): let (l, r) = try typeCheckMatching(env: env, lhs: lhs, rhs: rhs, location: location) - - if !(l.type.supports(op: op)) { + + if !l.type.supports(op: op) { throw TypeCheckException(errorMessage: "operator \(op) is not supported for type \(l.type)", sourceLocation: location) } - + return .binary(.relational(op: op, lhs: l, rhs: r)) - } } - + func typeCheckMatching(env: StaticEnvironment, lhs: Expression, rhs: Expression, location: SourceLocation) throws -> (TypedExpression, TypedExpression) { let typedLhs = try lhs.typeCheck(env: env) let typedRhs = try rhs.typeCheck(env: env) - - if (typedLhs.type != typedRhs.type) { + + if typedLhs.type != typedRhs.type { throw TypeCheckException(errorMessage: "lhs type \(typedLhs.type) did not match rhs type \(typedRhs.type)", sourceLocation: location) } - + return (typedLhs, typedRhs) } } extension TypedExpression { @discardableResult func expectAssignableTo(expectedType: Type, location: SourceLocation) throws -> TypedExpression { - if (type == expectedType) { + if type == expectedType { return self - } - else { + } else { throw TypeCheckException(errorMessage: "expected type \(expectedType), but was \(type)", sourceLocation: location) } } @@ -402,12 +394,11 @@ extension StaticEnvironment { } return result } - + func bindType(name: String, type: Type, location: SourceLocation, mutable: Bool) throws -> BindingReference { do { - return try self.bind(name: name, type: type, mutable: mutable) - } - catch { + return try bind(name: name, type: type, mutable: mutable) + } catch { throw TypeCheckException(errorMessage: "variable already bound '\(name)'", sourceLocation: location) } } diff --git a/Swona/Sources/Swona/VM.swift b/Sources/Swona/VM.swift similarity index 79% rename from Swona/Sources/Swona/VM.swift rename to Sources/Swona/VM.swift index c614f36..527d4b0 100644 --- a/Swona/Sources/Swona/VM.swift +++ b/Sources/Swona/VM.swift @@ -5,9 +5,8 @@ * the original size. (For example the call stack of the system.) */ public struct DataSegment: CustomStringConvertible { - private var bindings = [Value?](repeating: nil, count: 1024) - + subscript(index: Int) -> Value { /** * Assigns a new value to existing variable. @@ -16,7 +15,7 @@ public struct DataSegment: CustomStringConvertible { ensureCapacity(capacity: index + 1) bindings[index] = value } - + /** * Returns the value bound to given variable. */ @@ -28,30 +27,27 @@ public struct DataSegment: CustomStringConvertible { return result } } - + /** * Returns the value bound to given variable. */ subscript(base: Int, offset: Int) -> Value { - get { - // We don't need to call ensureCapacity here because we can never read uninitialized values - guard let result = bindings[base + offset] else { fatalError("uninitialized read at \(base)+\(offset)") } - return result - } + // We don't need to call ensureCapacity here because we can never read uninitialized values + guard let result = bindings[base + offset] else { fatalError("uninitialized read at \(base)+\(offset)") } + return result } - + private mutating func ensureCapacity(capacity: Int) { - if (capacity > bindings.count) { + if capacity > bindings.count { let newSize = max(capacity, bindings.count * 2) - bindings.count - + bindings = bindings + [Value?](repeating: nil, count: newSize) } } public var description: String { - return "[\(bindings.prefix(10).map { $0!.description }.joined(separator: ", "))]" + "[\(bindings.prefix(10).map { $0!.description }.joined(separator: ", "))]" } - } public enum OpCode: CustomStringConvertible { @@ -60,14 +56,14 @@ public enum OpCode: CustomStringConvertible { case let .jump(address): return .jump(address: baseAddress + address) case let .jumpIfFalse(sp, address): - return .jumpIfFalse(sp: sp ,address: baseAddress + address) + return .jumpIfFalse(sp: sp, address: baseAddress + address) default: return self } } - + case not(target: Int, source: Int) - + public enum Binary { case add(target: Int, lhs: Int, rhs: Int) case subtract(target: Int, lhs: Int, rhs: Int) @@ -77,7 +73,7 @@ public enum OpCode: CustomStringConvertible { case lessThan(target: Int, lhs: Int, rhs: Int) case lessThanOrEqual(target: Int, lhs: Int, rhs: Int) case concatString(target: Int, lhs: Int, rhs: Int) - + var name: String { switch self { case .add: @@ -96,10 +92,9 @@ public enum OpCode: CustomStringConvertible { return "<=" case .concatString: return "++" - } } - + public var target: Int { switch self { case let .add(target, _, _), @@ -111,10 +106,9 @@ public enum OpCode: CustomStringConvertible { let .lessThanOrEqual(target, _, _), let .concatString(target, _, _): return target - } } - + public var lhs: Int { switch self { case let .add(_, lhs, _), @@ -126,10 +120,9 @@ public enum OpCode: CustomStringConvertible { let .lessThanOrEqual(_, lhs, _), let .concatString(_, lhs, _): return lhs - } } - + public var rhs: Int { switch self { case let .add(_, _, rhs), @@ -141,12 +134,10 @@ public enum OpCode: CustomStringConvertible { let .lessThanOrEqual(_, _, rhs), let .concatString(_, _, rhs): return rhs - } } - } - + case binary(Binary) case nop case call(offset: Int, argumentCount: Int) @@ -158,13 +149,13 @@ public enum OpCode: CustomStringConvertible { case storeGlobal(targetGlobal: Int, source: Int, name: String) case jump(address: Int) case jumpIfFalse(sp: Int, address: Int) - + public var description: String { switch self { case let .not(target, source): return "stack[fp+\(target)] = !stack[fp+\(source)]" case let .binary(binary): - return "stack[fp+\(binary.target)] = stack[fp+\(binary.lhs)] \(binary.name) stack[fp+\(binary.rhs)]" + return "stack[fp+\(binary.target)] = stack[fp+\(binary.lhs)] \(binary.name) stack[fp+\(binary.rhs)]" case .nop: return "Nop" case let .call(offset, argumentCount): @@ -185,7 +176,6 @@ public enum OpCode: CustomStringConvertible { return "jump \(address)" case let .jumpIfFalse(sp, address): return "jump-if-false stack[fp+\(sp)] \(address)" - } } } @@ -195,40 +185,38 @@ public enum OpCode: CustomStringConvertible { */ public struct CodeSegment: CustomStringConvertible { private let opCodes: [OpCode] - + init(opCodes: [OpCode]) { self.opCodes = opCodes } - + init() { self.init(opCodes: []) } - + /** * Returns [OpCode] with given address. */ subscript(address: Int) -> OpCode { - get { - return opCodes[address] - } + opCodes[address] } - + var size: Int { - return opCodes.count + opCodes.count } - + func mergeWithRelocatedSegment(segment: CodeSegment) -> (CodeSegment, Int) { - var ops = Array() + var ops = [OpCode]() let address = opCodes.count - + ops += opCodes for op in segment.opCodes { ops += [op.relocate(baseAddress: address)] } - + return (CodeSegment(opCodes: ops), address) } - + /** * Extracts given region of code, relocated so that every address makes sense * (assuming the addresses stay within the region). @@ -237,14 +225,13 @@ public struct CodeSegment: CustomStringConvertible { */ func getRegion(address: Int, size: Int) -> CodeSegment { let ops = opCodes.subList(fromIndex: address, toIndex: address + size) - + return CodeSegment(opCodes: ops.map { $0.relocate(baseAddress: -address) }) } - + public var description: String { - return opCodes.enumerated().map { i, op in "\(i) \(op)" }.joined(separator: "\n") + opCodes.enumerated().map { i, op in "\(i) \(op)" }.joined(separator: "\n") } - } /** @@ -252,21 +239,21 @@ public struct CodeSegment: CustomStringConvertible { */ struct ThreadState: CustomStringConvertible { private var stack = DataSegment() - + /** Program counter: the next instruction to be executed */ var pc = 0 - + /** Frame pointer */ var fp = 0 - + subscript(offset: Int) -> Value { /** * Accesses data relative to current frame. */ get { - return stack[fp, offset] + stack[fp, offset] } - + /** * Accesses data relative to current frame. */ @@ -274,32 +261,31 @@ struct ThreadState: CustomStringConvertible { stack[fp + offset] = value } } - - + /** * Returns the arguments from current stack-frame. */ - func getArgs(count: Int) -> Array { - var values = Array() - - for i in 0...(count - 1) { + func getArgs(count: Int) -> [Value] { + var values = [Value]() + + for i in 0 ... (count - 1) { values += [self[i]] } - + return values } - + public var description: String { - return " pc = \(pc)\n fp = \(fp)\n data = \(stack)" + " pc = \(pc)\n fp = \(fp)\n data = \(stack)" } - + mutating func evalBinary(op: OpCode, f: (Value, Value) -> Value) { guard case let .binary(binary) = op else { fatalError("invalid op to evalBinary: \(op.description)") } self[binary.target] = f(self[binary.lhs], self[binary.rhs]) } - + mutating func evalBinaryBool(op: OpCode, f: (Value, Value) -> Bool) { guard case let .binary(binary) = op else { fatalError("invalid op to evalBinaryBool: \(op.description)") @@ -319,18 +305,18 @@ public struct EvaluationResult { * @see OpCode */ public class Evaluator { - private var globalData = DataSegment() - private let globalTypeEnvironment = GlobalStaticEnvironment() + var globalData = DataSegment() + let globalTypeEnvironment = GlobalStaticEnvironment() private var globalCode = CodeSegment() private let functionTranslator: FunctionTranslator - var trace = false + public var trace = false public var optimize = true - + public init(trace: Bool = false) { - self.functionTranslator = FunctionTranslator(env: globalTypeEnvironment) + functionTranslator = FunctionTranslator(env: globalTypeEnvironment) self.trace = trace } - + /** * Binds a global name to given value. */ @@ -338,53 +324,62 @@ public class Evaluator { let bindingReference = try globalTypeEnvironment.bind(name: name, type: value.type, mutable: mutable) globalData[bindingReference.binding.index] = value } - + /** * Evaluates code which can either be a definition, statement or expression. * If the code represented an expression, returns its value. Otherwise [Value.Unit] is returned. */ @discardableResult public func evaluate(code: String) throws -> EvaluationResult { - if (try LookaheadLexer(source: code).nextTokenIs(token: Token.keyword(.fun))) { + if try LookaheadLexer(source: code).nextTokenIs(token: Token.keyword(.fun)) { let definition = try parseFunctionDefinition(code: code) try bindFunction(func: definition) return EvaluationResult(value: Value.unit, type: Type.unit) - } - else { + } else { let (segment, type) = try translate(code: code) return EvaluationResult(value: try evaluateSegment(segment: segment), type: type) } } - + + /** + * Loads contents of given file. + */ + public func loadResource(source: String, file: String) throws { + let defs = try parseFunctionDefinitions(code: source, file: file) + + for def in defs { + try bindFunction(func: def) + } + } + /** * Returns the names of all global bindings. */ - func bindingsNames() -> Set { - return globalTypeEnvironment.bindingNames() + public func bindingsNames() -> Set { + globalTypeEnvironment.bindingNames() } - + /** * Translates given code to opcodes and returns string representation of the opcodes. */ public func dump(code: String) throws -> String { let exp = try parseAndTypeCheck(code: code) - if case let .ref(bindingReference) = exp, case .function(_) = exp.type { + if case let .ref(bindingReference) = exp, case .function = exp.type { let value = globalData[bindingReference.binding.index] guard case let .function(function) = value else { fatalError("expected function, got \(value)") } - + switch function { case let .compound(address, codeSize, _, _): return globalCode.getRegion(address: address, size: codeSize).description - case .native(_): + case .native: return "native function \(value)" } - } - else { + } else { return try translate(exp: exp).description } } - + /** * Compiles and binds a global function. */ @@ -394,29 +389,27 @@ public class Evaluator { // (most probably to type-checking), we need to unbind the binding. var binding: Binding? if let returnType = `func`.returnType { - binding = try globalTypeEnvironment.bind(name: `func`.name, type: Type.function(.function(argumentTypes: `func`.args.map { $0.1 }, returnType: returnType)), mutable: false).binding - + binding = try globalTypeEnvironment.bind(name: `func`.name, type: Type.function(.function(argumentTypes: `func`.args.map(\.1), returnType: returnType)), mutable: false).binding } - + do { let (signature, code) = try functionTranslator.translateFunction(func: `func`, optimize: optimize) - + let (newGlobalCode, address) = globalCode.mergeWithRelocatedSegment(segment: code) globalCode = newGlobalCode - if (binding == nil) { + if binding == nil { binding = try globalTypeEnvironment.bind(name: `func`.name, type: .function(signature), mutable: false).binding } - + globalData[binding!.index] = Value.function(.compound(address: address, codeSize: code.size, name: `func`.name, signature: signature)) - } - catch { - if (binding != nil) { + } catch { + if binding != nil { globalTypeEnvironment.unbind(name: `func`.name) } throw error } } - + /** * Translates code to opcodes. */ @@ -424,119 +417,116 @@ public class Evaluator { let exp = try parseAndTypeCheck(code: code) return (try translate(exp: exp), exp.type) } - - private func translate(exp: TypedExpression) throws -> CodeSegment { + + public func translate(exp: TypedExpression) throws -> CodeSegment { let optExp: TypedExpression - if (optimize) { + if optimize { optExp = exp.optimize() - } - else { + } else { optExp = exp } - + let blocks = optExp.translateToIR() - - if (optimize) { + + if optimize { blocks.optimize() } - + return try blocks.translateToCode(argumentCount: 0) } - + private func parseAndTypeCheck(code: String) throws -> TypedExpression { - return try parseExpression(code: code).typeCheck(env: globalTypeEnvironment) + try parseExpression(code: code).typeCheck(env: globalTypeEnvironment) } - + /** * Evaluates given code segment. */ - private func evaluateSegment(segment: CodeSegment) throws -> Value { + public func evaluateSegment(segment: CodeSegment) throws -> Value { let (code, startAddress) = globalCode.mergeWithRelocatedSegment(segment: segment) let quitPointer = Value.pointer(value: -1, .code) - + var state = ThreadState() state.pc = startAddress state.fp = 0 state[0] = quitPointer var run = true - while(run) { + while run { let op = code[state.pc] - if (trace) { + if trace { print("\(state.pc.description.padStart(count: 4)): \(op.description.padEnd(count: 40)) [fp=\(state.fp)]") } - + state.pc += 1 - - switch(op) { + + switch op { case let .not(target, source): let s = state[source] guard case let .bool(value) = s else { fatalError("expected bool, got \(s)") } - state[target] = .bool(value: !(value)) + state[target] = .bool(value: !value) case let .binary(binary): - switch(binary) { - case .add : state.evalBinary(op: op) { l, r in l.plus(rhs: r) } - case .subtract : state.evalBinary(op: op) { l, r in l.minus(rhs: r) } - case .multiply : state.evalBinary(op: op) { l, r in l.times(rhs: r) } - case .divide : state.evalBinary(op: op) { l, r in l.div(rhs: r) } - case .equal : state.evalBinaryBool(op: op) { l, r in l == r } - case .lessThan : state.evalBinaryBool(op: op) { l, r in l.lessThan(r: r) } - case .lessThanOrEqual : state.evalBinaryBool(op: op) { l, r in ((l == r) || (l.lessThan(r: r))) } - case .concatString : state.evalBinary(op: op) { l, r in l.plus(rhs: r) } + switch binary { + case .add: state.evalBinary(op: op) { l, r in l.plus(rhs: r) } + case .subtract: state.evalBinary(op: op) { l, r in l.minus(rhs: r) } + case .multiply: state.evalBinary(op: op) { l, r in l.times(rhs: r) } + case .divide: state.evalBinary(op: op) { l, r in l.div(rhs: r) } + case .equal: state.evalBinaryBool(op: op) { l, r in l == r } + case .lessThan: state.evalBinaryBool(op: op) { l, r in l.lessThan(r: r) } + case .lessThanOrEqual: state.evalBinaryBool(op: op) { l, r in (l == r) || l.lessThan(r: r) } + case .concatString: state.evalBinary(op: op) { l, r in l.plus(rhs: r) } } case let .loadConstant(target, value): state[target] = value case let .copy(target, source, _): state[target] = state[source] case let .loadGlobal(target, sourceGlobal, _): state[target] = globalData[sourceGlobal] case let .storeGlobal(targetGlobal, source, _): globalData[targetGlobal] = state[source] - case let .jump(address) : state.pc = address - case let .jumpIfFalse(sp, address) : + case let .jump(address): state.pc = address + case let .jumpIfFalse(sp, address): let s = state[sp] guard case let .bool(value) = s else { fatalError("expected bool, got \(s)") } - if !(value) { + if !value { state.pc = address } - case .call(_, _): try evalCall(op: op, state: &state) + case .call: try evalCall(op: op, state: &state) case let .restoreFrame(sp): state.fp -= sp case let .ret(valuePointer, returnAddressPointer): let returnAddress = state[returnAddressPointer] state[0] = state[valuePointer] - if (returnAddress == quitPointer) { + if returnAddress == quitPointer { run = false break } guard case let .pointer(value, type) = returnAddress, type == .code else { fatalError("expected bool, got \(returnAddress)") } - + state.pc = value case .nop: break } } return state[0] } - + private func evalCall(op: OpCode, state: inout ThreadState) throws { guard case let .call(offset, argumentCount) = op else { fatalError("expected call, got: \(op)") } - + guard case let .function(`func`) = state[offset] else { fatalError("expected function, got: \(op)") } - + state.fp += offset - argumentCount switch `func` { case let .compound(address, _, _, _): state[argumentCount] = Value.pointer(value: state.pc, .code) state.pc = address - case let .native(`func`): + case let .native(`func`, _, _): let args = state.getArgs(count: argumentCount) - state[0] = try `func`.func.function(args) + state[0] = try `func`.function(args) } - } } - diff --git a/Swona/.sourcery.yml b/Swona/.sourcery.yml deleted file mode 100644 index 1c58fec..0000000 --- a/Swona/.sourcery.yml +++ /dev/null @@ -1,6 +0,0 @@ -sources: - - ./Sources/Swona -templates: - - ./Sources/Swona/Templates -output: - ./Sources/Swona/Autogenerated \ No newline at end of file diff --git a/Swona/Sources/Swona/AutoEquatable.swift b/Swona/Sources/Swona/AutoEquatable.swift deleted file mode 100644 index 191ef38..0000000 --- a/Swona/Sources/Swona/AutoEquatable.swift +++ /dev/null @@ -1,19 +0,0 @@ -protocol AutoEquatable {} - -extension Token: AutoEquatable {} -extension Token.Keyword: AutoEquatable {} -extension Token.Operator: AutoEquatable {} -extension Token.Punctuation: AutoEquatable {} -extension Value: AutoEquatable {} -extension Value.Function: AutoEquatable {} -extension Type: AutoEquatable {} -extension Type.Function: AutoEquatable {} -extension OpCode: AutoEquatable {} -extension OpCode.Binary: AutoEquatable {} -extension Binding: AutoEquatable {} -extension RelationalOp: AutoEquatable {} -extension Expression: AutoEquatable {} -extension Expression.Binary: AutoEquatable {} -extension IR: AutoEquatable {} -extension IR.LocalFrameIR: AutoEquatable {} - diff --git a/Swona/Sources/Swona/Autogenerated/AutoEquatable.generated.swift b/Swona/Sources/Swona/Autogenerated/AutoEquatable.generated.swift deleted file mode 100644 index 4523e3e..0000000 --- a/Swona/Sources/Swona/Autogenerated/AutoEquatable.generated.swift +++ /dev/null @@ -1,494 +0,0 @@ -// Generated using Sourcery 0.16.0 — https://github.com/krzysztofzablocki/Sourcery -// DO NOT EDIT - -// swiftlint:disable file_length -fileprivate func compareOptionals(lhs: T?, rhs: T?, compare: (_ lhs: T, _ rhs: T) -> Bool) -> Bool { - switch (lhs, rhs) { - case let (lValue?, rValue?): - return compare(lValue, rValue) - case (nil, nil): - return true - default: - return false - } -} - -fileprivate func compareArrays(lhs: [T], rhs: [T], compare: (_ lhs: T, _ rhs: T) -> Bool) -> Bool { - guard lhs.count == rhs.count else { return false } - for (idx, lhsItem) in lhs.enumerated() { - guard compare(lhsItem, rhs[idx]) else { return false } - } - - return true -} - - -// MARK: - AutoEquatable for classes, protocols, structs - -// MARK: - AutoEquatable for Enums -// MARK: - Binding AutoEquatable -extension Binding: Equatable {} -public func == (lhs: Binding, rhs: Binding) -> Bool { - switch (lhs, rhs) { - case (.global(let lhs), .global(let rhs)): - if lhs.name != rhs.name { return false } - if lhs.type != rhs.type { return false } - if lhs.index != rhs.index { return false } - if lhs.mutable != rhs.mutable { return false } - return true - case (.local(let lhs), .local(let rhs)): - if lhs.name != rhs.name { return false } - if lhs.type != rhs.type { return false } - if lhs.index != rhs.index { return false } - if lhs.mutable != rhs.mutable { return false } - return true - case (.argument(let lhs), .argument(let rhs)): - if lhs.name != rhs.name { return false } - if lhs.type != rhs.type { return false } - if lhs.index != rhs.index { return false } - return true - default: return false - } -} -// MARK: - Expression AutoEquatable -extension Expression: Equatable {} -public func == (lhs: Expression, rhs: Expression) -> Bool { - switch (lhs, rhs) { - case (.ref(let lhs), .ref(let rhs)): - if lhs.name != rhs.name { return false } - if lhs.location != rhs.location { return false } - return true - case (.lit(let lhs), .lit(let rhs)): - if lhs.value != rhs.value { return false } - if lhs.location != rhs.location { return false } - return true - case (.not(let lhs), .not(let rhs)): - if lhs.exp != rhs.exp { return false } - if lhs.location != rhs.location { return false } - return true - case (.call(let lhs), .call(let rhs)): - if lhs.func != rhs.func { return false } - if lhs.args != rhs.args { return false } - return true - case (.binary(let lhs), .binary(let rhs)): - return lhs == rhs - case (.assign(let lhs), .assign(let rhs)): - if lhs.variable != rhs.variable { return false } - if lhs.expression != rhs.expression { return false } - if lhs.location != rhs.location { return false } - return true - case (.`var`(let lhs), .`var`(let rhs)): - if lhs.variable != rhs.variable { return false } - if lhs.expression != rhs.expression { return false } - if lhs.mutable != rhs.mutable { return false } - if lhs.location != rhs.location { return false } - return true - case (.`if`(let lhs), .`if`(let rhs)): - if lhs.condition != rhs.condition { return false } - if lhs.consequent != rhs.consequent { return false } - if lhs.alternative != rhs.alternative { return false } - if lhs.location != rhs.location { return false } - return true - case (.`while`(let lhs), .`while`(let rhs)): - if lhs.condition != rhs.condition { return false } - if lhs.body != rhs.body { return false } - if lhs.location != rhs.location { return false } - return true - case (.`expressionList`(let lhs), .`expressionList`(let rhs)): - if lhs.expressions != rhs.expressions { return false } - if lhs.location != rhs.location { return false } - return true - default: return false - } -} -// MARK: - Expression.Binary AutoEquatable -extension Expression.Binary: Equatable {} -public func == (lhs: Expression.Binary, rhs: Expression.Binary) -> Bool { - switch (lhs, rhs) { - case (.plus(let lhs), .plus(let rhs)): - if lhs.lhs != rhs.lhs { return false } - if lhs.rhs != rhs.rhs { return false } - if lhs.location != rhs.location { return false } - return true - case (.minus(let lhs), .minus(let rhs)): - if lhs.lhs != rhs.lhs { return false } - if lhs.rhs != rhs.rhs { return false } - if lhs.location != rhs.location { return false } - return true - case (.multiply(let lhs), .multiply(let rhs)): - if lhs.lhs != rhs.lhs { return false } - if lhs.rhs != rhs.rhs { return false } - if lhs.location != rhs.location { return false } - return true - case (.divide(let lhs), .divide(let rhs)): - if lhs.lhs != rhs.lhs { return false } - if lhs.rhs != rhs.rhs { return false } - if lhs.location != rhs.location { return false } - return true - case (.and(let lhs), .and(let rhs)): - if lhs.lhs != rhs.lhs { return false } - if lhs.rhs != rhs.rhs { return false } - if lhs.location != rhs.location { return false } - return true - case (.or(let lhs), .or(let rhs)): - if lhs.lhs != rhs.lhs { return false } - if lhs.rhs != rhs.rhs { return false } - if lhs.location != rhs.location { return false } - return true - case (.relational(let lhs), .relational(let rhs)): - if lhs.op != rhs.op { return false } - if lhs.lhs != rhs.lhs { return false } - if lhs.rhs != rhs.rhs { return false } - if lhs.location != rhs.location { return false } - return true - default: return false - } -} -// MARK: - IR AutoEquatable -extension IR: Equatable {} -public func == (lhs: IR, rhs: IR) -> Bool { - switch (lhs, rhs) { - case (.not, .not): - return true - case (.add, .add): - return true - case (.subtract, .subtract): - return true - case (.multiply, .multiply): - return true - case (.divide, .divide): - return true - case (.equal, .equal): - return true - case (.lessThan, .lessThan): - return true - case (.lessThanOrEqual, .lessThanOrEqual): - return true - case (.concatString, .concatString): - return true - case (.pop, .pop): - return true - case (.dup, .dup): - return true - case (.call(let lhs), .call(let rhs)): - return lhs == rhs - case (.restoreFrame, .restoreFrame): - return true - case (.ret, .ret): - return true - case (.pushUnit, .pushUnit): - return true - case (.push(let lhs), .push(let rhs)): - return lhs == rhs - case (.loadGlobal(let lhs), .loadGlobal(let rhs)): - if lhs.index != rhs.index { return false } - if lhs.name != rhs.name { return false } - return true - case (.loadArgument(let lhs), .loadArgument(let rhs)): - if lhs.index != rhs.index { return false } - if lhs.name != rhs.name { return false } - return true - case (.storeGlobal(let lhs), .storeGlobal(let rhs)): - if lhs.index != rhs.index { return false } - if lhs.name != rhs.name { return false } - return true - case (.localFrameIR(let lhs), .localFrameIR(let rhs)): - return lhs == rhs - default: return false - } -} -// MARK: - IR.LocalFrameIR AutoEquatable -extension IR.LocalFrameIR: Equatable {} -public func == (lhs: IR.LocalFrameIR, rhs: IR.LocalFrameIR) -> Bool { - switch (lhs, rhs) { - case (.loadLocal(let lhs), .loadLocal(let rhs)): - if lhs.index != rhs.index { return false } - if lhs.name != rhs.name { return false } - return true - case (.storeLocal(let lhs), .storeLocal(let rhs)): - if lhs.index != rhs.index { return false } - if lhs.name != rhs.name { return false } - return true - default: return false - } -} -// MARK: - OpCode AutoEquatable -extension OpCode: Equatable {} -public func == (lhs: OpCode, rhs: OpCode) -> Bool { - switch (lhs, rhs) { - case (.not(let lhs), .not(let rhs)): - if lhs.target != rhs.target { return false } - if lhs.source != rhs.source { return false } - return true - case (.binary(let lhs), .binary(let rhs)): - return lhs == rhs - case (.nop, .nop): - return true - case (.call(let lhs), .call(let rhs)): - if lhs.offset != rhs.offset { return false } - if lhs.argumentCount != rhs.argumentCount { return false } - return true - case (.restoreFrame(let lhs), .restoreFrame(let rhs)): - return lhs == rhs - case (.ret(let lhs), .ret(let rhs)): - if lhs.valuePointer != rhs.valuePointer { return false } - if lhs.returnAddressPointer != rhs.returnAddressPointer { return false } - return true - case (.copy(let lhs), .copy(let rhs)): - if lhs.target != rhs.target { return false } - if lhs.source != rhs.source { return false } - if lhs.description != rhs.description { return false } - return true - case (.loadConstant(let lhs), .loadConstant(let rhs)): - if lhs.target != rhs.target { return false } - if lhs.value != rhs.value { return false } - return true - case (.loadGlobal(let lhs), .loadGlobal(let rhs)): - if lhs.target != rhs.target { return false } - if lhs.sourceGlobal != rhs.sourceGlobal { return false } - if lhs.name != rhs.name { return false } - return true - case (.storeGlobal(let lhs), .storeGlobal(let rhs)): - if lhs.targetGlobal != rhs.targetGlobal { return false } - if lhs.source != rhs.source { return false } - if lhs.name != rhs.name { return false } - return true - case (.jump(let lhs), .jump(let rhs)): - return lhs == rhs - case (.jumpIfFalse(let lhs), .jumpIfFalse(let rhs)): - if lhs.sp != rhs.sp { return false } - if lhs.address != rhs.address { return false } - return true - default: return false - } -} -// MARK: - OpCode.Binary AutoEquatable -extension OpCode.Binary: Equatable {} -public func == (lhs: OpCode.Binary, rhs: OpCode.Binary) -> Bool { - switch (lhs, rhs) { - case (.add(let lhs), .add(let rhs)): - if lhs.target != rhs.target { return false } - if lhs.lhs != rhs.lhs { return false } - if lhs.rhs != rhs.rhs { return false } - return true - case (.subtract(let lhs), .subtract(let rhs)): - if lhs.target != rhs.target { return false } - if lhs.lhs != rhs.lhs { return false } - if lhs.rhs != rhs.rhs { return false } - return true - case (.multiply(let lhs), .multiply(let rhs)): - if lhs.target != rhs.target { return false } - if lhs.lhs != rhs.lhs { return false } - if lhs.rhs != rhs.rhs { return false } - return true - case (.divide(let lhs), .divide(let rhs)): - if lhs.target != rhs.target { return false } - if lhs.lhs != rhs.lhs { return false } - if lhs.rhs != rhs.rhs { return false } - return true - case (.equal(let lhs), .equal(let rhs)): - if lhs.target != rhs.target { return false } - if lhs.lhs != rhs.lhs { return false } - if lhs.rhs != rhs.rhs { return false } - return true - case (.lessThan(let lhs), .lessThan(let rhs)): - if lhs.target != rhs.target { return false } - if lhs.lhs != rhs.lhs { return false } - if lhs.rhs != rhs.rhs { return false } - return true - case (.lessThanOrEqual(let lhs), .lessThanOrEqual(let rhs)): - if lhs.target != rhs.target { return false } - if lhs.lhs != rhs.lhs { return false } - if lhs.rhs != rhs.rhs { return false } - return true - case (.concatString(let lhs), .concatString(let rhs)): - if lhs.target != rhs.target { return false } - if lhs.lhs != rhs.lhs { return false } - if lhs.rhs != rhs.rhs { return false } - return true - default: return false - } -} -// MARK: - RelationalOp AutoEquatable -extension RelationalOp: Equatable {} -public func == (lhs: RelationalOp, rhs: RelationalOp) -> Bool { - switch (lhs, rhs) { - case (.equals, .equals): - return true - case (.notEquals, .notEquals): - return true - case (.lessThan, .lessThan): - return true - case (.lessThanOrEqual, .lessThanOrEqual): - return true - case (.greaterThan, .greaterThan): - return true - case (.greaterThanOrEqual, .greaterThanOrEqual): - return true - default: return false - } -} -// MARK: - Token AutoEquatable -extension Token: Equatable {} -public func == (lhs: Token, rhs: Token) -> Bool { - switch (lhs, rhs) { - case (.identifier(let lhs), .identifier(let rhs)): - return lhs == rhs - case (.literal(let lhs), .literal(let rhs)): - return lhs == rhs - case (.keyword(let lhs), .keyword(let rhs)): - return lhs == rhs - case (.`operator`(let lhs), .`operator`(let rhs)): - return lhs == rhs - case (.punctuation(let lhs), .punctuation(let rhs)): - return lhs == rhs - default: return false - } -} -// MARK: - Token.Keyword AutoEquatable -extension Token.Keyword: Equatable {} -public func == (lhs: Token.Keyword, rhs: Token.Keyword) -> Bool { - switch (lhs, rhs) { - case (.`else`, .`else`): - return true - case (.fun, .fun): - return true - case (.`if`, .`if`): - return true - case (.`var`, .`var`): - return true - case (.val, .val): - return true - case (.`while`, .`while`): - return true - default: return false - } -} -// MARK: - Token.Operator AutoEquatable -extension Token.Operator: Equatable {} -public func == (lhs: Token.Operator, rhs: Token.Operator) -> Bool { - switch (lhs, rhs) { - case (.plus, .plus): - return true - case (.minus, .minus): - return true - case (.multiply, .multiply): - return true - case (.divide, .divide): - return true - case (.equalEqual, .equalEqual): - return true - case (.notEqual, .notEqual): - return true - case (.not, .not): - return true - case (.lessThan, .lessThan): - return true - case (.greaterThan, .greaterThan): - return true - case (.lessThanOrEqual, .lessThanOrEqual): - return true - case (.greaterThanOrEqual, .greaterThanOrEqual): - return true - case (.and, .and): - return true - case (.or, .or): - return true - default: return false - } -} -// MARK: - Token.Punctuation AutoEquatable -extension Token.Punctuation: Equatable {} -public func == (lhs: Token.Punctuation, rhs: Token.Punctuation) -> Bool { - switch (lhs, rhs) { - case (.leftParen, .leftParen): - return true - case (.rightParen, .rightParen): - return true - case (.leftBrace, .leftBrace): - return true - case (.rightBrace, .rightBrace): - return true - case (.equal, .equal): - return true - case (.colon, .colon): - return true - case (.semicolon, .semicolon): - return true - case (.comma, .comma): - return true - default: return false - } -} -// MARK: - Type AutoEquatable -extension Type: Equatable {} -public func == (lhs: Type, rhs: Type) -> Bool { - switch (lhs, rhs) { - case (.string, .string): - return true - case (.int, .int): - return true - case (.boolean, .boolean): - return true - case (.unit, .unit): - return true - case (.function(let lhs), .function(let rhs)): - return lhs == rhs - case (.array(let lhs), .array(let rhs)): - return lhs == rhs - default: return false - } -} -// MARK: - Type.Function AutoEquatable -extension Type.Function: Equatable {} -public func == (lhs: Type.Function, rhs: Type.Function) -> Bool { - switch (lhs, rhs) { - case (.function(let lhs), .function(let rhs)): - if lhs.argumentTypes != rhs.argumentTypes { return false } - if lhs.returnType != rhs.returnType { return false } - return true - } -} -// MARK: - Value AutoEquatable -extension Value: Equatable {} -public func == (lhs: Value, rhs: Value) -> Bool { - switch (lhs, rhs) { - case (.unit, .unit): - return true - case (.string(let lhs), .string(let rhs)): - return lhs == rhs - case (.bool(let lhs), .bool(let rhs)): - return lhs == rhs - case (.integer(let lhs), .integer(let rhs)): - return lhs == rhs - case (.function(let lhs), .function(let rhs)): - return lhs == rhs - case (.array(let lhs), .array(let rhs)): - if lhs.elements != rhs.elements { return false } - if lhs.elementType != rhs.elementType { return false } - return true - case (.pointer(let lhs), .pointer(let rhs)): - if lhs.value != rhs.value { return false } - if lhs.1 != rhs.1 { return false } - return true - default: return false - } -} -// MARK: - Value.Function AutoEquatable -extension Value.Function: Equatable {} -public func == (lhs: Value.Function, rhs: Value.Function) -> Bool { - switch (lhs, rhs) { - case (.compound(let lhs), .compound(let rhs)): - if lhs.address != rhs.address { return false } - if lhs.codeSize != rhs.codeSize { return false } - if lhs.name != rhs.name { return false } - if lhs.signature != rhs.signature { return false } - return true - case (.native(let lhs), .native(let rhs)): - if lhs.func != rhs.func { return false } - if lhs.name != rhs.name { return false } - if lhs.signature != rhs.signature { return false } - return true - default: return false - } -} diff --git a/Swona/Sources/Swona/Helpers.swift b/Swona/Sources/Swona/Helpers.swift deleted file mode 100644 index 791117b..0000000 --- a/Swona/Sources/Swona/Helpers.swift +++ /dev/null @@ -1,182 +0,0 @@ -extension String { - // Modified from Kotlin (Apache License, Version 2.0). See LICENSE-THIRD-PARTY in this repo - public func lines() -> Array { - return self.split(separator: "\n", omittingEmptySubsequences: false) - } - - // Modified from Kotlin (Apache License, Version 2.0). See LICENSE-THIRD-PARTY in this repo - public func substring(startOffset: Int, endOffset: Int) -> String { - let substringStartIndex = self.index(self.startIndex, offsetBy: startOffset) - let substringEndIndex = self.index(self.startIndex, offsetBy: endOffset) - return String(self[substringStartIndex.. String { - if count <= self.count { - return self.substring(startOffset: 0, endOffset: self.count) - } - - var result = self - for _ in 1...(count - self.count) { - result += String(padChar) - } - return result - } - - // Modified from Kotlin (Apache License, Version 2.0). See LICENSE-THIRD-PARTY in this repo - public func padStart(count: Int, padChar: UnicodeScalar = " ") -> String { - if count <= self.count { - return self.substring(startOffset: 0, endOffset: self.count) - } - var result = "" - for _ in 1...(count - self.count) { - result += String(padChar) - } - return result + self - } -} - -extension Array { - // Modified from Kotlin (Apache License, Version 2.0). See LICENSE-THIRD-PARTY in this repo - func subList(fromIndex: Int, toIndex: Int) -> Array { - return Array(self[fromIndex.. Int) -> Int { - var sum: Int = 0 - for element in self { - sum += selector(element) - } - return sum - } - - // Modified from Kotlin (Apache License, Version 2.0). See LICENSE-THIRD-PARTY in this repo - func singleOrNull() -> Element? { - if (count == 1) { - return self[0] - } - else { - return nil - } - } - - // Modified from Kotlin (Apache License, Version 2.0). See LICENSE-THIRD-PARTY in this repo - public func single() -> Element { - switch count { - case 0: - fatalError("List is empty.") - case 1: - return self[0] - default: - fatalError("List has more than one element.") - } - } -} - -extension Int { - public init(_ value: Bool) { - self = value ? 1 : 0 - } -} - -/* - OrderedSet is part of the Swift.org open source project - - Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See http://swift.org/LICENSE.txt for license information - See http://swift.org/CONTRIBUTORS.txt for Swift project authors - - The below code was modified from the original. - */ - -/// An ordered set is an ordered collection of instances of `Element` in which -/// uniqueness of the objects is guaranteed. -public struct OrderedSet: Equatable, Collection { - public typealias Element = E - - var array: [Element] - var elementToIndex: [Element: Int] - - /// Creates an empty ordered set. - public init() { - self.array = [] - self.elementToIndex = [:] - } - - /// Creates an ordered set with the contents of `array`. - /// - /// If an element occurs more than once in `element`, only the first one - /// will be included. - public init(_ array: [Element]) { - self.init() - for element in array { - append(element) - } - } - - /// Returns the contents of the set as an array. - public var contents: [Element] { return array } - - /// Adds an element to the ordered set. - /// - /// If it already contains the element, then the set is unchanged. - /// - /// - returns: True if the item was inserted. - @discardableResult - public mutating func append(_ newElement: Element) -> Bool { - guard elementToIndex[newElement] == nil else { - return false - } - - array.append(newElement) - elementToIndex[newElement] = array.count - 1 - - return true - } - - /// Remove an element. - @discardableResult public mutating func remove(_ element: Element) -> Bool { - guard let index = elementToIndex[element] else { - return false - } - array.remove(at: index) - elementToIndex[element] = nil - for (e, i) in elementToIndex { - guard i >= index else { - continue - } - elementToIndex[e] = i - 1 - } - - return true - } - -} - -extension OrderedSet: ExpressibleByArrayLiteral { - /// Create an instance initialized with `elements`. - /// - /// If an element occurs more than once in `element`, only the first one - /// will be included. - public init(arrayLiteral elements: Element...) { - self.init(elements) - } -} - -extension OrderedSet: RandomAccessCollection { - public var startIndex: Int { return contents.startIndex } - public var endIndex: Int { return contents.endIndex } - public subscript(index: Int) -> Element { - return contents[index] - } -} - -public func == (lhs: OrderedSet, rhs: OrderedSet) -> Bool { - return lhs.contents == rhs.contents -} - -extension OrderedSet: Hashable where Element: Hashable { } diff --git a/Swona/Sources/Swona/Templates/AutoEquatable.stencil b/Swona/Sources/Swona/Templates/AutoEquatable.stencil deleted file mode 100644 index 62e035e..0000000 --- a/Swona/Sources/Swona/Templates/AutoEquatable.stencil +++ /dev/null @@ -1,62 +0,0 @@ -// swiftlint:disable file_length -fileprivate func compareOptionals(lhs: T?, rhs: T?, compare: (_ lhs: T, _ rhs: T) -> Bool) -> Bool { - switch (lhs, rhs) { - case let (lValue?, rValue?): - return compare(lValue, rValue) - case (nil, nil): - return true - default: - return false - } -} - -fileprivate func compareArrays(lhs: [T], rhs: [T], compare: (_ lhs: T, _ rhs: T) -> Bool) -> Bool { - guard lhs.count == rhs.count else { return false } - for (idx, lhsItem) in lhs.enumerated() { - guard compare(lhsItem, rhs[idx]) else { return false } - } - - return true -} - -{% macro compareVariables variables %} - {% for variable in variables where variable.readAccess != "private" and variable.readAccess != "fileprivate" %}{% if not variable.annotations.skipEquality %}guard {% if not variable.isOptional %}{% if not variable.annotations.arrayEquality %}lhs.{{ variable.name }} == rhs.{{ variable.name }}{% else %}compareArrays(lhs: lhs.{{ variable.name }}, rhs: rhs.{{ variable.name }}, compare: ==){% endif %}{% else %}compareOptionals(lhs: lhs.{{ variable.name }}, rhs: rhs.{{ variable.name }}, compare: ==){% endif %} else { return false }{% endif %} - {% endfor %} -{% endmacro %} - -// MARK: - AutoEquatable for classes, protocols, structs -{% for type in types.types|!enum where type.implements.AutoEquatable or type|annotated:"AutoEquatable" %} -// MARK: - {{ type.name }} AutoEquatable -{% if not type.kind == "protocol" and not type.based.NSObject %}extension {{ type.name }}: Equatable {}{% endif %} -{% if type.supertype.based.Equatable or type.supertype.implements.AutoEquatable or type.supertype|annotated:"AutoEquatable" %}THIS WONT COMPILE, WE DONT SUPPORT INHERITANCE for AutoEquatable{% endif %} -public func == (lhs: {{ type.name }}, rhs: {{ type.name }}) -> Bool { - {% if not type.kind == "protocol" %} - {% call compareVariables type.storedVariables %} - {% else %} - {% call compareVariables type.allVariables %} - {% endif %} - return true -} -{% endfor %} - -// MARK: - AutoEquatable for Enums -{% for type in types.enums where type.implements.AutoEquatable or type|annotated:"AutoEquatable" %} -// MARK: - {{ type.name }} AutoEquatable -extension {{ type.name }}: Equatable {} -public func == (lhs: {{ type.name }}, rhs: {{ type.name }}) -> Bool { - switch (lhs, rhs) { - {% for case in type.cases %} - {% if case.hasAssociatedValue %}case (.{{ case.name }}(let lhs), .{{ case.name }}(let rhs)):{% else %}case (.{{ case.name }}, .{{ case.name }}):{% endif %} - {% if not case.hasAssociatedValue %}return true{% else %} - {% if case.associatedValues.count == 1 %} - return lhs == rhs - {% else %} - {% for associated in case.associatedValues %}if lhs.{{ associated.externalName }} != rhs.{{ associated.externalName }} { return false } - {% endfor %}return true - {% endif %} - {% endif %} - {% endfor %} - {{ 'default: return false' if type.cases.count > 1 }} - } -} -{% endfor %} diff --git a/Swona/Tests/SwonaTests/TranslatorTests.swift b/Swona/Tests/SwonaTests/TranslatorTests.swift deleted file mode 100644 index c3ead17..0000000 --- a/Swona/Tests/SwonaTests/TranslatorTests.swift +++ /dev/null @@ -1,203 +0,0 @@ -import XCTest -import class Foundation.Bundle -import Swona - -final class TranslatorTests: XCTestCase { - - func testStackDelta() { - let block = BasicBlock() - - XCTAssertEqual(0, block.stackDelta) - - block += IR.push(value: .integer(value: 42)) - XCTAssertEqual(1, block.stackDelta) - - block += IR.push(value: .integer(value: 1)) - XCTAssertEqual(2, block.stackDelta) - - block += IR.push(value: .integer(value: 42)) - XCTAssertEqual(3, block.stackDelta) - - block += IR.add - XCTAssertEqual(2, block.stackDelta) - - block.endWithBranch(trueBlock: BasicBlock(), falseBlock: BasicBlock()) - XCTAssertEqual(1, block.stackDelta) - } - - func testLocalVariableOffsets() { - let block = BasicBlock() - - block += IR.localFrameIR(.loadLocal(index: 0, name: "square")) - block += IR.localFrameIR(.storeLocal(index: 1, name: "sq")) - - XCTAssertEqual(1, block.maxLocalVariableOffset) - } - - func testJumpBackwardsDoesNotMaintainBalance() { - let graph = BasicBlockGraph() - - let end = BasicBlock() - graph.start += IR.push(value: .integer(value: 42)) - graph.start += IR.push(value: .integer(value: 42)) - graph.start.endWithBranch(trueBlock: graph.start, falseBlock: end) - end += IR.push(value: .integer(value: 42)) - - assertInvalidStackUse(graph: graph) - } - - func testStackUnderflow() { - let graph = BasicBlockGraph() - - graph.start += IR.pop - assertInvalidStackUse(graph: graph) - } - - private func assertInvalidStackUse(graph: BasicBlockGraph) { - var thrownError: Error? - XCTAssertThrowsError(try graph.buildStackDepthMap(), "expected invalid stack use exception") { - thrownError = $0 - } - XCTAssert(thrownError is InvalidStackUseException) - } - - let evaluator = Evaluator() - - func testSimpleTranslation() throws { - try assertTranslation(source: """ - { - var x = 4 + 1; - while (x != 0) - x = x - 1 - } - """, - expectedInstructionsAsString: """ - 0 stack[fp+2] = 5 - 1 stack[fp+1] = stack[fp+2] ; store local x - 2 jump 3 - 3 stack[fp+2] = stack[fp+1] ; load local x - 4 stack[fp+3] = 0 - 5 stack[fp+2] = stack[fp+2] == stack[fp+3] - 6 stack[fp+2] = !stack[fp+2] - 7 jump-if-false stack[fp+2] 14 - 8 jump 9 - 9 stack[fp+2] = stack[fp+1] ; load local x - 10 stack[fp+3] = 1 - 11 stack[fp+2] = stack[fp+2] - stack[fp+3] - 12 stack[fp+1] = stack[fp+2] ; store local x - 13 jump 3 - 14 stack[fp+2] = Unit - 15 ret value=stack[fp+2], address=stack[fp+0] - """) - } - - func testReadMeExample() throws { - try assertTranslation(source: """ - { - var x = 4; - var s = ""; - if (x == 2 + 2) { var t = "It"; s = t + " worked!" } - } - """, - expectedInstructionsAsString: """ - 0 stack[fp+4] = 4 - 1 stack[fp+1] = stack[fp+4] ; store local x - 2 stack[fp+4] = "" - 3 stack[fp+2] = stack[fp+4] ; store local s - 4 stack[fp+4] = stack[fp+1] ; load local x - 5 stack[fp+5] = 4 - 6 stack[fp+4] = stack[fp+4] == stack[fp+5] - 7 jump-if-false stack[fp+4] 16 - 8 jump 9 - 9 stack[fp+4] = "It" - 10 stack[fp+5] = stack[fp+4] ; dup - 11 stack[fp+3] = stack[fp+5] ; store local t - 12 stack[fp+5] = " worked!" - 13 stack[fp+4] = stack[fp+4] ++ stack[fp+5] - 14 stack[fp+2] = stack[fp+4] ; store local s - 15 jump 16 - 16 stack[fp+4] = Unit - 17 ret value=stack[fp+4], address=stack[fp+0] - """) - } - - func testRuntimeFunctions() throws { - try registerRuntimeFunctions(evaluator: evaluator) - try assertTranslation(source: """ - { - var a = stringArrayOfSize(3, ""); - var done = false; - var i = 0; - - while (!done) { stringArraySet(a, i, "" + i); i = i + 1; if (i==stringArrayLength(a)) done=true }; - - stringArrayGet(a, 2) - } - """, - expectedInstructionsAsString: """ - 0 stack[fp+4] = 3 - 1 stack[fp+5] = "" - 2 stack[fp+6] = heap[2] ; stringArrayOfSize - 3 call stack[fp+6], 2 - 4 fp = fp - 4 - 5 stack[fp+1] = stack[fp+4] ; store local a - 6 stack[fp+4] = false - 7 stack[fp+2] = stack[fp+4] ; store local done - 8 stack[fp+4] = 0 - 9 stack[fp+3] = stack[fp+4] ; store local i - 10 jump 11 - 11 stack[fp+4] = stack[fp+2] ; load local done - 12 stack[fp+4] = !stack[fp+4] - 13 jump-if-false stack[fp+4] 40 - 14 jump 15 - 15 stack[fp+4] = stack[fp+1] ; load local a - 16 stack[fp+5] = stack[fp+3] ; load local i - 17 stack[fp+6] = "" - 18 stack[fp+7] = stack[fp+3] ; load local i - 19 stack[fp+6] = stack[fp+6] ++ stack[fp+7] - 20 stack[fp+7] = heap[5] ; stringArraySet - 21 call stack[fp+7], 3 - 22 fp = fp - 4 - 23 Nop - 24 stack[fp+4] = stack[fp+3] ; load local i - 25 stack[fp+5] = 1 - 26 stack[fp+4] = stack[fp+4] + stack[fp+5] - 27 stack[fp+5] = stack[fp+4] ; dup - 28 stack[fp+3] = stack[fp+5] ; store local i - 29 stack[fp+5] = stack[fp+1] ; load local a - 30 stack[fp+6] = heap[3] ; stringArrayLength - 31 call stack[fp+6], 1 - 32 fp = fp - 5 - 33 stack[fp+4] = stack[fp+4] == stack[fp+5] - 34 jump-if-false stack[fp+4] 39 - 35 jump 36 - 36 stack[fp+4] = true - 37 stack[fp+2] = stack[fp+4] ; store local done - 38 jump 39 - 39 jump 11 - 40 stack[fp+4] = stack[fp+1] ; load local a - 41 stack[fp+5] = 2 - 42 stack[fp+6] = heap[4] ; stringArrayGet - 43 call stack[fp+6], 2 - 44 fp = fp - 4 - 45 ret value=stack[fp+4], address=stack[fp+0] - """) - } - - private func assertTranslation(source: String, expectedInstructionsAsString: String) throws { - let instructions = try evaluator.dump(code: source).lines().map { $0.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines) } - let expectedInstructions = expectedInstructionsAsString.lines().map { $0.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines) } - XCTAssertEqual(instructions, expectedInstructions) - } - - static var allTests = [ - ("testStackDelta", testStackDelta), - ("testLocalVariableOffsets", testLocalVariableOffsets), - ("testJumpBackwardsDoesNotMaintainBalance", testJumpBackwardsDoesNotMaintainBalance), - ("testStackUnderflow", testStackUnderflow), - ("testSimpleTranslation", testSimpleTranslation), - ("testReadMeExample", testReadMeExample), - ("testRuntimeFunctions", testRuntimeFunctions), - - ] -} diff --git a/Swona/Tests/SwonaTests/XCTestManifests.swift b/Swona/Tests/SwonaTests/XCTestManifests.swift deleted file mode 100644 index 17a3a6d..0000000 --- a/Swona/Tests/SwonaTests/XCTestManifests.swift +++ /dev/null @@ -1,15 +0,0 @@ -import XCTest - -#if !os(macOS) -public func allTests() -> [XCTestCaseEntry] { - return [ - testCase(LexerTests.allTests), - testCase(ParserTests.allTests), - testCase(VMTests.allTests), - testCase(TranslatorTests.allTests), - testCase(OptimizerTests.allTests), - testCase(TypesTests.allTests), - testCase(HelpersTests.allTests), - ] -} -#endif diff --git a/Swona/Tests/LinuxMain.swift b/Tests/LinuxMain.swift similarity index 86% rename from Swona/Tests/LinuxMain.swift rename to Tests/LinuxMain.swift index eebac65..f9ba266 100644 --- a/Swona/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -4,4 +4,4 @@ import SwonaTests var tests = [XCTestCaseEntry]() tests += SwonaTests.allTests() -XCTMain(tests) \ No newline at end of file +XCTMain(tests) diff --git a/Tests/SwonaTests/BridgeTests.swift b/Tests/SwonaTests/BridgeTests.swift new file mode 100644 index 0000000..29b8044 --- /dev/null +++ b/Tests/SwonaTests/BridgeTests.swift @@ -0,0 +1,79 @@ +import XCTest +@testable import Swona + +final class BridgeTests: XCTestCase { + func testBridge() throws { + let b = try Bridge() + b.x = "string1".value + XCTAssertEqual(b.x, "string1".value) + b.x = nil + XCTAssertNil(b.x) + + b.x = "string2".value + XCTAssertEqual(b.x, "string2".value) + + b.y = 5.value + XCTAssertEqual(b.y, 5.value) + + b.z = true.value + XCTAssertEqual(b.z, true.value) + + b.z = false.value + XCTAssertEqual(b.z, false.value) + } + + func testBridgedRuntimeFunctions() throws { + let b = try Bridge() + let array = try b.stringArrayOfSize(2.lit, "empty".lit) + b.x = array + let value: Value? = b.x + guard case let .array(elements, elementType) = value! else { + XCTFail() + return + } + + XCTAssertEqual(elements.array, ["empty".value, "empty".value]) + XCTAssertEqual(elementType, Type.string) + + let length = try b.stringArrayLength(b.x) + + XCTAssertEqual(length, 2.value) + + try b.stringArraySet(b.x, 0.lit, "element0".lit) + + let result = try b.stringArrayGet(b.x, 0.lit) + + XCTAssertEqual(result, "element0".value) + } + + func testCallAsFunction() throws { + let inc = fun1(name: "inc", argType: .int, returnType: .int, func: { + arg in + guard case let .integer(value) = arg else { + fatalError("Expected int arg, got \(arg)") + } + + return .integer(value: value + 1) + }) + + let result = inc(1) + XCTAssertEqual(result as? Value, Value(integerLiteral: 2)) + } + + func testValueArray() throws { + let array: Value = ["item1", "item2", "item3"] + guard case let .array(elements, elementType) = array, elementType == .string else { + XCTFail("Invalid value array") + return + } + + XCTAssertEqual(elements.array, ["item1".value, "item2".value, "item3".value]) + } + + static var allTests = [ + ("testBridge", testBridge), + ("testBridgedRuntimeFunctions", testBridgedRuntimeFunctions), + ("testCallAsFunction", testCallAsFunction), + ("testValueArray", testValueArray), + ] +} diff --git a/Tests/SwonaTests/ExtensionsTests.swift b/Tests/SwonaTests/ExtensionsTests.swift new file mode 100644 index 0000000..1a0f33e --- /dev/null +++ b/Tests/SwonaTests/ExtensionsTests.swift @@ -0,0 +1,101 @@ +import XCTest +@testable import Swona + +final class ExtensionsTests: XCTestCase { + func testSingles() { + XCTAssertNil([].singleOrNull()) + XCTAssertNil([1, 2].singleOrNull()) + XCTAssertEqual([1].singleOrNull(), 1) + XCTAssertEqual([2].single(), 2) + } + + func testSumBy() { + XCTAssertEqual([1].sumBy { $0 + 1 }, 2) + XCTAssertEqual([1, 2].sumBy { $0 + 1 }, 5) + XCTAssertEqual([1, 2, 3].sumBy { $0 * 2 }, 12) + XCTAssertEqual([].sumBy { $0 + 1 }, 0) + } + + func testSubList() { + XCTAssertEqual([1].subList(fromIndex: 0, toIndex: 0), []) + XCTAssertEqual([1, 2].subList(fromIndex: 0, toIndex: 0), []) + XCTAssertEqual([1].subList(fromIndex: 0, toIndex: 1), [1]) + XCTAssertEqual([1, 2].subList(fromIndex: 0, toIndex: 1), [1]) + XCTAssertEqual([1, 2].subList(fromIndex: 1, toIndex: 1), []) + XCTAssertEqual([1, 2].subList(fromIndex: 0, toIndex: 2), [1, 2]) + XCTAssertEqual([1, 2].subList(fromIndex: 1, toIndex: 2), [2]) + XCTAssertEqual([1, 2, 3].subList(fromIndex: 1, toIndex: 2), [2]) + XCTAssertEqual([1, 2, 3].subList(fromIndex: 0, toIndex: 2), [1, 2]) + XCTAssertEqual([1, 2, 3].subList(fromIndex: 2, toIndex: 2), []) + XCTAssertEqual([1, 2, 3].subList(fromIndex: 2, toIndex: 3), [3]) + XCTAssertEqual([1, 2, 3, 4].subList(fromIndex: 2, toIndex: 3), [3]) + XCTAssertEqual([1, 2, 3, 4].subList(fromIndex: 2, toIndex: 4), [3, 4]) + } + + func testBoolInt() { + XCTAssertEqual(Int(true), 1) + XCTAssertEqual(Int(false), 0) + } + + func testLines() { + XCTAssertEqual("".lines(), [""]) + XCTAssertEqual("1".lines(), ["1"]) + XCTAssertEqual("1\n2".lines(), ["1", "2"]) + XCTAssertEqual("1\n2\n3".lines(), ["1", "2", "3"]) + XCTAssertEqual("1\n\n2\n3".lines(), ["1", "", "2", "3"]) + XCTAssertEqual("\n1\n\n2\n3\n\n".lines(), ["", "1", "", "2", "3", "", ""]) + } + + func testSubstring() { + XCTAssertEqual("a".substring(startOffset: 0, endOffset: 0), "") + XCTAssertEqual("a".substring(startOffset: 0, endOffset: 0), "") + XCTAssertEqual("a".substring(startOffset: 0, endOffset: 1), "a") + + XCTAssertEqual("ab".substring(startOffset: 0, endOffset: 0), "") + XCTAssertEqual("ab".substring(startOffset: 0, endOffset: 0), "") + XCTAssertEqual("ab".substring(startOffset: 0, endOffset: 1), "a") + XCTAssertEqual("ab".substring(startOffset: 1, endOffset: 1), "") + XCTAssertEqual("ab".substring(startOffset: 1, endOffset: 2), "b") + XCTAssertEqual("ab".substring(startOffset: 0, endOffset: 2), "ab") + + XCTAssertEqual("abc".substring(startOffset: 0, endOffset: 0), "") + XCTAssertEqual("abc".substring(startOffset: 1, endOffset: 1), "") + XCTAssertEqual("abc".substring(startOffset: 1, endOffset: 2), "b") + XCTAssertEqual("abc".substring(startOffset: 0, endOffset: 2), "ab") + XCTAssertEqual("abc".substring(startOffset: 1, endOffset: 2), "b") + XCTAssertEqual("abc".substring(startOffset: 1, endOffset: 3), "bc") + XCTAssertEqual("abc".substring(startOffset: 0, endOffset: 3), "abc") + XCTAssertEqual("abc".substring(startOffset: 2, endOffset: 3), "c") + } + + func testPadEnd() { + XCTAssertEqual("".padEnd(count: 0, padChar: "."), "") + XCTAssertEqual("".padEnd(count: 1, padChar: "."), ".") + XCTAssertEqual("".padEnd(count: 4, padChar: "."), "....") + XCTAssertEqual("a".padEnd(count: 0, padChar: "."), "a") + XCTAssertEqual("a".padEnd(count: 1, padChar: "."), "a") + XCTAssertEqual("a".padEnd(count: 4, padChar: "."), "a...") + XCTAssertEqual("ab".padEnd(count: 4, padChar: "."), "ab..") + } + + func testPadStart() { + XCTAssertEqual("".padStart(count: 0, padChar: "."), "") + XCTAssertEqual("".padStart(count: 1, padChar: "."), ".") + XCTAssertEqual("".padStart(count: 4, padChar: "."), "....") + XCTAssertEqual("a".padStart(count: 0, padChar: "."), "a") + XCTAssertEqual("a".padStart(count: 1, padChar: "."), "a") + XCTAssertEqual("a".padStart(count: 4, padChar: "."), "...a") + XCTAssertEqual("ab".padStart(count: 4, padChar: "."), "..ab") + } + + static var allTests = [ + ("testSingles", testSingles), + ("testSumBy", testSumBy), + ("testSubList", testSubList), + ("testBoolInt", testBoolInt), + ("testSubstring", testSubstring), + ("testLines", testLines), + ("testPadEnd", testPadEnd), + ("testPadStart", testPadStart), + ] +} diff --git a/Swona/Tests/SwonaTests/LexerTests.swift b/Tests/SwonaTests/LexerTests.swift similarity index 92% rename from Swona/Tests/SwonaTests/LexerTests.swift rename to Tests/SwonaTests/LexerTests.swift index cb481ef..4dad799 100644 --- a/Swona/Tests/SwonaTests/LexerTests.swift +++ b/Tests/SwonaTests/LexerTests.swift @@ -1,6 +1,5 @@ -import XCTest -import class Foundation.Bundle import Swona +import XCTest final class LexerTests: XCTestCase { func testEmptySourceHasNoTokens() throws { @@ -8,7 +7,7 @@ final class LexerTests: XCTestCase { assertNoTokens(source: " ") assertNoTokens(source: " \n \n \t \t ") } - + func testKeywords() throws { assertTokens(source: "if", tokens: Token.keyword(Token.Keyword.if)) assertTokens(source: "else", tokens: Token.keyword(Token.Keyword.else)) @@ -17,12 +16,12 @@ final class LexerTests: XCTestCase { assertTokens(source: "val", tokens: Token.keyword(Token.Keyword.val)) assertTokens(source: "while", tokens: Token.keyword(Token.Keyword.while)) } - + func testIdentifiers() { assertTokens(source: "foo", tokens: Token.identifier(name: "foo")) assertTokens(source: "bar", tokens: Token.identifier(name: "bar")) } - + func testOperators() throws { assertTokens(source: "+", tokens: Token.operator(Token.Operator.plus)) assertTokens(source: "-", tokens: Token.operator(Token.Operator.minus)) @@ -49,36 +48,35 @@ final class LexerTests: XCTestCase { assertTokens(source: ";", tokens: Token.punctuation(Token.Punctuation.semicolon)) assertTokens(source: ",", tokens: Token.punctuation(Token.Punctuation.comma)) } - + func testLiteralNumbers() { assertTokens(source: "42", tokens: Token.literal(value: .integer(value: 42))) } - + func testLiteralBooleans() { assertTokens(source: "true", tokens: Token.literal(value: Value.bool(value: true))) assertTokens(source: "false", tokens: Token.literal(value: Value.bool(value: false))) } - + func testLiteralStrings() { assertTokens(source: "\"\"", tokens: Token.literal(value: Value.string(value: ""))) assertTokens(source: "\"foo\"", tokens: Token.literal(value: Value.string(value: "foo"))) assertTokens(source: "\"bar \\\"baz\\\" quux\"", tokens: Token.literal(value: Value.string(value: "bar \"baz\" quux"))) } - func testUnterminatedStringLiteral() throws { assertSyntaxError(source: "\"bar") } - + func testUnexpectedCharacter() throws { assertSyntaxError(source: "€") } - + func testMultipleTokens() throws { assertTokens(source: "if (foo) \"bar\" else 42", tokens: Token.keyword(.if), .punctuation(.leftParen), .identifier(name: "foo"), .punctuation(.rightParen), .literal(value: Value.string(value: "bar")), .keyword(.else), .literal(value: Value.integer(value: 42))) } - + func testTokenLocations() throws { let source = """ if (foo) @@ -86,13 +84,13 @@ final class LexerTests: XCTestCase { else baz() """ - let locations = try readAllTokens(source: source).map { $0.location } - - XCTAssertTrue([1, 1, 1, 1, 2, 2, 2, 3, 4, 4, 4].elementsEqual(locations.map { $0.line }), "lines") + let locations = try readAllTokens(source: source).map(\.location) + + XCTAssertTrue([1, 1, 1, 1, 2, 2, 2, 3, 4, 4, 4].elementsEqual(locations.map(\.line)), "lines") XCTAssertTrue([1, 1, 1, 1, 2, 2, 2, 3, 4, 4, 4].elementsEqual(locations.map { source.lines().map { String($0) }.firstIndex(of: $0.lineText)! + 1 }), "lineTexts") - XCTAssertTrue([1, 4, 5, 8, 5, 8, 9, 1, 5, 8, 9].elementsEqual(locations.map { $0.column }), "columns") + XCTAssertTrue([1, 4, 5, 8, 5, 8, 9, 1, 5, 8, 9].elementsEqual(locations.map(\.column)), "columns") } - + func testNextTokenOnEmptyThrowsSyntaxError() throws { let lexer = try Lexer(source: " ") var thrownError: Error? @@ -101,15 +99,15 @@ final class LexerTests: XCTestCase { } XCTAssert(thrownError is SyntaxErrorException) } - + private func assertTokens(source: String, tokens: Token...) { - XCTAssertTrue(tokens.elementsEqual(try readAllTokens(source: source).map { $0.token })) + XCTAssertTrue(tokens.elementsEqual(try readAllTokens(source: source).map(\.token))) } - + private func assertNoTokens(source: String) { assertTokens(source: source) } - + private func assertSyntaxError(source: String) { var thrownError: Error? XCTAssertThrowsError(try readAllTokens(source: source), "Expected syntax error") { @@ -118,70 +116,70 @@ final class LexerTests: XCTestCase { XCTAssert(thrownError is SyntaxErrorException) } - private func readAllTokens(source: String) throws -> Array { + private func readAllTokens(source: String) throws -> [TokenInfo] { let lexer = try Lexer(source: source) var result = [TokenInfo]() - - while (lexer.hasMore) { + + while lexer.hasMore { result.append(try lexer.readToken()) } - + return result } - + func testBasicLookAhead() throws { let lexer = try LookaheadLexer(source: "foo 123 bar") - + XCTAssertTrue(lexer.hasMore) XCTAssertEqual(Token.identifier(name: "foo"), try lexer.peekToken().token) - + XCTAssertTrue(lexer.hasMore) XCTAssertEqual(Token.identifier(name: "foo"), try lexer.readToken().token) - + XCTAssertTrue(lexer.hasMore) XCTAssertEqual(Token.literal(value: .integer(value: 123)), try lexer.readToken().token) - + XCTAssertTrue(lexer.hasMore) XCTAssertEqual(Token.identifier(name: "bar"), try lexer.peekToken().token) XCTAssertEqual(Token.identifier(name: "bar"), try lexer.peekToken().token) - + XCTAssertTrue(lexer.hasMore) XCTAssertEqual(Token.identifier(name: "bar"), try lexer.readToken().token) - + XCTAssertFalse(lexer.hasMore) } - + func testConditionalReading() throws { let lexer = try LookaheadLexer(source: "foo fun ()") - + XCTAssertFalse(try lexer.readNextIf(token: Token.identifier(name: "bar"))) XCTAssertFalse(try lexer.readNextIf(token: Token.keyword(.if))) XCTAssertTrue(try lexer.readNextIf(token: Token.identifier(name: "foo"))) - + XCTAssertFalse(try lexer.readNextIf(token: Token.punctuation(Token.Punctuation.leftParen))) XCTAssertTrue(try lexer.readNextIf(token: Token.keyword(.fun))) - + XCTAssertTrue(try lexer.readNextIf(token: Token.punctuation(Token.Punctuation.leftParen))) XCTAssertTrue(try lexer.readNextIf(token: Token.punctuation(Token.Punctuation.rightParen))) - + XCTAssertFalse(lexer.hasMore) } - + func testConditionalReadingWorksOnEndOfInput() throws { let lexer = try LookaheadLexer(source: "") - + XCTAssertFalse(try lexer.readNextIf(token: Token.punctuation(Token.Punctuation.leftParen))) } - + func testExpect() throws { let lexer = try LookaheadLexer(source: "()") - + XCTAssertEqual(1, try lexer.expect(expected: Token.punctuation(Token.Punctuation.leftParen)).column) XCTAssertEqual(2, try lexer.expect(expected: Token.punctuation(Token.Punctuation.rightParen)).column) - + XCTAssertFalse(lexer.hasMore) } - + func testUnmetExpectThrowsError() throws { let lexer = try LookaheadLexer(source: "()") var thrownError: Error? @@ -190,24 +188,23 @@ final class LexerTests: XCTestCase { } XCTAssert(thrownError is SyntaxErrorException) } - + func testDefaultToStringProvidesBasicInfo() throws { - let location = SourceLocation(file: "dummy.sk", line:42, column:14, lineText:" if (foo) bar() else baz()") - + let location = SourceLocation(file: "dummy.sk", line: 42, column: 14, lineText: " if (foo) bar() else baz()") + XCTAssertEqual("[dummy.sk:42:14]", location.description) } - + func testStringRepresentationProvidesInformationAboutCurrentLine() { let location = SourceLocation(file: "dummy.sk", line: 42, column: 14, lineText: " if (foo) bar() else baz()") - + XCTAssertEqual(""" - [dummy.sk:42:14] if (foo) bar() else baz() - ^ - - """, location.toLongString()) + [dummy.sk:42:14] if (foo) bar() else baz() + ^ + + """, location.toLongString()) } - static var allTests = [ ("testEmptySourceHasNoTokens", testEmptySourceHasNoTokens), ("testKeywords", testKeywords), diff --git a/Swona/Tests/SwonaTests/OptimizerTests.swift b/Tests/SwonaTests/OptimizerTests.swift similarity index 93% rename from Swona/Tests/SwonaTests/OptimizerTests.swift rename to Tests/SwonaTests/OptimizerTests.swift index 9b6e149..64f3141 100644 --- a/Swona/Tests/SwonaTests/OptimizerTests.swift +++ b/Tests/SwonaTests/OptimizerTests.swift @@ -1,10 +1,9 @@ -import XCTest -import class Foundation.Bundle import Swona +import XCTest final class OptimizerTests: XCTestCase { let env = GlobalStaticEnvironment() - + func testEvaluateConstantExpressions() throws { try assertOptimized(code: "1+1", expectedAST: "[Lit 2]") try assertOptimized(code: "3+4*5", expectedAST: "[Lit 23]") @@ -15,66 +14,65 @@ final class OptimizerTests: XCTestCase { try assertOptimized(code: "4 != 4", expectedAST: "[Lit false]") try assertOptimized(code: "4 != 4 == false", expectedAST: "[Lit true]") } - + func testConstantIf() throws { try env.bind(name: "foo", type: Type.function(.function(argumentTypes: [], returnType: Type.unit))) try env.bind(name: "bar", type: Type.function(.function(argumentTypes: [], returnType: Type.unit))) - + try assertOptimized(code: "if (true) foo()", expectedAST: "[Call [Ref foo] []]") try assertOptimized(code: "if (true) foo() else bar()", expectedAST: "[Call [Ref foo] []]") try assertOptimized(code: "if (false) foo()", expectedAST: "[ExpressionList []]") try assertOptimized(code: "if (false) foo() else bar()", expectedAST: "[Call [Ref bar] []]") try assertOptimized(code: "if (1 == 2) foo() else bar()", expectedAST: "[Call [Ref bar] []]") } - + func testWhileFalse() throws { try env.bind(name: "foo", type: Type.function(.function(argumentTypes: [], returnType: Type.unit))) - + try assertOptimized(code: "while (false) foo()", expectedAST: "[ExpressionList []]") try assertOptimized(code: "while (1 == 2) foo()", expectedAST: "[ExpressionList []]") } - + func testNot() throws { try env.bind(name: "x", type: Type.boolean) - + try assertOptimized(code: "!x", expectedAST: "[Not [Ref x]]") try assertOptimized(code: "!!x", expectedAST: "[Ref x]") try assertOptimized(code: "!!!x", expectedAST: "[Not [Ref x]]") try assertOptimized(code: "!!!!x", expectedAST: "[Ref x]") try assertOptimized(code: "!!!!!x", expectedAST: "[Not [Ref x]]") } - + func testPropagateConstantVariables() throws { try env.bind(name: "foo", type: Type.function(.function(argumentTypes: [Type.string], returnType: Type.unit))) try assertOptimized(code: """ -if (true) { val s = "hello"; foo(s + ", world!") } -""", + if (true) { val s = "hello"; foo(s + ", world!") } + """, expectedAST: - - """ -[ExpressionList [[Var [Local 0 (s)] [Lit "hello"]], [Call [Ref foo] [[Lit "hello, world!"]]]]] -""") + + """ + [ExpressionList [[Var [Local 0 (s)] [Lit "hello"]], [Call [Ref foo] [[Lit "hello, world!"]]]]] + """) } - - + private func assertOptimized(code: String, expectedAST: String) throws { let statement = try parseExpression(code: code).typeCheck(env: env).optimize() - + XCTAssertEqual(expectedAST, statement.description, code) } - + func testSimpleConstantEvaluation() throws { try assertConstantEvaluation(code: "1+1", expected: "[Lit 2]") try assertConstantEvaluation(code: "6/2", expected: "[Lit 3]") try assertConstantEvaluation(code: "\"foo\" + \"bar\"", expected: "[Lit \"foobar\"]") try assertConstantEvaluation(code: "\"foo\" + 42", expected: "[Lit \"foo42\"]") } - + func testDivisionByZeroWillNotBeThrownAtCompileTime() throws { try assertConstantEvaluation(code: "1/0", expected: "[Divide [Lit 1] [Lit 0]]") try assertConstantEvaluation(code: "(1+1)/(1-1)", expected: "[Divide [Lit 2] [Lit 0]]") } - + private func assertConstantEvaluation(code: String, expected: String) throws { let result = try parseExpression(code: code).typeCheck(env: GlobalStaticEnvironment()).evaluateConstantExpressions() XCTAssertEqual(expected, result.description) @@ -86,37 +84,36 @@ if (true) { val s = "hello"; foo(s + ", world!") } block += IR.localFrameIR(.storeLocal(index: 4, name: "foo")) block += IR.localFrameIR(.loadLocal(index: 4, name: "foo")) block += IR.multiply - + block.peepholeOptimize() - + XCTAssertEqual([IR.add, IR.dup, IR.localFrameIR(IR.LocalFrameIR.storeLocal(index: 4, name: "foo")), IR.multiply], block.opCodes) } - + func testRemoveRedundantStoreAndLoad() { let block = BasicBlock() block += IR.add block += IR.localFrameIR(.loadLocal(index: 4, name: "foo")) block += IR.localFrameIR(.storeLocal(index: 4, name: "foo")) block += IR.multiply - + block.peepholeOptimize() - + XCTAssertEqual([IR.add, IR.multiply], block.opCodes) } - + func testRemoveRedundantPushUnits() { let block = BasicBlock() block += IR.add block += IR.pushUnit block += IR.pop block += IR.multiply - + block.peepholeOptimize() - + XCTAssertEqual([IR.add, IR.multiply], block.opCodes) } - static var allTests = [ ("testEvaluateConstantExpressions", testEvaluateConstantExpressions), ("testConstantIf", testConstantIf), diff --git a/Swona/Tests/SwonaTests/HelpersTests.swift b/Tests/SwonaTests/OrderedSetTests.swift similarity index 60% rename from Swona/Tests/SwonaTests/HelpersTests.swift rename to Tests/SwonaTests/OrderedSetTests.swift index ebe8d87..6e175f5 100644 --- a/Swona/Tests/SwonaTests/HelpersTests.swift +++ b/Tests/SwonaTests/OrderedSetTests.swift @@ -1,8 +1,19 @@ +/* + This source file is part of the Swift.org open source project + + Copyright 2016 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See http://swift.org/LICENSE.txt for license information + See http://swift.org/CONTRIBUTORS.txt for Swift project authors + + The below code was modified from the original. + */ + import XCTest -import class Foundation.Bundle @testable import Swona -final class HelpersTests: XCTestCase { +final class OrderedSetTests: XCTestCase { func testOrderedSetWithSingleElement() { var orderedSet = OrderedSet() XCTAssertEqual(orderedSet.count, 0) @@ -25,7 +36,7 @@ final class HelpersTests: XCTestCase { XCTAssertEqual(orderedSet.count, 0) XCTAssertTrue(orderedSet.isEmpty) } - + func testOrderedSetWithMultipleElements() { var orderedSet = OrderedSet() XCTAssertEqual(orderedSet.count, 0) @@ -51,7 +62,7 @@ final class HelpersTests: XCTestCase { XCTAssertEqual(orderedSet[0], "1") XCTAssertEqual(orderedSet[1], "2") XCTAssertEqual(orderedSet[2], "3") - + XCTAssertEqual(orderedSet.array, ["1", "2", "3"]) XCTAssertTrue(orderedSet.remove("2")) XCTAssertEqual(orderedSet.count, 2) @@ -60,7 +71,7 @@ final class HelpersTests: XCTestCase { XCTAssertEqual(orderedSet.elementToIndex["3"], 1) XCTAssertEqual(orderedSet[0], "1") XCTAssertEqual(orderedSet[1], "3") - + XCTAssertEqual(orderedSet.array, ["1", "3"]) XCTAssertEqual(orderedSet, OrderedSet(["1", "3"])) XCTAssertEqual(orderedSet.contents, ["1", "3"]) @@ -73,7 +84,7 @@ final class HelpersTests: XCTestCase { XCTAssertEqual(orderedSet[2], "2") XCTAssertEqual(orderedSet, OrderedSet(["1", "3", "2"])) XCTAssertEqual(orderedSet.contents, ["1", "3", "2"]) - + XCTAssertTrue(orderedSet.remove("1")) XCTAssertEqual(orderedSet.count, 2) XCTAssertNil(orderedSet.elementToIndex["1"]) @@ -88,11 +99,11 @@ final class HelpersTests: XCTestCase { XCTAssertEqual(orderedSet[0], "3") XCTAssertEqual(orderedSet[1], "2") XCTAssertEqual(orderedSet[2], "1") - + XCTAssertTrue(orderedSet.contains("3")) XCTAssertFalse(orderedSet.contains("4")) XCTAssertNil(orderedSet.elementToIndex["4"]) - + orderedSet = OrderedSet(["1", "2", "3", "4"]) XCTAssertEqual(orderedSet[0], "1") XCTAssertEqual(orderedSet[1], "2") @@ -106,7 +117,7 @@ final class HelpersTests: XCTestCase { XCTAssertEqual(orderedSet[0], "1") XCTAssertEqual(orderedSet[1], "3") XCTAssertEqual(orderedSet[2], "4") - + orderedSet = OrderedSet(["1", "2", "3", "4"]) XCTAssertEqual(orderedSet[0], "1") XCTAssertEqual(orderedSet[1], "2") @@ -120,39 +131,38 @@ final class HelpersTests: XCTestCase { XCTAssertEqual(orderedSet[3], "2") XCTAssertEqual(orderedSet, OrderedSet(["1", "3", "4", "2"])) XCTAssertEqual(orderedSet, OrderedSet(arrayLiteral: "1", "3", "4", "2")) - } - + func testOrderedSetFromArray() { var orderedSet = OrderedSet(["1", "2", "3"]) XCTAssertEqual(orderedSet.count, 3) XCTAssertEqual(orderedSet.map { $0 + "testing" }, ["1testing", "2testing", "3testing"]) XCTAssertEqual(orderedSet.enumerated().map { $0.element + "testing" + "\($0.offset)" }, ["1testing0", "2testing1", "3testing2"]) - + XCTAssert(orderedSet.remove("2")) XCTAssertEqual(orderedSet.count, 2) XCTAssertEqual(orderedSet.map { $0 + "testing" }, ["1testing", "3testing"]) - XCTAssertEqual(orderedSet.enumerated().map { $0.element + "testing" + "\($0.offset)" }, ["1testing0", "3testing1"]) + XCTAssertEqual(orderedSet.enumerated().map { $0.element + "testing" + "\($0.offset)" }, ["1testing0", "3testing1"]) } - + func testOrderedSetExtensions() { let orderedSet = OrderedSet(["1", "2", "3"]) var dictionary: [String: OrderedSet] = [:] dictionary["a"] = orderedSet - XCTAssertEqual(orderedSet, dictionary["a"]) - - XCTAssertEqual(OrderedSet(["3", "2", "1"]), OrderedSet(["3", "2", "1"])) - XCTAssertNotEqual(OrderedSet(["3", "2", "1"]), OrderedSet(["2", "1"])) + XCTAssertEqual(orderedSet, dictionary["a"]) + + XCTAssertEqual(OrderedSet(["3", "2", "1"]), OrderedSet(["3", "2", "1"])) + XCTAssertNotEqual(OrderedSet(["3", "2", "1"]), OrderedSet(["2", "1"])) } - - // https://github.com/apple/swift-package-manager/blob/927a57c33cf105748977f4d066a08f84372a87ac/Tests/BasicTests/OrderedSetTests.swift - // Modified from Swift Package Manager (Apache License, Version 2.0). See LICENSE-THIRD-PARTY in this repo + + // See https://github.com/apple/swift-package-manager/blob/927a57c33cf105748977f4d066a08f84372a87ac/Tests/BasicTests/OrderedSetTests.swift + // Modified from Swift Package Manager (Apache License, Version 2.0). See LICENSE in this repo func testOrderedSetBasics() { // Create an empty set. var set = OrderedSet() XCTAssertTrue(set.isEmpty) XCTAssertEqual(set.contents, []) - + // Create a new set with some strings. set = OrderedSet(["one", "two", "three"]) XCTAssertFalse(set.isEmpty) @@ -161,108 +171,20 @@ final class HelpersTests: XCTestCase { XCTAssertEqual(set[1], "two") XCTAssertEqual(set[2], "three") XCTAssertEqual(set.contents, ["one", "two", "three"]) - + // Try adding the same item again - the set should be unchanged. XCTAssertEqual(set.append("two"), false) XCTAssertEqual(set.count, 3) XCTAssertEqual(set[0], "one") XCTAssertEqual(set[1], "two") XCTAssertEqual(set[2], "three") - + // Remove the last element. let three = set.remove("three") XCTAssertEqual(set.count, 2) XCTAssertEqual(set[0], "one") XCTAssertEqual(set[1], "two") XCTAssert(three) - - } - - func testSingles() { - XCTAssertNil([].singleOrNull()) - XCTAssertNil([1, 2].singleOrNull()) - XCTAssertEqual([1].singleOrNull(), 1) - XCTAssertEqual([2].single(), 2) - } - - func testSumBy() { - XCTAssertEqual([1].sumBy { $0 + 1 }, 2) - XCTAssertEqual([1, 2].sumBy { $0 + 1 }, 5) - XCTAssertEqual([1, 2, 3].sumBy { $0 * 2 }, 12) - XCTAssertEqual([].sumBy { $0 + 1 }, 0) - } - - func testSubList() { - XCTAssertEqual([1].subList(fromIndex: 0, toIndex: 0), []) - XCTAssertEqual([1, 2].subList(fromIndex: 0, toIndex: 0), []) - XCTAssertEqual([1].subList(fromIndex: 0, toIndex: 1), [1]) - XCTAssertEqual([1, 2].subList(fromIndex: 0, toIndex: 1), [1]) - XCTAssertEqual([1, 2].subList(fromIndex: 1, toIndex: 1), []) - XCTAssertEqual([1, 2].subList(fromIndex: 0, toIndex: 2), [1, 2]) - XCTAssertEqual([1, 2].subList(fromIndex: 1, toIndex: 2), [2]) - XCTAssertEqual([1, 2, 3].subList(fromIndex: 1, toIndex: 2), [2]) - XCTAssertEqual([1, 2, 3].subList(fromIndex: 0, toIndex: 2), [1, 2]) - XCTAssertEqual([1, 2, 3].subList(fromIndex: 2, toIndex: 2), []) - XCTAssertEqual([1, 2, 3].subList(fromIndex: 2, toIndex: 3), [3]) - XCTAssertEqual([1, 2, 3, 4].subList(fromIndex: 2, toIndex: 3), [3]) - XCTAssertEqual([1, 2, 3, 4].subList(fromIndex: 2, toIndex: 4), [3, 4]) - - } - - func testBoolInt() { - XCTAssertEqual(Int(true), 1) - XCTAssertEqual(Int(false), 0) - } - - func testLines() { - XCTAssertEqual("".lines(), [""]) - XCTAssertEqual("1".lines(), ["1"]) - XCTAssertEqual("1\n2".lines(), ["1", "2"]) - XCTAssertEqual("1\n2\n3".lines(), ["1", "2", "3"]) - XCTAssertEqual("1\n\n2\n3".lines(), ["1", "", "2", "3"]) - XCTAssertEqual("\n1\n\n2\n3\n\n".lines(), ["", "1", "", "2", "3", "", ""]) - } - - func testSubstring() { - XCTAssertEqual("a".substring(startOffset: 0, endOffset: 0), "") - XCTAssertEqual("a".substring(startOffset: 0, endOffset: 0), "") - XCTAssertEqual("a".substring(startOffset: 0, endOffset: 1), "a") - - XCTAssertEqual("ab".substring(startOffset: 0, endOffset: 0), "") - XCTAssertEqual("ab".substring(startOffset: 0, endOffset: 0), "") - XCTAssertEqual("ab".substring(startOffset: 0, endOffset: 1), "a") - XCTAssertEqual("ab".substring(startOffset: 1, endOffset: 1), "") - XCTAssertEqual("ab".substring(startOffset: 1, endOffset: 2), "b") - XCTAssertEqual("ab".substring(startOffset: 0, endOffset: 2), "ab") - - XCTAssertEqual("abc".substring(startOffset: 0, endOffset: 0), "") - XCTAssertEqual("abc".substring(startOffset: 1, endOffset: 1), "") - XCTAssertEqual("abc".substring(startOffset: 1, endOffset: 2), "b") - XCTAssertEqual("abc".substring(startOffset: 0, endOffset: 2), "ab") - XCTAssertEqual("abc".substring(startOffset: 1, endOffset: 2), "b") - XCTAssertEqual("abc".substring(startOffset: 1, endOffset: 3), "bc") - XCTAssertEqual("abc".substring(startOffset: 0, endOffset: 3), "abc") - XCTAssertEqual("abc".substring(startOffset: 2, endOffset: 3), "c") - } - - func testPadEnd() { - XCTAssertEqual("".padEnd(count: 0, padChar: "."), "") - XCTAssertEqual("".padEnd(count: 1, padChar: "."), ".") - XCTAssertEqual("".padEnd(count: 4, padChar: "."), "....") - XCTAssertEqual("a".padEnd(count: 0, padChar: "."), "a") - XCTAssertEqual("a".padEnd(count: 1, padChar: "."), "a") - XCTAssertEqual("a".padEnd(count: 4, padChar: "."), "a...") - XCTAssertEqual("ab".padEnd(count: 4, padChar: "."), "ab..") - } - - func testPadStart() { - XCTAssertEqual("".padStart(count: 0, padChar: "."), "") - XCTAssertEqual("".padStart(count: 1, padChar: "."), ".") - XCTAssertEqual("".padStart(count: 4, padChar: "."), "....") - XCTAssertEqual("a".padStart(count: 0, padChar: "."), "a") - XCTAssertEqual("a".padStart(count: 1, padChar: "."), "a") - XCTAssertEqual("a".padStart(count: 4, padChar: "."), "...a") - XCTAssertEqual("ab".padStart(count: 4, padChar: "."), "..ab") } static var allTests = [ @@ -271,13 +193,5 @@ final class HelpersTests: XCTestCase { ("testOrderedSetFromArray", testOrderedSetFromArray), ("testOrderedSetExtensions", testOrderedSetExtensions), ("testOrderedSetBasics", testOrderedSetBasics), - ("testSingles", testSingles), - ("testSumBy", testSumBy), - ("testSubList", testSubList), - ("testBoolInt", testBoolInt), - ("testSubstring", testSubstring), - ("testLines", testLines), - ("testPadEnd", testPadEnd), - ("testPadStart", testPadStart), ] } diff --git a/Swona/Tests/SwonaTests/ParserTests.swift b/Tests/SwonaTests/ParserTests.swift similarity index 86% rename from Swona/Tests/SwonaTests/ParserTests.swift rename to Tests/SwonaTests/ParserTests.swift index ab3634f..70525c7 100644 --- a/Swona/Tests/SwonaTests/ParserTests.swift +++ b/Tests/SwonaTests/ParserTests.swift @@ -1,58 +1,64 @@ -import XCTest -import class Foundation.Bundle import Swona +import XCTest final class ParserTests: XCTestCase { func testVariables() throws { try assertParseExpression(source: "foo", expected: "[Ref foo]") } - + func testLiterals() throws { try assertParseExpression(source: "42", expected: "[Lit 42]") try assertParseExpression(source: "\"foo\"", expected: "[Lit \"foo\"]") try assertParseExpression(source: "true", expected: "[Lit true]") } - - + func testIfStatements() throws { try assertParseExpression(source: "if (x) y else z", expected: "[If [Ref x] [Ref y] [Ref z]]") try assertParseExpression(source: "if (x) y", expected: "[If [Ref x] [Ref y] []]") } - - + + func testUnlessStatements() throws { + try assertParseExpression(source: "unless (x) y else z", expected: "[If [Not [Ref x]] [Ref y] [Ref z]]") + try assertParseExpression(source: "unless (x) y", expected: "[If [Not [Ref x]] [Ref y] []]") + } + func testWhileStatements() throws { try assertParseExpression(source: "while (x) y", expected: "[While [Ref x] [Ref y]]") } - - + func testAssignment() throws { try assertParseExpression(source: "foo = bar", expected: "[Assign foo [Ref bar]]") } - + func testVars() throws { try assertParseExpression(source: "var foo = bar", expected: "[Var foo [Ref bar]]") } - + func testVals() throws { try assertParseExpression(source: "val foo = bar", expected: "[Val foo [Ref bar]]") } - + func testIfAsAnExpression() throws { try assertParseExpression(source: "1 + if (true) 2 else 3", expected: "[Plus [Lit 1] [If [Lit true] [Lit 2] [Lit 3]]]") try assertParseExpression(source: "if (true) 2 else 3 + 4", expected: "[If [Lit true] [Lit 2] [Plus [Lit 3] [Lit 4]]]") try assertParseExpression(source: "(if (true) 2 else 3) + 4", expected: "[Plus [If [Lit true] [Lit 2] [Lit 3]] [Lit 4]]") } - + + func testUnlessAsAnExpression() throws { + try assertParseExpression(source: "1 + unless (true) 2 else 3", expected: "[Plus [Lit 1] [If [Not [Lit true]] [Lit 2] [Lit 3]]]") + try assertParseExpression(source: "unless (true) 2 else 3 + 4", expected: "[If [Not [Lit true]] [Lit 2] [Plus [Lit 3] [Lit 4]]]") + try assertParseExpression(source: "(unless (true) 2 else 3) + 4", expected: "[Plus [If [Not [Lit true]] [Lit 2] [Lit 3]] [Lit 4]]") + } + func testExpressionList() throws { try assertParseExpression(source: "{}", expected: "[ExpressionList []]") try assertParseExpression(source: "{ x; y; z }", expected: "[ExpressionList [[Ref x], [Ref y], [Ref z]]]") } - + func testAssignmentToLiteralIsSyntaxError() throws { assertSyntaxError(code: "1 = bar;") } - - + func testBinaryOperators() throws { try assertParseExpression(source: "1 + 2", expected: "[Plus [Lit 1] [Lit 2]]") try assertParseExpression(source: "1 - 2", expected: "[Minus [Lit 1] [Lit 2]]") @@ -65,12 +71,11 @@ final class ParserTests: XCTestCase { try assertParseExpression(source: "true && false", expected: "[And [Lit true] [Lit false]]") try assertParseExpression(source: "true || false", expected: "[Or [Lit true] [Lit false]]") } - + func testNot() throws { try assertParseExpression(source: "!x", expected: "[Not [Ref x]]") } - func testOperatorPrecedence() throws { try assertParseExpression(source: "a + b == c + d", expected: "[== [Plus [Ref a] [Ref b]] [Plus [Ref c] [Ref d]]]") try assertParseExpression(source: "a + (b == c) + d", expected: "[Plus [Plus [Ref a] [== [Ref b] [Ref c]]] [Ref d]]") @@ -80,29 +85,29 @@ final class ParserTests: XCTestCase { try assertParseExpression(source: "a == b < c", expected: "[== [Ref a] [< [Ref b] [Ref c]]]") try assertParseExpression(source: "a == b || c == d && e == f", expected: "[Or [== [Ref a] [Ref b]] [And [== [Ref c] [Ref d]] [== [Ref e] [Ref f]]]]") } - + func testFunctionCall() throws { try assertParseExpression(source: "foo()", expected: "[Call [Ref foo] []]") try assertParseExpression(source: "bar(1)", expected: "[Call [Ref bar] [[Lit 1]]]") try assertParseExpression(source: "baz(1, x)", expected: "[Call [Ref baz] [[Lit 1], [Ref x]]]") try assertParseExpression(source: "(baz)()", expected: "[Call [Ref baz] []]") } - + func testFunctionDefinition() throws { try assertParseFunctionDefinition(source: "fun square(x: Int, y: Int): Int = x * x", - expected: "FunctionDefinition(name=square, args=[(x, Int), (y, Int)], returnType=Int, body=[Multiply [Ref x] [Ref x]])") + expected: "FunctionDefinition(name=square, args=[(x, Int), (y, Int)], returnType=Int, body=[Multiply [Ref x] [Ref x]])") } - + func testSamples() throws { try assertParseExpression(source: """ if (x == 2 + 2) { var t = "It"; s = t + " worked!" } """, - expected: + expected: """ [If [== [Ref x] [Plus [Lit 2] [Lit 2]]] [ExpressionList [[Var t [Lit "It"]], [Assign s [Plus [Ref t] [Lit " worked!"]]]]] []] """) - + try assertParseFunctionDefinition(source: """ fun fib(i: Int): Int = if (i == 0 || i == 1) @@ -110,30 +115,30 @@ final class ParserTests: XCTestCase { else fib(i-1) + fib(i-2) """, - expected: + expected: """ FunctionDefinition(name=fib, args=[(i, Int)], returnType=Int, body=[If [Or [== [Ref i] [Lit 0]] [== [Ref i] [Lit 1]]] [Ref i] [Plus [Call [Ref fib] [[Minus [Ref i] [Lit 1]]]] [Call [Ref fib] [[Minus [Ref i] [Lit 2]]]]]]) """) - + try assertParseFunctionDefinition(source: """ fun square(x: Int) = x * x """, - expected: + expected: """ FunctionDefinition(name=square, args=[(x, Int)], returnType=null, body=[Multiply [Ref x] [Ref x]]) """) - + try assertParseFunctionDefinition(source: """ fun cube(x: Int) = x * x * x """, - expected: + expected: """ FunctionDefinition(name=cube, args=[(x, Int)], returnType=null, body=[Multiply [Multiply [Ref x] [Ref x]] [Ref x]]) """) } - + private func assertSyntaxError(code: String) { var thrownError: Error? XCTAssertThrowsError(try parseExpression(code: code), "expected syntax error") { @@ -141,28 +146,30 @@ final class ParserTests: XCTestCase { } XCTAssert(thrownError is SyntaxErrorException) } - + private func assertParseExpression(source: String, expected: String) throws { let expression = try parseExpression(code: source) - + XCTAssertEqual(expected, expression.description, source) } - + private func assertParseFunctionDefinition(source: String, expected: String) throws { let expression = try parseFunctionDefinition(code: source) - + XCTAssertEqual(expected, expression.description, source) } - + static var allTests = [ ("testVariables", testVariables), ("testLiterals", testLiterals), ("testIfStatements", testIfStatements), + ("testUnlessStatements", testUnlessStatements), ("testWhileStatements", testWhileStatements), ("testAssignment", testAssignment), ("testVars", testVars), ("testVals", testVals), ("testIfAsAnExpression", testIfAsAnExpression), + ("testUnlessAsAnExpression", testUnlessAsAnExpression), ("testExpressionList", testExpressionList), ("testAssignmentToLiteralIsSyntaxError", testAssignmentToLiteralIsSyntaxError), ("testBinaryOperators", testBinaryOperators), diff --git a/Tests/SwonaTests/TranslatorTests.swift b/Tests/SwonaTests/TranslatorTests.swift new file mode 100644 index 0000000..de1a81d --- /dev/null +++ b/Tests/SwonaTests/TranslatorTests.swift @@ -0,0 +1,200 @@ +import Swona +import XCTest + +final class TranslatorTests: XCTestCase { + func testStackDelta() { + let block = BasicBlock() + + XCTAssertEqual(0, block.stackDelta) + + block += IR.push(value: .integer(value: 42)) + XCTAssertEqual(1, block.stackDelta) + + block += IR.push(value: .integer(value: 1)) + XCTAssertEqual(2, block.stackDelta) + + block += IR.push(value: .integer(value: 42)) + XCTAssertEqual(3, block.stackDelta) + + block += IR.add + XCTAssertEqual(2, block.stackDelta) + + block.endWithBranch(trueBlock: BasicBlock(), falseBlock: BasicBlock()) + XCTAssertEqual(1, block.stackDelta) + } + + func testLocalVariableOffsets() { + let block = BasicBlock() + + block += IR.localFrameIR(.loadLocal(index: 0, name: "square")) + block += IR.localFrameIR(.storeLocal(index: 1, name: "sq")) + + XCTAssertEqual(1, block.maxLocalVariableOffset) + } + + func testJumpBackwardsDoesNotMaintainBalance() { + let graph = BasicBlockGraph() + + let end = BasicBlock() + graph.start += IR.push(value: .integer(value: 42)) + graph.start += IR.push(value: .integer(value: 42)) + graph.start.endWithBranch(trueBlock: graph.start, falseBlock: end) + end += IR.push(value: .integer(value: 42)) + + assertInvalidStackUse(graph: graph) + } + + func testStackUnderflow() { + let graph = BasicBlockGraph() + + graph.start += IR.pop + assertInvalidStackUse(graph: graph) + } + + private func assertInvalidStackUse(graph: BasicBlockGraph) { + var thrownError: Error? + XCTAssertThrowsError(try graph.buildStackDepthMap(), "expected invalid stack use exception") { + thrownError = $0 + } + XCTAssert(thrownError is InvalidStackUseException) + } + + let evaluator = Evaluator() + + func testSimpleTranslation() throws { + try assertTranslation(source: """ + { + var x = 4 + 1; + while (x != 0) + x = x - 1 + } + """, + expectedInstructionsAsString: """ + 0 stack[fp+2] = 5 + 1 stack[fp+1] = stack[fp+2] ; store local x + 2 jump 3 + 3 stack[fp+2] = stack[fp+1] ; load local x + 4 stack[fp+3] = 0 + 5 stack[fp+2] = stack[fp+2] == stack[fp+3] + 6 stack[fp+2] = !stack[fp+2] + 7 jump-if-false stack[fp+2] 14 + 8 jump 9 + 9 stack[fp+2] = stack[fp+1] ; load local x + 10 stack[fp+3] = 1 + 11 stack[fp+2] = stack[fp+2] - stack[fp+3] + 12 stack[fp+1] = stack[fp+2] ; store local x + 13 jump 3 + 14 stack[fp+2] = Unit + 15 ret value=stack[fp+2], address=stack[fp+0] + """) + } + + func testReadMeExample() throws { + try assertTranslation(source: """ + { + var x = 4; + var s = ""; + if (x == 2 + 2) { var t = "It"; s = t + " worked!" } + } + """, + expectedInstructionsAsString: """ + 0 stack[fp+4] = 4 + 1 stack[fp+1] = stack[fp+4] ; store local x + 2 stack[fp+4] = "" + 3 stack[fp+2] = stack[fp+4] ; store local s + 4 stack[fp+4] = stack[fp+1] ; load local x + 5 stack[fp+5] = 4 + 6 stack[fp+4] = stack[fp+4] == stack[fp+5] + 7 jump-if-false stack[fp+4] 16 + 8 jump 9 + 9 stack[fp+4] = "It" + 10 stack[fp+5] = stack[fp+4] ; dup + 11 stack[fp+3] = stack[fp+5] ; store local t + 12 stack[fp+5] = " worked!" + 13 stack[fp+4] = stack[fp+4] ++ stack[fp+5] + 14 stack[fp+2] = stack[fp+4] ; store local s + 15 jump 16 + 16 stack[fp+4] = Unit + 17 ret value=stack[fp+4], address=stack[fp+0] + """) + } + + func testRuntimeFunctions() throws { + try registerRuntimeFunctions(evaluator: evaluator) + try assertTranslation(source: """ + { + var a = stringArrayOfSize(3, ""); + var done = false; + var i = 0; + + while (!done) { stringArraySet(a, i, "" + i); i = i + 1; if (i==stringArrayLength(a)) done=true }; + + stringArrayGet(a, 2) + } + """, + expectedInstructionsAsString: """ + 0 stack[fp+4] = 3 + 1 stack[fp+5] = "" + 2 stack[fp+6] = heap[2] ; stringArrayOfSize + 3 call stack[fp+6], 2 + 4 fp = fp - 4 + 5 stack[fp+1] = stack[fp+4] ; store local a + 6 stack[fp+4] = false + 7 stack[fp+2] = stack[fp+4] ; store local done + 8 stack[fp+4] = 0 + 9 stack[fp+3] = stack[fp+4] ; store local i + 10 jump 11 + 11 stack[fp+4] = stack[fp+2] ; load local done + 12 stack[fp+4] = !stack[fp+4] + 13 jump-if-false stack[fp+4] 40 + 14 jump 15 + 15 stack[fp+4] = stack[fp+1] ; load local a + 16 stack[fp+5] = stack[fp+3] ; load local i + 17 stack[fp+6] = "" + 18 stack[fp+7] = stack[fp+3] ; load local i + 19 stack[fp+6] = stack[fp+6] ++ stack[fp+7] + 20 stack[fp+7] = heap[5] ; stringArraySet + 21 call stack[fp+7], 3 + 22 fp = fp - 4 + 23 Nop + 24 stack[fp+4] = stack[fp+3] ; load local i + 25 stack[fp+5] = 1 + 26 stack[fp+4] = stack[fp+4] + stack[fp+5] + 27 stack[fp+5] = stack[fp+4] ; dup + 28 stack[fp+3] = stack[fp+5] ; store local i + 29 stack[fp+5] = stack[fp+1] ; load local a + 30 stack[fp+6] = heap[3] ; stringArrayLength + 31 call stack[fp+6], 1 + 32 fp = fp - 5 + 33 stack[fp+4] = stack[fp+4] == stack[fp+5] + 34 jump-if-false stack[fp+4] 39 + 35 jump 36 + 36 stack[fp+4] = true + 37 stack[fp+2] = stack[fp+4] ; store local done + 38 jump 39 + 39 jump 11 + 40 stack[fp+4] = stack[fp+1] ; load local a + 41 stack[fp+5] = 2 + 42 stack[fp+6] = heap[4] ; stringArrayGet + 43 call stack[fp+6], 2 + 44 fp = fp - 4 + 45 ret value=stack[fp+4], address=stack[fp+0] + """) + } + + private func assertTranslation(source: String, expectedInstructionsAsString: String) throws { + let instructions = try evaluator.dump(code: source).lines().map { $0.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines) } + let expectedInstructions = expectedInstructionsAsString.lines().map { $0.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines) } + XCTAssertEqual(instructions, expectedInstructions) + } + + static var allTests = [ + ("testStackDelta", testStackDelta), + ("testLocalVariableOffsets", testLocalVariableOffsets), + ("testJumpBackwardsDoesNotMaintainBalance", testJumpBackwardsDoesNotMaintainBalance), + ("testStackUnderflow", testStackUnderflow), + ("testSimpleTranslation", testSimpleTranslation), + ("testReadMeExample", testReadMeExample), + ("testRuntimeFunctions", testRuntimeFunctions), + ] +} diff --git a/Swona/Tests/SwonaTests/TypesTests.swift b/Tests/SwonaTests/TypesTests.swift similarity index 92% rename from Swona/Tests/SwonaTests/TypesTests.swift rename to Tests/SwonaTests/TypesTests.swift index daa80c7..1ab2f91 100644 --- a/Swona/Tests/SwonaTests/TypesTests.swift +++ b/Tests/SwonaTests/TypesTests.swift @@ -1,15 +1,13 @@ -import XCTest -import class Foundation.Bundle import Swona +import XCTest final class TypesTests: XCTestCase { func testPrimitiveTypeToString() { XCTAssertEqual("String", Type.string.description) XCTAssertEqual("Int", Type.int.description) XCTAssertEqual("Boolean", Type.boolean.description) - } - + func testFunctionTypeToString() { XCTAssertEqual("() -> String", Type.function(.function(argumentTypes: [], returnType: Type.string)).description) XCTAssertEqual("(Int) -> Boolean", Type.function(.function(argumentTypes: [Type.int], returnType: Type.boolean)).description) @@ -19,166 +17,146 @@ final class TypesTests: XCTestCase { func testArrayTypeToString() { XCTAssertEqual("Array", Type.array(elementType: Type.string).description) XCTAssertEqual("Array>", Type.array(elementType: Type.array(elementType: Type.string)).description) - } - + var env: StaticEnvironment = GlobalStaticEnvironment() - + func testLiteralTypes() throws { assertType(expectedType: Type.string, code: "\"foo\"") assertType(expectedType: Type.int, code: "123") assertType(expectedType: Type.boolean, code: "true") } - - + func testBoundVariableTypes() throws { try env.bind(name: "s", type: Type.string) try env.bind(name: "b", type: Type.boolean) - + assertType(expectedType: Type.string, code: "s") assertType(expectedType: Type.boolean, code: "b") } - - + func testNot() throws { assertType(expectedType: Type.boolean, code: "!true") assertTypeCheckFails(code: "!1") } - - + func testEqualityComparison() throws { assertType(expectedType: Type.boolean, code: "true == false") assertType(expectedType: Type.boolean, code: "1 == 1") assertType(expectedType: Type.boolean, code: "\"foo\" == \"bar\"") - + assertType(expectedType: Type.boolean, code: "true != false") assertType(expectedType: Type.boolean, code: "1 != 1") assertType(expectedType: Type.boolean, code: "\"foo\" != \"bar\"") - + assertTypeCheckFails(code: "true == 1") assertTypeCheckFails(code: "true != 1") } - - + func testNumericOperators() throws { assertType(expectedType: Type.int, code: "1 + 1") assertType(expectedType: Type.int, code: "1 - 1") - + assertTypeCheckFails(code: "1 + true") assertTypeCheckFails(code: "true + 1") assertTypeCheckFails(code: "true + true") assertTypeCheckFails(code: "1 + \"foo\"") assertTypeCheckFails(code: "true + \"foo\"") - + assertTypeCheckFails(code: "1 - true") assertTypeCheckFails(code: "true - 1") assertTypeCheckFails(code: "true - true") assertTypeCheckFails(code: "\"foo\" - \"bar\"") } - - + func testIfWithoutElseProducesUnit() throws { assertType(expectedType: Type.unit, code: "if (true) 42") } - - + func testIfWithIncompatibleTypesProducesUnit() throws { assertType(expectedType: Type.unit, code: "if (true) 42 else false") } - - + func testTypeOfEmptyExpressionListIsUnit() throws { assertType(expectedType: Type.unit, code: "{}") } - - + func testTypeOfNonEmptyExpressionListIsTypeOfLast() throws { assertType(expectedType: Type.int, code: "{ 1 }") assertType(expectedType: Type.string, code: "{ 1; \"\" }") assertType(expectedType: Type.int, code: "{ 1; \"\"; 3 }") } - - + func testIfWithCompatibleTypesReturnsTheCommonType() throws { assertType(expectedType: Type.int, code: "if (true) 42 else 31") assertType(expectedType: Type.string, code: "if (true) \"foo\" else \"bar\"") } - - + func testPlusWithStringLiteral() throws { assertType(expectedType: Type.string, code: "\"foo\" + \"bar\"") assertType(expectedType: Type.string, code: "\"foo\" + 42") assertType(expectedType: Type.string, code: "\"foo\" + true") } - - + func testVariableCanBeReboundInNestedEnvironment() throws { try env.bind(name: "x", type: Type.boolean) - + try typeCheck(code: "if (x) { var x = 42 }") try typeCheck(code: "while (x) { var x = 42 }") } - - + func testVariableIsVisibleInNestedEnvironment() throws { try typeCheck(code: """ - if (true) { - var x = 4; if (true) { - var y = x + var x = 4; + if (true) { + var y = x + } } - } - """) + """) } - - + func testVariablesDefinedByNestedEnvironmentAreNotVisibleOutside() throws { assertTypeCheckFails(code: """ - if (true) { if (true) { - var x = 4 - }; - var y = x - } - """) - } - - + if (true) { + var x = 4 + }; + var y = x + } + """) + } + func testUnboundVariables() throws { assertTypeCheckFails(code: "x") assertTypeCheckFails(code: "x = 4") } - - + func testEvaluationFailsForRebindingVariables() throws { assertTypeCheckFails(code: "{ var x = 4; var x = 4 }") } - - + func testUnboundVariableType() throws { assertTypeCheckFails(code: "s") } - - + func testAssigningToParameters() throws { env = GlobalStaticEnvironment().newScope(args: [("foo", Type.int)]) assertTypeCheckFails(code: "foo = 42") } - - + func testAssignmentToImmutableVariables() throws { assertTypeCheckFails(code: """ - if (true) { - val x = 4; - x = 2 - } - """) - } - - + if (true) { + val x = 4; + x = 2 + } + """) + } + func testRelationalOperatorsAreNotSupportedForUnit() throws { try env.bind(name: "foo", type: Type.unit) - + assertTypeCheckFails(code: "foo == foo") assertTypeCheckFails(code: "foo != foo") assertTypeCheckFails(code: "foo < foo") @@ -186,11 +164,10 @@ final class TypesTests: XCTestCase { assertTypeCheckFails(code: "foo <= foo") assertTypeCheckFails(code: "foo >= foo") } - - + func testRelationalOperatorsAreNotSupportedForFunctions() throws { try env.bind(name: "foo", type: Type.function(.function(argumentTypes: [Type.string], returnType: Type.int))) - + assertTypeCheckFails(code: "foo == foo") assertTypeCheckFails(code: "foo != foo") assertTypeCheckFails(code: "foo < foo") @@ -198,7 +175,7 @@ final class TypesTests: XCTestCase { assertTypeCheckFails(code: "foo <= foo") assertTypeCheckFails(code: "foo >= foo") } - + private func assertTypeCheckFails(code: String) { var thrownError: Error? XCTAssertThrowsError(try typeCheck(code: code), "expected type check exception") { @@ -210,12 +187,11 @@ final class TypesTests: XCTestCase { private func assertType(expectedType: Type, code: String) { XCTAssertEqual(expectedType, try typeCheck(code: code).type) } - + @discardableResult private func typeCheck(code: String) throws -> TypedExpression { - return try parseExpression(code: code).typeCheck(env: env) + try parseExpression(code: code).typeCheck(env: env) } - static var allTests = [ ("testPrimitiveTypeToString", testPrimitiveTypeToString), ("testFunctionTypeToString", testFunctionTypeToString), @@ -241,7 +217,5 @@ final class TypesTests: XCTestCase { ("testAssignmentToImmutableVariables", testAssignmentToImmutableVariables), ("testRelationalOperatorsAreNotSupportedForUnit", testRelationalOperatorsAreNotSupportedForUnit), ("testRelationalOperatorsAreNotSupportedForFunctions", testRelationalOperatorsAreNotSupportedForFunctions), - - ] } diff --git a/Swona/Tests/SwonaTests/VMTests.swift b/Tests/SwonaTests/VMTests.swift similarity index 82% rename from Swona/Tests/SwonaTests/VMTests.swift rename to Tests/SwonaTests/VMTests.swift index 5197c0e..6b50e47 100644 --- a/Swona/Tests/SwonaTests/VMTests.swift +++ b/Tests/SwonaTests/VMTests.swift @@ -1,61 +1,81 @@ -import XCTest -import class Foundation.Bundle import Swona +import XCTest final class VMTests: XCTestCase { let evaluator = Evaluator(trace: false) - + func testLiteralEvaluation() throws { try assertEvaluation(code: "42", expectedValue: Value.integer(value: 42)) try assertEvaluation(code: "true", expectedValue: Value.bool(value: true)) try assertEvaluation(code: "\"foo\"", expectedValue: Value.string(value: "foo")) } - + func testVariableEvaluation() throws { try evaluator.bind(name: "x", value: .integer(value: 123)) - + try assertEvaluation(code: "x", expectedValue: .integer(value: 123)) } - + func testVarStatements() throws { try evaluate(code: "var x = 42") - + try assertEvaluation(code: "x", expectedValue: .integer(value: 42)) } - + func testAssignments() throws { try evaluator.bind(name: "x", value: .integer(value: 42)) - + try evaluate(code: "x = 123") - + try assertEvaluation(code: "x", expectedValue: .integer(value: 123)) } - + func testArithmetic() throws { try assertEvaluation(code: "1 + 2 * 3 + 4 / 2", expectedValue: .integer(value: 9)) } - + func testIfExpressions() throws { try evaluator.bind(name: "x", value: Value.bool(value: true)) try evaluator.bind(name: "y", value: .integer(value: 42)) try evaluator.bind(name: "r", value: .integer(value: 0)) - + try evaluate(code: "if (x) r = 123 else r = y") try assertEvaluation(code: "r", expectedValue: .integer(value: 123)) - + try evaluate(code: "x = false") - + try evaluate(code: "if (x) r = 123 else r = y") try assertEvaluation(code: "r", expectedValue: .integer(value: 42)) } - + func testIfExpressionValues() throws { try evaluator.bind(name: "x", value: Value.bool(value: true)) - + try assertEvaluation(code: "if (x) 1 else 2", expectedValue: .integer(value: 1)) try assertEvaluation(code: "if (!x) 1 else 2", expectedValue: .integer(value: 2)) } - + + func testUnlessExpressions() throws { + try evaluator.bind(name: "x", value: Value.bool(value: true)) + try evaluator.bind(name: "y", value: .integer(value: 42)) + try evaluator.bind(name: "r", value: .integer(value: 0)) + + try evaluate(code: "unless (x) r = 123 else r = y") + try assertEvaluation(code: "r", expectedValue: .integer(value: 42)) + + try evaluate(code: "x = false") + + try evaluate(code: "unless (x) r = 123 else r = y") + try assertEvaluation(code: "r", expectedValue: .integer(value: 123)) + } + + func testUnlessExpressionValues() throws { + try evaluator.bind(name: "x", value: Value.bool(value: true)) + + try assertEvaluation(code: "unless (x) 1 else 2", expectedValue: .integer(value: 2)) + try assertEvaluation(code: "unless (!x) 1 else 2", expectedValue: .integer(value: 1)) + } + func testBinaryExpressions() throws { try assertEvaluation(code: "1 + 2", expectedValue: .integer(value: 3)) try assertEvaluation(code: "1 - 2", expectedValue: .integer(value: -1)) @@ -64,62 +84,61 @@ final class VMTests: XCTestCase { try assertEvaluation(code: "1 != 2", expectedValue: Value.bool(value: true)) try assertEvaluation(code: "1 != 1", expectedValue: Value.bool(value: false)) } - + func testIfWithoutElse() throws { try evaluator.bind(name: "r", value: .integer(value: 0)) - + try evaluate(code: "if (false) r = 1") try assertEvaluation(code: "r", expectedValue: .integer(value: 0)) try evaluate(code: "if (true) r = 2") try assertEvaluation(code: "r", expectedValue: .integer(value: 2)) } - + func testWhileLoop() throws { try evaluator.bind(name: "x", value: .integer(value: 5)) try evaluator.bind(name: "a", value: .integer(value: 0)) try evaluator.bind(name: "b", value: .integer(value: 0)) - + try evaluate(code: """ - while (x != 0) { - x = x - 1; - a = a + 1; - b = a + b - } - """) - - + while (x != 0) { + x = x - 1; + a = a + 1; + b = a + b + } + """) + try assertEvaluation(code: "x", expectedValue: .integer(value: 0)) try assertEvaluation(code: "a", expectedValue: .integer(value: 5)) try assertEvaluation(code: "b", expectedValue: .integer(value: 15)) } - + func testNot() throws { try evaluate(code: "val x = true") try assertEvaluation(code: "!x", expectedValue: .bool(value: false)) try assertEvaluation(code: "!!x", expectedValue: .bool(value: true)) } - + func testEvaluationFailuresForCoercions() throws { assertTypeCheckFails(s: "1 + \"foo\"") assertTypeCheckFails(s: "!1") } - + func testDirectCalls() throws { try defineSquareFunction() try assertEvaluation(code: "square(4)", expectedValue: .integer(value: 16)) } - + func testFunctionCallsThroughLocalVariable() throws { try defineSquareFunction() try evaluator.bind(name: "result", value: .integer(value: 0)) - + try evaluate(code: """ - if (true) { - var sq = square; - result = sq(5) - } - """) - + if (true) { + var sq = square; + result = sq(5) + } + """) + try assertEvaluation(code: "result", expectedValue: .integer(value: 25)) } @@ -127,23 +146,23 @@ final class VMTests: XCTestCase { try defineSquareFunction() try assertEvaluation(code: "(square)(6)", expectedValue: .integer(value: 36)) } - + func testExpressionFunctions() throws { - try evaluate(code:"fun sub(x: Int, y: Int): Int = x - y") + try evaluate(code: "fun sub(x: Int, y: Int): Int = x - y") try assertEvaluation(code: "sub(7, 4)", expectedValue: .integer(value: 3)) } - + func testEvaluationFailsForUnboundVariables() throws { assertTypeCheckFails(s: "x") assertTypeCheckFails(s: "x = 4") } - + func testLogicalOperators() throws { try assertEvaluation(code: "false || false", expectedValue: Value.bool(value: false)) try assertEvaluation(code: "false || true", expectedValue: Value.bool(value: true)) try assertEvaluation(code: "true || false", expectedValue: Value.bool(value: true)) try assertEvaluation(code: "true || true", expectedValue: Value.bool(value: true)) - + try assertEvaluation(code: "false && false", expectedValue: Value.bool(value: false)) try assertEvaluation(code: "false && true", expectedValue: Value.bool(value: false)) try assertEvaluation(code: "true && false", expectedValue: Value.bool(value: false)) @@ -153,13 +172,13 @@ final class VMTests: XCTestCase { func testEvaluationFailsForRebindingVariables() throws { assertTypeCheckFails(s: "{ var x = 4; var x = 4 }") } - + func testPlusWithStringLiteralOnLeftSideIsStringConcatenation() throws { try assertEvaluation(code: "\"foo \" + \"bar\"", expectedValue: Value.string(value: "foo bar")) try assertEvaluation(code: "\"foo \" + 42", expectedValue: Value.string(value: "foo 42")) try assertEvaluation(code: "\"foo \" + true", expectedValue: Value.string(value: "foo true")) } - + func testRelationalOperators() throws { try assertEvaluation(code: "1 == 1", expectedValue: Value.bool(value: true)) try assertEvaluation(code: "1 != 1", expectedValue: Value.bool(value: false)) @@ -167,7 +186,7 @@ final class VMTests: XCTestCase { try assertEvaluation(code: "1 <= 1", expectedValue: Value.bool(value: true)) try assertEvaluation(code: "1 > 1", expectedValue: Value.bool(value: false)) try assertEvaluation(code: "1 >= 1", expectedValue: Value.bool(value: true)) - + try assertEvaluation(code: "1 == 2", expectedValue: Value.bool(value: false)) try assertEvaluation(code: "1 != 2", expectedValue: Value.bool(value: true)) try assertEvaluation(code: "1 < 2", expectedValue: Value.bool(value: true)) @@ -175,44 +194,43 @@ final class VMTests: XCTestCase { try assertEvaluation(code: "1 > 2", expectedValue: Value.bool(value: false)) try assertEvaluation(code: "1 >= 2", expectedValue: Value.bool(value: false)) } - + func testNativeFunctionCallWithSingleParameter() throws { - - try evaluator.bind(name: "inc", value: .function(fun1(name: "inc", argType: Type.int, returnType: Type.int) { (value: Value) -> Value in - return value.plus(rhs: .integer(value: 1))}), mutable: false) - - try assertEvaluation(code: "inc(4)", expectedValue:.integer(value: 5)) + try evaluator.bind(name: "inc", value: .function(fun1(name: "inc", argType: Type.int, returnType: Type.int) { (value: Value) -> Value in + value.plus(rhs: .integer(value: 1)) + }), mutable: false) + + try assertEvaluation(code: "inc(4)", expectedValue: .integer(value: 5)) } - + func testNativeFunctionCallWithMultipleParameters() throws { - - try evaluator.bind(name: "sub", value: .function(fun2(name: "sub", argType: Type.int, returnType: Type.int) { (a: Value, b: Value) -> Value in - return a.minus(rhs: b)}), mutable: false) - - try assertEvaluation(code: "sub(7, 4)", expectedValue:.integer(value: 3)) + try evaluator.bind(name: "sub", value: .function(fun2(name: "sub", argType: Type.int, returnType: Type.int) { (a: Value, b: Value) -> Value in + a.minus(rhs: b) + }), mutable: false) + + try assertEvaluation(code: "sub(7, 4)", expectedValue: .integer(value: 3)) } - func testNestedIfs() throws { try assertEvaluation(code: "if (false) 1 else if (true) 2 else 3", expectedValue: .integer(value: 2)) } - + func testRecursion() throws { try evaluate(code: """ - fun fib(i: Int): Int = - if (i == 0) - 0 - else if (i == 1) - 1 - else - fib(i-1) + fib(i-2) - """) - + fun fib(i: Int): Int = + if (i == 0) + 0 + else if (i == 1) + 1 + else + fib(i-1) + fib(i-2) + """) + try assertEvaluation(code: "fib(2)", expectedValue: .integer(value: 1)) try assertEvaluation(code: "fib(10)", expectedValue: .integer(value: 55)) try assertEvaluation(code: "fib(20)", expectedValue: .integer(value: 6765)) } - + func testRuntimeFunctions() throws { try registerRuntimeFunctions(evaluator: evaluator) try evaluate(code: #"var a = stringArrayOfSize(3, "")"#) @@ -223,7 +241,7 @@ final class VMTests: XCTestCase { try assertEvaluation(code: "stringArrayGet(a, 1)", expectedValue: .string(value: "1")) try assertEvaluation(code: "stringArrayGet(a, 2)", expectedValue: .string(value: "2")) } - + private func assertTypeCheckFails(s: String) { var thrownError: Error? XCTAssertThrowsError(try evaluate(code: s), "expected type check exception") { @@ -231,23 +249,22 @@ final class VMTests: XCTestCase { } XCTAssert(thrownError is TypeCheckException) } - + private func assertEvaluation(code: String, expectedValue: Value) throws { evaluator.optimize = true XCTAssertEqual(expectedValue, try evaluate(code: code)) evaluator.optimize = false XCTAssertEqual(expectedValue, try evaluate(code: code)) } - + private func defineSquareFunction() throws { try evaluator.evaluate(code: "fun square(x: Int) = x * x") } @discardableResult private func evaluate(code: String) throws -> Value { - return try evaluator.evaluate(code: code).value + try evaluator.evaluate(code: code).value } - static var allTests = [ ("testLiteralEvaluation", testLiteralEvaluation), ("testVariableEvaluation", testVariableEvaluation), @@ -256,6 +273,8 @@ final class VMTests: XCTestCase { ("testArithmetic", testArithmetic), ("testIfExpressions", testIfExpressions), ("testIfExpressionValues", testIfExpressionValues), + ("testUnlessExpressions", testUnlessExpressions), + ("testUnlessExpressionValues", testUnlessExpressionValues), ("testBinaryExpressions", testBinaryExpressions), ("testIfWithoutElse", testIfWithoutElse), ("testWhileLoop", testWhileLoop), diff --git a/Tests/SwonaTests/XCTestManifests.swift b/Tests/SwonaTests/XCTestManifests.swift new file mode 100644 index 0000000..f38f263 --- /dev/null +++ b/Tests/SwonaTests/XCTestManifests.swift @@ -0,0 +1,17 @@ +import XCTest + +#if os(Linux) + public func allTests() -> [XCTestCaseEntry] { + [ + testCase(LexerTests.allTests), + testCase(ParserTests.allTests), + testCase(VMTests.allTests), + testCase(TranslatorTests.allTests), + testCase(OptimizerTests.allTests), + testCase(TypesTests.allTests), + testCase(ExtensionsTests.allTests), + testCase(OrderedSetTests.allTests), + testCase(BridgeTests.allTests), + ] + } +#endif diff --git a/docker-compose.yml b/docker-compose.yml index 5070042..4d19bf5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ services: repl: build: context: . - dockerfile: Dockerfile.swift + dockerfile: Dockerfile volumes: - .:/app working_dir: /app/Repl @@ -12,8 +12,17 @@ services: tests: build: context: . - dockerfile: Dockerfile.swift + dockerfile: Dockerfile volumes: - .:/app - working_dir: /app/Swona + working_dir: /app command: swift test + format: + build: + context: . + dockerfile: Dockerfile.swiftformat + volumes: + - .:/app + working_dir: /app + command: swiftformat . +