jellyfin-smart-playlist/Jellyfin.Plugin.SmartPlaylist/Lisp/Compiler/TokenStream.cs

147 lines
5.1 KiB
C#
Raw Normal View History

2024-06-27 01:47:44 +02:00
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) {
char[] spaces = [' ', '\n'];
if (program.Available() == 0) {
2024-06-27 01:47:44 +02:00
return null;
}
var t = program.Get();
if (spaces.Contains(t)) {
return new SpaceToken(t.ToString());
2024-06-27 01:47:44 +02:00
}
return null;
}
}
class GroupingToken: Token<string> {
private GroupingToken(string value) : base(value) {}
private static IToken<string>? take(CharStream program) {
if (program.Available() == 0) {
2024-06-27 01:47:44 +02:00
return null;
}
char t = program.Get();
2024-06-27 01:47:44 +02:00
if ("()\"'".Contains(t)) {
return new GroupingToken(t.ToString());
}
return null;
}
private GroupingToken? _closing_value() {
if (_value == "(") {
return new GroupingToken(")");
} 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();
2024-06-27 01:47:44 +02:00
if (!"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".Contains(t)) {
if (value.Equals("")) {
return null;
}
program.Rewind(1);
2024-06-27 01:47:44 +02:00
return new AtomToken(value);
}
value += t;
}
return null;
}
}
class OperatorToken : Token<string> {
private OperatorToken(string value) : base(value) {}
private static IToken<string>? take(CharStream program) {
if (program.Available() == 0) {
2024-06-27 01:47:44 +02:00
return null;
}
return new OperatorToken(program.Get().ToString());
2024-06-27 01:47:44 +02:00
//char t = program.get();
//if ("+-*/%".Contains(t)) {
// return new OperatorToken(t.ToString());
//}
//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),
typeof(OperatorToken),
};
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) {
2024-06-27 01:47:44 +02:00
break;
} else if (prev_avail == program.Available()) {
2024-06-27 01:47:44 +02:00
throw new ApplicationException("Program is invalid");
}
prev_avail = program.Available();
2024-06-27 01:47:44 +02:00
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();
2024-06-27 01:47:44 +02:00
continue;
}
program.Commit();
2024-06-27 01:47:44 +02:00
result.Add(t);
break;
}
}
return new StringTokenStream(result);
}
public static StringTokenStream generate(string program) {
return StringTokenStream.generate(new CharStream(program));
}
}
}