use std::marker::PhantomData;
use std::ptr::NonNull;
use crate::ast_names::{AstName, AstNameTable, LexemeType};
use crate::location::{Location, Position};
mod string;
mod token;
pub(crate) use string::{fixup_string_bytes, multiline_string_bytes};
pub use token::{LexedToken, QuoteStyle, ReservedWord, Token, TokenKind};
#[derive(Clone)]
pub struct Lexer<'source, 'ast, 'name, 'names>
where
'name: 'ast,
{
source: &'source [u8],
offset: usize,
line: u32,
line_offset: usize,
current: LexedToken<'source, 'ast>,
previous_location: Location,
brace_stack: Vec<BraceType>,
names: NonNull<AstNameTable<'name>>,
read_names: bool,
_names: PhantomData<&'names mut AstNameTable<'name>>,
}
#[derive(Clone, Copy)]
struct LexerSnapshot<'source, 'ast> {
offset: usize,
line: u32,
line_offset: usize,
current: LexedToken<'source, 'ast>,
previous_location: Location,
brace_stack_size: usize,
brace_type: BraceType,
}
impl<'source, 'ast, 'name, 'names> Lexer<'source, 'ast, 'name, 'names>
where
'name: 'ast,
{
pub fn new(source: &'source [u8], names: &'names mut AstNameTable<'name>) -> Self {
Self::with_position(source, names, Position::zero())
}
pub fn with_position(
source: &'source [u8],
names: &'names mut AstNameTable<'name>,
start_position: Position,
) -> Self {
let current = LexedToken {
token: Token::Eof,
start: 0,
end: 0,
location: Location::new(start_position, start_position),
};
Self {
source,
offset: 0,
line: start_position.line,
line_offset: 0usize.wrapping_sub(start_position.column as usize),
current,
previous_location: Location::new(start_position, start_position),
brace_stack: Vec::new(),
names: NonNull::from(names),
read_names: true,
_names: PhantomData,
}
}
pub fn source(&self) -> &'source [u8] {
self.source
}
pub(crate) fn interpolation_brace_is_open(&self) -> bool {
self.brace_stack.last() == Some(&BraceType::InterpolatedString)
}
pub fn set_read_names(&mut self, read_names: bool) {
self.read_names = read_names;
}
pub fn next_token_span(&mut self, skip_comments: bool) -> (Token<'source, 'ast>, usize, usize) {
self.advance_lexeme(skip_comments);
(self.current.token, self.current.start, self.current.end)
}
pub fn next_lexeme(&mut self, skip_comments: bool) -> LexedToken<'source, 'ast> {
self.advance_lexeme(skip_comments);
self.current
}
pub fn advance_lexeme(&mut self, skip_comments: bool) {
self.advance_lexeme_with_previous(skip_comments, true);
}
pub(crate) fn advance_lexeme_with_previous(
&mut self,
skip_comments: bool,
mut update_previous_location: bool,
) {
loop {
while is_space(self.peekch()) {
self.consume_any();
}
if update_previous_location {
self.previous_location = self.current.location;
}
self.current = self.read_next();
update_previous_location = false;
if skip_comments
&& matches!(
self.current.token,
Token::Comment(_) | Token::BlockComment(_)
)
{
continue;
}
return;
}
}
pub fn current_lexeme(&self) -> LexedToken<'source, 'ast> {
self.current
}
pub fn current_token(&self) -> Token<'source, 'ast> {
self.current.token
}
pub fn current_location(&self) -> Location {
self.current.location
}
pub fn current_span(&self) -> (usize, usize) {
(self.current.start, self.current.end)
}
pub fn previous_location(&self) -> Location {
self.previous_location
}
pub fn lookahead_token(&mut self, skip_comments: bool) -> Token<'source, 'ast> {
self.lookahead_lexeme(skip_comments).token
}
pub fn lookahead_nth_token(&mut self, n: usize, skip_comments: bool) -> Token<'source, 'ast> {
self.lookahead_nth_lexeme(n, skip_comments).token
}
pub fn lookahead_lexeme(&mut self, skip_comments: bool) -> LexedToken<'source, 'ast> {
let snapshot = self.snapshot();
let result = self.next_lexeme(skip_comments);
self.restore(snapshot);
result
}
pub fn lookahead_nth_lexeme(
&mut self,
n: usize,
skip_comments: bool,
) -> LexedToken<'source, 'ast> {
debug_assert!(n > 0);
let snapshot = self.snapshot();
let mut result = self.next_lexeme(skip_comments);
for _ in 1..n {
result = self.next_lexeme(skip_comments);
}
self.restore(snapshot);
result
}
fn snapshot(&self) -> LexerSnapshot<'source, 'ast> {
LexerSnapshot {
offset: self.offset,
line: self.line,
line_offset: self.line_offset,
current: self.current,
previous_location: self.previous_location,
brace_stack_size: self.brace_stack.len(),
brace_type: self
.brace_stack
.last()
.copied()
.unwrap_or(BraceType::Normal),
}
}
fn restore(&mut self, snapshot: LexerSnapshot<'source, 'ast>) {
self.offset = snapshot.offset;
self.line = snapshot.line;
self.line_offset = snapshot.line_offset;
self.current = snapshot.current;
self.previous_location = snapshot.previous_location;
if self.brace_stack.len() < snapshot.brace_stack_size {
self.brace_stack.push(snapshot.brace_type);
} else if self.brace_stack.len() > snapshot.brace_stack_size {
self.brace_stack.pop();
}
}
#[inline(always)]
fn peekch(&self) -> u8 {
if self.offset < self.source.len() {
unsafe { *self.source.get_unchecked(self.offset) }
} else {
0
}
}
#[inline(always)]
fn peekch_n(&self, lookahead: usize) -> u8 {
let offset = self.offset + lookahead;
if offset < self.source.len() {
unsafe { *self.source.get_unchecked(offset) }
} else {
0
}
}
#[inline(always)]
fn position(&self) -> Position {
Position::new(self.line, self.offset.wrapping_sub(self.line_offset) as u32)
}
#[inline(always)]
fn consume(&mut self) {
debug_assert!(self.peekch() != b'\n');
self.offset += 1;
}
#[inline(always)]
fn consume_any(&mut self) {
if is_newline(self.peekch()) {
self.line += 1;
self.line_offset = self.offset + 1;
}
self.offset += 1;
}
fn names_mut(&mut self) -> &mut AstNameTable<'name> {
unsafe { self.names.as_mut() }
}
fn name_with_type_bytes(&mut self, name: &[u8]) -> (AstName<'ast>, LexemeType) {
let (name, kind) = if self.read_names {
self.names_mut().get_or_add_bytes_with_type(name)
} else {
self.names_mut()
.get_bytes_with_type(name)
.unwrap_or((AstName::empty_key(), LexemeType::Name))
};
(name.narrow(), kind)
}
pub(crate) fn intern_name_bytes(&mut self, name: &[u8]) -> AstName<'ast> {
let name = if self.read_names {
self.names_mut().get_or_add_bytes(name)
} else {
self.names_mut()
.get_bytes(name)
.unwrap_or_else(AstName::empty_key)
};
name.narrow()
}
#[inline(always)]
fn lexed_from_range(
&self,
start: Position,
start_offset: usize,
end_offset: usize,
token: Token<'source, 'ast>,
) -> LexedToken<'source, 'ast> {
LexedToken {
token,
start: start_offset,
end: end_offset,
location: Location::new(start, self.position()),
}
}
fn read_comment_body(&mut self) -> LexedToken<'source, 'ast> {
let start = self.position();
let start_offset = self.offset;
debug_assert!(self.peekch() == b'-' && self.peekch_n(1) == b'-');
self.consume();
self.consume();
let body_offset = self.offset;
if self.peekch() == b'[' {
let sep = self.skip_long_separator();
if sep >= 0 {
return self.read_long_string(
start,
start_offset,
sep as usize,
LongTokenKind::BlockComment,
);
}
}
while self.peekch() != 0 && self.peekch() != b'\r' && !is_newline(self.peekch()) {
self.consume();
}
self.lexed_from_range(
start,
start_offset,
self.offset,
Token::Comment(&self.source[body_offset..self.offset]),
)
}
fn skip_long_separator(&mut self) -> i32 {
let start = self.peekch();
debug_assert!(start == b'[' || start == b']');
self.consume();
let mut count = 0i32;
while self.peekch() == b'=' {
self.consume();
count += 1;
}
if start == self.peekch() {
count
} else {
-count - 1
}
}
fn read_long_string(
&mut self,
start: Position,
start_offset: usize,
sep: usize,
kind: LongTokenKind,
) -> LexedToken<'source, 'ast> {
debug_assert!(self.peekch() == b'[');
self.consume();
let body_offset = self.offset;
while self.peekch() != 0 {
if self.peekch() == b']' {
if self.skip_long_separator() == sep as i32 {
debug_assert!(self.peekch() == b']');
self.consume();
let end_offset = self.offset - sep - 2;
let token = match kind {
LongTokenKind::RawString => Token::RawString {
value: &self.source[body_offset..end_offset],
block_depth: sep,
},
LongTokenKind::BlockComment => Token::BlockComment(sep),
};
return self.lexed_from_range(start, start_offset, self.offset, token);
}
} else {
self.consume_any();
}
}
let token = match kind {
LongTokenKind::RawString => Token::BrokenString,
LongTokenKind::BlockComment => Token::BrokenComment,
};
self.lexed_from_range(start, start_offset, self.offset, token)
}
fn read_backslash_in_string(&mut self) {
debug_assert!(self.peekch() == b'\\');
self.consume();
match self.peekch() {
b'\r' => {
self.consume();
if self.peekch() == b'\n' {
self.consume_any();
}
}
0 => {}
b'z' => {
self.consume();
while is_space(self.peekch()) {
self.consume_any();
}
}
_ => self.consume_any(),
}
}
fn read_quoted_string(&mut self) -> LexedToken<'source, 'ast> {
let start = self.position();
let start_offset = self.offset;
let delimiter = self.peekch();
debug_assert!(delimiter == b'\'' || delimiter == b'"');
self.consume();
let body_offset = self.offset;
while self.peekch() != delimiter {
match self.peekch() {
0 | b'\r' | b'\n' => {
return self.lexed_from_range(
start,
start_offset,
self.offset,
Token::BrokenString,
);
}
b'\\' => self.read_backslash_in_string(),
_ => self.consume(),
}
}
self.consume();
self.lexed_from_range(
start,
start_offset,
self.offset,
Token::QuotedString {
value: &self.source[body_offset..self.offset - 1],
quote_style: QuoteStyle::from_delimiter(delimiter),
},
)
}
fn read_interpolated_string_begin(&mut self) -> LexedToken<'source, 'ast> {
debug_assert!(self.peekch() == b'`');
let start = self.position();
let start_offset = self.offset;
self.consume();
self.read_interpolated_string_section(start, start_offset, InterpolatedSectionKind::Begin)
}
fn read_interpolated_string_section(
&mut self,
start: Position,
start_offset: usize,
kind: InterpolatedSectionKind,
) -> LexedToken<'source, 'ast> {
let body_offset = self.offset;
while self.peekch() != b'`' {
match self.peekch() {
0 | b'\r' | b'\n' => {
return self.lexed_from_range(
start,
start_offset,
self.offset,
Token::BrokenString,
);
}
b'\\' => {
if self.peekch_n(1) == b'u' && self.peekch_n(2) == b'{' {
self.consume();
self.consume();
self.consume();
} else {
self.read_backslash_in_string();
}
}
b'{' => {
self.brace_stack.push(BraceType::InterpolatedString);
if self.peekch_n(1) == b'{' {
let token =
Token::BrokenInterpDoubleBrace(&self.source[body_offset..self.offset]);
self.consume();
self.consume();
return self.lexed_from_range(start, start_offset, self.offset, token);
}
self.consume();
let token = match kind {
InterpolatedSectionKind::Begin => {
Token::InterpStringBegin(&self.source[body_offset..self.offset - 1])
}
InterpolatedSectionKind::Mid => {
Token::InterpStringMid(&self.source[body_offset..self.offset - 1])
}
};
return self.lexed_from_range(start, start_offset, self.offset, token);
}
_ => self.consume(),
}
}
self.consume();
let token = match kind {
InterpolatedSectionKind::Begin => {
Token::InterpStringSimple(&self.source[body_offset..self.offset - 1])
}
InterpolatedSectionKind::Mid => {
Token::InterpStringEnd(&self.source[body_offset..self.offset - 1])
}
};
self.lexed_from_range(start, start_offset, self.offset, token)
}
#[inline(always)]
fn read_number(&mut self, start: Position, start_offset: usize) -> LexedToken<'source, 'ast> {
debug_assert!(is_digit(self.peekch()));
loop {
self.consume();
let ch = self.peekch();
if !(is_digit(ch) || ch == b'.' || ch == b'_') {
break;
}
}
if matches!(self.peekch(), b'e' | b'E') {
self.consume();
if matches!(self.peekch(), b'+' | b'-') {
self.consume();
}
}
while is_alpha(self.peekch()) || is_digit(self.peekch()) || self.peekch() == b'_' {
self.consume();
}
self.lexed_from_range(
start,
start_offset,
self.offset,
Token::Number(&self.source[start_offset..self.offset]),
)
}
#[inline(always)]
fn read_name(&mut self) -> (AstName<'ast>, LexemeType) {
debug_assert!(is_alpha(self.peekch()) || self.peekch() == b'_');
let start_offset = self.offset;
loop {
self.consume();
let ch = self.peekch();
if !(is_alpha(ch) || is_digit(ch) || ch == b'_') {
break;
}
}
self.name_with_type_bytes(&self.source[start_offset..self.offset])
}
fn read_attribute_name(&mut self) -> AstName<'ast> {
debug_assert!(is_alpha(self.peekch()) || self.peekch() == b'_');
let start_offset = self.offset;
loop {
self.consume();
let ch = self.peekch();
if !(is_alpha(ch) || is_digit(ch) || ch == b'_') {
break;
}
}
self.intern_name_bytes(&self.source[start_offset..self.offset])
}
fn read_utf8_error(&mut self) -> LexedToken<'source, 'ast> {
let start = self.position();
let start_offset = self.offset;
let first = self.peekch();
let (size, mut codepoint) = if (first & 0b1000_0000) == 0b0000_0000 {
(1usize, u32::from(first & 0x7f))
} else if (first & 0b1110_0000) == 0b1100_0000 {
(2usize, u32::from(first & 0b1_1111))
} else if (first & 0b1111_0000) == 0b1110_0000 {
(3usize, u32::from(first & 0b1111))
} else if (first & 0b1111_1000) == 0b1111_0000 {
(4usize, u32::from(first & 0b111))
} else {
self.consume();
return self.lexed_from_range(
start,
start_offset,
self.offset,
Token::BrokenUnicode { codepoint: 0 },
);
};
self.consume();
for _ in 1..size {
if (self.peekch() & 0b1100_0000) != 0b1000_0000 {
return self.lexed_from_range(
start,
start_offset,
self.offset,
Token::BrokenUnicode { codepoint: 0 },
);
}
codepoint = (codepoint << 6) | u32::from(self.peekch() & 0b0011_1111);
self.consume();
}
self.lexed_from_range(
start,
start_offset,
self.offset,
Token::BrokenUnicode { codepoint },
)
}
fn read_next(&mut self) -> LexedToken<'source, 'ast> {
let start = self.position();
let start_offset = self.offset;
match self.peekch() {
0 => self.lexed_from_range(start, start_offset, start_offset, Token::Eof),
b'-' => {
if self.peekch_n(1) == b'>' {
self.consume();
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::SkinnyArrow)
} else if self.peekch_n(1) == b'=' {
self.consume();
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::MinusEqual)
} else if self.peekch_n(1) == b'-' {
self.read_comment_body()
} else {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::Minus)
}
}
b'[' => {
let sep = self.skip_long_separator();
if sep >= 0 {
self.read_long_string(
start,
start_offset,
sep as usize,
LongTokenKind::RawString,
)
} else if sep == -1 {
self.lexed_from_range(start, start_offset, self.offset, Token::LeftBracket)
} else {
self.lexed_from_range(start, start_offset, self.offset, Token::BrokenString)
}
}
b'{' => {
self.consume();
if !self.brace_stack.is_empty() {
self.brace_stack.push(BraceType::Normal);
}
self.lexed_from_range(start, start_offset, self.offset, Token::LeftBrace)
}
b'}' => {
self.consume();
let Some(brace) = self.brace_stack.pop() else {
return self.lexed_from_range(
start,
start_offset,
self.offset,
Token::RightBrace,
);
};
if brace != BraceType::InterpolatedString {
return self.lexed_from_range(
start,
start_offset,
self.offset,
Token::RightBrace,
);
}
self.read_interpolated_string_section(
start,
start_offset,
InterpolatedSectionKind::Mid,
)
}
b'=' => {
self.consume();
if self.peekch() == b'=' {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::EqualEqual)
} else {
self.lexed_from_range(start, start_offset, self.offset, Token::Equal)
}
}
b'<' => {
self.consume();
if self.peekch() == b'=' {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::LessEqual)
} else {
self.lexed_from_range(start, start_offset, self.offset, Token::Less)
}
}
b'>' => {
self.consume();
if self.peekch() == b'=' {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::GreaterEqual)
} else {
self.lexed_from_range(start, start_offset, self.offset, Token::Greater)
}
}
b'~' => {
self.consume();
if self.peekch() == b'=' {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::TildeEqual)
} else {
self.lexed_from_range(start, start_offset, self.offset, Token::Error)
}
}
b'"' | b'\'' => self.read_quoted_string(),
b'`' => self.read_interpolated_string_begin(),
b'.' => {
self.consume();
if self.peekch() == b'.' {
self.consume();
if self.peekch() == b'.' {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::Ellipsis)
} else if self.peekch() == b'=' {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::DotDotEqual)
} else {
self.lexed_from_range(start, start_offset, self.offset, Token::DotDot)
}
} else if is_digit(self.peekch()) {
self.read_number(start, start_offset)
} else {
self.lexed_from_range(start, start_offset, self.offset, Token::Dot)
}
}
b'+' => {
self.consume();
if self.peekch() == b'=' {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::PlusEqual)
} else {
self.lexed_from_range(start, start_offset, self.offset, Token::Plus)
}
}
b'/' => {
self.consume();
match self.peekch() {
b'=' => {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::SlashEqual)
}
b'/' => {
self.consume();
if self.peekch() == b'=' {
self.consume();
self.lexed_from_range(
start,
start_offset,
self.offset,
Token::SlashSlashEqual,
)
} else {
self.lexed_from_range(
start,
start_offset,
self.offset,
Token::SlashSlash,
)
}
}
_ => self.lexed_from_range(start, start_offset, self.offset, Token::Slash),
}
}
b'*' => {
self.consume();
if self.peekch() == b'=' {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::StarEqual)
} else {
self.lexed_from_range(start, start_offset, self.offset, Token::Star)
}
}
b'%' => {
self.consume();
if self.peekch() == b'=' {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::PercentEqual)
} else {
self.lexed_from_range(start, start_offset, self.offset, Token::Percent)
}
}
b'^' => {
self.consume();
if self.peekch() == b'=' {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::CaretEqual)
} else {
self.lexed_from_range(start, start_offset, self.offset, Token::Caret)
}
}
b':' => {
self.consume();
if self.peekch() == b':' {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::DoubleColon)
} else {
self.lexed_from_range(start, start_offset, self.offset, Token::Colon)
}
}
b'(' => {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::LeftParen)
}
b')' => {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::RightParen)
}
b']' => {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::RightBracket)
}
b';' => {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::Semicolon)
}
b',' => {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::Comma)
}
b'#' => {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::Hash)
}
b'!' => {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::Bang)
}
b'?' => {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::Question)
}
b'&' => {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::Ampersand)
}
b'|' => {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::Pipe)
}
b'@' => {
if self.peekch_n(1) == b'[' {
self.consume();
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::AttributeOpen)
} else {
self.consume();
let token = if is_alpha(self.peekch()) || self.peekch() == b'_' {
Token::Attribute(self.read_attribute_name())
} else {
Token::Attribute(AstName::from_static(""))
};
self.lexed_from_range(start, start_offset, self.offset, token)
}
}
byte if is_digit(byte) => self.read_number(start, start_offset),
byte if is_alpha(byte) || byte == b'_' => {
let (name, kind) = self.read_name();
let token = ReservedWord::from_lexeme_type(kind)
.map(Token::Reserved)
.unwrap_or(Token::Ident(name));
self.lexed_from_range(start, start_offset, self.offset, token)
}
byte if (byte & 0x80) != 0 => self.read_utf8_error(),
_ => {
self.consume();
self.lexed_from_range(start, start_offset, self.offset, Token::Error)
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum BraceType {
InterpolatedString,
Normal,
}
#[derive(Clone, Copy)]
enum LongTokenKind {
RawString,
BlockComment,
}
#[derive(Clone, Copy)]
enum InterpolatedSectionKind {
Begin,
Mid,
}
fn is_alpha(ch: u8) -> bool {
(ch | b' ').wrapping_sub(b'a') < 26
}
fn is_digit(ch: u8) -> bool {
ch.wrapping_sub(b'0') < 10
}
fn is_hex_digit(ch: u8) -> bool {
ch.wrapping_sub(b'0') < 10 || (ch | b' ').wrapping_sub(b'a') < 6
}
fn is_newline(ch: u8) -> bool {
ch == b'\n'
}
fn is_space(ch: u8) -> bool {
matches!(ch, b' ' | b'\t' | b'\r' | b'\n' | 0x0b | 0x0c)
}
fn unescape(ch: u8) -> u8 {
match ch {
b'a' => 0x07,
b'b' => 0x08,
b'f' => 0x0c,
b'n' => b'\n',
b'r' => b'\r',
b't' => b'\t',
b'v' => 0x0b,
other => other,
}
}
fn hex_value(ch: u8) -> Option<u8> {
match ch {
b'0'..=b'9' => Some(ch - b'0'),
b'a'..=b'f' => Some(ch - b'a' + 10),
b'A'..=b'F' => Some(ch - b'A' + 10),
_ => None,
}
}
fn to_utf8(data: &mut [u8], code: u32) -> Option<usize> {
match code {
0x0000..=0x007f => {
data[0] = code as u8;
Some(1)
}
0x0080..=0x07ff => {
data[0] = (0xc0 | (code >> 6)) as u8;
data[1] = (0x80 | (code & 0x3f)) as u8;
Some(2)
}
0x0800..=0xffff => {
data[0] = (0xe0 | (code >> 12)) as u8;
data[1] = (0x80 | ((code >> 6) & 0x3f)) as u8;
data[2] = (0x80 | (code & 0x3f)) as u8;
Some(3)
}
0x10000..=0x10ffff => {
data[0] = (0xf0 | (code >> 18)) as u8;
data[1] = (0x80 | ((code >> 12) & 0x3f)) as u8;
data[2] = (0x80 | ((code >> 6) & 0x3f)) as u8;
data[3] = (0x80 | (code & 0x3f)) as u8;
Some(4)
}
_ => None,
}
}