summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cgram/cgram.c465
1 files changed, 286 insertions, 179 deletions
diff --git a/cgram/cgram.c b/cgram/cgram.c
index 9f7a096e..e9e5813e 100644
--- a/cgram/cgram.c
+++ b/cgram/cgram.c
@@ -1,11 +1,11 @@
-/* $NetBSD: cgram.c,v 1.9 2021/02/21 17:16:00 rillig Exp $ */
+/* $NetBSD: cgram.c,v 1.10 2021/02/21 20:33:42 rillig Exp $ */
/*-
- * Copyright (c) 2013 The NetBSD Foundation, Inc.
+ * Copyright (c) 2013, 2021 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
- * by David A. Holland.
+ * by David A. Holland and Roland Illig.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -29,6 +29,11 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+__RCSID("$NetBSD: cgram.c,v 1.10 2021/02/21 20:33:42 rillig Exp $");
+#endif
+
#include <assert.h>
#include <ctype.h>
#include <curses.h>
@@ -43,19 +48,6 @@
////////////////////////////////////////////////////////////
-static char *
-xstrdup(const char *s)
-{
- char *ret;
-
- ret = malloc(strlen(s) + 1);
- if (ret == NULL) {
- errx(1, "Out of memory");
- }
- strcpy(ret, s);
- return ret;
-}
-
static char
ch_toupper(char ch)
{
@@ -71,29 +63,74 @@ ch_tolower(char ch)
static bool
ch_isalpha(char ch)
{
- return isalpha((unsigned char)ch);
+ return isalpha((unsigned char)ch) != 0;
}
static bool
ch_islower(char ch)
{
- return islower((unsigned char)ch);
+ return islower((unsigned char)ch) != 0;
}
static bool
ch_isupper(char ch)
{
- return isupper((unsigned char)ch);
+ return isupper((unsigned char)ch) != 0;
+}
+
+static int
+imax(int a, int b)
+{
+ return a > b ? a : b;
+}
+
+static int
+imin(int a, int b)
+{
+ return a < b ? a : b;
}
////////////////////////////////////////////////////////////
+struct string {
+ char *s;
+ size_t len;
+ size_t cap;
+};
+
struct stringarray {
- char **v;
+ struct string *v;
size_t num;
};
static void
+string_init(struct string *s)
+{
+ s->s = NULL;
+ s->len = 0;
+ s->cap = 0;
+}
+
+static void
+string_add(struct string *s, char ch)
+{
+ if (s->len >= s->cap) {
+ s->cap = 2 * s->cap + 16;
+ s->s = realloc(s->s, s->cap);
+ if (s->s == NULL)
+ errx(1, "Out of memory");
+ }
+ s->s[s->len++] = ch;
+}
+
+static void
+string_finish(struct string *s)
+{
+ string_add(s, '\0');
+ s->len--;
+}
+
+static void
stringarray_init(struct stringarray *a)
{
a->v = NULL;
@@ -103,18 +140,33 @@ stringarray_init(struct stringarray *a)
static void
stringarray_cleanup(struct stringarray *a)
{
+ for (size_t i = 0; i < a->num; i++)
+ free(a->v[i].s);
free(a->v);
}
static void
-stringarray_add(struct stringarray *a, const char *s)
+stringarray_add(struct stringarray *a, struct string *s)
{
- a->v = realloc(a->v, (a->num + 1) * sizeof(a->v[0]));
- if (a->v == NULL) {
+ size_t num = a->num++;
+ a->v = realloc(a->v, a->num * sizeof a->v[0]);
+ if (a->v == NULL)
errx(1, "Out of memory");
+ a->v[num] = *s;
+}
+
+static void
+stringarray_dup(struct stringarray *dst, const struct stringarray *src)
+{
+ assert(dst->num == 0);
+ for (size_t i = 0; i < src->num; i++) {
+ struct string str;
+ string_init(&str);
+ for (const char *p = src->v[i].s; *p != '\0'; p++)
+ string_add(&str, *p);
+ string_finish(&str);
+ stringarray_add(dst, &str);
}
- a->v[a->num] = xstrdup(s);
- a->num++;
}
////////////////////////////////////////////////////////////
@@ -122,44 +174,59 @@ stringarray_add(struct stringarray *a, const char *s)
static struct stringarray lines;
static struct stringarray sollines;
static bool hinting;
-static int scrolldown;
-static int curx;
-static int cury;
+static int extent_x;
+static int extent_y;
+static int offset_x;
+static int offset_y;
+static int cursor_x;
+static int cursor_y;
+
+static int
+cur_max_x(void)
+{
+ return (int)lines.v[cursor_y].len;
+}
+
+static int
+cur_max_y(void)
+{
+ return extent_y - 1;
+}
static void
readquote(void)
{
FILE *f = popen(_PATH_FORTUNE, "r");
- if (f == NULL) {
+ if (f == NULL)
err(1, "%s", _PATH_FORTUNE);
- }
- char buf[128], buf2[8 * sizeof(buf)];
- while (fgets(buf, sizeof buf, f) != NULL) {
- char *s = strrchr(buf, '\n');
- assert(s != NULL);
- assert(strlen(s) == 1);
- *s = '\0';
-
- int i, j;
- for (i = j = 0; buf[i] != '\0'; i++) {
- if (buf[i] == '\t') {
- buf2[j++] = ' ';
- while (j % 8 != 0)
- buf2[j++] = ' ';
- } else if (buf[i] == '\b') {
- if (j > 0)
- j--;
- } else {
- buf2[j++] = buf[i];
- }
+ struct string line;
+ string_init(&line);
+
+ int ch;
+ while ((ch = fgetc(f)) != EOF) {
+ if (ch == '\n') {
+ string_finish(&line);
+ stringarray_add(&lines, &line);
+ string_init(&line);
+ } else if (ch == '\t') {
+ string_add(&line, ' ');
+ while (line.len % 8 != 0)
+ string_add(&line, ' ');
+ } else if (ch == '\b') {
+ if (line.len > 0)
+ line.len--;
+ } else {
+ string_add(&line, (char)ch);
}
- buf2[j] = '\0';
-
- stringarray_add(&lines, buf2);
- stringarray_add(&sollines, buf2);
}
+ stringarray_dup(&sollines, &lines);
+
+ extent_y = (int)lines.num;
+ for (int i = 0; i < extent_y; i++)
+ extent_x = imax(extent_x, (int)lines.v[i].len);
+
pclose(f);
}
@@ -178,8 +245,8 @@ encode(void)
key[c] = t;
}
- for (int y = 0; y < (int)lines.num; y++) {
- for (char *p = lines.v[y]; *p != '\0'; p++) {
+ for (int y = 0; y < extent_y; y++) {
+ for (char *p = lines.v[y].s; *p != '\0'; p++) {
if (ch_islower(*p))
*p = (char)('a' + key[*p - 'a']);
if (ch_isupper(*p))
@@ -191,13 +258,14 @@ encode(void)
static bool
substitute(char ch)
{
- assert(cury >= 0 && cury < (int)lines.num);
- if (curx >= (int)strlen(lines.v[cury])) {
+ assert(cursor_x >= 0 && cursor_x < extent_x);
+ assert(cursor_y >= 0 && cursor_y < extent_y);
+ if (cursor_x >= cur_max_x()) {
beep();
return false;
}
- char och = lines.v[cury][curx];
+ char och = lines.v[cursor_y].s[cursor_x];
if (!ch_isalpha(och)) {
beep();
return false;
@@ -209,7 +277,7 @@ substitute(char ch)
char uch = ch_toupper(ch);
for (int y = 0; y < (int)lines.num; y++) {
- for (char *p = lines.v[y]; *p != '\0'; p++) {
+ for (char *p = lines.v[y].s; *p != '\0'; p++) {
if (*p == loch)
*p = lch;
else if (*p == uoch)
@@ -225,44 +293,50 @@ substitute(char ch)
////////////////////////////////////////////////////////////
+static bool
+is_solved(void)
+{
+ for (size_t i = 0; i < lines.num; i++)
+ if (strcmp(lines.v[i].s, sollines.v[i].s) != 0)
+ return false;
+ return true;
+}
+
static void
redraw(void)
{
erase();
- bool won = true;
- for (int i = 0; i < LINES - 1; i++) {
- move(i, 0);
- int ln = i + scrolldown;
- if (ln < (int)lines.num) {
- for (unsigned j = 0; lines.v[i][j] != '\0'; j++) {
- char ch = lines.v[i][j];
- if (ch != sollines.v[i][j] && ch_isalpha(ch)) {
- won = false;
- }
- bool bold = false;
- if (hinting && ch == sollines.v[i][j] &&
- ch_isalpha(ch)) {
- bold = true;
- attron(A_BOLD);
- }
- addch(lines.v[i][j]);
- if (bold) {
- attroff(A_BOLD);
- }
- }
+
+ int max_y = imin(LINES - 1, extent_y - offset_y);
+ for (int y = 0; y < max_y; y++) {
+ move(y, 0);
+
+ int len = (int)lines.v[offset_y + y].len;
+ int max_x = imin(COLS - 1, len - offset_x);
+ const char *line = lines.v[offset_y + y].s;
+ const char *solline = sollines.v[offset_y + y].s;
+
+ for (int x = 0; x < max_x; x++) {
+ char ch = line[offset_x + x];
+ bool bold = hinting &&
+ ch == solline[offset_x + x] &&
+ ch_isalpha(ch);
+
+ if (bold)
+ attron(A_BOLD);
+ addch(ch);
+ if (bold)
+ attroff(A_BOLD);
}
clrtoeol();
}
move(LINES - 1, 0);
- if (won) {
+ if (is_solved())
addstr("*solved* ");
- }
addstr("~ to quit, * to cheat, ^pnfb to move");
- move(LINES - 1, 0);
-
- move(cury - scrolldown, curx);
+ move(cursor_y - offset_y, cursor_x - offset_x);
refresh();
}
@@ -273,6 +347,7 @@ opencurses(void)
initscr();
cbreak();
noecho();
+ keypad(stdscr, true);
}
static void
@@ -284,117 +359,149 @@ closecurses(void)
////////////////////////////////////////////////////////////
static void
-loop(void)
+saturate_cursor(void)
{
- bool done = false;
- while (!done) {
- redraw();
- int ch = getch();
- switch (ch) {
- case 1: /* ^A */
- case KEY_HOME:
- curx = 0;
- break;
- case 2: /* ^B */
- case KEY_LEFT:
- if (curx > 0) {
- curx--;
- } else if (cury > 0) {
- cury--;
- curx = (int)strlen(lines.v[cury]);
- }
- break;
- case 5: /* ^E */
- case KEY_END:
- curx = (int)strlen(lines.v[cury]);
- break;
- case 6: /* ^F */
- case KEY_RIGHT:
- if (curx < (int)strlen(lines.v[cury])) {
- curx++;
- } else if (cury < (int)lines.num - 1) {
- cury++;
- curx = 0;
- }
- break;
- case 12: /* ^L */
- clear();
- break;
- case 14: /* ^N */
- case KEY_DOWN:
- if (cury < (int)lines.num - 1) {
- cury++;
- }
- if (curx > (int)strlen(lines.v[cury])) {
- curx = (int)strlen(lines.v[cury]);
- }
- if (scrolldown < cury - (LINES - 2)) {
- scrolldown = cury - (LINES - 2);
- }
- break;
- case 16: /* ^P */
- case KEY_UP:
- if (cury > 0) {
- cury--;
- }
- if (curx > (int)strlen(lines.v[cury])) {
- curx = (int)strlen(lines.v[cury]);
- }
- if (scrolldown > cury) {
- scrolldown = cury;
- }
- break;
- case '*':
- hinting = !hinting;
- break;
- case '~':
- done = true;
- break;
- default:
- if (isascii(ch) && ch_isalpha((char)ch)) {
- if (substitute((char)ch)) {
- if (curx < (int)strlen(lines.v[cury])) {
- curx++;
- }
- if (curx == (int)strlen(lines.v[cury]) &&
- cury < (int)lines.num - 1) {
- curx = 0;
- cury++;
- }
- }
- } else if (curx < (int)strlen(lines.v[cury]) &&
- ch == lines.v[cury][curx]) {
- curx++;
- if (curx == (int)strlen(lines.v[cury]) &&
- cury < (int)lines.num - 1) {
- curx = 0;
- cury++;
- }
- } else {
- beep();
+ assert(cursor_y >= 0);
+ assert(cursor_y <= cur_max_y());
+
+ assert(cursor_x >= 0);
+ cursor_x = imin(cursor_x, cur_max_x());
+}
+
+static void
+scroll_into_view(void)
+{
+ if (cursor_x < offset_x)
+ offset_x = cursor_x;
+ if (cursor_x > offset_x + COLS - 1)
+ offset_x = cursor_x - (COLS - 1);
+
+ if (cursor_y < offset_y)
+ offset_y = cursor_y;
+ if (cursor_y > offset_y + LINES - 2)
+ offset_y = cursor_y - (LINES - 2);
+}
+
+static void
+handle_char_input(int ch)
+{
+ if (isascii(ch) && ch_isalpha((char)ch)) {
+ if (substitute((char)ch)) {
+ if (cursor_x < cur_max_x())
+ cursor_x++;
+ if (cursor_x == cur_max_x() &&
+ cursor_y < cur_max_y()) {
+ cursor_x = 0;
+ cursor_y++;
}
- break;
}
+ } else if (cursor_x < cur_max_x() &&
+ ch == lines.v[cursor_y].s[cursor_x]) {
+ cursor_x++;
+ if (cursor_x == cur_max_x() &&
+ cursor_y < cur_max_y()) {
+ cursor_x = 0;
+ cursor_y++;
+ }
+ } else {
+ beep();
}
}
-////////////////////////////////////////////////////////////
-
-int
-main(void)
+static bool
+handle_key(void)
{
+ int ch = getch();
+
+ switch (ch) {
+ case 1: /* ^A */
+ case KEY_HOME:
+ cursor_x = 0;
+ break;
+ case 2: /* ^B */
+ case KEY_LEFT:
+ if (cursor_x > 0) {
+ cursor_x--;
+ } else if (cursor_y > 0) {
+ cursor_y--;
+ cursor_x = cur_max_x();
+ }
+ break;
+ case 5: /* ^E */
+ case KEY_END:
+ cursor_x = cur_max_x();
+ break;
+ case 6: /* ^F */
+ case KEY_RIGHT:
+ if (cursor_x < cur_max_x()) {
+ cursor_x++;
+ } else if (cursor_y < cur_max_y()) {
+ cursor_y++;
+ cursor_x = 0;
+ }
+ break;
+ case 12: /* ^L */
+ clear();
+ break;
+ case 14: /* ^N */
+ case KEY_DOWN:
+ if (cursor_y < cur_max_y())
+ cursor_y++;
+ break;
+ case 16: /* ^P */
+ case KEY_UP:
+ if (cursor_y > 0)
+ cursor_y--;
+ break;
+ case '*':
+ hinting = !hinting;
+ break;
+ case '~':
+ return false;
+ default:
+ handle_char_input(ch);
+ break;
+ }
+ return true;
+}
+static void
+init(void)
+{
stringarray_init(&lines);
stringarray_init(&sollines);
srandom((unsigned int)time(NULL));
readquote();
encode();
opencurses();
+}
- keypad(stdscr, true);
- loop();
+static void
+loop(void)
+{
+ for (;;) {
+ redraw();
+ if (!handle_key())
+ break;
+ saturate_cursor();
+ scroll_into_view();
+ }
+}
+static void
+clean_up(void)
+{
closecurses();
stringarray_cleanup(&sollines);
stringarray_cleanup(&lines);
}
+
+////////////////////////////////////////////////////////////
+
+int
+main(void)
+{
+ init();
+ loop();
+ clean_up();
+}