eC - Overview
Fork me on GitHub

Overview

A quick look at the features eC provides on top of C

Free Open Source Software

The eC compiler is released as part of the Ecere SDK under a revised BSD license.

Documentation

  1. Samples (some prebuilt and described here)
  2. The Ecere Tao of Programming
      (Programmer's guide, work in progress)
  3. Coursework
      (Exercises and lab suggestions)
  4. An Introduction to building an eC application with the Ecere SDK
      (Building a simple Tic Tac Toe game)
  5. eC Object Notation (ECON)
      (A JSON superset with extra features)

Compiles Natively Just Like C

eC is compiled and runs natively on the target platforms ensuring maximum performance. The language has been designed keeping C performance in mind.

C superset, C ABI

eC is a superset of C and its ABI is fully compatible with C. Standard C programming can be used anywhere inside eC modules, and other object modules can be linked to eC code as if it was a standard C module.

Modules, an alternative to header files

While it is still possible to include header files to make use of code in C libraries or modules, eC does away with a need for header files while writing eC libraries or modules. Instead, it features an import mechanism making all constructs of imported modules available without requiring to write a separate file. Furthermore, there is no need for forward declaration (prototypes).

         import "ecere"

         import "gameAI"
         

As an alternative to simple #define macros not taking parameters, eC lets you define identifiers which resolve to expressions, and can be exported from a module or library without headers.

         public define Pi = Radians { 3.14159265 };

         public define myColor = Color { 85, 85, 85 };
         

Module restricted access control

eC offers better control over what is visible when building an API library. Library developers don't need to worry about the private content of the class definition being seen by the end user, only what is declared public will be visible. eC provides three level of access control:

  • static : Current source file only

  • private: Private to library (.dll, .dylib or .so)

  • public : Accessible wherever the module is accessible from

On the other hand, eC offers no restriction regarding member access between classes of the same access-level module, as we feel that is counter-productive and results in writing unnecessary accessor functions which negatively affect performance. Developers are instead encouraged to modularize using different source files and libraries to ensure the desired access level for encapsulation.

Powerful and Extensible Type System

All variables and literals can be thought of as objects because all data types are treated in an object-oriented manner, and virtual methods are defined and can be overridden to control how data is (de)serialized, (de)stringified, sorted, visualized and edited. GUI common controls, distributed objects, Ecere Data Access, generics and containers all benefit from this.




Object Oriented Features

eC offers a number of modern object oriented features, the syntax for which should feel familiar to programmers experienced with either C++, C# or Java.

Classes can encapsulate data, methods as well as properties. eC supports polymorphism through both subclassing and generics. Even though eC has a class data type, all data types including standard C types are essentially classes, as pertaining to the type system.




Instantiation

A distinctive feature of eC is its curly braces instantiation syntax, inspired from the syntax for list initialization in C. Instantiations can either be named (and are considered declarations):

Point p { 123, 23 };

or anonymous (and are expressions):

Point { 123, 23 }

If member names are not specified, the order of declaration of public members determine to which ones those values are assigned. eC also allows explicitly specifying to which instance members to assign, for greater clarity and convenience, omitting the dot required by the similar C99 syntax in list initializations. Specifiying the members is required for private members. Since Point declares x before y, the above is equivalent to:

Point { x = 123, y = 23 };

If a data type is expected, the class is inferred and can be omitted:

Point p;
p = { 123, 23 };
         

The instantiation syntax is heavily used in eC and member assignment replaces the use of parameters for constructors, which never take any.

Properties

In addition to methods and data members, classes can have member properties, a useful way to encapsulate data. Properties are accessors which can cause code to be executed, but their syntactic usage is the same as regular data members. For classes used with an Object Designer (such as the GUI designer), they will show up in IDE property sheet and can be edited while providing instant visual feedback.

         class Pen
         {
            Color color;
            public property Color color
            {
               get { return color; }
               set { color = value; }
            }
         }

         void demo()
         {
            Pen pen { };
            pen.color = red;
         }
         



Classes

eC distinguishes between class and struct, unlike C++ where the only distinction is the default access mode. One reason behind this is to avoid the need to think of handles for dynamic objects as pointers, and the added complexity and need to be familiar with them for writing basic object oriented programs. Instances of a class are always allocated on the heap, and thus the underlying implementation of a class instance is a pointer, but the developer does not need to think of them as such. The '.' operator is still used to refer to members rather than '->'.

As a class is meant for more complex objects which may manage other dynamic objects or some other form of allocated resources, it has more features which are not available in the simpler struct:

  • Class methods can be virtual, and each instance has a virtual function table which can be proper to itself, or point to its class methods if it does not contain an override at the instance level. They can also set default values for any members of a class, and have constructors and destructors (which are always virtual).

  • An instance of a class will also have a pointer to the class itself (runtime type information) which is useful for reflection.

  • It has a reference count which serves for memory management.

         class SampleClass
         {
            MemberClass m { };
            int a;

            a = 123;

            virtual void vMethod()
            {
               PrintLn("vMethod default");
            };

            byte * data;

            MyClass()
            {
               data = new byte[100];
            }

            ~MyClass()
            {
               delete data;
            }
         }

         SampleClass object
         {
            void vMethod()
            {
               PrintLn("object's vMethod");
            }
         };

         void demo()
         {
            Class c = object._class;
            incref object;
            // Call object's own implementation
            object.vMethod();
         }
         

Structs

The struct on the other hand are meant to be used as C structures. They have no overhead (consists only of data members) and are allocated 'in place', e.g. on the stack for local function variables, or as part of the bigger memory block of a containing class. They are useful for small self-contained data types, e.g. Point, objects we want allocated on the stack, objects to be stored contiguously in arrays. They typically do not hold allocated memory. The same instantiation syntax is used for both class and struct. Functions taking a struct as a parameter will automatically see it passed with an extra indirection level without having to declare it as a pointer, unless declared using the C syntax struct NameOfTheStruct rather than simply NameOfTheStruct. This naturally discourages pushing big data blocks to the stack. The developer still uses the '.' operator to access members and must understand that it is the same object that was passed, and thus not perform unwanted modifications.

         struct Point
         {
            int x, y;
         }; // Semicolon required for struct

         double length(Point p)
         {
            return sqrt(p.x * p.x + p.y + p.y);
         }

         void demo()
         {
            Point * points = new Point[100];
            Point a { 3, 4 };
            double l = length(a); // passed by address
            l = length({ 6, 8 });
            delete points;
         }
         

It is possible to allocate a struct dynamically on the heap using the new operator, but this then introduces an indirection level and the pointer notation must then be used. As an alternative, eC features a compromise between class and struct (class : struct), allowing to strip classes of their overhead information so that it contains only data members. Such a class will behave the same way as a regular class, except it will not support reference counting, runtime type information, or virtual functions (destructors will not be virtual either). It is typically used for numerous objects for which we don't need the overhead of regular classes, but which are allocated individually, such as those to be stored in linked lists. Because of this, only some particular scenarios calls for the extra reference level and the -> operator.

         class Link : struct
         {
            Link prev, next;
         }

         void demo()
         {
            Link a { }; // On the heap
            Link b { };
            a.next = b; // Still accessed with '.'
            b.prev = a;
         }
         



Subclassing

eC supports class inheritance. A class inherits all the members of a base class. If a method is virtual, invoking it on an instance will call the highest derivation level class, regardless of how the variable used for the call is typed.

         class BaseClass
         {
            virtual void doStuff()
            {
               PrintLn("base");
            }
         }

         class DerivedClass : BaseClass
         {
            void doStuff()
            {
               PrintLn("derived");
            }
         }

         BaseClass a { };
         DerivedClass b { };

         void demo()
         {
            a.doStuff();
            b.doStuff();
         }
         

Generics

eC supports parametric polymorphism of classes with types. Since eC does not rely on header files, instantiating a generic class does not involve a separate compilation. Only the generic class definition is compiled and used for all parameterizations. Instead eC relies on a common representation for all types and its type system to take proper action based on a type parameter. The syntax for generic uses the angled brackets, as in C++. Complex cases with recursions are supported as well, as can be seen here:

class A<class T> { T test; }
class B : A<B> { int a; }

Containers

eC provides containers (built using generics) for dynamic arrays (Array), link lists (List for use with an arbitrary type, LinkList for a type embedding links to avoid an extra indirection level), associative arrays (Map), as well as self balancing binary trees (AVLTree).

Array literals are supported and eC offers a for syntax to iterate through elements, with an optional condition:

         for(i : [ 0, 1, 2, 3 ]; i > 0)
            PrintLn(i);
         

A container' contents can be initialized with a clear syntax:

         Map<String, int> wordCount
         { [
            { "Foo",       1 },
            { "Bar",       2 },
            { "Cool",      3 },
            { "Language",  4 }
         ] };

Associative arrays support the indexing operator:

         const String s = "Bar";
         wordCount[s]++;

The Iterator class has a concise iteration syntax, and containers can be used in a generic manner, regardless of the implementation:

         void test(Container<int> c)
         {
            Iterator<int> i { c };  
            while(i.Next())
               PrintLn(i.data);  
         }
         

New container classes can be defined, and can be used to iterate through infinite collections, or through data contained outside the actual container class.




Unit Types

Another kind of data types in eC is unit types, meant to hold scalar values associated with a particular unit of measurement, e.g. Meters or Degrees. Types for different units for the same measurement can be related by simply defining a forward and reverse conversion to one default unit for that measurement. This has the advantage of great simplification for adding new related units, over manually overloading every type of operator; only a single pair of conversion must be defined. Still once these units are defined, operations between related units or conversion for assignment or passing as a parameter is automatically handled properly. (eC does not yet feature operator overloading, but there are plans to add it to support vector operations and string concatenation among other things).

         // Declare Distance units to use doubles
         class Distance : double;
         // Meters as default (no conversion needed)
         class Meters : Distance
         {
            property Distance {}
         };

         class Centimeters : Distance
         {
            // Conversion to Meter (100cm / m)
            property Meters
            {
               set { return value * 100; }
               get { return this / 100; }
            }
         };
         

Conversion Properties

These auomatic conversions are defined through conversion properties, which share a syntax similar to properties except the property does not define a name. Different kind of objects can be automatically converted from one type to another. If a conversion is loaded from a prebuilt library when compiling and applied to constants, the conversion is precomputed and thus does not affect the performance of the code (e.g. angles are always stored in Radians, but despite using a more familiar Degrees unit the compiler will precompute the Radians value).

         class Feet : Distance
         {
            // Conversion to Meter (~3.28 feet / m)
            property Meters
            {
               set { return value * 3.28083f; }
               get { return this / 3.28083f; }
            }
         };

         void demo()
         {
            Meters a;
            a = Feet { 12 } + Centimeters { 3 };
         }
         



Context-sensitive Enumerations

Enumerations in eC are context-sensitive, thus allowing to reuse a generic value for multiple enumerations (e.g. none). They support subclassing as well to add new values while inheriting values of an existing enumeration.

         enum LineStyle
         {
            none, solid, dashed, dotted
         };

         enum FillStyle
         {
            none, solid, hatch, stipple
         };

         enum ExtFillStyle : FillStyle
         {
            bitmap, gradient
         };

         void demo()
         {
            FillStyle fs = solid;
            LineStyle ls = none;
            int efs = ExtFillStyle::gradient;
         }
         

Bit Collection Classes

Another useful data type is bit collection classes, which defines a class-like type (where members are accessed with .), yet the entire value is stored in one scalar integer type. It is meant as an alternative to numerous #define macros often seen in C headers definining how to mask a particular bit or multi-bit values within a larger integer for flags collection. It also lends itself very well to things like Color classes and conversion from one color depth to another.

         class Color : uint
         {
         public:
            // Specifying which bits to use
            byte r:8:16, g:8:8, b:8:0;
         }

         class Color565 : uint16
         {
         public:
            byte r:5:11, g:6:5, b:5:0;
            property Color
            {
               set { return Color565 { value.r >> 3,
                  value.g >> 2, value.b >> 3 }; }
               get { return Color {r<<3,g<<2,b<<3}; }
            }
         }

         Color565 orange16Bit = Color { 255, 128, 0 };
         



Memory Management

eC already takes care of many aspects of memory management through reference counting. However, it does not automatically manage memroy allocated with the new operator, which must be manually freed with the delete operator. Furthermore, to avoid the overhead and recognizing the limits of reference counting, reference count increments and decrements is left to the user, except for the initial construction (at instantiation) and destruction of the object (when it goes out of scope). At the moment this automatic behavior is also limited to global and member instances, other instances currently start with a 0 reference count and must be manually destroyed with delete.

We plan to improve on this to make the behavior more consistent. (See related tracked issue#513).

eC also provides its own memory error detection library to facilitate identifying and rapidly resolving the unavoidable typical memory problems associated with memory management in an non-managed environment such as leaks, free memory writes & reads and buffer overruns & underruns. It can be easily turned on during the debugging and will provide full call stack information as to where the problem is occuring. The Ecere IDE also provides integration with Valgrind for reporting memory leaks.




Dynamic Module Importing

Just as one can import an eC library through the import keyword, eC shared libraries can also be dynamically imported at runtime. This is most useful as it provides a built-in plug-in architecture model.

The Ecere IDE for example makes use of this feature to support the integration with the Object Designer, such as the Form designer for the Ecere GUI.

Reflective Object Model

Object oriented code written in eC is built around a reflective object model framework. All required information about the programming interfaces is readily and dynamically available. Classes can be instantiated and methods can be called using textual names. This also makes it ideal for automatically generating binding to other languages completely automatically. The unique features of the language offers a solid foundation on which to build a uniform Application Programming Interface using eC that will scale nicely to other languages, since eC was meant as a framework to easily build, publish and maintain classes. This powerful model can also be used in a distributed manner: it supports using objects across a network the same way as if they were local objects.