Jump to:  OMake Home • Guide Home • Guide (single-page) • Contents (short) • Contents (long)
Index:  All • Variables • Functions • Objects • Targets • Options

Chapter 6  Variables and Naming

During evaluation, there are three different kinds of namespaces. Variables can be private, or they may refer to fields in the current this object, or they can be part of the global global namespace. In addition, in version 0.9.9 onward, each file has itsown public namespace (see Section 6.10). A variable's namespace can be specified directly by including an explicit qualifier before the variable name. The three namespaces are separate; a variable can be bound in one or more simultaneously.

    # private, local to this file
    osh>private.X = 1
    - : "1" : Sequence
    # A field of the current object
    osh>this.X = 2
    - : "2" : Sequence
    # Global, dynamically scoped
    osh>public.X = 3
    - : "3" : Sequence

    osh>value $(this.X)
    - : "2" : Sequence

6.1  private.

The private. qualifier is used to define variables that are private to the current file/scope. The values are not accessible outside the scope. Private variables are statically (lexically) scoped.

    Obj. =
       private.X = 1

       print() =
          println(The value of X is: $X)

    # Prints:
    #    The private value of X is: 1
    Obj.print()

    # This is an error--X is private in Obj
    y = $(Obj.X)

In addition, private definitions do not affect the global value of a variable.

   # The public value of x is 1
   x = 1

   # This object uses a private value of x
   Obj. =
       private.x = 2

       print() =
          x = 3
          println(The private value of x is: $x)
          println(The public value of x is: $(public.x))
          f()

   # Prints:
   #    The private value of x is: 3
   #    The public value of x is: 1
   Obj.print()

Private variables have two additional properties.

  1. Private variables are local to the file in which they are defined.
  2. Private variables are not exported by the export directive, unless they are mentioned explicitly by the export directive.
           private. =
              FLAG = true
    
           section
              FLAG = false
              export
    
           # FLAG is still true
           section
              FLAG = false
              export FLAG
    
           # FLAG is now false
      

6.2  this.

The this. qualifier is used to define fields that are local to an object. Object variables are dynamically scoped.

    X = 1
    f() =
       println(The public value of X is: $(X))

    # Prints:
    #    The public value of X is: 2
    section
       X = 2
       f()

    # X is a protected field in the object
    Obj. =
       this.X = 3

       print() =
          println(The value of this.X is: $(X))
          f()

    # Prints:
    #    The value of this.X is: 3
    #    The public value of X is: 1
    Obj.print()

    # This is legal, it defines Y as 3
    Y = $(Obj.X)

6.3  global.

The global. qualifier is used to specify global dynamically-scoped variables. In the following example, the global. definition specifies that the binding X = 4 is to be dynamically scoped. Global variables are not defined as fields of an object.

    X = 1
    f() =
       println(The global value of X is: $(X))

    # Prints:
    #    The global value of X is: 2
    section
       X = 2
       f()

    Obj. =
       this.X = 3

       print() =
          println(The "this" value of X is: $(X))
          global.X = 4
          f()

    # Prints:
    #    The protected value of X is: 3
    #    The global value of X is: 4
    Obj.print()

6.4  protected.

This feature will be introduced in version 0.9.9.0.

The qualifier protected means that a variable is local to the current object or file, and may not be accessed outside it.

6.5  public.

This feature will be introduced in version 0.9.8.5.

The qualifier public means that a variable is publically accessible, and may be accessed outside the current file or object.

6.6  const

This feature will be introduced in version 0.9.9.0.

The qualifier const is used for variables that are to be defined exactly once. Any additional definitions are an error.

    osh>const.one = 1
    - : "1" : Sequence
    osh>one = 2
    *** omake error:
       File -: line 2, characters 0-7
       Modifying a const variable: protected.const.[interactive shell input]::one
          The variable was defined at the following location
          File -: line 1, characters 0-13

6.7  auto.

This feature will be introduced in version 0.9.9.0.

The qualifier auto is used for variables that are to be auto-exported from all blocks in scope. The export does not have to be explicit.

    osh>auto.i = 0
    osh>foreach(j => ..., 1 2 3 4 5)
            auto.i = $(add $i, $j)
    osh>value $i
    - : 30 : Int

It is important to keep in mind that the auto-export status of a variable does not escape its scope. For example, we might think of writing a “reference-cell” kind of object.

    osh>Ref. =
            auto.contents =
            this.new(x) =
                auto.contents = $x
                value $(this)
            this.set(x) =
                auto.contents = $x
            this.get() =
                value $(contents)
    osh>cell = $(Ref.new 1)
    osh>cell.get()
    *** omake error:
       File /Users/jyh/projects/omake/git/auto/x.om: line 9, characters 14-25
       unbound variable: auto.[x.om]::contents

The reason for the error is that the variable auto.contents is exported only within its scope. The definition cell = $(Ref.new 1) is not in its scope, so the value is not exported.

6.8  Qualified blocks

If several qualified variables are defined simultaneously, a block form of qualifier can be defined. The syntax is similar to an object definition, where the name of the object is the qualifier itself. For example, the following program defines two private variables X and Y.

    private. =
        X = 1
        Y = 2

The qualifier specifies a default namespace for new definitions in the block. The contents of the block is otherwise general.

    private. =
        X = 1
        Y = 2
        public.Z = $(add $X, $Y)
        # Prints "The value of Z is 3"
        echo The value of Z is $Z

Stylistically, it is usually better to avoid large qualified blocks because the qualifier status can be easy to forget. For example, consider the following fragment.

    private. =
        # Large code sequence
        ...
        # build foo.o with -g option (ERROR)
        CFLAGS = -g
        foo.o:

In this case, the programmer probably forgot that the definition of the variable CFLAGS is in the private block, so a fresh variable private.CFLAGS is being defined, not the global one. The target foo.o does not use this definition of CFLAGS.

6.9  declare

When a variable name is unqualified, its namespace is determined by the most recent definition or declaration that is in scope for that variable. We have already seen this in the examples, where a variable definition is qualified, but the subsequent uses are not qualified explicitly. In the following example, the first occurrence of $X refers to the private definition, because that is the most recent. The public definition of X is still 0, but the variable must be qualified explicitly in order to access the public value.

    public.X = 0
    private.X = 1

    public.print() =
        println(The value of private.X is: $X)
        println(The value of public.X is: $(public.X))

Sometimes it can be useful to declare a variable without defining it. For example, we might have a function that uses a variable X that is to be defined later in the program. The declare directive can be used for this.

    declare public.X

    public.print() =
        println(The value of X is $X)

    # Prints "The value of X is 2"
    X = 2
    print()

Finally, what about variables that are used but not explicitly qualified? In this case, the following rules are used.

6.10  Files and scoping

This feature will be introduced in version 0.9.9.0.

In OMake version 0.9.8 and before, there is a single global namespace that all public variables belong to. This restriction often prevents programs from being scalable. For example, suppose two developers write their code in a modular fashion, but they happen to use a common variable name public.X. There is no harm if the two modules do not call one another, but if they do the values for X might conflict.

The most significant change in version 0.9.9 is the introduction of more modular namespaces. Instead of a single public namespace for an entire project, each file in a project has its own namespace. There is no single global namespace. The mapping between variable names and their modules is managed through the use of explicit open and import directives.

In practical terms, this usually makes little difference in writing programs. Consider the following program fragment.

    # This is file Boo.om
    open Foo
    open Bar
    ...
    X = 1

The namespace for the variable X is determined by the most recently opened file that defines it, or if none do, then the variable is defined in the current file. That is, if the file Bar defines X, then X = 1 is actually Bar::X = 1; otherwise if Foo defines X, then the definition is Foo::X = 1; otherwise it is Boo::X = 1.

The syntax <File>::<id> provides an explicit way to specify the namespace. For example, the following fragment defines Foo::X even if the file Bar also defines X.

    open Foo
    open Bar
    Foo::X = 1

If a file has multiple components to its path, the module name is dtermined by the final component of the path. For example, a directive open build/C defines a module C.

The open directive also allows the module name to be specified explicitly, on a subsequent line with an as directive. This is also useful if the module name is not statically defined.

    open a/Foo
    as AFoo
    open b/Foo
    as BFoo

    private.myfile = ...
    open $(myfile)
    as CFoo

    AFoo::X = 1

6.10.1  import

The import directive is similar to open, but it does not bind any subsequent names. Any reference to a variable in a imported file must be fully qualified.

    import Foo
    Foo::X = 1
    # Unqualified variables are local to the current file
    X = 1

6.10.2  include

The include directive is similar to textual inclusion.

    osh>cat x.om
    public.Y = $(add $X, 1)
    osh>X = 1
    osh>include x
    osh>value $Y
    - : 2 : Int

6.10.3  Guidelines and best practices: explicit naming

As we mentioned previously, the namespace for an unqualified variable is determined by the open directives in scope. However, for variables that are intended to be part of the current file's namespace, it is better to qualify the first occurrence explicitly, using either a declare directive, or by qualifying the definition with public or global.

    open Foo
    open Bar
    ...
    public.X = 1

The fully-qualified name should be used even if it is known that the files Foo and Bar do not define the variable X. If the files are subsequently modified so that one of them does define X, the fully-qualified definition will be unchanged. In contrast, an unqualified definition would switch from being defined in the current namespace, to a variable defined by the opened file, which may have unpredictable consequences.

The -Wdeclare option can be used to help enforce this style restriction. When the -Wdeclare option is used, a warning is issued whenever the first definition of a variable is unqualified and the variable is not bound by one of the opened files.

Jump to:  OMake Home • Guide Home • Guide (single-page) • Contents (short) • Contents (long)
Index:  All • Variables • Functions • Objects • Targets • Options