While drafting clauses in ClauseBase is certainly not “programming” in the traditional sense, ClauseBase borrows some best-practices from traditional programming languages. Legal experts who have never done any traditional programming are often confused by these practices.
One such issue is the concept of data types — aka the issue of not mixing apples and oranges. This page deliberately takes a deep-dive into dealing with these types, to fully explain how they work in ClauseBase.
Natural language versus conditions & calculations
A first important idea to grasp is the distinction between natural language mode and conditions/calculations mode.
The standard mode in ClauseBase is the natural language mode. This is the mode you will implicitly arrive in when starting to type something in a clause. It will consist of “human” language stuff, such as words in English/French/Dutch, punctuation and numbering, etc. For example, the following clause entirely consists of the natural language mode elements:

A clause that consists entirely of natural language mode elements will be completely static, i.e. aside from the numbering and formatting of the clause, this clause will not change appearance. To insert dynamic elements, you insert conditions and calculations, for example:

As soon as you type in an opening accolade {
you will leave the natural language mode, and enter either
- the condition mode, for which the result’s type is always true or false); or
- the calculation mode (for which the result’s type depends on the kind of calculation being made).
In the example above, #contract^value < 500 EUR
and #contract^value < 2000 EUR
are written in the condition mode, while apples^amount * #apples^unit-price
is written in the calculation mode.
After the closing accolade }
you will in any case leave these modes, and return to the natural language mode. However, the switch between the modes is more complex than just looking at the opening and closing accolade. For example, in paragraph 1. above, the parts between the colon (:
) and the vertical bar (|
) are once again in the natural language mode, i.e. the words low and medium and high.
Calculation mode
As the name implies, calculation mode expects to perform calculations. The input to these calculations consists of a mix of countable elements and optional operators — e.g. 5 + 24
or 5 * 4.5 EUR
, but also 12 months * 4
or 2020_11_23 + 3 days
, because you can perform interesting calculations with durations and dates.
Instead of using directly countable elements, you can also work with elements that ultimately boil down to numbers. For example, in apples^amount * #apples^unit-price
, the datafields “amount
” and “unit-price
” can be assumed to contain countable things. When, for example, the number 5 would be assigned to apples^amount
and the #apples^unit-price
would be assigned the value 0.4 EUR, ClauseBase will happily perform the calculation for you and return the result (2 EUR).
Note that natural text cannot, as such, be used in calculation mode. After all, text is — as such such — not countable. It does not make sense to, for example, perform a calculation to divide the words “the Buyer buys an apple” by the words “the Seller sells a pear”.
Condition mode
Writing in the condition mode will be writing in calculation mode, with one exception. Condition mode is meant to ultimately result in true or false, and will therefore contain a left side with a calculation, a comparison operator and a right side with yet another calculation.
For example, in the condition apples^amount * #apples^unit-price < 2000 EUR
, you will see a left side (containing a calculation on apples), a comparison-operator (smaller than <), and a right side (with a single currency-value).
Data types
In ClauseBase, as in most programming languages, you can only calculate with elements of the same data type, and you can only compare elements of the same data type. For example, it does not make sense to add 25 EUR to a date, or to compare the text “alpha” to the duration of 5 months.
ClauseBase offers the following basic data types:
Data type | Examples |
---|---|
whole number | 0 ; 5 ; 21456 |
floating point number | 4.56 ; 2457.8975 |
currency number | 0 EUR ; 56.34 EUR |
text | "alpha" ; "Main Street 56 Brussels" |
date | 5 January 2020 ; 8 July 2022 |
duration | 3 years ; 4 months ; 2 days |
true/false | true |
In addition, the following special data types are also offered.
Data type | Example |
---|---|
list of elements (any of the other types allowed) | 5 , 6 EUR , 3 January 2020 |
clause text | 1. Alpha |
clause part | @ALPHA |
defined term | #contract |
datafield reference | #contract^value |
nothing / undefined |
The three different modes result in three different data types:
Mode | Result |
---|---|
natural language mode | always results in clause text |
calculation mode | result type will depend on the elements used — e.g. 3 + 3 results in a whole number |
condition mode | always results in true/false |
Supported currencies
ClauseBase currently supports the following currencies:
Currency | Symbol used in ClauseBase |
---|---|
euros | EUR |
British pound | GBP |
United States dollar | USD |
Japanese Yen | JPY |
Australian dollar | AUD |
Canadian dollar | CAD |
Swiss franc | CHF |
yuan | CNY |
Kenyan shilling | KSH |
Hong Kong dollar | HKD |
New Zealand dollar | NZD |
Swedish krona | SEK |
South Korean won | KRW |
Singapore dollar | SGD |
Norwegian krone | NOK |
Mexican peso | MXN |
Indian rupee | INR |
Russian ruble | RUB |
South African rand | ZAR |
Turkish lira | TRY |
Brazilian real | BRL |
New Taiwan dollar | TWD |
Danish krone | DKK |
Polish złoty | PLN |
Thai baht | THB |
Indonesian rupiah | IDR |
Hungarian forint | HUF |
Czech koruna | CZK |
Israeli shekel | ILS |
Chilean peso | CLP |
Phillipine peso | PHP |
UAE dirham | AED |
Colombian peso | COP |
Saudi riyal | SAR |
Malaysian ringgit | MYR |
Romanian leu | RON |
Conversions between data types
While it is not possible to perform calculations or comparisons with elements of different data types, it is possible to convert an element to another data type. This conversion can either be done explicitly or implicitly.
Explicit conversions
Explicit conversions can be achieved using special functions. For example, the special function @extract-number
will take a currency number (e.g., 5.5 EUR
) and return the floating point number (5.5
). Similarly, the sole purpose of the special function @str
is to convert the argument passed to it into text — e.g. @str(3 months)
will, depending on the styling settings, result in the text 3 months
when the active language is English.
At first glance, this may look like nothing changed; under the hood, however, the type of this element changed from a duration to a text, meaning that the operations that can be applied to a duration element (e.g., addition or subtraction) will suddenly no longer work, while the operations that can be applied to a text element (e.g., capitalization) will suddenly become possible.
If the active language is not English, the conversion will be much more visible. For example, @str(3 months)
may also be converted to “trois (3) mois” in French, with a styling setting where numbers are always converted to both letters and numbers.
Most special functions will perform something more than solely converting an argument from one data type to another data type. Examples:
@round
and@floor
will convert the floating point number passed to them, and then either round that number or truncate the decimal part, returning a whole number@comma-split
will take a text element and return a list element that contains individual text elements (e.g.@comma-split("alpha, beta, gamma")
will return a list with text elements"alpha"
,"beta"
and"gamma"
.
Implicit conversions of basic data types
It would be cumbersome to always have to explicitly convert data types — e.g., imagine that in order to take 5.5 times the duration of a contract, you would have to write 5.5 * @float(@months-in(#contract^duration))
The underlying reasoning is as follows: you are performing a multiplication of a floating point number and a duration, which is not possible. There exists a special function @months-in
that takes a duration and returns a whole number that corresponds to the amount of months in that duration. However, you would then have to perform yet another conversion from this whole number to the floating point number. You would therefore have to use the hypothetical @float
function.
To avoid these situations, ClauseBase will implicitly perform conversions for you when no ambiguity is involved:
Data type 1 | Data type 2 | Resulting data type | Example |
---|---|---|---|
whole nr | floating point nr | floating point nr | 5 + 5.5 results in 10.5 |
whole nr | currency nr | currency nr | 200 + 300 EUR results in 500 EUR |
floating point nr | currency nr | currency nr | 229.5 + 300 EUR results in 529.5 EUR |
date (*) | duration | date | 2018_7_12 + 1 month results in 2018_8_12 |
list (**) | (any data type) | list | @list("alpha", "beta") + "gamma" results in @list("alpha", "beta", "gamma") |
- (*) When mixing a date with a duration, note that the order and operation matters. The conversion will only be applied with
date +/- duration
. If you change the ordering (e.g., duration first) or the operation (e.g. multiplication), the result will be an error. - (**) When mixing a list with, the order and operation matters: the conversion will only be applied with
list +/- element
, not withelement +/- list
.- When using the
-
operator, the element(s) at the right side are removed from the list at the left side. For example,@list("alpha", "beta") - "alpha"
and@list("alpha", "beta") - @list("alpha")
both result in@list("beta")
. - When using the
+
operator, the result depends on the right-side:- If both the left & right side are lists, then the result will be a concatenation of the two lists, with all the elements of the right list appended to the left list, irrespective of whether they were already present in the left list. For example,
@list("alpha", "beta", "gamma") + @list("alpha", "delta")
will result in@list("alpha", "beta", "gamma", "alpha", "delta")
. (If you want a union of the list, where elements-already-present are not added again, use the@union
special function instead of +). - If the right side is not a list, then it will be added as a new element of the left list, irrespective of whether the right side element was already present in the list. For example,
@list("alpha", "beta") + "alpha"
will result in@list("alpha", "beta", "alpha")
. (If you want a union of the list and the single element, where the right-side element is not added again to the left list, use the@union
special function instead of +).
- If both the left & right side are lists, then the result will be a concatenation of the two lists, with all the elements of the right list appended to the left list, irrespective of whether they were already present in the left list. For example,
- When using the
Implicit conversions of undefined
When using datafields, values will often not be present, because they have not (yet) been filled in by the end-user. Within a calculation/condition, to avoid errors, ClauseBase will try to convert this undefined value to a sensible default value:
Datatype | Converted into | Example |
number | 0 | 55 * undefined results in 0 |
currency | 0 (same valuta) | 5 EUR + undefined results in 5 EUR5 EUR * undefined results in 0 EUR |
duration | duration with length 0 | 5 days + undefined results in 5 days |
text | empty text | empty text is simply ignored in the output |
list | empty list | @list("alpha") + undefined results in @list("alpha") |
Note that the date field is missing from the table above: an undefined will never be implicitly converted to a date field. (After all, there exists no date that would make sense here: what could possibly be considered “date zero”?)
Tip: when you do not like these implicit conversions of unavailable values inside a calculation, you may want to use an exclamation point after the hash.
For example, while you may be happy with {#apples^amount * 0.2 EUR}
resulting in 0 EUR when the amount of apples has not yet been completed by the end-user, it may equally be the case that you absolutely need the user to fill in this amount. If so, then use {#apples^!amount * 0.2 EUR}
instead — this will result in a box that invites the user to fill in this value, signaling that the calculation simply cannot be completed without a value assigned to the amount of apples.
Implicit conversions to true/false in a condition
In condition mode, the following implicit conversions are carried out to convert a data type to true/false:
Data type | Converts to true when | Converts to false when |
---|---|---|
Text | Non-empty text value | Equal to the empty-text value (“”) |
(Whole/floating/currency) number | Not equal to 0 | Equal to 0 |
Duration | Amount higher than 0 | Amount equal to 0 |
List | Not empty | Empty (no elements) |
Date | Assigned some value | Never |
Undefined (e.g., datafield without value) | Never | Always |
Note that in an if-then-else condition, you can omit the operator and right-hand side of the comparison for the sake of brevity; ClauseBase will then automatically insert = true
. For example:
- Instead of writing
{#contract^is-dutch-law = true: ... }
you can also write{#contract^is-dutch-law: ... }
. If the datafieldis-dutch-law
is a true/false datafield, then no implicit conversions will occur. - Instead of writing
{#contract^value > 0: ... }
you can also write{#contract^value = true: ... }
or the shorter version{#contract^value: ... }
. In both of these cases, thecontract-value
whole number datafield will be converted into a true/false, meaning it will result in true if a non-zero value is assigned to this datafield; it will instead result in false when no value is assigned at all, or when the value that is assigned to this datafield, is equal to zero.
Implicit duration conversions
When combining duration elements of different units in a calculation, conversions may be necessary — e.g., when you add 2 days
to 3 months
. You should be aware that such conversions will in many cases result in rounding errors and unexpected results, because a duration does not contain any context about the month or year it relates to.
ClauseBase will convert durations with different units into the lowest time unit, e.g. year + months will be converted to months, year + days will be converted into days, and months + weeks will be converted into weeks. ClauseBase tries to use sensible averages during this conversion, but unexpected results are bound to happen due to the lack of context.
The bottom line is that you should try to avoid these implicit conversions as much as possible.
For example, ClauseBase happens to convert 27 months + 1 day
to 822 days, because it first converts 27 months to two years (= 730 days) and 3 months (= 3 times average of 30.417 days, so 91 days), and then adds yet another day. An equally sensible result would, however, be 811 days (= 27 * 30 days + 1 day).
Similarly, ClauseBase happens to convert a month into 4.345 weeks, so that 3 months + 1 week
gets converted to 14 weeks. Other software or people would perhaps answer 13 weeks.
Implicit conversions to clause text
ClauseBase allows you insert a datafield directly into natural text. For example:

While perhaps not immediately obvious, this is also a situation where apples (clause text) are combined with oranges (number datafield). To avoid that you would have to insert all kinds of ugly codes to allow this, ClauseBase will implicitly convert this datafield into clause text for you.
In most situations, this conversion will be very straightforward. However, do note the following.
- A (whole/floating point/currency) number and a date value be converted into text in accordance with the locale styling rules (e.g., for the decimals style, currency symbol placement, short/long date format, etc.).
- A true/false value will get translated — e.g.,
Alpha {true}.
will be converted intoAlpha true
in English andAlpha vrai
in French. - A list value will have its elements printed consequentially with spaces in between — e.g.
@list("alpha", "beta", "gamma")
will be converted intoalpha beta gamma
. If you want to have comma’s plus and/or etc. in such list, or perhaps convert it into a bullet-list, then you need to use one of the special functions — e.g.,@enumerate-andor(@list("alpha", "beta", "gamma"))
.
Avoided implicit conversions
Some conversions are deliberately not performed, because they would be ambiguous:
- Adding a (whole/floating/currency) number to a duration or date. For example,
3 months + 3
results in an error because it will be difficult for the software to figure out whether you want the result to be a duration (6 months) or a whole number instead (6). - Adding a number to a date. For example,
2020_04_07 + 3
results in an error, because it is not clear whether you would like to add 3 years, 3 months, 3 weeks or 3 days instead.
Other conversions are deliberately not performed because they make no sense:
- Mixing text and whole/floating/currency numbers. It will usually be obvious that this does not make sense — e.g.
"alpha" + 6
— but this may not always be intuitive to those new to ClauseBase.
For example, if you create a text datafield#contract^value
, then ClauseBase will treat the contents of this datafield as text, and issue an error when you try to do a calculation such as#contract^value * 2
. This error will make perfect sense when the end-user would type in “real” text (such as the word “Paris”), because you can obviously not multiply Paris by two. However, when an end-user would type in a number (e.g., 3500), then it will not be so obvious why the software is complaining.
Disallowed operations
While not a data type conversion issue, you should also note that some operations are not allowed between elements of the same data type:
- You cannot mix different currencies — e.g., adding
{200 EUR + 300 USD}
results in an error. - You cannot add dates to each other — e.g.
{2020_04_03 + 2020_05_3}
results in an invalid operation error.
Dealing with the data type of if/then/else
An if/then/else construction will normally result in clause text. For example,
1. Upon drafting @CONTRACT, the parties agreed to have @CONTRACT screened by the authorities. If @CONTRACT would turn out to be ...
CONTRACT = {#contract^with-annexes: this Employment And Restructuring Contract (ERC) including its annexes | else: this ERC}
The contents of the internal snippet CONTRACT will be clause text, because its sole element is an if/then/else construction between curly braces. Such clause text can be inserted into the main body of the clause without any conversion being necessary.
However, it is also possible to use the if/then/else construction to return other data values. You can do this by wrapping the content of the if/then/else after the colon (:) between curly braces. For example:
1. Buyer will pay an amount equal to {@INTEREST * 1.5} before 1st January. If this payment is not successfully performed, it will be automatically increased to {@INTEREST * 2}.
INTEREST = {#contract^jurisdiction = "dutch": {0.05 * #contract^value} | else: {0.08 * #contract^value} }
It is interesting to analyse why you will get a “value is not a number” error when you omit the two pairs of curly braces in the if/then/else construction:
1. Buyer will pay an amount equal to {@INTEREST * 1.5} before 1st January. If this payment is not successfully performed, it will be automatically increased to {@INTEREST * 2}.
INTEREST = {#contract^jurisdiction = "dutch": 0.05 * #contract^value | else: 0.08 * #contract^value }
The reason is that ClauseBase will do the following:
- When encountering the
{@INTEREST * 1.5}
, it will replace the @SNIPPET with the contents of the internal snippet, arriving at{#contract^jurisdiction = "dutch": 0.05 * #contract^value | else: 0.08 * #contract^value } * 1.5
- It will then execute the if/then/else construction, and check whether the contract’s jurisdiction is equal to “dutch”. Assuming this is indeed the case, ClauseBase will notice that the associated fragment it has to insert into the main part of the clause is
0.05 * #contract^value
. - Because this fragment is not wrapped in curly braces, ClauseBase will treat the fragment as clause text. For example, if the contract’s value is currently set to 1000, and the current styling happens to be metric, it would convert the fragment into the text “0.05 * 1.000” . Notice that:
- because the fragment is treated as clause text, ClauseBase will treat the 0.05 literally, i.e. not consider it to be a number. Accordingly, despite the fact that the styling settings are set to metric (so that a floating point number such as 0.05 would be printed with a comma as “0,05”), ClauseBase will print it as “0.05”
- the contract’s value (1000) is a whole number that is inserted into clause text, and will therefore get converted to clause text, respecting all the styling settings (such as the metric style’s dot between the 1 and the three zeroes)
- Next, it will try to multiply the clause text “0.05 * 1.000” by the number 1.5, which obviously does not make sense because you cannot multiply text by a number. It will therefore issue a warning “value is not a number”.
Pursuant to article 3§4 of the Civil Code, #Buyer shall ...
Examples
Why is shall pay 10.20 EUR
printed as “shall pay 10.20 EUR”, while my styling settings dictate metric style with a euro-symbol?
The software is treating the 10.20 EUR as a part of natural text, and therefore prints it literally, without applying any styling to it. While, as humans, we clearly spot a special pattern (i.e., a value + currency) here, the software will blindly accept whatever natural text you insert, without further analysis.
Similar issues would arise when saying shall pay on shall pay 3 * 10 EUR
— it would be printed as such.
The solution is to state shall pay {10.20 EUR}
. This causes the software to be alerted that instead the “10.20 EUR” is something special that should not be taken literally as natural text.
Why do I see an error message with Pursuant to article 3§4 of the Civil Code, #Buyer shall ...
?
The symbol § has a special meaning to the — as is also the case for symbols such as [ ], < >, | and { } .
When you insert a §, the software assumes you are using a special reference to another clause. The solution is to turn the “3§4” into text that will be treated literally by the software, such as a text data type:
Pursuant to article {"3§4"} of the Civil Code, #Buyer shall ...
I get an “incompatible argument types” error when using the following clause:#Buyer shall pay interest equal to @days-between(#contract^start-date, @today)
The @days-between special function requires you to pass two dates as arguments.
The second argument (@today) is always available, so the problem is most likely that #contract^start-date
has not yet been assigned a value.
In such case, this datafield will result in an undefined value
, which is incompatible with (and will never be implicitly converted to) a date.
The software does not accept the following clause (shows a yellow error):#Buyer shall pay interest equal to @days-between({#contract^prolonged: #contract^prolongation-date | else: #contract^start-date}, @today)
The ClauseBase grammar does not allow you to use the if/then/else construct (the stuff between curly braces) as an argument to a special function. You can, however, change it into an internal snippet, and write:
#Buyer shall pay interest equal to @days-between(@DATE, @today)
DATE = {#contract^prolonged: #contract^prolongation-date | else: #contract^start-date}
The software will accept this — the yellow error will go away — but you will then face an “incompatible argument types” error. The reason is that the if/then/else construct in the internal snippet results in clause text, while the @days-between
function requires a date as its first argument.
The solution is to embed the date-fields in curly braces within the if/then/else. This ensures that the if/then/else does not convert the date-field in text when constructing its result.
#Buyer shall pay interest equal to @days-between(@DATE, @today)
DATE = {#contract^prolonged: {#contract^prolongation-date} | else: {#contract^start-date}}