diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Cargo.lock | 23 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | flake.nix | 1 | ||||
-rw-r--r-- | src/eval/builtins.rs (renamed from src/builtins.rs) | 70 | ||||
-rw-r--r-- | src/eval/mod.rs (renamed from src/eval.rs) | 172 | ||||
-rw-r--r-- | src/lib.rs | 1 |
7 files changed, 211 insertions, 58 deletions
@@ -1,2 +1,3 @@ | |||
1 | target | 1 | target |
2 | result | 2 | result |
3 | .direnv | ||
@@ -49,6 +49,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
49 | checksum = "066fce287b1d4eafef758e89e09d724a24808a9196fe9756b8ca90e86d0719a2" | 49 | checksum = "066fce287b1d4eafef758e89e09d724a24808a9196fe9756b8ca90e86d0719a2" |
50 | 50 | ||
51 | [[package]] | 51 | [[package]] |
52 | name = "dissimilar" | ||
53 | version = "1.0.9" | ||
54 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
55 | checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d" | ||
56 | |||
57 | [[package]] | ||
58 | name = "expect-test" | ||
59 | version = "1.5.0" | ||
60 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
61 | checksum = "9e0be0a561335815e06dab7c62e50353134c796e7a6155402a64bcff66b6a5e0" | ||
62 | dependencies = [ | ||
63 | "dissimilar", | ||
64 | "once_cell", | ||
65 | ] | ||
66 | |||
67 | [[package]] | ||
52 | name = "la-arena" | 68 | name = "la-arena" |
53 | version = "0.3.1" | 69 | version = "0.3.1" |
54 | source = "registry+https://github.com/rust-lang/crates.io-index" | 70 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -77,6 +93,12 @@ dependencies = [ | |||
77 | ] | 93 | ] |
78 | 94 | ||
79 | [[package]] | 95 | [[package]] |
96 | name = "once_cell" | ||
97 | version = "1.20.2" | ||
98 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
99 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" | ||
100 | |||
101 | [[package]] | ||
80 | name = "proc-macro2" | 102 | name = "proc-macro2" |
81 | version = "1.0.86" | 103 | version = "1.0.86" |
82 | source = "registry+https://github.com/rust-lang/crates.io-index" | 104 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -159,6 +181,7 @@ name = "tbsp" | |||
159 | version = "0.1.0" | 181 | version = "0.1.0" |
160 | dependencies = [ | 182 | dependencies = [ |
161 | "argh", | 183 | "argh", |
184 | "expect-test", | ||
162 | "la-arena", | 185 | "la-arena", |
163 | "nom", | 186 | "nom", |
164 | "regex", | 187 | "regex", |
@@ -29,3 +29,4 @@ python = ["tree-sitter-python"] | |||
29 | rust = ["tree-sitter-rust"] | 29 | rust = ["tree-sitter-rust"] |
30 | 30 | ||
31 | [dev-dependencies] | 31 | [dev-dependencies] |
32 | expect-test = "1.5.0" | ||
@@ -68,7 +68,6 @@ | |||
68 | pkgs.rust-bin.nightly.latest.default | 68 | pkgs.rust-bin.nightly.latest.default |
69 | pkgs.rust-analyzer | 69 | pkgs.rust-analyzer |
70 | 70 | ||
71 | pkgs.mermaid-cli | ||
72 | ]; | 71 | ]; |
73 | RUST_LOG = "info"; | 72 | RUST_LOG = "info"; |
74 | RUST_BACKTRACE = 1; | 73 | RUST_BACKTRACE = 1; |
diff --git a/src/builtins.rs b/src/eval/builtins.rs index 0c9dbf3..7820a8e 100644 --- a/src/builtins.rs +++ b/src/eval/builtins.rs | |||
@@ -24,6 +24,7 @@ macro_rules! builtins { | |||
24 | 24 | ||
25 | builtins! { | 25 | builtins! { |
26 | print, | 26 | print, |
27 | println, | ||
27 | 28 | ||
28 | // string | 29 | // string |
29 | isupper, | 30 | isupper, |
@@ -41,16 +42,27 @@ builtins! { | |||
41 | member, | 42 | member, |
42 | push, | 43 | push, |
43 | pop, | 44 | pop, |
45 | isempty, | ||
44 | } | 46 | } |
45 | 47 | ||
46 | fn print(ctx: &mut Context, args: &[ast::Expr]) -> Result { | 48 | fn print(ctx: &mut Context, args: &[ast::Expr]) -> Result { |
47 | for arg in args { | 49 | for arg in args { |
48 | let val = ctx.eval_expr(arg)?; | 50 | let val = ctx.eval_expr(arg)?; |
49 | print!("{val}"); | 51 | let mut default_stream =Box::new(std::io::stdout()) as Box<dyn std::io::Write> ; |
52 | let stream = ctx | ||
53 | .output_stream | ||
54 | .as_mut() | ||
55 | .unwrap_or(&mut default_stream); | ||
56 | write!(stream, "{val}").unwrap(); | ||
50 | } | 57 | } |
51 | Ok(Value::Unit) | 58 | Ok(Value::Unit) |
52 | } | 59 | } |
53 | 60 | ||
61 | fn println(ctx: &mut Context, args: &[ast::Expr]) -> Result { | ||
62 | print(ctx, args)?; | ||
63 | print(ctx, &[ast::Expr::Lit(ast::Literal::Str("\n".to_owned()))]) | ||
64 | } | ||
65 | |||
54 | fn isupper(ctx: &mut Context, args: &[ast::Expr]) -> Result { | 66 | fn isupper(ctx: &mut Context, args: &[ast::Expr]) -> Result { |
55 | Ok(ctx | 67 | Ok(ctx |
56 | .eval_expr(&get_args::<1>(args)?[0])? | 68 | .eval_expr(&get_args::<1>(args)?[0])? |
@@ -169,10 +181,7 @@ fn push(ctx: &mut Context, args: &[ast::Expr]) -> Result { | |||
169 | l.push(element); | 181 | l.push(element); |
170 | Ok(Value::Unit) | 182 | Ok(Value::Unit) |
171 | } | 183 | } |
172 | _ => Err(Error::TypeMismatch { | 184 | _ => Err(v.ty().expected([ast::Type::List])), |
173 | expected: ast::Type::List, | ||
174 | got: v.ty().clone(), | ||
175 | }), | ||
176 | }) | 185 | }) |
177 | } | 186 | } |
178 | 187 | ||
@@ -189,16 +198,59 @@ fn pop(ctx: &mut Context, args: &[ast::Expr]) -> Result { | |||
189 | Value::List(l) => l | 198 | Value::List(l) => l |
190 | .pop() | 199 | .pop() |
191 | .ok_or_else(|| Error::ArrayOutOfBounds { idx: 0, len: 0 }), | 200 | .ok_or_else(|| Error::ArrayOutOfBounds { idx: 0, len: 0 }), |
192 | _ => Err(Error::TypeMismatch { | 201 | _ => Err(v.ty().expected([ast::Type::List])), |
193 | expected: ast::Type::List, | ||
194 | got: v.ty().clone(), | ||
195 | }), | ||
196 | }) | 202 | }) |
197 | } | 203 | } |
198 | 204 | ||
205 | fn isempty(ctx: &mut Context, args: &[ast::Expr]) -> Result { | ||
206 | let v = ctx.eval_expr(&get_args::<1>(args)?[0])?; | ||
207 | match v.ty() { | ||
208 | ast::Type::List => v | ||
209 | .as_list() | ||
210 | .unwrap() | ||
211 | .is_empty() | ||
212 | .wrap(Value::Boolean) | ||
213 | .wrap(Ok), | ||
214 | ast::Type::String => v | ||
215 | .as_str() | ||
216 | .unwrap() | ||
217 | .is_empty() | ||
218 | .wrap(Value::Boolean) | ||
219 | .wrap(Ok), | ||
220 | _ => Err(v.ty().expected([ast::Type::List])), | ||
221 | } | ||
222 | } | ||
223 | |||
199 | fn get_args<const N: usize>(args: &[ast::Expr]) -> std::result::Result<&[ast::Expr; N], Error> { | 224 | fn get_args<const N: usize>(args: &[ast::Expr]) -> std::result::Result<&[ast::Expr; N], Error> { |
200 | args.try_into().map_err(|_| Error::IncorrectArgFormat { | 225 | args.try_into().map_err(|_| Error::IncorrectArgFormat { |
201 | wanted: N, | 226 | wanted: N, |
202 | got: args.len(), | 227 | got: args.len(), |
203 | }) | 228 | }) |
204 | } | 229 | } |
230 | |||
231 | #[cfg(test)] | ||
232 | mod test { | ||
233 | use super::*; | ||
234 | use crate::{ast::*, eval::*}; | ||
235 | |||
236 | #[test] | ||
237 | fn test_ts_builtins() { | ||
238 | let language = tree_sitter_python::language(); | ||
239 | let mut ctx = Context::new(language).with_program(Program::new()); | ||
240 | |||
241 | assert_eq!( | ||
242 | ctx.eval_block(&Block { | ||
243 | body: vec![Statement::decl(Type::List, "a", Expr::list([Expr::int(5)]),)] | ||
244 | }), | ||
245 | Ok(Value::Unit) | ||
246 | ); | ||
247 | assert_eq!( | ||
248 | ctx.lookup(&String::from("a")).unwrap().clone(), | ||
249 | Variable { | ||
250 | ty: Type::List, | ||
251 | name: "a".to_owned(), | ||
252 | value: vec![5usize.into()].into(), | ||
253 | } | ||
254 | ); | ||
255 | } | ||
256 | } | ||
diff --git a/src/eval.rs b/src/eval/mod.rs index e9fbbf2..c4460c0 100644 --- a/src/eval.rs +++ b/src/eval/mod.rs | |||
@@ -1,7 +1,9 @@ | |||
1 | //! tree walking interpreter for tbsp | 1 | //! tree walking interpreter for tbsp |
2 | 2 | ||
3 | use crate::{ast, Wrap}; | 3 | use crate::{ast, Wrap}; |
4 | use std::{collections::HashMap, fmt}; | 4 | use std::{collections::HashMap, fmt, io}; |
5 | |||
6 | mod builtins; | ||
5 | 7 | ||
6 | #[derive(Debug, PartialEq, Eq, Clone)] | 8 | #[derive(Debug, PartialEq, Eq, Clone)] |
7 | pub struct Variable { | 9 | pub struct Variable { |
@@ -15,19 +17,16 @@ impl Variable { | |||
15 | &self.value | 17 | &self.value |
16 | } | 18 | } |
17 | 19 | ||
18 | pub(crate) fn ty(&self) -> &ast::Type { | 20 | pub(crate) fn ty(&self) -> ast::Type { |
19 | &self.ty | 21 | self.ty |
20 | } | 22 | } |
21 | 23 | ||
22 | fn assign(&mut self, value: Value) -> Result { | 24 | fn assign(&mut self, value: Value) -> Result { |
23 | if self.ty() == &value.ty() { | 25 | if self.ty() == value.ty() { |
24 | self.value = value; | 26 | self.value = value; |
25 | Ok(self.value.clone()) | 27 | Ok(self.value.clone()) |
26 | } else { | 28 | } else { |
27 | Err(Error::TypeMismatch { | 29 | Err(value.ty().expected([self.ty()])) |
28 | expected: self.ty().clone(), | ||
29 | got: value.ty(), | ||
30 | }) | ||
31 | } | 30 | } |
32 | } | 31 | } |
33 | 32 | ||
@@ -90,50 +89,35 @@ impl Value { | |||
90 | fn as_boolean(&self) -> std::result::Result<bool, Error> { | 89 | fn as_boolean(&self) -> std::result::Result<bool, Error> { |
91 | match self { | 90 | match self { |
92 | Self::Boolean(b) => Ok(*b), | 91 | Self::Boolean(b) => Ok(*b), |
93 | v => Err(Error::TypeMismatch { | 92 | v => Err(v.ty().expected([ast::Type::Boolean])), |
94 | expected: ast::Type::Boolean, | ||
95 | got: v.ty(), | ||
96 | }), | ||
97 | } | 93 | } |
98 | } | 94 | } |
99 | 95 | ||
100 | pub(crate) fn as_str(&self) -> std::result::Result<&str, Error> { | 96 | pub(crate) fn as_str(&self) -> std::result::Result<&str, Error> { |
101 | match self { | 97 | match self { |
102 | Self::String(s) => Ok(s.as_str()), | 98 | Self::String(s) => Ok(s.as_str()), |
103 | v => Err(Error::TypeMismatch { | 99 | v => Err(v.ty().expected([ast::Type::String])), |
104 | expected: ast::Type::String, | ||
105 | got: v.ty(), | ||
106 | }), | ||
107 | } | 100 | } |
108 | } | 101 | } |
109 | 102 | ||
110 | pub(crate) fn as_int(&self) -> std::result::Result<i128, Error> { | 103 | pub(crate) fn as_int(&self) -> std::result::Result<i128, Error> { |
111 | match self { | 104 | match self { |
112 | Self::Integer(i) => Ok(*i), | 105 | Self::Integer(i) => Ok(*i), |
113 | v => Err(Error::TypeMismatch { | 106 | v => Err(v.ty().expected([ast::Type::Integer])), |
114 | expected: ast::Type::Integer, | ||
115 | got: v.ty(), | ||
116 | }), | ||
117 | } | 107 | } |
118 | } | 108 | } |
119 | 109 | ||
120 | pub(crate) fn as_node(&self) -> std::result::Result<NodeId, Error> { | 110 | pub(crate) fn as_node(&self) -> std::result::Result<NodeId, Error> { |
121 | match self { | 111 | match self { |
122 | Self::Node(id) => Ok(*id), | 112 | Self::Node(id) => Ok(*id), |
123 | v => Err(Error::TypeMismatch { | 113 | v => Err(v.ty().expected([ast::Type::Node])), |
124 | expected: ast::Type::Node, | ||
125 | got: v.ty(), | ||
126 | }), | ||
127 | } | 114 | } |
128 | } | 115 | } |
129 | 116 | ||
130 | pub(crate) fn as_list(&self) -> std::result::Result<Vec<Value>, Error> { | 117 | pub(crate) fn as_list(&self) -> std::result::Result<Vec<Value>, Error> { |
131 | match self { | 118 | match self { |
132 | Self::List(values) => Ok(values.clone()), | 119 | Self::List(values) => Ok(values.clone()), |
133 | v => Err(Error::TypeMismatch { | 120 | v => Err(v.ty().expected([ast::Type::List])), |
134 | expected: ast::Type::List, | ||
135 | got: v.ty(), | ||
136 | }), | ||
137 | } | 121 | } |
138 | } | 122 | } |
139 | 123 | ||
@@ -350,7 +334,7 @@ impl From<Vec<Value>> for Value { | |||
350 | pub enum Error { | 334 | pub enum Error { |
351 | FailedLookup(ast::Identifier), | 335 | FailedLookup(ast::Identifier), |
352 | TypeMismatch { | 336 | TypeMismatch { |
353 | expected: ast::Type, | 337 | expected: Vec<ast::Type>, |
354 | got: ast::Type, | 338 | got: ast::Type, |
355 | }, | 339 | }, |
356 | UndefinedBinOp(ast::BinOp, ast::Type, ast::Type), | 340 | UndefinedBinOp(ast::BinOp, ast::Type, ast::Type), |
@@ -376,9 +360,18 @@ pub enum Error { | |||
376 | CurrentNodeNotPresent, | 360 | CurrentNodeNotPresent, |
377 | } | 361 | } |
378 | 362 | ||
363 | impl ast::Type { | ||
364 | pub fn expected<const N: usize>(self, expected: [Self; N]) -> Error { | ||
365 | Error::TypeMismatch { | ||
366 | expected: expected.to_vec(), | ||
367 | got: self, | ||
368 | } | ||
369 | } | ||
370 | } | ||
371 | |||
379 | pub type Result = std::result::Result<Value, Error>; | 372 | pub type Result = std::result::Result<Value, Error>; |
380 | 373 | ||
381 | pub struct Context { | 374 | pub struct Context<'s> { |
382 | variables: HashMap<ast::Identifier, Variable>, | 375 | variables: HashMap<ast::Identifier, Variable>, |
383 | language: tree_sitter::Language, | 376 | language: tree_sitter::Language, |
384 | program: ast::Program, | 377 | program: ast::Program, |
@@ -386,9 +379,10 @@ pub struct Context { | |||
386 | cursor: Option<tree_sitter::TreeCursor<'static>>, | 379 | cursor: Option<tree_sitter::TreeCursor<'static>>, |
387 | tree: Option<&'static tree_sitter::Tree>, | 380 | tree: Option<&'static tree_sitter::Tree>, |
388 | cache: HashMap<NodeId, tree_sitter::Node<'static>>, | 381 | cache: HashMap<NodeId, tree_sitter::Node<'static>>, |
382 | output_stream: Option<Box<dyn io::Write + 's>>, | ||
389 | } | 383 | } |
390 | 384 | ||
391 | impl fmt::Debug for Context { | 385 | impl<'s> fmt::Debug for Context<'s> { |
392 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | 386 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
393 | f.debug_struct("Context") | 387 | f.debug_struct("Context") |
394 | .field("variables", &self.variables) | 388 | .field("variables", &self.variables) |
@@ -406,7 +400,7 @@ impl fmt::Debug for Context { | |||
406 | } | 400 | } |
407 | } | 401 | } |
408 | 402 | ||
409 | impl Context { | 403 | impl<'s> Context<'s> { |
410 | pub fn new(language: tree_sitter::Language) -> Self { | 404 | pub fn new(language: tree_sitter::Language) -> Self { |
411 | Self { | 405 | Self { |
412 | program: Default::default(), | 406 | program: Default::default(), |
@@ -416,6 +410,7 @@ impl Context { | |||
416 | cursor: None, | 410 | cursor: None, |
417 | tree: None, | 411 | tree: None, |
418 | cache: HashMap::default(), | 412 | cache: HashMap::default(), |
413 | output_stream: Some(Box::new(io::stdout()) as Box<dyn io::Write + 's>), | ||
419 | } | 414 | } |
420 | } | 415 | } |
421 | 416 | ||
@@ -469,6 +464,11 @@ impl Context { | |||
469 | self | 464 | self |
470 | } | 465 | } |
471 | 466 | ||
467 | pub fn with_output_stream(mut self, stream: Box<dyn io::Write + 's>) -> Self { | ||
468 | self.output_stream = Some(stream); | ||
469 | self | ||
470 | } | ||
471 | |||
472 | pub(crate) fn eval_expr(&mut self, expr: &ast::Expr) -> Result { | 472 | pub(crate) fn eval_expr(&mut self, expr: &ast::Expr) -> Result { |
473 | match expr { | 473 | match expr { |
474 | ast::Expr::Unit => Ok(Value::Unit), | 474 | ast::Expr::Unit => Ok(Value::Unit), |
@@ -644,7 +644,7 @@ impl Context { | |||
644 | } | 644 | } |
645 | 645 | ||
646 | fn eval_call(&mut self, call: &ast::Call) -> Result { | 646 | fn eval_call(&mut self, call: &ast::Call) -> Result { |
647 | ((&*crate::builtins::BUILTINS) | 647 | ((&*builtins::BUILTINS) |
648 | .get(call.function.as_str()) | 648 | .get(call.function.as_str()) |
649 | .ok_or_else(|| Error::FailedLookup(call.function.to_owned()))?)( | 649 | .ok_or_else(|| Error::FailedLookup(call.function.to_owned()))?)( |
650 | self, | 650 | self, |
@@ -779,6 +779,7 @@ pub fn evaluate(file: &str, program: &str, language: tree_sitter::Language) -> R | |||
779 | let mut ctx = Context::new(language) | 779 | let mut ctx = Context::new(language) |
780 | .with_input(file.to_owned()) | 780 | .with_input(file.to_owned()) |
781 | .with_tree(tree) | 781 | .with_tree(tree) |
782 | .with_output_stream(Box::new(io::stdout())) | ||
782 | .with_program(program); | 783 | .with_program(program); |
783 | 784 | ||
784 | ctx.eval() | 785 | ctx.eval() |
@@ -788,6 +789,28 @@ pub fn evaluate(file: &str, program: &str, language: tree_sitter::Language) -> R | |||
788 | mod test { | 789 | mod test { |
789 | use super::*; | 790 | use super::*; |
790 | use crate::ast::*; | 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().from_str(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 | } | ||
791 | 814 | ||
792 | #[test] | 815 | #[test] |
793 | fn bin() { | 816 | fn bin() { |
@@ -952,22 +975,77 @@ mod test { | |||
952 | } | 975 | } |
953 | 976 | ||
954 | #[test] | 977 | #[test] |
955 | fn test_ts_builtins() { | 978 | fn list_builtins() { |
956 | let language = tree_sitter_python::language(); | 979 | gen_test( |
957 | let mut ctx = Context::new(language).with_program(Program::new()); | 980 | "", |
958 | assert_eq!( | 981 | "BEGIN { |
959 | ctx.eval_block(&Block { | 982 | list a = [5]; |
960 | body: vec![Statement::decl(Type::List, "a", Expr::list([Expr::int(5)]),)] | 983 | print(a); |
961 | }), | 984 | } |
962 | Ok(Value::Unit) | 985 | ", |
986 | expect!["[5]"] | ||
963 | ); | 987 | ); |
964 | assert_eq!( | 988 | |
965 | ctx.lookup(&String::from("a")).unwrap().clone(), | 989 | gen_test( |
966 | Variable { | 990 | "", |
967 | ty: Type::List, | 991 | "BEGIN { |
968 | name: "a".to_owned(), | 992 | list a = [5, 4, 3]; |
969 | value: vec![5usize.into()].into(), | 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)); | ||
970 | } | 1006 | } |
1007 | "#, | ||
1008 | expect!["true, false"] | ||
971 | ); | 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 | |||
972 | } | 1050 | } |
973 | } | 1051 | } |
@@ -1,5 +1,4 @@ | |||
1 | mod ast; | 1 | mod ast; |
2 | mod builtins; | ||
3 | mod eval; | 2 | mod eval; |
4 | mod parser; | 3 | mod parser; |
5 | mod string; | 4 | mod string; |