rune/ast/
lit_str.rs
1use crate::alloc::borrow::Cow;
2use crate::ast::prelude::*;
3
4#[test]
5#[cfg(not(miri))]
6fn ast_parse() {
7 rt::<ast::LitStr>("\"hello world\"");
8 rt::<ast::LitStr>("\"hello\\nworld\"");
9}
10
11#[derive(Debug, TryClone, Clone, Copy, PartialEq, Eq, Spanned)]
16#[try_clone(copy)]
17#[non_exhaustive]
18pub struct LitStr {
19 pub span: Span,
21 #[rune(skip)]
23 pub source: ast::StrSource,
24}
25
26impl LitStr {
27 pub(crate) fn resolve_template_string<'a>(
29 &self,
30 cx: ResolveContext<'a>,
31 ) -> Result<Cow<'a, str>> {
32 self.resolve_inner(cx, ast::unescape::WithTemplate(true))
33 }
34
35 pub(crate) fn resolve_string<'a>(&self, cx: ResolveContext<'a>) -> Result<Cow<'a, str>> {
37 self.resolve_inner(cx, ast::unescape::WithTemplate(false))
38 }
39
40 fn resolve_inner<'a>(
42 &self,
43 cx: ResolveContext<'a>,
44 with_template: ast::unescape::WithTemplate,
45 ) -> Result<Cow<'a, str>> {
46 let span = self.span;
47
48 let text = match self.source {
49 ast::StrSource::Text(text) => text,
50 ast::StrSource::Synthetic(id) => {
51 let bytes = cx.storage.get_string(id).ok_or_else(|| {
52 compile::Error::new(
53 span,
54 ErrorKind::BadSyntheticId {
55 kind: SyntheticKind::String,
56 id,
57 },
58 )
59 })?;
60
61 return Ok(Cow::Borrowed(bytes));
62 }
63 };
64
65 let span = if text.wrapped {
66 span.narrow(1u32)
67 } else {
68 span
69 };
70
71 let string = cx
72 .sources
73 .source(text.source_id, span)
74 .ok_or_else(|| compile::Error::new(span, ErrorKind::BadSlice))?;
75
76 Ok(if text.escaped {
77 Cow::Owned(Self::parse_escaped(span, string, with_template)?)
78 } else {
79 Cow::Borrowed(string)
80 })
81 }
82
83 fn parse_escaped(
84 span: Span,
85 source: &str,
86 with_template: ast::unescape::WithTemplate,
87 ) -> Result<String> {
88 let mut buffer = String::try_with_capacity(source.len())?;
89
90 let start = span.start.into_usize();
91
92 let mut it = source
93 .char_indices()
94 .map(|(n, c)| (start + n, c))
95 .peekable();
96
97 while let Some((start, c)) = it.next() {
98 buffer.try_extend(match c {
99 '\\' => match ast::unescape::parse_char_escape(
100 &mut it,
101 with_template,
102 ast::unescape::WithLineCont(true),
103 ) {
104 Ok(c) => c,
105 Err(kind) => {
106 let end = it
107 .next()
108 .map(|n| n.0)
109 .unwrap_or_else(|| span.end.into_usize());
110 return Err(compile::Error::new(Span::new(start, end), kind));
111 }
112 },
113 c => Some(c),
114 })?;
115 }
116
117 Ok(buffer)
118 }
119}
120
121impl ToAst for LitStr {
122 fn to_ast(span: Span, kind: ast::Kind) -> Result<Self> {
123 match kind {
124 K![str(source)] => Ok(Self { span, source }),
125 _ => Err(compile::Error::expected(
126 ast::Token { span, kind },
127 Self::into_expectation(),
128 )),
129 }
130 }
131
132 #[inline]
133 fn matches(kind: &ast::Kind) -> bool {
134 matches!(kind, K![str])
135 }
136
137 #[inline]
138 fn into_expectation() -> Expectation {
139 Expectation::Description("a string literal")
140 }
141}
142
143impl Parse for LitStr {
144 fn parse(parser: &mut Parser<'_>) -> Result<Self> {
145 let t = parser.next()?;
146 LitStr::to_ast(t.span, t.kind)
147 }
148}
149
150impl<'a> Resolve<'a> for LitStr {
151 type Output = Cow<'a, str>;
152
153 fn resolve(&self, cx: ResolveContext<'a>) -> Result<Cow<'a, str>> {
154 self.resolve_string(cx)
155 }
156}
157
158impl ToTokens for LitStr {
159 fn to_tokens(
160 &self,
161 _: &mut MacroContext<'_, '_, '_>,
162 stream: &mut TokenStream,
163 ) -> alloc::Result<()> {
164 stream.push(ast::Token {
165 span: self.span,
166 kind: ast::Kind::Str(self.source),
167 })
168 }
169}