Some progress towards having a real editor

Signed-off-by: Christophe de Dinechin <christophe@dinechin.org>
This commit is contained in:
Christophe de Dinechin 2022-10-14 21:47:32 +02:00
parent 18a0bb3941
commit 884b3d164e
10 changed files with 1179 additions and 1187 deletions

View file

@ -51,6 +51,7 @@ DECIMAL_SOURCES=$(DECIMAL_SIZES:%=src/decimal-%.cc)
# C++ sources
CXX_SOURCES += \
src/main.cc \
src/input.cc \
src/menu.cc \
src/util.cc \
src/settings.cc \

View file

@ -317,6 +317,27 @@ void lcd_switchFont(disp_stat_t * ds, int nr)
else
fprintf(stderr, "lcd_switchFont not implemented\n");
}
int lcd_charWidth(disp_stat_t * ds, int c)
{
int width = 0;
const line_font_t *f = ds->f;
byte first = f->first_char;
byte count = f->char_cnt;
const uint16_t *offs = f->offs;
const uint8_t *data = f->data;
uint xspc = ds->xspc;
c -= first;
if (c >= 0 && c < count)
{
uint off = offs[c];
width += data[off + 0] + data[off + 2] + xspc;
}
return width;
}
int lcd_textWidth(disp_stat_t * ds, const char* text)
{
int width = 0;

View file

@ -34,6 +34,7 @@ SOURCES += \
../src/menu.cc \
../src/main.cc \
../src/util.cc \
../src/input.cc \
../src/settings.cc \
../src/object.cc \
../src/integer.cc \

View file

@ -91,7 +91,7 @@
#define KB_ON 33 //! ON
#define KB_ESC 33 //! Exit
#define KB_DOT 35 //! Dot
#define KB_SPC 36 //! Space
#define KB_SPC 37 //! Space
#define KB_RUNSTOP 36 //! R/S
#define KB_QUESTION 36 //! ?
#define KB_SHIFT 28 //! Shift

View file

@ -27,6 +27,8 @@
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// ****************************************************************************
ID(object) // Value 0 is reserved for "not implemented"
ID(string)
ID(integer)

825
src/input.cc Normal file
View file

@ -0,0 +1,825 @@
// ****************************************************************************
// input.cc DB48X project
// ****************************************************************************
//
// File Description:
//
//
//
//
//
//
//
//
//
//
// ****************************************************************************
// (C) 2022 Christophe de Dinechin <christophe@dinechin.org>
// This software is licensed under the terms outlined in LICENSE.txt
// ****************************************************************************
// This file is part of DB48X.
//
// DB48X is free software: you can redistribute it and/or modify
// it under the terms outlined in the LICENSE.txt file
//
// DB48X is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// ****************************************************************************
#include "input.h"
#include "runtime.h"
#include "settings.h"
#include "util.h"
#include <dmcp.h>
#include <stdio.h>
enum { LCD_W = LCD_X, LCD_H = LCD_Y };
// The primary input of the calculator
input Input;
runtime &input::RT = runtime::RT;
input::input()
// ----------------------------------------------------------------------------
// Initialize the input
// ----------------------------------------------------------------------------
: cursor(0), xoffset(0), mode(STACK), last(0),
shift(false), xshift(false), alpha(false), lowercase(false),
hideMenu(false), down(false), up(false)
{}
bool input::key(int key)
// ----------------------------------------------------------------------------
// Process an input key
// ----------------------------------------------------------------------------
{
bool result =
handle_shifts(key) ||
handle_editing(key) ||
handle_alpha(key) ||
handle_functions(key);
if (key && key != KEY_SHIFT)
shift = false;
return result;
}
void input::assign(int key, uint plane, object *code)
// ----------------------------------------------------------------------------
// Assign an object to a given key
// ----------------------------------------------------------------------------
{
if (key >= 1 && key <= NUM_KEYS && plane <= NUM_PLANES)
function[plane][key-1] = code;
}
object *input::assigned(int key, uint plane)
// ----------------------------------------------------------------------------
// Assign an object to a given key
// ----------------------------------------------------------------------------
{
if (key >= 1 && key <= NUM_KEYS && plane <= NUM_PLANES)
return function[plane][key-1];
return nullptr;
}
void input::menus(cstring labels[input::NUM_MENUS],
object *function[input::NUM_MENUS])
// ----------------------------------------------------------------------------
// Assign all menus at once
// ----------------------------------------------------------------------------
{
for (int m = 0; m < NUM_MENUS; m++)
menu(m, labels[m], function[m]);
}
void input::menu(uint menu_id, cstring label, object *fn)
// ----------------------------------------------------------------------------
// Assign one menu item
// ----------------------------------------------------------------------------
{
if (menu_id < NUM_MENUS)
{
int softkey_id = menu_id % NUM_SOFTKEYS;
int key = KEY_F1 + softkey_id;
int plane = menu_id / NUM_SOFTKEYS;
function[plane][key] = fn;
strncpy(menu_label[plane][softkey_id], label, NUM_LABEL_CHARS);
}
}
void input::draw_menus()
// ----------------------------------------------------------------------------
// Draw the softkey menus
// ----------------------------------------------------------------------------
{
int plane = shift_plane();
cstring labels[NUM_SOFTKEYS];
for (int k = 0; k < NUM_SOFTKEYS; k++)
labels[k] = menu_label[plane][k];
lcd_draw_menu_keys(labels);
}
void input::draw_annunciators()
// ----------------------------------------------------------------------------
// Draw the annunciators for Shift, Alpha, etc
// ----------------------------------------------------------------------------
{
// Don't clear line (we expect dark background already drawn)
if (alpha)
{
cstring label = lowercase ? "abc" : "ABC";
t20->lnfill = false;
t20->x = LCD_W - lcd_textWidth(t20, label) - 3;
t20->y = lcd_lineHeight(t20) + 2;
lcd_puts(t20, label);
}
if (shift)
{
const uint ann_height = 12;
static const byte ann_right[] =
{
0xfe, 0x3f, 0xff, 0x7f, 0x9f, 0x7f, 0xcf, 0x7f, 0xe7, 0x7f, 0x03, 0x78,
0x03, 0x70, 0xe7, 0x73, 0xcf, 0x73, 0x9f, 0x73, 0xff, 0x73, 0xfe, 0x33
};
static const byte ann_left[] =
{
0xfe, 0x3f, 0xff, 0x7f, 0xff, 0x7c, 0xff, 0x79, 0xff, 0x73, 0x0f, 0x60,
0x07, 0x60, 0xe7, 0x73, 0xe7, 0x79, 0xe7, 0x7c, 0xe7, 0x7f, 0xe6, 0x3f
};
const byte *source = xshift ? ann_right : ann_left;
int top = lcd_lineHeight(t20) + 2;
for (uint r = 0; r < ann_height; r++)
{
byte *dest = lcd_line_addr(r + top) + 48;
dest[0] = *source++;
dest[1] = *source++;
}
}
}
void input::draw_editor()
// ----------------------------------------------------------------------------
// Draw the editor
// ----------------------------------------------------------------------------
{
// Get the editor area
char *ed = RT.editor();
size_t len = RT.editing();
char *last = ed + len;
char *edline = ed;
if (!len)
return;
// Count rows and colums
int rows = 1; // Number of rows in editor
int column = 0; // Current column
int cwidth = 0; // Column width
int edrow = 0; // Row number of line being edited
int edcol = 0; // Column of line being edited
int cursx = 0; // Cursor X position
for (char *p = ed; p <= last; p++)
{
if (p - ed == (int) cursor)
{
edrow = rows - 1;
edcol = column;
edline = p - edcol;
cursx = cwidth;
}
if (p == last)
break;
if (*p == '\n')
{
rows++;
column = 0;
cwidth = 0;
}
else
{
column++;
cwidth += lcd_charWidth(fReg, (byte) *p);
}
}
// Check if we want to move the cursor up or down
if (up || down)
{
int r = 0;
int c = 0;
int tgt = edrow - up + down;
bool done = false;
for (char *p = ed; p < last && !done; p++)
{
if (*p == '\n')
{
r++;
c = 0;
}
else
{
c++;
}
if ((r == tgt && c >= edcol) || r > tgt)
{
cursor = p - ed;
edrow = r;
edline = p - c;
done = true;
}
}
up = false;
down = false;
}
// Draw the area that fits on the screen
int lineHeight = lcd_lineHeight(fReg);
int top = lcd_lineHeight(t20) + 2;
int bottom = LCD_H - (hideMenu ? 0 : LCD_MENU_LINES);
int availableHeight = bottom - top;
int availableRows = availableHeight / lineHeight;
char *display = ed;
if (rows > availableRows)
{
// Skip rows to show the cursor
int skip =
edrow < availableRows ? 0 :
edrow >= rows - availableRows ? rows - availableRows :
edrow - availableRows / 2;
for (int r = 0; r < skip; r++)
while (*display != '\n')
display++;
rows = availableRows;
}
// Draw the editor rows
int skip = 64;
int cursw = t20->f->width;
if (xoffset > cursx)
xoffset = (cursx > skip) ? cursx - skip : 0;
else if (xoffset + LCD_W - cursw < cursx)
xoffset = cursx - LCD_W + cursw + skip;
int y = bottom - rows * lineHeight;
int x = -xoffset;
fReg->x = x;
fReg->y = y;
fReg->lnfill = false;
fReg->bgfill = false;
t20->inv = true;
t20->lnfill = false;
t20->bgfill = false;
for (int r = 0; r < rows && display < last; r++)
{
int drawCursor = display == edline;
while (display < last && *display != '\n')
{
int c = *display++;
int cw = lcd_charWidth(fReg, c);
if (fReg->x >= 0 && fReg->x + cw < LCD_W)
{
const char buf[2] = { (char) c, 0 };
lcd_writeText(fReg, buf);
}
else
{
fReg->x += cw;
}
}
if (drawCursor)
{
char cursorChar =
mode == DIRECT ? 'd' :
mode == TEXT ? (lowercase ? 'l' : 'c') :
mode == PROGRAM ? 'p' :
mode == ALGEBRAIC ? 'a' :
mode == MATRIX ? 'm' : 'x';
char buf[2] = { cursorChar, 0 };
lcd_fill_rect(x + cursx, y, 2, lineHeight, 1);
t20->x = x + cursx;
t20->y = y + (lineHeight - top) / 2 + 1;
t20->bgfill = true;
lcd_putsR(t20, buf);
t20->bgfill = false;
}
fReg->x = x;
fReg->y += lineHeight;
}
// Restore display state
fReg->inv = false;
fReg->lnfill = true;
fReg->bgfill = true;
t20->inv = false;
t20->lnfill = true;
t20->bgfill = true;
}
void input::draw_error()
// ----------------------------------------------------------------------------
// Draw the error message if there is one
// ----------------------------------------------------------------------------
{
if (cstring err = RT.error())
{
const int b = 4;
lcd_switchFont(fReg, 5);
int lineHeight = lcd_lineHeight(fReg);
int top = lcd_lineHeight(t20) + 10;
int height = LCD_H / 3;
int width = LCD_W - 20;
int x = LCD_W / 2 - width / 2;
int y = top;
lcd_fill_rect(x, y, width, height, 1);
lcd_fill_rect(x + b, y + b, width - 2*b, height - 2*b, 0);
x += 2*b+1;
y += 2*b+1;
fReg->x = x;
fReg->y = y;
fReg->lnfill = false;
fReg->bgfill = false;
if (cstring cmd = RT.command())
{
lcd_print(fReg, "%s error:", cmd);
fReg->y += lineHeight;
}
lcd_puts(fReg, err);
fReg->lnfill = true;
fReg->bgfill = true;
}
}
bool input::handle_shifts(int key)
// ----------------------------------------------------------------------------
// Handle status changes in shift keys
// ----------------------------------------------------------------------------
{
bool consumed = false;
if (key == KEY_SHIFT)
{
#define SHM(d, a, s) ((d<<2) | (a<<1) | (s<<0))
#define SHD(d, a, s) (1 << SHM(d, a, s))
bool dshift = last == KEY_SHIFT; // Double shift toggles alpha
int plane = SHM(dshift, alpha, shift);
const unsigned nextShift =
SHD(0, 0, 0) |
SHD(0, 1, 0) |
SHD(1, 0, 0) |
SHD(1, 1, 0);
const unsigned nextAlpha =
SHD(0, 0, 1) |
SHD(0, 1, 0) |
SHD(0, 1, 1) |
SHD(1, 0, 1);
shift = (nextShift & (1 << plane)) != 0;
alpha = (nextAlpha & (1 << plane)) != 0;
consumed = true;
#undef SHM
#undef SHD
}
if (key)
{
last = key;
}
return consumed;
}
bool input::handle_editing(int key)
// ----------------------------------------------------------------------------
// Some keys always deal with editing
// ----------------------------------------------------------------------------
{
bool consumed = false;
size_t editing = RT.editing();
if (editing)
{
switch(key)
{
case KEY_BSP:
if (shift && cursor < editing)
{
// Shift + Backspace = Delete to right of cursor
RT.remove(cursor, 1);
}
else if (!shift && cursor > 0)
{
// Backspace = Erase on left of cursor
cursor--;
RT.remove(cursor, 1);
}
else
{
// Limits of line: beep
beep(4400, 50);
}
return true;
case KEY_ENTER:
{
if (shift)
{
// TODO: Show Alpha menu
// For now, shift lowercase
lowercase = !lowercase;
}
else
{
// Finish editing and parse the result
object *obj = object::parse(RT.editor());
if (obj)
{
// We successfully parsed the line
RT.clear();
shift = false;
alpha = false;
cursor = 0;
xoffset = 0;
obj->evaluate();
}
else
{
cstring pos = RT.source();
cstring ed = RT.editor();
if (pos >= ed && pos <= ed + editing)
cursor = pos - ed;
beep(3300, 100);
}
}
return true;
}
case KEY_EXIT:
if (shift)
{
SET_ST(STAT_PGM_END);
shift = false;
alpha = false;
}
else if (RT.error())
{
// Clear error
RT.error(nullptr);
shift = false;
}
else
{
// Clear the editor
RT.clear();
shift = false;
alpha = false;
}
return true;
case KEY_UP:
if (shift)
up = true;
else if (cursor > 0)
cursor--;
else
beep(4000, 50);
return true;
case KEY_DOWN:
if (shift)
down = true;
else if (cursor < editing)
cursor++;
else
beep(4800, 50);
return true;
case 0:
return false;
}
}
else
{
switch(key)
{
case KEY_BSP:
// RT.evaluate(ID_drop);
return true;
case KEY_ENTER:
// RT.evaluate(ID_dup);
return true;
case KEY_EXIT:
if (shift)
SET_ST(STAT_PGM_END);
shift = false;
alpha = false;
return true;
case KEY_UP:
// RT.evaluate(ID_stack);
return false;
case KEY_DOWN:
// Bring the object to the editor
return false;
case 0:
return false;
}
}
return consumed;
}
bool input::handle_alpha(int key)
// ----------------------------------------------------------------------------
// Handle alphabetic input
// ----------------------------------------------------------------------------
{
if (!alpha || !key)
return false;
static const char upper[] =
"ABCDEF"
"GHIJKL"
"_MNO_"
"_PQRS"
"_TUVW"
"_XYZ_"
"_:.? ";
static const char lower[] =
"abcdef"
"ghijkl"
"_mno_"
"_pqrs"
"_tuvw"
"_xyz-"
"_:.? ";
static const char shifted[] =
"\x85^\x82([{"
"\x86%\x87<=>"
"_\"'\x98_"
"_789\x80"
"_456\x81"
"_123-"
"_;,!+";
key--;
char c = shift ? shifted[key] : lowercase ? lower[key] : upper[key];
RT.insert(cursor, c);
cursor++;
// Test delimiters
int closing = 0;
switch(c)
{
case '(': closing = ')'; break;
case '[': closing = ']'; break;
case '{': closing = '}'; break;
case ':': closing = ':'; break;
case '"': closing = '"'; break;
case '\'': closing = '\''; break;
}
if (closing)
RT.insert(cursor, closing);
shift = false;
return 1;
}
// ============================================================================
//
// Tables with the default assignments
//
// ============================================================================
static const byte defaultUnshiftedCommand[2*input::NUM_KEYS] =
// ----------------------------------------------------------------------------
// RPL code for the commands assigned by default to each key
// ----------------------------------------------------------------------------
// All the default-assigned commands fit in one or two bytes
{
#define OP2BYTES(key, id) \
(id) < 0x80 ? (id) : ((id) & 0x7F) | 0x80, \
(id) < 0x80 ? 0 : ((id) >> 7)
OP2BYTES(KEY_SIGMA, 0),
OP2BYTES(KEY_INV, 0),
OP2BYTES(KEY_SQRT, 0),
OP2BYTES(KEY_LOG, 0),
OP2BYTES(KEY_LN, 0),
OP2BYTES(KEY_XEQ, 0),
OP2BYTES(KEY_STO, 0),
OP2BYTES(KEY_RCL, 0),
OP2BYTES(KEY_RDN, 0),
OP2BYTES(KEY_SIN, 0),
OP2BYTES(KEY_COS, 0),
OP2BYTES(KEY_TAN, 0),
OP2BYTES(KEY_ENTER, 0),
OP2BYTES(KEY_SWAP, 0),
OP2BYTES(KEY_CHS, 0),
OP2BYTES(KEY_E, 0),
OP2BYTES(KEY_BSP, 0),
OP2BYTES(KEY_UP, 0),
OP2BYTES(KEY_7, 0),
OP2BYTES(KEY_8, 0),
OP2BYTES(KEY_9, 0),
OP2BYTES(KEY_DIV, 0),
OP2BYTES(KEY_DOWN, 0),
OP2BYTES(KEY_4, 0),
OP2BYTES(KEY_5, 0),
OP2BYTES(KEY_6, 0),
OP2BYTES(KEY_MUL, 0),
OP2BYTES(KEY_SHIFT, 0),
OP2BYTES(KEY_1, 0),
OP2BYTES(KEY_2, 0),
OP2BYTES(KEY_3, 0),
OP2BYTES(KEY_SUB, 0),
OP2BYTES(KEY_EXIT, 0),
OP2BYTES(KEY_0, 0),
OP2BYTES(KEY_DOT, 0),
OP2BYTES(KEY_RUN, 0),
OP2BYTES(KEY_ADD, 0),
OP2BYTES(KEY_F1, 0),
OP2BYTES(KEY_F2, 0),
OP2BYTES(KEY_F3, 0),
OP2BYTES(KEY_F4, 0),
OP2BYTES(KEY_F5, 0),
OP2BYTES(KEY_F6, 0),
OP2BYTES(KEY_SCREENSHOT, 0),
OP2BYTES(KEY_SH_UP, 0),
OP2BYTES(KEY_SH_DOWN, 0),
};
static const byte defaultShiftedCommand[2*input::NUM_KEYS] =
// ----------------------------------------------------------------------------
// RPL code for the commands assigned by default to shifted keys
// ----------------------------------------------------------------------------
// All the default assigned commands fit in one or two bytes
{
OP2BYTES(KEY_SIGMA, 0),
OP2BYTES(KEY_INV, 0),
OP2BYTES(KEY_SQRT, 0),
OP2BYTES(KEY_LOG, 0),
OP2BYTES(KEY_LN, 0),
OP2BYTES(KEY_XEQ, 0),
OP2BYTES(KEY_STO, 0),
OP2BYTES(KEY_RCL, 0),
OP2BYTES(KEY_RDN, 0),
OP2BYTES(KEY_SIN, 0),
OP2BYTES(KEY_COS, 0),
OP2BYTES(KEY_TAN, 0),
OP2BYTES(KEY_ENTER, 0),
OP2BYTES(KEY_SWAP, 0),
OP2BYTES(KEY_CHS, 0),
OP2BYTES(KEY_E, 0),
OP2BYTES(KEY_BSP, 0),
OP2BYTES(KEY_UP, 0),
OP2BYTES(KEY_7, 0),
OP2BYTES(KEY_8, 0),
OP2BYTES(KEY_9, 0),
OP2BYTES(KEY_DIV, 0),
OP2BYTES(KEY_DOWN, 0),
OP2BYTES(KEY_4, 0),
OP2BYTES(KEY_5, 0),
OP2BYTES(KEY_6, 0),
OP2BYTES(KEY_MUL, 0),
OP2BYTES(KEY_SHIFT, 0),
OP2BYTES(KEY_1, 0),
OP2BYTES(KEY_2, 0),
OP2BYTES(KEY_3, 0),
OP2BYTES(KEY_SUB, 0),
OP2BYTES(KEY_EXIT, 0),
OP2BYTES(KEY_0, 0),
OP2BYTES(KEY_DOT, 0),
OP2BYTES(KEY_RUN, 0),
OP2BYTES(KEY_ADD, 0),
OP2BYTES(KEY_F1, 0),
OP2BYTES(KEY_F2, 0),
OP2BYTES(KEY_F3, 0),
OP2BYTES(KEY_F4, 0),
OP2BYTES(KEY_F5, 0),
OP2BYTES(KEY_F6, 0),
OP2BYTES(KEY_SCREENSHOT, 0),
OP2BYTES(KEY_SH_UP, 0),
OP2BYTES(KEY_SH_DOWN, 0),
};
static const byte defaultLongShiftedCommand[2*input::NUM_KEYS] =
// ----------------------------------------------------------------------------
// RPL code for the commands assigned by default to long-shifted keys
// ----------------------------------------------------------------------------
// All the default assigned commands fit in one or two bytes
{
OP2BYTES(KEY_SIGMA, 0),
OP2BYTES(KEY_INV, 0),
OP2BYTES(KEY_SQRT, 0),
OP2BYTES(KEY_LOG, 0),
OP2BYTES(KEY_LN, 0),
OP2BYTES(KEY_XEQ, 0),
OP2BYTES(KEY_STO, 0),
OP2BYTES(KEY_RCL, 0),
OP2BYTES(KEY_RDN, 0),
OP2BYTES(KEY_SIN, 0),
OP2BYTES(KEY_COS, 0),
OP2BYTES(KEY_TAN, 0),
OP2BYTES(KEY_ENTER, 0),
OP2BYTES(KEY_SWAP, 0),
OP2BYTES(KEY_CHS, 0),
OP2BYTES(KEY_E, 0),
OP2BYTES(KEY_BSP, 0),
OP2BYTES(KEY_UP, 0),
OP2BYTES(KEY_7, 0),
OP2BYTES(KEY_8, 0),
OP2BYTES(KEY_9, 0),
OP2BYTES(KEY_DIV, 0),
OP2BYTES(KEY_DOWN, 0),
OP2BYTES(KEY_4, 0),
OP2BYTES(KEY_5, 0),
OP2BYTES(KEY_6, 0),
OP2BYTES(KEY_MUL, 0),
OP2BYTES(KEY_SHIFT, 0),
OP2BYTES(KEY_1, 0),
OP2BYTES(KEY_2, 0),
OP2BYTES(KEY_3, 0),
OP2BYTES(KEY_SUB, 0),
OP2BYTES(KEY_EXIT, 0),
OP2BYTES(KEY_0, 0),
OP2BYTES(KEY_DOT, 0),
OP2BYTES(KEY_RUN, 0),
OP2BYTES(KEY_ADD, 0),
OP2BYTES(KEY_F1, 0),
OP2BYTES(KEY_F2, 0),
OP2BYTES(KEY_F3, 0),
OP2BYTES(KEY_F4, 0),
OP2BYTES(KEY_F5, 0),
OP2BYTES(KEY_F6, 0),
OP2BYTES(KEY_SCREENSHOT, 0),
OP2BYTES(KEY_SH_UP, 0),
OP2BYTES(KEY_SH_DOWN, 0),
};
static const byte *const defaultCommand[input::NUM_PLANES] =
// ----------------------------------------------------------------------------
// Pointers to the default commands
// ----------------------------------------------------------------------------
{
defaultUnshiftedCommand,
defaultShiftedCommand,
defaultLongShiftedCommand,
};
bool input::handle_functions(int key)
// ----------------------------------------------------------------------------
// Check if we have one of the soft menu functions
// ----------------------------------------------------------------------------
{
if (!key)
return false;
int plane = shift_plane();
object *obj = function[plane][key-1];
if (obj)
{
obj->evaluate();
return true;
}
const byte *ptr = defaultCommand[plane] + key-1;
if (*ptr)
{
obj = (object *) ptr; // Uh oh! Evaluate bytecode in ROM
obj->evaluate();
return true;
}
return false;
}

115
src/input.h Normal file
View file

@ -0,0 +1,115 @@
#ifndef INPUT_H
#define INPUT_H
// ****************************************************************************
// input.h DB48X project
// ****************************************************************************
//
// File Description:
//
// Calculator input
//
//
//
//
//
//
//
//
// ****************************************************************************
// (C) 2022 Christophe de Dinechin <christophe@dinechin.org>
// This software is licensed under the terms outlined in LICENSE.txt
// ****************************************************************************
// This file is part of DB48X.
//
// DB48X is free software: you can redistribute it and/or modify
// it under the terms outlined in the LICENSE.txt file
//
// DB48X is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// ****************************************************************************
#include <types.h>
#include <object.h>
struct runtime;
struct input
// ----------------------------------------------------------------------------
// Calculator input state
// ----------------------------------------------------------------------------
{
input();
enum modes
// ------------------------------------------------------------------------
// Current input mode
// ------------------------------------------------------------------------
{
STACK, // Showing the stack, not editing
DIRECT, // Keys like 'sin' evaluate directly
TEXT, // Alphanumeric entry, e.g. in strings
PROGRAM, // Keys like 'sin' show as 'sin' in the editor
ALGEBRAIC, // Keys like 'sin' show as 'sin()'
MATRIX, // Matrix/vector mode
};
enum
// ------------------------------------------------------------------------
// Dimensioning constants
// ------------------------------------------------------------------------
{
NUM_PLANES = 3, // NONE, Shift and "extended" shift
NUM_KEYS = 46, // Including SCREENSHOT, SH_UP and SH_DN
NUM_SOFTKEYS = 6, // Number of softkeys
NUM_LABEL_CHARS = 10, // Number of characters per menu label
NUM_MENUS = NUM_PLANES * NUM_SOFTKEYS,
};
bool key(int key);
void assign(int key, uint plane, object *code);
object * assigned(int key, uint plane);
void menus(cstring labels[NUM_MENUS], object *function[NUM_MENUS]);
void menu(uint index, cstring labels, object *function);
void draw_menus();
void draw_annunciators();
void draw_editor();
void draw_error();
protected:
bool handle_shifts(int key);
bool handle_editing(int key);
bool handle_alpha(int key);
bool handle_functions(int key);
bool handle_enter(int key);
bool handle_backspace(int key);
uint shift_plane() { return xshift ? 2 : shift ? 1 : 0; }
protected:
uint cursor; // Cursor position in buffer
int xoffset; // Offset of the cursor
modes mode; // Current editing mode
int last; // Last key
bool shift : 1; // Normal shift active
bool xshift : 1; // Extended shift active (simulate Right)
bool alpha : 1; // Alpha mode active
bool lowercase : 1; // Lowercase
bool hideMenu : 1; // Hide the menu
bool down : 1; // Move one line down
bool up : 1; // Move one line up
protected:
// Key mappings
object *function[NUM_PLANES][NUM_KEYS];
char menu_label[NUM_PLANES][NUM_SOFTKEYS][NUM_LABEL_CHARS];
static runtime &RT;
};
extern input Input;
#endif // INPUT_H

File diff suppressed because it is too large Load diff

View file

@ -77,6 +77,41 @@ struct object
~object() {}
// ========================================================================
//
// Object command protocol
//
// ========================================================================
enum command
// ------------------------------------------------------------------------
// The commands that all handlers must deal with
// ------------------------------------------------------------------------
{
EVAL, // Evaluate the object (e.g. push on stack)
SIZE, // Compute the size of the object
PARSE, // Parse the object
RENDER, // Render the object
};
enum result
// ------------------------------------------------------------------------
// Common return values for handlers
// ------------------------------------------------------------------------
{
OK = 0, // Command ran successfully
SKIP = -1, // Command not for this handler, try next
ERROR = -2, // Error processing the command
};
// ========================================================================
//
// Memory management
//
// ========================================================================
static size_t required_memory(id i)
// ------------------------------------------------------------------------
// Compute the amount of memory required for an object
@ -85,6 +120,52 @@ struct object
return leb128size(i);
}
id type()
// ------------------------------------------------------------------------
// Return the type of the object
// ------------------------------------------------------------------------
{
byte *ptr = (byte *) this;
return (id) leb128(ptr);
}
size_t size(runtime &rt = RT)
// ------------------------------------------------------------------------
// Compute the size of the object by calling the handler with SIZE
// ------------------------------------------------------------------------
{
return (size_t) run(rt, SIZE);
}
object *skip(runtime &rt = RT)
// ------------------------------------------------------------------------
// Return the pointer to the next object in memory by skipping its size
// ------------------------------------------------------------------------
{
return this + size(rt);
}
byte * payload()
// ------------------------------------------------------------------------
// Return the object's payload, i.e. first byte after ID
// ------------------------------------------------------------------------
{
byte *ptr = (byte *) this;
leb128(ptr); // Skip ID
return ptr;
}
void evaluate(runtime &rt = RT)
// ------------------------------------------------------------------------
// Evaluate an object by calling the handler
// ------------------------------------------------------------------------
{
run(rt, EVAL);
}
static object *parse(cstring beg, cstring *end = nullptr, runtime &rt = RT)
// ------------------------------------------------------------------------
// Try parsing the object as a top-level temporary
@ -104,30 +185,6 @@ struct object
}
enum command
// ------------------------------------------------------------------------
// The commands that all handlers must deal with
// ------------------------------------------------------------------------
{
EVAL,
SIZE,
PARSE,
RENDER,
LAST,
};
enum result
// ------------------------------------------------------------------------
// Common return values for handlers
// ------------------------------------------------------------------------
{
OK = 0, // Object parsed successfully
SKIP = -1, // Not for this handler
ERROR = -2, // Error processing the command
};
static intptr_t run(runtime &rt, id type, command cmd, void *arg = nullptr)
// ------------------------------------------------------------------------
// Run a command without an object
@ -140,43 +197,16 @@ struct object
intptr_t run(runtime &rt, command cmd, void *arg = nullptr)
// ------------------------------------------------------------------------
// Run an arbitrary command
// Run an arbitrary command on the object
// ------------------------------------------------------------------------
{
byte *ptr = (byte *) this;
id type = (id) leb128(ptr);
id type = (id) leb128(ptr); // Don't use type() to update payload
if (type >= NUM_IDS)
return -1;
return handler[type](rt, cmd, arg, this, (object *) ptr);
}
size_t size(runtime &rt = RT)
// ------------------------------------------------------------------------
// Compute the size of the object by calling the handler with SIZE
// ------------------------------------------------------------------------
{
return (size_t) run(rt, SIZE);
}
object *skip(runtime &rt = RT)
// ------------------------------------------------------------------------
// Return the pointer to the next object in memory by skipping its size
// ------------------------------------------------------------------------
{
return this + size(rt);
}
byte * payload()
// ------------------------------------------------------------------------
// Return the object's payload, i.e. first byte after ID
// ------------------------------------------------------------------------
{
byte *ptr = (byte *) this;
leb128(ptr); // Skip ID
return ptr;
}
struct parser
// ------------------------------------------------------------------------
// Arguments to the PARSE command

View file

@ -62,6 +62,7 @@ struct runtime
runtime(byte *mem = nullptr, size_t size = 0)
: Error(nullptr),
ErrorSource(nullptr),
ErrorCommand(nullptr),
Code(nullptr),
LowMem(),
Globals(),
@ -205,7 +206,7 @@ struct runtime
if (available(len) >= len)
{
size_t moved = Editing - offset;
memmove(editor() + offset, editor() + offset + len, moved);
memmove(editor() + offset + len, editor() + offset, moved);
memcpy(editor() + offset, data, len);
Editing += len;
}
@ -232,7 +233,7 @@ struct runtime
if (offset > end)
offset = end;
len = end - offset;
memmove(editor() + offset + len, editor() + offset, len);
memmove(editor() + offset, editor() + offset + len, Editing - end);
Editing -= len;
}
@ -285,8 +286,9 @@ struct runtime
// ------------------------------------------------------------------------
{
if (StackTop >= StackBottom)
return error("Cannot replace empty stack");
*StackTop = obj;
error("Cannot replace empty stack");
else
*StackTop = obj;
}
object *pop()
@ -315,8 +317,9 @@ struct runtime
// ------------------------------------------------------------------------
{
if (idx >= depth())
return error("Insufficient stack depth");
StackTop[idx] = obj;
error("Insufficient stack depth");
else
StackTop[idx] = obj;
}
uint depth()
@ -341,7 +344,10 @@ struct runtime
// ------------------------------------------------------------------------
{
if (available(sizeof(callee)) < sizeof(callee))
return error("Too many recursive calls");
{
error("Too many recursive calls");
return;
}
StackTop--;
StackBottom--;
for (object **s = StackBottom; s < StackTop; s++)
@ -356,7 +362,10 @@ struct runtime
// ------------------------------------------------------------------------
{
if ((byte *) Returns >= (byte *) HighMem)
return error("Cannot return without a caller");
{
error("Cannot return without a caller");
return;
}
Code = *Returns++;
StackTop++;
StackBottom++;
@ -450,19 +459,54 @@ struct runtime
//
// ========================================================================
void error(cstring message, cstring source = nullptr)
runtime &error(cstring message, cstring source = nullptr)
// ------------------------------------------------------------------------
// Set the error message
// ------------------------------------------------------------------------
{
Error = message;
ErrorSource = source;
return *this;
}
cstring error()
// ------------------------------------------------------------------------
// Get the error message
// ------------------------------------------------------------------------
{
return Error;
}
cstring source()
// ------------------------------------------------------------------------
// Get the pointer to the problem
// ------------------------------------------------------------------------
{
return ErrorSource;
}
runtime &command(cstring cmd)
// ------------------------------------------------------------------------
// Set the faulting command
// ------------------------------------------------------------------------
{
ErrorCommand = cmd;
return *this;
}
cstring command()
// ------------------------------------------------------------------------
// Get the faulting command if there is one
// ------------------------------------------------------------------------
{
return ErrorCommand;
}
public:
protected:
cstring Error; // Error message if any
cstring ErrorSource; // Source of the error if known
cstring ErrorCommand; // Source of the error if known
object *Code; // Currently executing code
object *LowMem;
global *Globals;
@ -476,6 +520,7 @@ struct runtime
// Pointers that are GC-adjusted
gcptr *GCSafe;
public:
// The one and only runtime
static runtime RT;
};