From aebbe20e009cc29c17b3f91bae18fffe85a1414d Mon Sep 17 00:00:00 2001 From: maxmindlin <35264981+maxmindlin@users.noreply.github.com> Date: Wed, 17 Jul 2024 13:50:54 -0400 Subject: [PATCH] maps --- scout-interpreter/src/builtin.rs | 52 +++++++++++++++++-- scout-interpreter/src/lib.rs | 31 +++++++++++- scout-interpreter/src/object.rs | 87 +++++++++++++++++++++----------- scout-lib/keys.sct | 2 +- scout-lib/str.sct | 4 +- scout-parser/src/ast.rs | 1 + scout-parser/src/lib.rs | 6 +++ 7 files changed, 145 insertions(+), 38 deletions(-) diff --git a/scout-interpreter/src/builtin.rs b/scout-interpreter/src/builtin.rs index 11a1e17..efb0bb5 100644 --- a/scout-interpreter/src/builtin.rs +++ b/scout-interpreter/src/builtin.rs @@ -1,11 +1,14 @@ -use std::{env, sync::Arc, thread::sleep, time::Duration}; +use std::{collections::HashMap, env, sync::Arc, thread::sleep, time::Duration}; use fantoccini::{ actions::{InputSource, KeyAction, KeyActions}, + client, + cookies::Cookie, elements::Element, key::Key, }; use futures::{future::BoxFuture, lock::Mutex, FutureExt, TryFutureExt}; +use scout_parser::ast::Identifier; use crate::{object::Object, EvalError, EvalResult, ScrapeResultsPtr}; @@ -36,13 +39,15 @@ pub enum BuiltinKind { IsWhitespace, List, Push, + Cookies, + SetCookies, } impl BuiltinKind { pub fn is_from(s: &str) -> Option { use BuiltinKind::*; match s { - "is_whitespace" => Some(IsWhitespace), + "isWhitespace" => Some(IsWhitespace), "url" => Some(Url), "number" => Some(Number), "args" => Some(Args), @@ -55,10 +60,12 @@ impl BuiltinKind { "input" => Some(Input), "contains" => Some(Contains), "type" => Some(Type), - "key_action" => Some(KeyPress), + "keyAction" => Some(KeyPress), "sleep" => Some(Sleep), "list" => Some(List), "push" => Some(Push), + "cookies" => Some(Cookies), + "setCookies" => Some(SetCookies), _ => None, } } @@ -71,6 +78,36 @@ impl BuiltinKind { ) -> EvalResult { use BuiltinKind::*; match self { + Cookies => { + let cookies = crawler + .get_all_cookies() + .await? + .iter() + .map(|c| { + ( + Identifier::new(c.name().to_string()), + Arc::new(Object::Str(c.value().to_string())), + ) + }) + .collect::>>(); + + Ok(Arc::new(Object::Map(Mutex::new(cookies)))) + } + SetCookies => { + assert_param_len!(args, 1); + if let Object::Map(m) = &*args[0] { + let inner = m.lock().await; + crawler.delete_all_cookies().await?; + for (key, val) in inner.iter() { + let cookie = Cookie::new(key.name.clone(), val.to_string()); + crawler.add_cookie(cookie).await?; + } + + Ok(Arc::new(Object::Null)) + } else { + Err(EvalError::InvalidFnParams) + } + } Push => { assert_param_len!(args, 2); match (&*args[0], args[1].clone()) { @@ -236,7 +273,14 @@ impl BuiltinKind { _ => Err(EvalError::InvalidFnParams), }, Object::List(v) => { - let contains = v.lock().await.contains(&args[1]); + let inner = v.lock().await; + let mut contains = false; + for obj in inner.iter() { + if obj.eq(&args[1]).await { + contains = true; + break; + } + } Ok(Arc::new(Object::Boolean(contains))) } _ => Err(EvalError::InvalidFnParams), diff --git a/scout-interpreter/src/lib.rs b/scout-interpreter/src/lib.rs index 80f0d3a..750d117 100644 --- a/scout-interpreter/src/lib.rs +++ b/scout-interpreter/src/lib.rs @@ -91,6 +91,7 @@ pub enum EvalError { UnknownIdent(Identifier), UnknownPrefixOp, UnknownInfixOp, + UnknownKey(Identifier), UncaughtException, URLParseError(String), DuplicateDeclare, @@ -230,6 +231,12 @@ fn eval_statement<'a>( } Ok(Arc::new(Object::Null)) } + (Object::Map(m), Object::Str(s)) => { + let mut inner = m.lock().await; + let ident = Identifier::new(s.clone()); + inner.insert(ident, val); + Ok(Arc::new(Object::Null)) + } _ => Err(EvalError::InvalidIndex), } } @@ -706,6 +713,17 @@ fn eval_expression<'a>( }, ExprKind::Str(s) => Ok(Arc::new(Object::Str(s.to_owned()))), ExprKind::Number(n) => Ok(Arc::new(Object::Number(*n))), + ExprKind::Map(map) => { + let mut out = HashMap::new(); + + for (key, val) in map.pairs.iter() { + let obj_val = + eval_expression(val, crawler, env.clone(), results.clone()).await?; + out.insert(key.clone(), obj_val); + } + + Ok(Arc::new(Object::Map(Mutex::new(out)))) + } ExprKind::Call(ident, params) => { apply_call(ident, params, crawler, None, env.clone(), results.clone()).await } @@ -786,8 +804,8 @@ async fn eval_infix( async fn eval_infix_op(lhs: Arc, op: &TokenKind, rhs: Arc) -> EvalResult { match op { - TokenKind::EQ => Ok(Arc::new(Object::Boolean(lhs == rhs))), - TokenKind::NEQ => Ok(Arc::new(Object::Boolean(lhs != rhs))), + TokenKind::EQ => Ok(Arc::new(Object::Boolean(lhs.eq(&rhs).await))), + TokenKind::NEQ => Ok(Arc::new(Object::Boolean(!lhs.eq(&rhs).await))), TokenKind::Plus => eval_plus_op(lhs, rhs), TokenKind::Minus => eval_minus_op(lhs, rhs), TokenKind::Asterisk => eval_asterisk_op(lhs, rhs), @@ -828,6 +846,15 @@ async fn eval_index(lhs: Arc, idx: Arc) -> EvalResult { Err(EvalError::IndexOutOfBounds) } } + (Object::Map(m), Object::Str(s)) => { + let inner = m.lock().await; + let ident = Identifier::new(s.clone()); + let mb_val = inner.get(&ident); + match mb_val { + Some(val) => Ok(val.clone()), + None => Err(EvalError::UnknownIdent(ident)), + } + } (Object::Str(a), Object::Number(b)) => match a.chars().nth(*b as usize) { Some(c) => Ok(Arc::new(Object::Str(c.to_string()))), None => Err(EvalError::IndexOutOfBounds), diff --git a/scout-interpreter/src/object.rs b/scout-interpreter/src/object.rs index b53cd4c..fa1f0a7 100644 --- a/scout-interpreter/src/object.rs +++ b/scout-interpreter/src/object.rs @@ -9,7 +9,7 @@ use crate::env::EnvPointer; #[derive(Debug)] pub enum Object { Null, - Map(HashMap>), + Map(Mutex>>), Str(String), Node(fantoccini::elements::Element), List(Mutex>>), @@ -62,11 +62,15 @@ impl Object { match self { Null => "Null".into(), Map(hash) => { - let mut out = "{{".to_string(); - for (i, o) in hash.iter() { - out.push_str(&format!("{}: {} ", i, o)); + let inner = hash.lock().await; + let mut out = "{ ".to_string(); + for (idx, (i, o)) in inner.iter().enumerate() { + out.push_str(&format!("{}: {}", i, o)); + if idx != inner.len() - 1 { + out.push_str(", "); + } } - out.push_str("}}"); + out.push_str(" }"); out } Str(s) => format!("\"{}\"", s), @@ -92,23 +96,52 @@ impl Object { } .boxed() } -} -impl PartialEq for Object { - fn eq(&self, other: &Self) -> bool { - use Object::*; - match (self, other) { - (Null, Null) => true, - (Map(a), Map(b)) => a == b, - (Str(a), Str(b)) => a == b, - // @TODO: check if this is even correct - (Node(a), Node(b)) => a.element_id() == b.element_id(), - // @TODO: this requires async awaits.... - (List(_a), List(_b)) => false, - (Boolean(a), Boolean(b)) => a == b, - (Number(a), Number(b)) => a == b, - _ => false, + pub fn eq<'a>(&'a self, other: &'a Self) -> BoxFuture<'a, bool> { + async move { + use Object::*; + match (self, other) { + (Null, Null) => true, + (Map(a), Map(b)) => { + let a_i = a.lock().await; + let b_i = b.lock().await; + for key in a_i.keys() { + match b_i.get(key) { + Some(obj) => { + if !a_i.get(key).unwrap().eq(obj).await { + return false; + } + } + None => return false, + } + } + true + } + (Str(a), Str(b)) => a == b, + // @TODO: check if this is even correct + (Node(a), Node(b)) => a.element_id() == b.element_id(), + (List(a), List(b)) => { + let a_i = a.lock().await; + let b_i = b.lock().await; + + if a_i.len() != b_i.len() { + return false; + } + + for idx in 0..(a_i.len() - 1) { + if !a_i[idx].eq(&b_i[idx]).await { + return false; + } + } + + true + } + (Boolean(a), Boolean(b)) => a == b, + (Number(a), Number(b)) => a == b, + _ => false, + } } + .boxed() } } @@ -117,13 +150,6 @@ impl Display for Object { use Object::*; match self { Null => write!(f, "Null"), - Map(hash) => { - write!(f, "{{")?; - for (i, o) in hash.iter() { - write!(f, "{}: {} ", i, o)?; - } - write!(f, "}}") - } Str(s) => write!(f, "\"{}\"", s), Node(_) => write!(f, "Node"), List(_objs) => write!(f, "list"), @@ -155,7 +181,10 @@ impl Object { // @TODO handle this better Node(_) => Value::String("Node".to_owned()), List(list) => self.vec_to_json(list).await, - Map(map) => Value::Object(obj_map_to_json(map).await), + Map(map) => { + let inner = map.lock().await; + Value::Object(obj_map_to_json(&*inner).await) + } Boolean(b) => Value::Bool(*b), Number(n) => json!(n), Fn(_, _) => panic!("cant serialize func"), @@ -168,7 +197,7 @@ impl Object { match self { Null => false, Str(s) => !s.is_empty(), - Map(m) => !m.is_empty(), + Map(m) => !m.lock().await.is_empty(), List(v) => !v.lock().await.is_empty(), Boolean(b) => *b, // @TODO: Idk what truthiness of floats should be diff --git a/scout-lib/keys.sct b/scout-lib/keys.sct index cb60e8c..5c8c1d8 100644 --- a/scout-lib/keys.sct +++ b/scout-lib/keys.sct @@ -59,5 +59,5 @@ COMMAND = "\u{e03d}" // Executes a key press action with // the given key unicode value. def press(code) do - key_action(code) + keyAction(code) end diff --git a/scout-lib/str.sct b/scout-lib/str.sct index d3cc64a..a39d7ea 100644 --- a/scout-lib/str.sct +++ b/scout-lib/str.sct @@ -2,7 +2,7 @@ // from the left start of a given string. def ltrim(s) do i = 0 - while is_whitespace(s[i]) and i < len(s) do + while isWhitespace(s[i]) and i < len(s) do i = i + 1 end @@ -18,7 +18,7 @@ end // from the right start of a given string. def rtrim(s) do i = len(s) - 1 - while is_whitespace(s[i]) and i > 0 do + while isWhitespace(s[i]) and i > 0 do i = i - 1 end diff --git a/scout-parser/src/ast.rs b/scout-parser/src/ast.rs index 62e64d8..11194a8 100644 --- a/scout-parser/src/ast.rs +++ b/scout-parser/src/ast.rs @@ -39,6 +39,7 @@ pub enum ExprKind { Boolean(bool), Ident(Identifier), List(Vec), + Map(HashLiteral), Null, // Selects diff --git a/scout-parser/src/lib.rs b/scout-parser/src/lib.rs index a7088d7..a6e91b2 100644 --- a/scout-parser/src/lib.rs +++ b/scout-parser/src/lib.rs @@ -61,6 +61,7 @@ fn map_prefix_fn(kind: &TokenKind) -> Option { Str => Some(Parser::parse_str_literal), Null => Some(Parser::parse_null), LBracket => Some(Parser::parse_list_literal), + LBrace => Some(Parser::parse_map), SelectAll => Some(Parser::parse_select_all), Select => Some(Parser::parse_select), Bang => Some(Parser::parse_prefix), @@ -395,6 +396,11 @@ impl Parser { Ok(HashLiteral { pairs }) } + fn parse_map(&mut self) -> ParseResult { + let lit = self.parse_hash_literal()?; + Ok(ExprKind::Map(lit)) + } + fn parse_number_literal(&mut self) -> ParseResult { Ok(ExprKind::Number( self.curr