blob: 7d1096606716dc96c65bc980cc93c1090a9858b6 [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.Generic;
using Avro.Generic;
namespace Avro.IO.Parsing
{
/// <summary>
/// The class that generates validating grammar.
/// </summary>
public class ValidatingGrammarGenerator
{
/// <summary>
/// Returns the non-terminal that is the start symbol for the grammar for the
/// given schema <tt>sc</tt>.
/// </summary>
public virtual Symbol Generate(Schema schema)
{
return Symbol.NewRoot(Generate(schema, new Dictionary<LitS, Symbol>()));
}
/// <summary>
/// Returns the non-terminal that is the start symbol for the grammar for the
/// given schema <tt>sc</tt>. If there is already an entry for the given schema
/// in the given map <tt>seen</tt> then that entry is returned. Otherwise a new
/// symbol is generated and an entry is inserted into the map.
/// </summary>
/// <param name="sc"> The schema for which the start symbol is required </param>
/// <param name="seen"> A map of schema to symbol mapping done so far. </param>
/// <returns> The start symbol for the schema </returns>
protected virtual Symbol Generate(Schema sc, IDictionary<LitS, Symbol> seen)
{
switch (sc.Tag)
{
case Schema.Type.Null:
return Symbol.Null;
case Schema.Type.Boolean:
return Symbol.Boolean;
case Schema.Type.Int:
return Symbol.Int;
case Schema.Type.Long:
return Symbol.Long;
case Schema.Type.Float:
return Symbol.Float;
case Schema.Type.Double:
return Symbol.Double;
case Schema.Type.String:
return Symbol.String;
case Schema.Type.Bytes:
return Symbol.Bytes;
case Schema.Type.Fixed:
return Symbol.NewSeq(new Symbol.IntCheckAction(((FixedSchema)sc).Size), Symbol.Fixed);
case Schema.Type.Enumeration:
return Symbol.NewSeq(new Symbol.IntCheckAction(((EnumSchema)sc).Symbols.Count), Symbol.Enum);
case Schema.Type.Array:
return Symbol.NewSeq(
Symbol.NewRepeat(Symbol.ArrayEnd, Generate(((ArraySchema)sc).ItemSchema, seen)),
Symbol.ArrayStart);
case Schema.Type.Map:
return Symbol.NewSeq(
Symbol.NewRepeat(Symbol.MapEnd, Generate(((MapSchema)sc).ValueSchema, seen), Symbol.String),
Symbol.MapStart);
case Schema.Type.Record:
{
LitS wsc = new LitS(sc);
if (!seen.TryGetValue(wsc, out Symbol rresult))
{
Symbol[] production = new Symbol[((RecordSchema)sc).Fields.Count];
// We construct a symbol without filling the array. Please see
// <see cref="Symbol.production"/> for the reason.
rresult = Symbol.NewSeq(production);
seen[wsc] = rresult;
int j = production.Length;
foreach (Field f in ((RecordSchema)sc).Fields)
{
production[--j] = Generate(f.Schema, seen);
}
}
return rresult;
}
case Schema.Type.Union:
IList<Schema> subs = ((UnionSchema)sc).Schemas;
Symbol[] symbols = new Symbol[subs.Count];
string[] labels = new string[subs.Count];
int i = 0;
foreach (Schema b in ((UnionSchema)sc).Schemas)
{
symbols[i] = Generate(b, seen);
labels[i] = b.Fullname;
i++;
}
return Symbol.NewSeq(Symbol.NewAlt(symbols, labels), Symbol.Union);
case Schema.Type.Logical:
return Generate((sc as LogicalSchema).BaseSchema, seen);
default:
throw new Exception("Unexpected schema type");
}
}
/// <summary>
/// A wrapper around Schema that does "==" equality.
/// </summary>
protected class LitS
{
private readonly Schema actual;
/// <summary>
/// Initializes a new instance of the <see cref="LitS"/> class.
/// </summary>
public LitS(Schema actual)
{
this.actual = actual;
}
/// <summary>
/// Two LitS are equal if and only if their underlying schema is the same (not
/// merely equal).
/// </summary>
public override bool Equals(object o)
{
if (o is null)
{
return false;
}
if (Object.ReferenceEquals(this, o))
{
return true;
}
if (GetType() != o.GetType())
{
return false;
}
return actual.Equals(((LitS)o).actual);
}
/// <summary>
/// Returns the hash code for the current <see cref="LitS" />.
/// </summary>
public override int GetHashCode()
{
return actual.GetHashCode();
}
}
}
}