feat(lisp): more builtins instead of derived.
This commit is contained in:
parent
6ee9bd7f67
commit
396384fd71
3 changed files with 84 additions and 26 deletions
|
@ -29,6 +29,10 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
|||
E Equals(T other);
|
||||
}
|
||||
|
||||
interface IInner {
|
||||
public object Inner();
|
||||
}
|
||||
|
||||
public abstract class Expression: IComparable<Expression, bool> {
|
||||
public override abstract string? ToString();
|
||||
public abstract override int GetHashCode();
|
||||
|
@ -48,7 +52,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
|||
}
|
||||
|
||||
public abstract class Atom : Expression {}
|
||||
public class Scalar<V> : Atom where V : notnull {
|
||||
public class Scalar<V> : Atom, IInner where V : notnull {
|
||||
protected V _value;
|
||||
public Scalar(V value) {
|
||||
_value = value;
|
||||
|
@ -68,6 +72,9 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
|||
public V Value() {
|
||||
return _value;
|
||||
}
|
||||
public object Inner() {
|
||||
return _value;
|
||||
}
|
||||
}
|
||||
public class Symbol : Atom {
|
||||
private string _name;
|
||||
|
@ -276,18 +283,24 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
|||
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;
|
||||
|
||||
IList<(string, Expression)> name_args = new List<(string, Expression)>();
|
||||
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]));
|
||||
name_args.Add((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))));
|
||||
name_args.Add((_params[idx_rest], Cons.FromList(args.Skip(idx_rest - 1).Select(x => _eval(e, x)))));
|
||||
}
|
||||
return new_e.eval(_body);
|
||||
foreach (var na in name_args) {
|
||||
new_e.environment.Set(na.Item1, na.Item2);
|
||||
}
|
||||
var r = new_e.eval(_body);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,15 +39,13 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
|||
public class DefaultEnvironment: Environment {
|
||||
public DefaultEnvironment() {
|
||||
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)))");
|
||||
this["fold"] = e.eval("(lambda (fc i l) (if (null l) i (fold fc (fc i (car l)) (cdr l))))");
|
||||
this["any"] = e.eval("(lambda (fc l) (apply or (map fc l)))");
|
||||
this["all"] = e.eval("(lambda (fc l) (apply and (map fc l)))");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,7 +102,10 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
|||
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["not"] = (x) => {
|
||||
return (x.First() == Boolean.FALSE) ? Boolean.TRUE : Boolean.FALSE;
|
||||
};
|
||||
|
||||
|
||||
this["haskeys"] = _haskeys;
|
||||
this["getitems"] = _getitems;
|
||||
|
@ -160,7 +161,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
|||
return Boolean.TRUE;
|
||||
}
|
||||
private static Expression _getitems(IEnumerable<Expression> args) {
|
||||
Object o = (Object) args.First();
|
||||
Object o = new Object(((IInner) args.First()).Inner());
|
||||
IList<Expression> r = new List<Expression>();
|
||||
foreach (var e in args.Skip(1)) {
|
||||
String s = (String) e;
|
||||
|
@ -180,7 +181,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
|||
}
|
||||
|
||||
private static Expression _invoke(IEnumerable<Expression> args) {
|
||||
Object o = (Object) args.First();
|
||||
Object o = new Object(((IInner) args.First()).Inner());
|
||||
String s = (String) args.Skip(1).First();
|
||||
IEnumerable<Expression> l;
|
||||
if (args.Skip(2).First() is Boolean lb && lb == Boolean.FALSE) {
|
||||
|
@ -190,25 +191,31 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
|||
} else {
|
||||
throw new ApplicationException($"Expected a list of arguments, got {args.Skip(2).First()}");
|
||||
}
|
||||
|
||||
IList<Expression> r = new List<Expression>();
|
||||
MethodInfo? mi = o.Value().GetType().GetMethod(s.Value());
|
||||
if (mi == null) {
|
||||
throw new ApplicationException($"{o.Value()} has not method {s.Value()}");
|
||||
}
|
||||
object?[]? l_ = l.Select<Expression, object?>(x => {
|
||||
object[]? l_ = l.Select<Expression, object>(x => {
|
||||
switch (x) {
|
||||
case Integer s:
|
||||
return s.Value();
|
||||
case Boolean b:
|
||||
return b.Value();
|
||||
case String s:
|
||||
return s.Value();
|
||||
case Object o:
|
||||
return o.Value();
|
||||
case Cons c:
|
||||
return c.ToList().ToList();
|
||||
}
|
||||
return null;
|
||||
throw new ApplicationException($"Unhandled value {x} (type {x.GetType()})");
|
||||
}).ToArray();
|
||||
Type[] l_types = l_.Select( x => {
|
||||
return x.GetType();
|
||||
}).ToArray();
|
||||
|
||||
IList<Expression> r = new List<Expression>();
|
||||
MethodInfo? mi = o.Value().GetType().GetMethod(s.Value(), l_types);
|
||||
if (mi == null) {
|
||||
throw new ApplicationException($"{o.Value()} has not method {s.Value()}");
|
||||
}
|
||||
|
||||
return Object.FromBase(mi.Invoke(o.Value(), l_));
|
||||
}
|
||||
}
|
||||
|
@ -218,11 +225,36 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
|||
this["quote"] = _quote;
|
||||
this["eval"] = _eval;
|
||||
this["cond"] = _cond;
|
||||
this["if"] = _if;
|
||||
this["define"] = _define;
|
||||
this["let"] = _let;
|
||||
this["let*"] = _let_star;
|
||||
this["lambda"] = _lambda;
|
||||
this["lambda*"] = _lambda_star;
|
||||
this["apply"] = _apply;
|
||||
|
||||
this["and"] = (e, x) => {
|
||||
Expression? r = null;
|
||||
foreach (var xi in x) {
|
||||
r = e.eval(xi);
|
||||
if (r == Boolean.FALSE) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
if (r is null) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
return r;
|
||||
};
|
||||
this["or"] = (e, x) => {
|
||||
foreach (var xi in x) {
|
||||
var r = e.eval(xi);
|
||||
if (r != Boolean.FALSE) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return Boolean.FALSE;
|
||||
};
|
||||
}
|
||||
private static Expression _quote(Executor e, IEnumerable<Expression> args) {
|
||||
return args.First();
|
||||
|
@ -243,6 +275,12 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
|||
}
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
private static Expression _if(Executor e, IEnumerable<Expression> args) {
|
||||
if (e.eval(args.First()).Equals(Boolean.FALSE)) {
|
||||
return e.eval(args.Skip(2).First());
|
||||
}
|
||||
return e.eval(args.Skip(1).First());
|
||||
}
|
||||
private static Expression _define(Executor e, IEnumerable<Expression> args) {
|
||||
Symbol refname = (Symbol) args.First();
|
||||
e.environment.Parent(true).Set(refname.Name(), args.Skip(1).Select(x => e.eval(x)).First());
|
||||
|
@ -269,7 +307,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
|||
}
|
||||
Symbol refname = (Symbol) pair_cons.Item1;
|
||||
Expression exp_ = ((Cons) pair_cons.Item2).Item1;
|
||||
vars.Add((refname, exp_));
|
||||
vars.Add((refname, e.eval(exp_)));
|
||||
}
|
||||
foreach (var pair in vars) {
|
||||
new_e.environment.Set(pair.Item1.Name(), pair.Item2);
|
||||
|
@ -294,6 +332,9 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
|||
}
|
||||
return new Procedure(proc_args, args.Skip(1).First(), false);
|
||||
}
|
||||
private static Expression _apply(Executor e, IEnumerable<Expression> args) {
|
||||
return e.eval(new Cons(args.First(), e.eval(args.Skip(1).First())));
|
||||
}
|
||||
}
|
||||
|
||||
public class Executor {
|
||||
|
|
|
@ -192,7 +192,6 @@ namespace Tests
|
|||
(define null (lambda* (x) (cond ((eval x) nil) (t t))))
|
||||
(null (quote (1 2))))
|
||||
""").ToString());
|
||||
|
||||
}
|
||||
[Fact]
|
||||
public static void ObjectTest() {
|
||||
|
@ -212,18 +211,23 @@ namespace Tests
|
|||
Executor e = new Executor(new DefaultEnvironment());
|
||||
Assert.Equal("1", e.eval("(if nil 0 1)").ToString());
|
||||
Assert.Equal("0", e.eval("(if t 0 1)").ToString());
|
||||
Assert.Equal("5", e.eval("(if t (if t 5 nil) nil)").ToString());
|
||||
Assert.Equal("nil", e.eval("(if t (if nil 5 nil) nil)").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());
|
||||
Assert.Equal("nil", e.eval("(and 1 2 3 nil)").ToString());
|
||||
Assert.Equal("t", e.eval("(and t t t t)").ToString());
|
||||
Assert.Equal("t", e.eval("(or nil nil t nil)").ToString());
|
||||
Assert.Equal("nil", e.eval("(or nil nil nil nil)").ToString());
|
||||
Assert.Equal("t", e.eval("(any (lambda (x) (= x 2)) (list 1 2 3 4 5 6))").ToString());
|
||||
Assert.Equal("nil", e.eval("(any (lambda (x) (= x 2)) (list 1 3 4 5 6))").ToString());
|
||||
Assert.Equal("nil", e.eval("(any (lambda (x) (= x 2)) nil)").ToString());
|
||||
Assert.Equal("t", e.eval("(all (lambda (x) (= 1 (% x 2))) (list 1 3 5))").ToString());
|
||||
Assert.Equal("nil", e.eval("(all (lambda (x) (= 1 (% x 2))) (list 1 3 4 5))").ToString());
|
||||
Assert.Equal("nil", e.eval("(all (lambda (x) (= x 2)) nil)").ToString());
|
||||
Assert.Equal("10", e.eval("(fold (lambda (a b) (+ a b)) 0 (list 1 2 3 4))").ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue