using System; using System.Reflection; using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler; namespace Jellyfin.Plugin.SmartPlaylist.Lisp { using Function = Func, Expression>; using FunctionLater = Func, Expression>; public class Environment : Dictionary {} 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["abs"] = _abs; this["append"] = _append; this["begin"] = _begin; this["car"] = _car; this["cdr"] = _cdr; this["cons"] = _cons; this["length"] = _length; this["haskeys"] = _haskeys; this["getitems"] = _getitems; this["invoke"] = _invoke; //this[new Symbol("!=")] = _ne; } 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) { 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 _ne(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 _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 _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["apply"] = _apply; } private static Expression _if(Executor e, IList args) { bool test = ((Compiler.Boolean) e.eval(args[0])).value; return e.eval(args[1 + (test ? 0 : 1)]); } private static Expression _define(Executor e, IList args) { e.environment[((Symbol) args[0]).name] = e.eval(args[1]); return new Compiler.Boolean(false); // NOOP } 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); } } public class Executor { Environment _environment; Builtins _builtins; BuiltinsLater _builtinsLater; public Executor(Environment environment, Builtins builtins, BuiltinsLater builtinsLater) { _environment = environment; _builtins = builtins; _builtinsLater = builtinsLater; } public Executor() { _environment = new Environment(); _builtins = new Builtins(); _builtinsLater = new BuiltinsLater(); } public Environment environment { get => _environment; } public Builtins builtins { get => _builtins; } public BuiltinsLater builtinsLater { get => _builtinsLater; } public Expression EvalFunction(Symbol fcname, IList args) { if (_environment.ContainsKey(fcname.name)) { Expression first = environment[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[s.name]; case Compiler.Boolean b: return b; case Integer i: return i; case Compiler.String s: return s; case List list: // do we really want to allow shadowing of builtins? if (list.expressions[0].GetType() == typeof(Symbol)) { return EvalFunction((Symbol) list.expressions[0], list.expressions.Skip(1).ToList()); } return new List(list.expressions.Select(x => eval(x)).ToList()); } throw new ApplicationException("Not handled case"); } 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)); } } }