From b84a1a3598f68c1575a92bcd2ba68e9d1322256d Mon Sep 17 00:00:00 2001 From: Akshay Date: Fri, 11 Oct 2024 08:25:17 +0530 Subject: improve tests --- src/eval/mod.rs | 345 ++++++++++++--------------------------------------- src/eval/test.rs | 371 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 452 insertions(+), 264 deletions(-) create mode 100644 src/eval/test.rs (limited to 'src') diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 9455704..589be37 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -4,6 +4,9 @@ use crate::{ast, Wrap}; use std::{collections::HashMap, fmt, io}; mod builtins; +mod analysis; +#[cfg(test)] +mod test; #[derive(Debug, PartialEq, Eq, Clone)] pub struct Variable { @@ -324,6 +327,12 @@ impl From<&str> for Value { } } +impl From for Value { + fn from(value: String) -> Self { + Self::String(value.to_owned()) + } +} + impl From> for Value { fn from(value: Vec) -> Self { Self::List(value) @@ -358,6 +367,71 @@ pub enum Error { }, // current node is only set in visitors, not in BEGIN or END blocks CurrentNodeNotPresent, + Analysis, +} + +impl Error { + pub fn code(self) -> i32 { + match self { + Self::FailedLookup(_) => 30, + Self::TypeMismatch { .. } => 31, + Self::UndefinedBinOp(..) => 32, + Self::UndefinedUnaryOp(..) => 33, + Self::AlreadyBound(..) => 34, + Self::MalformedExpr(..) => 35, + Self::InvalidNodeKind(..) => 36, + Self::NoParentNode(..) => 37, + Self::IncorrectArgFormat { .. } => 38, + Self::InvalidStringSlice { .. } => 39, + Self::ArrayOutOfBounds { .. } => 40, + Self::CurrentNodeNotPresent => 41, + Self::Analysis => 50, + } + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::FailedLookup(ident) => write!(f, "failed to lookup variable `{ident}`"), + Self::TypeMismatch { expected, got } => write!( + f, + "mismatched types, expected one of `{}`, got `{got}`", + expected + .iter() + .map(|t| t.to_string()) + .collect::>() + .join(", ") + ), + Self::UndefinedBinOp(op, ltype, rtype) => write!( + f, + "undefined operation `{op}` between `{ltype}` and `{rtype}`" + ), + Self::UndefinedUnaryOp(op, ty) => write!(f, "undefined operation `{op}` on `{ty}`"), + Self::AlreadyBound(ident) => write!(f, "variable `{ident}` is aleady declared"), + Self::MalformedExpr(s) => write!(f, "{s}"), + Self::InvalidNodeKind(s) => write!(f, "invalid node kind `{s}`"), + Self::NoParentNode(n) => write!( + f, + "current node (with kind {}) has no parent node ", + n.kind() + ), + Self::IncorrectArgFormat { wanted, got } => write!( + f, + "incorrect number of arguments, wanted {wanted} but got {got}" + ), + Self::InvalidStringSlice { length, start, end } => write!( + f, + "invalid string-slice operation, length was {length}, but slice was {start}:{end}" + ), + Self::ArrayOutOfBounds { idx, len } => write!( + f, + "array index out of bounds, length was {len} but index was {idx}" + ), + Self::CurrentNodeNotPresent => write!(f, "no current node captured in stanza"), + Self::Analysis => write!(f, "encountered analysis errors"), + } + } } impl ast::Type { @@ -719,6 +793,12 @@ impl<'s> Context<'s> { .unwrap_or_default() } + pub fn analyze(&mut self) -> Result { + let analysis = analysis::run(&*self); + analysis.print(); + Err(Error::Analysis) + } + pub fn eval(&mut self) -> Result { let program = std::mem::take(&mut self.program); let mut has_next = true; @@ -782,269 +862,6 @@ pub fn evaluate(file: &str, program: &str, language: tree_sitter::Language) -> R .with_output_stream(Box::new(io::stdout())) .with_program(program); + ctx.analyze()?; ctx.eval() } - -#[cfg(test)] -mod test { - use super::*; - use crate::ast::*; - use expect_test::{expect, Expect}; - - fn gen_test(file: &str, program: &str, expected: Expect) { - let language = tree_sitter_python::language(); - let mut parser = tree_sitter::Parser::new(); - let _ = parser.set_language(&language); - let tree = parser.parse(file, None).unwrap(); - let program = ast::Program::new().with_file(program).unwrap(); - - let mut output = Vec::new(); - let mut ctx = Context::new(language) - .with_input(file.to_owned()) - .with_tree(tree) - .with_program(program) - .with_output_stream(Box::new(&mut output) as Box); - - ctx.eval().unwrap(); - - drop(ctx); - - expected.assert_eq(&String::from_utf8(output).unwrap()) - } - - #[test] - fn bin() { - let language = tree_sitter_python::language(); - let mut ctx = Context::new(language).with_program(Program::new()); - assert_eq!( - ctx.eval_expr(&Expr::bin(Expr::int(5), "+", Expr::int(10),)), - Ok(Value::Integer(15)) - ); - assert_eq!( - ctx.eval_expr(&Expr::bin(Expr::int(5), "==", Expr::int(10),)), - Ok(Value::Boolean(false)) - ); - assert_eq!( - ctx.eval_expr(&Expr::bin(Expr::int(5), "<", Expr::int(10),)), - Ok(Value::Boolean(true)) - ); - assert_eq!( - ctx.eval_expr(&Expr::bin( - Expr::bin(Expr::int(5), "<", Expr::int(10),), - "&&", - Expr::false_(), - )), - Ok(Value::Boolean(false)) - ); - } - - #[test] - fn test_evaluate_blocks() { - let language = tree_sitter_python::language(); - let mut ctx = Context::new(language).with_program(Program::new()); - assert_eq!( - ctx.eval_block(&Block { - body: vec![ - Statement::Declaration(Declaration { - ty: Type::Integer, - name: "a".to_owned(), - init: None, - }), - Statement::Bare(Expr::bin(Expr::ident("a"), "+=", Expr::int(5),)), - ] - }), - Ok(Value::Unit) - ); - assert_eq!( - ctx.lookup(&String::from("a")).unwrap().clone(), - Variable { - ty: Type::Integer, - name: "a".to_owned(), - value: Value::Integer(5) - } - ); - } - - #[test] - fn test_evaluate_if() { - let language = tree_sitter_python::language(); - let mut ctx = Context::new(language).with_program(Program::new()); - assert_eq!( - ctx.eval_block(&Block { - body: vec![ - Statement::Declaration(Declaration { - ty: Type::Integer, - name: "a".to_owned(), - init: Some(Expr::int(1).boxed()), - }), - Statement::Bare(Expr::If(IfExpr { - condition: Expr::true_().boxed(), - then: Block { - body: vec![Statement::Bare(Expr::bin( - Expr::Ident("a".to_owned()), - "+=", - Expr::int(5), - ))] - }, - else_: Block { - body: vec![Statement::Bare(Expr::bin( - Expr::ident("a"), - "+=", - Expr::int(10), - ))] - } - })) - ] - }), - Ok(Value::Unit) - ); - assert_eq!( - ctx.lookup(&String::from("a")).unwrap().clone(), - Variable { - ty: Type::Integer, - name: "a".to_owned(), - value: Value::Integer(6) - } - ); - } - - #[test] - fn test_substring() { - let language = tree_sitter_python::language(); - let mut ctx = Context::new(language).with_program(Program::new()); - assert_eq!( - ctx.eval_block(&Block { - body: vec![ - Statement::Declaration(Declaration { - ty: Type::String, - name: "a".to_owned(), - init: Some(Expr::str("foobar").boxed()), - }), - Statement::Declaration(Declaration { - ty: Type::String, - name: "b".to_owned(), - init: Some( - Expr::call( - "substr", - [Expr::Ident("a".to_owned()), Expr::int(0), Expr::int(3),] - ) - .boxed() - ), - }), - ] - }), - Ok(Value::Unit) - ); - assert_eq!( - ctx.lookup(&String::from("b")).unwrap().clone(), - Variable { - ty: Type::String, - name: "b".to_owned(), - value: "foo".into() - } - ); - } - - #[test] - fn test_list() { - let language = tree_sitter_python::language(); - let mut ctx = Context::new(language).with_program(Program::new()); - assert_eq!( - ctx.eval_block(&Block { - body: vec![Statement::Declaration(Declaration { - ty: Type::List, - name: "a".to_owned(), - init: Some( - Expr::List(List { - items: vec![Expr::int(5)] - }) - .boxed() - ), - }),] - }), - Ok(Value::Unit) - ); - assert_eq!( - ctx.lookup(&String::from("a")).unwrap().clone(), - Variable { - ty: Type::List, - name: "a".to_owned(), - value: vec![5usize.into()].into(), - } - ); - } - - #[test] - fn list_builtins() { - gen_test( - "", - "BEGIN { - list a = [5]; - print(a); - } - ", - expect!["[5]"], - ); - - gen_test( - "", - "BEGIN { - list a = [5, 4, 3]; - print(length(a)); - } - ", - expect!["3"], - ); - - gen_test( - "", - r#"BEGIN { - list a = [5, 4, 3]; - print(member(a, 3)); - print(", "); - print(member(a, 6)); - } - "#, - expect!["true, false"], - ); - - gen_test( - "", - r#"BEGIN { - list a = [5]; - println(a); - push(a, 4); - println(a); - push(a, 3); - println(a); - pop(a); - println(a); - pop(a); - println(a); - } - "#, - expect![[r#" - [5] - [5, 4] - [5, 4, 3] - [5, 4] - [5] - "#]], - ); - - gen_test( - "", - r#"BEGIN { - list a = [5]; - println(isempty(a)); - pop(a); - println(isempty(a)); - } - "#, - expect![[r#" - false - true - "#]], - ); - } -} diff --git a/src/eval/test.rs b/src/eval/test.rs new file mode 100644 index 0000000..83749e0 --- /dev/null +++ b/src/eval/test.rs @@ -0,0 +1,371 @@ +use super::*; +use crate::ast::*; +use std::io::Write; +use expect_test::{expect, Expect}; + +fn gen_test(file: &str, program: &str, expected: Expect) { + let language = tree_sitter_python::language(); + let mut parser = tree_sitter::Parser::new(); + let _ = parser.set_language(&language); + let tree = parser.parse(file, None).unwrap(); + let program = ast::Program::new().with_file(program).unwrap(); + + let mut output = Vec::new(); + let result; + + { + let mut ctx = Context::new(language) + .with_input(file.to_owned()) + .with_tree(tree) + .with_program(program) + .with_output_stream(Box::new(&mut output) as Box); + + result = ctx.eval(); + } + + if let Err(e) = result { + writeln!(output, "{e:?}").unwrap(); + } + expected.assert_eq(&String::from_utf8(output).unwrap()) +} + +#[test] +fn bin() { + let language = tree_sitter_python::language(); + let mut ctx = Context::new(language).with_program(Program::new()); + assert_eq!( + ctx.eval_expr(&Expr::bin(Expr::int(5), "+", Expr::int(10),)), + Ok(Value::Integer(15)) + ); + assert_eq!( + ctx.eval_expr(&Expr::bin(Expr::int(5), "==", Expr::int(10),)), + Ok(Value::Boolean(false)) + ); + assert_eq!( + ctx.eval_expr(&Expr::bin(Expr::int(5), "<", Expr::int(10),)), + Ok(Value::Boolean(true)) + ); + assert_eq!( + ctx.eval_expr(&Expr::bin( + Expr::bin(Expr::int(5), "<", Expr::int(10),), + "&&", + Expr::false_(), + )), + Ok(Value::Boolean(false)) + ); +} + +#[test] +fn test_evaluate_blocks() { + let language = tree_sitter_python::language(); + let mut ctx = Context::new(language).with_program(Program::new()); + assert_eq!( + ctx.eval_block(&Block { + body: vec![ + Statement::Declaration(Declaration { + ty: Type::Integer, + name: "a".to_owned(), + init: None, + }), + Statement::Bare(Expr::bin(Expr::ident("a"), "+=", Expr::int(5),)), + ] + }), + Ok(Value::Unit) + ); + assert_eq!( + ctx.lookup(&String::from("a")).unwrap().clone(), + Variable { + ty: Type::Integer, + name: "a".to_owned(), + value: Value::Integer(5) + } + ); +} + +#[test] +fn test_evaluate_if() { + let language = tree_sitter_python::language(); + let mut ctx = Context::new(language).with_program(Program::new()); + assert_eq!( + ctx.eval_block(&Block { + body: vec![ + Statement::Declaration(Declaration { + ty: Type::Integer, + name: "a".to_owned(), + init: Some(Expr::int(1).boxed()), + }), + Statement::Bare(Expr::If(IfExpr { + condition: Expr::true_().boxed(), + then: Block { + body: vec![Statement::Bare(Expr::bin( + Expr::Ident("a".to_owned()), + "+=", + Expr::int(5), + ))] + }, + else_: Block { + body: vec![Statement::Bare(Expr::bin( + Expr::ident("a"), + "+=", + Expr::int(10), + ))] + } + })) + ] + }), + Ok(Value::Unit) + ); + assert_eq!( + ctx.lookup(&String::from("a")).unwrap().clone(), + Variable { + ty: Type::Integer, + name: "a".to_owned(), + value: Value::Integer(6) + } + ); +} + +#[test] +fn test_substring() { + let language = tree_sitter_python::language(); + let mut ctx = Context::new(language).with_program(Program::new()); + assert_eq!( + ctx.eval_block(&Block { + body: vec![ + Statement::Declaration(Declaration { + ty: Type::String, + name: "a".to_owned(), + init: Some(Expr::str("foobar").boxed()), + }), + Statement::Declaration(Declaration { + ty: Type::String, + name: "b".to_owned(), + init: Some( + Expr::call( + "substr", + [Expr::Ident("a".to_owned()), Expr::int(0), Expr::int(3),] + ) + .boxed() + ), + }), + ] + }), + Ok(Value::Unit) + ); + assert_eq!( + ctx.lookup(&String::from("b")).unwrap().clone(), + Variable { + ty: Type::String, + name: "b".to_owned(), + value: "foo".into() + } + ); +} + +#[test] +fn test_list() { + let language = tree_sitter_python::language(); + let mut ctx = Context::new(language).with_program(Program::new()); + assert_eq!( + ctx.eval_block(&Block { + body: vec![Statement::Declaration(Declaration { + ty: Type::List, + name: "a".to_owned(), + init: Some( + Expr::List(List { + items: vec![Expr::int(5)] + }) + .boxed() + ), + }),] + }), + Ok(Value::Unit) + ); + assert_eq!( + ctx.lookup(&String::from("a")).unwrap().clone(), + Variable { + ty: Type::List, + name: "a".to_owned(), + value: vec![5usize.into()].into(), + } + ); +} + +#[test] +fn list_1() { + gen_test( + "", + "BEGIN { + list a = [5]; + print(a); + } + ", + expect!["[5]"], + ); +} + +#[test] +fn list_2() { + gen_test( + "", + "BEGIN { + list a = [5, 4, 3]; + print(length(a)); + } + ", + expect!["3"], + ); +} + +#[test] +fn list_3() { + gen_test( + "", + r#"BEGIN { + list a = [5, 4, 3]; + print(member(a, 3)); + print(", "); + print(member(a, 6)); + } + "#, + expect!["true, false"], + ); +} + +#[test] +fn list_4() { + gen_test( + "", + r#"BEGIN { + list a = [5]; + println(a); + push(a, 4); + println(a); + push(a, 3); + println(a); + pop(a); + println(a); + pop(a); + println(a); + } + "#, + expect![[r#" + [5] + [5, 4] + [5, 4, 3] + [5, 4] + [5] + "#]], + ); +} + +#[test] +fn list_5() { + gen_test( + "", + r#"BEGIN { + list a = [5]; + println(isempty(a)); + pop(a); + println(isempty(a)); + } + "#, + expect![[r#" + false + true + "#]], + ); +} + +#[test] +fn string_1() { + gen_test( + "", + r#"BEGIN { + string a = "Foo"; + println(toupper(a)); + println(tolower(a)); + } + "#, + expect![[r#" + FOO + foo + "#]], + ); +} + +#[test] +fn string_2() { + gen_test( + "", + r#"BEGIN { + string a = "foo"; + println(a, " is upper? ", isupper(a)); + println(a, " is lower? ", islower(a)); + + string b = "Foo"; + println(b, " is upper? ", isupper(b)); + println(b, " is lower? ", islower(b)); + + string c = "FOO"; + println(c, " is upper? ", isupper(c)); + println(c, " is lower? ", islower(c)); + } + "#, + expect![[r#" + foo is upper? false + foo is lower? true + Foo is upper? false + Foo is lower? false + FOO is upper? true + FOO is lower? false + "#]], + ); +} + +#[test] +fn string_3() { + gen_test( + "", + r#"BEGIN { + string a = "foo bar baz"; + println("a[3:5]: `", substr(a, 3, 5), "`"); + println("a[2:9]: `", substr(a, 2, 9), "`"); + } + "#, + expect![[r#" + a[3:5]: ` b` + a[2:9]: `o bar b` + "#]], + ); +} + +#[test] +fn string_4() { + gen_test( + "", + r#"BEGIN { + string a = "foo bar baz"; + println("a[9:20]: `", substr(a, 9, 20), "`"); + } + "#, + expect![[r#" + a[9:20]: `InvalidStringSlice { length: 11, start: 9, end: 20 } + "#]], + ); +} + +#[test] +fn node_1() { + gen_test( + "def foo(a, b): hello()", + r#"enter function_definition { + println(text(node)); + println(text(node.name)); + }"#, + expect![[r#" + def foo(a, b): hello() + foo + "#]], + ); +} -- cgit v1.2.3