jellyfin-smart-playlist/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs
2024-11-11 18:00:55 +01:00

423 lines
17 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)))");
}
}
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> {
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<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 not method {s.Value()}");
}
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) {
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));
}
}
}