Features may be declared private to allow only the class in which they appear to access them. Accessor routines are automatically defined for reading object, shared, and constant attributes and for writing object and shared attributes. Attributes may also be declared to be readonly or private. Readonly attributes have a public reader and a private writer routine, while with private attributes both reader and writer are private. The private and readonly qualifications may also be used with shared attributes.
The set of non-private routines and iters in a class define its interface. In order to illustrate the simplest kinds of classes in Sather, consider an EMPLOYEE class, which holds information concerning an employee, such as his identification number and name.
class EMPLOYEE is
-- A concrete class i.e. a class whose features are all implemented.
readonly attr name: STR;
-- Generates the interface routines:
-- name: STR [the public reader routine]
-- private name(new_value: STR) [the private assignment routine]
attr id: INT;
private attr wage: INT;
create(a_name: STR,a_id: INT, a_wage:INT): SAME is
res ::= new;
res.id := a_id;
res.name := a_name; -- Sugar for the writer routine "name(a_name)"
res.wage := a_wage;
return(res);
end;
highly_paid: BOOL is
return wage > 40000;
end;
end;
class TESTEMP is
main is
john:EMPLOYEE := #EMPLOYEE("John",100,10000);
peter ::= #EMPLOYEE("Peter",3,10000); -- Infer the Peter's type
#OUT+john.name+"\n"; -- Prints "John"
#OUT+peter.id+"\n"; -- Prints "3"
-- ILLEGAL! #OUT+john.wage+"\n"; "wage" is private
john.id := 100; -- Set the value of the attribute "id" in john
-- ILLEGAL! john.name := "martha"; "name" is readonly.
end;
end;
The ``main'' routine in sather has a special meaning, similar to
its meaning in C. A working sather program must specify its main class,
which must contain a main routine that is the root of all routines
in the implementation.
To run the above example - type the code into a file emp.sa and then run the executable "emp"
cs emp.sa -main TESTEMP -o empThis generates the executable "emp", using the "main" routine in TESTEMP as its starting point. You can browse the resulting code by calling
bs emp.sa -main TESTFOO
attr a: INT; is sugar for a: INT; -- Reader routine, used to get the value of a a(value: INT); -- Writer routine, used to set the value of "a"If the attribute access is restricted the interface routines are typed accordingly. If the attribute is readonly, the writer is private; if the attribute is private, both reader and writer routines are private.
In order to make attribute assignment work, the := sign is syntactic sugar for calling any function with one argument. In particular, it can be used to call the attribute assignment routine.
foo is a := 3; -- Equivalent to a(3); end;Note that any function call to a function with one argument can make use of the syntactic sugar.
The advantage of this approach is that it is impossible to tell whether a certain feature is implemented as an attribute or as a routine. Hence, changing an implementation to use a routine rather than an attribute and vice-versa is trivial.
There is no performance loss associated with these interface routines - the compiler treats attribute reader and writer routines specially and generates inline assignments, as you might expect (this is true even with all optimizations off).
Note that not all assignments are syntactic sugar - assignments to locals and to out arguments are honest-to-goodness assigments and behave as they would in any other language.
In addition to permitting the hash sign for create, Sather permits syntactic sugar to be used for many other routine names. For instance, "+" is syntactic sugar for the plus routine, "-" for the minus routine "*" for the times routine, "=" for the is_eq routine etc. Refer to the Sather manual for the sugared routines.
class POINT is
attr x, y: INT;
create(x,y: INT): SAME is
res ::= new;
res.x := x; res.y := y;
return res;
end;
plus(p: SAME): SAME is return #(x+p.x,y+p.y) end;
minus(p: SAME): SAME is return #(x-p.x,y-p.y) end;
end;
main is
p ::=#POINT(4,5);
q ::= #POINT(11,15);
l ::= p+q; -- Invoke the "plus" routine in "POINT"
r ::= p-q; -- Invoke the "minus" routine in "POINT"
end;
end;
Constants are similar to attributes except that their values must be set at compile time to other constants or expressions consisting of other constants. Constants are usually set to literals of one of the built in types.
const a: STR := "This is a test";
const b: INT := 0;
const c: CHAR := 'a';
const d: FLT := 1.0;
const f: ARRAY{FLT} := |1.0,2.0,3.0|;
Constants are used for values that should never change in the
course of computation and can be computed at compile time. If a constant
cannot be computed at compile time, a shared may be used in place of
a constant as shown below. See initializing shareds
class POINT is
... Definition as show above
readonly shared origin: POINT := #POINT(0,0);
...
Shared attributes may be used either for efficiency reasons
(to save on storage) or as globals, for communication between
different objects. A shared may have a create expression that is built
out of constant expressions. In the above example, the origin point is
computed only once and may then be freely used later.
If the shared requires a more complex creation expression, the following
trick may be used
class POINT is
private shared actual_point:POINT;
origin: POINT is
if void(actual_point) then actual_point := #POINT(0,0); end;
return actual_point;
end;
Through the use of this trick, the shared is initialized the first
time it is accessed.
A common bug occurs in create routines when "res" is accidentally dropped.
create(a_name: STR, a_id: INT): SAME is
res ::= new;
res.id := a_id;
name := a_name; -- ERROR! self is void, should be res.name := a_name
return(res);
end;
This will generate a run-time error, since the "name" refers to
an attribute in "self"; self is still void - we are in the processs of
creating "res". One simple way to avoid this bug is to use the following idiom:
create(a_name: STR, a_id: INT): SAME is
return new.init(a_name,a_id);
end;
private init(a_name: STR, a_id: INT): SAME is
name := a_name; -- ERROR! self is void, should be res.name := a_name
id := a_id;
return self;
end;
Another minor bug to avoid is naming the routine parameter the
same as the attribute you are setting - the routine parameter will shadow
the attribute.