diff options
Diffstat (limited to 'src/eval')
-rw-r--r-- | src/eval/analysis.rs | 76 | ||||
-rw-r--r-- | src/eval/builtins.rs | 4 | ||||
-rw-r--r-- | src/eval/mod.rs | 34 | ||||
-rw-r--r-- | src/eval/test.rs | 61 |
4 files changed, 131 insertions, 44 deletions
diff --git a/src/eval/analysis.rs b/src/eval/analysis.rs index b3a0083..d5a7165 100644 --- a/src/eval/analysis.rs +++ b/src/eval/analysis.rs | |||
@@ -6,10 +6,17 @@ pub struct Analysis { | |||
6 | } | 6 | } |
7 | 7 | ||
8 | impl Analysis { | 8 | impl Analysis { |
9 | pub fn print(&self) { | 9 | pub fn contains_error(&self) -> bool { |
10 | self.diagnostics.iter().any(|d| d.level == Level::Error) | ||
11 | } | ||
12 | } | ||
13 | |||
14 | impl std::fmt::Display for Analysis { | ||
15 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
10 | for Diagnostic { level, kind } in &self.diagnostics { | 16 | for Diagnostic { level, kind } in &self.diagnostics { |
11 | eprintln!("{level} {kind}"); | 17 | writeln!(f, "{level} {kind}")?; |
12 | } | 18 | } |
19 | Ok(()) | ||
13 | } | 20 | } |
14 | } | 21 | } |
15 | 22 | ||
@@ -36,16 +43,20 @@ impl std::fmt::Display for Level { | |||
36 | 43 | ||
37 | #[derive(Debug, PartialEq, Eq)] | 44 | #[derive(Debug, PartialEq, Eq)] |
38 | pub enum Kind { | 45 | pub enum Kind { |
39 | Pattern { | 46 | InvalidPattern { |
40 | invalid_node_kind: String, | 47 | invalid_node_kind: String, |
41 | full_pattern: ast::Pattern, | 48 | full_pattern: ast::Pattern, |
42 | }, | 49 | }, |
50 | SimplifiablePattern { | ||
51 | pattern: ast::TreePattern, | ||
52 | simplified: ast::TreePattern, | ||
53 | }, | ||
43 | } | 54 | } |
44 | 55 | ||
45 | impl std::fmt::Display for Kind { | 56 | impl std::fmt::Display for Kind { |
46 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 57 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
47 | match self { | 58 | match self { |
48 | Self::Pattern { | 59 | Self::InvalidPattern { |
49 | invalid_node_kind, | 60 | invalid_node_kind, |
50 | full_pattern, | 61 | full_pattern, |
51 | } => { | 62 | } => { |
@@ -54,12 +65,21 @@ impl std::fmt::Display for Kind { | |||
54 | "invalid node kind `{invalid_node_kind}` in pattern `{full_pattern}`" | 65 | "invalid node kind `{invalid_node_kind}` in pattern `{full_pattern}`" |
55 | ) | 66 | ) |
56 | } | 67 | } |
68 | Self::SimplifiablePattern { | ||
69 | pattern, | ||
70 | simplified, | ||
71 | } => { | ||
72 | write!( | ||
73 | f, | ||
74 | "the pattern `{pattern}` can be reduced to just `{simplified}`" | ||
75 | ) | ||
76 | } | ||
57 | } | 77 | } |
58 | } | 78 | } |
59 | } | 79 | } |
60 | 80 | ||
61 | pub fn run(ctx: &super::Context) -> Analysis { | 81 | pub fn run(ctx: &super::Context) -> Analysis { |
62 | [validate_patterns(ctx)] | 82 | [validate_patterns(ctx), redundant_list_pattern(ctx)] |
63 | .into_iter() | 83 | .into_iter() |
64 | .flatten() | 84 | .flatten() |
65 | .collect::<Vec<_>>() | 85 | .collect::<Vec<_>>() |
@@ -67,11 +87,9 @@ pub fn run(ctx: &super::Context) -> Analysis { | |||
67 | } | 87 | } |
68 | 88 | ||
69 | fn validate_patterns(ctx: &super::Context) -> Vec<Diagnostic> { | 89 | fn validate_patterns(ctx: &super::Context) -> Vec<Diagnostic> { |
70 | fn validate_pattern( | 90 | fn check(pattern: &ast::TreePattern, language: &tree_sitter::Language) -> Option<String> { |
71 | pattern: &ast::TreePattern, | ||
72 | language: &tree_sitter::Language, | ||
73 | ) -> Option<String> { | ||
74 | match pattern { | 91 | match pattern { |
92 | // base case | ||
75 | ast::TreePattern::Atom(a) => { | 93 | ast::TreePattern::Atom(a) => { |
76 | if language.id_for_node_kind(a, true) == 0 { | 94 | if language.id_for_node_kind(a, true) == 0 { |
77 | Some(a.to_owned()) | 95 | Some(a.to_owned()) |
@@ -79,9 +97,10 @@ fn validate_patterns(ctx: &super::Context) -> Vec<Diagnostic> { | |||
79 | None | 97 | None |
80 | } | 98 | } |
81 | } | 99 | } |
100 | // recursive case | ||
82 | ast::TreePattern::List(items) => { | 101 | ast::TreePattern::List(items) => { |
83 | for item in items { | 102 | for item in items { |
84 | validate_pattern(item, language)?; | 103 | check(item, language)?; |
85 | } | 104 | } |
86 | None | 105 | None |
87 | } | 106 | } |
@@ -94,7 +113,7 @@ fn validate_patterns(ctx: &super::Context) -> Vec<Diagnostic> { | |||
94 | .flat_map(|s| match &s.pattern { | 113 | .flat_map(|s| match &s.pattern { |
95 | ast::Pattern::Begin | ast::Pattern::End => None, | 114 | ast::Pattern::Begin | ast::Pattern::End => None, |
96 | ast::Pattern::Tree { matcher, .. } => { | 115 | ast::Pattern::Tree { matcher, .. } => { |
97 | validate_pattern(matcher, &ctx.language).map(|invalid_node_kind| Kind::Pattern { | 116 | check(matcher, &ctx.language).map(|invalid_node_kind| Kind::InvalidPattern { |
98 | invalid_node_kind, | 117 | invalid_node_kind, |
99 | full_pattern: s.pattern.clone(), | 118 | full_pattern: s.pattern.clone(), |
100 | }) | 119 | }) |
@@ -106,3 +125,38 @@ fn validate_patterns(ctx: &super::Context) -> Vec<Diagnostic> { | |||
106 | }) | 125 | }) |
107 | .collect() | 126 | .collect() |
108 | } | 127 | } |
128 | |||
129 | fn redundant_list_pattern(ctx: &super::Context) -> Vec<Diagnostic> { | ||
130 | fn simplify(pattern: &ast::TreePattern) -> ast::TreePattern { | ||
131 | match pattern { | ||
132 | ast::TreePattern::Atom(a) => ast::TreePattern::Atom(a.to_owned()), | ||
133 | ast::TreePattern::List(l) => match l.as_slice() { | ||
134 | [a] => simplify(a), | ||
135 | items => ast::TreePattern::List(items.iter().map(simplify).collect::<Vec<_>>()), | ||
136 | }, | ||
137 | } | ||
138 | } | ||
139 | |||
140 | ctx.program | ||
141 | .stanzas | ||
142 | .iter() | ||
143 | .flat_map(|s| match &s.pattern { | ||
144 | ast::Pattern::Begin | ast::Pattern::End => None, | ||
145 | ast::Pattern::Tree { matcher, .. } => { | ||
146 | let simplified = simplify(matcher); | ||
147 | if &simplified != matcher { | ||
148 | Some(Kind::SimplifiablePattern { | ||
149 | pattern: matcher.clone(), | ||
150 | simplified, | ||
151 | }) | ||
152 | } else { | ||
153 | None | ||
154 | } | ||
155 | } | ||
156 | }) | ||
157 | .map(|kind| Diagnostic { | ||
158 | level: Level::Warning, | ||
159 | kind, | ||
160 | }) | ||
161 | .collect() | ||
162 | } | ||
diff --git a/src/eval/builtins.rs b/src/eval/builtins.rs index c91f7ba..e1fa10b 100644 --- a/src/eval/builtins.rs +++ b/src/eval/builtins.rs | |||
@@ -50,9 +50,7 @@ builtins! { | |||
50 | fn print(ctx: &mut Context, args: &[ast::Expr]) -> Result { | 50 | fn print(ctx: &mut Context, args: &[ast::Expr]) -> Result { |
51 | for arg in args { | 51 | for arg in args { |
52 | let val = ctx.eval_expr(arg)?; | 52 | let val = ctx.eval_expr(arg)?; |
53 | let mut default_stream = Box::new(std::io::stdout()) as Box<dyn std::io::Write>; | 53 | write!(ctx.output_stream, "{val}").unwrap(); |
54 | let stream = ctx.output_stream.as_mut().unwrap_or(&mut default_stream); | ||
55 | write!(stream, "{val}").unwrap(); | ||
56 | } | 54 | } |
57 | Ok(Value::Unit) | 55 | Ok(Value::Unit) |
58 | } | 56 | } |
diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 589be37..9e0d033 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs | |||
@@ -3,8 +3,8 @@ | |||
3 | use crate::{ast, Wrap}; | 3 | use crate::{ast, Wrap}; |
4 | use std::{collections::HashMap, fmt, io}; | 4 | use std::{collections::HashMap, fmt, io}; |
5 | 5 | ||
6 | mod builtins; | ||
7 | mod analysis; | 6 | mod analysis; |
7 | mod builtins; | ||
8 | #[cfg(test)] | 8 | #[cfg(test)] |
9 | mod test; | 9 | mod test; |
10 | 10 | ||
@@ -453,7 +453,8 @@ pub struct Context<'s> { | |||
453 | cursor: Option<tree_sitter::TreeCursor<'static>>, | 453 | cursor: Option<tree_sitter::TreeCursor<'static>>, |
454 | tree: Option<&'static tree_sitter::Tree>, | 454 | tree: Option<&'static tree_sitter::Tree>, |
455 | cache: HashMap<NodeId, tree_sitter::Node<'static>>, | 455 | cache: HashMap<NodeId, tree_sitter::Node<'static>>, |
456 | output_stream: Option<Box<dyn io::Write + 's>>, | 456 | output_stream: Box<dyn io::Write + 's>, |
457 | error_stream: Box<dyn io::Write + 's>, | ||
457 | } | 458 | } |
458 | 459 | ||
459 | impl<'s> fmt::Debug for Context<'s> { | 460 | impl<'s> fmt::Debug for Context<'s> { |
@@ -484,7 +485,8 @@ impl<'s> Context<'s> { | |||
484 | cursor: None, | 485 | cursor: None, |
485 | tree: None, | 486 | tree: None, |
486 | cache: HashMap::default(), | 487 | cache: HashMap::default(), |
487 | output_stream: Some(Box::new(io::stdout()) as Box<dyn io::Write + 's>), | 488 | output_stream: Box::new(io::stdout()) as Box<dyn io::Write + 's>, |
489 | error_stream: Box::new(io::stderr()) as Box<dyn io::Write + 's>, | ||
488 | } | 490 | } |
489 | } | 491 | } |
490 | 492 | ||
@@ -539,7 +541,12 @@ impl<'s> Context<'s> { | |||
539 | } | 541 | } |
540 | 542 | ||
541 | pub fn with_output_stream(mut self, stream: Box<dyn io::Write + 's>) -> Self { | 543 | pub fn with_output_stream(mut self, stream: Box<dyn io::Write + 's>) -> Self { |
542 | self.output_stream = Some(stream); | 544 | self.output_stream = stream; |
545 | self | ||
546 | } | ||
547 | |||
548 | pub fn with_error_stream(mut self, stream: Box<dyn io::Write + 's>) -> Self { | ||
549 | self.error_stream = stream; | ||
543 | self | 550 | self |
544 | } | 551 | } |
545 | 552 | ||
@@ -793,13 +800,11 @@ impl<'s> Context<'s> { | |||
793 | .unwrap_or_default() | 800 | .unwrap_or_default() |
794 | } | 801 | } |
795 | 802 | ||
796 | pub fn analyze(&mut self) -> Result { | 803 | pub fn analyze(&mut self) -> analysis::Analysis { |
797 | let analysis = analysis::run(&*self); | 804 | analysis::run(&*self) |
798 | analysis.print(); | ||
799 | Err(Error::Analysis) | ||
800 | } | 805 | } |
801 | 806 | ||
802 | pub fn eval(&mut self) -> Result { | 807 | fn eval_program(&mut self) -> Result { |
803 | let program = std::mem::take(&mut self.program); | 808 | let program = std::mem::take(&mut self.program); |
804 | let mut has_next = true; | 809 | let mut has_next = true; |
805 | let mut postorder = Vec::new(); | 810 | let mut postorder = Vec::new(); |
@@ -847,6 +852,15 @@ impl<'s> Context<'s> { | |||
847 | 852 | ||
848 | Ok(Value::Unit) | 853 | Ok(Value::Unit) |
849 | } | 854 | } |
855 | |||
856 | pub fn eval(&mut self) -> Result { | ||
857 | let analysis = self.analyze(); | ||
858 | write!(self.error_stream, "{analysis}").unwrap(); | ||
859 | if analysis.contains_error() { | ||
860 | return Err(Error::Analysis); | ||
861 | } | ||
862 | self.eval_program() | ||
863 | } | ||
850 | } | 864 | } |
851 | 865 | ||
852 | pub fn evaluate(file: &str, program: &str, language: tree_sitter::Language) -> Result { | 866 | pub fn evaluate(file: &str, program: &str, language: tree_sitter::Language) -> Result { |
@@ -860,8 +874,8 @@ pub fn evaluate(file: &str, program: &str, language: tree_sitter::Language) -> R | |||
860 | .with_input(file.to_owned()) | 874 | .with_input(file.to_owned()) |
861 | .with_tree(tree) | 875 | .with_tree(tree) |
862 | .with_output_stream(Box::new(io::stdout())) | 876 | .with_output_stream(Box::new(io::stdout())) |
877 | .with_error_stream(Box::new(io::stderr())) | ||
863 | .with_program(program); | 878 | .with_program(program); |
864 | 879 | ||
865 | ctx.analyze()?; | ||
866 | ctx.eval() | 880 | ctx.eval() |
867 | } | 881 | } |
diff --git a/src/eval/test.rs b/src/eval/test.rs index 83749e0..c9e7630 100644 --- a/src/eval/test.rs +++ b/src/eval/test.rs | |||
@@ -1,32 +1,27 @@ | |||
1 | use super::*; | 1 | use super::*; |
2 | use crate::ast::*; | 2 | use crate::ast::*; |
3 | use std::io::Write; | ||
4 | use expect_test::{expect, Expect}; | 3 | use expect_test::{expect, Expect}; |
5 | 4 | ||
6 | fn gen_test(file: &str, program: &str, expected: Expect) { | 5 | fn gen_test(file: &str, program: &str, stdout: Expect, stderr: Expect) { |
7 | let language = tree_sitter_python::language(); | 6 | let language = tree_sitter_python::language(); |
8 | let mut parser = tree_sitter::Parser::new(); | 7 | let mut parser = tree_sitter::Parser::new(); |
9 | let _ = parser.set_language(&language); | 8 | let _ = parser.set_language(&language); |
10 | let tree = parser.parse(file, None).unwrap(); | 9 | let tree = parser.parse(file, None).unwrap(); |
11 | let program = ast::Program::new().with_file(program).unwrap(); | 10 | let program = ast::Program::new().with_file(program).unwrap(); |
12 | 11 | ||
13 | let mut output = Vec::new(); | 12 | let mut out = Vec::new(); |
14 | let result; | 13 | let mut err = Vec::new(); |
15 | 14 | ||
16 | { | 15 | Context::new(language) |
17 | let mut ctx = Context::new(language) | 16 | .with_input(file.to_owned()) |
18 | .with_input(file.to_owned()) | 17 | .with_tree(tree) |
19 | .with_tree(tree) | 18 | .with_program(program) |
20 | .with_program(program) | 19 | .with_output_stream(Box::new(&mut out) as Box<dyn io::Write>) |
21 | .with_output_stream(Box::new(&mut output) as Box<dyn io::Write>); | 20 | .with_error_stream(Box::new(&mut err) as Box<dyn io::Write>) |
21 | .eval(); | ||
22 | 22 | ||
23 | result = ctx.eval(); | 23 | stdout.assert_eq(&String::from_utf8(out).unwrap()); |
24 | } | 24 | stderr.assert_eq(&String::from_utf8(err).unwrap()); |
25 | |||
26 | if let Err(e) = result { | ||
27 | writeln!(output, "{e:?}").unwrap(); | ||
28 | } | ||
29 | expected.assert_eq(&String::from_utf8(output).unwrap()) | ||
30 | } | 25 | } |
31 | 26 | ||
32 | #[test] | 27 | #[test] |
@@ -201,6 +196,7 @@ fn list_1() { | |||
201 | } | 196 | } |
202 | ", | 197 | ", |
203 | expect!["[5]"], | 198 | expect!["[5]"], |
199 | expect![], | ||
204 | ); | 200 | ); |
205 | } | 201 | } |
206 | 202 | ||
@@ -214,6 +210,7 @@ fn list_2() { | |||
214 | } | 210 | } |
215 | ", | 211 | ", |
216 | expect!["3"], | 212 | expect!["3"], |
213 | expect![], | ||
217 | ); | 214 | ); |
218 | } | 215 | } |
219 | 216 | ||
@@ -229,6 +226,7 @@ fn list_3() { | |||
229 | } | 226 | } |
230 | "#, | 227 | "#, |
231 | expect!["true, false"], | 228 | expect!["true, false"], |
229 | expect![], | ||
232 | ); | 230 | ); |
233 | } | 231 | } |
234 | 232 | ||
@@ -256,6 +254,7 @@ fn list_4() { | |||
256 | [5, 4] | 254 | [5, 4] |
257 | [5] | 255 | [5] |
258 | "#]], | 256 | "#]], |
257 | expect![], | ||
259 | ); | 258 | ); |
260 | } | 259 | } |
261 | 260 | ||
@@ -274,6 +273,7 @@ fn list_5() { | |||
274 | false | 273 | false |
275 | true | 274 | true |
276 | "#]], | 275 | "#]], |
276 | expect![], | ||
277 | ); | 277 | ); |
278 | } | 278 | } |
279 | 279 | ||
@@ -291,6 +291,7 @@ fn string_1() { | |||
291 | FOO | 291 | FOO |
292 | foo | 292 | foo |
293 | "#]], | 293 | "#]], |
294 | expect![], | ||
294 | ); | 295 | ); |
295 | } | 296 | } |
296 | 297 | ||
@@ -320,6 +321,7 @@ fn string_2() { | |||
320 | FOO is upper? true | 321 | FOO is upper? true |
321 | FOO is lower? false | 322 | FOO is lower? false |
322 | "#]], | 323 | "#]], |
324 | expect![], | ||
323 | ); | 325 | ); |
324 | } | 326 | } |
325 | 327 | ||
@@ -337,6 +339,7 @@ fn string_3() { | |||
337 | a[3:5]: ` b` | 339 | a[3:5]: ` b` |
338 | a[2:9]: `o bar b` | 340 | a[2:9]: `o bar b` |
339 | "#]], | 341 | "#]], |
342 | expect![], | ||
340 | ); | 343 | ); |
341 | } | 344 | } |
342 | 345 | ||
@@ -349,9 +352,8 @@ fn string_4() { | |||
349 | println("a[9:20]: `", substr(a, 9, 20), "`"); | 352 | println("a[9:20]: `", substr(a, 9, 20), "`"); |
350 | } | 353 | } |
351 | "#, | 354 | "#, |
352 | expect![[r#" | 355 | expect!["a[9:20]: `"], |
353 | a[9:20]: `InvalidStringSlice { length: 11, start: 9, end: 20 } | 356 | expect![], |
354 | "#]], | ||
355 | ); | 357 | ); |
356 | } | 358 | } |
357 | 359 | ||
@@ -367,5 +369,24 @@ fn node_1() { | |||
367 | def foo(a, b): hello() | 369 | def foo(a, b): hello() |
368 | foo | 370 | foo |
369 | "#]], | 371 | "#]], |
372 | expect![], | ||
373 | ); | ||
374 | } | ||
375 | |||
376 | #[test] | ||
377 | fn analysis_1() { | ||
378 | gen_test( | ||
379 | "def foo(a, b): hello()", | ||
380 | r#" | ||
381 | enter (function) { } | ||
382 | enter ((function (name))) { } | ||
383 | enter ((function (name args))) { } | ||
384 | "#, | ||
385 | expect![], | ||
386 | expect![[r#" | ||
387 | [warning] the pattern `(function)` can be reduced to just `function` | ||
388 | [warning] the pattern `((function (name)))` can be reduced to just `(function name)` | ||
389 | [warning] the pattern `((function (name args)))` can be reduced to just `(function (name args))` | ||
390 | "#]], | ||
370 | ); | 391 | ); |
371 | } | 392 | } |