blob: d5f4ee09c43335343ba4db5e9b2014df96888c3f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Avro.IO.Parsing
{
/// <summary>
/// Symbol is the base of all symbols (terminals and non-terminals) of the
/// grammar.
/// </summary>
public abstract class Symbol
{
/// <summary>
/// The type of symbol.
/// </summary>
public enum Kind
{
/// <summary>
/// terminal symbols which have no productions </summary>
Terminal,
/// <summary>
/// Start symbol for some grammar </summary>
Root,
/// <summary>
/// non-terminal symbol which is a sequence of one or more other symbols </summary>
Sequence,
/// <summary>
/// non-terminal to represent the contents of an array or map </summary>
Repeater,
/// <summary>
/// non-terminal to represent the union </summary>
Alternative,
/// <summary>
/// non-terminal action symbol which are automatically consumed </summary>
ImplicitAction,
/// <summary>
/// non-terminal action symbol which is explicitly consumed </summary>
ExplicitAction
}
/// The kind of this symbol.
public Kind SymKind { get; private set; }
/// <summary>
/// The production for this symbol. If this symbol is a terminal this is
/// <tt>null</tt>. Otherwise this holds the the sequence of the symbols that
/// forms the production for this symbol. The sequence is in the reverse order of
/// production. This is useful for easy copying onto parsing stack.
///
/// Please note that this is a final. So the production for a symbol should be
/// known before that symbol is constructed. This requirement cannot be met for
/// those symbols which are recursive (e.g. a record that holds union a branch of
/// which is the record itself). To resolve this problem, we initialize the
/// symbol with an array of nulls. Later we fill the symbols. Not clean, but
/// works. The other option is to not have this field a final. But keeping it
/// final and thus keeping symbol immutable gives some comfort. See various
/// generators how we generate records.
/// </summary>
public Symbol[] Production { get; private set; }
/// <summary>
/// Constructs a new symbol of the given kind.
/// </summary>
protected Symbol(Kind kind) : this(kind, null)
{
}
/// <summary>
/// Constructs a new symbol of the given kind and production.
/// </summary>
protected Symbol(Kind kind, Symbol[] production)
{
Production = production;
SymKind = kind;
}
/// <summary>
/// A convenience method to construct a root symbol.
/// </summary>
public static Symbol NewRoot(params Symbol[] symbols) => new Root(symbols);
/// <summary>
/// A convenience method to construct a sequence.
/// </summary>
/// <param name="production"> The constituent symbols of the sequence. </param>
public static Symbol NewSeq(params Symbol[] production) => new Sequence(production);
/// <summary>
/// A convenience method to construct a repeater.
/// </summary>
/// <param name="endSymbol"> The end symbol. </param>
/// <param name="symsToRepeat"> The symbols to repeat in the repeater. </param>
public static Symbol NewRepeat(Symbol endSymbol, params Symbol[] symsToRepeat) =>
new Repeater(endSymbol, symsToRepeat);
/// <summary>
/// A convenience method to construct a union.
/// </summary>
public static Symbol NewAlt(Symbol[] symbols, string[] labels) => new Alternative(symbols, labels);
/// <summary>
/// A convenience method to construct an ErrorAction.
/// </summary>
/// <param name="e"> </param>
protected static Symbol Error(string e) => new ErrorAction(e);
/// <summary>
/// A convenience method to construct a ResolvingAction.
/// </summary>
/// <param name="w"> The writer symbol </param>
/// <param name="r"> The reader symbol </param>
protected static Symbol Resolve(Symbol w, Symbol r) => new ResolvingAction(w, r);
/// <summary>
/// Fixup symbol.
/// </summary>
protected class Fixup
{
private readonly Symbol[] symbols;
/// <summary>
/// The symbols.
/// </summary>
public Symbol[] Symbols
{
get { return (Symbol[])symbols.Clone(); }
}
/// <summary>
/// The position.
/// </summary>
public int Pos { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Fixup"/> class.
/// </summary>
public Fixup(Symbol[] symbols, int pos)
{
this.symbols = (Symbol[])symbols.Clone();
Pos = pos;
}
}
/// <summary>
/// Flatten the given sub-array of symbols into a sub-array of symbols.
/// </summary>
protected virtual Symbol Flatten(IDictionary<Sequence, Sequence> map, IDictionary<Sequence, IList<Fixup>> map2) => this;
/// <summary>
/// Returns the flattened size.
/// </summary>
public virtual int FlattenedSize() => 1;
/// <summary>
/// Flattens the given sub-array of symbols into an sub-array of symbols. Every
/// <tt>Sequence</tt> in the input are replaced by its production recursively.
/// Non-<tt>Sequence</tt> symbols, they internally have other symbols those
/// internal symbols also get flattened. When flattening is done, the only place
/// there might be Sequence symbols is in the productions of a Repeater,
/// Alternative, or the symToParse and symToSkip in a UnionAdjustAction or
/// SkipAction.
///
/// Why is this done? We want our parsers to be fast. If we left the grammars
/// unflattened, then the parser would be constantly copying the contents of
/// nested Sequence productions onto the parsing stack. Instead, because of
/// flattening, we have a long top-level production with no Sequences unless the
/// Sequence is absolutely needed, e.g., in the case of a Repeater or an
/// Alternative.
///
/// Well, this is not exactly true when recursion is involved. Where there is a
/// recursive record, that record will be "inlined" once, but any internal (ie,
/// recursive) references to that record will be a Sequence for the record. That
/// Sequence will not further inline itself -- it will refer to itself as a
/// Sequence. The same is true for any records nested in this outer recursive
/// record. Recursion is rare, and we want things to be fast in the typical case,
/// which is why we do the flattening optimization.
///
///
/// The algorithm does a few tricks to handle recursive symbol definitions. In
/// order to avoid infinite recursion with recursive symbols, we have a map of
/// Symbol->Symbol. Before fully constructing a flattened symbol for a
/// <tt>Sequence</tt> we insert an empty output symbol into the map and then
/// start filling the production for the <tt>Sequence</tt>. If the same
/// <tt>Sequence</tt> is encountered due to recursion, we simply return the
/// (empty) output <tt>Sequence</tt> from the map. Then we actually fill out
/// the production for the <tt>Sequence</tt>. As part of the flattening process
/// we copy the production of <tt>Sequence</tt>s into larger arrays. If the
/// original <tt>Sequence</tt> has not not be fully constructed yet, we copy a
/// bunch of <tt>null</tt>s. Fix-up remembers all those <tt>null</tt> patches.
/// The fix-ups gets finally filled when we know the symbols to occupy those
/// patches.
/// </summary>
/// <param name="input"> The array of input symbols to flatten </param>
/// <param name="start"> The position where the input sub-array starts. </param>
/// <param name="output"> The output that receives the flattened list of symbols. The
/// output array should have sufficient space to receive the
/// expanded sub-array of symbols. </param>
/// <param name="skip"> The position where the output input sub-array starts. </param>
/// <param name="map"> A map of symbols which have already been expanded. Useful for
/// handling recursive definitions and for caching. </param>
/// <param name="map2"> A map to to store the list of fix-ups. </param>
protected static void Flatten(Symbol[] input, int start, Symbol[] output, int skip,
IDictionary<Sequence, Sequence> map, IDictionary<Sequence, IList<Fixup>> map2)
{
for (int i = start, j = skip; i < input.Length; i++)
{
Symbol s = input[i].Flatten(map, map2);
if (s is Sequence)
{
Symbol[] p = s.Production;
if (!map2.TryGetValue((Sequence)s, out IList<Fixup> l))
{
Array.Copy(p, 0, output, j, p.Length);
// Copy any fixups that will be applied to p to add missing symbols
foreach (IList<Fixup> fixups in map2.Values)
{
CopyFixups(fixups, output, j, p);
}
}
else
{
l.Add(new Fixup(output, j));
}
j += p.Length;
}
else
{
output[j++] = s;
}
}
}
private static void CopyFixups(IList<Fixup> fixups, Symbol[] output, int outPos, Symbol[] toCopy)
{
for (int i = 0, n = fixups.Count; i < n; i += 1)
{
Fixup fixup = fixups[i];
if (fixup.Symbols == toCopy)
{
fixups.Add(new Fixup(output, fixup.Pos + outPos));
}
}
}
/// <summary>
/// Returns the amount of space required to flatten the given sub-array of
/// symbols.
/// </summary>
/// <param name="symbols"> The array of input symbols. </param>
/// <param name="start"> The index where the subarray starts. </param>
/// <returns> The number of symbols that will be produced if one expands the given
/// input. </returns>
protected static int FlattenedSize(Symbol[] symbols, int start)
{
int result = 0;
for (int i = start; i < symbols.Length; i++)
{
if (symbols[i] is Sequence)
{
Sequence s = (Sequence)symbols[i];
result += s.FlattenedSize();
}
else
{
result += 1;
}
}
return result;
}
/// <summary>
/// Terminal symbol.
/// </summary>
protected class Terminal : Symbol
{
/// <summary>
/// Printable name.
/// </summary>
public string PrintName { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Symbol.Terminal"/> class.
/// </summary>
public Terminal(string printName) : base(Kind.Terminal)
{
PrintName = printName;
}
/// <inheritdoc />
public override string ToString() => PrintName;
}
/// <summary>
/// Implicit action.
/// </summary>
public class ImplicitAction : Symbol
{
/// <summary>
/// Set to <tt>true</tt> if and only if this implicit action is a trailing
/// action. That is, it is an action that follows real symbol. E.g
/// <see cref="Symbol.DefaultEndAction"/>.
/// </summary>
public bool IsTrailing { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Symbol.ImplicitAction"/> class.
/// </summary>
public ImplicitAction() : this(false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Symbol.ImplicitAction"/> class.
/// </summary>
public ImplicitAction(bool isTrailing) : base(Kind.ImplicitAction)
{
IsTrailing = isTrailing;
}
}
/// <summary>
/// Root symbol.
/// </summary>
protected class Root : Symbol
{
/// <summary>
/// Initializes a new instance of the <see cref="Symbol.Root"/> class.
/// </summary>
public Root(params Symbol[] symbols) : base(Kind.Root, MakeProduction(symbols))
{
Production[0] = this;
}
private static Symbol[] MakeProduction(Symbol[] symbols)
{
Symbol[] result = new Symbol[FlattenedSize(symbols, 0) + 1];
Flatten(symbols, 0, result, 1, new Dictionary<Sequence, Sequence>(),
new Dictionary<Sequence, IList<Fixup>>());
return result;
}
}
/// <summary>
/// Sequence symbol.
/// </summary>
protected class Sequence : Symbol, IEnumerable<Symbol>
{
/// <summary>
/// Initializes a new instance of the <see cref="Symbol.Sequence"/> class.
/// </summary>
public Sequence(Symbol[] productions) : base(Kind.Sequence, productions)
{
}
/// <summary>
/// Get the symbol at the given index.
/// </summary>
public virtual Symbol this[int index] => Production[index];
/// <summary>
/// Get the symbol at the given index.
/// </summary>
public virtual Symbol Get(int index) => Production[index];
/// <summary>
/// Returns the number of symbols.
/// </summary>
public virtual int Size() => Production.Length;
/// <inheritdoc />
public IEnumerator<Symbol> GetEnumerator() => Enumerable.Reverse(Production).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <inheritdoc />
protected override Symbol Flatten(IDictionary<Sequence, Sequence> map,
IDictionary<Sequence, IList<Fixup>> map2)
{
if (!map.TryGetValue(this, out Sequence result))
{
result = new Sequence(new Symbol[FlattenedSize()]);
map[this] = result;
IList<Fixup> l = new List<Fixup>();
map2[result] = l;
Flatten(Production, 0, result.Production, 0, map, map2);
foreach (Fixup f in l)
{
Array.Copy(result.Production, 0, f.Symbols, f.Pos, result.Production.Length);
}
map2.Remove(result);
}
return result;
}
/// <inheritdoc />
public override int FlattenedSize() => FlattenedSize(Production, 0);
}
/// <summary>
/// Repeater symbol.
/// </summary>
public class Repeater : Symbol
{
/// <summary>
/// The end symbol.
/// </summary>
public Symbol End { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Symbol.Repeater"/> class.
/// </summary>
public Repeater(Symbol end, params Symbol[] sequenceToRepeat) : base(Kind.Repeater,
MakeProduction(sequenceToRepeat))
{
End = end;
Production[0] = this;
}
private static Symbol[] MakeProduction(Symbol[] p)
{
Symbol[] result = new Symbol[p.Length + 1];
Array.Copy(p, 0, result, 1, p.Length);
return result;
}
/// <inheritdoc />
protected override Symbol Flatten(IDictionary<Sequence, Sequence> map,
IDictionary<Sequence, IList<Fixup>> map2)
{
Repeater result = new Repeater(End, new Symbol[FlattenedSize(Production, 1)]);
Flatten(Production, 1, result.Production, 1, map, map2);
return result;
}
}
/// <summary>
/// Returns true if the Parser contains any Error symbol, indicating that it may
/// fail for some inputs.
/// </summary>
private static bool HasErrors(Symbol symbol)
{
return HasErrors(symbol, new HashSet<Symbol>());
}
private static bool HasErrors(Symbol symbol, ISet<Symbol> visited)
{
// avoid infinite recursion
if (visited.Contains(symbol))
{
return false;
}
visited.Add(symbol);
switch (symbol.SymKind)
{
case Kind.Alternative:
return HasErrors(symbol, ((Alternative)symbol).Symbols, visited);
case Kind.ExplicitAction:
return false;
case Kind.ImplicitAction:
if (symbol is ErrorAction)
{
return true;
}
if (symbol is UnionAdjustAction)
{
return HasErrors(((UnionAdjustAction)symbol).SymToParse, visited);
}
return false;
case Kind.Repeater:
Repeater r = (Repeater)symbol;
return HasErrors(r.End, visited) || HasErrors(symbol, r.Production, visited);
case Kind.Root:
case Kind.Sequence:
return HasErrors(symbol, symbol.Production, visited);
case Kind.Terminal:
return false;
default:
throw new Exception("unknown symbol kind: " + symbol.SymKind);
}
}
private static bool HasErrors(Symbol root, Symbol[] symbols, ISet<Symbol> visited)
{
if (null != symbols)
{
foreach (Symbol s in symbols)
{
if (s == root)
{
continue;
}
if (HasErrors(s, visited))
{
return true;
}
}
}
return false;
}
/// <summary>
/// Alternative symbol.
/// </summary>
public class Alternative : Symbol
{
/// <summary>
/// The symbols.
/// </summary>
public Symbol[] Symbols { get; private set; }
/// <summary>
/// The labels.
/// </summary>
public string[] Labels { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Symbol.Alternative"/> class.
/// </summary>
public Alternative(Symbol[] symbols, string[] labels) : base(Kind.Alternative)
{
Symbols = symbols;
Labels = labels;
}
/// <summary>
/// Returns the symbol at the given index.
/// </summary>
public virtual Symbol GetSymbol(int index)
{
return Symbols[index];
}
/// <summary>
/// Returns the label at the given index.
/// </summary>
public virtual string GetLabel(int index)
{
return Labels[index];
}
/// <summary>
/// Returns the size.
/// </summary>
public virtual int Size()
{
return Symbols.Length;
}
/// <summary>
/// Returns the index of the given label.
/// </summary>
public virtual int FindLabel(string label)
{
if (label != null)
{
for (int i = 0; i < Labels.Length; i++)
{
if (label.Equals(Labels[i]))
{
return i;
}
}
}
return -1;
}
/// <inheritdoc />
protected override Symbol Flatten(IDictionary<Sequence, Sequence> map,
IDictionary<Sequence, IList<Fixup>> map2)
{
Symbol[] ss = new Symbol[Symbols.Length];
for (int i = 0; i < ss.Length; i++)
{
ss[i] = Symbols[i].Flatten(map, map2);
}
return new Alternative(ss, Labels);
}
}
/// <summary>
/// The error action.
/// </summary>
public class ErrorAction : ImplicitAction
{
/// <summary>
/// The error message.
/// </summary>
public string Msg { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Symbol.ErrorAction"/> class.
/// </summary>
public ErrorAction(string msg)
{
Msg = msg;
}
}
/// <summary>
/// Int check action.
/// </summary>
public class IntCheckAction : Symbol
{
/// <summary>
/// The size.
/// </summary>
public int Size { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Symbol.IntCheckAction"/> class.
/// </summary>
public IntCheckAction(int size) : base(Kind.ExplicitAction)
{
Size = size;
}
}
/// <summary>
/// The writer union action.
/// </summary>
public class WriterUnionAction : ImplicitAction
{
}
/// <summary>
/// The resolving action.
/// </summary>
public class ResolvingAction : ImplicitAction
{
/// <summary>
/// The writer.
/// </summary>
public Symbol Writer { get; private set; }
/// <summary>
/// The reader.
/// </summary>
public Symbol Reader { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Symbol.ResolvingAction"/> class.
/// </summary>
public ResolvingAction(Symbol writer, Symbol reader)
{
Writer = writer;
Reader = reader;
}
/// <inheritdoc />
protected override Symbol Flatten(IDictionary<Sequence, Sequence> map,
IDictionary<Sequence, IList<Fixup>> map2)
{
return new ResolvingAction(Writer.Flatten(map, map2), Reader.Flatten(map, map2));
}
}
/// <summary>
/// The skip action.
/// </summary>
public class SkipAction : ImplicitAction
{
/// <summary>
/// The symbol to skip.
/// </summary>
public Symbol SymToSkip { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Symbol.SkipAction"/> class.
/// </summary>
public SkipAction(Symbol symToSkip) : base(true)
{
SymToSkip = symToSkip;
}
/// <inheritdoc />
protected override Symbol Flatten(IDictionary<Sequence, Sequence> map,
IDictionary<Sequence, IList<Fixup>> map2)
{
return new SkipAction(SymToSkip.Flatten(map, map2));
}
}
/// <summary>
/// The field adjust action.
/// </summary>
public class FieldAdjustAction : ImplicitAction
{
/// <summary>
/// The index.
/// </summary>
public int RIndex { get; private set; }
/// <summary>
/// The field name.
/// </summary>
public string FName { get; private set; }
/// <summary>
/// The field aliases.
/// </summary>
public IList<string> Aliases { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Symbol.FieldAdjustAction"/> class.
/// </summary>
public FieldAdjustAction(int rindex, string fname, IList<string> aliases)
{
RIndex = rindex;
FName = fname;
Aliases = aliases;
}
}
/// <summary>
/// THe field order action.
/// </summary>
public sealed class FieldOrderAction : ImplicitAction
{
/// <summary>
/// Whether no reorder is needed.
/// </summary>
public bool NoReorder { get; private set; }
/// <summary>
/// The fields.
/// </summary>
public Field[] Fields { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Symbol.FieldOrderAction"/> class.
/// </summary>
public FieldOrderAction(Field[] fields)
{
Fields = fields;
bool noReorder = true;
for (int i = 0; noReorder && i < fields.Length; i++)
{
noReorder &= (i == fields[i].Pos);
}
NoReorder = noReorder;
}
}
/// <summary>
/// The default start action.
/// </summary>
public class DefaultStartAction : ImplicitAction
{
/// <summary>
/// The contents.
/// </summary>
public byte[] Contents { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Symbol.DefaultStartAction"/> class.
/// </summary>
public DefaultStartAction(byte[] contents)
{
Contents = contents;
}
}
/// <summary>
/// The union adjust action.
/// </summary>
public class UnionAdjustAction : ImplicitAction
{
/// <summary>
/// The index.
/// </summary>
public int RIndex { get; private set; }
/// <summary>
/// The symbol to parser.
/// </summary>
public Symbol SymToParse { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Symbol.UnionAdjustAction"/> class.
/// </summary>
public UnionAdjustAction(int rindex, Symbol symToParse)
{
RIndex = rindex;
SymToParse = symToParse;
}
/// <inheritdoc />
protected override Symbol Flatten(IDictionary<Sequence, Sequence> map,
IDictionary<Sequence, IList<Fixup>> map2)
{
return new UnionAdjustAction(RIndex, SymToParse.Flatten(map, map2));
}
}
/// <summary>
/// The enum labels action.
/// </summary>
public class EnumLabelsAction : IntCheckAction
{
/// <summary>
/// The symbols.
/// </summary>
public IList<string> Symbols { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Symbol.EnumLabelsAction"/> class.
/// </summary>
public EnumLabelsAction(IList<string> symbols) : base(symbols.Count)
{
Symbols = symbols;
}
/// <summary>
/// Returns the label at the given index.
/// </summary>
public virtual string GetLabel(int n)
{
return Symbols[n];
}
/// <summary>
/// Returns index of the given label.
/// </summary>
public virtual int FindLabel(string label)
{
if (label != null)
{
for (int i = 0; i < Symbols.Count; i++)
{
if (label.Equals(Symbols[i]))
{
return i;
}
}
}
return -1;
}
}
/// <summary>
/// The terminal symbols for the grammar.
/// </summary>
public static Symbol Null { get; } = new Terminal("null");
/// <summary>
/// Boolean
/// </summary>
public static Symbol Boolean { get; } = new Terminal("boolean");
/// <summary>
/// Int
/// </summary>
public static Symbol Int { get; } = new Terminal("int");
/// <summary>
/// Long
/// </summary>
public static Symbol Long { get; } = new Terminal("long");
/// <summary>
/// Float
/// </summary>
public static Symbol Float { get; } = new Terminal("float");
/// <summary>
/// Double
/// </summary>
public static Symbol Double { get; } = new Terminal("double");
/// <summary>
/// String
/// </summary>
public static Symbol String { get; } = new Terminal("string");
/// <summary>
/// Bytes
/// </summary>
public static Symbol Bytes { get; } = new Terminal("bytes");
/// <summary>
/// Fixed
/// </summary>
public static Symbol Fixed { get; } = new Terminal("fixed");
/// <summary>
/// Enum
/// </summary>
public static Symbol Enum { get; } = new Terminal("enum");
/// <summary>
/// Union
/// </summary>
public static Symbol Union { get; } = new Terminal("union");
/// <summary>
/// ArrayStart
/// </summary>
public static Symbol ArrayStart { get; } = new Terminal("array-start");
/// <summary>
/// ArrayEnd
/// </summary>
public static Symbol ArrayEnd { get; } = new Terminal("array-end");
/// <summary>
/// MapStart
/// </summary>
public static Symbol MapStart { get; } = new Terminal("map-start");
/// <summary>
/// MapEnd
/// </summary>
public static Symbol MapEnd { get; } = new Terminal("map-end");
/// <summary>
/// ItemEnd
/// </summary>
public static Symbol ItemEnd { get; } = new Terminal("item-end");
/// <summary>
/// WriterUnion
/// </summary>
public static Symbol WriterUnion { get; } = new WriterUnionAction();
/// <summary>
/// FieldAction - a pseudo terminal used by parsers
/// </summary>
public static Symbol FieldAction { get; } = new Terminal("field-action");
/// <summary>
/// RecordStart
/// </summary>
public static Symbol RecordStart { get; } = new ImplicitAction(false);
/// <summary>
/// RecordEnd
/// </summary>
public static Symbol RecordEnd { get; } = new ImplicitAction(true);
/// <summary>
/// UnionEnd
/// </summary>
public static Symbol UnionEnd { get; } = new ImplicitAction(true);
/// <summary>
/// FieldEnd
/// </summary>
public static Symbol FieldEnd { get; } = new ImplicitAction(true);
/// <summary>
/// DefaultEndAction
/// </summary>
public static Symbol DefaultEndAction { get; } = new ImplicitAction(true);
/// <summary>
/// MapKeyMarker
/// </summary>
public static Symbol MapKeyMarker { get; } = new Terminal("map-key-marker");
}
}