use std::borrow::Borrow;
use std::collections::HashSet;
use std::fmt;
use std::ops::Deref;
use std::sync::Arc;
use crate::discovery::Language;
#[derive(Debug, Clone, Eq)]
pub struct Lexeme(Arc<str>);
impl Lexeme {
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl PartialEq for Lexeme {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0) || self.0 == other.0
}
}
impl std::hash::Hash for Lexeme {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl Deref for Lexeme {
type Target = str;
fn deref(&self) -> &str {
&self.0
}
}
impl AsRef<str> for Lexeme {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Borrow<str> for Lexeme {
fn borrow(&self) -> &str {
&self.0
}
}
impl From<&str> for Lexeme {
fn from(text: &str) -> Self {
Self(Arc::from(text))
}
}
impl PartialEq<str> for Lexeme {
fn eq(&self, other: &str) -> bool {
&*self.0 == other
}
}
impl PartialEq<&str> for Lexeme {
fn eq(&self, other: &&str) -> bool {
&*self.0 == *other
}
}
impl fmt::Display for Lexeme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Default)]
pub struct LexemeInterner {
known: HashSet<Lexeme>,
}
impl LexemeInterner {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn intern(&mut self, text: &str) -> Lexeme {
if let Some(found) = self.known.get(text) {
return found.clone();
}
let lexeme = Lexeme::from(text);
self.known.insert(lexeme.clone());
lexeme
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LiteralKind {
Integer,
Float,
String,
Char,
Bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenKind {
Identifier,
Keyword,
Literal(LiteralKind),
Lifetime,
Punctuation,
Unknown,
}
impl TokenKind {
#[must_use]
pub const fn tag(self) -> u8 {
match self {
Self::Identifier => 1,
Self::Keyword => 2,
Self::Literal(_) => 3,
Self::Punctuation => 4,
Self::Lifetime => 5,
Self::Unknown => 6,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SourceSpan {
pub start_byte: usize,
pub end_byte: usize,
pub start_line: u32,
pub start_column: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Token {
pub kind: TokenKind,
pub text: Lexeme,
pub span: SourceSpan,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticKind {
UnterminatedString,
UnterminatedChar,
UnterminatedBlockComment,
UnexpectedCharacter,
UnmatchedDelimiter,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
pub kind: DiagnosticKind,
pub span: SourceSpan,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnitKind {
Function,
Method,
Impl,
Record,
Closure,
}
impl UnitKind {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Function => "function",
Self::Method => "method",
Self::Impl => "impl",
Self::Record => "record",
Self::Closure => "closure",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Unit {
pub kind: UnitKind,
pub name: Option<String>,
pub token_start: usize,
pub token_end: usize,
pub span: SourceSpan,
}
#[derive(Debug, Clone)]
pub struct LexedFile {
pub language: Language,
pub frontend_version: &'static str,
pub tokens: Vec<Token>,
pub units: Vec<Unit>,
pub diagnostics: Vec<Diagnostic>,
}
pub trait Frontend {
fn language(&self) -> Language;
fn frontend_version(&self) -> &'static str;
fn lex(&self, source: &str) -> LexedFile;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kind_tags_are_distinct_and_stable() {
let tags = [
TokenKind::Identifier.tag(),
TokenKind::Keyword.tag(),
TokenKind::Literal(LiteralKind::Integer).tag(),
TokenKind::Punctuation.tag(),
TokenKind::Lifetime.tag(),
TokenKind::Unknown.tag(),
];
let mut sorted = tags.to_vec();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(sorted.len(), tags.len(), "tags must be distinct");
assert_eq!(
TokenKind::Literal(LiteralKind::Integer).tag(),
TokenKind::Literal(LiteralKind::String).tag()
);
}
#[test]
fn interning_shares_one_allocation_per_text() {
let mut interner = LexemeInterner::new();
let a = interner.intern("alpha");
let b = interner.intern("alpha");
let c = interner.intern("beta");
assert!(Arc::ptr_eq(&a.0, &b.0), "same text must share storage");
assert!(!Arc::ptr_eq(&a.0, &c.0));
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn lexeme_equality_and_hash_follow_content_across_interners() {
let a = LexemeInterner::new().intern("shared");
let b = LexemeInterner::new().intern("shared");
assert!(
!Arc::ptr_eq(&a.0, &b.0),
"distinct interners allocate separately"
);
assert_eq!(a, b, "equality is by content, not by pointer");
let set: HashSet<Lexeme> = [a].into();
assert!(set.contains("shared"), "str lookups must hash consistently");
}
#[test]
fn lexeme_compares_against_plain_strings() {
let lexeme = Lexeme::from("fn");
assert_eq!(lexeme, "fn");
assert_eq!(lexeme.as_str(), "fn");
assert_eq!(lexeme.to_string(), "fn");
assert_eq!(lexeme.as_bytes(), b"fn");
}
#[test]
fn unit_kind_names_are_stable() {
assert_eq!(UnitKind::Function.name(), "function");
assert_eq!(UnitKind::Method.name(), "method");
assert_eq!(UnitKind::Impl.name(), "impl");
assert_eq!(UnitKind::Closure.name(), "closure");
}
}