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

  1. Simple Example
  2. Literals and Symbols
  3. Multiple Return Values
  4. OOP Style
  5. Block Closures
  6. Genericity
  7. Variadic Arguments
  8. Order of Definitions Is Not Significant

Simple Example

const Integer = Int32

struct Point
    attr x : Integer
    attr y : Integer

    method initialize (x: Integer, y: Integer)
        self x = x
        self y = y
        return self
    method translate (dx: Integer, dy: Integer)
        self x += dx
        self y += dy

function main
    var pt : Point
    pt initialize (3, 5)
    pt translate (1, 0)

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:

enum MouseButton
    'left // 0
    'middle // 1
    'right // 2
As symbols are defined in the namespace of the type, it's possible to define another enumeration with same symbols:
enum Alignment
    'left // 0
    'right // 1
    'center // 2
    'justify // 3
Now, depending on the expected type, the compiler resolves the symbol diffently
var button : MouseButton
var align : Alignment

button == 'right // 'right is evaluated to 2
align == 'right // 'right is evaluated to 1

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
    attr first : *Item
    attr 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
        return x, y

function main
    var min, max = sort (51, 15)

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.

struct Point
    attr x : Int32
    attr y : Int32

    static new (x, y)
        var pt = Point allocate // Allocate a point on the heap
        pt x = x
        pt y = y
        return pt
    method resize (factor)
        self x += scale (self x, factor)
        self y += scale (self y, factor)

    function scale (value, factor)
        return value * factor

function test (x, y, f)
    var pt = Point new (x, y)
    pt resize (f)

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 ++

function main
    var sum = Integer 0
    // Call the range function with start, limit and the block
    range (1, 10) do x  
        sum += x        
    return sum

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:


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
        return y

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)
    attr items : *[] T  // Pointer to an array of T
    attr size  : Size

Passing Types as Arguments

Types can be passed as regular arguments.

function new (T)
    var obj = malloc (T size) : *T
    return obj

function test
    var point = new (Point)

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
    return sum

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

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

Variadic functions without type is very useful:

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

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
    if x == a2
        return true
    if x == a3
        return true
    return false

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, ...