Compare commits

...

2 commits

9 changed files with 54 additions and 47 deletions

View file

@ -211,7 +211,10 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
public override string ToString() { public override string ToString() {
return _value.ToString(); return _value.ToString();
} }
public static Expression FromBase(object o) { public static Expression FromBase(object? o) {
if (o == null) {
return new Boolean(false);
}
switch (o) { switch (o) {
case bool b: case bool b:
return new Boolean(b); return new Boolean(b);

View file

@ -24,11 +24,13 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
class SpaceToken : Token<string> { class SpaceToken : Token<string> {
private SpaceToken(string value) : base(value) {} private SpaceToken(string value) : base(value) {}
private static IToken<string>? take(CharStream program) { private static IToken<string>? take(CharStream program) {
char[] spaces = [' ', '\n'];
if (program.Available() == 0) { if (program.Available() == 0) {
return null; return null;
} }
if (program.Get() == ' ') { var t = program.Get();
return new SpaceToken(" "); if (spaces.Contains(t)) {
return new SpaceToken(t.ToString());
} }
return null; return null;
} }

View file

@ -125,6 +125,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
this["car"] = _car; this["car"] = _car;
this["cdr"] = _cdr; this["cdr"] = _cdr;
this["cons"] = _cons; this["cons"] = _cons;
this["list"] = _list;
this["not"] = _not; this["not"] = _not;
this["length"] = _length; this["length"] = _length;
this["haskeys"] = _haskeys; this["haskeys"] = _haskeys;
@ -260,6 +261,9 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
} }
throw new ApplicationException(); throw new ApplicationException();
} }
private static Expression _list(IList<Expression> args) {
return new Compiler.List(args);
}
private static Expression _not(IList<Expression> args) { private static Expression _not(IList<Expression> args) {
if (args[0] == new Compiler.Boolean(false)) { if (args[0] == new Compiler.Boolean(false)) {
return new Compiler.Boolean(true); return new Compiler.Boolean(true);
@ -337,7 +341,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
} }
private static Expression _define(Executor e, IList<Expression> args) { private static Expression _define(Executor e, IList<Expression> args) {
var refname = ((Symbol) args[0]).name; var refname = ((Symbol) args[0]).name;
e.environment.Set(refname, e.eval(args[1])); e.environment.Set(refname, args[1]);
return new Compiler.Boolean(false); // NOOP return new Compiler.Boolean(false); // NOOP
} }
private static Expression _lambda(Executor e, IList<Expression> args) { private static Expression _lambda(Executor e, IList<Expression> args) {
@ -397,9 +401,9 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
public BuiltinsLater builtinsLater { get => _builtinsLater; } public BuiltinsLater builtinsLater { get => _builtinsLater; }
public Expression EvalFunction(Symbol fcname, IList<Expression> args) { public Expression EvalFunction(Symbol fcname, IList<Expression> args) {
if (_environment.Find(fcname.name) is IEnvironment<string, Expression> _e) { if (_environment.Find(fcname.name) is IEnvironment<string, Expression> _e && _e != null) {
Expression? first = _e.Get(fcname.name); Expression first = _e.Get(fcname.name);
return new List(new []{first}.ToList()) + new List(args.Select(x => eval(x)).ToList()); return new List((new []{first}).Concat(args.ToArray()).Select(x => eval(x)).ToList());
} }
if (_builtins.ContainsKey(fcname.name)) { if (_builtins.ContainsKey(fcname.name)) {
Function fc = _builtins[fcname.name]; Function fc = _builtins[fcname.name];
@ -416,7 +420,10 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
public Expression eval(Expression expression) { public Expression eval(Expression expression) {
switch (expression) { switch (expression) {
case Symbol s: case Symbol s:
return _environment.Find(s.name).Get(s.name); 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 Compiler.Boolean b: case Compiler.Boolean b:
return b; return b;
case Integer i: case Integer i:
@ -431,16 +438,14 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
if (list.expressions.Count == 0) { if (list.expressions.Count == 0) {
return list; return list;
} }
// do we really want to allow shadowing of builtins? if (list.expressions[0] is Symbol fc_symbol) {
if (list.expressions[0].GetType() == typeof(Symbol)) { return eval(EvalFunction(fc_symbol, list.expressions.Skip(1).ToList()));
return eval(EvalFunction((Symbol) list.expressions[0], list.expressions.Skip(1).ToList()));
} }
if (list.expressions[0].GetType() == typeof(Procedure)) { if (list.expressions[0] is Procedure procedure) {
Procedure procedure = (Procedure) list.expressions[0];
return eval(procedure.Call(this, list.expressions.Skip(1).ToList())); return eval(procedure.Call(this, list.expressions.Skip(1).ToList()));
} }
var l = new List(list.expressions.Select(x => eval(x)).ToList()); var l = new Compiler.List(list.expressions.Select(x => eval(x)).ToList());
if (l.expressions[0].GetType() == typeof(Procedure)) { if (l.expressions[0] is Symbol|| l.expressions[0] is Procedure) {
return eval(l); return eval(l);
} }
return l; return l;

View file

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
namespace Jellyfin.Plugin.SmartPlaylist { namespace Jellyfin.Plugin.SmartPlaylist {
public class PluginConfiguration : BasePluginConfiguration { public class PluginConfiguration : BasePluginConfiguration {

View file

@ -5,23 +5,11 @@ 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.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities; 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.Playlists;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
using Jellyfin.Plugin.SmartPlaylist.Lisp; using Jellyfin.Plugin.SmartPlaylist.Lisp;
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler; using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
@ -93,7 +81,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
} }
} }
private SmartPlaylistId CreateNewPlaylist(string name, UserId userId) { private PlaylistId CreateNewPlaylist(string name, UserId userId) {
_logger.LogDebug("Creating playlist '{0}'", name); _logger.LogDebug("Creating playlist '{0}'", name);
var req = new PlaylistCreationRequest { var req = new PlaylistCreationRequest {
Name = name, Name = name,

View file

@ -50,7 +50,7 @@ namespace Jellyfin.Plugin.SmartPlaylist {
public bool Enabled { get; set; } public bool Enabled { get; set; }
public SmartPlaylistDto() { public SmartPlaylistDto() {
Id = Guid.NewGuid(); Id = "";
Playlists = []; Playlists = [];
Name = Id.ToString(); Name = Id.ToString();
Program = DEFAULT_PROGRAM; Program = DEFAULT_PROGRAM;
@ -62,7 +62,7 @@ namespace Jellyfin.Plugin.SmartPlaylist {
if (info.GetValue("Id", typeof(SmartPlaylistId)) is SmartPlaylistId _Id) { if (info.GetValue("Id", typeof(SmartPlaylistId)) is SmartPlaylistId _Id) {
Id = _Id; Id = _Id;
} else { } else {
Id = Guid.NewGuid(); Id = "";
} }
if (info.GetValue("Playlists", typeof(SmartPlaylistLinkDto[])) is SmartPlaylistLinkDto[] _Playlists) { if (info.GetValue("Playlists", typeof(SmartPlaylistLinkDto[])) is SmartPlaylistLinkDto[] _Playlists) {
Playlists = _Playlists; Playlists = _Playlists;

View file

@ -19,7 +19,12 @@ namespace Jellyfin.Plugin.SmartPlaylist {
if (dto == null) { if (dto == null) {
throw new ApplicationException(""); throw new ApplicationException("");
} }
if (dto.Id == Path.GetFileNameWithoutExtension(filename)) {
dto.Id = Path.GetFileNameWithoutExtension(filename);
}
if (dto.Filename != filename) {
dto.Filename = filename; dto.Filename = filename;
}
return dto; return dto;
} }
public async Task<SmartPlaylistDto> GetSmartPlaylistAsync(SmartPlaylistId smartPlaylistId) { public async Task<SmartPlaylistDto> GetSmartPlaylistAsync(SmartPlaylistId smartPlaylistId) {

View file

@ -2,4 +2,4 @@ global using System;
global using UserId = System.Guid; global using UserId = System.Guid;
global using PlaylistId = System.Guid; global using PlaylistId = System.Guid;
global using SmartPlaylistId = System.Guid; global using SmartPlaylistId = string;

View file

@ -93,19 +93,19 @@ namespace Tests
e = new Executor().eval("(apply + (1 2))"); e = new Executor().eval("(apply + (1 2))");
Assert.Equal(((Integer) e).value, 3); Assert.Equal(((Integer) e).value, 3);
e = new Executor().eval("(car (10 20 30))"); e = new Executor().eval("(car (list 10 20 30))");
Assert.Equal(((Integer) e).value, 10); Assert.Equal(((Integer) e).value, 10);
e = new Executor().eval("(cdr (10 20 30))"); e = new Executor().eval("(cdr (list 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)"); 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))"); e = new Executor().eval("(cons 1 (list 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)))"); e = new Executor().eval("(length (cons 1 (list 2 3)))");
Assert.Equal(string.Format("{0}", e), "3"); Assert.Equal(string.Format("{0}", e), "3");
e = new Executor().eval("(>= 2 2)"); e = new Executor().eval("(>= 2 2)");
@ -126,10 +126,10 @@ namespace Tests
e = new Executor().eval("(or nil 4)"); e = new Executor().eval("(or nil 4)");
Assert.Equal("4", e.ToString()); Assert.Equal("4", e.ToString());
e = new Executor().eval("(= (1 2) (1 2))"); e = new Executor().eval("(= (list 1 2) (list 1 2))");
Assert.Equal(e.ToString(), "t"); Assert.Equal(e.ToString(), "t");
e = new Executor().eval("(= (1 2 3) (1 2))"); e = new Executor().eval("(= (list 1 2 3) (list 1 2))");
Assert.Equal(e.ToString(), "nil"); Assert.Equal(e.ToString(), "nil");
} }
[Fact] [Fact]
@ -165,19 +165,28 @@ namespace Tests
r = e.eval("(begin (define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1)))))) (fact 10))"); r = e.eval("(begin (define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1)))))) (fact 10))");
Assert.Equal(string.Format("{0}", r), "3628800"); Assert.Equal(string.Format("{0}", r), "3628800");
r = e.eval("(begin (define find (lambda (item list) (if (= list ()) nil (if (= item (car list)) (car list) (find item (cdr list)))))) (find 3 (1 2 3 4)))"); r = e.eval("(begin (define find (lambda (item list) (if (= list ()) nil (if (= item (car list)) (car list) (find item (cdr list)))))) (find 3 (list 1 2 3 4)))");
Assert.Equal(string.Format("{0}", r), "3"); Assert.Equal(string.Format("{0}", r), "3");
e = new Executor(); e = new Executor();
r = e.eval("(begin (define find (lambda (item list) (if (= list ()) nil (if (= item (car list)) (car list) (find item (cdr list)))))) (find 0 (1 2 3 4)))"); r = e.eval("(begin (define find (lambda (item list) (if (= list ()) nil (if (= item (car list)) (car list) (find item (cdr list)))))) (find 0 (list 1 2 3 4)))");
Assert.Equal(string.Format("{0}", r), "nil"); Assert.Equal(string.Format("{0}", r), "nil");
e = new Executor();
r = e.eval(@"
(begin
(define map (lambda (fc l) (if (= l ()) () (cons (fc (car l)) (map fc (cdr l))))))
(define multwo (lambda (x) (* 2 x)))
(map multwo (list 1 2 3)))
");
Assert.Equal(string.Format("{0}", r), "(2 4 6)");
} }
[Fact] [Fact]
public static void DefaultEnvironmentTest() { public static void DefaultEnvironmentTest() {
Executor e = new Executor(new DefaultEnvironment()); Executor e = new Executor(new DefaultEnvironment());
Assert.Equal("nil", e.eval("(find 0 (1 2 3 4))").ToString()); Assert.Equal("nil", e.eval("(find 0 (list 1 2 3 4))").ToString());
Assert.Equal("3", e.eval("(find 3 (1 2 3 4))").ToString()); Assert.Equal("3", e.eval("(find 3 (list 1 2 3 4))").ToString());
} }
} }
} }