diff options
-rw-r--r-- | Cargo.lock | 7 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/eval.rs | 154 |
3 files changed, 150 insertions, 12 deletions
@@ -49,6 +49,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
49 | checksum = "066fce287b1d4eafef758e89e09d724a24808a9196fe9756b8ca90e86d0719a2" | 49 | checksum = "066fce287b1d4eafef758e89e09d724a24808a9196fe9756b8ca90e86d0719a2" |
50 | 50 | ||
51 | [[package]] | 51 | [[package]] |
52 | name = "la-arena" | ||
53 | version = "0.3.1" | ||
54 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
55 | checksum = "3752f229dcc5a481d60f385fa479ff46818033d881d2d801aa27dffcfb5e8306" | ||
56 | |||
57 | [[package]] | ||
52 | name = "memchr" | 58 | name = "memchr" |
53 | version = "2.7.4" | 59 | version = "2.7.4" |
54 | source = "registry+https://github.com/rust-lang/crates.io-index" | 60 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -153,6 +159,7 @@ name = "tbsp" | |||
153 | version = "0.1.0" | 159 | version = "0.1.0" |
154 | dependencies = [ | 160 | dependencies = [ |
155 | "argh", | 161 | "argh", |
162 | "la-arena", | ||
156 | "nom", | 163 | "nom", |
157 | "regex", | 164 | "regex", |
158 | "serde", | 165 | "serde", |
@@ -18,6 +18,7 @@ tree-sitter-javascript = {version = "0.21", optional = true} | |||
18 | tree-sitter-python = {version = "0.21", optional = true} | 18 | tree-sitter-python = {version = "0.21", optional = true} |
19 | tree-sitter-rust = {version = "0.21", optional = true} | 19 | tree-sitter-rust = {version = "0.21", optional = true} |
20 | argh = "0.1.12" | 20 | argh = "0.1.12" |
21 | la-arena = "0.3.1" | ||
21 | 22 | ||
22 | [features] | 23 | [features] |
23 | default = ["md", "typescript", "javascript", "rust", "python"] | 24 | default = ["md", "typescript", "javascript", "rust", "python"] |
diff --git a/src/eval.rs b/src/eval.rs index 029cffe..1a3f8a8 100644 --- a/src/eval.rs +++ b/src/eval.rs | |||
@@ -76,10 +76,33 @@ impl Value { | |||
76 | Self::String(String::default()) | 76 | Self::String(String::default()) |
77 | } | 77 | } |
78 | 78 | ||
79 | fn as_boolean(&self) -> Option<bool> { | 79 | fn as_boolean(&self) -> std::result::Result<bool, Error> { |
80 | match self { | 80 | match self { |
81 | Self::Boolean(b) => Some(*b), | 81 | Self::Boolean(b) => Ok(*b), |
82 | _ => None, | 82 | v => Err(Error::TypeMismatch { |
83 | expected: ast::Type::Boolean, | ||
84 | got: v.ty(), | ||
85 | }), | ||
86 | } | ||
87 | } | ||
88 | |||
89 | fn as_str(&self) -> std::result::Result<&str, Error> { | ||
90 | match self { | ||
91 | Self::String(s) => Ok(s.as_str()), | ||
92 | v => Err(Error::TypeMismatch { | ||
93 | expected: ast::Type::String, | ||
94 | got: v.ty(), | ||
95 | }), | ||
96 | } | ||
97 | } | ||
98 | |||
99 | fn as_int(&self) -> std::result::Result<i128, Error> { | ||
100 | match self { | ||
101 | Self::Integer(i) => Ok(*i), | ||
102 | v => Err(Error::TypeMismatch { | ||
103 | expected: ast::Type::Integer, | ||
104 | got: v.ty(), | ||
105 | }), | ||
83 | } | 106 | } |
84 | } | 107 | } |
85 | 108 | ||
@@ -250,6 +273,24 @@ impl fmt::Display for Value { | |||
250 | } | 273 | } |
251 | } | 274 | } |
252 | 275 | ||
276 | impl From<bool> for Value { | ||
277 | fn from(value: bool) -> Self { | ||
278 | Self::Boolean(value) | ||
279 | } | ||
280 | } | ||
281 | |||
282 | impl From<i128> for Value { | ||
283 | fn from(value: i128) -> Self { | ||
284 | Self::Integer(value) | ||
285 | } | ||
286 | } | ||
287 | |||
288 | impl From<&str> for Value { | ||
289 | fn from(value: &str) -> Self { | ||
290 | Self::String(value.to_owned()) | ||
291 | } | ||
292 | } | ||
293 | |||
253 | type NodeKind = u16; | 294 | type NodeKind = u16; |
254 | 295 | ||
255 | #[derive(Debug, Default)] | 296 | #[derive(Debug, Default)] |
@@ -312,12 +353,20 @@ impl Visitors { | |||
312 | #[derive(Debug, PartialEq, Eq)] | 353 | #[derive(Debug, PartialEq, Eq)] |
313 | pub enum Error { | 354 | pub enum Error { |
314 | FailedLookup(ast::Identifier), | 355 | FailedLookup(ast::Identifier), |
315 | TypeMismatch { expected: ast::Type, got: ast::Type }, | 356 | TypeMismatch { |
357 | expected: ast::Type, | ||
358 | got: ast::Type, | ||
359 | }, | ||
316 | UndefinedBinOp(ast::BinOp, ast::Type, ast::Type), | 360 | UndefinedBinOp(ast::BinOp, ast::Type, ast::Type), |
317 | UndefinedUnaryOp(ast::UnaryOp, ast::Type), | 361 | UndefinedUnaryOp(ast::UnaryOp, ast::Type), |
318 | AlreadyBound(ast::Identifier), | 362 | AlreadyBound(ast::Identifier), |
319 | MalformedExpr(String), | 363 | MalformedExpr(String), |
320 | InvalidNodeKind(String), | 364 | InvalidNodeKind(String), |
365 | InvalidStringSlice { | ||
366 | length: usize, | ||
367 | start: i128, | ||
368 | end: i128, | ||
369 | }, | ||
321 | // current node is only set in visitors, not in BEGIN or END blocks | 370 | // current node is only set in visitors, not in BEGIN or END blocks |
322 | CurrentNodeNotPresent, | 371 | CurrentNodeNotPresent, |
323 | } | 372 | } |
@@ -496,10 +545,7 @@ impl<'a> Context<'a> { | |||
496 | let l = self.eval_expr(lhs)?; | 545 | let l = self.eval_expr(lhs)?; |
497 | 546 | ||
498 | // short-circuit | 547 | // short-circuit |
499 | let l_value = l.as_boolean().ok_or_else(|| Error::TypeMismatch { | 548 | let l_value = l.as_boolean()?; |
500 | expected: ast::Type::Boolean, | ||
501 | got: l.ty(), | ||
502 | })?; | ||
503 | 549 | ||
504 | match op { | 550 | match op { |
505 | ast::LogicOp::Or => { | 551 | ast::LogicOp::Or => { |
@@ -531,10 +577,7 @@ impl<'a> Context<'a> { | |||
531 | fn eval_if(&mut self, if_expr: &ast::If) -> Result { | 577 | fn eval_if(&mut self, if_expr: &ast::If) -> Result { |
532 | let cond = self.eval_expr(&if_expr.condition)?; | 578 | let cond = self.eval_expr(&if_expr.condition)?; |
533 | 579 | ||
534 | if cond.as_boolean().ok_or_else(|| Error::TypeMismatch { | 580 | if cond.as_boolean()? { |
535 | expected: ast::Type::Boolean, | ||
536 | got: cond.ty(), | ||
537 | })? { | ||
538 | self.eval_block(&if_expr.then) | 581 | self.eval_block(&if_expr.then) |
539 | } else { | 582 | } else { |
540 | self.eval_block(&if_expr.else_) | 583 | self.eval_block(&if_expr.else_) |
@@ -550,6 +593,50 @@ impl<'a> Context<'a> { | |||
550 | } | 593 | } |
551 | Ok(Value::Unit) | 594 | Ok(Value::Unit) |
552 | } | 595 | } |
596 | (predicate @ ("isupper" | "islower"), [arg]) => Ok(self | ||
597 | .eval_expr(arg)? | ||
598 | .as_str()? | ||
599 | .chars() | ||
600 | .all(|c| match predicate { | ||
601 | "isupper" => c.is_ascii_uppercase(), | ||
602 | "islower" => c.is_ascii_lowercase(), | ||
603 | _ => unreachable!(), | ||
604 | }) | ||
605 | .into()), | ||
606 | ("substr", [string, indices @ ..]) => { | ||
607 | let v = self.eval_expr(string)?; | ||
608 | let s = v.as_str()?; | ||
609 | match indices { | ||
610 | [start, end] => { | ||
611 | let start = self.eval_expr(start)?.as_int()?; | ||
612 | let end = self.eval_expr(end)?.as_int()?; | ||
613 | if start < 0 | ||
614 | || start >= s.len() as i128 | ||
615 | || end >= s.len() as i128 | ||
616 | || start > end | ||
617 | { | ||
618 | return Err(Error::InvalidStringSlice { | ||
619 | length: s.len(), | ||
620 | start, | ||
621 | end, | ||
622 | }); | ||
623 | } | ||
624 | Ok(s[start as usize..end as usize].into()) | ||
625 | } | ||
626 | [end] => { | ||
627 | let end = self.eval_expr(end)?.as_int()?; | ||
628 | if end >= s.len() as i128 { | ||
629 | return Err(Error::InvalidStringSlice { | ||
630 | length: s.len(), | ||
631 | start: 0, | ||
632 | end, | ||
633 | }); | ||
634 | } | ||
635 | Ok(s[..end as usize].into()) | ||
636 | } | ||
637 | _ => todo!(), | ||
638 | } | ||
639 | } | ||
553 | ("text", [arg]) => { | 640 | ("text", [arg]) => { |
554 | let node = match self.eval_expr(arg)? { | 641 | let node = match self.eval_expr(arg)? { |
555 | Value::Node => self | 642 | Value::Node => self |
@@ -804,4 +891,47 @@ mod test { | |||
804 | } | 891 | } |
805 | ); | 892 | ); |
806 | } | 893 | } |
894 | |||
895 | #[test] | ||
896 | fn test_substring() { | ||
897 | let language = tree_sitter_python::language(); | ||
898 | let mut ctx = Context::new(language) | ||
899 | .with_program(ast::Program::new()) | ||
900 | .unwrap(); | ||
901 | assert_eq!( | ||
902 | ctx.eval_block(&ast::Block { | ||
903 | body: vec![ | ||
904 | ast::Statement::Declaration(ast::Declaration { | ||
905 | ty: ast::Type::String, | ||
906 | name: "a".to_owned(), | ||
907 | init: Some(ast::Expr::str("foobar").boxed()), | ||
908 | }), | ||
909 | ast::Statement::Declaration(ast::Declaration { | ||
910 | ty: ast::Type::String, | ||
911 | name: "b".to_owned(), | ||
912 | init: Some( | ||
913 | ast::Expr::Call(ast::Call { | ||
914 | function: "substr".into(), | ||
915 | parameters: vec![ | ||
916 | ast::Expr::Ident("a".to_owned()), | ||
917 | ast::Expr::int(0), | ||
918 | ast::Expr::int(3), | ||
919 | ] | ||
920 | }) | ||
921 | .boxed() | ||
922 | ), | ||
923 | }), | ||
924 | ] | ||
925 | }), | ||
926 | Ok(Value::Unit) | ||
927 | ); | ||
928 | assert_eq!( | ||
929 | ctx.lookup(&String::from("b")).unwrap().clone(), | ||
930 | Variable { | ||
931 | ty: ast::Type::String, | ||
932 | name: "b".to_owned(), | ||
933 | value: "foo".into() | ||
934 | } | ||
935 | ); | ||
936 | } | ||
807 | } | 937 | } |