1use crate::ast::{Atom, 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 Spanned {
35 pub fn new(span: Span, form: SpannedForm) -> Self {
36 Self { span, form }
37 }
38
39 pub fn synthetic_nil() -> Self {
42 Self {
43 span: Span::synthetic(),
44 form: SpannedForm::Nil,
45 }
46 }
47
48 pub fn to_sexp(&self) -> Sexp {
50 match &self.form {
51 SpannedForm::Nil => Sexp::Nil,
52 SpannedForm::Atom(a) => Sexp::Atom(a.clone()),
53 SpannedForm::List(xs) => Sexp::List(xs.iter().map(Spanned::to_sexp).collect()),
54 SpannedForm::Quote(inner) => Sexp::Quote(Box::new(inner.to_sexp())),
55 SpannedForm::Quasiquote(inner) => Sexp::Quasiquote(Box::new(inner.to_sexp())),
56 SpannedForm::Unquote(inner) => Sexp::Unquote(Box::new(inner.to_sexp())),
57 SpannedForm::UnquoteSplice(inner) => Sexp::UnquoteSplice(Box::new(inner.to_sexp())),
58 }
59 }
60
61 pub fn from_sexp_synthetic(s: &Sexp) -> Self {
65 Self::from_sexp_at(s, Span::synthetic())
66 }
67
68 pub fn from_sexp_at(s: &Sexp, span: Span) -> Self {
72 let form = match s {
73 Sexp::Nil => SpannedForm::Nil,
74 Sexp::Atom(a) => SpannedForm::Atom(a.clone()),
75 Sexp::List(xs) => {
76 SpannedForm::List(xs.iter().map(|x| Spanned::from_sexp_at(x, span)).collect())
77 }
78 Sexp::Quote(inner) => SpannedForm::Quote(Box::new(Spanned::from_sexp_at(inner, span))),
79 Sexp::Quasiquote(inner) => {
80 SpannedForm::Quasiquote(Box::new(Spanned::from_sexp_at(inner, span)))
81 }
82 Sexp::Unquote(inner) => {
83 SpannedForm::Unquote(Box::new(Spanned::from_sexp_at(inner, span)))
84 }
85 Sexp::UnquoteSplice(inner) => {
86 SpannedForm::UnquoteSplice(Box::new(Spanned::from_sexp_at(inner, span)))
87 }
88 };
89 Spanned { span, form }
90 }
91
92 pub fn is_list(&self) -> bool {
95 matches!(self.form, SpannedForm::List(_))
96 }
97 pub fn as_list(&self) -> Option<&[Spanned]> {
98 match &self.form {
99 SpannedForm::List(xs) => Some(xs),
100 _ => None,
101 }
102 }
103 pub fn as_symbol(&self) -> Option<&str> {
104 match &self.form {
105 SpannedForm::Atom(Atom::Symbol(s)) => Some(s),
106 _ => None,
107 }
108 }
109 pub fn as_keyword(&self) -> Option<&str> {
110 match &self.form {
111 SpannedForm::Atom(Atom::Keyword(s)) => Some(s),
112 _ => None,
113 }
114 }
115 pub fn as_string(&self) -> Option<&str> {
116 match &self.form {
117 SpannedForm::Atom(Atom::Str(s)) => Some(s),
118 _ => None,
119 }
120 }
121 pub fn as_int(&self) -> Option<i64> {
122 match &self.form {
123 SpannedForm::Atom(Atom::Int(n)) => Some(*n),
124 _ => None,
125 }
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn to_sexp_roundtrip_preserves_structure() {
135 let s = Spanned::new(
136 Span::new(0, 10),
137 SpannedForm::List(vec![
138 Spanned::new(
139 Span::new(1, 4),
140 SpannedForm::Atom(Atom::Symbol("foo".into())),
141 ),
142 Spanned::new(Span::new(5, 6), SpannedForm::Atom(Atom::Int(42))),
143 ]),
144 );
145 let plain = s.to_sexp();
146 assert_eq!(plain, Sexp::List(vec![Sexp::symbol("foo"), Sexp::int(42)]));
147 }
148
149 #[test]
150 fn from_sexp_synthetic_marks_all_nodes() {
151 let s = Sexp::List(vec![Sexp::symbol("a"), Sexp::List(vec![Sexp::int(1)])]);
152 let lifted = Spanned::from_sexp_synthetic(&s);
153 assert!(lifted.span.is_synthetic());
154 let SpannedForm::List(children) = &lifted.form else {
155 panic!("expected list")
156 };
157 assert!(children[0].span.is_synthetic());
158 let SpannedForm::List(inner) = &children[1].form else {
159 panic!("expected inner list")
160 };
161 assert!(inner[0].span.is_synthetic());
162 }
163
164 #[test]
165 fn from_sexp_at_stamps_span_on_every_node() {
166 let s = Sexp::List(vec![Sexp::symbol("a"), Sexp::int(1)]);
167 let sp = Span::new(10, 20);
168 let lifted = Spanned::from_sexp_at(&s, sp);
169 assert_eq!(lifted.span, sp);
170 let SpannedForm::List(children) = &lifted.form else {
171 panic!()
172 };
173 assert_eq!(children[0].span, sp);
174 assert_eq!(children[1].span, sp);
175 }
176}