using System.Reflection; using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler; namespace Jellyfin.Plugin.SmartPlaylist.Lisp { using Function = Func, Expression>; using FunctionLater = Func, Expression>; public interface IEnvironment { public V? Get(K k); public void Set(K k, V v); public IEnvironment? Find(K k); public IEnvironment Parent(bool recursive); } 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 IEnvironment Parent(bool recursive) { return this; } } public class DefaultEnvironment: Environment { public DefaultEnvironment() { var e = new Executor(); this["null"] = new Symbol("not"); this["list"] = e.eval("(lambda (. args) args)"); this["find"] = e.eval("(lambda (item list_) (if (null list_) nil (if (= item (car list_)) (car list_) (find item (cdr list_)))))"); this["map"] = e.eval("(lambda (fc l) (if (null l) nil (cons (fc (car l)) (map fc (cdr l)))))"); this["fold"] = e.eval("(lambda (fc i l) (if (null l) i (fold fc (fc i (car l)) (cdr l))))"); this["any"] = e.eval("(lambda (fc l) (apply or (map fc l)))"); this["all"] = e.eval("(lambda (fc l) (apply and (map fc l)))"); this["append"] = e.eval("(lambda (l i) (if (null l) i (cons (car l) (append (cdr l) i))))"); this["qsort"] = e.eval( """ (lambda (fc list00) (let (getpivot (lambda (list0) (car list0))) (split (lambda (list0 pivot fc h0 h1) (cond ((null list0) (list list0 pivot fc h0 h1)) ((fc (car list0) pivot) (split (cdr list0) pivot fc h0 (cons (car list0) h1))) (t (split (cdr list0) pivot fc (cons (car list0) h0) h1))))) (sort (lambda (fc list0) (cond ((null list0) nil) ((null (cdr list0)) list0) (t (let* (halves (split list0 (getpivot list0) fc nil nil)) (h0 (car (cdr (cdr (cdr halves))))) (h1 (car (cdr (cdr (cdr (cdr halves)))))) (append (sort fc h0) (sort fc h1))))))) (sort fc list00))) """ ); } } 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 IEnvironment Parent(bool recursive) { if (recursive) { return this._super.Parent(recursive); } return this._super; } } public class Builtins : Dictionary { private static Dictionary AssemblyMapping = new Dictionary(); public Builtins() : base() { this["atom"] = _atom; this["eq"] = _eq; this["car"] = _car; this["cdr"] = _cdr; this["cons"] = _cons; this["begin"] = _begin; this["+"] = (x) => _agg((Integer a, Integer b) => a + b, x); this["-"] = (x) => _agg((Integer a, Integer b) => a - b, x); this["*"] = (x) => _agg((Integer a, Integer b) => a * b, x); this["/"] = (x) => _agg((Integer a, Integer b) => a / b, x); this["%"] = (x) => _agg((Integer a, Integer b) => a % b, x); this["="] = (x) => _cmp((Atom a, Atom b) => (a == b)? Boolean.TRUE : Boolean.FALSE, x); this["!="] = (x) => _cmp((Atom a, Atom b) => (a != b)? Boolean.TRUE : Boolean.FALSE, x); this["<"] = (x) => _cmp((Integer a, Integer b) => a < b, x); this["<="] = (x) => _cmp((Integer a, Integer b) => a <= b, x); this[">"] = (x) => _cmp((Integer a, Integer b) => a > b, x); this[">="] = (x) => _cmp((Integer a, Integer b) => a >= b, x); this["not"] = (x) => { return (x.First() == Boolean.FALSE) ? Boolean.TRUE : Boolean.FALSE; }; this["string="] = (x) => _cmp((String a, String b) => a == b, x); this["string!="] = (x) => _cmp((String a, String b) => a != b, x); this["string>"] = (x) => _cmp((String a, String b) => a > b, x); this["string>="] = (x) => _cmp((String a, String b) => a >= b, x); this["string<"] = (x) => _cmp((String a, String b) => a < b, x); this["string<="] = (x) => _cmp((String a, String b) => a <= b, x); this["haskeys"] = _haskeys; this["getitems"] = _getitems; this["invoke"] = _invoke; this["invoke-generic"] = _invoke_generic; } private static T _agg(Func op, IEnumerable args) where T : Expression { T agg = (T) args.First(); foreach (var arg in args.Skip(1)) { var arg_ = (T) arg; agg = op(agg, arg_); } return agg; } private static E _cmp(Func op, IEnumerable args) where T : Expression where E : Expression { return op((T) args.First(), (T) args.Skip(1).First()); } private static Expression _atom(IEnumerable args) { return (args.First() is Atom) ? Boolean.TRUE : Boolean.FALSE; } private static Expression _eq(IEnumerable args) { return args.First().Equals(args.Skip(1).First()) ? Boolean.TRUE : Boolean.FALSE; } private static Expression _car(IEnumerable args) { return ((Cons)args.First()).Item1; } private static Expression _cdr(IEnumerable args) { return ((Cons)args.First()).Item2; } private static Expression _cons(IEnumerable args) { return new Cons(args.First(), args.Skip(1).First()); } private static Expression _begin(IEnumerable args) { return args.Last(); } private static Expression _haskeys(IEnumerable args) { Object o = new Object(((IInner) args.First()).Inner()); foreach (var e in args.Skip(1)) { String s = (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 Boolean.FALSE; } return Boolean.TRUE; } private static Expression _getitems(IEnumerable args) { Object o = new Object(((IInner) args.First()).Inner()); IList r = new List(); foreach (var e in args.Skip(1)) { String s = (String) e; PropertyInfo? pi = o.Value().GetType().GetProperty(s.Value()); if (pi != null) { r.Add(Object.FromBase(pi.GetValue(o.Value()))); continue; } FieldInfo? fi = o.Value().GetType().GetField(s.Value()); if (fi != null) { r.Add(Object.FromBase(fi.GetValue(o.Value()))); continue; } throw new ApplicationException($"{o.Value()} has no property or field {s.Value()}"); } return Cons.FromList(r); } private static Expression _invoke(IEnumerable args) { Object o = new Object(((IInner) args.First()).Inner()); String s = (String) args.Skip(1).First(); IEnumerable l; if (args.Skip(2).First() is Boolean lb && lb == Boolean.FALSE) { l = new List(); } else if (args.Skip(2).First() is Cons lc) { l = lc.ToList(); } else { throw new ApplicationException($"Expected a list of arguments, got {args.Skip(2).First()}"); } object[]? l_ = l.Select(x => { switch (x) { case Integer s: return s.Value(); case Boolean b: return b.Value(); case String s: return s.Value(); case Object o: return o.Value(); case Cons c: return c.ToList().ToList(); } throw new ApplicationException($"Unhandled value {x} (type {x.GetType()})"); }).ToArray(); Type[] l_types = l_.Select( x => { return x.GetType(); }).ToArray(); IList r = new List(); MethodInfo? mi = o.Value().GetType().GetMethod(s.Value(), l_types); if (mi == null) { throw new ApplicationException($"{o.Value()} has no method {s.Value()}"); } return Object.FromBase(mi.Invoke(o.Value(), l_)); } private static Type? GetType(string name) { return Type.GetType( name, (name) => { return AppDomain.CurrentDomain.GetAssemblies().Where(z => (z.FullName != null) && z.FullName.StartsWith(name.FullName)).FirstOrDefault(); }, null, true ); } private static Expression _invoke_generic(IEnumerable args) { Object o = new Object(((IInner) args.First()).Inner()); String s = (String) args.Skip(1).First(); IEnumerable l; if (args.Skip(2).First() is Boolean lb && lb == Boolean.FALSE) { l = new List(); } else if (args.Skip(2).First() is Cons lc) { l = lc.ToList(); } else { throw new ApplicationException($"Expected a list of arguments, got {args.Skip(2).First()}"); } IEnumerable types; if (args.Skip(3).First() is Boolean lb_ && lb_ == Boolean.FALSE) { types = new List(); } else if (args.Skip(3).First() is Cons lc) { types = lc.ToList().Select(x => GetType(((String) x).Value())).ToList(); } else { throw new ApplicationException($"Expected a list of arguments, got {args.Skip(3).First()}"); } object[]? l_ = l.Select(x => { switch (x) { case Integer s: return s.Value(); case Boolean b: return b.Value(); case String s: return s.Value(); case Object o: return o.Value(); case Cons c: return c.ToList().ToList(); } throw new ApplicationException($"Unhandled value {x} (type {x.GetType()})"); }).ToArray(); Type[] l_types = l_.Select( x => { return x.GetType(); }).ToArray(); IList r = new List(); MethodInfo? mi = o.Value().GetType().GetMethod(s.Value(), l_types); if (mi == null) { throw new ApplicationException($"{o.Value()} has no method {s.Value()}"); } mi = mi.MakeGenericMethod(types.ToArray()); return Object.FromBase(mi.Invoke(o.Value(), l_)); } } public class BuiltinsLater : Dictionary { public BuiltinsLater() : base() { this["quote"] = _quote; this["eval"] = _eval; this["cond"] = _cond; this["if"] = _if; this["define"] = _define; this["let"] = _let; this["let*"] = _let_star; this["lambda"] = _lambda; this["lambda*"] = _lambda_star; this["apply"] = _apply; this["and"] = (e, x) => { Expression? r = null; foreach (var xi in x) { r = e.eval(xi); if (r == Boolean.FALSE) { return r; } } if (r is null) { return Boolean.FALSE; } return r; }; this["or"] = (e, x) => { foreach (var xi in x) { var r = e.eval(xi); if (r != Boolean.FALSE) { return r; } } return Boolean.FALSE; }; } private static Expression _quote(Executor e, IEnumerable args) { return args.First(); } private static Expression _eval(Executor e, IEnumerable args) { return e.eval(e.eval(args.First())); } private static Expression _cond(Executor e, IEnumerable args) { foreach (var a in args) { if (a is Cons a_cons) { var a_ = a_cons.ToList(); if (!e.eval(a_.First()).Equals(Boolean.FALSE)) { return e.eval(a_.Skip(1).First()); } } else { throw new ApplicationException($"Incorrect arguments to cond, expected list: {args}"); } } return Boolean.FALSE; } private static Expression _if(Executor e, IEnumerable args) { if (e.eval(args.First()).Equals(Boolean.FALSE)) { return e.eval(args.Skip(2).First()); } return e.eval(args.Skip(1).First()); } private static Expression _define(Executor e, IEnumerable args) { Symbol refname = (Symbol) args.First(); e.environment.Parent(true).Set(refname.Name(), args.Skip(1).Select(x => e.eval(x)).First()); return Boolean.TRUE; } private static Expression _let_star(Executor e, IEnumerable args) { Executor new_e = new Executor(new SubEnvironment(e.environment), e.builtins, e.builtinsLater); foreach (var pair in args.SkipLast(1)) { if (pair is not Cons pair_cons) { throw new ApplicationException("No expression for let*"); } Symbol refname = (Symbol) pair_cons.Item1; Expression exp = ((Cons) pair_cons.Item2).Item1; new_e.environment.Set(refname.Name(), new_e.eval(exp)); } return new_e.eval(args.Last()); } private static Expression _let(Executor e, IEnumerable args) { Executor new_e = new Executor(new SubEnvironment(e.environment), e.builtins, e.builtinsLater); List<(Symbol, Expression)> vars = new List<(Symbol, Expression)>(); foreach (var pair in args.SkipLast(1)) { if (pair is not Cons pair_cons) { throw new ApplicationException(""); } Symbol refname = (Symbol) pair_cons.Item1; Expression exp_ = ((Cons) pair_cons.Item2).Item1; vars.Add((refname, e.eval(exp_))); } foreach (var pair in vars) { new_e.environment.Set(pair.Item1.Name(), pair.Item2); } return new_e.eval(args.Last()); } private static Expression _lambda(Executor e, IEnumerable args) { IEnumerable proc_args; if (args.First() is Cons proc_args_) { proc_args = proc_args_.ToList().Select(x => (Symbol) x); } else if (args.First() == Boolean.FALSE) { proc_args = new List(); } else { throw new ApplicationException(""); } return new Procedure(proc_args, args.Skip(1).First(), true); } private static Expression _lambda_star(Executor e, IEnumerable args) { IEnumerable proc_args; if (args.First() is Cons proc_args_) { proc_args = proc_args_.ToList().Select(x => (Symbol) x); } else if (args.First() == Boolean.FALSE) { proc_args = new List(); } else { throw new ApplicationException(""); } return new Procedure(proc_args, args.Skip(1).First(), false); } private static Expression _apply(Executor e, IEnumerable args) { return e.eval(new Cons(args.First(), e.eval(args.Skip(1).First()))); } } 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, IEnumerable args) { if (builtins.ContainsKey(fcname.Name())) { return builtins[fcname.Name()](args.Select(x => eval(x)).ToList()); // call ToList for sideeffect } if (builtinsLater.ContainsKey(fcname.Name())) { return builtinsLater[fcname.Name()](this, args); } return null; } public Expression eval(Expression expression) { switch (expression) { case Symbol s: if (_environment.Find(s.Name()) is not IEnvironment env) { throw new ApplicationException($"Could not find '{s.Name()}'"); } var r_ = env.Get(s.Name()); if (r_ is null) { throw new ApplicationException($"Could not find '{s.Name()}'"); } return r_; case Boolean b: return b; case Integer i: return i; case String s: return s; case Object o: return o; case Procedure p: return p; case Cons cons: var l = cons.ToList(); if (cons.Item1 is Symbol cons_item1_symbol) { Expression? r = EvalFunction(cons_item1_symbol, l.Skip(1)); if (r is not null) { return r; } } var eval_Item1 = eval(cons.Item1); if (eval_Item1 is Symbol eval_item1_symbol1) { Expression? r = EvalFunction(eval_item1_symbol1, l.Skip(1)); if (r is not null) { return r; } } if (eval_Item1 is Procedure eval_item1_procedure) { return eval_item1_procedure.Call(this, l.Skip(1).Select(x => x).ToList()); } throw new ApplicationException($"Not handled case (type = {eval_Item1.GetType()}) '{cons}'"); } 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)); } } }