Skip to main content

sieve/compiler/grammar/
instruction.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
5 */
6
7use super::tests::test_ihave::Error;
8use super::{
9    Capability, Clear, Invalid, While,
10    actions::{
11        action_convert::Convert,
12        action_editheader::{AddHeader, DeleteHeader},
13        action_fileinto::FileInto,
14        action_flags::EditFlags,
15        action_include::Include,
16        action_keep::Keep,
17        action_mime::{Enclose, ExtractText, ForEveryPart, Replace},
18        action_notify::Notify,
19        action_redirect::Redirect,
20        action_reject::Reject,
21        action_set::{Let, Set},
22        action_vacation::Vacation,
23    },
24    expr::Expression,
25};
26use crate::{
27    Compiler, Sieve,
28    compiler::{
29        CompileError, ConstantId, ErrorType, RawValue, Value, VariableType,
30        grammar::{MatchType, test::Test},
31        lexer::{Token, tokenizer::Tokenizer, word::Word},
32    },
33};
34use ahash::{AHashMap, AHashSet};
35use hashbrown::HashTable;
36
37#[derive(Debug, Clone, Eq, PartialEq)]
38#[cfg_attr(
39    any(test, feature = "serde"),
40    derive(serde::Serialize, serde::Deserialize)
41)]
42#[repr(u8)]
43pub(crate) enum Instruction {
44    Require(Box<[Capability]>) = 0,
45    Keep(Box<Keep>) = 1,
46    FileInto(Box<FileInto>) = 2,
47    Redirect(Box<Redirect>) = 3,
48    Discard = 4,
49    Stop = 5,
50    Invalid(Box<Invalid>) = 6,
51    Test(Box<Test>) = 7,
52    Jmp(u32) = 8,
53    Jz(u32) = 9,
54    Jnz(u32) = 10,
55
56    // RFC 5703
57    ForEveryPartPush = 11,
58    ForEveryPart(ForEveryPart) = 12,
59    ForEveryPartPop(u32) = 13,
60    Replace(Box<Replace>) = 14,
61    Enclose(Box<Enclose>) = 15,
62    ExtractText(Box<ExtractText>) = 16,
63
64    // RFC 6558
65    Convert(Box<Convert>) = 17,
66
67    // RFC 5293
68    AddHeader(Box<AddHeader>) = 18,
69    DeleteHeader(Box<DeleteHeader>) = 19,
70
71    // RFC 5229
72    Set(Box<Set>) = 20,
73    Clear(Box<Clear>) = 21,
74
75    // RFC 5435
76    Notify(Box<Notify>) = 22,
77
78    // RFC 5429
79    Reject(Box<Reject>) = 23,
80
81    // RFC 5230
82    Vacation(Box<Vacation>) = 24,
83
84    // RFC 5463
85    Error(Box<Error>) = 25,
86
87    // RFC 5232
88    EditFlags(Box<EditFlags>) = 26,
89
90    // RFC 6609
91    Include(Box<Include>) = 27,
92    Return = 28,
93
94    // For every line extension
95    While(Box<While>) = 29,
96
97    // Expression extension
98    Eval(Box<[Expression]>) = 30,
99    Let(Box<Let>) = 31,
100
101    // Test only
102    #[cfg(test)]
103    TestCmd(Box<[Value]>) = 0x7f,
104}
105
106pub(crate) const MAX_PARAMS: usize = 11;
107
108#[derive(Debug)]
109pub(crate) struct Block {
110    pub(crate) btype: Word,
111    pub(crate) label: Option<String>,
112    pub(crate) line_num: usize,
113    pub(crate) line_pos: usize,
114    pub(crate) last_block_start: usize,
115    pub(crate) if_jmps: Vec<usize>,
116    pub(crate) break_jmps: Vec<usize>,
117    pub(crate) match_test_pos: Vec<usize>,
118    pub(crate) match_test_vars: u64,
119    pub(crate) vars_local: AHashMap<String, u16>,
120    pub(crate) capabilities: AHashSet<Capability>,
121    pub(crate) require_pos: usize,
122}
123
124pub(crate) struct CompilerState<'x> {
125    pub(crate) compiler: &'x Compiler,
126    pub(crate) tokens: Tokenizer<'x>,
127    pub(crate) instructions: Vec<Instruction>,
128    pub(crate) block_stack: Vec<Block>,
129    pub(crate) block: Block,
130    pub(crate) last_block_type: Word,
131    pub(crate) vars_global: AHashSet<String>,
132    pub(crate) vars_num: usize,
133    pub(crate) vars_num_max: usize,
134    pub(crate) vars_match_max: usize,
135    pub(crate) vars_local: usize,
136    pub(crate) param_check: [bool; MAX_PARAMS],
137    pub(crate) includes_num: usize,
138    pub(crate) constants: Vec<String>,
139    pub(crate) constants_map: HashTable<ConstantId>,
140    pub(crate) hasher: ahash::RandomState,
141}
142
143impl CompilerState<'_> {
144    pub(crate) fn intern(&mut self, text: impl AsRef<str>) -> ConstantId {
145        let text = text.as_ref();
146        match self.lookup_constant(text) {
147            Ok(id) => id,
148            Err(id) => {
149                self.constants.push(text.to_string());
150                id
151            }
152        }
153    }
154
155    pub(crate) fn intern_string(&mut self, text: String) -> ConstantId {
156        match self.lookup_constant(&text) {
157            Ok(id) => id,
158            Err(id) => {
159                self.constants.push(text);
160                id
161            }
162        }
163    }
164
165    fn lookup_constant(&mut self, text: &str) -> Result<ConstantId, ConstantId> {
166        let hash = self.hasher.hash_one(text);
167        let constants = &self.constants;
168        match self.constants_map.entry(
169            hash,
170            |id| constants.get(id.index()).is_some_and(|c| c == text),
171            |id| {
172                self.hasher
173                    .hash_one(constants.get(id.index()).map_or("", |c| c.as_str()))
174            },
175        ) {
176            hashbrown::hash_table::Entry::Occupied(entry) => Ok(*entry.get()),
177            hashbrown::hash_table::Entry::Vacant(entry) => {
178                let id = ConstantId::new(constants.len());
179                entry.insert(id);
180                Err(id)
181            }
182        }
183    }
184
185    pub(crate) fn constant(&self, id: ConstantId) -> &str {
186        self.constants.get(id.index()).map_or("", |v| v.as_ref())
187    }
188
189    pub(crate) fn text(&mut self, text: impl AsRef<str>) -> Value {
190        Value::Text(self.intern(text))
191    }
192
193    pub(crate) fn intern_raw(&mut self, value: RawValue) -> Value {
194        match value {
195            RawValue::Text(text) => Value::Text(self.intern_string(text)),
196            RawValue::Number(number) => Value::Number(number),
197            RawValue::Value(value) => value,
198        }
199    }
200
201    pub(crate) fn intern_raw_list(&mut self, values: Vec<RawValue>) -> Vec<Value> {
202        let mut result = Vec::with_capacity(values.len());
203        for value in values {
204            let value = self.intern_raw(value);
205            result.push(value);
206        }
207        result
208    }
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
212pub(crate) struct Program {
213    pub(crate) instructions: Vec<Instruction>,
214    pub(crate) constants: Vec<String>,
215    pub(crate) num_vars: u32,
216    pub(crate) num_match_vars: u32,
217}
218
219impl Program {
220    pub(crate) fn emit(&self) -> Result<Sieve<'static>, crate::sieve::LoadError> {
221        crate::compiler::emit::emit(
222            &self.instructions,
223            &self.constants,
224            self.num_vars,
225            self.num_match_vars,
226        )
227    }
228}
229
230impl Compiler {
231    pub fn compile(&self, script: &[u8]) -> Result<Sieve<'static>, CompileError> {
232        let program = self.compile_ast(script)?;
233        program.emit().map_err(|_| CompileError {
234            line_num: 0,
235            line_pos: 0,
236            error_type: ErrorType::ScriptTooLong,
237        })
238    }
239
240    pub(crate) fn compile_ast(&self, script: &[u8]) -> Result<Program, CompileError> {
241        if script.len() > self.max_script_size {
242            return Err(CompileError {
243                line_num: 0,
244                line_pos: 0,
245                error_type: ErrorType::ScriptTooLong,
246            });
247        }
248
249        let mut state = CompilerState {
250            compiler: self,
251            tokens: Tokenizer::new(self, script),
252            instructions: Vec::new(),
253            block_stack: Vec::new(),
254            block: Block::new(Word::Not),
255            last_block_type: Word::Not,
256            vars_global: AHashSet::new(),
257            vars_num: 0,
258            vars_num_max: 0,
259            vars_match_max: 0,
260            vars_local: 0,
261            param_check: [false; MAX_PARAMS],
262            includes_num: 0,
263            constants: Vec::new(),
264            constants_map: HashTable::new(),
265            hasher: ahash::RandomState::new(),
266        };
267
268        while let Some(token_info) = state.tokens.next() {
269            let token_info = token_info?;
270            state.reset_param_check();
271
272            match token_info.token {
273                Token::Identifier(instruction) => {
274                    let mut is_new_block = None;
275
276                    match instruction {
277                        Word::Require => {
278                            state.parse_require()?;
279                        }
280                        Word::If => {
281                            state.parse_test()?;
282                            state.block.if_jmps.clear();
283                            is_new_block = Block::new(Word::If).into();
284                        }
285                        Word::ElsIf => {
286                            if let Word::If | Word::ElsIf = &state.last_block_type {
287                                state.parse_test()?;
288                                is_new_block = Block::new(Word::ElsIf).into();
289                            } else {
290                                return Err(token_info.expected("'if' before 'elsif'"));
291                            }
292                        }
293                        Word::Else => {
294                            if let Word::If | Word::ElsIf = &state.last_block_type {
295                                is_new_block = Block::new(Word::Else).into();
296                            } else {
297                                return Err(token_info.expected("'if' or 'elsif' before 'else'"));
298                            }
299                        }
300                        Word::Keep => {
301                            state.parse_keep()?;
302                        }
303                        Word::FileInto => {
304                            state.validate_argument(
305                                0,
306                                Capability::FileInto.into(),
307                                token_info.line_num,
308                                token_info.line_pos,
309                            )?;
310                            state.parse_fileinto()?;
311                        }
312                        Word::Redirect => {
313                            state.parse_redirect()?;
314                        }
315                        Word::Discard => {
316                            state.instructions.push(Instruction::Discard);
317                        }
318                        Word::Stop => {
319                            state.instructions.push(Instruction::Stop);
320                        }
321
322                        // RFC 5703
323                        Word::ForEveryPart => {
324                            state.validate_argument(
325                                0,
326                                Capability::ForEveryPart.into(),
327                                token_info.line_num,
328                                token_info.line_pos,
329                            )?;
330
331                            if state
332                                .block_stack
333                                .iter()
334                                .filter(|b| matches!(&b.btype, Word::ForEveryPart))
335                                .count()
336                                == self.max_nested_foreverypart
337                            {
338                                return Err(
339                                    token_info.custom(ErrorType::TooManyNestedForEveryParts)
340                                );
341                            }
342
343                            is_new_block = if let Some(Ok(Token::Tag(Word::Name))) =
344                                state.tokens.peek().map(|r| r.map(|t| &t.token))
345                            {
346                                let tag = state.tokens.next().unwrap().unwrap();
347                                let label = state.tokens.expect_static_string()?;
348                                for block in &state.block_stack {
349                                    if block.label.as_ref().is_some_and(|n| n.eq(&label)) {
350                                        return Err(
351                                            tag.custom(ErrorType::LabelAlreadyDefined(label))
352                                        );
353                                    }
354                                }
355                                Block::new(Word::ForEveryPart).with_label(label)
356                            } else {
357                                Block::new(Word::ForEveryPart)
358                            }
359                            .into();
360
361                            state.instructions.push(Instruction::ForEveryPartPush);
362                            state
363                                .instructions
364                                .push(Instruction::ForEveryPart(ForEveryPart { jz_pos: u32::MAX }));
365                        }
366                        Word::Break => {
367                            if let Some(Ok(Token::Tag(Word::Name))) =
368                                state.tokens.peek().map(|r| r.map(|t| &t.token))
369                            {
370                                state.validate_argument(
371                                    0,
372                                    Capability::ForEveryPart.into(),
373                                    token_info.line_num,
374                                    token_info.line_pos,
375                                )?;
376
377                                let tag = state.tokens.next().unwrap().unwrap();
378                                let label = state.tokens.expect_static_string()?;
379                                let mut label_found = false;
380                                let mut num_pops = 0;
381
382                                for block in [&mut state.block]
383                                    .into_iter()
384                                    .chain(state.block_stack.iter_mut().rev())
385                                {
386                                    if let Word::ForEveryPart = &block.btype {
387                                        num_pops += 1;
388                                        if block.label.as_ref().is_some_and(|n| n.eq(&label)) {
389                                            state.instructions.push(Instruction::ForEveryPartPop(
390                                                num_pops as u32,
391                                            ));
392                                            block.break_jmps.push(state.instructions.len());
393                                            label_found = true;
394                                            break;
395                                        }
396                                    }
397                                }
398
399                                if !label_found {
400                                    return Err(tag.custom(ErrorType::LabelUndefined(label)));
401                                }
402                            } else {
403                                let mut block_found = None;
404                                if matches!(&state.block.btype, Word::ForEveryPart | Word::While) {
405                                    block_found = Some(&mut state.block);
406                                } else {
407                                    for block in state.block_stack.iter_mut().rev() {
408                                        if matches!(&block.btype, Word::ForEveryPart | Word::While)
409                                        {
410                                            block_found = Some(block);
411                                            break;
412                                        }
413                                    }
414                                }
415
416                                let block = block_found.ok_or_else(|| {
417                                    token_info.custom(ErrorType::BreakOutsideLoop)
418                                })?;
419                                if matches!(block.btype, Word::ForEveryPart) {
420                                    state.instructions.push(Instruction::ForEveryPartPop(1));
421                                }
422
423                                block.break_jmps.push(state.instructions.len());
424                            }
425
426                            state.instructions.push(Instruction::Jmp(u32::MAX));
427                        }
428                        Word::Replace => {
429                            state.validate_argument(
430                                0,
431                                Capability::Replace.into(),
432                                token_info.line_num,
433                                token_info.line_pos,
434                            )?;
435                            state.parse_replace()?;
436                        }
437                        Word::Enclose => {
438                            state.validate_argument(
439                                0,
440                                Capability::Enclose.into(),
441                                token_info.line_num,
442                                token_info.line_pos,
443                            )?;
444                            state.parse_enclose()?;
445                        }
446                        Word::ExtractText => {
447                            state.validate_argument(
448                                0,
449                                Capability::ExtractText.into(),
450                                token_info.line_num,
451                                token_info.line_pos,
452                            )?;
453                            state.parse_extracttext()?;
454                        }
455
456                        // RFC 6558
457                        Word::Convert => {
458                            state.validate_argument(
459                                0,
460                                Capability::Convert.into(),
461                                token_info.line_num,
462                                token_info.line_pos,
463                            )?;
464                            state.parse_convert()?;
465                        }
466
467                        // RFC 5293
468                        Word::AddHeader => {
469                            state.validate_argument(
470                                0,
471                                Capability::EditHeader.into(),
472                                token_info.line_num,
473                                token_info.line_pos,
474                            )?;
475                            state.parse_addheader()?;
476                        }
477                        Word::DeleteHeader => {
478                            state.validate_argument(
479                                0,
480                                Capability::EditHeader.into(),
481                                token_info.line_num,
482                                token_info.line_pos,
483                            )?;
484                            state.parse_deleteheader()?;
485                        }
486
487                        // RFC 5229
488                        Word::Set => {
489                            state.validate_argument(
490                                0,
491                                Capability::Variables.into(),
492                                token_info.line_num,
493                                token_info.line_pos,
494                            )?;
495                            state.parse_set()?;
496                        }
497
498                        // RFC 5435
499                        Word::Notify => {
500                            state.validate_argument(
501                                0,
502                                Capability::Enotify.into(),
503                                token_info.line_num,
504                                token_info.line_pos,
505                            )?;
506                            state.parse_notify()?;
507                        }
508
509                        // RFC 5429
510                        Word::Reject => {
511                            state.validate_argument(
512                                0,
513                                Capability::Reject.into(),
514                                token_info.line_num,
515                                token_info.line_pos,
516                            )?;
517                            state.parse_reject(false)?;
518                        }
519                        Word::Ereject => {
520                            state.validate_argument(
521                                0,
522                                Capability::Ereject.into(),
523                                token_info.line_num,
524                                token_info.line_pos,
525                            )?;
526                            state.parse_reject(true)?;
527                        }
528
529                        // RFC 5230
530                        Word::Vacation => {
531                            state.validate_argument(
532                                0,
533                                Capability::Vacation.into(),
534                                token_info.line_num,
535                                token_info.line_pos,
536                            )?;
537                            state.parse_vacation()?;
538                        }
539
540                        // RFC 5463
541                        Word::Error => {
542                            state.validate_argument(
543                                0,
544                                Capability::Ihave.into(),
545                                token_info.line_num,
546                                token_info.line_pos,
547                            )?;
548                            state.parse_error()?;
549                        }
550
551                        // RFC 5232
552                        Word::SetFlag | Word::AddFlag | Word::RemoveFlag => {
553                            state.validate_argument(
554                                0,
555                                Capability::Imap4Flags.into(),
556                                token_info.line_num,
557                                token_info.line_pos,
558                            )?;
559                            state.parse_flag_action(instruction)?;
560                        }
561
562                        // RFC 6609
563                        Word::Include => {
564                            if state.includes_num < self.max_includes {
565                                state.validate_argument(
566                                    0,
567                                    Capability::Include.into(),
568                                    token_info.line_num,
569                                    token_info.line_pos,
570                                )?;
571                                state.parse_include()?;
572                                state.includes_num += 1;
573                            } else {
574                                return Err(token_info.custom(ErrorType::TooManyIncludes));
575                            }
576                        }
577                        Word::Return => {
578                            state.validate_argument(
579                                0,
580                                Capability::Include.into(),
581                                token_info.line_num,
582                                token_info.line_pos,
583                            )?;
584                            let mut num_pops = 0;
585
586                            for block in [&state.block]
587                                .into_iter()
588                                .chain(state.block_stack.iter().rev())
589                            {
590                                if let Word::ForEveryPart = &block.btype {
591                                    num_pops += 1;
592                                }
593                            }
594
595                            if num_pops > 0 {
596                                state
597                                    .instructions
598                                    .push(Instruction::ForEveryPartPop(num_pops as u32));
599                            }
600
601                            state.instructions.push(Instruction::Return);
602                        }
603                        Word::Global => {
604                            state.validate_argument(
605                                0,
606                                Capability::Include.into(),
607                                token_info.line_num,
608                                token_info.line_pos,
609                            )?;
610                            state.validate_argument(
611                                0,
612                                Capability::Variables.into(),
613                                token_info.line_num,
614                                token_info.line_pos,
615                            )?;
616                            for global in state.parse_static_strings()? {
617                                if !state.is_var_local(&global) {
618                                    if global.len() < self.max_variable_name_size {
619                                        state.register_global_var(&global);
620                                    } else {
621                                        return Err(state
622                                            .tokens
623                                            .unwrap_next()?
624                                            .custom(ErrorType::VariableTooLong));
625                                    }
626                                } else {
627                                    return Err(state
628                                        .tokens
629                                        .unwrap_next()?
630                                        .custom(ErrorType::VariableIsLocal(global)));
631                                }
632                            }
633                        }
634
635                        // Expressions extension
636                        Word::Let => {
637                            state.validate_argument(
638                                0,
639                                Capability::Expressions.into(),
640                                token_info.line_num,
641                                token_info.line_pos,
642                            )?;
643                            state.parse_let()?;
644                        }
645                        Word::Eval => {
646                            state.validate_argument(
647                                0,
648                                Capability::Expressions.into(),
649                                token_info.line_num,
650                                token_info.line_pos,
651                            )?;
652                            let expr = state.parse_expr()?;
653                            state
654                                .instructions
655                                .push(Instruction::Eval(expr.into_boxed_slice()));
656                        }
657
658                        // While extension
659                        Word::While => {
660                            state.validate_argument(
661                                0,
662                                Capability::While.into(),
663                                token_info.line_num,
664                                token_info.line_pos,
665                            )?;
666
667                            is_new_block = Block::new(Word::While).into();
668
669                            let expr = state.parse_expr()?;
670                            state.instructions.push(Instruction::While(Box::new(While {
671                                expr: expr.into(),
672                                jz_pos: u32::MAX,
673                            })));
674                        }
675                        Word::Continue => {
676                            state.validate_argument(
677                                0,
678                                Capability::While.into(),
679                                token_info.line_num,
680                                token_info.line_pos,
681                            )?;
682                            let mut found_while = 0;
683                            for block in [&state.block]
684                                .into_iter()
685                                .chain(state.block_stack.iter().rev())
686                            {
687                                if let Word::While = &block.btype {
688                                    found_while += 1;
689                                } else if found_while == 1 {
690                                    state
691                                        .instructions
692                                        .push(Instruction::Jmp(block.last_block_start as u32));
693                                    found_while += 1;
694                                    break;
695                                }
696                            }
697                            if found_while != 2 {
698                                return Err(token_info.custom(ErrorType::ContinueOutsideLoop));
699                            }
700                        }
701
702                        _ => {
703                            if state.has_capability(&Capability::Ihave) {
704                                state.ignore_instruction()?;
705                                state
706                                    .instructions
707                                    .push(Instruction::Invalid(Box::new(Invalid {
708                                        name: instruction.to_string(),
709                                        line_num: token_info.line_num as u32,
710                                        line_pos: (token_info.line_pos) as u32,
711                                    })));
712                                continue;
713                            } else {
714                                return Err(CompileError {
715                                    line_num: state.block.line_num,
716                                    line_pos: state.block.line_pos,
717                                    error_type: ErrorType::UnexpectedToken {
718                                        expected: "command".into(),
719                                        found: instruction.to_string(),
720                                    },
721                                });
722                            }
723                        }
724                    }
725
726                    if let Some(mut new_block) = is_new_block {
727                        new_block.line_num = state.tokens.line_num;
728                        new_block.line_pos = state.tokens.pos - state.tokens.line_start;
729
730                        state.tokens.expect_token(Token::CurlyOpen)?;
731                        if state.block_stack.len() < self.max_nested_blocks {
732                            state.block.last_block_start = state.instructions.len() - 1;
733                            state.block_stack.push(state.block);
734                            state.block = new_block;
735                        } else {
736                            return Err(CompileError {
737                                line_num: state.block.line_num,
738                                line_pos: state.block.line_pos,
739                                error_type: ErrorType::TooManyNestedBlocks,
740                            });
741                        }
742                    } else {
743                        state.expect_instruction_end()?;
744                    }
745                }
746                Token::CurlyClose if !state.block_stack.is_empty() => {
747                    state.block_end();
748                    let mut prev_block = state.block_stack.pop().unwrap();
749                    match &state.block.btype {
750                        Word::ForEveryPart => {
751                            state
752                                .instructions
753                                .push(Instruction::Jmp(prev_block.last_block_start as u32));
754                            let cur_pos = state.instructions.len();
755                            if let Instruction::ForEveryPart(fep) =
756                                &mut state.instructions[prev_block.last_block_start]
757                            {
758                                fep.jz_pos = cur_pos as u32;
759                            } else {
760                                debug_assert!(false, "This should not have happened.");
761                            }
762                            for pos in state.block.break_jmps {
763                                if let Instruction::Jmp(jmp_pos) = &mut state.instructions[pos] {
764                                    *jmp_pos = cur_pos as u32;
765                                } else {
766                                    debug_assert!(false, "This should not have happened.");
767                                }
768                            }
769                            state.last_block_type = Word::Not;
770                        }
771                        Word::If | Word::ElsIf => {
772                            let next_is_block = matches!(
773                                state.tokens.peek().map(|r| r.map(|t| &t.token)),
774                                Some(Ok(Token::Identifier(Word::ElsIf | Word::Else)))
775                            );
776                            if next_is_block {
777                                prev_block.if_jmps.push(state.instructions.len());
778                                state.instructions.push(Instruction::Jmp(u32::MAX));
779                            }
780                            let cur_pos = state.instructions.len();
781                            if let Instruction::Jz(jmp_pos) =
782                                &mut state.instructions[prev_block.last_block_start]
783                            {
784                                *jmp_pos = cur_pos as u32;
785                            } else {
786                                debug_assert!(false, "This should not have happened.");
787                            }
788                            if !next_is_block {
789                                for pos in prev_block.if_jmps.drain(..) {
790                                    if let Instruction::Jmp(jmp_pos) = &mut state.instructions[pos]
791                                    {
792                                        *jmp_pos = cur_pos as u32;
793                                    } else {
794                                        debug_assert!(false, "This should not have happened.");
795                                    }
796                                }
797                                state.last_block_type = Word::Not;
798                            } else {
799                                state.last_block_type = state.block.btype;
800                            }
801                        }
802                        Word::Else => {
803                            let cur_pos = state.instructions.len();
804                            for pos in prev_block.if_jmps.drain(..) {
805                                if let Instruction::Jmp(jmp_pos) = &mut state.instructions[pos] {
806                                    *jmp_pos = cur_pos as u32;
807                                } else {
808                                    debug_assert!(false, "This should not have happened.");
809                                }
810                            }
811                            state.last_block_type = Word::Else;
812                        }
813                        Word::While => {
814                            state
815                                .instructions
816                                .push(Instruction::Jmp(prev_block.last_block_start as u32));
817                            let cur_pos = state.instructions.len();
818                            if let Instruction::While(fep) =
819                                &mut state.instructions[prev_block.last_block_start]
820                            {
821                                fep.jz_pos = cur_pos as u32;
822                            } else {
823                                debug_assert!(false, "This should not have happened.");
824                            }
825                            for pos in state.block.break_jmps {
826                                if let Instruction::Jmp(jmp_pos) = &mut state.instructions[pos] {
827                                    *jmp_pos = cur_pos as u32;
828                                } else {
829                                    debug_assert!(false, "This should not have happened.");
830                                }
831                            }
832                            state.last_block_type = Word::Not;
833                        }
834                        _ => {
835                            debug_assert!(false, "This should not have happened.");
836                        }
837                    }
838
839                    state.block = prev_block;
840                }
841
842                #[cfg(test)]
843                Token::Unknown(instruction) if instruction.contains("test") => {
844                    let has_arguments = instruction != "test";
845                    let mut arguments = vec![state.text(&instruction)];
846
847                    if !has_arguments {
848                        arguments.push(state.parse_string()?);
849                        state
850                            .instructions
851                            .push(Instruction::TestCmd(arguments.into_boxed_slice()));
852                        let mut new_block = Block::new(Word::Else);
853                        new_block.line_num = state.tokens.line_num;
854                        new_block.line_pos = state.tokens.pos - state.tokens.line_start;
855                        state.tokens.expect_token(Token::CurlyOpen)?;
856                        state.block.last_block_start = state.instructions.len() - 1;
857                        state.block_stack.push(state.block);
858                        state.block = new_block;
859                    } else {
860                        loop {
861                            arguments.push(match state.tokens.unwrap_next()?.token {
862                                Token::StringConstant(s) => state.intern_raw(s.into()),
863                                Token::StringVariable(s) => state
864                                    .tokenize_string(&s, true)
865                                    .map_err(|error_type| CompileError {
866                                        line_num: 0,
867                                        line_pos: 0,
868                                        error_type,
869                                    })?,
870                                Token::Number(n) => {
871                                    Value::Number(crate::compiler::Number::Integer(n as i64))
872                                }
873                                Token::Identifier(s) => state.text(s.to_string()),
874                                Token::Tag(s) => state.text(format!(":{s}")),
875                                Token::Unknown(s) => state.text(s),
876                                Token::Semicolon => break,
877                                other => panic!("Invalid test param {other:?}"),
878                            });
879                        }
880                        state
881                            .instructions
882                            .push(Instruction::TestCmd(arguments.into_boxed_slice()));
883                    }
884                }
885
886                Token::Unknown(instruction) => {
887                    if state.has_capability(&Capability::Ihave) {
888                        state.ignore_instruction()?;
889                        state
890                            .instructions
891                            .push(Instruction::Invalid(Box::new(Invalid {
892                                name: instruction,
893                                line_num: token_info.line_num as u32,
894                                line_pos: (token_info.line_pos) as u32,
895                            })));
896                    } else {
897                        return Err(CompileError {
898                            line_num: state.block.line_num,
899                            line_pos: state.block.line_pos,
900                            error_type: ErrorType::UnexpectedToken {
901                                expected: "command".into(),
902                                found: instruction,
903                            },
904                        });
905                    }
906                }
907                _ => {
908                    return Err(token_info.expected("instruction"));
909                }
910            }
911        }
912
913        if !state.block_stack.is_empty() {
914            return Err(CompileError {
915                line_num: state.block.line_num,
916                line_pos: state.block.line_pos,
917                error_type: ErrorType::UnterminatedBlock,
918            });
919        }
920
921        // Map local variables
922        let mut num_vars = std::cmp::max(state.vars_num_max, state.vars_num);
923        if state.vars_local > 0 {
924            state.map_local_vars(num_vars as u16);
925            num_vars += state.vars_local;
926        }
927
928        Ok(Program {
929            instructions: state.instructions,
930            constants: state.constants,
931            num_vars: num_vars as u32,
932            num_match_vars: state.vars_match_max as u32,
933        })
934    }
935}
936
937impl CompilerState<'_> {
938    pub(crate) fn is_var_local(&self, name: &str) -> bool {
939        let name = lowercase(name);
940        let name = name.as_ref();
941        if self.block.vars_local.contains_key(name) {
942            true
943        } else {
944            for block in self.block_stack.iter().rev() {
945                if block.vars_local.contains_key(name) {
946                    return true;
947                }
948            }
949            false
950        }
951    }
952
953    pub(crate) fn is_var_global(&self, name: &str) -> bool {
954        self.vars_global.contains(lowercase(name).as_ref())
955    }
956
957    pub(crate) fn register_local_var(&mut self, name: String, register_as_local: bool) -> u16 {
958        if let Some(var_id) = self.get_local_var(&name) {
959            var_id
960        } else if !register_as_local || self.block_stack.is_empty() {
961            let var_id = self.vars_num as u16;
962            self.block.vars_local.insert(name, var_id);
963            self.vars_num += 1;
964            var_id
965        } else {
966            let var_id = u16::MAX - self.vars_local as u16;
967            self.block_stack
968                .first_mut()
969                .unwrap()
970                .vars_local
971                .insert(name, var_id);
972            self.vars_local += 1;
973            var_id
974        }
975    }
976
977    pub(crate) fn register_global_var(&mut self, name: &str) {
978        self.vars_global.insert(name.to_ascii_lowercase());
979    }
980
981    pub(crate) fn get_local_var(&self, name: &str) -> Option<u16> {
982        let name = lowercase(name);
983        let name = name.as_ref();
984        if let Some(var_id) = self.block.vars_local.get(name) {
985            Some(*var_id)
986        } else {
987            for block in self.block_stack.iter().rev() {
988                if let Some(var_id) = block.vars_local.get(name) {
989                    return Some(*var_id);
990                }
991            }
992            None
993        }
994    }
995
996    pub(crate) fn register_match_var(&mut self, num: usize) -> bool {
997        let mut block = &mut self.block;
998
999        if block.match_test_pos.is_empty() {
1000            for block_ in self.block_stack.iter_mut().rev() {
1001                if !block_.match_test_pos.is_empty() {
1002                    block = block_;
1003                    break;
1004                }
1005            }
1006        }
1007
1008        if !block.match_test_pos.is_empty() {
1009            debug_assert!(num < 63);
1010
1011            for pos in &block.match_test_pos {
1012                if let Some(Instruction::Test(test)) = self.instructions.get_mut(*pos) {
1013                    let match_type = match &mut **test {
1014                        Test::Address(t) => &mut t.match_type,
1015                        Test::Body(t) => &mut t.match_type,
1016                        Test::Date(t) => &mut t.match_type,
1017                        Test::CurrentDate(t) => &mut t.match_type,
1018                        Test::Envelope(t) => &mut t.match_type,
1019                        Test::HasFlag(t) => &mut t.match_type,
1020                        Test::Header(t) => &mut t.match_type,
1021                        Test::Metadata(t) => &mut t.match_type,
1022                        Test::NotifyMethodCapability(t) => &mut t.match_type,
1023                        Test::SpamTest(t) => &mut t.match_type,
1024                        Test::String(t) | Test::Environment(t) => &mut t.match_type,
1025                        Test::VirusTest(t) => &mut t.match_type,
1026                        _ => {
1027                            debug_assert!(false, "This should not have happened: {test:?}");
1028                            return false;
1029                        }
1030                    };
1031                    if let MatchType::Matches(positions) | MatchType::Regex(positions) = match_type
1032                    {
1033                        *positions |= 1 << num;
1034                        block.match_test_vars = *positions;
1035                    } else {
1036                        debug_assert!(false, "This should not have happened");
1037                        return false;
1038                    }
1039                } else {
1040                    debug_assert!(false, "This should not have happened");
1041                    return false;
1042                }
1043            }
1044            true
1045        } else {
1046            false
1047        }
1048    }
1049
1050    pub(crate) fn block_end(&mut self) {
1051        let vars_num_block = self.block.vars_local.len();
1052        if vars_num_block > 0 {
1053            if self.vars_num > self.vars_num_max {
1054                self.vars_num_max = self.vars_num;
1055            }
1056            self.vars_num -= vars_num_block;
1057            self.instructions.push(Instruction::Clear(Box::new(Clear {
1058                match_vars: self.block.match_test_vars,
1059                local_vars_idx: self.vars_num as u32,
1060                local_vars_num: vars_num_block as u32,
1061            })));
1062        } else if self.block.match_test_vars != 0 {
1063            self.instructions.push(Instruction::Clear(Box::new(Clear {
1064                match_vars: self.block.match_test_vars,
1065                local_vars_idx: 0,
1066                local_vars_num: 0,
1067            })));
1068        }
1069    }
1070
1071    fn map_local_vars(&mut self, last_id: u16) {
1072        for instruction in &mut self.instructions {
1073            match instruction {
1074                Instruction::Test(v) => v.map_local_vars(last_id),
1075                Instruction::Keep(k) => k.flags.map_local_vars(last_id),
1076                Instruction::FileInto(v) => {
1077                    v.folder.map_local_vars(last_id);
1078                    v.flags.map_local_vars(last_id);
1079                    v.mailbox_id.map_local_vars(last_id);
1080                    v.special_use.map_local_vars(last_id);
1081                }
1082                Instruction::Redirect(v) => {
1083                    v.address.map_local_vars(last_id);
1084                    v.by_time.map_local_vars(last_id);
1085                }
1086                Instruction::Replace(v) => {
1087                    v.subject.map_local_vars(last_id);
1088                    v.from.map_local_vars(last_id);
1089                    v.replacement.map_local_vars(last_id);
1090                }
1091                Instruction::Enclose(v) => {
1092                    v.subject.map_local_vars(last_id);
1093                    v.headers.map_local_vars(last_id);
1094                    v.value.map_local_vars(last_id);
1095                }
1096                Instruction::ExtractText(v) => {
1097                    v.name.map_local_vars(last_id);
1098                }
1099                Instruction::Convert(v) => {
1100                    v.from_media_type.map_local_vars(last_id);
1101                    v.to_media_type.map_local_vars(last_id);
1102                    v.transcoding_params.map_local_vars(last_id);
1103                }
1104                Instruction::AddHeader(v) => {
1105                    v.field_name.map_local_vars(last_id);
1106                    v.value.map_local_vars(last_id);
1107                }
1108                Instruction::DeleteHeader(v) => {
1109                    v.field_name.map_local_vars(last_id);
1110                    v.value_patterns.map_local_vars(last_id);
1111                }
1112                Instruction::Set(v) => {
1113                    v.name.map_local_vars(last_id);
1114                    v.value.map_local_vars(last_id);
1115                }
1116                Instruction::Let(v) => {
1117                    v.name.map_local_vars(last_id);
1118                    v.expr.map_local_vars(last_id);
1119                }
1120                Instruction::While(v) => {
1121                    v.expr.map_local_vars(last_id);
1122                }
1123                Instruction::Eval(v) => {
1124                    v.map_local_vars(last_id);
1125                }
1126                Instruction::Notify(v) => {
1127                    v.from.map_local_vars(last_id);
1128                    v.importance.map_local_vars(last_id);
1129                    v.options.map_local_vars(last_id);
1130                    v.message.map_local_vars(last_id);
1131                    v.fcc.map_local_vars(last_id);
1132                    v.method.map_local_vars(last_id);
1133                }
1134                Instruction::Reject(v) => {
1135                    v.reason.map_local_vars(last_id);
1136                }
1137                Instruction::Vacation(v) => {
1138                    v.subject.map_local_vars(last_id);
1139                    v.from.map_local_vars(last_id);
1140                    v.fcc.map_local_vars(last_id);
1141                    v.reason.map_local_vars(last_id);
1142                }
1143                Instruction::Error(v) => {
1144                    v.message.map_local_vars(last_id);
1145                }
1146                Instruction::EditFlags(v) => {
1147                    v.name.map_local_vars(last_id);
1148                    v.flags.map_local_vars(last_id);
1149                }
1150                Instruction::Include(v) => {
1151                    v.value.map_local_vars(last_id);
1152                }
1153                _ => {}
1154            }
1155        }
1156    }
1157}
1158
1159pub(crate) fn lowercase(name: &str) -> std::borrow::Cow<'_, str> {
1160    if name.bytes().any(|b| b.is_ascii_uppercase()) {
1161        std::borrow::Cow::Owned(name.to_ascii_lowercase())
1162    } else {
1163        std::borrow::Cow::Borrowed(name)
1164    }
1165}
1166
1167pub trait MapLocalVars {
1168    fn map_local_vars(&mut self, last_id: u16);
1169}
1170
1171impl MapLocalVars for Test {
1172    fn map_local_vars(&mut self, last_id: u16) {
1173        match self {
1174            Test::Address(v) => {
1175                v.header_list.map_local_vars(last_id);
1176                v.key_list.map_local_vars(last_id);
1177            }
1178            Test::Envelope(v) => {
1179                v.key_list.map_local_vars(last_id);
1180            }
1181            Test::Exists(v) => {
1182                v.header_names.map_local_vars(last_id);
1183            }
1184            Test::Header(v) => {
1185                v.key_list.map_local_vars(last_id);
1186                v.header_list.map_local_vars(last_id);
1187                v.mime_opts.map_local_vars(last_id);
1188            }
1189            Test::Body(v) => {
1190                v.key_list.map_local_vars(last_id);
1191            }
1192            Test::Convert(v) => {
1193                v.from_media_type.map_local_vars(last_id);
1194                v.to_media_type.map_local_vars(last_id);
1195                v.transcoding_params.map_local_vars(last_id);
1196            }
1197            Test::Date(v) => {
1198                v.key_list.map_local_vars(last_id);
1199                v.header_name.map_local_vars(last_id);
1200            }
1201            Test::CurrentDate(v) => {
1202                v.key_list.map_local_vars(last_id);
1203            }
1204            Test::Duplicate(v) => {
1205                v.handle.map_local_vars(last_id);
1206                v.dup_match.map_local_vars(last_id);
1207            }
1208            Test::String(v) => {
1209                v.source.map_local_vars(last_id);
1210                v.key_list.map_local_vars(last_id);
1211            }
1212            Test::Environment(v) => {
1213                v.source.map_local_vars(last_id);
1214                v.key_list.map_local_vars(last_id);
1215            }
1216            Test::NotifyMethodCapability(v) => {
1217                v.key_list.map_local_vars(last_id);
1218                v.notification_capability.map_local_vars(last_id);
1219                v.notification_uri.map_local_vars(last_id);
1220            }
1221            Test::ValidNotifyMethod(v) => {
1222                v.notification_uris.map_local_vars(last_id);
1223            }
1224            Test::ValidExtList(v) => {
1225                v.list_names.map_local_vars(last_id);
1226            }
1227            Test::HasFlag(v) => {
1228                v.variable_list.map_local_vars(last_id);
1229                v.flags.map_local_vars(last_id);
1230            }
1231            Test::MailboxExists(v) => {
1232                v.mailbox_names.map_local_vars(last_id);
1233            }
1234            Test::Metadata(v) => {
1235                v.key_list.map_local_vars(last_id);
1236                v.medatata.map_local_vars(last_id);
1237            }
1238            Test::MetadataExists(v) => {
1239                v.annotation_names.map_local_vars(last_id);
1240                v.mailbox.map_local_vars(last_id);
1241            }
1242            Test::MailboxIdExists(v) => {
1243                v.mailbox_ids.map_local_vars(last_id);
1244            }
1245            Test::SpamTest(v) => {
1246                v.value.map_local_vars(last_id);
1247            }
1248            Test::VirusTest(v) => {
1249                v.value.map_local_vars(last_id);
1250            }
1251            Test::SpecialUseExists(v) => {
1252                v.mailbox.map_local_vars(last_id);
1253                v.attributes.map_local_vars(last_id);
1254            }
1255            Test::Vacation(v) => {
1256                v.addresses.map_local_vars(last_id);
1257                v.handle.map_local_vars(last_id);
1258                v.reason.map_local_vars(last_id);
1259            }
1260            #[cfg(test)]
1261            Test::TestCmd(cmd) => {
1262                cmd.arguments.map_local_vars(last_id);
1263            }
1264            _ => (),
1265        }
1266    }
1267}
1268
1269impl MapLocalVars for VariableType {
1270    fn map_local_vars(&mut self, last_id: u16) {
1271        match self {
1272            VariableType::Local(id) if *id > last_id => {
1273                *id = (u16::MAX - *id) + last_id;
1274            }
1275            _ => (),
1276        }
1277    }
1278}
1279
1280impl MapLocalVars for Value {
1281    fn map_local_vars(&mut self, last_id: u16) {
1282        match self {
1283            Value::Variable(var) => var.map_local_vars(last_id),
1284            Value::List(items) => items.map_local_vars(last_id),
1285            _ => (),
1286        }
1287    }
1288}
1289
1290impl<T: MapLocalVars> MapLocalVars for Option<T> {
1291    fn map_local_vars(&mut self, last_id: u16) {
1292        if let Some(value) = self {
1293            value.map_local_vars(last_id);
1294        }
1295    }
1296}
1297
1298impl MapLocalVars for Expression {
1299    fn map_local_vars(&mut self, last_id: u16) {
1300        match self {
1301            Expression::VariableLocal(id) if *id > last_id => {
1302                *id = (u16::MAX - *id) + last_id;
1303            }
1304            Expression::VariableOther(var) => var.map_local_vars(last_id),
1305            _ => (),
1306        }
1307    }
1308}
1309
1310impl<T: MapLocalVars> MapLocalVars for [T] {
1311    fn map_local_vars(&mut self, last_id: u16) {
1312        for item in self {
1313            item.map_local_vars(last_id);
1314        }
1315    }
1316}
1317
1318impl Block {
1319    pub fn new(btype: Word) -> Self {
1320        Block {
1321            btype,
1322            label: None,
1323            line_num: 0,
1324            line_pos: 0,
1325            last_block_start: 0,
1326            match_test_pos: vec![],
1327            match_test_vars: 0,
1328            if_jmps: vec![],
1329            break_jmps: vec![],
1330            vars_local: AHashMap::new(),
1331            capabilities: AHashSet::new(),
1332            require_pos: usize::MAX,
1333        }
1334    }
1335
1336    pub fn with_label(mut self, label: String) -> Self {
1337        self.label = label.into();
1338        self
1339    }
1340}