mirror of https://github.com/boa-dev/boa.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
98 lines
3.6 KiB
98 lines
3.6 KiB
//! This module implements lexing for template literals used in the JavaScript programing language. |
|
|
|
use super::{Cursor, Error, Tokenizer}; |
|
use crate::{ |
|
profiler::BoaProfiler, |
|
syntax::lexer::string::{unescape_string, StringTerminator}, |
|
syntax::{ |
|
ast::{Position, Span}, |
|
lexer::{Token, TokenKind}, |
|
}, |
|
}; |
|
use std::convert::TryFrom; |
|
use std::io::{self, ErrorKind, Read}; |
|
|
|
/// Template literal lexing. |
|
/// |
|
/// Expects: Initial ` to already be consumed by cursor. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// - [MDN documentation][mdn] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-template-literals |
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals |
|
#[derive(Debug, Clone, Copy)] |
|
pub(super) struct TemplateLiteral; |
|
|
|
impl<R> Tokenizer<R> for TemplateLiteral { |
|
fn lex(&mut self, cursor: &mut Cursor<R>, start_pos: Position) -> Result<Token, Error> |
|
where |
|
R: Read, |
|
{ |
|
let _timer = BoaProfiler::global().start_event("TemplateLiteral", "Lexing"); |
|
|
|
let mut buf = Vec::new(); |
|
loop { |
|
let next_chr = char::try_from(cursor.next_char()?.ok_or_else(|| { |
|
Error::from(io::Error::new( |
|
ErrorKind::UnexpectedEof, |
|
"unterminated template literal", |
|
)) |
|
})?) |
|
.unwrap(); |
|
match next_chr { |
|
'`' => { |
|
let raw = String::from_utf16_lossy(buf.as_slice()); |
|
let (cooked, _) = unescape_string( |
|
&mut Cursor::with_position(raw.as_bytes(), start_pos), |
|
start_pos, |
|
StringTerminator::End, |
|
true, |
|
)?; |
|
return Ok(Token::new( |
|
TokenKind::template_no_substitution(raw, cooked), |
|
Span::new(start_pos, cursor.pos()), |
|
)); |
|
} |
|
'$' if cursor.peek()? == Some(b'{') => { |
|
let _ = cursor.next_byte()?; |
|
let raw = String::from_utf16_lossy(buf.as_slice()); |
|
let (cooked, _) = unescape_string( |
|
&mut Cursor::with_position(raw.as_bytes(), start_pos), |
|
start_pos, |
|
StringTerminator::End, |
|
true, |
|
)?; |
|
return Ok(Token::new( |
|
TokenKind::template_middle(raw, cooked), |
|
Span::new(start_pos, cursor.pos()), |
|
)); |
|
} |
|
'\\' => { |
|
let escape = cursor.peek()?.ok_or_else(|| { |
|
Error::from(io::Error::new( |
|
ErrorKind::UnexpectedEof, |
|
"unterminated escape sequence in literal", |
|
)) |
|
})?; |
|
buf.push('\\' as u16); |
|
match escape { |
|
b'`' | b'$' | b'\\' => buf.push(cursor.next_byte()?.unwrap() as u16), |
|
_ => continue, |
|
} |
|
} |
|
next_ch => { |
|
if next_ch.len_utf16() == 1 { |
|
buf.push(next_ch as u16); |
|
} else { |
|
let mut code_point_bytes_buf = [0u16; 2]; |
|
let code_point_bytes = next_ch.encode_utf16(&mut code_point_bytes_buf); |
|
|
|
buf.extend(code_point_bytes.iter()); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
}
|
|
|