diff --git a/Jellyfin.Plugin.SmartPlaylist/Lisp/Compiler/Parser.cs b/Jellyfin.Plugin.SmartPlaylist/Lisp/Compiler/Parser.cs index a768129..af0b4ef 100644 --- a/Jellyfin.Plugin.SmartPlaylist/Lisp/Compiler/Parser.cs +++ b/Jellyfin.Plugin.SmartPlaylist/Lisp/Compiler/Parser.cs @@ -276,7 +276,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { case AtomToken at: return parse_atom(at); case OperatorToken ot: - return new Symbol(ot.value); + return parse_operator(ot); case SpaceToken sp: return parse(); } @@ -330,5 +330,19 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { _sts.commit(); return new Symbol(at.value); } + + Expression parse_operator(OperatorToken ot) { + string v = ot.value; + while (_sts.available() > 0) { + Token t = _sts.get(); + if (t is OperatorToken ot_) { + v += ot_.value; + continue; + } + _sts.rewind(1); + break; + } + return new Symbol(v); + } } } diff --git a/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs b/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs index ff9c17c..8dd89a9 100644 --- a/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs +++ b/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs @@ -27,6 +27,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp { this["car"] = _car; this["cdr"] = _cdr; this["cons"] = _cons; + this["not"] = _not; this["length"] = _length; this["haskeys"] = _haskeys; this["getitems"] = _getitems; @@ -170,6 +171,12 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp { } throw new ApplicationException(); } + private static Expression _not(IList args) { + if (args[0] == new Compiler.Boolean(false)) { + return new Compiler.Boolean(true); + } + return new Compiler.Boolean(false); + } private static Expression _length(IList args) { return new Integer(((Compiler.List)args[0]).expressions.Count()); } @@ -231,9 +238,11 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp { this["if"] = _if; this["define"] = _define; this["apply"] = _apply; + this["and"] = _and; + this["or"] = _or; } private static Expression _if(Executor e, IList args) { - bool test = ((Compiler.Boolean) e.eval(args[0])).value; + bool test = e.eval(args[0]) != (new Compiler.Boolean(false)); return e.eval(args[1 + (test ? 0 : 1)]); } private static Expression _define(Executor e, IList args) { @@ -251,6 +260,22 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp { Compiler.List other_args = (Compiler.List) args[1]; return e.EvalFunction(arg0, other_args.expressions); } + private static Expression _and(Executor e, IList args) { + Expression result = new Compiler.Boolean(false); + foreach (var exp in args) { + result = e.eval(exp); + if (result == new Compiler.Boolean(false)) { return result; } + } + return result; + } + private static Expression _or(Executor e, IList args) { + Expression result = new Compiler.Boolean(false); + foreach (var exp in args) { + result = e.eval(exp); + if (result != new Compiler.Boolean(false)) { return result; } + } + return result; + } } public class Executor { diff --git a/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GeneratePlaylist.cs b/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GeneratePlaylist.cs index 198bdb0..088ba18 100644 --- a/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GeneratePlaylist.cs +++ b/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GeneratePlaylist.cs @@ -110,7 +110,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks { _logger.LogDebug("Item {0} evaluated to {1}", i, r.ToString()); if (r is Lisp_Boolean r_bool) { if (r_bool.value) { - _logger.LogDebug("Added "{0}" to Smart Playlist {1}", i, smartPlaylist.Name); + _logger.LogDebug("Added '{0}' to Smart Playlist {1}", i, smartPlaylist.Name); results.Add(i.Id); } } else { diff --git a/Tests/Tests.cs b/Tests/Tests.cs index 6c94b12..e102eb3 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -21,7 +21,7 @@ namespace Tests public class Test { [Fact] public static void TestTokenizer() { - StringTokenStream sts = StringTokenStream.generate("(\"some literal string\" def ghj +100 -+300 1 ++ !=)"); + StringTokenStream sts = StringTokenStream.generate("(\"some literal string\" def ghj +100 -+300 1 >= ++ !=)"); Assert.Equal(sts.get().value, "("); Assert.Equal(sts.get().value, "\""); Assert.Equal(sts.get().value, "some"); @@ -44,9 +44,14 @@ namespace Tests Assert.Equal(sts.get().value, " "); Assert.Equal(sts.get().value, "1"); Assert.Equal(sts.get().value, " "); - Assert.Equal(sts.get().value, "++"); + Assert.Equal(sts.get().value, ">"); + Assert.Equal(sts.get().value, "="); Assert.Equal(sts.get().value, " "); - Assert.Equal(sts.get().value, "!="); + Assert.Equal(sts.get().value, "+"); + Assert.Equal(sts.get().value, "+"); + Assert.Equal(sts.get().value, " "); + Assert.Equal(sts.get().value, "!"); + Assert.Equal(sts.get().value, "="); Assert.Equal(sts.get().value, ")"); sts.commit(); Assert.Equal(sts.available(), 0); @@ -87,16 +92,22 @@ namespace Tests Assert.Equal(((Integer) e).value, 10); e = new Executor().eval("(cdr (10 20 30))"); - Assert.Equal(string.Format("{0}", e), "( 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)"); + 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)"); + 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"); + + e = new Executor().eval("(>= 2 2)"); + Assert.Equal(string.Format("{0}", e), "t"); + + e = new Executor().eval("(> 2 2))"); + Assert.Equal(string.Format("{0}", e), "nil"); } [Fact] public static void ObjectTest() { @@ -106,7 +117,7 @@ namespace Tests 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)"); + Assert.Equal(string.Format("{0}", r), "(5 nil)"); } } }