1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
extern crate tom;
use std::iter;
use tom::{
TomlDoc, CstNode, Symbol, TextRange, Position::*,
ast::{self, EntryOwner},
symbol::*,
};
fn main() {
let text = "\
title = \"TOML Example\"
[owner]
name = \"Tom Preston-Werner\"
dob = 1979-05-27T07:32:00-08:00 # First class dates
";
// `TomlDoc` is the API entry point.
let doc = TomlDoc::new(text);
// Internally, `TomlDoc` is represented as a homogeneous
// Concrete Syntax Tree (CST) which includes whitespace and
// comments explicitely. `.debug` method can be used to see
// this representation:
assert_eq!(doc.debug().trim(), r##"
DOC@[0; 112)
ENTRY@[0; 22)
KEY@[0; 5)
BARE_KEY@[0; 5) "title"
WHITESPACE@[5; 6)
EQ@[6; 7) "="
WHITESPACE@[7; 8)
VALUE@[8; 22)
BASIC_STRING@[8; 22) "\"TOML Example\""
WHITESPACE@[22; 24)
TABLE@[24; 111)
TABLE_HEADER@[24; 31)
L_BRACK@[24; 25) "["
KEY@[25; 30)
BARE_KEY@[25; 30) "owner"
R_BRACK@[30; 31) "]"
WHITESPACE@[31; 32)
ENTRY@[32; 59)
KEY@[32; 36)
BARE_KEY@[32; 36) "name"
WHITESPACE@[36; 37)
EQ@[37; 38) "="
WHITESPACE@[38; 39)
VALUE@[39; 59)
BASIC_STRING@[39; 59) "\"Tom Preston-Werner\""
WHITESPACE@[59; 60)
ENTRY@[60; 111)
KEY@[60; 63)
BARE_KEY@[60; 63) "dob"
WHITESPACE@[63; 64)
EQ@[64; 65) "="
WHITESPACE@[65; 66)
VALUE@[66; 91)
DATE_TIME@[66; 91) "1979-05-27T07:32:00-08:00"
WHITESPACE@[91; 92)
COMMENT@[92; 111) "# First class dates"
WHITESPACE@[111; 112)
"##.trim());
// Note that `new` method does not return a `Result`.
// This is because the library can parse even partially
// invalid toml documents. Use `.errors` method to check
// if file has any syntax errors;
let invalid_toml = TomlDoc::new(":-)\nfoo=1");
assert_eq!(invalid_toml.debug().trim(), r#"
DOC@[0; 9)
ERROR@[0; 1)
ERROR@[0; 1) ":"
ENTRY@[1; 9)
KEY@[1; 2)
BARE_KEY@[1; 2) "-"
ERROR@[2; 3)
ERROR@[2; 3) ")"
WHITESPACE@[3; 4)
KEY@[4; 7)
BARE_KEY@[4; 7) "foo"
EQ@[7; 8) "="
VALUE@[8; 9)
NUMBER@[8; 9) "1"
error@[0; 1) ":": expected a key
error@[2; 3) ")": expected `.`
error@[1; 8) "-)\nfoo=": newlines are forbidden in entries"#.trim());
assert_eq!(invalid_toml.errors().len(), 3);
// To access CST, use `.cst` method.
let root: CstNode = doc.cst();
// A CST node has a `Symbol`, which says what kind
// of syntactic construct this node represents, a
// range in the text and links to children, parent,
// and siblings.
//
// Note that every method on `CstNode` needs a `&TomlDoc` argument.
// This is because internally a `CstNode` is just an index, and it
// needs the document to extract information.
assert_eq!(root.symbol(&doc), DOC);
assert_eq!(root.children(&doc).iter().count(), 4);
let trailing_newline = root.children(&doc).last().unwrap();
let ws_symbol: Symbol = trailing_newline.symbol(&doc);
assert_eq!(ws_symbol, WHITESPACE);
assert_eq!(trailing_newline.get_text(&doc), "\n");
let title = root.children(&doc).first().unwrap();
assert_eq!(title.symbol(&doc), ENTRY);
assert_eq!(title.range(&doc), TextRange::from_to(0.into(), 22.into()));
assert_eq!(title.parent(&doc), Some(root));
assert_eq!(title.get_text(&doc), "title = \"TOML Example\"");
// Working with CST directly is powerful, because you get access to
// all comments, punctuation and whitespace, but it is not convenient
// if you need the meaning of the toml document. For that purpose,
// a higher-level `ast` API exists.
let root: ast::Doc = doc.ast();
assert_eq!(root.entries(&doc).count(), 1);
assert_eq!(root.tables(&doc).count(), 1);
let table: ast::Table = root.tables(&doc).next().unwrap();
assert_eq!(table.entries(&doc).count(), 2);
let entry: ast::Entry = table.entries(&doc).next().unwrap();
match entry.value(&doc).kind(&doc) {
ast::ValueKind::StringLit(lit) => {
assert_eq!(lit.value(&doc), "Tom Preston-Werner");
},
_ => panic!("unexpected entry")
}
// Internally, each AST node is a wrapper of the corresponding CST node,
// and you can use `.cst()` and `::cast()` methods to convert between the two:
let entry_raw = entry.cst();
assert_eq!(entry_raw.get_text(&doc), "name = \"Tom Preston-Werner\"");
match ast::Entry::cast(entry_raw, &doc) {
Some(entry) => assert_eq!(entry.keys(&doc).next().unwrap().name(&doc), "name"),
None => panic!("can't cast a node to entry"),
}
// Let's see how the API for modifying the document works.
// First, we need to call `.start_edit` method. This is required
// for two reasons:
// * A small edit can change the ranges of a large number of CST nodes
// which makes maintaining correct ranges costly. Thus, in edit mode
// ranges are assumed to be invalid and `CstNode::range` panics.
// * By design, arbitrary CST structure can be created during edits.
// That means that methods that assume that, for example, `ast::Value`'s
// parent is always an `ast::Entry` may panic as well.
let mut doc = doc;
doc.start_edit();
// The primary way to create new elements is the family of
// `TomlDoc::new_foo_from_text` methods. Using raw text, you
// have full control of formatting, whitespace and comments:
let new_entry: ast::Entry = doc.new_entry_from_text("foo= 92 #comments are preserved");
assert_eq!(new_entry.cst().get_text(&doc), "foo= 92 #comments are preserved");
// If the text can not be parsed as a requested syntactic construct,
// the call will panic:
//
// doc.new_entry_from_text(":("); // panics!
// You can also create new nodes in a typed way from components,
// using `TomlDoc::new_foo` family of methods.
let new_entry: ast::Entry = {
let key: ast::Key = doc.new_key("foo");
let value: ast::Value = doc.new_value(92);
doc.new_entry(iter::once(key), value)
};
assert_eq!(new_entry.cst().get_text(&doc), "foo = 92");
// `TomlDoc` has several methods which manipulate trees:
// `replace`, `detach`, `insert`.
// Because each node is an index, and not a reference, we
// are able mutate document without invalidating existing
// nodes.
doc.replace(entry, new_entry);
assert_eq!(doc.cst().get_text(&doc), "\
title = \"TOML Example\"
[owner]
foo = 92
dob = 1979-05-27T07:32:00-08:00 # First class dates
");
doc.detach(new_entry);
assert_eq!(doc.cst().get_text(&doc), "\
title = \"TOML Example\"
[owner]
dob = 1979-05-27T07:32:00-08:00 # First class dates
");
doc.insert(new_entry, PrependTo(root.cst()));
assert_eq!(doc.cst().get_text(&doc), "\
foo = 92
title = \"TOML Example\"
[owner]
dob = 1979-05-27T07:32:00-08:00 # First class dates
");
// AST nodes have type-safe mutating methods as well,
// although currently only few are actually implemented :)
let e1 = doc.new_entry_from_text("foo = 1");
let e2 = doc.new_entry_from_text("bar = 2");
let d = doc.new_dict_from_text("{}");
assert_eq!(d.cst().get_text(&doc), "{}");
d.append_entry(&mut doc, e1);
assert_eq!(d.cst().get_text(&doc), "{ foo = 1 }");
d.append_entry(&mut doc, e2);
assert_eq!(d.cst().get_text(&doc), "{ foo = 1, bar = 2 }");
// Remember that we've begun the editing with `.start_edit` call?
// We have two ways to finish edit, with different tradeoffs.
//
// `doc.finish_edit_no_reparse();` will recalculate correct
// ranges for elements, but it **will not** check that CST
// structure is valid. However, all existing CST and AST
// nodes (which, as a reminder, are indices), remain valid.
//
// `doc.finish_edit_full_reparse();` will reparse the document,
// creating a fresh, valid CST. Because the CST structure might
// change after the reparse, old indices become invalid.
doc.finish_edit_no_reparse();
}