Mynx Semantic Checks and Language Design for Declaration Before Usage
One goal of mine in the compiler implementation is to try and do things minimally - the fewer passes the better. Even better is to do things on-the-fly so that information does not accumulate and consume memory, and the time to iteratively process the Mynx source being compiled.
At first, it seems there are one of two choices. Nominally, I’d opt for the second choice as type annotation, validation, and compatibility follow in the compilation process. But then I realized with the appropriate data structure, the same approach used to store information about type, the identifier, and the sentence can be used if a declaration does not exist. Simply put, existence and uniqueness semantic checks do not have to be mutually exclusive and then require other compiler passes. The dilemma of two design choices was binary thinking, a false dilemma.
During the compiler pass, if a declaration sentence is encountered, the declaration is stored in a map with the identifier as the key, and the sentence as the value. For an executable or non-declaration, the type information is accessed in the map, and the type information annotated to the sentence. If it is not found, normally with declaration before use it would flag a semantic error in a one pass compiler.
When a declaration is not found, the expected declaration identifier is stored in the multi map, along with the sentence the expected declaration is not found. A multimap is used so that any other sentences in which an expected declaration is not found is stored multiply under the same identifier. Essentially a forward declaration is implicit, a declaration later on within the class scope.
As other sentences are processed declarations are handled by a check if the declaration identifier is a forward declaration. If so, the various sentences have the declaration type annotated and existence check as if the declaration type existed when the sentence was processed. The existence semantic check is performed on the fly along with the type annotation.
Example of Forward Declaration with the Class in Mynx:
with mynx.io.IOStream;
class X is
public void doMethod is
var Int y to null;
y = x; //x nonexistent,add (x, y = x>)
IO <<< x <<< eoln; //x nonexistent,add (x, y = x;,IO <<< x <<< eoln;)
IO <<< i <<< eoln; //i nonexistent,add (i, IO <<< i <<< eoln;)
end doMethod;
public Int x to 0; //update found x declaration
public Ord i to 0; //update found i declaration
end class;
If all the sentences that use a forward declaration are cleared from the multimap, then the semantic checks of existence and uniqueness are satisfied.
However, not all the identifiers need be declared. Left over identifiers are at first glance, erroneous, but in Mynx there is an implicit static method. An implicit static method is included by using an absolute namespace inclusion.
The implicit static method uses identifiers not declared as internal variables or constants. After a pass through the source code, the remaining identifiers if not associated with a declaration are checked against any absolute namespaces for inclusion as a static method.
Example of Implicit Static Methods with the Class in Mynx:
with mynx.io.IOStream;
class X is
public void doMethod is
var Int y to null;
y = x;
IO <<< x <<< eoln; //IO nonexistent, add (IO, IO <<< x <<< eoln;)
//eoln nonexistent, add (eoln,IO <<< x <<< eoln;)
IO <<< i <<< eoln; //IO nonexistent, add (IO, IO <<< x <<< eoln;)
//eoln nonexistent, add (eoln,IO <<< x <<< eoln;)
end doMethod;
public Int x to 0; //update found x declaration
public Ord i to 0; //update found i declaration
end class;
The same Mynx class has two static methods - IO, eoln. Rewriting the Mynx class with the static methods:
Example of Explicit Static Methods with the Class in Mynx:
with mynx.io.IOStream;
class X is
public void doMethod is
var Int y to null;
y = x;
IOStream.IO <<< x <<< IOStream.eoln;
IOStream.IO <<< i <<< IOStream.eoln;
end doMethod;
public Int x to 0;
public Ord i to 0;
end class;
The interesting point is that handling implicit static methods was not by design, but a result of the properties of using the multi map. The solution is flexible to encompass that aspect of the semantic checks, which is a indication of adaptability and generality.
One other type information annotation is which operator is associated with method for a given variable. Substituting that external type validation information in the class:
with mynx.io.IOStream;
class X is
public void doMethod is
var Int y to null;
y.set(x);
IOStream.IO.put(x).put(IOStream.eoln);
IOStream.IO.put(i).put(IOStream.eoln);
end doMethod;
public Int x to 0;
public Ord i to 0;
end class;
At the end of the semantic process, there is explicitness in methods (static or otherwise) and operators. This is for the next stage of the compilation process using a sentential compiler -- code synthesis. For code synthesis, all internal declarations must exist and are unique; externally all methods and operators are resolved, and type compatibility is valid.
A further goal is to do the external semantic checks for the existence of class elements and validation of types and type compatibility on the fly if possible. One design faux pas is to do things in batch mode, a compiler pass for each major compiler function. A batch process is repetitive and inefficient with storing information between each pass.
And one other kind of semantic check is sentence-method and sentence-unit, context checks that are not specifically external type or internal uniqueness/existence. For example, an abstract method in a static class, or generic variable in a default class method. Another semantic context check is obviating a method declared in the class (a semantic mistake in the language definition).
Labels: context semantic, existence semantics, mynx semantic, mynx semantic check

