AT&T Home | AT&T Labs | Research
AT&T Labs, Inc. - Research

The Yoix® Scripting Language

Home | What's New | Grammar | Documentation | Download | License | YChart | YDAT | YWAIT | Byzgraf | FAQs
functions grammar
 
Yoix functions can be defined to take a fixed or variable number of arguments or a combination of the two (i.e., some number of required arguments and a variable number of subsequent arguments). The description of a function definition can be summarized as follows:
FunctionDefinition:
	name ( ) Compound
	name ( ) = Expression
	name ( ... ) Compound
	name ( ... ) = Expression
	name ( ParameterList ) Compound
	name ( ParameterList ) = Expression
	name ( ParameterList , ... ) Compound
	name ( ParameterList , ... ) = Expression
The body of a function, which can be an expression or a compound statement, always has access to a local int variable named argc and a local Array variable named argv. The array starts with a string in argv[0] that is the name of the function, and ends with the arguments in the same order that they appeared in the function call. The value of argc is the length of the argv array, which means it is one more than the number of arguments that appeared in the function call. Note that within a function, the globally defined argc and argv are hidden by the local definitions, but they always can be accessed by using global.argc and global.argv.

The function definition process is straightforward, but there are some important points that need to be documented. A function definition begins with a name, which is the string that ends up in argv[0] whenever the function is called. Seems obvious until you realize that once a function is defined it can be assigned to another variable (or even passed as argument in a function call), but no matter what happens to that function the original name never changes. Here is a simple example that illustrates the behavior. Run

Original() {
    yoix.stdio.printf("argv[0]=%s\n", argv[0]);
}

Function Fake = Original;

Original();
Fake();
and
argv[0]=Original
argv[0]=Original
prints on standard output.

The second point is much more important. Defining a function is a runtime activity; the parser has to agree that a statement is a function definition and then it has to build a parse tree that represents that definition, but nothing else happens until the interpreter, at runtime, bumps into the function definition parse tree. At that point it builds an internal representation of the function, hides the function's name, the parse tree that represents the body, and a reference to the current global dictionary in the function's internal representation, and then officially associates name with the internal version of the function, which means the function can finally be used by the Yoix program.

So what happens to the dictionary that is saved with a function? One of the last things the interpreter does for a function call is build a block structure to handle variable lookups, and the first step in that process takes that dictionary and makes it the current global dictionary. The block structure that was in place when the function was called is not touched, but it is completely hidden because an unsuccessful variable lookup in Yoix always stops at the current global block. What happens at that point depends on the VM.create flag, but that is a subject we will have to cover elsewhere.

Usually the current global dictionary and the one saved in the function match, so hiding the current block structure is often the only reason the interpreter starts a new global scope. However it is not hard to create a function with its own global dictionary. Put

String  str = "now is the time";

Builder() {
    yoix.stdio.printf("global=%O\n", global);
}

return(Builder);
in a file somewhere (e.g., /tmp/xxx), start the Yoix interpreter, type,
import yoix.stdio.*;

f = execute(fopen("/tmp/xxx", "r"));
f();
and something like,
global=Dictionary[9:0]
    Builder=Builder()
    VM=Dictionary[22:0]
   >argc=1
    argv=Array[1:0]
    envp=NULL:ARRAY
    errordict=Dictionary[7:0]
    importdict=Array[0:0]
    str=^"now is the time"
    typedict=Dictionary[162:0]
prints on standard output. Look at the output carefully and make sure you understand what just happened. This is an important example and it should not be hard to imagine how the technique can be extended, particularly when you realize that execute can take additional arguments, the file could be anywhere on the network, and the function could return something useful, like a frame filled with other GUI components.

Once the function's global scope is ready the interpreter figures out where the function that it is calling came from, which may not be where it was originally defined. If the answer is somewhere other than the global dictionary, then the interpreter starts a second block that uses that object (i.e., the one that contains the function) for storage and as the one that is used to resolve this; otherwise global and this will be synonyms. The interpreter then prepares another block for the arguments and finally executes the body of the function.

Much of what we just explained can be illustrated in a few simple examples. The following program

import yoix.stdio.*;

String str = "Now is";

Dictionary d = {
    String str = "the time";

    f(String str) {
        printf("%s %s %s\n", global.str, this.str, str);
    }
};

d.f("for all good men");
prints
Now is the time for all good men
on standard output. If we pick a new name for the function argument but forget to make any other changes
import yoix.stdio.*;

String str = "Now is";

Dictionary d = {
    String str = "the time";

    f(String s) {
        printf("%s %s %s\n", global.str, this.str, str);
    }
};

d.f("for all good men");
then we get
Now is the time the time
on standard output.

We still have more to explain, but we think this is a decent start, so the rest of the story will have to wait for another release.
 
 Example:   The following is a more complicated example that defines a function and calls it in different ways. Variable names intentionally do not clash, which means we could avoid using global and this in the example.
import yoix.stdio.*;

Function fvbl;
Pointer  pfvbl;
String   variable1 = "global context";

Dictionary dict1 = {
    String variable2 = "dict1 context";

    Dictionary dict2 = {
	String variable3 = "dict2 context";

	func(String info) {
	    fprintf(stdout, "\nCalling info: %s\n", info);
	    fprintf(stdout, "variable1: %s\n", variable1);
	    try {
		fprintf(stdout, "variable2: %s\n", variable2);
	    }
	    catch(e) {
		fprintf(stdout, "variable2 is not available\n");
		return(true);
	    }
	    try {
		fprintf(stdout, "variable3: %s\n", variable3);
	    }
	    catch(e) {
		fprintf(stdout, "variable3 is not available\n");
		return(true);
	    }
	};

	fvbl = func;
	pfvbl = &func;
    };

    Function fvbl2 = fvbl;
};

dict1.dict2.func("direct");
fvbl("variable");
(*pfvbl)("pointer");
dict1.fvbl2("dict1 variable");
The output written to standard output would look something like:
Calling info: direct
variable1: global context
variable2 is not available
variable3: dict2 context

Calling info: variable
variable1: global context
variable2 is not available
variable3 is not available

Calling info: pointer
variable1: global context
variable2 is not available
variable3: dict2 context

Calling info: dict1 variable
variable1: global context
variable2: dict1 context
variable3 is not available
 
 See Also:   Function, global, reference, this, unroll

 

Yoix is a registered trademark of AT&T Inc.