From 3ad5ba1f6d3f4c5944bbf29a7d761c93cf3740e3 Mon Sep 17 00:00:00 2001 From: Akshay Date: Mon, 5 Aug 2024 21:52:22 +0100 Subject: add string::substr --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/eval.rs | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 150 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b853519..afa07fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,6 +48,12 @@ version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "066fce287b1d4eafef758e89e09d724a24808a9196fe9756b8ca90e86d0719a2" +[[package]] +name = "la-arena" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3752f229dcc5a481d60f385fa479ff46818033d881d2d801aa27dffcfb5e8306" + [[package]] name = "memchr" version = "2.7.4" @@ -153,6 +159,7 @@ name = "tbsp" version = "0.1.0" dependencies = [ "argh", + "la-arena", "nom", "regex", "serde", diff --git a/Cargo.toml b/Cargo.toml index 429338f..5d43ae0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ tree-sitter-javascript = {version = "0.21", optional = true} tree-sitter-python = {version = "0.21", optional = true} tree-sitter-rust = {version = "0.21", optional = true} argh = "0.1.12" +la-arena = "0.3.1" [features] 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 { Self::String(String::default()) } - fn as_boolean(&self) -> Option { + fn as_boolean(&self) -> std::result::Result { match self { - Self::Boolean(b) => Some(*b), - _ => None, + Self::Boolean(b) => Ok(*b), + v => Err(Error::TypeMismatch { + expected: ast::Type::Boolean, + got: v.ty(), + }), + } + } + + fn as_str(&self) -> std::result::Result<&str, Error> { + match self { + Self::String(s) => Ok(s.as_str()), + v => Err(Error::TypeMismatch { + expected: ast::Type::String, + got: v.ty(), + }), + } + } + + fn as_int(&self) -> std::result::Result { + match self { + Self::Integer(i) => Ok(*i), + v => Err(Error::TypeMismatch { + expected: ast::Type::Integer, + got: v.ty(), + }), } } @@ -250,6 +273,24 @@ impl fmt::Display for Value { } } +impl From for Value { + fn from(value: bool) -> Self { + Self::Boolean(value) + } +} + +impl From for Value { + fn from(value: i128) -> Self { + Self::Integer(value) + } +} + +impl From<&str> for Value { + fn from(value: &str) -> Self { + Self::String(value.to_owned()) + } +} + type NodeKind = u16; #[derive(Debug, Default)] @@ -312,12 +353,20 @@ impl Visitors { #[derive(Debug, PartialEq, Eq)] pub enum Error { FailedLookup(ast::Identifier), - TypeMismatch { expected: ast::Type, got: ast::Type }, + TypeMismatch { + expected: ast::Type, + got: ast::Type, + }, UndefinedBinOp(ast::BinOp, ast::Type, ast::Type), UndefinedUnaryOp(ast::UnaryOp, ast::Type), AlreadyBound(ast::Identifier), MalformedExpr(String), InvalidNodeKind(String), + InvalidStringSlice { + length: usize, + start: i128, + end: i128, + }, // current node is only set in visitors, not in BEGIN or END blocks CurrentNodeNotPresent, } @@ -496,10 +545,7 @@ impl<'a> Context<'a> { let l = self.eval_expr(lhs)?; // short-circuit - let l_value = l.as_boolean().ok_or_else(|| Error::TypeMismatch { - expected: ast::Type::Boolean, - got: l.ty(), - })?; + let l_value = l.as_boolean()?; match op { ast::LogicOp::Or => { @@ -531,10 +577,7 @@ impl<'a> Context<'a> { fn eval_if(&mut self, if_expr: &ast::If) -> Result { let cond = self.eval_expr(&if_expr.condition)?; - if cond.as_boolean().ok_or_else(|| Error::TypeMismatch { - expected: ast::Type::Boolean, - got: cond.ty(), - })? { + if cond.as_boolean()? { self.eval_block(&if_expr.then) } else { self.eval_block(&if_expr.else_) @@ -550,6 +593,50 @@ impl<'a> Context<'a> { } Ok(Value::Unit) } + (predicate @ ("isupper" | "islower"), [arg]) => Ok(self + .eval_expr(arg)? + .as_str()? + .chars() + .all(|c| match predicate { + "isupper" => c.is_ascii_uppercase(), + "islower" => c.is_ascii_lowercase(), + _ => unreachable!(), + }) + .into()), + ("substr", [string, indices @ ..]) => { + let v = self.eval_expr(string)?; + let s = v.as_str()?; + match indices { + [start, end] => { + let start = self.eval_expr(start)?.as_int()?; + let end = self.eval_expr(end)?.as_int()?; + if start < 0 + || start >= s.len() as i128 + || end >= s.len() as i128 + || start > end + { + return Err(Error::InvalidStringSlice { + length: s.len(), + start, + end, + }); + } + Ok(s[start as usize..end as usize].into()) + } + [end] => { + let end = self.eval_expr(end)?.as_int()?; + if end >= s.len() as i128 { + return Err(Error::InvalidStringSlice { + length: s.len(), + start: 0, + end, + }); + } + Ok(s[..end as usize].into()) + } + _ => todo!(), + } + } ("text", [arg]) => { let node = match self.eval_expr(arg)? { Value::Node => self @@ -804,4 +891,47 @@ mod test { } ); } + + #[test] + fn test_substring() { + let language = tree_sitter_python::language(); + let mut ctx = Context::new(language) + .with_program(ast::Program::new()) + .unwrap(); + assert_eq!( + ctx.eval_block(&ast::Block { + body: vec![ + ast::Statement::Declaration(ast::Declaration { + ty: ast::Type::String, + name: "a".to_owned(), + init: Some(ast::Expr::str("foobar").boxed()), + }), + ast::Statement::Declaration(ast::Declaration { + ty: ast::Type::String, + name: "b".to_owned(), + init: Some( + ast::Expr::Call(ast::Call { + function: "substr".into(), + parameters: vec![ + ast::Expr::Ident("a".to_owned()), + ast::Expr::int(0), + ast::Expr::int(3), + ] + }) + .boxed() + ), + }), + ] + }), + Ok(Value::Unit) + ); + assert_eq!( + ctx.lookup(&String::from("b")).unwrap().clone(), + Variable { + ty: ast::Type::String, + name: "b".to_owned(), + value: "foo".into() + } + ); + } } -- cgit v1.2.3