Implement object type, fix parsing bug regarding strings.
This commit is contained in:
parent
3365521283
commit
1f022d7f88
7 changed files with 353 additions and 166 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,4 +1,2 @@
|
||||||
**/obj/
|
**/obj/
|
||||||
**/bin/
|
**/bin/
|
||||||
cache/
|
|
||||||
config/
|
|
||||||
|
|
|
@ -18,11 +18,14 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
static abstract T operator %(T left, T right);
|
static abstract T operator %(T left, T right);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IComparable<T, E> where T : IComparable<T, E> {
|
interface ISortable<T, E> where T : ISortable<T, E> {
|
||||||
static abstract E operator >(T left, T right);
|
static abstract E operator >(T left, T right);
|
||||||
static abstract E operator <(T left, T right);
|
static abstract E operator <(T left, T right);
|
||||||
static abstract E operator >=(T left, T right);
|
static abstract E operator >=(T left, T right);
|
||||||
static abstract E operator <=(T left, T right);
|
static abstract E operator <=(T left, T right);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IComparable<T, E> where T : IComparable<T, E> {
|
||||||
static abstract E operator ==(T left, T right);
|
static abstract E operator ==(T left, T right);
|
||||||
static abstract E operator !=(T left, T right);
|
static abstract E operator !=(T left, T right);
|
||||||
}
|
}
|
||||||
|
@ -41,7 +44,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
return _name;
|
return _name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public class Boolean : Atom {
|
public class Boolean : Atom, IComparable<Boolean, Boolean> {
|
||||||
private readonly bool _value;
|
private readonly bool _value;
|
||||||
public Boolean(bool value) {
|
public Boolean(bool value) {
|
||||||
_value = value;
|
_value = value;
|
||||||
|
@ -50,8 +53,14 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
public override string ToString(string? format, IFormatProvider? provider) {
|
public override string ToString(string? format, IFormatProvider? provider) {
|
||||||
return _value? "t" : "nil";
|
return _value? "t" : "nil";
|
||||||
}
|
}
|
||||||
|
public static Boolean operator ==(Boolean a, Boolean b) {
|
||||||
|
return new Boolean(a.value == b.value);
|
||||||
}
|
}
|
||||||
public class Integer : Atom, IAddable<Integer>, ISubtractable<Integer>, IMultiplicatable<Integer>, IDivisible<Integer>, IComparable<Integer, Boolean> {
|
public static Boolean operator !=(Boolean a, Boolean b) {
|
||||||
|
return new Boolean(a.value != b.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class Integer : Atom, IAddable<Integer>, ISubtractable<Integer>, IMultiplicatable<Integer>, IDivisible<Integer>, ISortable<Integer, Boolean>, IComparable<Integer, Boolean> {
|
||||||
private readonly int _value;
|
private readonly int _value;
|
||||||
public Integer(int value) {
|
public Integer(int value) {
|
||||||
_value = value;
|
_value = value;
|
||||||
|
@ -107,6 +116,29 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
return new String (a.value + b.value);
|
return new String (a.value + b.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public class Object : Atom {
|
||||||
|
private readonly object _value;
|
||||||
|
public Object(object value) {
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
public object value { get => _value; }
|
||||||
|
public override string ToString(string? format, IFormatProvider? provider) {
|
||||||
|
return _value.ToString();
|
||||||
|
}
|
||||||
|
public static Atom FromBase(object o) {
|
||||||
|
switch (o) {
|
||||||
|
case bool b:
|
||||||
|
return new Boolean(b);
|
||||||
|
case int i:
|
||||||
|
return new Integer(i);
|
||||||
|
case string s:
|
||||||
|
return new String(s);
|
||||||
|
default:
|
||||||
|
return new Object(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class List : Expression {
|
public class List : Expression {
|
||||||
private IList<Expression> _expressions;
|
private IList<Expression> _expressions;
|
||||||
public List(IList<Expression> expressions) {
|
public List(IList<Expression> expressions) {
|
||||||
|
@ -122,9 +154,9 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
return r + ")";
|
return r + ")";
|
||||||
}
|
}
|
||||||
public static List operator +(List a, List b) {
|
public static List operator +(List a, List b) {
|
||||||
IList<Expression> r = new List<Expression>();
|
List<Expression> r = new List<Expression>();
|
||||||
r.Concat(a.expressions);
|
r.AddRange(a.expressions);
|
||||||
r.Concat(b.expressions);
|
r.AddRange(b.expressions);
|
||||||
return new List(r);
|
return new List(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,12 +183,12 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
}
|
}
|
||||||
|
|
||||||
Expression parse_string(GroupingToken start, GroupingToken end) {
|
Expression parse_string(GroupingToken start, GroupingToken end) {
|
||||||
Debug.Assert(start == end);
|
Debug.Assert(start.value == end.value);
|
||||||
Debug.Assert("'\"".Contains(start.value));
|
Debug.Assert("'\"".Contains(start.value));
|
||||||
string r = "";
|
string r = "";
|
||||||
while (_sts.available() > 0) {
|
while (_sts.available() > 0) {
|
||||||
Token<string> t = _sts.get();
|
Token<string> t = _sts.get();
|
||||||
if (t == end) {
|
if (t.value == end.value) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
r += t.value;
|
r += t.value;
|
||||||
|
@ -166,10 +198,13 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
}
|
}
|
||||||
|
|
||||||
Expression parse_grouping(GroupingToken start, GroupingToken end) {
|
Expression parse_grouping(GroupingToken start, GroupingToken end) {
|
||||||
|
if ("'\"".Contains(start.value)) {
|
||||||
|
return parse_string(start, end);
|
||||||
|
}
|
||||||
IList<Expression> expressions = new List<Expression>();
|
IList<Expression> expressions = new List<Expression>();
|
||||||
while (_sts.available() > 0) {
|
while (_sts.available() > 0) {
|
||||||
Token<string> t = _sts.get();
|
Token<string> t = _sts.get();
|
||||||
if (t.Equals(end)) {
|
if (t.value == end.value) {
|
||||||
_sts.commit();
|
_sts.commit();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,240 +1,312 @@
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
|
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
|
||||||
|
|
||||||
namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
public class EnvironmentEntry {};
|
using Function = Func<IList<Expression>, Expression>;
|
||||||
public class Entry: EnvironmentEntry {
|
using FunctionLater = Func<Executor, IList<Expression>, Expression>;
|
||||||
private readonly Expression _expression;
|
|
||||||
public Entry(Expression expression) {
|
public class Environment : Dictionary<string, Expression> {}
|
||||||
_expression = expression;
|
public class Builtins : Dictionary<string, Function> {
|
||||||
}
|
public Builtins() : base() {
|
||||||
public Expression expression { get => _expression; }
|
this["+"] = _add;
|
||||||
};
|
this["-"] = _sub;
|
||||||
public class NOOPEntry: Entry {
|
this["*"] = _mul;
|
||||||
public NOOPEntry() : base(new Compiler.Boolean(false)) {}
|
this["/"] = _div;
|
||||||
}
|
this["%"] = _mod;
|
||||||
public class Function : EnvironmentEntry {
|
this[">"] = _gt;
|
||||||
private readonly Func<IList<Expression>, Expression> _func;
|
this["<"] = _lt;
|
||||||
public Function(Func<IList<Expression>, Expression> func) {
|
this[">="] = _ge;
|
||||||
_func = func;
|
this["<="] = _le;
|
||||||
}
|
this["eq?"] = _eq;
|
||||||
public Func<IList<Expression>, Expression> func { get => _func; }
|
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[new Symbol("!=")] = _ne;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Environment : Dictionary<string, EnvironmentEntry> {
|
private static T _agg<T>(Func<T, T, T> op, IList<T> args) {
|
||||||
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];
|
T agg = args[0];
|
||||||
foreach (var arg in args.Skip(1)) {
|
foreach (var arg in args.Skip(1)) {
|
||||||
agg = op(agg, arg);
|
agg = op(agg, arg);
|
||||||
}
|
}
|
||||||
return agg;
|
return agg;
|
||||||
}
|
}
|
||||||
|
private static Expression _add(IList<Expression> args) {
|
||||||
private static Expression op_add(IList<Expression> args) {
|
|
||||||
Expression first = args[0];
|
Expression first = args[0];
|
||||||
switch (first) {
|
switch (first) {
|
||||||
case Integer i:
|
case Integer i:
|
||||||
return op_agg((a, b) => a + b, args.Select(x => (Integer) x).ToList());
|
return _agg((a, b) => a + b, args.Select(x => (Integer) x).ToList());
|
||||||
case Compiler.String s:
|
case Compiler.String s:
|
||||||
return op_agg((a, b) => a + b, args.Select(x => (Compiler.String) x).ToList());
|
return _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");
|
throw new ApplicationException();
|
||||||
}
|
}
|
||||||
|
private static Expression _sub(IList<Expression> args) {
|
||||||
private static Expression op_sub(IList<Expression> args) {
|
|
||||||
Expression first = args[0];
|
Expression first = args[0];
|
||||||
switch (first) {
|
switch (first) {
|
||||||
case Integer i:
|
case Integer i:
|
||||||
return op_agg((a, b) => a - b, args.Select(x => (Integer) x).ToList());
|
return _agg((a, b) => a - b, args.Select(x => (Integer) x).ToList());
|
||||||
}
|
}
|
||||||
throw new ApplicationException("Don't know how to subtract these types");
|
throw new ApplicationException();
|
||||||
}
|
}
|
||||||
|
private static Expression _mul(IList<Expression> args) {
|
||||||
private static Expression op_mul(IList<Expression> args) {
|
|
||||||
Expression first = args[0];
|
Expression first = args[0];
|
||||||
switch (first) {
|
switch (first) {
|
||||||
case Integer i:
|
case Integer i:
|
||||||
return op_agg((a, b) => a * b, args.Select(x => (Integer) x).ToList());
|
return _agg((a, b) => a * b, args.Select(x => (Integer) x).ToList());
|
||||||
}
|
}
|
||||||
throw new ApplicationException("Don't know how to subtract these types");
|
throw new ApplicationException();
|
||||||
}
|
}
|
||||||
|
private static Expression _div(IList<Expression> args) {
|
||||||
private static Expression op_div(IList<Expression> args) {
|
|
||||||
Expression first = args[0];
|
Expression first = args[0];
|
||||||
switch (first) {
|
switch (first) {
|
||||||
case Integer i:
|
case Integer i:
|
||||||
return op_agg((a, b) => a / b, args.Select(x => (Integer) x).ToList());
|
return _agg((a, b) => a / b, args.Select(x => (Integer) x).ToList());
|
||||||
}
|
}
|
||||||
throw new ApplicationException("Don't know how to subtract these types");
|
throw new ApplicationException();
|
||||||
}
|
}
|
||||||
|
private static Expression _mod(IList<Expression> args) {
|
||||||
private static Expression op_rem(IList<Expression> args) {
|
|
||||||
Expression first = args[0];
|
Expression first = args[0];
|
||||||
switch (first) {
|
switch (first) {
|
||||||
case Integer i:
|
case Integer i:
|
||||||
return op_agg((a, b) => a % b, args.Select(x => (Integer) x).ToList());
|
return _agg((a, b) => a % b, args.Select(x => (Integer) x).ToList());
|
||||||
}
|
}
|
||||||
throw new ApplicationException("Don't know how to subtract these types");
|
throw new ApplicationException();
|
||||||
}
|
}
|
||||||
|
private static E _cmp<T, E>(Func<T, T, E> op, IList<T> args) {
|
||||||
private static E op_cmp<T, E>(Func<T, T, E> op, IList<T> args) {
|
|
||||||
T first = args[0];
|
T first = args[0];
|
||||||
T second = args[1];
|
T second = args[1];
|
||||||
return op(first, second);
|
return op(first, second);
|
||||||
}
|
}
|
||||||
|
private static Expression _gt(IList<Expression> args) {
|
||||||
private static Expression op_gt(IList<Expression> args) {
|
|
||||||
Expression first = args[0];
|
Expression first = args[0];
|
||||||
switch (first) {
|
switch (first) {
|
||||||
case Integer i:
|
case Integer i:
|
||||||
return op_cmp((a, b) => a > b, args.Select(x => (Integer) x).ToList());
|
return _cmp((a, b) => a > b, args.Select(x => (Integer) x).ToList());
|
||||||
}
|
}
|
||||||
throw new ApplicationException("Don't know how to subtract these types");
|
throw new ApplicationException();
|
||||||
}
|
}
|
||||||
|
private static Expression _lt(IList<Expression> args) {
|
||||||
private static Expression op_lt(IList<Expression> args) {
|
|
||||||
Expression first = args[0];
|
Expression first = args[0];
|
||||||
switch (first) {
|
switch (first) {
|
||||||
case Integer i:
|
case Integer i:
|
||||||
return op_cmp((a, b) => a < b, args.Select(x => (Integer) x).ToList());
|
return _cmp((a, b) => a < b, args.Select(x => (Integer) x).ToList());
|
||||||
}
|
}
|
||||||
throw new ApplicationException("Don't know how to subtract these types");
|
throw new ApplicationException();
|
||||||
}
|
}
|
||||||
|
private static Expression _ge(IList<Expression> args) {
|
||||||
private static Expression op_ge(IList<Expression> args) {
|
|
||||||
Expression first = args[0];
|
Expression first = args[0];
|
||||||
switch (first) {
|
switch (first) {
|
||||||
case Integer i:
|
case Integer i:
|
||||||
return op_cmp((a, b) => a >= b, args.Select(x => (Integer) x).ToList());
|
return _cmp((a, b) => a >= b, args.Select(x => (Integer) x).ToList());
|
||||||
}
|
}
|
||||||
throw new ApplicationException("Don't know how to subtract these types");
|
throw new ApplicationException();
|
||||||
}
|
}
|
||||||
|
private static Expression _le(IList<Expression> args) {
|
||||||
private static Expression op_le(IList<Expression> args) {
|
|
||||||
Expression first = args[0];
|
Expression first = args[0];
|
||||||
switch (first) {
|
switch (first) {
|
||||||
case Integer i:
|
case Integer i:
|
||||||
return op_cmp((a, b) => a <= b, args.Select(x => (Integer) x).ToList());
|
return _cmp((a, b) => a <= b, args.Select(x => (Integer) x).ToList());
|
||||||
}
|
}
|
||||||
throw new ApplicationException("Don't know how to subtract these types");
|
throw new ApplicationException();
|
||||||
}
|
}
|
||||||
|
private static Expression _eq(IList<Expression> args) {
|
||||||
private static Expression op_eq(IList<Expression> args) {
|
|
||||||
Expression first = args[0];
|
Expression first = args[0];
|
||||||
switch (first) {
|
switch (first) {
|
||||||
case Integer i:
|
case Integer i:
|
||||||
return op_cmp((a, b) => a == b, args.Select(x => (Integer) x).ToList());
|
return _cmp((a, b) => a == b, args.Select(x => (Integer) x).ToList());
|
||||||
}
|
}
|
||||||
throw new ApplicationException("Don't know how to subtract these types");
|
throw new ApplicationException();
|
||||||
}
|
}
|
||||||
|
private static Expression _ne(IList<Expression> args) {
|
||||||
private static Expression op_ne(IList<Expression> args) {
|
|
||||||
Expression first = args[0];
|
Expression first = args[0];
|
||||||
switch (first) {
|
switch (first) {
|
||||||
case Integer i:
|
case Integer i:
|
||||||
return op_cmp((a, b) => a != b, args.Select(x => (Integer) x).ToList());
|
return _cmp((a, b) => a != b, args.Select(x => (Integer) x).ToList());
|
||||||
}
|
}
|
||||||
throw new ApplicationException("Don't know how to subtract these types");
|
throw new ApplicationException();
|
||||||
}
|
}
|
||||||
|
private static Expression _abs(IList<Expression> args) {
|
||||||
private static Expression op_abs(IList<Expression> args) {
|
|
||||||
Expression first = args[0];
|
Expression first = args[0];
|
||||||
switch (first) {
|
switch (first) {
|
||||||
case Integer i:
|
case Integer i:
|
||||||
return i.value >= 0 ? i : new Integer(-i.value);
|
return i.value >= 0 ? i : new Integer(-i.value);
|
||||||
}
|
}
|
||||||
throw new ApplicationException("Don't know how to subtract these types");
|
throw new ApplicationException();
|
||||||
}
|
}
|
||||||
|
private static Expression _append(IList<Expression> args) {
|
||||||
private static Expression op_append(IList<Expression> args) {
|
|
||||||
Expression first = args[0];
|
Expression first = args[0];
|
||||||
switch (first) {
|
switch (first) {
|
||||||
case List l:
|
case List l:
|
||||||
return l + new List(args);
|
return l + new List(args);
|
||||||
}
|
}
|
||||||
throw new ApplicationException("Don't know how to subtract these types");
|
throw new ApplicationException();
|
||||||
}
|
}
|
||||||
|
private static Expression _begin(IList<Expression> args) {
|
||||||
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();
|
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;
|
||||||
|
}
|
||||||
|
MethodInfo? mi = o.value.GetType().GetMethod(s.value);
|
||||||
|
if (mi != null) {
|
||||||
|
r.Add(Compiler.Object.FromBase(mi.Invoke(o.value, null)));
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
return new Compiler.List(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public EnvironmentEntry eval(Expression expression) {
|
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) {
|
switch (expression) {
|
||||||
case Symbol s:
|
case Symbol s:
|
||||||
return this[s.name];
|
return _environment[s.name];
|
||||||
|
case Compiler.Boolean b:
|
||||||
|
return b;
|
||||||
case Integer i:
|
case Integer i:
|
||||||
return new Entry(i);
|
return i;
|
||||||
case Compiler.String s_:
|
case Compiler.String s:
|
||||||
return new Entry(s_);
|
return s;
|
||||||
case List list:
|
case List list:
|
||||||
|
// do we really want to allow shadowing of builtins?
|
||||||
if (list.expressions[0].GetType() == typeof(Symbol)) {
|
if (list.expressions[0].GetType() == typeof(Symbol)) {
|
||||||
if (((Symbol) list.expressions[0]).name.Equals("if")) {
|
return EvalFunction((Symbol) list.expressions[0], list.expressions.Skip(1).ToList());
|
||||||
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")) {
|
return new List(list.expressions.Select(x => eval(x)).ToList());
|
||||||
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");
|
throw new ApplicationException("Not handled case");
|
||||||
}
|
}
|
||||||
public EnvironmentEntry eval(Parser p) {
|
public Expression eval(Parser p) {
|
||||||
return eval(p.parse());
|
return eval(p.parse());
|
||||||
}
|
}
|
||||||
public EnvironmentEntry eval(StringTokenStream sts) {
|
public Expression eval(StringTokenStream sts) {
|
||||||
return eval(new Parser(sts));
|
return eval(new Parser(sts));
|
||||||
}
|
}
|
||||||
public EnvironmentEntry eval(string p) {
|
public Expression eval(string p) {
|
||||||
return eval(StringTokenStream.generate(p));
|
return eval(StringTokenStream.generate(p));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,20 +5,44 @@ using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Playlists;
|
using MediaBrowser.Controller.Playlists;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Playlists;
|
||||||
|
using MediaBrowser.Controller.Providers;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Playlists;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
||||||
public class GeneratePlaylist : IScheduledTask {
|
public class GeneratePlaylist : IScheduledTask {
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
private readonly IUserManager _userManager;
|
||||||
|
|
||||||
public GeneratePlaylist(
|
public GeneratePlaylist(
|
||||||
ILogger<Plugin> logger
|
ILogger<Plugin> logger,
|
||||||
|
ILibraryManager libraryManager,
|
||||||
|
IUserManager userManager
|
||||||
) {
|
) {
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_logger.LogInformation("Constructed Task");
|
_libraryManager = libraryManager;
|
||||||
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Category => "Library";
|
public string Category => "Library";
|
||||||
public string Name => "(re)generate Smart Playlists";
|
public string Name => "(re)generate Smart Playlists";
|
||||||
public string Description => "Generate or regenerate all Smart Playlists";
|
public string Description => "Generate or regenerate all Smart Playlists";
|
||||||
public string Key => nameof(GeneratePlaylist);
|
public string Key => nameof(GeneratePlaylist);
|
||||||
|
|
||||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() {
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() {
|
||||||
return new[] {
|
return new[] {
|
||||||
new TaskTriggerInfo {
|
new TaskTriggerInfo {
|
||||||
|
@ -27,8 +51,23 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void GetUsers() {
|
||||||
|
foreach (var user in _userManager.Users) {
|
||||||
|
_logger.LogInformation("User {0}", user);
|
||||||
|
var query = new InternalItemsQuery(user) {
|
||||||
|
IncludeItemTypes = new[] {BaseItemKind.Audio},
|
||||||
|
Recursive = true,
|
||||||
|
};
|
||||||
|
foreach (BaseItem item in _libraryManager.GetItemsResult(query).Items) {
|
||||||
|
_logger.LogInformation("Item {0}", item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) {
|
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) {
|
||||||
_logger.LogInformation("This is a test");
|
_logger.LogInformation("This is a test");
|
||||||
|
GetUsers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
3
Test/.gitignore
vendored
Normal file
3
Test/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
cache
|
||||||
|
config
|
||||||
|
media
|
|
@ -4,14 +4,15 @@ set -eu
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
pwd
|
pwd
|
||||||
(
|
(
|
||||||
cd ..
|
cd ../Jellyfin.Plugin.SmartPlaylist/
|
||||||
dotnet build
|
dotnet build
|
||||||
)
|
)
|
||||||
pwd
|
pwd
|
||||||
mkdir -p ./cache ./config/plugins/jellyfin-smart-playlist
|
mkdir -p ./cache ./media ./config/plugins/jellyfin-smart-playlist
|
||||||
cp ../bin/Debug/net8.0/jellyfin-smart-playlist.dll ./config/plugins/jellyfin-smart-playlist/
|
cp ../Jellyfin.Plugin.SmartPlaylist/bin/Debug/net8.0/jellyfin-smart-playlist.dll ./config/plugins/jellyfin-smart-playlist/
|
||||||
docker run --rm --user "$(id -u):$(id -g)" \
|
docker run --rm --user "$(id -u):$(id -g)" \
|
||||||
-v ./cache:/cache \
|
-v ./cache:/cache \
|
||||||
-v ./config:/config \
|
-v ./config:/config \
|
||||||
|
-v ./media:/media \
|
||||||
-p 8096:8096 \
|
-p 8096:8096 \
|
||||||
jellyfin/jellyfin
|
jellyfin/jellyfin
|
||||||
|
|
|
@ -1,11 +1,23 @@
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Lisp_Environment = Jellyfin.Plugin.SmartPlaylist.Lisp.Environment;
|
using Lisp_Environment = Jellyfin.Plugin.SmartPlaylist.Lisp.Environment;
|
||||||
using Lisp_Boolean = Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler.Boolean;
|
using Lisp_Boolean = Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler.Boolean;
|
||||||
|
using Lisp_Object = Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler.Object;
|
||||||
using Jellyfin.Plugin.SmartPlaylist.Lisp;
|
using Jellyfin.Plugin.SmartPlaylist.Lisp;
|
||||||
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
|
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
|
||||||
|
|
||||||
namespace Tests
|
namespace Tests
|
||||||
{
|
{
|
||||||
|
public class O {
|
||||||
|
int _i;
|
||||||
|
bool _b;
|
||||||
|
public O(int i, bool b) {
|
||||||
|
_i = i;
|
||||||
|
_b = b;
|
||||||
|
}
|
||||||
|
public int i { get => _i; }
|
||||||
|
public bool b { get => _b; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Test {
|
public class Test {
|
||||||
[Fact]
|
[Fact]
|
||||||
public static void StringTokenStreamTest() {
|
public static void StringTokenStreamTest() {
|
||||||
|
@ -42,27 +54,54 @@ namespace Tests
|
||||||
StringTokenStream sts = StringTokenStream.generate(program);
|
StringTokenStream sts = StringTokenStream.generate(program);
|
||||||
Parser p = new Parser(sts);
|
Parser p = new Parser(sts);
|
||||||
Assert.Equal(program, string.Format("{0}", p.parse()));
|
Assert.Equal(program, string.Format("{0}", p.parse()));
|
||||||
|
|
||||||
|
program = "( haskeys o \"i\" \"b\")";
|
||||||
|
sts = StringTokenStream.generate(program);
|
||||||
|
p = new Parser(sts);
|
||||||
|
Assert.Equal(program, string.Format("{0}", p.parse()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public static void EvaluateTest() {
|
public static void EvaluateTest() {
|
||||||
Expression p = new Parser(StringTokenStream.generate("(+ 5 (+ 1 2 3))")).parse();
|
Expression e = new Executor().eval("(+ 5 (+ 1 2 3))");
|
||||||
var e = Lisp_Environment.create();
|
Assert.Equal(((Integer) e).value, 11);
|
||||||
Entry r = (Entry) e.eval(p);
|
|
||||||
Assert.Equal(((Integer) r.expression).value, 11);
|
|
||||||
|
|
||||||
r = (Entry) Lisp_Environment.create().eval("(> 1 2)");
|
e = new Executor().eval("(> 1 2)");
|
||||||
Assert.Equal(((Lisp_Boolean) r.expression).value, false);
|
Assert.Equal(((Lisp_Boolean) e).value, false);
|
||||||
|
|
||||||
r = (Entry) Lisp_Environment.create().eval("(if (> 1 2) 3 4)");
|
e = new Executor().eval("(if (> 1 2) 3 4)");
|
||||||
Assert.Equal(((Integer) r.expression).value, 4);
|
Assert.Equal(((Integer) e).value, 4);
|
||||||
|
|
||||||
r = (Entry) Lisp_Environment.create().eval("(begin (define x 1) 4)");
|
e = new Executor().eval("(begin (define x 1) x)");
|
||||||
Assert.Equal(((Integer) r.expression).value, 4);
|
Assert.Equal(((Integer) e).value, 1);
|
||||||
|
|
||||||
|
e = new Executor().eval("(apply + (1 2))");
|
||||||
|
Assert.Equal(((Integer) e).value, 3);
|
||||||
|
|
||||||
r = (Entry) Lisp_Environment.create().eval("(apply + (1 2))");
|
e = new Executor().eval("(car (10 20 30))");
|
||||||
Assert.Equal(((Integer) r.expression).value, 3);
|
Assert.Equal(((Integer) e).value, 10);
|
||||||
|
|
||||||
|
e = new Executor().eval("(cdr (10 20 30))");
|
||||||
|
Assert.Equal(string.Format("{0}", e), "( 20 30)");
|
||||||
|
|
||||||
|
e = new Executor().eval("(cons 1 3)");
|
||||||
|
Assert.Equal(string.Format("{0}", e), "( 1 3)");
|
||||||
|
|
||||||
|
e = new Executor().eval("(cons 1 (2 3))");
|
||||||
|
Assert.Equal(string.Format("{0}", e), "( 1 2 3)");
|
||||||
|
|
||||||
|
e = new Executor().eval("(length (cons 1 (2 3)))");
|
||||||
|
Assert.Equal(string.Format("{0}", e), "3");
|
||||||
|
}
|
||||||
|
[Fact]
|
||||||
|
public static void ObjectTest() {
|
||||||
|
Executor e = new Executor();
|
||||||
|
Expression r;
|
||||||
|
e.environment["o"] = new Lisp_Object(new O(5, false));
|
||||||
|
r = e.eval("(haskeys o 'i' 'b')");
|
||||||
|
Assert.Equal(((Lisp_Boolean)r).value, true);
|
||||||
|
r = e.eval("(getitems o 'i' 'b')");
|
||||||
|
Assert.Equal(string.Format("{0}", r), "( 5 nil)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue