554 lines
23 KiB
C#
554 lines
23 KiB
C#
using System.Reflection;
|
|
|
|
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
|
|
|
|
namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
|
using Function = Func<IEnumerable<Expression>, Expression>;
|
|
using FunctionLater = Func<Executor, IEnumerable<Expression>, Expression>;
|
|
|
|
public interface IEnvironment<K, V> {
|
|
public V? Get(K k);
|
|
public void Set(K k, V v);
|
|
public IEnvironment<K, V>? Find(K k);
|
|
public IEnvironment<K, V> Parent(bool recursive);
|
|
}
|
|
|
|
public class Environment : Dictionary<string, Expression>, IEnvironment<string, Expression> {
|
|
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<string, Expression>? Find(string k) {
|
|
if (ContainsKey(k)) {
|
|
return this;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public IEnvironment<string, Expression> 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)))
|
|
"""
|
|
);
|
|
this["rand"] = e.eval(
|
|
"""
|
|
(lambda
|
|
(. a)
|
|
(cond
|
|
((null a) (random))
|
|
((null (cdr a)) (% (random) (car a)))
|
|
(t (+
|
|
(car a)
|
|
(%
|
|
(random)
|
|
(-
|
|
(car (cdr a))
|
|
(car a)))))))
|
|
"""
|
|
);
|
|
this["shuf"] = new Symbol("shuffle");
|
|
}
|
|
}
|
|
|
|
public class SubEnvironment : Dictionary<string, Expression>, IEnvironment<string, Expression> {
|
|
private IEnvironment<string, Expression> _super;
|
|
public SubEnvironment(IEnvironment<string, Expression> 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<string, Expression>? Find(string k) {
|
|
if (ContainsKey(k)) {
|
|
return this;
|
|
}
|
|
return _super.Find(k);
|
|
}
|
|
|
|
public IEnvironment<string, Expression> Parent(bool recursive) {
|
|
if (recursive) {
|
|
return this._super.Parent(recursive);
|
|
}
|
|
return this._super;
|
|
}
|
|
}
|
|
|
|
public class Builtins : Dictionary<string, Function> {
|
|
|
|
private static Dictionary<string, Type?> ResolvedTypes = new Dictionary<string, Type?>();
|
|
private Random Random;
|
|
|
|
public Builtins() : base() {
|
|
Random = new Random();
|
|
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;
|
|
|
|
this["random"] = (x) => new Lisp.Integer(Random.Next());
|
|
this["shuffle"] = (x) => {
|
|
var newx = ((Lisp.Cons) x.First()).ToList().ToArray();
|
|
Random.Shuffle<Expression>(newx);
|
|
return Lisp.Cons.FromList(newx);
|
|
};
|
|
}
|
|
private static T _agg<T>(Func<T, T, T> op, IEnumerable<Expression> 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<T, E>(Func<T, T, E> op, IEnumerable<Expression> args) where T : Expression where E : Expression {
|
|
return op((T) args.First(), (T) args.Skip(1).First());
|
|
}
|
|
private static Expression _atom(IEnumerable<Expression> args) {
|
|
return (args.First() is Atom) ? Boolean.TRUE : Boolean.FALSE;
|
|
}
|
|
private static Expression _eq(IEnumerable<Expression> args) {
|
|
return args.First().Equals(args.Skip(1).First()) ? Boolean.TRUE : Boolean.FALSE;
|
|
}
|
|
private static Expression _car(IEnumerable<Expression> args) {
|
|
return ((Cons)args.First()).Item1;
|
|
}
|
|
private static Expression _cdr(IEnumerable<Expression> args) {
|
|
return ((Cons)args.First()).Item2;
|
|
}
|
|
private static Expression _cons(IEnumerable<Expression> args) {
|
|
return new Cons(args.First(), args.Skip(1).First());
|
|
}
|
|
private static Expression _begin(IEnumerable<Expression> args) {
|
|
return args.Last();
|
|
}
|
|
private static Expression _haskeys(IEnumerable<Expression> 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<Expression> args) {
|
|
Object o = new Object(((IInner) args.First()).Inner());
|
|
IList<Expression> r = new List<Expression>();
|
|
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<Expression> args) {
|
|
Object o = new Object(((IInner) args.First()).Inner());
|
|
String s = (String) args.Skip(1).First();
|
|
IEnumerable<Expression> l;
|
|
if (args.Skip(2).First() is Boolean lb && lb == Boolean.FALSE) {
|
|
l = new List<Expression>();
|
|
} 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<Expression, object>(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<Expression> r = new List<Expression>();
|
|
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) {
|
|
if (ResolvedTypes.ContainsKey(name)) {
|
|
return ResolvedTypes[name];
|
|
}
|
|
var t = Type.GetType(
|
|
name,
|
|
(name) => { return AppDomain.CurrentDomain.GetAssemblies().Where(z => (z.FullName != null) && z.FullName.StartsWith(name.FullName)).FirstOrDefault(); },
|
|
null,
|
|
true
|
|
);
|
|
ResolvedTypes[name] = t;
|
|
return t;
|
|
}
|
|
|
|
private static Expression _invoke_generic(IEnumerable<Expression> args) {
|
|
Object o = new Object(((IInner) args.First()).Inner());
|
|
String s = (String) args.Skip(1).First();
|
|
IEnumerable<Expression> l;
|
|
if (args.Skip(2).First() is Boolean lb && lb == Boolean.FALSE) {
|
|
l = new List<Expression>();
|
|
} 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<Type> types;
|
|
if (args.Skip(3).First() is Boolean lb_ && lb_ == Boolean.FALSE) {
|
|
types = new List<Type>();
|
|
} 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<Expression, object>(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<Expression> r = new List<Expression>();
|
|
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<string, FunctionLater> {
|
|
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<Expression> args) {
|
|
return args.First();
|
|
}
|
|
private static Expression _eval(Executor e, IEnumerable<Expression> args) {
|
|
return e.eval(e.eval(args.First()));
|
|
}
|
|
private static Expression _cond(Executor e, IEnumerable<Expression> 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<Expression> 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<Expression> 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<Expression> 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<Expression> 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<Expression> args) {
|
|
IEnumerable<Symbol> 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<Symbol>(); }
|
|
else {
|
|
throw new ApplicationException("");
|
|
}
|
|
return new Procedure(proc_args, args.Skip(1).First(), true);
|
|
}
|
|
private static Expression _lambda_star(Executor e, IEnumerable<Expression> args) {
|
|
IEnumerable<Symbol> 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<Symbol>(); }
|
|
else {
|
|
throw new ApplicationException("");
|
|
}
|
|
return new Procedure(proc_args, args.Skip(1).First(), false);
|
|
}
|
|
private static Expression _apply(Executor e, IEnumerable<Expression> args) {
|
|
return e.eval(new Cons(args.First(), e.eval(args.Skip(1).First())));
|
|
}
|
|
}
|
|
|
|
public class Executor {
|
|
IEnvironment<string, Expression> _environment;
|
|
Builtins _builtins;
|
|
BuiltinsLater _builtinsLater;
|
|
public Executor(IEnvironment<string, Expression> environment, Builtins builtins, BuiltinsLater builtinsLater) {
|
|
_environment = environment;
|
|
_builtins = builtins;
|
|
_builtinsLater = builtinsLater;
|
|
}
|
|
public Executor(IEnvironment<string, Expression> environment) {
|
|
_environment = environment;
|
|
_builtins = new Builtins();
|
|
_builtinsLater = new BuiltinsLater();
|
|
}
|
|
public Executor() {
|
|
_environment = new Environment();
|
|
_builtins = new Builtins();
|
|
_builtinsLater = new BuiltinsLater();
|
|
}
|
|
|
|
public IEnvironment<string, Expression> environment { get => _environment; }
|
|
public Builtins builtins { get => _builtins; }
|
|
public BuiltinsLater builtinsLater { get => _builtinsLater; }
|
|
|
|
public Expression? EvalFunction(Symbol fcname, IEnumerable<Expression> 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<string, Expression> env) {
|
|
if (builtins.ContainsKey(s.Name()) || builtinsLater.ContainsKey(s.Name())) {
|
|
return s;
|
|
}
|
|
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));
|
|
}
|
|
}
|
|
}
|