pub type TokenText = compact_str::CompactString;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Span {
start: u32, end: u32, start_line: u32, end_line: u32, }
impl Span {
#[allow(clippy::cast_possible_truncation)] pub const fn new(start: usize, end: usize) -> Self {
Self {
start: start as u32,
end: end as u32,
start_line: 1,
end_line: 1,
}
}
#[allow(clippy::cast_possible_truncation)]
pub const fn with_lines(start: usize, end: usize, start_line: usize, end_line: usize) -> Self {
Self {
start: start as u32,
end: end as u32,
start_line: start_line as u32,
end_line: end_line as u32,
}
}
#[allow(clippy::cast_possible_truncation)]
pub const fn point(offset: usize) -> Self {
Self {
start: offset as u32,
end: offset as u32,
start_line: 1,
end_line: 1,
}
}
#[inline]
pub const fn start(self) -> usize {
self.start as usize
}
#[inline]
pub const fn end(self) -> usize {
self.end as usize
}
#[inline]
pub const fn start_line(self) -> usize {
self.start_line as usize
}
#[inline]
pub const fn end_line(self) -> usize {
self.end_line as usize
}
#[inline]
pub const fn len(self) -> usize {
(self.end - self.start) as usize
}
#[inline]
pub const fn is_empty(self) -> bool {
self.start == self.end
}
#[inline]
pub const fn range(self) -> std::ops::Range<usize> {
self.start as usize..self.end as usize
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Trivium {
EmptyLine(),
LineComment(Box<str>),
BlockComment(bool, Box<[Box<str>]>),
LanguageAnnotation(Box<str>),
}
#[derive(Debug, Clone, Default)]
pub struct Trivia(Option<Box<[Trivium]>>);
impl Trivia {
#[inline]
pub const fn new() -> Self {
Self(None)
}
pub fn one(t: Trivium) -> Self {
Self(Some(Box::new([t])))
}
pub fn push(&mut self, t: Trivium) {
let mut v: Vec<Trivium> = std::mem::take(self).into();
v.push(t);
*self = v.into();
}
pub fn insert(&mut self, idx: usize, t: Trivium) {
let mut v: Vec<Trivium> = std::mem::take(self).into();
v.insert(idx, t);
*self = v.into();
}
pub fn extend<I: IntoIterator<Item = Trivium>>(&mut self, iter: I) {
let mut iter = iter.into_iter();
if let Some(first) = iter.next() {
let mut v: Vec<Trivium> = std::mem::take(self).into();
v.push(first);
v.extend(iter);
*self = v.into();
}
}
#[inline]
pub fn clear(&mut self) {
self.0 = None;
}
}
impl PartialEq for Trivia {
fn eq(&self, other: &Self) -> bool {
self[..] == other[..]
}
}
impl Eq for Trivia {}
impl std::ops::Deref for Trivia {
type Target = [Trivium];
#[inline]
fn deref(&self) -> &Self::Target {
match &self.0 {
Some(v) => v,
None => &[],
}
}
}
impl From<Vec<Trivium>> for Trivia {
fn from(value: Vec<Trivium>) -> Self {
if value.is_empty() {
Self(None)
} else {
Self(Some(value.into_boxed_slice()))
}
}
}
impl From<Trivia> for Vec<Trivium> {
fn from(val: Trivia) -> Self {
val.0.map(Self::from).unwrap_or_default()
}
}
impl IntoIterator for Trivia {
type Item = Trivium;
type IntoIter = std::vec::IntoIter<Trivium>;
fn into_iter(self) -> Self::IntoIter {
Vec::from(self).into_iter()
}
}
impl<'a> IntoIterator for &'a Trivia {
type Item = &'a Trivium;
type IntoIter = std::slice::Iter<'a, Trivium>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TrailingComment(pub Box<str>);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ann<T> {
pub pre_trivia: Trivia,
pub span: Span,
pub value: T,
pub trail_comment: Option<TrailingComment>,
}
impl<T: Clone> Ann<T> {
pub fn without_trail(&self) -> Self {
Self {
trail_comment: None,
..self.clone()
}
}
pub fn without_pre(&self) -> Self {
Self {
pre_trivia: Trivia::new(),
..self.clone()
}
}
}
pub struct AnnSlot<'a> {
pub pre_trivia: &'a Trivia,
pub trail_comment: &'a Option<TrailingComment>,
}
pub struct AnnSlotMut<'a> {
pub pre_trivia: &'a mut Trivia,
pub trail_comment: &'a mut Option<TrailingComment>,
}
impl<'a, T> From<&'a Ann<T>> for AnnSlot<'a> {
fn from(a: &'a Ann<T>) -> Self {
AnnSlot {
pre_trivia: &a.pre_trivia,
trail_comment: &a.trail_comment,
}
}
}
impl<'a, T> From<&'a mut Ann<T>> for AnnSlotMut<'a> {
fn from(a: &'a mut Ann<T>) -> Self {
AnnSlotMut {
pre_trivia: &mut a.pre_trivia,
trail_comment: &mut a.trail_comment,
}
}
}
pub trait FirstToken {
fn first_token(&self) -> AnnSlot<'_>;
fn first_token_mut(&mut self) -> AnnSlotMut<'_>;
}
macro_rules! first_token_arm {
(leaf, $rec:ident, $e:expr) => {
$e.into()
};
(recurse, $rec:ident, $e:expr) => {
$e.$rec()
};
}
macro_rules! first_token_impl {
($ty:ty; $($pat:pat => $kind:ident $e:expr),+ $(,)?) => {
impl FirstToken for $ty {
fn first_token(&self) -> AnnSlot<'_> {
match self { $($pat => first_token_arm!($kind, first_token, $e),)+ }
}
fn first_token_mut(&mut self) -> AnnSlotMut<'_> {
match self { $($pat => first_token_arm!($kind, first_token_mut, $e),)+ }
}
}
};
}
first_token_impl! { Term;
Self::Token(l) => leaf l,
Self::SimpleString(s) | Self::IndentedString(s) => leaf s,
Self::Path(p) => leaf p,
Self::List { open, .. }
| Self::Parenthesized { open, .. }
| Self::Set { rec: None, open, .. } => leaf open,
Self::Set { rec: Some(rec), .. } => leaf rec,
Self::Selection { base, .. } => recurse base,
}
first_token_impl! { Parameter;
Self::Id(n) => leaf n,
Self::Set { open, .. } => leaf open,
Self::Context { lhs, .. } => recurse lhs,
}
first_token_impl! { Expression;
Self::Term(t) => recurse t,
Self::With { kw_with: kw, .. }
| Self::Let { kw_let: kw, .. }
| Self::Assert { kw_assert: kw, .. }
| Self::If { kw_if: kw, .. }
| Self::Negation { minus: kw, .. }
| Self::Inversion { bang: kw, .. } => leaf kw,
Self::Abstraction { param, .. } => recurse param,
Self::Application { func: g, .. }
| Self::Operation { lhs: g, .. }
| Self::MemberCheck { lhs: g, .. } => recurse g,
}
impl From<&TrailingComment> for Trivium {
fn from(tc: &TrailingComment) -> Self {
Self::LineComment(format!(" {}", tc.0).into_boxed_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Item<T> {
Item(T),
Comments(Trivia),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Items<T>(pub Vec<Item<T>>);
pub type Leaf = Ann<Token>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StringPart {
TextPart(Box<str>),
Interpolation(Box<Whole<Expression>>),
}
pub type Path = Ann<Vec<StringPart>>;
pub type NixString = Ann<Vec<Vec<StringPart>>>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SimpleSelector {
ID(Leaf),
Interpol(Ann<StringPart>),
String(NixString),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Selector {
pub dot: Option<Leaf>,
pub selector: SimpleSelector,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Binder {
Inherit {
kw: Leaf,
from: Option<Term>,
attrs: Vec<SimpleSelector>,
semi: Leaf,
},
Assignment {
path: Vec<Selector>,
eq: Leaf,
value: Expression,
semi: Leaf,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SetDefault {
pub or_kw: Leaf,
pub value: Box<Term>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Term {
Token(Leaf),
SimpleString(NixString),
IndentedString(NixString),
Path(Path),
List {
open: Leaf,
items: Items<Self>,
close: Leaf,
},
Set {
rec: Option<Leaf>,
open: Leaf,
items: Items<Binder>,
close: Leaf,
},
Selection {
base: Box<Self>,
selectors: Vec<Selector>,
default: Option<SetDefault>,
},
Parenthesized {
open: Leaf,
expr: Box<Expression>,
close: Leaf,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParamDefault {
pub question: Leaf,
pub value: Expression,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(clippy::large_enum_variant)]
pub enum ParamAttr {
Attr {
name: Leaf,
default: Option<ParamDefault>,
comma: Option<Leaf>,
},
Ellipsis(Leaf),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Parameter {
Id(Leaf),
Set {
open: Leaf,
attrs: Vec<ParamAttr>,
close: Leaf,
},
Context {
lhs: Box<Self>,
at: Leaf,
rhs: Box<Self>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expression {
Term(Term),
With {
kw_with: Leaf,
scope: Box<Self>,
semi: Leaf,
body: Box<Self>,
},
Let {
kw_let: Leaf,
bindings: Items<Binder>,
kw_in: Leaf,
body: Box<Self>,
},
Assert {
kw_assert: Leaf,
cond: Box<Self>,
semi: Leaf,
body: Box<Self>,
},
If {
kw_if: Leaf,
cond: Box<Self>,
kw_then: Leaf,
then_branch: Box<Self>,
kw_else: Leaf,
else_branch: Box<Self>,
},
Abstraction {
param: Parameter,
colon: Leaf,
body: Box<Self>,
},
Application {
func: Box<Self>,
arg: Box<Self>,
},
Operation {
lhs: Box<Self>,
op: Leaf,
rhs: Box<Self>,
},
MemberCheck {
lhs: Box<Self>,
question: Leaf,
path: Vec<Selector>,
},
Negation {
minus: Leaf,
expr: Box<Self>,
},
Inversion {
bang: Leaf,
expr: Box<Self>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Whole<T> {
pub value: T,
pub trailing_trivia: Trivia,
}
pub type File = Whole<Expression>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Token {
Integer(TokenText),
Float(TokenText),
Identifier(TokenText),
EnvPath(TokenText),
KAssert,
KElse,
KIf,
KIn,
KInherit,
KLet,
KOr,
KRec,
KThen,
KWith,
TBraceOpen,
TBraceClose,
TBrackOpen,
TBrackClose,
TInterOpen, TInterClose, TParenOpen,
TParenClose,
TAssign, TAt, TColon, TComma, TDot, TDoubleQuote, TDoubleSingleQuote, TEllipsis, TQuestion, TSemicolon, TConcat, TNegate, TUpdate, TPlus, TMinus, TMul, TDiv, TAnd, TOr, TEqual, TGreater, TGreaterEqual, TImplies, TLess, TLessEqual, TNot, TUnequal, TPipeForward, TPipeBackward,
Sof, TTilde, }
impl Token {
pub fn text(&self) -> &str {
match self {
Self::Identifier(s) | Self::Integer(s) | Self::Float(s) | Self::EnvPath(s) => {
s.as_str()
}
Self::KAssert => "assert",
Self::KElse => "else",
Self::KIf => "if",
Self::KIn => "in",
Self::KInherit => "inherit",
Self::KLet => "let",
Self::KOr => "or",
Self::KRec => "rec",
Self::KThen => "then",
Self::KWith => "with",
Self::TBraceOpen => "{",
Self::TBraceClose | Self::TInterClose => "}",
Self::TBrackOpen => "[",
Self::TBrackClose => "]",
Self::TInterOpen => "${",
Self::TParenOpen => "(",
Self::TParenClose => ")",
Self::TAssign => "=",
Self::TAt => "@",
Self::TColon => ":",
Self::TComma => ",",
Self::TDot => ".",
Self::TDoubleQuote => "\"",
Self::TDoubleSingleQuote => "''",
Self::TEllipsis => "...",
Self::TQuestion => "?",
Self::TSemicolon => ";",
Self::TPlus => "+",
Self::TMinus | Self::TNegate => "-",
Self::TMul => "*",
Self::TDiv => "/",
Self::TConcat => "++",
Self::TUpdate => "//",
Self::TAnd => "&&",
Self::TOr => "||",
Self::TEqual => "==",
Self::TGreater => ">",
Self::TGreaterEqual => ">=",
Self::TImplies => "->",
Self::TLess => "<",
Self::TLessEqual => "<=",
Self::TNot => "!",
Self::TUnequal => "!=",
Self::TPipeForward => "|>",
Self::TPipeBackward => "<|",
Self::Sof => "end of file",
Self::TTilde => "~",
}
}
}
impl Token {
pub const fn is_update_concat_plus(&self) -> bool {
matches!(self, Self::TUpdate | Self::TConcat | Self::TPlus)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn trivia_is_two_words() {
assert_eq!(std::mem::size_of::<Trivia>(), 16);
}
}