├── .gitignore ├── Runfile ├── src ├── lexer │ ├── LexerPatterns.java │ └── Lexer.java ├── parser │ ├── Patterns.java │ ├── ParseTree.java │ ├── TreeNode.java │ ├── UserFunction.java │ ├── Atom.java │ ├── Parser.java │ ├── Environment.java │ ├── Primitives.java │ └── SExpression.java ├── LispInterpreter.java └── helpers │ └── StringHelpers.java ├── LICENSE.txt ├── Makefile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | specs.pdf 2 | test.sh 3 | tests 4 | bin/* -------------------------------------------------------------------------------- /Runfile: -------------------------------------------------------------------------------- 1 | java -cp ./bin LispInterpreter < input_file > output_file 2 | -------------------------------------------------------------------------------- /src/lexer/LexerPatterns.java: -------------------------------------------------------------------------------- 1 | package lexer; 2 | 3 | /** 4 | * File: LexerPatters 5 | * 6 | * Seperates out some regular expressions which represent the meaningful and 7 | * acceptable strings in a Lisp program 8 | * 9 | * @author Joseph T. Anderson 10 | * @since 2012-11-01 11 | * @version 2012-11-01 12 | */ 13 | 14 | class LexerPatterns{ 15 | public static final String LETTER = "[a-zA-Z]"; 16 | public static final String LITERAL = "[a-zA-Z0-9]+"; 17 | public static final String WHIESPACE = "[\\s]+"; 18 | public static final String NUMERIC_ATOM = "[\\d\\+\\-]?[\\d]*"; 19 | public static final String SYMBOL = "[().]"; 20 | } -------------------------------------------------------------------------------- /src/parser/Patterns.java: -------------------------------------------------------------------------------- 1 | package parser; 2 | 3 | /** 4 | * File: Patterns.java 5 | * 6 | * This file holds some useful Regular Expressions 7 | * used in validating symbols and literals in Lisp 8 | * 9 | * @author Joseph T. Anderson 10 | * @since 2012-11-01 11 | * @version 2012-11-01 12 | * 13 | */ 14 | 15 | class Patterns{ 16 | protected static final String LETTER = "[a-zA-Z]"; 17 | protected static final String LITERAL = "[a-zA-Z0-9]+"; 18 | protected static final String VALID_FUNCTION_NAME = "[a-zA-Z][a-zA-Z0-9]*"; 19 | protected static final String WHIESPACE = "[\\s]+"; 20 | protected static final String NUMERIC_ATOM = "[\\d\\+\\-]?[\\d]*"; 21 | protected static final String SYMBOL = "[().]"; 22 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Joseph Anderson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/LispInterpreter.java: -------------------------------------------------------------------------------- 1 | import lexer.*; 2 | import parser.*; 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.math.BigInteger; 7 | 8 | /** 9 | * File: LispInterpreter.java 10 | * 11 | * This is the driver file for the Lisp Interpreter project 12 | * 13 | * It takes input from stdin and evaluates it line by line. 14 | * One should note that if muliple statements are on the same line, 15 | * their results will not be output until all have executed 16 | * successfully. 17 | * 18 | * @author Joseph T. Anderson 19 | * @since 2012-11-01 20 | * @version 2012-11-01 21 | */ 22 | 23 | class LispInterpreter { 24 | 25 | /** 26 | * Function: main 27 | * 28 | * The main function of the program. 29 | * 30 | * @author Joseph T. Anderson 31 | * @since 2012-11-01 32 | * @version 2012-11-01 33 | * 34 | * @param args Any command line arguments. None are used as of this version. 35 | */ 36 | 37 | public static void main(String[] args) { 38 | try{ 39 | Lexer l = new Lexer(System.in); 40 | Parser p = new Parser(l.getTokens()); 41 | // System.out.println(p.printParseTree()); 42 | p.evaluate(); 43 | } catch (IOException e){ 44 | System.out.println("End of input..."); 45 | } catch (Exception e){ 46 | System.out.println("Error!"); 47 | if ( args.length > 0 && args[0].matches("-d") ){ 48 | System.out.println(e.getMessage()); 49 | e.printStackTrace(); 50 | } 51 | System.exit(3); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/helpers/StringHelpers.java: -------------------------------------------------------------------------------- 1 | package helpers; 2 | 3 | import java.lang.String; 4 | import java.util.*; 5 | 6 | /** 7 | * File: StringHelpers.java 8 | * 9 | * This file is for some helpers to work with strings and vectors of strings. 10 | * 11 | * 12 | * @author Joseph T. Anderson 13 | * @since 2012-11-01 14 | * @version 2012-11-01 15 | */ 16 | 17 | public class StringHelpers{ 18 | 19 | /** 20 | * Function: join 21 | * 22 | * This function is used to implodde a generic list together 23 | * with the specified "glue" string 24 | * 25 | * @author Joseph T. Anderson 26 | * @since 2012-11-01 27 | * @version 2012-11-01 28 | * 29 | * @param s Any List whose elements support the toString method 30 | * @param glue A string with which to implode the list element strings 31 | * 32 | * @return The string of list elements stringified and joined 33 | */ 34 | public static String join(List s, String glue){ 35 | String newString = ""; 36 | for ( int i = 0; i < s.size() - 1; i++ ){ 37 | newString = newString + s.get(i).toString() + glue; 38 | } 39 | return newString + s.get(s.size() - 1).toString(); 40 | } 41 | 42 | /** 43 | * Function: vectorize 44 | * 45 | * @author Joseph T. Anderson 46 | * @since 2012-11-01 47 | * @version 2012-11-01 48 | * 49 | * @param s A string 50 | * @return A vector where each element is a signle character of the string 51 | */ 52 | public static Vector vectorize(String s){ 53 | Vector v = new Vector (); 54 | int i; 55 | for ( i = 0; i < s.length() - 1; i ++ ){ 56 | v.add(s.substring(i,i+1)); 57 | } 58 | if ( i > 0 ){ 59 | v.add(s.substring(i)); 60 | } 61 | return v; 62 | } 63 | } -------------------------------------------------------------------------------- /src/parser/ParseTree.java: -------------------------------------------------------------------------------- 1 | package parser; 2 | 3 | import java.util.*; 4 | import java.lang.*; 5 | import helpers.StringHelpers; 6 | 7 | /** 8 | * File: ParseTree.java 9 | * 10 | * This files contains the main tree structure of the Lisp program. 11 | * 12 | * @author Joseph T. Anderson 13 | * @since 2012-11-01 14 | * @version 2012-11-01 15 | * 16 | */ 17 | 18 | class ParseTree{ 19 | private TreeNode root; 20 | 21 | /** 22 | * Function: ParseTree 23 | * 24 | * Constructor(Vector s) 25 | * 26 | * Uses the given vector to create the root. It calls the 27 | * TreeNode factory constructor 28 | * 29 | * @author Joseph T. Anderson 30 | * @since 2012-11-01 31 | * @version 2012-11-01 32 | * 33 | * @param s A vector representing the outermost expression 34 | * 35 | * @throws Exception If node creation fails 36 | * 37 | */ 38 | public ParseTree(Vector s) throws Exception{ 39 | root = TreeNode.create(s); 40 | } 41 | 42 | /** 43 | * Function: evaluate 44 | * 45 | * Evaluates the root node and returns the result as a string 46 | * 47 | * @author Joseph T. Anderson 48 | * @since 2012-11-01 49 | * @version 2012-11-01 50 | * 51 | * @return The string of the executed statement 52 | * 53 | * @throws Exception If evaluation fails 54 | * 55 | */ 56 | protected String evaluate() throws Exception{ 57 | String rtn = root.evaluate().toString(); 58 | return rtn; 59 | } 60 | 61 | /** 62 | * Function: print 63 | * 64 | * Stringifies and returns the parsetree as-is 65 | * 66 | * @author Joseph T. Anderson 67 | * @since 2012-11-01 68 | * @version 2012-11-01 69 | * 70 | * @deprecated used only for early debuggin 71 | * 72 | * @return String of the root node 73 | * 74 | */ 75 | protected String print(){ 76 | String result = root.toString(); 77 | return result; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | 3 | # Makefile author: Joseph Anderson 4 | 5 | # This is where you specify the necessary source/package files 6 | 7 | # Program packages and files 8 | # - The packages should be the path inside your src directory. eg: package1 package2/package3 9 | # - All classes at the root of the src/ directory will be included. 10 | 11 | PACKAGES = helpers lexer parser 12 | 13 | ###################### DO NOT EDIT BELOW THIS LINE ####################### 14 | 15 | # Shell for any fanciness 16 | SHELL = /bin/bash 17 | 18 | # Make an easy-to-recognize empty string variable 19 | EMPTY = 20 | 21 | # Java compiler 22 | JAVAC = javac 23 | JVM_TMP = $(subst ., , $(word 3, $(shell java -version 2>&1 | grep "java version" | awk '{print $3}' | tr -d \"))) 24 | JVM = $(word 1, $(JVM_TMP)).$(word 2, $(JVM_TMP)) 25 | 26 | ifeq ($(JVM), $(EMPTY)) 27 | JVM = 1.6 28 | endif 29 | 30 | 31 | # Directory for compiled binaries 32 | BIN = ./bin/ 33 | 34 | # Directory of source files 35 | SRC = ./src/ 36 | 37 | # Java compiler flags 38 | JAVAFLAGS = -g -d $(BIN) -cp $(SRC) -target $(JVM) 39 | 40 | # Creating a .class file 41 | COMPILE = $(JAVAC) $(JAVAFLAGS) 42 | 43 | JAVA_FILES = $(subst $(SRC), $(EMPTY), $(wildcard $(SRC)*.java)) 44 | 45 | ifdef PACKAGES 46 | PACKAGEDIRS = $(addprefix $(SRC), $(PACKAGES)) 47 | PACKAGEFILES = $(subst $(SRC), $(EMPTY), $(foreach DIR, $(PACKAGEDIRS), $(wildcard $(DIR)/*.java))) 48 | ALL_FILES = $(PACKAGEFILES) $(JAVA_FILES) 49 | else 50 | ALL_FILES = $(JAVA_FILES) 51 | endif 52 | 53 | # One of these should be the "main" class listed in Runfile 54 | # CLASS_FILES = $(subst $(SRC), $(BIN), $(ALL_FILES:.java=.class)) 55 | CLASS_FILES = $(ALL_FILES:.java=.class) 56 | 57 | # The first target is the one that is executed when you invoke 58 | # "make". 59 | 60 | #all : $(subst $(SRC), $(BIN), $(CLASS_FILES)) 61 | all : $(addprefix $(BIN), $(CLASS_FILES)) 62 | 63 | # The line describing the action starts with 64 | $(BIN)%.class : $(SRC)%.java 65 | $(COMPILE) $< 66 | 67 | clean : 68 | rm -rf $(BIN)* -------------------------------------------------------------------------------- /src/parser/TreeNode.java: -------------------------------------------------------------------------------- 1 | package parser; 2 | 3 | import java.util.*; 4 | import helpers.*; 5 | 6 | /** 7 | * File: TreeNode.java 8 | * 9 | * This is the central data structure for representing Atoms 10 | * and S-Expressions. It maintains the string vector of 11 | * tokens which make up the object and also employs the factory 12 | * pattern to create new TreeNodes based on whether or not they 13 | * are valid S-Expressions. 14 | * 15 | * This class also stipulates the evaluate funcntions and the 16 | * isList method. 17 | * 18 | * @author Joseph T. Anderson 19 | * @since 2012-11-01 20 | * @version 2012-11-01 21 | * 22 | */ 23 | 24 | abstract public class TreeNode{ 25 | protected abstract boolean isList(); 26 | protected Vector tokens = new Vector (); 27 | 28 | /** 29 | * Function: create 30 | * 31 | * Uses the factory pattern to create an atom if the passed string 32 | * vector is appropriate or an S-Expression. 33 | * 34 | * @author Joseph T. Anderson 35 | * @since 2012-11-01 36 | * @version 2012-11-01 37 | * 38 | * @param s The string vector of the new TreeNode 39 | * 40 | * @return The new TreeNode object 41 | * 42 | * @throws Exception If the node is empty or if the constructor fails 43 | * 44 | */ 45 | static TreeNode create(Vector s) throws Exception{ 46 | if ( s.size() > 0 && s.get(0).matches("[(]") ){ 47 | return new SExpression(s); 48 | } else if ( s.size() > 0 ){ 49 | return new Atom(s.get(0)); 50 | } else { 51 | throw new Exception("Tried to create a TreeNode with no data"); 52 | } 53 | } 54 | 55 | /** 56 | * Function: create 57 | * 58 | * Adapts the factory pattern to force the creation of an Atom 59 | * if the data is a boolean value. 60 | * 61 | * @author Joseph T. Anderson 62 | * @since 2012-11-01 63 | * @version 2012-11-01 64 | * 65 | * @param b The boolean value of the Atom-to-be 66 | * 67 | * @return The new atom object 68 | * 69 | */ 70 | static TreeNode create(boolean b){ 71 | return new Atom(b); 72 | } 73 | 74 | /** 75 | * Function: create 76 | * 77 | * Adapts the factory pattern to create an Atom if the data 78 | * is an integer. 79 | * 80 | * @author Joseph T. Anderson 81 | * @since 2012-11-01 82 | * @version 2012-11-01 83 | * 84 | * @param i The integer to use as a literal 85 | * 86 | * @return The new Atom 87 | * 88 | */ 89 | static TreeNode create(int i){ 90 | return new Atom(i); 91 | } 92 | 93 | /** 94 | * Function: evaluate 95 | * 96 | * The evaluation of TreeNodes returns a new TreeNode. 97 | * 98 | * @author Joseph T. Anderson 99 | * @since 2012-11-01 100 | * @version 2012-11-01 101 | * 102 | * @return The result of evaluating the TreeNode 103 | * 104 | */ 105 | abstract TreeNode evaluate() throws Exception; 106 | 107 | /** 108 | * Function: evaluate 109 | * 110 | * The evaluation of TreeNodes returns a new TreeNode. 111 | * 112 | * @author Joseph T. Anderson 113 | * @since 2012-11-01 114 | * @version 2012-11-01 115 | * 116 | * @param flag Whether or not to take numericals literally 117 | * 118 | * @return The result of evaluating the TreeNode 119 | * 120 | */ 121 | abstract TreeNode evaluate(boolean flag) throws Exception; 122 | 123 | /** 124 | * Function: evaluate 125 | * 126 | * The evaluation of TreeNodes returns a new TreeNode. 127 | * 128 | * @author Joseph T. Anderson 129 | * @since 2012-11-01 130 | * @version 2012-11-01 131 | * 132 | * @param flag Whether or not to take numericals literally 133 | * @param env The scoped variables 134 | * 135 | * @return The result of evaluating the TreeNode 136 | * 137 | */ 138 | abstract TreeNode evaluate(boolean flag, Hashtable env) throws Exception; 139 | 140 | /** 141 | * Function: evaluate 142 | * 143 | * The evaluation of TreeNodes returns a new TreeNode. 144 | * 145 | * @author Joseph T. Anderson 146 | * @since 2012-11-01 147 | * @version 2012-11-01 148 | * 149 | * @param env The scoped variables 150 | * 151 | * @return The result of evaluating the TreeNode 152 | * 153 | */ 154 | abstract TreeNode evaluate(Hashtable env) throws Exception; 155 | } 156 | -------------------------------------------------------------------------------- /src/lexer/Lexer.java: -------------------------------------------------------------------------------- 1 | package lexer; 2 | 3 | import java.io.*; 4 | import java.util.*; 5 | 6 | /** 7 | * File: Lexer 8 | * 9 | * This is the main class for the Lisp Lexical Analalyzer. It's job is to 10 | * break apart the meaningful symbols in a Lisp program and put them 11 | * into a vector for parsing. 12 | * 13 | * @author Joseph T. Anderson 14 | * @since 2012-10-01 15 | * @version 2012-11-01 16 | */ 17 | 18 | public class Lexer{ 19 | 20 | private String programString = ""; 21 | private Vector tokenArray = new Vector (); 22 | 23 | /** 24 | * Function: Lexer 25 | * 26 | * Constructor which can take a stream as input. It reads the stream and 27 | * tokenizes it appropriately 28 | * 29 | * @author Joseph T. Anderson 30 | * @since 2012-11-01 31 | * @version 2012-11-01 32 | * 33 | * @param stream An input stream 34 | */ 35 | 36 | public Lexer(InputStream stream) throws IOException{ 37 | byte[] bytes = new byte[1024]; 38 | while (stream.available() > 0){ 39 | stream.read(bytes, 0, 1024); 40 | programString = programString.concat(new String(bytes)).trim().toUpperCase(); 41 | } 42 | tokenArray = tokenize(programString); 43 | } 44 | 45 | /** 46 | * Function: Lexer 47 | * 48 | * @author Joseph T. Anderson 49 | * @since 2012-11-01 50 | * @version 2012-11-01 51 | * 52 | * @param s A string of the program to be analyzed 53 | */ 54 | 55 | public Lexer(String s){ 56 | programString = s; 57 | tokenArray = tokenize(programString); 58 | } 59 | 60 | /** 61 | * Function: getTokens 62 | * 63 | * Returns out the vector of tokens gleaned from the input program 64 | * 65 | * @author Joseph T. Anderson 66 | * @since 2012-11-01 67 | * @version 2012-11-01 68 | * 69 | * @return The vector of string tokens 70 | */ 71 | 72 | public Vector getTokens(){ 73 | return tokenArray; 74 | } 75 | 76 | /** 77 | * Function: tokenize 78 | * 79 | * Splits a string up into chunks meaninful according to Lisp semantics 80 | * 81 | * @author Joseph T. Anderson 82 | * @since 2012-11-01 83 | * @version 2012-11-01 84 | * 85 | * @param s A string to be separated according to the Lisp semantics 86 | */ 87 | 88 | private Vector tokenize(String s){ 89 | int i = 0; 90 | Vector tokens = new Vector (); 91 | if ( s.length() == 1 ){ 92 | tokens.add(s); 93 | return tokens; 94 | } 95 | while ( i < s.length() ){ 96 | int j = i + 1; 97 | if ( s.substring(i, j).matches(LexerPatterns.LETTER) || s.substring(i, j).matches(LexerPatterns.NUMERIC_ATOM) ){ 98 | while ( s.substring(i,j + 1).matches(LexerPatterns.LITERAL) || s.substring(i, j + 1).matches(LexerPatterns.NUMERIC_ATOM) ){ 99 | j++; 100 | } 101 | tokens.add(s.substring(i,j)); 102 | } else if ( s.substring(i, j).matches(LexerPatterns.SYMBOL) ){ 103 | tokens.add(s.substring(i,j)); 104 | } 105 | i = j; 106 | } 107 | return tokens; 108 | } 109 | 110 | /** 111 | * Function: endOfExpression 112 | * 113 | * This function finds the index in a given string vector which denotes the closing 114 | * parenthesis of a lisp expression 115 | * 116 | * @author Joseph T. Anderson 117 | * @since 2012-11-01 118 | * @version 2012-11-01 119 | * 120 | * @deprecated Not necessary as of this version. Moved to the parser to be more effective overall 121 | * 122 | * @param s A string vector which will be analyzed. The first character must be the left parenthesis 123 | * 124 | * @throws IllegalArgumentException If the passed vector is not a parenthetical statement 125 | * @throws ArrayIndexOutOfBounds If the expression is malformed and the statement has no closing parenthesis 126 | */ 127 | 128 | private int endOfExpression(Vector s) throws IllegalArgumentException, ArrayIndexOutOfBoundsException{ 129 | if ( ! s.get(0).matches("[(]") ){ 130 | throw new IllegalArgumentException("ERROR."); 131 | } 132 | int openPairs = 1; 133 | int end = 1; 134 | while ( openPairs > 0 ){ 135 | if ( end >= s.size() ){ 136 | throw new ArrayIndexOutOfBoundsException("Error: Unbalanced parentheses"); 137 | } 138 | if ( s.get(end).matches("[)]") ){ 139 | openPairs--; 140 | } else if ( s.get(end).matches("[(]") ){ 141 | openPairs++; 142 | } 143 | if ( openPairs == 0 ){ 144 | break; 145 | } else { 146 | end++; 147 | } 148 | } 149 | return end; 150 | } 151 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lisp Interpreter 2 | ## Joe Anderson 3 | 4 | Running and compiling the interpreter 5 | ====================================== 6 | 7 | * The project comes with a custom Makefile to compiile the project to the version of Java present on the current system. The makefile can be run from the directory of the project with the command: `make` 8 | 9 | * The binary files can be quickly removed to re-build the project with `make clean` 10 | 11 | * The project uses several custom packages, so when invoking the project with `java` the classpath must be set to the top-most directory of the binary files 12 | 13 | * A generic run command can look like this: `java -cp ./bin LispInterpreter < file1 > file2` 14 | 15 | * This will run the lisp interpreter taking the data from file1 as input and direct all output to file2 16 | 17 | * This example can be found in Runfile 18 | 19 | 20 | Design information 21 | ================== 22 | 23 | The Lisp interpreter has two main packages: *Lexer* and *Parser*. These fill the standard interpreter roles of lexical analysis and parsing respectively. 24 | 25 | Lexer 26 | ----- 27 | 28 | This contains the functionality to split the input into meaningful "chunks" according to the Lisp semantics. For instance, literal words (PLUS, MINUS, T, NIL, etc.) are taken as single tokens while parentheses and dots are individual tokens. For numeric literals, the sign is kept with the literal (+1, -5, etc.) in the token list. This list of tokens is then made available via the `getTokens()` method. 29 | 30 | Parser 31 | ------ 32 | 33 | The parser is what does most of the "work" of the interpreter. It has several parts: 34 | 35 | * Converting the input program to dot-notation 36 | * Creating the parse tree from the program tokens 37 | * Call the evaluation on the parse tree in a top-down fashion 38 | * Handles the S-Expression data structure 39 | * Maintains an environment hash for defined functions and variables during function calls 40 | * Handles any errors raised by S-Expression evaluation or other parsing operations 41 | 42 | ### ParseTree 43 | 44 | The parse tree is a standard tree model where each node is a `TreeNode`. This abstract class represents either an S-Expression or an Atom and defines any common behavior. It also employs the factory design pattern so that one may call `TreeNode.create(fromData)` and it will return either an `Atom` or `SExpression` based on the content of the passed data. 45 | 46 | ### Atoms 47 | 48 | These represent the standard literals of the language: variables, function names, numerics, etc. When "evaluated" by the program, they either return themselves, or in the case of a variable, its value in the current environment. They have only one attribute - a string of the literal which is represented. 49 | 50 | ### S-Expressions 51 | 52 | S-Expressions are represented in the interpreter by the `SExpression` class. This class has two fields to reflect the structure of a Lisp S-Expression: address and data. These fields are `TreeNode` objects and thus enabling the S-Expressions to be recursive in nature. 53 | 54 | Evaluation also happens recursively: as per the operational semantics, the CDR is passed to any primitive functions and they operate from there. So if, in the operational semantics, CADR is used, they just take the CAR of their input. The `evaluate` method of the `SExpression` is set up to use one main version of the function but allowing essentially any combination of the parameters (including none) and passes defaults when none are given. This is inconsequential to the actual running of the interpreter, but provides compatibility for evaluation that takes no regard for either literal interpretation, environment variables, or both. 55 | 56 | ### User-Defined Functions 57 | 58 | When a call is made to DEFUN, the appropriate parts of the S-Expression are broken apart and used to define a new `UserFunction` object and bind it to the current environment. This is accomplished via an `Environment` class which has a static hash table for functions and a static hashtable for variables. The variable hash table is used to keep track of bindings made when calling a user-defined function. The function body is evaluated and passed a hash of the bindings to use. The S-Expression `evaluate` function merges the new bindings into the current variable bindings and when finished, restores the previous state of the environment. This is to prevent "upward" binding of variables where a variable bound later in a program can be used by a function defined earlier. 59 | 60 | ### Other notable components 61 | 62 | #### Patterns 63 | 64 | The parser uses a class of static regular expressions common to identifying legal function names, variable names, etc. They are kept in the `Patterns` class and are static and public to the entire `Parser` package. 65 | 66 | #### Program Evaluation 67 | 68 | The parser takes the input of tokens and processes it statement-by-statement. If one of them errors, program termination is halted an no further statements are executed. This is mainly to avoid errors if a later statement requires something defined by one that failed. 69 | 70 | #### Debug Mode 71 | 72 | In the event of errors, if one wants to see a stack trace of the error, the program can be run with the `-d` flag set: `java -cp ./bin LispInterpreter -d < infile` and the stack trace of any error will be sent to `stdout` 73 | -------------------------------------------------------------------------------- /src/parser/UserFunction.java: -------------------------------------------------------------------------------- 1 | package parser; 2 | import java.util.*; 3 | 4 | /** 5 | * File: UserFunction.java 6 | * 7 | * This is the data structure which represents user-defined 8 | * Lisp functions. 9 | * 10 | * @author Joseph T. Anderson 11 | * @since 2012-11-02 12 | * @version 2012-11-02 13 | * 14 | */ 15 | 16 | class UserFunction{ 17 | 18 | protected String name; 19 | protected Vector formals; 20 | protected TreeNode body; 21 | 22 | /** 23 | * Function: UserFunction 24 | * 25 | * Constructor: UserFunction(String n, TreeNode f, TreeNode b) 26 | * 27 | * This function creates a user-defined function with the specified 28 | * name, list of formal parameters, and body. 29 | * 30 | * @todo: allow the body to be a non-list 31 | * 32 | * @author Joseph T. Anderson 33 | * @since 2012-11-02 34 | * @version 2012-11-02 35 | * 36 | * @param n The name of the function 37 | * @param f The list of formals - can be () 38 | * @param b The body of the function 39 | * 40 | * @throws Exception If the parameters are not in the correct format 41 | * 42 | */ 43 | public UserFunction(String n, TreeNode f, TreeNode b) throws Exception{ 44 | name = n; 45 | if ( (!f.isList() && !f.toString().matches("NIL") ) || ( !b.isList() && !b.toString().matches("NIL") ) ){ 46 | throw new Exception("Invalid function parameters or body.\n" + f.toString() + "\n" + b.toString()); 47 | } 48 | 49 | String formalString = f.toString(); 50 | formals = splitParamList(formalString); 51 | body = b; 52 | } 53 | 54 | /** 55 | * Function: evaluate 56 | * 57 | * This carries out the basic evaluation of a custom function by 58 | * invoking the body with the passed actual parameters. 59 | * 60 | * @author Joseph T. Anderson 61 | * @since 2012-11-02 62 | * @version 2012-11-02 63 | * 64 | * @param actuals The list (possible NIL) of actual parameters 65 | * 66 | * @return The result of evaluating the body 67 | * 68 | * @throws Exception If evaluation fails 69 | * 70 | */ 71 | protected TreeNode evaluate(TreeNode actuals) throws Exception{ 72 | Hashtable bindings = bind(actuals); 73 | // Environment.vars.putAll(bindings); 74 | // Iterator it = bindings.entrySet().iterator(); 75 | // int i = 0; 76 | // TreeNode tmp = TreeNode.create(body.tokens); 77 | // while ( it.hasNext() ){ 78 | // Map.Entry pair = (Map.Entry) it.next(); 79 | // tmp.replace(formals.get(i), pair.getValue().toString()); 80 | // it.remove(); 81 | // i++; 82 | // } 83 | return body.evaluate(true, bindings); 84 | } 85 | 86 | /** 87 | * Function: splitParams 88 | * 89 | * This is a private helper function to create a vector of parameters 90 | * from a string. It also checks for distinct parameters and legal 91 | * parameter names. 92 | * 93 | * @author Joseph T. Anderson 94 | * @since 2012-11-02 95 | * @version 2012-11-02 96 | * 97 | * @param s The string of parameters 98 | * 99 | * @return A string vector of parameter names 100 | * 101 | * @throws Exception If the parameters are malformed or inappropriate 102 | * 103 | */ 104 | private static Vector splitParamList(String s) throws Exception{ 105 | String[] chunks = s.substring(1, s.length()-1).split("\\s"); 106 | Vector rtn = new Vector (); 107 | for ( int i = 0; i < chunks.length; i++ ){ 108 | if ( chunks[i].matches(Patterns.VALID_FUNCTION_NAME) ){ 109 | if ( ! rtn.contains(chunks[i]) ){ 110 | rtn.add(chunks[i]); 111 | } else { 112 | throw new Exception("Error! Formal parameter names must be distinct."); 113 | } 114 | } else { 115 | throw new Exception("Error! Invalid parameter name: " + chunks[i]); 116 | } 117 | } 118 | return rtn; 119 | } 120 | 121 | /** 122 | * Function: bind 123 | * 124 | * Creates a hashtable representing the bound values of 125 | * formal parameters to actual parameters. 126 | * 127 | * Also provides some checking to make sure the number of 128 | * actuals matches the number of formals. 129 | * 130 | * @author Joseph T. Anderson 131 | * @since 2012-11-02 132 | * @version 2012-11-02 133 | * 134 | * @param s The TreeNode object representing the actual paramters 135 | * 136 | * @return A Hashtable of bindings 137 | * 138 | * @throws Exception If the wrong number of parameters are used or they 139 | * are malformed 140 | * 141 | */ 142 | private Hashtable bind (TreeNode s) throws Exception{ 143 | if ( ! s.isList() && !s.toString().matches("NIL") ){ 144 | throw new Exception("Error! Invalid parameters to function: " + name); 145 | } 146 | 147 | Hashtable env = new Hashtable (); 148 | 149 | if ( ! s.isList() ){ 150 | return env; 151 | } 152 | 153 | SExpression tmp = new SExpression(s); 154 | int i; 155 | for ( i = 0; i < formals.size(); i++ ){ 156 | env.put(formals.get(i), tmp.address.evaluate()); 157 | try{ 158 | tmp = new SExpression(tmp.dataTokens); 159 | } catch (Exception e){ 160 | break; 161 | } 162 | } 163 | 164 | if ( i < formals.size() - 1 ){ 165 | throw new Exception("Error! Too few arguments for: " + name); 166 | } else if ( ! tmp.data.evaluate().toString().matches("NIL") ){ 167 | throw new Exception("Error! Too many arguments for: " + name); 168 | } 169 | 170 | return env; 171 | } 172 | } -------------------------------------------------------------------------------- /src/parser/Atom.java: -------------------------------------------------------------------------------- 1 | package parser; 2 | 3 | import java.lang.*; 4 | import java.util.Vector; 5 | import java.util.Hashtable; 6 | 7 | /** 8 | * File: Atom.java 9 | * 10 | * This file is the Atom class. 11 | * 12 | * The atom class is used for elements of a Lisp program which are alphanumeric literals 13 | * (appropriately formed with only leading alphabeticals) or strictly numerics. The 14 | * constructor uses the Patterns class to access the common regular expressions 15 | * to identify legal atoms. 16 | * 17 | * @author Joseph T. Anderson 18 | * @since 2012-11-01 19 | * @version 2012-11-01 20 | */ 21 | 22 | class Atom extends TreeNode { 23 | private String literalString; 24 | 25 | protected boolean isList(){ return false; } 26 | 27 | /** 28 | * Function: Atom 29 | * 30 | * Constructor(String s) 31 | * 32 | * This forms an atom (if it of valid form) from a string as a literal 33 | * 34 | * @author Joseph T. Anderson 35 | * @since 2012-11-01 36 | * @version 2012-11-01 37 | * 38 | * @param s A literal string to be used for the atom 39 | * 40 | * @throws Exception If the string is not that of an acceptable atom 41 | */ 42 | 43 | public Atom(String s) throws Exception{ 44 | if ( ! s.matches(Patterns.LITERAL) && ! s.matches(Patterns.NUMERIC_ATOM) ){ 45 | throw new Exception("Error!"); 46 | } 47 | literalString = s; 48 | tokens = new Vector (); 49 | tokens.add(literalString); 50 | } 51 | 52 | /** 53 | * Function: Atom 54 | * 55 | * Constructor(boolean b) 56 | * 57 | * This constructs an atom to be T or NIL from a boolean argument. If true is 58 | * given, then the atom will be T and NIL for false. 59 | * 60 | * @author Joseph T. Anderson 61 | * @since 2012-11-01 62 | * @version 2012-11-01 63 | * 64 | * @param b A boolean representing T or NIL 65 | */ 66 | 67 | public Atom(boolean b){ 68 | literalString = b ? "T" : "NIL"; 69 | tokens = new Vector (); 70 | tokens.add(literalString); 71 | } 72 | 73 | /** 74 | * Function Atom 75 | * 76 | * Constructor(int i) 77 | * 78 | * This constructs an Atom from the Integer primitive. Simply converts 79 | * the integer to a string to be used for the literal representation. 80 | * 81 | * @author Joseph T. Anderson 82 | * @since 2012-11-01 83 | * @version 2012-11-01 84 | * 85 | * @param i An integer 86 | */ 87 | 88 | public Atom(int i){ 89 | literalString = Integer.toString(i); 90 | tokens = new Vector (); 91 | tokens.add(literalString); 92 | } 93 | 94 | /** 95 | * Function toString 96 | * 97 | * This simply returns the atom literal. It accounts for redundantly positive 98 | * numbers - that is - numbers which are preceded by '+' and can simply be 99 | * expressed without it. That prevents Integer.parseInt() from throwing format 100 | * errors. 101 | * 102 | * @author Joseph T. Anderson 103 | * @since 2012-11-01 104 | * @version 2012-11-01 105 | * 106 | * @return The string version of the literal atom 107 | */ 108 | 109 | public String toString(){ 110 | if ( literalString.matches(Patterns.NUMERIC_ATOM) ){ 111 | return literalString.replaceAll("\\A\\+", ""); 112 | } else { 113 | return literalString; 114 | } 115 | } 116 | 117 | /** 118 | * Function evaluate 119 | * 120 | * Returns the atom itself, and if it is representing a variable, 121 | * then return the value of that variable. 122 | * 123 | * @author Joseph T. Anderson 124 | * @since 2012-11-01 125 | * @version 2012-11-01 126 | * 127 | * @return The atom or the variable value of it if applicable 128 | * 129 | * @throws Exception Require by parent class 130 | */ 131 | 132 | protected TreeNode evaluate() throws Exception{ 133 | if ( Environment.varIsDefined(literalString) ){ 134 | return Environment.getVarValue(literalString); 135 | } else { 136 | return this; 137 | } 138 | } 139 | 140 | /** 141 | * Function: evaluate 142 | * 143 | * This calls the base evaluate function but supports calls that come with extra parameters. 144 | * 145 | * @author Joseph T. Anderson 146 | * @since 2012-11-01 147 | * @version 2012-11-01 148 | * 149 | * @param env A Hashtable of bound variables. Not used. 150 | * 151 | */ 152 | 153 | protected TreeNode evaluate(Hashtable env) throws Exception{ 154 | return evaluate(); 155 | } 156 | 157 | /** 158 | * Function: evaluate 159 | * 160 | * This calls the base evaluate function but supports calls that come with extra parameters. 161 | * 162 | * @author Joseph T. Anderson 163 | * @since 2012-11-01 164 | * @version 2012-11-01 165 | * 166 | * @param env A Hashtable of bound variables. Not used. 167 | * 168 | */ 169 | 170 | protected TreeNode evaluate(boolean flag, Hashtable env) throws Exception{ 171 | return evaluate(); 172 | } 173 | 174 | /** 175 | * Function: evaluate 176 | * 177 | * This calls the base evaluate function but supports calls that come with extra parameters. 178 | * 179 | * @author Joseph T. Anderson 180 | * @since 2012-11-01 181 | * @version 2012-11-01 182 | * 183 | * @param env A Hashtable of bound variables. Not used. 184 | * 185 | */ 186 | 187 | protected TreeNode evaluate(boolean flag) throws Exception{ 188 | return evaluate(); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/parser/Parser.java: -------------------------------------------------------------------------------- 1 | package parser; 2 | 3 | import java.util.*; 4 | import java.lang.*; 5 | import helpers.StringHelpers; 6 | 7 | /** 8 | * File: Parser.java 9 | * 10 | * This is the main Parser class for the program. It handles the 11 | * tokens from the lexical analysis, converts them to dot-notation, 12 | * and provides public access to program evaluation. 13 | * 14 | * @author Joseph T. Anderson 15 | * @since 2012-11-01 16 | * @version 2012-11-01 17 | * 18 | * details 19 | * 20 | */ 21 | 22 | public class Parser { 23 | 24 | Vector statements = new Vector (); 25 | 26 | /** 27 | * Function: Parser 28 | * 29 | * Constructor(Vector tokens) 30 | * 31 | * This function initializes the parser fro the lexical tokens, 32 | * splitting them into statements, and storing them. 33 | * 34 | * @author Joseph T. Anderson 35 | * @since 2012-11-01 36 | * @version 2012-11-01 37 | * 38 | * @param tokens The vector of lexical tokens from analysis 39 | * 40 | * @throws Exception If conversion to dot-notation fails 41 | * 42 | */ 43 | public Parser(Vector tokens) throws Exception{ 44 | Vector t; 45 | int i = 0; 46 | int k, l; 47 | 48 | while ( i < tokens.size() && i >= 0 ){ 49 | // The variable l must always hold the last location of the current statement!!! 50 | k = tokens.indexOf("(", i); 51 | if ( k == i ){ 52 | // There is a new statement to add - starting at i 53 | l = endOfExpression(new Vector (tokens.subList(i, tokens.size()))) + i; 54 | } else if ( k > i ){ 55 | // The next statement is after some non-parenthetical stuff 56 | l = k - 1; 57 | } else { 58 | // There are no more parenthetical statements. Parse the rest of the program 59 | l = tokens.size() - 1; 60 | } 61 | 62 | t = convertToDotNotation( new Vector (tokens.subList(i, l+1))); 63 | statements.add(new ParseTree(t)); 64 | 65 | i = l + 1; 66 | } 67 | } 68 | 69 | /** 70 | * Function: evaluate 71 | * 72 | * This evaluates the statements one-by-one and prints 73 | * the results. 74 | * 75 | * @author Joseph T. Anderson 76 | * @since 2012-11-01 77 | * @version 2012-11-01 78 | * 79 | * @throws Exception If any evaluation fails on individual statements 80 | * 81 | */ 82 | public void evaluate() throws Exception{ 83 | // String rtn = ""; 84 | for ( int i = 0; i < statements.size(); i++ ){ 85 | System.out.println(statements.get(i).evaluate()); 86 | // rtn = rtn + statements.get(i).evaluate(); 87 | // if ( i < statements.size() - 1){ 88 | // rtn = rtn + "\n"; 89 | // } 90 | } 91 | // return rtn; 92 | } 93 | 94 | /** 95 | * Function: convertToDotNotation 96 | * 97 | * This uses a naïve grammar to split the given program and rejoin 98 | * it into legal Lisp S-Expressions. 99 | * 100 | * @author Joseph T. Anderson 101 | * @since 2012-11-01 102 | * @version 2012-11-01 103 | * 104 | * @param s The vector of tokens representing the program 105 | * 106 | * @return The program converted to dot notaion in a string vector 107 | * 108 | */ 109 | private Vector convertToDotNotation(Vector s){ 110 | Vector r = new Vector (); 111 | Vector temp; 112 | int nextInnerToken; 113 | if ( s.get(0).matches("[(]") ){ 114 | // We have a list or S-Expression 115 | 116 | int closeParen = endOfExpression(s); 117 | if ( closeParen > 1 ){ 118 | // The expression is not () 119 | 120 | r.add("("); 121 | if ( closeParen > 2 ){ 122 | // There is more than one token - so not ( a ) 123 | 124 | // Is the first one a nested expression? 125 | if ( s.get(1).matches("[(]") ){ 126 | nextInnerToken = endOfExpression(new Vector (s.subList(1, closeParen))) + 2; 127 | } else { 128 | nextInnerToken = 2; 129 | } 130 | 131 | if ( ! s.get(nextInnerToken).matches("[.]") ){ 132 | // The expression must be a list because it is not in dot-notation 133 | r.addAll(convertToDotNotation(new Vector (s.subList(1,nextInnerToken)))); 134 | r.add("."); 135 | temp = new Vector (); 136 | temp.add("("); 137 | temp.addAll(s.subList(nextInnerToken, closeParen)); 138 | temp.add(")"); 139 | r.addAll(convertToDotNotation(temp)); 140 | } else { 141 | // Since it is in the form of ( [stuff] . [stuff] ), we pass [stuff] to be converted 142 | r.addAll(convertToDotNotation(new Vector (s.subList(1,nextInnerToken)))); 143 | r.add("."); 144 | r.addAll(convertToDotNotation(new Vector (s.subList(nextInnerToken+1, closeParen)))); 145 | } 146 | } else { 147 | // The statement is in the form ( a ) 148 | r.add(s.get(1)); 149 | r.add("."); 150 | r.add("NIL"); 151 | } 152 | r.add(")"); 153 | } else { 154 | // We have () 155 | r.add("NIL"); 156 | } 157 | } else { 158 | if ( s.indexOf("(") > 0 ){ 159 | r.addAll(s.subList(0, s.indexOf("("))); 160 | r.addAll(convertToDotNotation(new Vector (s.subList(s.indexOf("("), s.size())))); 161 | } else { 162 | r = s; 163 | } 164 | } 165 | return r; 166 | } 167 | 168 | /** 169 | * Function: endOfExpression 170 | * 171 | * This finds the closing parenthesis of a given Lisp segment 172 | * 173 | * @author Joseph T. Anderson 174 | * @since 2012-11-01 175 | * @version 2012-11-01 176 | * 177 | * @param s The tokens (in dot-notation) of a segment of Lisp code 178 | * 179 | * @return the index of the closing parenthesis 180 | * 181 | * @throws IllegalArgumentException If the vector is not a parenthetical expression 182 | * @throws ArrayIndexOutOfBoundsException If the statement does not have a closing parenthesis 183 | * 184 | */ 185 | private int endOfExpression(Vector s) throws IllegalArgumentException, ArrayIndexOutOfBoundsException{ 186 | if ( ! s.get(0).matches("[(]") ){ 187 | throw new IllegalArgumentException("ERROR: Tried to find the end of an expression that did not begin with '('."); 188 | } 189 | int openPairs = 1; 190 | int end = 1; 191 | while ( openPairs > 0 ){ 192 | if ( end >= s.size() ){ 193 | throw new ArrayIndexOutOfBoundsException("Error! Unbalanced parentheses."); 194 | } 195 | if ( s.get(end).matches("[)]") ){ 196 | openPairs--; 197 | } else if ( s.get(end).matches("[(]") ){ 198 | openPairs++; 199 | } 200 | if ( openPairs == 0 ){ 201 | break; 202 | } else { 203 | end++; 204 | } 205 | } 206 | return end; 207 | } 208 | } -------------------------------------------------------------------------------- /src/parser/Environment.java: -------------------------------------------------------------------------------- 1 | package parser; 2 | import java.util.*; 3 | 4 | /** 5 | * File: Environment.java 6 | * 7 | * This houses the so-called "d-list" of the program. It mangages 8 | * the binding of functions and variables within the context of the 9 | * running Lisp program. 10 | * 11 | * @author Joseph T. Anderson 12 | * @since 2012-11-01 13 | * @version 2012-11-01 14 | * 15 | */ 16 | class Environment{ 17 | public static java.util.Hashtable funcs = new Hashtable (); 18 | public static java.util.Hashtable vars = new Hashtable (); 19 | 20 | /** 21 | * Function: executeFunction 22 | * 23 | * Executes a requested function with the actual parameters 24 | * 25 | * @author Joseph T. Anderson 26 | * @since 2012-11-01 27 | * @version 2012-11-01 28 | * 29 | * @param name The name of the function 30 | * @param params An SExpression or Atom to be used as actual parameter 31 | * 32 | * @return The TreeNode (S-Expression or Atom) which is the result of evaluation 33 | * 34 | * @throws Exception If the requested function is undefined 35 | * 36 | */ 37 | public static TreeNode executeFunction(String name, TreeNode params) throws Exception{ 38 | if ( !funcs.containsKey(name) ){ 39 | throw new Exception("Error! Undefined function: " + name); 40 | } 41 | 42 | UserFunction f = funcs.get(name); 43 | return f.evaluate(params); 44 | } 45 | 46 | /** 47 | * Function: registerFunction 48 | * 49 | * This defines a function in the "d-list" 50 | * 51 | * @author Joseph T. Anderson 52 | * @since 2012-11-01 53 | * @version 2012-11-01 54 | * 55 | * @param name The string name of the function 56 | * @param params The formal parameter list of the function 57 | * @param body The literal or SExpression representing the body of the function 58 | * 59 | * @throws Exception If the function definition is illegal 60 | * 61 | */ 62 | public static void registerFunction(String name, TreeNode params, TreeNode body) throws Exception{ 63 | UserFunction f = new UserFunction(name, params, body); 64 | funcs.put(name, f); 65 | } 66 | 67 | /** 68 | * Function: functionIsDefined 69 | * 70 | * Detects if a function is in the current scope 71 | * 72 | * @author Joseph T. Anderson 73 | * @since 2012-11-01 74 | * @version 2012-11-01 75 | * 76 | * @param name The string name of the function 77 | * 78 | * @return True if the function is defined. False if not. 79 | * 80 | */ 81 | public static boolean functionIsDefined(String name){ 82 | return funcs.containsKey(name); 83 | } 84 | 85 | /** 86 | * Function: print 87 | * 88 | * Sends the variable hashtable stringified to stdout 89 | * 90 | * @author Joseph T. Anderson 91 | * @since 2012-11-01 92 | * @version 2012-11-01 93 | * 94 | * 95 | */ 96 | public static void print(){ 97 | System.out.println(vars.toString()); 98 | } 99 | 100 | /** 101 | * Function: stringify 102 | * 103 | * Returns the string version of the variable hashtable. 104 | * 105 | * @author Joseph T. Anderson 106 | * @since 2012-11-01 107 | * @version 2012-11-01 108 | * 109 | * @return The stringified hashtable of bound variables 110 | * 111 | * @deprecated Only for early debugging 112 | * 113 | */ 114 | public static String stringify(){ 115 | return vars.toString(); 116 | } 117 | 118 | /** 119 | * Function: varIsDefined 120 | * 121 | * Detects if a variable is defined in the current scope 122 | * 123 | * @author Joseph T. Anderson 124 | * @since 2012-11-01 125 | * @version 2012-11-01 126 | * 127 | * @param name The string name of the requested variable 128 | * 129 | * @return True if the variable is bound. False if not. 130 | * 131 | */ 132 | public static boolean varIsDefined(String name){ 133 | return vars.containsKey(name); 134 | } 135 | 136 | /** 137 | * Function: unbindAll 138 | * 139 | * Used to remove variables from the environment 140 | * 141 | * @author Joseph T. Anderson 142 | * @since 2012-11-01 143 | * @version 2012-11-01 144 | * 145 | * details 146 | * 147 | */ 148 | public static void unbindAll(Hashtable tbl){ 149 | Iterator it = tbl.entrySet().iterator(); 150 | while (it.hasNext()) { 151 | Map.Entry pairs = (Map.Entry)it.next(); 152 | if ( vars.get(pairs.getKey()) == pairs.getValue() ){ 153 | vars.remove(pairs.getKey()); 154 | } 155 | it.remove(); // avoids a ConcurrentModificationException 156 | } 157 | } 158 | 159 | /** 160 | * Function: unbind 161 | * 162 | * Unbinds a specific variable from the envrionment 163 | * 164 | * @author Joseph T. Anderson 165 | * @since 2012-11-01 166 | * @version 2012-11-01 167 | * 168 | * @param name String name of the variable 169 | * 170 | */ 171 | public static void unbind(String name){ 172 | vars.remove(name); 173 | } 174 | 175 | /** 176 | * Function: getVarValue 177 | * 178 | * Finds a variable and returns it's value if it exists 179 | * 180 | * @author Joseph T. Anderson 181 | * @since 2012-11-01 182 | * @version 2012-11-01 183 | * 184 | * @param name The name of the variable 185 | * 186 | * @return The TreeNode value of the variable 187 | * 188 | * @throws Exception If the variable is not bound 189 | * 190 | */ 191 | public static TreeNode getVarValue(String name) throws Exception{ 192 | if ( vars.containsKey(name) ){ 193 | return vars.get(name); 194 | } else { 195 | throw new Exception("Error! No such variable."); 196 | } 197 | } 198 | 199 | /** 200 | * Function: mergeVars 201 | * 202 | * This substitutes in new variable bindings to the current environment 203 | * 204 | * @author Joseph T. Anderson 205 | * @since 2012-11-02 206 | * @version 2012-11-02 207 | * 208 | * @param newVars A Hashtable of the new variable bindings 209 | * 210 | */ 211 | public static void mergeVars(Hashtable newVars){ 212 | Iterator it = newVars.entrySet().iterator(); 213 | while (it.hasNext()) { 214 | Map.Entry pairs = (Map.Entry)it.next(); 215 | if ( vars.contains(pairs.getKey()) ){ // Do not let it store multiple things in one bucket 216 | vars.remove(pairs.getKey()); 217 | } 218 | vars.put( (String) pairs.getKey(), (TreeNode) pairs.getValue() ); 219 | it.remove(); // avoids a ConcurrentModificationException 220 | } 221 | } 222 | 223 | /** 224 | * Function: getVarTable 225 | * 226 | * Returns a copy of the current environment bindings. Uses a 227 | * copy to avoid accidental corruption. 228 | * 229 | * @author Joseph T. Anderson 230 | * @since 2012-11-02 231 | * @version 2012-11-02 232 | * 233 | * @return A copy of the current environment bindings 234 | */ 235 | public static Hashtable getVarTable(){ 236 | return new Hashtable (vars); 237 | } 238 | 239 | /** 240 | * Function: setVars 241 | * 242 | * This takes a table of bindings and swaps it into the current 243 | * variable table. 244 | * 245 | * @author Joseph T. Anderson 246 | * @since 2012-11-02 247 | * @version 2012-11-02 248 | * 249 | * @param v The new Hashtable of variable bindings 250 | * 251 | */ 252 | public static void setVars(Hashtable v){ 253 | vars = new Hashtable (v); 254 | } 255 | } -------------------------------------------------------------------------------- /src/parser/Primitives.java: -------------------------------------------------------------------------------- 1 | package parser; 2 | 3 | import java.lang.Integer; 4 | import java.lang.String; 5 | import java.util.*; 6 | import helpers.*; 7 | 8 | /** 9 | * File: Primitives.java 10 | * 11 | * This files has the required primitive functions for Lisp 12 | * 13 | * @author Joseph T. Anderson 14 | * @since 2012-11-01 15 | * @version 2012-11-01 16 | * 17 | */ 18 | 19 | class Primitives{ 20 | 21 | // public static enum Primitive{ 22 | // PLUS, 23 | // MINUS, 24 | // T, 25 | // NIL, 26 | // CONS, 27 | // CAR, 28 | // CDR, 29 | // ATOM, 30 | // EQ, 31 | // NULL, 32 | // INT, 33 | // QUOTIENT, 34 | // TIMES, 35 | // REMAINDER, 36 | // LESS, 37 | // GREATER, 38 | // COND, 39 | // QUOTE, 40 | // DEFUN 41 | // } 42 | 43 | /** 44 | * Function: T 45 | * 46 | * Creates a new atom representing the "true" value 47 | * 48 | * @author Joseph T. Anderson 49 | * @since 2012-11-01 50 | * @version 2012-11-01 51 | * 52 | * @return The T Atom 53 | * 54 | */ 55 | public static TreeNode T(){ 56 | return new Atom(true); 57 | }; 58 | 59 | /** 60 | * Function: NIL 61 | * 62 | * Creates an atom to represent the NIL value 63 | * 64 | * @author Joseph T. Anderson 65 | * @since 2012-11-01 66 | * @version 2012-11-01 67 | * 68 | * @return The NIL Atom 69 | * 70 | */ 71 | public static TreeNode NIL(){ 72 | return new Atom(false); 73 | }; 74 | 75 | /** 76 | * Function: CONS 77 | * 78 | * Carries out the CONS operation as defined by the Lisp 79 | * operational semantics. It combines the CAR and CADR of 80 | * the argument list into a single list. If the arguments 81 | * fail for these operations, an Exception will be thrown 82 | * in the SExpression constructor. 83 | * 84 | * @author Joseph T. Anderson 85 | * @since 2012-11-01 86 | * @version 2012-11-01 87 | * 88 | * @param s The SExpression arguments in dot-notation 89 | * 90 | * @return CONS[ CAR[s], CADR[s] ] - the semantically defined CONS 91 | * 92 | * @throws Exception if the arguments are inappropriate 93 | * 94 | */ 95 | public static SExpression CONS ( SExpression s ) throws Exception { 96 | SExpression tmp = new SExpression(s.dataTokens); 97 | return new SExpression(s.address.evaluate(), tmp.address.evaluate()); 98 | } 99 | 100 | /** 101 | * Function: CAR 102 | * 103 | * returns the car of the given S-Expression as defined by the 104 | * operational semantics. 105 | * 106 | * @author Joseph T. Anderson 107 | * @since 2012-11-01 108 | * @version 2012-11-01 109 | * 110 | * @param s The argument S-Expression 111 | * 112 | * @return The address of the given S-Exp 113 | * 114 | * @throws Exception If the S-Expression is incompatible 115 | * 116 | */ 117 | public static TreeNode CAR ( SExpression s ) throws Exception{ 118 | return s.address; 119 | } 120 | 121 | /** 122 | * Function: CDR 123 | * 124 | * Returns the data of the given S-Expression 125 | * 126 | * @author Joseph T. Anderson 127 | * @since 2012-11-01 128 | * @version 2012-11-01 129 | * 130 | * @param s An S-Expression 131 | * 132 | * @return The data of the S-Expression 133 | * 134 | */ 135 | public static TreeNode CDR ( SExpression s ) throws Exception{ 136 | return s.data; 137 | } 138 | 139 | /** 140 | * Function: ATOM 141 | * 142 | * Determines if an S-Expression is semantically an Atom 143 | * 144 | * @author Joseph T. Anderson 145 | * @since 2012-11-01 146 | * @version 2012-11-01 147 | * 148 | * @param s The S-Expression in question 149 | * 150 | * @return True if it is an atom literal. False otherwise. 151 | * 152 | */ 153 | public static TreeNode ATOM ( SExpression s ) throws Exception{ 154 | return TreeNode.create(s.address.evaluate().toString().matches(Patterns.LITERAL)); 155 | } 156 | 157 | /** 158 | * Function: EQ 159 | * 160 | * Determines if the value of two S-Expressions are equal 161 | * 162 | * @author Joseph T. Anderson 163 | * @since 2012-11-01 164 | * @version 2012-11-01 165 | * 166 | * @param s The paramenter S-Expression 167 | * 168 | * @return T or NIL whether or not they are the same 169 | * 170 | * @throws Exception If the S-Expression is malformed 171 | * 172 | */ 173 | public static TreeNode EQ ( SExpression s ) throws Exception{ 174 | return TreeNode.create(s.address.evaluate(true).toString().matches(s.data.evaluate(true).toString())); 175 | } 176 | 177 | /** 178 | * Function: NULL 179 | * 180 | * Determines if the S-Expression is NIL 181 | * 182 | * @author Joseph T. Anderson 183 | * @since 2012-11-01 184 | * @version 2012-11-01 185 | * 186 | * @param s The S-Expression in question 187 | * 188 | * @return T or NIL whether or not the S-Expression is NIL 189 | * 190 | * @throws Exception If the S-Expression is malformed 191 | * 192 | */ 193 | public static TreeNode NULL ( SExpression s ) throws Exception{ 194 | return TreeNode.create(s.data.evaluate().toString().matches("NIL")); 195 | } 196 | 197 | /** 198 | * Function: INT 199 | * 200 | * Determines if an S-Expression is an integer 201 | * 202 | * @author Joseph T. Anderson 203 | * @since 2012-11-01 204 | * @version 2012-11-01 205 | * 206 | * @param s An S-Expression 207 | * 208 | * @return T or NIL whether or not it is an integer 209 | * 210 | */ 211 | public static TreeNode INT ( SExpression s ) throws Exception{ 212 | return TreeNode.create(s.address.evaluate(true).toString().matches(Patterns.NUMERIC_ATOM)); 213 | } 214 | 215 | /** 216 | * Function: PLUS 217 | * 218 | * Adds two numbers 219 | * 220 | * @author Joseph T. Anderson 221 | * @since 2012-11-01 222 | * @version 2012-11-01 223 | * 224 | * @param s S-Expression 225 | * 226 | * @return The sum of the two elements in the given list (dot-notation form) 227 | * 228 | * @throws Exception If the S-Expression is malformed 229 | * 230 | */ 231 | public static TreeNode PLUS ( SExpression s ) throws Exception{ 232 | return TreeNode.create(Integer.parseInt(s.address.evaluate(true).toString()) + Integer.parseInt(s.data.evaluate(true).toString())); 233 | } 234 | 235 | /** 236 | * Function: MINUS 237 | * 238 | * Subtracts two numbers 239 | * 240 | * @author Joseph T. Anderson 241 | * @since 2012-11-01 242 | * @version 2012-11-01 243 | * 244 | * @param s S-Expression list in dot-notation 245 | * 246 | * @return The difference of the two paramenters as an atom 247 | * 248 | * @throws Exception If the S-Expression is malformed 249 | * 250 | */ 251 | public static TreeNode MINUS ( SExpression s ) throws Exception{ 252 | return TreeNode.create(Integer.parseInt(s.address.evaluate(true).toString()) - Integer.parseInt(s.data.evaluate(true).toString())); 253 | } 254 | 255 | /** 256 | * Function: QUOTIENT 257 | * 258 | * Divides two numbers with integer division 259 | * 260 | * @author Joseph T. Anderson 261 | * @since 2012-11-01 262 | * @version 2012-11-01 263 | * 264 | * @param s S-Expression 265 | * 266 | * @return The quotient of the two paramenters given by the S-Expression 267 | * 268 | * @throws Exception If the S-Expression is malformed 269 | * 270 | */ 271 | public static TreeNode QUOTIENT ( SExpression s ) throws Exception{ 272 | return TreeNode.create(Integer.parseInt(s.address.evaluate(true).toString()) / Integer.parseInt(s.data.evaluate(true).toString())); 273 | } 274 | 275 | /** 276 | * Funcntion: TIMES 277 | * 278 | * Computes the product of two numbers 279 | * 280 | * @author Joseph T. Anderson 281 | * @since 2012-11-01 282 | * @version 2012-11-01 283 | * 284 | * @param s S-Expression 285 | * 286 | * @return The product of the two parameters as derived from the S-Expression 287 | * 288 | * @throws Exception If the S-Expression is malformed 289 | * 290 | */ 291 | public static TreeNode TIMES ( SExpression s ) throws Exception{ 292 | return TreeNode.create(Integer.parseInt(s.address.evaluate(true).toString()) * Integer.parseInt(s.data.evaluate(true).toString())); 293 | } 294 | 295 | /** 296 | * Function: REMAINDER 297 | * 298 | * Returns the r term in the integer division of x by y as 299 | * given by x = y * q + r 300 | * 301 | * @author Joseph T. Anderson 302 | * @since 2012-11-01 303 | * @version 2012-11-01 304 | * 305 | * @param s S-Expression 306 | * 307 | * @return The remainder after division as derived from the S-Expression 308 | * 309 | * @throws Exception If the S-Expression is malformed 310 | * 311 | */ 312 | public static TreeNode REMAINDER ( SExpression s ) throws Exception{ 313 | return TreeNode.create(Integer.parseInt(s.address.evaluate(true).toString()) % Integer.parseInt(s.data.evaluate(true).toString())); 314 | } 315 | 316 | /** 317 | * Function: LESS 318 | * 319 | * Compares two objects and computes the strict 'less than' operator 320 | * 321 | * @author Joseph T. Anderson 322 | * @since 2012-11-01 323 | * @version 2012-11-01 324 | * 325 | * @param s S-Expression 326 | * 327 | * @return the boolean answer to the 'less than' operation 328 | * 329 | * @throws Exception If the S-Expression is malformed 330 | * 331 | */ 332 | public static TreeNode LESS ( SExpression s ) throws Exception{ 333 | return TreeNode.create(Integer.parseInt(s.address.evaluate(true).toString()) < Integer.parseInt(s.data.evaluate(true).toString())); 334 | } 335 | 336 | /** 337 | * Function: GREATER 338 | * 339 | * Computer the 'greater than' operator using the arguments 340 | * found in the paramter S-Expression 341 | * 342 | * @author Joseph T. Anderson 343 | * @since 2012-11-01 344 | * @version 2012-11-01 345 | * 346 | * @param s S-Expression 347 | * 348 | * @return The boolean answer to the 'greater than' operator 349 | * 350 | * @throws Exception If the S-Expression is malformed 351 | * 352 | */ 353 | public static TreeNode GREATER ( SExpression s ) throws Exception{ 354 | return TreeNode.create(Integer.parseInt(s.address.evaluate(true).toString()) > Integer.parseInt(s.data.evaluate(true).toString())); 355 | } 356 | 357 | /** 358 | * Function: COND 359 | * 360 | * As per the operational semantics of Lisp, takes an S-Expression 361 | * which represents a list of conditions. It evaluates them until 362 | * one's CAR evaluates to T and then returns the second item. 363 | * 364 | * This roughly approximates the evcon function in the operational semantics 365 | * where the error condition is taken care of by the SExpression 366 | * constructor. 367 | * 368 | * @author Joseph T. Anderson 369 | * @since 2012-11-01 370 | * @version 2012-11-01 371 | * 372 | * @param s The S-Expression describing the conditions 373 | * 374 | * @return The result of evaluating the expression in the list with the first boolean component 375 | * 376 | * @throws Exception If the S-Expression is malformed 377 | * 378 | */ 379 | public static TreeNode COND ( SExpression s ) throws Exception { 380 | SExpression a = new SExpression(s.addressTokens); 381 | if ( a.address.evaluate().toString().matches("T") ){ 382 | SExpression tmp = new SExpression(a.dataTokens); 383 | return tmp.address.evaluate(true); 384 | } else { 385 | SExpression b = new SExpression(s.dataTokens); 386 | return COND(b); 387 | } 388 | } 389 | 390 | /** 391 | * Function: QUOTE 392 | * 393 | * As per the operational semantics, returns the CADR of 394 | * the containing S-Expression. Note that in this context, 395 | * the 'data' has been passed as an argument similar to 396 | * the other primitives, so we must only take the address 397 | * portion and return it. 398 | * 399 | * @author Joseph T. Anderson 400 | * @since 2012-11-01 401 | * @version 2012-11-01 402 | * 403 | * @param s S-Expression 404 | * 405 | * @return The address of s 406 | * 407 | * @throws Exception If the S-Expression is malformed 408 | * 409 | */ 410 | public static TreeNode QUOTE ( SExpression s ) throws Exception { 411 | return s.address; 412 | } 413 | 414 | /** 415 | * Function: DEFUN 416 | * 417 | * This function takes a List denoting the name of a new function, 418 | * its parameter list, and its body. It is then broken down and 419 | * entered into the environment, the representation of the operational 420 | * 'd-list' and then simply returns an Atom of the name of the function. 421 | * 422 | * @author Joseph T. Anderson 423 | * @since 2012-11-01 424 | * @version 2012-11-01 425 | * 426 | * @param s S-Expression containing all the necessary information 427 | * 428 | * @return An Atom of the function name if the registration is successful 429 | * 430 | * @throws Exception If the S-Expression is malformed 431 | * 432 | */ 433 | public static TreeNode DEFUN (SExpression s) throws Exception { 434 | String name = s.address.toString(); 435 | 436 | if ( ! name.matches(Patterns.VALID_FUNCTION_NAME) ){ 437 | throw new Exception("Error! Function names must be character literals only"); 438 | } 439 | 440 | if ( Primitives.primitiveExists(name) ){ 441 | throw new Exception("Error! Cannot override a primitive function."); 442 | } 443 | 444 | SExpression d = new SExpression(s.dataTokens); 445 | TreeNode params = TreeNode.create(d.addressTokens); 446 | SExpression tmp = new SExpression(d.dataTokens); 447 | TreeNode body = TreeNode.create(tmp.addressTokens); 448 | 449 | Environment.registerFunction(name, params, body); 450 | 451 | return new Atom(name); 452 | } 453 | 454 | /** 455 | * Function: primitiveExists 456 | * 457 | * This uses reflection to check if a particular primitive is defined. 458 | * 459 | * @author Joseph T. Anderson 460 | * @since 2012-11-01 461 | * @version 2012-11-01 462 | * 463 | * @param name The name of the primitive in question 464 | * 465 | * @return true or false depending on whether or not the primitive is defined 466 | * 467 | */ 468 | private static boolean primitiveExists(String name){ 469 | java.lang.reflect.Method m; 470 | try{ 471 | m = Primitives.class.getDeclaredMethod(name, SExpression.class); 472 | return true; 473 | } catch (Exception e){ 474 | return false; 475 | } 476 | } 477 | } -------------------------------------------------------------------------------- /src/parser/SExpression.java: -------------------------------------------------------------------------------- 1 | package parser; 2 | 3 | import java.util.Vector; 4 | import java.util.Hashtable; 5 | import helpers.*; 6 | import java.lang.reflect.*; 7 | 8 | /** 9 | * File: SExpression.java 10 | * 11 | * This file is the class which represents S-Expressions in the 12 | * interpreter. It handles evaluation and construction from various 13 | * kinds of input. It is also what controls whether or not the 14 | * output is returned in list notation or dot notation. 15 | * 16 | * @author Joseph T. Anderson 17 | * @since 2012-11-01 18 | * @version 2012-11-01 19 | * 20 | */ 21 | 22 | class SExpression extends TreeNode{ 23 | protected TreeNode address; 24 | protected TreeNode data; 25 | protected Vector dataTokens; 26 | protected Vector addressTokens; 27 | 28 | /** 29 | * Function: SExpression 30 | * 31 | * Constructor: SExpression(Vector s) 32 | * 33 | * This is the constructor that creates an S-Expression from a 34 | * vector string. It calls conshelper to make sure input is 35 | * valid and actually does the creation. 36 | * 37 | * @author Joseph T. Anderson 38 | * @since 2012-11-01 39 | * @version 2012-11-01 40 | * 41 | * @param s A string vector 42 | * 43 | * @throws Exception If the input is not representative of an S-Expression 44 | * 45 | * @see Parser.SExpression.consHelper 46 | * 47 | */ 48 | public SExpression(Vector s) throws Exception{ 49 | consHelper(s); 50 | } 51 | 52 | /** 53 | * Function: SExpression 54 | * 55 | * Constructor: SExpression(TreeNode t) 56 | * 57 | * This function creast 58 | * 59 | * @author Joseph T. Anderson 60 | * @since 2012-11-01 61 | * @version 2012-11-01 62 | * 63 | * @param t A treenode to be "cast" to an S-Expression 64 | * 65 | * @throws Exception If the Treenode is not suitable 66 | * 67 | * @see Parser.SExpression.consHelper 68 | * 69 | */ 70 | public SExpression(TreeNode t) throws Exception{ 71 | consHelper(t.tokens); 72 | } 73 | 74 | /** 75 | * Function: SExpression 76 | * 77 | * Constructor: SExpression(TreeNode a, Treenode d) 78 | * 79 | * This function takes two TreeNodes and puts one in the 80 | * address field and the other in the data field and updates 81 | * the tokens accordingly 82 | * 83 | * @author Joseph T. Anderson 84 | * @since 2012-11-01 85 | * @version 2012-11-01 86 | * 87 | * @param a The address-to-be TreeNode 88 | * @param d The data-to-be TreeNode 89 | * 90 | */ 91 | public SExpression(TreeNode a, TreeNode d){ 92 | address = a; 93 | data = d; 94 | dataTokens = d.tokens; 95 | addressTokens = a.tokens; 96 | tokens = new Vector (); 97 | tokens.add("("); 98 | tokens.addAll(a.tokens); 99 | tokens.add("."); 100 | tokens.addAll(d.tokens); 101 | tokens.add(")"); 102 | } 103 | 104 | /** 105 | * Function: SExpression 106 | * 107 | * Constructor: SExpression(SExpression s) 108 | * 109 | * This is essentially a deep-copy constructor 110 | * 111 | * @author Joseph T. Anderson 112 | * @since 2012-11-02 113 | * @version 2012-11-02 114 | * 115 | * @param s The to-be-copied S-Expression 116 | * 117 | * @throws Exception If any of the sub-expressions are unsuitable or malformed 118 | * 119 | */ 120 | public SExpression(SExpression s) throws Exception{ 121 | data = TreeNode.create(s.dataTokens); 122 | address = TreeNode.create(s.addressTokens); 123 | dataTokens = new Vector (s.dataTokens); 124 | addressTokens = new Vector (s.addressTokens); 125 | } 126 | 127 | /** 128 | * Function: consHelper 129 | * 130 | * This is a magic function to take a string vector, make sure it is a 131 | * suitable suitable representation of an S-Expression, and calculate 132 | * which parts are the address and which is the data. It appropriately 133 | * udpates the tokens as well. 134 | * 135 | * @author Joseph T. Anderson 136 | * @since 2012-11-02 137 | * @version 2012-11-02 138 | * 139 | * @param s The string vector to fit into an S-Expression 140 | * 141 | * @throws Exception If the vector is not fit for an S-Expression (i.e. does not begin with '(' ) 142 | * 143 | */ 144 | private void consHelper(Vector s) throws Exception{ 145 | if ( s.size() > 0 && s.get(0).matches("[(]") ){ // some sanity checking for now 146 | int i = 1; 147 | int dataStart = 3; 148 | if ( s.get(i) == "(" ){ 149 | int open = 1; 150 | while ( open > 0 && i < s.size() ){ 151 | i++; 152 | if ( s.get(i) == "(" ){ 153 | open++; 154 | } else if ( s.get(i) == ")" ){ 155 | open--; 156 | } 157 | } 158 | dataStart = i + 1; 159 | } 160 | i = dataStart > 3 ? s.indexOf(".", dataStart) : 2; 161 | addressTokens = new Vector (s.subList(1,i)); 162 | address = TreeNode.create(addressTokens); 163 | dataTokens = new Vector (s.subList(i+1, s.size() - 1)); 164 | data = TreeNode.create(dataTokens); 165 | tokens = new Vector (); 166 | tokens.add("("); 167 | tokens.addAll(addressTokens); 168 | tokens.add("."); 169 | tokens.addAll(dataTokens); 170 | tokens.add(")"); 171 | } else { 172 | throw new Exception("Error! Invalid S-Expression: " + s.toString()); 173 | } 174 | } 175 | 176 | /** 177 | * Function: isList() 178 | * 179 | * This function determines if the S-Expression is part of a 180 | * list or not by the fact that it is a list iff its data is 181 | * NIL or the data of one of its 'sub-expressions' is NIL 182 | * 183 | * @author Joseph T. Anderson 184 | * @since 2012-11-01 185 | * @version 2012-11-01 186 | * 187 | * @return True or False depending on if it is a list 188 | * 189 | */ 190 | protected boolean isList(){ 191 | return data.toString().matches("NIL") || data.isList(); 192 | } 193 | 194 | /** 195 | * Function: toString 196 | * 197 | * Provides the basic toString functionality. It initially tries 198 | * to print it as a list but if it cannot be converted to list notation 199 | * it uses standard dot notation. 200 | * 201 | * @author Joseph T. Anderson 202 | * @since 2012-11-02 203 | * @version 2012-11-02 204 | * 205 | * @return The dot- or list-notation of the S-Expression 206 | * 207 | */ 208 | public String toString(){ 209 | if ( isList() ){ 210 | try { 211 | return toListString(); 212 | } catch (Exception e){ 213 | return "(" + address.toString() + " . " + data.toString() + ")"; 214 | } 215 | } else { 216 | return "(" + address.toString() + " . " + data.toString() + ")"; 217 | } 218 | } 219 | 220 | /** 221 | * Function: toListString 222 | * 223 | * Outputs the SExpression data as a list using the prescribed 224 | * notation. 225 | * 226 | * @author Joseph T. Anderson 227 | * @since 2012-11-02 228 | * @version 2012-11-02 229 | * 230 | * @return The String representing the list-notation of the S-Expression 231 | * 232 | */ 233 | protected String toListString() throws Exception{ 234 | return "(" + StringHelpers.join(toVector(), " ") + ")"; 235 | } 236 | 237 | /** 238 | * Function: toVector 239 | * 240 | * This function converts the string data to a vector suitable 241 | * for conversion to list notation. 242 | * 243 | * @author Joseph T. Anderson 244 | * @since 2012-11-02 245 | * @version 2012-11-02 246 | * 247 | * @return The String vector of the S-Expression 248 | * 249 | * @throws Exception if the SExpression is not able to be converted 250 | * 251 | */ 252 | private Vector toVector() throws Exception{ 253 | if ( ! isList() ){ 254 | throw new Exception("Error!"); 255 | } 256 | Vector v = new Vector (); 257 | SExpression tmp = this; 258 | while ( tmp.isList() ){ 259 | v.add(tmp.address.toString()); 260 | try { 261 | tmp = new SExpression(tmp.dataTokens); 262 | } catch (Exception e){ 263 | break; 264 | } 265 | } 266 | return v; 267 | } 268 | 269 | /** 270 | * Function: evaluate 271 | * 272 | * This is a connector function to the main evaluation procedure. 273 | * It takes not arguments and just passes a default value to the main 274 | * subroutine. 275 | * 276 | * @author Joseph T. Anderson 277 | * @since 2012-11-02 278 | * @version 2012-11-02 279 | * 280 | * @return The result of evaluation of the SExpression 281 | * 282 | * @throws Exception If evaluation fails 283 | * 284 | * @see evaluate 285 | * 286 | */ 287 | protected TreeNode evaluate() throws Exception{ 288 | return this.evaluate(false); 289 | } 290 | 291 | /** 292 | * Function: evaluate 293 | * 294 | * This function allows invoking of the main evaluation routine 295 | * without specifying the flag. It passes a default value and the 296 | * given environment varables. 297 | * 298 | * @author Joseph T. Anderson 299 | * @since 2012-11-02 300 | * @version 2012-11-02 301 | * 302 | * @param env A Hashtable of the variables to be considered during evaluation 303 | * 304 | * @see evaluate 305 | * 306 | */ 307 | protected TreeNode evaluate(Hashtable env) throws Exception{ 308 | return evaluate(false, env); 309 | } 310 | 311 | /** 312 | * Function: evaluate 313 | * 314 | * Provides an interface to accept both the flag and environment varaibles 315 | * as arguments. This swaps in the current scope variables and when finished, 316 | * 'pops' them back out. 317 | * 318 | * @author Joseph T. Anderson 319 | * @since 2012-11-02 320 | * @version 2012-11-02 321 | * 322 | * @param flag Whether or not to take numericals literally 323 | * @param env Variable bindings to use 324 | * 325 | * @return The result of evaluation 326 | * 327 | * @see evaluate 328 | * 329 | */ 330 | protected TreeNode evaluate(boolean flag, Hashtable env) throws Exception{ 331 | Hashtable oldVars = Environment.getVarTable(); 332 | Environment.mergeVars(env); 333 | TreeNode rtn = evaluate(flag); 334 | Environment.setVars(oldVars); 335 | return rtn; 336 | } 337 | 338 | /** 339 | * Function: evaluate 340 | * 341 | * This is the main evaluation function for an SExpression. 342 | * It takes a flag that decides whether or not to take numerical 343 | * items literally. That should only happen when they are arguments 344 | * to a primitive or user-defined function. 345 | * 346 | * This function uses reflection on the Primitives object to find the 347 | * appropriate primitive function to run. It also first searches the 348 | * defined functions and variables for bound values. If none are set, 349 | * it uses the string name of the function call to find the appropriate 350 | * primitive function. Using reflection, that primitive is invoked with 351 | * the 'data' component of the current S-Expression as an argument. This 352 | * is because in the operational semantics, the primitives operate on the 353 | * CADR or CDR of the S-Expression. So we simplify here by just passing 354 | * the CDR and the primitives do any further chomping. 355 | * 356 | * @author Joseph T. Anderson 357 | * @since 2012-11-02 358 | * @version 2012-11-02 359 | * 360 | * @param flag Whether or not to interpret numerics literally 361 | * 362 | * @return The TreeNode representation of the result 363 | * 364 | * @throws Exception If the function name, variable, etc. is undefined 365 | * 366 | */ 367 | protected TreeNode evaluate( boolean flag ) throws Exception{ 368 | String a = address.evaluate().toString(); 369 | SExpression params; 370 | TreeNode rtn; 371 | 372 | if ( flag && a.matches(Patterns.NUMERIC_ATOM) ){ 373 | return address.evaluate(); 374 | } else if ( a.matches("NIL") || a.matches("T") ){ 375 | return a.matches("NIL") ? Primitives.NIL() : Primitives.T(); 376 | } else if ( Environment.varIsDefined(a) ){ 377 | return Environment.getVarValue(a); 378 | } else if ( Environment.functionIsDefined(a) ){ 379 | return Environment.executeFunction(a, TreeNode.create(dataTokens)); 380 | } else if ( a.matches("CAR") || a.matches("CDR") ){ 381 | SExpression s; 382 | if ( data.isList() ){ 383 | s = new SExpression(dataTokens); 384 | s = new SExpression(s.address.evaluate().tokens); 385 | // s = new SExpression(s.evaluate().tokens); 386 | } else { 387 | s = new SExpression(dataTokens); 388 | } 389 | params = s; 390 | } else if ( a.matches("DEFUN") ){ 391 | return Primitives.DEFUN((SExpression) data); 392 | } else { 393 | params = (SExpression) data; 394 | } 395 | 396 | try{ 397 | rtn = invokePrimitive(a, params); 398 | 399 | /** 400 | * @note: The commented code below was an attempt to improve 401 | * performance over using reflection, but standard timing 402 | * benchmarks showed no performance increase. So I kept the 403 | * reflection strategy as opposed to increasing code complexity. 404 | */ 405 | 406 | // Primitives.Primitive enumVal = Primitives.Primitive.valueOf(a); 407 | 408 | // switch(enumVal){ 409 | // case PLUS: rtn = Primitives.PLUS(params); break; 410 | // case MINUS: rtn = Primitives.MINUS(params); break; 411 | // case T: rtn = Primitives.T(); break; 412 | // case NIL: rtn = Primitives.NIL(); break; 413 | // case CONS: rtn = Primitives.CONS(params); break; 414 | // case CAR: rtn = Primitives.CAR(params); break; 415 | // case CDR: rtn = Primitives.CDR(params); break; 416 | // case ATOM: rtn = Primitives.ATOM(params); break; 417 | // case EQ: rtn = Primitives.EQ(params); break; 418 | // case NULL: rtn = Primitives.NULL(params); break; 419 | // case INT: rtn = Primitives.INT(params); break; 420 | // case QUOTIENT: rtn = Primitives.QUOTIENT(params); break; 421 | // case TIMES: rtn = Primitives.TIMES(params); break; 422 | // case REMAINDER: rtn = Primitives.REMAINDER(params); break; 423 | // case LESS: rtn = Primitives.LESS(params); break; 424 | // case GREATER: rtn = Primitives.GREATER(params); break; 425 | // case COND: rtn = Primitives.COND(params); break; 426 | // case QUOTE: rtn = Primitives.QUOTE(params); break; 427 | // case DEFUN: rtn = Primitives.DEFUN(params); break; 428 | // default: 429 | // throw new Exception("Error! Undfined literal: " + a); 430 | // } 431 | return rtn; 432 | } catch (Exception e){ 433 | throw e; 434 | // throw new Exception("Error! Undefined literal: " + toString()); 435 | // throw new Exception("Error!"); 436 | } 437 | } 438 | 439 | /** 440 | * Function: invokePrimitive 441 | * 442 | * This function contains the reflection logic to call one of the 443 | * functions of the Primitives class with the appropriate SExpression 444 | * passed as data. If the return value is boolean, it returns the 445 | * appropriate atom representation. 446 | * 447 | * @author Joseph T. Anderson 448 | * @since 2012-11-02 449 | * @version 2012-11-02 450 | * 451 | * @param name The string name of the requested function 452 | * @param obj The data to be used as primitive data 453 | * 454 | * @return The object returned by the primitive cast as a TreeNode (what it should be anyway) 455 | * 456 | * @throws Exception If the primitive does not exist 457 | * 458 | */ 459 | private TreeNode invokePrimitive(String name, SExpression obj) throws Exception{ 460 | java.lang.reflect.Method m; 461 | 462 | m = Primitives.class.getDeclaredMethod(name, SExpression.class); 463 | m.setAccessible(true); 464 | Object o = m.invoke(null, obj); 465 | if ( o.toString().matches("true") ){ 466 | return new Atom("T"); 467 | } else if ( o.toString().matches("false") ){ 468 | return new Atom("NIL"); 469 | } else { 470 | return (TreeNode) o; 471 | } 472 | } 473 | } 474 | --------------------------------------------------------------------------------