├── .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