visit
sylver
binary to a location in your $PATH
Let us create a blank workspace for this tutorial!We'll start by creating a new folder and a json.syl
file to write the Sylver specification for the JSON language.
mkdir sylver_getting_started
cd sylver_getting_started
touch json.syl
We will also store our test JSON file in config.json
.
{
"variables": [
{
"name": "country",
"description": "Customer's country of residence",
"values": ["us", "fr", "it"]
},
{
"name": "age",
"description": "Cusomer's age",
"type": "number"
}
]
}
We'll add the following node types declarations to our json.syl
spec:
node JsonNode { }
node Null: JsonNode { }
node Bool: JsonNode { }
node Number: JsonNode { }
node String: JsonNode { }
node Array: JsonNode {
elems: List<JsonNode>
}
node Object: JsonNode {
members: List<Member>
}
node Member: JsonNode {
key: String,
value: JsonNode
}
These declarations resemble object-type declarations in many mainstream languages. The :
syntax denotes inheritance.
Tokens are described using declarations of the form term NAME = <term_content>
where <term_content>
is either a literal surrounded by single-quotes ('
) or a regex between backticks (`
). The regexes use a syntax similar to Perl-style regular expressions. Characters in the input string that match one of the terminal literals or regexes will be grouped into a token of the given name.
term COMMA = ','
term COLON = ':'
term L_BRACE = '{'
term R_BRACE = '}'
term L_BRACKET = '['
term R_BRACKET = ']'
term NULL = 'null'
term BOOL_LIT = `true|false`
term NUMBER_LIT = `\-?(0|([1-9][0-9]*))(.[0-9]+)?((e|E)(\+|-)?[0-9]+)?`
term STRING_LIT = `"([^"\\]|(\\[\\/bnfrt"])|(\\u[a-fA-F0-9]{4}))*"`
ignore term WHITESPACE = `\s`
Note that the WHITESPACE
term declaration (matching a single whitespace character) is prefixed with the ignore
keyword. This means that WHITESPACE
tokens do not affect the structure of the document and can be ignored during syntactic analysis.
rule string = String { STRING_LIT }
For example, here is a rule specifying that a Member
node (corresponding to an object member
in JSON) can be built by building a node using the string
rule and then matching a COLON
token followed by any valid JSON value:
rule member = Member { key@string COLON value@main }
Nested nodes are associated with a field using the @
syntax.
The main
rule is the entry point for the parser, so in our case, it designates any valid JSON value.
rule main =
Null { NULL }
| Number { NUMBER_LIT }
| Bool { BOOL_LIT }
| string
| Array { L_BRACKET elems@sepBy(COMMA, main) R_BRACKET }
| Object { L_BRACE members@sepBy(COMMA, member) R_BRACE }
The sepBy(TOKEN, rule_name)
syntax is used to parse nodes using the main
rule, while matching a TOKEN
token between every parsed node.
node JsonNode { }
node Null: JsonNode { }
node Bool: JsonNode { }
node Number: JsonNode { }
node String: JsonNode { }
node Array: JsonNode {
elems: List<JsonNode>
}
node Object: JsonNode {
members: List<Member>
}
node Member: JsonNode {
key: String,
value: JsonNode
}
term COMMA = ','
term COLON = ':'
term L_BRACE = '{'
term R_BRACE = '}'
term L_BRACKET = '['
term R_BRACKET = ']'
term NULL = 'null'
term BOOL_LIT = `true|false`
term NUMBER_LIT = `\-?(0|([1-9][0-9]*))(.[0-9]+)?((e|E)(\+|-)?[0-9]+)?`
term STRING_LIT = `"([^"\\]|(\\[\\/bnfrt"])|(\\u[a-fA-F0-9]{4}))*"`
ignore term WHITESPACE = `\s`
rule string = String { STRING_LIT }
rule member = Member { key@string COLON value@main }
rule main =
Null { NULL }
| Number { NUMBER_LIT }
| Bool { BOOL_LIT }
| string
| Array { L_BRACKET elems@sepBy(COMMA, main) R_BRACKET }
| Object { L_BRACE members@sepBy(COMMA, member) R_BRACE }
This is done by invoking the following command:
sylver parse --spec=json.syl --file=config.json
Object {
. ● members: List<Member> {
. . Member {
. . . ● key: String { "variables" }
. . . ● value: Array {
. . . . ● elems: List<JsonNode> {
. . . . . Object {
. . . . . . ● members: List<Member> {
. . . . . . . Member {
. . . . . . . . ● key: String { "name" }
. . . . . . . . ● value: String { "country" }
. . . . . . . }
. . . . . . . Member {
. . . . . . . . ● key: String { "description" }
. . . . . . . . ● value: String { "Customer's country of residence" }
. . . . . . . }
. . . . . . . Member {
. . . . . . . . ● key: String { "values" }
. . . . . . . . ● value: Array {
. . . . . . . . . ● elems: List<JsonNode> {
. . . . . . . . . . String { "us" }
. . . . . . . . . . String { "fr" }
. . . . . . . . . . String { "it" }
. . . . . . . . . }
. . . . . . . . }
. . . . . . . }
. . . . . . }
. . . . . }
. . . . . Object {
. . . . . . ● members: List<Member> {
. . . . . . . Member {
. . . . . . . . ● key: String { "name" }
. . . . . . . . ● value: String { "age" }
. . . . . . . }
. . . . . . . Member {
. . . . . . . . ● key: String { "description" }
. . . . . . . . ● value: String { "Customer's age" }
. . . . . . . }
. . . . . . . Member {
. . . . . . . . ● key: String { "type" }
. . . . . . . . ● value: String { "number" }
. . . . . . . }
. . . . . . }
. . . . . }
. . . . }
. . . }
. . }
. }
}