Overview

Copper is a simple imperative language statically typed with type inference and genericity.

Simple Example

const Integer = Int32 struct Point var x : Integer var y : Integer function initialize (x: Integer, y: Integer) self x = x self y = y return self end function translate (dx: Integer, dy: Integer) self x += dx self y += dy end end function main var pt : Point pt initialize (3, 5) pt translate (1, 0) end

Literals and Symbols

Copper 3.0 changes the way it resolves literals and introduce symbols.

Copper has many kind of integers (signed/unsigned, 8/16/32/64 bits) and strings (8 or 16 bits). Before 3.0, integer literals were just 32 bit signed integers with automatic conversion when needed and there were two different syntaxes for 8 and 16 bit character strings.

Now the type of literal depends on the expected type where the expression is evaluated. For instance in a function call argument:

function f (x: Int32) ... function g (x: Unsigned8) ... f (12) // 12 is a 32 bit signed integer g (12) // 12 is a 8 bit unsigned integer function MessageBoxA (msg: *[]Unsigned8) // ASCII string function MessageBoxW (msg: *[]Unsigned16) // UTF-16 string MessageBoxA ("hello world") // The string is encoded with 8 bit characters MessageBoxW ("hello world") // The string is encoded with 16 bit characters

It becomes more interesting with symbols.

Symbols are just identifiers to constants starting with a quote, but they are resolved in the namespace of the expected type. The typical use is the enumeration values:

stype MouseButton 'left // 0 'middle // 1 'right // 2 end

stype Alignment 'left // 0 'right // 1 'center // 2 'justify // 3 end

var button : MouseButton var align : Alignment button == 'right // 'right is evaluated to 2 align == 'right // 'right is evaluated to 1

As symbols are defined in the namespace of the type, it's possible to define another enumeration with same symbols:Now, depending on the expected type, the compiler resolves the symbol diffently

It greatly reduces the number of identifiers in the global namespace and reduces the length of identifiers: no need of prefixes or uppercase, no need to specify the type explicitely (except where no type is expected). The source code is less verbose.

Symbols are not limited to enumerations, they can be used in structures and sub-types as well.

stype LineNumber : Int32 'first = 0 'last = -1 // special value ... struct LinkList var first : *Item var last : *Item 'empty = { nil, nil } // An empty link list ...

Actually, the combination of sub-types and literals+symbols makes the character type useless in the language: Copper 3 does not have any builtin character type, it is defined in the library. See How the character type is defined.

Multiple Return Values

function sort (x, y) if x > y return y, x else return x, y end end function main var min, max = sort (51, 15) end

Returning multiple values is often useful to return an error code and a value:

var error, file = open (...)

This way you don't need to use a special value to say that there is an error and retrieving the error in a second call forcing the callee to store a state.

Another advantage is to swap values, no need to use a temporary variable:

x, y = y, x

OOP Style

The language is not object oriented but it provides some features to facilitate object oriented designs.

Type inheritance.

Contextual functions (methods).

Create kind-of objects by embedding everything needed inside a type definition.

struct Point var x : Int32 var y : Int32 meta function new (x, y) var pt = Point allocate // Allocate a point on the heap pt x = x pt y = y return pt end function resize (factor) self x += scale (self x, factor) self y += scale (self y, factor) end static function scale (value, factor) return value * factor end end function test (x, y, f) var pt = Point new (x, y) pt resize (f) end

It creates a new function available from the Point meta-type.

function available from the meta-type. It creates a resize (self, x, y) function that applies only when the first argument is a pointer to a Point .

function that applies only when the first argument is a pointer to a . It creates a scale regular function this is visible only inside the Point structure so it does not need to have a long name with a prefix.

Block Closures

A block closure is an additional argument passed to a function. The function can evaluate this block using the yield statement.

// Iterates between start and limit function range (start: Integer, limit: Integer) var i = start var n = limit while i <= n yield i // Call the block with i as parameter i ++ end end function main var sum = Integer 0 // Call the range function with start, limit and the block range (1, 10) do x sum += x end return sum end

Block closures provides a better abstraction: they allow to enumerate sequence's elements without knowing the implementation. It also reduces the code: an enumeration is done with one operation instead of the three usual steps (initialization, test end of iteration, advance to next element). Closures are very efficient, they are implemented as inline functions, there is no overhead at execution.

This system is very simple and flexible:

Very concise syntax range (1, 10) do x ... end

Not limited to a single variable dictionary eachKeyAndValue do key, value list eachIndexAndValue do index, value

It is not limited to iterators: function readFile (name) var file = fopen (name, "r") if file notNil yield handle file fclose end end function main var filename = String "dummy.txt" readFile (filename) do file // Use file here end end

An object can have multiple iterators file eachLine do line file eachChar do char

Genericity

A limited genericity is implemented.

Optional Argument Types

The type is optional when declaring the arguments of a function.

function max (x, y) if x > y return x else return y end end

This function will work for any type of x and y as long as x > y is valid (the operators can be overloaded).

Parametric Structures

Structures can have parameters:

struct Vector (T) var items : *[] T // Pointer to an array of T var size : Size ... end

Passing Types as Arguments

Types can be passed as regular arguments.

function new (T) var obj = malloc (T size) : *T return obj end function test var point = new (Point) end

As types are intended for the compiler only, they are removed from the signature when exported Types are not real values as in some true object oriented languages such as Smalltalk or Ruby.

Variadic arguments

Functions can have a variable number of arguments.

function sum(...) var sum = Integer 0 each_extra do x sum += x end return sum end

Passing variadic arguments to another functions is really easy, the three dots represent a tuple:

function sum_plus_3(...) return sum(1, ..., 2) end

Variadic functions without type is very useful:

function in (x, ...) each_extra do value if x == value return true end end return false end

This function works with any type. Code will be generated for each different number and size of arguments.

The in function is a very interesting case as LLVM can optimize it very smartly:

if x in (1, 2, 5)

will first first translated by Copper with a function like this:

function in (x:Int32, a1: Int32, a2: Int32, a3: Int32) if x == a1 return true end if x == a2 return true end if x == a3 return true end return false end

and it will be then fully inlined and optimized by LLVM to something like this:

if bit_test (0b100110, x)

i.e. testing the bit number x of 0b100110 which is done with a single machine instruction.

Order of Definitions Is Not Significant

No need of forward declarations, you can group definitions logically, not in an order forced by the limitations of the compiler. Even imports can be located anywhere in the source file.

You can write for instance your program in a top down approach: write main first, then sub-function, then sub-sub-functions, ...