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)))"); } } 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 { 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((Integer a, Integer b) => a == b, x); this["eq?"] = (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[">="] = (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["haskeys"] = _haskeys; this["getitems"] = _getitems; this["invoke"] = _invoke; } 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 = (Object) args.First(); 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 not method {s.Value()}"); } 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()}'"); } return env.Get(s.Name()); 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)); } } }