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)> {
175 let mut parts: Vec<(String, Position, Position)> = Vec::new();
176 let (start, mut end) = match self.peek() {
177 Some(Token {
178 kind: TokenKind::Word(word),
179 start,
180 end,
181 }) => {
182 parts.push((word.clone(), start, end));
183 (start, end)
184 }
185 Some(Token {
186 kind: TokenKind::Number { text, .. },
187 start,
188 end,
189 }) => {
190 parts.push((text.clone(), start, end));
191 (start, end)
192 }
193 Some(token) => return Err(self.malformed("expected an identifier", &token)),
194 None => return Err(self.malformed("expected an identifier", self.eof())),
195 };
196 self.pos += 1;
197 while let Some(token) = self.peek() {
198 match token {
199 Token {
200 kind: TokenKind::Word(word),
201 start: word_start,
202 end: word_end,
203 } => {
204 if matches!(
205 self.peek_kind_at(1),
206 Some(TokenKind::Op(equal)) if equal == "="
207 ) {
208 break;
209 }
210 parts.push((word.clone(), word_start, word_end));
211 end = word_end;
212 self.pos += 1;
213 }
214 Token {
218 kind: TokenKind::Number { text, .. },
219 start: number_start,
220 end: number_end,
221 } => {
222 parts.push((text.clone(), number_start, number_end));
223 end = number_end;
224 self.pos += 1;
225 }
226 _ => break,
227 }
228 }
229 Ok((join_as_written(&parts), start, end))
230 }
231
232 pub(crate) fn phrase_on_line(&mut self) -> Result<(String, Position, Position)> {
233 self.phrase_on_line_impl(false)
234 }
235
236 pub(crate) fn settings_list_name(&mut self) -> Result<(String, Position, Position)> {
240 self.phrase_on_line_impl(true)
241 }
242
243 fn phrase_on_line_impl(&mut self, list_name: bool) -> Result<(String, Position, Position)> {
244 let mut parts: Vec<(String, Position, Position)> = Vec::new();
245 let (start, mut end, line) = match self.peek() {
246 Some(Token {
247 kind: TokenKind::Word(word),
248 start,
249 end,
250 }) => {
251 parts.push((word.clone(), start, end));
252 (start, end, start.line)
253 }
254 Some(Token {
255 kind: TokenKind::Number { text, .. },
256 start,
257 end,
258 }) => {
259 parts.push((text.clone(), start, end));
260 (start, end, start.line)
261 }
262 Some(token) => return Err(self.malformed("expected an identifier", &token)),
263 None => return Err(self.malformed("expected an identifier", self.eof())),
264 };
265 self.pos += 1;
266 while let Some(token) = self.peek() {
267 let (text, word_start, word_end) = match token {
268 Token {
269 kind: TokenKind::Word(word),
270 start,
271 end,
272 } => (word.clone(), start, end),
273 Token {
274 kind: TokenKind::Number { text, .. },
275 start,
276 end,
277 } => (text.clone(), start, end),
278 Token {
279 kind: TokenKind::Dot,
280 start,
281 end,
282 } => (".".to_string(), start, end),
283 Token {
284 kind: TokenKind::Op(op),
285 start,
286 end,
287 } if matches!(op.as_str(), "-" | "%") => (op.clone(), start, end),
288 Token {
289 kind: TokenKind::Colon,
290 start,
291 end,
292 } if list_name => (":".to_string(), start, end),
293 _ => break,
294 };
295 if word_start.line != line {
296 break;
297 }
298 parts.push((text, word_start, word_end));
299 end = word_end;
300 self.pos += 1;
301 }
302 Ok((join_as_written(&parts), start, end))
307 }
308
309 pub(crate) fn phrase_on_line_with_colon(&mut self) -> Result<(String, Position, Position)> {
310 let (mut phrase, start, mut end) = self.phrase_on_line()?;
311 if matches!(
312 self.peek(),
313 Some(Token {
314 kind: TokenKind::Colon,
315 ..
316 })
317 ) {
318 let colon_pos = self.pos;
319 self.next();
320 let (rest, _, rest_end) = self.phrase_on_line()?;
321 if matches!(
322 self.peek(),
323 Some(Token {
324 kind: TokenKind::LBrace,
325 ..
326 })
327 ) {
328 phrase.push(':');
329 phrase.push(' ');
330 phrase.push_str(&rest);
331 end = rest_end;
332 } else {
333 self.pos = colon_pos;
334 }
335 }
336 Ok((phrase, start, end))
337 }
338
339 pub(crate) fn enum_member_phrase(&mut self) -> Result<(String, Position, Position)> {
344 let first = self
345 .peek()
346 .ok_or_else(|| self.malformed("expected an enum member", self.eof()))?;
347 let start = first.start;
348 let line = first.start.line;
349 let mut end = first.end;
350 let mut parts: Vec<(String, Position, Position)> = Vec::new();
351 while let Some(token) = self.peek() {
352 if token.start.line != line
357 || matches!(
358 token.kind,
359 TokenKind::RParen | TokenKind::Comma | TokenKind::LParen
360 )
361 {
362 break;
363 }
364 self.pos += 1;
365 end = token.end;
366 parts.push((raw_token_text(&token.kind), token.start, token.end));
367 }
368 if parts.is_empty() {
369 return Err(self.malformed("expected an enum member", &first));
370 }
371 Ok((join_as_written(&parts), start, end))
372 }
373
374 pub(crate) fn line_text(&mut self) -> Result<(String, Option<Span>)> {
380 let mut parts: Vec<(String, Position, Position)> = Vec::new();
381 let mut start = None;
382 let mut end = None;
383 loop {
384 match self.peek() {
385 Some(Token {
386 kind: TokenKind::Semi,
387 ..
388 }) => {
389 self.pos += 1;
390 break;
391 }
392 Some(token) => {
393 start.get_or_insert(token.start);
394 end = Some(token.end);
395 match &token.kind {
396 TokenKind::Word(_)
397 | TokenKind::Number { .. }
398 | TokenKind::Dot
399 | TokenKind::Colon
400 | TokenKind::Op(_) => {}
401 _ => return Err(self.malformed("expected a text line", &token)),
402 }
403 parts.push((raw_token_text(&token.kind), token.start, token.end));
404 self.pos += 1;
405 }
406 None => {
407 return Err(self.malformed("unexpected end of input in line", self.eof()));
408 }
409 }
410 }
411 Ok((
412 join_as_written(&parts),
413 start
414 .zip(end)
415 .map(|(start, end)| Span::new(self.file(), start, end)),
416 ))
417 }
418
419 pub(crate) fn spelling_run(
428 &self,
429 pos: usize,
430 attaches_to: Option<Position>,
431 ) -> Vec<(usize, String, Position)> {
432 let mut ends = Vec::new();
433 let mut text = String::new();
434 let mut index = pos;
435 let mut prev_end = attaches_to;
436 while let Some(token) = self.tokens.get(index) {
437 let fragment = match &token.kind {
438 TokenKind::Word(word) => word.as_str(),
439 TokenKind::Number { text, .. } => text.as_str(),
440 TokenKind::Dot => ".",
441 TokenKind::Colon => ":",
442 TokenKind::Op(op) if op == "not" => "!",
443 TokenKind::Op(op) if op == "-" => "-",
447 _ => break,
448 };
449 let needs_adjacent = matches!(token.kind, TokenKind::Dot | TokenKind::Colon)
453 || matches!(&token.kind, TokenKind::Op(op) if op == "not");
454 if needs_adjacent && prev_end.is_none_or(|end| token.start != end) {
455 break;
456 }
457 if prev_end.is_some_and(|end| token.start != end) {
458 text.push(' ');
459 }
460 text.push_str(fragment);
461 prev_end = Some(token.end);
462 index += 1;
463 ends.push((text.len(), token.end));
464 }
465 ends.into_iter()
466 .enumerate()
467 .rev()
468 .map(|(i, (len, end))| (i + 1, text[..len].to_string(), end))
469 .collect()
470 }
471
472 pub(crate) fn catalog_spelling(
479 &mut self,
480 accept: impl Fn(&str) -> bool,
481 ) -> Option<(String, Position, Position)> {
482 let start = self.tokens.get(self.pos)?.start;
483 for (count, text, end) in self.spelling_run(self.pos, None) {
484 if count < 2 {
485 break;
486 }
487 if accept(&text) {
488 self.pos += count;
489 return Some((text, start, end));
490 }
491 }
492 None
493 }
494
495 pub(crate) fn extend_catalog_spelling(
501 &mut self,
502 phrase: &str,
503 phrase_end: Position,
504 accept: impl Fn(&str) -> bool,
505 ) -> Option<(String, Position)> {
506 for (count, tail, end) in self.spelling_run(self.pos, Some(phrase_end)) {
507 let joined = format!("{phrase}{tail}");
508 if accept(&joined) {
509 self.pos += count;
510 return Some((joined, end));
511 }
512 }
513 None
514 }
515
516 pub(crate) fn extend_entry_spelling(
517 &mut self,
518 phrase: &str,
519 phrase_end: Position,
520 kinds: &[Kind],
521 ) -> Option<(String, Position)> {
522 let catalog = self.catalog;
523 let locale = self.locale.clone();
524 let primary = catalog.primary_locale();
525 self.extend_catalog_spelling(phrase, phrase_end, |spelling| {
526 kinds.iter().any(|kind| {
527 catalog.resolve(*kind, &locale, spelling).is_some()
528 || (locale != *primary && catalog.resolve(*kind, primary, spelling).is_some())
529 })
530 })
531 }
532
533 pub(crate) fn consume_phrase(&mut self, expected: &str) -> Result<()> {
534 let (phrase, _, _) = self.phrase()?;
535 if phrase != expected {
536 return Err(self.malformed(&format!("expected '{expected}'"), self.previous()));
537 }
538 Ok(())
539 }
540
541 fn compute_structural_spellings(&self, id: &str) -> Vec<String> {
542 structural_spellings(self.catalog, &self.locale, id)
543 }
544
545 fn match_spelling_tokens(&self, spelling: &str) -> Option<usize> {
546 spelling_token_len(&self.tokens, self.pos, spelling)
547 }
548
549 pub(crate) fn keyword_prefix(&mut self, ids: &[&str]) -> Option<String> {
554 let mut best: Option<(String, usize)> = None;
555 for id in ids {
556 if !self.structural_spellings.contains_key(*id) {
557 let spellings = self.compute_structural_spellings(id);
558 self.structural_spellings
559 .insert((*id).to_string(), spellings);
560 }
561 for spelling in &self.structural_spellings[*id] {
562 if let Some(len) = self.match_spelling_tokens(spelling)
563 && best.as_ref().is_none_or(|(_, length)| len > *length)
564 {
565 best = Some(((*id).to_string(), len));
566 }
567 }
568 }
569 if let Some((id, len)) = best {
570 self.pos += len;
571 Some(id)
572 } else {
573 None
574 }
575 }
576
577 pub(crate) fn consume_keyword(&mut self, id: &str) -> Result<()> {
578 if self.keyword_prefix(&[id]).is_some() {
579 return Ok(());
580 }
581 match self.peek() {
582 Some(token) => Err(self.malformed(&format!("expected '{id}'"), &token)),
583 None => Err(self.malformed(&format!("expected '{id}'"), self.eof())),
584 }
585 }
586
587 pub(crate) fn expect_keyword(&mut self, expected: &str) -> Result<Position> {
588 let start = self.peek().map(|token| token.start);
589 if self.keyword_prefix(&[expected]).is_some() {
590 return Ok(start.unwrap_or_else(|| self.previous().start));
591 }
592 match self.next() {
593 Some(token) => Err(self.malformed(&format!("expected '{expected}'"), &token)),
594 None => Err(self.malformed(&format!("expected '{expected}'"), self.eof())),
595 }
596 }
597
598 pub(crate) fn expect(&mut self, kind: TokenKind, message: &str) -> Result<()> {
599 match self.next() {
600 Some(token) if token.kind == kind => Ok(()),
601 Some(token) => Err(self.malformed(message, &token)),
602 None => Err(self.malformed(message, self.eof())),
603 }
604 }
605
606 pub(crate) fn expect_semi(&mut self, message: &str) -> Result<()> {
607 match self.peek() {
608 Some(Token {
609 kind: TokenKind::Semi,
610 ..
611 }) => {
612 self.pos += 1;
613 Ok(())
614 }
615 Some(token) if token.start.line > self.previous().end.line => {
616 let end = self.previous().end;
617 Err(WorkshopError::malformed(
618 message.to_string(),
619 Some(Span::new(self.file(), end, end)),
620 ))
621 }
622 Some(token) => Err(self.malformed(message, &token)),
623 None => Err(self.malformed(message, self.eof())),
624 }
625 }
626
627 pub(crate) fn expect_string(&mut self, message: &str) -> Result<String> {
628 match self.next() {
629 Some(Token {
630 kind: TokenKind::String(content),
631 ..
632 }) => Ok(content),
633 Some(token) => Err(self.malformed(message, &token)),
634 None => Err(self.malformed(message, self.eof())),
635 }
636 }
637
638 pub(crate) fn malformed(&self, message: &str, token: &Token) -> WorkshopError {
639 WorkshopError::malformed(
640 message.to_string(),
641 Some(Span::new(self.file(), token.start, token.end)),
642 )
643 }
644
645 pub(crate) fn unknown(&self, kind: &'static str, spelling: &str) -> WorkshopError {
646 WorkshopError::unknown(kind, spelling.to_string(), self.locale.clone(), None)
647 }
648
649 pub(crate) fn unknown_at(
650 &self,
651 kind: &'static str,
652 spelling: &str,
653 start: Position,
654 end: Position,
655 ) -> WorkshopError {
656 WorkshopError::unknown(
657 kind,
658 spelling.to_string(),
659 self.locale.clone(),
660 Some(Span::new(self.file(), start, end)),
661 )
662 }
663
664 pub(crate) fn accepted_spellings(
665 &self,
666 kind: Kind,
667 include_primary: bool,
668 ) -> impl Iterator<Item = &str> {
669 let catalog = self.catalog;
670 let primary = catalog.primary_locale();
671 let include_primary = include_primary && self.locale != *primary;
672 catalog.entries_of(kind).flat_map(move |entry| {
673 let local: &[String] = entry.spellings(&self.locale);
674 let fallback: &[String] = if include_primary {
675 entry.spellings(primary)
676 } else {
677 &[]
678 };
679 local.iter().chain(fallback).map(String::as_str)
680 })
681 }
682
683 pub(crate) fn accepted_member_spellings<'b>(
684 &'b self,
685 domain: &'b str,
686 include_primary: bool,
687 ) -> impl Iterator<Item = &'b str> {
688 let catalog = self.catalog;
689 let primary = catalog.primary_locale();
690 let include_primary = include_primary && self.locale != *primary;
691 catalog
692 .enum_domain(domain)
693 .into_iter()
694 .flat_map(move |domain| {
695 domain.members.iter().flat_map(move |member| {
696 let local: &[String] = member.spellings(&self.locale);
697 let fallback: &[String] = if include_primary {
698 member.spellings(primary)
699 } else {
700 &[]
701 };
702 local.iter().chain(fallback).map(String::as_str)
703 })
704 })
705 }
706
707 pub(crate) fn spelling_candidates(
708 &self,
709 kind: Kind,
710 spelling: &str,
711 include_primary: bool,
712 ) -> Vec<String> {
713 crate::core::suggest::nearest(
714 spelling,
715 self.accepted_spellings(kind, include_primary),
716 crate::core::suggest::CANDIDATE_LIMIT,
717 )
718 }
719
720 pub(crate) fn member_candidates(
721 &self,
722 domain: &str,
723 spelling: &str,
724 include_primary: bool,
725 ) -> Vec<String> {
726 crate::core::suggest::nearest(
727 spelling,
728 self.accepted_member_spellings(domain, include_primary),
729 crate::core::suggest::CANDIDATE_LIMIT,
730 )
731 }
732
733 pub(crate) fn peek(&self) -> Option<Token> {
734 self.tokens.get(self.pos).cloned()
735 }
736
737 pub(crate) fn peek_at(&self, offset: usize) -> Option<Token> {
738 self.tokens.get(self.pos + offset).cloned()
739 }
740
741 pub(crate) fn peek_kind_at(&self, offset: usize) -> Option<&TokenKind> {
742 self.tokens.get(self.pos + offset).map(|token| &token.kind)
743 }
744
745 pub(crate) fn next(&mut self) -> Option<Token> {
746 let token = self.tokens.get(self.pos).cloned();
747 if token.is_some() {
748 self.pos += 1;
749 }
750 token
751 }
752
753 pub(crate) fn previous(&self) -> &Token {
754 self.tokens
755 .get(self.pos.saturating_sub(1))
756 .unwrap_or_else(|| self.tokens.last().unwrap())
757 }
758
759 pub(crate) fn previous_span(&self) -> (Position, Position) {
760 let token = self.previous();
761 (token.start, token.end)
762 }
763
764 pub(crate) fn span_here(&self) -> (Position, Position) {
765 let token = self
766 .peek()
767 .unwrap_or_else(|| self.tokens.last().unwrap().clone());
768 (token.start, token.end)
769 }
770
771 pub(crate) fn eof(&self) -> &Token {
772 self.tokens.last().unwrap()
773 }
774
775 pub(crate) fn file(&self) -> crate::core::ids::Id<SourceFile> {
776 crate::core::ids::Id::from_index(0)
777 }
778}
779
780pub(super) fn structural_keyword_map() -> &'static HashMap<String, String> {
781 static KEYWORDS: OnceLock<HashMap<String, String>> = OnceLock::new();
782 KEYWORDS.get_or_init(|| {
783 serde_json::from_str(include_str!("structural_keywords.json"))
784 .expect("structural keyword data is valid JSON")
785 })
786}
787
788pub(crate) fn canonical_keyword(keyword: &str) -> &str {
789 structural_keyword_map()
790 .get(keyword)
791 .map(String::as_str)
792 .unwrap_or(keyword)
793}
794
795pub(crate) fn join_as_written(parts: &[(String, Position, Position)]) -> String {
799 let mut text = String::new();
800 let mut prev_end: Option<Position> = None;
801 for (fragment, start, end) in parts {
802 if prev_end.is_some_and(|prev| prev != *start) {
803 text.push(' ');
804 }
805 text.push_str(fragment);
806 prev_end = Some(*end);
807 }
808 text
809}
810
811pub(crate) fn raw_token_text(kind: &TokenKind) -> String {
812 match kind {
813 TokenKind::Word(value) => value.clone(),
814 TokenKind::Number { text, .. } => text.clone(),
815 TokenKind::String(value) => format!("\"{}\"", crate::output::emitter::escape_string(value)),
816 TokenKind::Op(value) => match value.as_str() {
817 "not" => "!".to_string(),
818 "and" => "&&".to_string(),
819 "or" => "||".to_string(),
820 _ => value.clone(),
821 },
822 TokenKind::LParen => "(".to_string(),
823 TokenKind::RParen => ")".to_string(),
824 TokenKind::Comma => ",".to_string(),
825 TokenKind::Semi => ";".to_string(),
826 TokenKind::LBrace => "{".to_string(),
827 TokenKind::RBrace => "}".to_string(),
828 TokenKind::Colon => ":".to_string(),
829 TokenKind::Dot => ".".to_string(),
830 TokenKind::LBracket => "[".to_string(),
831 TokenKind::RBracket => "]".to_string(),
832 TokenKind::Eof => String::new(),
833 }
834}