From 8ec393f49469ca884677ac33d9da849f1ae46f81 Mon Sep 17 00:00:00 2001 From: redxef Date: Thu, 7 Nov 2024 00:48:56 +0100 Subject: [PATCH] fix!: simplify parser and executor, streamline tests. --- .../Lisp/Compiler/Parser.cs | 299 +---------- .../Lisp/Compiler/TokenStream.cs | 22 +- .../Lisp/Expression.cs | 294 +++++++++++ .../Lisp/Interpreter.cs | 468 +++++++----------- .../ScheduledTasks/GeneratePlaylist.cs | 6 +- Tests/Tests.cs | 270 +++++----- 6 files changed, 639 insertions(+), 720 deletions(-) create mode 100644 Jellyfin.Plugin.SmartPlaylist/Lisp/Expression.cs diff --git a/Jellyfin.Plugin.SmartPlaylist/Lisp/Compiler/Parser.cs b/Jellyfin.Plugin.SmartPlaylist/Lisp/Compiler/Parser.cs index 3b14a05..4415edd 100644 --- a/Jellyfin.Plugin.SmartPlaylist/Lisp/Compiler/Parser.cs +++ b/Jellyfin.Plugin.SmartPlaylist/Lisp/Compiler/Parser.cs @@ -1,272 +1,7 @@ using System.Diagnostics; +using Jellyfin.Plugin.SmartPlaylist.Lisp; namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { - interface IAddable where T : IAddable { - static abstract T operator +(T left, T right); - } - - interface ISubtractable where T : ISubtractable { - static abstract T operator -(T left, T right); - } - - interface IMultiplicatable where T : IMultiplicatable { - static abstract T operator *(T left, T right); - } - - interface IDivisible where T : IDivisible { - static abstract T operator /(T left, T right); - static abstract T operator %(T left, T right); - } - - 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); - E Equals(T other); - } - - public abstract class Expression: IComparable { - public override abstract string ToString(); - public abstract override int GetHashCode(); - public abstract bool Equals(Expression other); - public override bool Equals(object? other) { - if (other is Expression other_e) { - return Equals(other_e); - } - return false; - } - public static bool operator ==(Expression left, Expression right) { - return left.Equals(right); - } - public static bool operator !=(Expression left, Expression right) { - return !left.Equals(right); - } - public abstract object Inner(); - } - public abstract class Atom : Expression {} - public class Symbol : Atom { - private readonly string _name; - public Symbol(string name) { - _name = name; - } - public string name { get => _name; } - public override int GetHashCode() { - int hash = 17; - hash *= 23; - hash += _name.GetHashCode(); - return hash; - } - public override bool Equals(Expression? other) { - if (other is Symbol other_s) { - return _name == other_s._name; - } - return false; - } - public override string ToString() { - return _name; - } - public override object Inner() { - return _name; - } - } - - public class Boolean : Atom { - private readonly bool _value; - public Boolean(bool value) { - _value = value; - } - public bool value { get => _value; } - public override int GetHashCode() { - int hash = 17; - hash *= 23; - hash += _value.GetHashCode(); - return hash; - } - public override bool Equals(Expression other) { - if (other is Boolean other_b) { - return _value == other_b.value; - } - return false; - } - public override string ToString() { - return _value? "t" : "nil"; - } - public override object Inner() { - return _value; - } - } - - public class Integer : Atom, IAddable, ISubtractable, IMultiplicatable, IDivisible, ISortable { - private readonly int _value; - public Integer(int value) { - _value = value; - } - public int value { get => _value; } - public override int GetHashCode() { - int hash = 17; - hash *= 23; - hash += _value.GetHashCode(); - return hash; - } - public override bool Equals(Expression other) { - if (other is Integer other_i) { - return _value == other_i._value; - } - return false; - } - public override string ToString() { - return _value.ToString(); - } - public static Integer operator +(Integer a, Integer b) { - return new Integer(a.value + b.value); - } - public static Integer operator -(Integer a, Integer b) { - return new Integer(a.value - b.value); - } - public static Integer operator *(Integer a, Integer b) { - return new Integer(a.value * b.value); - } - public static Integer operator /(Integer a, Integer b) { - return new Integer(a.value / b.value); - } - public static Integer operator %(Integer a, Integer b) { - return new Integer(a.value % b.value); - } - public static Boolean operator >(Integer a, Integer b) { - return new Boolean(a.value > b.value); - } - public static Boolean operator <(Integer a, Integer b) { - return new Boolean(a.value < b.value); - } - public static Boolean operator >=(Integer a, Integer b) { - return new Boolean(a.value >= b.value); - } - public static Boolean operator <=(Integer a, Integer b) { - return new Boolean(a.value <= b.value); - } - public static Boolean operator ==(Integer a, Integer b) { - return new Boolean(a.value == b.value); - } - public static Boolean operator !=(Integer a, Integer b) { - return new Boolean(a.value != b.value); - } - public override object Inner() { - return _value; - } - } - - public class String : Atom, IAddable { - private readonly string _value; - public String(string value) { - _value = value; - } - public string value { get => _value; } - public override int GetHashCode() { - int hash = 17; - hash *= 23; - hash += _value.GetHashCode(); - return hash; - } - public override bool Equals(Expression other) { - if (other is String other_s) { - return _value == other_s._value; - } - return false; - } - public override string ToString() { - return "\"" + _value + "\""; - } - public static String operator +(String a, String b) { - return new String (a.value + b.value); - } - public override object Inner() { - return _value; - } - } - - public class Object : Atom { - private readonly object _value; - public Object(object value) { - _value = value; - } - public object value { get => _value; } - public override int GetHashCode() { - int hash = 17; - hash *= 23; - hash += _value.GetHashCode(); - return hash; - } - public override bool Equals(Expression other) { - if (other is Object other_o) { - return _value == other_o._value; - } - return false; - } - public override string ToString() { - return _value.ToString(); - } - public static Expression FromBase(object? o) { - if (o == null) { - return new Boolean(false); - } - switch (o) { - case bool b: - return new Boolean(b); - case int i: - return new Integer(i); - case string s: - return new String(s); - case IEnumerable e: - return new List(e.Select(x => Object.FromBase(x)).ToList()); - default: - return new Object(o); - } - } - public override object Inner() { - return _value; - } - } - - public class List : Expression { - private IList _expressions; - public List(IList expressions) { - _expressions = expressions; - } - public IList expressions { get => _expressions; } - public override int GetHashCode() { - int hash = 17; - foreach (Expression i in _expressions) { - hash *= 23; - hash += i.GetHashCode(); - } - return hash; - } - public override bool Equals(Expression other) { - if (other is List other_l) { - return _expressions.SequenceEqual(other_l._expressions); - } - return false; - } - public override string ToString() { - return "(" + string.Join(" ", _expressions.Select(x => x.ToString())) + ")"; - } - public static List operator +(List a, List b) { - List r = new List(); - r.AddRange(a.expressions); - r.AddRange(b.expressions); - return new List(r); - } - public override object Inner() { - return _expressions.Select(x => x.Inner()).ToArray(); - } - } - public class Parser { private StringTokenStream _sts; public Parser(StringTokenStream tokens) { @@ -283,17 +18,16 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { return parse_grouping(gt, gt.closing_value); case AtomToken at: return parse_atom(at); - case OperatorToken ot: - return parse_operator(ot); case SpaceToken sp: return parse(); } return parse(); } - Expression parse_string(GroupingToken start, GroupingToken end) { + Expression parse_string(GroupingToken start, GroupingToken? end) { + Debug.Assert(end != null); Debug.Assert(start.value == end.value); - Debug.Assert("'\"".Contains(start.value)); + Debug.Assert("\"".Contains(start.value)); string r = ""; while (_sts.Available() > 0) { Token t = _sts.Get(); @@ -306,8 +40,9 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { return new String(r); } - Expression parse_grouping(GroupingToken start, GroupingToken end) { - if ("'\"".Contains(start.value)) { + Expression parse_grouping(GroupingToken start, GroupingToken? end) { + Debug.Assert(end != null); + if ("\"".Contains(start.value)) { return parse_string(start, end); } IList expressions = new List(); @@ -320,7 +55,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { _sts.Rewind(1); expressions.Add(parse()); } - return new List(expressions); + return Cons.FromList(expressions); } Expression parse_atom(AtomToken at) { @@ -330,27 +65,13 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { return new Integer(parsed_value); } if (at.value.Equals("t")) { - return new Boolean(true); + return Boolean.TRUE; } if (at.value.Equals("nil")) { - return new Boolean(false); + return Boolean.FALSE; } _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/Compiler/TokenStream.cs b/Jellyfin.Plugin.SmartPlaylist/Lisp/Compiler/TokenStream.cs index fdf69f5..b5064a3 100644 --- a/Jellyfin.Plugin.SmartPlaylist/Lisp/Compiler/TokenStream.cs +++ b/Jellyfin.Plugin.SmartPlaylist/Lisp/Compiler/TokenStream.cs @@ -24,7 +24,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { class SpaceToken : Token { private SpaceToken(string value) : base(value) {} private static IToken? take(CharStream program) { - char[] spaces = [' ', '\n']; + string spaces = " \n"; if (program.Available() == 0) { return null; } @@ -43,7 +43,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { return null; } char t = program.Get(); - if ("()\"'".Contains(t)) { + if ("()\"".Contains(t)) { return new GroupingToken(t.ToString()); } return null; @@ -65,7 +65,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { string value = ""; while (program.Available() > 0) { char t = program.Get(); - if (!"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".Contains(t)) { + if (" \n()\"".Contains(t)) { if (value.Equals("")) { return null; } @@ -78,21 +78,6 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { } } - class OperatorToken : Token { - private OperatorToken(string value) : base(value) {} - private static IToken? take(CharStream program) { - if (program.Available() == 0) { - return null; - } - return new OperatorToken(program.Get().ToString()); - //char t = program.get(); - //if ("+-*/%".Contains(t)) { - // return new OperatorToken(t.ToString()); - //} - //return null; - } - } - class CharStream: Stream { public CharStream(IList items) : base(items) {} public CharStream(string items) : base(items.ToCharArray().Cast().ToList()) {} @@ -103,7 +88,6 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { typeof(SpaceToken), typeof(GroupingToken), typeof(AtomToken), - typeof(OperatorToken), }; protected StringTokenStream(IList> tokens) : base(tokens) {} private static StringTokenStream generate(CharStream program) { diff --git a/Jellyfin.Plugin.SmartPlaylist/Lisp/Expression.cs b/Jellyfin.Plugin.SmartPlaylist/Lisp/Expression.cs new file mode 100644 index 0000000..28d37ea --- /dev/null +++ b/Jellyfin.Plugin.SmartPlaylist/Lisp/Expression.cs @@ -0,0 +1,294 @@ +namespace Jellyfin.Plugin.SmartPlaylist.Lisp { + interface IAddable where T : IAddable { + static abstract T operator +(T left, T right); + } + + interface ISubtractable where T : ISubtractable { + static abstract T operator -(T left, T right); + } + + interface IMultiplicatable where T : IMultiplicatable { + static abstract T operator *(T left, T right); + } + + interface IDivisible where T : IDivisible { + static abstract T operator /(T left, T right); + static abstract T operator %(T left, T right); + } + + 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); + E Equals(T other); + } + + public abstract class Expression: IComparable { + public override abstract string? ToString(); + public abstract override int GetHashCode(); + public abstract bool Equals(Expression other); + public override bool Equals(object? other) { + if (other is Expression other_e) { + return Equals(other_e); + } + return false; + } + public static bool operator ==(Expression left, Expression right) { + return left.Equals(right); + } + public static bool operator !=(Expression left, Expression right) { + return !left.Equals(right); + } + } + + public abstract class Atom : Expression {} + public class Scalar : Atom where V : notnull { + protected V _value; + public Scalar(V value) { + _value = value; + } + public override int GetHashCode() { + return 17 * 23 + _value.GetHashCode(); + } + public override bool Equals(Expression other) { + if (other is Scalar other_scalar) { + return _value.Equals(other_scalar._value); + } + return false; + } + public override string? ToString() { + return _value.ToString(); + } + public V Value() { + return _value; + } + } + public class Symbol : Atom { + private string _name; + public Symbol(string name) { + _name = name; + } + public override int GetHashCode() { + return 17 * 23 + _name.GetHashCode(); + } + public override bool Equals(Expression other) { + if (other is Symbol other_symbol) { + return _name.Equals(other_symbol._name); + } + return false; + } + public override string? ToString() { + return _name.ToString(); + } + public string Name() { + return _name; + } + } + public class Integer : Scalar, IAddable, ISubtractable, IMultiplicatable, IDivisible, ISortable { + public Integer(int value) : base(value) {} + public static Integer operator +(Integer a, Integer b) { + return new Integer(a._value + b._value); + } + public static Integer operator -(Integer a, Integer b) { + return new Integer(a._value - b._value); + } + public static Integer operator *(Integer a, Integer b) { + return new Integer(a._value * b._value); + } + public static Integer operator /(Integer a, Integer b) { + return new Integer(a._value / b._value); + } + public static Integer operator %(Integer a, Integer b) { + return new Integer(a._value % b._value); + } + public static Boolean operator >(Integer a, Integer b) { + return (a._value > b._value) ? Boolean.TRUE : Boolean.FALSE; + } + public static Boolean operator <(Integer a, Integer b) { + return (a._value < b._value) ? Boolean.TRUE : Boolean.FALSE; + } + public static Boolean operator >=(Integer a, Integer b) { + return (a._value >= b._value) ? Boolean.TRUE : Boolean.FALSE; + } + public static Boolean operator <=(Integer a, Integer b) { + return (a._value <= b._value) ? Boolean.TRUE : Boolean.FALSE; + } + public static Boolean operator ==(Integer a, Integer b) { + return (a._value == b._value) ? Boolean.TRUE : Boolean.FALSE; + } + public static Boolean operator !=(Integer a, Integer b) { + return (a._value != b._value) ? Boolean.TRUE : Boolean.FALSE; + } + } + + public class Boolean: Scalar { + public static Boolean TRUE = new Boolean(true); + public static Boolean FALSE = new Boolean(false); + private Boolean(bool value) : base(value) {} + public override string? ToString() { + if (_value) { + return "t"; + } + return "nil"; + } + public IList ToList() { + if (_value) { + throw new ApplicationException("Cannot use t as list"); + } + return new List(); + } + } + public class String: Scalar { + public String(string value) : base(value) {} + public override string? ToString() { + return $"\"{base.ToString()}\""; + } + } + public class Cons: Expression { + public Expression Item1; + public Expression Item2; + public Cons(Expression item1, Expression item2) { + Item1 = item1; + Item2 = item2; + } + public static Expression FromList(IEnumerable expressions) { + var e = expressions.ToList(); + if (e.Count == 0) { + return Boolean.FALSE; + } + var item1 = expressions.First(); + if (e.Count == 1) { + return new Cons(item1, Boolean.FALSE); + } + var item2 = expressions.Skip(1).ToList(); + return new Cons(item1, FromList(item2)); + } + public IEnumerable ToList() { + var l = new List(); + l.Add(Item1); + if (Item2 == Boolean.FALSE) { + return l; + } + if (Item2 is Cons item2_cons) { + l.AddRange(item2_cons.ToList()); + return l; + } + l.Add(Item2); + return l; + } + public override int GetHashCode() { + var hash = 17; + hash *= 23; + hash += Item1.GetHashCode(); + hash *= 23; + hash += Item2.GetHashCode(); + return hash; + } + public override bool Equals(Expression other) { + if (other is Cons other_list) { + return Item1.Equals(other_list.Item1) && Item2.Equals(other_list.Item2); + } + return false; + } + private string? ToStringSimple() { + if (Item2.Equals(Boolean.FALSE)) { + return Item1.ToString(); + } + if (Item2 is Cons item2_cons) { + return $"{Item1} {item2_cons.ToStringSimple()}"; + } + return $"{Item1} . {Item2}"; + } + public override string? ToString() { + return $"({ToStringSimple()})"; + } + } + + public class Object : Scalar { + public Object(object value) : base(value) { } + public static Expression FromBase(object? o) { + if (o == null) { + return Boolean.FALSE; + } + switch (o) { + case bool b: + return b ? Boolean.TRUE : Boolean.FALSE; + case int i: + return new Integer(i); + case string s: + return new String(s); + case Expression e: + return e; + case IEnumerable e: + return Cons.FromList(e.Select(x => FromBase(x))); + default: + return new Object(o); + } + } + } + + public class Procedure : Expression { + private IEnumerable _parameters; + private Expression _body; + private bool _eval_args; + public Procedure(IEnumerable parameters, Expression body, bool eval_args) { + _parameters = parameters; + _body = body; + _eval_args = eval_args; + } + public override int GetHashCode() { + int hash = 17; + hash *= 23; + hash += _parameters.GetHashCode(); + hash *= 23; + hash += _body.GetHashCode(); + return hash; + } + public override bool Equals(Expression? other) { + if (other is Procedure other_p) { + return _parameters == other_p._parameters && _body == other_p._body; + } + return false; + } + public override string ToString() { + var star = _eval_args ? "" : "*"; + return $"(lambda{star} {Cons.FromList(_parameters)} {_body})"; + } + + private Expression __eval(Executor e, Expression exp) { + if (!_eval_args) return exp; + return e.eval(exp); + } + + private Expression _eval(Executor e, Expression exp) { + var r = __eval(e, exp); + //Console.WriteLine($"{exp} = {r}"); + return r; + } + + public Expression Call(Executor e, IList args) { + Executor new_e = new Executor(new SubEnvironment(e.environment), e.builtins, e.builtinsLater); + var _params = _parameters.Select(x => x.Name()).ToArray(); + var idx_rest = -1; + for (var i = 0; i < _parameters.Count(); i++) { + var name = _params[i]; + if (name.Equals(".")) { + idx_rest = i + 1; + break; + } + new_e.environment.Set(name, _eval(e, args[i])); + } + if (idx_rest > 0) { + new_e.environment.Set(_params[idx_rest], Cons.FromList(args.Skip(idx_rest - 1).Select(x => _eval(e, x)))); + } + return new_e.eval(_body); + } + } + +} diff --git a/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs b/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs index 15f3df6..b9db4d5 100644 --- a/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs +++ b/Jellyfin.Plugin.SmartPlaylist/Lisp/Interpreter.cs @@ -3,63 +3,19 @@ using System.Reflection; using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler; namespace Jellyfin.Plugin.SmartPlaylist.Lisp { - using Function = Func, Expression>; - using FunctionLater = Func, Expression>; - - public class Procedure : Expression { - private Compiler.List _parameters; - private Expression _body; - public Procedure(Compiler.List parameters, Expression body) { - _parameters = parameters; - _body = body; - } - private static IEnumerable<(T1, T2)> Zip(IEnumerable a, IEnumerable b) { - using (var e1 = a.GetEnumerator()) using (var e2 = b.GetEnumerator()) { - while (e1.MoveNext() && e2.MoveNext()) { - yield return (e1.Current, e2.Current); - } - } - } - public override int GetHashCode() { - int hash = 17; - hash *= 23; - hash += _parameters.GetHashCode(); - hash *= 23; - hash += _body.GetHashCode(); - return hash; - } - public override bool Equals(Expression? other) { - if (other is Procedure other_p) { - return _parameters == other_p._parameters && _body == other_p._body; - } - return false; - } - public override object Inner() { - throw new ApplicationException("This is not sensible"); - } - public override string ToString() { - return $"(lambda {_parameters} {_body})"; - } - - public Expression Call(Executor e, IList args) { - var p = _parameters.expressions.Select(x => x.Inner().ToString()).ToList(); - Executor new_e = new Executor(new SubEnvironment(e.environment), e.builtins, e.builtinsLater); - foreach (var tuple in Zip(p, args)) { - new_e.environment.Set(tuple.Item1, tuple.Item2); - } - return new_e.eval(_body); - } - } + using Function = Func, Expression>; + using FunctionLater = Func, Expression>; public interface IEnvironment { public V Get(K k); public void Set(K k, V v); public IEnvironment? Find(K k); + public IEnvironment Parent(bool recursive); } public class Environment : Dictionary, IEnvironment { public Expression? Get(string k) { - if (TryGetValue(k, out Expression v)) { + if (TryGetValue(k, out Expression? v)) { return v; } return null; @@ -74,11 +30,24 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp { } return null; } + + public IEnvironment Parent(bool recursive) { + return this; + } } public class DefaultEnvironment: Environment { public DefaultEnvironment() { - this["find"] = new Parser("(lambda (item list) (if (= list ()) nil (if (= item (car list)) (car list) (find item (cdr list)))))").parse(); + var e = new Executor(); + this["if"] = e.eval("(lambda* (condition a b) ( cond ((eval condition) (eval a)) (t (eval b))))"); + this["null"] = new Symbol("not"); + this["list"] = e.eval("(lambda (. args) args)"); + this["find"] = e.eval("(lambda (item list_) (if (null list_) nil (if (= item (car list_)) (car list_) (find item (cdr list_)))))"); + this["map"] = e.eval("(lambda (fc l) (if (null l) nil (cons (fc (car l)) (map fc (cdr l)))))"); + this["and"] = e.eval("(lambda (l) (if (null l) t (if (car l) (and (cdr l)) nil)))"); + this["or"] = e.eval("(lambda (l) (if (null l) nil (if (car l) t (or (cdr l)))))"); + this["any"] = e.eval("(lambda (fc l) (or (map fc l)))"); + this["all"] = e.eval("(lambda (fc l) (and (map fc l)))"); } } @@ -88,7 +57,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp { _super = super; } public Expression? Get(string k) { - if (TryGetValue(k, out Expression v)) { + if (TryGetValue(k, out Expression? v)) { return v; } return null; @@ -103,276 +72,206 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp { } return _super.Find(k); } + + public IEnvironment Parent(bool recursive) { + if (recursive) { + return this._super.Parent(recursive); + } + return this._super; + } } 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["!="] = _ne; - this["abs"] = _abs; - this["append"] = _append; - this["begin"] = _begin; + this["atom"] = _atom; + this["eq"] = _eq; this["car"] = _car; this["cdr"] = _cdr; this["cons"] = _cons; - this["list"] = _list; - this["not"] = _not; - this["length"] = _length; + + this["begin"] = _begin; + + this["+"] = (x) => _agg((Integer a, Integer b) => a + b, x); + this["-"] = (x) => _agg((Integer a, Integer b) => a - b, x); + this["*"] = (x) => _agg((Integer a, Integer b) => a * b, x); + this["/"] = (x) => _agg((Integer a, Integer b) => a / b, x); + this["%"] = (x) => _agg((Integer a, Integer b) => a % b, x); + + this["="] = (x) => _cmp((Integer a, Integer b) => a == b, x); + this["eq?"] = (x) => _cmp((Integer a, Integer b) => a == b, x); + this["<"] = (x) => _cmp((Integer a, Integer b) => a < b, x); + this["<="] = (x) => _cmp((Integer a, Integer b) => a <= b, x); + this[">"] = (x) => _cmp((Integer a, Integer b) => a > b, x); + this[">="] = (x) => _cmp((Integer a, Integer b) => a >= b, x); + this["!="] = (x) => _cmp((Integer a, Integer b) => a != b, x); + this["not"] = (x) => (x.First() == Boolean.FALSE) ? Boolean.TRUE : Boolean.FALSE; + this["haskeys"] = _haskeys; this["getitems"] = _getitems; this["invoke"] = _invoke; } - - private static T _agg(Func op, IList args) { - T agg = args[0]; + private static T _agg(Func op, IEnumerable args) where T : Expression { + T agg = (T) args.First(); foreach (var arg in args.Skip(1)) { - agg = op(agg, arg); + var arg_ = (T) arg; + agg = op(agg, arg_); } return agg; } - private static Expression _add(IList args) { - Expression first = args[0]; - switch (first) { - case Integer i: - return _agg((a, b) => a + b, args.Select(x => (Integer) x).ToList()); - case Compiler.String s: - return _agg((a, b) => a + b, args.Select(x => (Compiler.String) x).ToList()); - } - throw new ApplicationException(); + private static E _cmp(Func op, IEnumerable args) where T : Expression where E : Expression { + return op((T) args.First(), (T) args.Skip(1).First()); } - private static Expression _sub(IList args) { - Expression first = args[0]; - switch (first) { - case Integer i: - return _agg((a, b) => a - b, args.Select(x => (Integer) x).ToList()); - } - throw new ApplicationException(); + private static Expression _atom(IEnumerable args) { + return (args.First() is Atom) ? Boolean.TRUE : Boolean.FALSE; } - private static Expression _mul(IList args) { - Expression first = args[0]; - switch (first) { - case Integer i: - return _agg((a, b) => a * b, args.Select(x => (Integer) x).ToList()); - } - throw new ApplicationException(); + private static Expression _eq(IEnumerable args) { + return args.First().Equals(args.Skip(1).First()) ? Boolean.TRUE : Boolean.FALSE; } - private static Expression _div(IList args) { - Expression first = args[0]; - switch (first) { - case Integer i: - return _agg((a, b) => a / b, args.Select(x => (Integer) x).ToList()); - } - throw new ApplicationException(); + private static Expression _car(IEnumerable args) { + return ((Cons)args.First()).Item1; } - private static Expression _mod(IList args) { - Expression first = args[0]; - switch (first) { - case Integer i: - return _agg((a, b) => a % b, args.Select(x => (Integer) x).ToList()); - } - throw new ApplicationException(); + private static Expression _cdr(IEnumerable args) { + return ((Cons)args.First()).Item2; } - private static E _cmp(Func op, IList args) { - T first = args[0]; - T second = args[1]; - return op(first, second); + private static Expression _cons(IEnumerable args) { + return new Cons(args.First(), args.Skip(1).First()); } - private static Expression _gt(IList args) { - Expression first = args[0]; - switch (first) { - case Integer i: - return _cmp((a, b) => a > b, args.Select(x => (Integer) x).ToList()); - } - throw new ApplicationException(); - } - private static Expression _lt(IList args) { - Expression first = args[0]; - switch (first) { - case Integer i: - return _cmp((a, b) => a < b, args.Select(x => (Integer) x).ToList()); - } - throw new ApplicationException(); - } - private static Expression _ge(IList args) { - Expression first = args[0]; - switch (first) { - case Integer i: - return _cmp((a, b) => a >= b, args.Select(x => (Integer) x).ToList()); - } - throw new ApplicationException(); - } - private static Expression _le(IList args) { - Expression first = args[0]; - switch (first) { - case Integer i: - return _cmp((a, b) => a <= b, args.Select(x => (Integer) x).ToList()); - } - throw new ApplicationException(); - } - private static Expression _eq(IList args) { - bool r = _cmp((a, b) => a == b, args); - return new Compiler.Boolean(r); - } - private static Expression _ne(IList args) { - bool r = _cmp((a, b) => a != b, args); - return new Compiler.Boolean(r); - } - 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(); - } - private static Expression _append(IList args) { - Expression first = args[0]; - switch (first) { - case List l: - return l + new List(args); - } - throw new ApplicationException(); - } - private static Expression _begin(IList args) { + private static Expression _begin(IEnumerable 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 _list(IList args) { - return new Compiler.List(args); - } - 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()); - } - private static Expression _haskeys(IList args) { - Compiler.Object o = (Compiler.Object) args[0]; + private static Expression _haskeys(IEnumerable args) { + Object o = (Object) args.First(); foreach (var e in args.Skip(1)) { - Compiler.String s = (Compiler.String) e; - PropertyInfo? pi = o.value.GetType().GetProperty(s.value); + String s = (String) e; + PropertyInfo? pi = o.Value().GetType().GetProperty(s.Value()); if (pi != null) { continue; } - MethodInfo? mi = o.value.GetType().GetMethod(s.value); + MethodInfo? mi = o.Value().GetType().GetMethod(s.Value()); if (mi != null) { continue; } - FieldInfo? fi = o.value.GetType().GetField(s.value); + FieldInfo? fi = o.Value().GetType().GetField(s.Value()); if (fi != null) { continue; } - return new Compiler.Boolean(false); + return Boolean.FALSE; } - return new Compiler.Boolean(true); + return Boolean.TRUE; } - private static Expression _getitems(IList args) { - Compiler.Object o = (Compiler.Object) args[0]; + private static Expression _getitems(IEnumerable args) { + Object o = (Object) args.First(); 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); + String s = (String) e; + PropertyInfo? pi = o.Value().GetType().GetProperty(s.Value()); if (pi != null) { - r.Add(Compiler.Object.FromBase(pi.GetValue(o.value))); + r.Add(Object.FromBase(pi.GetValue(o.Value()))); continue; } - FieldInfo? fi = o.value.GetType().GetField(s.value); + FieldInfo? fi = o.Value().GetType().GetField(s.Value()); if (fi != null) { - r.Add(Compiler.Object.FromBase(fi.GetValue(o.value))); + r.Add(Object.FromBase(fi.GetValue(o.Value()))); continue; } - throw new ApplicationException($"{o.value} has no property or field {s.value}"); + throw new ApplicationException($"{o.Value()} has no property or field {s.Value()}"); } - return new Compiler.List(r); + return Cons.FromList(r); } - private static Expression _invoke(IList args) { - Compiler.Object o = (Compiler.Object) args[0]; - Compiler.String s = (Compiler.String) args[1]; - Compiler.List l = (Compiler.List) args[2]; + private static Expression _invoke(IEnumerable args) { + Object o = (Object) args.First(); + String s = (String) args.Skip(1).First(); + Cons l = (Cons) args.Skip(2).First(); IList r = new List(); - MethodInfo? mi = o.value.GetType().GetMethod(s.value); + MethodInfo? mi = o.Value().GetType().GetMethod(s.Value()); if (mi == null) { - throw new ApplicationException($"{o.value} has not method {s.value}"); + throw new ApplicationException($"{o.Value()} has not method {s.Value()}"); } - return Compiler.Object.FromBase(mi.Invoke(o.value, (object?[]?) l.Inner())); + return Object.FromBase(mi.Invoke(o.Value(), (object?[]?) l.ToList().ToArray())); } } public class BuiltinsLater : Dictionary { public BuiltinsLater() : base() { - this["if"] = _if; + this["quote"] = _quote; + this["eval"] = _eval; + this["cond"] = _cond; this["define"] = _define; + this["let"] = _let; + this["let*"] = _let_star; this["lambda"] = _lambda; - this["apply"] = _apply; - this["and"] = _and; - this["or"] = _or; + this["lambda*"] = _lambda_star; } - private static Expression _if(Executor e, IList args) { - bool test = e.eval(args[0]) != (new Compiler.Boolean(false)); - return e.eval(args[1 + (test ? 0 : 1)]); + private static Expression _quote(Executor e, IEnumerable args) { + return args.First(); } - private static Expression _define(Executor e, IList args) { - var refname = ((Symbol) args[0]).name; - e.environment.Set(refname, args[1]); - return new Compiler.Boolean(false); // NOOP + private static Expression _eval(Executor e, IEnumerable args) { + return e.eval(e.eval(args.First())); } - private static Expression _lambda(Executor e, IList args) { - return new Procedure((Compiler.List) args[0], args[1]); - } - private static Expression _apply(Executor e, IList args) { - if (args[0].GetType() != typeof(Symbol)) { - throw new ApplicationException(); + private static Expression _cond(Executor e, IEnumerable args) { + foreach (var a in args) { + if (a is Cons a_cons) { + var a_ = a_cons.ToList(); + if (!e.eval(a_.First()).Equals(Boolean.FALSE)) { + return e.eval(a_.Skip(1).First()); + } + } else { + throw new ApplicationException($"Incorrect arguments to cond, expected list: {args}"); + } } - 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); + return Boolean.FALSE; } - 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 _define(Executor e, IEnumerable args) { + Symbol refname = (Symbol) args.First(); + e.environment.Parent(true).Set(refname.Name(), args.Skip(1).Select(x => e.eval(x)).First()); + return Boolean.TRUE; } - 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; } + private static Expression _let_star(Executor e, IEnumerable args) { + Executor new_e = new Executor(new SubEnvironment(e.environment), e.builtins, e.builtinsLater); + foreach (var pair in args.SkipLast(1)) { + if (pair is not Cons pair_cons) { + throw new ApplicationException("No expression for let*"); + } + Symbol refname = (Symbol) pair_cons.Item1; + Expression exp = ((Cons) pair_cons.Item2).Item1; + new_e.environment.Set(refname.Name(), new_e.eval(exp)); } - return result; + return new_e.eval(args.Last()); + } + private static Expression _let(Executor e, IEnumerable args) { + Executor new_e = new Executor(new SubEnvironment(e.environment), e.builtins, e.builtinsLater); + List<(Symbol, Expression)> vars = new List<(Symbol, Expression)>(); + foreach (var pair in args.SkipLast(1)) { + if (pair is not Cons pair_cons) { + throw new ApplicationException(""); + } + Symbol refname = (Symbol) pair_cons.Item1; + Expression exp_ = ((Cons) pair_cons.Item2).Item1; + vars.Add((refname, exp_)); + } + foreach (var pair in vars) { + new_e.environment.Set(pair.Item1.Name(), pair.Item2); + } + return new_e.eval(args.Last()); + } + private static Expression _lambda(Executor e, IEnumerable args) { + IEnumerable proc_args; + if (args.First() is Cons proc_args_) { proc_args = proc_args_.ToList().Select(x => (Symbol) x); } + else if (args.First() == Boolean.FALSE) { proc_args = new List(); } + else { + throw new ApplicationException(""); + } + return new Procedure(proc_args, args.Skip(1).First(), true); + } + private static Expression _lambda_star(Executor e, IEnumerable args) { + IEnumerable proc_args; + if (args.First() is Cons proc_args_) { proc_args = proc_args_.ToList().Select(x => (Symbol) x); } + else if (args.First() == Boolean.FALSE) { proc_args = new List(); } + else { + throw new ApplicationException(""); + } + return new Procedure(proc_args, args.Skip(1).First(), false); } } @@ -400,55 +299,48 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp { public Builtins builtins { get => _builtins; } public BuiltinsLater builtinsLater { get => _builtinsLater; } - public Expression EvalFunction(Symbol fcname, IList args) { - if (_environment.Find(fcname.name) is IEnvironment _e && _e != null) { - Expression first = _e.Get(fcname.name); - return new List((new []{first}).Concat(args.ToArray()).Select(x => eval(x)).ToList()); + public Expression? EvalFunction(Symbol fcname, IEnumerable args) { + if (builtins.ContainsKey(fcname.Name())) { + return builtins[fcname.Name()](args.Select(x => eval(x)).ToList()); // call ToList for sideeffect } - if (_builtins.ContainsKey(fcname.name)) { - Function fc = _builtins[fcname.name]; - return fc(args.Select(x => eval(x)).ToList()); + if (builtinsLater.ContainsKey(fcname.Name())) { + return builtinsLater[fcname.Name()](this, args); } - 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"); - + return null; } public Expression eval(Expression expression) { switch (expression) { case Symbol s: - if (_environment.Find(s.name) is not IEnvironment env) { - throw new ApplicationException($"Could not find '{s.name}'"); + if (_environment.Find(s.Name()) is not IEnvironment env) { + throw new ApplicationException($"Could not find '{s.Name()}'"); } - return env.Get(s.name); - case Compiler.Boolean b: + return env.Get(s.Name()); + case Boolean b: return b; case Integer i: return i; - case Compiler.String s: + case String s: return s; - case Compiler.Object o: + case Object o: return o; case Procedure p: return p; - case List list: - if (list.expressions.Count == 0) { - return list; + case Cons cons: + var l = cons.ToList(); + if (cons.Item1 is Symbol cons_item1_symbol) { + Expression? r = EvalFunction(cons_item1_symbol, l.Skip(1)); + if (r is not null) { return r; } } - if (list.expressions[0] is Symbol fc_symbol) { - return eval(EvalFunction(fc_symbol, list.expressions.Skip(1).ToList())); + var eval_Item1 = eval(cons.Item1); + if (eval_Item1 is Symbol eval_item1_symbol1) { + Expression? r = EvalFunction(eval_item1_symbol1, l.Skip(1)); + if (r is not null) { return r; } } - if (list.expressions[0] is Procedure procedure) { - return eval(procedure.Call(this, list.expressions.Skip(1).ToList())); + if (eval_Item1 is Procedure eval_item1_procedure) { + return eval_item1_procedure.Call(this, l.Skip(1).Select(x => x).ToList()); } - var l = new Compiler.List(list.expressions.Select(x => eval(x)).ToList()); - if (l.expressions[0] is Symbol|| l.expressions[0] is Procedure) { - return eval(l); - } - return l; + throw new ApplicationException($"Not handled case (type = {eval_Item1.GetType()}) '{cons}'"); } throw new ApplicationException($"Not handled case '{expression}'"); } diff --git a/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GeneratePlaylist.cs b/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GeneratePlaylist.cs index d303753..1df6f71 100644 --- a/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GeneratePlaylist.cs +++ b/Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/GeneratePlaylist.cs @@ -13,8 +13,8 @@ using MediaBrowser.Model.Playlists; using Jellyfin.Plugin.SmartPlaylist.Lisp; using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler; -using Lisp_Object = Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler.Object; -using Lisp_Boolean = Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler.Boolean; +using Lisp_Object = Jellyfin.Plugin.SmartPlaylist.Lisp.Object; +using Lisp_Boolean = Jellyfin.Plugin.SmartPlaylist.Lisp.Boolean; namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks { @@ -102,7 +102,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks { executor.environment.Set("item", new Lisp_Object(i)); var r = executor.eval(expression); _logger.LogTrace("Item {0} evaluated to {1}", i, r.ToString()); - if ((r is not Lisp_Boolean r_bool) || (r_bool.value)) { + if ((r is not Lisp_Boolean r_bool) || (r_bool.Value())) { _logger.LogDebug("Added '{0}' to Smart Playlist {1}", i, smartPlaylist.Name); results.Add(i.Id); } diff --git a/Tests/Tests.cs b/Tests/Tests.cs index a0997b2..ec16a5a 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -1,7 +1,7 @@ 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 Lisp_Boolean = Jellyfin.Plugin.SmartPlaylist.Lisp.Boolean; +using Lisp_Object = Jellyfin.Plugin.SmartPlaylist.Lisp.Object; using Jellyfin.Plugin.SmartPlaylist.Lisp; using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler; @@ -22,39 +22,33 @@ namespace Tests [Fact] public static void TestTokenizer() { 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"); - Assert.Equal(sts.Get().value, " "); - Assert.Equal(sts.Get().value, "literal"); - Assert.Equal(sts.Get().value, " "); - Assert.Equal(sts.Get().value, "string"); - Assert.Equal(sts.Get().value, "\""); - Assert.Equal(sts.Get().value, " "); - Assert.Equal(sts.Get().value, "def"); - Assert.Equal(sts.Get().value, " "); - Assert.Equal(sts.Get().value, "ghj"); - Assert.Equal(sts.Get().value, " "); - Assert.Equal(sts.Get().value, "+"); - Assert.Equal(sts.Get().value, "100"); - Assert.Equal(sts.Get().value, " "); - Assert.Equal(sts.Get().value, "-"); - Assert.Equal(sts.Get().value, "+"); - Assert.Equal(sts.Get().value, "300"); - 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); + Assert.Equal("some", sts.Get().value); + Assert.Equal(" ", sts.Get().value); + Assert.Equal("literal", sts.Get().value); + Assert.Equal(" ", sts.Get().value); + Assert.Equal("string", sts.Get().value); + Assert.Equal("\"", sts.Get().value); + Assert.Equal(" ", sts.Get().value); + Assert.Equal("def", sts.Get().value); + Assert.Equal(" ", sts.Get().value); + Assert.Equal("ghj", sts.Get().value); + Assert.Equal(" ", sts.Get().value); + Assert.Equal("+100", sts.Get().value); + Assert.Equal(" ", sts.Get().value); + Assert.Equal("-+300", sts.Get().value); + Assert.Equal(" ", sts.Get().value); + Assert.Equal("1", 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); + Assert.Equal(0, sts.Available()); } [Fact] @@ -68,125 +62,159 @@ namespace Tests sts = StringTokenStream.generate(program); p = new Parser(sts); Assert.Equal(program, string.Format("{0}", p.parse())); - - //program = "(* 2.4 2)"; - //sts = StringTokenStream.generate(program); - //p = new Parser(sts); - //Assert.Equal(program, p.parse().ToString()); } [Fact] public static void TestFunctions() { - IList> cases = new List>(); - Expression e = new Executor().eval("(+ 10 20)"); - Assert.Equal(((Integer) e).value, 30); + Executor e = new Executor(); + Assert.Equal("(1 2 3)", e.eval("(quote (1 2 3))").ToString()); + Assert.Equal("abc", e.eval("(quote abc)").ToString()); - e = new Executor().eval("(> 1 2)"); - Assert.Equal(((Lisp_Boolean) e).value, false); + Assert.Equal("t", e.eval("(atom 1)").ToString()); + Assert.Equal("nil", e.eval("(atom (quote (1 2 3)))").ToString()); - e = new Executor().eval("(if (> 1 2) 3 4)"); - Assert.Equal(((Integer) e).value, 4); + Assert.Equal("t", e.eval("(eq 2 2)").ToString()); + Assert.Equal("nil", e.eval("(eq 2 3)").ToString()); - e = new Executor().eval("(begin (define x 1) x)"); - Assert.Equal(((Integer) e).value, 1); + Assert.Equal("1", e.eval("(car (quote (1 2 3)))").ToString()); + Assert.Equal("(2 3)", e.eval("(cdr (quote (1 2 3)))").ToString()); - e = new Executor().eval("(apply + (1 2))"); - Assert.Equal(((Integer) e).value, 3); + Assert.Equal("(1 . 2)", e.eval("(cons 1 2)").ToString()); + Assert.Equal("(1 2)", e.eval("(cons 1 (cons 2 nil))").ToString()); + Assert.Equal("(1)", e.eval("(cons 1 nil)").ToString()); + Assert.Equal("(1)", e.eval("(cons 1 ())").ToString()); - e = new Executor().eval("(car (list 10 20 30))"); - Assert.Equal(((Integer) e).value, 10); + Assert.Equal("\"Case 2\"", e.eval(""" + (cond + ((eq 1 2) "Case 1") + ((eq 2 2) "Case 2")) + """).ToString()); + Assert.Equal("\"Case 1\"", e.eval(""" + (cond + ((eq 2 2) "Case 1") + ((eq 2 2) "Case 2")) + """).ToString()); + Assert.Equal("nil", e.eval(""" + (cond + ((eq 1 2) "Case 1") + ((eq 3 2) "Case 2")) + """).ToString()); - e = new Executor().eval("(cdr (list 10 20 30))"); - Assert.Equal(string.Format("{0}", e), "(20 30)"); + Assert.Equal("t", e.eval("((lambda (a) (eq a a)) 2)").ToString()); - e = new Executor().eval("(cons 1 3)"); - Assert.Equal(string.Format("{0}", e), "(1 3)"); + Assert.Equal("t", e.eval("(begin (car (quote (nil 1))) t)").ToString()); + Assert.Equal("(1)", e.eval("(begin t (cdr (quote (nil 1))))").ToString()); - e = new Executor().eval("(cons 1 (list 2 3))"); - Assert.Equal(string.Format("{0}", e), "(1 2 3)"); + Assert.Equal("t", e.eval(""" + (begin + (define abc 10) + (eq abc abc)) + """).ToString()); - e = new Executor().eval("(length (cons 1 (list 2 3)))"); - Assert.Equal(string.Format("{0}", e), "3"); + Assert.Equal("1", e.eval(""" + (begin + (define if (lambda (condition a b) ( + cond (condition a) (t b)))) + (if (> 2 1) (car (quote (1 2 3))) (cdr (quote (2 3 4))))) + """).ToString()); + Assert.Equal("(3 4)", e.eval(""" + (begin + (define if (lambda (condition a b) ( + cond (condition a) (t b)))) + (if (> 0 1) (car (quote (1 2 3))) (cdr (quote (2 3 4))))) + """).ToString()); - 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 TestFunctionsAdvanced() { + Executor e = new Executor(); + Assert.Equal("2", e.eval(""" + ((lambda (b) b) (car (quote (2 3)))) + """).ToString()); - e = new Executor().eval("(and 2 3 4)"); - Assert.Equal("4", e.ToString()); + Assert.Equal("(3 4 5)", e.eval(""" + ((lambda (x y . z) z) 1 2 3 4 5) + """).ToString()); - e = new Executor().eval("(and 2 nil 4)"); - Assert.Equal("nil", e.ToString()); + Assert.Equal("3", e.eval(""" + (begin + (define if (lambda (condition a b) (cond (condition a) (t b)))) + (if (< 1 2) 3 2)) + """).ToString()); - e = new Executor().eval("(or 2 nil 4)"); - Assert.Equal("2", e.ToString()); + Assert.Equal("2", e.eval(""" + (begin + (define if (lambda (condition a b) (cond (condition a) (t b)))) + (if (> 1 2) 3 2)) + """).ToString()); + Assert.Equal("1", e.eval(""" + (begin + (define if (lambda* (condition a b) ( + cond ((eval condition) (eval a)) (t (eval b))))) + (if (> 2 1) (car (quote (1 2 3))) (cdr (quote (2 3 4))))) + """).ToString()); + Assert.Equal("(3 4)", e.eval(""" + (begin + (define if (lambda* (condition a b) ( + cond ((eval condition) (eval a)) (t (eval b))))) + (if (> 0 1) (car (quote (1 2 3))) (cdr (quote (2 3 4))))) + """).ToString()); + Assert.Equal("120", e.eval(""" + (begin + (define f (lambda (n) (cond ((<= n 1) 1) (t (* n (f (- n 1))))))) + (f 5)) + """).ToString()); + Assert.Equal("120", e.eval(""" + (begin + (define if (lambda* (condition a b) ( + cond ((eval condition) (eval a)) (t (eval b))))) + (define f (lambda (n) (if (<= n 1) 1 (* n (f (- n 1)))))) + (f 5)) + """).ToString()); + Assert.Equal("(1 2 3 4 5)", e.eval(""" + (begin + (define if (lambda* (condition a b) ( + cond ((eval condition) (eval a)) (t (eval b))))) + ((lambda (. args) args) 1 2 3 4 5)) + """).ToString()); + Assert.Equal("t", e.eval(""" + (begin + (define null (lambda* (x) ( + cond ((eval x) nil) (t t)))) + (null nil)) + """).ToString()); + Assert.Equal("nil", e.eval(""" + (begin + (define null (lambda* (x) (cond ((eval x) nil) (t t)))) + (null (quote (1 2)))) + """).ToString()); - e = new Executor().eval("(or nil 4)"); - Assert.Equal("4", e.ToString()); - - e = new Executor().eval("(= (list 1 2) (list 1 2))"); - Assert.Equal(e.ToString(), "t"); - - e = new Executor().eval("(= (list 1 2 3) (list 1 2))"); - Assert.Equal(e.ToString(), "nil"); } [Fact] public static void ObjectTest() { Executor e = new Executor(); Expression r; e.environment.Set("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')"); + 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)"); } - [Fact] - public static void ScalarTest() { - Executor e = new Executor(); - Expression r; - - r = e.eval("(* 2 2)"); - Assert.Equal("4", r.ToString()); - } - - [Fact] - public static void ProcedureTest() { - Executor e = new Executor(); - Expression r; - r = e.eval("((lambda (a) (* a a)) 2)"); - Assert.Equal(string.Format("{0}", r), "4"); - - r = e.eval("(begin (define mull (lambda (a) (* a a))) (mull 3))"); - Assert.Equal(string.Format("{0}", r), "9"); - - 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"); - - 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"); - - 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 (list 1 2 3 4)))"); - 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] public static void DefaultEnvironmentTest() { Executor e = new Executor(new DefaultEnvironment()); - Assert.Equal("nil", e.eval("(find 0 (list 1 2 3 4))").ToString()); + Assert.Equal("1", e.eval("(if nil 0 1)").ToString()); + Assert.Equal("0", e.eval("(if t 0 1)").ToString()); + Assert.Equal("(1 2 3)", e.eval("(list 1 2 3)").ToString()); Assert.Equal("3", e.eval("(find 3 (list 1 2 3 4))").ToString()); + Assert.Equal("nil", e.eval("(find 0 (list 1 2 3 4))").ToString()); + Assert.Equal("(2 4 6)", e.eval("(map (lambda (x) (* x 2)) (quote (1 2 3)))").ToString()); + Assert.Equal("nil", e.eval("(and (quote (1 2 3 nil)))").ToString()); + Assert.Equal("t", e.eval("(and (quote (1 2 3 4)))").ToString()); + Assert.Equal("t", e.eval("(or (quote (nil nil 1 nil)))").ToString()); + Assert.Equal("nil", e.eval("(or (quote (nil nil nil nil)))").ToString()); } } }