jellyfin-smart-playlist/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs

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));
}
}
}