419 lines
17 KiB
C#
419 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()}'");
|
|
}
|
|
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));
|
|
}
|
|
}
|
|
}
|