├── .gitignore ├── BitsyBASIC ├── AppDelegate.swift ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── BitsyBASIC_Swift-Info.plist ├── ConsoleViewController.swift ├── Images.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon_ipad.png │ │ ├── Icon_ipad@2x.png │ │ ├── Icon_iphone@2x.png │ │ ├── Icon_iphone@3x.png │ │ ├── Icon_settings.png │ │ ├── Icon_settings@2x-1.png │ │ ├── Icon_settings@2x.png │ │ ├── Icon_settings@3x.png │ │ ├── Icon_spotlight.png │ │ ├── Icon_spotlight@2x-1.png │ │ ├── Icon_spotlight@2x.png │ │ └── Icon_spotlight@3x.png │ └── ConsoleBackgroundPattern.imageset │ │ ├── ConsoleBackgroundPattern.png │ │ ├── ConsoleBackgroundPattern@2x.png │ │ ├── ConsoleBackgroundPattern@3x.png │ │ └── Contents.json ├── Info.plist ├── InterpreterThread.swift └── KeyboardNotification.swift ├── BitsyBASICIcon.sketch ├── BitsyBASICTests ├── BitsyBASICTests.swift └── Info.plist ├── LICENSE ├── README.md ├── bitsybasic.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── bitsybasic.xccheckout │ │ └── finchbasic.xccheckout └── xcshareddata │ └── xcschemes │ ├── BitsyBASIC.xcscheme │ ├── BitsyBASIC_Swift.xcscheme │ ├── finchbasic.xcscheme │ ├── finchlib.xcscheme │ ├── finchlib_Release.xcscheme │ └── finchlib_cpp.xcscheme ├── finchbasic └── main.swift ├── finchlib ├── Info.plist ├── Interpreter.swift ├── char.swift ├── finchlib.h ├── io.swift ├── parse.swift ├── pasteboard.swift ├── syntax.swift └── util.swift ├── finchlibTests ├── Info.plist └── finchlibTests.swift ├── finchlib_cpp ├── .clang-format ├── Info.plist ├── Interpreter.h ├── Interpreter.mm ├── InterpreterEngine.h ├── InterpreterEngine.mm ├── cppdefs.h ├── finchlib_cpp-Bridging-Header.h ├── finchlib_cpp.h ├── parse.h ├── parse.mm ├── pasteboard.h ├── pasteboard.mm ├── syntax.h └── syntax.mm ├── finchlib_cppTests └── Info.plist └── grammar.ebnf.txt /.gitignore: -------------------------------------------------------------------------------- 1 | CVS 2 | .#* 3 | 4 | .hg 5 | .hgignore 6 | 7 | .svn 8 | 9 | bin 10 | .bin 11 | Bin 12 | obj 13 | Debug 14 | Debug Dist 15 | Release 16 | Release Dist 17 | TestResults 18 | *.obj 19 | *.suo 20 | *.ncb 21 | *.aps 22 | *.user 23 | *.tli 24 | *.tlh 25 | *.idb 26 | *.pdb 27 | *.tlb 28 | *_i.h 29 | *_h.h 30 | *_i.c 31 | *_p.c 32 | *idl.h 33 | dlldata.c 34 | *ps.dll 35 | *ps.exp 36 | *ps.lib 37 | *.sdf 38 | *.opensdf 39 | ipch 40 | PrecompiledWeb 41 | 42 | build 43 | *.pbxuser 44 | *.perspectivev3 45 | .DS_Store 46 | xcuserdata 47 | *.sublime-project 48 | *.sublime-workspace 49 | 50 | .idea/workspace.xml 51 | .idea/tasks.xml 52 | 53 | *.old 54 | *.log 55 | *.out 56 | *.cache 57 | *.orig 58 | logs 59 | 60 | gen 61 | .metadata 62 | local.properties 63 | 64 | *~ 65 | 66 | TBout.txt 67 | _site 68 | .sass-cache 69 | 70 | -------------------------------------------------------------------------------- /BitsyBASIC/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2014, 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | import UIKit 25 | 26 | @UIApplicationMain 27 | final class AppDelegate: UIResponder, UIApplicationDelegate { 28 | 29 | var window: UIWindow? 30 | 31 | 32 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 33 | 34 | setWorkingDirectory() 35 | 36 | return true 37 | } 38 | 39 | func applicationWillResignActive(application: UIApplication) { 40 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 41 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 42 | } 43 | 44 | func applicationDidEnterBackground(application: UIApplication) { 45 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 46 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 47 | } 48 | 49 | func applicationWillEnterForeground(application: UIApplication) { 50 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 51 | } 52 | 53 | func applicationDidBecomeActive(application: UIApplication) { 54 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 55 | } 56 | 57 | func applicationWillTerminate(application: UIApplication) { 58 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 59 | } 60 | 61 | func application(application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool { 62 | return true 63 | } 64 | 65 | func application(application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool { 66 | return true 67 | } 68 | 69 | /// Set the working directory to be the user's Documents directory. 70 | func setWorkingDirectory() { 71 | let documentsPaths = NSSearchPathForDirectoriesInDomains( 72 | NSSearchPathDirectory.DocumentDirectory, 73 | NSSearchPathDomainMask.UserDomainMask, 74 | true) 75 | let documentsPath = documentsPaths[0] as! NSString 76 | chdir(documentsPath.UTF8String) 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /BitsyBASIC/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /BitsyBASIC/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 10 PRINT "Hello, world!" 31 | 20 END 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /BitsyBASIC/BitsyBASIC_Swift-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | net.kristopherjohnson.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /BitsyBASIC/ConsoleViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2014, 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | import Foundation 25 | import UIKit 26 | 27 | 28 | let ConsoleTextKey = "ConsoleText" 29 | let InputTextFieldTextKey = "InputTextFieldText" 30 | let InterpreterStateKey = "InterpreterState" 31 | 32 | 33 | final class ConsoleViewController: UIViewController, UITextFieldDelegate { 34 | 35 | /// Text field at bottom where user enters statements 36 | @IBOutlet weak var inputTextField: UITextField! 37 | 38 | /// Console view 39 | @IBOutlet weak var textView: UITextView! 40 | 41 | /// This constraint will be updated when the keyboard appears, disappears, 42 | /// or changes size. 43 | @IBOutlet weak var bottomLayoutConstraint: NSLayoutConstraint! 44 | 45 | /// NSAttributedString attributes used for output from the interpreter 46 | var outputAttributes: NSDictionary = [:] 47 | 48 | /// NSAttributedString attributes used for input from the user 49 | var inputAttributes: NSDictionary = [:] 50 | 51 | /// FinchBasic interpreter 52 | var interpreter: Interpreter! 53 | 54 | /// Delegate for interpreter 55 | var interpreterIO: ConsoleInterpreterIO! 56 | 57 | /// Set true if we have queued a call to `stepInterpreter()` 58 | var interpreterScheduled = false 59 | 60 | /// Set true if decodeRestorableStateWithCoder: was called 61 | var didRestoreState = false 62 | 63 | /// Text displayed in the console 64 | /// 65 | /// Setting this property automatically updates the console display 66 | /// and scrolls to the bottom. 67 | var consoleText: NSMutableAttributedString = NSMutableAttributedString(string: "") { 68 | didSet { 69 | if textView != nil { 70 | textView.attributedText = consoleText 71 | } 72 | } 73 | } 74 | 75 | override func encodeRestorableStateWithCoder(coder: NSCoder) { 76 | super.encodeRestorableStateWithCoder(coder) 77 | 78 | coder.encodeObject(consoleText, forKey: ConsoleTextKey) 79 | coder.encodeObject(inputTextField.text, forKey: InputTextFieldTextKey) 80 | coder.encodeObject(interpreter.stateAsPropertyList(), forKey: InterpreterStateKey) 81 | } 82 | 83 | override func decodeRestorableStateWithCoder(coder: NSCoder) { 84 | didRestoreState = true 85 | 86 | super.decodeRestorableStateWithCoder(coder) 87 | 88 | if let textViewAttributedText = coder.decodeObjectForKey(ConsoleTextKey) as? NSAttributedString { 89 | consoleText = NSMutableAttributedString(attributedString: textViewAttributedText) 90 | } 91 | else { 92 | assert(false, "unable to restore \(ConsoleTextKey)") 93 | } 94 | 95 | if let inputTextFieldText = coder.decodeObjectForKey(InputTextFieldTextKey) as? NSString { 96 | inputTextField.text = inputTextFieldText as String 97 | } 98 | else { 99 | assert(false, "unable to restore \(InputTextFieldTextKey)") 100 | } 101 | 102 | if let interpreterState = coder.decodeObjectForKey(InterpreterStateKey) as? NSDictionary { 103 | interpreter.restoreStateFromPropertyList(interpreterState as [NSObject : AnyObject]) 104 | } 105 | else { 106 | assert(false, "unable to restore \(InterpreterStateKey)") 107 | } 108 | } 109 | 110 | override func viewDidLoad() { 111 | super.viewDidLoad() 112 | 113 | if let consoleBackgroundPattern = UIImage(named: "ConsoleBackgroundPattern") { 114 | let patternColor = UIColor(patternImage: consoleBackgroundPattern) 115 | textView.backgroundColor = patternColor 116 | } 117 | 118 | outputAttributes = [ 119 | NSForegroundColorAttributeName: textView.textColor, 120 | NSFontAttributeName: textView.font, 121 | NSBackgroundColorAttributeName: UIColor.clearColor() 122 | ] 123 | 124 | inputAttributes = [ 125 | NSForegroundColorAttributeName: inputTextField.textColor, 126 | NSFontAttributeName: textView.font, 127 | NSBackgroundColorAttributeName: UIColor.clearColor() 128 | ] 129 | 130 | inputTextField.text = "" 131 | inputTextField.tintColor = UIColor.whiteColor() 132 | // inputTextField.attributedPlaceholder = NSAttributedString( 133 | // string: inputTextField.placeholder ?? "", 134 | // attributes: outputAttributes) 135 | inputTextField.delegate = self 136 | 137 | interpreterIO = ConsoleInterpreterIO(viewController: self) 138 | interpreter = Interpreter(interpreterIO: interpreterIO) 139 | scheduleInterpreter() 140 | } 141 | 142 | override func viewWillAppear(animated: Bool) { 143 | super.viewWillAppear(animated) 144 | 145 | // Call keyboardWillChangeFrameNotification() when keyboard shows/hides 146 | NSNotificationCenter.defaultCenter().addObserver(self, 147 | selector: "keyboardWillChangeFrameNotification:", 148 | name: UIKeyboardWillChangeFrameNotification, 149 | object: nil) 150 | 151 | // Scroll console text view to bottom whenever content size changes 152 | textView.addObserver(self, 153 | forKeyPath: "contentSize", 154 | options: NSKeyValueObservingOptions.New, 155 | context: nil) 156 | 157 | if !didRestoreState { 158 | consoleText = NSMutableAttributedString( 159 | string: "BitsyBASIC v1.0\nCopyright 2015 Kristopher Johnson\n\nType HELP if you don't know what to do.\n\nREADY\n", 160 | attributes: outputAttributes as [NSObject : AnyObject]) 161 | } 162 | } 163 | 164 | override func viewDidAppear(animated: Bool) { 165 | super.viewDidAppear(animated) 166 | inputTextField.becomeFirstResponder() 167 | } 168 | 169 | override func viewDidDisappear(animated: Bool) { 170 | NSNotificationCenter.defaultCenter().removeObserver(self) 171 | 172 | textView.removeObserver(self, forKeyPath: "contentSize") 173 | 174 | super.viewDidDisappear(animated) 175 | } 176 | 177 | override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer) { 178 | // textView's contentSize has changed 179 | scrollConsoleToBottom() 180 | } 181 | 182 | override func viewDidLayoutSubviews() { 183 | super.viewDidLayoutSubviews() 184 | scrollConsoleToBottom() 185 | } 186 | 187 | /// Update the text view so that the end of the text is at the bottom of the screen. 188 | /// 189 | /// Call this whenever views are laid out or when content size changes. 190 | /// 191 | /// If the content height is smaller than the view height, then contentOffset 192 | /// will be set to move the text to the bottom. Otherwise, will call 193 | /// textView.scrollRangeToVisible with the end of the text as the range. 194 | func scrollConsoleToBottom() { 195 | let viewHeight = textView.bounds.height 196 | let contentHeight = textView.contentSize.height 197 | 198 | textView.contentOffset = CGPointMake(0, contentHeight - viewHeight) 199 | } 200 | 201 | /// Handle appearance or disappearance of keyboard 202 | /// 203 | /// Updates bottom constraint so that inputTextField shows above the keyboard. 204 | func keyboardWillChangeFrameNotification(notification: NSNotification) { 205 | let change = KeyboardNotification(notification) 206 | let keyboardFrame = change.frameEndForView(self.view) 207 | let animationDuration = change.animationDuration 208 | let animationCurve = change.animationCurve 209 | 210 | let viewFrame = self.view.frame 211 | let newBottomOffset = viewFrame.maxY - keyboardFrame.minY + 8 212 | 213 | self.view.layoutIfNeeded() 214 | UIView.animateWithDuration(animationDuration, 215 | delay: 0, 216 | options: UIViewAnimationOptions(UInt(animationCurve << 16)), 217 | animations: { 218 | self.bottomLayoutConstraint.constant = newBottomOffset 219 | self.view.layoutIfNeeded() 220 | }, 221 | completion: nil 222 | ) 223 | } 224 | 225 | /// Append given text to the console display 226 | func appendToConsoleText(s: NSString, attributes: NSDictionary) { 227 | let attributedString = NSMutableAttributedString(string: s as String, attributes: attributes as [NSObject : AnyObject]) 228 | let newConsoleText = NSMutableAttributedString(attributedString: consoleText) 229 | newConsoleText.appendAttributedString(attributedString) 230 | consoleText = newConsoleText 231 | } 232 | 233 | func appendOutputToConsoleText(s: NSString) { 234 | appendToConsoleText(s, attributes: outputAttributes) 235 | } 236 | 237 | func appendInputToConsoleText(s: NSString) { 238 | appendToConsoleText(s, attributes: inputAttributes) 239 | } 240 | 241 | /// Handle Return key 242 | func textFieldShouldReturn(textField: UITextField) -> Bool { 243 | let text = textField.text 244 | textField.text = "" 245 | if !text.isEmpty { 246 | let line: NSString = text.stringByAppendingString("\n") 247 | appendInputToConsoleText(line) 248 | let chars = charsFromString(line as String) 249 | interpreterIO.sendInputChars(chars) 250 | if !interpreterScheduled { 251 | scheduleInterpreter() 252 | } 253 | } 254 | 255 | return false 256 | } 257 | 258 | 259 | // MARK: - Interpreter 260 | 261 | /// Drive the interpreter 262 | /// 263 | /// Calls interpreter.next() to get it to do its next action. 264 | /// Then schedules another step, unless the interpreter is 265 | /// waiting for input and we don't have any to give it. 266 | func stepInterpreter() { 267 | interpreterScheduled = false 268 | interpreter.next() 269 | 270 | #if BitsyBASIC_Swift 271 | let state = interpreter.state 272 | #else 273 | let state = interpreter.state() 274 | #endif 275 | 276 | switch state { 277 | case .Idle, .Running: 278 | scheduleInterpreter() 279 | 280 | case .ReadingStatement, .ReadingInput: 281 | if interpreterIO.inputBuffer.count > 0 { 282 | scheduleInterpreter() 283 | } 284 | 285 | default: 286 | assert(false, "unhandled interpreter state value \(state)") 287 | } 288 | } 289 | 290 | /// Queue a call to stepInterpreter() 291 | func scheduleInterpreter() { 292 | interpreterScheduled = true 293 | #if false 294 | dispatch_async(dispatch_get_main_queue()) { 295 | self.stepInterpreter() 296 | } 297 | #else 298 | let timer = NSTimer.scheduledTimerWithTimeInterval(0, 299 | target: self, 300 | selector: "stepInterpreter", 301 | userInfo: nil, 302 | repeats: false) 303 | #endif 304 | } 305 | 306 | func showCommandPrompt() { 307 | appendOutputToConsoleText("> ") 308 | } 309 | 310 | func showInputPrompt() { 311 | appendOutputToConsoleText("? ") 312 | } 313 | 314 | @IBAction func onBreakTapped(sender: UIBarButtonItem) { 315 | interpreter.breakExecution() 316 | if !interpreterScheduled { 317 | scheduleInterpreter() 318 | } 319 | } 320 | } 321 | 322 | /// Interface between the BASIC interpreter and ConsoleViewController 323 | @objc final class ConsoleInterpreterIO: NSObject, InterpreterIO { 324 | weak var viewController: ConsoleViewController? 325 | 326 | var inputBuffer: [Char] = Array() 327 | var inputIndex: Int = 0 328 | 329 | var outputBuffer: [Char] = Array() 330 | 331 | init(viewController: ConsoleViewController) { 332 | self.viewController = viewController 333 | } 334 | 335 | /// Return next input character for the interpreter, 336 | /// or nil if at end-of-file or an error occurs. 337 | func getInputCharForInterpreter(interpreter: Interpreter) -> InputCharResult { 338 | if inputIndex < inputBuffer.count { 339 | #if BitsyBASIC_Swift 340 | let result: InputCharResult = .Value(inputBuffer[inputIndex]) 341 | #else 342 | let result: InputCharResult = InputCharResult_Value(inputBuffer[inputIndex]) 343 | #endif 344 | 345 | ++inputIndex 346 | if inputIndex == inputBuffer.count { 347 | inputBuffer = Array() 348 | inputIndex = 0 349 | } 350 | return result 351 | } 352 | 353 | #if BitsyBASIC_Swift 354 | return .Waiting 355 | #else 356 | return InputCharResult_Waiting() 357 | #endif 358 | } 359 | 360 | /// Send characters from the console to the interpreter 361 | func sendInputChars(chars: [Char]) { 362 | inputBuffer.extend(chars) 363 | } 364 | 365 | /// Write specified output character 366 | func putOutputChar(c: Char, forInterpreter interpreter: Interpreter) { 367 | outputBuffer.append(c) 368 | if c == 10 || outputBuffer.count >= 40 { 369 | flushOutput() 370 | } 371 | } 372 | 373 | func flushOutput() { 374 | if outputBuffer.count > 0 { 375 | if let s = NSString(bytes: &self.outputBuffer, 376 | length: self.outputBuffer.count, 377 | encoding: NSUTF8StringEncoding) 378 | { 379 | viewController!.appendOutputToConsoleText(s) 380 | } 381 | else { 382 | println("to convert chars to string") 383 | } 384 | outputBuffer = Array() 385 | } 386 | } 387 | 388 | /// Display a prompt to the user for entering an immediate command or line of code 389 | func showCommandPromptForInterpreter(interpreter: Interpreter) { 390 | flushOutput() 391 | viewController!.showCommandPrompt() 392 | } 393 | 394 | /// Display a prompt to the user for entering data for an INPUT statement 395 | func showInputPromptForInterpreter(interpreter: Interpreter) { 396 | flushOutput() 397 | viewController!.showInputPrompt() 398 | } 399 | 400 | /// Display error message to user 401 | func showErrorMessage(message: String, forInterpreter interpreter: Interpreter) { 402 | flushOutput() 403 | let messageWithNewline = "\(message)\n" 404 | viewController!.appendOutputToConsoleText(messageWithNewline) 405 | } 406 | 407 | /// Display a debug trace message 408 | func showDebugTraceMessage(message: String, forInterpreter interpreter: Interpreter) { 409 | flushOutput() 410 | viewController!.appendOutputToConsoleText(message) 411 | } 412 | 413 | /// Called when BYE is executed 414 | func byeForInterpreter(interpreter: Interpreter) { 415 | flushOutput() 416 | viewController!.appendOutputToConsoleText("error: BYE has no effect in iOS\n") 417 | } 418 | } 419 | 420 | #if !BitsyBASIC_Swift 421 | /// Given a string, return array of Chars 422 | public func charsFromString(s: String) -> [UInt8] { 423 | return Array(s.utf8) 424 | } 425 | #endif 426 | -------------------------------------------------------------------------------- /BitsyBASIC/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "Icon_settings@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "29x29", 11 | "idiom" : "iphone", 12 | "filename" : "Icon_settings@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "40x40", 17 | "idiom" : "iphone", 18 | "filename" : "Icon_spotlight@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "40x40", 23 | "idiom" : "iphone", 24 | "filename" : "Icon_spotlight@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "60x60", 29 | "idiom" : "iphone", 30 | "filename" : "Icon_iphone@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "Icon_iphone@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "29x29", 41 | "idiom" : "ipad", 42 | "filename" : "Icon_settings.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "29x29", 47 | "idiom" : "ipad", 48 | "filename" : "Icon_settings@2x-1.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "40x40", 53 | "idiom" : "ipad", 54 | "filename" : "Icon_spotlight.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "40x40", 59 | "idiom" : "ipad", 60 | "filename" : "Icon_spotlight@2x-1.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "76x76", 65 | "idiom" : "ipad", 66 | "filename" : "Icon_ipad.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "76x76", 71 | "idiom" : "ipad", 72 | "filename" : "Icon_ipad@2x.png", 73 | "scale" : "2x" 74 | } 75 | ], 76 | "info" : { 77 | "version" : 1, 78 | "author" : "xcode" 79 | } 80 | } -------------------------------------------------------------------------------- /BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristopherjohnson/bitsybasic/cdaecf8c6c9a47e9d869e6eb8687db575e67d460/BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_ipad.png -------------------------------------------------------------------------------- /BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_ipad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristopherjohnson/bitsybasic/cdaecf8c6c9a47e9d869e6eb8687db575e67d460/BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_ipad@2x.png -------------------------------------------------------------------------------- /BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_iphone@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristopherjohnson/bitsybasic/cdaecf8c6c9a47e9d869e6eb8687db575e67d460/BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_iphone@2x.png -------------------------------------------------------------------------------- /BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_iphone@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristopherjohnson/bitsybasic/cdaecf8c6c9a47e9d869e6eb8687db575e67d460/BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_iphone@3x.png -------------------------------------------------------------------------------- /BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristopherjohnson/bitsybasic/cdaecf8c6c9a47e9d869e6eb8687db575e67d460/BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_settings.png -------------------------------------------------------------------------------- /BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_settings@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristopherjohnson/bitsybasic/cdaecf8c6c9a47e9d869e6eb8687db575e67d460/BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_settings@2x-1.png -------------------------------------------------------------------------------- /BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_settings@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristopherjohnson/bitsybasic/cdaecf8c6c9a47e9d869e6eb8687db575e67d460/BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_settings@2x.png -------------------------------------------------------------------------------- /BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_settings@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristopherjohnson/bitsybasic/cdaecf8c6c9a47e9d869e6eb8687db575e67d460/BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_settings@3x.png -------------------------------------------------------------------------------- /BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_spotlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristopherjohnson/bitsybasic/cdaecf8c6c9a47e9d869e6eb8687db575e67d460/BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_spotlight.png -------------------------------------------------------------------------------- /BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_spotlight@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristopherjohnson/bitsybasic/cdaecf8c6c9a47e9d869e6eb8687db575e67d460/BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_spotlight@2x-1.png -------------------------------------------------------------------------------- /BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_spotlight@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristopherjohnson/bitsybasic/cdaecf8c6c9a47e9d869e6eb8687db575e67d460/BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_spotlight@2x.png -------------------------------------------------------------------------------- /BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_spotlight@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristopherjohnson/bitsybasic/cdaecf8c6c9a47e9d869e6eb8687db575e67d460/BitsyBASIC/Images.xcassets/AppIcon.appiconset/Icon_spotlight@3x.png -------------------------------------------------------------------------------- /BitsyBASIC/Images.xcassets/ConsoleBackgroundPattern.imageset/ConsoleBackgroundPattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristopherjohnson/bitsybasic/cdaecf8c6c9a47e9d869e6eb8687db575e67d460/BitsyBASIC/Images.xcassets/ConsoleBackgroundPattern.imageset/ConsoleBackgroundPattern.png -------------------------------------------------------------------------------- /BitsyBASIC/Images.xcassets/ConsoleBackgroundPattern.imageset/ConsoleBackgroundPattern@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristopherjohnson/bitsybasic/cdaecf8c6c9a47e9d869e6eb8687db575e67d460/BitsyBASIC/Images.xcassets/ConsoleBackgroundPattern.imageset/ConsoleBackgroundPattern@2x.png -------------------------------------------------------------------------------- /BitsyBASIC/Images.xcassets/ConsoleBackgroundPattern.imageset/ConsoleBackgroundPattern@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristopherjohnson/bitsybasic/cdaecf8c6c9a47e9d869e6eb8687db575e67d460/BitsyBASIC/Images.xcassets/ConsoleBackgroundPattern.imageset/ConsoleBackgroundPattern@3x.png -------------------------------------------------------------------------------- /BitsyBASIC/Images.xcassets/ConsoleBackgroundPattern.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "ConsoleBackgroundPattern.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "ConsoleBackgroundPattern@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "ConsoleBackgroundPattern@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /BitsyBASIC/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | net.kristopherjohnson.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /BitsyBASIC/InterpreterThread.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2014 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | import Foundation 25 | 26 | final class InterpreterThread: NSThread { 27 | let interpreter: Interpreter 28 | 29 | init(interpreterIO: InterpreterIO) { 30 | self.interpreter = Interpreter(interpreterIO: interpreterIO) 31 | } 32 | 33 | override func main() { 34 | interpreter.interpretInputLines() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /BitsyBASIC/KeyboardNotification.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2014, 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | import UIKit 25 | 26 | /// Wrapper for the NSNotification userInfo values associated with a keyboard notification. 27 | /// 28 | /// It provides properties retrieve userInfo dictionary values with these keys: 29 | /// 30 | /// - UIKeyboardFrameBeginUserInfoKey 31 | /// - UIKeyboardFrameEndUserInfoKey 32 | /// - UIKeyboardAnimationDurationUserInfoKey 33 | /// - UIKeyboardAnimationCurveUserInfoKey 34 | 35 | public struct KeyboardNotification { 36 | 37 | let notification: NSNotification 38 | let userInfo: NSDictionary 39 | 40 | /// Initializer 41 | /// 42 | /// :param: notification Keyboard-related notification 43 | public init(_ notification: NSNotification) { 44 | self.notification = notification 45 | if let userInfo = notification.userInfo { 46 | self.userInfo = userInfo 47 | } 48 | else { 49 | self.userInfo = NSDictionary() 50 | } 51 | } 52 | 53 | /// Start frame of the keyboard in screen coordinates 54 | public var screenFrameBegin: CGRect { 55 | if let value = userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue { 56 | return value.CGRectValue() 57 | } 58 | else { 59 | return CGRectZero 60 | } 61 | } 62 | 63 | /// End frame of the keyboard in screen coordinates 64 | public var screenFrameEnd: CGRect { 65 | if let value = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue { 66 | return value.CGRectValue() 67 | } 68 | else { 69 | return CGRectZero 70 | } 71 | } 72 | 73 | /// Keyboard animation duration 74 | public var animationDuration: Double { 75 | if let number = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber { 76 | return number.doubleValue 77 | } 78 | else { 79 | return 0.25 80 | } 81 | } 82 | 83 | /// Keyboard animation curve 84 | /// 85 | /// Note that the value returned by this method may not correspond to a 86 | /// UIViewAnimationCurve enum value. For example, in iOS 7 and iOS 8, 87 | /// this returns the value 7. 88 | public var animationCurve: Int { 89 | if let number = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber { 90 | return number.integerValue 91 | } 92 | return UIViewAnimationCurve.EaseInOut.rawValue 93 | } 94 | 95 | /// Start frame of the keyboard in coordinates of specified view 96 | /// 97 | /// :param: view UIView to whose coordinate system the frame will be converted 98 | /// :returns: frame rectangle in view's coordinate system 99 | public func frameBeginForView(view: UIView) -> CGRect { 100 | return view.convertRect(screenFrameBegin, fromView: view.window) 101 | } 102 | 103 | /// End frame of the keyboard in coordinates of specified view 104 | /// 105 | /// :param: view UIView to whose coordinate system the frame will be converted 106 | /// :returns: frame rectangle in view's coordinate system 107 | public func frameEndForView(view: UIView) -> CGRect { 108 | return view.convertRect(screenFrameEnd, fromView: view.window) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /BitsyBASICIcon.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristopherjohnson/bitsybasic/cdaecf8c6c9a47e9d869e6eb8687db575e67d460/BitsyBASICIcon.sketch -------------------------------------------------------------------------------- /BitsyBASICTests/BitsyBASICTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2014, 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | import UIKit 25 | import XCTest 26 | 27 | class BitsyBASICTests: XCTestCase { 28 | 29 | } 30 | -------------------------------------------------------------------------------- /BitsyBASICTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | net.kristopherjohnson.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | FinchBasic 2 | Copyright (c) 2014 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 |  23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FinchBasic and BitsyBASIC 2 | 3 | Copyright 2014 Kristopher Johnson 4 | 5 | 6 | ## Overview 7 | 8 | FinchBasic is a dialect of [Tiny BASIC](http://en.wikipedia.org/wiki/Tiny_BASIC), implemented in [Swift](https://developer.apple.com/swift/). BitsyBASIC is an iOS app that uses the FinchBasic interpreter. 9 | 10 | The syntax and implementation are based upon these online sources: 11 | 12 | - The Wikipedia page: 13 | - [Dr. Dobb's Journal of Tiny BASIC Calisthenics & Orthodontia: Running Light Without Overbyte, Volume 1, Issue 1](http://www.drdobbs.com/architecture-and-design/sourcecode/dr-dobbs-journal-30/30000144) 14 | - ["The Return of Tiny Basic"](http://www.drdobbs.com/web-development/the-return-of-tiny-basic/184406381) 15 | - [Tiny Basic User's Manual](http://www.ittybittycomputers.com/IttyBitty/TinyBasic/TBuserMan.htm) 16 | - [TinyBasic.c](http://www.ittybittycomputers.com/IttyBitty/TinyBasic/TinyBasic.c) 17 | - [Li-Chen Wang's Tiny Basic Source Code for Intel 8080 Version 1.0](https://www.princeton.edu/~achaney/tmve/wiki100k/docs/Li-Chen_Wang.html) 18 | - [tinybc: Tiny BASIC for Beginners](http://tinybc.sourceforge.net/tinybctut.txt) 19 | 20 | 21 | ## Building FinchBasic 22 | 23 | To build the `finchbasic` executable for OS X, `cd` to the project directory and do this: 24 | 25 | xcodebuild 26 | 27 | The `finchbasic` executable will be in the `build/Release` directory. 28 | 29 | 30 | ## Using FinchBasic 31 | 32 | `finchbasic` currently only reads from standard input, writes to standard output, and sends error messages to standard error. 33 | 34 | To run the interpreter and enter commands, do this: 35 | 36 | finchbasic 37 | 38 | If you want to load a program into `finchbasic` and run it from the command line, you can do something like this: 39 | 40 | finchbasic < myprogram.basic 41 | 42 | If you want to load a program interactively, you can do this: 43 | 44 | finchbasic 45 | >load "myprogram.basic" 46 | >run 47 | 48 | `finchbasic` expects input to be a list of BASIC statements. If a line starts with a line number, then the line is added to the program stored in memory, overwriting any existing line with that same line number. If a line does not start with a line number, then it is executed immediately. 49 | 50 | For example: 51 | 52 | 10 PRINT "Hello, world" 53 | 20 LET A = 2 54 | 30 LET B = 3 55 | 40 PRINT "a + b = "; A + B 56 | 50 IF A < B THEN PRINT "a is less than b" 57 | 60 END 58 | LIST 59 | RUN 60 | 61 | You can use lowercase letters for BASIC keywords and variable names. The interpreter will automatically convert them to uppercase. 62 | 63 | Another example: 64 | 65 | 10 Print "Enter first number" 66 | 20 Input a 67 | 30 Print "Enter second number" 68 | 40 Input b 69 | 50 Print a; " + "; b; " = "; a + b 70 | 60 End 71 | run 72 | 73 | 74 | ## Building BitsyBASIC 75 | 76 | The BitsyBASIC iOS app is a work-in-progress. It generally works, but needs some bug fixes and polish before it is ready for the App Store. 77 | 78 | To build and run it, open the project in Xcode and select the `BitsyBASIC` scheme. 79 | 80 | BitsyBASIC currently uses the `finchlib_cpp` library, which is a translation of the Swift code in `finchlib` to Objective-C++. There is another target, `BitsyBASIC_Swift`, that uses the Swift code, but it crashes mysteriously when parsing statements. It is hoped that future updates to the Swift compiler will fix this issue, so that `finchlib_cpp` can be deprecated. 81 | 82 | 83 | ## Syntax 84 | 85 | FinchBasic supports this syntax: 86 | 87 | line ::= number statement CR | statement CR 88 | 89 | statement ::= PRINT (expr-list|ε) 90 | LET lvalue = expr 91 | lvalue = expr 92 | INPUT lvalue-list 93 | DIM "@(" expr ")" 94 | IF expr relop expr THEN statement 95 | IF expr relop expr statement 96 | GOTO expr 97 | GOSUB expr 98 | RETURN 99 | END 100 | CLEAR 101 | LIST (ε|expr (ε|(, expr))) 102 | SAVE string 103 | LOAD string 104 | FILES 105 | CLIPSAVE 106 | CLIPLOAD 107 | RUN 108 | REM comment | ' comment 109 | TRON 110 | TROFF 111 | BYE 112 | HELP 113 | 114 | expr-list ::= (string|expr) ((,|;) (string|expr) )* (,|;|ε) 115 | 116 | lvalue-list ::= lvalue (, lvalue)* 117 | 118 | expr ::= (+|-|ε) term ((+|-) term)* 119 | 120 | term ::= factor ((*|/) factor)* 121 | 122 | factor ::= var | "@(" expr ")" | number | "(" expr ")" | "RND(" expr ")" 123 | 124 | lvalue ::= var | "@(" expr ")" 125 | 126 | var ::= A | B | C ... | Y | Z 127 | 128 | number ::= digit digit* 129 | 130 | digit ::= 0 | 1 | 2 | 3 | ... | 8 | 9 131 | 132 | string ::= " char* " 133 | 134 | relop ::= < (>|=|ε) | > (<|=|ε) | = 135 | 136 | Abbreviations can be used for some of the keywords: 137 | 138 | - `PRINT`: `PR` or `?` 139 | - `INPUT`: `IN` 140 | - `GOTO`: `GT` 141 | - `GOSUB`: `GS` 142 | - `RETURN`: `RT` 143 | - `LIST`: `LS` 144 | - `SAVE`: `SV` 145 | - `LOAD`: `LD` 146 | - `FILES`: `FL` 147 | 148 | Most of these statements and expressions have the traditional Tiny BASIC behaviors, which are described elsewhere. What follows are some peculiarities of the FinchBasic implementation: 149 | 150 | 151 | **Numbers** 152 | 153 | Numbers are 64-bit signed integers on 64-bit platforms, or 32-bit signed integers on 32-bit platforms. So if your applications rely on the overflow behavior of 16-bit Tiny BASIC numbers, then you may get unexpected results. 154 | 155 | (If your applications rely upon 16-bit overflow behavior, you can change the definition of `Number` in `syntax.swift` from `Int` to `Int16`, and then rebuild `finchbasic`.) 156 | 157 | 158 | **PRINT** 159 | 160 | `PR` and `?` are both synonyms for `PRINT`. 161 | 162 | If `PRINT` has no arguments, it outputs a newline character. 163 | 164 | If expressions are separated by commas, then a tab character is output between them. If expressions are separated by semicolons, then there is no separator output between them. 165 | 166 | `PRINT` usually outputs a newline character after the expressions. You can suppress this behavior by ending the statement with a semicolon. End the statement with a comma to output a tab character rather than a newline. 167 | 168 | 169 | **INPUT** 170 | 171 | `IN` is a synonym for `INPUT`. 172 | 173 | The `INPUT` command displays a question-mark prompt, reads a single input line, and then tries to assign an expression to each variable in the list. So, for example, if these statements is executed: 174 | 175 | 100 PRINT "Enter three numbers:" 176 | 110 INPUT A, B, C 177 | 178 | then the user should respond with something like 179 | 180 | 123, 456, -789 181 | 182 | If there are too few numbers, or a syntax error, then an error message is printed and the prompt is displayed again. INPUT will not return control until it successfully reads the expected input or it reaches the end of the input stream. 183 | 184 | If there are more input numbers than variables, then the extra inputs are ignored. 185 | 186 | The user may enter a variable name instead of a number, and the result will be the value of that variable. This allows simple character-based input such as this: 187 | 188 | 10 LET Y = 999 189 | 20 LET N = -999 190 | 30 PRINT "Do you want a pony? (Y/N)" 191 | 40 INPUT A 192 | 50 IF A = Y THEN GOTO 100 193 | 60 IF A = N THEN GOTO 200 194 | 70 PRINT "You must answer with Y or N." 195 | 80 GOTO 30 196 | 100 PRINT "OK, you get a pony!" 197 | 110 END 198 | 200 PRINT "OK, here is a lollipop." 199 | 210 END 200 | 201 | 202 | **@** 203 | 204 | FinchBasic provides an array of numbers, named `@`. An array element is addressed as `@(i)`, where `i` is the index of the element. 205 | 206 | By default, the array has 1024 elements, numbered 0-1023. You can change the number of elements in the array with `DIM @(` *newsize* `)`. Calling `DIM` also clears all array element values to zero. 207 | 208 | You can use a negative index value to specify an element at the end of the array. For example, `@(-1)` is the last array element, `@(-2)` is the one before that, and so on. 209 | 210 | You can use `LET` or `INPUT` to set array element values, and you can use array elements in numeric expressions. For example, 211 | 212 | 10 let p = 1 213 | 20 print "Enter three numbers" 214 | 30 input @(p), @(p+1), @(p+2) 215 | 40 let @(p+3) = @(p) + @(p+1) + @(p+2) 216 | 50 print "Their sum is "; @(p+3) 217 | 60 end 218 | 219 | 220 | **CLEAR** 221 | 222 | Clear removes any existing program from memory, and resets all variables and array elements to zero. 223 | 224 | 225 | **LIST** 226 | 227 | `LIST` with no arguments will display the entire program. 228 | 229 | `LIST` followed by a single expression will display the specified line 230 | 231 | `LIST` followed by two expressions separated by a comma will display the lines between the first line number and second line number, including those line numbers. 232 | 233 | 234 | **SAVE** 235 | 236 | The `SAVE` command writes the program text to a file, as if `LIST` output was redirected to that file. 237 | 238 | For example, after this: 239 | 240 | 10 print "This is a saved file." 241 | 20 end 242 | save "myfile.bas" 243 | 244 | `myfile.bas` will have these contents: 245 | 246 | 10 PRINT "This is a saved file." 247 | 20 END 248 | 249 | 250 | **LOAD** 251 | 252 | The `LOAD` command reads lines from a file, as if the user was typing them. 253 | 254 | This is typically used to read a program from a file. For example, if the file `myfile.bas` contains a complete program, you can enter these commands to run the program: 255 | 256 | load "myfile.bas" 257 | run 258 | 259 | However, the file need not contain only numbered program lines. It can contain commands to be executed immediately. 260 | 261 | By default, files will be loaded from the program's current working directory, but you can provide a path to files outside that directory. For example: 262 | 263 | load "/Users/kdj/basic/example.bas" 264 | 265 | You should use the `CLEAR` command before `LOAD` if you want to avoid the possibility of merging incompatible program lines into an existing program. 266 | 267 | 268 | **FILES** 269 | 270 | The `FILES` command displays the names of files in the current directory. 271 | 272 | These files can be loaded by using the `LOAD` command. Note, however, that the output of `FILES` may include files that are not valid BASIC programs. 273 | 274 | 275 | **CLIPLOAD/CLIPSAVE** 276 | 277 | These are like `LOAD` and `SAVE` except, instead of reading from files, they read from and write to the system clipboard. 278 | 279 | `CLIPLOAD` is like pasting the clipboard contents into your program. So, you can copy a program from a text editor or other application and then use `CLIPLOAD` to load it into FinchBasic. 280 | 281 | `CLIPSAVE` is like doing a `LIST` and then copying the result to the clipboard. So you can do a `CLIPSAVE` and then paste the result into a text editor or other application. 282 | 283 | 284 | **TRON/TROFF** 285 | 286 | The `TRON` command enables statement tracing. Line numbers are printed as each statement is executed. `TROFF` disables statement tracing. 287 | 288 | 289 | **RND(number)** 290 | 291 | Returns a randomly generated number between 0 and `number`-1, inclusive. If `number` is less than 1, then the function returns 0. 292 | 293 | 294 | **BYE** 295 | 296 | The `BYE` command causes `finchbasic` to terminate gracefully. 297 | 298 | 299 | **HELP** 300 | 301 | The `HELP` command displays a summary of BASIC syntax. 302 | 303 | 304 | ## Code Organization 305 | 306 | - `finchbasic/` - source for `finchbasic` executable 307 | - `main.swift` - main entry point for the command-line tool 308 | - `finchlib/` - source for `finchbasic` executable and `finchlib` framework 309 | - `Interpreter.swift` - defines the `Interpreter` class 310 | - `io.swift` - defines the `InterpreterIO` protocol used for interface between `Interpreter` and its environment, and the `StandardIO` implementation of that protocol 311 | - `parse.swift` - defines the functions used to parse BASIC statements 312 | - `pasteboard.swift` - platform-specific code used by `CLIPSAVE` and `CLIPLOAD` 313 | - `syntax.swift` - defines the parse-tree data structures 314 | - `char.swift` - ASCII character constants and functions for converting between String and arrays of ASCII characters 315 | - `util.swift` - miscellaneous auxiliary types and functions 316 | - `finchlibTests/` - unit tests that exercise `finchlib` 317 | 318 | 319 | ## Hacking FinchBasic 320 | 321 | One of the goals of FinchBasic is that it should be easily "hackable", meaning that it is easy for programmers to modify it to support new statement types, new expression types, and so on. You are encouraged to experiment with the code. 322 | 323 | ### Using Other Schemes 324 | 325 | Running `xcodebuild` with no arguments builds the `finchbasic` scheme, which is the easiest way to build a runnable and releasable executable. There are other schemes available in the project which may be more suitable for you if you want to work on the FinchBasic source code. These are brief descriptions of each: 326 | 327 | - `finchbasic` builds and runs the OS X command-line tool. 328 | - `finchlib` builds an OS X Cocoa framework containing all of the FinchBasic code and a unit test bundle. This is the scheme used most often for development. See the `finchlibTests.swift` file for the unit tests. 329 | - `finchlib_Release` is like `finchlib`, but uses the Release configuration instead of Debug for unit tests and other tasks. Use this profile to verify that code correctly when built with Swift compiler optimization enabled. 330 | - `BitsyBASIC` is an iOS app that presents a console-like display and runs the FinchBasic interpreter. 331 | - `finchlib_cpp` is a translation of the Swift code in `finchlib` to Objective-C and C++. This is used by `BitsyBASIC` to work around bugs in the Swift compiler and/or run-time library. (Eventually this library will be deprecated when BitsyBASIC can be built entirely with Swift.) 332 | - `BitsyBASIC_Swift` is an iOS app that uses the Swift `finchlib` library instead of the Objective-C/C++ library. Due to apparent Swift compiler bugs, this app crashes. 333 | 334 | 335 | ### Parsing and Evaluation 336 | 337 | To add a new statement or new type of expression, there are basically three steps: 338 | 339 | 1. Add a new definition or modify an existing definition in `syntax.swift` to represent the syntax of the new construct. 340 | 2. Add code to parse the new construct. 341 | 3. Add code to execute/evaluate the new construct. 342 | 343 | Start by studying the enum types in `syntax.swift`. These implement the [parse trees](http://en.wikipedia.org/wiki/Parse_tree) that represent the syntactic structure of each statement. 344 | 345 | Study the parsing methods in `parse.swift` to determine where to add your new parsing code. For example, if you are adding a new statement type, you will probably add something to `statement()`, whereas if you are adding a new kind of expression or changing the way an expression is parsed, you will probably change something in `expression()`, `term()`, or `factor()`. 346 | 347 | Finally, to handle the execution or evaluation of the new construct, study the `execute` methods in `Interpreter.swift` and the `evaluate` methods in `syntax.swift`. 348 | 349 | Some things to remember while writing parsing code: 350 | 351 | - The `readInputLine()` method strips out all non-graphic characters, and converts tabs to spaces. So your parsing code won't need to deal with this. 352 | - The parser can only see the current input line. It cannot read the next line while parsing the current line. 353 | - In general, spaces should be ignored/skipped. So "GO TO 10 11" is equivalent to "GOTO 1011", and "1 0P R I N T" is equivalent to "10 PRINT". The only place where spaces are significant is in string literals. 354 | - In general, lowercase alphabetic characters should be treated in a case-insensitive manner or automatically converted to uppercase. The only place where this is not appropriate is in string literals. 355 | - Any incorrect syntax detected must result in a `Statement.Error(String)` element being returned by `statement()`, so that the error will be reported properly. 356 | - `char.swift` contains methods and definitions that may be useful for classifying input characters or for converting text between ASCII/UTF8 bytes and Swift Strings. 357 | 358 | 359 | ### Control Flow 360 | 361 | If you are adding a new control structure (like a `FOR...NEXT` or a `REPEAT...UNTIL`), then you will need to understand how the `RUN` statement works. 362 | 363 | The `Interpreter` class has an instance member `program` that is an array that holds all the program statements, each of which is a `(Number, Statement)` pair. The instance member `programIndex` is an Int that is the index of the next element of `program` to be executed. There is also a Boolean instance member `isRunning` that indicates whether the interpreter is in the running state (that is, `RUN` is being executed). 364 | 365 | When `RUN` starts, it sets `isRunning` to true, sets `programIndex` to 0, and then starts executing statements sequentially. It reads a statement from `program[programIndex]`, then increments `programIndex` to point to the next statement, then executes the statement it just read by calling the `execute(Statement)` method. Then it reads `program[programIndex]`, increments `programIndex`, and executes the statement it just read, and continues in that loop as long as `isRunning` is true. 366 | 367 | Some control-flow statements have an effect on this process: 368 | 369 | - The `GOTO` statement finds the index in `program` of the statement with a specified line number, then sets `programIndex` to that index. So when control returns to `RUN`, it will execute the statement referenced by the new value of `programIndex`. 370 | - The `GOSUB` statement pushes the current value of `programIndex` (which will be the index of the following statement) to the `returnStack`, then does the same thing `GOTO` does to look up the target statement and jump to it. 371 | - The `RETURN` statement pops an index off of the `returnStack` and sets the `programIndex` to that value, so `RUN` will resume at the statement following the `GOSUB`. 372 | - The `END` statement sets `isRunning` false, so that `RUN` will exit its loop. 373 | 374 | So, if you want to implement your own control-flow statements, you probably just need to figure out how to manipulate `programIndex`, `returnStack`, and `isRunning`. 375 | 376 | 377 | ## To-Do 378 | 379 | These fixes/changes/enhancements are planned: 380 | 381 | - Statements: 382 | - `CLS`: clear screen (iOS only) 383 | - `CSAVE`, `CLOAD`, `CFILES`: access iCloud Drive 384 | - More extensive help. For example, "HELP PRINT" will display detailed information about the PRINT statement. 385 | 386 | Contributions are welcome, but the goal is to keep this simple, so if you propose something really ambitious, you may be asked to create your own fork. 387 | -------------------------------------------------------------------------------- /bitsybasic.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /bitsybasic.xcodeproj/project.xcworkspace/xcshareddata/bitsybasic.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 24DC1E88-47BB-4132-9E41-0171B1CD6010 9 | IDESourceControlProjectName 10 | bitsybasic 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | D136DA64BB58085BA67FED8812E9BD23AF1758F3 14 | github.com:kristopherjohnson/bitsybasic.git 15 | 16 | IDESourceControlProjectPath 17 | bitsybasic.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | D136DA64BB58085BA67FED8812E9BD23AF1758F3 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | github.com:kristopherjohnson/bitsybasic.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | D136DA64BB58085BA67FED8812E9BD23AF1758F3 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | D136DA64BB58085BA67FED8812E9BD23AF1758F3 36 | IDESourceControlWCCName 37 | bitsybasic 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /bitsybasic.xcodeproj/project.xcworkspace/xcshareddata/finchbasic.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | AA44A56B-3B3B-457C-8D13-B267C5E915FA 9 | IDESourceControlProjectName 10 | finchbasic 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | D136DA64BB58085BA67FED8812E9BD23AF1758F3 14 | https://github.com/kristopherjohnson/FinchBasic.git 15 | 16 | IDESourceControlProjectPath 17 | finchbasic.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | D136DA64BB58085BA67FED8812E9BD23AF1758F3 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/kristopherjohnson/FinchBasic.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | D136DA64BB58085BA67FED8812E9BD23AF1758F3 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | D136DA64BB58085BA67FED8812E9BD23AF1758F3 36 | IDESourceControlWCCName 37 | finchbasic 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /bitsybasic.xcodeproj/xcshareddata/xcschemes/BitsyBASIC.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /bitsybasic.xcodeproj/xcshareddata/xcschemes/BitsyBASIC_Swift.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /bitsybasic.xcodeproj/xcshareddata/xcschemes/finchbasic.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /bitsybasic.xcodeproj/xcshareddata/xcschemes/finchlib.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /bitsybasic.xcodeproj/xcshareddata/xcschemes/finchlib_Release.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /bitsybasic.xcodeproj/xcshareddata/xcschemes/finchlib_cpp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /finchbasic/main.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2014, 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | import Foundation 25 | 26 | func runInterpreter() { 27 | let io = StandardIO() 28 | let interpreter = Interpreter(interpreterIO: io) 29 | interpreter.runUntilEndOfInput() 30 | } 31 | 32 | // Put -DUSE_INTERPRETER_THREAD=1 in Build Settings 33 | // to run the interpreter in another thread. 34 | // 35 | // This demonstrates that we get EXC_BAD_ACCESS when 36 | // running the interpreter in another thread. Do not 37 | // use this for production code. 38 | // 39 | // See http://www.openradar.me/19353741 40 | #if USE_INTERPRETER_THREAD 41 | 42 | final class InterpreterThread: NSThread { 43 | let completionSemaphore = dispatch_semaphore_create(0) 44 | 45 | override func main() { 46 | runInterpreter() 47 | dispatch_semaphore_signal(self.completionSemaphore) 48 | } 49 | } 50 | 51 | let thread = InterpreterThread() 52 | thread.start() 53 | 54 | // Main thread waits for the other thread to finish 55 | dispatch_semaphore_wait(thread.completionSemaphore, DISPATCH_TIME_FOREVER) 56 | 57 | #else 58 | 59 | // Normal case is to run the interpreter in the 60 | // main thread 61 | runInterpreter() 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /finchlib/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | net.kristopherjohnson.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2014-2015 Kristopher Johnson. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /finchlib/char.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2014, 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | import Foundation 25 | 26 | 27 | /// An input/output character is an 8-bit value 28 | /// 29 | /// Note: In most cases, Finch will ignore any character values 30 | /// that fall outside the 7-bit ASCII graphical character range. 31 | public typealias Char = UInt8 32 | 33 | // ASCII/UTF8 character codes that we use 34 | let Ch_Tab: Char = 9 // '\t' 35 | let Ch_Linefeed: Char = 10 // '\n' 36 | let Ch_Space: Char = 32 // ' ' 37 | let Ch_DQuote: Char = 34 // '"' 38 | let Ch_Comma: Char = 44 // ',' 39 | let Ch_0: Char = 48 // '0' 40 | let Ch_9: Char = 57 // '9' 41 | let Ch_LAngle: Char = 60 // '<' 42 | let Ch_Equal: Char = 61 // '=' 43 | let Ch_RAngle: Char = 62 // '>' 44 | let Ch_QuestionMark: Char = 63 // '?' 45 | let Ch_Colon: Char = 58 // ':' 46 | let Ch_Semicolon: Char = 59 // ';' 47 | let Ch_A: Char = 65 // 'A' 48 | let Ch_Z: Char = 90 // 'Z' 49 | let Ch_a: Char = 97 // 'a' 50 | let Ch_z: Char = 122 // 'z' 51 | let Ch_Tilde: Char = 126 // '~' 52 | 53 | /// Return true if `c` is a printable ASCII character, or false otherwise 54 | func isGraphicChar(c: Char) -> Bool { 55 | switch c { 56 | case Ch_Space...Ch_Tilde: return true 57 | default: return false 58 | } 59 | } 60 | 61 | /// Return true if `c` is in the range 'A'...'Z' or 'a'...'z', or false otherwise 62 | func isAlphabeticChar(c: Char) -> Bool { 63 | switch c { 64 | case Ch_A...Ch_Z, Ch_a...Ch_z: return true 65 | default: return false 66 | } 67 | } 68 | 69 | /// Return true if `c` is in the range '0'...'9', or false otherwise 70 | func isDigitChar(c: Char) -> Bool { 71 | switch c { 72 | case Ch_0...Ch_9: return true 73 | default: return false 74 | } 75 | } 76 | 77 | /// If `c` is in the range 'a'...'z', then return the uppercase variant of that character. 78 | /// Otherwise, return `c`. 79 | func toUpper(c: Char) -> Char { 80 | switch c { 81 | case Ch_a...Ch_z: return c - (Ch_a - Ch_A) 82 | default: return c 83 | } 84 | } 85 | 86 | /// Given array of Char (UInt8), return a null-terminated array of CChar (Int8) 87 | public func cStringFromChars(chars: [Char]) -> [CChar] { 88 | var cchars: [CChar] = chars.map { CChar(bitPattern: $0) } 89 | cchars.append(0) 90 | return cchars 91 | } 92 | 93 | /// Given array of Char, return a String 94 | public func stringFromChars(chars: [Char]) -> String { 95 | let cString = cStringFromChars(chars) 96 | if let result = String.fromCString(cString) { 97 | return result 98 | } 99 | 100 | // This will only happen if String.fromCString() fails, which 101 | // should never happen unless the input string contains invalid 102 | // UTF8 103 | assert(false, "unable to convert 8-bit characters to String") 104 | return "" 105 | } 106 | 107 | /// Given a single Char, return a single-character String 108 | public func stringFromChar(c: Char) -> String { 109 | let chars = [c] 110 | return stringFromChars(chars) 111 | } 112 | 113 | /// Given a string, return array of Chars 114 | public func charsFromString(s: String) -> [Char] { 115 | return Array(s.utf8) 116 | } 117 | -------------------------------------------------------------------------------- /finchlib/finchlib.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #import 25 | 26 | //! Project version number for finchlib. 27 | FOUNDATION_EXPORT double finchlibVersionNumber; 28 | 29 | //! Project version string for finchlib. 30 | FOUNDATION_EXPORT const unsigned char finchlibVersionString[]; 31 | 32 | // In this header, you should import all the public headers of your framework using statements like #import 33 | 34 | 35 | -------------------------------------------------------------------------------- /finchlib/io.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | import Foundation 25 | 26 | // MARK: - System I/O 27 | 28 | /// Possible results for the InterpreterIO.getInputChar method 29 | public enum InputLineResult { 30 | /// Input line 31 | case Value(InputLine) 32 | 33 | /// Reached end of input stream 34 | case EndOfStream 35 | 36 | /// No characters available now 37 | case Waiting 38 | } 39 | 40 | /// Possible results for the InterpreterIO.getInputChar method 41 | public enum InputCharResult { 42 | /// Char 43 | case Value(Char) 44 | 45 | /// Reached end of input stream 46 | case EndOfStream 47 | 48 | /// No characters available now 49 | case Waiting 50 | } 51 | 52 | /// Protocol implemented by object that provides I/O operations for an Interpreter 53 | public protocol InterpreterIO: NSObjectProtocol { 54 | /// Return next input character, or nil if at end-of-file or an error occurs 55 | func getInputCharForInterpreter(interpreter: Interpreter) -> InputCharResult 56 | 57 | /// Write specified output character 58 | func putOutputChar(c: Char, forInterpreter interpreter: Interpreter) 59 | 60 | /// Display a prompt to the user for entering an immediate command or line of code 61 | func showCommandPromptForInterpreter(interpreter: Interpreter) 62 | 63 | /// Display a prompt to the user for entering data for an INPUT statement 64 | func showInputPromptForInterpreter(interpreter: Interpreter) 65 | 66 | /// Display error message to user 67 | func showErrorMessage(message: String, forInterpreter interpreter: Interpreter) 68 | 69 | /// Display a debug trace message 70 | func showDebugTraceMessage(message: String, forInterpreter interpreter: Interpreter) 71 | 72 | /// Called when BYE is executed 73 | func byeForInterpreter(interpreter: Interpreter) 74 | } 75 | 76 | /// Default implementation of InterpreterIO that reads from stdin, 77 | /// writes to stdout, and sends error messages to stderr. The 78 | /// BYE command will cause the process to exit with a succesful 79 | /// result code. 80 | /// 81 | /// This implementation's `getInputChar()` will block until a 82 | /// character is read from standard input or end-of-stream is reached. 83 | /// It will never return `.Waiting`. 84 | public final class StandardIO: NSObject, InterpreterIO { 85 | public func getInputCharForInterpreter(interpreter: Interpreter) -> InputCharResult { 86 | let c = getchar() 87 | return c == EOF ? .EndOfStream : .Value(Char(c)) 88 | } 89 | 90 | public func putOutputChar(c: Char, forInterpreter interpreter: Interpreter) { 91 | putchar(Int32(c)) 92 | fflush(stdout) 93 | } 94 | 95 | public func showCommandPromptForInterpreter(interpreter: Interpreter) { 96 | putchar(Int32(Ch_RAngle)) 97 | fflush(stdout) 98 | } 99 | 100 | public func showInputPromptForInterpreter(interpreter: Interpreter) { 101 | putchar(Int32(Ch_QuestionMark)) 102 | putchar(Int32(Ch_Space)) 103 | fflush(stdout) 104 | } 105 | 106 | public func showErrorMessage(message: String, forInterpreter interpreter: Interpreter) { 107 | var chars = charsFromString(message) 108 | chars.append(Ch_Linefeed) 109 | fwrite(chars, 1, chars.count, stderr) 110 | fflush(stderr) 111 | } 112 | 113 | public func showDebugTraceMessage(message: String, forInterpreter interpreter: Interpreter) { 114 | var chars = charsFromString(message) 115 | fwrite(chars, 1, chars.count, stdout) 116 | fflush(stdout) 117 | } 118 | 119 | public func byeForInterpreter(interpreter: Interpreter) { 120 | exit(EXIT_SUCCESS) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /finchlib/pasteboard.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | import Foundation 25 | 26 | #if os(iOS) 27 | import UIKit 28 | #else 29 | import AppKit 30 | #endif 31 | 32 | /// Return string value currently on clipboard 33 | func getPasteboardContents() -> String? { 34 | #if os(iOS) 35 | 36 | let pasteboard = UIPasteboard.generalPasteboard() 37 | return pasteboard.string 38 | 39 | #else 40 | 41 | let pasteboard = NSPasteboard.generalPasteboard() 42 | return pasteboard.stringForType(NSPasteboardTypeString) 43 | 44 | #endif 45 | } 46 | 47 | /// Write a string value to the pasteboard 48 | func copyToPasteboard(text: String) { 49 | #if os(iOS) 50 | 51 | let pasteboard = UIPasteboard.generalPasteboard() 52 | pasteboard.string = text 53 | 54 | #else 55 | 56 | let pasteboard = NSPasteboard.generalPasteboard() 57 | pasteboard.clearContents() 58 | pasteboard.setString(text, forType: NSPasteboardTypeString) 59 | 60 | #endif 61 | } 62 | -------------------------------------------------------------------------------- /finchlib/syntax.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2014, 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | import Foundation 25 | 26 | // Syntax accepted by FinchBasic is based upon the charts here: 27 | // 28 | // http://en.wikipedia.org/wiki/Tiny_BASIC 29 | // 30 | // and in Appendix B of 31 | // 32 | // http://www.ittybittycomputers.com/IttyBitty/TinyBasic/TBuserMan.htm 33 | 34 | // Tokens 35 | 36 | let T_Asterisk = "*" 37 | let T_At = "@" 38 | let T_Comma = "," 39 | let T_Equal = "=" 40 | let T_LParen = "(" 41 | let T_Minus = "-" 42 | let T_Plus = "+" 43 | let T_QuestionMark = "?" 44 | let T_RParen = ")" 45 | let T_Semicolon = ";" 46 | let T_Slash = "/" 47 | let T_Tick = "'" 48 | 49 | let T_Greater = ">" 50 | let T_GreaterOrEqual = ">=" 51 | let T_Less = "<" 52 | let T_LessOrEqual = "<=" 53 | let T_NotEqual = "<>" 54 | let T_NotEqualAlt = "><" 55 | 56 | let T_BYE = "BYE" 57 | let T_CLEAR = "CLEAR" 58 | let T_CLIPLOAD = "CLIPLOAD" 59 | let T_CLIPSAVE = "CLIPSAVE" 60 | let T_DIM = "DIM" 61 | let T_END = "END" 62 | let T_FILES = "FILES" 63 | let T_FL = "FL" 64 | let T_GOSUB = "GOSUB" 65 | let T_GOTO = "GOTO" 66 | let T_GT = "GT" 67 | let T_GS = "GS" 68 | let T_HELP = "HELP" 69 | let T_IF = "IF" 70 | let T_IN = "IN" 71 | let T_INPUT = "INPUT" 72 | let T_LET = "LET" 73 | let T_LIST = "LIST" 74 | let T_LOAD = "LOAD" 75 | let T_LD = "LD" 76 | let T_LS = "LS" 77 | let T_PR = "PR" 78 | let T_PRINT = "PRINT" 79 | let T_REM = "REM" 80 | let T_RETURN = "RETURN" 81 | let T_RND = "RND" 82 | let T_RT = "RT" 83 | let T_RUN = "RUN" 84 | let T_SAVE = "SAVE" 85 | let T_SV = "SV" 86 | let T_THEN = "THEN" 87 | let T_TROFF = "TROFF" 88 | let T_TRON = "TRON" 89 | 90 | 91 | /// A Finch numeric value is a signed integer 92 | /// 93 | /// Note: Traditionally, Tiny Basic uses 16-bit integers, but we'll 94 | /// relax that restriction and use whatever native integer type 95 | /// the platform provides. 96 | typealias Number = Int 97 | 98 | /// There are 26 variables with names 'A'...'Z' 99 | /// 100 | /// Note that the names are uppercase. Any lowercase characters read 101 | /// by the interpreter must be converted to uppercase before 102 | /// using them as variable names. 103 | typealias VariableName = Char 104 | 105 | /// Each variable is bound to a numeric value 106 | typealias VariableBindings = [VariableName : Number] 107 | 108 | /// Result of parsing a statement 109 | enum Statement { 110 | /// "PRINT" printlist 111 | /// 112 | /// "PR" printlist 113 | /// 114 | /// "?" printlist 115 | case Print(PrintList) 116 | 117 | /// "PRINT" 118 | /// "PR" 119 | /// "?" 120 | case PrintNewline 121 | 122 | /// "INPUT" varlist 123 | case Input(LvalueList) 124 | 125 | /// "LET" lvalue "=" expression 126 | case Let(Lvalue, Expression) 127 | 128 | /// "DIM @(" expression ")" 129 | case DimArray(Expression) 130 | 131 | /// "GOTO" expression 132 | case Goto(Expression) 133 | 134 | /// "GOSUB" expression 135 | case Gosub(Expression) 136 | 137 | /// "RETURN" 138 | case Return 139 | 140 | /// "IF" expression relop expression "THEN" statement 141 | case If(Expression, RelOp, Expression, Box) 142 | 143 | /// "REM" commentstring 144 | case Rem(String) 145 | 146 | /// "CLEAR" 147 | case Clear 148 | 149 | /// "RUN" 150 | case Run 151 | 152 | /// "END" 153 | case End 154 | 155 | /// "LIST" [ expression, [ expression ] ] 156 | case List(ListRange) 157 | 158 | /// "SAVE" filenamestring 159 | case Save(String) 160 | 161 | /// "LOAD" filenamestring 162 | case Load(String) 163 | 164 | /// "FILES" 165 | case Files 166 | 167 | /// "CLIPSAVE" 168 | case ClipSave 169 | 170 | /// "CLIPLOAD" 171 | case ClipLoad 172 | 173 | /// "TRON" 174 | case Tron 175 | 176 | /// "TROFF" 177 | case Troff 178 | 179 | /// "BYE" 180 | case Bye 181 | 182 | /// "HELP" 183 | case Help 184 | 185 | 186 | /// Return pretty-printed statement 187 | var listText: String { 188 | switch self { 189 | 190 | case let .Print(printList): 191 | return "\(T_PRINT) \(printList.listText)" 192 | 193 | case .PrintNewline: 194 | return T_PRINT 195 | 196 | case let .Input(varlist): 197 | return "\(T_INPUT) \(varlist.listText)" 198 | 199 | case let .Let(lvalue, expr): 200 | return "\(T_LET) \(lvalue.listText) \(T_Equal) \(expr.listText)" 201 | 202 | case let .DimArray(expr): 203 | return "\(T_DIM) \(T_At)\(T_LParen)\(expr.listText)\(T_RParen)" 204 | 205 | case let .Goto(expr): 206 | return "\(T_GOTO) \(expr.listText)" 207 | 208 | case let .Gosub(expr): 209 | return "\(T_GOSUB) \(expr.listText)" 210 | 211 | case .Return: 212 | return T_RETURN 213 | 214 | case let .If(lhs, relop, rhs, box): 215 | return "\(T_IF) \(lhs.listText) \(relop.listText) \(rhs.listText) \(T_THEN) \(box.value.listText)" 216 | 217 | case let .Rem(comment): 218 | return "\(T_REM)\(comment)" 219 | 220 | case .Clear: 221 | return T_CLEAR 222 | 223 | case .End: 224 | return T_END 225 | 226 | case .Run: 227 | return T_RUN 228 | 229 | case let .List(range): 230 | return "\(T_LIST)\(range.listText)" 231 | 232 | case let .Save(filename): 233 | return "\(T_SAVE) \"\(filename)\"" 234 | 235 | case let .Load(filename): 236 | return "\(T_LOAD) \"\(filename)\"" 237 | 238 | case .Files: 239 | return T_FILES 240 | 241 | case .ClipLoad: 242 | return T_CLIPLOAD 243 | 244 | case .ClipSave: 245 | return T_CLIPSAVE 246 | 247 | case .Tron: 248 | return T_TRON 249 | 250 | case .Troff: 251 | return T_TROFF 252 | 253 | case .Bye: 254 | return T_BYE 255 | 256 | case .Help: 257 | return T_HELP 258 | } 259 | } 260 | } 261 | 262 | /// An element that can be assigned a value with LET or INPUT 263 | enum Lvalue { 264 | case Var(VariableName) 265 | case ArrayElement(Expression) 266 | 267 | var listText: String { 268 | switch self { 269 | case let Var(varname): return stringFromChar(varname) 270 | case let ArrayElement(expr): return "@(\(expr.listText))" 271 | } 272 | } 273 | } 274 | 275 | /// Result of parsing a varlist 276 | enum LvalueList { 277 | /// lvalue 278 | case Item(Lvalue) 279 | 280 | /// lvalue "," lvaluelist 281 | case Items(Lvalue, Box) 282 | 283 | 284 | /// Return pretty-printed program text 285 | var listText: String { 286 | switch self { 287 | case let .Item(lvalue): 288 | return lvalue.listText 289 | 290 | case let .Items(firstLvalue, items): 291 | var result = firstLvalue.listText 292 | 293 | var next = items.value 294 | loop: while true { 295 | switch next { 296 | case let .Item(lastVarName): 297 | result.extend(", \(lastVarName.listText)") 298 | break loop 299 | case let .Items(lvalue, box): 300 | result.extend(", \(lvalue.listText)") 301 | next = box.value 302 | } 303 | } 304 | 305 | return result 306 | } 307 | } 308 | 309 | /// Return the lvalues as an array 310 | var asArray: [Lvalue] { 311 | switch self { 312 | case let .Item(lvalue): 313 | return [lvalue] 314 | 315 | case let .Items(firstLvalue, items): 316 | var result = [firstLvalue] 317 | 318 | var next = items.value 319 | loop: while true { 320 | switch next { 321 | case let .Item(lastLvalue): 322 | result.append(lastLvalue) 323 | break loop 324 | case let .Items(lvalue, tail): 325 | result.append(lvalue) 326 | next = tail.value 327 | } 328 | } 329 | 330 | return result 331 | } 332 | } 333 | } 334 | 335 | /// Protocol supported by elements that provide text for the PRINT statement 336 | protocol PrintTextProvider { 337 | /// Return output text associated with this element 338 | func printText(v: VariableBindings, _ a: [Number]) -> [Char] 339 | } 340 | 341 | /// Result of parsing a printlist 342 | enum PrintList { 343 | /// expression 344 | case Item(PrintItem, PrintListTerminator) 345 | 346 | /// expression "," exprlist 347 | case Items(PrintItem, PrintListSeparator, Box) 348 | 349 | 350 | /// Return pretty-printed program text 351 | var listText: String { 352 | switch self { 353 | case let .Item(printItem, terminator): 354 | return "\(printItem.listText)\(terminator.listText)" 355 | 356 | case let .Items(printItem, sep, printItems): 357 | var result = "\(printItem.listText)\(sep.listText) " 358 | 359 | var x = printItems.value 360 | loop: while true { 361 | switch x { 362 | case let .Item(item, terminator): 363 | result.extend("\(item.listText)\(terminator.listText)") 364 | break loop 365 | case let .Items(item, sep, box): 366 | result.extend("\(item.listText)\(sep.listText) ") 367 | x = box.value 368 | } 369 | } 370 | 371 | return result 372 | } 373 | } 374 | } 375 | 376 | /// Items in a PrintList can be separated by a comma, which causes a tab 377 | /// character to be output between each character, or a semicolon, which 378 | /// causes items to be printed with no separator. 379 | enum PrintListSeparator: PrintTextProvider { 380 | case Tab 381 | case Empty 382 | 383 | /// Return text that should be included in output for this element 384 | func printText(v: VariableBindings, _ a: [Number]) -> [Char] { 385 | switch self { 386 | case .Tab: return [Ch_Tab] 387 | case .Empty: return [] 388 | } 389 | } 390 | 391 | /// Return pretty-printed program text 392 | var listText: String { 393 | switch self { 394 | case .Tab: return "," 395 | case .Empty: return ";" 396 | } 397 | } 398 | } 399 | 400 | /// A PrintList can end with a semicolon, indicating that there should 401 | /// be no separation from subsequent PRINT output, with a comma, 402 | /// indicating that a tab character should be the separator, or 403 | /// with nothing, indicating that a newline character should terminate 404 | /// the output. 405 | enum PrintListTerminator: PrintTextProvider { 406 | case Newline 407 | case Tab 408 | case Empty 409 | 410 | /// Return text that should be included in output for this element 411 | func printText(v: VariableBindings, _ a: [Number]) -> [Char] { 412 | switch self { 413 | case .Newline: return [Ch_Linefeed] 414 | case .Tab: return [Ch_Tab] 415 | case .Empty: return [] 416 | } 417 | } 418 | 419 | /// Return pretty-printed program text 420 | var listText: String { 421 | switch self { 422 | case .Newline: return "" 423 | case .Tab: return "," 424 | case .Empty: return ";" 425 | } 426 | } 427 | } 428 | 429 | /// Result of parsing an exprlist 430 | enum PrintItem: PrintTextProvider { 431 | /// expression 432 | case Expr(Expression) 433 | 434 | /// '"' string '"' 435 | case Str([Char]) 436 | 437 | /// Return text that should be included in output for this element 438 | func printText(v: VariableBindings, _ a: [Number]) -> [Char] { 439 | switch self { 440 | case let .Str(chars): return chars 441 | case let .Expr(expression): return charsFromString("\(expression.evaluate(v, a))") 442 | } 443 | } 444 | 445 | /// Return pretty-printed program text 446 | var listText: String { 447 | switch self { 448 | case let .Expr(expression): return expression.listText 449 | case let .Str(chars): return "\"\(stringFromChars(chars))\"" 450 | } 451 | } 452 | } 453 | 454 | /// Result of parsing an expression 455 | enum Expression { 456 | /// unsignedexpression 457 | case UnsignedExpr(UnsignedExpression) 458 | 459 | /// "+" unsignedexpression 460 | case Plus(UnsignedExpression) 461 | 462 | /// "-" unsignedexpression 463 | case Minus(UnsignedExpression) 464 | 465 | 466 | /// Return program text 467 | var listText: String { 468 | switch self { 469 | case let .UnsignedExpr(uexpr): 470 | return uexpr.listText 471 | 472 | case let .Plus(uexpr): 473 | return "\(T_Plus)\(uexpr.listText)" 474 | 475 | case let .Minus(uexpr): 476 | return "\(T_Minus)\(uexpr.listText)" 477 | } 478 | } 479 | 480 | /// Return the value of the expression 481 | func evaluate(v: VariableBindings, _ a: [Number]) -> Number { 482 | switch self { 483 | 484 | case let .UnsignedExpr(uexpr): 485 | return uexpr.evaluate(v, a) 486 | 487 | case let .Plus(uexpr): 488 | return uexpr.evaluate(v, a) 489 | 490 | case let .Minus(uexpr): 491 | switch uexpr { 492 | 493 | case .Value(_): 494 | return -(uexpr.evaluate(v, a)) 495 | 496 | case let .Compound(term, op, remainder): 497 | // Construct a new Expression with the first term negated, and evaluate that 498 | let termValue = term.evaluate(v, a) 499 | let negatedFactor = Factor.Num(-termValue) 500 | let negatedTerm = Term.Value(negatedFactor) 501 | let newExpr = Expression.UnsignedExpr(UnsignedExpression.Compound(negatedTerm, op, remainder)) 502 | return newExpr.evaluate(v, a) 503 | } 504 | } 505 | } 506 | } 507 | 508 | /// Binary operator for Numbers 509 | struct ArithOp { 510 | 511 | let fn: (Number, Number) -> Number 512 | let listText: String 513 | 514 | func apply(lhs: Number, _ rhs: Number) -> Number { 515 | return fn(lhs, rhs) 516 | } 517 | 518 | static let Add = ArithOp(fn: &+, listText: T_Plus) 519 | static let Subtract = ArithOp(fn: &-, listText: T_Minus) 520 | static let Multiply = ArithOp(fn: &*, listText: T_Asterisk) 521 | static let Divide = ArithOp(fn: /, listText: T_Slash) 522 | } 523 | 524 | /// Result of parsing an unsigned expression 525 | /// 526 | /// Note that "unsigned" means "does not have a leading + or - sign". 527 | /// It does not mean that the value is non-negative. 528 | enum UnsignedExpression { 529 | /// term 530 | case Value(Term) 531 | 532 | /// term "+" unsignedexpression 533 | /// term "-" unsignedexpression 534 | case Compound(Term, ArithOp, Box) 535 | 536 | 537 | /// Return pretty-printed program text 538 | var listText: String { 539 | switch self { 540 | case let .Value(term): 541 | return term.listText 542 | 543 | case let .Compound(term, op, boxedExpr): 544 | return "\(term.listText) \(op.listText) \(boxedExpr.value.listText)" 545 | } 546 | } 547 | 548 | /// Evaluate the expression using the specified bindings 549 | func evaluate(v: VariableBindings, _ a: [Number]) -> Number { 550 | 551 | switch self { 552 | case let .Value(t): 553 | return t.evaluate(v, a) 554 | 555 | case let .Compound(t, op, uexpr): 556 | var accumulator = t.evaluate(v, a) 557 | var lastOp = op 558 | 559 | var next = uexpr.value 560 | while true { 561 | switch next { 562 | case let .Value(lastTerm): 563 | return lastOp.apply(accumulator, lastTerm.evaluate(v, a)) 564 | 565 | case let .Compound(nextTerm, op, tail): 566 | accumulator = lastOp.apply(accumulator, nextTerm.evaluate(v, a)) 567 | lastOp = op 568 | next = tail.value 569 | } 570 | } 571 | } 572 | } 573 | } 574 | 575 | /// Result of parsing a term 576 | enum Term { 577 | /// factor 578 | case Value(Factor) 579 | 580 | /// factor "*" term 581 | /// factor "/" term 582 | case Compound(Factor, ArithOp, Box) 583 | 584 | 585 | /// Return pretty-printed program text 586 | var listText: String { 587 | switch self { 588 | 589 | case let .Value(factor): 590 | return factor.listText 591 | 592 | case let .Compound(factor, op, boxedTerm): 593 | return "\(factor.listText) \(op.listText) \(boxedTerm.value.listText)" 594 | } 595 | } 596 | 597 | /// Evaluate the expression using the specified bindings 598 | func evaluate(v: VariableBindings, _ a: [Number]) -> Number { 599 | 600 | switch self { 601 | case let .Value(fact): 602 | return fact.evaluate(v, a) 603 | 604 | case let .Compound(fact, op, trm): 605 | var accumulator = fact.evaluate(v, a) 606 | var lastOp = op 607 | 608 | var next = trm.value 609 | while true { 610 | switch next { 611 | case let .Value(lastFact): 612 | return lastOp.apply(accumulator, lastFact.evaluate(v, a)) 613 | 614 | case let .Compound(fact, op, tail): 615 | accumulator = lastOp.apply(accumulator, fact.evaluate(v, a)) 616 | lastOp = op 617 | next = tail.value 618 | } 619 | } 620 | } 621 | } 622 | } 623 | 624 | /// Result of parsing a factor 625 | enum Factor { 626 | /// var 627 | case Var(VariableName) 628 | 629 | /// "@(" expression ")" 630 | case ArrayElement(Box) 631 | 632 | /// number 633 | case Num(Number) 634 | 635 | /// "(" expression ")" 636 | case ParenExpr(Box) 637 | 638 | /// "RND(" expression ")" 639 | case Rnd(Box) 640 | 641 | 642 | /// Return pretty-printed program text 643 | var listText: String { 644 | switch self { 645 | case let .Var(varname): return stringFromChar(varname) 646 | case let .ArrayElement(expr): return "@(\(expr.value.listText))" 647 | case let .Num(number): return "\(number)" 648 | case let .ParenExpr(expr): return "(\(expr.value.listText))" 649 | case let .Rnd(expr): return "\(T_RND)(\(expr.value.listText))" 650 | } 651 | } 652 | 653 | /// Return the value of this Term 654 | func evaluate(v: VariableBindings, _ a: [Number]) -> Number { 655 | switch self { 656 | case let .Var(varname): return v[varname] ?? 0 657 | case let .Num(number): return number 658 | case let .ParenExpr(expr): return expr.value.evaluate(v, a) 659 | 660 | case let .ArrayElement(expr): 661 | let index = expr.value.evaluate(v, a) 662 | let remainderIndex = index % a.count 663 | if remainderIndex < 0 { 664 | return a[a.count + remainderIndex] 665 | } 666 | else { 667 | return a[remainderIndex] 668 | } 669 | 670 | case let .Rnd(expr): 671 | let n = expr.value.evaluate(v, a) 672 | if n < 1 { 673 | // TODO: signal a runtime error? 674 | return 0 675 | } 676 | return Number(arc4random_uniform(UInt32(n))) 677 | } 678 | } 679 | } 680 | 681 | /// Result of parsing a relational operator 682 | enum RelOp { 683 | /// "<" 684 | case Less 685 | 686 | /// ">" 687 | case Greater 688 | 689 | /// "=" 690 | case Equal 691 | 692 | /// "<=" 693 | case LessOrEqual 694 | 695 | /// ">=" 696 | case GreaterOrEqual 697 | 698 | /// "<>" or "><" 699 | case NotEqual 700 | 701 | 702 | /// Return pretty-printed program text 703 | var listText: String { 704 | switch self { 705 | case .Less: return T_Less 706 | case .Greater: return T_Greater 707 | case .Equal: return T_Equal 708 | case .LessOrEqual: return T_LessOrEqual 709 | case .GreaterOrEqual: return T_GreaterOrEqual 710 | case .NotEqual: return T_NotEqual 711 | } 712 | } 713 | 714 | /// Determine whether the relation is true for specified values 715 | func isTrueForNumbers(lhs: Number, _ rhs: Number) -> Bool { 716 | switch self { 717 | case .Less: return lhs < rhs 718 | case .Greater: return lhs > rhs 719 | case .Equal: return lhs == rhs 720 | case .LessOrEqual: return lhs <= rhs 721 | case .GreaterOrEqual: return lhs >= rhs 722 | case .NotEqual: return lhs != rhs 723 | } 724 | } 725 | } 726 | 727 | /// Range of lines for a LIST operation 728 | enum ListRange { 729 | /// List all lines 730 | case All 731 | 732 | /// List a single line 733 | case SingleLine(Expression) 734 | 735 | /// List all lines within an inclusive range 736 | case Range(Expression, Expression) 737 | 738 | /// Return pretty-printed representation 739 | var listText: String { 740 | switch self { 741 | case .All: return "" 742 | case let .SingleLine(expr): return " \(expr.listText)" 743 | case let .Range(from, to): return " \(from.listText), \(to.listText)" 744 | } 745 | } 746 | } 747 | 748 | 749 | /// A program is a sequence of numbered statements 750 | typealias Program = [(Number, Statement)] 751 | 752 | /// An input line is parsed to be a statement preceded by a line number, 753 | /// which will be inserted into the program, or a statement without a preceding 754 | /// line number, which will be executed immediately. 755 | /// 756 | /// Also possible are empty input lines, which are ignored, or unparseable 757 | /// input lines, which generate an error message. 758 | enum Line { 759 | // Parsed statement with a line number 760 | case NumberedStatement(Number, Statement) 761 | 762 | // Parsed statement without a preceding line number 763 | case UnnumberedStatement(Statement) 764 | 765 | // Empty input line 766 | case Empty 767 | 768 | // Input line with only a line number 769 | case EmptyNumberedLine(Number) 770 | 771 | // Error occurred while parsing the line, resulting in error message 772 | case Error(String) 773 | } 774 | -------------------------------------------------------------------------------- /finchlib/util.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2014, 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | import Foundation 25 | 26 | // Holds reference to a value 27 | // 28 | // Workaround for Swift's inability to define value types recursively 29 | public class Box { 30 | public var value: T 31 | 32 | public init(_ value: T) { 33 | self.value = value 34 | } 35 | } 36 | 37 | /// Given list of strings, produce single string with newlines between those strings 38 | public func lines(strings: String...) -> String { 39 | return "\n".join(strings) 40 | } 41 | 42 | /// Given list of strings, produce single string with newlines between those strings 43 | public func lines(strings: [String]) -> String { 44 | return "\n".join(strings) 45 | } 46 | 47 | /// Return error message associated with current value of `errno` 48 | public func errnoMessage() -> String { 49 | let err = strerror(errno) 50 | return String.fromCString(err) ?? "unknown error" 51 | } 52 | -------------------------------------------------------------------------------- /finchlibTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | net.kristopherjohnson.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /finchlib_cpp/.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Webkit 2 | BreakBeforeBraces: Allman 3 | BreakConstructorInitializersBeforeComma: false 4 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 5 | Cpp11BracedListStyle: true 6 | IndentCaseLabels: true 7 | MaxEmptyLinesToKeep: 2 8 | PointerBindsToType: false 9 | SpacesBeforeTrailingComments: 2 10 | Standard: Cpp11 11 | 12 | -------------------------------------------------------------------------------- /finchlib_cpp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | net.kristopherjohnson.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2015 Kristopher Johnson. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /finchlib_cpp/Interpreter.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #import 25 | 26 | // Note: This file is included by Objective-C and Swift code, 27 | // so it must not contain any C++ declarations. 28 | 29 | @class Interpreter; 30 | 31 | typedef unsigned char Char; 32 | typedef int Number; 33 | 34 | typedef NS_ENUM(NSInteger, InputResultKind) 35 | { 36 | InputResultKindValue, 37 | InputResultKindEndOfStream, 38 | InputResultKindWaiting 39 | }; 40 | 41 | typedef struct 42 | { 43 | InputResultKind kind; 44 | Char value; // only used when kind == InputResultKindValue 45 | } InputCharResult; 46 | 47 | // Functions for creating an InputCharResult with the appropriate kind 48 | __BEGIN_DECLS 49 | InputCharResult InputCharResult_Value(Char c); 50 | InputCharResult InputCharResult_EndOfStream(); 51 | InputCharResult InputCharResult_Waiting(); 52 | __END_DECLS 53 | 54 | /// Protocol implemented by object that provides I/O operations for an 55 | /// Interpreter 56 | @protocol InterpreterIO 57 | 58 | /// Return next input character, or nil if at end-of-file or an error occurs 59 | - (InputCharResult)getInputCharForInterpreter:(Interpreter *)interpreter; 60 | 61 | /// Write specified output character 62 | - (void)putOutputChar:(Char)c forInterpreter:(Interpreter *)interpreter; 63 | 64 | /// Display a prompt to the user for entering an immediate command or line of 65 | /// code 66 | - (void)showCommandPromptForInterpreter:(Interpreter *)interpreter; 67 | 68 | /// Display a prompt to the user for entering data for an INPUT statement 69 | - (void)showInputPromptForInterpreter:(Interpreter *)interpreter; 70 | 71 | /// Display error message to user 72 | - (void)showErrorMessage:(NSString *)message 73 | forInterpreter:(Interpreter *)interpreter; 74 | 75 | /// Display a debug trace message 76 | - (void)showDebugTraceMessage:(NSString *)message 77 | forInterpreter:(Interpreter *)interpreter; 78 | 79 | /// Called when BYE is executed 80 | - (void)byeForInterpreter:(Interpreter *)interpreter; 81 | 82 | @end 83 | 84 | /// State of the interpreter 85 | /// 86 | /// The interpreter begins in the `.Idle` state, which 87 | /// causes it to immediately display a statement prompt 88 | /// and then enter the `.ReadingStatement` state, where it 89 | /// will process numbered and unnumbered statements. 90 | /// 91 | /// A `RUN` statement will put it into `.Running` state, and it 92 | /// will execute the stored program. If an `INPUT` statement 93 | /// is executed, the interpreter will go into .ReadingInput 94 | /// state until valid input is received, and it will return 95 | /// to `.Running` state. 96 | /// 97 | /// The state returns to `.ReadingStatement` on an `END` 98 | /// statement or if `RUN` has to abort due to an error. 99 | typedef NS_ENUM(NSInteger, InterpreterState) 100 | { 101 | /// Interpreter is not "doing anything". 102 | /// 103 | /// When in this state, interpreter will display 104 | /// statement prompt and then enter the 105 | /// `ReadingStatement` state. 106 | InterpreterStateIdle, 107 | 108 | /// Interpreter is trying to read a statement/command 109 | InterpreterStateReadingStatement, 110 | 111 | /// Interpreter is running a program 112 | InterpreterStateRunning, 113 | 114 | /// Interpreter is processing an `INPUT` statement 115 | InterpreterStateReadingInput 116 | }; 117 | 118 | @interface Interpreter : NSObject 119 | 120 | @property id io; 121 | 122 | /// Initializer 123 | - (instancetype)initWithInterpreterIO:(id)interpreterIO; 124 | 125 | /// Return the state of the interpreter as a property-list dictionary. 126 | /// 127 | /// This property list can be used to restore interpreter state 128 | /// with restoreStateFromPropertyList() 129 | - (NSDictionary *)stateAsPropertyList; 130 | 131 | /// Set interpreter's properties using archived state produced by stateAsPropertyList()' 132 | - (void)restoreStateFromPropertyList:(NSDictionary *)propertyList; 133 | 134 | /// Display prompt and read input lines and interpret them until end of input. 135 | /// 136 | /// This method should only be used when `InterpreterIO.getInputChar()` 137 | /// will never return `InputCharResult.Waiting`. 138 | /// Otherwise, host should call `next()` in a loop. 139 | - (void)runUntilEndOfInput; 140 | 141 | /// Perform next operation. 142 | /// 143 | /// The host can drive the interpreter by calling `next()` 144 | /// in a loop. 145 | - (void)next; 146 | 147 | /// Return interpreter state 148 | - (InterpreterState)state; 149 | 150 | /// Halt running machine 151 | - (void)breakExecution; 152 | 153 | @end 154 | -------------------------------------------------------------------------------- /finchlib_cpp/Interpreter.mm: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #import "Interpreter.h" 25 | #import "InterpreterEngine.h" 26 | 27 | using namespace finchlib_cpp; 28 | 29 | 30 | static NSString *InterpreterPropertyListKey = @"InterpreterPropertyList"; 31 | 32 | 33 | InputCharResult InputCharResult_Value(Char c) 34 | { 35 | return {InputResultKindValue, c}; 36 | } 37 | 38 | InputCharResult InputCharResult_EndOfStream() 39 | { 40 | return {InputResultKindEndOfStream, 0}; 41 | } 42 | 43 | InputCharResult InputCharResult_Waiting() 44 | { 45 | return {InputResultKindWaiting, 0}; 46 | } 47 | 48 | 49 | @implementation Interpreter 50 | { 51 | // The real implementation is in the C++ InterpreterEngine class. 52 | InterpreterEngine *_engine; 53 | } 54 | 55 | - (instancetype)initWithInterpreterIO:(id)interpreterIO 56 | { 57 | self = [super init]; 58 | if (!self) 59 | return nil; 60 | 61 | self.io = interpreterIO; 62 | 63 | _engine = new InterpreterEngine(self); 64 | 65 | return self; 66 | } 67 | 68 | - (instancetype)initWithCoder:(NSCoder *)coder 69 | { 70 | self = [super init]; 71 | if (!self) 72 | return nil; 73 | 74 | _engine = new InterpreterEngine(self); 75 | 76 | NSDictionary *propertyList = [coder decodeObjectForKey:InterpreterPropertyListKey]; 77 | if (propertyList) 78 | { 79 | _engine->restoreStateFromPropertyList(propertyList); 80 | } 81 | else 82 | { 83 | NSAssert(false, @"unable to decode property list"); 84 | } 85 | 86 | return self; 87 | } 88 | 89 | - (void)dealloc 90 | { 91 | delete _engine; 92 | } 93 | 94 | - (void)encodeWithCoder:(NSCoder *)coder 95 | { 96 | NSDictionary *propertyList = _engine->stateAsPropertyList(); 97 | [coder encodeObject:propertyList forKey:InterpreterPropertyListKey]; 98 | } 99 | 100 | - (NSDictionary *)stateAsPropertyList 101 | { 102 | return _engine->stateAsPropertyList(); 103 | } 104 | 105 | - (void)restoreStateFromPropertyList:(NSDictionary *)propertyList 106 | { 107 | _engine->restoreStateFromPropertyList(propertyList); 108 | } 109 | 110 | - (void)runUntilEndOfInput 111 | { 112 | _engine->runUntilEndOfInput(); 113 | } 114 | 115 | - (void)next 116 | { 117 | _engine->next(); 118 | } 119 | 120 | - (InterpreterState)state 121 | { 122 | return _engine->state(); 123 | } 124 | 125 | - (void)breakExecution 126 | { 127 | _engine->breakExecution(); 128 | } 129 | 130 | @end 131 | -------------------------------------------------------------------------------- /finchlib_cpp/InterpreterEngine.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #ifndef __finchbasic__InterpreterEngine__ 25 | #define __finchbasic__InterpreterEngine__ 26 | 27 | #import "Interpreter.h" 28 | #import "syntax.h" 29 | 30 | namespace finchlib_cpp 31 | { 32 | 33 | using InputLine = vec; 34 | 35 | /// Result of attempting to read a line of input 36 | struct InputLineResult 37 | { 38 | InputResultKind kind; 39 | InputLine value; // only used when kind == InputResultKindValue 40 | 41 | static InputLineResult inputLine(const InputLine &input) 42 | { 43 | return {InputResultKindValue, input}; 44 | } 45 | 46 | static InputLineResult endOfStream() { return {InputResultKindEndOfStream}; } 47 | 48 | static InputLineResult waiting() { return {InputResultKindWaiting}; } 49 | }; 50 | 51 | #pragma mark - InterpreterEngine 52 | 53 | class InterpreterEngine 54 | { 55 | public: 56 | /// Constructor 57 | InterpreterEngine(Interpreter *interpreter); 58 | 59 | /// Return the state of the interpreter as a property-list dictionary. 60 | /// 61 | /// This property list can be used to restore interpreter state 62 | /// with restoreStateFromPropertyList() 63 | NSDictionary *stateAsPropertyList(); 64 | 65 | /// Set interpreter's properties using archived state produced by stateAsPropertyList()' 66 | void restoreStateFromPropertyList(NSDictionary *propertyList); 67 | 68 | /// Display prompt and read input lines and interpret them until end of input. 69 | /// 70 | /// This method should only be used when `InterpreterIO.getInputChar()` 71 | /// will never return `InputCharResult.Waiting`. 72 | /// Otherwise, host should call `next()` in a loop. 73 | void runUntilEndOfInput(); 74 | 75 | /// Perform next operation. 76 | /// 77 | /// The host can drive the interpreter by calling `next()` 78 | /// in a loop. 79 | void next(); 80 | 81 | /// Return interpreter state 82 | InterpreterState state(); 83 | 84 | /// Halt running machine 85 | void breakExecution(); 86 | 87 | /// Execute a PRINT statement with arguments 88 | void PRINT(const PrintList &printList); 89 | 90 | /// Execute a PRINT statement that takes no arguments 91 | void PRINT(); 92 | 93 | /// Execute an INPUT statement 94 | void INPUT(const Lvalues &lvalues); 95 | 96 | /// Execute a LIST statement 97 | void LIST(const Expression &lowExpr, const Expression &highExpr); 98 | 99 | /// Execute an IF statement 100 | void IF(const Expression &lhs, const RelOp &op, const Expression &rhs, 101 | const Statement &consequent); 102 | 103 | /// Execute a RUN statement 104 | void RUN(); 105 | 106 | /// Execute END statement 107 | void END(); 108 | 109 | /// Execute GOTO statement 110 | void GOTO(const Expression &lineNumber); 111 | 112 | /// Execute GOSUB statement 113 | void GOSUB(const Expression &lineNumber); 114 | 115 | /// Execute RETURN statement 116 | void RETURN(); 117 | 118 | /// Execute CLEAR statement 119 | void CLEAR(); 120 | 121 | /// Execute BYE statement 122 | void BYE(); 123 | 124 | /// Execute HELP statement 125 | void HELP(); 126 | 127 | /// Execute a DIM statement 128 | void DIM(const Expression &expr); 129 | 130 | /// Execute a SAVE statement 131 | void SAVE(const string &filename); 132 | 133 | /// Execute a LOAD statement 134 | void LOAD(const string &filename); 135 | 136 | /// Execute a FILES statement 137 | void FILES(); 138 | 139 | /// Execute a CLIPSAVE statement 140 | void CLIPSAVE(); 141 | 142 | /// Execute a CLIPLOAD statement 143 | void CLIPLOAD(); 144 | 145 | /// Execute a TRON statement 146 | void TRON(); 147 | 148 | /// Execute a TROFF statement 149 | void TROFF(); 150 | 151 | /// Evaluate an expression 152 | Number evaluate(const Expression &expr); 153 | 154 | Number getVariableValue(VariableName variableName) const; 155 | void setVariableValue(VariableName variableName, Number value); 156 | 157 | Number getArrayElementValue(Number index); 158 | void setArrayElementValue(Number index, Number value); 159 | void setArrayElementValue(const Expression &indexExpression, Number value); 160 | 161 | private: 162 | #pragma mark - Private data members 163 | 164 | /// Interpreter instance that owns this engine 165 | Interpreter *interpreter; 166 | 167 | /// Interpreter state 168 | InterpreterState st{InterpreterStateIdle}; 169 | 170 | /// Variable values 171 | VariableBindings v; 172 | 173 | /// Array of numbers, addressable using the syntax "@(i)" 174 | Numbers a; 175 | 176 | /// Characters that have been read from input but not yet been returned by 177 | /// readInputLine() 178 | InputLine inputLineBuffer; 179 | 180 | /// Array of program lines 181 | Program program; 182 | 183 | /// Index of currently executing line in program 184 | size_t programIndex{0}; 185 | 186 | /// Return stack used by GOSUB/RETURN 187 | ReturnStack returnStack; 188 | 189 | /// If true, print line numbers while program runs 190 | bool isTraceOn{false}; 191 | 192 | /// If true, have encountered EOF while processing input 193 | bool hasReachedEndOfInput{false}; 194 | 195 | /// Lvalues being read by current INPUT statement 196 | Lvalues inputLvalues; 197 | 198 | /// State that interpreter was in when INPUT was called 199 | InterpreterState stateBeforeInput{InterpreterStateIdle}; 200 | 201 | #pragma mark - Private methods 202 | 203 | /// Return the entire program listing as a single String 204 | string programAsString(); 205 | 206 | /// Interpret a string 207 | void interpretString(const string &s); 208 | 209 | /// Set values of all variables and array elements to zero 210 | void clearVariablesAndArray(); 211 | 212 | /// Remove program from memory 213 | void clearProgram(); 214 | 215 | /// Remove all items from the return stack 216 | void clearReturnStack(); 217 | 218 | /// Parse an input line and execute it or add it to the program 219 | void processInput(const InputLine &input); 220 | 221 | struct Line parseInputLine(const InputLine &input); 222 | 223 | void insertLineIntoProgram(Number lineNumber, Statement statement); 224 | 225 | /// Delete the line with the specified number from the program. 226 | /// 227 | /// No effect if there is no such line. 228 | void deleteLineFromProgram(Number lineNumber); 229 | 230 | Program::iterator programLineWithNumber(Number lineNumber); 231 | 232 | /// Return line number of the last line in the program. 233 | /// 234 | /// Returns 0 if there is no program. 235 | Number getLastProgramLineNumber(); 236 | 237 | void execute(Statement s); 238 | 239 | void executeNextProgramStatement(); 240 | 241 | /// Display error message and stop running 242 | /// 243 | /// Call this method if an unrecoverable error happens while executing a 244 | /// statement 245 | void abortRunWithErrorMessage(string message); 246 | 247 | /// Send a single character to the output stream 248 | void writeOutput(Char c); 249 | 250 | /// Send characters to the output stream 251 | void writeOutput(const vec &chars); 252 | 253 | /// Send string to the output stream 254 | void writeOutput(const string &s); 255 | 256 | /// Print an object that conforms to the PrintTextProvider protocol 257 | void writeOutput(const PrintTextProvider &p); 258 | 259 | /// Display error message 260 | void showError(const string &message); 261 | 262 | /// Read a line using the InterpreterIO interface. 263 | /// 264 | /// Return array of characters, or nil if at end of input stream. 265 | /// 266 | /// Result does not include any non-graphic characters that were in the input 267 | /// stream. 268 | /// Any horizontal tab ('\t') in the input will be converted to a single 269 | /// space. 270 | /// 271 | /// Result may be an empty array, indicating an empty input line, not end of 272 | /// input. 273 | InputLineResult readInputLine(); 274 | 275 | /// Get a line of input, using specified function to retrieve characters. 276 | /// 277 | /// Result does not include any non-graphic characters that were in the input 278 | /// stream. 279 | /// Any horizontal tab ('\t') in the input will be converted to a single 280 | /// space. 281 | InputLineResult getInputLine(function getChar); 282 | 283 | /// Perform an INPUT operation 284 | /// 285 | /// This may be called by INPUT(), or by next() if resuming an operation 286 | /// following a .Waiting result from readInputLine() 287 | void continueInput(); 288 | 289 | /// Display error message to user during an INPUT operation 290 | void showInputHelpMessage(); 291 | }; 292 | 293 | } // namespace finchlib_cpp 294 | 295 | #endif /* defined(__finchbasic__InterpreterEngine__) */ 296 | -------------------------------------------------------------------------------- /finchlib_cpp/cppdefs.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #ifndef finchlib_cpp_cppdefs_h 25 | #define finchlib_cpp_cppdefs_h 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | namespace finchlib_cpp 37 | { 38 | 39 | // Bring these types and functions from the std namespace into our namespace 40 | using std::equal_to; 41 | using std::function; 42 | using std::greater; 43 | using std::greater_equal; 44 | using std::initializer_list; 45 | using std::less; 46 | using std::less_equal; 47 | using std::make_shared; 48 | using std::make_unique; 49 | using std::map; 50 | using std::minus; 51 | using std::multiplies; 52 | using std::not_equal_to; 53 | using std::numeric_limits; 54 | using std::ostringstream; 55 | using std::plus; 56 | using std::string; 57 | using std::pair; 58 | using std::toupper; 59 | using std::tuple; 60 | 61 | // Use "sptr" as abbreviation for "std::shared_ptr" 62 | template 63 | using sptr = std::shared_ptr; 64 | 65 | // Use "uptr" as abbreviation for "std::unique_ptr" 66 | template 67 | using uptr = std::unique_ptr; 68 | 69 | // Use "vec" as abbreviation for "std::vector" 70 | template 71 | using vec = std::vector; 72 | } 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /finchlib_cpp/finchlib_cpp-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to 3 | // expose to Swift. 4 | // 5 | 6 | #import "Interpreter.h" 7 | -------------------------------------------------------------------------------- /finchlib_cpp/finchlib_cpp.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #import 25 | 26 | //! Project version number for finchlib_cpp. 27 | FOUNDATION_EXPORT double finchlib_cppVersionNumber; 28 | 29 | //! Project version string for finchlib_cpp. 30 | FOUNDATION_EXPORT const unsigned char finchlib_cppVersionString[]; 31 | 32 | // In this header, you should import all the public headers of your framework 33 | // using statements like #import 34 | -------------------------------------------------------------------------------- /finchlib_cpp/parse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #ifndef __finchbasic__parse__ 25 | #define __finchbasic__parse__ 26 | 27 | #include "InterpreterEngine.h" 28 | 29 | namespace finchlib_cpp 30 | { 31 | class InputPos; 32 | 33 | /// Result from an attempt to parse an element 34 | /// 35 | /// If `success()` is true, then call `value()` and 36 | /// `nextPos()` to get the parsed value and the position 37 | /// following the parsed element. 38 | /// 39 | /// `ParseResult` is an abstract class. Concrete subclasses 40 | /// are `NotParsed` and `Parsed`. 41 | template 42 | class ParseResult 43 | { 44 | public: 45 | /// Return true if parse was successful, false if not. 46 | virtual bool wasParsed() const = 0; 47 | 48 | // Only call these if success() returns true 49 | virtual const T *value() const = 0; 50 | virtual const InputPos *nextPos() const = 0; 51 | }; 52 | 53 | /// Concrete subclass of ParseResult for a failed parse attempt. 54 | template 55 | class NotParsed : public ParseResult 56 | { 57 | public: 58 | virtual bool wasParsed() const { return false; } 59 | virtual const T *value() const { return nullptr; } 60 | virtual const InputPos *nextPos() const { return nullptr; } 61 | }; 62 | 63 | // Parsed is defined after InputPos 64 | template 65 | struct Parsed; 66 | 67 | // Parse is defined after InputPos 68 | 69 | /// Container for a ParseResult 70 | template 71 | class Parse 72 | { 73 | private: 74 | sptr> result; 75 | 76 | public: 77 | Parse(sptr> res) : result(res) {} 78 | 79 | bool wasParsed() const { return result->wasParsed(); } 80 | 81 | // It is not valid to call value() or nextPos() if wasParsed() 82 | // is not true. The result would be an access violation. 83 | const T &value() const { return *result->value(); } 84 | const InputPos &nextPos() const { return *result->nextPos(); } 85 | }; 86 | 87 | #pragma mark - InputPos 88 | 89 | /// Current position on a line of input 90 | /// 91 | /// This encapsulates the concept of an index into a character array. 92 | /// It provides some convenient methods/properties used by the 93 | /// parsing code in `InterpreterEngine`. 94 | struct InputPos 95 | { 96 | sptr input; 97 | size_t index; 98 | 99 | InputPos(const InputLine &line, size_t n) 100 | : input{make_shared(line)}, index{n} {} 101 | 102 | InputPos(sptr line, size_t n) 103 | : input{line}, index{n} {} 104 | 105 | InputPos(const InputPos ©) 106 | : input{copy.input}, index{copy.index} {} 107 | 108 | /// Return the character at this position 109 | Char at() const 110 | { 111 | assert(!isAtEndOfLine()); 112 | return input->at(index); 113 | } 114 | 115 | /// Return true if there are no non-space characters at or following the 116 | /// specified index in the specified line 117 | bool isRemainingLineEmpty() const 118 | { 119 | return afterSpaces().index == input->size(); 120 | } 121 | 122 | /// Return number of characters following this position, including the 123 | /// character at this position) 124 | size_t remainingCount() const { return input->size() - index; } 125 | 126 | /// Return remaining characters on line, including the character at this 127 | /// position 128 | vec remainingChars() const 129 | { 130 | const auto count = input->size(); 131 | if (index >= count) 132 | { 133 | return {}; 134 | } 135 | 136 | vec result; 137 | for (auto i = index; i < count; ++i) 138 | { 139 | result.push_back(input->at(i)); 140 | } 141 | return result; 142 | } 143 | 144 | /// Return true if this position is at the end of the line 145 | bool isAtEndOfLine() const { return index >= input->size(); } 146 | 147 | /// Return the next input position 148 | InputPos next() const { return {input, index + 1}; } 149 | 150 | /// Return the position at the end of the line 151 | InputPos endOfLine() const { return {input, input->size()}; } 152 | 153 | /// Return position of first non-space character at or after this position 154 | InputPos afterSpaces() const 155 | { 156 | auto i = index; 157 | const auto count = input->size(); 158 | while (i < count && input->at(i) == ' ') 159 | { 160 | ++i; 161 | } 162 | return {input, i}; 163 | } 164 | 165 | // The parse() methods take a starting position and a sequence 166 | // of "parsing functions" to apply in order. 167 | // 168 | // Each parsing function takes an `InputPos` and returns a 169 | // `Parse>`, where `T` is the type of data parsed. 170 | // 171 | // `parse()` returns a tuple containing all the parsed elements 172 | // and the following `InputPos`, or returns `nullptr` if any 173 | // of the parsing functions fail. 174 | // 175 | // This allows us to write pattern-matching-like parsing code like this: 176 | // 177 | // // Try to parse "LET var = expr" 178 | // uptr> t 180 | // = pos.parse 181 | // (lit("LET"), variable, lit("="), expression); 182 | // if (t != nullptr) 183 | // { 184 | // // do something with the five-element tuple *t 185 | // // ... 186 | // } 187 | // 188 | // which is equivalent to this: 189 | // 190 | // const auto keyword = lit("LET")(pos); 191 | // if (keyword.wasParsed()) { 192 | // const auto varname = variable(keyword.nextPos()); 193 | // if (varname) { 194 | // const auto eq = lit("=")(varname.nextPos()); 195 | // if (varname.wasParsed()) { 196 | // const auto expr = expression(eq.nextPos()); 197 | // if (expr) { 198 | // // do something with keyword, varname, eq, and expr 199 | // } 200 | // } 201 | // } 202 | // } 203 | // 204 | // where `lit(String)`, `variable`, and `expression` are 205 | // functions that take an `InputPos` and return a Parse. 206 | 207 | template 208 | uptr> 209 | parse(function(const InputPos &)> a) const 210 | { 211 | const auto aP = a(*this); 212 | if (aP.wasParsed()) 213 | { 214 | return make_unique>( 215 | aP.value(), aP.nextPos()); 216 | } 217 | 218 | return nullptr; 219 | } 220 | 221 | template 222 | uptr> 223 | parse(function(const InputPos &)> a, 224 | function(const InputPos &)> b) const 225 | { 226 | const Parse aP = a(*this); 227 | if (aP.wasParsed()) 228 | { 229 | const Parse bP = b(aP.nextPos()); 230 | if (bP.wasParsed()) 231 | { 232 | return make_unique>( 233 | aP.value(), bP.value(), 234 | bP.nextPos()); 235 | } 236 | } 237 | 238 | return nullptr; 239 | } 240 | 241 | template 242 | uptr> 243 | parse(function(const InputPos &)> a, 244 | function(const InputPos &)> b, 245 | function(const InputPos &)> c) const 246 | { 247 | const Parse aP = a(*this); 248 | if (aP.wasParsed()) 249 | { 250 | const Parse bP = b(aP.nextPos()); 251 | if (bP.wasParsed()) 252 | { 253 | const Parse cP = c(bP.nextPos()); 254 | if (cP.wasParsed()) 255 | { 256 | return make_unique>( 257 | aP.value(), bP.value(), 258 | cP.value(), cP.nextPos()); 259 | } 260 | } 261 | } 262 | 263 | return nullptr; 264 | } 265 | 266 | template 267 | uptr> 268 | parse(function(const InputPos &)> a, 269 | function(const InputPos &)> b, 270 | function(const InputPos &)> c, 271 | function(const InputPos &)> d) const 272 | { 273 | const Parse aP = a(*this); 274 | if (aP.wasParsed()) 275 | { 276 | const Parse bP = b(aP.nextPos()); 277 | if (bP.wasParsed()) 278 | { 279 | const Parse cP = c(bP.nextPos()); 280 | if (cP.wasParsed()) 281 | { 282 | const Parse dP = d(cP.nextPos()); 283 | if (dP.wasParsed()) 284 | { 285 | return make_unique>( 286 | aP.value(), bP.value(), 287 | cP.value(), dP.value(), 288 | dP.nextPos()); 289 | } 290 | } 291 | } 292 | } 293 | 294 | return nullptr; 295 | } 296 | 297 | template 298 | uptr> 299 | parse(function(const InputPos &)> a, 300 | function(const InputPos &)> b, 301 | function(const InputPos &)> c, 302 | function(const InputPos &)> d, 303 | function(const InputPos &)> e) const 304 | { 305 | const Parse aP = a(*this); 306 | if (aP.wasParsed()) 307 | { 308 | const Parse bP = b(aP.nextPos()); 309 | if (bP.wasParsed()) 310 | { 311 | const Parse cP = c(bP.nextPos()); 312 | if (cP.wasParsed()) 313 | { 314 | const Parse dP = d(cP.nextPos()); 315 | if (dP.wasParsed()) 316 | { 317 | const Parse eP = e(dP.nextPos()); 318 | if (eP.wasParsed()) 319 | { 320 | return make_unique>( 321 | aP.value(), bP.value(), cP.value(), dP.value(), 322 | eP.value(), eP.nextPos()); 323 | } 324 | } 325 | } 326 | } 327 | } 328 | 329 | return nullptr; 330 | } 331 | 332 | template 334 | uptr> 335 | parse(function(const InputPos &)> a, 336 | function(const InputPos &)> b, 337 | function(const InputPos &)> c, 338 | function(const InputPos &)> d, 339 | function(const InputPos &)> e, 340 | function(const InputPos &)> f) const 341 | { 342 | const Parse aP = a(*this); 343 | if (aP.wasParsed()) 344 | { 345 | const Parse bP = b(aP.nextPos()); 346 | if (bP.wasParsed()) 347 | { 348 | const Parse cP = c(bP.nextPos()); 349 | if (cP.wasParsed()) 350 | { 351 | const Parse dP = d(cP.nextPos()); 352 | if (dP.wasParsed()) 353 | { 354 | const Parse eP = e(dP.nextPos()); 355 | if (eP.wasParsed()) 356 | { 357 | const Parse fP = f(eP.nextPos()); 358 | if (fP.wasParsed()) 359 | { 360 | return make_unique>( 361 | aP.value(), bP.value(), cP.value(), dP.value(), 362 | eP.value(), fP.value(), fP.nextPos()); 363 | } 364 | } 365 | } 366 | } 367 | } 368 | } 369 | 370 | return nullptr; 371 | } 372 | }; 373 | 374 | /// Concrete subclass of ParseResult for a successful parse operation 375 | template 376 | class Parsed : public ParseResult 377 | { 378 | private: 379 | T value_; 380 | InputPos nextPos_; 381 | 382 | public: 383 | Parsed(const T &v, const InputPos &next) : value_{v}, nextPos_{next} {} 384 | 385 | virtual bool wasParsed() const { return true; } 386 | virtual const T *value() const { return &value_; } 387 | virtual const InputPos *nextPos() const { return &nextPos_; } 388 | }; 389 | 390 | /// Return a newly constructed Parse object representing a failure 391 | template 392 | static Parse failedParse() 393 | { 394 | return Parse{make_shared>()}; 395 | } 396 | 397 | /// Return a newly constructed Parse object representing success 398 | template 399 | static Parse successfulParse(const T &v, const InputPos &next) 400 | { 401 | return Parse{make_shared>(v, next)}; 402 | } 403 | 404 | /// Determine whether character is a digit 405 | inline bool isDigitChar(Char c) { return '0' <= c && c <= '9'; } 406 | 407 | /// Parse a statement 408 | /// 409 | /// Returns a parsed statement and position of character 410 | /// following the end of the parsed statement, or nil 411 | /// if there is no valid statement. 412 | Parse statement(const InputPos &pos); 413 | 414 | /// Parse user entry for INPUT 415 | /// 416 | /// Return parsed number and following position if successful. 417 | /// 418 | /// Accepts entry of a number with optional leading sign (+|-), or a variable 419 | /// name. 420 | Parse inputExpression(const InputPos &pos, InterpreterEngine &engine); 421 | 422 | /// Attempt to read an unsigned number from input. If successful, returns 423 | /// parsed number and position of next input character. If not, returns nil. 424 | Parse numberLiteral(const InputPos &pos); 425 | 426 | /// Determine whether the remainder of the line starts with a specified sequence 427 | /// of characters. 428 | /// 429 | /// If true, returns position of the character following the matched string. If 430 | /// false, returns nil. 431 | /// 432 | /// Matching is case-insensitive. Spaces in the input are ignored. 433 | Parse literal(string s, const InputPos &pos); 434 | 435 | /// Attempt to parse an Lvalue (variable name or array element reference) from a String 436 | /// 437 | /// Returns Lvalue if successful, or nil if the string cannot be parsed as an Lvalue. 438 | Parse lvalueFromString(const string &input); 439 | 440 | } // namespace finchlib_cpp 441 | 442 | #endif /* defined(__finchbasic__parse__) */ 443 | -------------------------------------------------------------------------------- /finchlib_cpp/pasteboard.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #ifndef __finchbasic__pasteboard__ 25 | #define __finchbasic__pasteboard__ 26 | 27 | #include "cppdefs.h" 28 | 29 | namespace finchlib_cpp 30 | { 31 | /// Return contents of the system clipboard as a string, 32 | /// or empty string if no text on clipboard. 33 | string getPasteboardContents(); 34 | 35 | /// Copy the specified text to the system clipboard, 36 | /// replacing any existing contents of the clipboard. 37 | void copyToPasteboard(string text); 38 | } 39 | 40 | #endif /* defined(__finchbasic__pasteboard__) */ 41 | -------------------------------------------------------------------------------- /finchlib_cpp/pasteboard.mm: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #include "pasteboard.h" 25 | 26 | #include 27 | 28 | #if TARGET_OS_IPHONE 29 | #import 30 | #else 31 | #import 32 | #endif 33 | 34 | namespace finchlib_cpp 35 | { 36 | 37 | static NSString *getPasteboardContentsAsNSString() 38 | { 39 | #if TARGET_OS_IPHONE 40 | const auto pasteboard = [UIPasteboard generalPasteboard]; 41 | return [pasteboard string]; 42 | #else 43 | const auto pasteboard = [NSPasteboard generalPasteboard]; 44 | return [pasteboard stringForType:NSPasteboardTypeString]; 45 | #endif 46 | } 47 | 48 | static void setPasteboardContents(NSString *s) 49 | { 50 | #if TARGET_OS_IPHONE 51 | const auto pasteboard = [UIPasteboard generalPasteboard]; 52 | pasteboard.string = s; 53 | #else 54 | const auto pasteboard = [NSPasteboard generalPasteboard]; 55 | [pasteboard clearContents]; 56 | [pasteboard setString:s forType:NSPasteboardTypeString]; 57 | #endif 58 | } 59 | 60 | /// Return contents of the system clipboard as a string, 61 | /// or empty string if no text on clipboard. 62 | string getPasteboardContents() 63 | { 64 | NSString *nsString = getPasteboardContentsAsNSString(); 65 | 66 | if (!nsString) 67 | { 68 | return {}; 69 | } 70 | 71 | return {[nsString UTF8String]}; 72 | } 73 | 74 | /// Copy the specified text to the system clipboard, 75 | /// replacing any existing contents of the clipboard. 76 | void copyToPasteboard(string text) 77 | { 78 | NSString *nsString = [NSString stringWithCString:text.c_str() 79 | encoding:NSUTF8StringEncoding]; 80 | setPasteboardContents(nsString); 81 | } 82 | 83 | } // namespace finchlib_cpp 84 | -------------------------------------------------------------------------------- /finchlib_cpp/syntax.mm: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Kristopher Johnson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #include "syntax.h" 25 | #include "InterpreterEngine.h" 26 | 27 | using namespace finchlib_cpp; 28 | 29 | 30 | #pragma mark - Lvalue 31 | 32 | string Lvalue::listText() const 33 | { 34 | return subtype->listText(); 35 | } 36 | 37 | void Lvalue::setValue(Number n, InterpreterEngine &engine) const 38 | { 39 | subtype->setValue(n, engine); 40 | } 41 | 42 | void Lvalue::setValue(const Expression &expr, InterpreterEngine &engine) const 43 | { 44 | const auto number = engine.evaluate(expr); 45 | subtype->setValue(number, engine); 46 | } 47 | 48 | string Lvalue::Var::listText() const 49 | { 50 | return string(1, static_cast(variableName)); 51 | } 52 | 53 | void Lvalue::Var::setValue(Number n, InterpreterEngine &engine) const 54 | { 55 | engine.setVariableValue(variableName, n); 56 | } 57 | 58 | string Lvalue::ArrayElement::listText() const 59 | { 60 | return "@(" + subscript.listText() + ")"; 61 | } 62 | 63 | void Lvalue::ArrayElement::setValue(Number n, InterpreterEngine &engine) const 64 | { 65 | engine.setArrayElementValue(subscript, n); 66 | } 67 | 68 | #pragma mark - ArithOp 69 | 70 | // Our division operator returns 0 on an attempt 71 | // to divide by zero. 72 | // 73 | // This is better than letting the interpreter crash 74 | // if the user attempts to divide something by zero 75 | // in a BASIC program. A better solution might be 76 | // to let the interpreter report an error and abort 77 | // execution, returning to command mode, but we 78 | // don't have a way for expression evaluation to 79 | // signal such a condition. (In C++, we could do this 80 | // with exceptions, but we can't port that over to 81 | // the Swift implementation.) 82 | struct SafeDivides 83 | { 84 | Number operator()(Number lhs, Number rhs) 85 | { 86 | if (rhs == 0) 87 | { 88 | return 0; 89 | } 90 | return lhs / rhs; 91 | } 92 | }; 93 | 94 | const ArithOp ArithOp::Add{plus{}, "+"}; 95 | const ArithOp ArithOp::Subtract{minus{}, "-"}; 96 | const ArithOp ArithOp::Multiply{multiplies{}, "*"}; 97 | const ArithOp ArithOp::Divide{SafeDivides{}, "/"}; 98 | 99 | #pragma mark - RelOp 100 | 101 | const RelOp RelOp::Less{less{}, "<"}; 102 | const RelOp RelOp::Greater{greater{}, ">"}; 103 | const RelOp RelOp::Equal{equal_to{}, "="}; 104 | const RelOp RelOp::LessOrEqual{less_equal{}, "<="}; 105 | const RelOp RelOp::GreaterOrEqual{greater_equal{}, ">="}; 106 | const RelOp RelOp::NotEqual{not_equal_to{}, "<>"}; 107 | 108 | #pragma mark - Factor 109 | 110 | /// Return the value of the factor 111 | Number Factor::evaluate(const VariableBindings &v, const Numbers &a) const 112 | { 113 | return subtype->evaluate(v, a); 114 | } 115 | 116 | string Factor::listText() const { return subtype->listText(); } 117 | 118 | Number Factor::Num::evaluate(const VariableBindings &v, 119 | const Numbers &a) const 120 | { 121 | return number; 122 | } 123 | 124 | string Factor::Num::listText() const 125 | { 126 | auto s = ostringstream{}; 127 | s << number; 128 | return s.str(); 129 | } 130 | 131 | Factor::ParenExpr::ParenExpr(const Expression &expr) 132 | : expression{make_shared(expr)} {} 133 | 134 | Number Factor::ParenExpr::evaluate(const VariableBindings &v, 135 | const Numbers &a) const 136 | { 137 | return expression->evaluate(v, a); 138 | } 139 | 140 | string Factor::ParenExpr::listText() const 141 | { 142 | return "(" + expression->listText() + ")"; 143 | } 144 | 145 | Number Factor::Var::evaluate(const VariableBindings &v, 146 | const Numbers &a) const 147 | { 148 | const auto it = v.find(variableName); 149 | return it == v.end() ? 0 : it->second; 150 | } 151 | 152 | string Factor::Var::listText() const 153 | { 154 | return string(1, static_cast(variableName)); 155 | } 156 | 157 | Factor::ArrayElement::ArrayElement(const Expression &e) 158 | : expression{make_shared(e)} {} 159 | 160 | Number Factor::ArrayElement::evaluate(const VariableBindings &v, 161 | const Numbers &a) const 162 | { 163 | const auto index = expression->evaluate(v, a); 164 | if (index >= 0) 165 | { 166 | return a[index % a.size()]; 167 | } 168 | else 169 | { 170 | const auto fromEnd = -index % a.size(); 171 | return a[a.size() - fromEnd]; 172 | } 173 | } 174 | 175 | string Factor::ArrayElement::listText() const 176 | { 177 | return "@(" + expression->listText() + ")"; 178 | } 179 | 180 | Factor::Rnd::Rnd(const Expression &e) 181 | : expression{make_shared(e)} {} 182 | 183 | Number Factor::Rnd::evaluate(const VariableBindings &v, 184 | const Numbers &a) const 185 | { 186 | const auto n = expression->evaluate(v, a); 187 | if (n < 1) 188 | { 189 | // TODO: signal a runtime error? 190 | return 0; 191 | } 192 | return Number{static_cast(arc4random_uniform(n))}; 193 | } 194 | 195 | string Factor::Rnd::listText() const 196 | { 197 | return "RND(" + expression->listText() + ")"; 198 | } 199 | 200 | #pragma mark - Term 201 | 202 | /// Return the value of the term 203 | Number Term::evaluate(const VariableBindings &v, const Numbers &a) const 204 | { 205 | return subtype->evaluate(v, a); 206 | } 207 | 208 | /// Return pretty-printed text 209 | string Term::listText() const { return subtype->listText(); } 210 | 211 | bool Term::isCompound() const { return subtype->isCompound(); } 212 | 213 | Number Term::Value::evaluate(const VariableBindings &v, 214 | const Numbers &a) const 215 | { 216 | return factor.evaluate(v, a); 217 | } 218 | 219 | string Term::Value::listText() const { return factor.listText(); } 220 | 221 | Term::Compound::Compound(Factor f, ArithOp op, const Term &t) 222 | : factor{f}, arithOp{op}, term{make_shared(t)} {} 223 | 224 | Number Term::Compound::evaluate(const VariableBindings &v, 225 | const Numbers &a) const 226 | { 227 | auto accumulator = factor.evaluate(v, a); 228 | auto lastOp = arithOp; 229 | auto next = term; 230 | for (;;) 231 | { 232 | if (next->isCompound()) 233 | { 234 | // Pull out the components of the compound term, apply 235 | // the previous operator to the accumulator and new factor, 236 | // then go on to next term. 237 | auto compound = static_cast(next->subtype.get()); 238 | accumulator = lastOp.apply(accumulator, compound->factor.evaluate(v, a)); 239 | lastOp = compound->arithOp; 240 | next = compound->term; 241 | } 242 | else 243 | { 244 | // Reached the final non-compound term, so we can return result 245 | return lastOp.apply(accumulator, next->evaluate(v, a)); 246 | } 247 | } 248 | } 249 | 250 | string Term::Compound::listText() const 251 | { 252 | return factor.listText() + " " + arithOp.listText() + " " + term->listText(); 253 | } 254 | 255 | #pragma mark - UnsignedExpression 256 | 257 | /// Return the value of the expression 258 | Number UnsignedExpression::evaluate(const VariableBindings &v, 259 | const Numbers &a) const 260 | { 261 | return subtype->evaluate(v, a); 262 | } 263 | 264 | Number 265 | UnsignedExpression::evaluateWithNegatedFirstTerm(const VariableBindings &v, 266 | const Numbers &a) const 267 | { 268 | if (isCompound()) 269 | { 270 | // Pull out the components of the compound term, create 271 | // a new expression with the first term negated, and evaluate 272 | // that. 273 | auto compound = static_cast(subtype.get()); 274 | const auto termValue = compound->term.evaluate(v, a); 275 | const auto negatedFactor = Factor::number(-termValue); 276 | const auto negatedTerm = Term::factor(negatedFactor); 277 | const auto newUExpr = UnsignedExpression::compound( 278 | negatedTerm, compound->arithOp, *compound->tail); 279 | const auto newExpr = Expression::unsignedExpr(newUExpr); 280 | return newExpr.evaluate(v, a); 281 | } 282 | else 283 | { 284 | return -evaluate(v, a); 285 | } 286 | } 287 | 288 | string UnsignedExpression::listText() const { return subtype->listText(); } 289 | 290 | bool UnsignedExpression::isCompound() const { return subtype->isCompound(); } 291 | 292 | Number UnsignedExpression::Value::evaluate(const VariableBindings &v, 293 | const Numbers &a) const 294 | { 295 | return term.evaluate(v, a); 296 | } 297 | 298 | string UnsignedExpression::Value::listText() const 299 | { 300 | return term.listText(); 301 | } 302 | 303 | UnsignedExpression::Compound::Compound(Term t, ArithOp op, 304 | const UnsignedExpression &u) 305 | : term{t}, arithOp{op}, tail{make_shared(u)} {} 306 | 307 | Number UnsignedExpression::Compound::evaluate(const VariableBindings &v, 308 | const Numbers &a) const 309 | { 310 | auto accumulator = term.evaluate(v, a); 311 | auto lastOp = arithOp; 312 | auto next = tail; 313 | for (;;) 314 | { 315 | if (next->isCompound()) 316 | { 317 | // Pull out the components of the compound term, apply 318 | // the previous operator to the accumulator and new factor, 319 | // then go on to next term. 320 | auto compound = static_cast( 321 | next->subtype.get()); 322 | accumulator = lastOp.apply(accumulator, compound->term.evaluate(v, a)); 323 | lastOp = compound->arithOp; 324 | next = compound->tail; 325 | } 326 | else 327 | { 328 | // Reached the final non-compound term, so we can return result 329 | return lastOp.apply(accumulator, next->evaluate(v, a)); 330 | } 331 | } 332 | } 333 | 334 | string UnsignedExpression::Compound::listText() const 335 | { 336 | return term.listText() + " " + arithOp.listText() + " " + tail->listText(); 337 | } 338 | 339 | #pragma mark - Expression 340 | 341 | /// Construct an expression from a numeric constant 342 | Expression Expression::number(Number n) 343 | { 344 | const auto factor = Factor::number(n); 345 | const auto term = Term::factor(factor); 346 | const auto uexpr = UnsignedExpression::term(term); 347 | return Expression::unsignedExpr(uexpr); 348 | } 349 | 350 | /// Return the value of the expression 351 | Number Expression::evaluate(const VariableBindings &v, const Numbers &a) const 352 | { 353 | return subtype->evaluate(v, a); 354 | } 355 | 356 | string Expression::listText() const { return subtype->listText(); } 357 | 358 | Number Expression::UnsignedExpr::evaluate(const VariableBindings &v, 359 | const Numbers &a) const 360 | { 361 | return unsignedExpression.evaluate(v, a); 362 | } 363 | 364 | string Expression::UnsignedExpr::listText() const 365 | { 366 | return unsignedExpression.listText(); 367 | } 368 | 369 | Number Expression::Plus::evaluate(const VariableBindings &v, 370 | const Numbers &a) const 371 | { 372 | return unsignedExpression.evaluate(v, a); 373 | } 374 | 375 | string Expression::Plus::listText() const 376 | { 377 | return string("+") + unsignedExpression.listText(); 378 | } 379 | 380 | Number Expression::Minus::evaluate(const VariableBindings &v, 381 | const Numbers &a) const 382 | { 383 | return unsignedExpression.evaluateWithNegatedFirstTerm(v, a); 384 | } 385 | 386 | string Expression::Minus::listText() const 387 | { 388 | return string("-") + unsignedExpression.listText(); 389 | } 390 | 391 | #pragma mark - PrintItem 392 | 393 | vec PrintItem::printText(const VariableBindings &v, 394 | const Numbers &a) const 395 | { 396 | return subtype->printText(v, a); 397 | } 398 | 399 | string PrintItem::listText() const { return subtype->listText(); } 400 | 401 | vec PrintItem::Expr::printText(const VariableBindings &v, 402 | const Numbers &a) const 403 | { 404 | const auto n = expression.evaluate(v, a); 405 | 406 | auto s = ostringstream{}; 407 | s << n; 408 | 409 | vec result; 410 | for (const auto c : s.str()) 411 | { 412 | result.push_back(c); 413 | } 414 | return result; 415 | } 416 | 417 | string PrintItem::Expr::listText() const { return expression.listText(); } 418 | 419 | vec PrintItem::StringLiteral::printText(const VariableBindings &v, 420 | const Numbers &a) const 421 | { 422 | return chars; 423 | } 424 | 425 | string PrintItem::StringLiteral::listText() const 426 | { 427 | auto s = string{}; 428 | s.push_back('"'); 429 | for (const auto c : chars) 430 | { 431 | s.push_back(c); 432 | } 433 | s.push_back('"'); 434 | return s; 435 | } 436 | 437 | #pragma mark - PrintList 438 | 439 | vec PrintList::printText(const VariableBindings &v, 440 | const Numbers &a) const 441 | { 442 | auto chars = item.printText(v, a); 443 | 444 | switch (separator) 445 | { 446 | case PrintSeparatorNewline: 447 | chars.push_back('\n'); 448 | break; 449 | case PrintSeparatorTab: 450 | chars.push_back('\t'); 451 | break; 452 | case PrintSeparatorEmpty: 453 | // nothing 454 | break; 455 | default: 456 | // should be no other cases 457 | assert(false); 458 | break; 459 | } 460 | 461 | if (tail != nullptr) 462 | { 463 | const auto tailChars = tail->printText(v, a); 464 | chars.insert(chars.end(), tailChars.cbegin(), tailChars.cend()); 465 | } 466 | 467 | return chars; 468 | } 469 | 470 | string PrintList::listText() const 471 | { 472 | auto s = ostringstream{}; 473 | s << item.listText(); 474 | 475 | switch (separator) 476 | { 477 | case PrintSeparatorNewline: 478 | // nothing 479 | break; 480 | case PrintSeparatorTab: 481 | s << ","; 482 | break; 483 | case PrintSeparatorEmpty: 484 | s << ";"; 485 | break; 486 | default: 487 | // should be no other cases 488 | assert(false); 489 | break; 490 | } 491 | 492 | if (tail != nullptr) 493 | { 494 | s << " " << tail->listText(); 495 | } 496 | 497 | return s.str(); 498 | } 499 | 500 | #pragma mark - Statement 501 | 502 | void Statement::execute(InterpreterEngine &engine) const 503 | { 504 | subtype->execute(engine); 505 | } 506 | 507 | string Statement::listText() const { return subtype->listText(); } 508 | 509 | void Statement::Print::execute(InterpreterEngine &engine) const 510 | { 511 | engine.PRINT(printList); 512 | } 513 | 514 | string Statement::Print::listText() const 515 | { 516 | return "PRINT " + printList.listText(); 517 | } 518 | 519 | void Statement::PrintNewline::execute(InterpreterEngine &engine) const 520 | { 521 | engine.PRINT(); 522 | } 523 | 524 | string Statement::PrintNewline::listText() const { return "PRINT"; } 525 | 526 | void Statement::List::execute(InterpreterEngine &engine) const 527 | { 528 | engine.LIST(lowLineNumber, highLineNumber); 529 | } 530 | 531 | string Statement::List::listText() const { return "LIST"; } 532 | 533 | void Statement::Let::execute(InterpreterEngine &engine) const 534 | { 535 | lvalue.setValue(expression, engine); 536 | } 537 | 538 | string Statement::Let::listText() const 539 | { 540 | return "LET " + lvalue.listText() + " = " + expression.listText(); 541 | } 542 | 543 | void Statement::Input::execute(InterpreterEngine &engine) const 544 | { 545 | engine.INPUT(lvalues); 546 | } 547 | 548 | static string listTextFor(const Lvalues &lvalues) 549 | { 550 | if (lvalues.size() == 0) 551 | { 552 | return ""; 553 | } 554 | 555 | auto s = ostringstream{}; 556 | s << lvalues[0].listText(); 557 | for (auto it = lvalues.cbegin() + 1; it != lvalues.cend(); ++it) 558 | { 559 | s << ", " << it->listText(); 560 | } 561 | return s.str(); 562 | } 563 | 564 | string Statement::Input::listText() const 565 | { 566 | return "INPUT " + listTextFor(lvalues); 567 | } 568 | 569 | Statement::IfThen::IfThen(const Expression &left, const RelOp &relop, 570 | const Expression &right, 571 | const Statement &thenStatement) 572 | : lhs{left}, op{relop}, rhs{right}, consequent{make_shared(thenStatement)} {} 573 | 574 | void Statement::IfThen::execute(InterpreterEngine &engine) const 575 | { 576 | engine.IF(lhs, op, rhs, *consequent); 577 | } 578 | 579 | string Statement::IfThen::listText() const 580 | { 581 | return "IF " + lhs.listText() + " " + op.listText() + " " + rhs.listText() + " THEN " + consequent->listText(); 582 | } 583 | 584 | void Statement::Run::execute(InterpreterEngine &engine) const { engine.RUN(); } 585 | 586 | string Statement::Run::listText() const { return "RUN"; } 587 | 588 | void Statement::End::execute(InterpreterEngine &engine) const { engine.END(); } 589 | 590 | string Statement::End::listText() const { return "END"; } 591 | 592 | void Statement::Goto::execute(InterpreterEngine &engine) const 593 | { 594 | engine.GOTO(lineNumber); 595 | } 596 | 597 | string Statement::Goto::listText() const 598 | { 599 | return "GOTO " + lineNumber.listText(); 600 | } 601 | 602 | void Statement::Gosub::execute(InterpreterEngine &engine) const 603 | { 604 | engine.GOSUB(lineNumber); 605 | } 606 | 607 | string Statement::Gosub::listText() const 608 | { 609 | return "GOSUB " + lineNumber.listText(); 610 | } 611 | 612 | void Statement::Return::execute(InterpreterEngine &engine) const 613 | { 614 | engine.RETURN(); 615 | } 616 | 617 | string Statement::Return::listText() const { return "RETURN"; } 618 | 619 | void Statement::Rem::execute(InterpreterEngine &engine) const 620 | { 621 | // does nothing 622 | } 623 | 624 | string Statement::Rem::listText() const { return "REM" + text; } 625 | 626 | void Statement::Clear::execute(InterpreterEngine &engine) const 627 | { 628 | engine.CLEAR(); 629 | } 630 | 631 | string Statement::Clear::listText() const { return "CLEAR"; } 632 | 633 | void Statement::Bye::execute(InterpreterEngine &engine) const { engine.BYE(); } 634 | 635 | string Statement::Bye::listText() const { return "BYE"; } 636 | 637 | void Statement::Help::execute(InterpreterEngine &engine) const 638 | { 639 | engine.HELP(); 640 | } 641 | 642 | string Statement::Help::listText() const { return "HELP"; } 643 | 644 | void Statement::Dim::execute(InterpreterEngine &engine) const 645 | { 646 | engine.DIM(expression); 647 | } 648 | 649 | string Statement::Dim::listText() const 650 | { 651 | return "DIM @(" + expression.listText() + ")"; 652 | } 653 | 654 | void Statement::Save::execute(InterpreterEngine &engine) const 655 | { 656 | engine.SAVE(filename); 657 | } 658 | 659 | string Statement::Save::listText() const 660 | { 661 | return "SAVE \"" + filename + "\""; 662 | } 663 | 664 | void Statement::Load::execute(InterpreterEngine &engine) const 665 | { 666 | engine.LOAD(filename); 667 | } 668 | 669 | string Statement::Load::listText() const 670 | { 671 | return "LOAD \"" + filename + "\""; 672 | } 673 | 674 | void Statement::Files::execute(InterpreterEngine &engine) const 675 | { 676 | engine.FILES(); 677 | } 678 | 679 | string Statement::Files::listText() const { return "FILES"; } 680 | 681 | void Statement::ClipSave::execute(InterpreterEngine &engine) const 682 | { 683 | engine.CLIPSAVE(); 684 | } 685 | 686 | string Statement::ClipSave::listText() const 687 | { 688 | return "CLIPSAVE"; 689 | } 690 | 691 | void Statement::ClipLoad::execute(InterpreterEngine &engine) const 692 | { 693 | engine.CLIPLOAD(); 694 | } 695 | 696 | string Statement::ClipLoad::listText() const 697 | { 698 | return "CLIPLOAD"; 699 | } 700 | 701 | void Statement::Tron::execute(InterpreterEngine &engine) const 702 | { 703 | engine.TRON(); 704 | } 705 | 706 | string Statement::Tron::listText() const { return "TRON"; } 707 | 708 | void Statement::Troff::execute(InterpreterEngine &engine) const 709 | { 710 | engine.TROFF(); 711 | } 712 | 713 | string Statement::Troff::listText() const { return "TROFF"; } 714 | -------------------------------------------------------------------------------- /finchlib_cppTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | net.kristopherjohnson.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /grammar.ebnf.txt: -------------------------------------------------------------------------------- 1 | /* EBNF grammar for FinchBasic. */ 2 | /* This file can be used to generate syntax diagrams at http://bottlecaps.de/rr/ui */ 3 | 4 | input-line ::= statement | number statement 5 | 6 | statement ::= PRINT-statement 7 | | LET-statement 8 | | INPUT-statement 9 | | IF-statement 10 | | GOTO-statement 11 | | GOSUB-statement 12 | | RETURN-statement 13 | | END-statement 14 | | RUN-statement 15 | | REM-statement 16 | | LIST-statement 17 | | BYE-statement 18 | | CLEAR-statement 19 | | DIM-statement 20 | | SAVE-statement 21 | | LOAD-statement 22 | | FILES-statement 23 | | CLIPLOAD-statement 24 | | CLIPSAVE-statement 25 | | TRON-statement 26 | | TROFF-statement 27 | | HELP-statement 28 | 29 | PRINT-statement ::= ('PRINT'|'PR'|'?') ((expression|string-literal) ((';'|',') (expression|string-literal))* (';'|',')?)? 30 | string-literal ::= '"' (character)* '"' 31 | 32 | LET-statement ::= 'LET'? (variable|array-element) '=' expression 33 | 34 | INPUT-statement ::= ('INPUT'|'IN') (variable|array-element) (',' (variable|array-element))* 35 | 36 | IF-statement ::= 'IF' expression ( '=' | '<' | '<=' | '>' | '>=' | '<>' | '><') expression 'THEN'? statement 37 | 38 | GOTO-statement ::= ('GOTO'|'GT') expression 39 | 40 | GOSUB-statement ::= ('GOSUB'|'GS') expression 41 | 42 | RETURN-statement ::= 'RETURN' | 'RT' 43 | 44 | END-statement ::= 'END' 45 | 46 | RUN-statement ::= 'RUN' 47 | 48 | REM-statement ::= ('REM'|"'") comment 49 | 50 | LIST-statement ::= ('LIST'|'LS') (expression (',' expression)?)? 51 | 52 | BYE-statement ::= 'BYE' 53 | 54 | CLEAR-statement ::= 'CLEAR' 55 | 56 | DIM-statement ::= 'DIM' '@(' expression ')' 57 | 58 | SAVE-statement ::= ('SAVE'|'SV') string-literal 59 | 60 | LOAD-statement ::= ('LOAD'|'LD') string-literal 61 | 62 | FILES-statement ::= ('FILES'|'FL') 63 | 64 | CLIPLOAD-statement ::= 'CLIPLOAD' 65 | 66 | CLIPSAVE-statement ::= 'CLIPSAVE' 67 | 68 | TRON-statement ::= 'TRON' 69 | 70 | TROFF-statement ::= 'TROFF' 71 | 72 | expression ::= ('+'|'-')? ( number | variable | array-element | '(' expression ')' | expression ('+'|'-'|'*'|'/') expression | 'RND(' expression ')') 73 | 74 | number ::= [0-9]+ 75 | variable ::= [A-Z] 76 | array-element ::= '@(' expression ')' 77 | --------------------------------------------------------------------------------