/*
* lali (Lali Another Lisp Implementation)
*
* Author: Daniel Cerqueira (dan.git@lispclub.com)
* Maintainer: Daniel Cerqueira (dan.git@lispclub.com)
* Version: 0.0
* Keywords: lali, lisp, implementation, interpreter, lisp1.5,
* computer programming language
* Homepage: https://gitlab.com/alexandre1985/lali
* SPDX-License-Identifier: AGPL-3.0-or-later AND Unlicense
*
* Copyright (C) 2025, 2026 Daniel Cerqueira
*
* This file is part of lali.
*
* lali is free software; you can redistribute it and/or modify it under the terms
* of the GNU Affero General Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program; if not, see .
*
*
* lali software is based on tiny-lisp
* version from 2016, written by Matthias Pirstitz, which is released to public
* domain under Unlicense .
*/
#include "liblali.h"
#include
#define YEARS "2025, 2026"
// MAIN ///////////////////////////////////////////////////////////////////////
void runFile(int infd, Object **env, GC_PARAM) {
Stream stream = { STREAM_TYPE_FILE, .fd = infd };
GC_TRACE(gcObject, nil);
if (setjmp(exceptionEnv))
return;
// deal with shebang
if (peekForward(&stream, true) == EOF)
return;
do {
*gcObject = nil;
*gcObject = readExpr(&stream, GC_ROOTS);
evalExpr(gcObject, env, GC_ROOTS);
} while (peekNext(&stream) != EOF);
}
void runREPL(int infd, Object **env, GC_PARAM) {
Stream stream = { STREAM_TYPE_FILE, .fd = infd };
GC_TRACE(gcObject, nil);
for (;;) {
if (setjmp(exceptionEnv))
continue;
for (;;) {
*gcObject = nil;
fputs("lali> ", stdout);
fflush(stdout);
if (peekNext(&stream) == EOF) {
fputc('\n', stdout);
return;
}
*gcObject = readExpr(&stream, GC_ROOTS);
*gcObject = evalExpr(gcObject, env, GC_ROOTS);
writeObject(*gcObject, true, stdout);
fputc('\n', stdout);
}
}
}
int main(int argc, char *argv[]) {
/// Options processing
int opt_repl = 0; // false
int opt_close = 0; // false
int opt_quiet = 0; // false
struct option const longopts[] =
{
{ "repl", no_argument, NULL, 'r' },
{ "close", no_argument, NULL, 'c' },
{ "quiet", no_argument, NULL, 'q' },
{ "help", no_argument, NULL, 'h' },
{ 0, 0, 0, 0 }
};
char const shortopts[] = "crqh";
while (true) {
int opt = getopt_long(argc, argv, shortopts, longopts, NULL);
if (opt < 0)
break;
switch (opt)
{
case 0:
/* If getopt returns 0, then it has already processed a
long-named option. We should do nothing. */
break;
case 'h':
/* Show help message. */
fprintf(stdout, "basic usages:\n\
normal: %s [file-name]...\n\
run repl: %s -r|--repl [-q|--quiet] [file-name]...\n\
close stdin: %s -c|--close [file-name]...\n\
show help: %s -h|--help\n\
\n\
in normal mode, reads from standard input.\n\
when present, load file-name(s) at startup. can be used as library file-name(s).\n\
\n\
you may use unambiguous abbreviations for the long option names.\n",
argv[0], argv[0], argv[0], argv[0]);
return EXIT_SUCCESS;
case 'c':
/* Close stdin. Useful to just use file-name(s), not stdin. */
opt_close = 1; // true
break;
case 'r':
/* Enter a REPL. */
opt_repl = 1; // true
break;
case 'q':
/* Supress copyright message. */
opt_quiet = 1; // true
break;
default:
fprintf (stderr,
"\ttry '%s --help' for a complete list of options.\n",
argv[0]);
return EXIT_FAILURE;
}
}
int options = opt_close + opt_repl + opt_quiet;
/// Lisp setting
GC_PARAM = nil;
if (setjmp(exceptionEnv))
return EXIT_FAILURE;
symbols = nil;
symbols = newCons(&nil, &symbols, GC_ROOTS);
symbols = newCons(&n, &symbols, GC_ROOTS);
symbols = newCons(&t, &symbols, GC_ROOTS);
symbols = newCons(&f, &symbols, GC_ROOTS);
GC_TRACE(gcEnv, newRootEnv(GC_ROOTS));
// File sources processing
int fd = 0;
if (argc > 1) {
for (int i = ++options; i < argc; i++) {
if ((fd = open(argv[i], O_RDONLY)) == -1) {
fprintf(stderr, "%s: open() failed, %s\n", argv[0], strerror(errno));
return EXIT_FAILURE;
}
runFile(fd, gcEnv, GC_ROOTS);
}
}
// Main processing
if (opt_repl) {
if(!isatty(STDIN_FILENO)) {
fprintf(stderr, "%s: repl does not accept piping\n", argv[0]);
return EXIT_FAILURE;
}
if (!opt_quiet)
fprintf(stdout, "lali Copyright (C) %s Daniel Cerqueira\n\nThis is free software: you are free to change and redistribute it,\nunder certain conditions.\nThis program comes with NO WARRANTY, to the extent permitted by law.\n", YEARS);
runREPL(STDIN_FILENO, gcEnv, GC_ROOTS);
}
else if (!opt_close)
runFile(STDIN_FILENO, gcEnv, GC_ROOTS);
return EXIT_SUCCESS;
}