/* * 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 #include #include #define YEARS "2025, 2026" #define SOURCE_URL "https://gitlab.com/alexandre1985/lali/" // MAIN /////////////////////////////////////////////////////////////////////// void runFile(int infd, Object **env, GC_PARAM) { Stream stream = { STREAM_TYPE_FILE, .fd = infd }; GC_TRACE(gcObject, nil); if (setjmp(exceptionEnv)) exit(EXIT_FAILURE); // 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) { /// STRING char *replLine = (char *)NULL; Stream stream = { STREAM_TYPE_STRING, .buffer = "" }; GC_TRACE(gcObject, nil); setjmp(exceptionEnv); do { /* If the buffer has already been allocated, return the memory to the free pool. */ if (replLine) { free(replLine); replLine = (char *)NULL; } replLine = readline("lali> "); stream.length = 0; stream.offset = 0; stream.buffer = replLine; while (peekNext(&stream) != EOF) { *gcObject = nil; *gcObject = readExpr(&stream, GC_ROOTS); *gcObject = evalExpr(gcObject, env, GC_ROOTS); writeObject(*gcObject, true, stdout); fputc('\n', stdout); } // If the line has any text in it, save it on the history. if (replLine && *replLine) add_history (replLine); } while (replLine != NULL); fputc('\n', stdout); stream.buffer = (char *)NULL; free(replLine); } 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\ you may use unambiguous abbreviations for the long option names.\n\ \n\ source code at %s .\n", argv[0], argv[0], argv[0], argv[0], SOURCE_URL); 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\ \n\ This is free software: you are free to change and redistribute it,\n\ under certain conditions.\n\ This program comes with NO WARRANTY, to the extent permitted by law.\n\ \n\ Source code at %s .\n", YEARS, SOURCE_URL); runREPL(STDIN_FILENO, gcEnv, GC_ROOTS); } else if (!opt_close) runFile(STDIN_FILENO, gcEnv, GC_ROOTS); return EXIT_SUCCESS; }