User Tools

Site Tools


code-organisation

In this section we introduce the way in which source code is organised for projects that use multiple components, particularly how we separate interfaces from implementations - a key feature of Dana's code re-use and runtime adaptation capabilities.

Programs that you build in Dana are a mixture of your own components and components from Dana's central source tree, or CST (which is a bit like a standard library). All components in the CST are open-source so you can browse them yourself. You can also add your own components to the CST to make them available to multiple different programs.

In either case Dana requires you to organise your code in a particular way, which we describe here.

Developing in a local directory

We generally recommend that you develop new projects in a directory outside of the CST. In this case you must organise that directory as follows. Imagine your code is in such a directory called “projectx”. This directory will have a sub-directory called “resources” and may have further sub-directories for components that implement interfaces in sub-directories of the resources directory. Every Dana project must follow this pattern of source code organisation for successful compilation and execution. This is illustrated below:

The green lines in this diagram indicate “in-directory” relationships; the projectx directory therefore contains four items (two directories and two files) while the resources directory contains two items (one directory and one file) and so on.

The resources directory, and its sub-directories, is used to contain source files that describe interface and data types. In general, a source file that contains a type definition (such as an interface) should have the same file name as the type name that it defines.

The projectx directory, and each of its sub-directories (except for the resources sub-directory), contains source files of components that implement interfaces. The general convention for these source files is to name them with the same name as the main interface type that the component provides.

In the above example, the file Road.dn in the projectx directory therefore contains the source code for a component that provides the Road interface as defined in the resources directory.

Using this example, we assume that the file Main.dn has the following code:

component provides App requires Road, vehicles.Car{
 
   int App:main(AppParam params[])
      {
      Road r = new Road()
 
      Car a = new Car()
      Car b = new Car()
 
      r.add(a)
      r.add(b)
 
      return 0
      }
   }

When we try to compile this component, the compiler will need to search for each of the interface types used by the component. This is done by converting any dots to slashes and then adding “.dn” to the end. Road therefore becomes Road.dn while vehicles.Car becomes vehicles/Car.dn. The compiler then expects to find these files within the “resources” sub-directory of this directory (or else in the resources directory of the central source tree, if they are not found locally).

When we attempt to run this program, using the command:

dana Main.o

The Dana runtime then tries to automatically locate default implementing components of each required interface that has been declared by this component. The runtime does this in a very similar way to the approach used by the compiler; for each required interface, a search path is derived by converting any dots to slashes and then adding “.o” to the end. Road therefore becomes Road.o while vehicles.Car becomes vehicles/Car.o.

Instead of looking in the resources sub-directory, however, the runtime expects to find these files in the current directory (or else in the components directory of the central source tree, if they are not found locally). As you can see from the directory structure illustrated above, the resource and component source files appear in the correct locations for this search procedure to be successful.

Finally, note that you must compile every component in this example from a command prompt in the projectx directory, i.e.:

C:\projectx> dnc Main.dn
C:\projectx> dnc Road.dn
C:\projectx> dnc vehicles/Car.dn

For convenience the Dana compiler has a quick way to perform a recursive compilation of all components in a directory and all sub-directories (assuming that directory is structured in the correct way as described here), using the following syntax:

C:\projectx> dnc .

Developing in the central source tree

You can also develop components in Dana's central source tree (CST). We generally advise against developing entire projects here but you may wish to place utility / shared components here that are useful across multiple projects.

Developing code in the CST is very similar to the local directory case described above, except that we do not use a “resources” sub-directory. Instead the CST is split into two branches, “components” and “resources”. These two branches have a symmetrical structure (take a look for yourself).

When compiling a component, any interface types (other type files) are searched for by converting any dots to slashes, adding .dn to the end, and then looking in the resources directory of the CST for a file matching that name. However, the compiler will also search from the symmetrical location within the resources folder of the CST. Imagine, for example, that you have a component in a file called File.dn within the io sub-directory of the components directory in the CST. If this component uses an interface simply called File, the compiler will therefore look at both resources/File.dn and resources/io/File.dn for this file.

At runtime, when attempting to resolve required interfaces of a component in the CST against default implementing components of those interfaces, the Dana runtime will perform a similar procedure. First, any dots in the interface type are converted to slashes and then .o is added to the end. The runtime then checks for a file in the components directory of the CST that matches the resulting path.

Search priority

When searching for a file containing an interface definition, the resources sub-directory of the local directory takes priority over the resources directory of the CST.

When searching for a default implementing component of a required interface, the local directory is again given higher priority that the CST (even if both locations contain a file of the same name). However, for component searches, the Dana runtime also uses manifest files to allow additional control over the search.

Manifest files take precedence over simple file name equivalence, and can be used both in a local directory and in the CST. In detail, when searching for a default implementing component for io.File, the Dana runtime does the following. As described above, it first converts dots to slashes and adds .o to the end, resulting in the path io/File.o. The runtime then does this, in order:

  • Check if there is a local directory called io.
  • If so, check if there is a manifest file in the directory
  • If so, open the manifest file and check for an entry for File; if it exists, use the specified component
  • If no manifest file exists, check if there a compiled component called File.o in the local io directory; if so, use that component
  • If no local directory exists called io, check for a sub-directory called io in the CST's components directory
  • If this directory exists, check if there is a manifest file in the directory
  • If so, open the manifest file and check for an entry for File; if it exists, use the specified component
  • If no manifest file exists, check if there a compiled component called File.o in the io directory of the CST; if so, use that component

This search procedure therefore allows the local directory to override the CST, and allows manifest files to override name equivalence when searching for a default implementing component of a given interface. The structure of manifest files is described next.

Using manifest files

A manifest file is a file with the name .manifest. If present, it describes how to map an interface type onto a default implementing component in the current directory. Manifest files use a simple JSON format as follows:

{
"defaultLinks":	[
		{"interface": "FileSystem", "component": "File"},
		{"interface": "Carem", "component": "Orion"}
		]
}

If the above file is present in a given directory, an interface type named FileSystem that has been resolved against that directory (using the search procedure described above) will by default be matched to the default implementing component File.o in that same directory.

Manifest files are often not required when simple name equivalence is good enough (i.e. name equivalence between the interface's type name and the name of the source file containing the component that implements that interface). Manifest files are useful however when the programmer either wishes to override name equivalence, for example to test an alternative implementation, or when it is not possible for name equivalence to exist, for example when the same component is the default implementing component for more than one interface type.

code-organisation.txt · Last modified: 2017/06/29 06:42 by barryfp

Page Tools