1use nu_engine::command_prelude::*;
2use nu_parser::{flatten_block, parse};
3use nu_protocol::{engine::StateWorkingSet, record};
4use serde_json::{Value as JsonValue, json};
5
6#[derive(Clone)]
7pub struct Ast;
8
9impl Command for Ast {
10 fn name(&self) -> &str {
11 "ast"
12 }
13
14 fn description(&self) -> &str {
15 "Print the abstract syntax tree (ast) for a pipeline."
16 }
17
18 fn signature(&self) -> Signature {
19 Signature::build("ast")
20 .input_output_types(vec![
21 (Type::Nothing, Type::table()),
22 (Type::Nothing, Type::record()),
23 (Type::Nothing, Type::String),
24 ])
25 .required(
26 "pipeline",
27 SyntaxShape::String,
28 "The pipeline to print the ast for.",
29 )
30 .switch("json", "Serialize to json", Some('j'))
31 .switch("minify", "Minify the nuon or json output", Some('m'))
32 .switch("flatten", "An easier to read version of the ast", Some('f'))
33 .allow_variants_without_examples(true)
34 .category(Category::Debug)
35 }
36
37 fn examples(&self) -> Vec<Example> {
38 vec![
39 Example {
40 description: "Print the ast of a string",
41 example: "ast 'hello'",
42 result: None,
43 },
44 Example {
45 description: "Print the ast of a pipeline",
46 example: "ast 'ls | where name =~ README'",
47 result: None,
48 },
49 Example {
50 description: "Print the ast of a pipeline with an error",
51 example: "ast 'for x in 1..10 { echo $x '",
52 result: None,
53 },
54 Example {
55 description: "Print the ast of a pipeline with an error, as json, in a nushell table",
56 example: "ast 'for x in 1..10 { echo $x ' --json | get block | from json",
57 result: None,
58 },
59 Example {
60 description: "Print the ast of a pipeline with an error, as json, minified",
61 example: "ast 'for x in 1..10 { echo $x ' --json --minify",
62 result: None,
63 },
64 Example {
65 description: "Print the ast of a string flattened",
66 example: r#"ast "'hello'" --flatten"#,
67 result: Some(Value::test_list(vec![Value::test_record(record! {
68 "content" => Value::test_string("'hello'"),
69 "shape" => Value::test_string("shape_string"),
70 "span" => Value::test_record(record! {
71 "start" => Value::test_int(0),
72 "end" => Value::test_int(7),}),
73 })])),
74 },
75 Example {
76 description: "Print the ast of a string flattened, as json, minified",
77 example: r#"ast "'hello'" --flatten --json --minify"#,
78 result: Some(Value::test_string(
79 r#"[{"content":"'hello'","shape":"shape_string","span":{"start":0,"end":7}}]"#,
80 )),
81 },
82 Example {
83 description: "Print the ast of a pipeline flattened",
84 example: r#"ast 'ls | sort-by type name -i' --flatten"#,
85 result: Some(Value::test_list(vec![
86 Value::test_record(record! {
87 "content" => Value::test_string("ls"),
88 "shape" => Value::test_string("shape_external"),
89 "span" => Value::test_record(record! {
90 "start" => Value::test_int(0),
91 "end" => Value::test_int(2),}),
92 }),
93 Value::test_record(record! {
94 "content" => Value::test_string("|"),
95 "shape" => Value::test_string("shape_pipe"),
96 "span" => Value::test_record(record! {
97 "start" => Value::test_int(3),
98 "end" => Value::test_int(4),}),
99 }),
100 Value::test_record(record! {
101 "content" => Value::test_string("sort-by"),
102 "shape" => Value::test_string("shape_internalcall"),
103 "span" => Value::test_record(record! {
104 "start" => Value::test_int(5),
105 "end" => Value::test_int(12),}),
106 }),
107 Value::test_record(record! {
108 "content" => Value::test_string("type"),
109 "shape" => Value::test_string("shape_string"),
110 "span" => Value::test_record(record! {
111 "start" => Value::test_int(13),
112 "end" => Value::test_int(17),}),
113 }),
114 Value::test_record(record! {
115 "content" => Value::test_string("name"),
116 "shape" => Value::test_string("shape_string"),
117 "span" => Value::test_record(record! {
118 "start" => Value::test_int(18),
119 "end" => Value::test_int(22),}),
120 }),
121 Value::test_record(record! {
122 "content" => Value::test_string("-i"),
123 "shape" => Value::test_string("shape_flag"),
124 "span" => Value::test_record(record! {
125 "start" => Value::test_int(23),
126 "end" => Value::test_int(25),}),
127 }),
128 ])),
129 },
130 ]
131 }
132
133 fn run(
134 &self,
135 engine_state: &EngineState,
136 stack: &mut Stack,
137 call: &Call,
138 _input: PipelineData,
139 ) -> Result<PipelineData, ShellError> {
140 let pipeline: Spanned<String> = call.req(engine_state, stack, 0)?;
141 let to_json = call.has_flag(engine_state, stack, "json")?;
142 let minify = call.has_flag(engine_state, stack, "minify")?;
143 let flatten = call.has_flag(engine_state, stack, "flatten")?;
144
145 let mut working_set = StateWorkingSet::new(engine_state);
146 let offset = working_set.next_span_start();
147 let parsed_block = parse(&mut working_set, None, pipeline.item.as_bytes(), false);
148
149 if flatten {
150 let flat = flatten_block(&working_set, &parsed_block);
151 if to_json {
152 let mut json_val: JsonValue = json!([]);
153 for (span, shape) in flat {
154 let content =
155 String::from_utf8_lossy(working_set.get_span_contents(span)).to_string();
156
157 let json = json!(
158 {
159 "content": content,
160 "shape": shape.to_string(),
161 "span": {
162 "start": span.start.checked_sub(offset),
163 "end": span.end.checked_sub(offset),
164 },
165 }
166 );
167 json_merge(&mut json_val, &json);
168 }
169 let json_string = if minify {
170 if let Ok(json_str) = serde_json::to_string(&json_val) {
171 json_str
172 } else {
173 "{}".to_string()
174 }
175 } else if let Ok(json_str) = serde_json::to_string_pretty(&json_val) {
176 json_str
177 } else {
178 "{}".to_string()
179 };
180
181 Ok(Value::string(json_string, pipeline.span).into_pipeline_data())
182 } else {
183 let mut rec = vec![];
185 for (span, shape) in flat {
186 let content =
187 String::from_utf8_lossy(working_set.get_span_contents(span)).to_string();
188 let each_rec = record! {
189 "content" => Value::test_string(content),
190 "shape" => Value::test_string(shape.to_string()),
191 "span" => Value::test_record(record!{
192 "start" => Value::test_int(match span.start.checked_sub(offset) {
193 Some(start) => start as i64,
194 None => 0
195 }),
196 "end" => Value::test_int(match span.end.checked_sub(offset) {
197 Some(end) => end as i64,
198 None => 0
199 }),
200 }),
201 };
202 rec.push(Value::test_record(each_rec));
203 }
204 Ok(Value::list(rec, pipeline.span).into_pipeline_data())
205 }
206 } else {
207 let error_output = working_set.parse_errors.first();
208 let block_span = match &parsed_block.span {
209 Some(span) => span,
210 None => &pipeline.span,
211 };
212 if to_json {
213 let serde_block_str = if minify {
215 serde_json::to_string(&*parsed_block)
216 } else {
217 serde_json::to_string_pretty(&*parsed_block)
218 };
219 let block_json = match serde_block_str {
220 Ok(json) => json,
221 Err(e) => Err(ShellError::CantConvert {
222 to_type: "string".to_string(),
223 from_type: "block".to_string(),
224 span: *block_span,
225 help: Some(format!(
226 "Error: {e}\nCan't convert {parsed_block:?} to string"
227 )),
228 })?,
229 };
230 let serde_error_str = if minify {
232 serde_json::to_string(&error_output)
233 } else {
234 serde_json::to_string_pretty(&error_output)
235 };
236
237 let error_json = match serde_error_str {
238 Ok(json) => json,
239 Err(e) => Err(ShellError::CantConvert {
240 to_type: "string".to_string(),
241 from_type: "error".to_string(),
242 span: *block_span,
243 help: Some(format!(
244 "Error: {e}\nCan't convert {error_output:?} to string"
245 )),
246 })?,
247 };
248
249 let output_record = Value::record(
251 record! {
252 "block" => Value::string(block_json, *block_span),
253 "error" => Value::string(error_json, Span::test_data()),
254 },
255 pipeline.span,
256 );
257 Ok(output_record.into_pipeline_data())
258 } else {
259 let block_value = Value::string(
260 if minify {
261 format!("{parsed_block:?}")
262 } else {
263 format!("{parsed_block:#?}")
264 },
265 pipeline.span,
266 );
267 let error_value = Value::string(
268 if minify {
269 format!("{error_output:?}")
270 } else {
271 format!("{error_output:#?}")
272 },
273 pipeline.span,
274 );
275 let output_record = Value::record(
276 record! {
277 "block" => block_value,
278 "error" => error_value
279 },
280 pipeline.span,
281 );
282 Ok(output_record.into_pipeline_data())
283 }
284 }
285 }
286}
287
288fn json_merge(a: &mut JsonValue, b: &JsonValue) {
289 match (a, b) {
290 (JsonValue::Object(a), JsonValue::Object(b)) => {
291 for (k, v) in b {
292 json_merge(a.entry(k).or_insert(JsonValue::Null), v);
293 }
294 }
295 (JsonValue::Array(a), JsonValue::Array(b)) => {
296 a.extend(b.clone());
297 }
298 (JsonValue::Array(a), JsonValue::Object(b)) => {
299 a.extend([JsonValue::Object(b.clone())]);
300 }
301 (a, b) => {
302 *a = b.clone();
303 }
304 }
305}
306
307#[cfg(test)]
308mod test {
309 #[test]
310 fn test_examples() {
311 use super::Ast;
312 use crate::test_examples;
313 test_examples(Ast {})
314 }
315}