Skip to content

Commit

Permalink
Merge pull request #391 from Luthetus/staging
Browse files Browse the repository at this point in the history
Staging
  • Loading branch information
Luthetus authored Dec 20, 2024
2 parents f73d4c9 + fb15565 commit 0fe144c
Show file tree
Hide file tree
Showing 8 changed files with 475 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public IExpressionNode AnyMergeToken(
return EmptyExpressionNode.EmptyFollowsMemberAccessToken;
}

if (UtilityApi.IsBinaryOperatorSyntaxKind(token.SyntaxKind))
return HandleBinaryOperator(expressionPrimary, token, compilationUnit, ref parserModel);

switch (expressionPrimary.SyntaxKind)
{
case SyntaxKind.EmptyExpressionNode:
Expand Down Expand Up @@ -106,6 +109,8 @@ public IExpressionNode AnyMergeExpression(

switch (expressionPrimary.SyntaxKind)
{
case SyntaxKind.BinaryExpressionNode:
return BinaryMergeExpression((BinaryExpressionNode)expressionPrimary, expressionSecondary, compilationUnit, ref parserModel);
case SyntaxKind.ParenthesizedExpressionNode:
return ParenthesizedMergeExpression((ParenthesizedExpressionNode)expressionPrimary, expressionSecondary, compilationUnit, ref parserModel);
case SyntaxKind.CommaSeparatedExpressionNode:
Expand Down Expand Up @@ -136,6 +141,84 @@ public IExpressionNode AnyMergeExpression(
return new BadExpressionNode(CSharpFacts.Types.Void.ToTypeClause(), expressionPrimary, expressionSecondary);
};
}

public IExpressionNode HandleBinaryOperator(
IExpressionNode expressionPrimary, ISyntaxToken token, CSharpCompilationUnit compilationUnit, ref CSharpParserModel parserModel)
{
var parentExpressionNode = GetParentNode(expressionPrimary, compilationUnit, ref parserModel);

if (parentExpressionNode.SyntaxKind == SyntaxKind.BinaryExpressionNode)
{
var parentBinaryExpressionNode = (BinaryExpressionNode)parentExpressionNode;
var precedenceParent = UtilityApi.GetOperatorPrecedence(parentBinaryExpressionNode.BinaryOperatorNode.OperatorToken.SyntaxKind);

var precedenceChild = UtilityApi.GetOperatorPrecedence(token.SyntaxKind);

if (parentBinaryExpressionNode.RightExpressionNode.SyntaxKind == SyntaxKind.EmptyExpressionNode)
{
if (precedenceParent >= precedenceChild)
{
// Child's left expression becomes the parent.

parentBinaryExpressionNode.SetRightExpressionNode(expressionPrimary);

var typeClauseNode = expressionPrimary.ResultTypeClauseNode;
var binaryOperatorNode = new BinaryOperatorNode(typeClauseNode, token, typeClauseNode, typeClauseNode);
var binaryExpressionNode = new BinaryExpressionNode(parentBinaryExpressionNode, binaryOperatorNode);

ClearFromExpressionList(parentBinaryExpressionNode, compilationUnit, ref parserModel);
parserModel.ExpressionList.Add((SyntaxKind.EndOfFileToken, binaryExpressionNode));

return EmptyExpressionNode.Empty;
}
else
{
// Parent's right expression becomes the child.

var typeClauseNode = expressionPrimary.ResultTypeClauseNode;
var binaryOperatorNode = new BinaryOperatorNode(typeClauseNode, token, typeClauseNode, typeClauseNode);
var binaryExpressionNode = new BinaryExpressionNode(expressionPrimary, binaryOperatorNode);

parserModel.ExpressionList.Add((SyntaxKind.EndOfFileToken, binaryExpressionNode));

//parentBinaryExpressionNode.SetRightExpressionNode(binaryExpressionNode);

return EmptyExpressionNode.Empty;
}
}
else
{
// Weird situation?
// This sounds like it just wouldn't compile.
//
// Something like:
// 1 + 2 3 + 4
//
// NOTE: There is no operator between the '2' and the '3'.
// It is just two binary expressions side by side.
//
// I think you'd want to pretend that the parent binary expression didn't exist
// for the sake of parser recovery.

var typeClauseNode = expressionPrimary.ResultTypeClauseNode;
var binaryOperatorNode = new BinaryOperatorNode(typeClauseNode, token, typeClauseNode, typeClauseNode);
var binaryExpressionNode = new BinaryExpressionNode(expressionPrimary, binaryOperatorNode);

ClearFromExpressionList(parentBinaryExpressionNode, compilationUnit, ref parserModel);
parserModel.ExpressionList.Add((SyntaxKind.EndOfFileToken, binaryExpressionNode));
return EmptyExpressionNode.Empty;
}
}

// Scope to avoid variable name collision.
{
var typeClauseNode = expressionPrimary.ResultTypeClauseNode;
var binaryOperatorNode = new BinaryOperatorNode(typeClauseNode, token, typeClauseNode, typeClauseNode);
var binaryExpressionNode = new BinaryExpressionNode(expressionPrimary, binaryOperatorNode);
parserModel.ExpressionList.Add((SyntaxKind.EndOfFileToken, binaryExpressionNode));
return EmptyExpressionNode.Empty;
}
}

public IExpressionNode AmbiguousIdentifierMergeToken(
AmbiguousIdentifierExpressionNode ambiguousIdentifierExpressionNode, ISyntaxToken token, CSharpCompilationUnit compilationUnit, ref CSharpParserModel parserModel)
Expand All @@ -159,10 +242,8 @@ public IExpressionNode AmbiguousIdentifierMergeToken(
BindFunctionInvocationNode(
functionInvocationNode,
compilationUnit);

parserModel.ExpressionList.Add((SyntaxKind.CloseParenthesisToken, functionInvocationNode));
parserModel.ExpressionList.Add((SyntaxKind.CommaToken, functionInvocationNode.FunctionParametersListingNode));
return EmptyExpressionNode.Empty;

return ParseFunctionParametersListingNode(functionInvocationNode, functionInvocationNode.FunctionParametersListingNode, compilationUnit, ref parserModel);
}
else if (token.SyntaxKind == SyntaxKind.OpenAngleBracketToken)
{
Expand Down Expand Up @@ -472,6 +553,18 @@ public IExpressionNode BinaryMergeToken(
}
}

public IExpressionNode BinaryMergeExpression(
BinaryExpressionNode binaryExpressionNode, IExpressionNode expressionSecondary, CSharpCompilationUnit compilationUnit, ref CSharpParserModel parserModel)
{
if (binaryExpressionNode.RightExpressionNode.SyntaxKind == SyntaxKind.EmptyExpressionNode)
{
binaryExpressionNode.SetRightExpressionNode(expressionSecondary);
return binaryExpressionNode;
}

return new BadExpressionNode(CSharpFacts.Types.Void.ToTypeClause(), binaryExpressionNode, expressionSecondary);
}

public IExpressionNode CommaSeparatedMergeToken(
CommaSeparatedExpressionNode commaSeparatedExpressionNode, ISyntaxToken token, CSharpCompilationUnit compilationUnit, ref CSharpParserModel parserModel)
{
Expand Down Expand Up @@ -533,9 +626,8 @@ public IExpressionNode ConstructorInvocationMergeToken(
constructorInvocationExpressionNode.SetFunctionParametersListingNode(functionParametersListingNode);

constructorInvocationExpressionNode.ConstructorInvocationStageKind = ConstructorInvocationStageKind.FunctionParameters;
parserModel.ExpressionList.Add((SyntaxKind.CloseParenthesisToken, constructorInvocationExpressionNode));
parserModel.ExpressionList.Add((SyntaxKind.CommaToken, constructorInvocationExpressionNode.FunctionParametersListingNode));
return EmptyExpressionNode.Empty;

return ParseFunctionParametersListingNode(constructorInvocationExpressionNode, constructorInvocationExpressionNode.FunctionParametersListingNode, compilationUnit, ref parserModel);
case SyntaxKind.CloseParenthesisToken:
if (constructorInvocationExpressionNode.FunctionParametersListingNode is not null)
{
Expand Down Expand Up @@ -961,6 +1053,10 @@ public IExpressionNode FunctionParametersListingMergeToken(
{
case SyntaxKind.CommaToken:
parserModel.ExpressionList.Add((SyntaxKind.CommaToken, functionParametersListingNode));
parserModel.ExpressionList.Add((SyntaxKind.ColonToken, functionParametersListingNode));
return ParseNamedParameterSyntaxAndReturnEmptyExpressionNode(compilationUnit, ref parserModel);
case SyntaxKind.ColonToken:
parserModel.ExpressionList.Add((SyntaxKind.ColonToken, functionParametersListingNode));
return EmptyExpressionNode.Empty;
default:
return new BadExpressionNode(CSharpFacts.Types.Void.ToTypeClause(), functionParametersListingNode, token);
Expand All @@ -970,6 +1066,13 @@ public IExpressionNode FunctionParametersListingMergeToken(
public IExpressionNode FunctionParametersListingMergeExpression(
FunctionParametersListingNode functionParametersListingNode, IExpressionNode expressionSecondary, CSharpCompilationUnit compilationUnit, ref CSharpParserModel parserModel)
{
Console.WriteLine("aaa FunctionParametersListingMergeExpression");
if (parserModel.TokenWalker.Current.SyntaxKind == SyntaxKind.ColonToken)
{
Console.WriteLine($"aaa CURRENT optional? bbb {expressionSecondary.SyntaxKind}");
return functionParametersListingNode;
}

if (expressionSecondary.SyntaxKind == SyntaxKind.EmptyExpressionNode)
return functionParametersListingNode;

Expand Down Expand Up @@ -1179,19 +1282,7 @@ public IExpressionNode LambdaMergeExpression(
public IExpressionNode LiteralMergeToken(
LiteralExpressionNode literalExpressionNode, ISyntaxToken token, CSharpCompilationUnit compilationUnit, ref CSharpParserModel parserModel)
{
switch (token.SyntaxKind)
{
case SyntaxKind.PlusToken:
case SyntaxKind.MinusToken:
case SyntaxKind.StarToken:
case SyntaxKind.DivisionToken:
case SyntaxKind.EqualsEqualsToken:
var typeClauseNode = literalExpressionNode.ResultTypeClauseNode;
var binaryOperatorNode = new BinaryOperatorNode(typeClauseNode, token, typeClauseNode, typeClauseNode);
return new BinaryExpressionNode(literalExpressionNode, binaryOperatorNode);
default:
return new BadExpressionNode(CSharpFacts.Types.Void.ToTypeClause(), literalExpressionNode, token);
}
return new BadExpressionNode(CSharpFacts.Types.Void.ToTypeClause(), literalExpressionNode, token);
}

public IExpressionNode ParenthesizedMergeToken(
Expand Down Expand Up @@ -1323,9 +1414,7 @@ public IExpressionNode TypeClauseMergeToken(
functionInvocationNode,
compilationUnit);

parserModel.ExpressionList.Add((SyntaxKind.CloseParenthesisToken, functionInvocationNode));
parserModel.ExpressionList.Add((SyntaxKind.CommaToken, functionInvocationNode.FunctionParametersListingNode));
return EmptyExpressionNode.Empty;
return ParseFunctionParametersListingNode(functionInvocationNode, functionInvocationNode.FunctionParametersListingNode, compilationUnit, ref parserModel);
}

goto default;
Expand Down Expand Up @@ -1617,4 +1706,70 @@ public void ClearFromExpressionList(IExpressionNode expressionNode, CSharpCompil
parserModel.ExpressionList.RemoveAt(i);
}
}

/// <summary>
/// 'bool foundChild' usage:
/// If the child is NOT in the ExpressionList then this is true,
///
/// But, if the child is in the ExpressionList, and is not the final entry in the ExpressionList,
/// then this needs to be set to 'false', otherwise a descendent node of 'childExpressionNode'
/// will be thought to be the parent node due to the list being traversed end to front order.
/// </summary>
public IExpressionNode GetParentNode(
IExpressionNode childExpressionNode, CSharpCompilationUnit compilationUnit, ref CSharpParserModel parserModel, bool foundChild = true)
{
for (int i = parserModel.ExpressionList.Count - 1; i > -1; i--)
{
var delimiterExpressionTuple = parserModel.ExpressionList[i];

if (foundChild)
{
if (delimiterExpressionTuple.ExpressionNode is null)
break;

if (!Object.ReferenceEquals(childExpressionNode, delimiterExpressionTuple.ExpressionNode))
return delimiterExpressionTuple.ExpressionNode;
}
else
{
if (Object.ReferenceEquals(childExpressionNode, delimiterExpressionTuple.ExpressionNode))
foundChild = true;
}
}

return EmptyExpressionNode.Empty;
}

/// <summary>
/// The argument 'IExpressionNode functionInvocationNode' is not of type 'FunctionInvocationNode'
/// in order to permit this method to be invoked for 'ConstructorInvocationExpressionNode' as well.
/// </summary>
public IExpressionNode ParseFunctionParametersListingNode(IExpressionNode functionInvocationNode, FunctionParametersListingNode functionParametersListingNode, CSharpCompilationUnit compilationUnit, ref CSharpParserModel parserModel)
{
parserModel.ExpressionList.Add((SyntaxKind.CloseParenthesisToken, functionInvocationNode));
parserModel.ExpressionList.Add((SyntaxKind.CommaToken, functionParametersListingNode));
parserModel.ExpressionList.Add((SyntaxKind.ColonToken, functionParametersListingNode));
return ParseNamedParameterSyntaxAndReturnEmptyExpressionNode(compilationUnit, ref parserModel);
}

public IExpressionNode ParseNamedParameterSyntaxAndReturnEmptyExpressionNode(CSharpCompilationUnit compilationUnit, ref CSharpParserModel parserModel)
{
if (UtilityApi.IsConvertibleToIdentifierToken(parserModel.TokenWalker.Peek(1).SyntaxKind) &&
parserModel.TokenWalker.Peek(2).SyntaxKind == SyntaxKind.ColonToken)
{
// Consume the open parenthesis
_ = parserModel.TokenWalker.Consume();

// Consume the identifierToken
compilationUnit.Binder.CreateVariableSymbol(
UtilityApi.ConvertToIdentifierToken(parserModel.TokenWalker.Consume(), compilationUnit, ref parserModel),
VariableKind.Local,
compilationUnit);

// Consume the ColonToken
_ = parserModel.TokenWalker.Consume();
}

return EmptyExpressionNode.Empty;
}
}
11 changes: 11 additions & 0 deletions Source/Lib/CompilerServices/CSharp/ParserCase/CSharpParserModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,18 @@ public CSharpParserModel(
public TokenWalker TokenWalker { get; }
public Stack<ISyntax> SyntaxStack { get; set; }
public CSharpStatementBuilder StatementBuilder { get; set; } = new();

/// <summary>
/// The C# IParserModel implementation will only "short circuit" if the 'SyntaxKind DelimiterSyntaxKind'
/// is registered as a delimiter.
///
/// This is done in order to speed up the while loop, as the list of short circuits doesn't have to be
/// iterated unless the current token is a possible delimiter.
///
/// Luthetus.CompilerServices.CSharp.ParserCase.Internals.ParseOthers.SyntaxIsEndDelimiter(SyntaxKind syntaxKind) {...}
/// </summary>
public List<(SyntaxKind DelimiterSyntaxKind, IExpressionNode ExpressionNode)> ExpressionList { get; set; }

public IExpressionNode? NoLongerRelevantExpressionNode { get; set; }
public List<SyntaxKind> TryParseExpressionSyntaxKindList { get; } = new();
public IExpressionNode ForceParseExpressionInitialPrimaryExpression { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,6 @@ public static FunctionArgumentsListingNode HandleFunctionArguments(CSharpCompila

if (successTypeClauseNode)
{
// 'TypeClauseNode' or 'VariableDeclarationNode'
var successNameableToken = false;

if (UtilityApi.IsConvertibleToIdentifierToken(parserModel.TokenWalker.Current.SyntaxKind))
Expand All @@ -178,7 +177,11 @@ public static FunctionArgumentsListingNode HandleFunctionArguments(CSharpCompila

if (parserModel.TokenWalker.Current.SyntaxKind == SyntaxKind.EqualsToken)
{
// Optional
_ = parserModel.TokenWalker.Consume();

parserModel.ExpressionList.Add((SyntaxKind.CloseParenthesisToken, null));
parserModel.ExpressionList.Add((SyntaxKind.CommaToken, null));
var expressionNode = ParseOthers.ParseExpression(compilationUnit, ref parserModel);
}

var variableDeclarationNode = new VariableDeclarationNode(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ public static IExpressionNode ParseExpression(CSharpCompilationUnit compilationU
var indexTokenRoot = parserModel.TokenWalker.Index;
var expressionPrimaryPreviousRoot = expressionPrimary;

while (!parserModel.TokenWalker.IsEof)
while (true)
{
#if DEBUG
WriteExpressionList(parserModel.ExpressionList);
Expand Down Expand Up @@ -208,7 +208,19 @@ public static IExpressionNode ParseExpression(CSharpCompilationUnit compilationU
}
}

if (forceExit) // delimiterExpressionTuple.ExpressionNode is null
// The while loop used to be 'while (!parserModel.TokenWalker.IsEof)'
// This caused an issue where 'BubbleUpParseExpression(...)' would not run
// if the end of file was reached.
//
// Given how this parser is written, adding 'SyntaxKind.EndOfFile' to 'parserModel.ExpressionList'
// would follow the pattern of how 'SyntaxKind.StatementDelimiterToken' is written.
//
// But, the 'while (true)' loop makes me extremely uncomfortable.
//
// So I added '|| parserModel.TokenWalker.IsEof' here.
//
// If upon further inspection on way or the other is deemed safe then this redundancy can be removed.
if (forceExit || parserModel.TokenWalker.IsEof) // delimiterExpressionTuple.ExpressionNode is null
{
expressionPrimary = BubbleUpParseExpression(0, expressionPrimary, compilationUnit, ref parserModel);
break;
Expand Down Expand Up @@ -292,6 +304,7 @@ public static IExpressionNode ParseExpression(CSharpCompilationUnit compilationU
// It is vital that this 'clear' and 'add' are done in a way that permits an invoker of the 'ParseExpression' method to 'add' a similar 'forceExit' delimiter
// Example: 'parserModel.ExpressionList.Add((SyntaxKind.CloseParenthesisToken, null));'
parserModel.ExpressionList.Clear();
parserModel.ExpressionList.Add((SyntaxKind.EndOfFileToken, null));
parserModel.ExpressionList.Add((SyntaxKind.StatementDelimiterToken, null));

if (expressionPrimary.SyntaxKind == SyntaxKind.AmbiguousIdentifierExpressionNode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ public static bool IsBinaryOperatorSyntaxKind(SyntaxKind syntaxKind)
case SyntaxKind.MinusToken:
case SyntaxKind.StarToken:
case SyntaxKind.DivisionToken:
case SyntaxKind.EqualsToken:
case SyntaxKind.EqualsEqualsToken:
case SyntaxKind.AmpersandAmpersandToken:
case SyntaxKind.PipePipeToken:
case SyntaxKind.QuestionMarkQuestionMarkToken:
return true;
default:
return false;
Expand Down
2 changes: 1 addition & 1 deletion Source/Lib/Ide/Ide.RazorLib/Luthetus.Ide.RazorLib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>0.9.7.10</Version>
<Version>0.9.7.11</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
Loading

0 comments on commit 0fe144c

Please sign in to comment.