Modules and packages

In Firefly, source code is organized in packages, which can be versioned, released and depended on. Inside packages, there are modules, which are individual files that can be imported from other modules.

The minimal package consists of just a single module file, and nothing else. Such a module can contain the following top level constructs:

// Package information
package mygroup:mypackage:1.2.3
dependency ff:webserver:0.0.0
include "node_modules"

// Module imports
import WebServer from ff:webserver

// Named constants
answer: Int = 42

// Function definitions
f(x: Int): Int {
    
}

// Type definitions
data Point(x: Int, y: Int)

// Method definitions
extend self: Point {

}

// Traits and trait instances
trait T: HasCenter {

}
instance Point: HasCenter {

}

In a multi-file package, the package information is instead specified in a separate package.ff file which must live in the .firefly/ subdirectory. A typical multi-file package looks like this:

mypackage/
    .firefly/
        package.ff
    MyModule.ff
    MyOtherModule.ff

The two module files here MyModule.ff and MyOtherModule.ff defines the modules MyModule and MyOtherModule respectively. Module files must start with a capital letter.

In general, identifiers of any kind in Firefly must start with an ASCII letter, and can only contain ASCII letters and numbers. This also applies to module and package names.

Apart from containing the package.ff file, the .firefly/ subdirectory is used for various compiler output and can contain an include/ directory with JavaScript that you want to include verbatim into the build via the include directive.

Package information

The package keyword specifies the group name, package name and major.minor.patch package version:

package mygroup:mypackage:1.2.3

A group is a person, organization or similar entity that's allowed to publish packages under that group name.

If present, the package keyword must be the first token in the file. In a single file package, all the package information goes before any other content.

The dependency keyword specifies the group name, package name and major.minor.patch package version of a package that is a dependency of this package:

dependency ff:webserver:0.0.0

There may be zero or more dependencies. If there are conflicting versions of the same package in the dependencies or transitive dependencies, the first version that's encountered in a breadth first search from top to bottom will be used.

The include keyword includes files verbatim in the JavaScript that is emitted by the compiler:

include "node_modules"

This instructs the compiler to copy the file or directory mypackage/.firefly/include/node_modules verbatim into the mypackage/.firefly/ouput/node/mygroup/mypackage/node/node_modules directory. It doesn't do anything for the browser target.

Imports and exports

To access the symbols that a module exports, it is necessary to import it:

import WebServer from ff:webserver

This imports the WebServer module from the ff:webserver package, which must have been declared as a dependency.

Symbols exported from the module can then be accessed using the WebServer. prefix:

WebServer.new(system, "localhost", 8080)

Types, variants and traits are available under the prefix, but can also be accessed with no prefix at all. In the case of naming collisions between these, the last import wins.

If two imported modules have the same name, or a different prefix is desired, the symbols can be imported under a different prefix:

import WebServer as W from ff:webserver

The symbols can then be accessed using the W. prefix:

W.new(system, "localhost", 8080)

Currently, all top level definitions are automatically exported. This is likely to change in the future.

Main functions

In Firefly, there are three targets, each with its own main function:

nodeMain(system: NodeSystem) {
    system.writeLine("Hello server!")
}

browserMain(system: BrowserSystem) {
    system.writeLine("Hello browser!")
}

buildMain(system: BuildSystem) {
    system.writeLine("Hello build!")
}

The three main functions may coexist in the same file. The nodeMain function runs when you run your executable, the browserMain function runs in the browser, and the buildMain function runs when you build your program.

The system parameter is an object with methods that let you do I/O in the target system.

Constants

Named constants may be defined at the top level, and must have an explict type:

answer: Int = 42

Here answer is defined to be an Int with the value 42 .

There's no global state in Firefly, and to enforce this, the type of a named constant must have be declared with the data or newtype keyword.

Named constants that occur on the right hand side must not directly or indirecly refer to the named constant being defined.

Neither of the requirements are enforced currently, but this is likely to change in the future.