1pub(crate) use std::collections::HashMap;
10use std::sync::OnceLock;
11
12pub(crate) use crate::core::signatures::ExpectedDomain;
13pub(crate) use crate::core::source::{Position, SourceFile, Span};
14use crate::program::Program;
15pub(crate) use crate::settings::table::{self, KeyKind, PathPart};
16pub(crate) use crate::settings::{Settings, SettingsListElement, SettingsNode};
17pub(crate) use crate::wir::{
18 self, Action, Event, EventTarget, EventTeam, ModifyOp, PlayerEventKind, Value, ValueNode,
19};
20
21pub(crate) use super::lexer::{Token, TokenKind, tokenize};
22pub(crate) use crate::catalog::{Catalog, Kind, Locale, ParamCoercions};
23pub(crate) use crate::core::error::{Result, WorkshopError};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub(crate) enum Stop {
28 SectionClosed,
30 End,
32 ElseIf,
34 Else,
36}
37
38pub(crate) enum AssignmentOperator {
39 Set,
40 Modify(ModifyOp),
41}
42
43pub fn parse(input: &str, catalog: &Catalog, locale: &Locale) -> Result<Program> {
49 parse_with_context(input, catalog, locale, catalog)
50}
51
52#[doc(hidden)]
55pub fn parse_wir(input: &str, catalog: &Catalog, locale: &Locale) -> Result<wir::Program> {
56 parse_wir_with_context(input, catalog, locale, catalog)
57}
58
59pub fn parse_with_context(
68 input: &str,
69 catalog: &Catalog,
70 locale: &Locale,
71 context: &dyn ExpectedDomain,
72) -> Result<Program> {
73 parse_wir_with_context(input, catalog, locale, context).and_then(Program::from_wir)
74}
75
76#[doc(hidden)]
77pub fn parse_wir_with_context(
78 input: &str,
79 catalog: &Catalog,
80 locale: &Locale,
81 context: &dyn ExpectedDomain,
82) -> Result<wir::Program> {
83 let tokens = tokenize(input).map_err(|error| WorkshopError::Malformed {
84 message: error.message,
85 span: Some(synthetic_span(error.position)),
86 })?;
87 ParseContext {
88 tokens,
89 pos: 0,
90 source: input,
91 catalog,
92 locale: locale.clone(),
93 context,
94 expected_domain: None,
95 call_stack: Vec::new(),
96 target: wir::Program::default(),
97 globals: HashMap::new(),
98 players: HashMap::new(),
99 subroutines: HashMap::new(),
100 next_global_index: 0,
101 next_player_index: 0,
102 }
103 .program()
104}
105
106pub(crate) fn synthetic_span(position: Position) -> Span {
108 Span::new(crate::core::ids::Id::from_index(0), position, position)
109}
110
111pub(crate) struct ParseContext<'a> {
112 pub(crate) tokens: Vec<Token>,
113 pub(crate) pos: usize,
114 pub(crate) source: &'a str,
115 pub(crate) catalog: &'a Catalog,
116 pub(crate) locale: Locale,
117 pub(crate) context: &'a dyn ExpectedDomain,
120 pub(crate) expected_domain: Option<&'a str>,
123 pub(crate) call_stack: Vec<String>,
124 pub(crate) target: wir::Program,
125 pub(crate) globals: HashMap<String, wir::GlobalVarId>,
126 pub(crate) players: HashMap<String, wir::PlayerVarId>,
127 pub(crate) subroutines: HashMap<String, wir::SubroutineId>,
128 pub(crate) next_global_index: u32,
129 pub(crate) next_player_index: u32,
130}
131
132impl<'a> ParseContext<'a> {
133 pub(crate) fn resolve_entry(
134 &self,
135 kind: Kind,
136 spelling: &str,
137 ) -> Option<&'a crate::catalog::CatalogEntry> {
138 self.catalog
139 .resolve(kind, &self.locale, spelling)
140 .or_else(|| {
141 if self.locale != *self.catalog.primary_locale() {
142 self.catalog
143 .resolve(kind, self.catalog.primary_locale(), spelling)
144 } else {
145 None
146 }
147 })
148 }
149
150 pub(crate) fn canonical_keyword(&self, spelling: &str) -> String {
151 self.catalog
152 .resolve(Kind::Structural, &self.locale, spelling)
153 .or_else(|| {
154 (self.locale != *self.catalog.primary_locale()).then(|| {
155 self.catalog
156 .resolve(Kind::Structural, self.catalog.primary_locale(), spelling)
157 })?
158 })
159 .map(|entry| entry.id.clone())
160 .unwrap_or_else(|| canonical_keyword(spelling).to_string())
161 }
162
163 pub(crate) fn line_has_assignment(&self) -> bool {
164 let tokens: Vec<_> = self.tokens[self.pos..]
165 .iter()
166 .take_while(|token| !matches!(token.kind, TokenKind::Semi | TokenKind::RBrace))
167 .collect();
168 tokens.iter().any(|token| {
169 matches!(&token.kind, TokenKind::Op(op) if matches!(op.as_str(), "=" | "+=" | "-=" | "*=" | "/=" | "%="))
170 }) || tokens.windows(2).any(|window| {
171 matches!(&window[0].kind, TokenKind::Word(_))
172 && matches!(&window[1].kind, TokenKind::Op(op) if op == "=")
173 })
174 }
175
176 pub(crate) fn phrase(&mut self) -> Result<(String, Position, Position)> {
179 let mut words = Vec::new();
180 let (start, mut end) = match self.peek() {
181 Some(Token {
182 kind: TokenKind::Word(word),
183 start,
184 end,
185 }) => {
186 words.push(word.clone());
187 (start, end)
188 }
189 Some(Token {
190 kind: TokenKind::Number { text, .. },
191 start,
192 end,
193 }) => {
194 words.push(text.clone());
195 (start, end)
196 }
197 Some(token) => return Err(self.malformed("expected an identifier", &token)),
198 None => return Err(self.malformed("expected an identifier", self.eof())),
199 };
200 self.pos += 1;
201 while let Some(token) = self.peek() {
202 match token {
203 Token {
204 kind: TokenKind::Word(word),
205 end: word_end,
206 ..
207 } => {
208 if matches!(
209 self.peek_at(1).map(|token| token.kind),
210 Some(TokenKind::Op(equal)) if equal == "="
211 ) {
212 break;
213 }
214 words.push(word.clone());
215 end = word_end;
216 self.pos += 1;
217 }
218 Token {
222 kind: TokenKind::Number { text, .. },
223 end: number_end,
224 ..
225 } => {
226 words.push(text.clone());
227 end = number_end;
228 self.pos += 1;
229 }
230 _ => break,
231 }
232 }
233 Ok((words.join(" "), start, end))
234 }
235
236 pub(crate) fn phrase_on_line(&mut self) -> Result<(String, Position, Position)> {
239 let mut words = Vec::new();
240 let (start, mut end, line) = match self.peek() {
241 Some(Token {
242 kind: TokenKind::Word(word),
243 start,
244 end,
245 }) => {
246 words.push(word.clone());
247 (start, end, start.line)
248 }
249 Some(Token {
250 kind: TokenKind::Number { text, .. },
251 start,
252 end,
253 }) => {
254 words.push(text.clone());
255 (start, end, start.line)
256 }
257 Some(token) => return Err(self.malformed("expected an identifier", &token)),
258 None => return Err(self.malformed("expected an identifier", self.eof())),
259 };
260 self.pos += 1;
261 while let Some(token) = self.peek() {
262 let (word, word_start, word_end) = match token {
263 Token {
264 kind: TokenKind::Word(word),
265 start,
266 end,
267 } => (word, start, end),
268 Token {
269 kind: TokenKind::Number { text, .. },
270 start,
271 end,
272 } => (text, start, end),
273 Token {
274 kind: TokenKind::Dot,
275 start,
276 end,
277 } => (".".to_string(), start, end),
278 Token {
279 kind: TokenKind::Op(op),
280 start,
281 end,
282 } if matches!(op.as_str(), "-" | "%") => (op.clone(), start, end),
283 _ => break,
284 };
285 if word_start.line != line {
286 break;
287 }
288 words.push(word);
289 end = word_end;
290 self.pos += 1;
291 }
292 Ok((
293 words
294 .join(" ")
295 .replace(" .", ".")
296 .replace(". ", ".")
297 .replace(" : ", ":")
298 .replace(" %", "%"),
299 start,
300 end,
301 ))
302 }
303
304 pub(crate) fn phrase_on_line_with_colon(&mut self) -> Result<(String, Position, Position)> {
305 let (mut phrase, start, mut end) = self.phrase_on_line()?;
306 if matches!(
307 self.peek(),
308 Some(Token {
309 kind: TokenKind::Colon,
310 ..
311 })
312 ) {
313 let colon_pos = self.pos;
314 self.next();
315 let (rest, _, rest_end) = self.phrase_on_line()?;
316 if matches!(
317 self.peek(),
318 Some(Token {
319 kind: TokenKind::LBrace,
320 ..
321 })
322 ) {
323 phrase.push(':');
324 phrase.push(' ');
325 phrase.push_str(&rest);
326 end = rest_end;
327 } else {
328 self.pos = colon_pos;
329 }
330 }
331 Ok((phrase, start, end))
332 }
333
334 pub(crate) fn enum_member_phrase(&mut self) -> Result<(String, Position, Position)> {
335 let first = self
336 .peek()
337 .ok_or_else(|| self.malformed("expected an enum member", self.eof()))?;
338 let start = first.start;
339 let line = first.start.line;
340 let mut end = first.end;
341 let mut parts = Vec::new();
342 while let Some(token) = self.peek() {
343 if token.start.line != line
344 || matches!(token.kind, TokenKind::RParen | TokenKind::Comma)
345 {
346 break;
347 }
348 self.pos += 1;
349 end = token.end;
350 parts.push(raw_token_text(&token.kind));
351 }
352 if parts.is_empty() {
353 return Err(self.malformed("expected an enum member", &first));
354 }
355 Ok((
356 parts
357 .join(" ")
358 .replace(" : ", ":")
359 .replace(" .", ".")
360 .replace(". ", "."),
361 start,
362 end,
363 ))
364 }
365
366 pub(crate) fn line_text(&mut self) -> Result<String> {
369 let mut parts = Vec::new();
370 loop {
371 match self.peek() {
372 Some(Token {
373 kind: TokenKind::Semi,
374 ..
375 }) => {
376 self.pos += 1;
377 break;
378 }
379 Some(Token {
380 kind: TokenKind::Word(word),
381 ..
382 }) => {
383 parts.push(word.clone());
384 self.pos += 1;
385 }
386 Some(Token {
387 kind: TokenKind::Op(op),
388 ..
389 }) if op == "-" => {
390 parts.push("-".to_string());
391 self.pos += 1;
392 }
393 Some(Token {
394 kind: TokenKind::Number { value, .. },
395 ..
396 }) => {
397 parts.push(value.to_string());
398 self.pos += 1;
399 }
400 Some(Token {
401 kind: TokenKind::Dot,
402 ..
403 }) => {
404 parts.push(".".to_string());
405 self.pos += 1;
406 }
407 Some(Token {
408 kind: TokenKind::Colon,
409 ..
410 }) => {
411 parts.push(":".to_string());
412 self.pos += 1;
413 }
414 Some(token) => return Err(self.malformed("expected a text line", &token)),
415 None => return Err(self.malformed("unexpected end of input in line", self.eof())),
416 }
417 }
418 Ok(parts
419 .join(" ")
420 .replace(" .", ".")
421 .replace(". ", ".")
422 .replace(" : ", ":"))
423 }
424
425 pub(crate) fn consume_phrase(&mut self, expected: &str) -> Result<()> {
427 let (phrase, _, _) = self.phrase()?;
428 if phrase != expected {
429 return Err(self.malformed(&format!("expected '{expected}'"), self.previous()));
430 }
431 Ok(())
432 }
433
434 pub(crate) fn expect_keyword(&mut self, expected: &str) -> Result<Position> {
435 match self.next() {
436 Some(Token {
437 kind: TokenKind::Word(word),
438 start,
439 ..
440 }) if self.canonical_keyword(&word) == expected => Ok(start),
441 Some(token) => Err(self.malformed(&format!("expected '{expected}'"), &token)),
442 None => Err(self.malformed(&format!("expected '{expected}'"), self.eof())),
443 }
444 }
445
446 pub(crate) fn expect(&mut self, kind: TokenKind, message: &str) -> Result<()> {
447 match self.next() {
448 Some(token) if token.kind == kind => Ok(()),
449 Some(token) => Err(self.malformed(message, &token)),
450 None => Err(self.malformed(message, self.eof())),
451 }
452 }
453
454 pub(crate) fn expect_string(&mut self, message: &str) -> Result<String> {
455 match self.next() {
456 Some(Token {
457 kind: TokenKind::String(content),
458 ..
459 }) => Ok(content),
460 Some(token) => Err(self.malformed(message, &token)),
461 None => Err(self.malformed(message, self.eof())),
462 }
463 }
464
465 pub(crate) fn malformed(&self, message: &str, token: &Token) -> WorkshopError {
466 WorkshopError::Malformed {
467 message: message.to_string(),
468 span: Some(Span::new(self.file(), token.start, token.end)),
469 }
470 }
471
472 pub(crate) fn unknown(&self, kind: &'static str, spelling: &str) -> WorkshopError {
473 WorkshopError::Unknown {
474 kind,
475 spelling: spelling.to_string(),
476 locale: self.locale.clone(),
477 span: None,
478 }
479 }
480
481 pub(crate) fn peek(&self) -> Option<Token> {
482 self.tokens.get(self.pos).cloned()
483 }
484
485 pub(crate) fn peek_at(&self, offset: usize) -> Option<Token> {
486 self.tokens.get(self.pos + offset).cloned()
487 }
488
489 pub(crate) fn next(&mut self) -> Option<Token> {
490 let token = self.tokens.get(self.pos).cloned();
491 if token.is_some() {
492 self.pos += 1;
493 }
494 token
495 }
496
497 pub(crate) fn previous(&self) -> &Token {
498 self.tokens
499 .get(self.pos.saturating_sub(1))
500 .unwrap_or_else(|| self.tokens.last().unwrap())
501 }
502
503 pub(crate) fn previous_span(&self) -> (Position, Position) {
504 let token = self.previous();
505 (token.start, token.end)
506 }
507
508 pub(crate) fn span_here(&self) -> (Position, Position) {
509 let token = self
510 .peek()
511 .unwrap_or_else(|| self.tokens.last().unwrap().clone());
512 (token.start, token.end)
513 }
514
515 pub(crate) fn eof(&self) -> &Token {
516 self.tokens.last().unwrap()
517 }
518
519 pub(crate) fn file(&self) -> crate::core::ids::Id<SourceFile> {
520 crate::core::ids::Id::from_index(0)
521 }
522}
523
524pub(crate) fn is_comparison(op: &str) -> bool {
525 matches!(op, "==" | "!=" | "<" | "<=" | ">" | ">=")
526}
527
528pub(crate) fn canonical_keyword(keyword: &str) -> &str {
529 static KEYWORDS: OnceLock<HashMap<String, String>> = OnceLock::new();
530 KEYWORDS
531 .get_or_init(|| {
532 serde_json::from_str(include_str!("structural_keywords.json"))
533 .expect("structural keyword data is valid JSON")
534 })
535 .get(keyword)
536 .map(String::as_str)
537 .unwrap_or(keyword)
538}
539
540pub(crate) fn raw_token_text(kind: &TokenKind) -> String {
541 match kind {
542 TokenKind::Word(value) => value.clone(),
543 TokenKind::Number { text, .. } => text.clone(),
544 TokenKind::String(value) => format!("\"{}\"", value.replace('"', "\\\"")),
545 TokenKind::Op(value) => value.clone(),
546 TokenKind::LParen => "(".to_string(),
547 TokenKind::RParen => ")".to_string(),
548 TokenKind::Comma => ",".to_string(),
549 TokenKind::Semi => ";".to_string(),
550 TokenKind::LBrace => "{".to_string(),
551 TokenKind::RBrace => "}".to_string(),
552 TokenKind::Colon => ":".to_string(),
553 TokenKind::Dot => ".".to_string(),
554 TokenKind::LBracket => "[".to_string(),
555 TokenKind::RBracket => "]".to_string(),
556 TokenKind::Eof => String::new(),
557 }
558}