Compare commits

...

6 commits

11 changed files with 150 additions and 77 deletions

View file

@ -29,6 +29,10 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
E Equals(T other); E Equals(T other);
} }
interface IInner {
public object Inner();
}
public abstract class Expression: IComparable<Expression, bool> { public abstract class Expression: IComparable<Expression, bool> {
public override abstract string? ToString(); public override abstract string? ToString();
public abstract override int GetHashCode(); public abstract override int GetHashCode();
@ -48,7 +52,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
} }
public abstract class Atom : Expression {} 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; protected V _value;
public Scalar(V value) { public Scalar(V value) {
_value = value; _value = value;
@ -68,6 +72,9 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
public V Value() { public V Value() {
return _value; return _value;
} }
public object Inner() {
return _value;
}
} }
public class Symbol : Atom { public class Symbol : Atom {
private string _name; 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); Executor new_e = new Executor(new SubEnvironment(e.environment), e.builtins, e.builtinsLater);
var _params = _parameters.Select(x => x.Name()).ToArray(); var _params = _parameters.Select(x => x.Name()).ToArray();
var idx_rest = -1; var idx_rest = -1;
IList<(string, Expression)> name_args = new List<(string, Expression)>();
for (var i = 0; i < _parameters.Count(); i++) { for (var i = 0; i < _parameters.Count(); i++) {
var name = _params[i]; var name = _params[i];
if (name.Equals(".")) { if (name.Equals(".")) {
idx_rest = i + 1; idx_rest = i + 1;
break; break;
} }
new_e.environment.Set(name, _eval(e, args[i])); name_args.Add((name, _eval(e, args[i])));
} }
if (idx_rest > 0) { 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 class DefaultEnvironment: Environment {
public DefaultEnvironment() { public DefaultEnvironment() {
var e = new Executor(); 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["null"] = new Symbol("not");
this["list"] = e.eval("(lambda (. args) args)"); 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["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["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["fold"] = e.eval("(lambda (fc i l) (if (null l) i (fold fc (fc i (car l)) (cdr l))))");
this["or"] = e.eval("(lambda (l) (if (null l) nil (if (car l) t (or (cdr l)))))"); this["any"] = e.eval("(lambda (fc l) (apply or (map fc l)))");
this["any"] = e.eval("(lambda (fc l) (or (map fc l)))"); this["all"] = e.eval("(lambda (fc l) (apply and (map fc l)))");
this["all"] = e.eval("(lambda (fc l) (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[">="] = (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["haskeys"] = _haskeys;
this["getitems"] = _getitems; this["getitems"] = _getitems;
@ -160,7 +161,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
return Boolean.TRUE; return Boolean.TRUE;
} }
private static Expression _getitems(IEnumerable<Expression> args) { 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>(); IList<Expression> r = new List<Expression>();
foreach (var e in args.Skip(1)) { foreach (var e in args.Skip(1)) {
String s = (String) e; String s = (String) e;
@ -180,7 +181,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
} }
private static Expression _invoke(IEnumerable<Expression> args) { 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(); String s = (String) args.Skip(1).First();
IEnumerable<Expression> l; IEnumerable<Expression> l;
if (args.Skip(2).First() is Boolean lb && lb == Boolean.FALSE) { if (args.Skip(2).First() is Boolean lb && lb == Boolean.FALSE) {
@ -190,25 +191,31 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
} else { } else {
throw new ApplicationException($"Expected a list of arguments, got {args.Skip(2).First()}"); throw new ApplicationException($"Expected a list of arguments, got {args.Skip(2).First()}");
} }
object[]? l_ = l.Select<Expression, object>(x => {
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 => {
switch (x) { switch (x) {
case Integer s: case Integer s:
return s.Value(); return s.Value();
case Boolean b: case Boolean b:
return b.Value(); return b.Value();
case String s:
return s.Value();
case Object o: case Object o:
return o.Value(); return o.Value();
case Cons c: case Cons c:
return c.ToList().ToList(); return c.ToList().ToList();
} }
return null; throw new ApplicationException($"Unhandled value {x} (type {x.GetType()})");
}).ToArray(); }).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_)); return Object.FromBase(mi.Invoke(o.Value(), l_));
} }
} }
@ -218,11 +225,36 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
this["quote"] = _quote; this["quote"] = _quote;
this["eval"] = _eval; this["eval"] = _eval;
this["cond"] = _cond; this["cond"] = _cond;
this["if"] = _if;
this["define"] = _define; this["define"] = _define;
this["let"] = _let; this["let"] = _let;
this["let*"] = _let_star; this["let*"] = _let_star;
this["lambda"] = _lambda; this["lambda"] = _lambda;
this["lambda*"] = _lambda_star; 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) { private static Expression _quote(Executor e, IEnumerable<Expression> args) {
return args.First(); return args.First();
@ -243,6 +275,12 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
} }
return Boolean.FALSE; 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) { private static Expression _define(Executor e, IEnumerable<Expression> args) {
Symbol refname = (Symbol) args.First(); Symbol refname = (Symbol) args.First();
e.environment.Parent(true).Set(refname.Name(), args.Skip(1).Select(x => e.eval(x)).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; Symbol refname = (Symbol) pair_cons.Item1;
Expression exp_ = ((Cons) pair_cons.Item2).Item1; Expression exp_ = ((Cons) pair_cons.Item2).Item1;
vars.Add((refname, exp_)); vars.Add((refname, e.eval(exp_)));
} }
foreach (var pair in vars) { foreach (var pair in vars) {
new_e.environment.Set(pair.Item1.Name(), pair.Item2); 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); 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 { public class Executor {

View file

@ -2,8 +2,19 @@ using MediaBrowser.Model.Plugins;
namespace Jellyfin.Plugin.SmartPlaylist { namespace Jellyfin.Plugin.SmartPlaylist {
public class PluginConfiguration : BasePluginConfiguration { public class PluginConfiguration : BasePluginConfiguration {
public PluginConfiguration( public PluginConfiguration() {
) { InitialProgram = """
(begin
(define lower (lambda (s) (invoke s "ToLower" nil)))
(define is-genre (lambda (g g-list) (any (lambda (x) (invoke (lower x) "Contains" (list (lower g)))) g-list)))
(define is-genre-exact (lambda (g g-list) (find g g-list)))
(define genre-list (lambda nil (let (_g (getitems item "Genres")) (if (null _g) nil (car _g)))))
(define is-favorite (lambda nil (invoke item "IsFavoriteOrLiked" (list user)))))
(define is-favourite is-favorite)
""";
} }
public string InitialProgram { get; set; }
} }
} }

View file

@ -98,6 +98,11 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
Expression expression = new Parser(StringTokenStream.generate(smartPlaylist.Program)).parse(); Expression expression = new Parser(StringTokenStream.generate(smartPlaylist.Program)).parse();
Executor executor = new Executor(new DefaultEnvironment()); Executor executor = new Executor(new DefaultEnvironment());
executor.environment.Set("user", new Lisp_Object(user)); executor.environment.Set("user", new Lisp_Object(user));
if (Plugin.Instance is not null) {
executor.eval(Plugin.Instance.Configuration.InitialProgram);
} else {
throw new ApplicationException("Plugin Instance is not yet initialized");
}
foreach (var i in items) { foreach (var i in items) {
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);

View file

@ -41,7 +41,7 @@ namespace Jellyfin.Plugin.SmartPlaylist {
[Serializable] [Serializable]
public class SmartPlaylistDto : ISerializable { public class SmartPlaylistDto : ISerializable {
private static string DEFAULT_PROGRAM = "(begin (invoke item 'IsFavoriteOrLiked' (user)))"; private static string DEFAULT_PROGRAM = "(begin (invoke item \"IsFavoriteOrLiked\" (list user)))";
public SmartPlaylistId Id { get; set; } public SmartPlaylistId Id { get; set; }
public SmartPlaylistLinkDto[] Playlists { get; set; } public SmartPlaylistLinkDto[] Playlists { get; set; }
public string Name { get; set; } public string Name { get; set; }

View file

@ -16,13 +16,21 @@ namespace Jellyfin.Plugin.SmartPlaylist {
} }
public string StoragePath { get; } public string StoragePath { get; }
public string GetSmartPlaylistFilePath(SmartPlaylistId smartPlaylistId) { public string GetSmartPlaylistFilePath(SmartPlaylistId smartPlaylistId) {
return Path.Combine(StoragePath, $"{smartPlaylistId}.json"); return Path.Combine(StoragePath, $"{smartPlaylistId}.yaml");
} }
public string FindSmartPlaylistFilePath(SmartPlaylistId smartPlaylistId) { public string FindSmartPlaylistFilePath(SmartPlaylistId smartPlaylistId) {
return Directory.GetFiles(StoragePath, $"{smartPlaylistId}.json", SearchOption.AllDirectories).First(); return Directory.GetFiles(StoragePath, $"{smartPlaylistId}.yaml", SearchOption.AllDirectories).Concat(
Directory.GetFiles(StoragePath, $"{smartPlaylistId}.yml", SearchOption.AllDirectories)
).Concat(
Directory.GetFiles(StoragePath, $"{smartPlaylistId}.json", SearchOption.AllDirectories)
).First();
} }
public string[] FindAllSmartPlaylistFilePaths() { public string[] FindAllSmartPlaylistFilePaths() {
return Directory.GetFiles(StoragePath, "*.json", SearchOption.AllDirectories); return Directory.GetFiles(StoragePath, "*.yaml", SearchOption.AllDirectories).Concat(
Directory.GetFiles(StoragePath, "*.yml", SearchOption.AllDirectories)
).Concat(
Directory.GetFiles(StoragePath, "*.json", SearchOption.AllDirectories)
).ToArray();
} }
} }
} }

View file

@ -1,4 +1,4 @@
using System.Text.Json; using YamlDotNet.Serialization;
namespace Jellyfin.Plugin.SmartPlaylist { namespace Jellyfin.Plugin.SmartPlaylist {
public interface IStore { public interface IStore {
@ -14,14 +14,21 @@ namespace Jellyfin.Plugin.SmartPlaylist {
_fileSystem = fileSystem; _fileSystem = fileSystem;
} }
private async Task<SmartPlaylistDto> LoadPlaylistAsync(string filename) { private async Task<SmartPlaylistDto> LoadPlaylistAsync(string filename) {
await using var r = File.OpenRead(filename); var r = File.ReadAllText(filename);
var dto = (await JsonSerializer.DeserializeAsync<SmartPlaylistDto>(r).ConfigureAwait(false)); if (r.Equals("")) {
if (dto == null) { r = "{}";
}
var dto = new DeserializerBuilder().Build().Deserialize<SmartPlaylistDto>(r);
if (dto == null)
{
throw new ApplicationException(""); throw new ApplicationException("");
} }
if (dto.Id == Path.GetFileNameWithoutExtension(filename)) { if (dto.Id != Path.GetFileNameWithoutExtension(filename)) {
dto.Id = Path.GetFileNameWithoutExtension(filename); dto.Id = Path.GetFileNameWithoutExtension(filename);
} }
if (dto.Name != Path.GetFileNameWithoutExtension(filename)) {
dto.Name = Path.GetFileNameWithoutExtension(filename);
}
if (dto.Filename != filename) { if (dto.Filename != filename) {
dto.Filename = filename; dto.Filename = filename;
} }
@ -38,8 +45,8 @@ namespace Jellyfin.Plugin.SmartPlaylist {
} }
public async Task SaveSmartPlaylistAsync(SmartPlaylistDto smartPlaylist) { public async Task SaveSmartPlaylistAsync(SmartPlaylistDto smartPlaylist) {
string filename = _fileSystem.GetSmartPlaylistFilePath(smartPlaylist.Id); string filename = _fileSystem.GetSmartPlaylistFilePath(smartPlaylist.Id);
await using var w = File.Create(filename); var text = new SerializerBuilder().Build().Serialize(smartPlaylist);
await JsonSerializer.SerializeAsync(w, smartPlaylist).ConfigureAwait(false); File.WriteAllText(filename, text);
} }
private void DeleteSmartPlaylistById(SmartPlaylistId smartPlaylistId) { private void DeleteSmartPlaylistById(SmartPlaylistId smartPlaylistId) {
try { try {

View file

@ -12,5 +12,7 @@ description: |
category: "General" category: "General"
artifacts: artifacts:
- jellyfin-smart-playlist.dll - jellyfin-smart-playlist.dll
- YamlDotNet.dll
changelog: | changelog: |
- Switch to yaml loading.
- Initial Alpha release. - Initial Alpha release.

View file

@ -5,32 +5,14 @@
<title>Template</title> <title>Template</title>
</head> </head>
<body> <body>
<div id="TemplateConfigPage" data-role="page" class="page type-interior pluginConfigurationPage" data-require="emby-input,emby-button,emby-select,emby-checkbox"> <div id="SmartPlaylistConfigPage" data-role="page" class="page type-interior pluginConfigurationPage" data-require="emby-input,emby-button,emby-select,emby-checkbox">
<div data-role="content"> <div data-role="content">
<div class="content-primary"> <div class="content-primary">
<form id="TemplateConfigForm"> <form id="SmartPlaylistConfigForm">
<div class="selectContainer">
<label class="selectLabel" for="Options">Several Options</label>
<select is="emby-select" id="Options" name="Options" class="emby-select-withcolor emby-select">
<option id="optOneOption" value="OneOption">One Option</option>
<option id="optAnotherOption" value="AnotherOption">Another Option</option>
</select>
</div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="AnInteger">An Integer</label> <label class="inputLabel inputLabelUnfocused" for="InitialProgram">Initial Program</label>
<input id="AnInteger" name="AnInteger" type="number" is="emby-input" min="0" /> <div class="fieldDescription">A program which can set up the environment</div>
<div class="fieldDescription">A Description</div> <input id="InitialProgram" name="InitialProgram" type="text" is="emby-input" />
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="TrueFalseSetting" name="TrueFalseCheckBox" type="checkbox" is="emby-checkbox" />
<span>A Checkbox</span>
</label>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="AString">A String</label>
<input id="AString" name="AString" type="text" is="emby-input" />
<div class="fieldDescription">Another Description</div>
</div> </div>
<div> <div>
<button is="emby-button" type="submit" class="raised button-submit block emby-button"> <button is="emby-button" type="submit" class="raised button-submit block emby-button">
@ -41,31 +23,25 @@
</div> </div>
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
var TemplateConfig = { var SmartPlaylistConfig = {
pluginUniqueId: 'dd2326e3-4d3e-4bfc-80e6-28502c1131df' pluginUniqueId: 'dd2326e3-4d3e-4bfc-80e6-28502c1131df'
}; };
document.querySelector('#TemplateConfigPage') document.querySelector('#SmartPlaylistConfigPage')
.addEventListener('pageshow', function() { .addEventListener('pageshow', function() {
Dashboard.showLoadingMsg(); Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(TemplateConfig.pluginUniqueId).then(function (config) { ApiClient.getPluginConfiguration(SmartPlaylistConfig.pluginUniqueId).then(function (config) {
document.querySelector('#Options').value = config.Options; document.querySelector('#InitialProgram').value = config.InitialProgram;
document.querySelector('#AnInteger').value = config.AnInteger;
document.querySelector('#TrueFalseSetting').checked = config.TrueFalseSetting;
document.querySelector('#AString').value = config.AString;
Dashboard.hideLoadingMsg(); Dashboard.hideLoadingMsg();
}); });
}); });
document.querySelector('#TemplateConfigForm') document.querySelector('#SmartPlaylistConfigForm')
.addEventListener('submit', function(e) { .addEventListener('submit', function(e) {
Dashboard.showLoadingMsg(); Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(TemplateConfig.pluginUniqueId).then(function (config) { ApiClient.getPluginConfiguration(SmartPlaylistConfig.pluginUniqueId).then(function (config) {
config.Options = document.querySelector('#Options').value; config.InitialProgram = document.querySelector('#InitialProgram').value;
config.AnInteger = document.querySelector('#AnInteger').value; ApiClient.updatePluginConfiguration(SmartPlaylistConfig.pluginUniqueId, config).then(function (result) {
config.TrueFalseSetting = document.querySelector('#TrueFalseSetting').checked;
config.AString = document.querySelector('#AString').value;
ApiClient.updatePluginConfiguration(TemplateConfig.pluginUniqueId, config).then(function (result) {
Dashboard.processPluginConfigurationUpdateResult(result); Dashboard.processPluginConfigurationUpdateResult(result);
}); });
}); });

View file

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RootNamespace>jellyfin_smart_playlist</RootNamespace> <RootNamespace>Jellyfin.Plugin.SmartPlaylist</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Version>0.1.1.0</Version> <Version>0.1.1.0</Version>
@ -11,6 +11,12 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Jellyfin.Controller" Version="10.10.0" /> <PackageReference Include="Jellyfin.Controller" Version="10.10.0" />
<PackageReference Include="Jellyfin.Model" Version="10.10.0" /> <PackageReference Include="Jellyfin.Model" Version="10.10.0" />
<PackageReference Include="YamlDotNet" Version="16.1.3" />
</ItemGroup>
<ItemGroup>
<None Remove="configPage.html"/>
<EmbeddedResource Include="configPage.html"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -192,7 +192,6 @@ namespace Tests
(define null (lambda* (x) (cond ((eval x) nil) (t t)))) (define null (lambda* (x) (cond ((eval x) nil) (t t))))
(null (quote (1 2)))) (null (quote (1 2))))
""").ToString()); """).ToString());
} }
[Fact] [Fact]
public static void ObjectTest() { public static void ObjectTest() {
@ -212,18 +211,23 @@ namespace Tests
Executor e = new Executor(new DefaultEnvironment()); Executor e = new Executor(new DefaultEnvironment());
Assert.Equal("1", e.eval("(if nil 0 1)").ToString()); Assert.Equal("1", e.eval("(if nil 0 1)").ToString());
Assert.Equal("0", e.eval("(if t 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("(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("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("(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("nil", e.eval("(and 1 2 3 nil)").ToString());
Assert.Equal("t", e.eval("(and (quote (1 2 3 4)))").ToString()); Assert.Equal("t", e.eval("(and t t t t)").ToString());
Assert.Equal("t", e.eval("(or (quote (nil nil 1 nil)))").ToString()); Assert.Equal("t", e.eval("(or nil nil t nil)").ToString());
Assert.Equal("nil", e.eval("(or (quote (nil nil nil 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("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)) (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("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) (= 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());
} }
} }
} }