Reducing dependencies in ec

Revision as of 16:49, 6 November 2006 by Paulb (Talk | contribs) (Goal)


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 (Work in progress, not ready for review)

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 page. The following content will be based on this information.

What should be included in compiler lib

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.

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.

This is not where the argument ends. There needs to be a discussion on the existing compiler and its specific implementation to support multiple platforms. An example is CLASS_C with it features such as assembly_info and set_assembly_info, which pertain to .NET and include .NET specific types that are of no use on a Unix platform. Although embedded in the currently implementation, there are ways around removing this implementation an proffering it via an extension attribute. Extension attributes are a basic attribute accessible to clients and provider an abstract mean of retrieving extension data or metadata. The attribute may be specifically types for clarity or abstractly typed for genericity.

class CLASS_I

feature -- Access

    extension: ANY is
            -- Extension object
        do
            --| Implementation
        end

    --| Members elided for clarity

end -- class {CLASS_I}

Alternatively there is the choice of factories for creating abstract objects, which have advantages and disadvantages.

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.

What Should Be Excluded From the Compiler Library

  • 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)

Interacting and Hooking Into the Compiler

With a clean break there needs to be a standardized means of proffering clients with the ability to set/retrieve custom data on closed compiler types.

There are already a number of common proven patterns used by the compiler. Factories and Visitors are a concrete way of providing extension to existing implementation through derivation. However such patterns cannot always be used. One attractive method is the use of an extender permitting clients to set and retrieve name mapped data via a common interface. An example of a base implementation for all such classes is described below:

indexing
	description: "[
		Abstract base implementation for all objects proffering access to extension data
	]"
	date: "$Date$"
	revision: "$Revision$"

deferred class
	EXTENDER

feature {NONE} -- Initialization

	make is
			-- Initialize extender.
		do
			create internal_extenders.make (1)
			internal_extenders.compare_objects
		end

feature -- Access

	extender_names: LIST [STRING_8] is
			-- Maintained list of registered extender names	
		do
			create {ARRAYED_LIST [STRING_8]}Result.make_from_array (internal_extenders.current_keys)
			Result.compare_objects
		ensure
			result_attached: Result /= Void
			result_compares_objects: Result.object_comparison
		end

feature -- Extension

	put_extender (a_obj: EXTENSION_OBJECT; a_name: STRING_8) is
			-- Inserts extender `a_obj' id-ed with `a_name'
		require
			a_obj_attached: a_obj /= Void
			already_has_extender: extender (a_name) = Void
		do
			internal_extenders [a_name] := a_obj
		ensure
			has_extender: extender (a_name) = a_obj
		end

feature -- Query

	extender (a_name: STRING_8): EXTENSION_OBJECT is
			-- Retrieve extension object using extender id `a_name'
		require
			a_name_attached: a_name /= Void
			not_a_name_is_empty: not a_name.is_empty
			extender_names_has_a_name: extender_names.has (a_name)
		do
			Result := internal_extenders [a_name]
		ensure
			result_attached: Result /= Void
		end

feature {NONE} -- Internal implementation cache

	internal_extenders: HASH_TABLE [EXTENSION_OBJECT, STRING_8]
			-- Extender map
			-- Key: Case-sensitive extender name
			-- Value: Extender object

invariant
	internal_extenders_attached: internal_extenders /= Void
	internal_extenders_compares_objects: internal_extenders.object_comparison

end -- class {EXTENDER}

The extender exposes a typed data entity EXTENSION_OBJECT used to ensure basic single data (a single reference or expanded instance) is not stored and retrieved directly, but a new class is authored to expose a collection of attributes/functions specific to the extender. Use of single data, especially expanded, there is little way to guarantee the existence of extension data. With expanded a client will always receive a default initialized instance if no data exists. It can be argued that clients should test the existence of extension data with a call to {EXTENDER}.extender_name.has (...) however for performance reasons this may not be the optimal solution.

indexing
	description: "[
		Base class for all extension object accessible through extenders
	]"
	date: "$Date$"
	revision: "$Revision$"

class
	EXTENSION_OBJECT

end -- class {EXTENSION_OBJECT}

to be continued

Using a Service Base Architecture

One very elegant solution to revoking hardwired dependencies is switching to a service-base architecture. To review details on a proposed service-based framewor implemenation for Eiffel, see Implementation of a Service-Base Architecture in Eiffel.