Add clickable parsing errors in build tool

This commit is contained in:
Wojtek Figat
2024-02-06 16:01:49 +01:00
parent 873be6ac17
commit 5a50ec592f

View File

@@ -47,6 +47,20 @@ namespace Flax.Build.Bindings
}
}
private class ParseException : Exception
{
public ParseException(ref ParsingContext context, string msg)
: base(GetParseErrorLocation(ref context, msg))
{
}
}
private static string GetParseErrorLocation(ref ParsingContext context, string msg)
{
// Make it a link clickable in Visual Studio build output
return $"{context.File.Name}({context.Tokenizer.CurrentLine}): {msg}";
}
private static string[] ParseComment(ref ParsingContext context)
{
if (context.StringCache == null)
@@ -180,7 +194,7 @@ namespace Flax.Build.Bindings
case TokenType.RightParent:
parameters.Add(tag);
return parameters;
default: throw new Exception($"Expected right parent or next argument, but got {token.Type}.");
default: throw new ParseException(ref context, $"Expected right parent or next argument, but got {token.Type}.");
}
}
}
@@ -302,7 +316,7 @@ namespace Flax.Build.Bindings
if (context.PreprocessorDefines.TryGetValue(length, out var define))
length = define;
if (!int.TryParse(length, out type.ArraySize))
throw new Exception($"Failed to parse the field {entry} array size '{length}'");
throw new ParseException(ref context, $"Failed to parse the field {entry} array size '{length}'");
}
private static List<FunctionInfo.ParameterInfo> ParseFunctionParameters(ref ParsingContext context)
@@ -354,7 +368,7 @@ namespace Flax.Build.Bindings
if (valid)
break;
var location = "function parameter";
Log.Warning($"Unknown or not supported tag parameter {tag} used on {location} at line {context.Tokenizer.CurrentLine}");
Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{location}'"));
break;
}
}
@@ -483,8 +497,7 @@ namespace Flax.Build.Bindings
desc.Inheritance = new List<TypeInfo>();
desc.Inheritance.Add(inheritType);
token = context.Tokenizer.NextToken();
while (token.Type == TokenType.CommentSingleLine
|| token.Type == TokenType.CommentMultiLine)
while (token.Type == TokenType.CommentSingleLine || token.Type == TokenType.CommentMultiLine)
{
token = context.Tokenizer.NextToken();
}
@@ -563,7 +576,7 @@ namespace Flax.Build.Bindings
// Read 'class' keyword
var token = context.Tokenizer.NextToken();
if (token.Value != "class")
throw new Exception($"Invalid {ApiTokens.Class} usage (expected 'class' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}').");
throw new ParseException(ref context, $"Invalid {ApiTokens.Class} usage (expected 'class' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}').");
// Read specifiers
while (true)
@@ -644,7 +657,7 @@ namespace Flax.Build.Bindings
ParseTypeTag?.Invoke(ref valid, tag, desc);
if (valid)
break;
Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name} at line {context.Tokenizer.CurrentLine}");
Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'"));
break;
}
}
@@ -672,7 +685,7 @@ namespace Flax.Build.Bindings
// Read 'class' keyword
var token = context.Tokenizer.NextToken();
if (token.Value != "class")
throw new Exception($"Invalid {ApiTokens.Interface} usage (expected 'class' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}').");
throw new ParseException(ref context, $"Invalid {ApiTokens.Interface} usage (expected 'class' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}').");
// Read specifiers
while (true)
@@ -735,7 +748,7 @@ namespace Flax.Build.Bindings
ParseTypeTag?.Invoke(ref valid, tag, desc);
if (valid)
break;
Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name} at line {context.Tokenizer.CurrentLine}");
Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'"));
break;
}
}
@@ -815,13 +828,13 @@ namespace Flax.Build.Bindings
{
case "const":
if (desc.IsConst)
throw new Exception($"Invalid double 'const' specifier in function {desc.Name} at line {context.Tokenizer.CurrentLine}.");
throw new ParseException(ref context, $"Invalid double 'const' specifier in function {desc.Name}");
desc.IsConst = true;
break;
case "override":
desc.IsVirtual = true;
break;
default: throw new Exception($"Unknown identifier '{token.Value}' in function {desc.Name} at line {context.Tokenizer.CurrentLine}.");
default: throw new ParseException(ref context, $"Unknown identifier '{token.Value}' in function {desc.Name}");
}
}
else if (token.Type == TokenType.LeftCurlyBrace)
@@ -875,7 +888,7 @@ namespace Flax.Build.Bindings
ParseMemberTag?.Invoke(ref valid, tag, desc);
if (valid)
break;
Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name} at line {context.Tokenizer.CurrentLine}");
Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'"));
break;
}
}
@@ -892,11 +905,11 @@ namespace Flax.Build.Bindings
var classInfo = context.ScopeInfo as ClassInfo;
if (classInfo == null)
throw new Exception($"Found property {propertyName} outside the class at line {context.Tokenizer.CurrentLine}.");
throw new ParseException(ref context, $"Found property {propertyName} outside the class");
var isGetter = !functionInfo.ReturnType.IsVoid;
if (!isGetter && functionInfo.Parameters.Count == 0)
throw new Exception($"Property {propertyName} setter method in class {classInfo.Name} has to have value parameter to set (line {context.Tokenizer.CurrentLine}).");
throw new ParseException(ref context, $"Property {propertyName} setter method in class {classInfo.Name} has to have value parameter to set (line {context.Tokenizer.CurrentLine}).");
var propertyType = isGetter ? functionInfo.ReturnType : functionInfo.Parameters[0].Type;
var propertyInfo = classInfo.Properties.FirstOrDefault(x => x.Name == propertyName);
@@ -917,7 +930,7 @@ namespace Flax.Build.Bindings
else
{
if (propertyInfo.IsStatic != functionInfo.IsStatic)
throw new Exception($"Property {propertyName} in class {classInfo.Name} has to have both getter and setter methods static or non-static (line {context.Tokenizer.CurrentLine}).");
throw new ParseException(ref context, $"Property {propertyName} in class {classInfo.Name} has to have both getter and setter methods static or non-static (line {context.Tokenizer.CurrentLine}).");
}
if (functionInfo.Tags != null)
{
@@ -934,9 +947,9 @@ namespace Flax.Build.Bindings
}
if (isGetter && propertyInfo.Getter != null)
throw new Exception($"Property {propertyName} in class {classInfo.Name} cannot have multiple getter method (line {context.Tokenizer.CurrentLine}). Getter methods of properties must return value, while setters take this as a parameter.");
throw new ParseException(ref context, $"Property {propertyName} in class {classInfo.Name} cannot have multiple getter method (line {context.Tokenizer.CurrentLine}). Getter methods of properties must return value, while setters take this as a parameter.");
if (!isGetter && propertyInfo.Setter != null)
throw new Exception($"Property {propertyName} in class {classInfo.Name} cannot have multiple setter method (line {context.Tokenizer.CurrentLine}). Getter methods of properties must return value, while setters take this as a parameter.");
throw new ParseException(ref context, $"Property {propertyName} in class {classInfo.Name} cannot have multiple setter method (line {context.Tokenizer.CurrentLine}). Getter methods of properties must return value, while setters take this as a parameter.");
if (isGetter)
propertyInfo.Getter = functionInfo;
@@ -963,7 +976,7 @@ namespace Flax.Build.Bindings
return propertyInfo;
if (getterType.Type == "Array" && setterType.Type == "Span" && getterType.GenericArgs?.Count == 1 && setterType.GenericArgs?.Count == 1 && getterType.GenericArgs[0].Equals(setterType.GenericArgs[0]))
return propertyInfo;
throw new Exception($"Property {propertyName} in class {classInfo.Name} (line {context.Tokenizer.CurrentLine}) has mismatching getter return type ({getterType}) and setter parameter type ({setterType}). Both getter and setter methods must use the same value type used for property.");
throw new ParseException(ref context, $"Property {propertyName} in class {classInfo.Name} (line {context.Tokenizer.CurrentLine}) has mismatching getter return type ({getterType}) and setter parameter type ({setterType}). Both getter and setter methods must use the same value type used for property.");
}
if (propertyInfo.Comment != null)
@@ -996,7 +1009,7 @@ namespace Flax.Build.Bindings
// Read 'enum' or `enum class` keywords
var token = context.Tokenizer.NextToken();
if (token.Value != "enum")
throw new Exception($"Invalid {ApiTokens.Enum} usage at line {context.Tokenizer.CurrentLine} (expected 'enum' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}').");
throw new ParseException(ref context, $"Invalid {ApiTokens.Enum} usage (expected 'enum' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}').");
token = context.Tokenizer.NextToken();
if (token.Value != "class")
context.Tokenizer.PreviousToken();
@@ -1079,7 +1092,7 @@ namespace Flax.Build.Bindings
entry.Attributes = tag.Value;
break;
default:
Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name + " enum entry"} at line {context.Tokenizer.CurrentLine}");
Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'"));
break;
}
}
@@ -1153,7 +1166,7 @@ namespace Flax.Build.Bindings
ParseTypeTag?.Invoke(ref valid, tag, desc);
if (valid)
break;
Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name} at line {context.Tokenizer.CurrentLine}");
Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'"));
break;
}
}
@@ -1176,7 +1189,7 @@ namespace Flax.Build.Bindings
// Read 'struct' keyword
var token = context.Tokenizer.NextToken();
if (token.Value != "struct")
throw new Exception($"Invalid {ApiTokens.Struct} usage (expected 'struct' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}').");
throw new ParseException(ref context, $"Invalid {ApiTokens.Struct} usage (expected 'struct' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}').");
// Read name
desc.Name = desc.NativeName = ParseName(ref context);
@@ -1233,7 +1246,7 @@ namespace Flax.Build.Bindings
ParseTypeTag?.Invoke(ref valid, tag, desc);
if (valid)
break;
Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name} at line {context.Tokenizer.CurrentLine}");
Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'"));
break;
}
}
@@ -1366,7 +1379,7 @@ namespace Flax.Build.Bindings
ParseMemberTag?.Invoke(ref valid, tag, desc);
if (valid)
break;
Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name} at line {context.Tokenizer.CurrentLine}");
Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'"));
break;
}
}
@@ -1396,7 +1409,7 @@ namespace Flax.Build.Bindings
if (desc.Type.Type == "Action")
desc.Type = new TypeInfo { Type = "Delegate", GenericArgs = new List<TypeInfo>() };
else if (desc.Type.Type != "Delegate")
throw new Exception($"Invalid {ApiTokens.Event} type. Only Action and Delegate<> types are supported. '{desc.Type}' used on event at line {context.Tokenizer.CurrentLine}.");
throw new ParseException(ref context, $"Invalid {ApiTokens.Event} type. Only Action and Delegate<> types are supported. '{desc.Type}' used on event.");
// Read name
desc.Name = ParseName(ref context);
@@ -1438,7 +1451,7 @@ namespace Flax.Build.Bindings
ParseMemberTag?.Invoke(ref valid, tag, desc);
if (valid)
break;
Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name} at line {context.Tokenizer.CurrentLine}");
Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'"));
break;
}
}
@@ -1474,7 +1487,7 @@ namespace Flax.Build.Bindings
// Read 'typedef' keyword
var token = context.Tokenizer.NextToken();
if (token.Value != "typedef")
throw new Exception($"Invalid {ApiTokens.Typedef} usage (expected 'typedef' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}').");
throw new ParseException(ref context, $"Invalid {ApiTokens.Typedef} usage (expected 'typedef' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}').");
// Read type definition
desc.Type = ParseType(ref context);
@@ -1513,7 +1526,7 @@ namespace Flax.Build.Bindings
ParseTypeTag?.Invoke(ref valid, tag, desc);
if (valid)
break;
Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name} at line {context.Tokenizer.CurrentLine}");
Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'"));
break;
}
}