241 lines
10 KiB
C#
241 lines
10 KiB
C#
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
|
|
|
|
namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
|
public class EnvironmentEntry {};
|
|
public class Entry: EnvironmentEntry {
|
|
private readonly Expression _expression;
|
|
public Entry(Expression expression) {
|
|
_expression = expression;
|
|
}
|
|
public Expression expression { get => _expression; }
|
|
};
|
|
public class NOOPEntry: Entry {
|
|
public NOOPEntry() : base(new Compiler.Boolean(false)) {}
|
|
}
|
|
public class Function : EnvironmentEntry {
|
|
private readonly Func<IList<Expression>, Expression> _func;
|
|
public Function(Func<IList<Expression>, Expression> func) {
|
|
_func = func;
|
|
}
|
|
public Func<IList<Expression>, Expression> func { get => _func; }
|
|
}
|
|
|
|
public class Environment : Dictionary<string, EnvironmentEntry> {
|
|
public static Environment create() {
|
|
Environment e = new Environment();
|
|
e.Add("+", new Function(op_add));
|
|
e.Add("-", new Function(op_sub));
|
|
e.Add("*", new Function(op_mul));
|
|
e.Add("/", new Function(op_div));
|
|
e.Add("%", new Function(op_rem));
|
|
e.Add(">", new Function(op_gt));
|
|
e.Add("<", new Function(op_lt));
|
|
e.Add(">=", new Function(op_ge));
|
|
e.Add("<=", new Function(op_le));
|
|
e.Add("==", new Function(op_eq));
|
|
e.Add("!=", new Function(op_ne));
|
|
e.Add("abs", new Function(op_abs));
|
|
e.Add("append", new Function(op_append));
|
|
e.Add("apply", new Function(op_apply));
|
|
e.Add("begin", new Function(op_begin));
|
|
return e;
|
|
}
|
|
|
|
private static T op_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 op_add(IList<Expression> args) {
|
|
Expression first = args[0];
|
|
switch (first) {
|
|
case Integer i:
|
|
return op_agg((a, b) => a + b, args.Select(x => (Integer) x).ToList());
|
|
case Compiler.String s:
|
|
return op_agg((a, b) => a + b, args.Select(x => (Compiler.String) x).ToList());
|
|
//case Compiler.List:
|
|
// return op_agg((a, b) => a + b, args.Select(x => (Compiler.List) x).ToList());
|
|
}
|
|
throw new ApplicationException("Don't know how to add these types");
|
|
}
|
|
|
|
private static Expression op_sub(IList<Expression> args) {
|
|
Expression first = args[0];
|
|
switch (first) {
|
|
case Integer i:
|
|
return op_agg((a, b) => a - b, args.Select(x => (Integer) x).ToList());
|
|
}
|
|
throw new ApplicationException("Don't know how to subtract these types");
|
|
}
|
|
|
|
private static Expression op_mul(IList<Expression> args) {
|
|
Expression first = args[0];
|
|
switch (first) {
|
|
case Integer i:
|
|
return op_agg((a, b) => a * b, args.Select(x => (Integer) x).ToList());
|
|
}
|
|
throw new ApplicationException("Don't know how to subtract these types");
|
|
}
|
|
|
|
private static Expression op_div(IList<Expression> args) {
|
|
Expression first = args[0];
|
|
switch (first) {
|
|
case Integer i:
|
|
return op_agg((a, b) => a / b, args.Select(x => (Integer) x).ToList());
|
|
}
|
|
throw new ApplicationException("Don't know how to subtract these types");
|
|
}
|
|
|
|
private static Expression op_rem(IList<Expression> args) {
|
|
Expression first = args[0];
|
|
switch (first) {
|
|
case Integer i:
|
|
return op_agg((a, b) => a % b, args.Select(x => (Integer) x).ToList());
|
|
}
|
|
throw new ApplicationException("Don't know how to subtract these types");
|
|
}
|
|
|
|
private static E op_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 op_gt(IList<Expression> args) {
|
|
Expression first = args[0];
|
|
switch (first) {
|
|
case Integer i:
|
|
return op_cmp((a, b) => a > b, args.Select(x => (Integer) x).ToList());
|
|
}
|
|
throw new ApplicationException("Don't know how to subtract these types");
|
|
}
|
|
|
|
private static Expression op_lt(IList<Expression> args) {
|
|
Expression first = args[0];
|
|
switch (first) {
|
|
case Integer i:
|
|
return op_cmp((a, b) => a < b, args.Select(x => (Integer) x).ToList());
|
|
}
|
|
throw new ApplicationException("Don't know how to subtract these types");
|
|
}
|
|
|
|
private static Expression op_ge(IList<Expression> args) {
|
|
Expression first = args[0];
|
|
switch (first) {
|
|
case Integer i:
|
|
return op_cmp((a, b) => a >= b, args.Select(x => (Integer) x).ToList());
|
|
}
|
|
throw new ApplicationException("Don't know how to subtract these types");
|
|
}
|
|
|
|
private static Expression op_le(IList<Expression> args) {
|
|
Expression first = args[0];
|
|
switch (first) {
|
|
case Integer i:
|
|
return op_cmp((a, b) => a <= b, args.Select(x => (Integer) x).ToList());
|
|
}
|
|
throw new ApplicationException("Don't know how to subtract these types");
|
|
}
|
|
|
|
private static Expression op_eq(IList<Expression> args) {
|
|
Expression first = args[0];
|
|
switch (first) {
|
|
case Integer i:
|
|
return op_cmp((a, b) => a == b, args.Select(x => (Integer) x).ToList());
|
|
}
|
|
throw new ApplicationException("Don't know how to subtract these types");
|
|
}
|
|
|
|
private static Expression op_ne(IList<Expression> args) {
|
|
Expression first = args[0];
|
|
switch (first) {
|
|
case Integer i:
|
|
return op_cmp((a, b) => a != b, args.Select(x => (Integer) x).ToList());
|
|
}
|
|
throw new ApplicationException("Don't know how to subtract these types");
|
|
}
|
|
|
|
private static Expression op_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("Don't know how to subtract these types");
|
|
}
|
|
|
|
private static Expression op_append(IList<Expression> args) {
|
|
Expression first = args[0];
|
|
switch (first) {
|
|
case List l:
|
|
return l + new List(args);
|
|
}
|
|
throw new ApplicationException("Don't know how to subtract these types");
|
|
}
|
|
|
|
private static Expression op_apply(IList<Expression> args) {
|
|
IList<EnvironmentEntry> e_list = (IList<EnvironmentEntry>) args.Select(x => new Entry(x)).ToList();
|
|
if (e_list.Count != 2) {
|
|
throw new ApplicationException("Expected exactly two arguments");
|
|
}
|
|
if (e_list[0].GetType() != typeof(Function)) {
|
|
throw new ApplicationException("Expected first argument to be a function to apply");
|
|
}
|
|
Function f = (Function) e_list[0];
|
|
IList<Expression> new_args = e_list.Skip(1).Select(x => ((Entry) x).expression).ToList();
|
|
return f.func(args);
|
|
}
|
|
|
|
private static Expression op_begin(IList<Expression> args) {
|
|
return args.Last();
|
|
}
|
|
|
|
public EnvironmentEntry eval(Expression expression) {
|
|
switch (expression) {
|
|
case Symbol s:
|
|
return this[s.name];
|
|
case Integer i:
|
|
return new Entry(i);
|
|
case Compiler.String s_:
|
|
return new Entry(s_);
|
|
case List list:
|
|
if (list.expressions[0].GetType() == typeof(Symbol)) {
|
|
if (((Symbol) list.expressions[0]).name.Equals("if")) {
|
|
Compiler.Boolean test = (Compiler.Boolean) ((Entry) eval(list.expressions[1])).expression;
|
|
return eval(list.expressions[2 + (test.value? 0 : 1)]);
|
|
}
|
|
if (((Symbol) list.expressions[0]).name.Equals("define")) {
|
|
Symbol test;
|
|
if (list.expressions[1].GetType() == typeof(Symbol)) {
|
|
test = (Symbol) list.expressions[1];
|
|
} else {
|
|
test = (Symbol) ((Entry) eval(list.expressions[1])).expression;
|
|
}
|
|
this[test.name] = eval(list.expressions[2]);
|
|
return new NOOPEntry();
|
|
}
|
|
}
|
|
IList<EnvironmentEntry> e_list = list.expressions.Select(x => eval(x)).ToList();
|
|
if (e_list.Count > 0 && e_list[0].GetType() == typeof(Function)) {
|
|
Function f = (Function) e_list[0];
|
|
IList<Expression> args = e_list.Skip(1).Select(x => ((Entry) x).expression).ToList();
|
|
return new Entry(f.func(args));
|
|
}
|
|
return new Entry(new List(list.expressions.Select(x => ((Entry) eval(x)).expression).ToList()));
|
|
}
|
|
throw new ApplicationException("Not handled case");
|
|
}
|
|
public EnvironmentEntry eval(Parser p) {
|
|
return eval(p.parse());
|
|
}
|
|
public EnvironmentEntry eval(StringTokenStream sts) {
|
|
return eval(new Parser(sts));
|
|
}
|
|
public EnvironmentEntry eval(string p) {
|
|
return eval(StringTokenStream.generate(p));
|
|
}
|
|
}
|
|
}
|