0

I have a LINQ expression that works but I wanted to make it simpler and cleaner.

var tryCatchTerminator = true;

return tryCatchTerminator
            ? from varKeyword in MatchToken(SyntaxKind.VarKeyword)
            from declarations in ParseVarDeclarationClause.AtLeastOnceDelimitedBy(MatchToken(SyntaxKind.Comma))
            from terminator in MatchToken(SyntaxKind.SemiColon).OptionalOrDefault()
            select (StatementSyntax) new VarDeclarationStatement(varKeyword, declarations, terminator)
            : from varKeyword in MatchToken(SyntaxKind.VarKeyword)
            from declarations in ParseVarDeclarationClause.AtLeastOnceDelimitedBy(MatchToken(SyntaxKind.Comma))
            select (StatementSyntax) new VarDeclarationStatement(varKeyword, declarations, Token<SyntaxKind>.Empty);

I looked all over the internet for some way to include an if statement inside the LINQ expression where I could stop if some condition is met and return an object... or continue to execute another query if the condition is not met.

Maybe this is obvious but I'm really clueless.

see sharper
  • 11,505
  • 8
  • 46
  • 65
xDGameStudios
  • 321
  • 1
  • 13
  • do you mind sharing a bit more code? I'm mostly after your `MatchToken()` code or at least a method signature? – timur Dec 08 '19 at 22:37
  • 1
    Use a temporary variable? Just declare a temporary, then use an `if` statement to determine how it should be set, and return the temporary. Remember, the compiler and JITter are not required to create that temporary variable in the executable code. In all likelihood it will be removed in RELEASE builds. If a temporary makes your code more readable, go for it – Flydog57 Dec 08 '19 at 22:37
  • @timur it's from the superpower library it's actually a wrapper around the Token.Equals(...) that also accepts params to match the first of a series. – xDGameStudios Dec 08 '19 at 22:46
  • @xDGameStudiosI was mostly wondering what it's return type was, but it seems as long as they are `IEnumerable` my suggestion should work (I was testing my snippet on `List`). – timur Dec 08 '19 at 23:01

4 Answers4

2

It seems to me that this should work for you:

return
    from varKeyword in MatchToken(SyntaxKind.VarKeyword)
    from declarations in ParseVarDeclarationClause.AtLeastOnceDelimitedBy(MatchToken(SyntaxKind.Comma))
    from terminator in tryCatchTerminator ? MatchToken(SyntaxKind.SemiColon).OptionalOrDefault() : new[] { Token<SyntaxKind>.Empty } 
    select (StatementSyntax)new VarDeclarationStatement(varKeyword, declarations, terminator);

The key to it working is just giving the from terminator expression a single element array to return the empty token if tryCatchTerminator is false.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • Thank you so much for this observation! I didn't knew I could use a check after the "in" keyword (this was a game changer for me). – xDGameStudios Dec 09 '19 at 11:47
  • @xDGameStudios - I'm glad to hear it. Did this solve your problem? – Enigmativity Dec 09 '19 at 12:29
  • not completely I had to change the `new[] { Token.Empty }` and create a PassThrough method... as I said in the tags I'm using the parsing library Superpower... so the returning type is a `TokenListParser` not a plain `IEnumerable`. I posted my solution to the problem.. but this was great help :) – xDGameStudios Dec 09 '19 at 13:20
  • @xDGameStudios - It would then be ideal to accept either my answer or yours. – Enigmativity Dec 09 '19 at 23:06
  • I cannot accept my own question I need to wait 20 hours more.... so I'm waiting! – xDGameStudios Dec 10 '19 at 01:01
  • @xDGameStudios - Do keep in mind that your answer is solves your real-world issue, but it's not a direct answer to your question. You should either include the relevant details in your question (so that you can then mark your answer as correct) or just mark mine as correct. Do you understand the point I'm making? – Enigmativity Dec 10 '19 at 01:28
0

I ended up creating a passthrough parser.. that doesn't consume tokens and returns an empty token.

    private static TokenListParser<SyntaxKind, StatementSyntax> ParseExpressionStatement(
            bool lookForTerminator)
    {
        return from expression in ParsePrefixExpression.Or(ParseCallExpression())
                from terminator in lookForTerminator
                    ? MatchToken(SyntaxKind.SemiColon).OptionalOrDefault()
                    : PassThrough<SynaxKind>()
                select (StatementSyntax) new ExpressionStatementSyntax(expression, terminator);
    }

    private static TokenListParser<T, Token<T>> PassThrough<T>(Token<T> empty)
    {
        return input =>
        {
            var output = input.ConsumeToken();
            return TokenListParserResult.Value(Token<T>.Empty, output.Location, output.Location);
        };
    }
xDGameStudios
  • 321
  • 1
  • 13
-1

It's hard to tell if this will work based on your code sample, but I don't see why you couldn't check for the condition inside the LINQ query:

return from varKeyword in MatchToken(SyntaxKind.VarKeyword)
          from declarations in ParseVarDeclarationClause.AtLeastOnceDelimitedBy(MatchToken(SyntaxKind.Comma))
          from terminator in MatchToken(SyntaxKind.SemiColon).DefaultIfEmpty()
          select (StatementSyntax)new VarDeclarationStatement(varKeyword, declarations, tryCatchTerminator ? terminator : Token<SyntaxKind>.Empty); // check here and pass correct value to VarDeclarationStatement
timur
  • 14,239
  • 2
  • 11
  • 32
-1

If I understand your question properly, then no, there's no (built-in) way to "stop" a query once it is started. If you wanted to add what amounts to a cancellation predicate during enumeration, which signals whether the enumeration should continue, the easiest way to do this would be by creating a custom iterator. Such an implementation might look like this:

public sealed class BreakingEnumerable<T> : IEnumerable<T>
{
    private readonly IEnumerable<T> _query;
    private readonly Predicate<T> _continuePredicate;

    public BreakingEnumerable(IEnumerable<T> query, Predicate<T> predicate)
    {
        _query = query ?? throw new ArgumentNullException(nameof(query));
        _continuePredicate = predicate ?? throw new ArgumentNullException(nameof(predicate));
    }

    public IEnumerator<T> GetEnumerator()
    {
        foreach (var item in _query)
        {
            if (_continuePredicate(item))
            {
                yield return item;
            }
            else
            {
                yield break;
            }
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Of course, you'd want to make this part of your query, so you'd probably want an extension methods class to convert a query to this custom enumerable:

public static class BreakingEnumerableExtensions {
    public static BreakingEnumerable<T> WithTerminationClause<T>(
        this IEnumerable<T> query,
        Predicate<T> breakCondition)
    {
        return new BreakingEnumerable<T>(query, breakCondition);
    }
}

And here's the actual usage:

static void Main(string[] args)
{
    var enumerable = Enumerable.Range(1, 100);

    var array = enumerable.WithTerminationClause(i => i > 100).ToArray();
    Console.WriteLine($"Enumerable with termination clause array length: {array.Length}");

    array = enumerable.Where(i => i < 20).WithTerminationClause(i => i % 2 == 0)
        .ToArray();

    Console.WriteLine($"Enumerable with termination clause length: {array.Length}");
}

Which produces the result:

Enumerable with termination clause array length: 0
Enumerable with termination clause length: 9

This can be chained to produce some minor optimizations:

// Outputs: `Query results: [100, 200, 300]`
var enumerable = Enumerable.Range(1, 100);

var sub = enumerable.WithTerminationClause(i => i <= 3)
    .Select(i => i * 100);
Console.WriteLine("Query results: [{0}]", string.Join(", ", sub));

The only "hitch" is you would never want to use this unless you could guarantee some form of ordering: all numbers appear in an ordered sequence, for example. If you didn't enforce this guarantee, then you could possibly produce incorrect results from your program.

Hope this helps!

PSGuy
  • 653
  • 6
  • 17