1use std::mem;
2use std::str::FromStr;
3
4use combine::easy::Error;
5use combine::error::StreamError;
6
7use format::{Displayable, Formatter};
8use position::Pos;
9use tokenizer::Token;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct Value {
19 position: Pos,
20 pub(crate) data: Vec<Item>,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub(crate) enum Item {
25 Literal(String),
26 Variable(String),
27}
28
29
30impl Value {
31 pub(crate) fn parse<'a>(position: Pos, tok: Token<'a>)
32 -> Result<Value, Error<Token<'a>, Token<'a>>>
33 {
34 Value::parse_str(position, tok.value)
35 }
36 pub(crate) fn parse_str<'a>(position: Pos, token: &str)
37 -> Result<Value, Error<Token<'a>, Token<'a>>>
38 {
39 let data = if token.starts_with('"') {
40 Value::scan_quoted('"', token)?
41 } else if token.starts_with("'") {
42 Value::scan_quoted('\'', token)?
43 } else {
44 Value::scan_raw(token)?
45 };
46 Ok(Value { position, data })
47 }
48
49 fn scan_raw<'a>(value: &str)
50 -> Result<Vec<Item>, Error<Token<'a>, Token<'a>>>
51 {
52 use self::Item::*;
53 let mut buf = Vec::new();
54 let mut chiter = value.char_indices().peekable();
55 let mut prev_char = ' '; let mut cur_slice = 0;
57 while let Some((idx, cur_char)) = chiter.next() {
59 match cur_char {
60 _ if prev_char == '\\' => {
61 prev_char = ' ';
62 continue;
63 }
64 '$' => {
65 let vstart = idx + 1;
66 if idx != cur_slice {
67 buf.push(Literal(value[cur_slice..idx].to_string()));
68 }
69 let fchar = chiter.next().map(|(_, c)| c)
70 .ok_or_else(|| Error::unexpected_message(
71 "bare $ in expression"))?;
72 match fchar {
73 '{' => {
74 while let Some(&(_, c)) = chiter.peek() {
75 match c {
76 'a'...'z' | 'A'...'Z' | '_' | '0'...'9'
77 => chiter.next(),
78 '}' => break,
79 _ => {
80 return Err(Error::expected("}".into()));
81 }
82 };
83 }
84 let now = chiter.peek().map(|&(idx, _)| idx)
85 .unwrap();
86 buf.push(Variable(
87 value[vstart+1..now].to_string()));
88 cur_slice = now+1;
89 }
90 'a'...'z' | 'A'...'Z' | '_' | '0'...'9' => {
91 while let Some(&(_, c)) = chiter.peek() {
92 match c {
93 'a'...'z' | 'A'...'Z' | '_' | '0'...'9'
94 => chiter.next(),
95 _ => break,
96 };
97 }
98 let now = chiter.peek().map(|&(idx, _)| idx)
99 .unwrap_or(value.len());
100 buf.push(Variable(
101 value[vstart..now].to_string()));
102 cur_slice = now;
103 }
104 _ => {
105 return Err(Error::unexpected_message(
106 format!("variable name starts with \
107 bad char {:?}", fchar)));
108 }
109 }
110 }
111 _ => {}
112 }
113 prev_char = cur_char;
114 }
115 if cur_slice != value.len() {
116 buf.push(Literal(value[cur_slice..].to_string()));
117 }
118 Ok(buf)
119 }
120
121 fn scan_quoted<'a>(quote: char, value: &str)
122 -> Result<Vec<Item>, Error<Token<'a>, Token<'a>>>
123 {
124 use self::Item::*;
125 let mut buf = Vec::new();
126 let mut chiter = value.char_indices().peekable();
127 chiter.next(); let mut prev_char = ' '; let mut cur_slice = String::new();
130 while let Some((idx, cur_char)) = chiter.next() {
131 match cur_char {
132 _ if prev_char == '\\' => {
133 cur_slice.push(cur_char);
134 continue;
135 }
136 '"' | '\'' if cur_char == quote => {
137 if cur_slice.len() > 0 {
138 buf.push(Literal(cur_slice));
139 }
140 if idx + 1 != value.len() {
141 return Err(Error::unexpected_message(
145 "quote closes prematurely"));
146 }
147 return Ok(buf);
148 }
149 '$' => {
150 let vstart = idx + 1;
151 if cur_slice.len() > 0 {
152 buf.push(Literal(
153 mem::replace(&mut cur_slice, String::new())));
154 }
155 let fchar = chiter.next().map(|(_, c)| c)
156 .ok_or_else(|| Error::unexpected_message(
157 "bare $ in expression"))?;
158 match fchar {
159 '{' => {
160 unimplemented!();
161 }
162 'a'...'z' | 'A'...'Z' | '_' | '0'...'9' => {
163 while let Some(&(_, c)) = chiter.peek() {
164 match c {
165 'a'...'z' | 'A'...'Z' | '_' | '0'...'9'
166 => chiter.next(),
167 _ => break,
168 };
169 }
170 let now = chiter.peek().map(|&(idx, _)| idx)
171 .ok_or_else(|| {
172 Error::unexpected_message("unclosed quote")
173 })?;
174 buf.push(Variable(
175 value[vstart..now].to_string()));
176 }
177 _ => {
178 return Err(Error::unexpected_message(
179 format!("variable name starts with \
180 bad char {:?}", fchar)));
181 }
182 }
183 }
184 _ => cur_slice.push(cur_char),
185 }
186 prev_char = cur_char;
187 }
188 return Err(Error::unexpected_message("unclosed quote"));
189 }
190}
191
192impl FromStr for Value {
193 type Err = String;
194 fn from_str(s: &str) -> Result<Value, String> {
195 Value::parse_str(Pos { line: 0, column: 0 }, s)
196 .map_err(|e| e.to_string())
197 }
198}
199
200impl Value {
201 fn has_specials(&self) -> bool {
202 use self::Item::*;
203 for item in &self.data {
204 match *item {
205 Literal(ref x) => {
206 for c in x.chars() {
207 match c {
208 ' ' | ';' | '\r' | '\n' | '\t' | '{' | '}' => {
209 return true;
210 }
211 _ => {}
212 }
213 }
214 }
215 Variable(_) => {}
216 }
217 }
218 return false;
219 }
220
221 pub fn replace_vars<'a, F, S>(&mut self, mut f: F)
223 where F: FnMut(&str) -> Option<S>,
224 S: AsRef<str> + Into<String> + 'a,
225 {
226 use self::Item::*;
227 for item in &mut self.data {
229 let new_value = match *item {
230 Literal(..) => continue,
231 Variable(ref name) => match f(name) {
232 Some(value) => value.into(),
233 None => continue,
234 },
235 };
236 *item = Literal(new_value);
237 }
238 }
239}
240
241fn next_alphanum(data: &Vec<Item>, index: usize) -> bool {
242 use self::Item::*;
243 data.get(index+1).and_then(|item| {
244 match item {
245 Literal(s) => Some(s),
246 Variable(_) => None,
247 }
248 }).and_then(|s| {
249 s.chars().next().map(|c| c.is_alphanumeric())
250 }).unwrap_or(false)
251}
252
253impl Displayable for Value {
254 fn display(&self, f: &mut Formatter) {
255 use self::Item::*;
256 if self.data.is_empty() || self.has_specials() {
257 f.write("\"");
258 for (index, item) in self.data.iter().enumerate() {
259 match *item {
260 Literal(ref v) => f.write(v),
262 Variable(ref v) if next_alphanum(&self.data, index) => {
263 f.write("${");
264 f.write(v);
265 f.write("}");
266 }
267 Variable(ref v) => {
268 f.write("$");
269 f.write(v);
270 }
271 }
272 }
273 f.write("\"");
274 } else {
275 for (index, item) in self.data.iter().enumerate() {
276 match *item {
277 Literal(ref v) => f.write(v),
278 Variable(ref v) if next_alphanum(&self.data, index) => {
279 f.write("${");
280 f.write(v);
281 f.write("}");
282 }
283 Variable(ref v) => {
284 f.write("$");
285 f.write(v);
286 }
287 }
288 }
289 }
290 }
291}