Skip to content

Commit

Permalink
feat(compiler): add support for integer expressions
Browse files Browse the repository at this point in the history
- Added support for parsing and generating integer constants.
- At the moment there isn't any error handling, so it will generate invalid expressions (for example
  an i32 expression being assigned to an i8).
- At the moment there's a slight blurring between a _type definition_ vs a _type reference_. In
  order to fully support constants and checking that the expression is compatible with the type of
  the constant we'll need to sort that, but for now I've just fudged it enough to work by adding
  static members for each base type in `BaseType`.
- Added some sample Thrift files for constants.

Issues: #8
  • Loading branch information
adamconnelly committed Jan 2, 2021
1 parent 8dcec32 commit 01479b7
Show file tree
Hide file tree
Showing 39 changed files with 849 additions and 85 deletions.
19 changes: 18 additions & 1 deletion src/Thrift.Net.Antlr/Thrift.g4
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ definitions: (
enumDefinition |
structDefinition |
unionDefinition |
exceptionDefinition)*;
exceptionDefinition |
constDefinition)*;

enumDefinition: ENUM name=IDENTIFIER?
'{'
Expand Down Expand Up @@ -72,6 +73,9 @@ listType: LIST LT_OPERATOR? fieldType? GT_OPERATOR?;
setType: SET LT_OPERATOR? fieldType? GT_OPERATOR?;
mapType: MAP LT_OPERATOR? keyType=fieldType? COMMA? valueType=fieldType? GT_OPERATOR?;

constDefinition: CONST fieldType name=IDENTIFIER? EQUALS_OPERATOR constExpression;
constExpression: value=INT_CONSTANT | value=DOUBLE_CONSTANT | value=.*?;

listSeparator: COMMA | SEMICOLON;

NAMESPACE: 'namespace';
Expand All @@ -84,6 +88,7 @@ OPTIONAL: 'optional';
LIST: 'list';
SET: 'set';
MAP: 'map';
CONST: 'const';
LT_OPERATOR: '<';
GT_OPERATOR: '>';
COMMA: ',';
Expand All @@ -94,6 +99,18 @@ KNOWN_NAMESPACE_SCOPES: '*' | 'c_glib' | 'cpp' | 'csharp' | 'delphi' | 'go' |
EQUALS_OPERATOR: '=';
LITERAL: ( '"' .*? '"' ) | ( '\'' .*? '\'' );
IDENTIFIER: ( [a-zA-Z] | '_' ) ( [a-zA-Z] | [0-9] | '.' | '_' )*;

// Examples of valid doubles (based on the Thrift IDL) include:
// 100.5
// -100.5
// +100.5
// 100.5e10
// 100e2 (for some reason only doubles support exponent syntax, and an integer value using this syntax is classed as a double)
DOUBLE_CONSTANT: ('+' | '-')? [0-9]* (
('.' [0-9]+) ([eE] INT_CONSTANT)? |
([eE] INT_CONSTANT)
);

INT_CONSTANT: ('+' | '-')? [0-9]+;

// NOTE: HEX_CONSTANT deliberately allows invalid hex (i.e. letters > F) to allow
Expand Down
12 changes: 12 additions & 0 deletions src/Thrift.Net.Compilation/Binding/BinderProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public class BinderProvider : IBinderProvider
private static readonly ListTypeBinder ListTypeBinder;
private static readonly SetTypeBinder SetTypeBinder;
private static readonly MapTypeBinder MapTypeBinder;
private static readonly ConstantBinder ConstantBinder;
private static readonly ConstantValueBinder ConstantValueBinder;
private static readonly BinderProvider ProviderInstance;

static BinderProvider()
Expand All @@ -44,6 +46,8 @@ static BinderProvider()
ListTypeBinder = new ListTypeBinder(ProviderInstance);
MapTypeBinder = new MapTypeBinder(ProviderInstance);
SetTypeBinder = new SetTypeBinder(ProviderInstance);
ConstantBinder = new ConstantBinder(ProviderInstance);
ConstantValueBinder = new ConstantValueBinder();
}

private BinderProvider()
Expand Down Expand Up @@ -128,6 +132,14 @@ public IBinder GetBinder(IParseTree node)
{
return ExceptionBinder;
}
else if (node is ConstDefinitionContext)
{
return ConstantBinder;
}
else if (node is ConstExpressionContext)
{
return ConstantValueBinder;
}

throw new ArgumentException(
$"No binder could be found to bind the {node.GetType().Name} node type",
Expand Down
35 changes: 35 additions & 0 deletions src/Thrift.Net.Compilation/Binding/ConstantBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace Thrift.Net.Compilation.Binding
{
using Thrift.Net.Compilation.Symbols;
using Thrift.Net.Compilation.Symbols.Builders;
using static Thrift.Net.Antlr.ThriftParser;

/// <summary>
/// Binds <see cref="IConstant" /> objects from <see cref="ConstDefinitionContext" /> nodes.
/// </summary>
public class ConstantBinder : Binder<ConstDefinitionContext, IConstant, IDocument>
{
private readonly IBinderProvider binderProvider;

/// <summary>
/// Initializes a new instance of the <see cref="ConstantBinder" /> class.
/// </summary>
/// <param name="binderProvider">Used to get binders.</param>
public ConstantBinder(IBinderProvider binderProvider)
{
this.binderProvider = binderProvider;
}

/// <inheritdoc/>
protected override IConstant Bind(ConstDefinitionContext node, IDocument parent)
{
var builder = new ConstantBuilder()
.SetBinderProvider(this.binderProvider)
.SetNode(node)
.SetParent(parent)
.SetName(node.name?.Text);

return builder.Build();
}
}
}
68 changes: 68 additions & 0 deletions src/Thrift.Net.Compilation/Binding/ConstantValueBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
namespace Thrift.Net.Compilation.Binding
{
using Thrift.Net.Compilation.Symbols;
using Thrift.Net.Compilation.Symbols.Builders;
using static Thrift.Net.Antlr.ThriftParser;

/// <summary>
/// Used to bind <see cref="IConstantValue" /> symbols from <see cref="ConstExpressionContext" />
/// nodes.
/// </summary>
public class ConstantValueBinder : Binder<ConstExpressionContext, IConstantValue, IConstant>
{
/// <inheritdoc/>
protected override IConstantValue Bind(ConstExpressionContext node, IConstant parent)
{
var type = GetExpressionType(node);

var builder = new ConstantValueBuilder()
.SetNode(node)
.SetParent(parent)
.SetRawValue(node.value?.Text)
.SetType(type);

return builder.Build();
}

/// <summary>
/// Gets the type of the constant expression to allow us to check that
/// the expression is assignable to the constant.
/// </summary>
/// <remarks>
/// When calculating the type for numeric types, we attempt to use the
/// smallest possible type that the expression could be represented using.
/// So for integers we'll use the following order: i8, i16, 132, 164. This
/// means that we can automatically figure out whether or not an expression
/// is assignable to the constant type definition using the principle that
/// a smaller type can be assigned to a larger type, but not the other way
/// round.
/// </remarks>
private static BaseType GetExpressionType(ConstExpressionContext node)
{
if (node.INT_CONSTANT() != null && long.TryParse(node.value?.Text, out var value))
{
if (value < int.MinValue || value > int.MaxValue)
{
return BaseType.I64;
}
else if (value < short.MinValue || value > short.MaxValue)
{
return BaseType.I32;
}
else if (value < sbyte.MinValue || value > sbyte.MaxValue)
{
return BaseType.I16;
}

return BaseType.I8;
}

if (node.DOUBLE_CONSTANT() != null)
{
return BaseType.Double;
}

return BaseType.String;
}
}
}
2 changes: 1 addition & 1 deletion src/Thrift.Net.Compilation/CompilationVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ public override void VisitField(IField field)
}
}

if (field.Type.Name == BaseType.Slist)
if (field.Type.Name == BaseType.SlistName)
{
// A field has been declared using a deprecated type. For example:
// ```
Expand Down
9 changes: 5 additions & 4 deletions src/Thrift.Net.Compilation/IThriftDocumentGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ namespace Thrift.Net.Compilation
public interface IThriftDocumentGenerator
{
/// <summary>
/// Generates the C# required for the specified document.
/// Generates the C# representation of the specified Thrift document.
/// </summary>
/// <param name="document">The document to generate code for.</param>
/// <returns>The generated C#.</returns>
string Generate(IDocument document);
/// <param name="file">Contains information about the thrift input file.</param>
/// <param name="document">The document to generate code from.</param>
/// <returns>The generated code.</returns>
string Generate(ThriftFile file, IDocument document);
}
}
112 changes: 82 additions & 30 deletions src/Thrift.Net.Compilation/Symbols/BaseType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,68 +12,118 @@ public class BaseType : Symbol<BaseTypeContext, ISymbol>, IBaseType
/// <summary>
/// The name of the byte type.
/// </summary>
public const string Byte = "byte";
public const string ByteName = "byte";

/// <summary>
/// The name of the i8 type.
/// </summary>
public const string I8 = "i8";
public const string I8Name = "i8";

/// <summary>
/// The name of the i16 type.
/// </summary>
public const string I16 = "i16";
public const string I16Name = "i16";

/// <summary>
/// The name of the i32 type.
/// </summary>
public const string I32 = "i32";
public const string I32Name = "i32";

/// <summary>
/// The name of the i64 type.
/// </summary>
public const string I64 = "i64";
public const string I64Name = "i64";

/// <summary>
/// The name of the bool type.
/// </summary>
public const string Bool = "bool";
public const string BoolName = "bool";

/// <summary>
/// The name of the double type.
/// </summary>
public const string Double = "double";
public const string DoubleName = "double";

/// <summary>
/// The name of the string type.
/// </summary>
public const string String = "string";
public const string StringName = "string";

/// <summary>
/// The name of the binary type.
/// </summary>
public const string Binary = "binary";
public const string BinaryName = "binary";

/// <summary>
/// The name of the slist type.
/// </summary>
public const string Slist = "slist";
public const string SlistName = "slist";

/// <summary>
/// The bool base type.
/// </summary>
public static readonly BaseType Bool = new BaseType(null, null, BoolName, "bool", "bool?");

/// <summary>
/// The byte base type.
/// </summary>
public static readonly BaseType Byte = new BaseType(null, null, ByteName, "byte", "byte?");

/// <summary>
/// The i8 base type.
/// </summary>
public static readonly BaseType I8 = new BaseType(null, null, I8Name, "sbyte", "sbyte?");

/// <summary>
/// The i16 base type.
/// </summary>
public static readonly BaseType I16 = new BaseType(null, null, I16Name, "short", "short?");

/// <summary>
/// The i32 base type.
/// </summary>
public static readonly BaseType I32 = new BaseType(null, null, I32Name, "int", "int?");

/// <summary>
/// The i64 base type.
/// </summary>
public static readonly BaseType I64 = new BaseType(null, null, I64Name, "long", "long?");

/// <summary>
/// The double base type.
/// </summary>
public static readonly BaseType Double = new BaseType(null, null, DoubleName, "double", "double?");

/// <summary>
/// The string base type.
/// </summary>
public static readonly BaseType String = new BaseType(null, null, StringName, "string", "string");

/// <summary>
/// The binary base type.
/// </summary>
public static readonly BaseType Binary = new BaseType(null, null, BinaryName, "byte[]", "byte[]");

/// <summary>
/// The slist base type.
/// </summary>
public static readonly BaseType Slist = new BaseType(null, null, SlistName, "string", "string");

/// <summary>
/// The list of all base type names.
/// </summary>
public static readonly IReadOnlyCollection<string> Names = new List<string>
{
Byte,
I8,
I16,
I32,
I64,
Bool,
Double,
String,
Binary,
Slist,
ByteName,
I8Name,
I16Name,
I32Name,
I64Name,
BoolName,
DoubleName,
StringName,
BinaryName,
SlistName,
};

/// <summary>
Expand Down Expand Up @@ -147,18 +197,20 @@ public BaseType(
/// </exception>
public static BaseType Resolve(BaseTypeContext node, ISymbol parent)
{
// TODO: Remove this method once we refactor and separate the concept of
// types vs type references.
return node.typeName.Text switch
{
"bool" => new BaseType(node, parent, "bool", "bool", "bool?"),
"byte" => new BaseType(node, parent, "byte", "byte", "byte?"),
"i8" => new BaseType(node, parent, "i8", "sbyte", "sbyte?"),
"i16" => new BaseType(node, parent, "i16", "short", "short?"),
"i32" => new BaseType(node, parent, "i32", "int", "int?"),
"i64" => new BaseType(node, parent, "i64", "long", "long?"),
"double" => new BaseType(node, parent, "double", "double", "double?"),
"string" => new BaseType(node, parent, "string", "string", "string"),
"binary" => new BaseType(node, parent, "binary", "byte[]", "byte[]"),
"slist" => new BaseType(node, parent, "slist", "string", "string"),
BoolName => new BaseType(node, parent, BoolName, "bool", "bool?"),
ByteName => new BaseType(node, parent, ByteName, "byte", "byte?"),
I8Name => new BaseType(node, parent, I8Name, "sbyte", "sbyte?"),
I16Name => new BaseType(node, parent, I16Name, "short", "short?"),
I32Name => new BaseType(node, parent, I32Name, "int", "int?"),
I64Name => new BaseType(node, parent, I64Name, "long", "long?"),
DoubleName => new BaseType(node, parent, DoubleName, "double", "double?"),
StringName => new BaseType(node, parent, StringName, "string", "string"),
BinaryName => new BaseType(node, parent, BinaryName, "byte[]", "byte[]"),
SlistName => new BaseType(node, parent, SlistName, "string", "string"),
_ => throw new InvalidOperationException($"'{node.typeName.Text}' is not a known base type"),
};
}
Expand Down
Loading

0 comments on commit 01479b7

Please sign in to comment.