BlockExpression not the same when inside another BlockExpression

Oct 15, 2010 at 12:23 AM

I’m working on a scripting language that has arrays. I am trying to provide useful runtime errors to users when they do an illegal array access.  I tried using the same approach I used when handling run time errors for binary operators like + - / and *, but it doesn’t seem to work.

I’m trying to create a block that:
1.    Sets a variable in the internal run time environment to what line number is currently being executed
2.    Return an indexed expression

If that  throws an exception due to being out of range the variable from #1 can be used to determine the line number, array and index and a nice error message can be displayed.

The issue I’m having is that I can’t get a child block to work the same when it is inside another block.

Here is what I’m talking about:

            int[] array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };


            IndexExpression indexdExpression = Expression.ArrayAccess(
                Expression.Constant(array),
                Expression.Constant(2)
            );


            //LabelTarget targetForIndex = Expression.Label(typeof(IndexExpression));

            BlockExpression childBlock = Expression.Block(
                //typeof(IndexExpression),
                new List<ParameterExpression>(),
                Expression.Call(typeof(Trace).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("WriteLine was called")),
                indexdExpression //last item in a block is the return value
                //Expression.Return(targetForIndex, indexdExpression),
                //Expression.Label(targetForIndex)
            );
                       


            Expression binaryExp = Expression.MakeBinary(
                ExpressionType.Assign,
                childBlock.Result,
                Expression.Constant(99999999, typeof(int))
            );

            BlockExpression parentBlock = Expression.Block(
                //bunch of Expressions from script will go here
                binaryExp
                //bunch of Expressions from script will go there
            );


            //Trace.WriteLine does NOT get called, variable DOES get set
            Trace.WriteLine("calling parent block");
            Expression.Lambda(parentBlock).Compile().DynamicInvoke();
            Trace.WriteLine("array[2]=" + array[2]);
            Trace.WriteLine("");
            array[2] = 2;

            //Trace.WriteLine DOES get called, variable does NOT get set
            Trace.WriteLine("calling child block");
            Expression.Lambda(childBlock).Compile().DynamicInvoke();
            Trace.WriteLine("array[2]=" + array[2]);

The output is:

calling parent block
array[6]=99999999

calling internal block
WriteLine was called
array[6]=6

 

I would think the output should be:

calling parent block
WriteLine was called
array[6]=99999999

calling internal block
WriteLine was called
array[6]=99999999

 

 

Any input as to why the expected and actual output are different would be great. I'm probably missing something minor. As a side note, the commented out lines in the childBlock are in there as I was trying different things, none of which worked.

Oct 15, 2010 at 3:49 AM

Well considering that you don't compile an assignment operation in the child block it's no wonder the array index doesn't get set. When you put the "binaryExp" in the "parentBlock" and compile it the assignment operation gets executed, but when you just compile childBlock then there is not assignment operation compiled.

Oct 15, 2010 at 2:49 PM

Wow, not sure what I was doing with childBlock. I wrote up the example, and then changed it, and didn't review it thoroughly enough.

Either way, I'm trying to:

1. use a parent BlockExpression that

2. has a BinaryExpression that

3. gets the left hand value from a BlockExpression and

4. the BlockExpression from 3 executes code

 

Is that possible? I've reposted code that hopefully isn't goofy:

int[] array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };


IndexExpression indexdExpression = Expression.ArrayAccess(
	Expression.Constant(array),
	Expression.Constant(2)
);


//LabelTarget targetForIndex = Expression.Label(typeof(IndexExpression));

BlockExpression childBlock = Expression.Block(
	//typeof(IndexExpression),
	new List<ParameterExpression>(),
	Expression.Call(typeof(Trace).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("WriteLine was called")),
	indexdExpression //last item in a block is the return value
	//Expression.Return(targetForIndex, indexdExpression),
	//Expression.Label(targetForIndex)
);
		   


Expression binaryExp = Expression.MakeBinary(
	ExpressionType.Assign,
	childBlock.Result,
	Expression.Constant(99999999, typeof(int))
);

BlockExpression parentBlock = Expression.Block(
	//bunch of Expressions from script will go here
	binaryExp
	//bunch of Expressions from script will go there
);


//Trace.WriteLine does NOT get called, variable DOES get set
Trace.WriteLine("calling parent block");
Expression.Lambda(parentBlock).Compile().DynamicInvoke();
Trace.WriteLine("array[2]=" + array[2]); 

Expected:

calling parent block
WriteLine was called
array[2]=99999999

 

Actual:

calling parent block
array[2]=99999999

Oct 15, 2010 at 2:58 PM

Well, you're not executing the code in the child block are you? It's not inserted anywhere in the expression tree, all you do is pull childBlock.Result, and if you look at the documentation for BlockExpression.Result http://msdn.microsoft.com/en-us/library/system.linq.expressions.blockexpression.result.aspx you will see that .Result returns the last expression in the block. That means the expression tree representing the last expression in the block will be returned, and since you have a constant expression (ArrayAccess with 2 constants in it) this is will work since 'indexedExpression' is inserted in the middle of the binaryExp. The childblock never gets executed.

Also what you're trying to do "return a reference from a block to be used in a parent block for assignment" is not possible.

Oct 15, 2010 at 3:23 PM

Is there any syntax magic to make it possible to make the return value from a block assignable/writeable? I know you can use a return expression from a block, as show here:

            int[] array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };


            IndexExpression indexdExpressionLeft = Expression.ArrayAccess(
                Expression.Constant(array),
                Expression.Constant(2)
            );

            IndexExpression indexdExpressionRight = Expression.ArrayAccess(
                Expression.Constant(array),
                Expression.Constant(10)
            );

            BlockExpression childBlock = Expression.Block(
                Expression.Call(typeof(Trace).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("WriteLine was called in childBlock")),
                indexdExpressionRight
            );

            BlockExpression parentBlock = Expression.Block(
                Expression.Assign(
                    indexdExpressionLeft,
                    childBlock
                )
            );


            Expression.Lambda(parentBlock).Compile().DynamicInvoke();
            Trace.WriteLine("array[2]=" + array[2]);

The output here is:

WriteLine was called in childBlock

array[2]=10

 

This is what I thought it would be.

Oct 15, 2010 at 6:34 PM
Edited Oct 15, 2010 at 6:35 PM

The problem is that you're "copying" the result expression of the block to the place where you use .Result. And no there is no way to make the result of a block expression writable as far as I know. What you want to do is something like this: 

      var array = new int[1];
      var arrayConst = System.Linq.Expressions.Expression.Constant(array);

      var childBlock =
        System.Linq.Expressions.Expression.Block(
          System.Linq.Expressions.Expression.ArrayIndex(
            arrayConst, System.Linq.Expressions.Expression.Constant(0)
          )
        );

      var lambdaBlock =
        System.Linq.Expressions.Expression.Block(
          System.Linq.Expressions.Expression.Assign(
            childBlock, System.Linq.Expressions.Expression.Constant(99)
          )
        );

But if you try to run this you will get an exception saying "Expression must be writable, Parameter name: left", ergo this is not possible to do. To achieve this you need to share a variable between the blocks and set/get indexes on that.

Oct 15, 2010 at 7:59 PM

thanks for the help. i figured out how to get done what i wanted. i was just a single line away all this time. the only change that was made was in parentBlock. The first line executes childBlock, and then the Result is there and assignable. I didn't rename stuff based on left/right, but easy enough to read. The output is what I wanted to see too.

I'm working this into my language and seems to be working out well.

            int[] array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };


            IndexExpression indexdExpressionLeft = Expression.ArrayAccess(
                Expression.Constant(array),
                Expression.Constant(2)
            );

            IndexExpression indexdExpressionRight = Expression.ArrayAccess(
                Expression.Constant(array),
                Expression.Constant(10)
            );

            ParameterExpression pe = Expression.Variable(typeof(IndexExpression));

            BlockExpression childBlock = Expression.Block(
                Expression.Call(typeof(Trace).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("WriteLine was called in childBlock")),
                indexdExpressionRight
            );

            BlockExpression parentBlock = Expression.Block(
                childBlock,
                Expression.Assign(
                    childBlock.Result,
                    indexdExpressionLeft                    
                )
            );

            Expression.Lambda(parentBlock).Compile().DynamicInvoke();
            Trace.WriteLine("array[2]=" + array[2]);
            Trace.WriteLine("array[10]=" + array[10]);

 

thanks for the help.

Coordinator
Oct 15, 2010 at 10:30 PM

We don't have a generalized l-value model, but yes, this works since the last expr in the block is on our white list of accepted l-value expr types.

Sorry we didn't chime in earlier, but great to see you guys worked it out :-).

Bill

From: codemoniker [mailto:notifications@codeplex.com]
Sent: Friday, October 15, 2010 1:00 PM
To: Bill Chiles
Subject: Re: BlockExpression not the same when inside another BlockExpression [dlr:230979]

From: codemoniker

thanks for the help. i figured out how to get done what i wanted. i was just a single line away all this time. the only change that was made was in parentBlock. The first line executes childBlock, and then the Result is there and assignable. I didn't rename stuff based on left/right, but easy enough to read. The output is what I wanted to see too.

I'm working this into my language and seems to be working out well.

            int[] array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 
 
            IndexExpression indexdExpressionLeft = Expression.ArrayAccess(
                Expression.Constant(array),
                Expression.Constant(2)
            );
 
            IndexExpression indexdExpressionRight = Expression.ArrayAccess(
                Expression.Constant(array),
                Expression.Constant(10)
            );
 
            ParameterExpression pe = Expression.Variable(typeof(IndexExpression));
 
            BlockExpression childBlock = Expression.Block(
                Expression.Call(typeof(Trace).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("WriteLine was called in childBlock")),
                indexdExpressionRight
            );
 
            BlockExpression parentBlock = Expression.Block(
                childBlock,
                Expression.Assign(
                    childBlock.Result,
                    indexdExpressionLeft                    
                )
            );
 
            Expression.Lambda(parentBlock).Compile().DynamicInvoke();
            Trace.WriteLine("array[2]=" + array[2]);
            Trace.WriteLine("array[10]=" + array[10]);

thanks for the help.

Read the full discussion online.

To add a post to this discussion, reply to this email (dlr@discussions.codeplex.com)

To start a new discussion for this project, email dlr@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com

Oct 16, 2010 at 6:12 AM

Just two quick notes:

  • This only works if the last expression in the block is a writable expression
  • The expression gets evaluated twice, so for example if you have an array expression with a function call that produces the index. The function will be called twice.