Skip to content

Commit

Permalink
import modules
Browse files Browse the repository at this point in the history
  • Loading branch information
maxmindlin committed Jun 26, 2024
1 parent f042cb0 commit 120c14c
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 69 deletions.
17 changes: 14 additions & 3 deletions scout-interpreter/src/builtin.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{env, sync::Arc};
use std::{env, sync::Arc, thread::sleep, time::Duration};

use fantoccini::{
actions::{InputSource, KeyAction, KeyActions},
Expand Down Expand Up @@ -33,6 +33,7 @@ pub enum BuiltinKind {
KeyPress,
Number,
Url,
Sleep,
}

impl BuiltinKind {
Expand All @@ -53,6 +54,7 @@ impl BuiltinKind {
"contains" => Some(Contains),
"type" => Some(Type),
"key_action" => Some(KeyPress),
"sleep" => Some(Sleep),
_ => None,
}
}
Expand All @@ -65,6 +67,15 @@ impl BuiltinKind {
) -> EvalResult {
use BuiltinKind::*;
match self {
Sleep => {
assert_param_len!(args, 1);
if let Object::Number(ms) = &*args[0] {
sleep(Duration::from_millis(ms.round() as u64));
Ok(Arc::new(Object::Null))
} else {
Err(EvalError::InvalidFnParams)
}
}
Url => {
let url = crawler.current_url().await?;
Ok(Arc::new(Object::Str(url.to_string())))
Expand All @@ -87,8 +98,8 @@ impl BuiltinKind {
Args => {
let env_args = env::args().collect::<Vec<String>>();
let mut out = Vec::new();
// start at 2 because idx 0 is the executable location & idx 1 is the filename
for idx in 2..env_args.len() {
// start at 1 because idx 0 is the executable location
for idx in 1..env_args.len() {
out.push(Arc::new(Object::Str(env_args[idx].clone())));
}
Ok(Arc::new(Object::List(out)))
Expand Down
2 changes: 1 addition & 1 deletion scout-interpreter/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::borrow::Borrow;
use std::collections::HashMap;
use std::sync::{Arc, Weak};

pub(crate) type EnvPointer = Arc<Mutex<Env>>;
pub type EnvPointer = Arc<Mutex<Env>>;

#[derive(Debug, Default)]
pub struct Env {
Expand Down
59 changes: 59 additions & 0 deletions scout-interpreter/src/import.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use std::path::{Path, PathBuf};

use scout_lexer::TokenKind;
use scout_parser::ast::{ExprKind, Identifier};

use crate::EvalError;

#[derive(Debug)]
pub struct ResolvedMod {
pub ident: Identifier,
pub filepath: String,
}

pub fn resolve_module(module: &ExprKind) -> Result<ResolvedMod, EvalError> {
let ident = match module {
ExprKind::Ident(ident) => Ok(ident.clone()),
ExprKind::Infix(_, _, rhs) => match rhs.as_ref() {
ExprKind::Ident(ident) => Ok(ident.clone()),
_ => Err(EvalError::InvalidImport),
},
_ => Err(EvalError::InvalidImport),
}?;
let buf = resolve_module_file(module)?;
let filepath = convert_path_buf(buf)?;
Ok(ResolvedMod { filepath, ident })
}

fn resolve_std_file(ident: &Identifier) -> Result<PathBuf, EvalError> {
if *ident == Identifier::new("std".into()) {
let path = Path::new("scout-lib").to_owned();
Ok(path)
} else {
Ok(Path::new(&ident.name).to_owned())
}
}

fn convert_path_buf(buf: PathBuf) -> Result<String, EvalError> {
let res = buf.to_str().ok_or(EvalError::InvalidImport)?.to_owned();
Ok(res)
}

fn resolve_module_file(module: &ExprKind) -> Result<PathBuf, EvalError> {
match module {
ExprKind::Ident(ident) => resolve_std_file(ident),
ExprKind::Infix(lhs, TokenKind::DbColon, rhs) => match (lhs.as_ref(), rhs.as_ref()) {
(ExprKind::Ident(base), ExprKind::Ident(file)) => {
let buf = resolve_std_file(base)?.join(&file.name);
Ok(buf)
}
(l @ ExprKind::Infix(_, TokenKind::DbColon, _), ExprKind::Ident(file)) => {
let base = resolve_module_file(l)?;
let buf = base.join(&file.name);
Ok(buf)
}
_ => Err(EvalError::InvalidImport),
},
_ => Err(EvalError::InvalidImport),
}
}
178 changes: 133 additions & 45 deletions scout-interpreter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::collections::HashSet;
use std::env::current_dir;

Check warning on line 2 in scout-interpreter/src/lib.rs

View workflow job for this annotation

GitHub Actions / test

unused import: `std::env::current_dir`
use std::fs;
use std::path::{Path, PathBuf};

Check warning on line 4 in scout-interpreter/src/lib.rs

View workflow job for this annotation

GitHub Actions / test

unused import: `Path`
use std::thread::sleep;
use std::time::Duration;
use std::{collections::HashMap, sync::Arc};
Expand All @@ -8,6 +10,7 @@ use env::EnvPointer;
use fantoccini::Locator;
use futures::lock::Mutex;
use futures::{future::BoxFuture, FutureExt};
use import::ResolvedMod;

Check warning on line 13 in scout-interpreter/src/lib.rs

View workflow job for this annotation

GitHub Actions / test

unused import: `import::ResolvedMod`
use object::{obj_map_to_json, Object};
use scout_lexer::{Lexer, TokenKind};
use scout_parser::ast::{
Expand All @@ -17,10 +20,12 @@ use scout_parser::Parser;
use serde::{Deserialize, Serialize};
use serde_json::{json, Map, Value};

use crate::import::resolve_module;
use crate::{builtin::BuiltinKind, env::Env};

pub mod builtin;
pub mod env;
pub mod import;
pub mod object;

pub type EvalResult = Result<Arc<Object>, EvalError>;
Expand Down Expand Up @@ -239,40 +244,11 @@ fn eval_statement<'a>(
Some(expr) => eval_expression(expr, crawler, env.clone(), results.clone()).await,
},
StmtKind::Use(import) => {
println!("{import:?}");
let file = eval_expression(import, crawler, env.clone(), results.clone()).await?;
match &*file {
Object::Str(f_str) => {
let curr_dir = std::env::current_dir().map_err(|_| EvalError::OSError)?;
let f_path = std::path::Path::new(f_str);
let mut path = curr_dir.join(f_path);
path.set_extension("sct");
match path.to_str() {
Some(path) => {
let content =
fs::read_to_string(path).map_err(|_| EvalError::OSError)?;
let lex = Lexer::new(&content);
let mut parser = Parser::new(lex);
match parser.parse_program() {
Ok(prgm) => {
let module_env = Arc::new(Mutex::new(Env::default()));
let _ = eval(
NodeKind::Program(prgm),
crawler,
module_env.clone(),
results.clone(),
)
.await?;
Ok(Arc::new(Object::Module(module_env)))
}
Err(_) => Err(EvalError::InvalidImport),
}
}
None => Err(EvalError::InvalidImport),
}
}
_ => Err(EvalError::InvalidImport),
}
let resolved = resolve_module(import)?;
let path = std::env::current_dir()
.map_err(|_| EvalError::OSError)?
.join(&resolved.filepath);
eval_use_chain(path, &resolved.ident, crawler, env.clone(), results.clone()).await
}
StmtKind::Crawl(lit) => {
let mut visited = HashSet::new();
Expand All @@ -286,6 +262,96 @@ fn eval_statement<'a>(
.boxed()
}

fn eval_use_chain<'a>(
path: PathBuf,
ident: &'a Identifier,
crawler: &'a fantoccini::Client,
env: EnvPointer,
results: ScrapeResultsPtr,
) -> BoxFuture<'a, EvalResult> {
async move {
if path.with_extension("sct").exists() {
let content =
fs::read_to_string(path.with_extension("sct")).map_err(|_| EvalError::OSError)?;
let lex = Lexer::new(&content);
let mut parser = Parser::new(lex);
match parser.parse_program() {
Ok(prgm) => {
let module_env = Arc::new(Mutex::new(Env::default()));
let _ = eval(
NodeKind::Program(prgm),
crawler,
module_env.clone(),
results.clone(),
)
.await?;
env.lock()
.await
.set(ident, Arc::new(Object::Module(module_env)))
.await;
Ok(Arc::new(Object::Null))
}
Err(_) => Err(EvalError::InvalidImport),
}
} else if path
.parent()
.ok_or(EvalError::InvalidImport)?
.with_extension("sct")
.exists()
{
// this is safe because of the if condition
let parent_module_path = path.parent().unwrap();
let parent_module = parent_module_path
.file_name()
.ok_or(EvalError::InvalidImport)?
.to_str()
.ok_or(EvalError::InvalidImport)?
.to_string();
let parent_ident = Identifier::new(parent_module);
let mb_obj = env.lock().await.get(&parent_ident).await;
match mb_obj {
Some(obj) => match &*obj {
// Parent module is already loaded, so simply get the object
// from the parent module env and put it into the current env
Object::Module(mod_env) => {
let final_name = path
.file_name()
.ok_or(EvalError::InvalidImport)?
.to_str()
.ok_or(EvalError::InvalidImport)?
.to_string();
let final_ident = Identifier::new(final_name);
let obj_exists = mod_env.lock().await.get(&final_ident).await;
if let Some(obj) = obj_exists {
env.lock().await.set(&final_ident, obj.clone()).await;
Ok(Arc::new(Object::Null))
} else {
Err(EvalError::InvalidImport)
}
}
_ => Err(EvalError::InvalidImport),
},
None => {
// load the parent module, then load the specific object
eval_use_chain(
parent_module_path.to_path_buf(),
&parent_ident,
crawler,
env.clone(),
results.clone(),
)
.await?;
eval_use_chain(path, ident, crawler, env.clone(), results.clone()).await?;
Ok(Arc::new(Object::Null))
}
}
} else {
Err(EvalError::InvalidImport)
}
}
.boxed()
}

fn eval_crawl<'a>(
lit: &'a CrawlLiteral,
crawler: &'a fantoccini::Client,
Expand Down Expand Up @@ -579,8 +645,15 @@ fn eval_expression<'a>(
}
ExprKind::Infix(lhs, op, rhs) => {
let l_obj = eval_expression(lhs, crawler, env.clone(), results.clone()).await?;
let r_obj = eval_expression(rhs, crawler, env.clone(), results.clone()).await?;
let res = eval_infix(l_obj.clone(), op, r_obj.clone())?;
let res = eval_infix(
l_obj.clone(),
op,
rhs,
crawler,
env.clone(),
results.clone(),
)
.await?;
Ok(res)
}
ExprKind::Boolean(val) => Ok(Arc::new(Object::Boolean(*val))),
Expand All @@ -604,7 +677,30 @@ fn eval_expression<'a>(
.boxed()
}

fn eval_infix(lhs: Arc<Object>, op: &TokenKind, rhs: Arc<Object>) -> EvalResult {
async fn eval_infix(
lhs: Arc<Object>,
op: &TokenKind,
rhs: &ExprKind,
crawler: &fantoccini::Client,
env: EnvPointer,
results: ScrapeResultsPtr,
) -> EvalResult {
match op {
TokenKind::DbColon => match &*lhs {
Object::Module(mod_env) => {
mod_env.lock().await.add_outer(env).await;
eval_expression(rhs, crawler, mod_env.clone(), results.clone()).await
}
_ => Err(EvalError::UnknownInfixOp),
},
_ => {
let rhs_obj = eval_expression(rhs, crawler, env.clone(), results.clone()).await?;
eval_infix_op(lhs, op, rhs_obj)
}
}
}

fn eval_infix_op(lhs: Arc<Object>, op: &TokenKind, rhs: Arc<Object>) -> EvalResult {
match op {
TokenKind::EQ => Ok(Arc::new(Object::Boolean(lhs == rhs))),
TokenKind::NEQ => Ok(Arc::new(Object::Boolean(lhs != rhs))),
Expand All @@ -623,14 +719,6 @@ fn eval_infix(lhs: Arc<Object>, op: &TokenKind, rhs: Arc<Object>) -> EvalResult
TokenKind::Or => Ok(Arc::new(Object::Boolean(
lhs.is_truthy() || rhs.is_truthy(),
))),
TokenKind::DbColon => eval_dbcolon_op(lhs, rhs),
_ => Err(EvalError::UnknownInfixOp),
}
}

fn eval_dbcolon_op(lhs: Arc<Object>, rhs: Arc<Object>) -> EvalResult {
println!("lhs {lhs:?}, rhs {rhs:?}");
match (&*lhs, &*rhs) {
_ => Err(EvalError::UnknownInfixOp),
}
}
Expand Down
14 changes: 0 additions & 14 deletions scout-lib/lib.sct → scout-lib/keys.sct
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,6 @@ F12 = "\u{e03c}"
META = "\u{e03d}"
COMMAND = "\u{e03d}"


// links returns a list of nodes
// that are anchor elements.
//
// Optionally provide a scope to limit
// the search space.
def links(scope = null) do
if scope do
return $$(scope)"a[href]" |> href()
end

$$"a[href]" |> href()
end

// Executes a key press action with
// the given key unicode value.
def press(code) do
Expand Down
12 changes: 12 additions & 0 deletions scout-lib/utils.sct
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// links returns a list of nodes
// that are anchor elements.
//
// Optionally provide a scope to limit
// the search space.
def links(scope = null) do
if scope do
return $$(scope)"a[href]" |> href()
end

$$"a[href]" |> href()
end
Loading

0 comments on commit 120c14c

Please sign in to comment.