This file has been translated from LaTeX by HeVeA.  Node: Section 3-5, Next: Subsection 3-5-1, Prev: Section 3-4, Up: Chapter 3 3.5 Collapsing the hierarchy, .SUBDIRS bodies *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* Some projects have many subdirectories that all have the same configuration. For instance, suppose you have a project with many subdirectories, each containing a set of images that are to be composed into a web page. Apart from the specific images, the configuration of each file is the same. To make this more concrete, suppose the project has four subdirectories `page1', `page2', `page3', and `page4'. Each contains two files `image1.jpg' and `image2.jpg' that are part of a web page generated by a program `genhtml'. Instead of of defining a `OMakefile' in each directory, we can define it as a body to the `.SUBDIRS' command. << .SUBDIRS: page1 page2 page3 page4 index.html: image1.jpg image2jpg genhtml $+ > $@ >> The body of the `.SUBDIRS' is interpreted exactly as if it were the `OMakefile', and it can contain any of the normal statements. The body is evaluated in the subdirectory for each of the subdirectories. We can see this if we add a statement that prints the current directory (`$(CWD)'). << .SUBDIRS: page1 page2 page3 page4 println($(absname $(CWD))) index.html: image1.jpg image2jpg genhtml $+ > $@ # prints /home/jyh/.../page1 /home/jyh/.../page2 /home/jyh/.../page3 /home/jyh/.../page4 >> * Menu: * Subsection 3-5-1:: Using glob patterns * Subsection 3-5-2:: Simplified sub-configurations * Subsection 3-5-3:: Computing the subdirectory list * Subsection 3-5-4:: Temporary directories  Node: Subsection 3-5-1, Next: Subsection 3-5-2, Prev: Section 3-5, Up: Section 3-5 3.5.1 Using glob patterns =========================== Of course, this specification is quite rigid. In practice, it is likely that each subdirectory will have a different set of images, and all should be included in the web page. One of the easier solutions is to use one of the directory-listing functions, like 'glob' or 'ls'. The `glob' function takes a shell pattern, and returns an array of file with matching filenames in the current directory. << .SUBDIRS: page1 page2 page3 page4 IMAGES = $(glob *.jpg) index.html: $(IMAGES) genhtml $+ > $@ >>  Node: Subsection 3-5-2, Next: Subsection 3-5-3, Prev: Subsection 3-5-1, Up: Section 3-5 3.5.2 Simplified sub-configurations ===================================== Another option is to add a configuration file in each of the subdirectories that defines directory-specific information. For this example, we might define a file `BuildInfo.om' in each of the subdirectories that defines a list of images in that directory. The `.SUBDIRS' line is similar, but we include the BuildInfo file. << .SUBDIRS: page1 page2 page3 page4 include BuildInfo # Defines the IMAGES variable index.html: $(IMAGES) genhtml $+ > $@ >> Where we might have the following configurations. << page1/BuildInfo.om: IMAGES[] = image.jpg page2/BuildInfo.om: IMAGES[] = ../common/header.jpg winlogo.jpg page3/BuildInfo.om: IMAGES[] = ../common/header.jpg unixlogo.jpg daemon.jpg page4/BuildInfo.om: IMAGES[] = fee.jpg fi.jpg foo.jpg fum.jpg >>  Node: Subsection 3-5-3, Next: Subsection 3-5-4, Prev: Subsection 3-5-2, Up: Section 3-5 3.5.3 Computing the subdirectory list ======================================= The other hardcoded specification is the list of subdirectories `page1', ..., `page4'. Rather than editing the project `OMakefile' each time a directory is added, we could compute it (again with `glob'). << .SUBDIRS: $(glob page*) index.html: $(glob *.jpg) genhtml $+ > $@ >> Alternately, the directory structure may be hierarchical. Instead of using `glob', we could use the `subdirs' function, returns each of the directories in a hierarchy. For example, this is the result of evaluating the `subdirs' function in the omake project root. The `P' option, passed as the first argument, specifies that the listing is "proper," it should not include the `omake' directory itself. << osh> subdirs(P, .) - : >> Using `subdirs', our example is now as follows. << .SUBDIRS: $(subdirs P, .) index.html: $(glob *.jpg) genhtml $+ > $@ >> In this case, every subdirectory will be included in the project. If we are using the `BuildInfo.om' option. Instead of including every subdirectory, we could include only those that contain a `BuildInfo.om' file. For this purpose, we can use the `find' function, which traverses the directory hierarchy looking for files that match a test expression. In our case, we want to search for files with the name `BuildInfo.om'. Here is an example call. << osh> FILES = $(find . -name BuildInfo.om) - : osh> DIRS = $(dirof $(FILES)) - : >> In this example, there are three `BuildInfo.om' files, in the `doc/html', `src', and `tests/simple' directories. The `dirof' function returns the directories for each of the files. Returning to our original example, we modify it as follows. << .SUBDIRS: $(dirof $(find . -name BuildInfo.om)) include BuildInfo # Defines the IMAGES variable index.html: $(IMAGES) genhtml $+ > $@ >>  Node: Subsection 3-5-4, Next: Chapter 4, Prev: Subsection 3-5-3, Up: Section 3-5 3.5.4 Temporary directories ============================= Sometimes, your project may include temporary directories--directories where you place intermediate results. these directories are deleted whenever the project is cleanup up. This means, in particular, that you can't place an `OMakefile' in a temporary directory, because it will be removed when the directory is removed. Instead, if you need to define a configuration for any of these directories, you will need to define it using a `.SUBDIRS' body. << section CREATE_SUBDIRS = true .SUBDIRS: tmp # Compute an MD5 digest %.digest: %.comments echo $(digest $<) > $@ # Extract comments from the source files %.comments: ../src/%.src grep '^#' $< > $@ .DEFAULT: foo.digest .PHONY: clean clean: rm -rf tmp >> In this example, we define the `CREATE_SUBDIRS' variable as true, so that the `tmp' directory will be created if it does not exist. The `.SUBDIRS' body in this example is a bit contrived, but it illustrates the kind of specification you might expect. The `clean' phony-target indicates that the `tmp' directory should be removed when the project is cleaned up.  Node: Chapter 4, Next: Section 4-1, Prev: Section 3-5, Up: Top Chapter 4 OMake concepts and syntax *************************************** Projects are specified to omake with OMakefiles. The OMakefile has a format similar to a Makefile. An OMakefile has three main kinds of syntactic objects: variable definitions, function definitions, and rule definitions. * Menu: * Section 4-1:: Variables * Section 4-2:: Adding to a variable definition * Section 4-3:: Arrays * Section 4-4:: Special characters and quoting * Section 4-5:: Function definitions * Section 4-6:: Comments * Section 4-7:: File inclusion * Section 4-8:: Scoping, sections * Section 4-9:: Conditionals * Section 4-10:: Matching * Section 4-11:: Objects * Section 4-12:: Classes * Section 4-13:: Inheritance * Section 4-14:: Special objects/sections * Section 4-15:: private. * Section 4-16:: protected. * Section 4-17:: public. * Section 4-18:: static. * Section 4-19:: Short syntax for scoping objects * Section 4-20:: Modular programming  Node: Section 4-1, Next: Section 4-2, Prev: Chapter 4, Up: Chapter 4 4.1 Variables *=*=*=*=*=*=*=* Variables are defined with the following syntax. The name is any sequence of alphanumeric characters, underscore `_', and hyphen `-'. << = >> Values are defined as a sequence of literal characters and variable expansions. A variable expansion has the form `$()', which represents the value of the `' variable in the current environment. Some examples are shown below. << CC = gcc CFLAGS = -Wall -g COMMAND = $(CC) $(CFLAGS) -O2 >> In this example, the value of the `COMMAND' variable is the string `gcc -Wall -g -O2'. Unlike make(1), variable expansion is eager and functional (see also the section on Scoping). That is, variable values are expanded immediately and new variable definitions do not affect old ones. For example, suppose we extend the previous example with following variable definitions. << X = $(COMMAND) COMMAND = $(COMMAND) -O3 Y = $(COMMAND) >> In this example, the value of the `X' variable is the string `gcc -Wall -g -O2' as before, and the value of the `Y' variable is `gcc -Wall -g -O2 -O3'.  Node: Section 4-2, Next: Section 4-3, Prev: Section 4-1, Up: Chapter 4 4.2 Adding to a variable definition *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* Variables definitions may also use the += operator, which adds the new text to an existing definition. The following two definitions are equivalent. << # Add options to the CFLAGS variable CFLAGS = $(CFLAGS) -Wall -g # The following definition is equivalent CFLAGS += -Wall -g >>  Node: Section 4-3, Next: Section 4-4, Prev: Section 4-2, Up: Chapter 4 4.3 Arrays *=*=*=*=*=*= Arrays can be defined by appending the `[]' sequence to the variable name and defining initial values for the elements as separate lines. Whitespace is significant on each line. The following code sequence prints `c d e'. << X[] = a b c d e f println($(nth 2, $(X))) >>  Node: Section 4-4, Next: Section 4-5, Prev: Section 4-3, Up: Chapter 4 4.4 Special characters and quoting *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*= The following characters are special to omake: `$():,=#\'. To treat any of these characters as normal text, they should be escaped with the backslash character `\'. << DOLLAR = \$ >> Newlines may also be escaped with a backslash to concatenate several lines. << FILES = a.c\ b.c\ c.c >> Note that the backslash is not an escape for any other character, so the following works as expected (that is, it preserves the backslashes in the string). << DOSTARGET = C:\WINDOWS\control.ini >> An alternative mechanism for quoting special text is the use `$"..."' escapes. The number of double-quotations is arbitrary. The outermost quotations are not included in the text. << A = $""String containing "quoted text" "" B = $"""Multi-line text. The # character is not special""" >>  Node: Section 4-5, Next: Section 4-6, Prev: Section 4-4, Up: Chapter 4 4.5 Function definitions *=*=*=*=*=*=*=*=*=*=*=*=*= Functions are defined using the following syntax. << () = >> The parameters are a comma-separated list of identifiers, and the body must be placed on a separate set of lines that are indented from the function definition itself. For example, the following text defines a function that concatenates its arguments, separating them with a colon. << ColonFun(a, b) = return($(a):$(b)) >> The `return' expression can be used to return a value from the function. A `return' statement is not required; if it is omitted, the returned value is the value of the last expression in the body to be evaluated. NOTE: as of version `0.9.6', `return' is a control operation, causing the function to immediately return. In the following example, when the argument `a' is true, the function `f' immediately returns the value 1 without evaluating the print statement. << f(a) = if $(a) return 1 println(The argument is false) return 0 >> In many cases, you may wish to return a value from a section or code block without returning from the function. In this case, you would use the `value' operator. In fact, the `value' operator is not limited to functions, it can be used any place where a value is required. In the following definition, the variable `X' is defined as 1 or 2, depending on the value of a, then result is printed, and returned from the function. << f_value(a) = X = if $(a) value 1 else value 2 println(The value of X is $(X)) value $(X) >> Functions are called using the GNU-make syntax, `$( ' is a comma-separated list of values. For example, in the following program, the variable `X' contains the value `foo:bar'. << X = $(ColonFun foo, bar) >> If the value of a function is not needed, the function may also be called using standard function call notation. For example, the following program prints the string "She says: Hello world". << Printer(name) = println($(name) says: Hello world) Printer(She) >>  Node: Section 4-6, Next: Section 4-7, Prev: Section 4-5, Up: Chapter 4 4.6 Comments *=*=*=*=*=*=*= Comments begin with the `#' character and continue to the end of the line.  Node: Section 4-7, Next: Section 4-8, Prev: Section 4-6, Up: Chapter 4 4.7 File inclusion *=*=*=*=*=*=*=*=*=*= Files may be included with the `include' or `open' form. The included file must use the same syntax as an OMakefile. << include $(Config_file) >> The `open' operation is similar to an `include', but the file is included at most once. << open Config # Repeated opens are ignored, so this # line has no effect. open Config >> If the file specified is not an absolute filenmame, both `include' and `open' operations search for the file based on the 'OMAKEPATH' variable. In case of the `open' directive, the search is performed at parse time, and the argument to `open' may not contain any expressions.  Node: Section 4-8, Next: Section 4-9, Prev: Section 4-7, Up: Chapter 4 4.8 Scoping, sections *=*=*=*=*=*=*=*=*=*=*=* Scopes in omake are defined by indentation level. When indentation is increased, such as in the body of a function, a new scope is introduced. The `section' form can also be used to define a new scope. For example, the following code prints the line `X = 2', followed by the line `X = 1'. << X = 1 section X = 2 println(X = $(X)) println(X = $(X)) >> This result may seem surprising--the variable definition within the `section' is not visible outside the scope of the `section'. The `export' form, which will be described in detail in Section 5.3*Note Section 5-3::, can be used to circumvent this restriction by exporting variable values from an inner scope. It must be the final expression in a scope. For example, if we modify the previous example by adding an `export' expression, the new value for the `X' variable is retained, and the code prints the line `X = 2' twice. << X = 1 section X = 2 println(X = $(X)) export println(X = $(X)) >> There are also cases where separate scoping is quite important. For example, each OMakefile is evaluated in its own scope. Since each part of a project may have its own configuration, it is important that variable definitions in one OMakefile do not affect the definitions in another. To give another example, in some cases it is convenient to specify a separate set of variables for different build targets. A frequent idiom in this case is to use the `section' command to define a separate scope. << section CFLAGS += -g %.c: %.y $(YACC) $< .SUBDIRS: foo .SUBDIRS: bar baz >> In this example, the `-g' option is added to the `CFLAGS' variable by the `foo' subdirectory, but not by the `bar' and `baz' directories. The implicit rules are scoped as well and in this example, the newly added yacc rule will be inherited by the `foo' subdirectory, but not by the `bar' and `baz' ones; furthermore this implicit rule will not be in scope in the current directory.  Node: Section 4-9, Next: Section 4-10, Prev: Section 4-8, Up: Chapter 4 4.9 Conditionals *=*=*=*=*=*=*=*=*= Top level conditionals have the following form. << if elseif else >> The `' expression is evaluated, and if it evaluates to a true value (see Section 8.2*Note Section 8-2:: for more information on logical values, and Boolean functions), the code for the `' is evaluated; otherwise the remaining clauses are evaluated. There may be multiple `elseif' clauses; both the `elseif' and `else' clauses are optional. Note that the clauses are indented, so they introduce new scopes. When viewed as a predicate, a value corresponds to the Boolean false, if its string representation is the empty string, or one of the strings `false', `no', `nil', `undefined', or `0'. All other values are true. The following example illustrates a typical use of a conditional. The `OSTYPE' variable is the current machine architecture. << # Common suffixes for files if $(equal $(OSTYPE), Win32) EXT_LIB = .lib EXT_OBJ = .obj EXT_ASM = .asm EXE = .exe export elseif $(mem $(OSTYPE), Unix Cygwin) EXT_LIB = .a EXT_OBJ = .o EXT_ASM = .s EXE = export else # Abort on other architectures eprintln(OS type $(OSTYPE) is not recognized) exit(1) >>  Node: Section 4-10, Next: Section 4-11, Prev: Section 4-9, Up: Chapter 4 4.10 Matching *=*=*=*=*=*=*=* Pattern matching is performed with the `switch' and `match' forms. << switch case case ... default >> The number of cases is arbitrary. The `default' clause is optional; however, if it is used it should be the last clause in the pattern match. For `switch', the string is compared with the patterns literally. << switch $(HOST) case mymachine println(Building on mymachine) default println(Building on some other machine) >> Patterns need not be constant strings. The following function tests for a literal match against `pattern1', and a match against `pattern2' with `##' delimiters. << Switch2(s, pattern1, pattern2) = switch $(s) case $(pattern1) println(Pattern1) case $"##$(pattern2)##" println(Pattern2) default println(Neither pattern matched) >> For `match' the patterns are egrep(1)-style regular expressions. The numeric variables `$1, $2, ...' can be used to retrieve values that are matched by `\(...\)' expressions. << match $(NODENAME)@$(SYSNAME)@$(RELEASE) case $"mymachine.*@\(.*\)@\(.*\)" println(Compiling on mymachine; sysname $1 and release $2 are ignored) case $".*@Linux@.*2\.4\.\(.*\)" println(Compiling on a Linux 2.4 system; subrelease is $1) default eprintln(Machine configuration not implemented) exit(1) >>  Node: Section 4-11, Next: Section 4-12, Prev: Section 4-10, Up: Chapter 4 4.11 Objects *=*=*=*=*=*=*= OMake is an object-oriented language. Generally speaking, an object is a value that contains fields and methods. An object is defined with a `.' suffix for a variable. For example, the following object might be used to specify a point (1, 5) on the two-dimensional plane. << Coord. = x = 1 y = 5 print(message) = println($"$(message): the point is ($(x), $(y)") # Define X to be 5 X = $(Coord.x) # This prints the string, "Hi: the point is (1, 5)" Coord.print(Hi) >> The fields `x' and `y' represent the coordinates of the point. The method `print' prints out the position of the point.  Node: Section 4-12, Next: Section 4-13, Prev: Section 4-11, Up: Chapter 4 4.12 Classes *=*=*=*=*=*=*= We can also define classes. For example, suppose we wish to define a generic `Point' class with some methods to create, move, and print a point. A class is really just an object with a name, defined with the `class' directive. << Point. = class Point # Default values for the fields x = 0 y = 0 # Create a new point from the coordinates new(x, y) = this.x = $(x) this.y = $(y) return $(this) # Move the point to the right move-right() = x = $(add $(x), 1) return $(this) # Print the point print() = println($"The point is ($(x), $(y)") p1 = $(Point.new 1, 5) p2 = $(p1.move-right) # Prints "The point is (1, 5)" p1.print() # Prints "The point is (2, 5)" p2.print() >> Note that the variable `$(this)' is used to refer to the current object. Also, classes and objects are functional---the `new' and `move-right' methods return new objects. In this example, the object `p2' is a different object from `p1', which retains the original (1, 5) coordinates.  Node: Section 4-13, Next: Section 4-14, Prev: Section 4-12, Up: Chapter 4 4.13 Inheritance *=*=*=*=*=*=*=*=*= Classes and objects support inheritance (including multiple inheritance) with the `extends' directive. The following definition of `Point3D' defines a point with `x', `y', and `z' fields. The new object inherits all of the methods and fields of the parent classes/objects. << Z. = z = 0 Point3D. = extends $(Point) extends $(Z) class Point3D print() = println($"The 3D point is ($(x), $(y), $(z))") # The "new" method was not redefined, so this # defines a new point (1, 5, 0). p = $(Point3D.new 1, 5) >>  Node: Section 4-14, Next: Section 4-15, Prev: Section 4-13, Up: Chapter 4 4.14 Special objects/sections *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* Objects provide one way to manage the OMake namespace. There are also four special objects that are further used to control the namespace.  Node: Section 4-15, Next: Section 4-16, Prev: Section 4-14, Up: Chapter 4 4.15 private. *=*=*=*=*=*=*=* The `private.' section is used to define variables that are private to the current file/scope. The values are not accessible outside the scope. Variables defined in a `private.' object can be accessed only from within the section where they are defined. << 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 f() = println(The public value of x is: $(x)) # This object uses a private value of x Obj. = private. = x = 2 print() = x = 3 println(The private value of x is: $(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. << private. = FLAG = true section FLAG = false export # FLAG is still true section FLAG = false export FLAG # FLAG is now false >>  Node: Section 4-16, Next: Section 4-17, Prev: Section 4-15, Up: Chapter 4 4.16 protected. *=*=*=*=*=*=*=*=* The `protected.' object is used to define fields that are local to an object. They can be accessed as fields, but they are not passed dynamically to other functions. The purpose of a protected variable is to prevent a variable definition within the object from affecting other parts of the project. << 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. = protected. = X = 3 print() = println(The protected value of X is: $(X)) f() # Prints: # The protected value of X is: 3 # The public value of X is: 1 Obj.print() # This is legal, it defines Y as 3 Y = $(Obj.X) >> In general, it is a good idea to define object variables as protected. The resulting code is more modular because variables in your object will not produce unexpected clashes with variables defined in other parts of the project.  Node: Section 4-17, Next: Section 4-18, Prev: Section 4-16, Up: Chapter 4 4.17 public. *=*=*=*=*=*=*= The `public.' object is used to specify public dynamically-scoped variables. In the following example, the `public.' object specifies that the value `X = 4' is to be dynamically scoped. Public variables are not defined as fields of an object. << X = 1 f() = println(The public value of X is: $(X)) # Prints: # The public value of X is: 2 section X = 2 f() Obj. = protected. = X = 3 print() = println(The protected value of X is: $(X)) public. = X = 4 f() # Prints: # The protected value of X is: 3 # The public value of X is: 4 Obj.print() >>  Node: Section 4-18, Next: Section 4-19, Prev: Section 4-17, Up: Chapter 4 4.18 static. *=*=*=*=*=*=*= The `static.' object is used to specify values that are persistent across runs of OMake. They are frequently used for configuring a project. Configuring a project can be expensive, so the `static.' object ensure that the configuration is performed just once. In the following (somewhat trivial) example, a `static' section is used to determine if the LaTeX command is available. The `$(where latex)' function returns the full pathname for `latex', or `false' if the command is not found. << static. = LATEX_ENABLED = false print(--- Determining if LaTeX is installed ) if $(where latex) LATEX_ENABLED = true export if $(LATEX_ENABLED) println($'(enabled)') else println($'(disabled)') >> The OMake standard library provides a number of useful functions for programming the `static.' tests, as described in Chapter 13*Note Chapter 13::. Using the standard library, the above can be rewritten as << open configure/Configure static. = LATEX_ENABLED = $(CheckProg latex) >> As a matter of style, a `static.' section that is used for configuration should print what it is doing using the 'ConfMsgChecking' and 'ConfMsgResult' functions (of couse, most of helper functions in the standard library would do that automatically).  Node: Section 4-19, Next: Section 4-20, Prev: Section 4-18, Up: Chapter 4 4.19 Short syntax for scoping objects *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* The usual dot-notation can be used for private, protected, and public variables (but not static variables). << # Public definition of X public.X = 1 # Private definition of X private.X = 2 # Prints: # The public value of X is: 1 # The private value of X is: 2 println(The public value of X is: $(public.X)) println(The private value of X is: $(private.X)) >>  Node: Section 4-20, Next: Chapter 5, Prev: Section 4-19, Up: Chapter 4 4.20 Modular programming *=*=*=*=*=*=*=*=*=*=*=*=*= The scoping objects help provide a form of modularity. When you write a new file or program, explicit scoping declarations can be used to define an explicit interface for your code, and help avoid name clashes with other parts of the project. Variable definitions are public by default, but you can control this with private definitions. << # These variables are private to this file private. = FILES = foo1 foo2 foo3 SUFFIX = .o OFILES = $(addsuffix $(SUFFIX), $(FILES)) # These variables are public public. = CFLAGS += -g # Build the files with the -g option $(OFILES): >>  Node: Chapter 5, Next: Section 5-1, Prev: Chapter 4, Up: Top Chapter 5 Expressions and values ************************************ omake provides a full programming-language including many system and IO functions. The language is object-oriented -- everything is an object, including the base values like numbers and strings. However, the omake language differs from other scripting languages in three main respects. - Scoping is dynamic. - Apart from IO, the language is entirely functional -- there is no assignment operator in the language. - Evaluation is normally eager -- that is, expressions are evaluated as soon as they are encountered. To illustrate these features, we will use the osh(1) omake program shell. The osh(1) program provides a toploop, where expressions can be entered and the result printed. osh(1) normally interprets input as command text to be executed by the shell, so in many cases we will use the `value' form to evaluate an expression directly. << osh> 1 *** omake error: File -: line 1, characters 0-1 command not found: 1 osh> value 1 - : "1" : Sequence osh> ls -l omake -rwxrwxr-x 1 jyh jyh 1662189 Aug 25 10:24 omake* >> * Menu: * Section 5-1:: Dynamic scoping * Section 5-2:: Functional evaluation * Section 5-3:: Exporting the environment * Section 5-4:: Eager evaluation * Section 5-5:: Objects * Section 5-6:: Field and method calls * Section 5-7:: Method override * Section 5-8:: Super calls  Node: Section 5-1, Next: Section 5-2, Prev: Chapter 5, Up: Chapter 5 5.1 Dynamic scoping *=*=*=*=*=*=*=*=*=*=* Dynamic scoping means that the value of a variable is determined by the most recent binding of the variable in scope at runtime. Consider the following program. << OPTIONS = a b c f() = println(OPTIONS = $(OPTIONS)) g() = OPTIONS = d e f f() >> If `f()' is called without redefining the `OPTIONS' variable, the function should print the string `OPTIONS = a b c'. In contrast, the function `g()' redefines the `OPTIONS' variable and evaluates `f()' in that scope, which now prints the string `OPTIONS = d e f'. The body of `g' defines a local scope -- the redefinition of the `OPTIONS' variable is local to `g' and does not persist after the function terminates. << osh> g() OPTIONS = d e f osh> f() OPTIONS = a b c >> Dynamic scoping can be tremendously helpful for simplifying the code in a project. For example, the OMakeroot file defines a set of functions and rules for building projects using such variables as `CC', `CFLAGS', etc. However, different parts of a project may need different values for these variables. For example, we may have a subdirectory called `opt' where we want to use the `-03' option, and a subdirectory called `debug' where we want to use the `-g' option. Dynamic scoping allows us to redefine these variables in the parts of the project without having to redefine the functions that use them. << section CFLAGS = -O3 .SUBDIRS: opt section CFLAGS = -g .SUBDIRS: debug >> However, dynamic scoping also has drawbacks. First, it can become confusing: you might have a variable that is intended to be private, but it is accidentally redefined elsewhere. For example, you might have the following code to construct search paths. << PATHSEP = : make-path(dirs) = return $(concat $(PATHSEP), $(dirs)) make-path(/bin /usr/bin /usr/X11R6/bin) - : "/bin:/usr/bin:/usr/X11R6/bin" : String >> However, elsewhere in the project, the `PATHSEP' variable is redefined as a directory separator `/', and your function suddenly returns the string `/bin//usr/bin//usr/X11R6/bin', obviously not what you want. The `private' block is used to solve this problem. Variables that are defined in a `private' block use static scoping -- that is, the value of the variable is determined by the most recent definition in scope in the source text. << private PATHSEP = : make-path(dirs) = return $(concat $(PATHSEP), $(dirs)) PATHSEP = / make-path(/bin /usr/bin /usr/X11R6/bin) - : "/bin:/usr/bin:/usr/X11R6/bin" : String >>  Node: Section 5-2, Next: Section 5-3, Prev: Section 5-1, Up: Chapter 5 5.2 Functional evaluation *=*=*=*=*=*=*=*=*=*=*=*=*=* Apart from I/O, omake programs are entirely functional. This has two parts: - There is no assignment operator. - Functions are values, and may be passed as arguments, and returned from functions just like any other value. The second item is straightforward. For example, the following program defines an increment function by returning a function value. << incby(n) = g(i) = return $(add $(i), $(n)) return $(g) f = $(incby 5) value $(f 3) - : 8 : Int >> The first item may be the most confusing initially. Without assignment, how is it possible for a subproject to modify the global behavior of the project? In fact, the omission is intentional. Build scripts are much easier to write when there is a guarantee that subprojects do not interfere with one another. However, there are times when a subproject needs to propagate information back to its parent object, or when an inner scope needs to propagate information back to the outer scope.  Node: Section 5-3, Next: Section 5-4, Prev: Section 5-2, Up: Chapter 5 5.3 Exporting the environment *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* The `export' directive can be used to propagate all or part of an inner scope back to its parent. The `export' directive should be the last statement in a block. If used without arguments, the entire scope is propagated back to the parent; otherwise the arguments specify which part of the environment to propagate. The most common usage is to export the definitions in a conditional block. In the following example, the variable `B' is bound to 2 after the conditional. The `A' variable is not redefined. << if $(test) A = 1 B = $(add $(A), 1) export B else B = 2 export >> If the `export' directive is used without an argument, all of the following is exported: - The values of all the dynamically scoped variables (as described in Section 4.17*Note Section 4-17::). - The current working directory. - The current Unix environment. - The current implicit rules and implicit dependencies (see also Section 7.11.1*Note Subsection 7-11-1::). - The current set of "phony" target declarations (see Sections 7.10*Note Section 7-10:: and 7.11.3*Note Subsection 7-11-3::). If the `export' directive is used with an argument, the argument expression is evaluated and the resulting value is interpreted as follows: - If the value is empty, everything is exported, as described above. - If the value represents a environment (or a partial environment) captured using the 'export' function, then the corresponding environment or partial environment is exported. - Otherwise, the value must be a sequence of strings specifying which items are to be propagated back. The following strings have special meaning: - `.RULE' --- implicit rules and implicit dependencies. - `.PHONY' --- the set of "phony" target declarations. All other strings are interpreted as names of the variables that need to be propagated back. For example, in the following (somewhat artificial) example, the variables `A' and `B' will be exported, and the implicit rule will remain in the environment after the section ends, but the variable `TMP' and the target `tmp_phony' will remain unchanged. <>  Node: Section 5-4, Next: Section 5-5, Prev: Section 5-3, Up: Chapter 5 5.4 Eager evaluation *=*=*=*=*=*=*=*=*=*=*= Evaluation in omake is eager. That is, expressions are evaluated as soon as they are encountered by the evaluator. One effect of this is that the right-hand-side of a variable definition is expanded when the variable is defined. << osh> A = 1 - : "1" osh> A = $(A)$(A) - : "11" >> In the second definition, `A = $(A)$(A)', the right-hand-side is evaluated first, producing the sequence `11'. Then the variable `A' is redefined as the new value. When combined with dynamic scoping, this has many of the same properties as conventional imperative programming. << osh> A = 1 - : "1" osh> printA() = println($"A = $A") osh> A = $(A)$(A) - : "11" osh> printA() 11 >> In this example, the print function is defined in the scope of `A'. When it is called on the last line, the dynamic value of `A' is `11', which is what is printed. However, dynamic scoping and imperative programming should not be confused. The following example illustrates a difference. The second `printA' is not in the scope of the definition `A = x$(A)$(A)x', so it prints the original value, `1'. << osh> A = 1 - : "1" osh> printA() = println($"A = $A") osh> section A = x$(A)$(A)x printA() x11x osh> printA() 1 >> See also Section 6.5*Note Section 6-5:: for further ways to control the evaluation order through the use of "lazy" expressions.  Node: Section 5-5, Next: Section 5-6, Prev: Section 5-4, Up: Chapter 5 5.5 Objects *=*=*=*=*=*=* omake is an object-oriented language. Everything is an object, including base values like numbers and strings. In many projects, this may not be so apparent because most evaluation occurs in the default toplevel object, the `Pervasives' object, and few other objects are ever defined. However, objects provide additional means for data structuring, and in some cases judicious use of objects may simplify your project. Objects are defined with the following syntax. This defines `name' to be an object with several methods an values. << name. = # += may be used as well extends parent-object # optional class class-name # optional # Fields X = value Y = value # Methods f(args) = body g(arg) = body >> An `extends' directive specifies that this object inherits from the specified `parent-object'. The object may have any number of `extends' directives. If there is more than on `extends' directive, then fields and methods are inherited from all parent objects. If there are name conflicts, the later definitions override the earlier definitions. The `class' directive is optional. If specified, it defines a name for the object that can be used in `instanceof' operations, as well as `::' scoping directives discussed below. The body of the object is actually an arbitrary program. The variables defined in the body of the object become its fields, and the functions defined in the body become its methods.  Node: Section 5-6, Next: Section 5-7, Prev: Section 5-5, Up: Chapter 5 5.6 Field and method calls *=*=*=*=*=*=*=*=*=*=*=*=*=*= The fields and methods of an object are named using `object.name' notation. For example, let's define a one-dimensional point value. << Point. = class Point # Default value x = $(int 0) # Create a new point new(x) = x = $(int $(x)) return $(this) # Move by one move() = x = $(add $(x), 1) return $(this) osh> p1 = $(Point.new 15) osh> value $(p1.x) - : 15 : Int osh> p2 = $(p1.move) osh> value $(p2.x) - : 16 : Int >> The `$(this)' variable always represents the current object. The expression `$(p1.x)' fetches the value of the `x' field in the `p1' object. The expression `$(Point.new 15)' represents a method call to the `new' method of the `Point' object, which returns a new object with 15 as its initial value. The expression `$(p1.move)' is also a method call, which returns a new object at position 16. Note that objects are functional --- it is not possible to modify the fields or methods of an existing object in place. Thus, the `new' and `move' methods return new objects.  Node: Section 5-7, Next: Section 5-8, Prev: Section 5-6, Up: Chapter 5 5.7 Method override *=*=*=*=*=*=*=*=*=*=* Suppose we wish to create a new object that moves by 2 units, instead of just 1. We can do it by overriding the `move' method. << Point2. = extends $(Point) # Override the move method move() = x = $(add $(x), 2) return $(this) osh> p2 = $(Point2.new 15) osh> p3 = $(p2.move) osh> value $(p3.x) - : 17 : Int >> However, by doing this, we have completely replaced the old `move' method.  Node: Section 5-8, Next: Chapter 6, Prev: Section 5-7, Up: Chapter 5 5.8 Super calls *=*=*=*=*=*=*=*=* Suppose we wish to define a new `move' method that just calls the old one twice. We can refer to the old definition of move using a super call, which uses the notation `$(classname::name )'. The `classname' should be the name of the superclass, and `name' the field or method to be referenced. An alternative way of defining the `Point2' object is then as follows. << Point2. = extends $(Point) # Call the old method twice move() = this = $(Point::move) return $(Point::move) >> Note that the first call to `$(Point::move)' redefines the current object (the `this' variable). This is because the method returns a new object, which is re-used for the second call.  Node: Chapter 6, Next: Section 6-1, Prev: Chapter 5, Up: Top Chapter 6 Additional language examples ****************************************** In this section, we'll explore the core language through a series of examples (examples of the build system are the topic of the Chapter 3*Note Chapter 3::). For most of these examples, we'll use the `osh' command interpreter. For simplicity, the values printed by `osh' have been abbreviated. * Menu: * Section 6-1:: Strings and arrays * Section 6-2:: Quoted strings * Section 6-3:: Files and directories * Section 6-4:: Iteration, mapping, and foreach * Section 6-5:: Lazy expressions * Section 6-6:: Scoping and exports * Section 6-7:: Shell aliases * Section 6-8:: Input/output redirection on the cheap  Node: Section 6-1, Next: Section 6-2, Prev: Chapter 6, Up: Chapter 6 6.1 Strings and arrays *=*=*=*=*=*=*=*=*=*=*=*= The basic OMake values are strings, sequences, and arrays of values. Sequences are like arrays of values separated by whitespace; the sequences are split on demand by functions that expect arrays. << osh> X = 1 2 - : "1 2" : Sequence osh> addsuffix(.c, $X) - : : Array >> Sometimes you want to define an array explicitly. For this, use the `[]' brackets after the variable name, and list each array entry on a single indented line. << osh> A[] = Hello world $(getenv HOME) - : : Array >> One central property of arrays is that whitespace in the elements is significant. This can be useful, especially for filenames that contain whitespace. << # List the current files in the directory osh> ls -Q "fee" "fi" "foo" "fum" osh> NAME[] = Hello world - : : Array osh> touch $(NAME) osh> ls -Q "fee" "fi" "foo" "fum" "Hello world" >>  Node: Section 6-2, Next: Section 6-3, Prev: Section 6-1, Up: Chapter 6 6.2 Quoted strings *=*=*=*=*=*=*=*=*=*= A `String' is a single value; whitespace is significant in a string. Strings are introduced with quotes. There are four kinds of quoted elements; the kind is determined by the opening quote. The symbols `'' (single-quote) and `"' (double-quote) introduce the normal shell-style quoted elements. The quotation symbols are included in the result string. Variables are always expanded within a quote of this kind. Note that the osh(1) (Chapter 14*Note Chapter 14::) printer escapes double-quotes within the string; these are only for printing, they are not part of the string itself. << osh> A = 'Hello "world"' - : "'Hello \"world\"'" : String osh> B = "$(A)" - : "\"'Hello \"world\"'\"" : String osh> C = 'Hello \'world\'' - : "'Hello 'world''" : String >> A second kind of quote is introduced with the `$'' and `$"' quotes. The number of opening and closing quote symbols is arbitrary. These quotations have several properties: - The quote delimiters are not part of the string. - Backslash `\' symbols within the string are treated as normal characters. - The strings may span several lines. - Variables are expanded within `$"' sequences, but not within `$'' sequences. << osh> A = $'''Here $(IS) an '''' \(example\) string[''' - : "Here $(IS) an '''' \\(example\\) string[" : String osh> B = $""""A is "$(A)" """" - : "A is \"Here $(IS) an '''' \\(example\\) string[\" " : String osh> value $(A.length) - : 38 : Int osh> value $(A.nth 5) - : "$" : String osh> value $(A.rev) - : "[gnirts )\\elpmaxe(\\ '''' na )SI($ ereH" : String >> Strings and sequences both have the property that they can be merged with adjacent non-whitespace text. << osh> A = a b c - : "a b c" : Sequence osh> B = $(A).c - : : Sequence osh> value $(nth 2, $(B)) - : "c.c" : String osh> value $(length $(B)) - : 3 : Int >> Arrays are different. The elements of an array are never merged with adjacent text of any kind. Arrays are defined by adding square brackets `[]' after a variable name and defining the elements with an indented body. The elements may include whitespace. << osh> A[] = a b foo bar - : : Array osh> echo $(A).c a b foo bar .c osh> value $(A.length) - : 2 : Int osh> value $(A.nth 1) - : "foo bar" : Sequence >> Arrays are quite helpful on systems where filenames often contain whitespace. << osh> FILES[] = c:\Documents and Settings\jyh\one file c:\Program Files\omake\second file osh> CFILES = $(addsuffix .c, $(FILES)) osh> echo $(CFILES) c:\Documents and Settings\jyh\one file.c c:\Program Files\omake\second file.c >>  Node: Section 6-3, Next: Section 6-4, Prev: Section 6-2, Up: Chapter 6 6.3 Files and directories *=*=*=*=*=*=*=*=*=*=*=*=*=* OMake projects usually span multiple directories, and different parts of the project execute commands in different directories. There is a need to define a location-independent name for a file or directory. This is done with the `$(file )' and `$(dir )' functions. << osh> mkdir tmp osh> F = $(file fee) osh> section: cd tmp echo $F ../fee osh> echo $F fee >> Note the use of a `section:' to limit the scope of the `cd' command. The section temporarily changes to the `tmp' directory where the name of the file is `../fee'. Once the section completes, we are still in the current directory, where the name of the file is `fee'. One common way to use the file functions is to define proper file names in your project `OMakefile', so that references within the various parts of the project will refer to the same file. << osh> cat OMakefile ROOT = $(dir .) TMP = $(dir tmp) BIN = $(dir bin) ... >>