xref: /freebsd-src/contrib/less/lesskey_parse.c (revision c77c488926555ca344ae3a417544cf7a720e1de1)
130a1828cSXin LI /*
2*c77c4889SXin LI  * Copyright (C) 1984-2024  Mark Nudelman
330a1828cSXin LI  *
430a1828cSXin LI  * You may distribute under the terms of either the GNU General Public
530a1828cSXin LI  * License or the Less License, as specified in the README file.
630a1828cSXin LI  *
730a1828cSXin LI  * For more information, see the README file.
830a1828cSXin LI  */
930a1828cSXin LI 
10d713e089SXin LI #include "defines.h"
1130a1828cSXin LI #include <stdio.h>
1230a1828cSXin LI #include <string.h>
1330a1828cSXin LI #include <stdlib.h>
1430a1828cSXin LI #include "lesskey.h"
1530a1828cSXin LI #include "cmd.h"
1630a1828cSXin LI #include "xbuf.h"
1730a1828cSXin LI 
1830a1828cSXin LI #define CONTROL(c)      ((c)&037)
1930a1828cSXin LI #define ESC             CONTROL('[')
2030a1828cSXin LI 
2130a1828cSXin LI extern void lesskey_parse_error(char *msg);
2230a1828cSXin LI extern char *homefile(char *filename);
23*c77c4889SXin LI extern void *ecalloc(size_t count, size_t size);
24d713e089SXin LI extern int lstrtoi(char *str, char **end, int radix);
2595270f73SXin LI extern char version[];
2630a1828cSXin LI 
2730a1828cSXin LI static int linenum;
2830a1828cSXin LI static int errors;
2995270f73SXin LI static int less_version = 0;
30*c77c4889SXin LI static char *lesskey_file = NULL;
3130a1828cSXin LI 
32*c77c4889SXin LI static constant struct lesskey_cmdname cmdnames[] =
3330a1828cSXin LI {
3430a1828cSXin LI 	{ "back-bracket",         A_B_BRACKET },
3530a1828cSXin LI 	{ "back-line",            A_B_LINE },
3630a1828cSXin LI 	{ "back-line-force",      A_BF_LINE },
3730a1828cSXin LI 	{ "back-screen",          A_B_SCREEN },
3830a1828cSXin LI 	{ "back-scroll",          A_B_SCROLL },
3930a1828cSXin LI 	{ "back-search",          A_B_SEARCH },
4030a1828cSXin LI 	{ "back-window",          A_B_WINDOW },
4130a1828cSXin LI 	{ "clear-mark",           A_CLRMARK },
42*c77c4889SXin LI 	{ "clear-search",         A_CLR_SEARCH },
4330a1828cSXin LI 	{ "debug",                A_DEBUG },
4430a1828cSXin LI 	{ "digit",                A_DIGIT },
4530a1828cSXin LI 	{ "display-flag",         A_DISP_OPTION },
4630a1828cSXin LI 	{ "display-option",       A_DISP_OPTION },
4730a1828cSXin LI 	{ "end",                  A_GOEND },
4830a1828cSXin LI 	{ "end-scroll",           A_RRSHIFT },
4930a1828cSXin LI 	{ "examine",              A_EXAMINE },
5030a1828cSXin LI 	{ "filter",               A_FILTER },
5130a1828cSXin LI 	{ "first-cmd",            A_FIRSTCMD },
5230a1828cSXin LI 	{ "firstcmd",             A_FIRSTCMD },
5330a1828cSXin LI 	{ "flush-repaint",        A_FREPAINT },
5430a1828cSXin LI 	{ "forw-bracket",         A_F_BRACKET },
5530a1828cSXin LI 	{ "forw-forever",         A_F_FOREVER },
5630a1828cSXin LI 	{ "forw-line",            A_F_LINE },
5730a1828cSXin LI 	{ "forw-line-force",      A_FF_LINE },
5830a1828cSXin LI 	{ "forw-screen",          A_F_SCREEN },
5930a1828cSXin LI 	{ "forw-screen-force",    A_FF_SCREEN },
6030a1828cSXin LI 	{ "forw-scroll",          A_F_SCROLL },
6130a1828cSXin LI 	{ "forw-search",          A_F_SEARCH },
62*c77c4889SXin LI 	{ "forw-until-hilite",    A_F_UNTIL_HILITE },
6330a1828cSXin LI 	{ "forw-window",          A_F_WINDOW },
6430a1828cSXin LI 	{ "goto-end",             A_GOEND },
6530a1828cSXin LI 	{ "goto-end-buffered",    A_GOEND_BUF },
6630a1828cSXin LI 	{ "goto-line",            A_GOLINE },
6730a1828cSXin LI 	{ "goto-mark",            A_GOMARK },
6830a1828cSXin LI 	{ "help",                 A_HELP },
6930a1828cSXin LI 	{ "index-file",           A_INDEX_FILE },
7030a1828cSXin LI 	{ "invalid",              A_UINVALID },
7130a1828cSXin LI 	{ "left-scroll",          A_LSHIFT },
7230a1828cSXin LI 	{ "next-file",            A_NEXT_FILE },
7330a1828cSXin LI 	{ "next-tag",             A_NEXT_TAG },
7430a1828cSXin LI 	{ "no-scroll",            A_LLSHIFT },
75*c77c4889SXin LI 	{ "noaction",             A_NOACTION },
76*c77c4889SXin LI 	{ "osc8-forw-search",     A_OSC8_F_SEARCH },
77*c77c4889SXin LI 	{ "osc8-back-search",     A_OSC8_B_SEARCH },
78*c77c4889SXin LI 	{ "osc8-open",            A_OSC8_OPEN },
7930a1828cSXin LI 	{ "percent",              A_PERCENT },
8030a1828cSXin LI 	{ "pipe",                 A_PIPE },
8130a1828cSXin LI 	{ "prev-file",            A_PREV_FILE },
8230a1828cSXin LI 	{ "prev-tag",             A_PREV_TAG },
83*c77c4889SXin LI 	{ "pshell",               A_PSHELL },
8430a1828cSXin LI 	{ "quit",                 A_QUIT },
8530a1828cSXin LI 	{ "remove-file",          A_REMOVE_FILE },
8630a1828cSXin LI 	{ "repaint",              A_REPAINT },
8730a1828cSXin LI 	{ "repaint-flush",        A_FREPAINT },
8830a1828cSXin LI 	{ "repeat-search",        A_AGAIN_SEARCH },
8930a1828cSXin LI 	{ "repeat-search-all",    A_T_AGAIN_SEARCH },
9030a1828cSXin LI 	{ "reverse-search",       A_REVERSE_SEARCH },
9130a1828cSXin LI 	{ "reverse-search-all",   A_T_REVERSE_SEARCH },
9230a1828cSXin LI 	{ "right-scroll",         A_RSHIFT },
9330a1828cSXin LI 	{ "set-mark",             A_SETMARK },
9430a1828cSXin LI 	{ "set-mark-bottom",      A_SETMARKBOT },
9530a1828cSXin LI 	{ "shell",                A_SHELL },
9630a1828cSXin LI 	{ "status",               A_STAT },
9730a1828cSXin LI 	{ "toggle-flag",          A_OPT_TOGGLE },
9830a1828cSXin LI 	{ "toggle-option",        A_OPT_TOGGLE },
9930a1828cSXin LI 	{ "undo-hilite",          A_UNDO_SEARCH },
10030a1828cSXin LI 	{ "version",              A_VERSION },
10130a1828cSXin LI 	{ "visual",               A_VISUAL },
10230a1828cSXin LI 	{ NULL,   0 }
10330a1828cSXin LI };
10430a1828cSXin LI 
105*c77c4889SXin LI static constant struct lesskey_cmdname editnames[] =
10630a1828cSXin LI {
10730a1828cSXin LI 	{ "back-complete",      EC_B_COMPLETE },
10830a1828cSXin LI 	{ "backspace",          EC_BACKSPACE },
10930a1828cSXin LI 	{ "delete",             EC_DELETE },
11030a1828cSXin LI 	{ "down",               EC_DOWN },
11130a1828cSXin LI 	{ "end",                EC_END },
11230a1828cSXin LI 	{ "expand",             EC_EXPAND },
11330a1828cSXin LI 	{ "forw-complete",      EC_F_COMPLETE },
11430a1828cSXin LI 	{ "home",               EC_HOME },
11530a1828cSXin LI 	{ "insert",             EC_INSERT },
11630a1828cSXin LI 	{ "invalid",            EC_UINVALID },
11730a1828cSXin LI 	{ "kill-line",          EC_LINEKILL },
11830a1828cSXin LI 	{ "abort",              EC_ABORT },
11930a1828cSXin LI 	{ "left",               EC_LEFT },
12030a1828cSXin LI 	{ "literal",            EC_LITERAL },
12130a1828cSXin LI 	{ "right",              EC_RIGHT },
12230a1828cSXin LI 	{ "up",                 EC_UP },
12330a1828cSXin LI 	{ "word-backspace",     EC_W_BACKSPACE },
12430a1828cSXin LI 	{ "word-delete",        EC_W_DELETE },
12530a1828cSXin LI 	{ "word-left",          EC_W_LEFT },
12630a1828cSXin LI 	{ "word-right",         EC_W_RIGHT },
12730a1828cSXin LI 	{ NULL, 0 }
12830a1828cSXin LI };
12930a1828cSXin LI 
13030a1828cSXin LI /*
13130a1828cSXin LI  * Print a parse error message.
13230a1828cSXin LI  */
133*c77c4889SXin LI static void parse_error(constant char *fmt, constant char *arg1)
13430a1828cSXin LI {
13530a1828cSXin LI 	char buf[1024];
136*c77c4889SXin LI 	int n = SNPRINTF2(buf, sizeof(buf), "%s: line %d: ", lesskey_file, linenum);
137*c77c4889SXin LI 	if (n >= 0)
138*c77c4889SXin LI 	{
139*c77c4889SXin LI 		size_t len = (size_t) n;
140*c77c4889SXin LI 		if (len < sizeof(buf))
141*c77c4889SXin LI 			SNPRINTF1(buf+len, sizeof(buf)-len, fmt, arg1);
142*c77c4889SXin LI 	}
14330a1828cSXin LI 	++errors;
14430a1828cSXin LI 	lesskey_parse_error(buf);
14530a1828cSXin LI }
14630a1828cSXin LI 
14730a1828cSXin LI /*
14830a1828cSXin LI  * Initialize lesskey_tables.
14930a1828cSXin LI  */
150d713e089SXin LI static void init_tables(struct lesskey_tables *tables)
15130a1828cSXin LI {
15230a1828cSXin LI 	tables->currtable = &tables->cmdtable;
15330a1828cSXin LI 
15430a1828cSXin LI 	tables->cmdtable.names = cmdnames;
15530a1828cSXin LI 	tables->cmdtable.is_var = 0;
15630a1828cSXin LI 	xbuf_init(&tables->cmdtable.buf);
15730a1828cSXin LI 
15830a1828cSXin LI 	tables->edittable.names = editnames;
15930a1828cSXin LI 	tables->edittable.is_var = 0;
16030a1828cSXin LI 	xbuf_init(&tables->edittable.buf);
16130a1828cSXin LI 
16230a1828cSXin LI 	tables->vartable.names = NULL;
16330a1828cSXin LI 	tables->vartable.is_var = 1;
16430a1828cSXin LI 	xbuf_init(&tables->vartable.buf);
16530a1828cSXin LI }
16630a1828cSXin LI 
16795270f73SXin LI #define CHAR_STRING_LEN 8
16895270f73SXin LI 
169*c77c4889SXin LI static constant char * char_string(char *buf, char ch, int lit)
17095270f73SXin LI {
17195270f73SXin LI 	if (lit || (ch >= 0x20 && ch < 0x7f))
17295270f73SXin LI 	{
17395270f73SXin LI 		buf[0] = ch;
17495270f73SXin LI 		buf[1] = '\0';
17595270f73SXin LI 	} else
17695270f73SXin LI 	{
177*c77c4889SXin LI 		SNPRINTF1(buf, CHAR_STRING_LEN, "\\x%02x", ch);
17895270f73SXin LI 	}
17995270f73SXin LI 	return buf;
18095270f73SXin LI }
18195270f73SXin LI 
18295270f73SXin LI /*
18395270f73SXin LI  * Increment char pointer by one up to terminating nul byte.
18495270f73SXin LI  */
185d713e089SXin LI static char * increment_pointer(char *p)
18695270f73SXin LI {
18795270f73SXin LI 	if (*p == '\0')
18895270f73SXin LI 		return p;
18995270f73SXin LI 	return p+1;
19095270f73SXin LI }
19195270f73SXin LI 
19230a1828cSXin LI /*
19330a1828cSXin LI  * Parse one character of a string.
19430a1828cSXin LI  */
195*c77c4889SXin LI static constant char * tstr(char **pp, int xlate)
19630a1828cSXin LI {
19730a1828cSXin LI 	char *p;
19830a1828cSXin LI 	char ch;
19930a1828cSXin LI 	int i;
20095270f73SXin LI 	static char buf[CHAR_STRING_LEN];
20130a1828cSXin LI 	static char tstr_control_k[] =
20230a1828cSXin LI 		{ SK_SPECIAL_KEY, SK_CONTROL_K, 6, 1, 1, 1, '\0' };
20330a1828cSXin LI 
20430a1828cSXin LI 	p = *pp;
20530a1828cSXin LI 	switch (*p)
20630a1828cSXin LI 	{
20730a1828cSXin LI 	case '\\':
20830a1828cSXin LI 		++p;
20930a1828cSXin LI 		switch (*p)
21030a1828cSXin LI 		{
21130a1828cSXin LI 		case '0': case '1': case '2': case '3':
21230a1828cSXin LI 		case '4': case '5': case '6': case '7':
21330a1828cSXin LI 			/*
21430a1828cSXin LI 			 * Parse an octal number.
21530a1828cSXin LI 			 */
21630a1828cSXin LI 			ch = 0;
21730a1828cSXin LI 			i = 0;
21830a1828cSXin LI 			do
219*c77c4889SXin LI 				ch = (char) (8*ch + (*p - '0'));
22030a1828cSXin LI 			while (*++p >= '0' && *p <= '7' && ++i < 3);
22130a1828cSXin LI 			*pp = p;
22230a1828cSXin LI 			if (xlate && ch == CONTROL('K'))
22330a1828cSXin LI 				return tstr_control_k;
22495270f73SXin LI 			return char_string(buf, ch, 1);
22530a1828cSXin LI 		case 'b':
22630a1828cSXin LI 			*pp = p+1;
22730a1828cSXin LI 			return ("\b");
22830a1828cSXin LI 		case 'e':
22930a1828cSXin LI 			*pp = p+1;
23095270f73SXin LI 			return char_string(buf, ESC, 1);
23130a1828cSXin LI 		case 'n':
23230a1828cSXin LI 			*pp = p+1;
23330a1828cSXin LI 			return ("\n");
23430a1828cSXin LI 		case 'r':
23530a1828cSXin LI 			*pp = p+1;
23630a1828cSXin LI 			return ("\r");
23730a1828cSXin LI 		case 't':
23830a1828cSXin LI 			*pp = p+1;
23930a1828cSXin LI 			return ("\t");
24030a1828cSXin LI 		case 'k':
24130a1828cSXin LI 			if (xlate)
24230a1828cSXin LI 			{
24330a1828cSXin LI 				switch (*++p)
24430a1828cSXin LI 				{
24595270f73SXin LI 				case 'b': ch = SK_BACKSPACE; break;
24695270f73SXin LI 				case 'B': ch = SK_CTL_BACKSPACE; break;
24730a1828cSXin LI 				case 'd': ch = SK_DOWN_ARROW; break;
24830a1828cSXin LI 				case 'D': ch = SK_PAGE_DOWN; break;
24930a1828cSXin LI 				case 'e': ch = SK_END; break;
25095270f73SXin LI 				case 'h': ch = SK_HOME; break;
25195270f73SXin LI 				case 'i': ch = SK_INSERT; break;
25295270f73SXin LI 				case 'l': ch = SK_LEFT_ARROW; break;
25395270f73SXin LI 				case 'L': ch = SK_CTL_LEFT_ARROW; break;
25495270f73SXin LI 				case 'r': ch = SK_RIGHT_ARROW; break;
25595270f73SXin LI 				case 'R': ch = SK_CTL_RIGHT_ARROW; break;
25695270f73SXin LI 				case 't': ch = SK_BACKTAB; break;
25795270f73SXin LI 				case 'u': ch = SK_UP_ARROW; break;
25895270f73SXin LI 				case 'U': ch = SK_PAGE_UP; break;
25930a1828cSXin LI 				case 'x': ch = SK_DELETE; break;
26095270f73SXin LI 				case 'X': ch = SK_CTL_DELETE; break;
26195270f73SXin LI 				case '1': ch = SK_F1; break;
26295270f73SXin LI 				default:
26395270f73SXin LI 					parse_error("invalid escape sequence \"\\k%s\"", char_string(buf, *p, 0));
26495270f73SXin LI 					*pp = increment_pointer(p);
26595270f73SXin LI 					return ("");
26630a1828cSXin LI 				}
26730a1828cSXin LI 				*pp = p+1;
26830a1828cSXin LI 				buf[0] = SK_SPECIAL_KEY;
26930a1828cSXin LI 				buf[1] = ch;
27030a1828cSXin LI 				buf[2] = 6;
27130a1828cSXin LI 				buf[3] = 1;
27230a1828cSXin LI 				buf[4] = 1;
27330a1828cSXin LI 				buf[5] = 1;
27430a1828cSXin LI 				buf[6] = '\0';
27530a1828cSXin LI 				return (buf);
27630a1828cSXin LI 			}
27730a1828cSXin LI 			/* FALLTHRU */
27830a1828cSXin LI 		default:
27930a1828cSXin LI 			/*
28030a1828cSXin LI 			 * Backslash followed by any other char
28130a1828cSXin LI 			 * just means that char.
28230a1828cSXin LI 			 */
28395270f73SXin LI 			*pp = increment_pointer(p);
28495270f73SXin LI 			char_string(buf, *p, 1);
28530a1828cSXin LI 			if (xlate && buf[0] == CONTROL('K'))
28630a1828cSXin LI 				return tstr_control_k;
28730a1828cSXin LI 			return (buf);
28830a1828cSXin LI 		}
28930a1828cSXin LI 	case '^':
29030a1828cSXin LI 		/*
29130a1828cSXin LI 		 * Caret means CONTROL.
29230a1828cSXin LI 		 */
29395270f73SXin LI 		*pp = increment_pointer(p+1);
29495270f73SXin LI 		char_string(buf, CONTROL(p[1]), 1);
29530a1828cSXin LI 		if (xlate && buf[0] == CONTROL('K'))
29630a1828cSXin LI 			return tstr_control_k;
29730a1828cSXin LI 		return (buf);
29830a1828cSXin LI 	}
29995270f73SXin LI 	*pp = increment_pointer(p);
30095270f73SXin LI 	char_string(buf, *p, 1);
30130a1828cSXin LI 	if (xlate && buf[0] == CONTROL('K'))
30230a1828cSXin LI 		return tstr_control_k;
30330a1828cSXin LI 	return (buf);
30430a1828cSXin LI }
30530a1828cSXin LI 
306d713e089SXin LI static int issp(char ch)
30730a1828cSXin LI {
30830a1828cSXin LI 	return (ch == ' ' || ch == '\t');
30930a1828cSXin LI }
31030a1828cSXin LI 
31130a1828cSXin LI /*
31230a1828cSXin LI  * Skip leading spaces in a string.
31330a1828cSXin LI  */
314d713e089SXin LI static char * skipsp(char *s)
31530a1828cSXin LI {
31630a1828cSXin LI 	while (issp(*s))
31730a1828cSXin LI 		s++;
31830a1828cSXin LI 	return (s);
31930a1828cSXin LI }
32030a1828cSXin LI 
32130a1828cSXin LI /*
32230a1828cSXin LI  * Skip non-space characters in a string.
32330a1828cSXin LI  */
324d713e089SXin LI static char * skipnsp(char *s)
32530a1828cSXin LI {
32630a1828cSXin LI 	while (*s != '\0' && !issp(*s))
32730a1828cSXin LI 		s++;
32830a1828cSXin LI 	return (s);
32930a1828cSXin LI }
33030a1828cSXin LI 
33130a1828cSXin LI /*
33230a1828cSXin LI  * Clean up an input line:
33330a1828cSXin LI  * strip off the trailing newline & any trailing # comment.
33430a1828cSXin LI  */
335d713e089SXin LI static char * clean_line(char *s)
33630a1828cSXin LI {
33730a1828cSXin LI 	int i;
33830a1828cSXin LI 
33930a1828cSXin LI 	s = skipsp(s);
34030a1828cSXin LI 	for (i = 0;  s[i] != '\0' && s[i] != '\n' && s[i] != '\r';  i++)
34130a1828cSXin LI 		if (s[i] == '#' && (i == 0 || s[i-1] != '\\'))
34230a1828cSXin LI 			break;
34330a1828cSXin LI 	s[i] = '\0';
34430a1828cSXin LI 	return (s);
34530a1828cSXin LI }
34630a1828cSXin LI 
34730a1828cSXin LI /*
34830a1828cSXin LI  * Add a byte to the output command table.
34930a1828cSXin LI  */
350d713e089SXin LI static void add_cmd_char(unsigned char c, struct lesskey_tables *tables)
35130a1828cSXin LI {
352d713e089SXin LI 	xbuf_add_byte(&tables->currtable->buf, c);
35330a1828cSXin LI }
35430a1828cSXin LI 
355d713e089SXin LI static void erase_cmd_char(struct lesskey_tables *tables)
35695270f73SXin LI {
35795270f73SXin LI 	xbuf_pop(&tables->currtable->buf);
35895270f73SXin LI }
35995270f73SXin LI 
36030a1828cSXin LI /*
36130a1828cSXin LI  * Add a string to the output command table.
36230a1828cSXin LI  */
363*c77c4889SXin LI static void add_cmd_str(constant char *s, struct lesskey_tables *tables)
36430a1828cSXin LI {
36530a1828cSXin LI 	for ( ;  *s != '\0';  s++)
366*c77c4889SXin LI 		add_cmd_char((unsigned char) *s, tables);
36730a1828cSXin LI }
36830a1828cSXin LI 
36930a1828cSXin LI /*
37095270f73SXin LI  * Does a given version number match the running version?
37195270f73SXin LI  * Operator compares the running version to the given version.
37230a1828cSXin LI  */
373d713e089SXin LI static int match_version(char op, int ver)
37495270f73SXin LI {
37595270f73SXin LI 	switch (op)
37695270f73SXin LI 	{
37795270f73SXin LI 	case '>': return less_version > ver;
37895270f73SXin LI 	case '<': return less_version < ver;
37995270f73SXin LI 	case '+': return less_version >= ver;
38095270f73SXin LI 	case '-': return less_version <= ver;
38195270f73SXin LI 	case '=': return less_version == ver;
38295270f73SXin LI 	case '!': return less_version != ver;
38395270f73SXin LI 	default: return 0; /* cannot happen */
38495270f73SXin LI 	}
38595270f73SXin LI }
38695270f73SXin LI 
38795270f73SXin LI /*
38895270f73SXin LI  * Handle a #version line.
38995270f73SXin LI  * If the version matches, return the part of the line that should be executed.
39095270f73SXin LI  * Otherwise, return NULL.
39195270f73SXin LI  */
392*c77c4889SXin LI static char * version_line(char *s)
39395270f73SXin LI {
39495270f73SXin LI 	char op;
39595270f73SXin LI 	int ver;
39695270f73SXin LI 	char *e;
39795270f73SXin LI 	char buf[CHAR_STRING_LEN];
39895270f73SXin LI 
39995270f73SXin LI 	s += strlen("#version");
40095270f73SXin LI 	s = skipsp(s);
40195270f73SXin LI 	op = *s++;
40295270f73SXin LI 	/* Simplify 2-char op to one char. */
40395270f73SXin LI 	switch (op)
40495270f73SXin LI 	{
40595270f73SXin LI 	case '<': if (*s == '=') { s++; op = '-'; } break;
40695270f73SXin LI 	case '>': if (*s == '=') { s++; op = '+'; } break;
40795270f73SXin LI 	case '=': if (*s == '=') { s++; } break;
40895270f73SXin LI 	case '!': if (*s == '=') { s++; } break;
40995270f73SXin LI 	default:
41095270f73SXin LI 		parse_error("invalid operator '%s' in #version line", char_string(buf, op, 0));
41195270f73SXin LI 		return (NULL);
41295270f73SXin LI 	}
41395270f73SXin LI 	s = skipsp(s);
414d713e089SXin LI 	ver = lstrtoi(s, &e, 10);
41595270f73SXin LI 	if (e == s)
41695270f73SXin LI 	{
41795270f73SXin LI 		parse_error("non-numeric version number in #version line", "");
41895270f73SXin LI 		return (NULL);
41995270f73SXin LI 	}
42095270f73SXin LI 	if (!match_version(op, ver))
42195270f73SXin LI 		return (NULL);
42295270f73SXin LI 	return (e);
42395270f73SXin LI }
42495270f73SXin LI 
42595270f73SXin LI /*
42695270f73SXin LI  * See if we have a special "control" line.
42795270f73SXin LI  */
428d713e089SXin LI static char * control_line(char *s, struct lesskey_tables *tables)
42930a1828cSXin LI {
43030a1828cSXin LI #define PREFIX(str,pat) (strncmp(str,pat,strlen(pat)) == 0)
43130a1828cSXin LI 
43230a1828cSXin LI 	if (PREFIX(s, "#line-edit"))
43330a1828cSXin LI 	{
43430a1828cSXin LI 		tables->currtable = &tables->edittable;
43595270f73SXin LI 		return (NULL);
43630a1828cSXin LI 	}
43730a1828cSXin LI 	if (PREFIX(s, "#command"))
43830a1828cSXin LI 	{
43930a1828cSXin LI 		tables->currtable = &tables->cmdtable;
44095270f73SXin LI 		return (NULL);
44130a1828cSXin LI 	}
44230a1828cSXin LI 	if (PREFIX(s, "#env"))
44330a1828cSXin LI 	{
44430a1828cSXin LI 		tables->currtable = &tables->vartable;
44595270f73SXin LI 		return (NULL);
44630a1828cSXin LI 	}
44730a1828cSXin LI 	if (PREFIX(s, "#stop"))
44830a1828cSXin LI 	{
44930a1828cSXin LI 		add_cmd_char('\0', tables);
45030a1828cSXin LI 		add_cmd_char(A_END_LIST, tables);
45195270f73SXin LI 		return (NULL);
45230a1828cSXin LI 	}
45395270f73SXin LI 	if (PREFIX(s, "#version"))
45495270f73SXin LI 	{
455*c77c4889SXin LI 		return (version_line(s));
45695270f73SXin LI 	}
45795270f73SXin LI 	return (s);
45830a1828cSXin LI }
45930a1828cSXin LI 
46030a1828cSXin LI /*
46130a1828cSXin LI  * Find an action, given the name of the action.
46230a1828cSXin LI  */
463d713e089SXin LI static int findaction(char *actname, struct lesskey_tables *tables)
46430a1828cSXin LI {
46530a1828cSXin LI 	int i;
46630a1828cSXin LI 
46730a1828cSXin LI 	for (i = 0;  tables->currtable->names[i].cn_name != NULL;  i++)
46830a1828cSXin LI 		if (strcmp(tables->currtable->names[i].cn_name, actname) == 0)
46930a1828cSXin LI 			return (tables->currtable->names[i].cn_action);
47095270f73SXin LI 	parse_error("unknown action: \"%s\"", actname);
47130a1828cSXin LI 	return (A_INVALID);
47230a1828cSXin LI }
47330a1828cSXin LI 
47430a1828cSXin LI /*
47530a1828cSXin LI  * Parse a line describing one key binding, of the form
47630a1828cSXin LI  *  KEY ACTION [EXTRA]
47730a1828cSXin LI  * where KEY is the user key sequence, ACTION is the
47830a1828cSXin LI  * resulting less action, and EXTRA is an "extra" user
47930a1828cSXin LI  * key sequence injected after the action.
48030a1828cSXin LI  */
481d713e089SXin LI static void parse_cmdline(char *p, struct lesskey_tables *tables)
48230a1828cSXin LI {
48330a1828cSXin LI 	char *actname;
48430a1828cSXin LI 	int action;
485*c77c4889SXin LI 	constant char *s;
48630a1828cSXin LI 	char c;
48730a1828cSXin LI 
48830a1828cSXin LI 	/*
48930a1828cSXin LI 	 * Parse the command string and store it in the current table.
49030a1828cSXin LI 	 */
49130a1828cSXin LI 	do
49230a1828cSXin LI 	{
49330a1828cSXin LI 		s = tstr(&p, 1);
49430a1828cSXin LI 		add_cmd_str(s, tables);
49530a1828cSXin LI 	} while (*p != '\0' && !issp(*p));
49630a1828cSXin LI 	/*
49730a1828cSXin LI 	 * Terminate the command string with a null byte.
49830a1828cSXin LI 	 */
49930a1828cSXin LI 	add_cmd_char('\0', tables);
50030a1828cSXin LI 
50130a1828cSXin LI 	/*
50230a1828cSXin LI 	 * Skip white space between the command string
50330a1828cSXin LI 	 * and the action name.
50430a1828cSXin LI 	 * Terminate the action name with a null byte.
50530a1828cSXin LI 	 */
50630a1828cSXin LI 	p = skipsp(p);
50730a1828cSXin LI 	if (*p == '\0')
50830a1828cSXin LI 	{
50930a1828cSXin LI 		parse_error("missing action", "");
51030a1828cSXin LI 		return;
51130a1828cSXin LI 	}
51230a1828cSXin LI 	actname = p;
51330a1828cSXin LI 	p = skipnsp(p);
51430a1828cSXin LI 	c = *p;
51530a1828cSXin LI 	*p = '\0';
51630a1828cSXin LI 
51730a1828cSXin LI 	/*
51830a1828cSXin LI 	 * Parse the action name and store it in the current table.
51930a1828cSXin LI 	 */
52030a1828cSXin LI 	action = findaction(actname, tables);
52130a1828cSXin LI 
52230a1828cSXin LI 	/*
52330a1828cSXin LI 	 * See if an extra string follows the action name.
52430a1828cSXin LI 	 */
52530a1828cSXin LI 	*p = c;
52630a1828cSXin LI 	p = skipsp(p);
52730a1828cSXin LI 	if (*p == '\0')
52830a1828cSXin LI 	{
529d713e089SXin LI 		add_cmd_char((unsigned char) action, tables);
53030a1828cSXin LI 	} else
53130a1828cSXin LI 	{
53230a1828cSXin LI 		/*
53330a1828cSXin LI 		 * OR the special value A_EXTRA into the action byte.
53430a1828cSXin LI 		 * Put the extra string after the action byte.
53530a1828cSXin LI 		 */
536d713e089SXin LI 		add_cmd_char((unsigned char) (action | A_EXTRA), tables);
53730a1828cSXin LI 		while (*p != '\0')
53830a1828cSXin LI 			add_cmd_str(tstr(&p, 0), tables);
53930a1828cSXin LI 		add_cmd_char('\0', tables);
54030a1828cSXin LI 	}
54130a1828cSXin LI }
54230a1828cSXin LI 
54330a1828cSXin LI /*
54430a1828cSXin LI  * Parse a variable definition line, of the form
54530a1828cSXin LI  *  NAME = VALUE
54630a1828cSXin LI  */
547d713e089SXin LI static void parse_varline(char *line, struct lesskey_tables *tables)
54830a1828cSXin LI {
549*c77c4889SXin LI 	constant char *s;
55030a1828cSXin LI 	char *p = line;
55195270f73SXin LI 	char *eq;
55230a1828cSXin LI 
55395270f73SXin LI 	eq = strchr(line, '=');
55495270f73SXin LI 	if (eq != NULL && eq > line && eq[-1] == '+')
55595270f73SXin LI 	{
55695270f73SXin LI 		/*
55795270f73SXin LI 		 * Rather ugly way of handling a += line.
55895270f73SXin LI 		 * {{ Note that we ignore the variable name and
55995270f73SXin LI 		 *    just append to the previously defined variable. }}
56095270f73SXin LI 		 */
56195270f73SXin LI 		erase_cmd_char(tables); /* backspace over the final null */
56295270f73SXin LI 		p = eq+1;
56395270f73SXin LI 	} else
56495270f73SXin LI 	{
56530a1828cSXin LI 		do
56630a1828cSXin LI 		{
56730a1828cSXin LI 			s = tstr(&p, 0);
56830a1828cSXin LI 			add_cmd_str(s, tables);
56930a1828cSXin LI 		} while (*p != '\0' && !issp(*p) && *p != '=');
57030a1828cSXin LI 		/*
57130a1828cSXin LI 		 * Terminate the variable name with a null byte.
57230a1828cSXin LI 		 */
57330a1828cSXin LI 		add_cmd_char('\0', tables);
57430a1828cSXin LI 		p = skipsp(p);
57530a1828cSXin LI 		if (*p++ != '=')
57630a1828cSXin LI 		{
57795270f73SXin LI 			parse_error("missing = in variable definition", "");
57830a1828cSXin LI 			return;
57930a1828cSXin LI 		}
58030a1828cSXin LI 		add_cmd_char(EV_OK|A_EXTRA, tables);
58195270f73SXin LI 	}
58230a1828cSXin LI 	p = skipsp(p);
58330a1828cSXin LI 	while (*p != '\0')
58430a1828cSXin LI 	{
58530a1828cSXin LI 		s = tstr(&p, 0);
58630a1828cSXin LI 		add_cmd_str(s, tables);
58730a1828cSXin LI 	}
58830a1828cSXin LI 	add_cmd_char('\0', tables);
58930a1828cSXin LI }
59030a1828cSXin LI 
59130a1828cSXin LI /*
59230a1828cSXin LI  * Parse a line from the lesskey file.
59330a1828cSXin LI  */
594d713e089SXin LI static void parse_line(char *line, struct lesskey_tables *tables)
59530a1828cSXin LI {
59630a1828cSXin LI 	char *p;
59730a1828cSXin LI 
59830a1828cSXin LI 	/*
59930a1828cSXin LI 	 * See if it is a control line.
60030a1828cSXin LI 	 */
60195270f73SXin LI 	p = control_line(line, tables);
60295270f73SXin LI 	if (p == NULL)
60330a1828cSXin LI 		return;
60430a1828cSXin LI 	/*
60530a1828cSXin LI 	 * Skip leading white space.
60630a1828cSXin LI 	 * Replace the final newline with a null byte.
60730a1828cSXin LI 	 * Ignore blank lines and comments.
60830a1828cSXin LI 	 */
60995270f73SXin LI 	p = clean_line(p);
61030a1828cSXin LI 	if (*p == '\0')
61130a1828cSXin LI 		return;
61230a1828cSXin LI 
61330a1828cSXin LI 	if (tables->currtable->is_var)
61430a1828cSXin LI 		parse_varline(p, tables);
61530a1828cSXin LI 	else
61630a1828cSXin LI 		parse_cmdline(p, tables);
61730a1828cSXin LI }
61830a1828cSXin LI 
61930a1828cSXin LI /*
62030a1828cSXin LI  * Parse a lesskey source file and store result in tables.
62130a1828cSXin LI  */
622*c77c4889SXin LI int parse_lesskey(constant char *infile, struct lesskey_tables *tables)
62330a1828cSXin LI {
62430a1828cSXin LI 	FILE *desc;
62530a1828cSXin LI 	char line[1024];
62630a1828cSXin LI 
627*c77c4889SXin LI 	lesskey_file = (infile != NULL) ? strdup(infile) : homefile(DEF_LESSKEYINFILE);
628*c77c4889SXin LI 	if (lesskey_file == NULL)
629*c77c4889SXin LI 		return (-1);
63030a1828cSXin LI 
63130a1828cSXin LI 	init_tables(tables);
63230a1828cSXin LI 	errors = 0;
63330a1828cSXin LI 	linenum = 0;
63495270f73SXin LI 	if (less_version == 0)
635d713e089SXin LI 		less_version = lstrtoi(version, NULL, 10);
63630a1828cSXin LI 
63730a1828cSXin LI 	/*
63830a1828cSXin LI 	 * Open the input file.
63930a1828cSXin LI 	 */
640*c77c4889SXin LI 	if (strcmp(lesskey_file, "-") == 0)
64130a1828cSXin LI 		desc = stdin;
642*c77c4889SXin LI 	else if ((desc = fopen(lesskey_file, "r")) == NULL)
64330a1828cSXin LI 	{
644*c77c4889SXin LI 		/* parse_error("cannot open lesskey file %s", lesskey_file); */
645*c77c4889SXin LI 		errors = -1;
64630a1828cSXin LI 	}
64730a1828cSXin LI 
64830a1828cSXin LI 	/*
64930a1828cSXin LI 	 * Read and parse the input file, one line at a time.
65030a1828cSXin LI 	 */
651*c77c4889SXin LI 	if (desc != NULL)
652*c77c4889SXin LI 	{
65330a1828cSXin LI 		while (fgets(line, sizeof(line), desc) != NULL)
65430a1828cSXin LI 		{
65530a1828cSXin LI 			++linenum;
65630a1828cSXin LI 			parse_line(line, tables);
65730a1828cSXin LI 		}
658*c77c4889SXin LI 		if (desc != stdin)
65995270f73SXin LI 			fclose(desc);
660*c77c4889SXin LI 	}
661*c77c4889SXin LI 	free(lesskey_file);
662*c77c4889SXin LI 	lesskey_file = NULL;
663*c77c4889SXin LI 	return (errors);
664*c77c4889SXin LI }
665*c77c4889SXin LI 
666*c77c4889SXin LI /*
667*c77c4889SXin LI  * Parse a lesskey source content and store result in tables.
668*c77c4889SXin LI  */
669*c77c4889SXin LI int parse_lesskey_content(constant char *content, struct lesskey_tables *tables)
670*c77c4889SXin LI {
671*c77c4889SXin LI 	size_t cx = 0;
672*c77c4889SXin LI 
673*c77c4889SXin LI 	lesskey_file = "lesskey-content";
674*c77c4889SXin LI 	init_tables(tables);
675*c77c4889SXin LI 	errors = 0;
676*c77c4889SXin LI 	linenum = 0;
677*c77c4889SXin LI 	if (less_version == 0)
678*c77c4889SXin LI 		less_version = lstrtoi(version, NULL, 10);
679*c77c4889SXin LI 
680*c77c4889SXin LI 	while (content[cx] != '\0')
681*c77c4889SXin LI 	{
682*c77c4889SXin LI 		/* Extract a line from the content buffer and parse it. */
683*c77c4889SXin LI 		char line[1024];
684*c77c4889SXin LI 		size_t lx = 0;
685*c77c4889SXin LI 		while (content[cx] != '\0' && content[cx] != '\n' && content[cx] != ';')
686*c77c4889SXin LI 		{
687*c77c4889SXin LI 			if (lx >= sizeof(line)-1) break;
688*c77c4889SXin LI 			if (content[cx] == '\\' && content[cx+1] == ';')
689*c77c4889SXin LI 				++cx; /* escaped semicolon: skip the backslash */
690*c77c4889SXin LI 			line[lx++] = content[cx++];
691*c77c4889SXin LI 		}
692*c77c4889SXin LI 		line[lx] = '\0';
693*c77c4889SXin LI 		++linenum;
694*c77c4889SXin LI 		parse_line(line, tables);
695*c77c4889SXin LI 		if (content[cx] != '\0') ++cx; /* skip newline or semicolon */
696*c77c4889SXin LI 	}
697*c77c4889SXin LI 	lesskey_file = NULL;
69830a1828cSXin LI 	return (errors);
69930a1828cSXin LI }
700