Table of contents


General code structure

Optional semicolons

Off-side rule (indentation indicates block scope)

Optional parentheses around conditions

Message passing using dot notation

Keywords

Objective-C keywords without ‘@’

Logical and bitwise operators and, or, not, etc.

The goto statement is illegal

Namespaces

Built-in symbol search on names with the “NS” prefix

User-defined prefix namespaces

Variables and literals

Class types are pointer types

Local type inference

No variable shadowing

NSString literals using single quotes

No ‘@’ needed for array and dictionary literals

No ‘@()’ needed for primitive members of array and dictionary literals

Mutable array and dictionary literals

Variable definitions using type inference and nil or Nil

Selector and protocol literals

NSRange literals (and expressions) using ‘..’ or ‘…’

NSRange variables and literals in for in loops

Underscores permitted in numeric literals

Stricter enum type checking

Classes

Default superclass is NSObject

Method parameter type names are not enclosed in parentheses

Method parameters have default variable names

Method return type specified by trailing return, void by default if omitted

Methods are instance methods by default

Optional parameters and default arguments

Default return expression

No curly braces needed around member-variable declarations

Simpler property declarations

[Method instancetype parameters] (#instancetypeparams)

Object operators

Built-in equality and inequality comparison operators

Built-in ‘+’ (binary version) and ‘«’ string operators

Binary operators (effectively, operator overloading)

Array and dictionary subscript operators with integer and object indexes

Object subscript operators with NSRange indexes

Special object casts (boxing and unboxing)

Converting primitive data types to objects (boxing)

Converting objects to primitive data types (unboxing)

Blocks enhancements

Compact blocks

Nested functions (const blocks)

The switch statement

No fall-through

Comma-separated lists of cases

Case-value ranges

Loop enhancements

for-in loop indexing

The preprocessor

Direct #import of Eero and legacy (Objective-C and C) headers

C++ interoperability #pragma

Reserved symbols for future features

import

=>


General code structure

Optional semicolons

Semicolons serve the same general purpose in Eero as they do in C/Objective-C. That is, they terminate and separate statements and declarations. However, with rare exception, they’re optional in Eero. As in Python and Ruby, statements generally end with newlines, but the parser will also handle unambiguous statement continuations, such as dangling commas or arithmetic operators, onto subsequent lines. The parser handles other situations as well.

Motivation: readability, DRY

Off-side rule (indentation indicates block scope)

Like Python (and various other languages), Eero adheres to the off-side rule for block scope. The compiler doesn’t dictate the level of indentation, but it must remain consistent for consecutive lines in a particular block. This mechanism completely supplants the use of curly braces as block delimiters.

Motivation: readability, safety, DRY, WYSIWYG

Optional parentheses around conditions

Eero doesn’t require the first level of parentheses around conditional expressions. This applies to if, for, while, switch, catch, etc..

if counter >= 0
   counter++

Motivation: readability, DRY

Message passing using dot notation

In Eero, you send messages to objects and classes by placing a dot (period) between the receiver and the selector(s) – no square brackets are used. For messages with no arguments, this is exactly the same notation used for Objective-C properties.

If the selector takes an argument, a colon follows the selector. Multi-part selectors are separated by commas.

The dot operator has the standard C (high) precedence against other operators.

id myarray = NSMutableArray.new
myarray.addObject: myobject
myarray.insertObject: myobject, atIndex: 0

Motivation: readability

Keywords

Objective-C keywords without ‘@’

Eero recognizes Objective-C keywords as regular language keywords. They don’t need to be preceded by an ‘@’ character.

interface MyClass : SomeBaseClass
end

Motivation: readability

Logical and bitwise operators and, or, not, etc.

Eero recognizes the alternative logical and bitwise operators introduced in C++. (They’re also available to standard C through the inclusion of header iso646.h, introduced to the C90 standard in 1995.)

if ready and initialized
   counter++

Motivation: readability

The goto statement is illegal

In Eero, goto is still a recognized keyword but its use is forbidden and will result in a compilation error.

Motivation: safety

Namespaces

Neither C nor Objective-C supports namespaces, relying on unique prefixes to prevent symbol collisions. Eero provides a backward-compatible way of obtaining some of the readability benefits of namespaces.

Built-in symbol search on names with the “NS” prefix

When resolving symbol names, the Eero compiler first checks the name as-is, and if not found, tries again with the “NS” prefix. This means you can use the types, functions, non-macro constants, and so on found throughout the Foundation and Cocoa frameworks without “NS”. For example, anywhere a class name is used, you can use “String” instead of “NSString”.

id mystring = String.stringWithUTF8String: "Hello, World"

Motivation: readability

User-defined prefix namespaces

In addition to the built-in “NS” prefix, users can declare their own prefixes to be applied to any scope (file, method, function, etc.). This is done through the using prefix declaration. For example, to find symbol names without providing an explicit “AB” prefix (from the AddressBook framework), use the following:

using prefix AB
...
id theAddressBook = AddressBook.sharedAddressBook 

Motivation: readability

Variables and literals

Class types are pointer types

In Objective-C, objects are always references (pointers to objects), and it is never valid to define object variables as anything but pointers. Eero takes advantage of this fact to simplify things: it treats all variable declarations of a class type as a pointer to that type. This means no asterisk is used in the declaration.

NSString mystring = myobject.description

You can think of class names as typedefs to the equivalent pointer types. So if you do provide an asterisk, it will be a pointer to a pointer:

NSError* error // can be used to pass an error object by reference

These rules apply to casts as well:

NSString mystring = (NSString) someObject

Motivation: readability, DRY

Local type inference

You can declare and initialize local variables using the Eero-specific “:=” operator. For example, the following code declares an integer initialized with a value:

i := 100

The const specifier also works with this operator:

const name := "Danielle"

You can still declare variables in the standard C/Objective-C way, using explicit types:

int i = 100

Motivation: readability, DRY, safety (reduces temptation to use types like id)

No variable shadowing

Eero does not permit variable shadowing. Attempting to re-declare a variable of the same name in an inner scope will result in a compilation error.

counter := 0
if isReady
   counter := 0 // compiler error

Motivation: safety, readability

NSString literals using single quotes

In Eero, you enclose Objective-C/Foundation string literals in single quotes, with no preceding ‘@’ symbol:

mynsstring := 'Hello, world'

Motivation: readability

No ‘@’ needed for array and dictionary literals

As in Objective-C, you can express array and dictionary literals with square and curly brackets, respectively. However, no ‘@’ symbol is needed:

myarray := ['a', 'b', 'c']

mydict := { 'a' : 'A', 'b' : 'B', 'c' : 'C' }

Motivation: readability, DRY

No ‘@()’ needed for primitive members of array and dictionary literals

Members of collection literals having the appropriate primitive data types get automatically boxed via implicit @() directives. This includes variables and literals.

myarray := [1, 2, 3]

mydict := { 'x' : 1.5, 'y' : 2.5, 'z' : somevariable }

Motivation: readability, DRY

Mutable array and dictionary literals

Non-empty array and dictionary literals are immutable. However, unlike Objective-C, empty literals are the mutable versions of their classes, allowing:

mydict : = {}
mydict['a'] = 'A'

Motivation: readability, DRY

Variable definitions using type inference and nil or Nil

If used with a nil or Nil, inferred variable types are of type id or Class, respectively:

myobject := nil  // variable is of type 'id'
myclass := Nil   // variable is of type 'Class'

Motivation: readability, DRY

Selector and protocol literals

While you can still use the selector compiler directive, Eero recognizes selector literals enclosed in vertical bars:

myobject.performSelector: |applicationReady:|, withObject: nil, afterDelay: 0

Enclose protocol literals in angle brackets:

if myobject.conformsToProtocol: <Coding>
   ...

Motivation: readability

NSRange literals (and expressions) using ‘..’ or ‘…’

You can create range literals with integer values (or variables) separated by an ellipsis, which expresses an (inclusive) sequence stored as an NSRange struct. For example:

myrange := 0 .. 10  // the equivalent of NSMakeRange( 0, 11 ) or (NSRange){ 0, 11 }

Note that Eero interprets both two and three consecutive periods as an ellipsis.

Motivation: readability

NSRange variables and literals in for in loops

You can use variable or literal ranges directly in for-in loops:

for int i in myrange
   // perform loop operations

for int i in 0 .. 10
   // perform loop operations

Motivation: readability, DRY

Underscores permitted in numeric literals

For improved readability, underscores can appear within numeric literals. The compiler ignores the underscores, which do not modify the value in any way. Underscores cannot be the first or last character of the literal. Some examples:

const TimeInMilliseconds := 30_000  // 30 seconds
const int MinimumBalanceInCents = 100_00 // 100 dollars
const RGBASolidWhite := 0x_FF_FF_FF_FF
const long double Pi = 3.141_592_653_589

Motivation: readability

Stricter enum type checking

Variables of enum types have stronger type checking than in standard C/Objective-C. Without an explicit cast, they can only be assigned values defined for their specific enum type. Since the enum values carry type information, they can also be used with type inference.

enum RGBColors { RED, GREEN, BLUE }
color := RED // inferred to enum type RGBColors

color = GREEN // valid
color = 0 // compiler error

enum CMYKColors { CYAN, MAGENTA, YELLOW, BLACK }
color = MAGENTA // compiler error

Implicit conversion from an enum value to an integer type is still allowed, however:

int value = RED // valid

Classes

Default superclass is NSObject

When you declare a class but don’t specify a superclass, NSObject is assumed.

interface MyClass // superclass is implicitly NSObject
end

Otherwise, subclassing is done in the same way as in standard Objective-C:

interface MyStringSubclass : String
end

In the rare cases where you need to declare a new root class, specify an explicit superclass of void.

interface MyRootClass : void
end

Motivation: readability (via sane defaults)

Method parameter type names are not enclosed in parentheses

In Eero, you do not enclose method parameter types in parentheses. However, like their message-sending counterparts, you do separate parameters with commas.

 initWithBytes: const void* bytes,
        length: UInteger length,
      encoding: StringEncoding encoding

Motivation: readability

Method parameters have default variable names

If omitted, Eero generates argument variable names derived from their selectors. They’re determined by the following rules, listed by most to least commonly encountered:

  • If the method or parameter name contains words separated by camel case, then the last word (scanning left to right), converted entirely to lowercase, is used. For example, method name “initWithString” results in variable name “string.”

  • The first camel-case word encountered that contains two consecutive uppercase characters (scanning left to right) is used, along with all subsequent words, and no character cases are modified. For example, method name “initWithUTF8String” results in variable name “UTF8String.”

  • If no uppercase characters are encountered, the entire method or parameter name is used. For example, parameter name “encoding” results in variable name “encoding.”

  • If the first character in the method or parameter name is uppercase, the entire name is used. For example, method name “CreateNewString” results in variable name “CreateNewString”

 initWithBytes: const void*,
        length: UInteger,
      encoding: StringEncoding

Motivation: readability, DRY

Method return type specified by trailing return, void by default if omitted

To specify a return type, use a comma followed by the return keyword and a type. Unlike Objective-C, if unspecified, there is no return value for the method. This does not affect Foundation, Cocoa, or any other imported legacy headers.

 initWithBytes: const void*,
        length: UInteger,
      encoding: StringEncoding,
         return id

Motivation: readability, WYSIWYG

Methods are instance methods by default

Like standard Objective-C, whether a method is an instance or class method is determined by a preceding ‘-‘ or ‘+’, respectively. However, the ‘-‘ is optional for Eero.

interface MyStringClass
   + stringWithUTF8String: const char*, return instancetype
   initWithCString: const char*, encoding: StringEncoding, return instancetype
   initWithString: String, return instancetype
end

Motivation: readability (via sane defaults)

Optional parameters and default arguments

Eero supports a shorthand notation for optional method parameters and default arguments. The Objective-C way of doing this – defining a set of methods, some of which omit parameters – is still possible, and can be mixed freely with this notation.

There are two distinct parts to the process: flagging the parameters in the interface/protocol as optional, and specifying default values in the implementation.

For the interface, optional parameters are enclosed in square brackets, following a common technical documentation convention:

interface MyClass
   openFile String, [withPermissions: String], return FileHandle
end

For the implementation, the default values of the optional parameters are assigned using an ‘=’ (equal) after the type or variable name, followed by the default assignment expression:

implementation MyClass
   openFile: String, withPermissions: String = 'r', return FileHandle
      handle := nil
      if permissions == 'r'
         handle = FileHandle.fileHandleForReadingAtPath: file
      else if permissions == 'w' or permissions == 'rw'
         handle = FileHandle.fileHandleForUpdatingAtPath: file
      return handle
end

An added advantage is that the user of the interface should not generally know (or care about) what the default value is; it’s an implementation detail.

Because of this separation of interface and implementation, and like properties’ synthesize, this convenience notation is not required. The individual methods can be constructed manually, like so:

implementation MyClass
   openFile: String, withPermissions: String, return FileHandle
      ...
   openFile: String, return FileHandle
      ...
end

The same holds for interfaces with discrete method declarations and corresponding implementations using the assignment notation. Thus even standard Objective-C interfaces and protocols can benefit.

Optional parameters are restricted to the second and subsequent items. However, required and optional parameters may be alternated. (Thanks to named parameters, there are no C++-like restrictions in this regard.) For example:

interface MyClass
   openFile: String, [withPermissions: String],
                            seekToEnd: BOOL, 
                   [closeAfterReading: BOOL], 
                                return FileHandle
end

Like all other Eero features, this does not introduce any binary breaks. This is just syntactic sugar that generates simple boiler-plate methods “under the hood.”

Motivation: readability, DRY

Default return expression

You can specify a default return expression for a method definition by providing an ‘=’ (equal) after the return type, followed by an expression:

openFile: String, withPermissions: String, return FileHandle = nil
   if permissions == 'r'
      return FileHandle.fileHandleForReadingAtPath: file
   else if permissions == 'w' or permissions == 'rw'
      return = FileHandle.fileHandleForUpdatingAtPath: file

If no return statement is encountered in the method body, the method will return the value of this expression. This also allows compact method definitions for getter-like or stubbed methods:

interface MyClass 
   model, return String = 'G35'
   serialNumber, return String = 'X344434AABC'
end

Motivation: readability, DRY

No curly braces needed around member-variable declarations, only allowed in implementations

Member variables, while still requiring placement at the beginning of an implementation, don’t need to be surrounded by curly braces:

implementation MyClass
   int counter
   id delegate
end

Furthermore, member variables can only be declared in implementations. This restriction removes the fragile base class problem, and obviates the need for private, protected, and public access specifiers (they are now, in effect, always private). This restriction does not apply to legacy headers and frameworks.

Motivation: readability, DRY, safety

Simpler property declarations

Property declarations now look very similar to variable declarations. Attributes, if specified, follow the declaration and are enclosed in curly braces.

interface MyClass
   String name {assign}
   String desc {assign}
end

Motivation: readability

Method instancetype parameters

Objective-C supports the use of instancetype for method return types, but not method parameters. Eero removes this restriction, supporting instancetype parameters as well as return types:

protocol OrderedCollection <FastEnumeration>
   at: UInteger index, return instancetype
   put: instancetype object
end

Motivation: safety

Object operators

Eero supports a limited set of common operators for Objective-C objects. The operators are, in fact, aliases for certain methods. However, the operators follow the same precedence rules as their primitive-data-type counterparts.

For any given operator expression, at least one of the operands must be an object (this includes blocks as right-hand-side operands). Non-object operands of the appropriate primitive type get automatically boxed by an implicit @() directive. Operand autoboxing applies to all binary, but not subscript, operators.

Number mynumber 
...
mynumber + 100  // "100" gets implicitly boxed as "@(100)"

Built-in equality and inequality comparison operators

For all objects, the comparison operators ‘==’ (equals) and ‘!=’ (not equal) map to the isEqual method. The result is negated for ‘!=’. This replaces the low-level address comparison used in standard Objective-C for two objects. This allows valid comparisons of the form:

mystring := MutableString new
mystring.appendString: 'Hello, World'

if mystring == 'Hello, World'
   // gets here, since above condition is true

You can still perform explicit address comparisons by casting both operands to void** types. Furthermore, object comparisons to *nil and Nil literals are always pointer comparisons, since the non-object operand isn’t autoboxed.

Motivation: readability, WYSIWYG

Built-in ‘+’ (binary version) and ‘<<’ string operators

When the ‘+’ binary operator is used, stringByAppendingString will be sent to instances of classes or protocols that respond to it (mainly NSString and its subclasses):

helloString := 'Hello'
worldString := 'World'

helloWorldString := helloString + ', ' + worldString

When the ‘<<’ binary operator is used, appendString will be sent to instances of classes or protocols that respond to it (mainly MutableString):

mystring := ''
mystring << 'Hello, World'

Motivation: readability

Binary operators (effectively, operator overloading)

The following chart shows the supported object binary operators and resulting methods. In the cases of ‘+’ and ‘<<’, defining these methods for a particular class overrides the built-in versions.

OperatorMethod nameNotes
+plusincludes implicit support for +=
-minusincludes implicit support for -=
*multipliedByincludes implicit support for *=
/dividedByincludes implicit support for /=
%moduloincludes implicit support for %=
<isLessThan
<=isLessThanOrEqualTo
>isGreaterThan
>=isGreaterThanOrEqualTo
<<shiftLeft
>>shiftRight

Note that methods isLessThan and isGreaterThan (and “OrEqualTo” counterparts) are found in Foundation protocol NSComparisonMethods, providing convenient comparison operators for many objects.

Motivation: readability

Array and dictionary subscript operators with integer and object indexes

Eero supports the same array and dictionary object subscript operators (‘[]’) found in Objective-C. For platforms that support them, these operators acts as aliases for methods IndexedSubscript, KeyedSubscript, etc.. However, on systems with older versions of the Foundation library that are missing this family of methods, Eero falls back to objectAtIndex, objectForKey, etc..

Motivation: readability, backward compatibility

Object subscript operators with NSRange indexes

Objects indexed using ‘[NSRange]’ that are derived from classes or protocols that respond to method substringWithRange (mainly NSString and its subclasses) will be sent that message. This allows concise string slices:

mystring := 'Hello, World'
worldString := mystring[7 .. 11]

All other object types indexed using ‘[NSRange]’ will be sent subarrayWithRange (found in NSArray and its subclasses). This allows concise array slices:

myarray := ['a', 'b', 'c', 'd', 'e', 'f']
abcRange := 0 .. 2
abcSubarray := myarray[abcRange]

Motivation: readability

Special object casts (boxing and unboxing)

Converting primitive data types to objects (boxing)

“Casting” a primitive data type to an Objective-C class results in the creation of an object of that type (boxing), implicitly using the appropriate method to do so. For example:

unsigned int age = 25
ageAsNumberObject := (Number) age  // same as (Number numberWithUnsignedInt: age)

This works with any class that implements the appropriate numberWith<Type> class methods, not just NSNumber. Note that Objective-C’s ‘@’ boxing operator is still supported and unchanged.

C-style character strings are also supported, implicitly using class method stringWithUTF8String:

const char* helloworld = "Hello, World"
stringObject := (String) helloworld
mutableStringObject := (MutableString) helloworld

Motivation: readability, DRY

Converting objects to primitive data types (unboxing)

“Casting” an Objective-C object to a primitive data type results in a value extraction (unboxing), implicitly using the appropriate method to do so:

age := (unsigned int) ageAsNumberObject

Methods intValue, unsignedIntValue, doubleValue, etc. are used, as determined by the cast type. These messages are sent to objects of any type, so any class that supports these methods (not just NSNumber and its subclasses) can take advantage of them. For example, since NSString supports floatValue, this works:

pi := (float) '3.14159'

Conversion of NSString objects to C strings (technically, UTF8 strings) is also supported, sending message UTF8String to the source object:

helloworld := (const char*) 'Hello World'
FILE* file = fopen( (const char*)fileName, "r" )

(Please see NSString Class Reference for details on memory management of the returned C string).

Motivation: readability, DRY

Blocks enhancements

Blocks are based on Apple’s C/Objective-C implementation. The primary differences in Eero are the use of indentation instead of braces, and the fact that no caret (‘^’) is used to precede a block literal (the caret is still used in block type declarations). You can think of a block as an anonymous function, where the function name is simply omitted.

myblock := (int x, int y)
   if x < 0
      printf( "value was negative! (%d)\n", x )
      x = 0
   return x + y

Compact blocks

Eero supports a compact form for blocks that uses a single expression or return statement. Within the parentheses following the parameter declaration list, a vertical bar separates the parameters from the expression or return statement. Aside from a return, no statements are allowed, so condition statements and loops cannot be present.

xyblock := (int x, int y | return x + y)

descriptions := mylist.mapWith: (id element | return element.description)

Motivation: readability

Nested functions (const blocks)

Eero supports constructions that look and act like nested functions but are semantically const blocks. These can be defined in methods, functions, or other blocks, and the normal block-closure rules apply. They can also be defined within if, else, while, for, etc. indented bodies. It is possible to call them directly, like a normal function, or pass them as block arguments.

implementation MyClass
   processMutableStrings: Array
      void addPositionSuffix( id obj,  UInteger idx, BOOL* stop )
         if obj == ''
            *stop = YES
         else
            obj.appendFormat: '%u', idx
      strings.enumerateObjectsUsingBlock: addPositionSuffix
      addPositionSuffix = nil  // generates a compiler error: addPositionSuffix is a const
end

Motivation: readability

The switch statement

While being very similar in structure (and the same in performance) to the switch statement in the C language family, Eero makes various changes to the construct.

No fall-through

Eero case statements do not fall through, and thus do not require break statements. The ‘:’ (colon) following the case value is optional, but the newline and indented code block is not. (The usual off-side rules apply.) Furthermore, these blocks automatically form declaration scopes, allowing declarations and definitions local to these scopes.

switch color
   case kRed
      colorName = 'red'
   case kGreen
      colorName = 'green'
   case kBlue
      colorName = 'blue'
   case kAlpha
      ; // do nothing
   default 
      colorName = 'unknown'

The loss of convenience due to the absence of fall-through is compensated for by the safer mechanisms described in the next two sections.

Motivation: safety, readability

Comma-separated lists of cases

Cases can be grouped together as comma-separated lists of values:

switch color
   case kRed, kGreen, kBlue
      colorIsValid = YES
   default
      colorIsValid = NO

These can be freely mixed with single and ranged case values within the same switch statement.

Motivation: safety, readability

Case-value ranges

You can specify a set of case values using an ellipsis between the first and last items in a consecutive range of values:

enum Colors { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET }

switch color
   case RED .. VIOLET
      colorIsValid = YES
   default
      colorIsValid = NO

These can be freely mixed with single and comma-separated list values within the same switch statement.

Motivation: safety, readability

Loop enhancements

for-in loop indexing

Eero introduces a convenient and compact way to get a loop index in a for-in loop. This is similar to Python’s for-in-enumerate construct. Index counting always starts at zero.

The syntax for the feature is an integral variable declaration or expression preceding the variable-in-range component, the two sections separated by a colon:

for int index : id obj in myEnumerableObject
   Log('Index: %d', index) // index will count from 0 to n loops
   Log('Object: %@', obj)

int idx 
for idx : int i in 1 .. 10
   Log('Index: %d', idx) // idx will count from 0 to 9

As demonstrated, the integral variable holding the index value can either be a pre-existing variable (defined before and outside of the loop) or declared and valid only within the loop.

This feature is supported for all types of for-in loops: fast enumeration objects, NSRanges, and when the C++ interoperability pragma is in effect, C++11 for-range loops.

Motivation: DRY

The preprocessor

Direct #import of Eero and legacy (Objective-C and C) headers

Eero code can directly import and use Eero and standard Objective-C and C header files. This is done through the Objective-C #import and #include preprocessor directives.

When the filename is enclosed in single quotes, the imported/included file is treated as an Eero file. When either angle brackets or double quotes are used, the file is treated as standard Objective-C or C.

#import <Foundation/Foundation.h>
#import "my_objc_header.h"
#import "my_c_header.h"

#import 'my_eero_header.h'

System header files, regardless of the type of filename string literal used, are always treated as as C/Objective-C. (Please see the Clang Compiler User’s Manual for more details on system headers).

Motivation: backward compatibility

C++ interoperability #pragma

You can directly use C++ APIs and libraries via an Eero-specfic compiler pragma:

#pragma eero "C++"

#import <vector>
...
std::vector<int> v
v.push_back(10)
v.push_back(20)

Please see the Eero wiki for more details.

Motivation: interoperability

Reserved symbols for future features

  • The symbol import is an unused but reserved keyword, in anticipation of the @import feature, which will likely be introduced by Apple in the future.

  • ’=>’ is an unused but reserved operator symbol.