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