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

321 lines
13 KiB
C#

using System;
using System.Reflection;
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
using Function = Func<IList<Expression>, Expression>;
using FunctionLater = Func<Executor, IList<Expression>, Expression>;
public class Environment : Dictionary<string, Expression> {}
public class Builtins : Dictionary<string, Function> {
public Builtins() : base() {
this["+"] = _add;
this["-"] = _sub;
this["*"] = _mul;
this["/"] = _div;
this["%"] = _mod;
this[">"] = _gt;
this["<"] = _lt;
this[">="] = _ge;
this["<="] = _le;
this["eq?"] = _eq;
this["="] = _eq;
this["abs"] = _abs;
this["append"] = _append;
this["begin"] = _begin;
this["car"] = _car;
this["cdr"] = _cdr;
this["cons"] = _cons;
this["length"] = _length;
this["haskeys"] = _haskeys;
this["getitems"] = _getitems;
this["invoke"] = _invoke;
//this[new Symbol("!=")] = _ne;
}
private static T _agg<T>(Func<T, T, T> op, IList<T> args) {
T agg = args[0];
foreach (var arg in args.Skip(1)) {
agg = op(agg, arg);
}
return agg;
}
private static Expression _add(IList<Expression> args) {
Expression first = args[0];
switch (first) {
case Integer i:
return _agg((a, b) => a + b, args.Select(x => (Integer) x).ToList());
case Compiler.String s:
return _agg((a, b) => a + b, args.Select(x => (Compiler.String) x).ToList());
}
throw new ApplicationException();
}
private static Expression _sub(IList<Expression> args) {
Expression first = args[0];
switch (first) {
case Integer i:
return _agg((a, b) => a - b, args.Select(x => (Integer) x).ToList());
}
throw new ApplicationException();
}
private static Expression _mul(IList<Expression> args) {
Expression first = args[0];
switch (first) {
case Integer i:
return _agg((a, b) => a * b, args.Select(x => (Integer) x).ToList());
}
throw new ApplicationException();
}
private static Expression _div(IList<Expression> args) {
Expression first = args[0];
switch (first) {
case Integer i:
return _agg((a, b) => a / b, args.Select(x => (Integer) x).ToList());
}
throw new ApplicationException();
}
private static Expression _mod(IList<Expression> args) {
Expression first = args[0];
switch (first) {
case Integer i:
return _agg((a, b) => a % b, args.Select(x => (Integer) x).ToList());
}
throw new ApplicationException();
}
private static E _cmp<T, E>(Func<T, T, E> op, IList<T> args) {
T first = args[0];
T second = args[1];
return op(first, second);
}
private static Expression _gt(IList<Expression> args) {
Expression first = args[0];
switch (first) {
case Integer i:
return _cmp((a, b) => a > b, args.Select(x => (Integer) x).ToList());
}
throw new ApplicationException();
}
private static Expression _lt(IList<Expression> args) {
Expression first = args[0];
switch (first) {
case Integer i:
return _cmp((a, b) => a < b, args.Select(x => (Integer) x).ToList());
}
throw new ApplicationException();
}
private static Expression _ge(IList<Expression> args) {
Expression first = args[0];
switch (first) {
case Integer i:
return _cmp((a, b) => a >= b, args.Select(x => (Integer) x).ToList());
}
throw new ApplicationException();
}
private static Expression _le(IList<Expression> args) {
Expression first = args[0];
switch (first) {
case Integer i:
return _cmp((a, b) => a <= b, args.Select(x => (Integer) x).ToList());
}
throw new ApplicationException();
}
private static Expression _eq(IList<Expression> args) {
Expression first = args[0];
switch (first) {
case Integer i:
return _cmp((a, b) => a == b, args.Select(x => (Integer) x).ToList());
}
throw new ApplicationException();
}
private static Expression _ne(IList<Expression> args) {
Expression first = args[0];
switch (first) {
case Integer i:
return _cmp((a, b) => a != b, args.Select(x => (Integer) x).ToList());
}
throw new ApplicationException();
}
private static Expression _abs(IList<Expression> args) {
Expression first = args[0];
switch (first) {
case Integer i:
return i.value >= 0 ? i : new Integer(-i.value);
}
throw new ApplicationException();
}
private static Expression _append(IList<Expression> args) {
Expression first = args[0];
switch (first) {
case List l:
return l + new List(args);
}
throw new ApplicationException();
}
private static Expression _begin(IList<Expression> args) {
return args.Last();
}
private static Expression _car(IList<Expression> args) {
return ((List) args.First()).expressions.First();
}
private static Expression _cdr(IList<Expression> args) {
return new List(((List) args.First()).expressions.Skip(1).ToList());
}
private static Expression _cons(IList<Expression> args) {
switch (args[1]) {
case Compiler.List other_list:
return (new Compiler.List(new []{args[0]}.ToList()) + new Compiler.List(other_list.expressions));
case Atom other_atom:
return new Compiler.List(new[]{args[0], args[1]}.ToList());
}
throw new ApplicationException();
}
private static Expression _length(IList<Expression> args) {
return new Integer(((Compiler.List)args[0]).expressions.Count());
}
private static Expression _haskeys(IList<Expression> args) {
Compiler.Object o = (Compiler.Object) args[0];
foreach (var e in args.Skip(1)) {
Compiler.String s = (Compiler.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 new Compiler.Boolean(false);
}
return new Compiler.Boolean(true);
}
private static Expression _getitems(IList<Expression> args) {
Compiler.Object o = (Compiler.Object) args[0];
IList<Expression> r = new List<Expression>();
foreach (var e in args.Skip(1)) {
Compiler.String s = (Compiler.String) e;
PropertyInfo? pi = o.value.GetType().GetProperty(s.value);
if (pi != null) {
r.Add(Compiler.Object.FromBase(pi.GetValue(o.value)));
continue;
}
FieldInfo? fi = o.value.GetType().GetField(s.value);
if (fi != null) {
r.Add(Compiler.Object.FromBase(fi.GetValue(o.value)));
continue;
}
throw new ApplicationException($"{o.value} has no property or field {s.value}");
}
return new Compiler.List(r);
}
private static Expression _invoke(IList<Expression> args) {
Compiler.Object o = (Compiler.Object) args[0];
Compiler.String s = (Compiler.String) args[1];
Compiler.List l = (Compiler.List) args[2];
IList<Expression> r = new List<Expression>();
MethodInfo? mi = o.value.GetType().GetMethod(s.value);
if (mi == null) {
throw new ApplicationException($"{o.value} has not method {s.value}");
}
return Compiler.Object.FromBase(mi.Invoke(o.value, (object?[]?) l.Inner()));
}
}
public class BuiltinsLater : Dictionary<string, FunctionLater> {
public BuiltinsLater() : base() {
this["if"] = _if;
this["define"] = _define;
this["apply"] = _apply;
}
private static Expression _if(Executor e, IList<Expression> args) {
bool test = ((Compiler.Boolean) e.eval(args[0])).value;
return e.eval(args[1 + (test ? 0 : 1)]);
}
private static Expression _define(Executor e, IList<Expression> args) {
e.environment[((Symbol) args[0]).name] = e.eval(args[1]);
return new Compiler.Boolean(false); // NOOP
}
private static Expression _apply(Executor e, IList<Expression> args) {
if (args[0].GetType() != typeof(Symbol)) {
throw new ApplicationException();
}
if (args[1].GetType() != typeof(List)) {
throw new ApplicationException();
}
Symbol arg0 = (Compiler.Symbol) args[0];
Compiler.List other_args = (Compiler.List) args[1];
return e.EvalFunction(arg0, other_args.expressions);
}
}
public class Executor {
Environment _environment;
Builtins _builtins;
BuiltinsLater _builtinsLater;
public Executor(Environment environment, Builtins builtins, BuiltinsLater builtinsLater) {
_environment = environment;
_builtins = builtins;
_builtinsLater = builtinsLater;
}
public Executor() {
_environment = new Environment();
_builtins = new Builtins();
_builtinsLater = new BuiltinsLater();
}
public Environment environment { get => _environment; }
public Builtins builtins { get => _builtins; }
public BuiltinsLater builtinsLater { get => _builtinsLater; }
public Expression EvalFunction(Symbol fcname, IList<Expression> args) {
if (_environment.ContainsKey(fcname.name)) {
Expression first = environment[fcname.name];
return new List(new []{first}.ToList()) + new List(args.Select(x => eval(x)).ToList());
}
if (_builtins.ContainsKey(fcname.name)) {
Function fc = _builtins[fcname.name];
return fc(args.Select(x => eval(x)).ToList());
}
if (_builtinsLater.ContainsKey(fcname.name)) {
FunctionLater fc = _builtinsLater[fcname.name];
return fc(this, args);
}
throw new ApplicationException($"Key '{fcname.name}' not found in environment or builtins");
}
public Expression eval(Expression expression) {
switch (expression) {
case Symbol s:
return _environment[s.name];
case Compiler.Boolean b:
return b;
case Integer i:
return i;
case Compiler.String s:
return s;
case List list:
// do we really want to allow shadowing of builtins?
if (list.expressions[0].GetType() == typeof(Symbol)) {
return EvalFunction((Symbol) list.expressions[0], list.expressions.Skip(1).ToList());
}
return new List(list.expressions.Select(x => eval(x)).ToList());
}
throw new ApplicationException("Not handled case");
}
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));
}
}
}