fix!: simplify parser and executor, streamline tests.
This commit is contained in:
parent
5835351401
commit
8ec393f494
6 changed files with 639 additions and 720 deletions
|
@ -1,272 +1,7 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using Jellyfin.Plugin.SmartPlaylist.Lisp;
|
||||||
|
|
||||||
namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
interface IAddable<T> where T : IAddable<T> {
|
|
||||||
static abstract T operator +(T left, T right);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ISubtractable<T> where T : ISubtractable<T> {
|
|
||||||
static abstract T operator -(T left, T right);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IMultiplicatable<T> where T : IMultiplicatable<T> {
|
|
||||||
static abstract T operator *(T left, T right);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IDivisible<T> where T : IDivisible<T> {
|
|
||||||
static abstract T operator /(T left, T right);
|
|
||||||
static abstract T operator %(T left, T right);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ISortable<T, E> where T : ISortable<T, E> {
|
|
||||||
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<T, E> where T : IComparable<T, E> {
|
|
||||||
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<Expression, bool> {
|
|
||||||
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<Integer>, ISubtractable<Integer>, IMultiplicatable<Integer>, IDivisible<Integer>, ISortable<Integer, Boolean> {
|
|
||||||
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<String> {
|
|
||||||
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<object> 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<Expression> _expressions;
|
|
||||||
public List(IList<Expression> expressions) {
|
|
||||||
_expressions = expressions;
|
|
||||||
}
|
|
||||||
public IList<Expression> 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<Expression> r = new List<Expression>();
|
|
||||||
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 {
|
public class Parser {
|
||||||
private StringTokenStream _sts;
|
private StringTokenStream _sts;
|
||||||
public Parser(StringTokenStream tokens) {
|
public Parser(StringTokenStream tokens) {
|
||||||
|
@ -283,17 +18,16 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
return parse_grouping(gt, gt.closing_value);
|
return parse_grouping(gt, gt.closing_value);
|
||||||
case AtomToken at:
|
case AtomToken at:
|
||||||
return parse_atom(at);
|
return parse_atom(at);
|
||||||
case OperatorToken ot:
|
|
||||||
return parse_operator(ot);
|
|
||||||
case SpaceToken sp:
|
case SpaceToken sp:
|
||||||
return parse();
|
return parse();
|
||||||
}
|
}
|
||||||
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(start.value == end.value);
|
||||||
Debug.Assert("'\"".Contains(start.value));
|
Debug.Assert("\"".Contains(start.value));
|
||||||
string r = "";
|
string r = "";
|
||||||
while (_sts.Available() > 0) {
|
while (_sts.Available() > 0) {
|
||||||
Token<string> t = _sts.Get();
|
Token<string> t = _sts.Get();
|
||||||
|
@ -306,8 +40,9 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
return new String(r);
|
return new String(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
Expression parse_grouping(GroupingToken start, GroupingToken end) {
|
Expression parse_grouping(GroupingToken start, GroupingToken? end) {
|
||||||
if ("'\"".Contains(start.value)) {
|
Debug.Assert(end != null);
|
||||||
|
if ("\"".Contains(start.value)) {
|
||||||
return parse_string(start, end);
|
return parse_string(start, end);
|
||||||
}
|
}
|
||||||
IList<Expression> expressions = new List<Expression>();
|
IList<Expression> expressions = new List<Expression>();
|
||||||
|
@ -320,7 +55,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
_sts.Rewind(1);
|
_sts.Rewind(1);
|
||||||
expressions.Add(parse());
|
expressions.Add(parse());
|
||||||
}
|
}
|
||||||
return new List(expressions);
|
return Cons.FromList(expressions);
|
||||||
}
|
}
|
||||||
|
|
||||||
Expression parse_atom(AtomToken at) {
|
Expression parse_atom(AtomToken at) {
|
||||||
|
@ -330,27 +65,13 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
return new Integer(parsed_value);
|
return new Integer(parsed_value);
|
||||||
}
|
}
|
||||||
if (at.value.Equals("t")) {
|
if (at.value.Equals("t")) {
|
||||||
return new Boolean(true);
|
return Boolean.TRUE;
|
||||||
}
|
}
|
||||||
if (at.value.Equals("nil")) {
|
if (at.value.Equals("nil")) {
|
||||||
return new Boolean(false);
|
return Boolean.FALSE;
|
||||||
}
|
}
|
||||||
_sts.Commit();
|
_sts.Commit();
|
||||||
return new Symbol(at.value);
|
return new Symbol(at.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Expression parse_operator(OperatorToken ot) {
|
|
||||||
string v = ot.value;
|
|
||||||
while (_sts.Available() > 0) {
|
|
||||||
Token<string> t = _sts.Get();
|
|
||||||
if (t is OperatorToken ot_) {
|
|
||||||
v += ot_.value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
_sts.Rewind(1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return new Symbol(v);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
class SpaceToken : Token<string> {
|
class SpaceToken : Token<string> {
|
||||||
private SpaceToken(string value) : base(value) {}
|
private SpaceToken(string value) : base(value) {}
|
||||||
private static IToken<string>? take(CharStream program) {
|
private static IToken<string>? take(CharStream program) {
|
||||||
char[] spaces = [' ', '\n'];
|
string spaces = " \n";
|
||||||
if (program.Available() == 0) {
|
if (program.Available() == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
char t = program.Get();
|
char t = program.Get();
|
||||||
if ("()\"'".Contains(t)) {
|
if ("()\"".Contains(t)) {
|
||||||
return new GroupingToken(t.ToString());
|
return new GroupingToken(t.ToString());
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -65,7 +65,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
string value = "";
|
string value = "";
|
||||||
while (program.Available() > 0) {
|
while (program.Available() > 0) {
|
||||||
char t = program.Get();
|
char t = program.Get();
|
||||||
if (!"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".Contains(t)) {
|
if (" \n()\"".Contains(t)) {
|
||||||
if (value.Equals("")) {
|
if (value.Equals("")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -78,21 +78,6 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OperatorToken : Token<string> {
|
|
||||||
private OperatorToken(string value) : base(value) {}
|
|
||||||
private static IToken<string>? 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<char> {
|
class CharStream: Stream<char> {
|
||||||
public CharStream(IList<char> items) : base(items) {}
|
public CharStream(IList<char> items) : base(items) {}
|
||||||
public CharStream(string items) : base(items.ToCharArray().Cast<char>().ToList()) {}
|
public CharStream(string items) : base(items.ToCharArray().Cast<char>().ToList()) {}
|
||||||
|
@ -103,7 +88,6 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
typeof(SpaceToken),
|
typeof(SpaceToken),
|
||||||
typeof(GroupingToken),
|
typeof(GroupingToken),
|
||||||
typeof(AtomToken),
|
typeof(AtomToken),
|
||||||
typeof(OperatorToken),
|
|
||||||
};
|
};
|
||||||
protected StringTokenStream(IList<Token<string>> tokens) : base(tokens) {}
|
protected StringTokenStream(IList<Token<string>> tokens) : base(tokens) {}
|
||||||
private static StringTokenStream generate(CharStream program) {
|
private static StringTokenStream generate(CharStream program) {
|
||||||
|
|
294
Jellyfin.Plugin.SmartPlaylist/Lisp/Expression.cs
Normal file
294
Jellyfin.Plugin.SmartPlaylist/Lisp/Expression.cs
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
|
interface IAddable<T> where T : IAddable<T> {
|
||||||
|
static abstract T operator +(T left, T right);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ISubtractable<T> where T : ISubtractable<T> {
|
||||||
|
static abstract T operator -(T left, T right);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IMultiplicatable<T> where T : IMultiplicatable<T> {
|
||||||
|
static abstract T operator *(T left, T right);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IDivisible<T> where T : IDivisible<T> {
|
||||||
|
static abstract T operator /(T left, T right);
|
||||||
|
static abstract T operator %(T left, T right);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ISortable<T, E> where T : ISortable<T, E> {
|
||||||
|
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<T, E> where T : IComparable<T, E> {
|
||||||
|
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<Expression, bool> {
|
||||||
|
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<V> : 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<V> 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<int>, IAddable<Integer>, ISubtractable<Integer>, IMultiplicatable<Integer>, IDivisible<Integer>, ISortable<Integer, Boolean> {
|
||||||
|
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<bool> {
|
||||||
|
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<Expression> ToList() {
|
||||||
|
if (_value) {
|
||||||
|
throw new ApplicationException("Cannot use t as list");
|
||||||
|
}
|
||||||
|
return new List<Expression>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class String: Scalar<string> {
|
||||||
|
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<Expression> 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<Expression> ToList() {
|
||||||
|
var l = new List<Expression>();
|
||||||
|
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<object> {
|
||||||
|
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<object> e:
|
||||||
|
return Cons.FromList(e.Select(x => FromBase(x)));
|
||||||
|
default:
|
||||||
|
return new Object(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Procedure : Expression {
|
||||||
|
private IEnumerable<Symbol> _parameters;
|
||||||
|
private Expression _body;
|
||||||
|
private bool _eval_args;
|
||||||
|
public Procedure(IEnumerable<Symbol> 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<Expression> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -3,63 +3,19 @@ using System.Reflection;
|
||||||
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
|
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
|
||||||
|
|
||||||
namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
using Function = Func<IList<Expression>, Expression>;
|
using Function = Func<IEnumerable<Expression>, Expression>;
|
||||||
using FunctionLater = Func<Executor, IList<Expression>, Expression>;
|
using FunctionLater = Func<Executor, IEnumerable<Expression>, 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<T1, T2>(IEnumerable<T1> a, IEnumerable<T2> 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<Expression> 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<string, Expression>(p, args)) {
|
|
||||||
new_e.environment.Set(tuple.Item1, tuple.Item2);
|
|
||||||
}
|
|
||||||
return new_e.eval(_body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IEnvironment<K, V> {
|
public interface IEnvironment<K, V> {
|
||||||
public V Get(K k);
|
public V Get(K k);
|
||||||
public void Set(K k, V v);
|
public void Set(K k, V v);
|
||||||
public IEnvironment<K, V>? Find(K k);
|
public IEnvironment<K, V>? Find(K k);
|
||||||
|
public IEnvironment<K, V> Parent(bool recursive);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Environment : Dictionary<string, Expression>, IEnvironment<string, Expression> {
|
public class Environment : Dictionary<string, Expression>, IEnvironment<string, Expression> {
|
||||||
public Expression? Get(string k) {
|
public Expression? Get(string k) {
|
||||||
if (TryGetValue(k, out Expression v)) {
|
if (TryGetValue(k, out Expression? v)) {
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -74,11 +30,24 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnvironment<string, Expression> Parent(bool recursive) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DefaultEnvironment: Environment {
|
public class DefaultEnvironment: Environment {
|
||||||
public DefaultEnvironment() {
|
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;
|
_super = super;
|
||||||
}
|
}
|
||||||
public Expression? Get(string k) {
|
public Expression? Get(string k) {
|
||||||
if (TryGetValue(k, out Expression v)) {
|
if (TryGetValue(k, out Expression? v)) {
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -103,276 +72,206 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
|
||||||
}
|
}
|
||||||
return _super.Find(k);
|
return _super.Find(k);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnvironment<string, Expression> Parent(bool recursive) {
|
||||||
|
if (recursive) {
|
||||||
|
return this._super.Parent(recursive);
|
||||||
|
}
|
||||||
|
return this._super;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Builtins : Dictionary<string, Function> {
|
public class Builtins : Dictionary<string, Function> {
|
||||||
public Builtins() : base() {
|
public Builtins() : base() {
|
||||||
this["+"] = _add;
|
this["atom"] = _atom;
|
||||||
this["-"] = _sub;
|
this["eq"] = _eq;
|
||||||
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["car"] = _car;
|
this["car"] = _car;
|
||||||
this["cdr"] = _cdr;
|
this["cdr"] = _cdr;
|
||||||
this["cons"] = _cons;
|
this["cons"] = _cons;
|
||||||
this["list"] = _list;
|
|
||||||
this["not"] = _not;
|
this["begin"] = _begin;
|
||||||
this["length"] = _length;
|
|
||||||
|
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["haskeys"] = _haskeys;
|
||||||
this["getitems"] = _getitems;
|
this["getitems"] = _getitems;
|
||||||
this["invoke"] = _invoke;
|
this["invoke"] = _invoke;
|
||||||
}
|
}
|
||||||
|
private static T _agg<T>(Func<T, T, T> op, IEnumerable<Expression> args) where T : Expression {
|
||||||
private static T _agg<T>(Func<T, T, T> op, IList<T> args) {
|
T agg = (T) args.First();
|
||||||
T agg = args[0];
|
|
||||||
foreach (var arg in args.Skip(1)) {
|
foreach (var arg in args.Skip(1)) {
|
||||||
agg = op(agg, arg);
|
var arg_ = (T) arg;
|
||||||
|
agg = op(agg, arg_);
|
||||||
}
|
}
|
||||||
return agg;
|
return agg;
|
||||||
}
|
}
|
||||||
private static Expression _add(IList<Expression> args) {
|
private static E _cmp<T, E>(Func<T, T, E> op, IEnumerable<Expression> args) where T : Expression where E : Expression {
|
||||||
Expression first = args[0];
|
return op((T) args.First(), (T) args.Skip(1).First());
|
||||||
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 Expression _atom(IEnumerable<Expression> args) {
|
||||||
|
return (args.First() is Atom) ? Boolean.TRUE : Boolean.FALSE;
|
||||||
}
|
}
|
||||||
private static Expression _sub(IList<Expression> args) {
|
private static Expression _eq(IEnumerable<Expression> args) {
|
||||||
Expression first = args[0];
|
return args.First().Equals(args.Skip(1).First()) ? Boolean.TRUE : Boolean.FALSE;
|
||||||
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<Expression> args) {
|
||||||
|
return ((Cons)args.First()).Item1;
|
||||||
}
|
}
|
||||||
private static Expression _mul(IList<Expression> args) {
|
private static Expression _cdr(IEnumerable<Expression> args) {
|
||||||
Expression first = args[0];
|
return ((Cons)args.First()).Item2;
|
||||||
switch (first) {
|
|
||||||
case Integer i:
|
|
||||||
return _agg((a, b) => a * b, args.Select(x => (Integer) x).ToList());
|
|
||||||
}
|
}
|
||||||
throw new ApplicationException();
|
private static Expression _cons(IEnumerable<Expression> args) {
|
||||||
|
return new Cons(args.First(), args.Skip(1).First());
|
||||||
}
|
}
|
||||||
private static Expression _div(IList<Expression> args) {
|
private static Expression _begin(IEnumerable<Expression> 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 _mod(IList<Expression> 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 E _cmp<T, E>(Func<T, T, E> op, IList<T> args) {
|
|
||||||
T first = args[0];
|
|
||||||
T second = args[1];
|
|
||||||
return op(first, second);
|
|
||||||
}
|
|
||||||
private static Expression _gt(IList<Expression> 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<Expression> 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<Expression> 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<Expression> 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<Expression> args) {
|
|
||||||
bool r = _cmp((a, b) => a == b, args);
|
|
||||||
return new Compiler.Boolean(r);
|
|
||||||
}
|
|
||||||
private static Expression _ne(IList<Expression> args) {
|
|
||||||
bool r = _cmp((a, b) => a != b, args);
|
|
||||||
return new Compiler.Boolean(r);
|
|
||||||
}
|
|
||||||
private static Expression _abs(IList<Expression> 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<Expression> args) {
|
|
||||||
Expression first = args[0];
|
|
||||||
switch (first) {
|
|
||||||
case List l:
|
|
||||||
return l + new List(args);
|
|
||||||
}
|
|
||||||
throw new ApplicationException();
|
|
||||||
}
|
|
||||||
private static Expression _begin(IList<Expression> args) {
|
|
||||||
return args.Last();
|
return args.Last();
|
||||||
}
|
}
|
||||||
private static Expression _car(IList<Expression> args) {
|
private static Expression _haskeys(IEnumerable<Expression> args) {
|
||||||
return ((List) args.First()).expressions.First();
|
Object o = (Object) args.First();
|
||||||
}
|
|
||||||
private static Expression _cdr(IList<Expression> args) {
|
|
||||||
return new List(((List) args.First()).expressions.Skip(1).ToList());
|
|
||||||
}
|
|
||||||
private static Expression _cons(IList<Expression> 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<Expression> args) {
|
|
||||||
return new Compiler.List(args);
|
|
||||||
}
|
|
||||||
private static Expression _not(IList<Expression> args) {
|
|
||||||
if (args[0] == new Compiler.Boolean(false)) {
|
|
||||||
return new Compiler.Boolean(true);
|
|
||||||
}
|
|
||||||
return new Compiler.Boolean(false);
|
|
||||||
}
|
|
||||||
private static Expression _length(IList<Expression> args) {
|
|
||||||
return new Integer(((Compiler.List)args[0]).expressions.Count());
|
|
||||||
}
|
|
||||||
private static Expression _haskeys(IList<Expression> args) {
|
|
||||||
Compiler.Object o = (Compiler.Object) args[0];
|
|
||||||
foreach (var e in args.Skip(1)) {
|
foreach (var e in args.Skip(1)) {
|
||||||
Compiler.String s = (Compiler.String) e;
|
String s = (String) e;
|
||||||
PropertyInfo? pi = o.value.GetType().GetProperty(s.value);
|
PropertyInfo? pi = o.Value().GetType().GetProperty(s.Value());
|
||||||
if (pi != null) {
|
if (pi != null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
MethodInfo? mi = o.value.GetType().GetMethod(s.value);
|
MethodInfo? mi = o.Value().GetType().GetMethod(s.Value());
|
||||||
if (mi != null) {
|
if (mi != null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
FieldInfo? fi = o.value.GetType().GetField(s.value);
|
FieldInfo? fi = o.Value().GetType().GetField(s.Value());
|
||||||
if (fi != null) {
|
if (fi != null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
return new Compiler.Boolean(false);
|
return Boolean.FALSE;
|
||||||
}
|
}
|
||||||
return new Compiler.Boolean(true);
|
return Boolean.TRUE;
|
||||||
}
|
}
|
||||||
private static Expression _getitems(IList<Expression> args) {
|
private static Expression _getitems(IEnumerable<Expression> args) {
|
||||||
Compiler.Object o = (Compiler.Object) args[0];
|
Object o = (Object) args.First();
|
||||||
IList<Expression> r = new List<Expression>();
|
IList<Expression> r = new List<Expression>();
|
||||||
foreach (var e in args.Skip(1)) {
|
foreach (var e in args.Skip(1)) {
|
||||||
Compiler.String s = (Compiler.String) e;
|
String s = (String) e;
|
||||||
PropertyInfo? pi = o.value.GetType().GetProperty(s.value);
|
PropertyInfo? pi = o.Value().GetType().GetProperty(s.Value());
|
||||||
if (pi != null) {
|
if (pi != null) {
|
||||||
r.Add(Compiler.Object.FromBase(pi.GetValue(o.value)));
|
r.Add(Object.FromBase(pi.GetValue(o.Value())));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
FieldInfo? fi = o.value.GetType().GetField(s.value);
|
FieldInfo? fi = o.Value().GetType().GetField(s.Value());
|
||||||
if (fi != null) {
|
if (fi != null) {
|
||||||
r.Add(Compiler.Object.FromBase(fi.GetValue(o.value)));
|
r.Add(Object.FromBase(fi.GetValue(o.Value())));
|
||||||
continue;
|
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<Expression> args) {
|
private static Expression _invoke(IEnumerable<Expression> args) {
|
||||||
Compiler.Object o = (Compiler.Object) args[0];
|
Object o = (Object) args.First();
|
||||||
Compiler.String s = (Compiler.String) args[1];
|
String s = (String) args.Skip(1).First();
|
||||||
Compiler.List l = (Compiler.List) args[2];
|
Cons l = (Cons) args.Skip(2).First();
|
||||||
IList<Expression> r = new List<Expression>();
|
IList<Expression> r = new List<Expression>();
|
||||||
MethodInfo? mi = o.value.GetType().GetMethod(s.value);
|
MethodInfo? mi = o.Value().GetType().GetMethod(s.Value());
|
||||||
if (mi == null) {
|
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<string, FunctionLater> {
|
public class BuiltinsLater : Dictionary<string, FunctionLater> {
|
||||||
public BuiltinsLater() : base() {
|
public BuiltinsLater() : base() {
|
||||||
this["if"] = _if;
|
this["quote"] = _quote;
|
||||||
|
this["eval"] = _eval;
|
||||||
|
this["cond"] = _cond;
|
||||||
this["define"] = _define;
|
this["define"] = _define;
|
||||||
|
this["let"] = _let;
|
||||||
|
this["let*"] = _let_star;
|
||||||
this["lambda"] = _lambda;
|
this["lambda"] = _lambda;
|
||||||
this["apply"] = _apply;
|
this["lambda*"] = _lambda_star;
|
||||||
this["and"] = _and;
|
|
||||||
this["or"] = _or;
|
|
||||||
}
|
}
|
||||||
private static Expression _if(Executor e, IList<Expression> args) {
|
private static Expression _quote(Executor e, IEnumerable<Expression> args) {
|
||||||
bool test = e.eval(args[0]) != (new Compiler.Boolean(false));
|
return args.First();
|
||||||
return e.eval(args[1 + (test ? 0 : 1)]);
|
|
||||||
}
|
}
|
||||||
private static Expression _define(Executor e, IList<Expression> args) {
|
private static Expression _eval(Executor e, IEnumerable<Expression> args) {
|
||||||
var refname = ((Symbol) args[0]).name;
|
return e.eval(e.eval(args.First()));
|
||||||
e.environment.Set(refname, args[1]);
|
|
||||||
return new Compiler.Boolean(false); // NOOP
|
|
||||||
}
|
}
|
||||||
private static Expression _lambda(Executor e, IList<Expression> args) {
|
private static Expression _cond(Executor e, IEnumerable<Expression> args) {
|
||||||
return new Procedure((Compiler.List) args[0], args[1]);
|
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());
|
||||||
}
|
}
|
||||||
private static Expression _apply(Executor e, IList<Expression> args) {
|
} else {
|
||||||
if (args[0].GetType() != typeof(Symbol)) {
|
throw new ApplicationException($"Incorrect arguments to cond, expected list: {args}");
|
||||||
throw new ApplicationException();
|
|
||||||
}
|
}
|
||||||
if (args[1].GetType() != typeof(List)) {
|
|
||||||
throw new ApplicationException();
|
|
||||||
}
|
}
|
||||||
Symbol arg0 = (Compiler.Symbol) args[0];
|
return Boolean.FALSE;
|
||||||
Compiler.List other_args = (Compiler.List) args[1];
|
|
||||||
return e.EvalFunction(arg0, other_args.expressions);
|
|
||||||
}
|
}
|
||||||
private static Expression _and(Executor e, IList<Expression> args) {
|
private static Expression _define(Executor e, IEnumerable<Expression> args) {
|
||||||
Expression result = new Compiler.Boolean(false);
|
Symbol refname = (Symbol) args.First();
|
||||||
foreach (var exp in args) {
|
e.environment.Parent(true).Set(refname.Name(), args.Skip(1).Select(x => e.eval(x)).First());
|
||||||
result = e.eval(exp);
|
return Boolean.TRUE;
|
||||||
if (result == new Compiler.Boolean(false)) { return result; }
|
|
||||||
}
|
}
|
||||||
return result;
|
private static Expression _let_star(Executor e, IEnumerable<Expression> 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*");
|
||||||
}
|
}
|
||||||
private static Expression _or(Executor e, IList<Expression> args) {
|
Symbol refname = (Symbol) pair_cons.Item1;
|
||||||
Expression result = new Compiler.Boolean(false);
|
Expression exp = ((Cons) pair_cons.Item2).Item1;
|
||||||
foreach (var exp in args) {
|
new_e.environment.Set(refname.Name(), new_e.eval(exp));
|
||||||
result = e.eval(exp);
|
|
||||||
if (result != new Compiler.Boolean(false)) { return result; }
|
|
||||||
}
|
}
|
||||||
return result;
|
return new_e.eval(args.Last());
|
||||||
|
}
|
||||||
|
private static Expression _let(Executor e, IEnumerable<Expression> 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<Expression> args) {
|
||||||
|
IEnumerable<Symbol> 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<Symbol>(); }
|
||||||
|
else {
|
||||||
|
throw new ApplicationException("");
|
||||||
|
}
|
||||||
|
return new Procedure(proc_args, args.Skip(1).First(), true);
|
||||||
|
}
|
||||||
|
private static Expression _lambda_star(Executor e, IEnumerable<Expression> args) {
|
||||||
|
IEnumerable<Symbol> 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<Symbol>(); }
|
||||||
|
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 Builtins builtins { get => _builtins; }
|
||||||
public BuiltinsLater builtinsLater { get => _builtinsLater; }
|
public BuiltinsLater builtinsLater { get => _builtinsLater; }
|
||||||
|
|
||||||
public Expression EvalFunction(Symbol fcname, IList<Expression> args) {
|
public Expression? EvalFunction(Symbol fcname, IEnumerable<Expression> args) {
|
||||||
if (_environment.Find(fcname.name) is IEnvironment<string, Expression> _e && _e != null) {
|
if (builtins.ContainsKey(fcname.Name())) {
|
||||||
Expression first = _e.Get(fcname.name);
|
return builtins[fcname.Name()](args.Select(x => eval(x)).ToList()); // call ToList for sideeffect
|
||||||
return new List((new []{first}).Concat(args.ToArray()).Select(x => eval(x)).ToList());
|
|
||||||
}
|
}
|
||||||
if (_builtins.ContainsKey(fcname.name)) {
|
if (builtinsLater.ContainsKey(fcname.Name())) {
|
||||||
Function fc = _builtins[fcname.name];
|
return builtinsLater[fcname.Name()](this, args);
|
||||||
return fc(args.Select(x => eval(x)).ToList());
|
|
||||||
}
|
}
|
||||||
if (_builtinsLater.ContainsKey(fcname.name)) {
|
return null;
|
||||||
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) {
|
public Expression eval(Expression expression) {
|
||||||
switch (expression) {
|
switch (expression) {
|
||||||
case Symbol s:
|
case Symbol s:
|
||||||
if (_environment.Find(s.name) is not IEnvironment<string, Expression> env) {
|
if (_environment.Find(s.Name()) is not IEnvironment<string, Expression> env) {
|
||||||
throw new ApplicationException($"Could not find '{s.name}'");
|
throw new ApplicationException($"Could not find '{s.Name()}'");
|
||||||
}
|
}
|
||||||
return env.Get(s.name);
|
return env.Get(s.Name());
|
||||||
case Compiler.Boolean b:
|
case Boolean b:
|
||||||
return b;
|
return b;
|
||||||
case Integer i:
|
case Integer i:
|
||||||
return i;
|
return i;
|
||||||
case Compiler.String s:
|
case String s:
|
||||||
return s;
|
return s;
|
||||||
case Compiler.Object o:
|
case Object o:
|
||||||
return o;
|
return o;
|
||||||
case Procedure p:
|
case Procedure p:
|
||||||
return p;
|
return p;
|
||||||
case List list:
|
case Cons cons:
|
||||||
if (list.expressions.Count == 0) {
|
var l = cons.ToList();
|
||||||
return list;
|
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) {
|
var eval_Item1 = eval(cons.Item1);
|
||||||
return eval(EvalFunction(fc_symbol, list.expressions.Skip(1).ToList()));
|
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) {
|
if (eval_Item1 is Procedure eval_item1_procedure) {
|
||||||
return eval(procedure.Call(this, list.expressions.Skip(1).ToList()));
|
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());
|
throw new ApplicationException($"Not handled case (type = {eval_Item1.GetType()}) '{cons}'");
|
||||||
if (l.expressions[0] is Symbol|| l.expressions[0] is Procedure) {
|
|
||||||
return eval(l);
|
|
||||||
}
|
|
||||||
return l;
|
|
||||||
}
|
}
|
||||||
throw new ApplicationException($"Not handled case '{expression}'");
|
throw new ApplicationException($"Not handled case '{expression}'");
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ using MediaBrowser.Model.Playlists;
|
||||||
|
|
||||||
using Jellyfin.Plugin.SmartPlaylist.Lisp;
|
using Jellyfin.Plugin.SmartPlaylist.Lisp;
|
||||||
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
|
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
|
||||||
using Lisp_Object = Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler.Object;
|
using Lisp_Object = Jellyfin.Plugin.SmartPlaylist.Lisp.Object;
|
||||||
using Lisp_Boolean = Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler.Boolean;
|
using Lisp_Boolean = Jellyfin.Plugin.SmartPlaylist.Lisp.Boolean;
|
||||||
|
|
||||||
|
|
||||||
namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
||||||
|
@ -102,7 +102,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
||||||
executor.environment.Set("item", new Lisp_Object(i));
|
executor.environment.Set("item", new Lisp_Object(i));
|
||||||
var r = executor.eval(expression);
|
var r = executor.eval(expression);
|
||||||
_logger.LogTrace("Item {0} evaluated to {1}", i, r.ToString());
|
_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);
|
_logger.LogDebug("Added '{0}' to Smart Playlist {1}", i, smartPlaylist.Name);
|
||||||
results.Add(i.Id);
|
results.Add(i.Id);
|
||||||
}
|
}
|
||||||
|
|
270
Tests/Tests.cs
270
Tests/Tests.cs
|
@ -1,7 +1,7 @@
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Lisp_Environment = Jellyfin.Plugin.SmartPlaylist.Lisp.Environment;
|
using Lisp_Environment = Jellyfin.Plugin.SmartPlaylist.Lisp.Environment;
|
||||||
using Lisp_Boolean = Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler.Boolean;
|
using Lisp_Boolean = Jellyfin.Plugin.SmartPlaylist.Lisp.Boolean;
|
||||||
using Lisp_Object = Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler.Object;
|
using Lisp_Object = Jellyfin.Plugin.SmartPlaylist.Lisp.Object;
|
||||||
using Jellyfin.Plugin.SmartPlaylist.Lisp;
|
using Jellyfin.Plugin.SmartPlaylist.Lisp;
|
||||||
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
|
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
|
||||||
|
|
||||||
|
@ -22,39 +22,33 @@ namespace Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public static void TestTokenizer() {
|
public static void TestTokenizer() {
|
||||||
StringTokenStream sts = StringTokenStream.generate("(\"some literal string\" def ghj +100 -+300 1 >= ++ !=)");
|
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, "\"");
|
Assert.Equal("\"", sts.Get().value);
|
||||||
Assert.Equal(sts.Get().value, "some");
|
Assert.Equal("some", sts.Get().value);
|
||||||
Assert.Equal(sts.Get().value, " ");
|
Assert.Equal(" ", sts.Get().value);
|
||||||
Assert.Equal(sts.Get().value, "literal");
|
Assert.Equal("literal", sts.Get().value);
|
||||||
Assert.Equal(sts.Get().value, " ");
|
Assert.Equal(" ", sts.Get().value);
|
||||||
Assert.Equal(sts.Get().value, "string");
|
Assert.Equal("string", 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, "def");
|
Assert.Equal("def", sts.Get().value);
|
||||||
Assert.Equal(sts.Get().value, " ");
|
Assert.Equal(" ", sts.Get().value);
|
||||||
Assert.Equal(sts.Get().value, "ghj");
|
Assert.Equal("ghj", sts.Get().value);
|
||||||
Assert.Equal(sts.Get().value, " ");
|
Assert.Equal(" ", sts.Get().value);
|
||||||
Assert.Equal(sts.Get().value, "+");
|
Assert.Equal("+100", sts.Get().value);
|
||||||
Assert.Equal(sts.Get().value, "100");
|
Assert.Equal(" ", sts.Get().value);
|
||||||
Assert.Equal(sts.Get().value, " ");
|
Assert.Equal("-+300", sts.Get().value);
|
||||||
Assert.Equal(sts.Get().value, "-");
|
Assert.Equal(" ", sts.Get().value);
|
||||||
Assert.Equal(sts.Get().value, "+");
|
Assert.Equal("1", sts.Get().value);
|
||||||
Assert.Equal(sts.Get().value, "300");
|
Assert.Equal(" ", sts.Get().value);
|
||||||
Assert.Equal(sts.Get().value, " ");
|
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(sts.Get().value, "!");
|
|
||||||
Assert.Equal(sts.Get().value, "=");
|
|
||||||
Assert.Equal(sts.Get().value, ")");
|
|
||||||
sts.Commit();
|
sts.Commit();
|
||||||
Assert.Equal(sts.Available(), 0);
|
Assert.Equal(0, sts.Available());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@ -68,125 +62,159 @@ namespace Tests
|
||||||
sts = StringTokenStream.generate(program);
|
sts = StringTokenStream.generate(program);
|
||||||
p = new Parser(sts);
|
p = new Parser(sts);
|
||||||
Assert.Equal(program, string.Format("{0}", p.parse()));
|
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]
|
[Fact]
|
||||||
public static void TestFunctions() {
|
public static void TestFunctions() {
|
||||||
IList<Tuple<string, Expression>> cases = new List<Tuple<string, Expression>>();
|
Executor e = new Executor();
|
||||||
Expression e = new Executor().eval("(+ 10 20)");
|
Assert.Equal("(1 2 3)", e.eval("(quote (1 2 3))").ToString());
|
||||||
Assert.Equal(((Integer) e).value, 30);
|
Assert.Equal("abc", e.eval("(quote abc)").ToString());
|
||||||
|
|
||||||
e = new Executor().eval("(> 1 2)");
|
Assert.Equal("t", e.eval("(atom 1)").ToString());
|
||||||
Assert.Equal(((Lisp_Boolean) e).value, false);
|
Assert.Equal("nil", e.eval("(atom (quote (1 2 3)))").ToString());
|
||||||
|
|
||||||
e = new Executor().eval("(if (> 1 2) 3 4)");
|
Assert.Equal("t", e.eval("(eq 2 2)").ToString());
|
||||||
Assert.Equal(((Integer) e).value, 4);
|
Assert.Equal("nil", e.eval("(eq 2 3)").ToString());
|
||||||
|
|
||||||
e = new Executor().eval("(begin (define x 1) x)");
|
Assert.Equal("1", e.eval("(car (quote (1 2 3)))").ToString());
|
||||||
Assert.Equal(((Integer) e).value, 1);
|
Assert.Equal("(2 3)", e.eval("(cdr (quote (1 2 3)))").ToString());
|
||||||
|
|
||||||
e = new Executor().eval("(apply + (1 2))");
|
Assert.Equal("(1 . 2)", e.eval("(cons 1 2)").ToString());
|
||||||
Assert.Equal(((Integer) e).value, 3);
|
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("\"Case 2\"", e.eval("""
|
||||||
Assert.Equal(((Integer) e).value, 10);
|
(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("t", e.eval("((lambda (a) (eq a a)) 2)").ToString());
|
||||||
Assert.Equal(string.Format("{0}", e), "(20 30)");
|
|
||||||
|
|
||||||
e = new Executor().eval("(cons 1 3)");
|
Assert.Equal("t", e.eval("(begin (car (quote (nil 1))) t)").ToString());
|
||||||
Assert.Equal(string.Format("{0}", e), "(1 3)");
|
Assert.Equal("(1)", e.eval("(begin t (cdr (quote (nil 1))))").ToString());
|
||||||
|
|
||||||
e = new Executor().eval("(cons 1 (list 2 3))");
|
Assert.Equal("t", e.eval("""
|
||||||
Assert.Equal(string.Format("{0}", e), "(1 2 3)");
|
(begin
|
||||||
|
(define abc 10)
|
||||||
|
(eq abc abc))
|
||||||
|
""").ToString());
|
||||||
|
|
||||||
e = new Executor().eval("(length (cons 1 (list 2 3)))");
|
Assert.Equal("1", e.eval("""
|
||||||
Assert.Equal(string.Format("{0}", e), "3");
|
(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))");
|
[Fact]
|
||||||
Assert.Equal(string.Format("{0}", e), "nil");
|
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("(3 4 5)", e.eval("""
|
||||||
Assert.Equal("4", e.ToString());
|
((lambda (x y . z) z) 1 2 3 4 5)
|
||||||
|
""").ToString());
|
||||||
|
|
||||||
e = new Executor().eval("(and 2 nil 4)");
|
Assert.Equal("3", e.eval("""
|
||||||
Assert.Equal("nil", e.ToString());
|
(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.eval("""
|
||||||
Assert.Equal("2", e.ToString());
|
(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]
|
[Fact]
|
||||||
public static void ObjectTest() {
|
public static void ObjectTest() {
|
||||||
Executor e = new Executor();
|
Executor e = new Executor();
|
||||||
Expression r;
|
Expression r;
|
||||||
e.environment.Set("o", new Lisp_Object(new O(5, false)));
|
e.environment.Set("o", new Lisp_Object(new O(5, false)));
|
||||||
r = e.eval("(haskeys o 'i' 'b')");
|
r = e.eval("""(haskeys o "i" "b")""");
|
||||||
Assert.Equal(((Lisp_Boolean)r).value, true);
|
Assert.Equal(((Lisp_Boolean)r).Value(), true);
|
||||||
r = e.eval("(getitems o 'i' 'b')");
|
r = e.eval("""(getitems o "i" "b")""");
|
||||||
Assert.Equal(string.Format("{0}", r), "(5 nil)");
|
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]
|
[Fact]
|
||||||
public static void DefaultEnvironmentTest() {
|
public static void DefaultEnvironmentTest() {
|
||||||
Executor e = new Executor(new DefaultEnvironment());
|
Executor e = new Executor(new DefaultEnvironment());
|
||||||
Assert.Equal("nil", e.eval("(find 0 (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("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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue