boa/syntax/lexer/
operator.rs

1//! This module implements lexing for operators (+, - etc.) used in the JavaScript programing language.
2
3use super::{Cursor, Error, Tokenizer};
4use crate::syntax::lexer::TokenKind;
5use crate::{
6    profiler::BoaProfiler,
7    syntax::{
8        ast::{Position, Punctuator, Span},
9        lexer::Token,
10    },
11};
12use std::io::Read;
13
14/// `vop` tests the next token to see if we're on an assign operation of just a plain binary operation.
15///
16/// If the next value is not an assignment operation it will pattern match  the provided values and return the corresponding token.
17macro_rules! vop {
18    ($cursor:ident, $assign_op:expr, $op:expr) => ({
19        match $cursor.peek()? {
20            None => Err(Error::syntax("abrupt end - could not preview next value as part of the operator", $cursor.pos())),
21            Some(b'=') => {
22                $cursor.next_byte()?.expect("= token vanished");
23                $cursor.next_column();
24                $assign_op
25            }
26            Some(_) => $op,
27        }
28    });
29    ($cursor:ident, $assign_op:expr, $op:expr, {$($case:pat => $block:expr), +}) => ({
30        match $cursor.peek()? {
31            None => Err(Error::syntax("abrupt end - could not preview next value as part of the operator", $cursor.pos())),
32            Some(b'=') => {
33                $cursor.next_byte()?.expect("= token vanished");
34                $cursor.next_column();
35                $assign_op
36            },
37            $($case => {
38                $cursor.next_byte()?.expect("Token vanished");
39                $cursor.next_column();
40                $block
41            })+,
42            _ => $op,
43        }
44    });
45    ($cursor:ident, $op:expr, {$($case:pat => $block:expr),+}) => {
46        match $cursor.peek().ok_or_else(|| Error::syntax("could not preview next value", $cursor.pos()))? {
47            $($case => {
48                $cursor.next_byte()?;
49                $cursor.next_column();
50                $block
51            })+,
52            _ => $op
53        }
54    }
55}
56
57/// The `op` macro handles binary operations or assignment operations and converts them into tokens.
58macro_rules! op {
59    ($cursor:ident, $start_pos:expr, $assign_op:expr, $op:expr) => ({
60        Ok(Token::new(
61            vop!($cursor, $assign_op, $op)?.into(),
62            Span::new($start_pos, $cursor.pos()),
63        ))
64    });
65    ($cursor:ident, $start_pos:expr, $assign_op:expr, $op:expr, {$($case:pat => $block:expr),+}) => ({
66        let punc: Punctuator = vop!($cursor, $assign_op, $op, {$($case => $block),+})?;
67        Ok(Token::new(
68            punc.into(),
69            Span::new($start_pos, $cursor.pos()),
70        ))
71    });
72}
73
74#[derive(Debug, Clone, Copy)]
75pub(super) struct Operator {
76    init: u8,
77}
78
79/// Operator lexing.
80///
81/// Assumes that the cursor has already consumed the operator starting symbol (stored in init).
82///
83/// More information:
84///  - [ECMAScript reference][spec]
85///  - [MDN documentation][mdn]
86///
87/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-language-expressions
88/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators
89impl Operator {
90    /// Creates a new operator lexer.
91    pub(super) fn new(init: u8) -> Self {
92        Self { init }
93    }
94}
95
96impl<R> Tokenizer<R> for Operator {
97    fn lex(&mut self, cursor: &mut Cursor<R>, start_pos: Position) -> Result<Token, Error>
98    where
99        R: Read,
100    {
101        let _timer = BoaProfiler::global().start_event("Operator", "Lexing");
102
103        match self.init {
104            b'*' => op!(cursor, start_pos, Ok(Punctuator::AssignMul), Ok(Punctuator::Mul), {
105                Some(b'*') => vop!(cursor, Ok(Punctuator::AssignPow), Ok(Punctuator::Exp))
106            }),
107            b'+' => op!(cursor, start_pos, Ok(Punctuator::AssignAdd), Ok(Punctuator::Add), {
108                Some(b'+') => Ok(Punctuator::Inc)
109            }),
110            b'-' => op!(cursor, start_pos, Ok(Punctuator::AssignSub), Ok(Punctuator::Sub), {
111                Some(b'-') => {
112                    Ok(Punctuator::Dec)
113                }
114            }),
115            b'%' => op!(
116                cursor,
117                start_pos,
118                Ok(Punctuator::AssignMod),
119                Ok(Punctuator::Mod)
120            ),
121            b'|' => op!(cursor, start_pos, Ok(Punctuator::AssignOr), Ok(Punctuator::Or), {
122                Some(b'|') => vop!(cursor, Ok(Punctuator::AssignBoolOr), Ok(Punctuator::BoolOr))
123            }),
124            b'&' => op!(cursor, start_pos, Ok(Punctuator::AssignAnd), Ok(Punctuator::And), {
125                Some(b'&') => vop!(cursor, Ok(Punctuator::AssignBoolAnd), Ok(Punctuator::BoolAnd))
126            }),
127            b'?' => match cursor.peek()? {
128                Some(b'?') => {
129                    let _ = cursor.next_byte()?.expect("? vanished");
130                    op!(
131                        cursor,
132                        start_pos,
133                        Ok(Punctuator::AssignCoalesce),
134                        Ok(Punctuator::Coalesce)
135                    )
136                }
137                _ => Ok(Token::new(
138                    TokenKind::Punctuator(Punctuator::Question),
139                    Span::new(start_pos, cursor.pos()),
140                )),
141            },
142            b'^' => op!(
143                cursor,
144                start_pos,
145                Ok(Punctuator::AssignXor),
146                Ok(Punctuator::Xor)
147            ),
148            b'=' => op!(cursor, start_pos, if cursor.next_is(b'=')? {
149                Ok(Punctuator::StrictEq)
150            } else {
151                Ok(Punctuator::Eq)
152            }, Ok(Punctuator::Assign), {
153                Some(b'>') => {
154                    Ok(Punctuator::Arrow)
155                }
156            }),
157            b'<' => {
158                op!(cursor, start_pos, Ok(Punctuator::LessThanOrEq), Ok(Punctuator::LessThan), {
159                    Some(b'<') => vop!(cursor, Ok(Punctuator::AssignLeftSh), Ok(Punctuator::LeftSh))
160                })
161            }
162            b'>' => {
163                op!(cursor, start_pos, Ok(Punctuator::GreaterThanOrEq), Ok(Punctuator::GreaterThan), {
164                    Some(b'>') => vop!(cursor, Ok(Punctuator::AssignRightSh), Ok(Punctuator::RightSh), {
165                        Some(b'>') => vop!(cursor, Ok(Punctuator::AssignURightSh), Ok(Punctuator::URightSh))
166                    })
167                })
168            }
169            b'!' => op!(
170                cursor,
171                start_pos,
172                vop!(cursor, Ok(Punctuator::StrictNotEq), Ok(Punctuator::NotEq)),
173                Ok(Punctuator::Not)
174            ),
175            b'~' => Ok(Token::new(
176                Punctuator::Neg.into(),
177                Span::new(start_pos, cursor.pos()),
178            )),
179            op => unimplemented!("operator {}", op),
180        }
181    }
182}