Reducing dependencies in ec


Overview

Actually the compiler and compiler_api cluster (Src/Eiffel/eiffel and Src/Eiffel/api) are full of unwanted dependencies on other component. What could be really useful is to break those dependencies to be able to have a compiler library (compiler.ecf for instance). Since this is a long task, we'll go step by step, listing the current issue and bad dependencies on other component (for instance compiler depends on graphical classes, or debugger classes which are fake in batch ec .. and this is no good).

So please when changing some code in the compiler or in any component, please try to reduce dependencies, when we'll have a compiler.ecf this will be much easier to make sure no bad dependencies are introduced.

Goal

The goal is to remove any inter-dependencies between the compiler, user interface elements, code tools and possible code generation. Once accomplished tools or other libraries can be created to compile Eiffel source code into a binary representation.

The final result should be modeled such that distinct regions specifying function can be extracted from any application using the compiler library:

Compiler split overview.png

Current issues

  • profiler, queries and query_language folders should be moved out of Eiffel/api
we should create Eiffel/utilities for instance to welcome those 3 folders.

Compiler library

Defining the Eiffel Compiler's Job

The compiler library, as its name suggests, should only contain the compiler. This question is not deep enough. First we must ask What is a compiler? The question does not refer to to global notion of what is a compiler, as this has many representation by a number of disparate bodies, but must retain most of its logic. That is to say the answer cannot be - a mechanism for instilling an inert object with a form of kinetic energy to aid locomotion - this is not a compiler. At better question; What should an Eiffel compiler do?

This page is dedicate to the separation of peripheral tools that used compiled data to produce an output or tools that hook into the compiler to modify the compilation process, from the compiler. Given that, the question What should an Eiffel compiler do? is discussed in another page. The following content will be based on this information.

What Should Be Included in the Compiler Library

The compiler library should, of course, contain the compiler. This should be in it's most rudimentary form without any attachment to any tool or API that does not perform the job of translating the Eiffel source text (code) into a binary representation, where the binary representation be a final executable on a target system or the generate byte code pending machine code dependent translation.

Although the final executable stage seems logical, by modern definition of the term Compiler this does not have to be the case. Instead of remarking the compiler library as a "library" it's possible that we should create a framework instead. The framework leaves holes (hooks) for translating the intermediate byte code to a final platform dependent binary.

What Should Be Excluded From the Compiler Library

Currently there are independencies in the compiler that need to be removed and independent clusters/libraries created from them. The know areas cover:

  • Documentation generation (Eiffel\flat_short\bench\)
  • Debugger manager: in E_PROJECT.melt the debugger_manager is used to refresh breakpoints data : done
  • Query language (Eiffel\api\query_language)

Library vs Framework

At first creating a library for the compiler seems logical. By convention, in most cases, a compiler takes a collection of encoded files to product, as an end result, a platform dependent binary. Such a concept can be wrapped and delivered in a library. However the Eiffel compiler supports multiple code generation choices. For now, on a Windows platform, there is classic and .NET code generation. Although a complete solution, this is not a compact or elegant solution.

  • Non-.NET platforms include dummy class implementation that have to be updated to synchronize changes made for the .NET-applicable platforms class implementation. This is a source of broken builds, bulks the finalize compiler binary size and leads to confusion in the Open Source (OS) community regarding their placement.
  • There are tools, such as EiffelEnvision and Eiffel for ASP.NET that do not have a shred of interest in classic code generation, yet are forced to use compiler that support it. In reverse there may be OS tools that compile classic code yet are burdened with .NET code generation classes.

It is therefore considerable to establish the compiler library instead as a compiler framework.

The work has already been accomplished to take the Eiffel source text and product an intermediate byte code representation, subsequently used by both code generators. Such work would lend itself to the possibility of providing a hook to generate code for the respective code-spec (classic or .NET) and/or platform. Such a hook could permit the attachment of an applicable code generator(s) at compile time, without the need to compile in all code generators into the compiler. Such a mechanism can be perform though the use of a simple factory function place in a framework root class.

deferred class COMPILER
 
feature {NONE} -- Factory
 
    code_generator (a_cfg: SOME_CFG_CLASS): CODE_GENERATOR is
            -- Retrieve code generator for `a_cfg'.
            -- `a_cfg' contains details about compilation and project configuration so the correct
            -- generator can be instantiate.
        require
            a_cfg_attached: a_cfg /= Void
        deferred
        ensure
            result_attached: Result /= Void
        end
 
end -- class {MY_COMPILER}

With such an approach it could be entirely possible to compile an alternative platform binary on any given platform, permitting the existing of a platform specific code generator and compatible C/C++ compiler. It would also be possible to hook a Java byte-code generator, which would not require a C/C++ compiler at all.

A Packaged Compiler Framework

If the compiler is to be packaged as a framework it should be as simple as possible to get start. The introduction of a deferred COMPILER class could be the primary extension point for the framework. Any implementation of COMPILER should only reference the rudimentary internals of the Eiffel compiler and not as to compile as little code as possible. As an example, from the example above, if code_generator was to return a default object which happens to be the code generator for classic and .NET the value of what is trying to be achieved is lost.

All compiler projects should derive COMPILER, implemented all deferred features and redefine any appliable features. The specialized COMPILER implementation should then be used as the root class, or at least a facade for execution of the compiler functions.

Milestones

Here are the milestone targets to achieve

Milestone 1 (Completed)

M1 is simple. It involves moving out code that has light dependencies.

  • Remove Debugger: DONE
  • Remove Query Language: DONE
  • Remove Profiler. Seems DONE

Milestone 2 (Completed)

M2 concerns itself with abstraction in preparation for M3. This involves the creation of a compiler framework that can be extended by a compiler package.

  • Add a framework root class COMPILER: DONE
  • Add a series of interfaces required for abstract integration: DONE

The framework for the compiler can be found under Src/Eiffel/API/framework. No fully documentation exists on using the framework or the service-base architecture introduced. Class header comments are as verbose as possible given the time. Full documentation will appear on the Wiki soon. In addition these classes are not finalized but a in research and so are subject to radical changes.

The service-coupling implementation and interfaces can be found at Src/framework/patterns/service_coupling

Milestone 3

M3 is removing more complex ingrained interdependencies. The most complex identified so far is the documentation

  • Extend framework to abstract documentation generation.
  • Remove Documentation generation by implementing abstract interfaces and have internals interacting through the interfaces only.

Removing Documentation

Throughout the compiler there are classes that are responsible for documenting themselves using a TEXT_FORMATTER or various derivations. All of these routines need to be extracted. The proposed method would be to retrieve a documentation visitor formatter (one for errors and warning, one for code related AST formatting, ...) via a documentation visitor factory class, taken from the COMPILER framework.

deferred class COMPILER
 
feature -- Access
 
	documentation_factory: DOCUMENTATION_VISITOR_FACTORY
		deferred
		ensure
			result_attached: Result /= Void
		end
 
feature {TEXT_STREAM_ACCESS} -- Output
 
	typed_stream (a_type: UUID): TEXT_STREAM
		require
			is_known_stream: is_known_stream (a_type)
		local
			l_table: like internal_stream_table
		do
			l_table := internal_stream_table
			if l_table.has (a_type) then
				Result := l_table [a_type]
			else
				Result := stream (a_type)
				l_table.put (Result, a_type)
			end
		ensure
			result_attached: Result /= Void
			internal_stream_table_has_a_type: internal_stream_table.has (a_type)
		end
 
feature {NONE} -- Output
 
	stream (a_type: UUID): TEXT_STREAM
		require
			is_known_stream: is_known_stream (a_type)
		deferred
		ensure
			result_attached: Result /= Void
		end
 
feature {TEXT_STREAM_ACCESS} -- Query
 
	is_known_stream (a_type: UUID): BOOLEAN is
			-- Determines if `a_type' is a known type of text stream
		do
			Result = a_type = output_stream or a_type = error_stream
		end
 
feature {NONE} -- Steam types
 
	output_stream_type: UUID is
			-- UUID of output stream
		once
		end
 
	error_stream_type: UUID is
			-- UUID of error stream
		once
		end
 
end -- class {COMPILER}

COMPILER has been augmented with the ability to retrieve a TEXT_STREAM. TEXT_STREAM is a fully deferred class whose interface permits writing basic data to an implementation. Routines such as put_string and put_integer_32 are defined but not implemented.

Streams are retrieved by a unique id, which is a UUID. UUID are as a unique id as can be. The implementation model is to free the framework from specifying how many output text streams there are. All output could be piped to a single stream, in CUI mode there is a standard and error stream. EiffelStudio ups the stream count by introducing a warnings pane that makes a minimum for three streams. EiffelStudio actually uses OUTPUT_WINDOW and TEXT_FORMATTER for writing text to the output window. Now TEXT_FORMATTER should inherit and implement TEXT_STREAM.

deferred class DOCUMENTATION_VISITOR_FACTORY
 
inherit
	TEXT_STREAM_ACCESS
		export
			{NONE} all
		end
 
	SHARED_COMPILER
		export
			{NONE} all
		end
 
feature -- Factory
 
	create_error_visitor: ERROR_DOCUMENTATION_VISITOR [TEXT_STREAM]
		deferred
		ensure
			result_attached: Result /= Void
		end
 
	create_warning_visitor: WARNING_DOCUMENTATION_VISITOR [TEXT_STREAM]
		deferred
		ensure
			result_attached: Result /= Void
		end
 
	create_output_visitor (a_stream: TEXT_STREAM): OUTPUT_DOCUMENTATION_VISITOR [TEXT_STREAM]
		deferred
		ensure
			result_attached: Result /= Void
		end
 
end -- class {DOCUMENTATION_VISITOR_FACTORY}

Using a case study for EiffelStudio, the revised version of TEXT_FORMATTER and/or OUTPUT_WINDOW can be used to implement a concrete class for each visitor of DOCUMENTATION_VISITOR_FACTORY. Simply convariently redefine each visitor factory function, e.g. create_output_visitor: OUTPUT_DOCUMENTATION_VISITOR [OUTPUT_WINDOW]. EiffelStudio could also extend DOCUMENTATION_VISITOR_FACTORY to expose visitors for the debugger etc.

For other compiler documenting classes, such as DEGREE_OUTPUT, these classes are to be fully deferred and implemented as necessary by the compiler framework consumer application

To Be Continued...