diff options
-rw-r--r-- | src/eval/mod.rs | 345 | ||||
-rw-r--r-- | src/eval/test.rs | 371 |
2 files changed, 452 insertions, 264 deletions
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}; | |||
4 | use std::{collections::HashMap, fmt, io}; | 4 | use std::{collections::HashMap, fmt, io}; |
5 | 5 | ||
6 | mod builtins; | 6 | mod builtins; |
7 | mod analysis; | ||
8 | #[cfg(test)] | ||
9 | mod test; | ||
7 | 10 | ||
8 | #[derive(Debug, PartialEq, Eq, Clone)] | 11 | #[derive(Debug, PartialEq, Eq, Clone)] |
9 | pub struct Variable { | 12 | pub struct Variable { |
@@ -324,6 +327,12 @@ impl From<&str> for Value { | |||
324 | } | 327 | } |
325 | } | 328 | } |
326 | 329 | ||
330 | impl From<String> for Value { | ||
331 | fn from(value: String) -> Self { | ||
332 | Self::String(value.to_owned()) | ||
333 | } | ||
334 | } | ||
335 | |||
327 | impl From<Vec<Value>> for Value { | 336 | impl From<Vec<Value>> for Value { |
328 | fn from(value: Vec<Value>) -> Self { | 337 | fn from(value: Vec<Value>) -> Self { |
329 | Self::List(value) | 338 | Self::List(value) |
@@ -358,6 +367,71 @@ pub enum Error { | |||
358 | }, | 367 | }, |
359 | // current node is only set in visitors, not in BEGIN or END blocks | 368 | // current node is only set in visitors, not in BEGIN or END blocks |
360 | CurrentNodeNotPresent, | 369 | CurrentNodeNotPresent, |
370 | Analysis, | ||
371 | } | ||
372 | |||
373 | impl Error { | ||
374 | pub fn code(self) -> i32 { | ||
375 | match self { | ||
376 | Self::FailedLookup(_) => 30, | ||
377 | Self::TypeMismatch { .. } => 31, | ||
378 | Self::UndefinedBinOp(..) => 32, | ||
379 | Self::UndefinedUnaryOp(..) => 33, | ||
380 | Self::AlreadyBound(..) => 34, | ||
381 | Self::MalformedExpr(..) => 35, | ||
382 | Self::InvalidNodeKind(..) => 36, | ||
383 | Self::NoParentNode(..) => 37, | ||
384 | Self::IncorrectArgFormat { .. } => 38, | ||
385 | Self::InvalidStringSlice { .. } => 39, | ||
386 | Self::ArrayOutOfBounds { .. } => 40, | ||
387 | Self::CurrentNodeNotPresent => 41, | ||
388 | Self::Analysis => 50, | ||
389 | } | ||
390 | } | ||
391 | } | ||
392 | |||
393 | impl std::fmt::Display for Error { | ||
394 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
395 | match self { | ||
396 | Self::FailedLookup(ident) => write!(f, "failed to lookup variable `{ident}`"), | ||
397 | Self::TypeMismatch { expected, got } => write!( | ||
398 | f, | ||
399 | "mismatched types, expected one of `{}`, got `{got}`", | ||
400 | expected | ||
401 | .iter() | ||
402 | .map(|t| t.to_string()) | ||
403 | .collect::<Vec<_>>() | ||
404 | .join(", ") | ||
405 | ), | ||
406 | Self::UndefinedBinOp(op, ltype, rtype) => write!( | ||
407 | f, | ||
408 | "undefined operation `{op}` between `{ltype}` and `{rtype}`" | ||
409 | ), | ||
410 | Self::UndefinedUnaryOp(op, ty) => write!(f, "undefined operation `{op}` on `{ty}`"), | ||
411 | Self::AlreadyBound(ident) => write!(f, "variable `{ident}` is aleady declared"), | ||
412 | Self::MalformedExpr(s) => write!(f, "{s}"), | ||
413 | Self::InvalidNodeKind(s) => write!(f, "invalid node kind `{s}`"), | ||
414 | Self::NoParentNode(n) => write!( | ||
415 | f, | ||
416 | "current node (with kind {}) has no parent node ", | ||
417 | n.kind() | ||
418 | ), | ||
419 | Self::IncorrectArgFormat { wanted, got } => write!( | ||
420 | f, | ||
421 | "incorrect number of arguments, wanted {wanted} but got {got}" | ||
422 | ), | ||
423 | Self::InvalidStringSlice { length, start, end } => write!( | ||
424 | f, | ||
425 | "invalid string-slice operation, length was {length}, but slice was {start}:{end}" | ||
426 | ), | ||
427 | Self::ArrayOutOfBounds { idx, len } => write!( | ||
428 | f, | ||
429 | "array index out of bounds, length was {len} but index was {idx}" | ||
430 | ), | ||
431 | Self::CurrentNodeNotPresent => write!(f, "no current node captured in stanza"), | ||
432 | Self::Analysis => write!(f, "encountered analysis errors"), | ||
433 | } | ||
434 | } | ||
361 | } | 435 | } |
362 | 436 | ||
363 | impl ast::Type { | 437 | impl ast::Type { |
@@ -719,6 +793,12 @@ impl<'s> Context<'s> { | |||
719 | .unwrap_or_default() | 793 | .unwrap_or_default() |
720 | } | 794 | } |
721 | 795 | ||
796 | pub fn analyze(&mut self) -> Result { | ||
797 | let analysis = analysis::run(&*self); | ||
798 | analysis.print(); | ||
799 | Err(Error::Analysis) | ||
800 | } | ||
801 | |||
722 | pub fn eval(&mut self) -> Result { | 802 | pub fn eval(&mut self) -> Result { |
723 | let program = std::mem::take(&mut self.program); | 803 | let program = std::mem::take(&mut self.program); |
724 | let mut has_next = true; | 804 | let mut has_next = true; |
@@ -782,269 +862,6 @@ pub fn evaluate(file: &str, program: &str, language: tree_sitter::Language) -> R | |||
782 | .with_output_stream(Box::new(io::stdout())) | 862 | .with_output_stream(Box::new(io::stdout())) |
783 | .with_program(program); | 863 | .with_program(program); |
784 | 864 | ||
865 | ctx.analyze()?; | ||
785 | ctx.eval() | 866 | ctx.eval() |
786 | } | 867 | } |
787 | |||
788 | #[cfg(test)] | ||
789 | mod test { | ||
790 | use super::*; | ||
791 | use crate::ast::*; | ||
792 | use expect_test::{expect, Expect}; | ||
793 | |||
794 | fn gen_test(file: &str, program: &str, expected: Expect) { | ||
795 | let language = tree_sitter_python::language(); | ||
796 | let mut parser = tree_sitter::Parser::new(); | ||
797 | let _ = parser.set_language(&language); | ||
798 | let tree = parser.parse(file, None).unwrap(); | ||
799 | let program = ast::Program::new().with_file(program).unwrap(); | ||
800 | |||
801 | let mut output = Vec::new(); | ||
802 | let mut ctx = Context::new(language) | ||
803 | .with_input(file.to_owned()) | ||
804 | .with_tree(tree) | ||
805 | .with_program(program) | ||
806 | .with_output_stream(Box::new(&mut output) as Box<dyn io::Write>); | ||
807 | |||
808 | ctx.eval().unwrap(); | ||
809 | |||
810 | drop(ctx); | ||
811 | |||
812 | expected.assert_eq(&String::from_utf8(output).unwrap()) | ||
813 | } | ||
814 | |||
815 | #[test] | ||
816 | fn bin() { | ||
817 | let language = tree_sitter_python::language(); | ||
818 | let mut ctx = Context::new(language).with_program(Program::new()); | ||
819 | assert_eq!( | ||
820 | ctx.eval_expr(&Expr::bin(Expr::int(5), "+", Expr::int(10),)), | ||
821 | Ok(Value::Integer(15)) | ||
822 | ); | ||
823 | assert_eq!( | ||
824 | ctx.eval_expr(&Expr::bin(Expr::int(5), "==", Expr::int(10),)), | ||
825 | Ok(Value::Boolean(false)) | ||
826 | ); | ||
827 | assert_eq!( | ||
828 | ctx.eval_expr(&Expr::bin(Expr::int(5), "<", Expr::int(10),)), | ||
829 | Ok(Value::Boolean(true)) | ||
830 | ); | ||
831 | assert_eq!( | ||
832 | ctx.eval_expr(&Expr::bin( | ||
833 | Expr::bin(Expr::int(5), "<", Expr::int(10),), | ||
834 | "&&", | ||
835 | Expr::false_(), | ||
836 | )), | ||
837 | Ok(Value::Boolean(false)) | ||
838 | ); | ||
839 | } | ||
840 | |||
841 | #[test] | ||
842 | fn test_evaluate_blocks() { | ||
843 | let language = tree_sitter_python::language(); | ||
844 | let mut ctx = Context::new(language).with_program(Program::new()); | ||
845 | assert_eq!( | ||
846 | ctx.eval_block(&Block { | ||
847 | body: vec![ | ||
848 | Statement::Declaration(Declaration { | ||
849 | ty: Type::Integer, | ||
850 | name: "a".to_owned(), | ||
851 | init: None, | ||
852 | }), | ||
853 | Statement::Bare(Expr::bin(Expr::ident("a"), "+=", Expr::int(5),)), | ||
854 | ] | ||
855 | }), | ||
856 | Ok(Value::Unit) | ||
857 | ); | ||
858 | assert_eq!( | ||
859 | ctx.lookup(&String::from("a")).unwrap().clone(), | ||
860 | Variable { | ||
861 | ty: Type::Integer, | ||
862 | name: "a".to_owned(), | ||
863 | value: Value::Integer(5) | ||
864 | } | ||
865 | ); | ||
866 | } | ||
867 | |||
868 | #[test] | ||
869 | fn test_evaluate_if() { | ||
870 | let language = tree_sitter_python::language(); | ||
871 | let mut ctx = Context::new(language).with_program(Program::new()); | ||
872 | assert_eq!( | ||
873 | ctx.eval_block(&Block { | ||
874 | body: vec![ | ||
875 | Statement::Declaration(Declaration { | ||
876 | ty: Type::Integer, | ||
877 | name: "a".to_owned(), | ||
878 | init: Some(Expr::int(1).boxed()), | ||
879 | }), | ||
880 | Statement::Bare(Expr::If(IfExpr { | ||
881 | condition: Expr::true_().boxed(), | ||
882 | then: Block { | ||
883 | body: vec![Statement::Bare(Expr::bin( | ||
884 | Expr::Ident("a".to_owned()), | ||
885 | "+=", | ||
886 | Expr::int(5), | ||
887 | ))] | ||
888 | }, | ||
889 | else_: Block { | ||
890 | body: vec![Statement::Bare(Expr::bin( | ||
891 | Expr::ident("a"), | ||
892 | "+=", | ||
893 | Expr::int(10), | ||
894 | ))] | ||
895 | } | ||
896 | })) | ||
897 | ] | ||
898 | }), | ||
899 | Ok(Value::Unit) | ||
900 | ); | ||
901 | assert_eq!( | ||
902 | ctx.lookup(&String::from("a")).unwrap().clone(), | ||
903 | Variable { | ||
904 | ty: Type::Integer, | ||
905 | name: "a".to_owned(), | ||
906 | value: Value::Integer(6) | ||
907 | } | ||
908 | ); | ||
909 | } | ||
910 | |||
911 | #[test] | ||
912 | fn test_substring() { | ||
913 | let language = tree_sitter_python::language(); | ||
914 | let mut ctx = Context::new(language).with_program(Program::new()); | ||
915 | assert_eq!( | ||
916 | ctx.eval_block(&Block { | ||
917 | body: vec![ | ||
918 | Statement::Declaration(Declaration { | ||
919 | ty: Type::String, | ||
920 | name: "a".to_owned(), | ||
921 | init: Some(Expr::str("foobar").boxed()), | ||
922 | }), | ||
923 | Statement::Declaration(Declaration { | ||
924 | ty: Type::String, | ||
925 | name: "b".to_owned(), | ||
926 | init: Some( | ||
927 | Expr::call( | ||
928 | "substr", | ||
929 | [Expr::Ident("a".to_owned()), Expr::int(0), Expr::int(3),] | ||
930 | ) | ||
931 | .boxed() | ||
932 | ), | ||
933 | }), | ||
934 | ] | ||
935 | }), | ||
936 | Ok(Value::Unit) | ||
937 | ); | ||
938 | assert_eq!( | ||
939 | ctx.lookup(&String::from("b")).unwrap().clone(), | ||
940 | Variable { | ||
941 | ty: Type::String, | ||
942 | name: "b".to_owned(), | ||
943 | value: "foo".into() | ||
944 | } | ||
945 | ); | ||
946 | } | ||
947 | |||
948 | #[test] | ||
949 | fn test_list() { | ||
950 | let language = tree_sitter_python::language(); | ||
951 | let mut ctx = Context::new(language).with_program(Program::new()); | ||
952 | assert_eq!( | ||
953 | ctx.eval_block(&Block { | ||
954 | body: vec![Statement::Declaration(Declaration { | ||
955 | ty: Type::List, | ||
956 | name: "a".to_owned(), | ||
957 | init: Some( | ||
958 | Expr::List(List { | ||
959 | items: vec![Expr::int(5)] | ||
960 | }) | ||
961 | .boxed() | ||
962 | ), | ||
963 | }),] | ||
964 | }), | ||
965 | Ok(Value::Unit) | ||
966 | ); | ||
967 | assert_eq!( | ||
968 | ctx.lookup(&String::from("a")).unwrap().clone(), | ||
969 | Variable { | ||
970 | ty: Type::List, | ||
971 | name: "a".to_owned(), | ||
972 | value: vec![5usize.into()].into(), | ||
973 | } | ||
974 | ); | ||
975 | } | ||
976 | |||
977 | #[test] | ||
978 | fn list_builtins() { | ||
979 | gen_test( | ||
980 | "", | ||
981 | "BEGIN { | ||
982 | list a = [5]; | ||
983 | print(a); | ||
984 | } | ||
985 | ", | ||
986 | expect!["[5]"], | ||
987 | ); | ||
988 | |||
989 | gen_test( | ||
990 | "", | ||
991 | "BEGIN { | ||
992 | list a = [5, 4, 3]; | ||
993 | print(length(a)); | ||
994 | } | ||
995 | ", | ||
996 | expect!["3"], | ||
997 | ); | ||
998 | |||
999 | gen_test( | ||
1000 | "", | ||
1001 | r#"BEGIN { | ||
1002 | list a = [5, 4, 3]; | ||
1003 | print(member(a, 3)); | ||
1004 | print(", "); | ||
1005 | print(member(a, 6)); | ||
1006 | } | ||
1007 | "#, | ||
1008 | expect!["true, false"], | ||
1009 | ); | ||
1010 | |||
1011 | gen_test( | ||
1012 | "", | ||
1013 | r#"BEGIN { | ||
1014 | list a = [5]; | ||
1015 | println(a); | ||
1016 | push(a, 4); | ||
1017 | println(a); | ||
1018 | push(a, 3); | ||
1019 | println(a); | ||
1020 | pop(a); | ||
1021 | println(a); | ||
1022 | pop(a); | ||
1023 | println(a); | ||
1024 | } | ||
1025 | "#, | ||
1026 | expect![[r#" | ||
1027 | [5] | ||
1028 | [5, 4] | ||
1029 | [5, 4, 3] | ||
1030 | [5, 4] | ||
1031 | [5] | ||
1032 | "#]], | ||
1033 | ); | ||
1034 | |||
1035 | gen_test( | ||
1036 | "", | ||
1037 | r#"BEGIN { | ||
1038 | list a = [5]; | ||
1039 | println(isempty(a)); | ||
1040 | pop(a); | ||
1041 | println(isempty(a)); | ||
1042 | } | ||
1043 | "#, | ||
1044 | expect![[r#" | ||
1045 | false | ||
1046 | true | ||
1047 | "#]], | ||
1048 | ); | ||
1049 | } | ||
1050 | } | ||
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 @@ | |||
1 | use super::*; | ||
2 | use crate::ast::*; | ||
3 | use std::io::Write; | ||
4 | use expect_test::{expect, Expect}; | ||
5 | |||
6 | fn gen_test(file: &str, program: &str, expected: Expect) { | ||
7 | let language = tree_sitter_python::language(); | ||
8 | let mut parser = tree_sitter::Parser::new(); | ||
9 | let _ = parser.set_language(&language); | ||
10 | let tree = parser.parse(file, None).unwrap(); | ||
11 | let program = ast::Program::new().with_file(program).unwrap(); | ||
12 | |||
13 | let mut output = Vec::new(); | ||
14 | let result; | ||
15 | |||
16 | { | ||
17 | let mut ctx = Context::new(language) | ||
18 | .with_input(file.to_owned()) | ||
19 | .with_tree(tree) | ||
20 | .with_program(program) | ||
21 | .with_output_stream(Box::new(&mut output) as Box<dyn io::Write>); | ||
22 | |||
23 | result = ctx.eval(); | ||
24 | } | ||
25 | |||
26 | if let Err(e) = result { | ||
27 | writeln!(output, "{e:?}").unwrap(); | ||
28 | } | ||
29 | expected.assert_eq(&String::from_utf8(output).unwrap()) | ||
30 | } | ||
31 | |||
32 | #[test] | ||
33 | fn bin() { | ||
34 | let language = tree_sitter_python::language(); | ||
35 | let mut ctx = Context::new(language).with_program(Program::new()); | ||
36 | assert_eq!( | ||
37 | ctx.eval_expr(&Expr::bin(Expr::int(5), "+", Expr::int(10),)), | ||
38 | Ok(Value::Integer(15)) | ||
39 | ); | ||
40 | assert_eq!( | ||
41 | ctx.eval_expr(&Expr::bin(Expr::int(5), "==", Expr::int(10),)), | ||
42 | Ok(Value::Boolean(false)) | ||
43 | ); | ||
44 | assert_eq!( | ||
45 | ctx.eval_expr(&Expr::bin(Expr::int(5), "<", Expr::int(10),)), | ||
46 | Ok(Value::Boolean(true)) | ||
47 | ); | ||
48 | assert_eq!( | ||
49 | ctx.eval_expr(&Expr::bin( | ||
50 | Expr::bin(Expr::int(5), "<", Expr::int(10),), | ||
51 | "&&", | ||
52 | Expr::false_(), | ||
53 | )), | ||
54 | Ok(Value::Boolean(false)) | ||
55 | ); | ||
56 | } | ||
57 | |||
58 | #[test] | ||
59 | fn test_evaluate_blocks() { | ||
60 | let language = tree_sitter_python::language(); | ||
61 | let mut ctx = Context::new(language).with_program(Program::new()); | ||
62 | assert_eq!( | ||
63 | ctx.eval_block(&Block { | ||
64 | body: vec![ | ||
65 | Statement::Declaration(Declaration { | ||
66 | ty: Type::Integer, | ||
67 | name: "a".to_owned(), | ||
68 | init: None, | ||
69 | }), | ||
70 | Statement::Bare(Expr::bin(Expr::ident("a"), "+=", Expr::int(5),)), | ||
71 | ] | ||
72 | }), | ||
73 | Ok(Value::Unit) | ||
74 | ); | ||
75 | assert_eq!( | ||
76 | ctx.lookup(&String::from("a")).unwrap().clone(), | ||
77 | Variable { | ||
78 | ty: Type::Integer, | ||
79 | name: "a".to_owned(), | ||
80 | value: Value::Integer(5) | ||
81 | } | ||
82 | ); | ||
83 | } | ||
84 | |||
85 | #[test] | ||
86 | fn test_evaluate_if() { | ||
87 | let language = tree_sitter_python::language(); | ||
88 | let mut ctx = Context::new(language).with_program(Program::new()); | ||
89 | assert_eq!( | ||
90 | ctx.eval_block(&Block { | ||
91 | body: vec![ | ||
92 | Statement::Declaration(Declaration { | ||
93 | ty: Type::Integer, | ||
94 | name: "a".to_owned(), | ||
95 | init: Some(Expr::int(1).boxed()), | ||
96 | }), | ||
97 | Statement::Bare(Expr::If(IfExpr { | ||
98 | condition: Expr::true_().boxed(), | ||
99 | then: Block { | ||
100 | body: vec![Statement::Bare(Expr::bin( | ||
101 | Expr::Ident("a".to_owned()), | ||
102 | "+=", | ||
103 | Expr::int(5), | ||
104 | ))] | ||
105 | }, | ||
106 | else_: Block { | ||
107 | body: vec![Statement::Bare(Expr::bin( | ||
108 | Expr::ident("a"), | ||
109 | "+=", | ||
110 | Expr::int(10), | ||
111 | ))] | ||
112 | } | ||
113 | })) | ||
114 | ] | ||
115 | }), | ||
116 | Ok(Value::Unit) | ||
117 | ); | ||
118 | assert_eq!( | ||
119 | ctx.lookup(&String::from("a")).unwrap().clone(), | ||
120 | Variable { | ||
121 | ty: Type::Integer, | ||
122 | name: "a".to_owned(), | ||
123 | value: Value::Integer(6) | ||
124 | } | ||
125 | ); | ||
126 | } | ||
127 | |||
128 | #[test] | ||
129 | fn test_substring() { | ||
130 | let language = tree_sitter_python::language(); | ||
131 | let mut ctx = Context::new(language).with_program(Program::new()); | ||
132 | assert_eq!( | ||
133 | ctx.eval_block(&Block { | ||
134 | body: vec![ | ||
135 | Statement::Declaration(Declaration { | ||
136 | ty: Type::String, | ||
137 | name: "a".to_owned(), | ||
138 | init: Some(Expr::str("foobar").boxed()), | ||
139 | }), | ||
140 | Statement::Declaration(Declaration { | ||
141 | ty: Type::String, | ||
142 | name: "b".to_owned(), | ||
143 | init: Some( | ||
144 | Expr::call( | ||
145 | "substr", | ||
146 | [Expr::Ident("a".to_owned()), Expr::int(0), Expr::int(3),] | ||
147 | ) | ||
148 | .boxed() | ||
149 | ), | ||
150 | }), | ||
151 | ] | ||
152 | }), | ||
153 | Ok(Value::Unit) | ||
154 | ); | ||
155 | assert_eq!( | ||
156 | ctx.lookup(&String::from("b")).unwrap().clone(), | ||
157 | Variable { | ||
158 | ty: Type::String, | ||
159 | name: "b".to_owned(), | ||
160 | value: "foo".into() | ||
161 | } | ||
162 | ); | ||
163 | } | ||
164 | |||
165 | #[test] | ||
166 | fn test_list() { | ||
167 | let language = tree_sitter_python::language(); | ||
168 | let mut ctx = Context::new(language).with_program(Program::new()); | ||
169 | assert_eq!( | ||
170 | ctx.eval_block(&Block { | ||
171 | body: vec![Statement::Declaration(Declaration { | ||
172 | ty: Type::List, | ||
173 | name: "a".to_owned(), | ||
174 | init: Some( | ||
175 | Expr::List(List { | ||
176 | items: vec![Expr::int(5)] | ||
177 | }) | ||
178 | .boxed() | ||
179 | ), | ||
180 | }),] | ||
181 | }), | ||
182 | Ok(Value::Unit) | ||
183 | ); | ||
184 | assert_eq!( | ||
185 | ctx.lookup(&String::from("a")).unwrap().clone(), | ||
186 | Variable { | ||
187 | ty: Type::List, | ||
188 | name: "a".to_owned(), | ||
189 | value: vec![5usize.into()].into(), | ||
190 | } | ||
191 | ); | ||
192 | } | ||
193 | |||
194 | #[test] | ||
195 | fn list_1() { | ||
196 | gen_test( | ||
197 | "", | ||
198 | "BEGIN { | ||
199 | list a = [5]; | ||
200 | print(a); | ||
201 | } | ||
202 | ", | ||
203 | expect!["[5]"], | ||
204 | ); | ||
205 | } | ||
206 | |||
207 | #[test] | ||
208 | fn list_2() { | ||
209 | gen_test( | ||
210 | "", | ||
211 | "BEGIN { | ||
212 | list a = [5, 4, 3]; | ||
213 | print(length(a)); | ||
214 | } | ||
215 | ", | ||
216 | expect!["3"], | ||
217 | ); | ||
218 | } | ||
219 | |||
220 | #[test] | ||
221 | fn list_3() { | ||
222 | gen_test( | ||
223 | "", | ||
224 | r#"BEGIN { | ||
225 | list a = [5, 4, 3]; | ||
226 | print(member(a, 3)); | ||
227 | print(", "); | ||
228 | print(member(a, 6)); | ||
229 | } | ||
230 | "#, | ||
231 | expect!["true, false"], | ||
232 | ); | ||
233 | } | ||
234 | |||
235 | #[test] | ||
236 | fn list_4() { | ||
237 | gen_test( | ||
238 | "", | ||
239 | r#"BEGIN { | ||
240 | list a = [5]; | ||
241 | println(a); | ||
242 | push(a, 4); | ||
243 | println(a); | ||
244 | push(a, 3); | ||
245 | println(a); | ||
246 | pop(a); | ||
247 | println(a); | ||
248 | pop(a); | ||
249 | println(a); | ||
250 | } | ||
251 | "#, | ||
252 | expect![[r#" | ||
253 | [5] | ||
254 | [5, 4] | ||
255 | [5, 4, 3] | ||
256 | [5, 4] | ||
257 | [5] | ||
258 | "#]], | ||
259 | ); | ||
260 | } | ||
261 | |||
262 | #[test] | ||
263 | fn list_5() { | ||
264 | gen_test( | ||
265 | "", | ||
266 | r#"BEGIN { | ||
267 | list a = [5]; | ||
268 | println(isempty(a)); | ||
269 | pop(a); | ||
270 | println(isempty(a)); | ||
271 | } | ||
272 | "#, | ||
273 | expect![[r#" | ||
274 | false | ||
275 | true | ||
276 | "#]], | ||
277 | ); | ||
278 | } | ||
279 | |||
280 | #[test] | ||
281 | fn string_1() { | ||
282 | gen_test( | ||
283 | "", | ||
284 | r#"BEGIN { | ||
285 | string a = "Foo"; | ||
286 | println(toupper(a)); | ||
287 | println(tolower(a)); | ||
288 | } | ||
289 | "#, | ||
290 | expect![[r#" | ||
291 | FOO | ||
292 | foo | ||
293 | "#]], | ||
294 | ); | ||
295 | } | ||
296 | |||
297 | #[test] | ||
298 | fn string_2() { | ||
299 | gen_test( | ||
300 | "", | ||
301 | r#"BEGIN { | ||
302 | string a = "foo"; | ||
303 | println(a, " is upper? ", isupper(a)); | ||
304 | println(a, " is lower? ", islower(a)); | ||
305 | |||
306 | string b = "Foo"; | ||
307 | println(b, " is upper? ", isupper(b)); | ||
308 | println(b, " is lower? ", islower(b)); | ||
309 | |||
310 | string c = "FOO"; | ||
311 | println(c, " is upper? ", isupper(c)); | ||
312 | println(c, " is lower? ", islower(c)); | ||
313 | } | ||
314 | "#, | ||
315 | expect![[r#" | ||
316 | foo is upper? false | ||
317 | foo is lower? true | ||
318 | Foo is upper? false | ||
319 | Foo is lower? false | ||
320 | FOO is upper? true | ||
321 | FOO is lower? false | ||
322 | "#]], | ||
323 | ); | ||
324 | } | ||
325 | |||
326 | #[test] | ||
327 | fn string_3() { | ||
328 | gen_test( | ||
329 | "", | ||
330 | r#"BEGIN { | ||
331 | string a = "foo bar baz"; | ||
332 | println("a[3:5]: `", substr(a, 3, 5), "`"); | ||
333 | println("a[2:9]: `", substr(a, 2, 9), "`"); | ||
334 | } | ||
335 | "#, | ||
336 | expect![[r#" | ||
337 | a[3:5]: ` b` | ||
338 | a[2:9]: `o bar b` | ||
339 | "#]], | ||
340 | ); | ||
341 | } | ||
342 | |||
343 | #[test] | ||
344 | fn string_4() { | ||
345 | gen_test( | ||
346 | "", | ||
347 | r#"BEGIN { | ||
348 | string a = "foo bar baz"; | ||
349 | println("a[9:20]: `", substr(a, 9, 20), "`"); | ||
350 | } | ||
351 | "#, | ||
352 | expect![[r#" | ||
353 | a[9:20]: `InvalidStringSlice { length: 11, start: 9, end: 20 } | ||
354 | "#]], | ||
355 | ); | ||
356 | } | ||
357 | |||
358 | #[test] | ||
359 | fn node_1() { | ||
360 | gen_test( | ||
361 | "def foo(a, b): hello()", | ||
362 | r#"enter function_definition { | ||
363 | println(text(node)); | ||
364 | println(text(node.name)); | ||
365 | }"#, | ||
366 | expect![[r#" | ||
367 | def foo(a, b): hello() | ||
368 | foo | ||
369 | "#]], | ||
370 | ); | ||
371 | } | ||