TwoTerm/Emulators/GSOSConsole.mm.ragel
2018-02-28 21:06:06 -05:00

609 lines
19 KiB
Plaintext

//
// GSOSConsole.m
// 2Term
//
// Created by Kelvin Sherlock on 7/9/2016.
//
//
#import "GSOSConsole.h"
#include "OutputChannel.h"
#include "Screen.h"
#include "algorithm.h"
// currently no way to set consFill :-)
static char_info remap_fill(const gsos_context &ctx) { return char_info(' ', 0); }
static void advance(Screen *screen, gsos_context &ctx) {
if (ctx.consAdvance) {
if (ctx.cursor.x < ctx.window.maxX() - 1) ctx.cursor.x++;
else if (ctx.consWrap) {
ctx.cursor.x = ctx.window.minX();
if (ctx.cursor.y < ctx.window.maxY() - 1) ctx.cursor.y++;
else if (ctx.consScroll) screen->scrollUp(ctx.window);
}
}
}
%%{
machine console;
alphtype unsigned int;
action nop {}
arg1 = any ${ _scratch[0] = (fc - 32); };
arg2 = any ${ _scratch[1] = (fc - 32); };
arg3 = any ${ _scratch[2] = (fc - 32); };
arg4 = any ${ _scratch[3] = (fc - 32); };
action advance_if {
if (_context.consAdvance) advance(screen, _context);
}
main := (
0x00 $nop
| 0x01 ${
/*
Save Current Text Port and Reset Default Text Port
Saves the current text port and resets to the default text port. If the system is out of memory, no error is returned, and the text port is simply reset.
*/
_context_stack.push_back(_context);
_context = gsos_context();
_context.cursor = iPoint(0,0);
_context.window = iRect(0, 0, 80, 24);
}
| 0x02 arg1 arg2 arg3 arg4 ${
/*
Set Text Port Size
Accepts the next four bytes as absolute screen coordinates + 32. Sets the current text port to the new parameters. The parameters are in the following order: windLeft, windTop, windRight, windBottom. Any parameter outside the screen boundaries is clipped to a legal value. The cursor is set to the upper-left corner of the new text port.
*/
_scratch[0] = clamp(_scratch[0], 0, 80-1);
_scratch[1] = clamp(_scratch[1], 0, 24-1);
_scratch[2] = clamp(_scratch[2], 0, 80-1)+1;
_scratch[3] = clamp(_scratch[3], 0, 24-1)+1;
window = iRect(_scratch[0],
_scratch[1],
_scratch[2] - _scratch[0],
_scratch[3] - _scratch[1]
);
cursor = window.origin;
}
| 0x03 ${
/*
Clear from Beginning of Line
Clears all characters from the left edge to and including the cursor. Sets them to the current consFill character.
*/
char_info ci = remap_fill(_context);
iRect tmp(
iPoint(window.minX(), cursor.y),
iPoint(cursor.x+1, cursor.y+1)
);
screen->fillRect(tmp, ci);
}
| 0x04 ${
/*
Pop Text Port
Restores the text port to the most recently saved value (see code $01). If no saved ports exist, resets the text port to the default values. If an 80-column text port is pushed and subsequently restored in 40-column mode, the text port may be altered to fit in the 40-column screen (see code $11, Set 40-Column Mode).
*/
if (!_context_stack.empty()) {
_context = _context_stack.back();
_context_stack.pop_back();
} else {
_context = gsos_context();
}
}
| 0x05 any ${
/*
Horizontal Scroll
Interprets the next byte as an 8-bit signed integer depicting the number (N) of
columns to shift. N equal to zero is a null operation. If N is less than zero, the text
port is shifted to the left; A greater than zero shifts to the right. If the shift magnitude is equal to or greater than windWidth, the text port is cleared.
The shifted characters are moved directly to their destination location. The space vacated by the shifted characters is set to the current consFill character (see the description of consFill earlier in this chapter). Characters shifted out of the text port are removed from the screen and are not recoverable.
*/
int8_t n = fc;
if (n < 0) {
screen->scrollLeft(window, n);
}
if (n > 0) {
screen->scrollRight(window, n);
}
// .... TODO
}
| 0x06 any ${
/*
Set Vertical Position
Interprets the next byte as a text port-relative vertical position + 32. If the destination is outside the current text port, the cursor is moved to the nearest edge.
*/
unsigned n = clamp(fc - 32, 0, window.height()-1);
cursor.y = window.minY() + n;
}
| 0x07 ${
/*
Ring Bell
Causes the System Beep to be played. It has no effect on the screen.
*/
NSBeep();
}
| 0x08 ${
/*
Backspace
Moves the cursor one position to the left. If the cursor was on the left edge of the
text port and consWrap is TRUE, the cursor is placed one row higher and at the right edge. If the cursor was also on the topmost row and consScroll is TRUE, thetext port will scroll backward one line.
*/
if (cursor.x > window.minX()) cursor.x--;
else if (_context.consWrap) {
cursor.x = window.maxX() - 1;
if (cursor.y > window.minY()) cursor.y--;
else if (_context.consScroll) screen->scrollDown(window);
}
}
| 0x09 $nop
| 0x0a ${
/*
Line Feed
Causes the cursor to move down one line. If the cursor was at the bottom edge of the text port and consScroll is TRUE, the text port scrolls up one line.
*/
if (cursor.y < window.maxY()-1) cursor.y++;
else if (_context.consScroll)
screen->scrollUp(window);
}
| 0x0b ${
/*
Clear to End of Text Port
Clears all characters from the cursor to the end of the current text port and sets them to be equal to the current consFill character.
*/
iRect tmp;
tmp.origin = cursor;
tmp.size = iSize(window.size.width - cursor.x, 1);
screen->eraseRect(tmp);
tmp = window;
tmp.origin.y = cursor.y+1;
tmp.size.height -= cursor.y+1;
screen->eraseRect(tmp);
}
| 0x0c ${
/*
Clear Text Port and Home Cursor
Clears the entire text port and resets the cursor to windLeft, windTop.
*/
screen->eraseRect(window);
cursor = window.origin;
}
| 0x0d ${
/*
Carriage Return
Resets the cursor to the left edge of the text port; if consLF is TRUE, performs a line feed (see $0A, Line Feed).
*/
cursor.x = window.minX();
if (_context.consLF) {
if (cursor.y < window.maxY() - 1) cursor.y++;
else if (_context.consScroll) screen->scrollUp(window);
}
}
| 0x0e ${
/*
Set Normal Display Mode
After this character, displays all subsequent characters in normal mode,
*/
_context.clearFlagBit(Screen::FlagInverse);
_context.consFill = 0xa0;
_context.consVideo = true;
}
| 0x0f ${
/*
Set Inverse Display Mode
After this character, displays all subsequent characters in inverse mode.
*/
_context.setFlagBit(Screen::FlagInverse);
_context.consFill = 0x20;
_context.consVideo = false;
}
| 0x10 any ${
/*
DLE Space Expansion
If consDLE is TRUE, interprets the next character as number of spaces + 32, and the
correct number of spaces is issued to die screen. If consDLE is FALSE, the DLE character is ignored and the following character is processed normally.
*/
if (_context.consDLE) {
unsigned count = (fc - 32) & 0xff;
//char_info ci = remap_fill(_context);
if (_context.consAdvance) {
while (count--) {
screen->putc(' ', _context);
if (_context.consAdvance) advance(screen, _context);
}
}
else {
if (count) screen->putc(' ', _context);
}
}
else { fhold; }
}
| 0x11 ${ /* 40 column mode */ }
| 0x12 ${ /* 80 column mode */ }
| 0x13 ${
}
| 0x14 any ${
/*
Set Horizontal Position
Interprets the next byte as a text port-relative horizontal position + 32. If the destination is outside the current text port, the cursor is moved to the nearest edge.
*/
unsigned n = clamp(fc - 32, 0, window.width() - 1);
cursor.x = window.minX() + n;
}
| 0x15 any ${
/*
Set Cursor Movement Word
Interprets the next byte as cursor movement control, and sets the values of these Boolean flags:
*/
unsigned flags = fc;
_context.consAdvance = flags & 0x01;
_context.consLF = flags & 0x02;
_context.consWrap = flags & 0x04;
_context.consScroll = flags & 0x08;
_context.consDLE = flags & 0x10;
}
| 0x16 ${
/*
Scroll Down One Line
Scrolls the text port down one line. Does not move the cursor.
*/
screen->scrollDown(window);
}
| 0x16 ${
/*
Scroll Up One Line
Scrolls the text port up one line. Does not move the cursor.
*/
screen->scrollUp(window);
}
| 0x18 ${
/*
Disable MouseText Mapping
When MouseText is disabled, uppercase inverse characters are displayed as such (see the section "Character Set Mapping" earlier in this chapter).
*/
_context.clearFlagBit(Screen::FlagMouseText);
_context.consMouse = false;
}
| 0x19 ${
/*
Home Cursor
Resets the cursor to the upper-left corner of the text port.
*/
cursor = window.origin;
}
| 0x1a ${
/*
Clear Line
Clears the line that the cursor is on. Resets the cursor to the leftmost column in the window.
*/
iRect tmp;
tmp.origin = iPoint(window.origin.x, cursor.y);
tmp.size = iSize(window.size.width, 1);
screen->eraseRect(tmp);
}
| 0x1b ${
/*
Enable MouseText Mapping
When MouseText is enabled, uppercase inverse letters are displayed as MouseText symbols (see the section "Character Set Mapping" earlier in this chapter).
*/
_context.setFlagBit(Screen::FlagMouseText);
_context.consMouse = true;
}
| 0x1c ${
/*
Move Cursor Right
Performs a nondestructive forward-space of the cursor. If consWrap is TRUE,
the cursor may go to the next line; if consScroll is TRUE, the screen may scroll up one line.
*/
advance(screen, _context);
}
| 0x1d ${
/*
Clear to End of Line
Clears from the position underneath the cursor to the end of the current line.
*/
iRect tmp;
tmp.origin = cursor;
tmp.size = iSize(window.size.width - cursor.x, 1);
screen->eraseRect(tmp);
}
| 0x1e arg1 arg2 ${
/*
Go to X,Y
Adjusts the cursor position relative to the text port. The parameters passed are X+32
and Y+32. If the new locations are outside the current text port, the cursor is placed on the nearest edge.
*/
iPoint dca;
dca.x = clamp(_scratch[0], 0, window.width() - 1);
dca.y = clamp(_scratch[1], 0, window.height() - 1);
cursor.x = dca.x + window.minX();
cursor.y = dca.y + window.minY();
cursor = dca;
}
| 0x1f ${
/*
Move Cursor Up
Moves the cursor up one line (reverse line feed). If the cursor is already on the
uppermost line of the textport and consScroll isTRUE, it will cause a reverse scroll.
*/
if (cursor.y > window.minY()) cursor.y--;
else if (_context.consScroll) screen->scrollDown(window);
}
| 0x20 .. 0x7f ${
screen->putc(fc, _context);
} $advance_if
| 0x80 .. 0x9f ${
/* uppercase inverse/normal */
uint8_t flags = _context.flags ^ Screen::FlagInverse;
screen->putc(fc - 0x40, cursor, flags);
} $advance_if
| 0xa0 .. 0xbf ${
/* special inverse/normal */
uint8_t flags = _context.flags ^ Screen::FlagInverse;
screen->putc(fc - 0x80, cursor, flags);
} $advance_if
| 0xc0 .. 0xdf ${
/* uppercase normal / mouse text. */
uint8_t flags = _context.flags ^ Screen::FlagInverse;
if (flags) flags |= Screen::FlagMouseText;
screen->putc(fc - 0x80, cursor, flags);
} $advance_if
| 0xe0 .. 0xff ${
/* special inverse/normal */
uint8_t flags = _context.flags ^ Screen::FlagInverse;
screen->putc(fc - 0x80, cursor, flags);
} $advance_if
)* $err{ fgoto main; };
write data;
}%%
@implementation GSOSConsole
+(void)load
{
[EmulatorManager registerClass: self];
}
+(NSString *)name
{
return @"GS/OS Console";
}
-(NSString *)name
{
return @"GS/OS Console";
}
-(const char *)termName
{
return "gsos-console";
}
-(BOOL)resizable
{
return NO;
}
-(struct winsize)defaultSize
{
struct winsize ws = { 24, 80, 0, 0 };
return ws;
}
-(void)initTerm: (struct termios *)term
{
// Control-U is used by the up-arrow key.
term->c_cc[VKILL] = CTRL('X');
// \t is a NOP so expand to spaces.
term->c_oflag |= OXTABS;
}
-(id)init
{
if ((self = [super init]))
{
[self reset: YES];
}
return self;
}
-(void)reset: (BOOL)hard
{
%%write init;
if (hard) {
_context_stack.clear();
_context.window = iRect(0, 0, 80, 24);
_context.cursor = iPoint(0,0);
}
_context.consWrap = true;
_context.consAdvance = true;
_context.consLF = true;
_context.consScroll = true;
_context.consVideo = true;
_context.consDLE = true;
_context.consMouse = false;
_context.consFill = 0xa0;
_cursorType = Screen::CursorTypeUnderscore;
}
-(void)processData: (uint8_t *)data length: (size_t)length screen:(Screen *)screen output:(OutputChannel *)output
{
const uint8_t *eof = nullptr;
const uint8_t *p = data;
const uint8_t *pe = data + length;
auto &cursor = _context.cursor;
auto &window = _context.window;
%%write exec;
screen->setCursor(cursor);
}
-(void)keyDown:(NSEvent *)event screen:(Screen *)screen output:(OutputChannel *)output
{
NSEventModifierFlags flags = [event modifierFlags];
NSString *chars = [event charactersIgnoringModifiers];
NSUInteger length = [chars length];
for (unsigned i = 0; i < length; ++i)
{
unichar uc = [chars characterAtIndex: i];
switch (uc)
{
case NSEnterCharacter:
output->write(CTRL('M'));
break;
// todo -- verify...
#if 0
case NSDeleteCharacter:
output->write(0x7f);
break;
#endif
// todo -- verify...
case NSBackspaceCharacter:
output->write(0x7f);
break;
case NSLeftArrowFunctionKey:
output->write(CTRL('H'));
break;
case NSRightArrowFunctionKey:
output->write(CTRL('U'));
break;
case NSUpArrowFunctionKey:
output->write(CTRL('K'));
break;
case NSDownArrowFunctionKey:
output->write(CTRL('J'));
break;
default:
if (uc <= 0x7f)
{
char c = uc;
if (flags & (NSShiftKeyMask | NSAlphaShiftKeyMask))
{
c = toupper(c);
}
if (flags & NSControlKeyMask)
c = CTRL(c);
output->write(c);
}
break;
}
}
}
@end