using System.Reflection; using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler; namespace Jellyfin.Plugin.SmartPlaylist.Lisp { using Function = Func, Expression>; using FunctionLater = Func, Expression>; public class Procedure : Expression { private Compiler.List _parameters; private Expression _body; public Procedure(Compiler.List parameters, Expression body) { _parameters = parameters; _body = body; } private static IEnumerable<(T1, T2)> Zip(IEnumerable a, IEnumerable b) { using (var e1 = a.GetEnumerator()) using (var e2 = b.GetEnumerator()) { while (e1.MoveNext() && e2.MoveNext()) { yield return (e1.Current, e2.Current); } } } public override int GetHashCode() { int hash = 17; hash *= 23; hash += _parameters.GetHashCode(); hash *= 23; hash += _body.GetHashCode(); return hash; } public override bool Equals(Expression? other) { if (other is Procedure other_p) { return _parameters == other_p._parameters && _body == other_p._body; } return false; } public override object Inner() { throw new ApplicationException("This is not sensible"); } public override string ToString() { return $"(lambda {_parameters} {_body})"; } public Expression Call(Executor e, IList args) { var p = _parameters.expressions.Select(x => x.Inner().ToString()).ToList(); Executor new_e = new Executor(new SubEnvironment(e.environment), e.builtins, e.builtinsLater); foreach (var tuple in Zip(p, args)) { new_e.environment.Set(tuple.Item1, tuple.Item2); } return new_e.eval(_body); } } public interface IEnvironment { public V Get(K k); public void Set(K k, V v); public IEnvironment? Find(K k); } public class Environment : Dictionary, IEnvironment { public Expression? Get(string k) { if (TryGetValue(k, out Expression v)) { return v; } return null; } public void Set(string k, Expression v) { this[k] = v; } public IEnvironment? Find(string k) { if (ContainsKey(k)) { return this; } return null; } } public class DefaultEnvironment: Environment { public DefaultEnvironment() { this["find"] = new Parser("(lambda (item list) (if (= list ()) nil (if (= item (car list)) (car list) (find item (cdr list)))))").parse(); } } public class SubEnvironment : Dictionary, IEnvironment { private IEnvironment _super; public SubEnvironment(IEnvironment super) { _super = super; } public Expression? Get(string k) { if (TryGetValue(k, out Expression v)) { return v; } return null; } public void Set(string k, Expression v) { Add(k, v); } public IEnvironment? Find(string k) { if (ContainsKey(k)) { return this; } return _super.Find(k); } } public class Builtins : Dictionary { public Builtins() : base() { this["+"] = _add; this["-"] = _sub; this["*"] = _mul; this["/"] = _div; this["%"] = _mod; this[">"] = _gt; this["<"] = _lt; this[">="] = _ge; this["<="] = _le; this["eq?"] = _eq; this["="] = _eq; this["!="] = _ne; this["abs"] = _abs; this["append"] = _append; this["begin"] = _begin; this["car"] = _car; this["cdr"] = _cdr; this["cons"] = _cons; this["not"] = _not; this["length"] = _length; this["haskeys"] = _haskeys; this["getitems"] = _getitems; this["invoke"] = _invoke; } private static T _agg(Func op, IList args) { T agg = args[0]; foreach (var arg in args.Skip(1)) { agg = op(agg, arg); } return agg; } private static Expression _add(IList args) { Expression first = args[0]; switch (first) { case Integer i: return _agg((a, b) => a + b, args.Select(x => (Integer) x).ToList()); case Compiler.String s: return _agg((a, b) => a + b, args.Select(x => (Compiler.String) x).ToList()); } throw new ApplicationException(); } private static Expression _sub(IList args) { Expression first = args[0]; switch (first) { case Integer i: return _agg((a, b) => a - b, args.Select(x => (Integer) x).ToList()); } throw new ApplicationException(); } private static Expression _mul(IList args) { Expression first = args[0]; switch (first) { case Integer i: return _agg((a, b) => a * b, args.Select(x => (Integer) x).ToList()); } throw new ApplicationException(); } private static Expression _div(IList args) { Expression first = args[0]; switch (first) { case Integer i: return _agg((a, b) => a / b, args.Select(x => (Integer) x).ToList()); } throw new ApplicationException(); } private static Expression _mod(IList args) { Expression first = args[0]; switch (first) { case Integer i: return _agg((a, b) => a % b, args.Select(x => (Integer) x).ToList()); } throw new ApplicationException(); } private static E _cmp(Func op, IList args) { T first = args[0]; T second = args[1]; return op(first, second); } private static Expression _gt(IList args) { Expression first = args[0]; switch (first) { case Integer i: return _cmp((a, b) => a > b, args.Select(x => (Integer) x).ToList()); } throw new ApplicationException(); } private static Expression _lt(IList args) { Expression first = args[0]; switch (first) { case Integer i: return _cmp((a, b) => a < b, args.Select(x => (Integer) x).ToList()); } throw new ApplicationException(); } private static Expression _ge(IList args) { Expression first = args[0]; switch (first) { case Integer i: return _cmp((a, b) => a >= b, args.Select(x => (Integer) x).ToList()); } throw new ApplicationException(); } private static Expression _le(IList args) { Expression first = args[0]; switch (first) { case Integer i: return _cmp((a, b) => a <= b, args.Select(x => (Integer) x).ToList()); } throw new ApplicationException(); } private static Expression _eq(IList args) { bool r = _cmp((a, b) => a == b, args); return new Compiler.Boolean(r); } private static Expression _ne(IList args) { bool r = _cmp((a, b) => a != b, args); return new Compiler.Boolean(r); } private static Expression _abs(IList args) { Expression first = args[0]; switch (first) { case Integer i: return i.value >= 0 ? i : new Integer(-i.value); } throw new ApplicationException(); } private static Expression _append(IList args) { Expression first = args[0]; switch (first) { case List l: return l + new List(args); } throw new ApplicationException(); } private static Expression _begin(IList args) { return args.Last(); } private static Expression _car(IList args) { return ((List) args.First()).expressions.First(); } private static Expression _cdr(IList args) { return new List(((List) args.First()).expressions.Skip(1).ToList()); } private static Expression _cons(IList args) { switch (args[1]) { case Compiler.List other_list: return (new Compiler.List(new []{args[0]}.ToList()) + new Compiler.List(other_list.expressions)); case Atom other_atom: return new Compiler.List(new[]{args[0], args[1]}.ToList()); } throw new ApplicationException(); } private static Expression _not(IList args) { if (args[0] == new Compiler.Boolean(false)) { return new Compiler.Boolean(true); } return new Compiler.Boolean(false); } private static Expression _length(IList args) { return new Integer(((Compiler.List)args[0]).expressions.Count()); } private static Expression _haskeys(IList args) { Compiler.Object o = (Compiler.Object) args[0]; foreach (var e in args.Skip(1)) { Compiler.String s = (Compiler.String) e; PropertyInfo? pi = o.value.GetType().GetProperty(s.value); if (pi != null) { continue; } MethodInfo? mi = o.value.GetType().GetMethod(s.value); if (mi != null) { continue; } FieldInfo? fi = o.value.GetType().GetField(s.value); if (fi != null) { continue; } return new Compiler.Boolean(false); } return new Compiler.Boolean(true); } private static Expression _getitems(IList args) { Compiler.Object o = (Compiler.Object) args[0]; IList r = new List(); foreach (var e in args.Skip(1)) { Compiler.String s = (Compiler.String) e; PropertyInfo? pi = o.value.GetType().GetProperty(s.value); if (pi != null) { r.Add(Compiler.Object.FromBase(pi.GetValue(o.value))); continue; } FieldInfo? fi = o.value.GetType().GetField(s.value); if (fi != null) { r.Add(Compiler.Object.FromBase(fi.GetValue(o.value))); continue; } throw new ApplicationException($"{o.value} has no property or field {s.value}"); } return new Compiler.List(r); } private static Expression _invoke(IList args) { Compiler.Object o = (Compiler.Object) args[0]; Compiler.String s = (Compiler.String) args[1]; Compiler.List l = (Compiler.List) args[2]; IList r = new List(); MethodInfo? mi = o.value.GetType().GetMethod(s.value); if (mi == null) { throw new ApplicationException($"{o.value} has not method {s.value}"); } return Compiler.Object.FromBase(mi.Invoke(o.value, (object?[]?) l.Inner())); } } public class BuiltinsLater : Dictionary { public BuiltinsLater() : base() { this["if"] = _if; this["define"] = _define; this["lambda"] = _lambda; this["apply"] = _apply; this["and"] = _and; this["or"] = _or; } private static Expression _if(Executor e, IList args) { bool test = e.eval(args[0]) != (new Compiler.Boolean(false)); return e.eval(args[1 + (test ? 0 : 1)]); } private static Expression _define(Executor e, IList args) { var refname = ((Symbol) args[0]).name; e.environment.Set(refname, e.eval(args[1])); return new Compiler.Boolean(false); // NOOP } private static Expression _lambda(Executor e, IList args) { return new Procedure((Compiler.List) args[0], args[1]); } private static Expression _apply(Executor e, IList args) { if (args[0].GetType() != typeof(Symbol)) { throw new ApplicationException(); } if (args[1].GetType() != typeof(List)) { throw new ApplicationException(); } Symbol arg0 = (Compiler.Symbol) args[0]; Compiler.List other_args = (Compiler.List) args[1]; return e.EvalFunction(arg0, other_args.expressions); } private static Expression _and(Executor e, IList args) { Expression result = new Compiler.Boolean(false); foreach (var exp in args) { result = e.eval(exp); if (result == new Compiler.Boolean(false)) { return result; } } return result; } private static Expression _or(Executor e, IList args) { Expression result = new Compiler.Boolean(false); foreach (var exp in args) { result = e.eval(exp); if (result != new Compiler.Boolean(false)) { return result; } } return result; } } public class Executor { IEnvironment _environment; Builtins _builtins; BuiltinsLater _builtinsLater; public Executor(IEnvironment environment, Builtins builtins, BuiltinsLater builtinsLater) { _environment = environment; _builtins = builtins; _builtinsLater = builtinsLater; } public Executor(IEnvironment environment) { _environment = environment; _builtins = new Builtins(); _builtinsLater = new BuiltinsLater(); } public Executor() { _environment = new Environment(); _builtins = new Builtins(); _builtinsLater = new BuiltinsLater(); } public IEnvironment environment { get => _environment; } public Builtins builtins { get => _builtins; } public BuiltinsLater builtinsLater { get => _builtinsLater; } public Expression EvalFunction(Symbol fcname, IList args) { if (_environment.Find(fcname.name) is IEnvironment _e) { Expression? first = _e.Get(fcname.name); return new List(new []{first}.ToList()) + new List(args.Select(x => eval(x)).ToList()); } if (_builtins.ContainsKey(fcname.name)) { Function fc = _builtins[fcname.name]; return fc(args.Select(x => eval(x)).ToList()); } if (_builtinsLater.ContainsKey(fcname.name)) { FunctionLater fc = _builtinsLater[fcname.name]; return fc(this, args); } throw new ApplicationException($"Key '{fcname.name}' not found in environment or builtins"); } public Expression eval(Expression expression) { switch (expression) { case Symbol s: return _environment.Find(s.name).Get(s.name); case Compiler.Boolean b: return b; case Integer i: return i; case Compiler.String s: return s; case Compiler.Object o: return o; case Procedure p: return p; case List list: if (list.expressions.Count == 0) { return list; } // do we really want to allow shadowing of builtins? if (list.expressions[0].GetType() == typeof(Symbol)) { return eval(EvalFunction((Symbol) list.expressions[0], list.expressions.Skip(1).ToList())); } if (list.expressions[0].GetType() == typeof(Procedure)) { Procedure procedure = (Procedure) list.expressions[0]; return eval(procedure.Call(this, list.expressions.Skip(1).ToList())); } var l = new List(list.expressions.Select(x => eval(x)).ToList()); if (l.expressions[0].GetType() == typeof(Procedure)) { return eval(l); } return l; } throw new ApplicationException($"Not handled case '{expression}'"); } public Expression eval(Parser p) { return eval(p.parse()); } public Expression eval(StringTokenStream sts) { return eval(new Parser(sts)); } public Expression eval(string p) { return eval(StringTokenStream.generate(p)); } } }