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

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);
}
}
}