You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
412 lines
12 KiB
412 lines
12 KiB
using System;
|
|
using System.Collections;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
|
|
namespace libsex
|
|
{
|
|
[Serializable]
|
|
public class SExpression : IList<object>
|
|
{
|
|
private readonly List<object> inter = new List<object>();
|
|
|
|
public SExpression()
|
|
{
|
|
|
|
}
|
|
|
|
public static SExpression Concat(params SExpression[] sexs)
|
|
{
|
|
var sex = new SExpression();
|
|
sex.inter.AddRange(sexs);
|
|
return sex;
|
|
}
|
|
|
|
public object Car()
|
|
=> inter.Count < 1 ? null : inter[0];
|
|
public SExpression Cdr()
|
|
{
|
|
if (inter.Count < 2) return new SExpression();
|
|
else return new SExpression(inter.Skip(1));
|
|
}
|
|
|
|
public SExpression IPWhere(Func<object,bool> predicate)
|
|
{
|
|
var add = inter.Where(predicate).ToArray();
|
|
inter.Clear();
|
|
inter.AddRange(add);
|
|
return this;
|
|
}
|
|
|
|
public SExpression IPSelect(Func<object,object> transform)
|
|
{
|
|
var add = inter.Select(transform).ToArray();
|
|
inter.Clear();
|
|
inter.AddRange(add);
|
|
return this;
|
|
}
|
|
|
|
public SExpression Take(Func<object, bool> transform)
|
|
{
|
|
SExpression sexpr = new SExpression();
|
|
foreach (var obj in inter.ToArray())
|
|
{
|
|
if (transform(obj))
|
|
{
|
|
sexpr.Add(obj);
|
|
inter.Remove(obj);
|
|
}
|
|
}
|
|
return sexpr;
|
|
}
|
|
|
|
public string CarString() => Car() as string;
|
|
|
|
public SExpression CarExpr() => Car() switch { SExpression sex => sex, IEnumerable<object> ie => new SExpression(ie), _ => null };
|
|
|
|
public T Car<T>(T or = default) => Car() is T t ? t : or;
|
|
|
|
public SExpression(params object[] initial)
|
|
{
|
|
inter.AddRange(initial);
|
|
}
|
|
|
|
public object[] Slice(int start,int length)
|
|
{
|
|
object[] slice = new object[length];
|
|
inter.CopyTo(start, slice, 0, length);
|
|
return slice;
|
|
}
|
|
|
|
public object Path(params int[] indecies)
|
|
{
|
|
IList<object> elements = this.inter;
|
|
for (int i = 0; i < indecies.Length; i++)
|
|
{
|
|
int index = indecies[i];
|
|
if (index < 0 || index >= elements.Count) throw new IndexOutOfRangeException("Index " + i + " (" + index + ") was out of bounds.");
|
|
else
|
|
{
|
|
if (elements[index] is IList<object> il)
|
|
{
|
|
elements = il;
|
|
}
|
|
else return elements[index];
|
|
}
|
|
}
|
|
return elements;
|
|
}
|
|
|
|
public SExpression(IEnumerable<object> from)
|
|
{
|
|
inter.AddRange(from);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
//sb.Append("(");
|
|
|
|
foreach (var item in inter)
|
|
{
|
|
if (item is SExpression se)
|
|
sb.Append("(" + se.ToString() + ")");
|
|
else if (item is string str)
|
|
sb.Append(escape(str));
|
|
else if (item is IEnumerable<object> il)
|
|
sb.Append("(" + new SExpression(il).ToString() + ")");
|
|
else if (item is IEnumerable li)
|
|
{
|
|
SExpression sex = new SExpression();
|
|
foreach (var o in li)
|
|
{
|
|
sex.inter.Add(o);
|
|
}
|
|
sb.Append("(" + sex.ToString() + ")");
|
|
}
|
|
else sb.Append(escape(item.ToString()));
|
|
sb.Append(" ");
|
|
}
|
|
|
|
return sb.ToString().Trim();// + ")";
|
|
}
|
|
|
|
public SExpression FlattenTopLevel(bool full=false)
|
|
{
|
|
if (full) while (inter.Count == 1 && inter[0] is SExpression se)
|
|
{
|
|
inter.Clear();
|
|
inter.AddRange(se.inter);
|
|
}
|
|
else if (inter.Count == 1 && inter[0] is SExpression se)
|
|
{
|
|
inter.Clear();
|
|
inter.AddRange(se.inter);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private static readonly System.Text.RegularExpressions.Regex escape_re = new System.Text.RegularExpressions.Regex(@"[\s\(\)]", System.Text.RegularExpressions.RegexOptions.Compiled);
|
|
private string escape(string str)
|
|
{
|
|
str = str.Replace("\\", "\\\\");
|
|
str = str.Replace("\"", "\\\"");
|
|
if (escape_re.IsMatch(str)) return $"\"{str}\"";
|
|
else return str;
|
|
}
|
|
|
|
#region IList
|
|
public object this[int index] { get => inter[index]; set => inter[index] = value; }
|
|
|
|
public int Count => inter.Count;
|
|
|
|
public bool IsReadOnly => false;
|
|
|
|
public void Add(object item)
|
|
{
|
|
inter.Add(item);
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
inter.Clear();
|
|
}
|
|
|
|
public bool Contains(object item)
|
|
{
|
|
return inter.Contains(item);
|
|
}
|
|
|
|
public void CopyTo(object[] array, int arrayIndex)
|
|
{
|
|
inter.CopyTo(array, arrayIndex);
|
|
}
|
|
|
|
public IEnumerator<object> GetEnumerator()
|
|
{
|
|
return inter.GetEnumerator();
|
|
}
|
|
|
|
public int IndexOf(object item)
|
|
{
|
|
return inter.IndexOf(item);
|
|
}
|
|
|
|
public void Insert(int index, object item)
|
|
{
|
|
inter.Insert(index, item);
|
|
}
|
|
|
|
public bool Remove(object item)
|
|
{
|
|
return inter.Remove(item);
|
|
}
|
|
|
|
public void RemoveAt(int index)
|
|
{
|
|
inter.RemoveAt(index);
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return (inter as IEnumerable).GetEnumerator();
|
|
}
|
|
#endregion
|
|
|
|
private static async Task<SExpression> parse(AsyncConsumer<char> input)
|
|
{
|
|
SExpression main = new SExpression();
|
|
var (isEscaped, isStringed) = (false, false);
|
|
StringBuilder token = new StringBuilder();
|
|
|
|
void addToken()
|
|
{
|
|
if (token.Length > 0)
|
|
main.inter.Add(token.ToString());
|
|
token.Clear();
|
|
}
|
|
|
|
(char, bool) ok;
|
|
while ((ok = await input.TryTake()).Item2)
|
|
{
|
|
var letter = ok.Item1;
|
|
if (isEscaped)
|
|
{
|
|
token.Append(letter);
|
|
isEscaped = false;
|
|
}
|
|
else if (letter == '\\')
|
|
{
|
|
isEscaped = true;
|
|
}
|
|
else if (isStringed)
|
|
{
|
|
if (letter == '"') isStringed = false;
|
|
else token.Append(letter);
|
|
}
|
|
else if (letter == '(')
|
|
{
|
|
addToken();
|
|
main.inter.Add(await parse(input));
|
|
}
|
|
else if (letter == ')')
|
|
{
|
|
addToken();
|
|
return main;
|
|
}
|
|
else if (letter == ' ' || letter == '\t' || letter == '\n' || letter == '\r' || letter == '\f')
|
|
{
|
|
addToken();
|
|
}
|
|
else if (letter == '"')
|
|
{
|
|
isStringed = true;
|
|
}
|
|
else token.Append(letter);
|
|
}
|
|
|
|
addToken();
|
|
return main;
|
|
}
|
|
|
|
|
|
private static async IAsyncEnumerable<char> preprocess(IAsyncEnumerable<char> input)
|
|
{
|
|
var (isEscaped, isStringed, isCommented) = (false, false, false);
|
|
await foreach (var letter in input)
|
|
{
|
|
if (isEscaped)
|
|
{
|
|
yield return letter;
|
|
isEscaped = false;
|
|
}
|
|
else if (letter == '\\')
|
|
{
|
|
isEscaped = true;
|
|
yield return letter;
|
|
}
|
|
else if (isStringed)
|
|
{
|
|
yield return letter;
|
|
if (letter == '"') isStringed = false;
|
|
}
|
|
else if (isCommented)
|
|
{
|
|
if (letter == '\n' || letter == '\r' || letter == '\f')
|
|
{
|
|
yield return '\n';
|
|
isCommented = false;
|
|
}
|
|
}
|
|
else if (letter == '"')
|
|
{
|
|
isStringed = true;
|
|
yield return letter;
|
|
}
|
|
else if (letter == ';')
|
|
{
|
|
isCommented = true;
|
|
}
|
|
else if (letter == '\t')
|
|
{
|
|
yield return ' ';
|
|
}
|
|
else yield return letter;
|
|
|
|
}
|
|
}
|
|
|
|
public static async Task<SExpression> ParseAsync(IAsyncEnumerable<char> str)
|
|
{
|
|
AsyncConsumer<char> s;
|
|
var res = await parse(s = new AsyncConsumer<char>(preprocess(str)));
|
|
await s.DisposeAsync();
|
|
return res;
|
|
}
|
|
|
|
public static async IAsyncEnumerable<SExpression> ParseMultipleAsync(IAsyncEnumerable<char> str, [EnumeratorCancellation] CancellationToken token = default)
|
|
{
|
|
str = preprocess(str);
|
|
|
|
await using var consumer = new AsyncConsumer<char>(str);
|
|
|
|
(char, bool) cur = default;
|
|
while ((await consumer.TryTake(token)).Item2)
|
|
{
|
|
if(cur.Item1 == '(')
|
|
{
|
|
yield return await parse(consumer);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static SExpression Parse(IEnumerable<char> str)
|
|
{
|
|
async IAsyncEnumerable<char> interop()
|
|
{
|
|
await Task.Yield();
|
|
foreach (var s in str) yield return s;
|
|
}
|
|
try
|
|
{
|
|
var awaiter = ParseAsync(interop());
|
|
awaiter.Wait();
|
|
if (awaiter.IsFaulted) throw awaiter.Exception;
|
|
return awaiter.Result;
|
|
}
|
|
catch (Exception ex) { throw ex; }
|
|
}
|
|
}
|
|
|
|
|
|
class AsyncConsumer<T> : IAsyncDisposable
|
|
{
|
|
readonly IAsyncEnumerator<T> e;
|
|
public AsyncConsumer(IAsyncEnumerable<T> input)
|
|
{
|
|
e = input.GetAsyncEnumerator();
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
await e.DisposeAsync();
|
|
}
|
|
|
|
public async ValueTask<(T, bool)> TryTake(CancellationToken token)
|
|
{
|
|
if (token == default) return await TryTake();
|
|
|
|
TaskCompletionSource<bool> cancel = new TaskCompletionSource<bool>();
|
|
using var _c = token.Register(() => cancel.SetResult(false));
|
|
var res = e.MoveNextAsync().AsTask();
|
|
if (await Task.WhenAny(res, cancel.Task) == res)
|
|
{
|
|
var ok = res.Result;
|
|
if (ok)
|
|
{
|
|
return (e.Current, true);
|
|
}
|
|
else return (default, false);
|
|
}
|
|
else
|
|
throw new OperationCanceledException();
|
|
}
|
|
|
|
public async ValueTask<(T, bool)> TryTake()
|
|
{
|
|
var ok = await e.MoveNextAsync();
|
|
if (ok)
|
|
{
|
|
return (e.Current, true);
|
|
}
|
|
else return (default, false);
|
|
}
|
|
}
|
|
}
|