1pub(crate) use std::collections::HashMap;
2use std::sync::OnceLock;
3
4pub(crate) use crate::core::signatures::ExpectedDomain;
5pub(crate) use crate::core::source::{Position, SourceFile, Span};
6use crate::program::Program;
7pub(crate) use crate::settings::table::KeyKind;
8pub(crate) use crate::settings::{PathPart, table};
9pub(crate) use crate::settings::{Settings, SettingsListElement, SettingsNode};
10pub(crate) use crate::wir::{
11 self, Action, Event, EventTarget, EventTeam, ModifyOp, PlayerEventKind, Value, ValueNode,
12};
13
14use super::keywords::{canonical_structural_id, spelling_token_len, structural_spellings};
15pub(crate) use super::lexer::{Token, TokenKind, tokenize};
16pub(crate) use crate::catalog::{Catalog, Kind, Locale};
17pub(crate) use crate::core::error::{Result, WorkshopError};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub(crate) enum Stop {
21 SectionClosed,
22 End,
23 ElseIf,
24 Else,
25}
26
27pub(crate) enum AssignmentOperator {
28 Set,
29 Modify(ModifyOp),
30}
31
32pub fn parse(input: &str, catalog: &Catalog, locale: &Locale) -> Result<Program> {
38 parse_with_context(input, catalog, locale, catalog)
39}
40
41#[cfg(test)]
42pub(crate) fn parse_wir(input: &str, catalog: &Catalog, locale: &Locale) -> Result<wir::Program> {
43 parse_wir_with_context(input, catalog, locale, catalog)
44}
45
46pub fn parse_with_context(
57 input: &str,
58 catalog: &Catalog,
59 locale: &Locale,
60 context: &dyn ExpectedDomain,
61) -> Result<Program> {
62 parse_wir_with_context(input, catalog, locale, context).and_then(Program::from_wir)
63}
64
65pub(crate) fn parse_wir_with_context(
66 input: &str,
67 catalog: &Catalog,
68 locale: &Locale,
69 context: &dyn ExpectedDomain,
70) -> Result<wir::Program> {
71 let tokens = tokenize(input).map_err(|error| {
72 WorkshopError::malformed(error.message, Some(synthetic_span(error.position)))
73 })?;
74 ParseContext {
75 tokens,
76 pos: 0,
77 source: input,
78 catalog,
79 locale: locale.clone(),
80 context,
81 expected_domain: None,
82 condition_pending: false,
83 in_condition: false,
84 call_stack: Vec::new(),
85 target: wir::Program::default(),
86 globals: HashMap::new(),
87 players: HashMap::new(),
88 subroutines: HashMap::new(),
89 next_global_index: 0,
90 next_player_index: 0,
91 structural_spellings: HashMap::new(),
92 }
93 .program()
94}
95
96pub(crate) fn synthetic_span(position: Position) -> Span {
97 Span::new(crate::core::ids::Id::from_index(0), position, position)
98}
99
100pub(crate) struct ParseContext<'a> {
101 pub(crate) tokens: Vec<Token>,
102 pub(crate) pos: usize,
103 pub(crate) source: &'a str,
104 pub(crate) catalog: &'a Catalog,
105 pub(crate) locale: Locale,
106 pub(crate) context: &'a dyn ExpectedDomain,
107 pub(crate) expected_domain: Option<&'a str>,
108 pub(crate) condition_pending: bool,
109 pub(crate) in_condition: bool,
110 pub(crate) call_stack: Vec<String>,
111 pub(crate) target: wir::Program,
112 pub(crate) globals: HashMap<String, wir::GlobalVarId>,
113 pub(crate) players: HashMap<String, wir::PlayerVarId>,
114 pub(crate) subroutines: HashMap<String, wir::SubroutineId>,
115 pub(crate) next_global_index: u32,
116 pub(crate) next_player_index: u32,
117 structural_spellings: HashMap<String, Vec<String>>,
118}
119
120impl<'a> ParseContext<'a> {
121 pub(crate) fn with_domain<T>(
122 &mut self,
123 domain: Option<&'a str>,
124 f: impl FnOnce(&mut Self) -> Result<T>,
125 ) -> Result<T> {
126 let saved = self.expected_domain;
127 self.expected_domain = domain;
128 let result = f(self);
129 self.expected_domain = saved;
130 result
131 }
132
133 pub(crate) fn value_in_domain(&mut self, domain: Option<&'a str>) -> Result<wir::ValueId> {
134 self.with_domain(domain, |p| p.value())
135 }
136
137 pub(crate) fn resolve_entry(
138 &self,
139 kind: Kind,
140 spelling: &str,
141 ) -> Option<&'a crate::catalog::CatalogEntry> {
142 self.catalog
143 .resolve(kind, &self.locale, spelling)
144 .or_else(|| {
145 if self.locale != *self.catalog.primary_locale() {
146 self.catalog
147 .resolve(kind, self.catalog.primary_locale(), spelling)
148 } else {
149 None
150 }
151 })
152 }
153
154 pub(crate) fn canonical_keyword(&self, spelling: &str) -> String {
155 canonical_structural_id(self.catalog, &self.locale, spelling)
156 }
157
158 pub(crate) fn line_has_assignment(&self) -> bool {
159 let tokens: Vec<_> = self.tokens[self.pos..]
160 .iter()
161 .take_while(|token| !matches!(token.kind, TokenKind::Semi | TokenKind::RBrace))
162 .collect();
163 tokens.iter().any(|token| {
164 matches!(&token.kind, TokenKind::Op(op) if matches!(op.as_str(), "=" | "+=" | "-=" | "*=" | "/=" | "%="))
165 }) || tokens.windows(2).any(|window| {
166 matches!(&window[0].kind, TokenKind::Word(_))
167 && matches!(&window[1].kind, TokenKind::Op(op) if op == "=")
168 })
169 }
170
171 pub(crate) fn phrase(&mut self) -> Result<(String, Position, Position)> {
172 let mut parts: Vec<(String, Position, Position)> = Vec::new();
173 let (start, mut end) = match self.peek() {
174 Some(Token {
175 kind: TokenKind::Word(word),
176 start,
177 end,
178 }) => {
179 parts.push((word.clone(), start, end));
180 (start, end)
181 }
182 Some(Token {
183 kind: TokenKind::Number { text, .. },
184 start,
185 end,
186 }) => {
187 parts.push((text.clone(), start, end));
188 (start, end)
189 }
190 Some(token) => return Err(self.malformed("expected an identifier", &token)),
191 None => return Err(self.malformed("expected an identifier", self.eof())),
192 };
193 self.pos += 1;
194 while let Some(token) = self.peek() {
195 match token {
196 Token {
197 kind: TokenKind::Word(word),
198 start: word_start,
199 end: word_end,
200 } => {
201 if matches!(
202 self.peek_kind_at(1),
203 Some(TokenKind::Op(equal)) if equal == "="
204 ) {
205 break;
206 }
207 parts.push((word.clone(), word_start, word_end));
208 end = word_end;
209 self.pos += 1;
210 }
211 Token {
212 kind: TokenKind::Number { text, .. },
213 start: number_start,
214 end: number_end,
215 } => {
216 parts.push((text.clone(), number_start, number_end));
217 end = number_end;
218 self.pos += 1;
219 }
220 _ => break,
221 }
222 }
223 Ok((join_as_written(&parts), start, end))
224 }
225
226 pub(crate) fn phrase_on_line(&mut self) -> Result<(String, Position, Position)> {
227 self.phrase_on_line_impl(false)
228 }
229
230 pub(crate) fn settings_list_name(&mut self) -> Result<(String, Position, Position)> {
231 self.phrase_on_line_impl(true)
232 }
233
234 fn phrase_on_line_impl(&mut self, list_name: bool) -> Result<(String, Position, Position)> {
235 let mut parts: Vec<(String, Position, Position)> = Vec::new();
236 let (start, mut end, line) = match self.peek() {
237 Some(Token {
238 kind: TokenKind::Word(word),
239 start,
240 end,
241 }) => {
242 parts.push((word.clone(), start, end));
243 (start, end, start.line)
244 }
245 Some(Token {
246 kind: TokenKind::Number { text, .. },
247 start,
248 end,
249 }) => {
250 parts.push((text.clone(), start, end));
251 (start, end, start.line)
252 }
253 Some(token) => return Err(self.malformed("expected an identifier", &token)),
254 None => return Err(self.malformed("expected an identifier", self.eof())),
255 };
256 self.pos += 1;
257 while let Some(token) = self.peek() {
258 let (text, word_start, word_end) = match token {
259 Token {
260 kind: TokenKind::Word(word),
261 start,
262 end,
263 } => (word.clone(), start, end),
264 Token {
265 kind: TokenKind::Number { text, .. },
266 start,
267 end,
268 } => (text.clone(), start, end),
269 Token {
270 kind: TokenKind::Dot,
271 start,
272 end,
273 } => (".".to_string(), start, end),
274 Token {
275 kind: TokenKind::Op(op),
276 start,
277 end,
278 } if matches!(op.as_str(), "-" | "%") => (op.clone(), start, end),
279 Token {
280 kind: TokenKind::Colon,
281 start,
282 end,
283 } if list_name => (":".to_string(), start, end),
284 _ => break,
285 };
286 if word_start.line != line {
287 break;
288 }
289 parts.push((text, word_start, word_end));
290 end = word_end;
291 self.pos += 1;
292 }
293 Ok((join_as_written(&parts), start, end))
294 }
295
296 pub(crate) fn phrase_on_line_with_colon(&mut self) -> Result<(String, Position, Position)> {
297 let (mut phrase, start, mut end) = self.phrase_on_line()?;
298 if matches!(
299 self.peek(),
300 Some(Token {
301 kind: TokenKind::Colon,
302 ..
303 })
304 ) {
305 let colon_pos = self.pos;
306 self.next();
307 let (rest, _, rest_end) = self.phrase_on_line()?;
308 if matches!(
309 self.peek(),
310 Some(Token {
311 kind: TokenKind::LBrace,
312 ..
313 })
314 ) {
315 phrase.push(':');
316 phrase.push(' ');
317 phrase.push_str(&rest);
318 end = rest_end;
319 } else {
320 self.pos = colon_pos;
321 }
322 }
323 Ok((phrase, start, end))
324 }
325
326 pub(crate) fn enum_member_phrase(&mut self) -> Result<(String, Position, Position)> {
327 let first = self
328 .peek()
329 .ok_or_else(|| self.malformed("expected an enum member", self.eof()))?;
330 let start = first.start;
331 let line = first.start.line;
332 let mut end = first.end;
333 let mut parts: Vec<(String, Position, Position)> = Vec::new();
334 while let Some(token) = self.peek() {
335 if token.start.line != line
336 || matches!(
337 token.kind,
338 TokenKind::RParen | TokenKind::Comma | TokenKind::LParen
339 )
340 {
341 break;
342 }
343 self.pos += 1;
344 end = token.end;
345 parts.push((raw_token_text(&token.kind), token.start, token.end));
346 }
347 if parts.is_empty() {
348 return Err(self.malformed("expected an enum member", &first));
349 }
350 Ok((join_as_written(&parts), start, end))
351 }
352
353 pub(crate) fn line_text(&mut self) -> Result<(String, Option<Span>)> {
354 let mut parts: Vec<(String, Position, Position)> = Vec::new();
355 let mut start = None;
356 let mut end = None;
357 loop {
358 match self.peek() {
359 Some(Token {
360 kind: TokenKind::Semi,
361 ..
362 }) => {
363 self.pos += 1;
364 break;
365 }
366 Some(token) => {
367 start.get_or_insert(token.start);
368 end = Some(token.end);
369 match &token.kind {
370 TokenKind::Word(_)
371 | TokenKind::Number { .. }
372 | TokenKind::Dot
373 | TokenKind::Colon
374 | TokenKind::Op(_) => {}
375 _ => return Err(self.malformed("expected a text line", &token)),
376 }
377 parts.push((raw_token_text(&token.kind), token.start, token.end));
378 self.pos += 1;
379 }
380 None => {
381 return Err(self.malformed("unexpected end of input in line", self.eof()));
382 }
383 }
384 }
385 Ok((
386 join_as_written(&parts),
387 start
388 .zip(end)
389 .map(|(start, end)| Span::new(self.file(), start, end)),
390 ))
391 }
392
393 pub(crate) fn spelling_run(
394 &self,
395 pos: usize,
396 attaches_to: Option<Position>,
397 ) -> Vec<(usize, String, Position)> {
398 let mut ends = Vec::new();
399 let mut text = String::new();
400 let mut index = pos;
401 let mut prev_end = attaches_to;
402 while let Some(token) = self.tokens.get(index) {
403 let fragment = match &token.kind {
404 TokenKind::Word(word) => word.as_str(),
405 TokenKind::Number { text, .. } => text.as_str(),
406 TokenKind::Dot => ".",
407 TokenKind::Colon => ":",
408 TokenKind::Op(op) if op == "not" => "!",
409 TokenKind::Op(op) if op == "-" => "-",
410 _ => break,
411 };
412 let needs_adjacent = matches!(token.kind, TokenKind::Dot | TokenKind::Colon)
416 || matches!(&token.kind, TokenKind::Op(op) if op == "not");
417 if needs_adjacent && prev_end.is_none_or(|end| token.start != end) {
418 break;
419 }
420 if prev_end.is_some_and(|end| token.start != end) {
421 text.push(' ');
422 }
423 text.push_str(fragment);
424 prev_end = Some(token.end);
425 index += 1;
426 ends.push((text.len(), token.end));
427 }
428 ends.into_iter()
429 .enumerate()
430 .rev()
431 .map(|(i, (len, end))| (i + 1, text[..len].to_string(), end))
432 .collect()
433 }
434
435 pub(crate) fn catalog_spelling(
436 &mut self,
437 accept: impl Fn(&str) -> bool,
438 ) -> Option<(String, Position, Position)> {
439 let start = self.tokens.get(self.pos)?.start;
440 for (count, text, end) in self.spelling_run(self.pos, None) {
441 if count < 2 {
442 break;
443 }
444 if accept(&text) {
445 self.pos += count;
446 return Some((text, start, end));
447 }
448 }
449 None
450 }
451
452 pub(crate) fn extend_catalog_spelling(
453 &mut self,
454 phrase: &str,
455 phrase_end: Position,
456 accept: impl Fn(&str) -> bool,
457 ) -> Option<(String, Position)> {
458 for (count, tail, end) in self.spelling_run(self.pos, Some(phrase_end)) {
459 let joined = format!("{phrase}{tail}");
460 if accept(&joined) {
461 self.pos += count;
462 return Some((joined, end));
463 }
464 }
465 None
466 }
467
468 pub(crate) fn extend_entry_spelling(
469 &mut self,
470 phrase: &str,
471 phrase_end: Position,
472 kinds: &[Kind],
473 ) -> Option<(String, Position)> {
474 let catalog = self.catalog;
475 let locale = self.locale.clone();
476 let primary = catalog.primary_locale();
477 self.extend_catalog_spelling(phrase, phrase_end, |spelling| {
478 kinds.iter().any(|kind| {
479 catalog.resolve(*kind, &locale, spelling).is_some()
480 || (locale != *primary && catalog.resolve(*kind, primary, spelling).is_some())
481 })
482 })
483 }
484
485 pub(crate) fn consume_phrase(&mut self, expected: &str) -> Result<()> {
486 let (phrase, _, _) = self.phrase()?;
487 if phrase != expected {
488 return Err(self.malformed(&format!("expected '{expected}'"), self.previous()));
489 }
490 Ok(())
491 }
492
493 fn compute_structural_spellings(&self, id: &str) -> Vec<String> {
494 structural_spellings(self.catalog, &self.locale, id)
495 }
496
497 fn match_spelling_tokens(&self, spelling: &str) -> Option<usize> {
498 spelling_token_len(&self.tokens, self.pos, spelling)
499 }
500
501 pub(crate) fn keyword_prefix(&mut self, ids: &[&str]) -> Option<String> {
502 let mut best: Option<(String, usize)> = None;
503 for id in ids {
504 if !self.structural_spellings.contains_key(*id) {
505 let spellings = self.compute_structural_spellings(id);
506 self.structural_spellings
507 .insert((*id).to_string(), spellings);
508 }
509 for spelling in &self.structural_spellings[*id] {
510 if let Some(len) = self.match_spelling_tokens(spelling)
511 && best.as_ref().is_none_or(|(_, length)| len > *length)
512 {
513 best = Some(((*id).to_string(), len));
514 }
515 }
516 }
517 if let Some((id, len)) = best {
518 self.pos += len;
519 Some(id)
520 } else {
521 None
522 }
523 }
524
525 pub(crate) fn consume_keyword(&mut self, id: &str) -> Result<()> {
526 if self.keyword_prefix(&[id]).is_some() {
527 return Ok(());
528 }
529 match self.peek() {
530 Some(token) => Err(self.malformed(&format!("expected '{id}'"), &token)),
531 None => Err(self.malformed(&format!("expected '{id}'"), self.eof())),
532 }
533 }
534
535 pub(crate) fn expect_keyword(&mut self, expected: &str) -> Result<Position> {
536 let start = self.peek().map(|token| token.start);
537 if self.keyword_prefix(&[expected]).is_some() {
538 return Ok(start.unwrap_or_else(|| self.previous().start));
539 }
540 match self.next() {
541 Some(token) => Err(self.malformed(&format!("expected '{expected}'"), &token)),
542 None => Err(self.malformed(&format!("expected '{expected}'"), self.eof())),
543 }
544 }
545
546 pub(crate) fn expect(&mut self, kind: TokenKind, message: &str) -> Result<Position> {
547 match self.next() {
548 Some(token) if token.kind == kind => Ok(token.end),
549 Some(token) => Err(self.malformed(message, &token)),
550 None => Err(self.malformed(message, self.eof())),
551 }
552 }
553
554 pub(crate) fn expect_semi(&mut self, message: &str) -> Result<Position> {
557 match self.peek() {
558 Some(Token {
559 kind: TokenKind::Semi,
560 ..
561 }) => {
562 let end = self.previous_span().1;
563 self.pos += 1;
564 Ok(end)
565 }
566 Some(token) if token.start.line > self.previous().end.line => {
567 let end = self.previous().end;
568 Err(WorkshopError::malformed(
569 message.to_string(),
570 Some(Span::new(self.file(), end, end)),
571 ))
572 }
573 Some(token) => Err(self.malformed(message, &token)),
574 None => Err(self.malformed(message, self.eof())),
575 }
576 }
577
578 pub(crate) fn expect_string(&mut self, message: &str) -> Result<String> {
579 match self.next() {
580 Some(Token {
581 kind: TokenKind::String(content),
582 ..
583 }) => Ok(content),
584 Some(token) => Err(self.malformed(message, &token)),
585 None => Err(self.malformed(message, self.eof())),
586 }
587 }
588
589 pub(crate) fn malformed(&self, message: &str, token: &Token) -> WorkshopError {
590 WorkshopError::malformed(
591 message.to_string(),
592 Some(Span::new(self.file(), token.start, token.end)),
593 )
594 }
595
596 pub(crate) fn unknown(&self, kind: &'static str, spelling: &str) -> WorkshopError {
597 WorkshopError::unknown(kind, spelling.to_string(), self.locale.clone(), None)
598 }
599
600 pub(crate) fn unknown_at(
601 &self,
602 kind: &'static str,
603 spelling: &str,
604 start: Position,
605 end: Position,
606 ) -> WorkshopError {
607 WorkshopError::unknown(
608 kind,
609 spelling.to_string(),
610 self.locale.clone(),
611 Some(Span::new(self.file(), start, end)),
612 )
613 }
614
615 pub(crate) fn accepted_spellings(
616 &self,
617 kind: Kind,
618 include_primary: bool,
619 ) -> impl Iterator<Item = &str> {
620 let catalog = self.catalog;
621 let primary = catalog.primary_locale();
622 let include_primary = include_primary && self.locale != *primary;
623 catalog.entries_of(kind).flat_map(move |entry| {
624 let local: &[String] = entry.spellings(&self.locale);
625 let fallback: &[String] = if include_primary {
626 entry.spellings(primary)
627 } else {
628 &[]
629 };
630 local.iter().chain(fallback).map(String::as_str)
631 })
632 }
633
634 pub(crate) fn accepted_member_spellings<'b>(
635 &'b self,
636 domain: &'b str,
637 include_primary: bool,
638 ) -> impl Iterator<Item = &'b str> {
639 let catalog = self.catalog;
640 let primary = catalog.primary_locale();
641 let include_primary = include_primary && self.locale != *primary;
642 catalog
643 .enum_domain(domain)
644 .into_iter()
645 .flat_map(move |domain| {
646 domain.members.iter().flat_map(move |member| {
647 let local: &[String] = member.spellings(&self.locale);
648 let fallback: &[String] = if include_primary {
649 member.spellings(primary)
650 } else {
651 &[]
652 };
653 local.iter().chain(fallback).map(String::as_str)
654 })
655 })
656 }
657
658 pub(crate) fn spelling_candidates(
659 &self,
660 kind: Kind,
661 spelling: &str,
662 include_primary: bool,
663 ) -> Vec<String> {
664 crate::core::suggest::nearest(
665 spelling,
666 self.accepted_spellings(kind, include_primary),
667 crate::core::suggest::CANDIDATE_LIMIT,
668 )
669 }
670
671 pub(crate) fn member_candidates(
672 &self,
673 domain: &str,
674 spelling: &str,
675 include_primary: bool,
676 ) -> Vec<String> {
677 crate::core::suggest::nearest(
678 spelling,
679 self.accepted_member_spellings(domain, include_primary),
680 crate::core::suggest::CANDIDATE_LIMIT,
681 )
682 }
683
684 pub(crate) fn peek(&self) -> Option<Token> {
685 self.tokens.get(self.pos).cloned()
686 }
687
688 pub(crate) fn peek_at(&self, offset: usize) -> Option<Token> {
689 self.tokens.get(self.pos + offset).cloned()
690 }
691
692 pub(crate) fn peek_kind_at(&self, offset: usize) -> Option<&TokenKind> {
693 self.tokens.get(self.pos + offset).map(|token| &token.kind)
694 }
695
696 pub(crate) fn next(&mut self) -> Option<Token> {
697 let token = self.tokens.get(self.pos).cloned();
698 if token.is_some() {
699 self.pos += 1;
700 }
701 token
702 }
703
704 pub(crate) fn previous(&self) -> &Token {
705 self.tokens
706 .get(self.pos.saturating_sub(1))
707 .unwrap_or_else(|| self.tokens.last().unwrap())
708 }
709
710 pub(crate) fn previous_span(&self) -> (Position, Position) {
711 let token = self.previous();
712 (token.start, token.end)
713 }
714
715 pub(crate) fn span_here(&self) -> (Position, Position) {
716 let token = self
717 .peek()
718 .unwrap_or_else(|| self.tokens.last().unwrap().clone());
719 (token.start, token.end)
720 }
721
722 pub(crate) fn eof(&self) -> &Token {
723 self.tokens.last().unwrap()
724 }
725
726 pub(crate) fn file(&self) -> crate::core::ids::Id<SourceFile> {
727 crate::core::ids::Id::from_index(0)
728 }
729}
730
731pub(super) fn structural_keyword_map() -> &'static HashMap<String, String> {
732 static KEYWORDS: OnceLock<HashMap<String, String>> = OnceLock::new();
733 KEYWORDS.get_or_init(|| {
734 serde_json::from_str(include_str!("structural_keywords.json"))
735 .expect("structural keyword data is valid JSON")
736 })
737}
738
739pub(crate) fn canonical_keyword(keyword: &str) -> &str {
740 structural_keyword_map()
741 .get(keyword)
742 .map(String::as_str)
743 .unwrap_or(keyword)
744}
745
746pub(crate) fn join_as_written(parts: &[(String, Position, Position)]) -> String {
747 let mut text = String::new();
748 let mut prev_end: Option<Position> = None;
749 for (fragment, start, end) in parts {
750 if prev_end.is_some_and(|prev| prev != *start) {
751 text.push(' ');
752 }
753 text.push_str(fragment);
754 prev_end = Some(*end);
755 }
756 text
757}
758
759pub(crate) fn raw_token_text(kind: &TokenKind) -> String {
760 match kind {
761 TokenKind::Word(value) => value.clone(),
762 TokenKind::Number { text, .. } => text.clone(),
763 TokenKind::String(value) => format!("\"{}\"", crate::output::emitter::escape_string(value)),
764 TokenKind::Op(value) => match value.as_str() {
765 "not" => "!".to_string(),
766 "and" => "&&".to_string(),
767 "or" => "||".to_string(),
768 _ => value.clone(),
769 },
770 TokenKind::LParen => "(".to_string(),
771 TokenKind::RParen => ")".to_string(),
772 TokenKind::Comma => ",".to_string(),
773 TokenKind::Semi => ";".to_string(),
774 TokenKind::LBrace => "{".to_string(),
775 TokenKind::RBrace => "}".to_string(),
776 TokenKind::Colon => ":".to_string(),
777 TokenKind::Dot => ".".to_string(),
778 TokenKind::LBracket => "[".to_string(),
779 TokenKind::RBracket => "]".to_string(),
780 TokenKind::Eof => String::new(),
781 }
782}