use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Span {
pub start: usize,
pub end: usize,
pub line: usize,
pub column: usize,
}
impl Span {
#[inline]
pub fn new(start: usize, end: usize, line: usize, column: usize) -> Self {
Self {
start,
end,
line,
column,
}
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line, self.column)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TokenKind {
AgentId(u32),
Ident(char),
Param(char),
Straight,
Right,
Left,
Number(i32),
Colon,
LParen,
RParen,
Comma,
Plus,
Minus,
Equals,
Directive(String),
DirectiveValue(String),
Space,
Newline,
Eof,
}
impl TokenKind {
pub fn description(&self) -> &'static str {
match self {
TokenKind::AgentId(_) => "agent ID",
TokenKind::Ident(_) => "identifier",
TokenKind::Param(_) => "parameter",
TokenKind::Straight => "'s'",
TokenKind::Right => "'r'",
TokenKind::Left => "'l'",
TokenKind::Number(_) => "number",
TokenKind::Colon => "':'",
TokenKind::LParen => "'('",
TokenKind::RParen => "')'",
TokenKind::Comma => "','",
TokenKind::Plus => "'+'",
TokenKind::Minus => "'-'",
TokenKind::Equals => "'='",
TokenKind::Directive(_) => "directive",
TokenKind::DirectiveValue(_) => "directive value",
TokenKind::Space => "space",
TokenKind::Newline => "newline",
TokenKind::Eof => "end of input",
}
}
#[inline]
pub fn is_command(&self) -> bool {
matches!(
self,
TokenKind::Straight | TokenKind::Right | TokenKind::Left
)
}
#[inline]
pub fn is_whitespace(&self) -> bool {
matches!(self, TokenKind::Space | TokenKind::Newline)
}
}
impl fmt::Display for TokenKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TokenKind::AgentId(n) => write!(f, "{}", n),
TokenKind::Ident(c) => write!(f, "{}", c),
TokenKind::Param(c) => write!(f, "{}", c),
TokenKind::Straight => write!(f, "s"),
TokenKind::Right => write!(f, "r"),
TokenKind::Left => write!(f, "l"),
TokenKind::Number(n) => write!(f, "{}", n),
TokenKind::Colon => write!(f, ":"),
TokenKind::LParen => write!(f, "("),
TokenKind::RParen => write!(f, ")"),
TokenKind::Comma => write!(f, ","),
TokenKind::Plus => write!(f, "+"),
TokenKind::Minus => write!(f, "-"),
TokenKind::Equals => write!(f, "="),
TokenKind::Directive(s) => write!(f, "{}", s),
TokenKind::DirectiveValue(s) => write!(f, "{}", s),
TokenKind::Space => write!(f, " "),
TokenKind::Newline => write!(f, "\\n"),
TokenKind::Eof => write!(f, "EOF"),
}
}
}
#[derive(Debug, Clone)]
pub struct Token {
pub kind: TokenKind,
pub span: Span,
}
impl Token {
#[inline]
pub fn new(kind: TokenKind, span: Span) -> Self {
Self { kind, span }
}
}
impl fmt::Display for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} at {}", self.kind, self.span)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_span_creation() {
let span = Span::new(0, 5, 1, 1);
assert_eq!(span.start, 0);
assert_eq!(span.end, 5);
assert_eq!(span.line, 1);
assert_eq!(span.column, 1);
}
#[test]
fn test_span_display() {
let span = Span::new(0, 5, 3, 7);
assert_eq!(format!("{}", span), "3:7");
}
#[test]
fn test_token_kind_description() {
assert_eq!(TokenKind::Straight.description(), "'s'");
assert_eq!(TokenKind::Right.description(), "'r'");
assert_eq!(TokenKind::Left.description(), "'l'");
assert_eq!(TokenKind::AgentId(0).description(), "agent ID");
assert_eq!(TokenKind::Number(42).description(), "number");
}
#[test]
fn test_token_kind_is_command() {
assert!(TokenKind::Straight.is_command());
assert!(TokenKind::Right.is_command());
assert!(TokenKind::Left.is_command());
assert!(!TokenKind::Ident('f').is_command());
assert!(!TokenKind::Number(1).is_command());
}
#[test]
fn test_token_kind_is_whitespace() {
assert!(TokenKind::Space.is_whitespace());
assert!(TokenKind::Newline.is_whitespace());
assert!(!TokenKind::Straight.is_whitespace());
assert!(!TokenKind::Eof.is_whitespace());
}
#[test]
fn test_token_kind_display() {
assert_eq!(format!("{}", TokenKind::Straight), "s");
assert_eq!(format!("{}", TokenKind::AgentId(5)), "5");
assert_eq!(format!("{}", TokenKind::Ident('f')), "f");
}
#[test]
fn test_token_creation() {
let token = Token::new(TokenKind::Straight, Span::new(0, 1, 1, 1));
assert_eq!(token.kind, TokenKind::Straight);
assert_eq!(token.span.start, 0);
}
#[test]
fn test_token_display() {
let token = Token::new(TokenKind::Right, Span::new(5, 6, 2, 3));
assert_eq!(format!("{}", token), "r at 2:3");
}
}