use super::functions::*;
use crate::tokens::{Span, Token, TokenKind};
#[derive(Clone, Debug, PartialEq)]
#[allow(missing_docs)]
pub struct TokenMeta {
pub token: Token,
pub category: TokenCategory,
#[allow(missing_docs)]
pub text: String,
pub preceded_by_space: bool,
pub preceded_by_newline: bool,
}
impl TokenMeta {
#[allow(missing_docs)]
pub fn new(
token: Token,
category: TokenCategory,
text: impl Into<String>,
preceded_by_space: bool,
preceded_by_newline: bool,
) -> Self {
Self {
token,
category,
text: text.into(),
preceded_by_space,
preceded_by_newline,
}
}
#[allow(missing_docs)]
pub fn from_token(token: Token, source: &str) -> Self {
let span = &token.span;
let text = source.get(span.start..span.end).unwrap_or("").to_string();
let category = categorise(&token.kind);
Self {
token,
category,
text,
preceded_by_space: false,
preceded_by_newline: false,
}
}
#[allow(missing_docs)]
pub fn span(&self) -> &Span {
&self.token.span
}
#[allow(missing_docs)]
pub fn kind(&self) -> &TokenKind {
&self.token.kind
}
#[allow(missing_docs)]
pub fn is_ident(&self) -> bool {
self.token.is_ident()
}
#[allow(missing_docs)]
pub fn is_keyword(&self) -> bool {
self.category == TokenCategory::Keyword
}
#[allow(missing_docs)]
pub fn is_literal(&self) -> bool {
self.category == TokenCategory::Literal
}
}
impl TokenMeta {
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn set_preceded_by_newline(&mut self, v: bool) {
self.preceded_by_newline = v;
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn set_preceded_by_space(&mut self, v: bool) {
self.preceded_by_space = v;
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn len(&self) -> usize {
self.token.span.end.saturating_sub(self.token.span.start)
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn is_numeric(&self) -> bool {
matches!(self.token.kind, TokenKind::Nat(_) | TokenKind::Float(_))
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn is_string(&self) -> bool {
matches!(self.token.kind, TokenKind::String(_))
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn is_operator(&self) -> bool {
self.category == TokenCategory::Operator
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum TokenCategory {
Keyword,
Identifier,
Literal,
Punctuation,
Operator,
Comment,
Eof,
Other,
}
impl TokenCategory {
#[allow(missing_docs)]
pub fn name(&self) -> &'static str {
match self {
TokenCategory::Keyword => "keyword",
TokenCategory::Identifier => "identifier",
TokenCategory::Literal => "literal",
TokenCategory::Punctuation => "punctuation",
TokenCategory::Operator => "operator",
TokenCategory::Comment => "comment",
TokenCategory::Eof => "end-of-file",
TokenCategory::Other => "token",
}
}
#[allow(missing_docs)]
pub fn can_start_expr(&self) -> bool {
matches!(
self,
TokenCategory::Identifier | TokenCategory::Literal | TokenCategory::Punctuation
)
}
}
impl TokenCategory {
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn is_meaningful(&self) -> bool {
!matches!(self, TokenCategory::Eof | TokenCategory::Other)
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn ansi_color(&self) -> &'static str {
match self {
TokenCategory::Keyword => ansi::BOLD_BLUE,
TokenCategory::Identifier => ansi::RESET,
TokenCategory::Literal => ansi::BOLD_GREEN,
TokenCategory::Operator => ansi::CYAN,
TokenCategory::Punctuation => ansi::YELLOW,
TokenCategory::Comment => ansi::GREEN,
TokenCategory::Eof => ansi::RESET,
TokenCategory::Other => ansi::RESET,
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn all() -> &'static [TokenCategory] {
&[
TokenCategory::Keyword,
TokenCategory::Identifier,
TokenCategory::Literal,
TokenCategory::Punctuation,
TokenCategory::Operator,
TokenCategory::Comment,
TokenCategory::Eof,
TokenCategory::Other,
]
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[allow(missing_docs)]
pub struct OperatorPriority(pub u32);
impl OperatorPriority {
pub fn new(p: u32) -> Self {
Self(p)
}
#[allow(missing_docs)]
pub const MIN: Self = Self(0);
pub const MAX: Self = Self(u32::MAX);
}
#[derive(Clone, Debug)]
#[allow(missing_docs)]
pub struct RichToken {
pub token: Token,
pub category: TokenCategory,
#[allow(missing_docs)]
pub arity: OperatorArity,
pub priority: OperatorPriority,
}
impl RichToken {
#[allow(missing_docs)]
pub fn from_token(token: Token) -> Self {
let category = categorise(&token.kind);
let arity = operator_arity(&token.kind);
let priority = operator_priority(&token.kind);
Self {
token,
category,
arity,
priority,
}
}
#[allow(missing_docs)]
pub fn is_infix(&self) -> bool {
self.arity == OperatorArity::Binary
}
#[allow(missing_docs)]
pub fn is_prefix(&self) -> bool {
self.arity == OperatorArity::Unary
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
#[derive(Clone, Debug)]
pub struct ReformatOptions {
pub space_before_op: bool,
pub space_after_op: bool,
#[allow(missing_docs)]
pub space_after_comma: bool,
pub no_space_before_close: bool,
}
#[derive(Clone, Debug)]
#[allow(missing_docs)]
pub struct TokenStream {
tokens: Vec<Token>,
pos: usize,
}
impl TokenStream {
#[allow(missing_docs)]
pub fn new(tokens: Vec<Token>) -> Self {
Self { tokens, pos: 0 }
}
#[allow(missing_docs)]
pub fn peek(&self) -> Option<&Token> {
self.tokens.get(self.pos)
}
#[allow(missing_docs)]
pub fn peek_ahead(&self, n: usize) -> Option<&Token> {
self.tokens.get(self.pos + n)
}
#[allow(clippy::should_implement_trait)]
#[allow(missing_docs)]
pub fn next(&mut self) -> Option<Token> {
if self.pos < self.tokens.len() {
let tok = self.tokens[self.pos].clone();
self.pos += 1;
Some(tok)
} else {
None
}
}
#[allow(missing_docs)]
pub fn eat(&mut self, expected: &TokenKind) -> Option<Token> {
if self.peek().map(|t| &t.kind) == Some(expected) {
self.next()
} else {
None
}
}
#[allow(missing_docs)]
pub fn eat_while<F>(&mut self, mut pred: F) -> Vec<Token>
where
F: FnMut(&Token) -> bool,
{
let mut consumed = Vec::new();
while let Some(tok) = self.peek() {
if pred(tok) {
consumed.push(self.next().expect("peek confirmed token exists"));
} else {
break;
}
}
consumed
}
#[allow(missing_docs)]
pub fn position(&self) -> usize {
self.pos
}
#[allow(missing_docs)]
pub fn is_empty(&self) -> bool {
self.pos >= self.tokens.len()
}
#[allow(missing_docs)]
pub fn remaining(&self) -> usize {
self.tokens.len().saturating_sub(self.pos)
}
#[allow(missing_docs)]
pub fn rewind(&mut self, saved: usize) {
self.pos = saved.min(self.tokens.len());
}
#[allow(missing_docs)]
pub fn save(&self) -> usize {
self.pos
}
#[allow(missing_docs)]
pub fn expect(&mut self, expected: &TokenKind) -> Result<Token, String> {
match self.peek() {
Some(tok) if &tok.kind == expected => {
Ok(self.next().expect("peek confirmed token exists"))
}
Some(tok) => Err(format!(
"expected {:?}, got {:?} at {}:{}",
expected, tok.kind, tok.span.line, tok.span.column
)),
None => Err(format!("expected {:?}, got end-of-file", expected)),
}
}
#[allow(missing_docs)]
pub fn collect_remaining(mut self) -> Vec<Token> {
let mut result = Vec::new();
while let Some(t) = self.next() {
result.push(t);
}
result
}
}
impl TokenStream {
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn look_ahead(&self, n: usize) -> Option<&Token> {
self.tokens.get(self.pos + n)
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn skip_while<F: FnMut(&Token) -> bool>(&mut self, mut pred: F) {
while let Some(tok) = self.peek() {
if pred(tok) {
self.pos += 1;
} else {
break;
}
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn skip_to_inclusive(&mut self, kind: &TokenKind) {
while let Some(tok) = self.peek() {
let found = &tok.kind == kind;
self.pos += 1;
if found {
break;
}
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn skip_to(&mut self, kind: &TokenKind) {
while let Some(tok) = self.peek() {
if &tok.kind == kind {
break;
}
self.pos += 1;
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn peek_slice(&self, n: usize) -> &[Token] {
let end = (self.pos + n).min(self.tokens.len());
&self.tokens[self.pos..end]
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn matches_sequence(&self, kinds: &[&TokenKind]) -> bool {
for (i, k) in kinds.iter().enumerate() {
match self.tokens.get(self.pos + i) {
Some(tok) if &&tok.kind == k => {}
_ => return false,
}
}
true
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn consume_n(&mut self, n: usize) -> Vec<Token> {
let mut result = Vec::with_capacity(n);
for _ in 0..n {
if let Some(tok) = self.next() {
result.push(tok);
}
}
result
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn peek_all(&self) -> &[Token] {
&self.tokens[self.pos..]
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn inject(&mut self, tokens: Vec<Token>) {
let mut new_tokens = self.tokens[..self.pos].to_vec();
new_tokens.extend(tokens);
new_tokens.extend_from_slice(&self.tokens[self.pos..]);
self.tokens = new_tokens;
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn len(&self) -> usize {
self.tokens.len()
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub struct TokenNgramIter<'a> {
pub(crate) tokens: &'a [Token],
pub(crate) window: usize,
pub(crate) pos: usize,
}
impl<'a> TokenNgramIter<'a> {
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn new(tokens: &'a [Token], window: usize) -> Self {
Self {
tokens,
window,
pos: 0,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum BracketKind {
Paren,
Brace,
Bracket,
}
#[allow(dead_code)]
#[allow(missing_docs)]
#[derive(Clone, Debug)]
pub enum TokenPattern {
Exact(TokenKind),
Category(TokenCategory),
Any,
Optional(Box<TokenPattern>),
Sequence(Vec<TokenPattern>),
Alternatives(Vec<TokenPattern>),
}
impl TokenPattern {
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn matches_single(&self, tok: &Token) -> bool {
match self {
TokenPattern::Exact(k) => &tok.kind == k,
TokenPattern::Category(cat) => categorise(&tok.kind) == *cat,
TokenPattern::Any => true,
TokenPattern::Optional(inner) => inner.matches_single(tok),
TokenPattern::Sequence(_) => false,
TokenPattern::Alternatives(alts) => alts.iter().any(|a| a.matches_single(tok)),
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn try_match(&self, tokens: &[Token]) -> Option<usize> {
match self {
TokenPattern::Exact(k) => {
if tokens.first().map(|t| &t.kind) == Some(k) {
Some(1)
} else {
None
}
}
TokenPattern::Category(cat) => {
if tokens.first().map(|t| categorise(&t.kind)) == Some(*cat) {
Some(1)
} else {
None
}
}
TokenPattern::Any => {
if tokens.is_empty() {
None
} else {
Some(1)
}
}
TokenPattern::Optional(inner) => Some(inner.try_match(tokens).unwrap_or(0)),
TokenPattern::Sequence(pats) => {
let mut consumed = 0;
for pat in pats {
match pat.try_match(&tokens[consumed..]) {
Some(n) => consumed += n,
None => return None,
}
}
Some(consumed)
}
TokenPattern::Alternatives(alts) => {
for alt in alts {
if let Some(n) = alt.try_match(tokens) {
return Some(n);
}
}
None
}
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn find_all<'a>(&self, tokens: &'a [Token]) -> Vec<&'a [Token]> {
let mut results = Vec::new();
let mut pos = 0;
while pos < tokens.len() {
if let Some(n) = self.try_match(&tokens[pos..]) {
if n > 0 {
results.push(&tokens[pos..pos + n]);
pos += n;
} else {
pos += 1;
}
} else {
pos += 1;
}
}
results
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
#[derive(Clone, Debug)]
pub struct AnnotatedToken {
pub token: Token,
pub category: TokenCategory,
pub depth: i32,
pub index: usize,
}
impl AnnotatedToken {
#[allow(dead_code)]
#[allow(missing_docs)]
pub fn new(token: Token, depth: i32, index: usize) -> Self {
let category = categorise(&token.kind);
Self {
token,
category,
depth,
index,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum OperatorArity {
Unary,
Binary,
None,
}