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