Skip to content

Basic syntax

Paul M Fox edited this page Nov 9, 2016 · 3 revisions

SCL is arranged into blocks, each of one which contains zero or more statements. SCL uses whitespace indentation, rather than curly braces or keywords, to delimit blocks. An increase in indentation comes after certain statements; a decrease in indentation signifies the end of the current block. In that way, it's quite similar to Python. Blocks determine sections for the output configuration, and they also contribute to scope.

Block structure

Let's take a look at a very simple set of blocked statements:

block1
    declaration_statement_in_block1 = "declaration value"

    block2
        another_declaration_statement_in_block2 = "declaration value"

declaration_statement_without_a_block = "declaration value"

That's valid SCL, but probably meaningless in any real world application. The important thing to understand is the hierarchy of entities being created here. You can think of it looking a bit like this:

<root>
|-- block1
|   |-- declaration_statement_in_block1
|   +-- block2
|       +-- another_declaration_statement_in_block2
+-- declaration_statement_without_a_block

There is no limit to block nesting, though it's generally kept fairly shallow for the purposes of readability. Every concept in SCL is built on the block structure, including more sophisticated topics like mixins and variables.

It's worth noting that SCL is line-based. That is, it accepts only one statement per line, and it expects each line to be a complete statement, blank like or comment. The only exception to the rule is heredocs, which are covered on the literals page.

Indentation

Obviously the block structure is predicated on consistent indentation. The measurement used by the SCL parser is the total number of whitespace characters from the start of the line. SCL treats tabs and spaces both as a single character, and makes no educated guesses as to the width of tabs. What that means functionally is that you can't mix tabs and spaces. SCL has no mandatory standard, but tabs require fewer characters.

That said, blocks are identified by a difference in indentation between lines, not by a global level of indentation. This code, for example, works as if all the indentation were the same:

block1
    key = "value"

block2
  key = "value"

SCL doesn't care that the two key = "value" lines have different indentation to each other; it only cares they they have different indentation to the lines above them.

Curly braces

In SCL, braces are optional, as long as any curly braces are in "one true brace style" (meaning that the opening brace must appear at the end of a block declaration, and the closing brace must appear on its own line) and the file is still indented properly.

Furthermore, you don't have to be consistent with braces: you can use them in some places but not others, or in outer blocks but not inner ones. SCL only cares about indentation; it's not interested in braces.

This is perfectly valid SCL:

block {
    key = "value"

    innerBlock
        something = 1

        eventMoreInnerBlock {
            somethingElse = 2
    	}
}

Aside from making the language flexible, this allows for two distinct 'dialects' of SCL: the 'High' brace-less SCL and the 'Low' braced SCL. Though there is no technical distinction at a language or transpiler level, the dialects are often used to distinguish different flavours of code. For example, you might use the high dialect for 'proper' code and the low dialect for hacks; or one for server config and the other for client config. Again, there is no formal standard, but switching between the dialects can have utility.

Comments

SCL supports C/C++ style block and line comments and hash comments. C-style like comments (//) are generally preferable for short comments because they're more consistent with other languages, and C-style block comments are used before mixins to export documentation. C-Style comments will be omitted from the resulting HCL, but hash comments will be passed through. Note that, since they are part of the HCL, hash comments are subject to variable interpolation.

Some examples of valid comments:

// This is C-style line comment
block1
    block2 // ...And so is this
        # This is a valid comment, which will appear in the HCL
        somethingElse = 1

// Multiple
// lines can be
// commented like this

/*
    This is documentation for myMixin, below.
    Notice the indentation.
*/
@myMixin($arg1, $arg2="default")
    # This is a comment made by the mixin
    # Hash comments can include variable interpolation:
    # Mixin arguments: \$arg1 = $arg1, \$arg2 = $arg2
    block3
       arg1 = $arg1
       arg2 = $arg2

// This is the call to myMixin
myMixin(1)

Running the SCL above results in the following HCL:

block1 {
  block2 {
    # This is a valid comment, which will appear in the HCL
    somethingElse = 1
  }
}
# This is a comment made by the mixin
# Hash comments can include variable interpolation:
# Mixin arguments: $arg1 = 1, $arg2 = "default"
block3 {
  arg1 = 1
  arg2 = "default"
}

Variables

Variables are interpolated at compile time, meaning that the value they represent will be replaced with their underlying value.

A simple use of a variable might look like this:

$myVar1 = "value"
$myVar2 = value

block
    key = $myVar1 // This is interpolated to 'key = "value"'

block
    key = $myVar2 // This is interpolated as 'key = value'

They can be used anywhere in SCL, including block declarations. For example, this is valid SCL:

$myBlockName = block
$myValue     = "value"

$myBlockName
	value = $myValue

Which results in the HCL:

block
  value = "value"

Variables can be escaped by placing a backslash in front of the variable declaration (\$myvar), using two dollar signs ($$myvar) or by wrapping them in backticks. There is also an alternate ${myvar} syntax to help with complex interpolation. See the Variables section for full documentation.

Function calls

As you'll see in the mixins section, function calls form the basic of idiomatic SCL programming. A function call's signature is name([<arg1[,argX...]]), where name is the name of the function. Arguments are separated by commas.

For example, you might call a simple function like this:

block
    myFunction("some value", "some other value")

If a function call opens a new block (which is fairly common), then it can optionally be (and usually is) followed by a :. If it also has no arguments, then the parentheses can be omitted from the call. An example of this might look like:

blockFunctionWithPararms("some value"):
    key = value

blockFunctionWithoutParams:
    key = value

// This is also valid
blockFunctionWithPararms("some value")
    key = value

Function calls that don't open new blocks must be called with parentheses, even if they have no arguments:

block
    myFunction()

This might initially seem like an arbitrarily complicated set of rules, but it becomes intuitive very quickly.

Mixins

(See also the mixins documentation)

Mixins are the cornerstone of SCL programming. They define drop-in features of the language, and allow for radical DRYness. A mixin is defined with the following signature:

@name([$arg1[,$argX...]])

Once defined, a mixin can be invoked using a function call (see above), provided it's in scope.

Clone this wiki locally