Add lua repl interface to redbean

You can now interact with the global web server state on the command
line, which the web server is running. This supports Emacs shortcuts
with history, readline parity, <tab> completions, plus hints. Enjoy!
This commit is contained in:
Justine Tunney 2022-04-16 20:29:08 -07:00
parent f6b6204b9e
commit a6b02ce5a6
24 changed files with 848 additions and 463 deletions

View file

@ -36,13 +36,23 @@ textwindows int ioctl_tcgets_nt(int ignored, struct termios *tio) {
if (inok | outok) {
bzero(tio, sizeof(*tio));
if (inok) {
if (inmode & kNtEnableLineInput) tio->c_lflag |= ICANON;
if (inmode & kNtEnableEchoInput) tio->c_lflag |= ECHO;
if (inmode & kNtEnableProcessedInput) tio->c_lflag |= IEXTEN | ISIG;
if (inmode & kNtEnableLineInput) {
tio->c_lflag |= ICANON;
}
if (inmode & kNtEnableEchoInput) {
tio->c_lflag |= ECHO;
}
if (inmode & kNtEnableProcessedInput) {
tio->c_lflag |= IEXTEN | ISIG;
}
}
if (outok) {
if (outmode & kNtEnableProcessedOutput) tio->c_oflag |= OPOST;
if (!(outmode & kNtDisableNewlineAutoReturn)) tio->c_oflag |= ONLCR;
if (outmode & kNtEnableProcessedOutput) {
tio->c_oflag |= OPOST;
}
if (!(outmode & kNtDisableNewlineAutoReturn)) {
tio->c_oflag |= ONLCR;
}
}
return 0;
} else {

View file

@ -23,11 +23,10 @@
#include "libc/calls/termios.internal.h"
#include "libc/dce.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/sysv/consts/termios.h"
#include "libc/sysv/errfuns.h"
extern bool __nomultics;
int ioctl_tcsets_nt(int, uint64_t, const struct termios *);
static int ioctl_tcsets_metal(int fd, uint64_t request,
@ -85,7 +84,9 @@ int ioctl_tcsets(int fd, uint64_t request, ...) {
rc = einval();
}
if (rc != -1) {
__nomultics = !(tio->c_oflag & OPOST);
if (__nomultics == 0 || __nomultics == 1) {
__nomultics = !(tio->c_oflag & OPOST);
}
}
STRACE("ioctl_tcsets(%d, %p, %p) → %d% m", fd, request, tio, rc);
return rc;

View file

@ -20,7 +20,11 @@
#include "libc/calls/internal.h"
#include "libc/calls/strace.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/kprintf.h"
#include "libc/macros.internal.h"
#include "libc/sock/internal.h"
#include "libc/sock/sock.h"
#include "libc/sysv/errfuns.h"
@ -62,8 +66,9 @@
* @norestart
*/
int poll(struct pollfd *fds, size_t nfds, int timeout_ms) {
int rc;
int i, rc;
uint64_t millis;
if (IsAsan() && !__asan_is_valid(fds, nfds * sizeof(struct pollfd))) {
rc = efault();
} else if (!IsWindows()) {
@ -76,6 +81,23 @@ int poll(struct pollfd *fds, size_t nfds, int timeout_ms) {
millis = timeout_ms;
rc = sys_poll_nt(fds, nfds, &millis);
}
STRACE("poll(%p, %'lu, %'d) → %d% lm", fds, nfds, timeout_ms, rc);
#if defined(SYSDEBUG) && _POLLTRACE
if (__strace > 0) {
if (rc == -1 && errno == EFAULT) {
STRACE("poll(%p, %'lu, %'d) → %d% lm", fds, nfds, timeout_ms, rc);
} else {
kprintf(STRACE_PROLOGUE "poll({");
for (i = 0; i < MIN(5, nfds); ++i) {
kprintf("%s{%d,%s,%s}", i ? ", " : "", fds[i].fd,
DescribePollFlags(fds[i].events),
DescribePollFlags(fds[i].revents));
}
kprintf("%s}, %'zu, %'d) → %d% lm%n", i == 5 ? "..." : "", nfds,
timeout_ms, rc);
}
}
#endif
return rc;
}

View file

@ -23,6 +23,32 @@
/**
* Obtains the termios struct.
*
* Here are the general defaults you can expect across platforms:
*
* c_iflag = ICRNL IXON
* c_oflag = OPOST ONLCR NL0 CR0 TAB0 BS0 VT0 FF0
* c_cflag = ISIG CREAD CS8
* c_lflag = ISIG ICANON ECHO ECHOE ECHOK IEXTEN ECHOCTL ECHOKE
* c_ispeed = 38400
* c_ospeed = 38400
* c_cc[VINTR] = CTRL-C
* c_cc[VQUIT] = CTRL-\ # ignore this comment
* c_cc[VERASE] = CTRL-?
* c_cc[VKILL] = CTRL-U
* c_cc[VEOF] = CTRL-D
* c_cc[VTIME] = CTRL-@
* c_cc[VMIN] = CTRL-A
* c_cc[VSTART] = CTRL-Q
* c_cc[VSTOP] = CTRL-S
* c_cc[VSUSP] = CTRL-Z
* c_cc[VEOL] = CTRL-@
* c_cc[VSWTC] = CTRL-@
* c_cc[VREPRINT] = CTRL-R
* c_cc[VDISCARD] = CTRL-O
* c_cc[VWERASE] = CTRL-W
* c_cc[VLNEXT] = CTRL-V
* c_cc[VEOL2] = CTRL-@
*
* @param fd open file descriptor that isatty()
* @param tio is where result is stored
* @return -1 w/ errno on error

View file

@ -24,6 +24,7 @@
#include "libc/fmt/fmts.h"
#include "libc/fmt/internal.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/bsr.h"
@ -40,9 +41,6 @@
} \
} while (0)
extern bool __nomultics;
extern bool __replmode;
static const char kSpecialFloats[2][2][4] = {{"INF", "inf"}, {"NAN", "nan"}};
static void __fmt_free_dtoa(char **mem) {

View file

@ -29,6 +29,7 @@
#include "libc/intrin/asancodes.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/lockcmpxchg.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/log/backtrace.internal.h"
#include "libc/log/internal.h"
#include "libc/log/libfatal.internal.h"
@ -153,7 +154,6 @@ struct ReportOriginHeap {
};
bool __asan_noreentry;
extern bool __nomultics;
static struct AsanMorgue __asan_morgue;
static wontreturn void __asan_unreachable(void) {

View file

@ -14,6 +14,7 @@ const char *DescribeFlags(char *, size_t, struct DescribeFlags *, size_t,
const char *DescribeMapFlags(int);
const char *DescribeProtFlags(int);
const char *DescribePollFlags(int);
const char *DescribeRemapFlags(int);
const char *DescribeNtPageFlags(uint32_t);

View file

@ -0,0 +1,41 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/intrin/describeflags.internal.h"
#include "libc/macros.internal.h"
#include "libc/nt/enum/filemapflags.h"
#include "libc/sysv/consts/poll.h"
const char *DescribePollFlags(int x) {
const struct DescribeFlags kPollFlags[] = {
{POLLIN, "IN"}, // order matters
{POLLOUT, "OUT"}, // order matters
{POLLPRI, "PRI"}, //
{POLLHUP, "HUP"}, //
{POLLERR, "ERR"}, //
{POLLNVAL, "NVAL"}, //
{POLLRDBAND, "RDBAND"}, //
{POLLRDHUP, "RDHUP"}, //
{POLLRDNORM, "RDNORM"}, //
{POLLWRBAND, "WRBAND"}, //
{POLLWRNORM, "WRNORM"}, //
};
static char pollflags[64];
return DescribeFlags(pollflags, sizeof(pollflags), kPollFlags,
ARRAYLEN(kPollFlags), "POLL", x);
}

View file

@ -29,6 +29,7 @@
#include "libc/intrin/cmpxchg.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/lockcmpxchg.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/intrin/spinlock.h"
#include "libc/limits.h"
#include "libc/macros.internal.h"
@ -54,8 +55,6 @@ struct Timestamps {
};
extern int __pid;
extern bool __replmode;
extern bool __nomultics;
unsigned long long __kbirth; // see fork-nt.c
privileged static struct Timestamps kenter(void) {

View file

@ -26,5 +26,13 @@
*
* @see kprintf()
*/
bool __nomultics;
bool __replmode;
char __nomultics;
/**
* Controls ANSI prefix for log emissions.
*
* This should be true in raw tty mode repls.
*
* @see kprintf(), vflogf(), linenoise()
*/
char __replmode;

View file

@ -0,0 +1,11 @@
#ifndef COSMOPOLITAN_LIBC_INTRIN_NOMULTICS_INTERNAL_H_
#define COSMOPOLITAN_LIBC_INTRIN_NOMULTICS_INTERNAL_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
extern bool __nomultics;
extern bool __replmode;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_INTRIN_NOMULTICS_INTERNAL_H_ */

View file

@ -4,6 +4,7 @@
#include "libc/calls/struct/rusage.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/winsize.h"
#include "libc/errno.h"
#include "libc/nexgen32e/stackframe.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
@ -200,22 +201,26 @@ extern unsigned __log_level; /* log level for runtime check */
#define LOGIFNEG1(FORM) \
({ \
int e = errno; \
autotype(FORM) Ax = (FORM); \
if (UNLIKELY(Ax == (typeof(Ax))(-1)) && LOGGABLE(kLogWarn)) { \
++g_ftrace; \
__logerrno(__FILE__, __LINE__, #FORM); \
--g_ftrace; \
errno = e; \
} \
Ax; \
})
#define LOGIFNULL(FORM) \
({ \
int e = errno; \
autotype(FORM) Ax = (FORM); \
if (Ax == NULL && LOGGABLE(kLogWarn)) { \
++g_ftrace; \
__logerrno(__FILE__, __LINE__, #FORM); \
--g_ftrace; \
errno = e; \
} \
Ax; \
})

View file

@ -54,7 +54,7 @@ void vflogf_onfail(FILE *f) {
fseek(f, SEEK_SET, 0);
f->beg = f->end = 0;
clearerr(f);
(fprintf)(f, "performed emergency log truncation: %s\n", strerror(err));
(fprintf)(f, "performed emergency log truncation: %s%n", strerror(err));
}
}
@ -104,7 +104,7 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f,
vflogf_onfail(f);
}
(vfprintf)(f, fmt, va);
fputs("\n", f);
fprintf(f, "%n");
if (bufmode == _IOLBF) {
f->bufmode = _IOLBF;
fflush(f);
@ -113,7 +113,7 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f,
__start_fatal(file, line);
strcpy(buf32, "unknown");
gethostname(buf32, sizeof(buf32));
(dprintf)(STDERR_FILENO, "fatality %s pid %d\n", buf32, getpid());
(dprintf)(STDERR_FILENO, "fatality %s pid %d%n", buf32, getpid());
__die();
unreachable;
}

View file

@ -19,8 +19,12 @@
#include "libc/calls/calls.h"
#include "libc/calls/strace.internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/termios.h"
#include "libc/calls/termios.h"
#include "libc/calls/ttydefaults.h"
#include "libc/dce.h"
#include "libc/dns/dns.h"
#include "libc/errno.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/kprintf.h"
#include "libc/macros.internal.h"
@ -44,6 +48,7 @@
#include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/termios.h"
#include "tool/decode/lib/idname.h"
#include "tool/decode/lib/x86idnames.h"
@ -133,11 +138,13 @@ textstartup void __printargs(const char *prologue) {
uintptr_t *auxp;
struct utsname uts;
char path[PATH_MAX];
int x, st, ft, flags;
struct termios termios;
int e, x, st, ft, flags;
struct pollfd pfds[128];
struct AuxiliaryValue *auxinfo;
st = __strace, __strace = 0;
ft = g_ftrace, g_ftrace = 0;
e = errno;
PRINT("");
PRINT("SYSTEM");
@ -327,6 +334,143 @@ textstartup void __printargs(const char *prologue) {
PRINT("MEMTRACK");
PrintMemoryIntervals(2, &_mmi);
PRINT("");
PRINT("TERMIOS");
for (i = 0; i < 2; ++i) {
if (!tcgetattr(i, &termios)) {
PRINT(" - stdin");
kprintf(prologue);
kprintf(" c_iflag =");
if (termios.c_iflag & IGNBRK) kprintf(" IGNBRK");
if (termios.c_iflag & BRKINT) kprintf(" BRKINT");
if (termios.c_iflag & IGNPAR) kprintf(" IGNPAR");
if (termios.c_iflag & PARMRK) kprintf(" PARMRK");
if (termios.c_iflag & INPCK) kprintf(" INPCK");
if (termios.c_iflag & ISTRIP) kprintf(" ISTRIP");
if (termios.c_iflag & INLCR) kprintf(" INLCR");
if (termios.c_iflag & IGNCR) kprintf(" IGNCR");
if (termios.c_iflag & ICRNL) kprintf(" ICRNL");
if (termios.c_iflag & IXON) kprintf(" IXON");
if (termios.c_iflag & IXANY) kprintf(" IXANY");
if (termios.c_iflag & IXOFF) kprintf(" IXOFF");
if (termios.c_iflag & IMAXBEL) kprintf(" IMAXBEL");
if (termios.c_iflag & IUTF8) kprintf(" IUTF8");
if (termios.c_iflag & IUCLC) kprintf(" IUCLC");
kprintf("%n");
kprintf(prologue);
kprintf(" c_oflag =");
if (termios.c_oflag & OPOST) kprintf(" OPOST");
if (termios.c_oflag & ONLCR) kprintf(" ONLCR");
if (termios.c_oflag & OCRNL) kprintf(" OCRNL");
if (termios.c_oflag & ONOCR) kprintf(" ONOCR");
if (termios.c_oflag & ONLRET) kprintf(" ONLRET");
if (termios.c_oflag & OFILL) kprintf(" OFILL");
if (termios.c_oflag & OFDEL) kprintf(" OFDEL");
if (termios.c_oflag & OLCUC) kprintf(" OLCUC");
if ((termios.c_oflag & NLDLY) == NL0) {
kprintf(" NL0");
} else if ((termios.c_oflag & NLDLY) == NL1) {
kprintf(" NL1");
} else if ((termios.c_oflag & NLDLY) == NL2) {
kprintf(" NL2");
} else if ((termios.c_oflag & NLDLY) == NL3) {
kprintf(" NL3");
}
if ((termios.c_oflag & CRDLY) == CR0) {
kprintf(" CR0");
} else if ((termios.c_oflag & CRDLY) == CR1) {
kprintf(" CR1");
} else if ((termios.c_oflag & CRDLY) == CR2) {
kprintf(" CR2");
} else if ((termios.c_oflag & CRDLY) == CR3) {
kprintf(" CR3");
}
if ((termios.c_oflag & TABDLY) == TAB0) {
kprintf(" TAB0");
} else if ((termios.c_oflag & TABDLY) == TAB1) {
kprintf(" TAB1");
} else if ((termios.c_oflag & TABDLY) == TAB2) {
kprintf(" TAB2");
} else if ((termios.c_oflag & TABDLY) == TAB3) {
kprintf(" TAB3");
}
if ((termios.c_oflag & BSDLY) == BS0) {
kprintf(" BS0");
} else if ((termios.c_oflag & BSDLY) == BS1) {
kprintf(" BS1");
}
if ((termios.c_oflag & VTDLY) == VT0) {
kprintf(" VT0");
} else if ((termios.c_oflag & VTDLY) == VT1) {
kprintf(" VT1");
}
if ((termios.c_oflag & FFDLY) == FF0) {
kprintf(" FF0");
} else if ((termios.c_oflag & FFDLY) == FF1) {
kprintf(" FF1");
}
kprintf("%n");
kprintf(prologue);
kprintf(" c_cflag =");
if (termios.c_cflag & ISIG) kprintf(" ISIG");
if (termios.c_cflag & CSTOPB) kprintf(" CSTOPB");
if (termios.c_cflag & CREAD) kprintf(" CREAD");
if (termios.c_cflag & PARENB) kprintf(" PARENB");
if (termios.c_cflag & PARODD) kprintf(" PARODD");
if (termios.c_cflag & HUPCL) kprintf(" HUPCL");
if (termios.c_cflag & CLOCAL) kprintf(" CLOCAL");
if ((termios.c_cflag & CSIZE) == CS5) {
kprintf(" CS5");
} else if ((termios.c_cflag & CSIZE) == CS6) {
kprintf(" CS6");
} else if ((termios.c_cflag & CSIZE) == CS7) {
kprintf(" CS7");
} else if ((termios.c_cflag & CSIZE) == CS8) {
kprintf(" CS8");
}
kprintf("%n");
kprintf(prologue);
kprintf(" c_lflag =");
if (termios.c_lflag & ISIG) kprintf(" ISIG");
if (termios.c_lflag & ICANON) kprintf(" ICANON");
if (termios.c_lflag & ECHO) kprintf(" ECHO");
if (termios.c_lflag & ECHOE) kprintf(" ECHOE");
if (termios.c_lflag & ECHOK) kprintf(" ECHOK");
if (termios.c_lflag & ECHONL) kprintf(" ECHONL");
if (termios.c_lflag & NOFLSH) kprintf(" NOFLSH");
if (termios.c_lflag & TOSTOP) kprintf(" TOSTOP");
if (termios.c_lflag & IEXTEN) kprintf(" IEXTEN");
if (termios.c_lflag & ECHOCTL) kprintf(" ECHOCTL");
if (termios.c_lflag & ECHOPRT) kprintf(" ECHOPRT");
if (termios.c_lflag & ECHOKE) kprintf(" ECHOKE");
if (termios.c_lflag & FLUSHO) kprintf(" FLUSHO");
if (termios.c_lflag & PENDIN) kprintf(" PENDIN");
if (termios.c_lflag & XCASE) kprintf(" XCASE");
kprintf("%n");
PRINT(" c_ispeed = %u", termios.c_ispeed);
PRINT(" c_ospeed = %u", termios.c_ospeed);
PRINT(" c_cc[VINTR] = CTRL-%c", CTRL(termios.c_cc[VINTR]));
PRINT(" c_cc[VQUIT] = CTRL-%c", CTRL(termios.c_cc[VQUIT]));
PRINT(" c_cc[VERASE] = CTRL-%c", CTRL(termios.c_cc[VERASE]));
PRINT(" c_cc[VKILL] = CTRL-%c", CTRL(termios.c_cc[VKILL]));
PRINT(" c_cc[VEOF] = CTRL-%c", CTRL(termios.c_cc[VEOF]));
PRINT(" c_cc[VTIME] = CTRL-%c", CTRL(termios.c_cc[VTIME]));
PRINT(" c_cc[VMIN] = CTRL-%c", CTRL(termios.c_cc[VMIN]));
PRINT(" c_cc[VSTART] = CTRL-%c", CTRL(termios.c_cc[VSTART]));
PRINT(" c_cc[VSTOP] = CTRL-%c", CTRL(termios.c_cc[VSTOP]));
PRINT(" c_cc[VSUSP] = CTRL-%c", CTRL(termios.c_cc[VSUSP]));
PRINT(" c_cc[VEOL] = CTRL-%c", CTRL(termios.c_cc[VEOL]));
PRINT(" c_cc[VSWTC] = CTRL-%c", CTRL(termios.c_cc[VSWTC]));
PRINT(" c_cc[VREPRINT] = CTRL-%c", CTRL(termios.c_cc[VREPRINT]));
PRINT(" c_cc[VDISCARD] = CTRL-%c", CTRL(termios.c_cc[VDISCARD]));
PRINT(" c_cc[VWERASE] = CTRL-%c", CTRL(termios.c_cc[VWERASE]));
PRINT(" c_cc[VLNEXT] = CTRL-%c", CTRL(termios.c_cc[VLNEXT]));
PRINT(" c_cc[VEOL2] = CTRL-%c", CTRL(termios.c_cc[VEOL2]));
} else {
PRINT(" - tcgetattr(%d) failed %m", i);
}
}
if (IsWindows()) {
struct NtStartupInfo startinfo;
GetStartupInfo(&startinfo);
@ -400,4 +544,5 @@ textstartup void __printargs(const char *prologue) {
PRINT("");
__strace = st;
g_ftrace = ft;
errno = e;
}

View file

@ -26,6 +26,7 @@
#include "libc/errno.h"
#include "libc/fmt/fmt.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/log/libfatal.internal.h"
#include "libc/macros.internal.h"
#include "libc/nexgen32e/bsr.h"
@ -86,7 +87,6 @@ struct WinArgs {
};
extern int __pid;
extern bool __nomultics;
extern uint32_t __winmainpid;
extern int64_t __wincrashearly;
extern const char kConsoleHandles[3];

View file

@ -22,6 +22,6 @@ bool startswithi(const char *s, const char *prefix) {
for (;;) {
if (!*prefix) return true;
if (!*s) return false;
if (kToLower[*s++ & 255] != (*prefix++ & 255)) return false;
if (kToLower[*s++ & 255] != kToLower[*prefix++ & 255]) return false;
}
}

View file

@ -122,7 +122,9 @@
#include "libc/assert.h"
#include "libc/bits/bits.h"
#include "libc/calls/calls.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/sigbits.h"
#include "libc/calls/strace.internal.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/stat.h"
#include "libc/calls/termios.h"
@ -131,6 +133,9 @@
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
@ -160,6 +165,8 @@ Copyright 2018-2020 Justine Tunney <jtunney@gmail.com>\\n\
Copyright 2010-2016 Salvatore Sanfilippo <antirez@gmail.com>\\n\
Copyright 2010-2013 Pieter Noordhuis <pcnoordhuis@gmail.com>\"");
#define LINENOISE_POLL_MS __SIG_POLLING_INTERVAL_MS
#define LINENOISE_MAX_RING 8
#define LINENOISE_MAX_DEBUG 16
#define LINENOISE_MAX_HISTORY 1024
@ -249,7 +256,6 @@ static char maskmode;
static char ispaused;
static char iscapital;
static int historylen;
extern bool __replmode;
static struct linenoiseRing ring;
static struct sigaction orig_int;
static struct sigaction orig_quit;
@ -257,6 +263,7 @@ static struct sigaction orig_cont;
static struct sigaction orig_winch;
static struct termios orig_termios;
static char *history[LINENOISE_MAX_HISTORY];
static linenoisePollCallback *pollCallback;
static linenoiseXlatCallback *xlatCallback;
static linenoiseHintsCallback *hintsCallback;
static linenoiseFreeHintsCallback *freeHintsCallback;
@ -703,6 +710,22 @@ static void linenoiseDebug(struct linenoiseState *l, const char *fmt, ...) {
free(msg);
}
static int linenoisePoll(struct linenoiseState *l, int fd) {
int rc, ms;
for (ms = LINENOISE_POLL_MS;;) {
if (pollCallback) {
rc = pollCallback(fd, ms);
} else {
rc = poll((struct pollfd[]){{fd, POLLIN}}, 1, ms);
}
if (rc) {
return rc;
} else {
linenoiseRefreshLine(l);
}
}
}
static ssize_t linenoiseRead(int fd, char *buf, size_t size,
struct linenoiseState *l) {
ssize_t rc;
@ -719,6 +742,7 @@ static ssize_t linenoiseRead(int fd, char *buf, size_t size,
}
if (l && gotwinch) refreshme = 1;
if (refreshme) linenoiseRefreshLine(l);
if (linenoisePoll(l, fd) == -1) return -1;
rc = readansi(fd, buf, size);
} while (rc == -1 && errno == EINTR);
if (l && rc > 0) {
@ -777,7 +801,7 @@ void linenoiseClearScreen(int fd) {
}
static void linenoiseBeep(void) {
/* THE TERMINAL BELL IS DEAD - HISTORY HAS KILLED IT */
// THE TERMINAL BELL IS DEAD - HISTORY HAS KILLED IT
}
static char linenoiseGrow(struct linenoiseState *ls, size_t n) {
@ -829,27 +853,31 @@ static ssize_t linenoiseCompleteLine(struct linenoiseState *ls, char *seq,
// if there's a multiline completions, then do nothing and wait and
// see if the user presses tab again. if the user does this we then
// print ALL the completions, to above the editing line
nread = linenoiseRead(ls->ifd, seq, size, ls);
if (nread == 1 && seq[0] == '\t') {
itemlen = linenoiseMaxCompletionLength(&lc) + 4;
perline = MAX(1, (ls->ws.ws_col - 1) / itemlen);
abInit(&ab);
abAppends(&ab, "\r\033[K");
for (i = 0; i < lc.len;) {
for (j = 0; i < lc.len && j < perline; ++j, ++i) {
n = GetMonospaceWidth(lc.cvec[i], strlen(lc.cvec[i]), 0);
abAppends(&ab, lc.cvec[i]);
for (k = n; k < itemlen; ++k) {
abAppendw(&ab, ' ');
for (;;) {
nread = linenoiseRead(ls->ifd, seq, size, ls);
if (nread == 1 && seq[0] == '\t') {
itemlen = linenoiseMaxCompletionLength(&lc) + 4;
perline = MAX(1, (ls->ws.ws_col - 1) / itemlen);
abInit(&ab);
abAppends(&ab, "\r\n\033[K");
for (i = 0; i < lc.len;) {
for (j = 0; i < lc.len && j < perline; ++j, ++i) {
n = GetMonospaceWidth(lc.cvec[i], strlen(lc.cvec[i]), 0);
abAppends(&ab, lc.cvec[i]);
for (k = n; k < itemlen; ++k) {
abAppendw(&ab, ' ');
}
}
abAppendw(&ab, READ16LE("\r\n"));
}
abAppendw(&ab, READ16LE("\r\n"));
ab.len -= 2;
abAppends(&ab, "\n");
linenoiseWriteStr(ls->ofd, ab.b);
linenoiseRefreshLine(ls);
abFree(&ab);
} else {
break;
}
ab.len -= 2;
abAppends(&ab, "\n");
linenoiseWriteStr(ls->ofd, ab.b);
linenoiseRefreshLine(ls);
abFree(&ab);
}
}
linenoiseFreeCompletions(&lc);
@ -1697,6 +1725,7 @@ static void linenoiseEditCtrlq(struct linenoiseState *l) {
*/
static ssize_t linenoiseEdit(int stdin_fd, int stdout_fd, const char *prompt,
char **obuf) {
int st;
ssize_t rc;
uint64_t w;
size_t nread;
@ -2108,6 +2137,7 @@ char *linenoise(const char *prompt) {
* however if it contains a slash / dot then we'll assume prog is
* the history filename which as determined by the caller
* @return chomped allocated string of read line or null on eof/error
* noting that on eof your errno is not changed
*/
char *linenoiseWithHistory(const char *prompt, const char *prog) {
char *line, *res;
@ -2137,7 +2167,9 @@ char *linenoiseWithHistory(const char *prompt, const char *prog) {
}
}
if (path.len) {
linenoiseHistoryLoad(path.b);
if (linenoiseHistoryLoad(path.b) == -1) {
kprintf("%r%s: failed to load history: %m%n", path);
}
}
line = linenoise(prompt);
if (path.len && line && *line) {
@ -2185,6 +2217,13 @@ void linenoiseSetXlatCallback(linenoiseXlatCallback *fn) {
xlatCallback = fn;
}
/**
* Sets terminal fd pollin callback.
*/
void linenoiseSetPollCallback(linenoisePollCallback *fn) {
pollCallback = fn;
}
/**
* Adds completion.
*

View file

@ -13,12 +13,14 @@ typedef char *(linenoiseHintsCallback)(const char *, const char **,
const char **);
typedef void(linenoiseFreeHintsCallback)(void *);
typedef wint_t(linenoiseXlatCallback)(wint_t);
typedef int(linenoisePollCallback)(int, int);
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
void linenoiseSetHintsCallback(linenoiseHintsCallback *);
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
void linenoiseSetXlatCallback(linenoiseXlatCallback *);
void linenoiseSetPollCallback(linenoisePollCallback *);
char *linenoise(const char *) dontdiscard;
char *linenoiseRaw(const char *, int, int) dontdiscard;

296
third_party/lua/lrepl.c vendored Normal file
View file

@ -0,0 +1,296 @@
#define lua_c
#include "libc/calls/calls.h"
#include "libc/calls/sigbits.h"
#include "libc/log/check.h"
#include "libc/runtime/gc.h"
#include "libc/runtime/runtime.h"
#include "libc/sysv/consts/sa.h"
#include "libc/x/x.h"
#include "third_party/linenoise/linenoise.h"
#include "third_party/lua/lauxlib.h"
#include "third_party/lua/lprefix.h"
#include "third_party/lua/lua.h"
#include "third_party/lua/lualib.h"
// clang-format off
static lua_State *globalL;
static const char *g_progname;
/*
** {==================================================================
** Read-Eval-Print Loop (REPL)
** ===================================================================
*/
#if !defined(LUA_PROMPT)
#define LUA_PROMPT ">: "
#define LUA_PROMPT2 ">>: "
#endif
#if !defined(LUA_MAXINPUT)
#define LUA_MAXINPUT 512
#endif
static void lua_readline_addcompletion(linenoiseCompletions *c, char *s) {
char **p = c->cvec;
size_t n = c->len + 1;
if ((p = realloc(p, n * sizeof(*p)))) {
p[n - 1] = s;
c->cvec = p;
c->len = n;
}
}
void lua_readline_completions(const char *p, linenoiseCompletions *c) {
lua_State *L;
const char *name;
L = globalL;
lua_pushglobaltable(L);
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
name = lua_tostring(L, -2);
if (startswithi(name, p)) {
lua_readline_addcompletion(c, strdup(name));
}
lua_pop(L, 1);
}
lua_pop(L, 1);
}
char *lua_readline_hint(const char *p, const char **ansi1, const char **ansi2) {
char *h = 0;
linenoiseCompletions c = {0};
lua_readline_completions(p, &c);
if (c.len == 1) h = strdup(c.cvec[0] + strlen(p));
linenoiseFreeCompletions(&c);
return h;
}
static void lua_freeline (lua_State *L, char *b) {
free(b);
}
/*
** Return the string to be used as a prompt by the interpreter. Leave
** the string (or nil, if using the default value) on the stack, to keep
** it anchored.
*/
static const char *get_prompt (lua_State *L, int firstline) {
if (lua_getglobal(L, firstline ? "_PROMPT" : "_PROMPT2") == LUA_TNIL)
return (firstline ? LUA_PROMPT : LUA_PROMPT2); /* use the default */
else { /* apply 'tostring' over the value */
const char *p = luaL_tolstring(L, -1, NULL);
lua_remove(L, -2); /* remove original value */
return p;
}
}
/* mark in error messages for incomplete statements */
#define EOFMARK "<eof>"
#define marklen (sizeof(EOFMARK)/sizeof(char) - 1)
/*
** Check whether 'status' signals a syntax error and the error
** message at the top of the stack ends with the above mark for
** incomplete statements.
*/
static int incomplete (lua_State *L, int status) {
if (status == LUA_ERRSYNTAX) {
size_t lmsg;
const char *msg = lua_tolstring(L, -1, &lmsg);
if (lmsg >= marklen && strcmp(msg + lmsg - marklen, EOFMARK) == 0) {
lua_pop(L, 1);
return 1;
}
}
return 0; /* else... */
}
/*
** Prompt the user, read a line, and push it into the Lua stack.
*/
static int pushline (lua_State *L, int firstline) {
char *b;
size_t l;
globalL = L;
const char *prmt = get_prompt(L, firstline);
if (!(b = linenoiseWithHistory(prmt, g_progname)))
return 0; /* no input (prompt will be popped by caller) */
lua_pop(L, 1); /* remove prompt */
l = strlen(b);
if (l > 0 && b[l-1] == '\n') /* line ends with newline? */
b[--l] = '\0'; /* remove it */
if (firstline && b[0] == '=') /* for compatibility with 5.2, ... */
lua_pushfstring(L, "return %s", b + 1); /* change '=' to 'return' */
else
lua_pushlstring(L, b, l);
lua_freeline(L, b);
return 1;
}
/*
** Try to compile line on the stack as 'return <line>;'; on return, stack
** has either compiled chunk or original line (if compilation failed).
*/
static int addreturn (lua_State *L) {
const char *line = lua_tostring(L, -1); /* original line */
const char *retline = lua_pushfstring(L, "return %s;", line);
int status = luaL_loadbuffer(L, retline, strlen(retline), "=stdin");
if (status == LUA_OK) {
lua_remove(L, -2); /* remove modified line */
} else {
lua_pop(L, 2); /* pop result from 'luaL_loadbuffer' and modified line */
}
return status;
}
/*
** Hook set by signal function to stop the interpreter.
*/
static void lstop (lua_State *L, lua_Debug *ar) {
(void)ar; /* unused arg. */
lua_sethook(L, NULL, 0, 0); /* reset hook */
luaL_error(L, "interrupted!");
}
/*
** Read multiple lines until a complete Lua statement
*/
static int multiline (lua_State *L) {
for (;;) { /* repeat until gets a complete statement */
size_t len;
const char *line = lua_tolstring(L, 1, &len); /* get what it has */
int status = luaL_loadbuffer(L, line, len, "=stdin"); /* try it */
if (!incomplete(L, status) || !pushline(L, 0)) {
return status; /* cannot or should not try to add continuation line */
}
lua_pushliteral(L, "\n"); /* add newline... */
lua_insert(L, -2); /* ...between the two lines */
lua_concat(L, 3); /* join them */
}
}
void lua_initrepl(const char *progname) {
g_progname = progname;
linenoiseSetCompletionCallback(lua_readline_completions);
linenoiseSetHintsCallback(lua_readline_hint);
linenoiseSetFreeHintsCallback(free);
}
/*
** Read a line and try to load (compile) it first as an expression (by
** adding "return " in front of it) and second as a statement. Return
** the final status of load/call with the resulting function (if any)
** in the top of the stack.
*/
int lua_loadline (lua_State *L) {
int status;
lua_settop(L, 0);
if (!pushline(L, 1))
return -1; /* no input */
if ((status = addreturn(L)) != LUA_OK) /* 'return ...' did not work? */
status = multiline(L); /* try as command, maybe with continuation lines */
lua_remove(L, 1); /* remove line from the stack */
lua_assert(lua_gettop(L) == 1);
return status;
}
/*
** Function to be called at a C signal. Because a C signal cannot
** just change a Lua state (as there is no proper synchronization),
** this function only sets a hook that, when called, will stop the
** interpreter.
*/
static void laction (int i) {
int flag = LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT;
lua_sethook(globalL, lstop, flag, 1);
}
/*
** Message handler used to run all chunks
*/
static int msghandler (lua_State *L) {
const char *msg = lua_tostring(L, 1);
if (msg == NULL) { /* is error object not a string? */
if (luaL_callmeta(L, 1, "__tostring") && /* does it have a metamethod */
lua_type(L, -1) == LUA_TSTRING) /* that produces a string? */
return 1; /* that is the message */
else
msg = lua_pushfstring(L, "(error object is a %s value)",
luaL_typename(L, 1));
}
luaL_traceback(L, L, msg, 1); /* append a standard traceback */
return 1; /* return the traceback */
}
/*
** Interface to 'lua_pcall', which sets appropriate message function
** and C-signal handler. Used to run all chunks.
*/
int lua_runchunk (lua_State *L, int narg, int nres) {
struct sigaction sa, saold;
int status;
int base = lua_gettop(L) - narg; /* function index */
lua_pushcfunction(L, msghandler); /* push message handler */
lua_insert(L, base); /* put it under function and args */
globalL = L; /* to be available to 'laction' */
sa.sa_flags = SA_RESETHAND; /* if another int happens, terminate */
sa.sa_handler = laction;
sigemptyset(&sa.sa_mask); /* do not mask any signal */
sigaction(SIGINT, &sa, &saold);
status = lua_pcall(L, narg, nres, base);
sigaction(SIGINT, &saold, 0); /* restore C-signal handler */
lua_remove(L, base); /* remove message handler from the stack */
return status;
}
/*
** Prints an error message, adding the program name in front of it
** (if present)
*/
void lua_l_message (const char *pname, const char *msg) {
if (pname) lua_writestringerror("%s: ", pname);
lua_writestringerror("%s\n", msg);
}
/*
** Prints (calling the Lua 'print' function) any values on the stack
*/
void lua_l_print (lua_State *L) {
int n = lua_gettop(L);
if (n > 0) { /* any result to be printed? */
luaL_checkstack(L, LUA_MINSTACK, "too many results to print");
lua_getglobal(L, "print");
lua_insert(L, 1);
if (lua_pcall(L, n, 0, 0) != LUA_OK)
lua_l_message(g_progname, lua_pushfstring(L, "error calling 'print' (%s)",
lua_tostring(L, -1)));
}
}
/*
** Check whether 'status' is not OK and, if so, prints the error
** message on the top of the stack. It assumes that the error object
** is a string, as it was either generated by Lua or by 'msghandler'.
*/
int lua_report (lua_State *L, int status) {
if (status != LUA_OK) {
const char *msg = lua_tostring(L, -1);
lua_l_message(g_progname, msg);
lua_pop(L, 1); /* remove message */
}
return status;
}

19
third_party/lua/lrepl.h vendored Normal file
View file

@ -0,0 +1,19 @@
#ifndef COSMOPOLITAN_THIRD_PARTY_LUA_LREPL_H_
#define COSMOPOLITAN_THIRD_PARTY_LUA_LREPL_H_
#include "third_party/linenoise/linenoise.h"
#include "third_party/lua/lauxlib.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
int lua_loadline(lua_State *);
void lua_l_print(lua_State *);
void lua_initrepl(const char *);
int lua_report(lua_State *, int);
int lua_runchunk(lua_State *, int, int);
void lua_l_message(const char *, const char *);
char *lua_readline_hint(const char *, const char **, const char **);
void lua_readline_completions(const char *, linenoiseCompletions *);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_THIRD_PARTY_LUA_LREPL_H_ */

View file

@ -13,11 +13,14 @@
#include "libc/log/log.h"
#include "libc/runtime/gc.h"
#include "libc/runtime/stack.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/exit.h"
#include "libc/sysv/consts/sa.h"
#include "libc/x/x.h"
#include "third_party/linenoise/linenoise.h"
#include "third_party/lua/lauxlib.h"
#include "third_party/lua/lprefix.h"
#include "third_party/lua/lrepl.h"
#include "third_party/lua/lua.h"
#include "third_party/lua/lualib.h"
@ -38,49 +41,10 @@ STATIC_STACK_SIZE(0x40000);
static lua_State *globalL = NULL;
static const char *progname = LUA_PROGNAME;
static const char *histpath;
#if defined(LUA_USE_POSIX) /* { */
/*
** Use 'sigaction' when available.
*/
static void setsignal (int sig, void (*handler)(int)) {
struct sigaction sa;
sa.sa_handler = handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask); /* do not mask any signal */
sigaction(sig, &sa, NULL);
}
#else /* }{ */
#define setsignal signal
#endif /* } */
/*
** Hook set by signal function to stop the interpreter.
*/
static void lstop (lua_State *L, lua_Debug *ar) {
(void)ar; /* unused arg. */
lua_sethook(L, NULL, 0, 0); /* reset hook */
luaL_error(L, "interrupted!");
}
/*
** Function to be called at a C signal. Because a C signal cannot
** just change a Lua state (as there is no proper synchronization),
** this function only sets a hook that, when called, will stop the
** interpreter.
*/
static void laction (int i) {
int flag = LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT;
setsignal(i, SIG_DFL); /* if another SIGINT happens, terminate process */
lua_sethook(globalL, lstop, flag, 1);
static bool lua_stdin_is_tty(void) {
return isatty(0);
}
@ -106,67 +70,6 @@ static void print_usage (const char *badoption) {
}
/*
** Prints an error message, adding the program name in front of it
** (if present)
*/
static void l_message (const char *pname, const char *msg) {
if (pname) lua_writestringerror("%s: ", pname);
lua_writestringerror("%s\n", msg);
}
/*
** Check whether 'status' is not OK and, if so, prints the error
** message on the top of the stack. It assumes that the error object
** is a string, as it was either generated by Lua or by 'msghandler'.
*/
static int report (lua_State *L, int status) {
if (status != LUA_OK) {
const char *msg = lua_tostring(L, -1);
l_message(progname, msg);
lua_pop(L, 1); /* remove message */
}
return status;
}
/*
** Message handler used to run all chunks
*/
static int msghandler (lua_State *L) {
const char *msg = lua_tostring(L, 1);
if (msg == NULL) { /* is error object not a string? */
if (luaL_callmeta(L, 1, "__tostring") && /* does it have a metamethod */
lua_type(L, -1) == LUA_TSTRING) /* that produces a string? */
return 1; /* that is the message */
else
msg = lua_pushfstring(L, "(error object is a %s value)",
luaL_typename(L, 1));
}
luaL_traceback(L, L, msg, 1); /* append a standard traceback */
return 1; /* return the traceback */
}
/*
** Interface to 'lua_pcall', which sets appropriate message function
** and C-signal handler. Used to run all chunks.
*/
static int docall (lua_State *L, int narg, int nres) {
int status;
int base = lua_gettop(L) - narg; /* function index */
lua_pushcfunction(L, msghandler); /* push message handler */
lua_insert(L, base); /* put it under function and args */
globalL = L; /* to be available to 'laction' */
setsignal(SIGINT, laction); /* set C-signal handler */
status = lua_pcall(L, narg, nres, base);
setsignal(SIGINT, SIG_DFL); /* reset C-signal handler */
lua_remove(L, base); /* remove message handler from the stack */
return status;
}
static void print_version (void) {
lua_writestring(LUA_COPYRIGHT, strlen(LUA_COPYRIGHT));
lua_writeline();
@ -195,8 +98,8 @@ static void createargtable (lua_State *L, char **argv, int argc, int script) {
static int dochunk (lua_State *L, int status) {
if (status == LUA_OK) status = docall(L, 0, 0);
return report(L, status);
if (status == LUA_OK) status = lua_runchunk(L, 0, 0);
return lua_report(L, status);
}
@ -218,10 +121,10 @@ static int dolibrary (lua_State *L, const char *name) {
int status;
lua_getglobal(L, "require");
lua_pushstring(L, name);
status = docall(L, 1, 1); /* call 'require(name)' */
status = lua_runchunk(L, 1, 1); /* call 'require(name)' */
if (status == LUA_OK)
lua_setglobal(L, name); /* global[name] = require return */
return report(L, status);
return lua_report(L, status);
}
@ -249,9 +152,9 @@ static int handle_script (lua_State *L, char **argv) {
status = luaL_loadfile(L, fname);
if (status == LUA_OK) {
int n = pushargs(L); /* push arguments to script */
status = docall(L, n, LUA_MULTRET);
status = lua_runchunk(L, n, LUA_MULTRET);
}
return report(L, status);
return lua_report(L, status);
}
@ -364,225 +267,6 @@ static int handle_luainit (lua_State *L) {
}
/*
** {==================================================================
** Read-Eval-Print Loop (REPL)
** ===================================================================
*/
#if !defined(LUA_PROMPT)
#define LUA_PROMPT ">: "
#define LUA_PROMPT2 ">>: "
#endif
#if !defined(LUA_MAXINPUT)
#define LUA_MAXINPUT 512
#endif
static bool lua_stdin_is_tty(void) {
return isatty(0);
}
static bool lua_istartswith(const char *s, const char *prefix) {
for (;;) {
if (!*prefix) return true;
if (!*s) return false;
if (tolower(*s++) != tolower(*prefix++)) return false;
}
}
static void lua_readline_addcompletion(linenoiseCompletions *c, char *s) {
char **p = c->cvec;
size_t n = c->len + 1;
if ((p = realloc(p, n * sizeof(*p)))) {
p[n - 1] = s;
c->cvec = p;
c->len = n;
}
}
static void lua_readline_completions(const char *p, linenoiseCompletions *c) {
lua_State *L;
const char *name;
L = globalL;
lua_pushglobaltable(L);
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
name = lua_tostring(L, -2);
if (lua_istartswith(name, p)) {
lua_readline_addcompletion(c, strdup(name));
}
lua_pop(L, 1);
}
lua_pop(L, 1);
}
static char *lua_readline_hint(const char *p, const char **ansi1, const char **ansi2) {
char *h = 0;
linenoiseCompletions c = {0};
lua_readline_completions(p, &c);
if (c.len == 1) h = strdup(c.cvec[0] + strlen(p));
linenoiseFreeCompletions(&c);
return h;
}
static void lua_initreadline(lua_State *L) {
histpath = xasprintf("%s/.%s_history", _gc(xhomedir()), LUA_PROGNAME);
linenoiseSetCompletionCallback(lua_readline_completions);
linenoiseSetHintsCallback(lua_readline_hint);
linenoiseSetFreeHintsCallback(free);
}
static int lua_readline(lua_State *L, char **b, const char *prompt) {
globalL = L;
linenoiseHistoryLoad(histpath);
return !!(*b = linenoise(prompt));
}
static void lua_saveline(lua_State *L, const char *line) {
linenoiseHistoryLoad(histpath);
linenoiseHistoryAdd(line);
linenoiseHistorySave(histpath);
}
static void lua_freeline (lua_State *L, char *b) {
free(b);
}
/*
** Return the string to be used as a prompt by the interpreter. Leave
** the string (or nil, if using the default value) on the stack, to keep
** it anchored.
*/
static const char *get_prompt (lua_State *L, int firstline) {
if (lua_getglobal(L, firstline ? "_PROMPT" : "_PROMPT2") == LUA_TNIL)
return (firstline ? LUA_PROMPT : LUA_PROMPT2); /* use the default */
else { /* apply 'tostring' over the value */
const char *p = luaL_tolstring(L, -1, NULL);
lua_remove(L, -2); /* remove original value */
return p;
}
}
/* mark in error messages for incomplete statements */
#define EOFMARK "<eof>"
#define marklen (sizeof(EOFMARK)/sizeof(char) - 1)
/*
** Check whether 'status' signals a syntax error and the error
** message at the top of the stack ends with the above mark for
** incomplete statements.
*/
static int incomplete (lua_State *L, int status) {
if (status == LUA_ERRSYNTAX) {
size_t lmsg;
const char *msg = lua_tolstring(L, -1, &lmsg);
if (lmsg >= marklen && strcmp(msg + lmsg - marklen, EOFMARK) == 0) {
lua_pop(L, 1);
return 1;
}
}
return 0; /* else... */
}
/*
** Prompt the user, read a line, and push it into the Lua stack.
*/
static int pushline (lua_State *L, int firstline) {
char buffer[LUA_MAXINPUT];
char *b = buffer;
size_t l;
const char *prmt = get_prompt(L, firstline);
int readstatus = lua_readline(L, &b, prmt);
if (readstatus == 0)
return 0; /* no input (prompt will be popped by caller) */
lua_pop(L, 1); /* remove prompt */
l = strlen(b);
if (l > 0 && b[l-1] == '\n') /* line ends with newline? */
b[--l] = '\0'; /* remove it */
if (firstline && b[0] == '=') /* for compatibility with 5.2, ... */
lua_pushfstring(L, "return %s", b + 1); /* change '=' to 'return' */
else
lua_pushlstring(L, b, l);
lua_freeline(L, b);
return 1;
}
/*
** Try to compile line on the stack as 'return <line>;'; on return, stack
** has either compiled chunk or original line (if compilation failed).
*/
static int addreturn (lua_State *L) {
const char *line = lua_tostring(L, -1); /* original line */
const char *retline = lua_pushfstring(L, "return %s;", line);
int status = luaL_loadbuffer(L, retline, strlen(retline), "=stdin");
if (status == LUA_OK) {
lua_remove(L, -2); /* remove modified line */
if (line[0] != '\0') /* non empty? */
lua_saveline(L, line); /* keep history */
}
else
lua_pop(L, 2); /* pop result from 'luaL_loadbuffer' and modified line */
return status;
}
/*
** Read multiple lines until a complete Lua statement
*/
static int multiline (lua_State *L) {
for (;;) { /* repeat until gets a complete statement */
size_t len;
const char *line = lua_tolstring(L, 1, &len); /* get what it has */
int status = luaL_loadbuffer(L, line, len, "=stdin"); /* try it */
if (!incomplete(L, status) || !pushline(L, 0)) {
lua_saveline(L, line); /* keep history */
return status; /* cannot or should not try to add continuation line */
}
lua_pushliteral(L, "\n"); /* add newline... */
lua_insert(L, -2); /* ...between the two lines */
lua_concat(L, 3); /* join them */
}
}
/*
** Read a line and try to load (compile) it first as an expression (by
** adding "return " in front of it) and second as a statement. Return
** the final status of load/call with the resulting function (if any)
** in the top of the stack.
*/
static int loadline (lua_State *L) {
int status;
lua_settop(L, 0);
if (!pushline(L, 1))
return -1; /* no input */
if ((status = addreturn(L)) != LUA_OK) /* 'return ...' did not work? */
status = multiline(L); /* try as command, maybe with continuation lines */
lua_remove(L, 1); /* remove line from the stack */
lua_assert(lua_gettop(L) == 1);
return status;
}
/*
** Prints (calling the Lua 'print' function) any values on the stack
*/
static void l_print (lua_State *L) {
int n = lua_gettop(L);
if (n > 0) { /* any result to be printed? */
luaL_checkstack(L, LUA_MINSTACK, "too many results to print");
lua_getglobal(L, "print");
lua_insert(L, 1);
if (lua_pcall(L, n, 0, 0) != LUA_OK)
l_message(progname, lua_pushfstring(L, "error calling 'print' (%s)",
lua_tostring(L, -1)));
}
}
/*
** Do the REPL: repeatedly read (load) a line, evaluate (call) it, and
** print any results.
@ -591,12 +275,15 @@ static void doREPL (lua_State *L) {
int status;
const char *oldprogname = progname;
progname = NULL; /* no 'progname' on errors in interactive mode */
lua_initreadline(L);
while ((status = loadline(L)) != -1) {
lua_initrepl(LUA_PROGNAME);
while ((status = lua_loadline(L)) != -1) {
if (status == LUA_OK)
status = docall(L, 0, LUA_MULTRET);
if (status == LUA_OK) l_print(L);
else report(L, status);
status = lua_runchunk(L, 0, LUA_MULTRET);
if (status == LUA_OK) {
lua_l_print(L);
} else {
lua_report(L, status);
}
}
lua_settop(L, 0); /* clear stack */
lua_writeline();
@ -654,13 +341,15 @@ static int pmain (lua_State *L) {
int main (int argc, char **argv) {
ShowCrashReports();
int status, result;
lua_State *L;
if (!IsModeDbg()) {
ShowCrashReports();
}
/* if (IsModeDbg()) ShowCrashReports(); */
L = luaL_newstate(); /* create state */
if (L == NULL) {
l_message(argv[0], "cannot create state: not enough memory");
lua_l_message(argv[0], "cannot create state: not enough memory");
return EXIT_FAILURE;
}
lua_pushcfunction(L, &pmain); /* to call 'pmain' in protected mode */
@ -668,7 +357,10 @@ int main (int argc, char **argv) {
lua_pushlightuserdata(L, argv); /* 2nd argument */
status = lua_pcall(L, 2, 1, 0); /* do the call */
result = lua_toboolean(L, -1); /* get result */
report(L, status);
lua_report(L, status);
lua_close(L);
if (IsModeDbg()) {
CheckForMemoryLeaks();
}
return (result && status == LUA_OK) ? EXIT_SUCCESS : EXIT_FAILURE;
}

View file

@ -31,6 +31,7 @@
#include "libc/fmt/fmt.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/mem/mem.h"
@ -66,9 +67,6 @@
#define PIPE 11
#define OCTAL 0x80
extern bool __replmode;
extern bool __nomultics;
static const long __NR_brk = 12;
static const long __NR_sigreturn = 15;

View file

@ -54,16 +54,17 @@ TOOL_NET_DIRECTDEPS = \
LIBC_ZIPOS \
NET_HTTP \
NET_HTTPS \
TOOL_BUILD_LIB \
THIRD_PARTY_ARGON2 \
THIRD_PARTY_GDTOA \
THIRD_PARTY_GETOPT \
THIRD_PARTY_LINENOISE \
THIRD_PARTY_LUA \
THIRD_PARTY_MAXMIND \
THIRD_PARTY_MBEDTLS \
THIRD_PARTY_REGEX \
THIRD_PARTY_MAXMIND \
THIRD_PARTY_SQLITE3 \
THIRD_PARTY_ZLIB \
THIRD_PARTY_ARGON2 \
TOOL_BUILD_LIB \
TOOL_DECODE_LIB
TOOL_NET_DEPS := \

View file

@ -37,6 +37,7 @@
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/log/backtrace.internal.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
@ -92,6 +93,7 @@
#include "libc/sysv/consts/sol.h"
#include "libc/sysv/consts/tcp.h"
#include "libc/sysv/consts/w.h"
#include "libc/sysv/errfuns.h"
#include "libc/testlib/testlib.h"
#include "libc/time/time.h"
#include "libc/x/x.h"
@ -102,8 +104,10 @@
#include "net/http/url.h"
#include "net/https/https.h"
#include "third_party/getopt/getopt.h"
#include "third_party/linenoise/linenoise.h"
#include "third_party/lua/cosmo.h"
#include "third_party/lua/lauxlib.h"
#include "third_party/lua/lrepl.h"
#include "third_party/lua/ltests.h"
#include "third_party/lua/lua.h"
#include "third_party/lua/luaconf.h"
@ -360,6 +364,7 @@ static bool hascontenttype;
static bool sslclientverify;
static bool connectionclose;
static bool hasonworkerstop;
static bool isexitingworker;
static bool hasonworkerstart;
static bool leakcrashreports;
static bool hasonhttprequest;
@ -408,6 +413,7 @@ static const char *brand;
static char gzip_footer[8];
static const char *pidpath;
static const char *logpath;
static const char *histpath;
static struct pollfd *polls;
static struct Strings loops;
static size_t payloadlength;
@ -453,7 +459,6 @@ static struct TlsBio g_bio;
static char slashpath[PATH_MAX];
static struct DeflateGenerator dg;
static wontreturn void ExitWorker(void);
static char *Route(const char *, size_t, const char *, size_t);
static char *RouteHost(const char *, size_t, const char *, size_t);
static char *RoutePath(const char *, size_t);
@ -6563,8 +6568,18 @@ static void CloseServerFds(void) {
}
}
static void HandleConnection(size_t i) {
int pid;
static int ExitWorker(void) {
if (!IsModeDbg()) {
_Exit(0);
} else {
isexitingworker = true;
return eintr();
}
}
// returns 0 otherwise -1 if worker needs to unwind stack and exit
static int HandleConnection(size_t i) {
int pid, rc = 0;
clientaddrsize = sizeof(clientaddr);
if ((client = accept4(servers.p[i].fd, &clientaddr, &clientaddrsize,
SOCK_CLOEXEC)) != -1) {
@ -6572,7 +6587,7 @@ static void HandleConnection(size_t i) {
messageshandled = 0;
if (hasonclientconnection && LuaOnClientConnection()) {
close(client);
return;
return 0;
}
if (uniprocess) {
pid = -1;
@ -6591,7 +6606,7 @@ static void HandleConnection(size_t i) {
break;
case -1:
HandleForkFailure();
return;
return 0;
default:
++shared->workers;
close(client);
@ -6599,7 +6614,7 @@ static void HandleConnection(size_t i) {
if (hasonprocesscreate) {
LuaOnProcessCreate(pid);
}
return;
return 0;
}
}
if (!pid && !IsWindows()) {
@ -6613,7 +6628,7 @@ static void HandleConnection(size_t i) {
if (hasonworkerstop) {
CallSimpleHook("OnWorkerStop");
}
ExitWorker();
rc = ExitWorker();
} else {
close(client);
oldin.p = 0;
@ -6631,68 +6646,87 @@ static void HandleConnection(size_t i) {
mbedtls_ssl_session_reset(&ssl);
}
#endif
CollectGarbage();
}
} else if (errno == EINTR || errno == EAGAIN) {
LockInc(&shared->c.acceptinterrupts);
} else if (errno == ENFILE) {
LockInc(&shared->c.enfiles);
WARNF("(srvr) too many open files");
meltdown = true;
} else if (errno == EMFILE) {
LockInc(&shared->c.emfiles);
WARNF("(srvr) ran out of open file quota");
meltdown = true;
} else if (errno == ENOMEM) {
LockInc(&shared->c.enomems);
WARNF("(srvr) ran out of memory");
meltdown = true;
} else if (errno == ENOBUFS) {
LockInc(&shared->c.enobufs);
WARNF("(srvr) ran out of buffer");
meltdown = true;
} else if (errno == ENONET) {
LockInc(&shared->c.enonets);
WARNF("(srvr) %s network gone", DescribeServer());
polls[i].fd = -polls[i].fd;
} else if (errno == ENETDOWN) {
LockInc(&shared->c.enetdowns);
WARNF("(srvr) %s network down", DescribeServer());
polls[i].fd = -polls[i].fd;
} else if (errno == ECONNABORTED) {
LockInc(&shared->c.acceptresets);
WARNF("(srvr) %s connection reset before accept");
} else if (errno == ENETUNREACH || errno == EHOSTUNREACH ||
errno == EOPNOTSUPP || errno == ENOPROTOOPT || errno == EPROTO) {
LockInc(&shared->c.accepterrors);
WARNF("(srvr) %s ephemeral accept error: %m", DescribeServer());
CollectGarbage();
} else {
DIEF("(srvr) %s accept error: %m", DescribeServer());
if (errno == EINTR || errno == EAGAIN) {
LockInc(&shared->c.acceptinterrupts);
} else if (errno == ENFILE) {
LockInc(&shared->c.enfiles);
WARNF("(srvr) too many open files");
meltdown = true;
} else if (errno == EMFILE) {
LockInc(&shared->c.emfiles);
WARNF("(srvr) ran out of open file quota");
meltdown = true;
} else if (errno == ENOMEM) {
LockInc(&shared->c.enomems);
WARNF("(srvr) ran out of memory");
meltdown = true;
} else if (errno == ENOBUFS) {
LockInc(&shared->c.enobufs);
WARNF("(srvr) ran out of buffer");
meltdown = true;
} else if (errno == ENONET) {
LockInc(&shared->c.enonets);
WARNF("(srvr) %s network gone", DescribeServer());
polls[i].fd = -polls[i].fd;
} else if (errno == ENETDOWN) {
LockInc(&shared->c.enetdowns);
WARNF("(srvr) %s network down", DescribeServer());
polls[i].fd = -polls[i].fd;
} else if (errno == ECONNABORTED) {
LockInc(&shared->c.acceptresets);
WARNF("(srvr) %s connection reset before accept");
} else if (errno == ENETUNREACH || errno == EHOSTUNREACH ||
errno == EOPNOTSUPP || errno == ENOPROTOOPT || errno == EPROTO) {
LockInc(&shared->c.accepterrors);
WARNF("(srvr) %s ephemeral accept error: %m", DescribeServer());
} else {
DIEF("(srvr) %s accept error: %m", DescribeServer());
}
errno = 0;
}
errno = 0;
return rc;
}
static void HandlePoll(void) {
// returns 2 if we should stay in the redbean event loop
// returns 1 if poll() says stdin has user input available
// returns 0 if poll() timed out after ms
// returns -1 if worker is unwinding exit
static int HandlePoll(int ms) {
size_t i;
if (poll(polls, servers.n, HEARTBEAT) != -1) {
int nfds;
if ((nfds = poll(polls, 1 + servers.n, ms)) != -1) {
for (i = 0; i < servers.n; ++i) {
if (polls[i].revents) {
if (polls[1 + i].revents) {
serveraddr = &servers.p[i].addr;
ishandlingconnection = true;
HandleConnection(i);
if (HandleConnection(i) == -1) return -1;
ishandlingconnection = false;
}
}
} else if (errno == EINTR || errno == EAGAIN) {
LockInc(&shared->c.pollinterrupts);
} else if (errno == ENOMEM) {
LockInc(&shared->c.enomems);
WARNF("(srvr) %s ran out of memory");
meltdown = true;
// are we polling stdin for the repl?
if (polls[0].fd >= 0) {
if (polls[0].revents) {
return 1; // user entered a keystroke
} else if (!nfds) {
return 0; // let linenoise know it timed out
}
}
} else {
DIEF("(srvr) poll error: %m");
if (errno == EINTR || errno == EAGAIN) {
LockInc(&shared->c.pollinterrupts);
} else if (errno == ENOMEM) {
LockInc(&shared->c.enomems);
WARNF("(srvr) %s ran out of memory");
meltdown = true;
} else {
DIEF("(srvr) poll error: %m");
}
errno = 0;
}
errno = 0;
return 2;
}
static void RestoreApe(void) {
@ -6769,18 +6803,21 @@ static void Listen(void) {
}
}
servers.n = n;
polls = malloc(n * sizeof(*polls));
polls = malloc((1 + n) * sizeof(*polls));
polls[0].fd = -1;
polls[0].events = POLLIN;
polls[0].revents = 0;
for (i = 0; i < n; ++i) {
polls[i].fd = servers.p[i].fd;
polls[i].events = POLLIN;
polls[i].revents = 0;
polls[1 + i].fd = servers.p[i].fd;
polls[1 + i].events = POLLIN;
polls[1 + i].revents = 0;
}
}
static void HandleShutdown(void) {
CloseServerFds();
INFOF("(srvr) received %s", strsignal(shutdownsig));
if (shutdownsig == SIGTERM) {
if (shutdownsig != SIGINT && shutdownsig != SIGQUIT) {
if (!killed) terminated = false;
INFOF("(srvr) killing process group");
KillGroup();
@ -6788,9 +6825,14 @@ static void HandleShutdown(void) {
WaitAll();
}
static void HandleEvents(void) {
// this function coroutines with linenoise
static int EventLoop(int fd, int ms) {
int rc;
long double t;
rc = -1;
polls[0].fd = 0;
while (!terminated) {
errno = 0;
if (zombied) {
ReapZombies();
} else if (invalidated) {
@ -6799,13 +6841,42 @@ static void HandleEvents(void) {
} else if (meltdown) {
EnterMeltdownMode();
meltdown = false;
} else if ((t = nowl()) - lastheartbeat > .5) {
} else if ((t = nowl()) - lastheartbeat > HEARTBEAT / 1000.) {
lastheartbeat = t;
HandleHeartbeat();
} else {
HandlePoll();
} else if ((rc = HandlePoll(ms)) != 2) {
break; // return control to linenoise
}
}
polls[0].fd = -1;
return rc;
}
static void ReplEventLoop(void) {
int status;
lua_State *L = GL;
__nomultics = 2;
__replmode = true;
lua_initrepl("redbean");
linenoiseSetPollCallback(EventLoop);
while ((status = lua_loadline(L)) != -1) {
if (status == LUA_OK) {
status = lua_runchunk(L, 0, LUA_MULTRET);
}
if (status == LUA_OK) {
lua_l_print(L);
} else {
lua_report(L, status);
}
}
if (!terminated && !isexitingworker) {
OnTerm(SIGHUP); // eof event
}
lua_settop(L, 0); // clear stack
lua_writeline();
__replmode = false;
__nomultics = 0;
polls[0].fd = -1;
}
static void SigInit(void) {
@ -6917,16 +6988,6 @@ static void MemDestroy(void) {
Free(&polls);
}
static wontreturn void ExitWorker(void) {
if (IsModeDbg()) {
LuaDestroy();
TlsDestroy();
MemDestroy();
CheckForMemoryLeaks();
}
_Exit(0);
}
static void GetOpts(int argc, char *argv[]) {
int opt;
bool storeasset = false;
@ -7032,9 +7093,19 @@ void RedBean(int argc, char *argv[]) {
inbuf = inbuf_actual;
isinitialized = true;
CallSimpleHookIfDefined("OnServerStart");
HandleEvents();
HandleShutdown();
CallSimpleHookIfDefined("OnServerStop");
#ifdef STATIC
EventLoop();
#else
if (isatty(0)) {
ReplEventLoop();
} else {
EventLoop(-1, HEARTBEAT);
}
#endif
if (!isexitingworker) {
HandleShutdown();
CallSimpleHookIfDefined("OnServerStop");
}
if (!IsTiny()) {
LuaDestroy();
TlsDestroy();