aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOmar Polo <op@omarpolo.com>2021-06-16 14:43:16 +0000
committerOmar Polo <op@omarpolo.com>2021-06-16 14:43:16 +0000
commit74f0778b9ae93a700d8b0f759b05f24e69f54921 (patch)
tree7e67078f430fce32caf192e38dc91dc8a6accc5a
parent984c46a82e002089b3a4035ba34873ad9c75d973 (diff)
drop the dependency on lex by implementing yylex by ourselves
The actual implementation is based off doas' parse.y. This gave us various benefits, like cleaner code, \ to break long lines, better handling of quotes etc...
-rw-r--r--ChangeLog7
-rw-r--r--Makefile7
-rw-r--r--README.md4
-rwxr-xr-xconfigure14
-rw-r--r--gmid.h6
-rw-r--r--parse.y198
6 files changed, 203 insertions, 33 deletions
diff --git a/ChangeLog b/ChangeLog
index d4b2817..76e321f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2021-06-16 Omar Polo <op@omarpolo.com>
+
+ * parse.y (yylex): drop the dependency on lex by implementing
+ yylex by ourselves (the actual implementation is based off doas'
+ parse.y). This gave us various benefits, like cleaner code, \ to
+ break long lines, better handling of quotes etc...
+
2021-06-11 Omar Polo <op@omarpolo.com>
* parse.y (servopt): add `param' keyword
diff --git a/Makefile b/Makefile
index 5f91bd7..be01f67 100644
--- a/Makefile
+++ b/Makefile
@@ -7,15 +7,12 @@ Makefile.local: configure
include Makefile.local
-lex.yy.c: lex.l y.tab.c
- ${LEX} lex.l
-
y.tab.c: parse.y
- ${YACC} -b y -d parse.y
+ ${YACC} -b y parse.y
SRCS = gmid.c iri.c utf8.c ex.c server.c sandbox.c mime.c puny.c \
utils.c log.c dirs.c fcgi.c
-OBJS = ${SRCS:.c=.o} lex.yy.o y.tab.o ${COMPAT}
+OBJS = ${SRCS:.c=.o} y.tab.o ${COMPAT}
gmid: ${OBJS}
${CC} ${OBJS} -o gmid ${LDFLAGS}
diff --git a/README.md b/README.md
index 8b2ebeb..67cd275 100644
--- a/README.md
+++ b/README.md
@@ -92,8 +92,8 @@ server "example.com" {
## Building
gmid depends on a POSIX libc, libevent2, OpenSSL/LibreSSL and libtls
-(provided either by LibreSSL or libretls). At build time, flex and
-yacc (or GNU bison) are also needed.
+(provided either by LibreSSL or libretls). At build time, yacc (or
+GNU bison) is also needed.
The build is as simple as
diff --git a/configure b/configure
index 84ed361..d5f4035 100755
--- a/configure
+++ b/configure
@@ -40,7 +40,6 @@ CFLAGS="${CFLAGS} -g -W -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes"
CFLAGS="${CFLAGS} -Wwrite-strings -Wno-unused-parameter"
LDFLAGS="-ltls -levent"
LD_IMSG=
-LEX=lex
STATIC=
YACC=yacc
@@ -74,17 +73,6 @@ if which pkg-config 2>/dev/null 1>&2; then
esac
fi
-# auto detect lex/flex
-which ${LEX} 2>/dev/null 1>&2 || {
- echo "${LEX} not found: trying flex" 1>&2
- echo "${LEX} not found: trying flex" 1>&3
- LEX=flex
- which ${LEX} 2>/dev/null 1>&2 || {
- echo "${LEX} not found: giving up" 1>&2
- echo "${LEX} not found: giving up" 1>&3
- }
-}
-
# auto detect yacc/bison
which ${YACC} 2>/dev/null 1>&2 || {
echo "${YACC} not found: trying bison" 1>&2
@@ -112,7 +100,6 @@ for keyvals in "$@"; do
CFLAGS) CFLAGS="$val" ;;
DESTDIR) DESTDIR="$val" ;;
LDFLAGS) LDFLAGS="$val" ;;
- LEX) LEX="$lex" ;;
PREFIX) PREFIX="$val" ;;
YACC) YACC="$val" ;;
*)
@@ -398,7 +385,6 @@ CC = ${CC}
CFLAGS = ${CFLAGS}
LDFLAGS = ${LDFLAGS} ${LD_IMSG}
YACC = ${YACC}
-LEX = ${LEX}
STATIC = ${STATIC}
PREFIX = ${PREFIX}
BINDIR = ${BINDIR}
diff --git a/gmid.h b/gmid.h
index a013af5..731a4fd 100644
--- a/gmid.h
+++ b/gmid.h
@@ -310,12 +310,6 @@ void init_config(void);
void free_config(void);
void drop_priv(void);
-/* provided by lex/yacc */
-extern FILE *yyin;
-extern int yylineno;
-extern int yyparse(void);
-extern int yylex(void);
-
void yyerror(const char*, ...);
int parse_portno(const char*);
void parse_conf(const char*);
diff --git a/parse.y b/parse.y
index 661e8e8..9182fd4 100644
--- a/parse.y
+++ b/parse.y
@@ -1,4 +1,3 @@
-/* -*- mode: fundamental; indent-tabs-mode: t; -*- */
%{
/*
@@ -17,13 +16,17 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include "gmid.h"
+FILE *yyfp;
+
/*
* #define YYDEBUG 1
* int yydebug = 1;
@@ -32,12 +35,14 @@
struct vhost *host;
struct location *loc;
-int goterror = 0;
+static int goterror;
+static int lineno, colno;
static struct vhost *new_vhost(void);
static struct location *new_location(void);
void yyerror(const char*, ...);
+static int yylex(void);
int parse_portno(const char*);
void parse_conf(const char*);
char *ensure_absolute_path(char*);
@@ -109,7 +114,7 @@ vhost : TSERVER TSTRING {
if (strstr($2, "xn--") != NULL) {
warnx("%s:%d \"%s\" looks like punycode: "
"you should use the decoded hostname.",
- config_path, yylineno, $2);
+ config_path, lineno, $2);
}
} '{' servopts locations '}' {
@@ -278,12 +283,193 @@ yyerror(const char *msg, ...)
goterror = 1;
va_start(ap, msg);
- fprintf(stderr, "%s:%d: ", config_path, yylineno);
+ fprintf(stderr, "%s:%d: ", config_path, lineno);
vfprintf(stderr, msg, ap);
fprintf(stderr, "\n");
va_end(ap);
}
+static struct keyword {
+ const char *word;
+ int token;
+} keywords[] = {
+ {"alias", TALIAS},
+ {"auto", TAUTO},
+ {"block", TBLOCK},
+ {"ca", TCA},
+ {"cert", TCERT},
+ {"cgi", TCGI},
+ {"chroot", TCHROOT},
+ {"client", TCLIENT},
+ {"default", TDEFAULT},
+ {"entrypoint", TENTRYPOINT},
+ {"env", TENV},
+ {"fastcgi", TFASTCGI},
+ {"index", TINDEX},
+ {"ipv6", TIPV6},
+ {"key", TKEY},
+ {"lang", TLANG},
+ {"location", TLOCATION},
+ {"log", TLOG},
+ {"mime", TMIME},
+ {"param", TPARAM},
+ {"port", TPORT},
+ {"prefork", TPREFORK},
+ {"protocols", TPROTOCOLS},
+ {"require", TREQUIRE},
+ {"return", TRETURN},
+ {"root", TROOT},
+ {"server", TSERVER},
+ {"spawn", TSPAWN},
+ {"strip", TSTRIP},
+ {"tcp", TTCP},
+ {"type", TTYPE},
+ {"user", TUSER},
+};
+
+/*
+ * Taken an adapted from doas' parse.y
+ */
+static int
+yylex(void)
+{
+ char buf[1024], *ebuf, *p, *str;
+ int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0;
+ size_t i;
+
+ p = buf;
+ ebuf = buf + sizeof(buf);
+
+repeat:
+ /* skip whitespace first */
+ for (c = getc(yyfp); isspace(c); c = getc(yyfp)) {
+ colno++;
+ if (c == '\n') {
+ lineno++;
+ colno = 0;
+ }
+ }
+
+ /* check for special one-character constructions */
+ switch (c) {
+ case '{':
+ case '}':
+ return c;
+ case '#':
+ /* skip comments; NUL is allowed; no continuation */
+ while ((c = getc(yyfp)) != '\n')
+ if (c == EOF)
+ goto eof;
+ colno = 0;
+ lineno++;
+ goto repeat;
+ case EOF:
+ goto eof;
+ }
+
+ /* parsing next word */
+ for (;; c = getc(yyfp), colno++) {
+ switch (c) {
+ case '\0':
+ yyerror("unallowed character NULL in column %d",
+ colno+1);
+ escape = 0;
+ continue;
+ case '\\':
+ escape = !escape;
+ if (escape)
+ continue;
+ break;
+ case '\n':
+ if (quotes)
+ yyerror("unterminated quotes in column %d",
+ colno+1);
+ if (escape) {
+ nonkw = 1;
+ escape = 0;
+ colno = 0;
+ lineno++;
+ }
+ goto eow;
+ case EOF:
+ if (escape)
+ yyerror("unterminated escape in column %d",
+ colno);
+ if (quotes)
+ yyerror("unterminated quotes in column %d",
+ qpos+1);
+ goto eow;
+ case '{':
+ case '}':
+ case '#':
+ case ' ':
+ case '\t':
+ if (!escape && !quotes)
+ goto eow;
+ break;
+ case '"':
+ if (!escape) {
+ quotes = !quotes;
+ if (quotes) {
+ nonkw = 1;
+ qpos = colno;
+ }
+ continue;
+ }
+ }
+ *p++ = c;
+ if (p == ebuf) {
+ yyerror("line too long");
+ p = buf;
+ }
+ escape = 0;
+ }
+
+eow:
+ *p = 0;
+ if (c != EOF)
+ ungetc(c, yyfp);
+ if (p == buf) {
+ /*
+ * There could be a number of reason for empty buffer,
+ * and we handle all of them here, to avoid cluttering
+ * the main loop.
+ */
+ if (c == EOF)
+ goto eof;
+ else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */
+ goto repeat;
+ }
+ if (!nonkw) {
+ for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); ++i) {
+ if (!strcmp(buf, keywords[i].word))
+ return keywords[i].token;
+ }
+ }
+ c = *buf;
+ if (!nonkw && (c == '-' || isdigit(c))) {
+ yylval.num = parse_portno(buf);
+ return TNUM;
+ }
+ if (!nonkw && !strcmp(buf, "on")) {
+ yylval.num = 1;
+ return TBOOL;
+ }
+ if (!nonkw && !strcmp(buf, "off")) {
+ yylval.num = 0;
+ return TBOOL;
+ }
+ if ((str = strdup(buf)) == NULL)
+ err(1, "%s", __func__);
+ yylval.str = str;
+ return TSTRING;
+
+eof:
+ if (ferror(yyfp))
+ yyerror("input error reading config");
+ return 0;
+}
+
int
parse_portno(const char *p)
{
@@ -300,10 +486,10 @@ void
parse_conf(const char *path)
{
config_path = path;
- if ((yyin = fopen(path, "r")) == NULL)
+ if ((yyfp = fopen(path, "r")) == NULL)
err(1, "cannot open config: %s", path);
yyparse();
- fclose(yyin);
+ fclose(yyfp);
if (goterror)
exit(1);