/** * Serene (simple) - A PoC lisp to collect data on Serenes concepts * Copyright (C) 2019-2020 Sameer Rahmani * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package serene.simple; import java.io.IOException; import java.io.ByteArrayInputStream; import java.io.Console; import java.io.FileInputStream; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; import java.net.ServerSocket; import java.net.Socket; /** * What is missing: * * A namespace functionality. Because creating and compiling dynamic classes * is a rabbit hole and tons of work which doesn't make sense for a toy * project. * * Unified function interface. * * Requiring different namespaces * * A sophisticated parser. My Reader implementation is really cheap that * suits a toy project. It might worth investigating on different solutions * including using a parser generator or a head of time read implementation. * * Primitive functions in Serene. I implemented lots of primitive functions * in java rather than Serene itself mostly because of two reasons. Lack of * macros and namespaces. * * Decent [functional] data structures. The only data structure I implemented * is list. * * Quality code. The general quality of this implementation is not great, I * sacrificed quality for time. */ public class Main { private static int port = 5544; private static String host = "localhost"; private static String licenseHeader = "\nSerene(simple), Copyright (C) 2019-2020 " + "Sameer Rahmani \n" + "Serene(simple) comes with ABSOLUTELY NO WARRANTY;\n" + "This is free software, and you are welcome\n" + "to redistribute it under certain conditions; \n" + "for details take a look at the LICENSE file.\n"; public static void main(String[] args) throws IOException, RuntimeException { if (args.length == 0) { startRepl(); return; } if (args[0].equals("nrepl")) { nrepl(); return; } runSerene(args[0]); } private static void startRepl() throws IOException { RootScope rootScope = new RootScope(); Console console = System.console(); System.out.println("Serene 'simple' v0.1.0"); System.out.println(licenseHeader); while(true) { String inputData = console.readLine("serene-> "); if (inputData == null) break; ByteArrayInputStream inputStream = new ByteArrayInputStream(inputData.getBytes()); try { ListNode nodes = Reader.read(inputStream); Object result = ListNode.EMPTY; for (Node n : nodes) { result = n.eval(rootScope); } System.out.print(";; "); if (result == null) { System.out.println("nil"); } else { System.out.println(result.toString()); } } catch(Exception e) { System.out.println("Error: "); e.printStackTrace(System.out); } } } /** * Serene's nRepl is super simple. It waits for a newline char and then evaluates the * given input. It sends back the result of the evaluation in `` format. * `status-char` is either `0` or `1`. **Zero** means the evaluation was successful and the `value` * is the result of evaluation while **one** means there was an error during the evaluation and * the `value` is the traceback for the exception. */ public static void nrepl() throws IOException { RootScope rootScope = new RootScope(); ServerSocket socket = new ServerSocket(Main.port);; System.out.println("Serene 'simple' v0.1.0"); System.out.println(licenseHeader); System.out.println( String.format("nRepl has been started on tcp://%s:%s", Main.host, Main.port)); // NOTE: The nRepl server is too simple. It supports one connection at a time // and will terminate when the client disconnects. try { Socket clientSocket = socket.accept(); PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); while(true) { String inputData = in.readLine(); if (inputData == null) break; ByteArrayInputStream inputStream = new ByteArrayInputStream(inputData.getBytes()); try { ListNode nodes = Reader.read(inputStream); Object result = ListNode.EMPTY; for (Node n : nodes) { result = n.eval(rootScope); } if (result == null) { out.println("0nil"); } else { out.println( String.format("0%s", result.toString())); } } catch(Exception e) { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); String exceptionAsString = sw.toString(); out.println( String.format("1%s", exceptionAsString)); } } } catch (IOException e) { System.out.println("Exception caught when trying to listen on port " + Main.port + " or listening for a connection"); System.out.println(e.getMessage()); } finally { socket.close(); } } private static void runSerene(String filePath) throws IOException { IScope rootScope = new RootScope(); FileInputStream input = new FileInputStream(filePath); ListNode nodes = Reader.read(input); for (Node n : nodes) { n.eval(rootScope); } } }