With static (lexical) coverage, the structure of the program source code determines which variables you refer to. With dynamic reach , the execution state of the program stack determines which variable you are accessing. This is probably a very unfamiliar concept, since every programming language used today (except, perhaps, emacs lisp) uses a lexical domain, which is usually much easier for people and analysis tools to reason about.
Consider this simpler sample program (written in pseudo-code syntax):
program a() { x: integer; // "x1" in discussions below x = 1; procedure b() { x = 2; // <-- which "x" do we write to? } procedure c() { x: integer; // "x2" in discussions below b(); } c(); print x; }
The program and the compiler refer to both variables as x , but I have designated them x1 and x2 to simplify the discussion below.
With lexical coverage, we determine at compile time which x we mean based on the static lexical structure of the program source code. The innermost definition of x in the region where , defining b , x1 , and therefore the record in question is resolved to x1 and that where x = 2 written, so we print 2 when we run this program.
With dynamic scope, we have a stack of variable definitions tracked at runtime, so the x we write depends on what exactly is in scope and dynamically defined at runtime . Starting from a, a pushes x => x1 onto the stack, calling c pushes x => x2 onto the stack, and then when we go to b , the top of the stack is x => x2 , and so we write to x2 . This leaves x1 untouched, and so we print 1 at the end of the program.
Also, consider this slightly different program:
program a() { x: integer; // "x1" in discussions below x = 1; procedure b() { x = 2; // <-- which "x" do we write to? } procedure c() { x: integer; // "x2" in discussions below b(); } c(); b(); }
Note b is called twice - the first time through c , the second time directly. When using lexical coverage, the explanation above does not change, and we write x1 both times. However, with dynamic spanning, it depends on how x bound at runtime. The first time we call b , we write in x2 , as explained above, but the second time we write in x1 , since this is what is on top of the stack! ( x => x2 appears when c returned.)
So, here is your professor code annotated by using the exact variable on which the lexical domain is written. Entries ending at the end of the program are marked with * :
program A() { x, y, z: integer; // x1, y1, z1 procedure B() { y: integer; // y2 y=0; // y2 = 0 x=z+1; // x1 = z1 + 1 = 12 + 1 = 13* z=y+2; // z1 = y2 + 2 = 0 + 2 = 2* } procedure C() { z: integer; // z2 procedure D() { x: integer; // x2 x = z + 1; // x2 = z2 + 1 = 5 + 1 = 6 y = x + 1; // y1 = x2 + 1 = 6 + 1 = 7* call B(); } z = 5; // z2 = 5 call D(); } x = 10; // x1 = 10 y = 11; // y1 = 11 z = 12; // z1 = 12 call C(); print x, y, z; // x1, y1, z1 }
And here it is with dynamic coverage. Note that the changes are only in b and in the tag location * :
program A() { x, y, z: integer; // x1, y1, z1 procedure B() { y: integer; // y2 y=0; // y2 = 0 x=z+1; // x2 = z2 + 1 = 5 + 1 = 6 z=y+2; // z2 = y2 + 2 = 0 + 2 = 2 } procedure C() { z: integer; // z2 procedure D() { x: integer; // x2 x = z + 1; // x2 = z2 + 1 = 5 + 1 = 6 y = x + 1; // y1 = x2 + 1 = 6 + 1 = 7* call B(); } z = 5; // z2 = 5 call D(); } x = 10; // x1 = 10* y = 11; // y1 = 11 z = 12; // z1 = 12* call C(); print x, y, z; }