/** * 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.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.function.Function; import serene.simple.builtin.AFn; import static java.util.Arrays.asList; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ListNode extends Node implements Iterable { public static final ListNode EMPTY = new ListNode<>(); public final T first; public final ListNode rest; public final int length; public ListNode() { this.first = null; this.rest = null; this.length = 0; } public ListNode(T f, ListNode r) { this.first = f; this.rest = r; this.length = r.length + 1; } @SafeVarargs public static ListNode list(T... objs) { return list(asList(objs)); } public static ListNode list(List objs) { ListNode l = (ListNode) EMPTY; for (int i = objs.size() - 1; i >= 0; i--) { l = l.cons(objs.get(i)); } return l; } public ListNode cons(T node) { return new ListNode(node, this); } @Override public Object eval(BaseScope scope) { SymbolNode firstElement = (SymbolNode) this.first(); List args = new ArrayList(); for (T node : this.rest()) { args.add(node.eval(scope)); } if (firstElement.name.startsWith(".")) { return this.evalInterop(scope, firstElement, args.subList(1, args.size())); } Object f = firstElement.eval(scope); if (f instanceof AFn) { return evalBuiltin(scope, (AFn) f, args); } else { return evalFn(scope, (Function) f, args); } } public Object evalBuiltin(BaseScope scope, AFn fn, List args) { fn.setArguments(args); return fn.eval(scope); } public Object evalFn(BaseScope scope, Function fn, List args) { return fn.apply(args.toArray()); } public Object evalInterop(BaseScope scope, SymbolNode firstElement, List rest) throws SereneException { String mName = firstElement.name.substring(1, firstElement.name.length()); Object target = this.rest().first().eval(scope); if (mName.startsWith("-")) { return this.evalInteropProperty( scope, target, mName.substring(1, mName.length())); } try { Method f = target.getClass().getMethod(mName); return f.invoke(target, rest.toArray()); } catch(NoSuchMethodException e) { throw new SereneException( String.format( "Can't find method '%s' on object '%s'.", mName, this.rest().first())); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); throw new SereneException( String.format( "Invocation of '%s' failed because of: %s%n", mName, cause.getMessage())); } catch (IllegalAccessException e) { throw new SereneException( String.format("Illegal access from '%s'", mName)); } } public Object evalInteropProperty(BaseScope scope, Object target, String propertyName) throws SereneException { try { Class targetClass = this.getClassOf(target); return targetClass.getField(propertyName); } catch(NoSuchFieldException e) { throw new SereneException( String.format( "Can't find field '%s' on object '%s'.", propertyName, target.toString())); } } public T first() { if (this != EMPTY) { return this.first; } return null; } public ListNode rest() { if (this != EMPTY) { return this.rest; } return (ListNode) EMPTY; } @Override public Iterator iterator() { return new Iterator() { private ListNode l = ListNode.this; @Override public boolean hasNext() { return this.l != EMPTY; } @Override public T next() { if (this.l == EMPTY) { return null; } T first = this.l.first; this.l = this.l.rest; return first; } @Override public void remove() { throw new SereneException("Iterator is immutable"); } }; } public String toString() { if (this.length == 0) { return "()"; } String output = "(" + this.first(); for(Node x : this.rest()) { output = output + " " + x.toString(); } return output + ")"; } private Class getClassOf(Object target) { if (target instanceof Class) { return (Class) target; } return target.getClass(); } }