Chapter 11. Scoping and subdirectories

Table of Contents

Lexical and dynamic scoping
Creating new scopes, and holey scopes
Named scopes and accessing them
Specifying library lists that work everywhere

Lexical and dynamic scoping

Some programming languages are lexically scoped. Others are dynamically scoped. The programming language of stirmake, Amyplan, supports both scoping types. An example of lexical scoping can be made as follows. Let us assume there are two nested directories, scoping and scoping/subdir. Both of them contain a Stirfile, scoping/Stirfile:

@toplevel
@strict

@phonyrule: 'all':

$VAR = 1

@function $FN()
  @dump("---")
  @dump(@L$VAR)
  @dump(@D$VAR)
@endfunction

@call $FN()
@dirinclude "subdir"

...and scoping/subdir/Stirfile:

@subfile
@strict

@phonyrule: 'all':

$VAR = 2

@call $FN()

In this case, the Stirfile system execution with smka prints:

stirmake: Using directory /home/YOURUSERNAME/scoping
"---"
1
1
"---"
1
2
stirmake: Nothing to be done.

So lexical scoping accesses the variable where the function was defined. Dynamic scoping accesses the variable where the function was called.

Six other scoping specifiers are:

  • @LP: lexical parent scope

  • @LO: lexical scope without recursion to parent

  • @LPO: lexical parent scope without recursion to grandparent

  • @DP: dynamic parent scope

  • @DO: dynamic scope without recursion to parent

  • @DPO: dynamic parent scope without recursion to grandparent

It is also possible to use immediate evaluation:

  • @I: immediate scope

  • @IO: immediate scope without recursion to parent

  • @IP: immediate parent scope

  • @IPO: immediate parent scope without recursion to grandparent

Immediate evaluation accesses the variable immediately, with no future changes applied. Example:

@toplevel
@strict

@phonyrule: 'all':

$VAR = 1

@function $FN()
  @dump(@L$VAR)
  @dump(@I$VAR)
@endfunction

$VAR = 2

@call $FN()

This prints:

2
1
stirmake: Nothing to be done.

Note how the immediate access captures the variable immediately at parsing time, with no future changes applied. In contrast, lexical access uses the scope of function definition (lexical scoping), but latest variable within that scope.

Creating new scopes, and holey scopes

It is possible to create scopes with @beginscope and @endscope. For example:

@toplevel
@strict
@phonyrule: 'all': 'thisscope' 'subscope'

$VAR = ["a"]

@beginscope
$VAR += ["b"]
@phonyrule: 'subscope':
@	["echo", @$VAR]
@endscope

@phonyrule: 'thisscope':
@	["echo", @$VAR]

In this case, there are two rules, 'thisscope' and 'subscope' which are in different scopes. Both rules print their idea of $VAR, which is different due to scoping. This Stirfile prints:

stirmake: Using directory /home/YOURUSERNAME/beginscope
[., subscope] echo a b
a b
[., thisscope] echo a
a

Also, holey scopes were demonstrated already, but re-demonstrating them is relevant in this section again. Holey scope is a scope where implicit recursion to parent scope is not present, but you can always access the lexical parent scope explicitly with @LP. This provides a convenient way of controlling what is present in the sub-scope. Example from Stirfile of stirmake:

@beginholeyscope
  $(CC) = @LP$(CC)
  $(CFLAGS) = @LP$(CFLAGS)
  $(WITH_LUA) = @LP$(WITH_LUA)
  $(LUAINCS) = @LP$(LUAINCS)
  $(LUALIBS) = @LP$(LUALIBS)
  $(FLEX) = @LP$(FLEX)
  $(BYACC) = @LP$(BYACC)
  @projdirinclude "abce"
@endscope

In this case, the defined variables are visible inside the holey scope (and inside the scope of the subproject "abce"), but others are not.

Named scopes and accessing them

Scopes can be named. With @beginscope or @beginholeyscope, it is possible to add a name of the created scope:

@toplevel
@strict
@phonyrule: 'all': 'thisscope' 'subscope'

$VAR = ["a"]

@beginscope $SCOPE
$VAR += ["b"]
@phonyrule: 'subscope':
@	["echo", @$VAR]
@endscope

@phonyrule: 'thisscope':
@	["echo", @$VAR]
@	["echo", @@SC($SCOPE)$VAR]

This prints:

stirmake: Using directory /home/YOURUSERNAME/namedscope
[., subscope] echo a b
a b
[., thisscope] echo a
a
[., thisscope] echo a b
a b

Note how in @@SC the first @ means "include the whole list into the array", and the second @ is part of the specifier @SC of accessing named scopes. Also instead of @SC you can use @SCO to prevent recursion to the parent of the named scope.

With @dirinclude and @projdirinclude, you specify the scope with @scopename:

@toplevel
@strict
@phonyrule: 'all': 'thisscope'

$VAR = ["a"]

@dirinclude @scopename $SCOPE "subdir"

@phonyrule: 'thisscope':
@	["echo", @$VAR]
@	["echo", @@SC($SCOPE)$VAR]

...and the subdir/Stirfile:

@subfile
@strict
@phonyrule: 'all':
$VAR += ["b"]

...which prints:

stirmake: Using directory /home/YOURUSERNAME/scopename
[., thisscope] echo a
a
[., thisscope] echo a b
a b

Note that @dirinclude can be used to include several directories in one line by having an array, but this does not work with @scopename. When @scopename is specified, only one included directory per line is permitted.

Specifying library lists that work everywhere

All stirmake commands are executed in the directory where the rule was created. It is possible to refer to files and directories inside parent directories with .. entries. For example, if you have directory prog/prog1/ and library lib/libcommon.a, inside the prog/prog1/ directory you would refer to this as: '../../lib/libcommon.a'.

However, sometimes changes will be made to the directory structures. An example is where prog/prog1/ is moved to prog1/ directly on the top-level directory. In this case, the '../../lib/libcommon.a' would fail and need to be modified to '../lib/libcommon.a'. Clearly, this kind of approach is not maintainable.

There are four special directory built-ins that are helpful in managing path strings. They are @dirdownall and @dirupall, and @dirdown and @dirup. The first two give paths to/from the root of the project structure, the last two give paths to/from the root of the current project.

For example, inside prog/prog1 you may instead of '../../lib/libcommon.a' use @dirupall . '/lib/libcommon.a'. In this case, @dirupall gives the relative path to the top-level directory of the entire project structure. This way, it is possible for example in the top-level Stirfile to add this variable function with delayed evaluation:

$LIBCOMMON<> = @dirupall . '/lib/libcommon.a'

If this function is evaluated as $LIBCOMMON<> in any subdirectory, it gives the correct path to libcommon.a.

Also it is possible in sub-Stirfile lib/Stirfile to add this:

$DIRDOWN = @dirdownall
$LIBCOMMON<> = @dirupall . '/' . @L$DIRDOWN . '/libcommon.a'

Note the usage of lexical scoping to evaluate $DIRDOWN, so that the @dirdownall from top-level diretory to lib directory is always used, not to the directory where the caller is present.

Then in the top-level Stirfile you would have:

@dirinclude @scopename $LIBSC "lib"
$LIBCOMMON = @SC($LIBSC) $LIBCOMMON
@dirinclude "prog"

Then any Stirfile inside prog can use $LIBCOMMON<> which gives the correct path to libcommon.a.

This way, library linking with relative paths is easy. Also it is possible to use the same way to construct include directory arguments to C compiler (-I argument).