aboutsummaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
authorAkshay <[email protected]>2024-10-12 14:33:22 +0100
committerAkshay <[email protected]>2024-10-12 14:33:22 +0100
commitbdbb1b33fdac8491bc9a517be3807d97e17d69c9 (patch)
treedca73ccae376a46dff8bec55903398e8d0a00ed8 /src/eval
parent63c606d17095e336ee0deeedb30f94a490ec4059 (diff)
trace errors in tests, add warnings to tbsp
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/analysis.rs76
-rw-r--r--src/eval/builtins.rs4
-rw-r--r--src/eval/mod.rs34
-rw-r--r--src/eval/test.rs61
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
8impl Analysis { 8impl 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
14impl 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)]
38pub enum Kind { 45pub 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
45impl std::fmt::Display for Kind { 56impl 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
61pub fn run(ctx: &super::Context) -> Analysis { 81pub 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
69fn validate_patterns(ctx: &super::Context) -> Vec<Diagnostic> { 89fn 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
129fn 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! {
50fn print(ctx: &mut Context, args: &[ast::Expr]) -> Result { 50fn 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 @@
3use crate::{ast, Wrap}; 3use crate::{ast, Wrap};
4use std::{collections::HashMap, fmt, io}; 4use std::{collections::HashMap, fmt, io};
5 5
6mod builtins;
7mod analysis; 6mod analysis;
7mod builtins;
8#[cfg(test)] 8#[cfg(test)]
9mod test; 9mod 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
459impl<'s> fmt::Debug for Context<'s> { 460impl<'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
852pub fn evaluate(file: &str, program: &str, language: tree_sitter::Language) -> Result { 866pub 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 @@
1use super::*; 1use super::*;
2use crate::ast::*; 2use crate::ast::*;
3use std::io::Write;
4use expect_test::{expect, Expect}; 3use expect_test::{expect, Expect};
5 4
6fn gen_test(file: &str, program: &str, expected: Expect) { 5fn 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]
377fn 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}