|
|
|
|
|
|
|
|
|
|
|
|
Note : This section was written by Avidan Efody (avidan_e@yahoo.com). You are more than welcome to send comments, complaints, corrections or tempting work offers to Avidan Efody.
|
|
|
|
|
|
Common Features
|
|
|
In this chapter I explore some of the features of E that are available, albeit sometimes in slight changes, in other object oriented languages. If you already have some experience with object oriented programming you can skip it and never return. If you are absolute beginners with E, leave it aside and come back later, since the topics discussed are a bit advanced.
|
|
|
|
|
|
|
|
|
|
|
|
'Like' Inheritance
|
|
|
As mentioned before, there are two types of inheritance in E: one using the keyword 'when' and the other using the keyword 'like'. 'When' inheritance, discussed above, is unique to E and is one of its best innovations. 'like' inheritance is just the E version of the common object oriented inheritance and is not widely used. Its unpopularity owes a lot to Verisity support, which underestimates it and discourages programmers from using it in their code.
|
|
|
|
|
|
The idea of inheritance is pretty simple: it allows you to separate the common features of some objects, from the specific features that are unique to each object. For example, there are numerous types of windows in all kinds of geometric shapes, sizes or colors. Some windows have manus and others don't, some demand immediate user response while others can wait patiently forever. Still, almost all windows have some common features: they all have to do something in response to a mouse click, they all draw themselves on the screen, and almost all of them have close and minimize buttons. Back to inheritance, the features that are common to all windows will be defined in the "base class" (or "base struct" in E), while their specific implementation and other specific features will be defined in the classes that inherit from it. Here is a simple example, written in E, although it is highly improbable that anyone will use E for anything like this in the near future:
|
|
|
|
|
|
1 // example 8
2
3 <'
4 type size_t : [small, medium, large];
5 type color_t : [yellow, red, blue];
6
7 struct window {
8 // All windows must have some response to a mouse click. The specific response: a sound, a change in color etc,
9 // will be determined in the specific(inherited) windows. This method is defined as "empty"- the specific
10 // windows will provide the appropriate implementation
11
12 draw() is empty;
13 // All windows must draw something on the screen. The specific graphics of
14 // each window will be determined in the specific (inherited) windows. This
15 // method is defined as "empty" and the specific windows will fill in the details
16 };
17
18
19 struct my_window like window {
20 // "size" and "background_color" are also fields of the inherited class "my_window" since they are
21 // fields of the base class "window" therefore I can constrain them here.
22
23 keep size == big;
24 keep background color == blue;
25 // "when_mouse_is_clicked" is defined in the base class since every window
26 // has to do something when it is clicked. However, what is it that it should do exactly is different
27 // from window to window. Mine, for example, sounds a bip, and then shows a pop up message.
28
29 when_mouse_is_clicked() is {
30 sound_a_bip();
31 jump_a_pop_up("Come on and milk my cow");
32 };
33
34 // same for "draw"
35 draw() is {
36 draw_caw();
37 draw_milk_bottle();
38 };
39 };
40
41
42 struct her_window like window {
43
44 keep size == small;
45 keep background color == blue;
46
47 when_mouse_is_clicked() is also {
48 sound_a_wof();
49 jump_a_pop_up("Come on and bite my dog");
50 };
51
52 draw() is also {
53 draw_dog();
54 };
55 };
56 '>
You could download file one_day_ex8.e
here
|
|
|
|
|
|
As you probably noted, the base class (struct) in this case is just an empty shell that does nothing. Obviously this is not always the case. For example, with real windows, the base class performs some crucial steps for all the windows that inherit from it, like registering the window with the operating system so that the operating system can tell the window when it is clicked. In the case of specific packet structures that inherit from a single base packet, the base packet might calculate the checksum. Still, even when the base class doesn't do much it has a great importance since it keeps the code orderly and makes it more comprehensible to other people. If someone understands how one window work, it will not take him more than ten minutes to understand how another window does. Give him five minutes more and he will even be able to do minor changes in the code. Of course, he wouldn't be able to do that if every window would name its basic methods differently and assign them with different functionality.
|
|
|
|
|
|
As I mentioned before, 'like' is not unique to E. Verisity, like any software company, is keen on promoting the special features it provides and therefore consistently dissuades programmers from using 'like'. It is true that most of the things that could be done with 'like' could also be done with 'when'. Still, with 'when' the objects you will get depend, after all, on a random value assigned to a determinant field. In those times when you know the object you need in advance why should you relay on random generation? For example, say a chip has several fixed interfaces that, although they are different, have a basic set of common features. In this case it is definitely better to use 'like'. Also, using 'when' has some inconveniences, but I will not go into them now.
|
|
|
|
|
|
Last point – 'like' is quite useful if you would like to give yourself the possibility, some day in the future, of adding some functionality instantly to all your structs, without using a global method. You could achieve this if you make all structs in your environment inherit from a basic global struct. However, since as far as I know E doesn't allow multiple lines of 'like' inheritance, it might limit you in other cases.
|
|
|
|
|
|
Encapsulation
|
|
|
Encapsulation has been incorporated rather lately into E (version 4.1). Simply put, it is a way to separate a struct into an interface and an implementation (or core). The interface, including shared struct members like high level methods or important fields, stays fixed and other programmers in the team know they can use it without fear of sudden changes. The implementation, on the other hand is dynamic, and the programmer is free to do in it as he likes – change method names and their parameters, add struct members or remove them without a warning, and so on. The implementation is accessible only to the programmer who owns the struct. Other programmers can't access it, which is better for them, since, as just said, it might be subject to sudden changes. An exact parallel from the, so called, hardware world is a Verilog or VHDL block, where the inputs and outputs are fixed and defined in the specification, but the implementation is totally up to the owner of the block and can undergo dramatic changes.
|
|
|
|
|
|
How does encapsulation prevents other programmers from using anything but what they are supposed to, namely the interface? Quite simply: The definitions of all of the methods and struct fields that belong to the implementation, and where no one except the owner of the struct is expected to shove his nose, are prefixed with the words "private", "protected" or "package" (the difference will be soon explained). If someone insists on accessing these fields from methods in other structs, the code will simply not compile (see exceptions below). If a field is not declared to have a limited access using "private" or "protected" it means that it is public and therefore belongs to the interface, and accessible to everyone. Thinking about it, it would have probably been better if things were the other way around, i.e. if the default was "private" or "protected" and the interface methods or fields would be prefixed with "public". I have two reasons for that: public struct members are smaller in number and since they are important, it would be nice to mark them with a prefix. Anyway, that's life. To show how simple it is, here is an example of a bus state machine:
|
|
|
|
|
|
1 // example 9
2
3 <'
4 type bus_state_t : [busy, idle, waiting, error];
5
6 struct bus {
7 // These are the interface or 'public' methods and fields. They are not prefixed
8 // with anything since 'public' access level is the default
9
10 reset()@clk_sys is { // "reset" can be called from other structs like the data driver or the CPU model.
11
12 'reset_bus' = '0'; // drive '0' to the reset pin 'reset bus'
13
14 wait 3*cycle; // wait 3 'clk_sys' cycles for reset to finish
15
16 state = idle;
17 // "state" is a private struct member (see below). Therefore other users can not access it directly.
18 };
19
20 bus_state_t get_state() is {
21 //To get the current state, users from other structs must call "get_state()" since state is private
22 // and they can not access it directly.
23
24 return state;
25
26 };
27
28 send_data (l: list of bit)@clk_sys is {
29 // send data calls some private TCMs that take care of actually sending the data.
30
31 if check_data(l) {
32 signal_data_start();
33 drive_data(l);
34 signal_data_end();
35 };
36 };
37
38 // Below are the private methods and fields. Nobody is supposed to use them except the owner of the struct.
39
40 private state : bus_state_t;
41 // other structs can change "state" or see its value only through "reset()" or "get_bus_state()"
42 keep state == idle;
43
44 // All the methods below are hidden from other users. They can not use them or they will get a compilation error.
45
46 private bool check_data (l: list of bit) is {
47 //...
48 };
49
50 private signal_data_start () is {
51 //...
52 };
53
54 private drive_data (l: list of bit) is {
55 //...
56 };
57
58 private signal_data_end (l: list of bit) is {
59 //...
60 };
61 };
62 '>
You could download file one_day_ex9.e
here
|
|
|
|
|
|
Diving a bit deeper into the nuances, there are some special cases in which structs can access the limited access fields of another struct. The "package", "protected" and "private" access modifiers that were mentioned above, simply define different groups of structs that are allowed to access the limited access fields. Fields that are "protected" can be accessed by all structs from the "same struct family", that is, by all structs that are related to each other through inheritance. This means that if you use 'like' or 'when' to extend a struct, the son (the new struct you have just created) can see all his father's (the base struct) "protected" fields and can use his father's "protected" methods.
|
|
|
|
|
|
Sometimes you would like to limit the access to a specific field to a group of structs that are not necessarily related to each other through inheritance. This is what packages are for. If you define several structs as belonging to the same package (see E language LRM chapter 26 for more details) they and only they will be permitted to see each other's "package" prefixed fields.
|
|
|
|
|
|
The "private" access modifier is a "logical and" of "protected" and "package". This means that "private" fields are accessible only to structs in the same struct family and in the same package. "private", "package" and "protected" can be used to create several layers of access permissions. For example, a programmer could build his code in three layers – a "public" layer, a "package" layer and a "private" layer. The "public" layer should be used by people who are working on totally different things, maybe even in another team, and that have only a very limited and superficial knowledge about the way his struct works. Then there is the "package" layer, which other people who work in the same team, on similar things, and have better knowledge of his struct can access. Finally there is the "private" part, which no one should touch since it is sensible or changes quite often.
|
|
|
|
|
|
It must be said that the limited access options in E are somewhat relaxed. None of the three options limits access only to a specific struct. Even "private" which is the most severe allows access to structs from the same struct family and the same package.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|