gobo icon indicating copy to clipboard operation
gobo copied to clipboard

Q: how to find all the renamed attributes of a (AST processed) class in the gec compiler ?

Open mw66 opened this issue 1 year ago • 70 comments

Hi,

I want to find all the renamed attributes of a (AST processed) class in the gec compiler:

I have checked et_class.e

and found this navigation:

ET_CLASS.queries: ET_QUERY_LIST

ET_QUERY.is_attribute

but where to find if the attribute is a renamed one?

Thanks.

mw66 avatar Feb 29 '24 00:02 mw66

This needs to be double-checked in the code, but if I recall correctly you have to look at first_precursor and other_precursors. If they are not Void, then see whether their names are the same as in the current class or not.

ebezault avatar Feb 29 '24 00:02 ebezault

Thanks. I also found this navigation:

pwd: /gobo/tool/gec

../../library/tools/src/eiffel/compilation/et_feature_flattener.e flatten_inherited_feature (a_feature: ET_INHERITED_FEATURE)

../../library/tools/src/eiffel/ast/feature/et_inherited_feature.e ET_INHERITED_FEATURE flattened_parent: ET_PARENT_FEATURE -- Parent feature from which `flattened_feature' is resulting

../../library/tools/src/eiffel/ast/feature/et_parent_feature.e precursor_feature: ET_FEATURE -- Feature inherited from `parent'

../../library/tools/src/eiffel/ast/feature/et_feature.e is_attribute: BOOLEAN -- Is feature an attribute?

Do you think this is good enough to scan all the renamed attribute (I don't care about methods)?

mw66 avatar Feb 29 '24 08:02 mw66

I think it would work, except that ET_INHERITED_FEATURE objects are intermediate objects only used in the feature flattener. They are not kept in the AST objects.

So with the former solution, you can call the code of gec up to Degree 4 included, and then write your own code (without having to modify any line of code in the Gobo library) to navigate through the resulting AST (ET_CLASS.queries.item(...).first_precursor).

With the latter solution, you would probably have to modify the feature flattener class to intercept the ET_INHERITED_FEATURE objects on the fly when they are processed. Because they are thrown away after being processed.

ebezault avatar Feb 29 '24 08:02 ebezault

Which solution is easiest to implement in your estimate?

(You know: "There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies.")

I want to take the simple way 🙂

mw66 avatar Feb 29 '24 09:02 mw66

Simplest way is the former solution: no need to modify Gobo's code, so no risk to break it. Just call the Gobo code from your code to generate the AST, and then traverse the AST (ET_CLASS, ET_FEATURE, precursors, ...) still from your code.

ebezault avatar Feb 29 '24 09:02 ebezault

Thanks.

I will use tool/gedoc/src/gedoc_implicit_converts_format.e as a template for quick start.

mw66 avatar Mar 01 '24 00:03 mw66

Hi @ebezault

I did a quick experiment using tool/gedoc/src/gedoc_implicit_converts_format.e as a template:

https://github.com/joortcom/gobo/blob/eiffel_rename/tool/gedoc/src/gedoc_field_rename_format.e#L119-L176

only two new methods:

process_et_feature

process_implicit_converts

Since gec cannot build this project (ref #71), I added a Makefile to build using ISE's ec:

https://github.com/joortcom/gobo/blob/eiffel_rename/tool/gedoc/Makefile

$ cd /gobo/tool/gedoc
$ make ise  # build gedoc

$ cd test/rename/

$ make demo  # try the new build
../../gedoc --format=field_rename app.ecf
Degree 6: 0/0/0 0:0:0.149
Degree 5: 0/0/0 0:0:7.063
Degree 4: 0/0/0 0:0:2.403
[renamed field] READABLE_INDEXABLE.upper => STRING_8.count

[renamed field] READABLE_INDEXABLE.upper => READABLE_STRING_8.count

[renamed field] READABLE_INDEXABLE.upper => STRING_32.count

[renamed field] TWO_WAY_LIST.first_element => TWO_WAY_TREE.first_child

[renamed field] TWO_WAY_LIST.last_element => TWO_WAY_TREE.last_child

[renamed field] LINKED_LIST.after => TWO_WAY_TREE.child_after

[renamed field] LINKED_LIST.first_element => LINKED_TREE.first_child

[renamed field] LINKED_LIST.before => TWO_WAY_TREE.child_before

[renamed field] LINKED_LIST.count => TWO_WAY_TREE.arity

[renamed field] LINKABLE.right => TWO_WAY_TREE.right_sibling

[renamed field] LINKED_LIST.after => LINKED_TREE.child_after

[renamed field] BI_LINKABLE.left => TWO_WAY_TREE.left_sibling

[renamed field] LINKED_LIST.active => TWO_WAY_TREE.child

[renamed field] LINKED_LIST.before => LINKED_TREE.child_before

[renamed field] LINKED_LIST.count => LINKED_TREE.arity

[renamed field] TWO_WAY_LIST_ITERATION_CURSOR.active => TWO_WAY_TREE_ITERATION_CURSOR.node

[renamed field] LINKABLE.right => LINKED_TREE.right_sibling

[renamed field] LINKED_LIST_ITERATION_CURSOR.active => LINKED_TREE_ITERATION_CURSOR.node

[renamed field] GENERAL_SPECIAL_ITERATION_CURSOR.area => SPECIAL_ITERATION_CURSOR.target

[renamed field] LINKED_LIST.active => LINKED_TREE.child

[renamed field] INDEXABLE_ITERATION_CURSOR.target_index => HASH_TABLE_ITERATION_CURSOR.iteration_position

[renamed field] READABLE_INDEXABLE.upper => DIRECTORY_NAME.count

[renamed field] IO_MEDIUM.handle_available => PLAIN_TEXT_FILE.descriptor_available

[renamed field] IO_MEDIUM.handle_available => RAW_FILE.descriptor_available

[renamed field] READABLE_INDEXABLE.upper => FILE_NAME.count

[renamed field] IO_MEDIUM.handle_available => FILE.descriptor_available

[renamed field] IO_MEDIUM.handle_available => CONSOLE.descriptor_available

[renamed field] READABLE_INDEXABLE.upper => PATH_NAME.count

[renamed field] READABLE_INDEXABLE.upper => READABLE_STRING_32.count

[renamed field] HASH_TABLE.count => SED_OBJECTS_TABLE.capacity

Total Time: 0/0/0 0:0:10.252

However in the test file research_assistant.e has two fields addr renames, but why it's not reported in the above output?

BTW:

$ cd test/rename/
$ make gobo  # the test app program can be compiled by gec, and executed fine.

So what's going wrong? why some of the class / queries not visited?

Can you help me to make this output research_assistant.e's renamed fields?

Thanks.

mw66 avatar Mar 01 '24 08:03 mw66

In particular: looks like it does not scan from the <root class="APP" feature="make"/> class?

https://github.com/joortcom/gobo/blob/eiffel_rename/tool/gedoc/test/rename/app.ecf#L4

mw66 avatar Mar 01 '24 08:03 mw66

Before looking at the rename issue, I have a couple of remarks:

  • See my comment in the other thread about gec and CAT-calls. You should be able to adapt your code so that it does not report a CAT-call.
  • In your Makefile, you can replace:
	ec  -config src/system.ecf
	cd EIFGENs/gedoc/$(DIR)/ && finish_freezing

with:

	ec  -c_compile -config src/system.ecf

ebezault avatar Mar 01 '24 08:03 ebezault

For class RESEARCH_ASSISTANT, assuming that this is this code:

class RESEARCH_ASSISTANT
inherit
	STUDENT rename addr as student_addr end  -- field student_addr inherit the dorm semantics
	FACULTY rename addr as faculty_addr end  -- field faculty_addr inherit the lab  semantics
	-- then select, NOTE: not needed by SmartEiffel, but needed by GOBO and ISE compiler
	PERSON  select addr end

I can see that there are replications. It means that from 1 attribute addr, you created 2 new attributes (student_addr and faculty_addr) to end up with 3 attributes in class RESEARCH_ASSISTANT. The attributes student_addr and faculty_addr have no precursors (at least not in the internals of the compiler) because the selected version is the one inherited from PERSON. It's as if two new attributes had been created in class RESEARCH_ASSISTANT. So using first_precursor and other_precursors will not help in this case because it will not catch renaming occurring on a replicated attribute.

Another solution could be to traverse ET_CLASS.parent_clauses, go down to the rename clauses (ET_PARENT.renames), look at the new name (ET_RENAME.new_name.feature_name) and see whether it is an attribute in the current class (ET_CLASS.named_query (...).is_attribute).

This should compile with no CAT-call warnings with gec :-)

ebezault avatar Mar 01 '24 09:03 ebezault

with:

	ec  -c_compile -config src/system.ecf

I tried this, the command runs fine without any error, but the executable EIFGENs/gedoc/W_code/gedoc is not updated.

mw66 avatar Mar 01 '24 18:03 mw66

Another solution could be to traverse ET_CLASS.parent_clauses, go down to the rename clauses (ET_PARENT.renames), look at the new name (ET_RENAME.new_name.feature_name) and see whether it is an attribute in the current class (ET_CLASS.named_query (...).is_attribute).

Thanks, the navigation from ET_CLASS.parent_clauses worked:

[renamed field] STUDENT.addr => RESEARCH_ASSISTANT.student_addr
[renamed field] FACULTY.addr => RESEARCH_ASSISTANT.faculty_addr

mw66 avatar Mar 01 '24 18:03 mw66

I've got the renamed fields path. Now, how do I get inherited (instead of self-declared) fields path? i.e.

PERSON.addr => STUDENT.addr  (since STUDENT does not declare .addr in itself, but inherited from PERSON)
PERSON.addr => FACULTY.addr  (same as above)

mw66 avatar Mar 01 '24 19:03 mw66

with:

	ec  -c_compile -config src/system.ecf

I tried this, the command runs fine without any error, but the executable EIFGENs/gedoc/W_code/gedoc is not updated.

Forcing the C compilation by calling finish_freezing will regenerate the same executable. What is updated after an incremental compilation is the .melted file, not the C files. If you want the C files to be updated, add the command-line option -freeze.

ebezault avatar Mar 01 '24 19:03 ebezault

I got the renamed fields path. Now, how do I get inherited (instead of self-declared) fields path? i.e.

PERSON.addr => STUDENT.addr  (since STUDENT does not declare .addr in itself, but inherited from PERSON)
PERSON.addr => FACULTY.addr  (same as above)

OK, so you are not just interested in renamed attributes anymore, but in all inherited attributes! Perhaps you should combine the two implementations (traversing the parent clauses for the renamings, and the first and other precursors to detect those which are not renamed). If it has a precursor, it means that it is inherited. The reverse is not true as we could see in case of replication.

Otherwise, in ET_CLASS.queries, the queries between 1 and `declared_count' are declared in the current class, and above they are inherited. But be careful, declared in the current class does not mean that they have been introduced in this class. It can also mean that they are inherited and redefined.

ebezault avatar Mar 01 '24 19:03 ebezault

I'm sorry that the solution is not as easy as it could be. This code has been written for the specific needs of gec, so it might be missing some metadata which were not needed by gec. But hopefully it should be possible to reconstruct what you need by combining several approaches, as suggested above.

ebezault avatar Mar 01 '24 19:03 ebezault

I'm still interested in renamed attributes, but need to dig deeper: basically I want to detect renamed attributes that coming from the same origin (e.g. in the case of diamond problem).

So:

[renamed field] STUDENT.addr => RESEARCH_ASSISTANT.student_addr
[renamed field] FACULTY.addr => RESEARCH_ASSISTANT.faculty_addr

I need to dig deeper:

PERSON.addr => STUDENT.addr  (since STUDENT does not declare .addr in itself, but inherited from PERSON)
PERSON.addr => FACULTY.addr  (same as above)

and find out RESEARCH_ASSISTANT.student_addr and RESEARCH_ASSISTANT.faculty_addr are both originated from the same PERSON.addr. This is the goal I want to detect.

Sounds like I need to take this 1st approach:

Perhaps you should combine the two implementations (traversing the parent clauses for the renamings, and the first and other precursors to detect those which are not renamed). If it has a precursor, it means that it is inherited. The reverse is not true as we could see in case of replication.

I will give it a try.

mw66 avatar Mar 01 '24 19:03 mw66

I just remembered that there is another data at your disposal (which you can use in combination with all the above):

  • ET_FEATURE.implementation_class tells you where the current feature was written,
  • ET_FEATURE.implementation_feature is the object of this feature in ET_FEATURE.implementation_class (which might be different from the object of this feature in the current class, in case of renaming for example, but it can also be when joining or when changing the export status among other things).

ebezault avatar Mar 01 '24 20:03 ebezault

  • ET_FEATURE.implementation_class tells you where the current feature was written,

what does "where the current feature was written" esp "written" mean? first time introduced? or last time (final)?

mw66 avatar Mar 01 '24 20:03 mw66

  • ET_FEATURE.implementation_class tells you where the current feature was written,

what does "where the current feature was written" esp "written" mean? first time introduced? or last time (final)?

Last time. "Written" means in which class file can I find its text. So if I have:

class A
feature
     f
          do
                ...
          end
end

class B
inherit
      A
             redefine
                    f
             end
feature
      f
          do
                ...
          end
end

class C
inherit
       B
end      

then feature C.f is written in class B.

And a more complicated example:

class D
feature
     f
          do
                ...
          end
end

class E
inherit
       D
            undefine
                  f
           end
       C
end

then E.f is written in class B (because the version from D is undefined).

ebezault avatar Mar 01 '24 20:03 ebezault

Tried this, but it's not working for PERSON.addr:

If it has a precursor, it means that it is inherited.

My code is here: https://github.com/joortcom/gobo/blob/eiffel_rename/tool/gedoc/src/gedoc_field_rename_format.e#L125-L136

output is here: https://github.com/joortcom/gobo/blob/eiffel_rename/tool/gedoc/test/rename/inherited_fields.path

it worked for things like this one:

https://github.com/joortcom/gobo/blob/a3af3982a213fdbf3886d1e4c52a505f2697be9b/tool/gedoc/test/rename/inherited_fields.path#L43

but not for PERSON.addr => FACULTY.addr.

Will continue try the other method ET_FEATURE.implementation_class.

mw66 avatar Mar 01 '24 21:03 mw66

If it has a precursor, it means that it is inherited. The reverse is not true

So we are unfortunately in the case where "the reverse is not true" (no precursor does not mean that it is not inherited).

ebezault avatar Mar 01 '24 21:03 ebezault

Looking at your code, I still recommend using:

if l_first_precursor.name.same_feature_name(query.name) then

instead of:

if l_lower_name.is_equal(query.lower_name) then

It's CAT-call free.

ebezault avatar Mar 01 '24 21:03 ebezault

I just remembered that there is another data at your disposal (which you can use in combination with all the above):

  • ET_FEATURE.implementation_class tells you where the current feature was written,
  • ET_FEATURE.implementation_feature is the object of this feature in ET_FEATURE.implementation_class (which might be different from the object of this feature in the current class, in case of renaming for example, but it can also be when joining or when changing the export status among other things).

This approach worked:

??? field: PERSON.addr => FACULTY.addr
[renamed field] STUDENT.addr => RESEARCH_ASSISTANT.student_addr
??? field: PERSON.addr => STUDENT.addr
[renamed field] FACULTY.addr => RESEARCH_ASSISTANT.faculty_addr
??? field: PERSON.addr => RESEARCH_ASSISTANT.student_addr
??? field: PERSON.addr => RESEARCH_ASSISTANT.faculty_addr
??? field: PERSON.addr => RESEARCH_ASSISTANT.addr

But now, I want to filter out non-direct super-sub-class relationship, i.e filter out:

??? field: PERSON.addr => RESEARCH_ASSISTANT.student_addr
??? field: PERSON.addr => RESEARCH_ASSISTANT.faculty_addr

So how to get a ET_CLASS's direct super-classes / sub-classes?

mw66 avatar Mar 01 '24 22:03 mw66

So how to get a ET_CLASS's direct super-classes / sub-classes?

You get the direct super-classes with ET_CLASS.parent_clauses. For the direct sub-classes, you will have to build them yourself. You can have a look at ET_CLASS.descendants which builds all sub-classes (direct and indirect).

ebezault avatar Mar 01 '24 22:03 ebezault

Is there a way to get rid of this: is_equal for class name:

https://github.com/joortcom/gobo/blob/eiffel_rename/tool/gedoc/src/gedoc_field_rename_format.e#L124

			if not query.implementation_class.upper_name.is_equal(a_class.upper_name) then

mw66 avatar Mar 01 '24 22:03 mw66

if not query.implementation_class.name.same_class_name (a_class.name) then

ebezault avatar Mar 01 '24 22:03 ebezault

You can also write:

if query.implementation_class /= a_class then

which is probably better because it is possible to have two classes with the same name from different libraries.

ebezault avatar Mar 01 '24 22:03 ebezault

And how about compare:

a ET_CLASS and a parent.type is the same class?

mw66 avatar Mar 01 '24 22:03 mw66

And how about compare:

a ET_CLASS and a parent.type is the same class?

if l_parent.type.base_class = l_class then

ebezault avatar Mar 01 '24 22:03 ebezault