1use crate::ast::{Atom, QuoteForm, Sexp};
12use crate::span::Span;
13
14#[derive(Clone, Debug, PartialEq)]
16pub struct Spanned {
17 pub span: Span,
18 pub form: SpannedForm,
19}
20
21#[derive(Clone, Debug, PartialEq)]
24pub enum SpannedForm {
25 Nil,
26 Atom(Atom),
27 List(Vec<Spanned>),
28 Quote(Box<Spanned>),
29 Quasiquote(Box<Spanned>),
30 Unquote(Box<Spanned>),
31 UnquoteSplice(Box<Spanned>),
32}
33
34impl SpannedForm {
35 #[must_use]
53 pub fn wrap(qf: QuoteForm, inner: Spanned) -> Self {
54 let boxed = Box::new(inner);
55 match qf {
56 QuoteForm::Quote => Self::Quote(boxed),
57 QuoteForm::Quasiquote => Self::Quasiquote(boxed),
58 QuoteForm::Unquote => Self::Unquote(boxed),
59 QuoteForm::UnquoteSplice => Self::UnquoteSplice(boxed),
60 }
61 }
62}
63
64impl Spanned {
65 pub fn new(span: Span, form: SpannedForm) -> Self {
66 Self { span, form }
67 }
68
69 pub fn synthetic_nil() -> Self {
72 Self {
73 span: Span::synthetic(),
74 form: SpannedForm::Nil,
75 }
76 }
77
78 pub fn to_sexp(&self) -> Sexp {
80 match &self.form {
81 SpannedForm::Nil => Sexp::Nil,
82 SpannedForm::Atom(a) => Sexp::Atom(a.clone()),
83 SpannedForm::List(xs) => Sexp::List(xs.iter().map(Spanned::to_sexp).collect()),
84 SpannedForm::Quote(inner) => Sexp::Quote(Box::new(inner.to_sexp())),
85 SpannedForm::Quasiquote(inner) => Sexp::Quasiquote(Box::new(inner.to_sexp())),
86 SpannedForm::Unquote(inner) => Sexp::Unquote(Box::new(inner.to_sexp())),
87 SpannedForm::UnquoteSplice(inner) => Sexp::UnquoteSplice(Box::new(inner.to_sexp())),
88 }
89 }
90
91 pub fn from_sexp_synthetic(s: &Sexp) -> Self {
95 Self::from_sexp_at(s, Span::synthetic())
96 }
97
98 pub fn from_sexp_at(s: &Sexp, span: Span) -> Self {
102 let form = match s {
103 Sexp::Nil => SpannedForm::Nil,
104 Sexp::Atom(a) => SpannedForm::Atom(a.clone()),
105 Sexp::List(xs) => {
106 SpannedForm::List(xs.iter().map(|x| Spanned::from_sexp_at(x, span)).collect())
107 }
108 Sexp::Quote(inner) => SpannedForm::Quote(Box::new(Spanned::from_sexp_at(inner, span))),
109 Sexp::Quasiquote(inner) => {
110 SpannedForm::Quasiquote(Box::new(Spanned::from_sexp_at(inner, span)))
111 }
112 Sexp::Unquote(inner) => {
113 SpannedForm::Unquote(Box::new(Spanned::from_sexp_at(inner, span)))
114 }
115 Sexp::UnquoteSplice(inner) => {
116 SpannedForm::UnquoteSplice(Box::new(Spanned::from_sexp_at(inner, span)))
117 }
118 };
119 Spanned { span, form }
120 }
121
122 pub fn is_list(&self) -> bool {
125 matches!(self.form, SpannedForm::List(_))
126 }
127 pub fn as_list(&self) -> Option<&[Spanned]> {
128 match &self.form {
129 SpannedForm::List(xs) => Some(xs),
130 _ => None,
131 }
132 }
133 pub fn as_symbol(&self) -> Option<&str> {
134 match &self.form {
135 SpannedForm::Atom(Atom::Symbol(s)) => Some(s),
136 _ => None,
137 }
138 }
139 pub fn as_keyword(&self) -> Option<&str> {
140 match &self.form {
141 SpannedForm::Atom(Atom::Keyword(s)) => Some(s),
142 _ => None,
143 }
144 }
145 pub fn as_string(&self) -> Option<&str> {
146 match &self.form {
147 SpannedForm::Atom(Atom::Str(s)) => Some(s),
148 _ => None,
149 }
150 }
151 pub fn as_int(&self) -> Option<i64> {
152 match &self.form {
153 SpannedForm::Atom(Atom::Int(n)) => Some(*n),
154 _ => None,
155 }
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn to_sexp_roundtrip_preserves_structure() {
165 let s = Spanned::new(
166 Span::new(0, 10),
167 SpannedForm::List(vec![
168 Spanned::new(
169 Span::new(1, 4),
170 SpannedForm::Atom(Atom::Symbol("foo".into())),
171 ),
172 Spanned::new(Span::new(5, 6), SpannedForm::Atom(Atom::Int(42))),
173 ]),
174 );
175 let plain = s.to_sexp();
176 assert_eq!(plain, Sexp::List(vec![Sexp::symbol("foo"), Sexp::int(42)]));
177 }
178
179 #[test]
180 fn from_sexp_synthetic_marks_all_nodes() {
181 let s = Sexp::List(vec![Sexp::symbol("a"), Sexp::List(vec![Sexp::int(1)])]);
182 let lifted = Spanned::from_sexp_synthetic(&s);
183 assert!(lifted.span.is_synthetic());
184 let SpannedForm::List(children) = &lifted.form else {
185 panic!("expected list")
186 };
187 assert!(children[0].span.is_synthetic());
188 let SpannedForm::List(inner) = &children[1].form else {
189 panic!("expected inner list")
190 };
191 assert!(inner[0].span.is_synthetic());
192 }
193
194 #[test]
195 fn spanned_form_wrap_is_the_span_carrying_dual_of_quote_form_wrap() {
196 let inner_plain = Sexp::symbol("payload");
202 for qf in QuoteForm::ALL {
203 let inner = Spanned::from_sexp_at(&inner_plain, Span::new(1, 8));
204 let wrapped = Spanned::new(Span::new(0, 8), SpannedForm::wrap(qf, inner));
205 assert_eq!(
206 wrapped.to_sexp(),
207 qf.wrap(inner_plain.clone()),
208 "QuoteForm::{qf:?} — SpannedForm::wrap drifted from QuoteForm::wrap"
209 );
210 }
211 }
212
213 #[test]
214 fn from_sexp_at_stamps_span_on_every_node() {
215 let s = Sexp::List(vec![Sexp::symbol("a"), Sexp::int(1)]);
216 let sp = Span::new(10, 20);
217 let lifted = Spanned::from_sexp_at(&s, sp);
218 assert_eq!(lifted.span, sp);
219 let SpannedForm::List(children) = &lifted.form else {
220 panic!()
221 };
222 assert_eq!(children[0].span, sp);
223 assert_eq!(children[1].span, sp);
224 }
225}