Lilt docs¶
Overview¶
Lilt is a content-free parser generator. It accepts a specification and returns a parser based on that specification. Lilt specifications look very similar to Backus-Naur form, but have extra syntax in order to be a parser generator rather than just a grammar specification.
Learning Lilt¶
To learn Lilt, it’s recommended to start with the tutortial.
Lilt Tutorial¶
Let’s look at how one would parse JSON using Lilt. Instead of creating a parser generator to begin, we’ll start by creating a BNF-like Lilt specification for JSON, and then adding the actual “parser generator” bits in afterwards.
Let’s start with something simple, a JSON string
. For now, we’ll skip implementing
escapes; backslashes will be handled literally, and "
will not be allowed in a string:
string: '"' *anyNonQuoteCharacter '"'
We’ll leave the rule anyNonQuoteCharacter
undefined for now.
So far, Lilt looks like any grammar specification, except using prefix notation rather than
postfix. We define a rule called string
(string:
) which consists of
a literal code ('"'
) followed by
0 or more (*
) of any non-quote character (anyNonQuoteCharacter
) and then a final, ending
literal quote ('"'
).
OK, fancy! Now let’s define a number:
number: ?"-" ["0" | +digit] ?["." *digit]
There are a few notable things about this snippet:
"
: In this snippet, double quotes are used around literals rather than single quotes. It makes no difference.?
: This notates that the following rule is optional.[]
: Square prackets in Lilt are like parenthesis in other languages; they are used for precedence.+
: This matches the following rule 1 or more times.|
: Several rules separated by|
will match code that matches any of the rules. So,'a' | 'b'
will match “a”, “b”, any nothing else.
Just for fun, let’s also allow the exponent synax:
number: ?"-" ["0" | +digit] ?["." *digit] ?[["e" | "E"] ["+" | "-"] +digit]
["e" | "E"]
is a little verbose; luckily, though, there’s some syntactic
sugar we can use! We can enclose many characters in angle brackets
<like this>
, which will match any of the contained characters. For
instance, the digit
builtin is equivalent to <1234567890>
.
So, we may rewrite this slightly more tersely as:
number: ?"-" ["0" | +digit] ?["." *digit] ?[<eE> <+-> +digit]
Cool, now we’ve defined what :code:`string`s and :code:`number`s look like. Before we continue, though, there’s one important conceptual detail that need to be ironed out.
It’s very easy to look at these definitions as predicates; it’s easy to think of number
as a function that takes some code and returns whether or not it matches the specification (i.e.
looks like a number). However, it’s important to not do this in order to better grok how Lilt
works. Instead, think of number
(and all other rules) as a function that takes some
code, tries to match it to the specification, and returns the concused code if it matches. If it doesn’t
match, it will fail, and signal to the caller that it has failed.
So, number("123") = "123"
and number("123 abc") = "123"
and number("x")
fails.
Now it’s time to revisit anyNonQuoteCharacter
, which we left undefined
before but can now think about since we’ve got our conception all fixed up.
In order to define this, We introduce the !
operator, called the guard operator. The
guard operator will fail if and only if the contained rule doesn’t fail. So !"2"
fails ONLY
on “2”, and !digit
fails on any digit.
Why is this useful? It allows us to create set differences. Some set A - B
would be expressed in Lilt as !B A
. For instance, we can replace anyNonQuoteCharacter
with !'"' any
. any
is a builtin that matches any character.
Now, we can complete our JSON specification (except for string escapes):
value: string | number | object | array | "true" | "false" | "null"
string: '"' *[!'"' any] '"'
number: ?"-" ["0" | +digit] ?["." *digit] ?[<eE> <+-> +digit]
object: "{" _ ?[pair *["," pair]] _ "}"
pair: string ":" value
array: "[" _ ?[value *["," value]] _ "]"
Fancy stuff. Look at that!
Now we can finally get to the “parser” part of “parser generator”. Instead of just returning some code, we want our specification to return an abstract syntax tree. Before we start changing the JSON specifiction, let’s learn how Lilt represents ASTs.
In Lilt, an AST node is one of 3 things:
- Code (a string)
- List (a list of Nodes)
- Node (an object with properties that are other AST nodes)
Note that “node” and “Node” are subtly different here, since a “node” may be Code, a List, or a Node. All Nodes are nodes; some nodes are Nodes.
We represent Lilt nodes in a manner similar to JSON. To exemplify, let’s create a node for the function
call: printLn(x, y, z)
. We will want a Node for the whole call which will have a “target” attribute that
represents the function reference as well as a “arguments” attribute which is a list of references.
Each reference will also be a Node with a “to” attriubte which is just the literal name of the reference:
call {
target: reference { to: "printLn" }
arguments: [
reference { to: "x" }
reference { to: "y" }
reference { to: "z" }
]
}
Since this is not formal code, and is just shorthand, commas aren’t really needed.
Now let’s design an AST for our spec. Take another look at the spec so far:
value: string | number | object | array | "true" | "false" | "null"
string: '"' *[!'"' any] '"'
number: ?"-" ["0" | +digit] ?["." *digit] ?[<eE> <+-> +digit]
object: "{" _ ?[pair *["," pair]] _ "}"
pair: string ":" value
array: "[" _ ?[value *["," value]] _ "]"
Let’s consider how we want to generate the AST.
string
should probably be a Node with a “value” attribute containing the code
of the string.
number
should probably be a Node with a “wholes” attribute containing the digits before
the decimal point. It may also have a “digit” attribute containing the digits after the decimal point
and an “exponent” attribute containing the digits after an “e” or “E”.
object
should be a Node with a “pairs” attribute, a List of pairs. Each pair
should
be a Node with a “key” attriubte and a “value” attribute.
Finally, array
should be a node with an “items” attribute, a list of Nodes of the contained
values.
Great! But, there’s an issue. string
, number
, object
, and array
will all evaluate to Nodes, but "true"
, "false"
, and "null"
will all
evaluate to Code. This means that value
cannot certainly evaluate to a Node nor
certainly evaluate to some Code. Since Lilt rules must be homogenous (i.e. return one and only one type), this isn’t
allowed. To fix it, we need to somehow return a Node for the literals as well.
We’ll create trueLiteral
, falseLiteral
, and nullLiteral
rules which will do that.
They will return a Node which has no attriubutes. Lilt Nodes are implicitely given an attribute
that is the name of the rule that defined them, so these blank nodes will still be distinguishable.
Phew, close one. Now, how do we reify our plan?
Named attributes are notated like someAttribute=rule
, which will set someAttribute
to
the value of rule
on the returned Node. Let’s start small and reimplement number
:
number: ?negative="-" wholes=["0" | +digit] ?["." decimals=*digit] exponent=?[<eE> <+-> +digit]
Pretty simple! Let’s see it in action:
number("-4.0") =
number {
negative: "-"
wholes: "4"
decimals: "0"
}
number("6.022e+23") =
number {
wholes: "6"
decimals: "022"
exponent: "e+23"
}
number("14") = number { wholes: "14" }
Hmmm, the “exponent” attribute is kind of ugly. It would be nice to actually parse the exponent as well, so let’s do that:
number: ?negative="-" wholes=["0" | +digit] ?["." decimals=*digit] ?exponent=numberExp
numberExp: <eE> sign=<+-> digits=+digit
Now, this parses nicer:
number("6.022e+23") =
number {
wholes: "6"
decimals: "022"
exponent: numberExp {
sign: "+"
digits: "23"
}
}
So that’s how we create nodes. We’ll also need to be able to create Lists and Code as well.
So far, Code has just been created with literals like "0"
and operations on literals
like *digit
. That will actually be enough for JSON, but there are other ways to create
Code that will be reviewed at the end of the tutorial
Lists can be created by applying *
or +
to a Node-returning rule, so *number
will be a List. However, it can also be created explicitly with &
. &
will append a node
to the resultant list. To exemplify, let’s implement array
next:
array: "[" _ items=?items _ "]"
items: &value *["," &value]
Since, as we planned before, value
will return a Node, then each call to &
will append
that node to the resultant list of items
, which will be returned when finished. let’s
see an array
example! Since we’ve only defined number
as well as array
, it will
be an array of numbers:
array("[1, 2, 3.4, 5.6, 7]") =
array {
items: [
number { wholes: "1" }
number { wholes: "2" }
number { wholes: "3", decimals: "4" }
number { wholes: "5", decimals: "6" }
number { wholes: "7" }
]
}
Knowing attr=
and &
actually gives us enough to finish making a real JSON parser:
value: string | number | object | array | trueLiteral | falseLiteral | nullLiteral
trueLiteral: _="" "true"
falseLiteral: _="" "false"
nullLiteral: _="" "null"
string: '"' value=*[!'"' any] '"'
number: ?negative="-" wholes=["0" | +digit] ?["." decimals=*digit] ?exponent=numberExp
numberExp: <eE> sign=<+-> digits=+digit
object: "{" _ pairs=?pairs _ "}"
pairs: &pair *["," &pair]
pair: key=string ":" value=value
array: "[" _ items=?items _ "]"
items: &value *["," &value]
Real quick: Remember when I said trueLiteral
, falseLiteral
, and nullLiteral
would
make an object with no attributes? I lied. That’s not (yet) possible in Lilt, so instead we consume
""
, which will always succeed, and set it to the dummy attribute “_”.
Great! We have a real, working JSON parser! And in only 12 lines of code! You’ll notice that in
the transition from grammar to parser, we had to add some auxiliary functions in order to work
with the type system: trueLiteral
, falseLiteral
, nullLiteral
numberExp
,
pairs
, and items
. But perhaps we don’t want these auxiliary functions?
Let’s say we hate that items
has to be defined as its own rule and wish we could just inline
it within array
. What would happen if we did?:
array: "[" _ items=?[&value *["," &value]] _ "]"
Now, this would confuse the type system. Since []
doesn’t introduce a new scope, items=
says that array
will return a Node,
but then &value
says that array
will return a List!
This can be solved with {}
, which is like []
but does introduce a new scope
and are used to create anonymous, inline rules. So a working version would be:
array: "[" _ items=?{&value *["," &value]} _ "]"
Now &value
affects the inner rule rather than array
, and everything is hunky-dory.
Since anonymous classes are, well, anonymous, they generally shouldn’t return a Node. As mentioned before, all nodes contain an attribute which refers to the rule that generated them. What should that be for a node created by an anonymous rule?
Anyway, now we can make the JSON definition more terse. If we inline all the (non-Node) auxiliary functions, it would look like::
value: string | number | object | array | trueLiteral | falseLiteral | nullLiteral
trueLiteral: _="" "true"
falseLiteral: _="" "false"
nullLiteral: _="" "null"
string: '"' value=*[!'"' any] '"'
number: ?negative="-" wholes=["0" | +digit] ?["." decimals=*digit] ?exponent=numberExp
numberExp: <eE> sign=<+-> digits=+digit
object: "{" _ pairs=?{&pair *["," &pair]} _ "}"
pair: key=string ":" value=value
array: "[" _ items=?{&value *["," &value]} _ "]"
We didn’t inline numberExp
since it returns a Node.
We’re almost done! We just have to make it handle escapes in strings, and whitespace. Let’s do strings first.
First, let’s replace the string
definition with:
string: '"' value=*stringChar '"'
Now we just have to define stringChar
. Well, it’s any character besides "
or baclslash, or
a blackslash followed by any of: "\/bfnrt
, or a u
and 4 hexadecimal digits. Let’s do it:
stringChar: [!<"\\> any] | "\\" [</\\bfnrt> | "u" hexDig hexDig hexDig hexDig]
hexDig: <1234567890ABCDEFabcdef>
Now, string
will correctly consume "string \""
. It will NOT interpret the backslash and
map it to a double quote; the returned text will be string \"
. Let’s include it in the parser:
value: string | number | object | array | trueLiteral | falseLiteral | nullLiteral
trueLiteral: _="" "true"
falseLiteral: _="" "false"
nullLiteral: _="" "null"
string: '"' value=*stringChar '"'
stringChar: [!<"\\> any] | "\\" [</\\bfnrt> | "u" hexDig hexDig hexDig hexDig]
hexDig: <1234567890ABCDEFabcdef>
number: ?negative="-" wholes=["0" | +digit] ?["." decimals=*digit] ?exponent=numberExp
numberExp: <eE> sign=<+-> digits=+digit
object: "{" _ pairs=?{&pair *["," &pair]} _ "}"
pair: key=string ":" value=value
array: "[" _ items=?{&value *["," &value]} _ "]"
One final job: Whitespace. Lilt includes a builtin function _
which consumes 0 or more whitespace
characters and returns them. It may be tempting to implement whitespace for value
like this:
value: _ [string | number | object | array | trueLiteral | falseLiteral | nullLiteral] _
but that won’t work. Why not? The type system will see that _
returns Code and will make
value
return Code as well, returning what it’s consumed. Instead, we want it to return
a Node. We can do this with the #
operator, which is kind of like return
; it will
return the notated value. It doesn’t return it until the end of the call, though, so the second
call to _
will still work, consuming trailing whitespace. The correct code looks like:
value: _ #[string | number | object | array | trueLiteral | falseLiteral | nullLiteral] _
(Excuse the misplaced italics)
Note that since #
doesn’t stop execution, it’s not quite like return
. Since it
doesn’t stop execution, multiple calls to #
will overwrite each other, the last value is
the one that will be returned. So for ex: #"a" #"b"
, ex("ab") = "b"
.
OK, let’s fill in whitespace:
value: _ #[string | number | object | array | trueLiteral | falseLiteral | nullLiteral] _
trueLiteral: _="" "true"
falseLiteral: _="" "false"
nullLiteral: _="" "null"
string: '"' value=*stringChar '"'
stringChar: [!<"\\> any] | "\\" [</\\bfnrt> | "u" hexDig hexDig hexDig hexDig]
hexDig: <1234567890ABCDEFabcdef>
number: ?negative="-" wholes=["0" | +digit] ?["." decimals=*digit] ?exponent=numberExp
numberExp: <eE> sign=<+-> digits=+digit
object: "{" _ pairs=?{&pair *["," &pair]} _ "}"
pair: _ key=string _ ":" _ value=value _
array: "[" _ items=?{&value *["," &value]} _ "]"
Aaand we’re done! A working JSON parser in just 9 lines of code.
Unfortunately, the tutorial is not quite done. One operator has escaped its scope, and that is
adjoinment, notated by $
. Rules containing $
will consume, but not return, most
consumed code. Only code passed to $
will be adjoined and returned. So, for:
ex: "prefix " $"value" " postfix"
ex("prefix value postfix") = "value"
.
The final bit to learn is the comment. Line comments start with /
and continue to the end
of the line, and block and inline comments look ((like this))
.
Actually using this in Nim is not too difficult and is covered in usage <usage.html>.
Cheatsheet¶
Constructs¶
Construct name | Syntax | Semantics |
---|---|---|
Line comments | /text |
Ignored by the parser |
Inline & block comments | ((text)) |
Ignored by the parser |
Brackets | [code] |
Like parenthesis |
Definition | identifier: body |
Defines a rule |
Reference | ruleName |
References / “calls” a named rule |
Literal | "text" or 'text' |
Matches exact text |
Set | <characters> |
Matches any single contained character |
Sequences | rule1 rule2 ... |
Matches several rules in order |
Choice | rule1 | rule2 | ... * |
Matches any of several rules |
Optional | ?rule |
Optionally matches a rule |
Oneplus | +rule |
Matches a rule once or more |
Zeroplus | *rule |
Matches a rule zero or more times |
Lambda | {rule} |
Makes a new state for rule |
Result | #rule |
Sets the state to value from rule |
Adjoinment | $rule |
Appends text from rule to state |
Property | key=rule |
Maps key on state to value from rule |
Extension | &rule |
Appends a node to the state |
* Leading and trailing pipes are allowed
Builtins¶
Name | Description or equivalent code |
---|---|
any |
Matches any single character except \0 |
newline |
+<\c\l> . Use in lieu of \n , which doesn’t exist. |
whitespace |
Matches any single whitespace character |
_ |
*whitespace |
lower |
<abcdefghijklmnopqrstuvwxyz> |
upper |
<ABCDEFGHIJKLMNOPQRSTUVWXYZ> |
alpha |
lower | upper |
digit |
<1234567890> |
alphanum |
alpha | digit |
Legislators¶
As most people know, legislators make the rules. This applies in Lilt, too.
A legislator takes a bit of code and returns a rule based on it.
Literal¶
One of the simplest legislators is the literal legislator, which returns a rule only matching exactly the given text.
Literal legislators begin and end with a double quote or single quote. Inbetween these two double quotes lies the content the resultant rule will match.
For instance, "banana"
will match any text beginning with “banana”. Matching this text, it will consume 6 characters (the length of the word “banana”) and return the text “banana”. If the text doesn’t start with “banana”, the rule will fail.
Escape Sequences¶
Literals may contain the following escape sequences
Key | Mapping |
---|---|
\\ |
Literal backslash |
\t |
Tab |
\r |
Carriage return |
\c |
Carriage return |
\l |
Linefeed |
\a |
Alert |
\b |
Backspace |
\e |
ESC |
\' |
Literal ‘ |
\" |
Literal “ |
\> |
Literal > |
\xHH |
Character with given hex value |
Set¶
Set legislators return a rule which matches any single character in the set.
Set legislators begin with a <
and end with a >
. Contained within are all the characters in the set.
For instance <abcdef>
will match “a”, “b”, “c”, “d”, “e”, or “f”. If it matches, it will consume the a single character and return it. If the text doesn’t match, it will fail.
Sets have the same escape sequences as literals.
Sequence¶
Sequence legislators are comprised of several rules in a row. Sequence legislators match text that matches all of the contained rules in order.
For instance, "word" " " "another"
is equivalent to "word another"
.
Slighyly more usefully, <ab> <?!>
matches “a?”, “a!”, “b?”, and “b!”.
Sequences always return text, concatenating together the text return values of their contined rules. Sequences will fail if any of the contained rules fail.
Choice¶
Choice legislators are also comprised of several rules. Choices return the return value of the first contained rule which matches the given text. The matching rule will also consume code, and possibly mutate the current state.
Choices are comprised of a sequence of rules, each separated by a pipe (|). Both a leading and a trailing pipe is allowed.
For instance, "firstname" | "lastname"
matches “firstname” and “lastname” only.
Choices will only fail if the given text matches none of the contained rules.
Ambiguous choices are allowed. For instance, "abc" | "abc"
is ambiguous – does the text “abc” match the first rule, or the second? To solve this, the choice defers to the first matching rule.
Optional¶
Optional legislators optionally match their contained rule. If the contained rule matches, the optional returns the value.
If the inner rule doesn’t match, and would have returned a node, the optional returns nothing.
If the inner rule doesn’t match, and would have returned text, the optional returns “”.
If the inner rule doesn’t match, and would have return a list of nodes, the optiional returns [].
Optionals begin with a ?
and are followed by a rule.
For instance, ?"fruit!"
applied to “fruit!” returns “fruit!” and applied to “NOT FRUIT” returns “”.
Oneplus¶
Oneplus legislators match their contained rule once or more.
They begin with +
and are followed by a rule.
If the inner rule returns text, the oneplus will return all the text returned by the inner rule concatenated together, similarly to a sequence.
For instance, *"a"
applied to “aaaaa” returns “aaaaa”.
If the inner rule returns a node, the oneplus will similarly return a list of nodes.
Oneplus rules will only fail if the inner rule is not matched at least once.
Zeroplus¶
Zeroplus legislators are like oneplus legislators, but match the inner rule zero or more times.
Zeroplus’ begin with a *
and are followed by a rule.
*rule
is actually expanded to ?+rule
; zeroplus legislators are macros.
Lambdas & States¶
Lambda legislators contain a rule and posses a mutable state.
They begin and end with {
and }
, containing the sequence/choice in between.
If the lambda doesn’t contain any adjoinments, properties, or extensions, it will return the value of the contained rule.
Otherwise, the lambda will return the state, which can be text, a node, or a list of nodes, after the inner rule has run. As it runs, the state will be mutated.
For instance, { *&"i" }
applied to “iiii” will return “iiii”, just as *"i"
would. Though effectively the same, the two are semantically different. The former reads like: zero or more times, append the text “i” to the state, returning it when complete; the latter reads like: match zero or more “i”s and return the consumed value.
Result¶
Result legislators modify the current state, setting it to the value of the result’s inner rule.
Results begin with a #
and are followed by any rule that doesn’t return nothing.
For instance, _ #"banana" _
will match the text ” banana “, returning “banana”.
Results return nothing and fail when their inner rule fails.
Adjoinment¶
Adjoinment legislators modify the current state, appending the text of the adjoinment’s inner rule.
Adjoinments begin with a $
and are followed by a text-returning rule.
For instance, $"banana"
matches the text “banana”, but instead of returning it, mutates the current state, appending the text “banana”. This distinction is covered in the description of lambdas.
Adjoinments return nothing and fail when the inner rule fails.
Property¶
Property legislators modify the current state, setting an attribute of the property’s inner rule.
Properties consist of an identifier followed by a =
and a node-returning, text-returning, or node-list-returning rule.
For instance, fruit="grapes"
will match the text “grapes”, setting the attribute “fruit” of the current state to the value “grapes”.
Properties return nothing and fail when the inner rule fails.
Extension¶
Extension legislators modify the current state, appending a node.
Extensions being with a &
and are followed by a node-returning rule.
For instance, if node
is a rule which matches the text “peach” and returns a node with the property {fruit: "peach"}
, &node
will match the text “peach”, appending the resultant node to the state.
Extensions return nothing and fail when the inner rule fails.
Examples¶
So, you want to see examples of Lilt! Well, you’re in luck.
Parsing a Number¶
Parsing a number, returning a node:
digit: <1234567890>
number: wholes=*digit ?["." decimals=+digit]
Results:
on "12.3" -> {
wholes: "12"
decimals: "3"
}
on "." -> Gives an error
on "45." -> Gives an error
on ".500" -> {
wholes: ""
decimals: "500"
}
Usage¶
Lilt is on nimble. A simple nimble refresh
will download or update the package. Then, in your Nim code, just import lilt
.
We’ll start with the bread and butter parser type:
Parser* = proc(text: string): LiltValue
A parser accepts some text and returns a parsed value. Alternatively, it may throw a RuleError
, which just says that the text didn’t match the parser.
The returned value may be text, a node, or a list of nodes. This is encoded in the next three types:
LiltType* = enum
ltText
ltNode
ltList
LiltValue* = object
case kind*: LiltType
of ltText:
text*: string
of ltNode:
node*: Node
of ltList:
list*: seq[Node]
Node* = object
kind*: string # name of the rule that this node was parsed by
properties*: TableRef[string, LiltValue] # properties of the node
Instead of writing node.properties[key]
, one can just write node[key]
via the following proc:
proc `[]`(node: Node, key: string): LiltValue =
return node.properties[key]
In order to create parsers, one should use the included makeParsers
proc, which looks like:
proc makeParsers*(code: string): Table[string, Parser]
It accepts a Lilt specification (code
), and returns all of the defined rules in that specification as a table mapping :code:`string`s to :code:`Parser`s.
For your convenience, three LiltValue
initializers have also been included:
proc initLiltValue*(text: string): LiltValue =
return LiltValue(kind: ltText, text: text)
proc initLiltValue*(node: Node): LiltValue =
return LiltValue(kind: ltNode, node: node)
proc initLiltValue*(list: seq[Node]): LiltValue =
return LiltValue(kind: ltList, list: list)
Example¶
We’ll use the JSON parser example from the tutorial:
import lilt
import tables
# Create our Lilt specification
# Could also be, for instance, read from a file
const spec = """
value: _ #[string | number | object | array | trueLiteral | falseLiteral | nullLiteral] _
trueLiteral: _="" "true"
falseLiteral: _="" "false"
nullLiteral: _="" "null"
string: '"' value=*stringChar '"'
stringChar: [!<"\\> any] | "\\" [</\\bfnrt> | "u" hexDig hexDig hexDig hexDig]
hexDig: <1234567890ABCDEFabcdef>
number: ?negative="-" wholes=["0" | +digit] ?["." decimals=*digit] ?exponent=numberExp
numberExp: <eE> sign=<+-> digits=+digit
object: "{" _ pairs=?{&pair *["," &pair]} _ "}"
pair: _ key=string _ ":" _ value=value _
array: "[" _ items=?{&value *["," &value]} _ "]"
"""
# Is a Table[string, Parser]
let parsers = makeParsers(spec)
# Let's say we want to parse a number
# Get that parser by the name of the rule: "number"
let numberParser = parsers["number"]
# ...and use it!
let parsedNumber = numberParser("3.0e+10")
echo parsedNumber.node["wholes"].text # "3"
echo parsedNumber.node["decimals"].text # "0"
echo parsedNumber.node["exponent"].node["sign"].text # "+"
echo parsedNumber.node["exponent"].node["digits"].text # "10"
# Let's try it with some simple JSON
let jsonParser = parsers["value"]
echo jsonParser("30").node # {"wholes": "30"}
echo jsonParser("\"string\"").node # {"value": "string"}
echo jsonParser("""
{
"name": "marbles",
"color": "red",
"count": 100
}
""").node
#[ becomes
{
"pairs": [
{
"key": {"value": "name"},
"value": {"value": "marbles"}
},
{
"key": {"value": "color"},
"value": {"value": "red"}
},
{
"key": {"value": "count"},
"value": {"wholes": 100},
}
]
}
]#
Sublime Text 3 Integration¶
st3/Lilt.sublime-syntax
contains a syntax definition for Lilt specifications usable with Sublime Text 3. Unfortuantely, there is no package on Package Control (yet).
To install, just drop Lilt.sublime-text
into ~/.config/sublime-text-3/Packages/User
. Then, in ST3, select view > syntax > Lilt. However, this should not be needed for .lilt
files.