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 { private readonly List inter = new List(); 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 predicate) { var add = inter.Where(predicate).ToArray(); inter.Clear(); inter.AddRange(add); return this; } public SExpression IPSelect(Func transform) { var add = inter.Select(transform).ToArray(); inter.Clear(); inter.AddRange(add); return this; } public SExpression Take(Func 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 ie => new SExpression(ie), _ => null }; public T Car(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 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 il) { elements = il; } else return elements[index]; } } return elements; } public SExpression(IEnumerable 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 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 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 parse(AsyncConsumer 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 preprocess(IAsyncEnumerable 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 ParseAsync(IAsyncEnumerable str) { AsyncConsumer s; var res = await parse(s = new AsyncConsumer(preprocess(str))); await s.DisposeAsync(); return res; } public static async IAsyncEnumerable ParseMultipleAsync(IAsyncEnumerable str, [EnumeratorCancellation] CancellationToken token = default) { str = preprocess(str); await using var consumer = new AsyncConsumer(str); (char, bool) cur = default; while ((await consumer.TryTake(token)).Item2) { if(cur.Item1 == '(') { yield return await parse(consumer); } } } public static SExpression Parse(IEnumerable str) { async IAsyncEnumerable 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 : IAsyncDisposable { readonly IAsyncEnumerator e; public AsyncConsumer(IAsyncEnumerable 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 cancel = new TaskCompletionSource(); 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); } } }