Wasm/Text/SExpression.cs
using System;
using System.Collections.Generic;

namespace WasmBox.Wasm.Text {
    /// <summary>
    /// An S-expression: a data structure that is either a single token or a keyword
    /// token followed by a tail.
    /// </summary>
    public struct SExpression {
        internal static SExpression Create(Lexer.Token head, IReadOnlyList<SExpression> tail) {
            return new SExpression {
                IsCall = true,
                Head = head,
                Tail = tail
            };
        }

        internal static SExpression Create(Lexer.Token head) {
            return new SExpression {
                IsCall = false,
                Head = head,
                Tail = Array.Empty<SExpression>()
            };
        }

        /// <summary>
        /// Tests if this S-expression represents a call.
        /// </summary>
        public bool IsCall { get; private set; }

        /// <summary>
        /// Gets the keyword token that is the head of this S-expression if the S-expression is a call;
        /// otherwise, the token that corresponds to the S-expression itself.
        /// </summary>
        public Lexer.Token Head { get; private set; }

        /// <summary>
        /// Gets the S-expression's tail: a sequence of S-expressions that trail the S-expression's head.
        /// Note that this tail may be empty even for S-expressions that are calls.
        /// </summary>
        public IReadOnlyList<SExpression> Tail { get; private set; }

        /// <summary>
        /// Tells if this S-expression represents a single identifier token.
        /// </summary>
        public bool IsIdentifier => !IsCall && Head.Kind == Lexer.TokenKind.Identifier;

        /// <summary>
        /// Tells if this S-expression represents a single keyword token.
        /// </summary>
        public bool IsKeyword => !IsCall && Head.Kind == Lexer.TokenKind.Keyword;

        /// <summary>
        /// Tells if this S-expression represents a specific keyword
        /// </summary>
        /// <param name="keyword">The keyword to look for.</param>
        /// <returns><c>true</c> if this S-expression is keyword token that matches <paramref name="keyword"/>, <c>false</c> otherwise.</returns>
        public bool IsSpecificKeyword(string keyword) {
            return !IsCall && Head.Kind == Lexer.TokenKind.Keyword && (string)Head.Value == keyword;
        }

        /// <summary>
        /// Tests if this S-expression is a call to a keyword with a particular name.
        /// </summary>
        /// <param name="keyword">The keyword to check for.</param>
        /// <returns>
        /// <c>true</c> if the S-expression is a call to <paramref name="keyword"/>; otherwise, <c>false</c>.
        /// </returns>
        public bool IsCallTo(string keyword) {
            return IsCall && Head.Kind == Lexer.TokenKind.Keyword && (string)Head.Value == keyword;
        }

        /// <inheritdoc/>
        public override string ToString() {
            return IsCall ? Head.Span.Text : $"({Head.Span.Text} {string.Join(" ", Tail)})";
        }
    }
}