I’ve decided to toss the whole original idea for RetroBASIC and add an interactive shell. My original idea was it would be used only with existing code, supplied in a text file, but over time I’ve come to question that decision.
But one thing… what should I use as the Break key? On PCs you often, but not always, have a Pause/Break key. But my Mac doesn’t have one, as is the case for lots of other systems.
Control-C is not an ideal option. Yes, I can signal() the SIGINT, but I would like to avoid that such that a control-C exits RetroBASIC, not just stops the user program.
There are other options, control-X, I see some seem to use control-C, and even control-escape seems like a possibility.
What say you all? Any favourites? Maybe some examples of what other people have done that could guide me? Open to all opinions!
There is an issues of timing too. If you’re running under a Pozix-y environment, you can disable SIGINT (and that’s may be a good idea anyway?) then poll at the end of each line/statement executed for characters like Ctrl-C or my favourite, ESC.
The issue then is handling keys not ESC when you poll (because you have to poll, read, …?) - do you store them in a buffer to give you type-ahead or throw them away? you can’t just read the first character and check - especially in a system that already processes keyboard input and buffers it, you need to read all of them.
Or let Ctrl-C do it’s thing, have a signal handler set a global variable and use that. Relatively easy.
I found in my Linux Basic the overhead to poll the keyboard (I use SDL) was significant - well, not large, but it all adds up. I only poll on jumps too not every statement.
My TinyBasic polls on jumps too, but polling a UART or other input means (one variant has software serial) isn’t a large overhead.
(Jumps being goto/gosub/proc/fn, for/while/repeat/loops - the down side is that 1000 statements might be executed before the interrupt is ‘seen’ but I deemed that sufficiently rare as to not be an issue)
I guess I don’t need Esc in my command line, because I don’t see people entering characters like I would on my Atari, where Esc was sort of like another control that was used to enter oddball characters like “cursor up”. That’s beyond the scope of my program.
So maybe that’s the way to go!
Or let Ctrl-C do its thing, have a signal handler set a global variable and use that.
My only concern here: how does the user force-quit the BASIC itself? It seems like catching it and then hoping the handler works is less than ideal.
Very popular in the world of Acorn - The BBC Micro. ESC was handled internally by the keyboard interrupt handler that then set a global flag.
Or let Ctrl-C do its thing, have a signal handler set a global variable and use that.
My only concern here: how does the user force-quit the BASIC itself? It seems like catching it and then hoping the handler works is less than ideal.
[/quote]
As well as SIGINT, (Usually Ctrl-C) there is SIGKILL - Usually Ctrl-\ and this can’t normally be trapped (although putting the terminal into raw mode will stop the signals being generated in the first instance)
If keyboard poll latency is an issue on your SDL app then run the event handling in a separate thread. I doubt anyone has a single core processor any more. At that point you can make the test a simple test and branch on a single atomic variable.
On micros of course you have the keyboard interrupt handler self modify the execution loop if you are really desperate 8)
I’d also think that ESC should be a rather natural choice. I.e., what would you be looking for as a casual user? Similarly, I’d expect CTRL-C to break out of BASIC (i.e., the hosting application) itself. (For a less radical solution, a gracefull quit, I may try CTRL-Q or CTRL-X, as well.)
Holy smokes, actually implementing this in unix is a horrible mess!
Because the terminal is normally line-buffered, pressing “normal” keys like escape doesn’t actually send the character, it only gets sent once you hit return. So after a bit of googling and gtping, I found the required incantations needed to turn off buffered mode, read a key, and then turn buffering back on - and you want buffering so things like INPUT work as you’d expect.
Well it turns out if you do that then you’ll get a segfault because if you call this code enough times to have reasonable input latency - say once every statement - then the terminal code goes wonk. So much for that.
Ok, next try, let’s put the terminal into raw mode once, at startup. That fixes the segfault, but now you have to re-write all your reads to handle this. This created all sorts of weird side-effects, like a semicolon at the end of the line made the next PRINT appear on the same line… but not the PRINT after that one.
So it seems to be working again, and I have a CLI and escape does a break, but I’m concerned there’s some other edge case I haven’t come across where this all falls down again.
Man, this would have been so easy to fix… SIGKEY and a setting in (the future) ANSI standard that lets you select which keys are sent immediately and all of this would be a single line of code. Which it is on the Windows/DOS side I should mention.
I rolled my own code for it using raw mode, I didn’t see any way around it. I wouldn’t call it elegant though, you can do the same thing in Windows in a single line, one lacking any of the crazy gobbligook #defines and such. Powerful, perhaps, but definitely not elegant!
In any event, it looks like it’s working, so I’m getting ready to check in a 3.x.
At this point, all of the assumptions I used to write 1.x have been overturned. It was originally going to just parse the programs and print stats… but then it ran them. It was originally just going to be MS and HP… but now it handles maybe 25 different dialects from the mini and mainframe world. It was originally going to lack and CLI… yet here I am.