#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TokKind {
Whitespace,
Newline,
Comment,
BlockComment,
Ident,
Integer,
BinInt,
OctInt,
HexInt,
Float,
Float32,
Char,
StringDelimOpen,
StringDelimClose,
CmdDelimOpen,
CmdDelimClose,
StringContent,
StringPrefix,
StringSuffix,
FunctionKw,
MacroKw,
EndKw,
IfKw,
ElseifKw,
ElseKw,
BeginKw,
TrueKw,
FalseKw,
WhileKw,
ForKw,
DoKw,
LetKw,
QuoteKw,
TryKw,
CatchKw,
FinallyKw,
StructKw,
MutableKw,
ModuleKw,
BaremoduleKw,
ReturnKw,
BreakKw,
ContinueKw,
ConstKw,
GlobalKw,
LocalKw,
ImportKw,
UsingKw,
ExportKw,
WhereKw,
Eq,
Plus,
Minus,
Star,
Slash,
Backslash,
SlashSlash,
Caret,
Percent,
StarStar,
MinusMinus,
EqEq,
NotEq,
EqEqEq,
NotEqEq,
Lt,
Le,
Gt,
Ge,
AndAnd,
OrOr,
Colon,
ColonColon,
Subtype,
Supertype,
Arrow,
LongArrow,
LeftRightArrow,
FatArrow,
Shl,
Shr,
UShr,
PlusEq,
MinusEq,
StarEq,
SlashEq,
BackslashEq,
SlashSlashEq,
CaretEq,
PercentEq,
PipeEq,
AmpEq,
Dot,
DotDot,
DotDotDot,
PipeGt,
PipeLt,
Bang,
Amp,
Pipe,
Tilde,
Question,
Transpose,
DotPlus,
DotMinus,
DotStar,
DotStarStar,
DotMinusMinus,
DotSlash,
DotBackslash,
DotSlashSlash,
DotCaret,
DotPercent,
DotEq,
DotEqEq,
DotNotEq,
DotEqEqEq,
DotNotEqEq,
DotLt,
DotLe,
DotGt,
DotGe,
DotSubtype,
DotSupertype,
DotFatArrow,
DotLongArrow,
DotPipeGt,
DotTilde,
DotAndAnd,
DotOrOr,
DotAmp,
DotPipe,
DotPlusEq,
DotMinusEq,
DotStarEq,
DotSlashEq,
DotBackslashEq,
DotSlashSlashEq,
DotCaretEq,
DotPercentEq,
UniArrow,
UniComparison,
UniColon,
UniPlus,
UniTimes,
UniPower,
UniAssign,
UniRadical,
LParen,
RParen,
LBracket,
RBracket,
LBrace,
RBrace,
Comma,
Semicolon,
At,
Dollar,
Unknown,
}
impl TokKind {
pub(crate) fn is_trivia(self) -> bool {
matches!(
self,
TokKind::Whitespace | TokKind::Newline | TokKind::Comment | TokKind::BlockComment
)
}
pub(crate) fn is_keyword(self) -> bool {
matches!(
self,
TokKind::FunctionKw
| TokKind::EndKw
| TokKind::IfKw
| TokKind::ElseifKw
| TokKind::ElseKw
| TokKind::BeginKw
| TokKind::TrueKw
| TokKind::FalseKw
| TokKind::WhileKw
| TokKind::ForKw
| TokKind::DoKw
| TokKind::LetKw
| TokKind::QuoteKw
| TokKind::TryKw
| TokKind::CatchKw
| TokKind::FinallyKw
| TokKind::StructKw
| TokKind::MutableKw
| TokKind::ModuleKw
| TokKind::BaremoduleKw
| TokKind::ReturnKw
| TokKind::BreakKw
| TokKind::ContinueKw
| TokKind::ConstKw
| TokKind::GlobalKw
| TokKind::LocalKw
| TokKind::ImportKw
| TokKind::UsingKw
| TokKind::ExportKw
| TokKind::WhereKw
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Token {
pub(crate) kind: TokKind,
pub(crate) text: String,
pub(crate) start: usize,
pub(crate) end: usize,
}
pub(crate) fn lex(input: &str) -> Vec<Token> {
Lexer::new(input).run()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
Str {
triple: bool,
raw: bool,
prefixed: bool,
},
Cmd {
triple: bool,
raw: bool,
prefixed: bool,
},
Interp { depth: usize },
}
struct Lexer<'a> {
input: &'a str,
bytes: &'a [u8],
pos: usize,
tokens: Vec<Token>,
mode_stack: Vec<Mode>,
}
impl<'a> Lexer<'a> {
fn new(input: &'a str) -> Self {
Self {
input,
bytes: input.as_bytes(),
pos: 0,
tokens: Vec::new(),
mode_stack: Vec::new(),
}
}
fn run(mut self) -> Vec<Token> {
while self.pos < self.bytes.len() {
self.next_token();
}
self.tokens
}
fn peek(&self, ahead: usize) -> Option<u8> {
self.bytes.get(self.pos + ahead).copied()
}
fn push(&mut self, kind: TokKind, start: usize, end: usize) {
self.tokens.push(Token {
kind,
text: self.input[start..end].to_string(),
start,
end,
});
}
fn push_op(&mut self, kind: TokKind, start: usize) {
if op_takes_suffix(kind) {
while self.pos < self.bytes.len() {
let c = self.char_at(self.pos);
if is_op_suffix_char(c) {
self.pos += c.len_utf8();
} else {
break;
}
}
}
self.push(kind, start, self.pos);
}
fn next_token(&mut self) {
if matches!(
self.mode_stack.last(),
Some(Mode::Str { .. } | Mode::Cmd { .. })
) {
self.lex_in_string_mode();
return;
}
let start = self.pos;
let b = self.bytes[self.pos];
if matches!(self.mode_stack.last(), Some(Mode::Interp { .. })) {
if b == b'(' {
self.pos += 1;
self.push(TokKind::LParen, start, self.pos);
if let Some(Mode::Interp { depth }) = self.mode_stack.last_mut() {
*depth += 1;
}
return;
}
if b == b')' {
self.pos += 1;
self.push(TokKind::RParen, start, self.pos);
if matches!(self.mode_stack.last(), Some(Mode::Interp { depth }) if *depth == 1) {
self.mode_stack.pop();
} else if let Some(Mode::Interp { depth }) = self.mode_stack.last_mut() {
*depth -= 1;
}
return;
}
}
match b {
b' ' | b'\t' => self.lex_whitespace(start),
b'\r' | b'\n' => self.lex_newline(start),
b'#' => self.lex_comment(start),
b'"' => self.lex_open_string(start, false),
b'`' => self.lex_open_cmd(start, false),
b'\'' if self.prev_ends_value() || self.prev_is_dot() => {
self.pos += 1;
self.push_op(TokKind::Transpose, start);
}
b'\'' => self.lex_char_literal(start),
b'0'..=b'9' => self.lex_number(start),
b'.' if self.peek(1).is_some_and(|c| c.is_ascii_digit()) => self.lex_number(start),
_ => {
if is_ident_start(self.char_at(self.pos)) {
self.lex_ident_or_keyword(start);
} else {
self.lex_operator_or_unknown(start);
}
}
}
}
fn char_at(&self, at: usize) -> char {
self.input[at..].chars().next().unwrap_or('\0')
}
fn lex_whitespace(&mut self, start: usize) {
while matches!(self.peek(0), Some(b' ' | b'\t')) {
self.pos += 1;
}
self.push(TokKind::Whitespace, start, self.pos);
}
fn lex_newline(&mut self, start: usize) {
match self.peek(0) {
Some(b'\r') if self.peek(1) == Some(b'\n') => self.pos += 2,
_ => self.pos += 1,
}
self.push(TokKind::Newline, start, self.pos);
}
fn lex_comment(&mut self, start: usize) {
if self.peek(1) == Some(b'=') {
self.lex_block_comment(start);
return;
}
self.pos += 1;
while !matches!(self.peek(0), Some(b'\n' | b'\r') | None) {
self.pos += 1;
}
self.push(TokKind::Comment, start, self.pos);
}
fn lex_block_comment(&mut self, start: usize) {
self.pos += 2; let mut depth = 1usize;
while depth > 0 && self.pos < self.bytes.len() {
match (self.peek(0), self.peek(1)) {
(Some(b'#'), Some(b'=')) => {
self.pos += 2;
depth += 1;
}
(Some(b'='), Some(b'#')) => {
self.pos += 2;
depth -= 1;
}
_ => self.pos += 1,
}
}
self.push(TokKind::BlockComment, start, self.pos);
}
fn lex_open_string(&mut self, start: usize, prefixed: bool) {
let triple = self.peek(1) == Some(b'"') && self.peek(2) == Some(b'"');
self.pos += if triple { 3 } else { 1 };
self.push(TokKind::StringDelimOpen, start, self.pos);
self.mode_stack.push(Mode::Str {
triple,
raw: prefixed,
prefixed,
});
}
fn lex_open_cmd(&mut self, start: usize, prefixed: bool) {
let triple = self.peek(1) == Some(b'`') && self.peek(2) == Some(b'`');
self.pos += if triple { 3 } else { 1 };
self.push(TokKind::CmdDelimOpen, start, self.pos);
self.mode_stack.push(Mode::Cmd {
triple,
raw: prefixed,
prefixed,
});
}
fn lex_in_string_mode(&mut self) {
let frame = *self.mode_stack.last().expect("string mode frame");
let (quote, triple, raw, prefixed) = match frame {
Mode::Str {
triple,
raw,
prefixed,
} => (b'"', triple, raw, prefixed),
Mode::Cmd {
triple,
raw,
prefixed,
} => (b'`', triple, raw, prefixed),
Mode::Interp { .. } => unreachable!("lex_in_string_mode called in interp mode"),
};
let close_kind = if quote == b'"' {
TokKind::StringDelimClose
} else {
TokKind::CmdDelimClose
};
let start = self.pos;
if self.at_close_delim(quote, triple) {
self.pos += if triple { 3 } else { 1 };
self.push(close_kind, start, self.pos);
self.mode_stack.pop();
if prefixed {
self.lex_suffix();
}
return;
}
while self.pos < self.bytes.len() {
if raw && self.peek(0) == Some(b'\\') {
let mut run = 0;
while self.peek(run) == Some(b'\\') {
run += 1;
}
if self.peek(run) == Some(quote) && run % 2 == 1 {
self.pos += run + 1;
} else {
self.pos += run;
}
continue;
}
if self.at_close_delim(quote, triple) {
break;
}
if !raw && self.peek(0) == Some(b'$') && self.is_interp_start(1) {
break;
}
self.consume_body_byte(raw);
}
if self.pos > start {
self.push(TokKind::StringContent, start, self.pos);
}
if self.at_close_delim(quote, triple) {
let delim_start = self.pos;
self.pos += if triple { 3 } else { 1 };
self.push(close_kind, delim_start, self.pos);
self.mode_stack.pop();
if prefixed {
self.lex_suffix();
}
} else if !raw && self.peek(0) == Some(b'$') && self.is_interp_start(1) {
self.lex_interp_sigil();
} else {
self.mode_stack.pop();
}
}
fn at_close_delim(&self, quote: u8, triple: bool) -> bool {
if triple {
self.peek(0) == Some(quote)
&& self.peek(1) == Some(quote)
&& self.peek(2) == Some(quote)
} else {
self.peek(0) == Some(quote)
}
}
fn is_interp_start(&self, ahead: usize) -> bool {
match self.peek(ahead) {
Some(b'(') => true,
Some(_) => is_ident_start(self.char_at(self.pos + ahead)),
None => false,
}
}
fn lex_interp_sigil(&mut self) {
let dollar = self.pos;
self.pos += 1;
self.push(TokKind::Dollar, dollar, self.pos);
if self.peek(0) == Some(b'(') {
let paren = self.pos;
self.pos += 1;
self.push(TokKind::LParen, paren, self.pos);
self.mode_stack.push(Mode::Interp { depth: 1 });
} else {
self.lex_interp_ident();
}
}
fn consume_body_byte(&mut self, raw: bool) {
if !raw && self.peek(0) == Some(b'\\') && self.pos + 1 < self.bytes.len() {
if self.peek(1) == Some(b'\r') && self.peek(2) == Some(b'\n') {
self.pos += 3;
} else {
self.pos += 2;
}
} else {
self.pos += self.char_at(self.pos).len_utf8();
}
}
fn lex_suffix(&mut self) {
let start = self.pos;
if matches!(self.peek(0), Some(c) if c.is_ascii_alphabetic()) {
self.pos += 1;
while matches!(self.peek(0), Some(c) if c.is_ascii_alphanumeric()) {
self.pos += 1;
}
}
if self.pos > start {
self.push(TokKind::StringSuffix, start, self.pos);
}
}
fn prev_ends_value(&self) -> bool {
matches!(
self.tokens.last().map(|t| t.kind),
Some(
TokKind::Ident
| TokKind::Integer
| TokKind::Float
| TokKind::Char
| TokKind::TrueKw
| TokKind::FalseKw
| TokKind::RParen
| TokKind::RBracket
| TokKind::RBrace
| TokKind::StringDelimClose
| TokKind::CmdDelimClose
| TokKind::StringSuffix
| TokKind::Transpose
)
)
}
fn prev_is_dot(&self) -> bool {
self.tokens.last().map(|t| t.kind) == Some(TokKind::Dot)
}
fn lex_char_literal(&mut self, start: usize) {
let mut idx = self.pos + 1;
let mut found = false;
while idx < self.bytes.len() {
match self.bytes[idx] {
b'\'' => {
found = true;
break;
}
b'\\' => {
idx += 1;
if idx < self.bytes.len() {
idx += self.char_at(idx).len_utf8();
}
}
_ => idx += self.char_at(idx).len_utf8(),
}
}
self.pos = if found { idx + 1 } else { idx };
self.push(TokKind::Char, start, self.pos);
}
fn lex_number(&mut self, start: usize) {
if self.peek(0) == Some(b'0') {
match self.peek(1) {
Some(b'x') => {
self.pos += 2;
self.consume_digits(|c| c.is_ascii_hexdigit());
let mut is_float = false;
if self.peek(0) == Some(b'.') {
is_float = true;
self.pos += 1;
self.consume_digits(|c| c.is_ascii_hexdigit());
}
if matches!(self.peek(0), Some(b'p' | b'P')) {
is_float = true;
self.pos += 1;
if matches!(self.peek(0), Some(b'+' | b'-')) {
self.pos += 1;
}
self.consume_digits(|c| c.is_ascii_digit());
}
let kind = if is_float {
TokKind::Float
} else {
TokKind::HexInt
};
self.push(kind, start, self.pos);
return;
}
Some(b'o') => {
self.pos += 2;
self.consume_digits(|c| (b'0'..=b'7').contains(&c));
self.push(TokKind::OctInt, start, self.pos);
return;
}
Some(b'b') => {
self.pos += 2;
self.consume_digits(|c| matches!(c, b'0' | b'1'));
self.push(TokKind::BinInt, start, self.pos);
return;
}
_ => {}
}
}
let mut is_float = false;
let mut is_f32 = false;
self.consume_digits(|c| c.is_ascii_digit());
if self.peek(0) == Some(b'.') && self.peek(1) != Some(b'.') {
is_float = true;
self.pos += 1;
self.consume_digits(|c| c.is_ascii_digit());
}
if matches!(self.peek(0), Some(b'e' | b'E' | b'f')) {
if self.peek(0) == Some(b'f') {
is_f32 = true;
} else {
is_float = true;
}
self.pos += 1;
if matches!(self.peek(0), Some(b'+' | b'-')) {
self.pos += 1;
}
while matches!(self.peek(0), Some(c) if c.is_ascii_digit()) {
self.pos += 1;
}
}
let kind = if is_f32 {
TokKind::Float32
} else if is_float {
TokKind::Float
} else {
TokKind::Integer
};
self.push(kind, start, self.pos);
}
fn consume_digits(&mut self, is_digit: impl Fn(u8) -> bool) {
while matches!(self.peek(0), Some(c) if is_digit(c) || c == b'_') {
self.pos += 1;
}
}
fn lex_ident_or_keyword(&mut self, start: usize) {
self.scan_ident();
if matches!(self.peek(0), Some(b'"' | b'`'))
&& keyword_kind(&self.input[start..self.pos]).is_none()
{
self.push(TokKind::StringPrefix, start, self.pos);
let open = self.pos;
if self.peek(0) == Some(b'"') {
self.lex_open_string(open, true);
} else {
self.lex_open_cmd(open, true);
}
return;
}
let text = &self.input[start..self.pos];
let kind = keyword_kind(text).unwrap_or(TokKind::Ident);
self.push(kind, start, self.pos);
}
fn lex_interp_ident(&mut self) {
let start = self.pos;
self.scan_ident();
self.push(TokKind::Ident, start, self.pos);
}
fn scan_ident(&mut self) {
self.pos += self.char_at(self.pos).len_utf8();
loop {
let c = self.char_at(self.pos);
if c == '!' && self.peek(1) == Some(b'=') {
break;
}
if self.pos < self.bytes.len() && is_ident_continue(c) {
self.pos += c.len_utf8();
} else {
break;
}
}
}
fn lex_operator_or_unknown(&mut self, start: usize) {
let b0 = self.peek(0);
let b1 = self.peek(1);
if (b0, b1, self.peek(2)) == (Some(b'.'), Some(b'.'), Some(b'.')) {
self.pos += 3;
self.push_op(TokKind::DotDotDot, start);
return;
}
if (b0, b1) == (Some(b'.'), Some(b'.')) {
self.pos += 2;
self.push_op(TokKind::DotDot, start);
return;
}
if b0 == Some(b'.') {
if (b1, self.peek(2), self.peek(3)) == (Some(b'/'), Some(b'/'), Some(b'=')) {
self.pos += 4;
self.push_op(TokKind::DotSlashSlashEq, start);
return;
}
if (b1, self.peek(2), self.peek(3)) == (Some(b'-'), Some(b'-'), Some(b'>')) {
self.pos += 4;
self.push_op(TokKind::DotLongArrow, start);
return;
}
if (b1, self.peek(2), self.peek(3)) == (Some(b'='), Some(b'='), Some(b'=')) {
self.pos += 4;
self.push_op(TokKind::DotEqEqEq, start);
return;
}
if (b1, self.peek(2), self.peek(3)) == (Some(b'!'), Some(b'='), Some(b'=')) {
self.pos += 4;
self.push_op(TokKind::DotNotEqEq, start);
return;
}
let dotted3 = match (b1, self.peek(2)) {
(Some(b'='), Some(b'=')) => Some(TokKind::DotEqEq),
(Some(b'!'), Some(b'=')) => Some(TokKind::DotNotEq),
(Some(b'<'), Some(b'=')) => Some(TokKind::DotLe),
(Some(b'>'), Some(b'=')) => Some(TokKind::DotGe),
(Some(b'<'), Some(b':')) => Some(TokKind::DotSubtype),
(Some(b'>'), Some(b':')) => Some(TokKind::DotSupertype),
(Some(b'/'), Some(b'/')) => Some(TokKind::DotSlashSlash),
(Some(b'*'), Some(b'*')) => Some(TokKind::DotStarStar),
(Some(b'-'), Some(b'-')) => Some(TokKind::DotMinusMinus),
(Some(b'='), Some(b'>')) => Some(TokKind::DotFatArrow),
(Some(b'|'), Some(b'>')) => Some(TokKind::DotPipeGt),
(Some(b'&'), Some(b'&')) => Some(TokKind::DotAndAnd),
(Some(b'|'), Some(b'|')) => Some(TokKind::DotOrOr),
(Some(b'+'), Some(b'=')) => Some(TokKind::DotPlusEq),
(Some(b'-'), Some(b'=')) => Some(TokKind::DotMinusEq),
(Some(b'*'), Some(b'=')) => Some(TokKind::DotStarEq),
(Some(b'/'), Some(b'=')) => Some(TokKind::DotSlashEq),
(Some(b'\\'), Some(b'=')) => Some(TokKind::DotBackslashEq),
(Some(b'^'), Some(b'=')) => Some(TokKind::DotCaretEq),
(Some(b'%'), Some(b'=')) => Some(TokKind::DotPercentEq),
_ => None,
};
if let Some(kind) = dotted3 {
self.pos += 3;
self.push_op(kind, start);
return;
}
let dotted2 = match b1 {
Some(b'+') => Some(TokKind::DotPlus),
Some(b'-') => Some(TokKind::DotMinus),
Some(b'*') => Some(TokKind::DotStar),
Some(b'/') => Some(TokKind::DotSlash),
Some(b'\\') => Some(TokKind::DotBackslash),
Some(b'^') => Some(TokKind::DotCaret),
Some(b'%') => Some(TokKind::DotPercent),
Some(b'=') => Some(TokKind::DotEq),
Some(b'<') => Some(TokKind::DotLt),
Some(b'>') => Some(TokKind::DotGt),
Some(b'~') => Some(TokKind::DotTilde),
Some(b'&') => Some(TokKind::DotAmp),
Some(b'|') => Some(TokKind::DotPipe),
_ => None,
};
if let Some(kind) = dotted2 {
self.pos += 2;
self.push_op(kind, start);
return;
}
if b1.is_some_and(|b| !b.is_ascii()) {
let ch = self.char_at(self.pos + 1);
if let Some(kind) =
super::unicode_ops::unicode_op_kind(ch).filter(|&k| is_unicode_infix_tier(k))
{
self.pos += 1 + ch.len_utf8();
self.push_op(kind, start);
return;
}
}
}
if (b0, b1, self.peek(2)) == (Some(b'/'), Some(b'/'), Some(b'=')) {
self.pos += 3;
self.push_op(TokKind::SlashSlashEq, start);
return;
}
if (b0, b1, self.peek(2), self.peek(3)) == (Some(b'<'), Some(b'-'), Some(b'-'), Some(b'>'))
{
self.pos += 4;
self.push_op(TokKind::LeftRightArrow, start);
return;
}
let three = match (b0, b1, self.peek(2)) {
(Some(b'-'), Some(b'-'), Some(b'>')) => Some(TokKind::LongArrow),
(Some(b'>'), Some(b'>'), Some(b'>')) => Some(TokKind::UShr),
(Some(b'='), Some(b'='), Some(b'=')) => Some(TokKind::EqEqEq),
(Some(b'!'), Some(b'='), Some(b'=')) => Some(TokKind::NotEqEq),
_ => None,
};
if let Some(kind) = three {
self.pos += 3;
self.push_op(kind, start);
return;
}
let two = match (b0, b1) {
(Some(b'*'), Some(b'*')) => Some(TokKind::StarStar),
(Some(b'-'), Some(b'-')) => Some(TokKind::MinusMinus),
(Some(b'/'), Some(b'/')) => Some(TokKind::SlashSlash),
(Some(b'='), Some(b'=')) => Some(TokKind::EqEq),
(Some(b'='), Some(b'>')) => Some(TokKind::FatArrow),
(Some(b'!'), Some(b'=')) => Some(TokKind::NotEq),
(Some(b'<'), Some(b'=')) => Some(TokKind::Le),
(Some(b'>'), Some(b'=')) => Some(TokKind::Ge),
(Some(b'&'), Some(b'&')) => Some(TokKind::AndAnd),
(Some(b'|'), Some(b'|')) => Some(TokKind::OrOr),
(Some(b':'), Some(b':')) => Some(TokKind::ColonColon),
(Some(b'<'), Some(b':')) => Some(TokKind::Subtype),
(Some(b'>'), Some(b':')) => Some(TokKind::Supertype),
(Some(b'-'), Some(b'>')) => Some(TokKind::Arrow),
(Some(b'|'), Some(b'>')) => Some(TokKind::PipeGt),
(Some(b'<'), Some(b'|')) => Some(TokKind::PipeLt),
(Some(b'<'), Some(b'<')) => Some(TokKind::Shl),
(Some(b'>'), Some(b'>')) => Some(TokKind::Shr),
(Some(b'+'), Some(b'=')) => Some(TokKind::PlusEq),
(Some(b'-'), Some(b'=')) => Some(TokKind::MinusEq),
(Some(b'*'), Some(b'=')) => Some(TokKind::StarEq),
(Some(b'/'), Some(b'=')) => Some(TokKind::SlashEq),
(Some(b'\\'), Some(b'=')) => Some(TokKind::BackslashEq),
(Some(b'^'), Some(b'=')) => Some(TokKind::CaretEq),
(Some(b'%'), Some(b'=')) => Some(TokKind::PercentEq),
(Some(b'|'), Some(b'=')) => Some(TokKind::PipeEq),
(Some(b'&'), Some(b'=')) => Some(TokKind::AmpEq),
_ => None,
};
if let Some(kind) = two {
self.pos += 2;
self.push_op(kind, start);
return;
}
let one = match b0 {
Some(b'=') => Some(TokKind::Eq),
Some(b'+') => Some(TokKind::Plus),
Some(b'-') => Some(TokKind::Minus),
Some(b'*') => Some(TokKind::Star),
Some(b'/') => Some(TokKind::Slash),
Some(b'\\') => Some(TokKind::Backslash),
Some(b'^') => Some(TokKind::Caret),
Some(b'%') => Some(TokKind::Percent),
Some(b'<') => Some(TokKind::Lt),
Some(b'>') => Some(TokKind::Gt),
Some(b':') => Some(TokKind::Colon),
Some(b'.') => Some(TokKind::Dot),
Some(b'!') => Some(TokKind::Bang),
Some(b'&') => Some(TokKind::Amp),
Some(b'|') => Some(TokKind::Pipe),
Some(b'~') => Some(TokKind::Tilde),
Some(b'?') => Some(TokKind::Question),
Some(b'(') => Some(TokKind::LParen),
Some(b')') => Some(TokKind::RParen),
Some(b'[') => Some(TokKind::LBracket),
Some(b']') => Some(TokKind::RBracket),
Some(b'{') => Some(TokKind::LBrace),
Some(b'}') => Some(TokKind::RBrace),
Some(b',') => Some(TokKind::Comma),
Some(b';') => Some(TokKind::Semicolon),
Some(b'@') => Some(TokKind::At),
Some(b'$') => Some(TokKind::Dollar),
_ => None,
};
match one {
Some(kind) => {
self.pos += 1;
self.push_op(kind, start);
}
None => {
let ch = self.char_at(self.pos);
if let Some(kind) = super::unicode_ops::unicode_op_kind(ch) {
self.pos += ch.len_utf8();
self.push_op(kind, start);
} else {
self.pos += ch.len_utf8();
self.push(TokKind::Unknown, start, self.pos);
}
}
}
}
}
fn keyword_kind(text: &str) -> Option<TokKind> {
Some(match text {
"function" => TokKind::FunctionKw,
"macro" => TokKind::MacroKw,
"end" => TokKind::EndKw,
"if" => TokKind::IfKw,
"elseif" => TokKind::ElseifKw,
"else" => TokKind::ElseKw,
"begin" => TokKind::BeginKw,
"true" => TokKind::TrueKw,
"false" => TokKind::FalseKw,
"while" => TokKind::WhileKw,
"for" => TokKind::ForKw,
"do" => TokKind::DoKw,
"let" => TokKind::LetKw,
"quote" => TokKind::QuoteKw,
"try" => TokKind::TryKw,
"catch" => TokKind::CatchKw,
"finally" => TokKind::FinallyKw,
"struct" => TokKind::StructKw,
"mutable" => TokKind::MutableKw,
"module" => TokKind::ModuleKw,
"baremodule" => TokKind::BaremoduleKw,
"return" => TokKind::ReturnKw,
"break" => TokKind::BreakKw,
"continue" => TokKind::ContinueKw,
"const" => TokKind::ConstKw,
"global" => TokKind::GlobalKw,
"local" => TokKind::LocalKw,
"import" => TokKind::ImportKw,
"using" => TokKind::UsingKw,
"export" => TokKind::ExportKw,
"where" => TokKind::WhereKw,
_ => return None,
})
}
fn is_ident_start(c: char) -> bool {
c == '_' || c.is_alphabetic() || (!c.is_ascii() && is_unicode_ident(c))
}
fn is_ident_continue(c: char) -> bool {
c == '_' || c == '!' || c.is_alphanumeric() || (!c.is_ascii() && is_unicode_ident(c))
}
fn is_unicode_ident(c: char) -> bool {
c.is_alphanumeric() || matches!(c, '\u{391}'..='\u{3c9}' | '\u{2070}'..='\u{209f}')
}
const OP_SUFFIX_CHARS: &str = "²³¹ʰʲʳʷʸˡˢˣᴬᴮᴰᴱᴳᴴᴵᴶᴷᴸᴹᴺᴼᴾᴿᵀᵁᵂᵃᵇᵈᵉᵍᵏᵐᵒᵖᵗᵘᵛᵝᵞᵟᵠᵡᵢᵣᵤᵥᵦᵧᵨᵩᵪᶜᶠᶥᶦᶫᶰᶸᶻᶿ′″‴‵‶‷⁗⁰ⁱ⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾ⁿ₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎ₐₑₒₓₕₖₗₘₙₚₛₜⱼⱽꜛꜜꜝ";
pub(crate) fn is_op_suffix_char(c: char) -> bool {
OP_SUFFIX_CHARS.contains(c)
}
fn op_takes_suffix(kind: TokKind) -> bool {
use TokKind::*;
matches!(
kind,
Plus | Minus
| Star
| Slash
| Backslash
| SlashSlash
| Caret
| Percent
| EqEq
| NotEq
| EqEqEq
| NotEqEq
| Lt
| Le
| Gt
| Ge
| Amp
| Pipe
| Shl
| Shr
| UShr
| PipeGt
| PipeLt
| FatArrow
| LongArrow
| LeftRightArrow
| Transpose
| DotPlus
| DotMinus
| DotStar
| DotSlash
| DotBackslash
| DotSlashSlash
| DotCaret
| DotPercent
| DotEqEq
| DotNotEq
| DotEqEqEq
| DotNotEqEq
| DotLt
| DotLe
| DotGt
| DotGe
| DotFatArrow
| DotLongArrow
| DotPipeGt
| DotAmp
| DotPipe
| UniArrow
| UniComparison
| UniColon
| UniPlus
| UniTimes
| UniPower
)
}
fn is_unicode_infix_tier(kind: TokKind) -> bool {
use TokKind::*;
matches!(
kind,
UniArrow | UniComparison | UniColon | UniPlus | UniTimes | UniPower
)
}
#[cfg(test)]
mod tests {
use super::*;
fn kinds(input: &str) -> Vec<TokKind> {
lex(input).into_iter().map(|t| t.kind).collect()
}
fn roundtrips(input: &str) -> bool {
let joined: String = lex(input).into_iter().map(|t| t.text).collect();
joined == input
}
#[test]
fn lossless_over_assorted_input() {
for input in [
"x = 1 + 2\n",
"f(a, b)\n",
"#= a #= nested =# b =#\n",
"function g(x)\n x ^ 2\nend\n",
"s = \"hello\\n\"\nc = 'a'\n",
"if a >= b\n a\nelseif c\n c\nelse\n b\nend\n",
"for i in 1:10\n x\nend\n",
"while x > 0\n x\nend\n",
"map(xs) do x, y\n x + y\nend\n",
"try\n f()\ncatch e\n g()\nfinally\n h()\nend\n",
"mutable struct Counter\n n\nend\n",
"module M\nend\n",
"α = β + 1\n",
"0x1f + 0b1010\n",
"x = 0o755\ny = 0x1.8p3\nz = 1f0\n",
"r = 3//4 + 1_000\nq = a .// b\n",
"n = 1.5e-3\nm = .5\nk = 2.\n",
"s = \"a$x b\"\n",
"s = \"a$(f(x))b\"\n",
"s = \"\"\"x$(y)\"\"\"\n",
"c = `echo $x`\n",
"r = raw\"\\d+\"\n",
"m = r\"pat\"ims\n",
"v = v\"1.2.3\"\n",
"b = b\"\\x00\"\n",
"s = \"$foo.bar\"\n",
"s = \"\\$lit\"\n",
"s = \"$$\"\n",
"s = \"unterminated\n",
"s = \"$(g(\"nested\"))\"\n",
] {
assert!(roundtrips(input), "did not round-trip: {input:?}");
}
}
#[test]
fn interpolation_with_bare_ident() {
assert_eq!(
kinds("\"a$x\""),
vec![
TokKind::StringDelimOpen,
TokKind::StringContent,
TokKind::Dollar,
TokKind::Ident,
TokKind::StringDelimClose,
]
);
}
#[test]
fn interpolation_with_parenthesized_expr() {
assert_eq!(
kinds("\"$(y)\""),
vec![
TokKind::StringDelimOpen,
TokKind::Dollar,
TokKind::LParen,
TokKind::Ident,
TokKind::RParen,
TokKind::StringDelimClose,
]
);
}
#[test]
fn nested_parens_in_interpolation() {
assert_eq!(
kinds("\"$(f(x))\""),
vec![
TokKind::StringDelimOpen,
TokKind::Dollar,
TokKind::LParen,
TokKind::Ident,
TokKind::LParen,
TokKind::Ident,
TokKind::RParen,
TokKind::RParen,
TokKind::StringDelimClose,
]
);
}
#[test]
fn raw_literal_does_not_interpolate() {
assert_eq!(
kinds("raw\"$x\\d\""),
vec![
TokKind::StringPrefix,
TokKind::StringDelimOpen,
TokKind::StringContent,
TokKind::StringDelimClose,
]
);
}
#[test]
fn prefix_and_suffix_flags() {
assert_eq!(
kinds("r\"pat\"ims"),
vec![
TokKind::StringPrefix,
TokKind::StringDelimOpen,
TokKind::StringContent,
TokKind::StringDelimClose,
TokKind::StringSuffix,
]
);
}
#[test]
fn command_literal_interpolates() {
assert_eq!(
kinds("`cmd $x`"),
vec![
TokKind::CmdDelimOpen,
TokKind::StringContent,
TokKind::Dollar,
TokKind::Ident,
TokKind::CmdDelimClose,
]
);
}
#[test]
fn escaped_dollar_is_content() {
assert_eq!(
kinds("\"\\$x\""),
vec![
TokKind::StringDelimOpen,
TokKind::StringContent,
TokKind::StringDelimClose,
]
);
}
#[test]
fn plain_string_adjacent_ident_is_not_a_suffix() {
assert_eq!(
kinds("\"a\"b"),
vec![
TokKind::StringDelimOpen,
TokKind::StringContent,
TokKind::StringDelimClose,
TokKind::Ident,
]
);
}
#[test]
fn prefix_requires_adjacent_quote() {
assert_eq!(
kinds("r \"x\""),
vec![
TokKind::Ident,
TokKind::Whitespace,
TokKind::StringDelimOpen,
TokKind::StringContent,
TokKind::StringDelimClose,
]
);
}
#[test]
fn keywords_and_operators() {
assert_eq!(
kinds("a == b"),
vec![
TokKind::Ident,
TokKind::Whitespace,
TokKind::EqEq,
TokKind::Whitespace,
TokKind::Ident
]
);
assert_eq!(keyword_kind("function"), Some(TokKind::FunctionKw));
assert_eq!(keyword_kind("ends"), None);
}
#[test]
fn bang_in_identifier() {
assert_eq!(kinds("push!"), vec![TokKind::Ident]);
assert_eq!(kinds("a!b"), vec![TokKind::Ident]);
assert_eq!(
kinds("a!=b"),
vec![TokKind::Ident, TokKind::NotEq, TokKind::Ident]
);
assert_eq!(
kinds("a!==b"),
vec![TokKind::Ident, TokKind::NotEqEq, TokKind::Ident]
);
assert_eq!(
kinds("a!!=b"),
vec![TokKind::Ident, TokKind::NotEq, TokKind::Ident]
);
}
#[test]
fn identity_operators() {
assert_eq!(
kinds("a===b"),
vec![TokKind::Ident, TokKind::EqEqEq, TokKind::Ident]
);
assert_eq!(
kinds("a==b"),
vec![TokKind::Ident, TokKind::EqEq, TokKind::Ident]
);
}
#[test]
fn numeric_literal_kinds() {
assert_eq!(kinds("123"), vec![TokKind::Integer]);
assert_eq!(kinds("1_000"), vec![TokKind::Integer]);
assert_eq!(
kinds("12345678901234567890123456789"),
vec![TokKind::Integer]
);
assert_eq!(kinds("0x1f"), vec![TokKind::HexInt]);
assert_eq!(kinds("0o755"), vec![TokKind::OctInt]);
assert_eq!(kinds("0b1010"), vec![TokKind::BinInt]);
assert_eq!(kinds("3.14"), vec![TokKind::Float]);
assert_eq!(kinds(".5"), vec![TokKind::Float]);
assert_eq!(kinds("2."), vec![TokKind::Float]);
assert_eq!(kinds("1.5e-3"), vec![TokKind::Float]);
assert_eq!(kinds("1f0"), vec![TokKind::Float32]);
assert_eq!(kinds("2.5f-3"), vec![TokKind::Float32]);
assert_eq!(kinds("0x1p0"), vec![TokKind::Float]);
assert_eq!(kinds("0x1.8p3"), vec![TokKind::Float]);
}
#[test]
fn rational_operators() {
assert_eq!(
kinds("3//4"),
vec![TokKind::Integer, TokKind::SlashSlash, TokKind::Integer]
);
assert_eq!(
kinds("a .// b"),
vec![
TokKind::Ident,
TokKind::Whitespace,
TokKind::DotSlashSlash,
TokKind::Whitespace,
TokKind::Ident,
]
);
}
#[test]
fn left_division_operators() {
assert_eq!(
kinds("a\\b"),
vec![TokKind::Ident, TokKind::Backslash, TokKind::Ident]
);
assert_eq!(
kinds("a\\=b"),
vec![TokKind::Ident, TokKind::BackslashEq, TokKind::Ident]
);
assert_eq!(
kinds("a .\\ b"),
vec![
TokKind::Ident,
TokKind::Whitespace,
TokKind::DotBackslash,
TokKind::Whitespace,
TokKind::Ident,
]
);
assert_eq!(
kinds("a.\\=b"),
vec![TokKind::Ident, TokKind::DotBackslashEq, TokKind::Ident]
);
}
#[test]
fn uppercase_hex_prefix_is_not_a_literal() {
assert_eq!(kinds("0X1"), vec![TokKind::Integer, TokKind::Ident]);
}
#[test]
fn inf_and_nan_are_identifiers() {
assert_eq!(kinds("Inf"), vec![TokKind::Ident]);
assert_eq!(kinds("NaN"), vec![TokKind::Ident]);
assert_eq!(kinds("Inf32"), vec![TokKind::Ident]);
}
#[test]
fn subtype_and_supertype_operators() {
assert_eq!(
kinds("T<:U"),
vec![TokKind::Ident, TokKind::Subtype, TokKind::Ident]
);
assert_eq!(
kinds("T>:U"),
vec![TokKind::Ident, TokKind::Supertype, TokKind::Ident]
);
}
#[test]
fn splat_is_three_dots() {
assert_eq!(kinds("x..."), vec![TokKind::Ident, TokKind::DotDotDot]);
assert_eq!(kinds(".."), vec![TokKind::DotDot]);
assert_eq!(
kinds("a.b"),
vec![TokKind::Ident, TokKind::Dot, TokKind::Ident]
);
}
#[test]
fn broadcasting_operators() {
assert_eq!(
kinds("a .+ b"),
vec![
TokKind::Ident,
TokKind::Whitespace,
TokKind::DotPlus,
TokKind::Whitespace,
TokKind::Ident
]
);
assert_eq!(kinds("x .== y").get(2), Some(&TokKind::DotEqEq));
assert_eq!(kinds("x .= y").get(2), Some(&TokKind::DotEq));
assert_eq!(kinds("a .<= b").get(2), Some(&TokKind::DotLe));
assert_eq!(kinds("x .=== y").get(2), Some(&TokKind::DotEqEqEq));
assert_eq!(kinds("x .!== y").get(2), Some(&TokKind::DotNotEqEq));
assert_eq!(
kinds("a.b"),
vec![TokKind::Ident, TokKind::Dot, TokKind::Ident]
);
assert_eq!(kinds(".."), vec![TokKind::DotDot]);
assert_eq!(
kinds("f.(x)"),
vec![
TokKind::Ident,
TokKind::Dot,
TokKind::LParen,
TokKind::Ident,
TokKind::RParen
]
);
}
#[test]
fn where_is_a_keyword() {
assert_eq!(keyword_kind("where"), Some(TokKind::WhereKw));
assert_eq!(kinds("where"), vec![TokKind::WhereKw]);
}
}