feat(lisp): more builtins instead of derived.

This commit is contained in:
redxef 2024-11-08 03:08:29 +01:00
parent 6ee9bd7f67
commit 396384fd71
Signed by: redxef
GPG key ID: 7DAC3AA211CBD921
3 changed files with 84 additions and 26 deletions

View file

@ -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;
}
}

View file

@ -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 {

View file

@ -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());
}
}
}