134 lines
4.7 KiB
C#
134 lines
4.7 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;
|
|
} else if (_value == ";") {
|
|
return new GroupingToken("\n");
|
|
}
|
|
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));
|
|
}
|
|
}
|
|
}
|