FS FormScript

Fields and types

A field is a question. Its type says what kind of answer it expects, and the type decides what the runtime will accept, what it will refuse, and what happens when the answer is not there.

forms
form applicant version = "1.0" {
  field name      { type = string }
  field age       { type = integer }
  field income    { type = number }
  field active    { type = boolean }
  field start     { type = date }
  field signed_at { type = datetime }
  field term      { type = duration }
  field status    { type = enum("employed" = "Employed", "self" = "Self-employed") }
  field tags      { type = array of string }
  field address   { type = object { street: string, city: string } }
}

The types

Type What it holds
string text
integer a whole number, signed, 64-bit
number a decimal number, exact, 128-bit
boolean true or false
date a calendar date
datetime a moment in time
duration a length of time
enum a closed set of named options
array a list of values of one type
object a fixed set of named members

file, signature, location and geo exist too, for the kinds of answer that are a document, a signature, a place or a point on a map.

integer is not number

This is the first thing that surprises people, so it is worth being precise:

  • an integer is a whole number. 1, -3, 0.
  • a number is a decimal. 1, 1.5, 0.05.

1 + 1 is an integer. 1 + 1.0 is a number. The result types differ even when the values look alike, and the difference is observable: the specification's conformance rows check it.

The reason is money. A loan form that adds interest must not lose a cent to floating point, so number is an exact decimal with 128 bits of it, and integer is a whole number. When you write 0.05 / 12 for a monthly rate, you have written a number on purpose.

Types can narrow themselves

A type can carry the range of answers it will accept, and the runtime checks it:

forms
form applicant version = "1.0" {
  field postcode { type = string(2..10) }
  field age      { type = integer(0..130) }
  field amount   { type = number(0..1000000) }
  field birth    { type = date("YYYY-MM-DD") }
}
  • string(2..10) — between two and ten characters.
  • integer(0..130) — between zero and a hundred and thirty.
  • date("YYYY-MM-DD") — a date, and the format it is written in.

A range written in the type is checked before the user answers, so a form that can never be satisfied is reported when it is compiled rather than when somebody fills it in.

enum carries its members

An enum is a closed set of options, and it writes down both the key and the label a person sees:

forms
form applicant version = "1.0" {
  field status {
    type = enum("employed" = "Employed", "self" = "Self-employed")
  }
}

"employed" is the key — what the data says. "Employed" is the label — what the person reads. They are different strings, and keeping them apart is what lets a form be translated without touching its data.

If an option has no label of its own, the key is the label:

forms
form applicant version = "1.0" {
  field status { type = enum("employed", "self") }
}

Arrays and objects

forms
form applicant version = "1.0" {
  field tags    { type = array of string }
  field address { type = object { street: string, city: string } }
}

An array is a list of values of one type. An object is a fixed set of named members, each with its own type. Both are types you can nest, so an object may hold an array of objects.

There are no implicit conversions

FormScript does not convert "36" into 36, and it does not treat "" as false. If a field is a number and the answer is text, that is an error, and the error is reported where it happens rather than swallowed.

That is a deliberate choice. A form that quietly turns "1,50" into 150 is a form that will be wrong in a way nobody can see. When you do want a conversion, you ask for it: toNumber(name), toString(age), toBoolean(flag). Those are builtins, and they are in the built-in list of section 7.

What a field can also declare

A field is more than its type. It can also carry:

  • label — the text a person reads;
  • hint — a line of help under the label;
  • placeholder — a hint inside the control, which never replaces the label;
  • default — the answer until somebody answers;
  • value — a computed answer;
  • options — the choices, when the type is not an enum;
  • validate { … } — the rules the answer must satisfy;
  • when — the condition under which the question is asked at all.

The next two sections are about value and default, because they are the difference between a form that asks and a form that calculates.