Only this pageAll pages
Powered by GitBook
1 of 30

CSML Reference

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Standard Library

Loading...

Loading...

Loading...

Loading...

Crypto Utilities

Available in CSML v1.5+

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Sending and Receiving Messages

CSML is able to handle many types of messages by default.

To send a message to the end user, simply use the keyword say followed by the message type you want to send.

start:
  say "I'm a text message"
  say Wait(1500)
  say Text("I'm also a text message")
  say Typing(1500)
  say Url("https://clevy.io")
  say Image("https://media.giphy.com/media/ASd0Ukj0y3qMM/source.gif")
  say Video("https://www.youtube.com/watch?v=DLzxrzFCyOs")
  say Audio("https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/253508261")
  say Question(
    title = "What is love?",
    buttons = [
      Button("baby don't hurt me"),
      Button("don't hurt me"),
      Button("no more")
    ]
  )
  say Carousel(
    cards = [
      Card(
        title="title",
        subtitle="an optional description",
        image_url="https://images.unsplash.com/photo-1567322329435-28658f2958c4",
        buttons=[Button("click me!")]
      )
    ]
  )

Styling text

You can style text using Markdown. Some channels however only partially support Markdown (Messenger for example only allows bold, italic, strikethrough, but not tables), so don't get too fancy there.

Quick Markdown reference

# Title 1
## Title 2

**bold**
*italic*
~strikethrough

[link text](https://google.com)

Message types

Below is a list of default valid message components, which are automatically converted to nicely formatted messages for the channel in which the user is talking with the bot.

name

description

fallback

Text(string)

Output a simple string (with no styling attached). This is also the default type, alias of say "string". This component supports Markdown on channels that allow text formatting.

Wait(number)

Wait number milliseconds before sending the next message

Typing(number)

Display a typing indicator for number milliseconds

Wait(number)

Url(string, text="text", title="title")

Format string as the URL. Optionally, the text and title parameters can provide a way to make "nicer" links, where supported.

Text(string)

Image(string)

Display the image available at URL string

Text(string)

Video(string)

Display the video available at URL string. Supports Youtube, Dailymotion and Vimeo URLs, or mp4 and ogg.

Url(string)

Audio(string)

Display the audio available at URL string. Supports Soundcloud embed links, or mp3, wav and ogg.

Url(string)

Button(string)

Display string inside a clickable button

Text(string*)

Question(title = string(, buttons = [Button]))

Display a list of buttons with a header of string. Title parameter is optional.

Text(string) + list of buttons

Card(title="string", buttons=[Button], image_url="string")

Display nicely formatted content in a Carousel component (on supported channels). Only title is mandatory.

Question(title, buttons) or Text(title)

Carousel(cards=[Card])

Display a list of Cardcomponents in a carousel (on supported channels). The cards parameter is mandatory.

Components may receive additional optional parameters to accommodate the needs of certain channels.

For example, in the Messenger channel you can add a button_type="quick_reply" parameter to the Question component to provide a different type of buttons.

Asking Questions

A conversation with a chatbot is not very different from a human-to-human dialogue: sometimes the user talks, sometimes the bot talks.

CSML provides a solution when you need the chatbot wait for the user's input: using the hold keyword, the chatbot will remember its position in the conversation and simply wait until the user says something, then continue from there.

somestep:
  say Question("Do you like cheese?", buttons=[Button("yes"), Button("no")])
  
  hold

  if (event == "yes") say "I'm glad to know that you like cheese!"
  else say "Oh that's too bad!"

Receiving messages

When receiving an event from an end-user, the CSML interpreter will try to:

  1. match it with a new flow,

  2. or consume it in the existing conversation,

  3. or trigger the default flow as defined in the bot settings,

by order of priority.

CSML Events are made available in the script when a step is triggered by a user's input. The event object is a complex structure which contains a lot more than just the string that was typed by the user. It contains a lot of information that is used by the CSML interpreter, for example the custom button payload to match a list of choices offered to the user. This makes it easy to handle both a click on a "OK" button or the user typing the word "yes".

By default, events are only expected when:

  • the flow was just triggered, the current step being triggered is start

  • the bot asked something to the user, and is waiting for the user's input at the same step

In those cases, a local variable, event, is made available, with the content of the user request. When no event is available, event is set to NULL.

⚠️ Limitations

The maximum theoretical size of a say payload is 16KB. However, each channel will have different limitations depending on the type of component. For a Text component for example, most channels are limited to a few hundred characters.

Please refer to each channel's official documentation to find out the practical limitations of each component.

Memory

Any good chatbot probably needs to be able to save and work with information gathered from the user at some point. The memory API provided in CSML is one of the most important parts of the language.

As you will see, some variables need to be stored in a long-term memory, while some variables are just temporary variables needed for local work. Some information is also provided by the context of the user and injected by the channel itself at the beginning of the conversation.

In this chapter, we will see how to save and use various types of memories depending on your scenario.

Installing CSML

No installation required: with CSML Playground

CSML Studio includes everything you need to create, develop and maintain your chatbots. You get a code editor and test chatbox, built-in services to author functions and install custom or managed integrations, deploy to many different communication channels including Messenger, Workplace Chat, Microsoft Teams, Slack, Whatsapp... and monitor the usage with custom analytics.

Install CSML Locally

With Docker

Using a pre-built CSML Engine binary

Compile From Source

Message Payloads

Standard message components

CSML message components all have a matching message format for client use in regular JSON. They can be extended by adding additional properties to the content wrapper.

Text()

Typing()

Wait()

Url()

Image()

Audio()

Video()

File()

Button()

Payload()

Question()

Child components

Component payloads can be included into one another seamlessly. For example:

The Event

The event keyword is a special variable in CSML that contains whatever the user did last. It can be generally be used as a simple string, but it is in fact a much more complex object that contains a lot of information about what the user did.

Event methods

event contains a number of useful methods that can be used to perform various operations.

event.match()

In many cases we want to find whether the user's input matches some value to perform conditional logic. For example, in the following example, we want to make sure that the user clicked on the right answer:

In some cases, you may also want to make sure that the user clicked on a button or typed any accepted response, in which case you can add several parameters to the .match() method:

The result of event.match(...) is whatever was matched, or Null. So the above example, if using the language to its full potential, could also be used like this for even more control over what value is remembered:

event.match_array()

Similar to event.match(), but takes an array as argument. It also returns the matched value, if any. In many places, it is easier to use as listing all the individual items of the array. The same example as above would now read:

event.get_type()

Returns the type of event. For example, if the user typed something, event.get_type() will be "text". Or if the user clicked on a button (which yields a payload the type of event is "payload".

Accessing event content values

To access any value in the content body of the event, simply use the dot notation with its expected property name. If it does not exist, this will return Null.

Temporary and Long-Term variables

Temporary Variables

Local or temporary variables are only usable within the current step. It is rather a way to write more readable, expressive code. But they are really powerful, because they allow you to do everything that a regular memory does, but temporarily. For more information about local variables, see the as keyword.

Local variables are useful for temporary calculations. You do not want to memorize your internal temporary steps, rather save useful information about the user.

Long-Term Memory

Long-term memories on the other hand are at the core of the purpose of CSML. When two persons converse, they constantly memorize information, which they can reuse in other discussions. For example, if I gathered that my friend likes Iron Man, I might propose later that we go see Captain America together. I do not have to ask them again about their favorite film, because I already learned this information before, even if it was in a different conversation, even if the topic of that conversation might have been completely unrelated.

The memory API is very powerful: by default a bot never forgets anything about the current user. For more information, see the remember keyword.

The Memory object

CSML provides a _memory global, read-only variable, which contains all variables saved until now for the current user. This especially useful if you need to debug the state of a user's memory at any given step:

⚠️ Limitations

Variables and memories (of any type) can not be larger than 50KB. Using data larger than 50KB in variables (for example as the return value of a function) may result in undefined behavior. Also, please note that messages larger than 30KB in size can not be displayed in the test webapp.

Custom Components

Custom components are clearly an advanced feature of CSML. Unless you are missing specific features with the default capabilities of standard CSML message components, you probably will never need to learn more about this topic!

Feel free to skip to the next section 😉

Introduction to CSML Component templates

Under the hood, all message components are created using a JSON-based templating system to describe how the component should behave, what properties it should accept, which are required or optional, their type, etc.

For instance, here is the low-level code to generate the simple Text() component:

Which outputs the following JSON payload when used in a CSML flow:

Obviously, more complex components are also possible. Here is an example with the standard Button() component:

You will notice the use of the $_get keyword. This function retrieves the value of the given parameter and sets if in place. So for example, in our example, the button's payload will be set to what the developers sets, or by default to the value of the title parameter. Same thing goes for the accepts property, which results in the following output:

Creating Custom Message Components

params

Each component is made of a list of params, that all have the same structure:

required

if set to true, will display an error when the value is not set in the flow

type

describe the type of the parameter to accept

  • Null

  • Bool

  • Number

  • String

  • Array

  • Object

default_value

default value to set on the object

add_value

values to add to the object in all cases (even if a default value is set)

$_get, $_set

  • $_get: will inject the value of the given parameter

  • $_set: will set the field to a specific value

Why Use Custom Components?

In some cases, you may want to specialize your chatbot for one specific channel, which has some complex formats available that you want to make easier to use. For example, CSML Studio has a QuickReply custom component, which is a quicker way to create quick replies, which are features of Messenger, Workplace Chat and the Webapp channels.

If you are making a specialized chatbot for a specific channel, consider using custom components to make your life easier!

How to use Custom Message Components

There are two ways to import your own custom components in the CSML Engine and use them in your flows.

  • as local components by adding a list of custom components to your chatbot's configuration when calling the engine. In that case, you should add a custom_components parameter to your bot description with an array of key/value objects where the key is the name of the component and the value its definition.

Native components and local components behave exactly as standard components. The only difference is that local components are used with a prefix (Component.MyComponent) in your flows, whereas native components can be called directly (MyComponent). This allows you to define different components per bot for the same engine instance, while being able to use common components across all your chatbots. Similarly, local components are easier to share with other users of your chatbots as they can be packaged together.

As an open-source programming language, there are several ways you can get started with CSML, either by compiling the sources yourself, using Docker, or using , a free online development environment provided by .

The easiest way to create CSML Chatbots is by using the free online development playground available at . No dependencies, no installation, no signup required!

➡ Try CSML Playground now on !

You can also create a free account on CSML Studio, a professional online development and deployment tool for all your CSML chatbots. Read more , and head over to the !

➡ Try CSML Studio now on !

To run CSML on your own machine, you will need a way to run the engine (CSML comes with bindings for nodejs or directly in rust), as well as a database. Optionally, you will also need a custom App runtime ().

explains how to run the CSML engine on your own server. It may be a useful starting point for a custom installation of CSML!

We provide an easy-to-use docker image to run CSML on your own computer. To get started, follow the instructions .

We provide builds for macOS and linux (arm64) for every new version of the CSML engine. You can find them on the releases page of the . For the latest available version, visit .

CSML is written in Rust. You can find the source code of the CSML interpreter and engine on , and compile it from source as per the instructions.

In its essence, event contains the as received by the bot. There is a type and a content. The content is an object that contains a text, a payload, a url, other objects... it can be anything, and in general, its content can be derived from the events type. However, using it as "{{event}}" in a string (or simply in say event without any curly brace and double quote) will behave as a string.

In addition to the , you can also add your own custom message components using the same templating format.

as native components by saving your all your custom component files in files in a given directory, and set the COMPONENTS_DIR environment variable in your . In that case, the name of the component will be the name of the file (without the extension).

CSML Studio
Clevy.io
https://play.csml.dev
https://play.csml.dev
about CSML Studio here
Studio documentation
https://studio.csml.dev
more about this on this page
This blog post
on Docker Hub
CSML Engine github repository
https://github.com/CSML-by-Clevy/csml-engine/releases/latest
this github repository
> Text()
{
  "content": {
    "text": "message"
  },
  "content_type": "text"
}
> Typing()
{
  "content": {
    "duration": 1000
  },
  "content_type": "typing"
}
> Wait()
{
  "content": {
    "duration": 1000
  },
  "content_type": "wait"
}
> Url()
{
  "content": {
    "url": "https://example.com",
    "title": "title",
    "text": "text"
  },
  "content_type": "url"
}
> Image()
{
  "content": {
    "url": "https://example.com/image.jpg",
  },
  "content_type": "image"
}
> Audio()
{
  "content": {
    "url": "https://example.com/audio.mp3",
  },
  "content_type": "audio"
}
> Video()
{
  "content": {
    "url": "https://example.com/video.mp4",
  },
  "content_type": "video"
}
> File()
{
  "content": {
    "url": "https://example.com/video.mp4",
  },
  "content_type": "file"
}
> Button()
{
  "content": {
    "title": "Button title",
    "payload": "Button payload"
  },
  "content_type": "button"
}
> Payload()
{
  "content": {
    "payload": "Custom payload"
  },
  "content_type": "payload"
}
> Question()
{
  "content": {
    "title": "Question",
    "buttons": [Button]
  },
  "content_type": "question"
}
// CSML
say Question(
    "Where is Brian",
    buttons = [
        Button("In the kitchen"),
        Button("Somewhere else"),
    ]
)

// JSON output
{
    "content": {
    "title": "Where is Brian",
    "buttons": [
      {
        "content_type": "button",
        "content": {
          "title": "In the kitchen",
        }
      },
      {
        "content_type": "button",
        "content": {
          "title": "Somewhere else",
        }
      },
    ],
  },
  "content_type": "question"
}
say Question(
  "What is 2+2?",
  buttons=[Button(4) as ok, Button(5) as notok]
)
hold

if (event.match(ok)) say "That's correct!"
else say "Wrong answer. The right answer was {{ok.title}}"
say Question(
  "What is your favorite color",
  buttons=[
    Button("red") as btnred,
    Button("blue") as btnblue,
  ]
)
hold

// this will be true if the user clicked
// on one of the buttons, or typed "red" or "blue"
if (event.match(btnred, btnblue)) remember favcolor = event
else say "{{event}} is not a valid answer"
say Question(
  "What is your favorite color",
  buttons=[
    Button("red", accepts=["Red", "Dark orange"], payload="COLOR_RED") as btnred,
    Button("blue", accepts=["Blue", "sky", "navy"], payload="COLOR_BLUE") as btnblue,
  ]
)
hold

// for a valid choice, the user can either click on the button or 
// manually type any of the accepted options for each button
do matched = event.match(btnred, btnblue)

// matched will contain the matched button object or Null

// in case it contains the button object
// we can prefer to remember its manually set payload
// instead of the button's title
if (matched) remember favcolor = matched.payload

// or continue the same way as before
else say "{{event}} is not a valid answer"
say Question(
  "What is your favorite color",
  buttons=[
    Button("red", accepts=["Red", "Dark orange"]),
    Button("blue", accepts=["Blue", "sky", "navy"]),
  ] as buttons
)
hold

// with match_array(), you don't have to list all options
do matched = event.match_array(buttons)

if (matched) remember favcolor = matched.title
else say "{{event}} is not a valid answer"
say "Type something below"
hold
say event.get_type() // "text"
// Example event object:
{
  "content_type": "text",
  "content": {
    // a valid text event will always have a content.text property
    "text": "Hi there", 
    // additional properties can be added
    "more_data": "some extra data",
    // extra properties of an event can be of any type.
    "some_number": 42,
    "an_object": { "wow": 1.23456, "impressive": true }
  }
}

say "text: {{event.text}}" // Hi there
say "more_data: {{event.more_data}}" // some extra data
say "doesnotexist: {{event.doesnotexist}}" // Null
somestep:
  do tmpvar = "Hi there"
  say tmpvar // "Hi there"
  goto otherstep
  
otherstep:
  say tmpvar // NULL
somestep:
  remember tmpvar = "Hi there"
  say tmpvar // "Hi there"
  goto otherstep
  
otherstep:
  say tmpvar // "Hi there"
somestep:
  remember something = 1
  // ...

someotherstep:
  do something = 8
  // ...
  
wherever:
  // when we get there, is `something` set to 1, 8
  // or even available at all?
  say "{{_memory}}"
  
  
{
  "params": [
    {
      "text": {
        "required": true,
        "type": "String",
      }
    }
  ]
}
{
  "content_type": "text",
  "content": {
    "text": "Hello!"
  }
}
{
  "params": [
    {
      "title": {
        "required": true,
        "type": "String"
      }
    },
    {
      "payload": {
        "required": false,
        "type": "String",
        "default_value": [
          {"$_get": "title"}
        ]
      }
    },
    {
      "accepts": {
        "required": false,
        "type": "Array",
        "default_value": [
        ],
        "add_value": [
          {"$_get": "title" },
          {"$_get": "payload" }
        ]
      }
    }
  ]
}
// input: Button(title = "hi")

// output:
{
  "content_type": "button",
  "content": {
    "title": "hi",
    // retrieve the title by default
    "payload": "hi",
    // in this case, we are retrieving the value of `title` and the value
    // of `payload` which happens to be set to the value
    // of `title` by default and not overriden by any user value.
    "accepts": ["hi", "hi"]
  }
}
{
  "MyComponent": {
    "params": [
      {
        "title": {
          "required": true,
          "type": "String",
        }
      },
      {
        "info": {
          "required": false,
          "type": "String",
          "default_value": [
            {"$_set": "this is a default message" },
          ]
        }
      },
      {
        "list": {
          "required": false,
          "type": "Array",
          "add_value": [
              {"$_get": "title" },
          ]
        }
      }
    ]
  }
}
{
  "params": [
    {
      "param_name": {
        "required": Boolean,
        "type": Type,
        "default_value": Array,
        "add_value": Array
      }
    }
  ]
}
  "default_value": [
      {"$_get": "title"},
  ]
  "add_value": [
      {"$_set": "text" },
  ]
say MessengerQuickReply(
    "here are some quick replies",
    buttons=["btn1", "btn2", "btn3"]
)

// same output, with the standard Question component
say Question(
    "here are some quick replies",
    button_type="quick_reply",
    buttons=[Button("btn1"), Button("btn2"), Button("btn3")]
)
// as a native component
start:
  say MyComponent("this is my custom component")
  goto end

// as local component
start:
  say Component.MyComponent("this is my custom component")
  goto end

// JSON output
{
  "content": {
    "title": "this is my custom component",
    "info": "this is a default message",
    "list": [
      "this is my custom component",
    ]
  },
  "content_type": "mycomponent"
}

Using Variables in Messages

To output the value of any variable in a string, you can use the string interpolation helper: double curly braces {{ }}.

This makes it extremely easy to concatenate multiple values of any type into a single string. Objects, arrays and numbers will be stringified automatically and missing properties will be evaluated as NULL.

start:
  say "Hi! My name is {{what}}"
  say "My name is {{who}}"
  say "My name is {{slim_shady}}"

// Printing values from object properties is also possible:
start:
  say "Hi! My name is {{eminem.what}}"
  say "My name is {{eminem.who}}"
  say "My name is {{eminem.slim_shady}}"

// Alternatively, the following syntax is possible too, but will result in only one text bubble:
start:
  say "Hi! My name is {{what}}. My name is {{who}}. My name is {{slim_shady}}"

To concatenate two strings, you can also use the + operator (starting in CSML v1.8.0):

// the three lines below are equivalent
say firstname + " likes " + food
say "{{firstname}} likes {{food}}"
say [firstname, "likes", food].join(" ")
full message payload
standard components
CSML Engine

Introduction

What is CSML?

CSML (Conversational Standard Meta Language) is an Open-Source, Domain-Specific Language designed for developing rich conversational experiences easily. It makes creating powerful chatbots extremely easy.

Written itself in Rust, the purpose of this language is to simplify the creation and maintenance of rich conversational interactions between humans and machines. With a very expressive and text-only syntax, CSML flows are easy to write and understand, making it easy to deploy and maintain conversational agents.

CSML natively handles short and long-term memory slots, metadata injection, and connecting to any third party API or injecting arbitrary code in any programming language thanks to its powerful runtime APIs.

Getting started

/* Always start the conversation with this step */
start:
  /* The bot knows the name of the user */
  if (username) {
    say "Hi {{username}}!"
    say "It's nice to see you again."
    goto end
  }

  /* This is the first time we speak with the user */
  else {
    say "Hello World!"
    goto getname
  }


/* Step to retrieve the user's name */
getname:
  say OneOf(["What's your name?", "How do people call you?"])
  hold

  remember username = event
  say "OK, I now know that your name is {{event}}."
  goto end

This is a simple CSML flow example, which describes a conversation between a user and a machine.

A CSML flow contains several steps, which are defined by a name (which must be unique within a flow) followed by a colon. Each step contains instructions on how to handle the interactions within the step, what to say, what to remember, where to go. The syntax is very descriptive and contains very few keywords.

This goal of the example flow on the right is to retrieve the name of the user.

To do so, we will first check if we already have it in memory. If so, we can use it to greet our user, and close this conversation. Otherwise, we can go to a different step and simply ask for it. When the user responds, we will be able to remember the name for future use, and close the conversation.

This simple flow shows a few of the features of the language. There are many more, which we will go into in detail now!

Native CSML Functions

Native CSML Functions

CSML functions are a simple way to define simple functional code that can be reused across all of your flows. They are defined in any flow file and use the same CSML syntax that is available in any other steps.

Conceptually, they can be viewed as simple placeholders that you can insert at any place in any flow, to perform repetitive tasks.

Declaring a function is similar to declaring a step with some minor differences:

  • the name of the function is preceded by the keyword fn ,

  • there is a list of parameters between parentheses after the name of the function,

  • but also, like a step declaration, it ends with a semicolon : or is surrounded by braces {...} (new syntax introduced in v1.10.0)

// v1.10.0+ syntax
fn my_function_name(param1, param2, param3, ...) {
    /** do something **/
    return some_val
}

// old syntax, but still supported in newer versions!
fn my_function_name(param1, param2, param3, ...):
    /** do something **/
    return some_val

Here is an example of a simple my_double_add() function, that takes 2 parameters, multiplies each of them by 2, then adds those numbers together, and returns the result:

fn my_double_add(x, y) {
  do dbl_x = x * 2
  do dbl_y = y * 2
  return dbl_x + dbl_y
}

start:
  do value = my_double_add(1, 2)
  say "The result is: {{value}}" // 6
  goto end

There is no limit on how many functions a flow can have, and of course you can call a function from within another function. The above example could be rewritten as:

fn my_double(num) {
  return num * 2
}

fn my_add(a, b) {
  return a + b
}

fn my_double_add(x, y) {
  do dbl_x = my_double(x)
  do dbl_y = my_double(y)
  return my_add(dbl_x, dbl_y)
}

Limitations

CSML native functions are isolated from the bot itself and can't interact with the memory or display messages. They have only access to non-state-binding keywords: do, if, foreach and return.

// Forbidden
fn my_func(a) {
    say "hello"
    remember something = 42
    hold
    goto somestep
}

However, other builtins can be used!

// Accepted
fn my_func(a) {
    do x = 0
    foreach (item) in a {
        do x = x + item.value
        if (x > 42) break
    }
    do HTTP("https://myapi.com/").post().send()
    return x
}

Importing Functions From Other Flows

Functions are by default attached to the flow where they are written. However, you can import functions from other flows to reuse them elsewhere!

// import this function specifically from this flow
import my_function from flow_2

start:
  do value = my_function(1, 2)
  say value
  goto end

You can also use a global import by not defining the flow from which a function can be imported.

// import a function with this name from any other flow
import my_function

Warning! If there is more than one flow defining a function with the same name, it will just import one of them randomly.

Don't use the same name on several functions, or specify which flow you want to import from

Advanced usage

You can import multiple functions at once, and even rename them:

// import several functions at once
import {my_function, other_function} from flow_2

// you can rename a function
import my_function as better_name from flow_2

start:
  do value = better_name(42)
  say "The result is {{value}}"

Modules

introduced in CSML v1.10.0

CSML Modules are an easy way to reuse CSML Functions across multiple chatbots or to provide access to your own services to other chatbot developers. You can create functions in separate flows that you can host on a repository to ease access.

A module at its core is simply a valid CSML flow, that exposes one or several functions, which can be imported into other flows without needing to copy the flow multiple times in multiple bots. An other benefit of using modules is that it makes it easier to replicate changes across multiple changes.

Usage

Example

Let's import the buttons module into a flow to access the YesNoButtons function and make it easier to display Yes/No types of buttons.

The module specification is as follows:

Then you can simply import it in your CSML script as you would any other function, the only difference being the modules/ prefix that tells CSML that it should look for this function in the buttons module and not in the bot's other flows.

import YesNoButtons from modules/buttons

start:
  say Question("Do you like strawberries?", buttons=YesNotButtons("Yes, of course", "Not really"))
  hold
  say "Your answer was: {{event}}"

Global variables

Inside any given step, a number of global variables are made available to the developer. These variables include past memories, step context, and request metadata. Some of these variables are available globally, including in CSML functions:

  • _metadata: read-only object that is injected into the conversation by the channel. Usage: _metadata.something

  • _env: read-only object containing the bot's defined environment variables. Usage: _env.something

  • _memory: read-only object containing the user's current memory state. This is especially helpful if you need to print the full state of the user's memory for debugging purposes!

_metadata: user-level context

The _metadata global contains user-level context for each request.

This memory type is injected by the channel at the beginning of the conversation and contains any data that the channel already knows about the user that can be used in the conversation later. For example, this could include the user's full name or email address, their job title, where they are from...

say "Hello {{_metadata.firstname}} {{_metadata.lastname}}!" // Hello Tony Stark!

_context: holds flow positioning information

The _context can be found under _metadata and contains current_step, current_flow and default_flow

This memory type is injected and updated automaticaly at the start of each step

say _metadata._context.current_step

goto @$_metadata._context.default_flow

_env: bot-level context

The _env global contains bot-level context for the entire bot and is shared across all users of the bot.

This memory type is injected by the bot itself and is meant to contain any data that the bot would need for common tasks across all users. For example, this is where you store the API keys, endpoints, feature flags...

do HTTP("{{_env.COUNTRY_API_ENDPOINT}}/all")
  .set({ "Authorization": "Bearer {{_env.COUNTRY_API_TOKEN}}" })
  .get()
  .send()

Automatic Type Inference

All incoming events are theoretically strings.

The reason is simple: there is no way for a chat client to differenciate between for example a number type and a string type as this was typed into an input field or spoken to a voice interface.

However, the language must have a way to perform arithmetic operations anyway.

At the same time, any external function used in the program can return any arbitrary JSON-like object representation, or string, or any type of value.

To handle all these cases at runtime, the CSML interpreter will automatically detect the type of any variable and try to make the best decision depending on how you want to use it. This process is called type inference.

The example below shows how it works for numbers.

Conditional Logic

As a CSML developer, you can easily implement any logic based on any variable or event, and very expressively decide how your conversation should be handled. Own of the main advantages of CSML is its descriptive textual interface, making it easy for anyone to understand the logic. It just makes sense and hides all the complexity of decision trees behind the simple concepts that every developer knows and uses.

A large part of developing CSML flows is about finding out whether an event matches a value in order to redirect the user to one step or another. The language comes with a number of helpers and operators:

  • comparison operators ==, !=, <, >, <=, >=

  • match keyword

  • && and || operators

  • if, else if, else keywords

Navigating in a CSML Bot

Navigating within a flow

A flow is made of at least one step (called start), but can also contain as many steps as necessary. You should think of steps as individual, simple bits of logic, linked inside the same conversation topic (which is the flow).

To go from one step to the next, you can simply use the keyword goto (or goto step) followed by the name of the step.

To finish a flow (and close the conversation), add goto end.

If, at the end of a step, the goto end instruction is omitted, the conversation will be closed anyway.

In other words,goto endcan be used to exit of a conversation early at any point, but it is implicitly added at the end of all steps if no other goto instruction is present.

Navigating between flows

Similarly to navigating between steps of the same flow, you can go to the beginning of any other flow by using the goto flow keyword. This will trigger the start step of the target flow and close the current flow, so coming back to the first flow will actually go back to the beginning of that flow.

Advanced navigation

Introduced in CSML v1.2

If you want to reach a specific step in a specific flow, you can use the @ notation:

This is the universal way of navigating between steps and flow. The above two methods are actually special cases of this notation:

  • When the @flow_name part is not specified, CSML interprets it as "in the current flow". So goto stepname actually is shorthand notation for goto stepname@current_flow, where current_flow is dynamically matched from the name of the current flow, and works accordingly.

  • When the step_name part is not specified, CSML interprets it as start. So as @ means "flow", goto @flow_name is the same as goto start@flow_name which is also the same as goto flow flow_name.

Examples

Dynamic step and flow names

Introduced in CSML v1.5

As you can see above, steps and flows use strings as identifiers, but they are expressed without double quotes "...". In order to navigate to dynamically set steps or flows, CSML adds a special syntax, similar to the dereference pointer concept in other languages.

To go to a variable step or flow, you can reference the variable name prefixed with a $ sign:

Any of the syntaxes presented above are supported. For instance:

Please note that dot notation (accessing child properties in an object or array, i.e a.b) is not supported in variable flow and step names.

HTTP Client

CSML includes a native HTTP client. The following verbs are accepted:

  • GET

  • POST

  • PUT

  • PATCH

  • DELETE

HTTP will automatically add the following headers to any request:

  • Content-Type: application/json;

  • Accept: application/json

In other words, HTTP requests only allow you to send and query json-formatted data.

Performing a HTTP request

To build your request, you can chain explicit methods to the HTTP function. No request is actually performed until .send() is added, allowing you to gradually build your request.

Here is a simple example:

You can also create more complex requests:

The available methods are:

  • .get() / .post(body) / .put(body) / .patch(body) / .delete() : set the request verb, and add an optional JSON-formatted body

  • .set(data): set the request headers, where {"x-api-key":"somevalue"} is added as x-api-key:somevalue headers

  • .auth(username, password) set basic auth headers

  • .query(data): set the query strings, where {"key":"value"} is automatically translated to ?key=value

  • .disable_ssl_verify() : for situations where you know that the endpoint comes with an invalid SSL certificate, you can disable SSL verification to bypass this protection. This option should only be used if you know what you are doing!

Analyzing the response

You can check whether the call returned a response by using the .is_error() method on the response object.

The .get_info() method lets you inspect the response of HTTP calls, even when the call returns an error (in which case .get_info() will also contain the body):

.is_error() and .get_info() are very useful methods to correctly handle HTTP responses!

External Code Execution

The CSML engine is able to automatically handle the execution of any payload, any code, in any language, thanks to the built-in App() macro. With CSML Apps, you can integrate your own business logic, on your own servers, in your language of choice.

Deprecation notice: the original Fn() notation for calling Apps (formerly called Functions) in your CSML code has been replaced by the newer App() built-in as of CSML v1.5. Both notations will continue to work until CSML v2 is released, but this documentation will only reference the new App() usage from now on.

Running App on CSML Studio

Executing App calls

To execute external functions in any programming language when provided a fn_endpoint.

For example, when a App is called, a HTTP POST request will be performed to the provided fn_endpoint with the following payload:

The endpoint should return a JSON payload, formatted as follows:

If the function fails, or returns an invalid payload, CSML will convert it as a Null value.

Installing a custom nodejs App runtime

Follow the instructions to install and add your own apps!

Value Types

CSML is a dynamically-typed language, even though it is written in Rust, a strong-typed, memory-safe, low-level language. It can handle a lot of different types but some magic happens behind the scenes to make it as easy for the developers to use the language.

In this section, let's learn more about how CSML handles the various quirks a chatbot needs to solve!

Literals

CSML is able to natively understand literal types (int, float, string, ...).

integer and float are separate types, but most of the time you should not have to worry about it and consider it as a more generic and virtual number type.

You can also express booleans with true or false .

Since CSML v1.1, you can also use \n, \t, \r , \ and " characters in strings, with proper escaping (\ and " must be preceded by a \ while a single \ will be ignored). For example:

NULL

NULL is its own type. Missing values evaluate to NULL. The CSML interpreter will automatically parse the object with the usual dot notation x.y.z and detect the type of the resulting property. If one of the properties in the chain does not exist or is not an object itself, it will evaluate to NULL.

Objects

You can create an object by returning a JSON-like object from any function, or directly in the CSML code with the Object() helper function or by using a shorthand notation similar to JSON format.

Arrays

You can also iterate over an array (represented by items inside square brackets: ["a", "b", "c"]) with the foreach keyword, and access any of its items by using its index in square brackets notation: items[2].

Note that foreach creates a copy of each item as it iterates over the array. So for example, in this next example, the array is not modified:

You can modify the contents of an array either by assigning a new value to any of the items in the array, adding new items at the end with array.push(elem) or removing the last element with array.pop().

Printing non-literal values

To print any value as a string, simply use the Text(value) component or use the curly-brace template syntax "{{value}}"

JWT

The JWT module lets you encode, decode and verify Json Web Tokens easily!

Methods

sign

exemple: JWT(data).sign(algorithm, secret)

  • data: json object to sign and convert to a JWT token

  • algorithm: see below

  • secret: a secret string used to sign the JWT

This method returns a properly encoded JWT as a string.

decode

exemple: JWT(token).decode(algorithm, secret)

  • token: the token to decode

Note: decode does not try to verify that the token is valid. It simply decodes its payload back to the original form.

This method returns the full JWT data, in the form:{ "payload": { ... your data }, "headers": { "alg": ALG, "typ": "JWT" }}

verify

exemple: JWT(token).verify(claims, algorithm, secret)

  • claims: set of claims (in JSON form) to verify the JWT against

This method returns the full JWT data, in the form:{ "payload": { ... your data }, "headers": { "alg": ALG, "typ": "JWT" }}

Supported algorithms

JWT supports the following algorithms: HS256, HS384, HS512 (corresponding to HMAC using SHA-256, SHA-384 and SHA-512 respectively).

The secret key must be a valid, url-safe string.

Error handling

When a sign, decode or verify operation fails, Null is returned and a say Error(error_message) is emitted.

SMTP Client

CSML includes a native SMTP client, allowing you to easily send emails from your chatbot.

Built-in Functions

Macros are built-in functions that perform some common tasks. Below is a list of some common macros that you can use at any stage in your CSML flows.

OneOf

Return one the elements of the array passed in the first argument at random.

Useful for randomisation of dialogue parts!

Or

If the first element exists return it, otherwise return the second argument as a default value

Shuffle

Given an array, return it with its elements in a random order.

Especially useful in questions to randomize the available options!

Find

Return whether a string is contained in another string.

Length

Return the length of a given string or array

Random

Return a random floating point number in the range 0-1 (0 included, 1 excluded).

Floor

Return the largest integer less than or equal to the given number.

This is useful to generate a random integer in a given range:

UUID

Generate a random UUID (v1 or v4, defaults to v4)

Time

The Time() helpers lets your manipulate timestamps and dates easily.

Exists

Check if a variable has been saved in the chatbot's memory before for the current user.

Exists only checks for the existance of top-level variable names in the chatbot's memory for the current user, i.e obj and not obj.prop

Keywords

Action keywords

goto

Inside a step, goto some other step. The step's memories are saved and its messages are sent right after this instruction, and before the next step is started.

Special cases:

  • If the target step does not exist, the conversation will be closed.

  • If the target step is end, the conversation is closed.

goto behaves as return in other languages: anything after a goto is ignored, the goto is immediately executed.

To reach another flow, you can also use goto flow otherflow .

say

Send a message to the end user. For the full reference on Components

debug

Print a simplify version of the following value. Its output is a special debug component (same structure as a Text component with a debug content_type).

If the value is a primitive (String, Number...), it will be printed entirely. If it is an Object or Array, only the structure of the first level will be printed.

Unlike the say keyword, it can also be used inside native CSML functions:

hold

Wait for user input: simply hold the conversation in place until the user responds.

hold_secure

introduced in CSML v1.10.0

Same as hold, except any user input that comes after that will not be saved in any CSML memory or displayable. However, you can perform operations on the value itself. This is a good use case for secret values that should not be saved in clear text anywhere in a database:

This keyword should be used with extreme care. It is for example a very bad practice to ask users for their password in a chat, and it should not be used for this purpose. However, this keyword can be extremely useful to share data (personal information, short-lived secrets...) that requires extra precautions or should not be stored in clear text (or at all).

remember

Save a value to the bot's memory with the given key. It can later be retrieved (as soon as the next step) with "{{memory_item}}".

By default, the scope is bot/user/channel. The same user on a different channel, or a different user on the same channel, or the same user with a different bot will get a fresh memory instance.

do

Execute the following expression.

Usually used for executing functions without caring for its return value, or for updating values of objects or arrays.

When used in an assignment (as in x = y), the value x is saved as a local (temporary) variable.

forget

The forget keyword lets you forget memories selectively, or globally.

Root-level keywords

These keywords can only be used at the root of the flow, outside of any step

const

Declare constant values that are accessible and immutable across the whole flow. Two different flows can have a different set of const or different values for the same const!

import

Other keywords

as

Save any value as a local variable, only available within the step. Local variables remain in memory after the step is done.

if / else if / else

Simple logic operators if, else if and else. See examples.

foreach

Iterate over each element of an array.

while

A simple loop with a condition check at every turn

break, continue

Exit from loop early, or skip an iteration

Deprecated keywords

match (deprecated)

This syntax is obsolete, but for backwards-compatibility reasons remains valid CSML. However, prefer using the event.match(...) alternative which is much more versatile.

Whether or not a variable "equals", in any way, another variable.

use..as (deprecated)

This syntax is obsolete, but for backwards-compatibility reasons remains valid CSML.

See as keyword.

Operators

Mathematical Operators

In CSML, you can use the 4 basic mathematical operators +, -, * and /, as well as the modulo operator %. The regular order of operations applies.

Additionally, since CSML v1.8.0, you can use the shortcut += and -= operators to add/subtract and assign values at the same time:

String Concatenation

Starting with CSML v1.8.0, you can concatenate two or more strings together by using the + sign:

It is also possible to use string templating to insert any value in another string:

Base64, Hex

You can easily encode and decode data to and from Base64 and Hex:

This can for example be useful when using Basic Auth in HTTP calls:

But in general, they are useful methods to encode/decode non-URL-safe or non-ASCII data to ensure exchanges between systems run smoothly.

Base64 and Hex are NOT encryption methods!

By using the CSML language, any developer can integrate arbitrarily complex conversational agents on any channel (Facebook Messenger, Slack, Facebook Workplace, Microsoft Teams, custom webapp, ...) and make any bot available to any end user. In addition to the language itself, , an online, full-featured development and deployment platform, comes with a large number of channel integrations that work out of the box, but developers are free to add new custom integrations by using the CSML interfaces.

To use a module, you must first declare it as a dependancy to your chatbot. when creating a new version of a chatbot or chatting with an unversioned chatbot.

Some example modules are given in this repository:

name: buttons url: auth: none

event: contains the user input, or NULL when empty. See .

As in any programming language, CSML is able to handle any type of logic, in the form of loops and if/else statements.

When used together with custom serverless function runtimes (cloud-based such as , , ), or on-premise with , or ), CSML Functions are a good way to execute custom business logic outside of the context of the conversation.

When using the , the heavy setup of creating an App runtime is already done for you, which means that you can easily import functions in java, python, nodejs, go... without any additional setup, simply by uploading your code and calling the function in your CSML script.

CSML Studio also comes with that are installable in one-click.

The following repository provides an easy way to run your own nodejs runtime:

You can also use to apply changes or get data from arrays.

Note: only valid claims are verified. See the list of official claims in the JWT specs .

CSML's Time function is based on Rust's Chrono library. All the formatting options are listed here:

see .

CSML Studio
This is done in the modules section of the body
https://github.com/CSML-by-Clevy/csml-modules
https://raw.githubusercontent.com/CSML-by-Clevy/csml-modules/master/modules/buttons.csml
The Event
/* event = "42" */

guessthenumber:
  say "pick a number!"
  hold

  /* compare the input with an arbitrary target number */
  if (event > 57) {
    say "Too high!"
    goto guessthenumber
  }
  else if (event < 57) {
    say "Too low!"
    goto guessthenumber
  }
  /* use the number as a string as well */
  else {
    say event
    say "You are right, {{event}} is the right number!"
    goto end
  }
do btn = Button("yes")
do num = 1987
do batman = "Bruce Wayne"

if ("Hello!" match btn) {
  say "Not at all"
}
// for shorter code you can also use this shorthand version
if (54 < num) say "Absolutely true!"

if (3 >= 2 && batman != "Robin") say "Correct"

if (username.match(batman)) say "I'm Batman"
else if (username.to_lowercase() == "robin") say "I'm Robin"
else {
  say "I'm apparently neither Batman nor Robin"
}
start:
  say "hi"
  goto step otherstep // note: in the following examples, we will use the shorthand notation `goto otherstep`

otherstep:
  say "I'm in otherstep"
  goto end
somestep:
  goto flow anotherflow
goto step_name@flow_name
goto stepname // navigate to the step named stepname in the current flow
goto flow flowname // navigate to the start step in flow flowname
goto @flowname // navigate to the start step in flow flowname
goto stepname@flowname // navigate to step stepname in flow flowname
do mystep = "somestep"
do myflow = "someflow"

goto $mystep@$myflow
do buttons = [Button("Burgers"), Button("Vegetables")]
say Question(
  "What is your favorite food?",
  buttons = buttons
)
hold

do matched = event.match_array(buttons)

if (!matched) { /* handle case where input does not match anything */ }

do target = matched.title
goto flow $target

// depending on which button the user selected, this is equivalent to
// goto flow Burgers or goto flow Vegetables
do pet = HTTP("https://example.com/pets/1").get().send()

// or, if no verb is specified, .get() is implicitly used
do pet2 = HTTP("https://example.com/pets/2").send()

say "This pet is called: {{pet.name}}"
do req = HTTP("https://example.com/pets").set({"authorization":"Bearer XXXXX"})

if (query_mode == "retrieve") {
    // retrieve all the available pets from the API
    do req = req.get()
}
else {
    // this will create a new pet
    do req = req.post({"name": "Oreo", "breed": "poodle"})
}

do res = req.send()

say "{{res}}"
do res = HTTP("https://example.com/does-not-exist").send()

say res.is_error() // true
do res = HTTP("https://example.com/does-not-exist").send()

say "{{res.get_info().headers}}" // the response headers
say "{{res.get_info().status}}" // 404
say "{{res.get_info().body}}" // only set in case of error

say "{{res}}" // the body in case of success, or Null in case of error
// You can handle error cases like this:
if (res.is_error()) {
  say "Hmmm... something happened"

  // For example, you can differentiate between 4XX and 5XX errors
  if (res.get_info().status >= 500) say "The server is not happy"
  else if (res.get_info().status >= 400) say "Something is wrong on the client side"
}
findweather:
  say "Let me query the weather in {{location}} for you..."
  do weather = Fn("weatherchannel", location = location)
  say "It will be {{weather.temperature}}°C tomorrow."
{
    "client": {
        "bot_id": "unique-bot-id",
        "user_id": "unique-user-id",
        "channel_id": "unique-channel-id"
    },
    "function_id": "name_of_fn",
    "data": {
        "example": "somevalue",
        "someparam": 123,
        "obj_val": { "hi": "there" }
    }
}
{
    "data": data_to_return<JSON>
}
do number = 3
do string = "something"
do float = -4.7
do object = { "hey": "you" }
do array = [1, 2, number]
do boolean = true
say "Hi!\n\nWe are proud to \ present this nice \"CSML v1.1\" update!"
// Object representation
do obj1 = Object(
  title = "toto",
  body = Object(
    somekey = "somevalue",
    otherkey = 123
  )
)


// Shorthand notation
do obj2 = { 
  "title": "toto",
  "body": { 
    "somekey": "somevalue", 
    "otherkey": 123
  }
}

say obj1.title // "toto"
say obj2.body.otherkey // 123
do items = ["a", "b", "c"]

// iterate over all the elements in the array
foreach (elem, index) in items {
  say "index: {{index}}, elem: {{elem}}"
}

say items[2] // "c"
do items = ["a", "b", "c"]

foreach (item) in items {
  do item = item.to_uppercase() 
  say item // "A", "B", "C"
}

say "{{items}}" // ["a", "b", "c"]
do items = ["a", "b", "c"]

do items = ["a", "b", "c"]
foreach (item, index) in items {
  do items[index] = item.to_uppercase() 
  say item // "a", "b", "c": the copy has not been affected
  say items[index] // "A", "B", "C": but the array content has been modified
}

say "{{items}}" // ["A", "B", "C"]: the array was modified
say items[2] // "C"
do items = ["a", "b", "c"]

// print a stringified version of the array
say "{{items}}"

// alternatively, you can also use the .to_string() array method
say items.to_string()
start:
  do payload = {
    "user": "name",
    "somekey": {
      "somevalue": 42
    },
    "exp": 1618064023,
    "iss": "CSML STUDIO"
  }

  do jwt = JWT(payload).sign("HS256", "SECRET_KEY")
  say jwt

  do decoded = JWT(jwt).decode("HS256", "SECRET_KEY")
  say "{{decoded}}"

  do claims = {
    "iss": "CSML STUDIO"
  }
  do verified = JWT(jwt).verify(claims, "HS256", "SECRET_KEY")
  say "{{verified}}"
do email = {
 // required
 "from": "John <john.doe@gmail.com>",
 "to": "Jane <jane.smith@hotmail.com>",
 
 // either html or text is required, both can be set
 "html": "<h1>Hello in HTML!</h1>",
 "text": "Hi there in plain text",
 
 // optional
 "reply_to": "Iron Man <tony.stark@gmx.de>",
 "bcc": "James <james.bond@yahoo.com>",
 "cc": "toto@truc.com",
 "subject": "Happy new year",
}

do SMTP(hostname) // mandatory
 .auth(username, password) // optional, defaults to no auth
 .tls(true) // optional, defaults to auto upgrade to TLS if available
 .port(465) // optional, defaults to 465
 .send(email) // perform the send request
say OneOf(["I like you", "You're awesome"])
say Or(var, "default value")
do btn1 = Button("Blue")
do btn2 = Button("Red")
do btns = Shuffle([btn1, btn2])

say Question(
  title = "Select a pill",
  buttons = btns
)
say Find("needle", in="haystack") // false
say Find("yes", in="well yes, I like cheese") // true
say Length("My horse is amazing") // 19
say Length([1, 2, 3]) // 3
say Random() // 0.03196249773128712
say Random() // 0.6416423921015862
say Floor(1.23456) // 1
say Floor(Random() * 5) // a random integer between 0-4 (included)
say Floor(Random() * 8) + 12 // a random integer between 12 - 19 (included)
say UUID() // "aa4b9fb4-4d37-488c-981f-8aebc4eb9eaa"
say UUID("v1") // "d0b40e8e-7ea4-11eb-9439-0242ac130002"
say UUID("v4") // "4b784011-e49b-4913-9d58-7abf4f8a56bc"
do myTime = Time() // initialize a Time object at the current UTC time
do myTime = Time().at(2021, 03, 28, 12, 53, 20, 123) // initialize a time object at 2021-03-28T12:53:20.123Z

do myTime = Time().unix() // generate the unix timestamp (in milliseconds)

do myTime = Time().format() // returns an ISO8601 string
do myTime = Time().format("%h%d") // returns a string with a custom format

do myTime = Time().add(60) // adds 60 seconds to the value
do myTime = Time().sub(60) // subtract 60 seconds to the value

do myTime = Time().with_timezone("Europe/Paris") // set the time in the given timezone

do myTime = Time().parse("2021-03-28") // parse a date
do myTime = Time().parse("2021-03-28T12:53:20Z") // parse an ISO-formatted string
do myTime = Time().parse("01/01/2021", "%d/%m/%Y") // parse a custom-formatted string
do Exists("myvar") // false
remember myvar = 123
do Exists("myvar") // true
onestep:
  goto somestep
  say "after the goto" /* will not be executed */
  goto someotherstep /* will not be executed */

somestep:
  say "hi"

someotherstep: /* will not be executed */
  say "hey"
say "The quick brown fox jumps over the lazy dog."
do somevalue = 123
debug somevalue // output: 123

do mylargeobj = {
    "val": 1,
    "something": {"toto": "tutu" },
    "other": "hello",
    "onemore": [1, 2, 3]
}
debug mylargeobj // output: {"val": 1, "something": "[Object]", "other": "hello", "onemore": "[Array]"}
fn is_triple_even(num):
  do triple = num * 3
  debug triple
  return (triple % 2)
somestep:
  say "What's up doc?"
  hold

  remember updoc = event
  goto end
start:
  say "Type something super secret, I promise I won't tell!"
  hold_secure // indicates that the next input will be secure

  // Value can not be displayed back or remembered in any way
  say "this is a secret: {{event}}" // prevented by CSML
  remember impossible = event // prevented by CSML
  do impossible = event // prevented by CSML

  
  // You can however perform operations on the event,
  // for example checking if the value is accepted
  if (event == "password") say "This is hardly a good secret!"
  else say "OK, good good"
// remember a hardcoded value
remember truc = "tutu"

// remember a local variable
do myvar = 123
remember tata = myvar

// overriding a local variable's scope
do myvar = 123 // `myvar` has a local scope
remember myvar = myvar // `myvar` is now a globally-available memory
do myvar = myvar // `myvar` will still be available globally
// execute a function
do Fn("someFunc")

// assign new values
do array[3] = "X"
do obj.val.toto = 123

// however, this will fail:
remember myarr = [1, 2]
do myarr[42] = 1 // the array needs to have at least the requested number of items

remember myobj = Object("key"="value")
do myobj.missing.otherkey = 1 // all the parent properties must exist and be objects as well
forget something // forget a single memory
forget [something, otherthing, thirdthing] // forget several memories at once
forget * // forget EVERYTHING (dangerous!)
const MY_CONST = 42

start:
  say "The value of MY_CONST is {{MY_CONST}}" // The value of MY_CONST is 42
  do MY_CONST = 14 // Forbidden! MY_CONST is immutable
do Button("A") as btn1
// is equivalent to
do btn2 = Button("B")

// say and save a component at the same time
say Question(
  title = "Pick one",
  buttons = [btn1, btn2] as btnlist
) as myquestion

// repeat the same question
say myquestion
// regular notation
if (1 > 2) {
  say "I have my doubts"
} else if ("mi casa" == "tu casa") {
  say "Welcome home"
} else {
  say "The force is strong with you"
}

// shorthand notation
if (sky == "blue") goto beach
else goto restaurant
do array = ["a", "b", "c"]
foreach (val, index) in array {
  say "at position {{index}} is element with value {{val}}"
}
do i = 0
while (i < 3) {
  say "i = {{i}}"
  do i = i + 1
}
remember lightsabers = [
  {"color": "red", "owner": "Kylo Ren"},
  {"color": "purple", "owner": "Mace Windu"},
  {"color": "yellow", "owner": "Rey Skywalker"},
  {"color": "green", "owner": "Yoda"},
  {"color": "red", "owner": "Darth Vader"},
  {"color": "green", "owner": "Luke Skywalker"},
 ]

foreach (ls) in lightsabers {
  // we want to skip any red lightsaber
  if (ls.color == "red") continue

  say "{{ls.owner}} had a {{ls.color}} lightsaber"
  // we want to stop after we find the first green lightsaber
  if (ls.color == "green") break
}

say "There might be even more lightsabers!"
do Button(
  title = "I agree",
  accept = ["OK", "yes", "right"]
) as btn

/* a direct click on the button will match the button */
/* typing "yes" will match the button */
/* typing "of course" will not match the button */
if (event match btn) { // Note: this is equivalent to event.match(btn)
  say "good"
} else {
  say "not good"
}
use 42 as answer

say "The answer is {{answer}}"
do i = 8
do i += 5
say "i = {{i}}" // i = 13
do val1 = "John"
do val2 = "Nutella"
say val1 + " likes " + val2 // John likes Nutella
do val1 = "John"
do val2 = "Nutella"
say "{{val1}} likes {{val2}}" // John likes Nutella
say Base64("Winter is coming 🥶").encode() // V2ludGVyIGlzIGNvbWluZyDwn6W2
say Base64("V2ludGVyIGlzIGNvbWluZyDwn6W2").decode() // Winter is coming 🥶

say Hex("Winter is coming 🥶").encode() // 57696e74657220697320636f6d696e6720f09fa5b6
say Hex("57696e74657220697320636f6d696e6720f09fa5b6").decode() // Winter is coming 🥶
do auth = Base64("user:password").encode()

do HTTP("https://example.com")
  .set({ "Authorization": "Basic {{auth}}" })
  .send()

Number methods

Although CSML differentiates between Float and Integer, all methods are available on all Number types. Hence, the Number type does not really exist but represents indifferently Float and Integer.

Given the automatic type inference, CSML will automatically try to map integers and floats to whichever type makes more sense in the context.

.pow(Number)

Raise the target integer to the power of the given integer.

number.pow(Number) => Number

// example
do val = 3
do val.pow(2) // 9

.abs()

Return the absolute value of the target number.

integer.abs() => Number

// example
do val = -42
do val.abs() // 42

.cos(), .sin(), .tan()

Return the cosine, sine or tangent of the target number.

float.cos() => Float
float.sin() => Float

// example
do val = 12.34
do val.cos() // 0,9744873988
do val.sin() // -0,224442219
do val.tan() // -0,2303182363

.floor(), .ceil(), .round()

Round the number respectively down, up or using mathematical rounding.

float.floor() => Integer
float.ceil() => Integer
float.round() => Integer

// example
do val = 10.5
do val.floor() // 10
do val.ceil() // 11
do val.round() // 11

.precision(n)

do val = 1.234567.precision(2) // 1.23

Hash, HMAC

Encode data using hash and HMAC methods:

start:
  say Crypto("Hello World 😆").create_hash("sha256").digest("hex")
  say Crypto("Hello World 😆").create_hmac("md5", "SECRET_KEY").digest("base64")

Methods

Crypto(data)

The data parameter is the data to encrypt or encode, and it must be a string.

create_hash(algorithm)

The algorithm must be one of the following values:

MD5
SHA1
SHA224
SHA256
SHA384
SHA512
SHA3_224
SHA3_256
SHA3_384
SHA3_512
SHAKE_128
SHAKE_256
RIPEMD160
SM3

create_hmac(algorithm, key)

Unlike the create_hash method, create_hmac requires a key to function. The list of accepted algorithms is the same.

digest(encoding)

The encoding must be either "base64" or "hex".

Error handling

When any operation fails, Null is returned and a say Error(error_message) is emitted.

Generic methods

The following methods are common to all CSML primitive types.

.type_of()

Return the type of the target value (string, array, object, float, boolean, int, null)

any_value.type_of() => String

.to_string()

Return the target value formatted as a string.

any_value.to_string() => String

.is_number()

Check if a variable can be interpreted as a number (float or int)

string.is_number() => boolean

// example
do var_int = "42"
say var_int.is_number() // true

do var_float = "42.42"
say var_int.is_number() // true

do obj = {"toto": "tutu"}
say obj.is_number() // false

.is_int()

Check if the variable can be interpreted as an int

string.is_int() => boolean

// example
do var_int = "42"
say var_int.is_number() // true

do var_float = 42.42
say var_int.is_number() // false

.is_float()

Check if the variable can be interpreted as an float

string.is_float() => boolean

// example
do var_int = 42
say var_int.is_number() // false

do var_float = 42.42
say var_int.is_number() // true

.is_error(), .get_info()

Some functions may resolve to an error due to a faulty user input, an unreachable API, or some other bug in the code. You can check the result of such functions call (like HTTP() or App()) with .is_error(), and get more information with .get_info().

  do x = HTTP("https://somewhere.com/does-not-exist").send()

  if (x.is_error()) {
    // get info a description of the error
    debug "{{x.get_info()}}"

    // try something else or ask again the user for input!
  }

Object methods

.keys()

Return the list of all 1st-level properties in the target object.

object.keys() => Array[String]

// example
do val = { "toto": 1, "tutu": 2 }
do val.keys() // ["toto", "tutu"]

.values()

Return the list of all 1st-level-property values in the target object.

object.values() => Array[String]

// example
do val = { "toto": 1, "tutu": 2 }
do val.values() // [1, 2]

.remove(String)

Remove the given property from the target object.

object.remove("property") => void

// example
do val = { "toto": 1, "tutu": 2 }
do val.remove("toto") // { "tutu": 2 }

.length()

Return the number of 1st-level properties in the target object.

object.length() => Integer

// example
do val = { "toto": 1, "tutu": 2 }
do val.length() // 2

.assign(b)

Insert all elements of the parameter object into the target object.

do obj = {"a": 1}
do obj.assign({"b": 2})
say "{{obj}}" // {"a": 1, "b": 2}
turing-complete
AWS Lambda
Azure Functions
Google Cloud Functions
FnProject
OpenFaas
OpenWhisk
CSML Studio
many ready-to-use integrations
https://github.com/CSML-by-Clevy/csml-fn-endpoint-node
Array methods
here
https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html
Native CSML Functions

Array methods

.length()

Return the number of elements contained in the target array.

array.length() => Integer

// example
do val = ["Batman", "Robin", "Superman"]
do val.length() // 3

.push(*)

Add an element at the end of an array.

array.push(val) => void

// example
do val = ["Batman", "Superman"]
do val.push("Robin") // ["Batman", "Superman", "Robin"]

.pop()

Remove the last element of an array.

array.pop() => void

// example
do val = ["Batman", "Robin", "Superman"]
do val.pop() // ["Batman", "Robin"]

.reverse()

Create a new Array with all elements reversed

do vec = [3, 2, 1]

do new_vec = vec.reverse()
say new_vec.to_string() // [1, 2, 3]

.append()

Add the elements of the array to the initial array

 do vec = [1, 2, 3]
 do vec2 = [4, 5, 6]

 do append_vec = vec.append(vec2)
 say append_vec.to_string() // [1, 2, 3, 4, 5, 6]

.insert_at(Integer, *)

Add an element at position n of an array (shifting the position of all the following elements).

array.insert_at(n, val) => void

// example
do val = ["Batman", "Superman"]
do val.insert_at(1, "Robin") // ["Batman", "Robin", "Superman"]

.remove_at(Integer)

Remove the nth element of an array (unshifting the position of all the following elements).

array.remove_at(n) => void

// example
do val = ["Batman", "Robin", "Superman"]
do val.remove_at(1) // ["Batman", "Superman"]

.find(*)

Returns a new array with all the values found in the original array matching the given value.

array.find(x) => Array

// example
do val = ["Batman", "Robin", "Superman", "Batman"]
do val.find("Ironman") // []
do val.find("Robin") // ["Robin"]
do val.find("Batman") // ["Batman", "Batman"]

.to_uppercase()

Return the same string in all uppercase characters.

string.to_uppercase() => String

// example
do val = "Where is Brian?"
do val.to_uppercase() // "WHERE IS BRIAN?"

.to_lowercase()

Return the same string in all lowercase characters.

string.to_lowercase() => String

// example
do val = "Where is Brian?"
do val.to_lowercase() // "where is brian?"

.length()

Return the length of the target string.

string.length() => Integer

// example
do val = "Where is Brian?"
do val.length() // 15

.contains(String), .contains_regex(String)

Return whether the string contains another string or expression.

haystack.contains(needle) => Boolean
haystack.contains_regex(needle) => Boolean

// example
do val = "Where is Brian?"
// does it contain any "r"?
do val.contains("r") // true
// does it contain the word "where"?
do val.contains("where") // false => no, because it is case sensitive
// does it contain any number?
do val.contains_regex("[0-9]") // true

.starts_with(String), .starts_with_regex(String)

Return whether a string starts with another string or expression.

haystack.starts_with(needle) => Boolean
haystack.starts_with_regex(needle) => Boolean

// example
do val = "Where is Brian?"
// does it start with "r"?
do val.starts_with("r") // false
// does it start with any uppercase letter?
do val.starts_with_regex("[A-Z]") // true

.ends_with(String), .ends_with_regex(String)

Return whether a string ends with another string or expression.

haystack.ends_with(needle) => Boolean
haystack.ends_with_regex(needle) => Boolean

// example
do val = "Where is Brian?"
// does it end with "r"?
do val.ends_with("r") // false
// does it end with any uppercase letter?
do val.ends_with_regex("[A-Z]") // false

.match(String), .match_regex(String)

Return all the matches of the string or expression in the target string, or Null if none are found.

haystack.match(needle) => Array[String]
haystack.match_regex(needle) => Array[String]

// example
do val = "Where is Brian?"
// does it match with "r"?
do val.match("r") // ["r", "r"] => yes, twice!
// does it match with any uppercase letter?
do val.match_regex("[A-Z]") // ["W", "B"] => yes, and these are the letters!

About _regex methods:

The \ (backslash) character has a special meaning. For technical reasons, in all strings, it must be properly escaped, by convention by adding another \ in front of itself, to avoid being interpreted as a special character. For example, if you mean to write the exact string "\n" you must in fact write \\n, otherwise \n will be interpreted as a line break.

We follow this nomenclature for CSML Regex handling, so a single Regex backslash must be written as a "\\" string, and an escaped backslash (that behaves as a literal "\" string character) must in fact be escaped twice, once for being in a string, and once for being in a Regex: you have to write "\\\\" to result in the Regex syntax \\which in turn matches the literal "\" string.

In a future release of CSML we might introduce a "raw string" method to bypass this limitation.

.init(size)

Create a new array of size n

do arr = [].init(3) // [null, null, null]

.slice(start, end)

Return a new array with all items between start and end. Some rules apply:

  • If end is not specified, all the items after start are returned.

  • When specified, end must be ≥ start.

  • If any of the parameters is < 0, the count is made from the end of the array.

do x = ["a", "b", "c", "d", "e"].slice(2, 4)
say "{{x}}" // c, d

do x = ["a", "b", "c", "d", "e"].slice(2)
say "{{x}}" // c, d, e

do x = ["a", "b", "c", "d", "e"].slice(-4)
say "{{x}}" // b, c, d, e

do x = ["a", "b", "c", "d", "e"].slice(-4, 3)
say "{{x}}" // b, c

do x = ["a", "b", "c", "d", "e"].slice(-2, 1)
say "{{x}}" // Error

do x = ["a", "b", "c", "d", "e"].slice(2, 1)
say "{{x}}" // Error

.map(fn), .filter(fn), .reduce(acc, fn)

These methods are useful ways to construct a new arrays from existing arrays. They are inspired from similar methods in other languages and follow the same general syntax.

Here are some examples of what these methods can help you achieve:

// create a new array where each original item is multiplied by 2
do newArray = [1, 2, 3, 4].map((item, index) {
  return x * 2
}) // newArray = [2, 4, 6, 8]

// create a new array containing even numbers from the original array
do newArray = [1, 2, 3, 4].filter((item, index) {
  return x % 2 == 0
}) // newArray = [2, 4]

// create a new value by adding all the elements together
do sum = [1, 2, 3, 4].reduce(0, (acc, val, index) {
  do acc = acc + val
  return acc
}) // sum = 1 + 2 + 3 + 4 = 10

.flatten()

Convert an array of arrays to an array containing all elements of the 1st level arrays.

do [[1, 2], [3, 4]].flatten() // [1, 2, 3, 4]

// if a 1st-level element is not an array, it will be kept as is
do [[1, 2], "something", [3, 4]].flatten() // [1, 2, "something", 3, 4]

String methods

.to_uppercase()

Return the same string in all uppercase characters.

string.to_uppercase() => String

// example
do val = "Where is Brian?"
do val.to_uppercase() // "WHERE IS BRIAN?"

.to_lowercase()

Return the same string in all lowercase characters.

string.to_lowercase() => String

// example
do val = "Where is Brian?"
do val.to_lowercase() // "where is brian?"

.capitalize()

Return the same string with the first letter in uppercase. The rest of the string remains unchanged.

string.capitalize() => String

// example
do val = "my name is John"
do val.capitalize() // "My name is John"

.trim(), .trim_left(), .trim_right()

Returns a new string with both leading and trailing whitespace removed. .trim_left() and .trim_right() only trim the leading and trainling whitespace respectively.

do text = "   Where is Brian?   "
do new_text = text.trim()
say new_text // "Where is Brian?"

do new_text = text.trim_left()
say new_text // "Where is Brian?   "

do new_text = text.trim_right()
say new_text // "   Where is Brian?"

.length()

Return the length of the target string.

string.length() => Integer

// example
do val = "Where is Brian?"
do val.length() // 15

.contains(String), .contains_regex(String)

Return whether the string contains another string or expression.

haystack.contains(needle) => Boolean
haystack.contains_regex(needle) => Boolean

// example
do val = "Where is Brian?"
// does it contain any "r"?
do val.contains("r") // true
// does it contain the word "where"?
do val.contains("where") // false => no, because it is case sensitive
// does it contain any number?
do val.contains_regex("[0-9]") // true

.replace(), .replace_all(), .replace_regex()

Replace the first, all or any occurrence matching the predicate:

say "toto".replace("o", "a") // "tato" the first o char is replaced
say "toto".replace_all("o", "a") // "tata" all o chars are replaced
say "toto".replace_regex("[to]", "a") // "aaaa" all chars that are t or o are replaced

.starts_with(String), .starts_with_regex(String)

Return whether a string starts with another string or expression.

haystack.starts_with(needle) => Boolean
haystack.starts_with_regex(needle) => Boolean

// example
do val = "Where is Brian?"
// does it start with "r"?
do val.starts_with("r") // false
// does it start with any uppercase letter?
do val.starts_with_regex("[A-Z]") // true

.ends_with(String), .ends_with_regex(String)

Return whether a string ends with another string or expression.

haystack.ends_with(needle) => Boolean
haystack.ends_with_regex(needle) => Boolean

// example
do val = "Where is Brian?"
// does it end with "r"?
do val.ends_with("r") // false
// does it end with any uppercase letter?
do val.ends_with_regex("[A-Z]") // false

.match(String), .match_regex(String)

Return all the matches of the string or expression in the target string, or Null if none are found.

haystack.match(needle) => Array[String]
haystack.match_regex(needle) => Array[String]

// example
do val = "Where is Brian?"
// does it match with "r"?
do val.match("r") // ["r", "r"] => yes, twice!
// does it match with any uppercase letter?
do val.match_regex("[A-Z]") // ["W", "B"] => yes, and these are the letters!

About _regex methods:

The \ (backslash) character has a special meaning. For technical reasons, in all strings, it must be properly escaped, by convention by adding another \ in front of itself, to avoid being interpreted as a special character. For example, if you mean to write the exact string "\n" you must in fact write \\n, otherwise \n will be interpreted as a line break.

We follow this nomenclature for CSML Regex handling, so a single Regex backslash must be written as a "\\" string, and an escaped backslash (that behaves as a literal "\" string character) must in fact be escaped twice, once for being in a string, and once for being in a Regex: you have to write "\\\\" to result in the Regex syntax \\which in turn matches the literal "\" string.

In a future release of CSML we might introduce a "raw string" method to bypass this limitation.

.is_number(), .is_int(), .is_float()

Return whether the given string represents a numerical value, an int, a float.

string.is_number() => Boolean

// example
do val = "Where is Brian?"
do val.is_number() // false

do val = "42"
do val.is_number() // true
do val.is_int() // true
do val.is_float() // false

.split(String)

Split a string by a given separator and return an array containing all elements in order. The separator can be a single or multiple characters. If the separator can not be found in the string, the returned array will only contain the original string.

string.split(String) => Array[String]

// example
do val = "this is a long string"
do val.split(" ") // ["this", "is", "a", "long", "string"]
do val.split("is") // ["th", " ", " a long string"]
do val.split("camembert") // ["this is a long string"]

.slice(start, end) => String

Cut a string between the start and end characters. Some rules apply:

  • If end is not specified, all the characters after start are returned.

  • When specified, end must be ≥ start.

  • If any of the parameters is < 0, the count is made from the end of the string.

say "abcdefghijklmnop".slice(2, 4) // "cd"
say "abcdefghijklmnop".slice(7) // "hijklmnop"
say "abcdefghijklmnop".slice(-4) // "mnop"
say "abcdefghijklmnop".slice(-4, 14) // "mn"

say "abcdefghijklmnop".slice(-4, 3) // Error
say "abcdefghijklmnop".slice(2, 1) // Error

.to_int(), .to_float() => Integer, Float

Convert a string representing a number to a value cast as an integer or float:

do val = "1.2345".to_int() // 1
do val = "1.2345".to_float() // 1.2345

do val = "not a number".to_int() // error

.to_yml(), .to_json()

Convert yaml to json and back

do json = "some:\n  yaml: 1".to_json()
say "{{json}}" // {"some":{"yaml":1}}

do yml = json.to_yaml()
say "{{yml}}" // some:\n  yaml: 1

.encode_uri(), .encode_uri_component(), .decode_uri(), .decode_uri_component()

say "https://mozilla.org/?x=шеллы".encode_uri()
say "https://mozilla.org/?x=шеллы".encode_uri_component()

say "https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B".decode_uri()
say "https%3A%2F%2Fmozilla.org%2F%3Fx%3D%D1%88%D0%B5%D0%BB%D0%BB%D1%8B".decode_uri_component()

.encode_html_entities(), .decode_html_entities()

debug "42 < 100".encode_html_entities()
debug "42 &lt; 100".decode_html_entities()

You can find more info about the particular regex syntax used in the *_regex methods on .

This Python documentation explains why it especially matters in Regex syntax to escape backslashes:

You can find more info about the particular regex syntax used in the *_regex methods on .

This Python documentation explains why it especially matters in Regex syntax to escape backslashes:

Encode and decode URI/URIComponent (see )

Encode and decode

this link
https://docs.python.org/2/howto/regex.html#the-backslash-plague
this link
https://docs.python.org/2/howto/regex.html#the-backslash-plague
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI#encodeuri_vs_encodeuricomponent
HTML entities