├── .gitignore ├── LICENSE.md ├── README.md ├── bitsy-swift.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── bitsy-swift ├── ArgumentConfig.swift ├── CharStream.swift ├── CodeGenerator.swift ├── Input.swift ├── Output.swift ├── Parser.swift ├── SwiftGenerator.swift ├── Token.swift ├── TokenType.swift ├── Tokenizer.swift ├── main.swift └── vendor │ ├── CommandLine │ ├── CommandLine.swift │ ├── Option.swift │ └── StringExtensions.swift │ └── Exec.swift ├── build.sh ├── runbitsy └── samples ├── collatz.bitsy ├── factorial.bitsy ├── fibonacci.bitsy └── gcd.bitsy /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X Finder 2 | .DS_Store 3 | 4 | # Xcode per-user config 5 | *.mode1 6 | *.mode1v3 7 | *.mode2v3 8 | *.perspective 9 | *.perspectivev3 10 | *.pbxuser 11 | xcuserdata 12 | *.xccheckout 13 | *.xcscheme 14 | 15 | # Build products 16 | build/ 17 | bin/ 18 | *.o 19 | *.LinkFileList 20 | *.hmap 21 | 22 | # Automatic backup files 23 | *~.nib/ 24 | *.swp 25 | *~ 26 | *.dat 27 | *.dep 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Ben DiFrancesco 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BitsySwift 2 | 3 | BitsySwift is a compiler for the [Bitsy](https://github.com/apbendi/bitsyspec) 4 | language implemented in Swift. It is currently the canonical implementation of Bitsy. 5 | 6 | ## Bitsy 7 | 8 | [Bitsy](https://github.com/apbendi/bitsyspec) is a programming language which 9 | aims to be the best language to implement 10 | when building your first compiler or interpreter. It is a resource for 11 | programmers learning about language implementation. 12 | 13 | To learn more about Bitsy or to try implementing it yourself, in your favorite 14 | language, check out the runnable, test based language specification, 15 | [BitsySpec](https://github.com/apbendi/bitsyspec). 16 | 17 | You can read more about the motivation behind creating Bitsy on the 18 | [ScopeLift Blog](http://www.scopelift.co/blog/introducing-bitsy-the-first-language-youll-implement-yourself). 19 | 20 | I spoke about creating Bitsy and implementing it in Swift at several conferences in 2016. You can [watch the talk on YouTube](https://youtu.be/XkjySn0nwzQ). 21 | 22 | 23 | ## Requirements 24 | 25 | This version of BitsySwift has been tested with: 26 | 27 | * macOS 10.14 (Mojave) or later 28 | * [Xcode](https://itunes.apple.com/us/app/xcode/id497799835?mt=12) 11.3 29 | * Swift 5.0 30 | 31 | Linux support is currently limited by 32 | [Swift Foundation](https://github.com/apple/swift-corelibs-foundation) but 33 | should come eventually. 34 | 35 | ## Installation 36 | 37 | To 'install' the compiler, simply clone and build the repository. You must have 38 | [Xcode](https://itunes.apple.com/us/app/xcode/id497799835?mt=12) 39 | and the `xcodebuild` utility installed. 40 | 41 | ```bash 42 | git clone https://github.com/apbendi/bitsy-swift.git 43 | cd bitsy-swift 44 | ./build.sh 45 | ``` 46 | 47 | ## Usage 48 | 49 | Once built, you can use the `runbitsy` script to conveniently build and immediately 50 | run any `.bitsy` file. 51 | 52 | ```bash 53 | ./runbitsy samples/collatz.bitsy # Print the Collatz sequence for 7 54 | 22 55 | 11 56 | 34 57 | 17 58 | 52 59 | 26 60 | 13 61 | 40 62 | 20 63 | 10 64 | 5 65 | 16 66 | 8 67 | 4 68 | 2 69 | 1 70 | ``` 71 | 72 | *Note: The `runbitsy` script currently hangs for Bitsy programs which accept 73 | user input, though the binaries themselves run fine. 74 | Any `bash` experts know why?* 75 | 76 | Alternatively, you may directly use the `bitsy-swift` command line utility 77 | directly for additional options: 78 | 79 | ```bash 80 | bin/bitsy-swift --help 81 | Usage: bin/bitsy-swift [options] 82 | -v, --version: 83 | Print the version of bitsy-swift 84 | -h, --help: 85 | Display bitsy-swift usage 86 | -c, --read-cli: 87 | Read Bitsy code from the command line, terminated by a '.' 88 | -o, --output: 89 | Specify a name for the binary output 90 | -e, --emit-cli: 91 | Emit intermediate compilation to command line 92 | -r, --run-delete: 93 | Immediately run and delete the compiled binary 94 | -i, --retain-intermediate: 95 | Retain results of intermediate representation 96 | ``` 97 | 98 | ## Resources 99 | 100 | While Bitsy has been created partially in response to a perceived lack of approachable 101 | resources for learning language implementation, there are still some good 102 | places to start. 103 | 104 | * [Let's Build a Compiler](http://www.compilers.iecc.com/crenshaw/); this 105 | paper from the late 80's (!) is an excellent introduction to compilation. 106 | The biggest downside is the use of 107 | [Pascal](https://en.wikipedia.org/wiki/Pascal_%28programming_language%29) 108 | and [m68K](https://en.wikipedia.org/wiki/Motorola_68000) assembly. While working 109 | through this tutorial, I 110 | [partially translate](https://github.com/apbendi/LetsBuildACompilerInSwift) 111 | his code to Swift. 112 | * [The Super Tiny Compiler](https://github.com/thejameskyle/the-super-tiny-compiler) 113 | is a great resource by James Kyle- a minimal, extremely well commented compiler 114 | written in JavaScript. Be sure to also checkout the associated 115 | [conference talk](https://www.youtube.com/watch?v=Tar4WgAfMr4) 116 | * [How to implement a programming language in JavaScript](http://lisperator.net/pltut/) 117 | is a slightly more advanced resource which is, for better or worse, also 118 | written in JavaScript. 119 | * [A Nanopass Framework for Compiler Education (PDF)](http://www.cs.indiana.edu/~dyb/pubs/nano-jfp.pdf) 120 | * [Stanford Compiler Course](https://www.youtube.com/watch?v=sm0QQO-WZlM&list=PLFB9EC7B8FE963EB8) 121 | with Alex Aiken; an advanced resource for learning some theory and going 122 | deeper. 123 | 124 | ## Contributing 125 | 126 | Contributions of all types are welcome! Open an issue, create a pull request, 127 | or just ask a question. The only requirement is that you be respectful of 128 | others. 129 | 130 | Please checkout the [BitsySpec](https://github.com/apbendi/bitsyspec) repo and join 131 | the discussion to codify version 1.0 of the Bitsy language specification. 132 | -------------------------------------------------------------------------------- /bitsy-swift.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B435CF011D676B8300F3C75C /* SwiftGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B435CF001D676B8300F3C75C /* SwiftGenerator.swift */; }; 11 | B44ABF4F1D2709010012F25B /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44ABF4E1D2709010012F25B /* main.swift */; }; 12 | B44ABF561D27098A0012F25B /* Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44ABF551D27098A0012F25B /* Input.swift */; }; 13 | B44ABF581D2709D60012F25B /* CharStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44ABF571D2709D60012F25B /* CharStream.swift */; }; 14 | B44ABF5A1D270EE40012F25B /* TokenType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44ABF591D270EE40012F25B /* TokenType.swift */; }; 15 | B44ABF5C1D270F580012F25B /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44ABF5B1D270F580012F25B /* Token.swift */; }; 16 | B44ABF5E1D270FFB0012F25B /* Tokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44ABF5D1D270FFB0012F25B /* Tokenizer.swift */; }; 17 | B44ABF601D275F760012F25B /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44ABF5F1D275F760012F25B /* Parser.swift */; }; 18 | B46B12F61D3596600037045F /* CommandLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = B46B12F31D3596600037045F /* CommandLine.swift */; }; 19 | B46B12F71D3596600037045F /* Option.swift in Sources */ = {isa = PBXBuildFile; fileRef = B46B12F41D3596600037045F /* Option.swift */; }; 20 | B46B12F81D3596600037045F /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B46B12F51D3596600037045F /* StringExtensions.swift */; }; 21 | B46B12FA1D3886DF0037045F /* ArgumentConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = B46B12F91D3886DF0037045F /* ArgumentConfig.swift */; }; 22 | B46B12FC1D388C620037045F /* Exec.swift in Sources */ = {isa = PBXBuildFile; fileRef = B46B12FB1D388C620037045F /* Exec.swift */; }; 23 | B4C99B291D418469003DDDD0 /* CodeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C99B281D418469003DDDD0 /* CodeGenerator.swift */; }; 24 | B4CEB1EF1D2EF99A0010AB4B /* Output.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CEB1EE1D2EF99A0010AB4B /* Output.swift */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXCopyFilesBuildPhase section */ 28 | B44ABF491D2709010012F25B /* CopyFiles */ = { 29 | isa = PBXCopyFilesBuildPhase; 30 | buildActionMask = 2147483647; 31 | dstPath = /usr/share/man/man1/; 32 | dstSubfolderSpec = 0; 33 | files = ( 34 | ); 35 | runOnlyForDeploymentPostprocessing = 1; 36 | }; 37 | /* End PBXCopyFilesBuildPhase section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | B435CF001D676B8300F3C75C /* SwiftGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftGenerator.swift; sourceTree = ""; }; 41 | B44ABF4B1D2709010012F25B /* bitsy-swift */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "bitsy-swift"; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | B44ABF4E1D2709010012F25B /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 43 | B44ABF551D27098A0012F25B /* Input.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Input.swift; sourceTree = ""; }; 44 | B44ABF571D2709D60012F25B /* CharStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharStream.swift; sourceTree = ""; }; 45 | B44ABF591D270EE40012F25B /* TokenType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenType.swift; sourceTree = ""; }; 46 | B44ABF5B1D270F580012F25B /* Token.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; 47 | B44ABF5D1D270FFB0012F25B /* Tokenizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tokenizer.swift; sourceTree = ""; }; 48 | B44ABF5F1D275F760012F25B /* Parser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = ""; }; 49 | B46B12F31D3596600037045F /* CommandLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandLine.swift; sourceTree = ""; }; 50 | B46B12F41D3596600037045F /* Option.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Option.swift; sourceTree = ""; }; 51 | B46B12F51D3596600037045F /* StringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; 52 | B46B12F91D3886DF0037045F /* ArgumentConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArgumentConfig.swift; sourceTree = ""; }; 53 | B46B12FB1D388C620037045F /* Exec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Exec.swift; sourceTree = ""; }; 54 | B4C99B281D418469003DDDD0 /* CodeGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeGenerator.swift; sourceTree = ""; }; 55 | B4CEB1EE1D2EF99A0010AB4B /* Output.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Output.swift; sourceTree = ""; }; 56 | /* End PBXFileReference section */ 57 | 58 | /* Begin PBXFrameworksBuildPhase section */ 59 | B44ABF481D2709010012F25B /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | B44ABF421D2709010012F25B = { 70 | isa = PBXGroup; 71 | children = ( 72 | B44ABF4D1D2709010012F25B /* bitsy-swift */, 73 | B44ABF4C1D2709010012F25B /* Products */, 74 | ); 75 | sourceTree = ""; 76 | }; 77 | B44ABF4C1D2709010012F25B /* Products */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | B44ABF4B1D2709010012F25B /* bitsy-swift */, 81 | ); 82 | name = Products; 83 | sourceTree = ""; 84 | }; 85 | B44ABF4D1D2709010012F25B /* bitsy-swift */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | B44ABF4E1D2709010012F25B /* main.swift */, 89 | B46B12F91D3886DF0037045F /* ArgumentConfig.swift */, 90 | B44ABF551D27098A0012F25B /* Input.swift */, 91 | B4CEB1EE1D2EF99A0010AB4B /* Output.swift */, 92 | B44ABF571D2709D60012F25B /* CharStream.swift */, 93 | B44ABF591D270EE40012F25B /* TokenType.swift */, 94 | B44ABF5B1D270F580012F25B /* Token.swift */, 95 | B44ABF5D1D270FFB0012F25B /* Tokenizer.swift */, 96 | B44ABF5F1D275F760012F25B /* Parser.swift */, 97 | B4C99B281D418469003DDDD0 /* CodeGenerator.swift */, 98 | B435CF001D676B8300F3C75C /* SwiftGenerator.swift */, 99 | B46B12F11D3595C80037045F /* vendor */, 100 | ); 101 | path = "bitsy-swift"; 102 | sourceTree = ""; 103 | }; 104 | B46B12F11D3595C80037045F /* vendor */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | B46B12FB1D388C620037045F /* Exec.swift */, 108 | B46B12F21D3595EE0037045F /* CommandLine */, 109 | ); 110 | path = vendor; 111 | sourceTree = ""; 112 | }; 113 | B46B12F21D3595EE0037045F /* CommandLine */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | B46B12F31D3596600037045F /* CommandLine.swift */, 117 | B46B12F41D3596600037045F /* Option.swift */, 118 | B46B12F51D3596600037045F /* StringExtensions.swift */, 119 | ); 120 | path = CommandLine; 121 | sourceTree = ""; 122 | }; 123 | /* End PBXGroup section */ 124 | 125 | /* Begin PBXNativeTarget section */ 126 | B44ABF4A1D2709010012F25B /* bitsy-swift */ = { 127 | isa = PBXNativeTarget; 128 | buildConfigurationList = B44ABF521D2709010012F25B /* Build configuration list for PBXNativeTarget "bitsy-swift" */; 129 | buildPhases = ( 130 | B44ABF471D2709010012F25B /* Sources */, 131 | B44ABF481D2709010012F25B /* Frameworks */, 132 | B44ABF491D2709010012F25B /* CopyFiles */, 133 | ); 134 | buildRules = ( 135 | ); 136 | dependencies = ( 137 | ); 138 | name = "bitsy-swift"; 139 | productName = "bitsy-swift"; 140 | productReference = B44ABF4B1D2709010012F25B /* bitsy-swift */; 141 | productType = "com.apple.product-type.tool"; 142 | }; 143 | /* End PBXNativeTarget section */ 144 | 145 | /* Begin PBXProject section */ 146 | B44ABF431D2709010012F25B /* Project object */ = { 147 | isa = PBXProject; 148 | attributes = { 149 | LastSwiftUpdateCheck = 0730; 150 | LastUpgradeCheck = 0730; 151 | ORGANIZATIONNAME = ScopeLift; 152 | TargetAttributes = { 153 | B44ABF4A1D2709010012F25B = { 154 | CreatedOnToolsVersion = 7.3.1; 155 | LastSwiftMigration = 1130; 156 | }; 157 | }; 158 | }; 159 | buildConfigurationList = B44ABF461D2709010012F25B /* Build configuration list for PBXProject "bitsy-swift" */; 160 | compatibilityVersion = "Xcode 3.2"; 161 | developmentRegion = English; 162 | hasScannedForEncodings = 0; 163 | knownRegions = ( 164 | English, 165 | en, 166 | ); 167 | mainGroup = B44ABF421D2709010012F25B; 168 | productRefGroup = B44ABF4C1D2709010012F25B /* Products */; 169 | projectDirPath = ""; 170 | projectRoot = ""; 171 | targets = ( 172 | B44ABF4A1D2709010012F25B /* bitsy-swift */, 173 | ); 174 | }; 175 | /* End PBXProject section */ 176 | 177 | /* Begin PBXSourcesBuildPhase section */ 178 | B44ABF471D2709010012F25B /* Sources */ = { 179 | isa = PBXSourcesBuildPhase; 180 | buildActionMask = 2147483647; 181 | files = ( 182 | B4CEB1EF1D2EF99A0010AB4B /* Output.swift in Sources */, 183 | B44ABF5C1D270F580012F25B /* Token.swift in Sources */, 184 | B46B12F71D3596600037045F /* Option.swift in Sources */, 185 | B44ABF5E1D270FFB0012F25B /* Tokenizer.swift in Sources */, 186 | B44ABF581D2709D60012F25B /* CharStream.swift in Sources */, 187 | B44ABF5A1D270EE40012F25B /* TokenType.swift in Sources */, 188 | B46B12F81D3596600037045F /* StringExtensions.swift in Sources */, 189 | B46B12FC1D388C620037045F /* Exec.swift in Sources */, 190 | B44ABF561D27098A0012F25B /* Input.swift in Sources */, 191 | B46B12FA1D3886DF0037045F /* ArgumentConfig.swift in Sources */, 192 | B46B12F61D3596600037045F /* CommandLine.swift in Sources */, 193 | B435CF011D676B8300F3C75C /* SwiftGenerator.swift in Sources */, 194 | B4C99B291D418469003DDDD0 /* CodeGenerator.swift in Sources */, 195 | B44ABF4F1D2709010012F25B /* main.swift in Sources */, 196 | B44ABF601D275F760012F25B /* Parser.swift in Sources */, 197 | ); 198 | runOnlyForDeploymentPostprocessing = 0; 199 | }; 200 | /* End PBXSourcesBuildPhase section */ 201 | 202 | /* Begin XCBuildConfiguration section */ 203 | B44ABF501D2709010012F25B /* Debug */ = { 204 | isa = XCBuildConfiguration; 205 | buildSettings = { 206 | ALWAYS_SEARCH_USER_PATHS = NO; 207 | CLANG_ANALYZER_NONNULL = YES; 208 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 209 | CLANG_CXX_LIBRARY = "libc++"; 210 | CLANG_ENABLE_MODULES = YES; 211 | CLANG_ENABLE_OBJC_ARC = YES; 212 | CLANG_WARN_BOOL_CONVERSION = YES; 213 | CLANG_WARN_CONSTANT_CONVERSION = YES; 214 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 215 | CLANG_WARN_EMPTY_BODY = YES; 216 | CLANG_WARN_ENUM_CONVERSION = YES; 217 | CLANG_WARN_INT_CONVERSION = YES; 218 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 219 | CLANG_WARN_UNREACHABLE_CODE = YES; 220 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 221 | CODE_SIGN_IDENTITY = "-"; 222 | COPY_PHASE_STRIP = NO; 223 | DEBUG_INFORMATION_FORMAT = dwarf; 224 | ENABLE_STRICT_OBJC_MSGSEND = YES; 225 | ENABLE_TESTABILITY = YES; 226 | GCC_C_LANGUAGE_STANDARD = gnu99; 227 | GCC_DYNAMIC_NO_PIC = NO; 228 | GCC_NO_COMMON_BLOCKS = YES; 229 | GCC_OPTIMIZATION_LEVEL = 0; 230 | GCC_PREPROCESSOR_DEFINITIONS = ( 231 | "DEBUG=1", 232 | "$(inherited)", 233 | ); 234 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 235 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 236 | GCC_WARN_UNDECLARED_SELECTOR = YES; 237 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 238 | GCC_WARN_UNUSED_FUNCTION = YES; 239 | GCC_WARN_UNUSED_VARIABLE = YES; 240 | MACOSX_DEPLOYMENT_TARGET = 10.11; 241 | MTL_ENABLE_DEBUG_INFO = YES; 242 | ONLY_ACTIVE_ARCH = YES; 243 | SDKROOT = macosx; 244 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 245 | SYMROOT = "${SRCROOT}/build"; 246 | }; 247 | name = Debug; 248 | }; 249 | B44ABF511D2709010012F25B /* Release */ = { 250 | isa = XCBuildConfiguration; 251 | buildSettings = { 252 | ALWAYS_SEARCH_USER_PATHS = NO; 253 | CLANG_ANALYZER_NONNULL = YES; 254 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 255 | CLANG_CXX_LIBRARY = "libc++"; 256 | CLANG_ENABLE_MODULES = YES; 257 | CLANG_ENABLE_OBJC_ARC = YES; 258 | CLANG_WARN_BOOL_CONVERSION = YES; 259 | CLANG_WARN_CONSTANT_CONVERSION = YES; 260 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 261 | CLANG_WARN_EMPTY_BODY = YES; 262 | CLANG_WARN_ENUM_CONVERSION = YES; 263 | CLANG_WARN_INT_CONVERSION = YES; 264 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 265 | CLANG_WARN_UNREACHABLE_CODE = YES; 266 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 267 | CODE_SIGN_IDENTITY = "-"; 268 | COPY_PHASE_STRIP = NO; 269 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 270 | ENABLE_NS_ASSERTIONS = NO; 271 | ENABLE_STRICT_OBJC_MSGSEND = YES; 272 | GCC_C_LANGUAGE_STANDARD = gnu99; 273 | GCC_NO_COMMON_BLOCKS = YES; 274 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 275 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 276 | GCC_WARN_UNDECLARED_SELECTOR = YES; 277 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 278 | GCC_WARN_UNUSED_FUNCTION = YES; 279 | GCC_WARN_UNUSED_VARIABLE = YES; 280 | MACOSX_DEPLOYMENT_TARGET = 10.11; 281 | MTL_ENABLE_DEBUG_INFO = NO; 282 | SDKROOT = macosx; 283 | SYMROOT = "${SRCROOT}/build"; 284 | }; 285 | name = Release; 286 | }; 287 | B44ABF531D2709010012F25B /* Debug */ = { 288 | isa = XCBuildConfiguration; 289 | buildSettings = { 290 | PRODUCT_NAME = "$(TARGET_NAME)"; 291 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 292 | SWIFT_VERSION = 5.0; 293 | }; 294 | name = Debug; 295 | }; 296 | B44ABF541D2709010012F25B /* Release */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | PRODUCT_NAME = "$(TARGET_NAME)"; 300 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 301 | SWIFT_VERSION = 5.0; 302 | }; 303 | name = Release; 304 | }; 305 | /* End XCBuildConfiguration section */ 306 | 307 | /* Begin XCConfigurationList section */ 308 | B44ABF461D2709010012F25B /* Build configuration list for PBXProject "bitsy-swift" */ = { 309 | isa = XCConfigurationList; 310 | buildConfigurations = ( 311 | B44ABF501D2709010012F25B /* Debug */, 312 | B44ABF511D2709010012F25B /* Release */, 313 | ); 314 | defaultConfigurationIsVisible = 0; 315 | defaultConfigurationName = Release; 316 | }; 317 | B44ABF521D2709010012F25B /* Build configuration list for PBXNativeTarget "bitsy-swift" */ = { 318 | isa = XCConfigurationList; 319 | buildConfigurations = ( 320 | B44ABF531D2709010012F25B /* Debug */, 321 | B44ABF541D2709010012F25B /* Release */, 322 | ); 323 | defaultConfigurationIsVisible = 0; 324 | defaultConfigurationName = Release; 325 | }; 326 | /* End XCConfigurationList section */ 327 | }; 328 | rootObject = B44ABF431D2709010012F25B /* Project object */; 329 | } 330 | -------------------------------------------------------------------------------- /bitsy-swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /bitsy-swift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /bitsy-swift/ArgumentConfig.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | * Exposes concrete instances of components needed for compilation, 5 | * based on the arguments passed by the user on the command line. 6 | * Will exit process if user arguments are invalid. 7 | */ 8 | struct ArgumentConfig { 9 | /** 10 | * Concrete CodeReader per user command line configuration 11 | */ 12 | let reader: CodeReader 13 | 14 | /** 15 | * Concrete CodeGenerator per user command line configuration 16 | */ 17 | let generator: CodeGenerator 18 | 19 | /** 20 | * Parse the arguments passed by the user and return 21 | * an exposing concrete instances of components needed for 22 | * the compilation process. Exit process with message to user if 23 | * arguments are invalid. 24 | * 25 | * - parameter version: The version string to report to the user if requested 26 | */ 27 | init(version: String) { 28 | let versionArg = CounterOption(shortFlag: "v", longFlag: "version", helpMessage: "Print the version of bitsy-swift") 29 | let help = CounterOption(shortFlag: "h", longFlag: "help", helpMessage: "Display bitsy-swift usage") 30 | let cliInput = CounterOption(shortFlag: "c", longFlag:"read-cli", helpMessage: "Read Bitsy code from the command line, terminated by a '.'") 31 | let outputPath = StringOption(shortFlag: "o", longFlag:"output", helpMessage: "Specify a name for the binary output") 32 | let cliOutput = CounterOption(shortFlag: "e", longFlag: "emit-cli", helpMessage: "Emit intermediate compilation to command line") 33 | let runDelete = CounterOption(shortFlag: "r", longFlag: "run-delete", helpMessage: "Immediately run and delete the compiled binary") 34 | let retainIntermediate = CounterOption(shortFlag: "i", longFlag: "retain-intermediate", helpMessage: "Retain results of intermediate representation") 35 | 36 | let cli = CommandLine() 37 | cli.addOptions(versionArg, help, cliInput, outputPath, cliOutput, runDelete, retainIntermediate) 38 | 39 | do { 40 | try cli.parse() 41 | } catch { 42 | cli.printUsage(error) 43 | exit(EX_USAGE) 44 | } 45 | 46 | if help.value > 0 { 47 | cli.printUsage() 48 | exit(EX_OK) 49 | } 50 | 51 | let shouldRunDelete = runDelete.value > 0 52 | let shouldRetain = retainIntermediate.value > 0 53 | 54 | ArgumentConfig.check(argument: versionArg, version: version) 55 | reader = ArgumentConfig.reader(freeArgs: cli.unparsedArguments, cliInput: cliInput) 56 | let emitter = ArgumentConfig.emitter(outputPath: outputPath, cliOutput: cliOutput, retain: shouldRetain, runDelete: shouldRunDelete) 57 | generator = SwiftGenerator(emitter: emitter) 58 | } 59 | } 60 | 61 | // MARK: Static Helpers 62 | 63 | private extension ArgumentConfig { 64 | 65 | /** 66 | * Exit reporting the version if requested by user 67 | */ 68 | static func check(argument arg:CounterOption, version: String) { 69 | if arg.value > 0 { 70 | print(version) 71 | exit(EX_OK) 72 | } 73 | } 74 | 75 | /** 76 | * Instantiate a concrete CodeReader based on the CLI input by user. 77 | * Exit with message to user if input is invalid or source cannot be read. 78 | */ 79 | static func reader(freeArgs: [String], cliInput: CounterOption) -> CodeReader { 80 | if cliInput.value > 0 { 81 | return CmdLineReader() 82 | } 83 | 84 | guard let filePath = freeArgs.first , filePath.hasSuffix("bitsy") else { 85 | print("Please provide a valid .bitsy file for compilation; run -h for more info") 86 | exit(EX_NOINPUT) 87 | } 88 | 89 | return FileReader(filePath: filePath) 90 | } 91 | 92 | /** 93 | * Return a configured concrete CodeEmitter base on the CLI input by user 94 | */ 95 | static func emitter(outputPath output:StringOption, cliOutput: CounterOption, retain: Bool, runDelete: Bool) -> CodeEmitter { 96 | if cliOutput.value > 0 { 97 | return CmdLineEmitter() 98 | } 99 | 100 | if let path = output.value { 101 | return FileEmitter(filePath: path, retainIntermediate: retain, runDeleteBinary: runDelete) 102 | } else { 103 | return FileEmitter(filePath: "b.out", retainIntermediate: retain, runDeleteBinary: runDelete) 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /bitsy-swift/CharStream.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | * Convenient interface for advancing one `Character` at a time 5 | * over a String 6 | */ 7 | struct CharStream { 8 | fileprivate let string: String 9 | fileprivate var index: String.Index 10 | 11 | /** 12 | * Initialize a character stream 13 | * 14 | * - parameter string: String to walk over 15 | */ 16 | init(string: String) { 17 | self.string = string 18 | self.index = self.string.startIndex 19 | } 20 | 21 | /** 22 | * Peek at the current character 23 | */ 24 | var current: Character { 25 | return string[index] 26 | } 27 | 28 | /** 29 | * Are there additional characters beyond `current` 30 | */ 31 | var hasMore: Bool { 32 | return index != string.endIndex 33 | } 34 | 35 | /** 36 | * Move to `current` to the next Character if `hasMore` 37 | */ 38 | mutating func advance() { 39 | guard hasMore else { return } 40 | index = string.index(after: index) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /bitsy-swift/CodeGenerator.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MARK: Intermediate Target Details 4 | 5 | /** 6 | * An entity which handles implementation details of a given intermediate representation, 7 | * i.e. the compilation target language 8 | */ 9 | protocol IntermediateBuilder { 10 | 11 | /** 12 | * Returns the external shell command for producing a binary executable from the 13 | * intermediate/target code 14 | * 15 | * - parameter forFinalPath: The final path of the binary executable to generate 16 | */ 17 | func buildCommand(forFinalPath path: String) -> String 18 | 19 | /** 20 | * The appropriate extension for the compilation target (including the '.'). 21 | * For example, ".c" if targeting C, ".js" if targeting JavaScript 22 | */ 23 | var intermediateExtension: String { get } 24 | } 25 | 26 | extension IntermediateBuilder { 27 | 28 | /** 29 | * Full path of the file where the intermediate compilation target will be 30 | * written, derived from the final path of the executable and the appropriate 31 | * extension 32 | * 33 | * - parameter forFinalPath: The final path of the binary executable to be generated 34 | */ 35 | func intermediatePath(forFinalPath path: String) -> String { 36 | return path + intermediateExtension 37 | } 38 | } 39 | 40 | // MARK: Operations 41 | 42 | /** 43 | * The mathematical operations that will need to be performed when generating 44 | * target code 45 | */ 46 | enum CodeGenOperation { 47 | case add, subtract, multiply, divide, modulus 48 | } 49 | 50 | // MARK: Branch Conditions 51 | 52 | /** 53 | * The cases- mapping to IFP, IFN, and IFZ in Bitsy- 54 | * for which a branch conditional will need to be generated in the compilation target 55 | */ 56 | enum CodeGenCondition { 57 | case positive, negative, zero 58 | } 59 | 60 | // MARK: Code Generation 61 | 62 | /** 63 | * Emits code in the compilation target language for a predefined 64 | * series of operations. 65 | * 66 | * CodeGenerator can be thought of as an interface to a very simple, 67 | * abstract instruction set with: 68 | * 69 | * - A single register 70 | * - A stack 71 | * - Arbitrary storage addressable by a String key 72 | * - Conditional branching on the signed-ness of the register 73 | * - Unconditional loop-open/close/exit instructions (admittedly atypical) 74 | * 75 | * A concrete CodeGenerator must emit code (via `emitLine(code:)`) that 76 | * executes equivalent instructions in the compilation target language. It 77 | * must also handle details of building the target language by implementing 78 | * `IntermediateBuilder` 79 | * 80 | * The generator itself may be stateful, 81 | * depending on the sematics available to the target language. For example, 82 | * a generator may need to maintain a stack of labels to properly implement 83 | * `loopOpen()`, `loopClose()`, and `breakLoop()` 84 | */ 85 | protocol CodeGenerator: IntermediateBuilder { 86 | 87 | /** 88 | * The CodeEmitter to write isntructions to 89 | */ 90 | var emitter: CodeEmitter { get } 91 | 92 | /** 93 | * Emits any header code which must precede all subsequent code in the 94 | * the target language 95 | */ 96 | func header() 97 | 98 | /** 99 | * Emits any footer code which must go after all other code generated in 100 | * the target language 101 | */ 102 | func footer() 103 | 104 | /** 105 | * Emits code defining a branch condition based on the state of the register. 106 | * Subsequent code *should* execute if the register matches the condition type. 107 | * 108 | * - parameter type: Execute subsequent code if the state of the register 109 | * matches this condition 110 | */ 111 | func startCond(type: CodeGenCondition) 112 | 113 | /** 114 | * Emits code defining alternate branch associated with a conditional 115 | */ 116 | func elseCond() 117 | 118 | /** 119 | * Emits code defining the end of a conditional branch 120 | */ 121 | func endCond() 122 | 123 | /** 124 | * Emits code defining the beginning of a repeating set of instructions 125 | */ 126 | func loopOpen() 127 | 128 | /** 129 | * Emits code defining the end of a repeating set of instructions 130 | */ 131 | func loopEnd() 132 | 133 | /** 134 | * Emits code which exits a repeating set of instructions 135 | */ 136 | func breakLoop() 137 | 138 | /** 139 | * Emits code which outputs the register value to STDOUT with a trailing newline 140 | */ 141 | func print() 142 | 143 | /** 144 | * Emits code which pauses to take input from STDIN and loads the integer value 145 | * into memory addressable by a name 146 | * 147 | * - Note: input other than a stream of digits, optionally prepended with +/-, 148 | * should load a value of 0 149 | * 150 | * - parameter variableName: The 'address' to load the input 151 | */ 152 | func read(variableName name: String) 153 | 154 | /** 155 | * Loads the addressed value into the register. A given address is always 156 | * initialized with 0. 157 | * 158 | * - parameter variableName: The name of the 'address' from which to load input 159 | */ 160 | func load(variableName name: String) 161 | 162 | /** 163 | * Loads an integer literal into the register 164 | * 165 | * - parameter integerValue: digit characters of the integer to load 166 | */ 167 | func load(integerValue value: String) 168 | 169 | /** 170 | * Stores the current value of the register into the 'address' identified 171 | * 172 | * - parameter variableName: The identifying name of the 'address' to write 173 | */ 174 | func set(variableName name: String) 175 | 176 | /** 177 | * Push the current register value onto the stack 178 | */ 179 | func push() 180 | 181 | /** 182 | * Pop a value off the top of the stack, perform an operation with that value 183 | * and the register (in that order), and place the result back in the register 184 | * 185 | * - parameter andPerform: The mathematical operation to perform 186 | */ 187 | func pop(andPerform operation: CodeGenOperation) 188 | 189 | /** 190 | * Reverse the sign of the value in the register 191 | */ 192 | func negate() 193 | } 194 | 195 | extension CodeGenerator { 196 | /** 197 | * Emit a given line of target language code 198 | */ 199 | func emitLine(_ code: String = "") { 200 | emitter.emit(code: "\(code)\n") 201 | } 202 | 203 | /** 204 | * Finalize the intermediate code in the target language 205 | * to produce an executable binary 206 | */ 207 | func finalize() { 208 | emitter.finalize(withIntermediate: self) 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /bitsy-swift/Input.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | * Reads characters representing code from some location 5 | */ 6 | protocol CodeReader { 7 | 8 | /** 9 | * Read code from a given source and return stream of Characters 10 | */ 11 | func readCode() -> CharStream 12 | } 13 | 14 | /** 15 | * Concrete CodeReader that takes arbitrary user input from STDIN 16 | * until receiving a terminating '.' line 17 | */ 18 | struct CmdLineReader: CodeReader { 19 | func readCode() -> CharStream { 20 | var input = "" 21 | var line = "" 22 | 23 | while line.first != "." { 24 | input += "\(line)\n" 25 | 26 | guard let nextLine = readLine() else { 27 | fatalError() 28 | } 29 | 30 | line = nextLine 31 | } 32 | 33 | return CharStream(string: input) 34 | } 35 | } 36 | 37 | /** 38 | * Concrete CodeReader which loads characters from a given file on 39 | * disk, or Exits with an error to the user if the contents of the file 40 | * fail to read 41 | */ 42 | struct FileReader: CodeReader { 43 | fileprivate let filePath: String 44 | 45 | init(filePath path: String) { 46 | self.filePath = path 47 | } 48 | 49 | func readCode() -> CharStream { 50 | guard let code = try? NSString(contentsOfFile: filePath, encoding: String.Encoding.utf8.rawValue) as String else { 51 | print("Path to bitsy code was not valid (\(filePath))") 52 | exit(EX_NOINPUT) 53 | } 54 | 55 | return CharStream(string: code) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /bitsy-swift/Output.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | * Emits target code to a given location 5 | */ 6 | protocol CodeEmitter { 7 | 8 | /** 9 | * Append code to the output buffer for the compilation target 10 | * 11 | * - parameter code: The code to append to the buffer 12 | */ 13 | func emit(code: String) 14 | 15 | /** 16 | * Finalize the code in the output buffer and perform any further transformations 17 | * to the compilation target to produce a binary 18 | * 19 | * - parameter withIntermediate: Concrete builder for performing any external build step 20 | * required by a given compilation target language 21 | */ 22 | func finalize(withIntermediate builder: IntermediateBuilder) 23 | } 24 | 25 | /** 26 | * Concrete CodeEmitter for writing compilation target code to console on STDOUT 27 | * 28 | * - warning: does not perform any finalization/build. useful for understanding 29 | * or debugging intermediate target 30 | */ 31 | struct CmdLineEmitter: CodeEmitter { 32 | func emit(code: String) { 33 | print(code, terminator: "") 34 | } 35 | 36 | func finalize(withIntermediate builder: IntermediateBuilder) {} 37 | } 38 | 39 | /** 40 | * Concrete CodeEmitter for writing compilation target to a file on disk and 41 | * subsequently performing any required build step on that output to produce an 42 | * an executable binary. Cleans up the 43 | * intermediate target unless instructed otherwise. Exits process with message to 44 | * user if cannot write to file. 45 | * 46 | * - warning: does not write buffer to disk until `finalize(withIntermediate:)` is called 47 | */ 48 | class FileEmitter: CodeEmitter { 49 | fileprivate let retainIntermediate: Bool 50 | fileprivate let runDeleteBinary: Bool 51 | fileprivate let finalPath: String 52 | fileprivate var code: String = "" 53 | 54 | /** 55 | * Initialize a configured FileEmitter 56 | * 57 | * - parameter filePath: Path to write the final executable to 58 | * - parameter retainIntermediate: Leave any intermediate compilation target on disk? 59 | * - parameter runDeleteBinary: Immediately execute, and subsequently delete, the resulting executable? 60 | */ 61 | init(filePath path: String, retainIntermediate retain: Bool = false, runDeleteBinary runDelete: Bool = false) { 62 | finalPath = path 63 | retainIntermediate = retain 64 | runDeleteBinary = runDelete 65 | } 66 | 67 | func emit(code newCode: String) { 68 | self.code += newCode 69 | } 70 | 71 | func finalize(withIntermediate builder: IntermediateBuilder) { 72 | let intermediatePath = builder.intermediatePath(forFinalPath: finalPath) 73 | let buildCommand = builder.buildCommand(forFinalPath: finalPath) 74 | 75 | do { 76 | try code.write(toFile: intermediatePath, atomically: true, encoding: String.Encoding.utf8) 77 | let swiftcOutput = exec(buildCommand) 78 | if swiftcOutput != "" { 79 | print(swiftcOutput) 80 | } 81 | } catch _ { 82 | print("Error writing to file: \(intermediatePath)") 83 | exit(EX_IOERR) 84 | } 85 | 86 | if !retainIntermediate { 87 | delete(filePath: intermediatePath) 88 | } 89 | 90 | if runDeleteBinary { 91 | let bitsyOut = exec("./\(finalPath)") 92 | print(bitsyOut, terminator: "") 93 | 94 | delete(filePath: finalPath) 95 | } 96 | } 97 | 98 | fileprivate func delete(filePath:String) { 99 | let _ = try? FileManager.default.removeItem(atPath: filePath) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /bitsy-swift/Parser.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | * 5 | * The `Parser` consumes the stream of `Token`s created by the `Tokenizer`. Parsing 6 | * is said to "succeed" if the `Parser` completes, in which case the Bitsy source 7 | * code being parsed is considered valid. If the Bitsy source is invalid for any reason, 8 | * for example, if the developer didn't start their program with the `BEGIN` keyword, 9 | * the `Parser` will cease the compilation process and return an error to the user. 10 | * 11 | * In this compiler, the `Parser` also performs code generation as it traverses the stream 12 | * of `Tokens`. It does so by making calls to an abstract `CodeGenerator` class, which implements 13 | * the details of emitting appropriate code in a given target language. Because parsing and 14 | * code generation are done simultaneously, this compiler is referred to as a "single pass" 15 | * compiler. 16 | * 17 | * In more advanced, production compilers, the output of the Parsing routine is often not code 18 | * in the target language, but rather an "Abstract Syntax Tree", or AST. An AST is a 19 | * data structure representing the code that has been parsed and is manipulated by subsequent 20 | * phases of the compilation process. These phases can include steps like optimization, type 21 | * checking, and ultimately code generation. For learning and getting started, a single pass 22 | * is sufficient! 23 | * 24 | * This `Parser` implements an algorithm called "Recursive Descent." Recursive 25 | * Descent parsers consist of a series of mutually recursive functions for each "production", 26 | * or component of the source language. For example: 27 | * 28 | * - A sequence of Bitsy statements can be defined as a 29 | * - A can contain (among other thing) zero more statements 30 | * - A statement contains a between the "LOOP" and "END" keywords 31 | * 32 | * Blocks and loops are mutually recursive, and in fact, our Parser implements two functions 33 | * named `block()` and `loop()` which can call each other. The role of the `block()` function 34 | * is to dispatch to other parsing functions. Other parsing functions consume the `Token`s which 35 | * are expected in their productions, calling others where needed. For example, since the 36 | * `PRINT` statement must be follwed by an expression to output, the `doPrint()` function 37 | * consumes a `.print` `Keyword` and calls `expression()`. Each function 38 | * similiarly follows the "shape" of its expected input, which is called the "grammar." 39 | */ 40 | class Parser { 41 | fileprivate let tokens: Tokenizer 42 | fileprivate let generator: CodeGenerator 43 | 44 | 45 | /** 46 | * Create a Parser 47 | * 48 | * - parameter tokens: The unprocessesd stream of `Token`s the Parser will inspect 49 | * - parameter generator: A code generator the `Parser` will use to output code in 50 | * its given target language 51 | */ 52 | init(tokens: Tokenizer, generator: CodeGenerator) { 53 | self.tokens = tokens 54 | self.generator = generator 55 | 56 | if currentToken.isSkippable { 57 | advanceToken() 58 | } 59 | } 60 | 61 | /** 62 | * Parses the program represented by the `Tokenizer` with which it was created. 63 | * As it does so, the `Parser` produces code in the target language by making calls 64 | * to the `CodeGenerator` with which it was initialized. 65 | * 66 | * If this method completes, the input to the Parser was a valid program, and the 67 | * code generation can be assumed to have completed successfully. This method will cease execution 68 | * and present an error to the user if the sequence of `Token`s does not represent a valid program. 69 | * 70 | * @warning This method should be called once per instance. Subsequent calls would result in an error 71 | */ 72 | func parse() { 73 | program() 74 | } 75 | } 76 | 77 | // MARK: Convenience Methods 78 | 79 | private extension Parser { 80 | 81 | /** 82 | * Convenience accessor to the current `Token` 83 | */ 84 | var currentToken: Token { return tokens.current } 85 | 86 | /** 87 | * Advance the token stream to the next non-`Whitespace`, non-`Comment` token 88 | */ 89 | func advanceToken() { 90 | guard tokens.hasMore else { 91 | return 92 | } 93 | 94 | tokens.advance() 95 | 96 | while currentToken.isSkippable { 97 | tokens.advance() 98 | } 99 | } 100 | 101 | /** 102 | * Ensure the current `Token` matches an expected value, terminating with an error to 103 | * the user if it does not. Advances the `Token` stream unless told to terminate. 104 | * Returns the original String representation of this `Token` in the source code. 105 | * 106 | * - parameter tokenType: The expected value of the current `Token`s type property 107 | * - parameter andTerminate: If true, this method will *not* advance the token stream 108 | */ 109 | @discardableResult 110 | func match(tokenType type: TokenType, andTerminate terminate: Bool = false) -> String { 111 | guard currentToken.type == type else { 112 | print("[ERROR] Expecting \(type.rawValue) but received \(currentToken.value)") 113 | exit(EX_DATAERR) 114 | } 115 | 116 | if terminate { 117 | return currentToken.value 118 | } 119 | 120 | let value = currentToken.value 121 | advanceToken() 122 | return value 123 | } 124 | } 125 | 126 | // MARK: Static Helpers 127 | 128 | private extension Parser { 129 | 130 | /** 131 | * The branch condition needed for code gen given a conditional `TokenType` 132 | * 133 | * - warning: causes a fatal error if not passed a conditional `TokenType` case 134 | * 135 | * - parameter forTokenType: A conditional `TokenType` case 136 | * - returns: The corresponding `CodeGenCondition` 137 | */ 138 | static func ifCond(forTokenType type: TokenType) -> CodeGenCondition { 139 | switch type { 140 | case .ifP: 141 | return .positive 142 | case .ifN: 143 | return .negative 144 | case .ifZ: 145 | return .zero 146 | default: 147 | fatalError("Unexpected non-IF code: \(type)") 148 | } 149 | } 150 | 151 | /** 152 | * The code generation operation for given operator `TokenType` 153 | * 154 | * - warning: causes a fatal error if not passed an operator `TokenType` 155 | * 156 | * - parameter forTokenType: An operator `TokenType` case 157 | * - returns: The corresponding `CodeGenCondition` 158 | */ 159 | static func codeOp(forTokenType type: TokenType) -> CodeGenOperation { 160 | switch type { 161 | case .plus: 162 | return .add 163 | case .minus: 164 | return .subtract 165 | case .multiply: 166 | return .multiply 167 | case .divide: 168 | return .divide 169 | case .modulus: 170 | return .modulus 171 | default: 172 | fatalError("Unexpected non-Operator code: \(type)") 173 | } 174 | } 175 | } 176 | 177 | // MARK: Recursive Descent 178 | 179 | private extension Parser { 180 | 181 | /** 182 | * The top level entry point of our recursive descent parser 183 | */ 184 | func program() { 185 | match(tokenType: .begin) 186 | generator.header() 187 | block() 188 | match(tokenType: .end, andTerminate: true) 189 | generator.footer() 190 | } 191 | 192 | /** 193 | * A block is defined as any number of valid Bitsy statements. Blocks in Bitsy 194 | * are always termintated by an `END`or `ELSE` keyword. Therefore, the block method in 195 | * our parser simply dispatches to the parsing function appropriate for the current 196 | * keyword until it encounters a block terminating keyword. 197 | */ 198 | func block() { 199 | while !currentToken.isBlockEnd { 200 | switch currentToken.type { 201 | case isIf: 202 | ifStatement() 203 | case .loop: 204 | loop() 205 | case .breakKey: 206 | doBreak() 207 | case .print: 208 | doPrint() 209 | case .read: 210 | read() 211 | default: 212 | assignment() 213 | } 214 | } 215 | } 216 | 217 | /** 218 | * Parses any of the Bitsy conditional statements, along with the 219 | * optional else branch. 220 | */ 221 | func ifStatement() { 222 | let condType = Parser.ifCond(forTokenType: currentToken.type) 223 | match(tokenType: currentToken.type) 224 | expression() 225 | generator.startCond(type: condType) 226 | block() 227 | 228 | if case .elseKey = currentToken.type { 229 | match(tokenType: .elseKey) 230 | generator.elseCond() 231 | block() 232 | } 233 | 234 | match(tokenType: .end) 235 | generator.endCond() 236 | } 237 | 238 | /** 239 | * Parses a Bitsy LOOP...END statement 240 | */ 241 | func loop() { 242 | match(tokenType: .loop) 243 | generator.loopOpen() 244 | 245 | block() 246 | 247 | match(tokenType: .end) 248 | generator.loopEnd() 249 | } 250 | 251 | /** 252 | * Parses the Bitsy BREAK statement 253 | */ 254 | func doBreak() { 255 | match(tokenType: .breakKey) 256 | generator.breakLoop() 257 | } 258 | 259 | /** 260 | * Parses a Bitsy PRINT statement 261 | */ 262 | func doPrint() { 263 | match(tokenType: .print) 264 | expression() 265 | generator.print() 266 | } 267 | 268 | /** 269 | * Parses a Bitsy READ statement 270 | */ 271 | func read() { 272 | match(tokenType: .read) 273 | let varName = match(tokenType: .variable) 274 | generator.read(variableName: varName) 275 | } 276 | 277 | /** 278 | * Parses a Bitsy variable assignment 279 | */ 280 | func assignment() { 281 | let varName = match(tokenType: .variable) 282 | match(tokenType: .assignment) 283 | expression() 284 | generator.set(variableName: varName) 285 | } 286 | 287 | /** 288 | * Parses a Bitsy expression. An expression is one or more terms seperated 289 | * by addition or subtraction operators. 290 | */ 291 | func expression() { 292 | term() 293 | 294 | while currentToken.isAdditionOperator { 295 | generator.push() 296 | let op = Parser.codeOp(forTokenType: currentToken.type) 297 | match(tokenType: currentToken.type) 298 | term() 299 | generator.pop(andPerform: op) 300 | } 301 | } 302 | 303 | /** 304 | * Parses a Bitsy term. A term is a factor, preceeded by an optional sign (+/-), 305 | * followed by any number of addtional factors seperated by multiplication, subtraction, 306 | * or modulus operators. 307 | */ 308 | func term() { 309 | signedFactor() 310 | 311 | while currentToken.isMultiplicationOperator { 312 | generator.push() 313 | let op = Parser.codeOp(forTokenType: currentToken.type) 314 | match(tokenType: currentToken.type) 315 | factor() 316 | generator.pop(andPerform: op) 317 | } 318 | } 319 | 320 | /** 321 | * Parses a Bitsy factor prepended by an optional addition or subtraction 322 | * operator, indicating the signedness (positive or negative) of the factor 323 | */ 324 | func signedFactor() { 325 | var op: CodeGenOperation = .add 326 | 327 | if currentToken.isAdditionOperator { 328 | op = Parser.codeOp(forTokenType: currentToken.type) 329 | match(tokenType: currentToken.type) 330 | } 331 | 332 | factor() 333 | 334 | if op == .subtract { 335 | generator.negate() 336 | } 337 | } 338 | 339 | /** 340 | * Parses a Bitsy expression. An expression is an integer literal, a single 341 | * variable, or an expression enclosed in parens. 342 | */ 343 | func factor() { 344 | if case .integer = currentToken.type { 345 | let integer = match(tokenType: .integer) 346 | generator.load(integerValue: integer) 347 | } else if case .variable = currentToken.type { 348 | let varName = match(tokenType: .variable) 349 | generator.load(variableName: varName) 350 | } else { 351 | match(tokenType: .leftParen) 352 | expression() 353 | match(tokenType: .rightParen) 354 | } 355 | } 356 | } 357 | 358 | // MARK: Convenience Token Extensions 359 | 360 | private extension Token { 361 | 362 | /** Is this `Token` one which has no effect on the execution of the program? */ 363 | var isSkippable: Bool { return self.type == .whitespace || self.type == .comment } 364 | 365 | /** Is this `Token` one which denominates the end of block? */ 366 | var isBlockEnd: Bool { return self.type == .end || self.type == .elseKey } 367 | 368 | /** Is this `Token` a mathematical operator with addition precedence */ 369 | var isAdditionOperator: Bool { return self.type == .plus || self.type == .minus } 370 | 371 | /** Is this `Token` a mathematical operator with mutiplication precedence */ 372 | var isMultiplicationOperator: Bool { return self.type == .multiply || self.type == .divide || self.type == .modulus } 373 | } 374 | 375 | // MARK: Custom Pattern Matching 376 | 377 | /** 378 | * Is this case one of the Bitsy conditionals? 379 | */ 380 | private func isIf(type:TokenType) -> Bool { 381 | return type == .ifP || type == .ifZ || type == .ifN 382 | } 383 | 384 | /** 385 | * Enable custom pattern matching on `TokenType` cases 386 | */ 387 | private func ~=(pattern: (TokenType) -> (Bool), value: TokenType) -> Bool { 388 | return pattern(value) 389 | } 390 | -------------------------------------------------------------------------------- /bitsy-swift/SwiftGenerator.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MARK: CodeGenerator 4 | 5 | /** 6 | * A concrete CodeGenerator which emits Swift 7 | * 8 | * Swift is, perhaps, an odd choice as a compilation target for "real" compiler. 9 | * For the sake of learning its a good choice for a couple of reasons: 10 | * 11 | * 1. It removes the barrier of having to learn some other, lower level language 12 | * like assembly or LLVM-IR in order to understand what's going on 13 | * 2. It shares certain semantics with the source language directly, making the 14 | * generated code itself more straightforward to digest 15 | */ 16 | struct SwiftGenerator: CodeGenerator { 17 | 18 | let emitter: CodeEmitter 19 | 20 | init(emitter: CodeEmitter) { 21 | self.emitter = emitter 22 | } 23 | 24 | func header() { 25 | emitLine("// Compiler Output\n") 26 | emitLine("struct Variables {") 27 | emitLine("private var values: [String: Int] = [:]") 28 | emitLine("subscript(index: String) -> Int {") 29 | emitLine("get { guard let v = values[index] else { return 0 }; return v }") 30 | emitLine("set (newValue) { values[index] = newValue } } }") 31 | emitLine("var register: Int = 0") 32 | emitLine("var variables = Variables()") 33 | emitLine("var stack: [Int] = []") 34 | emitLine("func readIn() -> Int {") 35 | emitLine("if let input = readLine(), let intInput = Int(input) { return intInput") 36 | emitLine("} else { return 0 } }") 37 | emitLine() 38 | } 39 | 40 | func footer() { 41 | emitLine("\n// End Compiler Output") 42 | } 43 | 44 | func startCond(type: CodeGenCondition) { 45 | func ifCode(_ type: CodeGenCondition) -> String { 46 | switch type { 47 | case .positive: 48 | return ">" 49 | case .negative: 50 | return "<" 51 | case .zero: 52 | return "==" 53 | } 54 | } 55 | 56 | emitLine("if register \(ifCode(type)) 0 {") 57 | } 58 | 59 | func elseCond() { 60 | emitLine("} else { ") 61 | } 62 | 63 | func endCond() { 64 | emitLine("}") 65 | } 66 | 67 | func loopOpen() { 68 | emitLine("while true {") 69 | } 70 | 71 | func loopEnd() { 72 | emitLine("}") 73 | } 74 | 75 | func breakLoop() { 76 | emitLine("break") 77 | } 78 | 79 | func print() { 80 | emitLine("print(register)") 81 | } 82 | 83 | func read(variableName name: String) { 84 | emitLine("variables[\"\(name)\"] = readIn()") 85 | } 86 | 87 | func load(variableName name: String) { 88 | emitLine("register = variables[\"\(name)\"]") 89 | } 90 | 91 | func load(integerValue value: String) { 92 | emitLine("register = \(value)") 93 | } 94 | 95 | func set(variableName name: String) { 96 | emitLine("variables[\"\(name)\"] = register") 97 | } 98 | 99 | func push() { 100 | emitLine("stack.append(register)") 101 | } 102 | 103 | func pop(andPerform operation: CodeGenOperation) { 104 | func operationChar(forType type:CodeGenOperation) -> String { 105 | switch type { 106 | case .add: 107 | return "+" 108 | case .subtract: 109 | return "-" 110 | case .multiply: 111 | return "*" 112 | case .divide: 113 | return "/" 114 | case .modulus: 115 | return "%" 116 | } 117 | } 118 | 119 | emitLine("register = stack.removeLast() \(operationChar(forType: operation)) register") 120 | } 121 | 122 | func negate() { 123 | emitLine("register = -register") 124 | } 125 | } 126 | 127 | 128 | // MARK: IntermediateBuilder 129 | 130 | extension SwiftGenerator { 131 | 132 | var intermediateExtension: String { return ".swift" } 133 | 134 | func buildCommand(forFinalPath path: String) -> String { 135 | return "swiftc \(intermediatePath(forFinalPath: path)) -o \(path) -suppress-warnings" 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /bitsy-swift/Token.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | * A `Token` represents a discrete element in the Bitsy language extracted 5 | * from the source code by the `Tokenizer` 6 | */ 7 | protocol Token { 8 | 9 | /** 10 | * The kind of Bitsy language element represented by this Token 11 | */ 12 | var type: TokenType { get } 13 | 14 | /** 15 | * The sequence of Characters from the Bitsy source code this Token 16 | * is representing 17 | */ 18 | var value: String { get } 19 | } 20 | 21 | 22 | /** 23 | * A concrete `Token` representing a sequence of aribtrary whitespace characters 24 | */ 25 | struct Whitespace: Token { 26 | let type: TokenType = .whitespace 27 | let value: String 28 | } 29 | 30 | /** 31 | * A concrete `Token` representing a sequence of arbitrary characters in a comment block 32 | */ 33 | struct Comment: Token { 34 | let type: TokenType = .comment 35 | let value: String 36 | } 37 | 38 | /** 39 | * A concrete `Token` representing a sequence of digit characters 40 | */ 41 | struct Integer: Token { 42 | let type: TokenType = .integer 43 | let value: String 44 | } 45 | 46 | /** 47 | * A concrete `Token` representing a sequence of letters and underscores identifying a variable 48 | */ 49 | struct Variable: Token { 50 | let type: TokenType = .variable 51 | let value: String 52 | } 53 | 54 | /** 55 | * A concrete `Token` representing the start or close of an expression 56 | */ 57 | struct Paren: Token { 58 | let type: TokenType 59 | var value: String { return type.rawValue } 60 | 61 | init?(string: String) { 62 | guard let type = TokenType(rawValue: string) else { 63 | return nil 64 | } 65 | 66 | self.type = type 67 | } 68 | } 69 | 70 | /** 71 | * A concrete `Token` representing an assignment or mathematical operator 72 | */ 73 | struct Operator: Token { 74 | let type: TokenType 75 | var value: String { return type.rawValue } 76 | 77 | init?(char: Character) { 78 | guard let type = TokenType(rawValue: String(char)) else { 79 | return nil 80 | } 81 | 82 | self.type = type 83 | } 84 | } 85 | 86 | /** 87 | * A concrete `Token` representing a keyword identifier in the Bitsy language 88 | */ 89 | struct Keyword: Token { 90 | let type: TokenType 91 | var value: String { return type.rawValue } 92 | 93 | init?(string: String) { 94 | guard let type = TokenType(rawValue: string) else { 95 | return nil 96 | } 97 | 98 | self.type = type 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /bitsy-swift/TokenType.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | * Enumaration of all kinds of Tokens available 5 | * For Parens, Operators, and Keywords, each case is backed 6 | * by a rawValue of the String representing that symbol in Bitsy 7 | */ 8 | enum TokenType: String { 9 | // MARK: Seperators 10 | case whitespace 11 | case variable 12 | 13 | // MARK: Identifiers 14 | case integer 15 | case comment 16 | 17 | // MARK: Parens 18 | case leftParen = "(" 19 | case rightParen = ")" 20 | 21 | // MARK: Operators 22 | case plus = "+" 23 | case minus = "-" 24 | case multiply = "*" 25 | case divide = "/" 26 | case modulus = "%" 27 | case assignment = "=" 28 | 29 | // MARK: Keywords 30 | case begin = "BEGIN" 31 | case end = "END" 32 | case ifP = "IFP" 33 | case ifZ = "IFZ" 34 | case ifN = "IFN" 35 | case elseKey = "ELSE" 36 | case loop = "LOOP" 37 | case breakKey = "BREAK" 38 | case print = "PRINT" 39 | case read = "READ" 40 | } 41 | 42 | extension TokenType { 43 | /** 44 | * All `TokenType`s which are considered Bitsy operators 45 | */ 46 | static var operators: [TokenType] = [.plus, .minus, .multiply, .divide, .modulus, .assignment] 47 | } -------------------------------------------------------------------------------- /bitsy-swift/Tokenizer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | * Character representing the start of a comment block in Bitsy source 5 | */ 6 | private let CommentOpen = Character("{") 7 | 8 | /** 9 | * Character representing the end of a comment block in Bitsy source 10 | */ 11 | private let CommentClose = Character("}") 12 | 13 | /** 14 | * The `Tokenizer` breaks the raw stream of characters loaded 15 | * from the Bitsy source code into discrete elements of the Bitsy language, called `Token`s. 16 | * This process is sometimes referred to as lexical analaysis, or lexing. 17 | * 18 | * The `Tokenizer` consumes the stream of characters and exposes a stream of `Token`s, allowing 19 | * later stages of the compiler, specifically the `Parser`, to operate at a level above 20 | * individual characters. 21 | * 22 | * Take, for example, the following simple Bitsy program: 23 | * 24 | * BEGIN 25 | * PRINT 42 26 | * END 27 | * 28 | * The `Tokenizer`, having been initialized with this stream of input, should produce the 29 | * following stream of `Token`s: 30 | * 31 | * -------------- ---------------- -------------- 32 | * |.begin/"BEGIN"| -> |.whitespace/"\n"| -> |.print/"PRINT"| 33 | * -------------- ---------------- -------------- 34 | * 35 | * ---------------- ------------- --------------- 36 | * -> |.whitespace/"\t"| -> |.integer/"42"| -> |.whitespace/" "| 37 | * ---------------- ------------- --------------- 38 | * 39 | * ---------- 40 | * -> |.end/"END"| 41 | * ---------- 42 | * 43 | */ 44 | class Tokenizer { 45 | fileprivate var codeStream: CharStream 46 | fileprivate(set) internal var current: Token = Variable(value: "placeholder") 47 | 48 | /** 49 | * Are there additional `Token`s beyond `current`? 50 | */ 51 | var hasMore: Bool { return codeStream.hasMore } 52 | 53 | /** 54 | * Create a `Tokenizer` instance 55 | * 56 | * - parameter code: The `CharStream` representing the Bitsy source to be Tokenized 57 | */ 58 | init(code: CharStream) { 59 | codeStream = code 60 | advance() 61 | } 62 | 63 | /** 64 | * Move to the next `Token` in this stream 65 | */ 66 | func advance() { 67 | current = takeNext() 68 | } 69 | } 70 | 71 | private extension Tokenizer { 72 | 73 | /** 74 | * This method is the meat of the tokenization process. The switch statement uses the 75 | * current character to predict the kind of `Token` that will be constructed. It takes all 76 | * subsequent characters which are legal in this class of `Token` and uses them to create 77 | * a the appropriate `Token`. 78 | * 79 | * In many instances the, the type of Token can be inferred directly from the current character. 80 | * In the case of identifiers, the failable `Keyword` initializer is used to distinguish between 81 | * a keyword and a variable. 82 | * 83 | * This method will also cease compilation if an unexpected or illegal character is encountered, 84 | * but it does nothing to analyze the 'correctness' or the intention of the source it Tokenizes. 85 | * (This is the job of the `Parser`). For example, the `Tokenizer` will happily process nonsensical 86 | * Bitsy sources, such as 87 | * 88 | * END 89 | * ELSE 100 90 | * IFZ true 91 | * LOOP LOOP LOOP {!} 92 | * BEGIN 93 | * 94 | */ 95 | func takeNext() -> Token { 96 | switch codeStream.current { 97 | case isWhitespace: 98 | return Whitespace(value: take(matching: isWhitespace)) 99 | case isNumber: 100 | return Integer(value: take(matching: isNumber)) 101 | case isParen: 102 | let parenChar = takeOne() 103 | guard let parenToken = Paren(string: parenChar) else { 104 | fatalError("Unexpected Paren String: \(parenChar)") 105 | } 106 | 107 | return parenToken 108 | case isIdentifier: 109 | let ident = take(matching: isIdentifier) 110 | if let key = Keyword(string: ident) { 111 | return key 112 | } else { 113 | return Variable(value: ident) 114 | } 115 | case isOperator: 116 | let opString = take(matching: isOperator) 117 | guard let opChar = opString.first, let opToken = Operator(char: opChar) , opString.count == 1 else { 118 | fatalError("Illegal Operator: \(opString)") 119 | } 120 | 121 | return opToken 122 | case CommentOpen: 123 | return takeComment() 124 | default: 125 | fatalError("Illegal Character: \"\(codeStream.current)\"") 126 | } 127 | } 128 | 129 | /** 130 | * Advance the code stream by one character and return it as a String 131 | */ 132 | func takeOne() -> String { 133 | let charString = String(codeStream.current) 134 | codeStream.advance() 135 | return charString 136 | } 137 | 138 | /** 139 | * Advance the code stream past all characters which match a given definition, 140 | * and return them concatenated as a String. 141 | * 142 | * - parameter matching: A function which defines which characters "match" 143 | */ 144 | func take(matching matches:(Character) -> Bool) -> String { 145 | var taken = "" 146 | 147 | while matches(codeStream.current) { 148 | taken += takeOne() 149 | } 150 | 151 | return taken 152 | } 153 | 154 | /** 155 | * Advance the code stream past the opening comment character and all subsequent characters 156 | * which are a part of the comment, including the closing comment character, returning a 157 | * `Comment` `Token` 158 | */ 159 | func takeComment() -> Token { 160 | let openChar = takeOne() 161 | guard openChar == String(CommentOpen) else { 162 | fatalError("Error processing comment, received: \(openChar) when expecting \(CommentOpen)") 163 | } 164 | 165 | var commentText = "" 166 | 167 | while codeStream.current != CommentClose { 168 | commentText += takeOne() 169 | } 170 | 171 | let _ = takeOne() // CommentClose 172 | 173 | return Comment(value: commentText) 174 | } 175 | } 176 | 177 | /** 178 | * Returns true if `char` represents a whitespace in Bitsy 179 | */ 180 | private func isWhitespace(_ char: Character) -> Bool { 181 | return char == "\n" || char == "\t" || char == " " 182 | } 183 | 184 | /** 185 | * Returns true if `char` is a digit, '0' through '9' 186 | */ 187 | private func isNumber(_ char: Character) -> Bool { 188 | switch char { 189 | case "0"..."9": 190 | return true 191 | default: 192 | return false 193 | } 194 | } 195 | 196 | /** 197 | * Returns true if `char` is an open or closed paren character in Bitsy 198 | */ 199 | private func isParen(_ char: Character) -> Bool { 200 | let stringChar = String(char) 201 | return stringChar == TokenType.leftParen.rawValue || 202 | stringChar == TokenType.rightParen.rawValue 203 | } 204 | 205 | /** 206 | * Returns true if `char` is a valid operator character in Bitsy 207 | */ 208 | private func isOperator(_ char: Character) -> Bool { 209 | return TokenType.operators.map { op in 210 | return String(char) == op.rawValue 211 | }.reduce(false) { acc, doesMatch in 212 | return acc || doesMatch 213 | } 214 | } 215 | 216 | /** 217 | * Returns true if `char` is a valid identifier character in Bitsy, 218 | * that is, one used for keywords and variable names 219 | */ 220 | private func isIdentifier(_ char: Character) -> Bool { 221 | switch char { 222 | case "a"..."z": 223 | return true 224 | case "A"..."Z": 225 | return true 226 | case "_": 227 | return true 228 | default: 229 | return false 230 | } 231 | } 232 | 233 | /** 234 | * Allow pattern matching on `Character`s 235 | */ 236 | private func ~=(pattern: (Character) -> (Bool), value: Character) -> Bool { 237 | return pattern(value) 238 | } 239 | -------------------------------------------------------------------------------- /bitsy-swift/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | let BitsyVersion = "0.1.0" 4 | let config = ArgumentConfig(version: BitsyVersion) 5 | 6 | let reader = config.reader 7 | let generator = config.generator 8 | 9 | let tokens = Tokenizer(code: reader.readCode()) 10 | let parser = Parser(tokens: tokens, generator: generator) 11 | 12 | parser.parse() 13 | generator.finalize() 14 | -------------------------------------------------------------------------------- /bitsy-swift/vendor/CommandLine/CommandLine.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * CommandLine.swift 3 | * Copyright (c) 2014 Ben Gollmer. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import Foundation 19 | /* Required for setlocale(3) */ 20 | #if os(OSX) 21 | import Darwin 22 | #elseif os(Linux) 23 | import Glibc 24 | #endif 25 | 26 | let ShortOptionPrefix = "-" 27 | let LongOptionPrefix = "--" 28 | 29 | /* Stop parsing arguments when an ArgumentStopper (--) is detected. This is a GNU getopt 30 | * convention; cf. https://www.gnu.org/prep/standards/html_node/Command_002dLine-Interfaces.html 31 | */ 32 | let ArgumentStopper = "--" 33 | 34 | /* Allow arguments to be attached to flags when separated by this character. 35 | * --flag=argument is equivalent to --flag argument 36 | */ 37 | let ArgumentAttacher: Character = "=" 38 | 39 | /* An output stream to stderr; used by CommandLine.printUsage(). */ 40 | private struct StderrOutputStream: TextOutputStream { 41 | static let stream = StderrOutputStream() 42 | func write(_ s: String) { 43 | fputs(s, stderr) 44 | } 45 | } 46 | 47 | /** 48 | * The CommandLine class implements a command-line interface for your app. 49 | * 50 | * To use it, define one or more Options (see Option.swift) and add them to your 51 | * CommandLine object, then invoke `parse()`. Each Option object will be populated with 52 | * the value given by the user. 53 | * 54 | * If any required options are missing or if an invalid value is found, `parse()` will throw 55 | * a `ParseError`. You can then call `printUsage()` to output an automatically-generated usage 56 | * message. 57 | */ 58 | public class CommandLine { 59 | private var _arguments: [String] 60 | private var _options: [Option] = [Option]() 61 | private var _maxFlagDescriptionWidth: Int = 0 62 | private var _usedFlags: Set { 63 | var usedFlags = Set(minimumCapacity: _options.count * 2) 64 | 65 | for option in _options { 66 | for case let flag? in [option.shortFlag, option.longFlag] { 67 | usedFlags.insert(flag) 68 | } 69 | } 70 | 71 | return usedFlags 72 | } 73 | 74 | /** 75 | * After calling `parse()`, this property will contain any values that weren't captured 76 | * by an Option. For example: 77 | * 78 | * ``` 79 | * let cli = CommandLine() 80 | * let fileType = StringOption(shortFlag: "t", longFlag: "type", required: true, helpMessage: "Type of file") 81 | * 82 | * do { 83 | * try cli.parse() 84 | * print("File type is \(type), files are \(cli.unparsedArguments)") 85 | * catch { 86 | * cli.printUsage(error) 87 | * exit(EX_USAGE) 88 | * } 89 | * 90 | * --- 91 | * 92 | * $ ./readfiles --type=pdf ~/file1.pdf ~/file2.pdf 93 | * File type is pdf, files are ["~/file1.pdf", "~/file2.pdf"] 94 | * ``` 95 | */ 96 | public private(set) var unparsedArguments: [String] = [String]() 97 | 98 | /** 99 | * If supplied, this function will be called when printing usage messages. 100 | * 101 | * You can use the `defaultFormat` function to get the normally-formatted 102 | * output, either before or after modifying the provided string. For example: 103 | * 104 | * ``` 105 | * let cli = CommandLine() 106 | * cli.formatOutput = { str, type in 107 | * switch(type) { 108 | * case .Error: 109 | * // Make errors shouty 110 | * return defaultFormat(str.uppercaseString, type: type) 111 | * case .OptionHelp: 112 | * // Don't use the default indenting 113 | * return ">> \(s)\n" 114 | * default: 115 | * return defaultFormat(str, type: type) 116 | * } 117 | * } 118 | * ``` 119 | * 120 | * - note: Newlines are not appended to the result of this function. If you don't use 121 | * `defaultFormat()`, be sure to add them before returning. 122 | */ 123 | public var formatOutput: ((String, OutputType) -> String)? 124 | 125 | /** 126 | * The maximum width of all options' `flagDescription` properties; provided for use by 127 | * output formatters. 128 | * 129 | * - seealso: `defaultFormat`, `formatOutput` 130 | */ 131 | public var maxFlagDescriptionWidth: Int { 132 | if _maxFlagDescriptionWidth == 0 { 133 | _maxFlagDescriptionWidth = _options.map { $0.flagDescription.count }.sorted().first ?? 0 134 | } 135 | 136 | return _maxFlagDescriptionWidth 137 | } 138 | 139 | /** 140 | * The type of output being supplied to an output formatter. 141 | * 142 | * - seealso: `formatOutput` 143 | */ 144 | public enum OutputType { 145 | /** About text: `Usage: command-example [options]` and the like */ 146 | case About 147 | 148 | /** An error message: `Missing required option --extract` */ 149 | case Error 150 | 151 | /** An Option's `flagDescription`: `-h, --help:` */ 152 | case OptionFlag 153 | 154 | /** An Option's help message */ 155 | case OptionHelp 156 | } 157 | 158 | /** A ParseError is thrown if the `parse()` method fails. */ 159 | public enum ParseError: Error, CustomStringConvertible { 160 | /** Thrown if an unrecognized argument is passed to `parse()` in strict mode */ 161 | case InvalidArgument(String) 162 | 163 | /** Thrown if the value for an Option is invalid (e.g. a string is passed to an IntOption) */ 164 | case InvalidValueForOption(Option, [String]) 165 | 166 | /** Thrown if an Option with required: true is missing */ 167 | case MissingRequiredOptions([Option]) 168 | 169 | public var description: String { 170 | switch self { 171 | case let .InvalidArgument(arg): 172 | return "Invalid argument: \(arg)" 173 | case let .InvalidValueForOption(opt, vals): 174 | let vs = vals.joined(separator: ", ") 175 | return "Invalid value(s) for option \(opt.flagDescription): \(vs)" 176 | case let .MissingRequiredOptions(opts): 177 | return "Missing required options: \(opts.map { return $0.flagDescription })" 178 | } 179 | } 180 | } 181 | 182 | /** 183 | * Initializes a CommandLine object. 184 | * 185 | * - parameter arguments: Arguments to parse. If omitted, the arguments passed to the app 186 | * on the command line will automatically be used. 187 | * 188 | * - returns: An initalized CommandLine object. 189 | */ 190 | public init(arguments: [String] = ProcessInfo.processInfo.arguments) { 191 | self._arguments = arguments 192 | 193 | /* Initialize locale settings from the environment */ 194 | setlocale(LC_ALL, "") 195 | } 196 | 197 | /* Returns all argument values from flagIndex to the next flag or the end of the argument array. */ 198 | private func _getFlagValues(_ flagIndex: Int, _ attachedArg: String? = nil) -> [String] { 199 | var args: [String] = [String]() 200 | var skipFlagChecks = false 201 | 202 | if let a = attachedArg { 203 | args.append(a) 204 | } 205 | 206 | for i in flagIndex + 1 ..< _arguments.count { 207 | if !skipFlagChecks { 208 | if _arguments[i] == ArgumentStopper { 209 | skipFlagChecks = true 210 | continue 211 | } 212 | 213 | if _arguments[i].hasPrefix(ShortOptionPrefix) && Int(_arguments[i]) == nil && 214 | _arguments[i].toDouble() == nil { 215 | break 216 | } 217 | } 218 | 219 | args.append(_arguments[i]) 220 | } 221 | 222 | return args 223 | } 224 | 225 | /** 226 | * Adds an Option to the command line. 227 | * 228 | * - parameter option: The option to add. 229 | */ 230 | public func addOption(_ option: Option) { 231 | let uf = _usedFlags 232 | for case let flag? in [option.shortFlag, option.longFlag] { 233 | assert(!uf.contains(flag), "Flag '\(flag)' already in use") 234 | } 235 | 236 | _options.append(option) 237 | _maxFlagDescriptionWidth = 0 238 | } 239 | 240 | /** 241 | * Adds one or more Options to the command line. 242 | * 243 | * - parameter options: An array containing the options to add. 244 | */ 245 | public func addOptions(_ options: [Option]) { 246 | for o in options { 247 | addOption(o) 248 | } 249 | } 250 | 251 | /** 252 | * Adds one or more Options to the command line. 253 | * 254 | * - parameter options: The options to add. 255 | */ 256 | public func addOptions(_ options: Option...) { 257 | for o in options { 258 | addOption(o) 259 | } 260 | } 261 | 262 | /** 263 | * Sets the command line Options. Any existing options will be overwritten. 264 | * 265 | * - parameter options: An array containing the options to set. 266 | */ 267 | public func setOptions(_ options: [Option]) { 268 | _options = [Option]() 269 | addOptions(options) 270 | } 271 | 272 | /** 273 | * Sets the command line Options. Any existing options will be overwritten. 274 | * 275 | * - parameter options: The options to set. 276 | */ 277 | public func setOptions(_ options: Option...) { 278 | _options = [Option]() 279 | addOptions(options) 280 | } 281 | 282 | /** 283 | * Parses command-line arguments into their matching Option values. 284 | * 285 | * - parameter strict: Fail if any unrecognized flags are present (default: false). 286 | * 287 | * - throws: A `ParseError` if argument parsing fails: 288 | * - `.InvalidArgument` if an unrecognized flag is present and `strict` is true 289 | * - `.InvalidValueForOption` if the value supplied to an option is not valid (for 290 | * example, a string is supplied for an IntOption) 291 | * - `.MissingRequiredOptions` if a required option isn't present 292 | */ 293 | public func parse(strict: Bool = false) throws { 294 | var strays = _arguments 295 | 296 | /* Nuke executable name */ 297 | strays[0] = "" 298 | 299 | let argumentsEnumerator = _arguments.enumerated() 300 | 301 | for (idx, arg) in argumentsEnumerator { 302 | if arg == ArgumentStopper { 303 | break 304 | } 305 | 306 | if !arg.hasPrefix(ShortOptionPrefix) { 307 | continue 308 | } 309 | 310 | let skipChars = arg.hasPrefix(LongOptionPrefix) ? 311 | LongOptionPrefix.count : ShortOptionPrefix.count 312 | 313 | let flagWithArg = arg[arg.index(arg.startIndex, offsetBy: skipChars).. 352 | */ 353 | let vals = (i == flagLength - 1) ? self._getFlagValues(idx, attachedArg) : [String]() 354 | guard option.setValue(vals) else { 355 | throw ParseError.InvalidValueForOption(option, vals) 356 | } 357 | 358 | var claimedIdx = idx + option.claimedValues 359 | if attachedArg != nil { claimedIdx -= 1 } 360 | for i in idx...claimedIdx { 361 | strays[i] = "" 362 | } 363 | 364 | flagMatched = true 365 | break 366 | } 367 | } 368 | } 369 | 370 | /* Invalid flag */ 371 | guard !strict || flagMatched else { 372 | throw ParseError.InvalidArgument(arg) 373 | } 374 | } 375 | 376 | /* Check to see if any required options were not matched */ 377 | let missingOptions = _options.filter { $0.required && !$0.wasSet } 378 | guard missingOptions.count == 0 else { 379 | throw ParseError.MissingRequiredOptions(missingOptions) 380 | } 381 | 382 | unparsedArguments = strays.filter { $0 != "" } 383 | } 384 | 385 | /** 386 | * Provides the default formatting of `printUsage()` output. 387 | * 388 | * - parameter s: The string to format. 389 | * - parameter type: Type of output. 390 | * 391 | * - returns: The formatted string. 392 | * - seealso: `formatOutput` 393 | */ 394 | public func defaultFormat(s: String, type: OutputType) -> String { 395 | switch type { 396 | case .About: 397 | return "\(s)\n" 398 | case .Error: 399 | return "\(s)\n\n" 400 | case .OptionFlag: 401 | return " \(s.padded(toWidth: maxFlagDescriptionWidth)):\n" 402 | case .OptionHelp: 403 | return " \(s)\n" 404 | } 405 | } 406 | 407 | /* printUsage() is generic for OutputStreamType because the Swift compiler crashes 408 | * on inout protocol function parameters in Xcode 7 beta 1 (rdar://21372694). 409 | */ 410 | 411 | /** 412 | * Prints a usage message. 413 | * 414 | * - parameter to: An OutputStreamType to write the error message to. 415 | */ 416 | public func printUsage(_ to: inout TargetStream) { 417 | /* Nil coalescing operator (??) doesn't work on closures :( */ 418 | let format = formatOutput != nil ? formatOutput! : defaultFormat 419 | 420 | let name = _arguments[0] 421 | print(format("Usage: \(name) [options]", .About), terminator: "", to: &to) 422 | 423 | for opt in _options { 424 | print(format(opt.flagDescription, .OptionFlag), terminator: "", to: &to) 425 | print(format(opt.helpMessage, .OptionHelp), terminator: "", to: &to) 426 | } 427 | } 428 | 429 | /** 430 | * Prints a usage message. 431 | * 432 | * - parameter error: An error thrown from `parse()`. A description of the error 433 | * (e.g. "Missing required option --extract") will be printed before the usage message. 434 | * - parameter to: An OutputStreamType to write the error message to. 435 | */ 436 | public func printUsage(_ error: Error, to: inout TargetStream) { 437 | let format = formatOutput != nil ? formatOutput! : defaultFormat 438 | print(format("\(error)", .Error), terminator: "", to: &to) 439 | printUsage(&to) 440 | } 441 | 442 | /** 443 | * Prints a usage message. 444 | * 445 | * - parameter error: An error thrown from `parse()`. A description of the error 446 | * (e.g. "Missing required option --extract") will be printed before the usage message. 447 | */ 448 | public func printUsage(_ error: Error) { 449 | var out = StderrOutputStream.stream 450 | printUsage(error, to: &out) 451 | } 452 | 453 | /** 454 | * Prints a usage message. 455 | */ 456 | public func printUsage() { 457 | var out = StderrOutputStream.stream 458 | printUsage(&out) 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /bitsy-swift/vendor/CommandLine/Option.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Option.swift 3 | * Copyright (c) 2014 Ben Gollmer. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /** 19 | * The base class for a command-line option. 20 | */ 21 | open class Option { 22 | public let shortFlag: String? 23 | public let longFlag: String? 24 | public let required: Bool 25 | public let helpMessage: String 26 | 27 | /** True if the option was set when parsing command-line arguments */ 28 | open var wasSet: Bool { 29 | return false 30 | } 31 | 32 | open var claimedValues: Int { return 0 } 33 | 34 | open var flagDescription: String { 35 | switch (shortFlag, longFlag) { 36 | case let (sf?, lf?): 37 | return "\(ShortOptionPrefix)\(sf), \(LongOptionPrefix)\(lf)" 38 | case (nil, let lf?): 39 | return "\(LongOptionPrefix)\(lf)" 40 | case (let sf?, nil): 41 | return "\(ShortOptionPrefix)\(sf)" 42 | default: 43 | return "" 44 | } 45 | } 46 | 47 | fileprivate init(_ shortFlag: String?, _ longFlag: String?, _ required: Bool, _ helpMessage: String) { 48 | if let sf = shortFlag { 49 | assert(sf.count == 1, "Short flag must be a single character") 50 | assert(Int(sf) == nil && sf.toDouble() == nil, "Short flag cannot be a numeric value") 51 | } 52 | 53 | if let lf = longFlag { 54 | assert(Int(lf) == nil && lf.toDouble() == nil, "Long flag cannot be a numeric value") 55 | } 56 | 57 | self.shortFlag = shortFlag 58 | self.longFlag = longFlag 59 | self.helpMessage = helpMessage 60 | self.required = required 61 | } 62 | 63 | /* The optional casts in these initalizers force them to call the private initializer. Without 64 | * the casts, they recursively call themselves. 65 | */ 66 | 67 | /** Initializes a new Option that has both long and short flags. */ 68 | public convenience init(shortFlag: String, longFlag: String, required: Bool = false, helpMessage: String) { 69 | self.init(shortFlag as String?, longFlag, required, helpMessage) 70 | } 71 | 72 | /** Initializes a new Option that has only a short flag. */ 73 | public convenience init(shortFlag: String, required: Bool = false, helpMessage: String) { 74 | self.init(shortFlag as String?, nil, required, helpMessage) 75 | } 76 | 77 | /** Initializes a new Option that has only a long flag. */ 78 | public convenience init(longFlag: String, required: Bool = false, helpMessage: String) { 79 | self.init(nil, longFlag as String?, required, helpMessage) 80 | } 81 | 82 | func flagMatch(_ flag: String) -> Bool { 83 | return flag == shortFlag || flag == longFlag 84 | } 85 | 86 | func setValue(_ values: [String]) -> Bool { 87 | return false 88 | } 89 | } 90 | 91 | /** 92 | * A boolean option. The presence of either the short or long flag will set the value to true; 93 | * absence of the flag(s) is equivalent to false. 94 | */ 95 | open class BoolOption: Option { 96 | fileprivate var _value: Bool = false 97 | 98 | open var value: Bool { 99 | return _value 100 | } 101 | 102 | override open var wasSet: Bool { 103 | return _value 104 | } 105 | 106 | override func setValue(_ values: [String]) -> Bool { 107 | _value = true 108 | return true 109 | } 110 | } 111 | 112 | /** An option that accepts a positive or negative integer value. */ 113 | open class IntOption: Option { 114 | fileprivate var _value: Int? 115 | 116 | open var value: Int? { 117 | return _value 118 | } 119 | 120 | override open var wasSet: Bool { 121 | return _value != nil 122 | } 123 | 124 | override open var claimedValues: Int { 125 | return _value != nil ? 1 : 0 126 | } 127 | 128 | override func setValue(_ values: [String]) -> Bool { 129 | if values.count == 0 { 130 | return false 131 | } 132 | 133 | if let val = Int(values[0]) { 134 | _value = val 135 | return true 136 | } 137 | 138 | return false 139 | } 140 | } 141 | 142 | /** 143 | * An option that represents an integer counter. Each time the short or long flag is found 144 | * on the command-line, the counter will be incremented. 145 | */ 146 | open class CounterOption: Option { 147 | fileprivate var _value: Int = 0 148 | 149 | open var value: Int { 150 | return _value 151 | } 152 | 153 | override open var wasSet: Bool { 154 | return _value > 0 155 | } 156 | 157 | open func reset() { 158 | _value = 0 159 | } 160 | 161 | override func setValue(_ values: [String]) -> Bool { 162 | _value += 1 163 | return true 164 | } 165 | } 166 | 167 | /** An option that accepts a positive or negative floating-point value. */ 168 | open class DoubleOption: Option { 169 | fileprivate var _value: Double? 170 | 171 | open var value: Double? { 172 | return _value 173 | } 174 | 175 | override open var wasSet: Bool { 176 | return _value != nil 177 | } 178 | 179 | override open var claimedValues: Int { 180 | return _value != nil ? 1 : 0 181 | } 182 | 183 | override func setValue(_ values: [String]) -> Bool { 184 | if values.count == 0 { 185 | return false 186 | } 187 | 188 | if let val = values[0].toDouble() { 189 | _value = val 190 | return true 191 | } 192 | 193 | return false 194 | } 195 | } 196 | 197 | /** An option that accepts a string value. */ 198 | open class StringOption: Option { 199 | fileprivate var _value: String? = nil 200 | 201 | open var value: String? { 202 | return _value 203 | } 204 | 205 | override open var wasSet: Bool { 206 | return _value != nil 207 | } 208 | 209 | override open var claimedValues: Int { 210 | return _value != nil ? 1 : 0 211 | } 212 | 213 | override func setValue(_ values: [String]) -> Bool { 214 | if values.count == 0 { 215 | return false 216 | } 217 | 218 | _value = values[0] 219 | return true 220 | } 221 | } 222 | 223 | /** An option that accepts one or more string values. */ 224 | open class MultiStringOption: Option { 225 | fileprivate var _value: [String]? 226 | 227 | open var value: [String]? { 228 | return _value 229 | } 230 | 231 | override open var wasSet: Bool { 232 | return _value != nil 233 | } 234 | 235 | override open var claimedValues: Int { 236 | if let v = _value { 237 | return v.count 238 | } 239 | 240 | return 0 241 | } 242 | 243 | override func setValue(_ values: [String]) -> Bool { 244 | if values.count == 0 { 245 | return false 246 | } 247 | 248 | _value = values 249 | return true 250 | } 251 | } 252 | 253 | /** An option that represents an enum value. */ 254 | open class EnumOption: Option where T.RawValue == String { 255 | fileprivate var _value: T? 256 | open var value: T? { 257 | return _value 258 | } 259 | 260 | override open var wasSet: Bool { 261 | return _value != nil 262 | } 263 | 264 | override open var claimedValues: Int { 265 | return _value != nil ? 1 : 0 266 | } 267 | 268 | /* Re-defining the intializers is necessary to make the Swift 2 compiler happy, as 269 | * of Xcode 7 beta 2. 270 | */ 271 | 272 | fileprivate override init(_ shortFlag: String?, _ longFlag: String?, _ required: Bool, _ helpMessage: String) { 273 | super.init(shortFlag, longFlag, required, helpMessage) 274 | } 275 | 276 | /** Initializes a new Option that has both long and short flags. */ 277 | public convenience init(shortFlag: String, longFlag: String, required: Bool = false, helpMessage: String) { 278 | self.init(shortFlag as String?, longFlag, required, helpMessage) 279 | } 280 | 281 | /** Initializes a new Option that has only a short flag. */ 282 | public convenience init(shortFlag: String, required: Bool = false, helpMessage: String) { 283 | self.init(shortFlag as String?, nil, required, helpMessage) 284 | } 285 | 286 | /** Initializes a new Option that has only a long flag. */ 287 | public convenience init(longFlag: String, required: Bool = false, helpMessage: String) { 288 | self.init(nil, longFlag as String?, required, helpMessage) 289 | } 290 | 291 | override func setValue(_ values: [String]) -> Bool { 292 | if values.count == 0 { 293 | return false 294 | } 295 | 296 | if let v = T(rawValue: values[0]) { 297 | _value = v 298 | return true 299 | } 300 | 301 | return false 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /bitsy-swift/vendor/CommandLine/StringExtensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * StringExtensions.swift 3 | * Copyright (c) 2014 Ben Gollmer. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* Required for localeconv(3) */ 19 | #if os(OSX) 20 | import Darwin 21 | #elseif os(Linux) 22 | import Glibc 23 | #endif 24 | 25 | internal extension String { 26 | /* Retrieves locale-specified decimal separator from the environment 27 | * using localeconv(3). 28 | */ 29 | fileprivate func _localDecimalPoint() -> Character { 30 | let locale = localeconv() 31 | if locale != nil { 32 | if let decimalPoint = locale?.pointee.decimal_point { 33 | return Character(UnicodeScalar(UInt32(decimalPoint.pointee))!) 34 | } 35 | } 36 | 37 | return "." 38 | } 39 | 40 | /** 41 | * Attempts to parse the string value into a Double. 42 | * 43 | * - returns: A Double if the string can be parsed, nil otherwise. 44 | */ 45 | func toDouble() -> Double? { 46 | var characteristic: String = "0" 47 | var mantissa: String = "0" 48 | var inMantissa: Bool = false 49 | var isNegative: Bool = false 50 | let decimalPoint = self._localDecimalPoint() 51 | 52 | let charactersEnumerator = self.enumerated() 53 | 54 | for (i, c) in charactersEnumerator { 55 | if i == 0 && c == "-" { 56 | isNegative = true 57 | continue 58 | } 59 | 60 | if c == decimalPoint { 61 | inMantissa = true 62 | continue 63 | } 64 | 65 | if Int(String(c)) != nil { 66 | if !inMantissa { 67 | characteristic.append(c) 68 | } else { 69 | mantissa.append(c) 70 | } 71 | } else { 72 | /* Non-numeric character found, bail */ 73 | return nil 74 | } 75 | } 76 | 77 | return (Double(Int(characteristic)!) + 78 | Double(Int(mantissa)!) / pow(Double(10), Double(mantissa.count - 1))) * 79 | (isNegative ? -1 : 1) 80 | } 81 | 82 | /** 83 | * Splits a string into an array of string components. 84 | * 85 | * - parameter by: The character to split on. 86 | * - parameter maxSplits: The maximum number of splits to perform. If 0, all possible splits are made. 87 | * 88 | * - returns: An array of string components. 89 | */ 90 | func split(by: Character, maxSplits: Int = 0) -> [String] { 91 | var s = [String]() 92 | var numSplits = 0 93 | 94 | var curIdx = self.startIndex 95 | for i in self.indices { 96 | let c = self[i] 97 | if c == by && (maxSplits == 0 || numSplits < maxSplits) { 98 | s.append(String(self[curIdx.. String { 120 | var s = self 121 | var currentLength = self.count 122 | 123 | while currentLength < width { 124 | s.append(padChar) 125 | currentLength += 1 126 | } 127 | 128 | return s 129 | } 130 | 131 | /** 132 | * Wraps a string to the specified width. 133 | * 134 | * This just does simple greedy word-packing, it doesn't go full Knuth-Plass. 135 | * If a single word is longer than the line width, it will be placed (unsplit) 136 | * on a line by itself. 137 | * 138 | * - parameter atWidth: The maximum length of a line. 139 | * - parameter wrapBy: The line break character to use. 140 | * - parameter splitBy: The character to use when splitting the string into words. 141 | * 142 | * - returns: A new string, wrapped at the given width. 143 | */ 144 | func wrapped(atWidth width: Int, wrapBy: Character = "\n", splitBy: Character = " ") -> String { 145 | var s = "" 146 | var currentLineWidth = 0 147 | 148 | for word in self.split(by: splitBy) { 149 | let wordLength = word.count 150 | 151 | if currentLineWidth + wordLength + 1 > width { 152 | /* Word length is greater than line length, can't wrap */ 153 | if wordLength >= width { 154 | s += word 155 | } 156 | 157 | s.append(wrapBy) 158 | currentLineWidth = 0 159 | } 160 | 161 | currentLineWidth += wordLength + 1 162 | s += word 163 | s.append(splitBy) 164 | } 165 | 166 | return s 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /bitsy-swift/vendor/Exec.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // https://gist.github.com/lmedinas/7963ac1985dba4dc60b5 4 | 5 | func exec(_ cmdname: String) -> String { 6 | var outstr = "" 7 | let task = Process() 8 | task.launchPath = "/bin/sh" 9 | task.arguments = ["-c", cmdname] 10 | 11 | let pipe = Pipe() 12 | task.standardOutput = pipe 13 | task.launch() 14 | 15 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 16 | if let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue) { 17 | outstr = output as String 18 | } 19 | 20 | task.waitUntilExit() 21 | let status = task.terminationStatus 22 | 23 | if status != 0 { 24 | print(status) 25 | } 26 | 27 | return outstr 28 | } 29 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | BUILDCMD=xcodebuild 4 | BUILDPATH=build/Release 5 | BINNAME=bitsy-swift 6 | BINDIR=bin 7 | 8 | $BUILDCMD 9 | 10 | if [ ! -d "$BINDIR" ]; then 11 | mkdir "$BINDIR" 12 | fi 13 | 14 | cp "$BUILDPATH/$BINNAME" "$BINDIR/" 15 | -------------------------------------------------------------------------------- /runbitsy: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$#" -ne 1 ]; then 4 | echo "usage: $0 path" >&2 5 | echo " compile and immediately run the .bitsy file at path" >&2 6 | exit 1 7 | fi 8 | 9 | BITSYPATH=bin 10 | BITSYNAME=bitsy-swift 11 | RUNARG="--run-delete" 12 | 13 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" 14 | BITSYBIN=$SCRIPTPATH/$BITSYPATH/$BITSYNAME 15 | 16 | if [ ! -f "$BITSYBIN" ]; then 17 | echo "[ERROR] $BITSYNAME has not been built" >&2 18 | echo "-> Run build.sh in $SCRIPTPATH" >&2 19 | exit 1 20 | fi 21 | 22 | $BITSYBIN $1 $RUNARG 23 | -------------------------------------------------------------------------------- /samples/collatz.bitsy: -------------------------------------------------------------------------------- 1 | { Print the Collatzy sequence for a given starting integer } 2 | BEGIN 3 | collatzOf = 7 4 | 5 | IFP collatzOf 6 | n = collatzOf 7 | 8 | LOOP 9 | IFZ n - 1 10 | BREAK 11 | END 12 | 13 | IFZ n % 2 14 | n = n / 2 15 | ELSE 16 | n = 3*n + 1 17 | END 18 | 19 | PRINT n 20 | END 21 | END 22 | END 23 | -------------------------------------------------------------------------------- /samples/factorial.bitsy: -------------------------------------------------------------------------------- 1 | { Calculate the factorial of a postive integer } 2 | BEGIN 3 | factorial_of = 10 4 | 5 | counter = factorial_of 6 | accumulator = 1 7 | 8 | LOOP 9 | IFZ counter 10 | BREAK 11 | END 12 | 13 | accumulator = accumulator * counter 14 | counter = counter - 1 15 | END 16 | 17 | PRINT accumulator 18 | END 19 | -------------------------------------------------------------------------------- /samples/fibonacci.bitsy: -------------------------------------------------------------------------------- 1 | { Description: Calculate the first 10 numbers in the fibonacci sequence } 2 | BEGIN 3 | fibCount = 10 4 | thisFib = 1 5 | 6 | LOOP 7 | PRINT lastFib 8 | fibCount = fibCount - 1 9 | 10 | IFP fibCount 11 | nextFib = thisFib + lastFib 12 | 13 | lastFib = thisFib 14 | thisFib = nextFib 15 | ELSE 16 | BREAK 17 | END 18 | END 19 | END 20 | -------------------------------------------------------------------------------- /samples/gcd.bitsy: -------------------------------------------------------------------------------- 1 | { Calculate the greatest common denominator between two postive integers } 2 | BEGIN 3 | intOne = 24 4 | intTwo = 54 5 | 6 | IFP intOne - intTwo 7 | counter = intOne 8 | ELSE 9 | counter = intTwo 10 | END 11 | 12 | LOOP 13 | IFZ intOne % counter {and} IFZ intTwo % counter 14 | PRINT counter 15 | BREAK 16 | END {/and} END 17 | 18 | counter = counter - 1 19 | END 20 | END 21 | --------------------------------------------------------------------------------