282 lines
7.5 KiB
C
282 lines
7.5 KiB
C
#define _GNU_SOURCE
|
||
|
||
#include "pebblisp.h"
|
||
|
||
#include <signal.h>
|
||
#include <stdlib.h>
|
||
#include <readline/readline.h>
|
||
#include <readline/history.h>
|
||
#include <unistd.h>
|
||
|
||
struct Settings {
|
||
int runTests;
|
||
int ignoreConfig;
|
||
int ignoreLib;
|
||
int moreToDo;
|
||
const char* configFile;
|
||
char historyFile[128];
|
||
} settings;
|
||
|
||
Object getPrompt(struct Environment* env)
|
||
{
|
||
Object prompt = fetchFromEnvironment("prompt", env);
|
||
prompt = cloneObject(prompt);
|
||
if (prompt.type == TYPE_STRING) {
|
||
return prompt;
|
||
}
|
||
Object param = stringFromSlice("", 1);
|
||
Object e = funcyEval(&prompt, ¶m, 1, env);
|
||
cleanObject(&prompt);
|
||
cleanObject(¶m);
|
||
return e;
|
||
}
|
||
|
||
char* prompt(struct Environment* env)
|
||
{
|
||
Object p = getPrompt(env);
|
||
char* ret = readline(p.string);
|
||
cleanObject(&p);
|
||
return ret;
|
||
}
|
||
|
||
char* preprocess(char* buf, struct Environment* env)
|
||
{
|
||
Object lambda = fetchFromEnvironment("preprocess", env);
|
||
Object buffer = nullTerminated(buf);
|
||
Object s = funcyEval(&lambda, &buffer, 1, env);
|
||
size_t length;
|
||
return stringObj(&s, &length);
|
||
}
|
||
|
||
void sigintHandler(unused int signalNumber)
|
||
{
|
||
Object p = getPrompt(global());
|
||
write(1, "\n", 1);
|
||
write(1, p.string, strlen(p.string));
|
||
cleanObject(&p);
|
||
}
|
||
|
||
char* completionGenerator(const char* text, int state)
|
||
{
|
||
static size_t i;
|
||
static size_t completionLength;
|
||
if (state == 0) {
|
||
i = 0;
|
||
completionLength = strlen(text);
|
||
}
|
||
|
||
struct ObjectTable* table = &global()->table;
|
||
for (; i < table->capacity; i++) {
|
||
if (table->elements[i].symbol && strncmp(table->elements[i].symbol, text, completionLength) == 0) {
|
||
char* symbol = malloc(sizeof(char) * (strlen(table->elements[i].symbol) + 1));
|
||
strcpy(symbol, table->elements[i].symbol);
|
||
i++;
|
||
return symbol;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
char** completer(const char* text, int start, int end)
|
||
{
|
||
return rl_completion_matches(text, completionGenerator);
|
||
}
|
||
|
||
void repl(struct Environment* env)
|
||
{
|
||
rl_attempted_completion_function = completer;
|
||
signal(SIGINT, sigintHandler);
|
||
char* buf;
|
||
using_history();
|
||
read_history(settings.historyFile);
|
||
|
||
while ((buf = prompt(env)) != NULL) {
|
||
if (strcmp("q", buf) == 0) {
|
||
free(buf);
|
||
break;
|
||
}
|
||
buf = preprocess(buf, env);
|
||
if (buf[0] == '\0') {
|
||
free(buf);
|
||
continue;
|
||
}
|
||
add_history(buf);
|
||
if ((buf[0] == 'c' && buf[1] == 'd')) {
|
||
char* oldBuf = buf;
|
||
buf = malloc(sizeof(char) * strlen(buf + 6));
|
||
sprintf(buf, "(cd \"%s\")", oldBuf + 3);
|
||
free(oldBuf);
|
||
}
|
||
if ((buf[0] == '?' && (buf[1] == ' ' || buf[1] == '\0'))) {
|
||
char* oldBuf = buf;
|
||
buf = malloc(sizeof(char) * strlen(buf + 3));
|
||
sprintf(buf, "(%s)", oldBuf);
|
||
free(oldBuf);
|
||
}
|
||
Object o = parseEval(buf, env);
|
||
if (isFuncy(o) || isError(o, DID_NOT_FIND_SYMBOL)) {
|
||
cleanObject(&o);
|
||
system(buf);
|
||
free(buf);
|
||
write_history(settings.historyFile);
|
||
continue;
|
||
}
|
||
free(buf);
|
||
|
||
size_t length;
|
||
char* output = stringObj(&o, &length);
|
||
cleanObject(&o);
|
||
printColored(output);
|
||
free(output);
|
||
printf("[0m\n");
|
||
write_history(settings.historyFile);
|
||
}
|
||
}
|
||
|
||
void loadArgsIntoEnv(int argc, const char* argv[], struct Environment* env)
|
||
{
|
||
Object args = listObject();
|
||
for (int i = 0; i < argc; i++) {
|
||
nf_addToList(&args, nullTerminated(argv[i]));
|
||
}
|
||
addToEnv(env, "args", args);
|
||
}
|
||
|
||
#ifdef __x86_64__
|
||
|
||
#include <ucontext.h>
|
||
|
||
int nestedSegfault = 0;
|
||
|
||
void segfaultHandler(unused int nSignum, unused siginfo_t* si, void* vcontext)
|
||
{
|
||
if (nestedSegfault) {
|
||
printf("Nested segfault!!!\n");
|
||
exit(139);
|
||
}
|
||
nestedSegfault = 1;
|
||
|
||
printf("Segfaulted!\n");
|
||
struct Slice* lastOpen = getLastOpen();
|
||
if (lastOpen) {
|
||
printf("line: %d\n%s\n", lastOpen->lineNumber, lastOpen->text);
|
||
} else {
|
||
printf("Happened before token processing.\n");
|
||
}
|
||
ucontext_t* context = vcontext;
|
||
context->uc_mcontext.gregs[REG_RIP]++;
|
||
exit(139);
|
||
}
|
||
|
||
void setupSegfaultHandler()
|
||
{
|
||
struct sigaction action;
|
||
memset(&action, 0, sizeof(struct sigaction));
|
||
action.sa_flags = SA_SIGINFO;
|
||
action.sa_sigaction = segfaultHandler;
|
||
sigaction(SIGSEGV, &action, NULL);
|
||
}
|
||
|
||
#else
|
||
void setupSegfaultHandler()
|
||
{
|
||
}
|
||
#endif
|
||
|
||
#define RUN_TESTS_ARG "--run-tests"
|
||
#define RUN_DETAILED_TESTS "=detailed"
|
||
#define IGNORE_CONFIG_ARG "--ignore-config"
|
||
#define IGNORE_LIB_ARG "--ignore-lib"
|
||
#define CONFIG_FILE_ARG "--config="
|
||
|
||
void getSettings(int argc, const char* argv[])
|
||
{
|
||
const char* const home = getenv("HOME");
|
||
|
||
settings.runTests = 0;
|
||
settings.ignoreConfig = 0;
|
||
settings.ignoreLib = 0;
|
||
settings.moreToDo = 0;
|
||
settings.configFile = NULL;
|
||
|
||
sprintf(settings.historyFile, "%s/.plhist", home);
|
||
|
||
size_t runTestsLen = strlen(RUN_TESTS_ARG);
|
||
size_t configFileLen = strlen(CONFIG_FILE_ARG);
|
||
for (int i = 1; i < argc; i++) {
|
||
if (strncmp(argv[i], RUN_TESTS_ARG, runTestsLen) == 0) {
|
||
int isDetailed = strcmp(argv[i] + runTestsLen, RUN_DETAILED_TESTS) == 0;
|
||
settings.runTests = isDetailed ? 2 : 1;
|
||
} else if (strncmp(argv[i], CONFIG_FILE_ARG, configFileLen) == 0) {
|
||
settings.configFile = argv[i] + configFileLen;
|
||
} else if (strcmp(argv[i], IGNORE_CONFIG_ARG) == 0) {
|
||
settings.ignoreConfig = 1;
|
||
} else if (strcmp(argv[i], IGNORE_LIB_ARG) == 0) {
|
||
settings.ignoreLib = 1;
|
||
} else if (argv[i][0] == '-') {
|
||
fprintf(stderr, "Unrecognized argument: '%s'\n", argv[i]);
|
||
} else if (i == (argc - 1)) {
|
||
settings.moreToDo = 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
int main(int argc, const char* argv[])
|
||
{
|
||
setupSegfaultHandler();
|
||
getSettings(argc, argv);
|
||
|
||
struct Environment env = defaultEnv();
|
||
setGlobal(&env);
|
||
|
||
if (settings.runTests) {
|
||
int ret = runTests(settings.runTests == 2);
|
||
shredDictionary();
|
||
deleteEnv(global());
|
||
return ret;
|
||
}
|
||
|
||
if (!settings.ignoreLib) {
|
||
readFile(SCRIPTDIR "/lib.pbl", &env);
|
||
}
|
||
|
||
Object o = parseEval("(def prompt \"pebblisp::> \")", &env);
|
||
cleanObject(&o);
|
||
o = parseEval("(def preprocess (fn (text) (text)))", &env);
|
||
cleanObject(&o);
|
||
|
||
if (!settings.ignoreConfig) {
|
||
char config[128];
|
||
if (settings.configFile) {
|
||
sprintf(config, "%s", settings.configFile);
|
||
} else {
|
||
const char* const home = getenv("HOME");
|
||
sprintf(config, "%s/.pebblisp.pbl", home);
|
||
}
|
||
if (readFile(config, &env) == 1 && settings.configFile) {
|
||
fprintf(stderr, "Config file not found at %s\n", config);
|
||
}
|
||
}
|
||
|
||
if (settings.moreToDo) {
|
||
FILE* file = fopen(argv[argc - 1], "r");
|
||
if (file) {
|
||
// Execute a file
|
||
loadArgsIntoEnv(argc, argv, &env);
|
||
_readFile(file, &env);
|
||
} else {
|
||
// Run arguments directly as pl code
|
||
Object r = parseEval(argv[argc - 1], &env);
|
||
printAndClean(&r);
|
||
}
|
||
} else {
|
||
// Run a repl
|
||
loadArgsIntoEnv(argc, argv, &env);
|
||
repl(&env);
|
||
}
|
||
deleteEnv(&env);
|
||
shredDictionary();
|
||
// eprintf("totalSearchDepth: %d of %d searches\n", getTotalSearchDepth(), getTotalSearches());
|
||
// eprintf("\nHEAP-ALLOCATED OBJECTS: %d\n", getAllocations());
|
||
// eprintf("TOTAL OBJECT.C ALLOC: %zu\n", getBytes());
|
||
} |