Skip to content

Commit

Permalink
Change NamedType definition syntax (#2616)
Browse files Browse the repository at this point in the history
Changes NamedType definitions to use `:=`
  • Loading branch information
adithyaselv authored Sep 28, 2024
1 parent 6a096a1 commit de16049
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 78 deletions.
6 changes: 4 additions & 2 deletions src/libraries/Microsoft.PowerFx.Core/Lexer/TexlLexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ public enum Flags
public const string PunctuatorColon = ":";
public const string PunctuatorAt = "@";
public const char IdentifierDelimiter = '\'';
public const string PunctuatorDoubleBarrelArrow = "=>";
public const string PunctuatorDoubleBarrelArrow = "=>";
public const string PunctuatorColonEqual = ":=";

// These puntuators are related to commenting in the formula bar
public const string PunctuatorBlockComment = "/*";
Expand Down Expand Up @@ -305,7 +306,8 @@ private TexlLexer(string preferredDecimalSeparator)
AddPunctuator(punctuators, PunctuatorAmpersand, TokKind.Ampersand);
AddPunctuator(punctuators, PunctuatorPercent, TokKind.PercentSign);
AddPunctuator(punctuators, PunctuatorAt, TokKind.At);
AddPunctuator(punctuators, PunctuatorDoubleBarrelArrow, TokKind.DoubleBarrelArrow);
AddPunctuator(punctuators, PunctuatorDoubleBarrelArrow, TokKind.DoubleBarrelArrow);
AddPunctuator(punctuators, PunctuatorColonEqual, TokKind.ColonEqual);

// Commenting punctuators
AddPunctuator(punctuators, PunctuatorBlockComment, TokKind.Comment);
Expand Down
8 changes: 7 additions & 1 deletion src/libraries/Microsoft.PowerFx.Core/Lexer/TokKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,12 @@ public enum TokKind
/// Start of body for user defined functions.
/// <code>=></code>
/// </summary>
DoubleBarrelArrow,
DoubleBarrelArrow,

/// <summary>
/// Colon immediately followed by equal.
/// <code>:=</code>
/// </summary>
ColonEqual,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,7 @@ internal static class TexlStrings

public static ErrorResourceKey ErrNamedFormula_MissingSemicolon = new ErrorResourceKey("ErrNamedFormula_MissingSemicolon");
public static ErrorResourceKey ErrNamedFormula_MissingValue = new ErrorResourceKey("ErrNamedFormula_MissingValue");
public static ErrorResourceKey ErrNamedType_MissingTypeLiteral = new ErrorResourceKey("ErrNamedType_MissingTypeLiteral");
public static ErrorResourceKey ErrUDF_MissingFunctionBody = new ErrorResourceKey("ErrUDF_MissingFunctionBody");
public static ErrorResourceKey ErrNamedFormula_AlreadyDefined = new ErrorResourceKey("ErrNamedFormula_AlreadyDefined");
public static ErrorResourceKey ErrorResource_NameConflict = new ErrorResourceKey("ErrorResource_NameConflict");
Expand Down
47 changes: 45 additions & 2 deletions src/libraries/Microsoft.PowerFx.Core/Parser/TexlParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,15 +300,15 @@ private ParseUserDefinitionResult ParseUDFsAndNamedFormulas(string script, Parse
continue;
}

if (_curs.TidCur == TokKind.Equ)
if (_curs.TidCur == TokKind.ColonEqual && _flagsMode.Peek().HasFlag(Flags.AllowTypeLiteral))
{
var declaration = script.Substring(declarationStart, _curs.TokCur.Span.Min - declarationStart);
_curs.TokMove();
definitionBeforeTrivia.Add(ParseTrivia());

if (_curs.TidCur == TokKind.Semicolon)
{
CreateError(thisIdentifier, TexlStrings.ErrNamedFormula_MissingValue);
CreateError(thisIdentifier, TexlStrings.ErrNamedType_MissingTypeLiteral);
}

// Extract expression
Expand All @@ -334,6 +334,49 @@ private ParseUserDefinitionResult ParseUDFsAndNamedFormulas(string script, Parse
definitionBeforeTrivia = new List<ITexlSource>();
continue;
}
else
{
CreateError(_curs.TokCur, TexlStrings.ErrNamedType_MissingTypeLiteral);
}

// If the result was an error, keep moving cursor until end of named type expression
if (result.Kind == NodeKind.Error)
{
while (_curs.TidCur != TokKind.Semicolon && _curs.TidCur != TokKind.Eof)
{
_curs.TokMove();
}
}
}

declarationStart = _curs.TokCur.Span.Lim;
_curs.TokMove();
ParseTrivia();
}
else if (_curs.TidCur == TokKind.Equ)
{
var declaration = script.Substring(declarationStart, _curs.TokCur.Span.Min - declarationStart);
_curs.TokMove();
definitionBeforeTrivia.Add(ParseTrivia());

if (_curs.TidCur == TokKind.Semicolon)
{
CreateError(thisIdentifier, TexlStrings.ErrNamedFormula_MissingValue);
}

// Extract expression
while (_curs.TidCur != TokKind.Semicolon)
{
// Check if we're at EOF before a semicolon is found
if (_curs.TidCur == TokKind.Eof)
{
CreateError(_curs.TokCur, TexlStrings.ErrNamedFormula_MissingSemicolon);
break;
}

// Parse expression
definitionBeforeTrivia.Add(ParseTrivia());
var result = ParseExpr(Precedence.None);

namedFormulas.Add(new NamedFormula(thisIdentifier.As<IdentToken>(), new Formula(result.GetCompleteSpan().GetFragment(script), result), _startingIndex, attribute));
userDefinitionSourceInfos.Add(new UserDefinitionSourceInfo(index++, UserDefinitionType.NamedFormula, thisIdentifier.As<IdentToken>(), declaration, new SourceList(definitionBeforeTrivia), GetExtraTriviaSourceList()));
Expand Down
8 changes: 8 additions & 0 deletions src/strings/PowerFxResources.en-US.resx
Original file line number Diff line number Diff line change
Expand Up @@ -4115,6 +4115,14 @@
<value>Named formula must be an expression.</value>
<comment>This error message shows up when Named formula is not an expression. For example, a = ;</comment>
</data>
<data name="ErrNamedType_MissingTypeLiteral" xml:space="preserve">
<value>Named type must be a type literal.</value>
<comment>
This error message shows up when Named type is not a type literal. A valid type literal expression is of syntax "Type(Expression)".
Some examples for valid named type declarations - "Point := Type({x: Number, y: Number});" , "T1 := Type(Number);" , "T2 := Type([Boolean]);".
Some examples for invalid named type declarations - "T1 := 5;" , "T2 := ;" , "T3 := [1, 2, 3];".
</comment>
</data>
<data name="ErrUDF_MissingFunctionBody" xml:space="preserve">
<value>User-defined function must have a body.</value>
<comment>This error message shows up when user-defined function does not have a body</comment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class NamedFormulasTests : PowerFxTest
private readonly ParserOptions _parseOptions = new ParserOptions() { AllowsSideEffects = true };

[Theory]
[InlineData("Foo = Type(Number);")]
[InlineData("Foo := Type(Number);")]
public void DefSimpleTypeTest(string script)
{
var parserOptions = new ParserOptions()
Expand All @@ -33,7 +33,7 @@ public void DefSimpleTypeTest(string script)
}

[Theory]
[InlineData("Foo = Type({ Age: Number });")]
[InlineData("Foo := Type({ Age: Number });")]
public void DefRecordTypeTest(string script)
{
var parserOptions = new ParserOptions()
Expand Down Expand Up @@ -63,7 +63,7 @@ public void AsTypeTest(string script)
}

[Theory]
[InlineData("Foo = Type({Age: Number}; Bar(x: Number): Number = Abs(x);")]
[InlineData("Foo := Type({Age: Number}; Bar(x: Number): Number = Abs(x);")]
public void FailParsingTest(string script)
{
var parserOptions = new ParserOptions()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,24 @@ public class UserDefinedTypeTests : PowerFxTest
[Theory]

// Check record, table types with primitive types
[InlineData("Point = Type({ x: Number, y: Number })", "![x:n,y:n]", true)]
[InlineData("Points = Type([{ x: Number, y: Number }])", "*[x:n,y:n]", true)]
[InlineData("Person = Type({ name: Text, dob: Date })", "![name:s,dob:D]", true)]
[InlineData("People = Type([{ name: Text, isReady: Boolean }])", "*[name:s,isReady:b]", true)]
[InlineData("Heights = Type([Number])", "*[Value:n]", true)]
[InlineData("Palette = Type([Color])", "*[Value:c]", true)]
[InlineData("Point := Type({ x: Number, y: Number })", "![x:n,y:n]", true)]
[InlineData("Points := Type([{ x: Number, y: Number }])", "*[x:n,y:n]", true)]
[InlineData("Person := Type({ name: Text, dob: Date })", "![name:s,dob:D]", true)]
[InlineData("People := Type([{ name: Text, isReady: Boolean }])", "*[name:s,isReady:b]", true)]
[InlineData("Heights := Type([Number])", "*[Value:n]", true)]
[InlineData("Palette := Type([Color])", "*[Value:c]", true)]

// Type alias
[InlineData("DTNZ = Type(DateTimeTZInd)", "Z", true)]
[InlineData("DTNZ := Type(DateTimeTZInd)", "Z", true)]

// Nested record types
[InlineData("Nested = Type({a: {b: DateTime, c: {d: GUID, e: Hyperlink}}, x: Time})", "![a:![b:d, c:![d:g, e:h]], x:T]", true)]
[InlineData("Nested := Type({a: {b: DateTime, c: {d: GUID, e: Hyperlink}}, x: Time})", "![a:![b:d, c:![d:g, e:h]], x:T]", true)]

// Invalid types
[InlineData("Pics = Type([Image])", "*[Value:i]", false)]
[InlineData("A = Type(B)", "", false)]
[InlineData("A = Type([])", "", false)]
[InlineData("A = Type({})", "", false)]
[InlineData("Pics := Type([Image])", "*[Value:i]", false)]
[InlineData("A := Type(B)", "", false)]
[InlineData("A := Type([])", "", false)]
[InlineData("A := Type({})", "", false)]
public void TestUserDefinedType(string typeDefinition, string expectedDefinedTypeString, bool isValid)
{
var parseOptions = new ParserOptions
Expand All @@ -66,38 +66,38 @@ public void TestUserDefinedType(string typeDefinition, string expectedDefinedTyp
}

[Theory]
[InlineData("X = 5; Point = Type({ x: Number, y: Number })", 1)]
[InlineData("Point = Type({ x: Number, y: Number }); Points = Type([Point])", 2)]
[InlineData("X = 5; Point := Type({ x: Number, y: Number })", 1)]
[InlineData("Point := Type({ x: Number, y: Number }); Points := Type([Point])", 2)]

// Mix named formula with named type
[InlineData("X = 5; Point = Type({ x: X, y: Number }); Points = Type([Point])", 0)]
[InlineData("X = 5; Point := Type({ x: X, y: Number }); Points := Type([Point])", 0)]

// Have invalid type expression
[InlineData("WrongType = Type(5+5); WrongTypes = Type([WrongType]); People = Type([{ name: Text, age: Number }])", 1)]
[InlineData("WrongType := Type(5+5); WrongTypes := Type([WrongType]); People := Type([{ name: Text, age: Number }])", 1)]

// Have incomplete expressions and parse errors
[InlineData("Point = Type({a:); Points = Type([Point]); People = Type([{ name: Text, age })", 0)]
[InlineData("Point = Type({a:; Points = Type([Point]); People = Type([{ name: Text, age: Number })", 1)]
[InlineData("Point := Type({a:); Points = Type([Point]); People := Type([{ name: Text, age })", 0)]
[InlineData("Point := Type({a:; Points = Type([Point]); People := Type([{ name: Text, age: Number })", 1)]

// Redeclare type
[InlineData("Point = Type({ x: Number, y: Number }); Point = Type(Number);", 1)]
[InlineData("Point := Type({ x: Number, y: Number }); Point := Type(Number);", 1)]

// Redeclare typed name in record
[InlineData("X= Type({ f:Number, f:Number});", 0)]
[InlineData("X:= Type({ f:Number, f:Number});", 0)]

// Cyclic definition
[InlineData("B = Type({ x: A }); A = Type(B);", 0)]
[InlineData("B = Type(B);", 0)]
[InlineData("B := Type({ x: A }); A := Type(B);", 0)]
[InlineData("B := Type(B);", 0)]

// Complex resolutions
[InlineData("C = Type({x: Boolean, y: Date, f: B});B = Type({ x: A }); A = Type(Number);", 3)]
[InlineData("D = Type({nArray: [Number]}), C = Type({x: Boolean, y: Date, f: B});B = Type({ x: A }); A = Type([C]);", 1)]
[InlineData("C := Type({x: Boolean, y: Date, f: B});B := Type({ x: A }); A := Type(Number);", 3)]
[InlineData("D := Type({nArray: [Number]}), C := Type({x: Boolean, y: Date, f: B});B := Type({ x: A }); A := Type([C]);", 1)]

// With Invalid types
[InlineData("A = Type(Blob); B = Type({x: Currency}); C = Type([DateTime]); D = Type(None)", 2)]
[InlineData("A := Type(Blob); B := Type({x: Currency}); C := Type([DateTime]); D := Type(None)", 2)]

// Have named formulas and udf in the script
[InlineData("NAlias = Type(Number);X = 5; ADDX(n:Number): Number = n + X; SomeType = Type(UntypedObject)", 2)]
[InlineData("NAlias := Type(Number);X := 5; ADDX(n:Number): Number = n + X; SomeType := Type(UntypedObject)", 2)]
public void TestValidUDTCounts(string typeDefinition, int expectedDefinedTypesCount)
{
var parseOptions = new ParserOptions
Expand All @@ -118,11 +118,13 @@ public void TestValidUDTCounts(string typeDefinition, int expectedDefinedTypesCo
[Theory]

//To test DefinitionsCheckResult.ApplyErrors method and error messages
[InlineData("Point = Type({ x: Number, y: Number }); Point = Type(Number);", 1, "ErrNamedType_TypeAlreadyDefined")]
[InlineData("X= Type({ f:Number, f:Number});", 1, "ErrNamedType_InvalidTypeDefinition")]
[InlineData("B = Type({ x: A }); A = Type(B);", 2, "ErrNamedType_InvalidCycles")]
[InlineData("B = Type(B);", 1, "ErrNamedType_InvalidCycles")]
[InlineData("Currency = Type({x: Text}); Record = Type([DateTime]); D = Type(None);", 2, "ErrNamedType_InvalidTypeName")]
[InlineData("Point := Type({ x: Number, y: Number }); Point := Type(Number);", 1, "ErrNamedType_TypeAlreadyDefined")]
[InlineData("X:= Type({ f:Number, f:Number});", 1, "ErrNamedType_InvalidTypeDefinition")]
[InlineData("B := Type({ x: A }); A := Type(B);", 2, "ErrNamedType_InvalidCycles")]
[InlineData("B := Type(B);", 1, "ErrNamedType_InvalidCycles")]
[InlineData("Currency := Type({x: Text}); Record := Type([DateTime]); D := Type(None);", 2, "ErrNamedType_InvalidTypeName")]
[InlineData("A = 5;C :=; B := Type(Number);", 1, "ErrNamedType_MissingTypeLiteral")]
[InlineData("C := 5; D := [1,2,3];", 2, "ErrNamedType_MissingTypeLiteral")]
public void TestUDTErrors(string typeDefinition, int expectedErrorCount, string expectedMessageKey)
{
var parseOptions = new ParserOptions
Expand All @@ -140,21 +142,21 @@ public void TestUDTErrors(string typeDefinition, int expectedErrorCount, string
}

[Theory]
[InlineData("T = Type({ x: 5+5, y: -5 });", 2)]
[InlineData("T = Type(Type(Number));", 1)]
[InlineData("T = Type({+});", 1)]
[InlineData("T = Type({);", 1)]
[InlineData("T = Type({x: true, y: \"Number\"});", 2)]
[InlineData("T1 = Type({A: Number}); T2 = Type(T1.A);", 1)]
[InlineData("T = Type((1, 2));", 1)]
[InlineData("T1 = Type(UniChar(955)); T2 = Type([Table(Number)])", 2)]
[InlineData("T = Type((1; 2));", 1)]
[InlineData("T = Type(Self.T);", 1)]
[InlineData("T = Type(Parent.T);", 1)]
[InlineData("T = Type(Number As T1);", 1)]
[InlineData("T = Type(Text); T1 = Type(Not T);", 1)]
[InlineData("T1 = Type({V: Number}); T2 = Type(T1[@V]);", 1)]
[InlineData("T = Type([{a: {b: {c: [{d: 10e+4}]}}}]);", 1)]
[InlineData("T := Type({ x: 5+5, y: -5 });", 2)]
[InlineData("T := Type(Type(Number));", 1)]
[InlineData("T := Type({+});", 1)]
[InlineData("T := Type({);", 1)]
[InlineData("T := Type({x: true, y: \"Number\"});", 2)]
[InlineData("T1 := Type({A: Number}); T2 := Type(T1.A);", 1)]
[InlineData("T := Type((1, 2));", 1)]
[InlineData("T1 := Type(UniChar(955)); T2 := Type([Table(Number)])", 2)]
[InlineData("T := Type((1; 2));", 1)]
[InlineData("T := Type(Self.T);", 1)]
[InlineData("T := Type(Parent.T);", 1)]
[InlineData("T := Type(Number As T1);", 1)]
[InlineData("T := Type(Text); T1 := Type(Not T);", 1)]
[InlineData("T1 := Type({V: Number}); T2 := Type(T1[@V]);", 1)]
[InlineData("T := Type([{a: {b: {c: [{d: 10e+4}]}}}]);", 1)]
public void TestUDTParseErrors(string typeDefinition, int expectedErrorCount)
{
var parseOptions = new ParserOptions
Expand Down
Loading

0 comments on commit de16049

Please sign in to comment.