Skip to content

Commit 8ade5ec

Browse files
committed
Improving detection and placement of block return keywords (fixes Bug#7) / Excluding blank string lines from code blocks
1 parent a67ebe3 commit 8ade5ec

File tree

5 files changed

+236
-11
lines changed

5 files changed

+236
-11
lines changed

ReadableExpressions.UnitTests/WhenTranslatingAssignments.cs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace AgileObjects.ReadableExpressions.UnitTests
22
{
33
using System;
4+
using System.Collections.Generic;
45
using System.IO;
56
using System.Linq;
67
using System.Linq.Expressions;
@@ -451,5 +452,145 @@ public void ShouldTranslateANestedBlockAssignment()
451452
}";
452453
Assert.AreEqual(EXPECTED.TrimStart(), translated);
453454
}
455+
456+
// See https://github.com/agileobjects/ReadableExpressions/issues/7
457+
[TestMethod]
458+
public void ShouldTranslateMultiStatementValueBlockAssignments()
459+
{
460+
ParameterExpression existingInts;
461+
var valueConditional = GetReturnStatementBlock(out existingInts);
462+
463+
Expression<Action> consoleRead = () => Console.Read();
464+
465+
var multiStatementValueBlock = Expression.Block(
466+
new[] { existingInts },
467+
consoleRead.Body,
468+
valueConditional);
469+
470+
var resultVariable = Expression.Variable(multiStatementValueBlock.Type, "result");
471+
var resultOneAssignment = Expression.Assign(resultVariable, multiStatementValueBlock);
472+
473+
var translated = resultOneAssignment.ToReadableString();
474+
475+
const string EXPECTED = @"
476+
result =
477+
{
478+
List<int> ints;
479+
Console.Read();
480+
return (ints == null)
481+
? new List<int>()
482+
: {
483+
var enumerator = ints.GetEnumerator();
484+
while (true)
485+
{
486+
if (enumerator.MoveNext())
487+
{
488+
var item = enumerator.Current;
489+
ints.Add(item);
490+
}
491+
else
492+
{
493+
break;
494+
}
495+
}
496+
497+
return ints;
498+
}
499+
}";
500+
Assert.AreEqual(EXPECTED.TrimStart(), translated);
501+
}
502+
503+
// See https://github.com/agileobjects/ReadableExpressions/issues/7
504+
[TestMethod]
505+
public void ShouldTranslateSingleStatementValueBlockAssignments()
506+
{
507+
ParameterExpression existingInts;
508+
var valueConditional = GetReturnStatementBlock(out existingInts);
509+
510+
var singleStatementValueBlock = Expression.Block(
511+
new[] { existingInts },
512+
valueConditional);
513+
514+
var resultVariable = Expression.Variable(singleStatementValueBlock.Type, "result");
515+
var resultOneAssignment = Expression.Assign(resultVariable, singleStatementValueBlock);
516+
517+
var translated = resultOneAssignment.ToReadableString();
518+
519+
const string EXPECTED = @"
520+
result =
521+
{
522+
List<int> ints;
523+
return (ints == null)
524+
? new List<int>()
525+
: {
526+
var enumerator = ints.GetEnumerator();
527+
while (true)
528+
{
529+
if (enumerator.MoveNext())
530+
{
531+
var item = enumerator.Current;
532+
ints.Add(item);
533+
}
534+
else
535+
{
536+
break;
537+
}
538+
}
539+
540+
return ints;
541+
}
542+
}";
543+
Assert.AreEqual(EXPECTED.TrimStart(), translated);
544+
}
545+
546+
private static Expression GetReturnStatementBlock(out ParameterExpression existingInts)
547+
{
548+
existingInts = Expression.Variable(typeof(List<int>), "ints");
549+
550+
var existingIntsEnumerator = Expression.Variable(typeof(List<int>.Enumerator), "enumerator");
551+
var getEnumeratorMethod = existingInts.Type.GetMethod("GetEnumerator");
552+
var getEnumeratorCall = Expression.Call(existingInts, getEnumeratorMethod);
553+
var enumeratorAssignment = Expression.Assign(existingIntsEnumerator, getEnumeratorCall);
554+
555+
var enumeratorMoveNextMethod = existingIntsEnumerator.Type.GetMethod("MoveNext");
556+
var enumeratorMoveNextCall = Expression.Call(existingIntsEnumerator, enumeratorMoveNextMethod);
557+
558+
var enumeratorItem = Expression.Variable(typeof(int), "item");
559+
var enumeratorCurrent = Expression.Property(existingIntsEnumerator, "Current");
560+
var itemAssignment = Expression.Assign(enumeratorItem, enumeratorCurrent);
561+
562+
var intsAddMethod = existingInts.Type.GetMethod("Add");
563+
var intsAddCall = Expression.Call(existingInts, intsAddMethod, enumeratorItem);
564+
565+
var addItemBlock = Expression.Block(
566+
new[] { enumeratorItem },
567+
itemAssignment,
568+
intsAddCall);
569+
570+
var loopBreakTarget = Expression.Label(typeof(void), "LoopBreak");
571+
572+
var conditionallyAddItems = Expression.Condition(
573+
Expression.IsTrue(enumeratorMoveNextCall),
574+
addItemBlock,
575+
Expression.Break(loopBreakTarget));
576+
577+
var addItemsLoop = Expression.Loop(conditionallyAddItems, loopBreakTarget);
578+
579+
var populateExistingInts = Expression.Block(
580+
new[] { existingIntsEnumerator },
581+
enumeratorAssignment,
582+
addItemsLoop);
583+
584+
var conditionFalseBlock = Expression.Block(
585+
populateExistingInts,
586+
existingInts);
587+
588+
var valueConditional = Expression.Condition(
589+
Expression.Equal(existingInts, Expression.Default(existingInts.Type)),
590+
Expression.New(conditionFalseBlock.Type),
591+
conditionFalseBlock);
592+
593+
return valueConditional;
594+
}
454595
}
455596
}

ReadableExpressions.UnitTests/WhenTranslatingBlocks.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,9 +256,9 @@ public void ShouldIgnoreAVariableOnlyBlockStatement()
256256

257257
var translated = countLambda.ToReadableString();
258258

259-
const string EXPECTED = @"() => false";
259+
const string EXPECTED = "() => false";
260260

261-
Assert.AreEqual(EXPECTED.TrimStart(), translated);
261+
Assert.AreEqual(EXPECTED, translated);
262262
}
263263

264264
[TestMethod]
@@ -316,5 +316,26 @@ public void ShouldTranslateASwitchWithMultipleVariableAssignments()
316316
}";
317317
Assert.AreEqual(EXPECTED.TrimStart(), translated);
318318
}
319+
320+
[TestMethod]
321+
public void ShouldIgnoreABlankLabelTargetLine()
322+
{
323+
var intVariable = Expression.Variable(typeof(int), "i");
324+
var intAssignment = Expression.Assign(intVariable, Expression.Constant(0));
325+
326+
var labelTarget = Expression.Label(typeof(void), "LabelTarget");
327+
328+
var intAssignmentBlock = Expression.Block(
329+
new[] { intVariable },
330+
intAssignment,
331+
Expression.Label(labelTarget));
332+
333+
var translated = intAssignmentBlock.ToReadableString();
334+
335+
const string EXPECTED = @"var i = 0;";
336+
337+
338+
Assert.AreEqual(EXPECTED, translated);
339+
}
319340
}
320341
}

ReadableExpressions/StringExtensions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ public static string[] SplitToLines(this string line, StringSplitOptions splitOp
3131

3232
private const string IndentSpaces = " ";
3333

34+
public static bool IsNotIndented(this string line)
35+
{
36+
return (line.Length > 0) && !line.StartsWith(IndentSpaces, StringComparison.Ordinal);
37+
}
38+
3439
public static string Indented(this string line)
3540
{
3641
if (string.IsNullOrEmpty(line))

ReadableExpressions/Translators/BlockExpressionTranslator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ private static string GetTerminatedStatementOrNull(Expression expression, Transl
6666
{
6767
var translation = context.Translate(expression);
6868

69-
if (translation == null)
69+
if (string.IsNullOrEmpty(translation))
7070
{
7171
return null;
7272
}

ReadableExpressions/Translators/Formatting/CodeBlock.cs

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace AgileObjects.ReadableExpressions.Translators.Formatting
22
{
33
using System;
4+
using System.Collections.Generic;
45
using System.Linq;
56
using System.Linq.Expressions;
67
using Extensions;
@@ -62,8 +63,14 @@ public string WithoutCurlyBraces()
6263

6364
public bool HasReturn()
6465
{
65-
return _blockLines.Last().SplitToLines().Last().StartsWith("return ", StringComparison.Ordinal) ||
66-
ExpressionHasReturn(_expression);
66+
if (ExpressionHasReturn(_expression))
67+
{
68+
return true;
69+
}
70+
71+
var blockInfo = new BlockInfo(_blockLines);
72+
73+
return blockInfo.LastLineHasReturnKeyword;
6774
}
6875

6976
private static bool ExpressionHasReturn(Expression expression)
@@ -110,12 +117,14 @@ private CodeBlock WithReturn()
110117
return this;
111118
}
112119

113-
return new CodeBlock(
114-
_expression,
115-
_blockLines
116-
.Take(_blockLines.Length - 1)
117-
.Concat(new[] { "return " + _blockLines.Last() })
118-
.ToArray());
120+
return new CodeBlock(_expression, GetBlockLinesWithInsert());
121+
}
122+
123+
private string[] GetBlockLinesWithInsert()
124+
{
125+
var blockInfo = new BlockInfo(_blockLines);
126+
127+
return blockInfo.GetBlockLinesWithReturnKeyword();
119128
}
120129

121130
private string GetCodeBlock()
@@ -132,5 +141,54 @@ private void AddSemiColonIfRequired()
132141
_blockLines[0] += ";";
133142
}
134143
}
144+
145+
private class BlockInfo
146+
{
147+
private readonly string[] _blockLines;
148+
private readonly int _lastNonIndentedStatementIndex;
149+
private readonly string _lastNonIndentedLine;
150+
private readonly int _lastNonIndentedLineIndex;
151+
152+
public BlockInfo(string[] blockLines)
153+
{
154+
_blockLines = blockLines;
155+
var lastNonIndentedStatement = blockLines.Last(line => line.IsNotIndented());
156+
_lastNonIndentedStatementIndex = Array.IndexOf(blockLines, lastNonIndentedStatement);
157+
158+
var lastStatementLines = lastNonIndentedStatement.SplitToLines();
159+
_lastNonIndentedLine = lastStatementLines.Last(line => line.IsNotIndented());
160+
_lastNonIndentedLineIndex = Array.IndexOf(lastStatementLines, _lastNonIndentedLine);
161+
}
162+
163+
public bool LastLineHasReturnKeyword =>
164+
_lastNonIndentedLine.StartsWith("return ", StringComparison.Ordinal);
165+
166+
public string[] GetBlockLinesWithReturnKeyword()
167+
{
168+
var lastNonIndentedStatement = _blockLines.Last(line => line.IsNotIndented());
169+
170+
var updatedBlockLines = new List<string>();
171+
172+
var preLastStatementStatements = _blockLines
173+
.Take(_blockLines.Length - (_lastNonIndentedStatementIndex + 1));
174+
175+
updatedBlockLines.AddRange(preLastStatementStatements);
176+
177+
var lastStatementLines = lastNonIndentedStatement.SplitToLines();
178+
179+
var preLastLineLines = lastStatementLines
180+
.Take(_lastNonIndentedLineIndex)
181+
.ToArray();
182+
183+
var postLastLineLines = lastStatementLines
184+
.Skip(preLastLineLines.Length + 1);
185+
186+
updatedBlockLines.AddRange(preLastLineLines);
187+
updatedBlockLines.Add("return " + _lastNonIndentedLine);
188+
updatedBlockLines.AddRange(postLastLineLines);
189+
190+
return updatedBlockLines.ToArray();
191+
}
192+
}
135193
}
136194
}

0 commit comments

Comments
 (0)