The sic
boilerplate generator¶
sic
is a tool for generating the boilerplate code that makes it possible to seamlessly interact with a CSL contract from another language.
Currently it supports two target languages: Kotlin and TypeScript.
Because Kotlin is a JVM-based language sic
indirectly supports other such languages like Java.
Likewise, TypeScript is a language that is compiled to JavaScript and is designed to be interoperable with JavaScript code, so sic
indirectly supports JavaScript as well.
Overview¶
When you have written a CSL contract and want to integrate it in a larger application you need some way of communicating with the system responsible for running the CSL contract. This can be done using the public API and one of the API clients, however, this means that you would have to take care of sending the right JSON-encoded data yourself, with no possibility of help from your language’s type checker.
sic
generates mappings from the CSL data types to native data types in the target language, meaning that you can get help from the target language’s type checker and IDE support when building values of these types.
Moreover, reports, events and entry points (templates) in the contract are mapped to suitable constructs in the target language.
Hence, instead of communicating directly using the Deon API, you can instead use specialized functions that use the generated native data types as input and output types, and which takes care of (de)serializing to the JSON format expected by Deon’s API.
This makes it easier to work with the contract as you get the support that you would otherwise get when working in the target language.
On top of that, it makes it dramatically less time-consuming to make changes to the CSL contract, as you will get the updated mappings for free by re-running sic
, and any inconsistencies in the way you use the generated functions will be immediately caught by the target language’s typechecker.
Given a CSL contract, sic
will generate the following components in the target language:
- Data type definitions
- Each data type in the contract will be mapped to a data type in the target language.
- Report functions
- For each report in the contract a corresponding function for invoking that report is created.
- Event application functions
- For each
Event
type in the contract a function for applying that event to a contract is created. - Contract instantiation functions
- For each top-level template in the contract a function for instantiating a contract from that template is created.
Usage¶
sic
is a command-line tool that works on Windows, MacOS, and Linux:
$ sic --help
sic <VERSION>
Usage: sic [-V|--version] [-n|--namespace NAMESPACE] [-t|--target ARG]
[--stdlib PATH] [-w|--write] [-d|--destination PATH] FILES
Available options:
-V,--version Print version information
-h,--help Show this help text
-n,--namespace NAMESPACE Namespace to put generated code in
-t,--target ARG Target language (default: Kotlin)
--stdlib PATH Use alternative CSL standard library
-w,--write Write generated source files to disk instead of just
printing them to stdout
-d,--destination PATH Root directory for generated source
files (default: "generated")
We shall use the contract “sic1.csl
” for demonstration of how to use sic
and for illustrating key points about the structure of the generated code:
type CustomerType
| Regular Int
| OneTime
type Address {
street: String,
number: Int,
floor: Int
}
type Customer {
name: String,
age: Int,
address: Address,
customerType: CustomerType
}
type AddCustomer : Event {
id : Int,
customer : Customer
}
// sum : List Int -> Int
val sum = \ints -> foldl (\(x : Int) -> \y -> x + y) 0 ints
// sumCustomerAge : List Customer -> Int
val sumCustomerAge =
\(customers : List Customer) ->
sum (List::map (\(c : Customer) -> c.age) customers)
template rec Shop(total) = <*> a: AddCustomer
where a.id = total then Shop(total + 1)
Using Kotlin as the target language¶
The default target language of sic
is Kotlin, and the default behaviour is to write the generated code to standard output.
Thus, when we run the command
$ sic sic1.csl
it will print a bunch of Kotlin code to the terminal.
If we pass the flag --write
to sic
it will write the code to disk:
$ sic --write contract.csl
Generating interface for sic1.csl
Wrote file generated/com/deondigital/api/contract/sic1/sic1.kt
Wrote file generated/com/deondigital/api/contract/sic1/sic1.csl.kt
Wrote file generated/com/deondigital/api/contract/sic1/preamble.kt
Wrote file generated/com/deondigital/api/contract/sic1/builtins.kt
Wrote file generated/com/deondigital/api/contract/sic1/RestContractOperations.kt
Wrote file generated/com/deondigital/api/contract/sic1/Entities.kt
Wrote file generated/com/deondigital/api/contract/sic1/fromValue.kt
Wrote file generated/com/deondigital/api/contract/sic1/InstanceDispatcher.kt
Wrote file generated/com/deondigital/api/contract/sic1/InstanceDispatcher.kt
Wrote file generated/com/deondigital/api/contract/sic1/ReportService.kt
Wrote file generated/com/deondigital/api/contract/sic1/ContractService.kt
We see here that one CSL contract is represented as two Kotlin source files in the package com.deondigital.api.contract.sic1
.
The generated code will be put into the root directory generated/
.
Both the package name and the root directory can be changed with the flags --namespace
(-n
) and --destination
(-d
), respectively.
The first file, sic1.kt
, contains all the interface and data definitions that enable us to interact with the CSL contract from Kotlin in a convenient manner.
The second file, sic1.csl.kt
, is an embedding of the contract source in Kotlin.
Data types¶
The file sic1.kt
will contain, amongst many other things, the definitions of the following data types:
sealed class CustomerType : Convertible {
/* ... */
data class Regular(val field0: Int) : CustomerType() {
/* .. */
}
object OneTime : CustomerType() {
/* .. */
}
}
open class Address(
val street: String,
val number: Int,
val floor: Int) : Record() {
/* ... */
}
open class Customer(
val name: String,
val age: Int,
val address: Address,
val customerType: CustomerType) : Record() {
/* ... */
}
class Shop(/* ..., */
val total: Int) : ContractInstance {
companion object {
fun instantiate(/*...,*/ total: Int) =
/* ... */
fun getInstance(ops: ContractOperations, contractId: ContractId) =
/* ... */
}
}
We have left out a lot of details here, but the snippet demonstrates how a sum type in CSL is converted to a sealed class
in CSL with a subclass for each constructor while a CSL record type is converted to an open class
.
The names of parameters of an open class
match the names in the CSL record.
Base types such as Int
and String
are represented by their native counterparts in Kotlin: kotlin.String
and kotlin.Int
.
Furthermore, the Shop
template entrypoint is the basis of a class with the same name that holds a static method instantiate
.
When used, the instantiate
method gives a Shop
that can be used to query the state of the running contract and to apply events.
It also provides access to instantiation arguments for the contract.
Reports¶
The CSL reports are converted to functions in the target language with appropriate types. That is, the input and output types are mappings from the CSL type to the target language type as described in the above section.
In our generated Kotlin code, we find the following class:
open class ReportService(
private val reportCaller : (
String,
String,
List<com.deondigital.api.Value>
) -> java.util.concurrent.CompletableFuture<com.deondigital.api.Value>,
private val declarationId : String
) : /* ... */ {
fun sum(ints: List<Int>) : Int = /* implementation */
fun sumCustomerAge(customers: List<Customer>) : Int = /* implementation */
}
The class com.deondigital.api.contract.sic1.ReportService
declares two functions, one for each of the CSL reports in sic1.csl
.
Input and output types of the functions are mapped from the corresponding CSL types; note that the CSL List a
type is mapped to Kotlin/Java’s List<T>
type.
To instantiate the class one must provide two arguments:
- A
reportCaller
which is the function used to issue the actual report call to the Deon API. The parameters of this function match the ones taken by theDeonApiClient::postReport
function, so in the usual case you can just supply thepostReport
method from aDeonApiClient
that is set up with the right URL. - A
declarationId
– this is the id of the declaration on the ledger with the CSL sources, as returned by the wrapping RESTContractOperation.
The following snippet illustrates how one would typically run a report with this interface:
val apiClient = DeonAPIClient(API_URL) // Connect to the ledger
val declarationId = RestContractOperations("myIdentity", apiClient).declarationId // Wrap the client
val r = ReportService(apiClient::postReport, declarationId)
val s = r.sumCustomerAge(listOf(
Customer("bob", 42, Address("Main st.", 1, 5), CustomerType.OneTime),
Customer("alice", 30, Address("Main st.", 10, 2), CustomerType.Regular(1)))
) // == 72
The astute reader will have noticed that the class ReportService
is an open class
and that it itself is a subclass of another class.
When sic
generates code for multi-file projects the report service classes inherit from one another, reflecting the project structure in the deon-project
.
For standalone CSL files the report service just inherits from an empty abstract base class.
Contract instantiation¶
Every top-level template declaration in a CSL contract represents a possible instantiation point of a contract in the system.
The generated Kotlin code for sic1.csl
contains the following class:
class Shop(/* ..., */
val total: Int) : ContractInstance {
companion object {
fun instantiate(`$ops`: ContractOperations, /*...,*/ total: Int) =
/* ... */
fun getInstance(ops: ContractOperations, contractId: ContractId) =
/* ... */
}
}
In order to make it possible to use any backend, the instantiate
function takes a ContractOperations
object that describes how contract state is being managed.
Moreover, all instantiation functions take the same parameters as the CSL templates (mapped to the Kotlin type), plus two additional optional parameters:
MetaArgs
that can be used to supply additional information about the contract that will be instantiated, such as which peers should be used in the Corda backend and which name will be given to the contract instancetimeProvider
to manage how event timestamps are set.
To create a ContractOperations
object in the following example, we use a REST backend.
val apiClient = DeonAPIClient(API_URL)
// Construct a ContractOperations for a REST backend
val ops = RestContractOperations("myIdentity", apiClient)
// Instantiate the template 'Shop'
val contract1 = Shop.instantiate(ops, 42)
// Instantiate a new contract from the template 'Shop'
val contract2 = Shop.instantiate(ops, 11)
// Instantiate yet another contract from the 'Shop'
// template, but give the instance a custom name
val namedContract = Shop.instantiate(ops, 47, MetaArgs(name = "shopContract47"))
Event application¶
Every subtype of Event
in the contract gets mapped to a function that applies an event of that type to a running contract.
Any fields that the event record might have is represented as a parameter to the event application function.
The classes created with .instantiate(...)
exposes a field applyEvent
that allows application of events as functions, i.e. AddCustomer()
.
Because the AddCustomer
event record contains two fields in addition to the fields in Event
, id : Int
and customer : Customer
, the Kotlin function accepts two parameters corresponding to the fields.
The return type is parameterized like it was the case for contract instantiation from Kotlin.
The example snippet below uses the contract contract1
instantiated above:
// Now we can apply two 'AddCustomer' events on the contract:
contract1.applyEvent.AddCustomer(0, Customer("bob",
42,
Address("Main st.", 1, 5),
CustomerType.OneTime));
contract1.applyEvent.AddCustomer(1, Customer("alice",
30,
Address("Main st.", 10, 2),
CustomerType.Regular(1)));
Targeting R3 Corda¶
sic
provides an additional Kotlin target named Corda
.
Using this target, sic
will, in addition to creating the usual Kotlin files, also create a contractOps.kt
file.
This file contains the class ContractOps
that provides an interface to contracts on the R3 Corda Distributed Ledger (https://www.corda.net/).
To construct an instance of the class, a CordaRPCOps
and a ContractHandler
is needed.
The former is obtained from creating a connection to a Corda Node; and the latter is available from the csl-cordapp
package:
dependencies {
compile group: 'com.deondigital', name: 'csl-cordapp', version: 'v0.58.0-SNAPSHOT' // Insert appropriate CSL version here
}
The plugin csl-cordapp
provides a default anonymized implementation of the ContractHandler
interface, but it is possible to implement a custom implementation.
For the complete documentation of CorDapps, we refer to https://docs.corda.net/building-a-cordapp-index.html.
Gradle plugin¶
The sic
boilerplate generator comes with a plugin that supports both Kotlin
and Corda
gradle projects.
To use it, the root gradle project will need the following additions:
settings.gradle
must, in addition to any other subprojects, include:
include 'flows'
include 'states'
build.gradle
must include:
buildscript {
dependencies {
classpath "com.deondigital:gradle-sic-plugin:v0.58.0-SNAPSHOT"
}
}
apply plugin: 'com.deondigital.gradle.sic-plugin'
csl {
destinationDir = 'generated/sic/'
cslDir = 'src/main/csl/'
relocate = true
cslVersion = 'v0.58.0-SNAPSHOT'
cordapp {
cordappVersion = '0.1.0'
cordappName = 'example'
cordappPackage = 'org.foocorp'
cordappVendor = 'FooCorp'
resourceApiVersion = '0.1.0'
}
}
In the csl {}
configuration block cslVersion = 'v0.58.0-SNAPSHOT'
refers to the version of the sic
Gradle plugin in use.
In csl.cordapp {}
configuration block, resourceApiVersion = '0.1.0'
specifies which version of the Deon Digital resource API to use.
The following properties are needed to download the relevant dependencies from Deon Digital’s Nexus repository:
NEXUS_USER
and NEXUS_PASSWORD
.
It is recommended to supply these via the command line as build arguments or have them in a Gradle settings file that is not checked into version control.
The relocate
parameter is used to do relocation of the com.deondigital.cordapp
packages which contain Corda contracts.
This is to support multiple deployments of sic
generated CorDapps in the same Corda network.
When relocate
is true
, package com.digital.cordapp
is relocated to <cordappPackage>-<cordappName>
.
The plugin provides the following tasks (use gradle flows:tasks
and gradle states:tasks
for an overview):
Task | Description |
---|---|
generateKotlin | Generate Kotlin code with sic |
generateAST | Generate the abstract syntax tree for the input CSL file and the CSL standard library |
assembleCorda | Assemble Corda projects for both ‘states’ and ‘flows’ |
flows:generateDeonCorDappFlows | Generate sic ‘flows’ output, compile, relocate, and package jar file |
states:generateDeonCorDappStates | Generate sic ‘states’ output and package jar file |
There are a number of additional internal tasks exposed by the plugin, but they should generally not be used in most applications.
For CorDapp projects, the following dependencies are needed to wire the output from flows
and states
into any root- or sub project requiring the generated code:
dependencies {
compile project (':flows')
compile project (':states')
}
If you need IDE support for working with generated classes, add the following to the relevant project. This will make it possible for, e.g., IntelliJ IDEA to show the generated and relocated code in the JAR files.
repositories {
flatDir {
dirs "$rootDir/flows/build/libs/"
}
}
dependencies {
// A runtime dependency to exclude it from the shadowed jar
if (rootProject.file("$rootDir/flows/build/libs/flows-0.1.0.jar").exists()) {
compileOnly name: "flows-0.1.0"
}
}
The version part of the dependency must match the version specified in csl.cordapp.cordappVersion
.
For a more thorough example of building a CorDapp, we refer to https://github.com/corda/samples where the sub projects flows
and states
correspond to the workflows-kotlin
and contracts-kotlin
respectively.
These examples also contain documentation of how to use CordFormation
to build and deploy CorDapps.
For a complete example of using the plugin to build CorDapps, see https://gitlab.deondigital.com/open/csl-cordapp-examples.
Using TypeScript as the target language¶
To instruct sic
to generate TypeScript mappings for the CSL contract, run it with the flag --target TypeScript
:
$ sic --write --target TypeScript sic1.csl
Generating interface for sic1.csl
Wrote file generated/./sic1.ts
Wrote file generated/./sic1.csl.ts
Wrote file generated/./preamble.ts
Wrote file generated/./builtins.ts
We get three files: sic1.ts
contains the data definitions and interfaces for our CSL contract, sic1.csl.ts
contains the CSL source, and builtins.ts
and preamble.ts
contains definitions that are not tied to the particular contract.
Data types¶
Amongst the definitions in the file sic1.ts
are the mappings of the CSL types:
export type CustomerType
= Regular
| OneTime
export class Regular {
constructor(field0 : number) { /* ... */ }
/* ... */
}
export class OneTime {
constructor() { /* ... */ }
}
export class Address extends Record {
street : string
_number : number
floor : number
constructor (street : string, _number : number, floor : number) { /* ... */ }
/* ... */
}
export class Customer extends Record {
name : string
age : number
address : Address
customerType : CustomerType
constructor (name : string, age : number, address : Address, customerType : CustomerType) {
/* ... */
}
/* ... */
}
These definitions allow us to work in TypeScript directly with, e.g., a Customer
class with the field name
as a native string
.
Note that, because “number
” is a reserved word in TypeScript, the field number
in the CSL contract has been renamed to _number
in the TypeScript embedding.
Reports¶
The generated TypeScript code contains the class:
// In sic1.ts
export class Reports extends builtins.Reports {
constructor(client : p.DeclarationsApi) {
super(client);
this.declarationId = addDeclaration(client);
}
sum(ints : number[]) : Promise<number> {
/* implementation */
}
sumCustomerAge(customers : Customer[]) : Promise<number> {
/* implementation */
}
}
Reports
requires one parameter in its constructor, an implementation of the DeclarationsApi
(from @deondigital/api-client
), used for issuing the actual call to the API.
Prior to calling reports, a check is made to ensure the declaration is stored on the server. Otherwise it is added.
We can call a report with native TypeScript types analogously to the way we did it for Kotlin:
// 'deonApiClient' and 'contractId' defined elsewhere
const r = new Reports(deonApiClient.declarations)
const s = await r.sumCustomerAge([new Customer (
"bob",
42,
new Address (
"Main st.",
1,
5
),
new OneTime()
), new Customer (
"alice",
30,
new Address (
"Main st.",
10,
5
),
new Regular(1)
)])
Contract instantiation¶
The generated TypeScript code for sic1.csl
contains the following class for contract instantiation:
export class Shop {
private constructor(
readonly contractId : string,
private readonly client : p.DeonApi,
readonly total : number
) {}
static async instantiate(
client: p.DeonApi,
total: number,
$args? : { peers? : p.ExternalObject[], name? : string }
) : Promise<Shop> {
const declarationId = await addDeclaration(client.declarations);
return await new Instantiate(client.contracts, declarationId).Shop(total, $args).then(cid => {
return {
contractId: cid,
total: total,
commands: (agent: string, timeProvider : () => Date = () => new Date()) => new Commands(client.contracts, cid, agent, timeProvider)
}
})
}
commands(agent: string, timeProvider? : () => Date){
return new Commands(this.client.contracts, this.contractId, agent, timeProvider)
}
}
This is similar to the Kotlin code: sic
generates one class per template.
The generated class is a representation of the instantiated contract, with instantiation parameters, contract id, and a commands
function to apply events.
Furthermore, it contains a static method instantiate
that takes as parameters:
- A
DeonApi
client (from@deondigital/api-client
) to do the declaration and instantiation call. - Any parameters the contract template expects (in this example,
total
) - An optional
$args
for specifying peers and name of the contract instance.
// 'deonApiClient' defined elsewhere
// Instantiate 'Shop'
const contract1 = await Shop.instantiate(deonApiClient, 42);
// Instantiate a new contract from 'Shop'
const contract2 = await Shop.instantiate(deonApiClient, 11);
// Instantiate yet another contract from the 'Shop'
// template, but give the instance a custom name
const namedContract = await Shop.instantiate(deonApiClient, 47, { name : "shopContract47" });
Event application¶
sic
has generated the following TypeScript class:
// In sic1.ts
export class Commands extends builtins.Commands {
async AddCustomer(
id : number,
customer : Customer,
$tag? : p.Tag) : Promise<p.Tag | void> {
/* implementation */
}
}
Again, the pattern is quite like it was for Kotlin: one function per event type and the functions take as parameters the fields of the event.
Note that the function for applying the basic event type Event
is contained in the super class builtins.Commands
.
Access to the Commands
object is through the .commands(...)
field on the template interface (here: Shop
).
Supply the following parameters to the commands
function:
- The originating agent for the event.
- An optional “time provider” that returns the timestamp to be used in the event. It can be left out, in which case the current time is used.
In this snippet we apply two AddCustomer
events on a contract:
// 'deonApiClient', 'contract', and 'agent' defined elsewhere
const c = contract.commands(agent);
// Now we can apply two 'AddCustomer' events on the contract:
await c.AddCustomer(0, new Customer (
"bob",
42,
new Address ("Main st.", 1, 5),
new OneTime()
));
await c.AddCustomer(1, new Customer (
"alice",
30,
new Address ("Main st.", 10, 5),
new Regular(1)
));
The @deondigital/sic
NPM package¶
The sic
tool is distributed in the NPM package @deondigital/sic
.
It provides a handy way to install sic
:
$ npx @deondigital/sic
This will download the latest version of sic
and run it.
If you want sic
code generation as part of your build process, add @deondigital/sic
as a project dependency and add a "generate"
entry to the "scripts"
section of package.json
:
/* ... */
"scripts": {
/* ... */
"generate": "sic --target TypeScript --write *.csl"
},
"dependencies": {
/* ... */
"@deondigital/sic": "0.58.0"
}
Now you can use the generate
script in your project:
$ npm run generate
Note
The version number of the @deondigital/sic
package follows that of the rest of Deon Digital CSL.
Version 0.58.0
of @deondigital/sic
will download a version of sic
that is compatible with version 0.58.0
of the Deon Digital CSL runtime.
Emit JSON representation of core AST¶
It is possible to use sic
to emit a structured core representation of a CSL project in JSON format by using the CoreAST
target.
$ sic --write --target CoreAST sic1.csl
Generating interface for sic1.csl
No project file found
Wrote file generated/./sic1.coreast.json
The file sic1.coreast.json
contains a JSON representation of the core AST of sic1.csl
along with the standard library.
Note that this core representation is the result of several steps of internal processing that among other things strips away type information.
Emit JSON representation of type definitions¶
It is possible to get a JSON-serialized representation of all types used in a contract.
To use, simply run
$ sic --write --target Ontology sic1.csl
Generating interface for sic1.csl
No project file found
Wrote file generated/./sic1.ontology.json
This will write the ontology for the whole project to sic1.ontology.json
, including the ontology for the the preamble and built-ins.
The output is an array of ontology elements.
Each element has the following form:
{
kind: { /** type descriptor */ },
name: {
name: /** type name (string) */,
qualifier: [ /** type qualifier */ ]
}
}
Type descriptors have one of the following forms:
{
tag: "OntologyRecord",
parent: { /** qualified name */ },
fields: {
"field1": { /** type identifier */ },
/** ... */
}
}
{
tag: "OntologyUnion",
parameters: [ /** type parameters */ ],
constructors: [ /** constructors */ ]
}
For example, the ontology element for the Maybe a
union type looks as follows:
{
"kind": {
"tag": "OntologyUnion",
"constructors": [
{
"arguments": [],
"name": "None"
},
{
"arguments": [
{
"tag": "Var",
"identifier": "a"
}
],
"name": "Some"
}
],
"parameters": [
{
"parameter": "a"
}
]
},
"name": {
"name": "Maybe",
"qualifier": []
}
}
The ontology element for the Event
record type looks as follows:
{
"kind": {
"parent": {
"name": "Record",
"qualifier": []
},
"tag": "OntologyRecord",
"fields": {
"agent": {
"tag": "Apply",
"params": [],
"name": {
"name": "Agent",
"qualifier": []
}
},
"timestamp": {
"tag": "Apply",
"params": [],
"name": {
"name": "DateTime",
"qualifier": []
}
}
}
},
"name": {
"name": "Event",
"qualifier": []
}
}
Emit declaration signature¶
It is possible to get a JSON representation of the types of all top-level declarations in a CSL file.
To use, simply run
$ sic --write --target Signature sic1.csl
Generating interface for sic1.csl
No project file found
Wrote file generated/./sic1.signature.json
This will write the signature for the whole project to sic1.signature.json
, including the signature for the the preamble and built-ins.
The output is an array of type declarations.
Each element has one of the following forms:
// Value declaration
{
"tag": "DeclVal",
"name": { /* ... a QualifiedName object ... */ },
"type": { /* ... an OntologyTypeIdentifier object ... */ }
}
// Contract declaration
{
"tag": "DeclContract",
"name": { /* ... a QualifiedName object ... */ }
}
// Template declaration
{
"tag": "DeclTemplate",
"parameterTypes": [
/* ... list of OntologyTypeIdentifier objects... */
],
"name": { /* ... a QualifiedName object ... */ },
"contractArity": 0
}
For example, given the following CSL file:
val x = 42
contract c = success
template [x] T(a) = <*> Event where a = 42 or x
If we strip out the signature for the preamble and built-ins (which is the same for all input files), then the following declarations are generated:
[
{
"tag": "DeclVal",
"name": {
"name": "x",
"qualifier": []
},
"type": {
"tag": "Apply",
"params": [],
"name": {
"name": "Int",
"qualifier": []
}
}
},
{
"tag": "DeclContract",
"name": {
"name": "c",
"qualifier": []
}
},
{
"tag": "DeclTemplate",
"parameterTypes": [
{
"tag": "Apply",
"params": [],
"name": {
"name": "Int",
"qualifier": []
}
}
],
"name": {
"name": "T",
"qualifier": []
},
"contractArity": 1
}
]
Projects with multiple CSL files¶
CSL contracts can be grouped into projects by defining a file called deon-project
in a folder.
This file contains a newline-separated list of relative or absolute paths to CSL files, its presence folder foo
means that the folder is a “project”, and that the CSL files should be loaded in the order specified in the deon-project
file.
For example, the following deon-project
file specifies a project that contains the files sic1.csl
, sic2.csl
, and sic3.csl
:
sic1.csl
subfolder/sic2.csl
/absolute/folder/sic3.csl
Contracts that are part of a deon-project
are typechecked in the context of all contracts that come before them in the project specification.
Thus, sic1.csl
may only refer to names declared in the same file or in the standard library, whereas anything declared in sic1.csl
is in scope in sic2.csl
, and anything in sic1.csl
and sic2.csl
is in scope in sic3.csl
.
Using projects in sic
¶
Given the project file myproject/deon-project
:
sic1.csl
sic2.csl
Running the command:
$ sic --write --target Kotlin --namespace org.foo myproject/*.csl
Will output the following:
Wrote file generated/org/foo/myproject/ContractOperations.kt
Wrote file generated/org/foo/myproject/RESTContractOperations.kt
Wrote file generated/org/foo/myproject/Entities.kt
Wrote file generated/org/foo/myproject/preamble.kt
Wrote file generated/org/foo/myproject/builtins.kt
Wrote file generated/org/foo/myproject/sic1.kt
Wrote file generated/org/foo/myproject/sic1.csl.kt
Wrote file generated/org/foo/myproject/sic2.kt
Wrote file generated/org/foo/myproject/sic2.csl.kt
Wrote file generated/org/foo/myproject/fromValue.kt
Wrote file generated/org/foo/myproject/InstanceDispatcher.kt
Wrote file generated/org/foo/myproject/ReportService.kt
Wrote file generated/org/foo/myproject/ContractService.kt