diff --git a/.gitignore b/.gitignore index 09e83c2..afec7c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ **/obj/ **/bin/ -cache/ -config/ diff --git a/Jellyfin.Plugin.SmartPlaylist/Lisp/Compiler/Parser.cs b/Jellyfin.Plugin.SmartPlaylist/Lisp/Compiler/Parser.cs index 1393ac6..656d4e6 100644 --- a/Jellyfin.Plugin.SmartPlaylist/Lisp/Compiler/Parser.cs +++ b/Jellyfin.Plugin.SmartPlaylist/Lisp/Compiler/Parser.cs @@ -18,11 +18,14 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { static abstract T operator %(T left, T right); } - interface IComparable where T : IComparable { + interface ISortable where T : ISortable { 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 where T : IComparable { 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; } } - public class Boolean : Atom { + public class Boolean : Atom, IComparable { private readonly bool _value; public Boolean(bool value) { _value = value; @@ -50,8 +53,14 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { public override string ToString(string? format, IFormatProvider? provider) { return _value? "t" : "nil"; } + public static Boolean operator ==(Boolean a, Boolean b) { + return new Boolean(a.value == b.value); + } + public static Boolean operator !=(Boolean a, Boolean b) { + return new Boolean(a.value != b.value); + } } - public class Integer : Atom, IAddable, ISubtractable, IMultiplicatable, IDivisible, IComparable { + public class Integer : Atom, IAddable, ISubtractable, IMultiplicatable, IDivisible, ISortable, IComparable { private readonly int _value; public Integer(int value) { _value = value; @@ -107,6 +116,29 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { 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 { private IList _expressions; public List(IList expressions) { @@ -122,10 +154,10 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { return r + ")"; } public static List operator +(List a, List b) { - IList r = new List(); - r.Concat(a.expressions); - r.Concat(b.expressions); - return new List (r); + List r = new List(); + r.AddRange(a.expressions); + r.AddRange(b.expressions); + return new List(r); } } @@ -151,12 +183,12 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { } Expression parse_string(GroupingToken start, GroupingToken end) { - Debug.Assert(start == end); + Debug.Assert(start.value == end.value); Debug.Assert("'\"".Contains(start.value)); string r = ""; while (_sts.available() > 0) { Token t = _sts.get(); - if (t == end) { + if (t.value == end.value) { break; } r += t.value; @@ -166,10 +198,13 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { } Expression parse_grouping(GroupingToken start, GroupingToken end) { + if ("'\"".Contains(start.value)) { + return parse_string(start, end); + } IList expressions = new List(); while (_sts.available() > 0) { Token t = _sts.get(); - if (t.Equals(end)) { + if (t.value == end.value) { _sts.commit(); break; } diff --git a/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs b/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs index 70e3d06..365e475 100644 --- a/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs +++ b/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs @@ -1,240 +1,312 @@ +using System; +using System.Reflection; + 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, Expression> _func; - public Function(Func, Expression> func) { - _func = func; - } - public Func, Expression> func { get => _func; } - } + using Function = Func, Expression>; + using FunctionLater = Func, Expression>; - public class Environment : Dictionary { - 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; + public class Environment : Dictionary {} + public class Builtins : Dictionary { + 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[new Symbol("!=")] = _ne; } - private static T op_agg(Func op, IList args) { + private static T _agg(Func op, IList args) { T agg = args[0]; foreach (var arg in args.Skip(1)) { agg = op(agg, arg); } return agg; } - - private static Expression op_add(IList args) { + private static Expression _add(IList args) { Expression first = args[0]; switch (first) { 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: - 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()); + return _agg((a, b) => a + b, args.Select(x => (Compiler.String) x).ToList()); } - throw new ApplicationException("Don't know how to add these types"); + throw new ApplicationException(); } - - private static Expression op_sub(IList args) { + private static Expression _sub(IList args) { Expression first = args[0]; switch (first) { 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 op_mul(IList args) { + private static Expression _mul(IList args) { Expression first = args[0]; switch (first) { 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 op_div(IList args) { + private static Expression _div(IList args) { Expression first = args[0]; switch (first) { 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 op_rem(IList args) { + private static Expression _mod(IList args) { Expression first = args[0]; switch (first) { 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 op_cmp(Func op, IList args) { + private static E _cmp(Func op, IList args) { T first = args[0]; T second = args[1]; return op(first, second); } - - private static Expression op_gt(IList args) { + private static Expression _gt(IList args) { Expression first = args[0]; switch (first) { 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 op_lt(IList args) { + private static Expression _lt(IList args) { Expression first = args[0]; switch (first) { 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 op_ge(IList args) { + private static Expression _ge(IList args) { Expression first = args[0]; switch (first) { 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 op_le(IList args) { + private static Expression _le(IList args) { Expression first = args[0]; switch (first) { 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 op_eq(IList args) { + private static Expression _eq(IList args) { Expression first = args[0]; switch (first) { 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 op_ne(IList args) { + private static Expression _ne(IList args) { Expression first = args[0]; switch (first) { 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 op_abs(IList args) { + private static Expression _abs(IList 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"); + throw new ApplicationException(); } - - private static Expression op_append(IList args) { + private static Expression _append(IList 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"); + throw new ApplicationException(); } - - private static Expression op_apply(IList args) { - IList e_list = (IList) 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 new_args = e_list.Skip(1).Select(x => ((Entry) x).expression).ToList(); - return f.func(args); - } - - private static Expression op_begin(IList args) { + private static Expression _begin(IList args) { return args.Last(); } + private static Expression _car(IList args) { + return ((List) args.First()).expressions.First(); + } + private static Expression _cdr(IList args) { + return new List(((List) args.First()).expressions.Skip(1).ToList()); + } + private static Expression _cons(IList 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 args) { + return new Integer(((Compiler.List)args[0]).expressions.Count()); + } + private static Expression _haskeys(IList 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 args) { + Compiler.Object o = (Compiler.Object) args[0]; + IList r = new List(); + 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 { + public BuiltinsLater() : base() { + this["if"] = _if; + this["define"] = _define; + this["apply"] = _apply; + } + private static Expression _if(Executor e, IList 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 args) { + e.environment[((Symbol) args[0]).name] = e.eval(args[1]); + return new Compiler.Boolean(false); // NOOP + } + private static Expression _apply(Executor e, IList 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 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 this[s.name]; + return _environment[s.name]; + case Compiler.Boolean b: + return b; case Integer i: - return new Entry(i); - case Compiler.String s_: - return new Entry(s_); + 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)) { - 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(); - } + return EvalFunction((Symbol) list.expressions[0], list.expressions.Skip(1).ToList()); } - IList 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 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())); + return new List(list.expressions.Select(x => eval(x)).ToList()); } throw new ApplicationException("Not handled case"); } - public EnvironmentEntry eval(Parser p) { + public Expression eval(Parser p) { return eval(p.parse()); } - public EnvironmentEntry eval(StringTokenStream sts) { + public Expression eval(StringTokenStream sts) { return eval(new Parser(sts)); } - public EnvironmentEntry eval(string p) { + public Expression eval(string p) { return eval(StringTokenStream.generate(p)); } } diff --git a/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GeneratePlaylist.cs b/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GeneratePlaylist.cs index 3f69d19..5b024f4 100644 --- a/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GeneratePlaylist.cs +++ b/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GeneratePlaylist.cs @@ -5,20 +5,44 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; 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 { public class GeneratePlaylist : IScheduledTask { private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly IUserManager _userManager; + public GeneratePlaylist( - ILogger logger + ILogger logger, + ILibraryManager libraryManager, + IUserManager userManager ) { _logger = logger; - _logger.LogInformation("Constructed Task"); + _libraryManager = libraryManager; + _userManager = userManager; } + public string Category => "Library"; public string Name => "(re)generate Smart Playlists"; public string Description => "Generate or regenerate all Smart Playlists"; public string Key => nameof(GeneratePlaylist); + public IEnumerable GetDefaultTriggers() { return new[] { 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 progress, CancellationToken cancellationToken) { _logger.LogInformation("This is a test"); + GetUsers(); } } } diff --git a/Test/.gitignore b/Test/.gitignore new file mode 100644 index 0000000..bf0c532 --- /dev/null +++ b/Test/.gitignore @@ -0,0 +1,3 @@ +cache +config +media diff --git a/Test/test.sh b/Test/test.sh index 19eefab..f56d07e 100755 --- a/Test/test.sh +++ b/Test/test.sh @@ -4,14 +4,15 @@ set -eu cd "$(dirname "$0")" pwd ( - cd .. + cd ../Jellyfin.Plugin.SmartPlaylist/ dotnet build ) pwd -mkdir -p ./cache ./config/plugins/jellyfin-smart-playlist -cp ../bin/Debug/net8.0/jellyfin-smart-playlist.dll ./config/plugins/jellyfin-smart-playlist/ +mkdir -p ./cache ./media ./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)" \ -v ./cache:/cache \ -v ./config:/config \ + -v ./media:/media \ -p 8096:8096 \ jellyfin/jellyfin diff --git a/Tests/Tests.cs b/Tests/Tests.cs index d9efa0f..e37a975 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -1,11 +1,23 @@ using Xunit; using Lisp_Environment = Jellyfin.Plugin.SmartPlaylist.Lisp.Environment; 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.Compiler; 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 { [Fact] public static void StringTokenStreamTest() { @@ -42,27 +54,54 @@ namespace Tests StringTokenStream sts = StringTokenStream.generate(program); Parser p = new Parser(sts); 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] public static void EvaluateTest() { - Expression p = new Parser(StringTokenStream.generate("(+ 5 (+ 1 2 3))")).parse(); - var e = Lisp_Environment.create(); - Entry r = (Entry) e.eval(p); - Assert.Equal(((Integer) r.expression).value, 11); + Expression e = new Executor().eval("(+ 5 (+ 1 2 3))"); + Assert.Equal(((Integer) e).value, 11); - r = (Entry) Lisp_Environment.create().eval("(> 1 2)"); - Assert.Equal(((Lisp_Boolean) r.expression).value, false); + e = new Executor().eval("(> 1 2)"); + Assert.Equal(((Lisp_Boolean) e).value, false); - r = (Entry) Lisp_Environment.create().eval("(if (> 1 2) 3 4)"); - Assert.Equal(((Integer) r.expression).value, 4); + e = new Executor().eval("(if (> 1 2) 3 4)"); + Assert.Equal(((Integer) e).value, 4); - r = (Entry) Lisp_Environment.create().eval("(begin (define x 1) 4)"); - Assert.Equal(((Integer) r.expression).value, 4); + e = new Executor().eval("(begin (define x 1) x)"); + 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))"); - Assert.Equal(((Integer) r.expression).value, 3); + e = new Executor().eval("(car (10 20 30))"); + 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)"); } } }