-
Notifications
You must be signed in to change notification settings - Fork 2
Basic syntax
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.
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.
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.
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.
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 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.
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.
(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.