jellyfin-smart-playlist/Jellyfin.Plugin.SmartPlaylist/Lisp/Compiler/TokenStream.cs
redxef f7cbebdd9c
feat: add single quote quoting.
'(a b c) = (quote (a b c))
2024-12-17 23:29:55 +01:00

132 lines
4.6 KiB
C#

using System.Reflection;
using Jellyfin.Plugin.SmartPlaylist.Util;
namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
public interface IToken<T> {
T value { get; }
abstract static IToken<T>? take<E>(E program);
}
public abstract class Token<T>: IToken<T> , IEquatable<Token<T>> {
protected readonly T _value;
protected Token(T value) {
_value = value;
}
public T value { get => _value; }
public static IToken<T>? take<E>(E program) {
throw new NotImplementedException("Subclass this class");
}
public bool Equals(Token<T>? b) {
return b != null && _value != null && _value.Equals(b._value);
}
}
class SpaceToken : Token<string> {
private SpaceToken(string value) : base(value) {}
private static IToken<string>? take(CharStream program) {
string spaces = " \n";
if (program.Available() == 0) {
return null;
}
var t = program.Get();
if (spaces.Contains(t)) {
return new SpaceToken(t.ToString());
}
return null;
}
}
class GroupingToken: Token<string> {
private GroupingToken(string value) : base(value) {}
private static IToken<string>? take(CharStream program) {
if (program.Available() == 0) {
return null;
}
char t = program.Get();
if ("()\"'".Contains(t)) {
return new GroupingToken(t.ToString());
}
return null;
}
private GroupingToken? _closing_value() {
if (_value == "(") {
return new GroupingToken(")");
} else if (_value == ")") {
return null;
} else if (_value == "'") {
return null;
}
return new GroupingToken(_value);
}
public GroupingToken? closing_value { get => _closing_value(); }
}
class AtomToken : Token<string> {
private AtomToken(string value) : base(value) {}
private static IToken<string>? take(CharStream program) {
string value = "";
while (program.Available() > 0) {
char t = program.Get();
if (" \n()\"".Contains(t)) {
if (value.Equals("")) {
return null;
}
program.Rewind(1);
return new AtomToken(value);
}
value += t;
}
return null;
}
}
class CharStream: Stream<char> {
public CharStream(IList<char> items) : base(items) {}
public CharStream(string items) : base(items.ToCharArray().Cast<char>().ToList()) {}
}
public class StringTokenStream : Stream<Token<string>> {
private static readonly IList<Type> _classes = new List<Type> {
typeof(SpaceToken),
typeof(GroupingToken),
typeof(AtomToken),
};
protected StringTokenStream(IList<Token<string>> tokens) : base(tokens) {}
private static StringTokenStream generate(CharStream program) {
IList<Token<string>> result = new List<Token<string>>();
int prev_avail = 0;
while (true) {
if (prev_avail == program.Available() && prev_avail == 0) {
break;
} else if (prev_avail == program.Available()) {
throw new ApplicationException("Program is invalid");
}
prev_avail = program.Available();
foreach (Type c in _classes) {
Token<string>? t = (Token<string>?) c.GetMethod(
"take",
BindingFlags.NonPublic | BindingFlags.Static,
null,
CallingConventions.Any,
new Type[] { typeof(CharStream) },
null
)?.Invoke(
null,
new object[]{program}
);
if (t == null) {
program.Rewind();
continue;
}
program.Commit();
result.Add(t);
break;
}
}
return new StringTokenStream(result);
}
public static StringTokenStream generate(string program) {
return StringTokenStream.generate(new CharStream(program));
}
}
}