use crate::ast::{BinaryOp, Expression, ExpressionKind, Statement, StatementTag};
use crate::ast_names::AstName;
use crate::confusables::find_confusable;
use crate::lexer::{ReservedWord as R, Token};
use crate::location::Position;
use std::fmt;
#[derive(Debug, Clone, Copy)]
enum Associativity {
Left,
Right,
}
pub(super) trait TokenAnalysis {
fn binary_priority(&self) -> Option<BinaryPriority>;
fn compound_assignment_op(&self) -> Option<BinaryOp>;
fn is_keyword(&self, expected: &str) -> bool;
fn description(&self) -> Option<TokenDescription>;
}
pub(super) enum TokenDescription {
BrokenUnicode { codepoint: u32 },
}
#[derive(Debug, Clone, Copy)]
pub(super) struct BinaryPriority {
pub op: BinaryOp,
pub left: u8,
pub right: u8,
pub op_position: Option<Position>,
}
impl fmt::Display for TokenDescription {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::BrokenUnicode { codepoint: 0 } => formatter.write_str("invalid UTF-8 sequence"),
Self::BrokenUnicode { codepoint } => {
if let Some(suggestion) = find_confusable(codepoint) {
write!(
formatter,
"Unicode character U+{codepoint:x} (did you mean '{suggestion}'?)"
)
} else {
write!(formatter, "Unicode character U+{codepoint:x}")
}
}
}
}
}
impl TokenAnalysis for Token<'_, '_> {
fn binary_priority(&self) -> Option<BinaryPriority> {
let (op, precedence, associativity) = match self {
Token::Reserved(R::Or) => (BinaryOp::Or, 1, Associativity::Left),
Token::Reserved(R::And) => (BinaryOp::And, 2, Associativity::Left),
Token::EqualEqual => (BinaryOp::Equal, 3, Associativity::Left),
Token::TildeEqual => (BinaryOp::NotEqual, 3, Associativity::Left),
Token::Less => (BinaryOp::Less, 3, Associativity::Left),
Token::LessEqual => (BinaryOp::LessEqual, 3, Associativity::Left),
Token::Greater => (BinaryOp::Greater, 3, Associativity::Left),
Token::GreaterEqual => (BinaryOp::GreaterEqual, 3, Associativity::Left),
Token::DotDot => (BinaryOp::Concat, 5, Associativity::Right),
Token::Plus => (BinaryOp::Add, 6, Associativity::Left),
Token::Minus => (BinaryOp::Subtract, 6, Associativity::Left),
Token::Star => (BinaryOp::Multiply, 7, Associativity::Left),
Token::Slash => (BinaryOp::Divide, 7, Associativity::Left),
Token::SlashSlash => (BinaryOp::FloorDivide, 7, Associativity::Left),
Token::Percent => (BinaryOp::Modulo, 7, Associativity::Left),
Token::Caret => (BinaryOp::Power, 10, Associativity::Right),
_ => return None,
};
let right_limit = match associativity {
Associativity::Left => precedence,
Associativity::Right => precedence - 1,
};
Some(BinaryPriority {
op,
left: precedence,
right: right_limit,
op_position: None,
})
}
fn compound_assignment_op(&self) -> Option<BinaryOp> {
match self {
Token::PlusEqual => Some(BinaryOp::Add),
Token::MinusEqual => Some(BinaryOp::Subtract),
Token::StarEqual => Some(BinaryOp::Multiply),
Token::SlashEqual => Some(BinaryOp::Divide),
Token::SlashSlashEqual => Some(BinaryOp::FloorDivide),
Token::PercentEqual => Some(BinaryOp::Modulo),
Token::CaretEqual => Some(BinaryOp::Power),
Token::DotDotEqual => Some(BinaryOp::Concat),
_ => None,
}
}
fn is_keyword(&self, expected: &str) -> bool {
match self {
Token::Ident(value) => value == expected,
Token::Reserved(word) => word.as_str() == expected,
_ => false,
}
}
fn description(&self) -> Option<TokenDescription> {
match self {
Token::BrokenUnicode { codepoint } => Some(TokenDescription::BrokenUnicode {
codepoint: *codepoint,
}),
_ => None,
}
}
}
pub(super) trait LuauKeyword {
fn is_reserved_word(&self) -> bool;
}
impl LuauKeyword for str {
fn is_reserved_word(&self) -> bool {
matches!(
self,
"and"
| "break"
| "do"
| "else"
| "elseif"
| "end"
| "false"
| "for"
| "function"
| "if"
| "in"
| "local"
| "nil"
| "not"
| "or"
| "repeat"
| "return"
| "then"
| "true"
| "until"
| "while"
)
}
}
impl LuauKeyword for AstName<'_> {
fn is_reserved_word(&self) -> bool {
matches!(
self.bytes(),
b"and"
| b"break"
| b"do"
| b"else"
| b"elseif"
| b"end"
| b"false"
| b"for"
| b"function"
| b"if"
| b"in"
| b"local"
| b"nil"
| b"not"
| b"or"
| b"repeat"
| b"return"
| b"then"
| b"true"
| b"until"
| b"while"
)
}
}
pub(super) trait ExpressionAnalysis {
fn accepts_call(&self) -> bool;
fn returns_multiple(&self) -> bool;
fn is_call_statement(&self) -> bool;
}
impl ExpressionAnalysis for Expression<'_> {
fn accepts_call(&self) -> bool {
matches!(
self.kind(),
ExpressionKind::Call { .. }
| ExpressionKind::Error { .. }
| ExpressionKind::FunctionLiteral(_)
| ExpressionKind::Grouped(_)
| ExpressionKind::IndexExpr { .. }
| ExpressionKind::IndexName { .. }
| ExpressionKind::Instantiate { .. }
| ExpressionKind::Local { .. }
| ExpressionKind::Global(_)
)
}
fn returns_multiple(&self) -> bool {
matches!(
self.kind(),
ExpressionKind::Call { .. } | ExpressionKind::Varargs
)
}
fn is_call_statement(&self) -> bool {
matches!(self.kind(), ExpressionKind::Call { .. })
}
}
pub(super) trait ExpressionSliceAnalysis {
fn has_enough_values(&self, binding_count: usize) -> bool;
}
impl ExpressionSliceAnalysis for [Expression<'_>] {
fn has_enough_values(&self, binding_count: usize) -> bool {
self.len() >= binding_count
|| self
.last()
.is_some_and(|expression| expression.returns_multiple())
}
}
pub(super) trait StatementAnalysis {
fn is_terminal(&self) -> bool;
}
impl StatementAnalysis for Statement<'_> {
fn is_terminal(&self) -> bool {
matches!(
self.tag,
StatementTag::Break | StatementTag::Continue | StatementTag::Return
)
}
}