Skip to content

Commit

Permalink
while loops
Browse files Browse the repository at this point in the history
  • Loading branch information
maxmindlin committed Jun 27, 2024
1 parent 9d6b09d commit 4fa9181
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 17 deletions.
31 changes: 21 additions & 10 deletions scout-interpreter/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ pub enum BuiltinKind {
Print,
TextContent,
Href,
Trim,
Click,
Results,
Len,
Expand All @@ -34,18 +33,20 @@ pub enum BuiltinKind {
Number,
Url,
Sleep,
IsWhitespace,
List,
}

impl BuiltinKind {
pub fn is_from(s: &str) -> Option<Self> {
use BuiltinKind::*;
match s {
"is_whitespace" => Some(IsWhitespace),
"url" => Some(Url),
"number" => Some(Number),
"args" => Some(Args),
"print" => Some(Print),
"textContent" => Some(TextContent),
"trim" => Some(Trim),
"href" => Some(Href),
"click" => Some(Click),
"results" => Some(Results),
Expand All @@ -55,6 +56,7 @@ impl BuiltinKind {
"type" => Some(Type),
"key_action" => Some(KeyPress),
"sleep" => Some(Sleep),
"list" => Some(List),
_ => None,
}
}
Expand All @@ -67,6 +69,23 @@ impl BuiltinKind {
) -> EvalResult {
use BuiltinKind::*;
match self {
List => {
assert_param_len!(args, 1);
if let Some(iterable) = args[0].into_iterable() {
Ok(Arc::new(Object::List(iterable.into_iter().collect())))
} else {
Err(EvalError::InvalidFnParams)
}
}
IsWhitespace => {
assert_param_len!(args, 1);
if let Object::Str(s) = &*args[0] {
let is_whitespace = s.chars().all(|c| c.is_whitespace());
Ok(Arc::new(Object::Boolean(is_whitespace)))
} else {
Err(EvalError::InvalidFnParams)
}
}
Sleep => {
assert_param_len!(args, 1);
if let Object::Number(ms) = &*args[0] {
Expand Down Expand Up @@ -145,14 +164,6 @@ impl BuiltinKind {
Err(EvalError::InvalidFnParams)
}
}
Trim => {
assert_param_len!(args, 1);
if let Object::Str(s) = &*args[0] {
Ok(Arc::new(Object::Str(s.trim().to_owned())))
} else {
Err(EvalError::InvalidFnParams)
}
}
Results => {
let json = results.lock().await.to_json();
println!("{}", json);
Expand Down
17 changes: 12 additions & 5 deletions scout-interpreter/src/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,18 @@ pub fn resolve_module(module: &ExprKind) -> Result<ResolvedMod, EvalError> {

fn resolve_std_file(ident: &Identifier) -> Result<PathBuf, EvalError> {
if *ident == Identifier::new("std".into()) {
let home = env::var("HOME").map_err(|_| EvalError::OSError)?;
let path = Path::new(&home)
.join("scout-lang")
.join("scout-lib")
.to_owned();
let scout_dir = match env::var("SCOUT_PATH") {
Ok(s) => Ok(Path::new(&s).to_path_buf()),
Err(_) => match env::var("HOME") {
Ok(s) => Ok(Path::new(&s).join("scout-lang")),
Err(_) => Err(EvalError::OSError),
},
}?;
// let root = env::var("SCOUT_PATH")
// .or(env::var("HOME"))
// .map_err(|_| EvalError::OSError)?;
// let home = env::var("HOME").map_err(|_| EvalError::OSError)?;
let path = scout_dir.join("scout-lib").to_owned();
Ok(path)
} else {
Ok(Path::new(&ident.name).to_owned())
Expand Down
14 changes: 14 additions & 0 deletions scout-interpreter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ pub enum EvalError {
InvalidUrl,
InvalidImport,
InvalidIndex,
IndexOutOfBounds,
NonFunction,
UnknownIdent,
UnknownPrefixOp,
Expand Down Expand Up @@ -190,6 +191,15 @@ fn eval_statement<'a>(
Err(EvalError::NonIterable)
}
}
StmtKind::WhileLoop(condition, block) => {
while eval_expression(condition, crawler, env.clone(), results.clone())
.await?
.is_truthy()
{
check_return_eval!(block, crawler, env.clone(), results.clone());
}
Ok(Arc::new(Object::Null))
}
StmtKind::Assign(ident, expr) => {
let val = eval_expression(expr, crawler, env.clone(), results.clone()).await?;
env.lock().await.set(ident, val).await;
Expand Down Expand Up @@ -774,6 +784,10 @@ fn eval_prefix(rhs: Arc<Object>, op: &TokenKind) -> EvalResult {
fn eval_index(lhs: Arc<Object>, idx: Arc<Object>) -> EvalResult {
match (&*lhs, &*idx) {
(Object::List(a), Object::Number(b)) => Ok(a[*b as usize].clone()),
(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),
},
_ => Err(EvalError::InvalidIndex),
}
}
Expand Down
2 changes: 2 additions & 0 deletions scout-lexer/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,14 @@ pub enum TokenKind {
Where,
And,
Or,
While,
}

impl TokenKind {
pub fn is_to_keyword(literal: &str) -> Option<Self> {
use TokenKind::*;
match literal {
"while" => Some(While),
"where" => Some(Where),
"for" => Some(For),
"in" => Some(In),
Expand Down
32 changes: 32 additions & 0 deletions scout-lib/str.sct
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
def ltrim(s) do
i = 0
while is_whitespace(s[i]) and i < len(s) do
i = i + 1
end

out = ""
while i < len(s) do
out = out + s[i]
i = i + 1
end
out
end

def rtrim(s) do
i = len(s) - 1
while is_whitespace(s[i]) and i > 0 do
i = i - 1
end

out = ""
j = 0
while j <= i do
out = out + s[j]
j = j + 1
end
out
end

def trim(s) do
s |> ltrim() |> rtrim()
end
1 change: 1 addition & 0 deletions scout-parser/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub enum StmtKind {
Crawl(CrawlLiteral),
Expr(ExprKind),
ForLoop(ForLoop),
WhileLoop(ExprKind, Block),
Func(FuncDef),
Goto(ExprKind),
IfElse(IfElseLiteral),
Expand Down
29 changes: 27 additions & 2 deletions scout-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ impl Parser {
TokenKind::Goto => self.parse_goto_stmt(),
TokenKind::Scrape => self.parse_scrape_stmt(),
TokenKind::For => self.parse_for_loop(),
TokenKind::While => self.parse_while_loop(),
TokenKind::Screenshot => self.parse_screenshot_stmt(),
TokenKind::If => self.parse_if_else(),
TokenKind::Ident => match self.peek.kind {
Expand Down Expand Up @@ -308,6 +309,15 @@ impl Parser {
Ok(StmtKind::TryCatch(try_b, catch_b))
}

fn parse_while_loop(&mut self) -> ParseResult<StmtKind> {
self.next_token();
let condition = self.parse_expr(Precedence::Lowest)?;
self.expect_peek(TokenKind::Do)?;
self.next_token();
let block = self.parse_block(vec![TokenKind::End])?;
Ok(StmtKind::WhileLoop(condition, block))
}

/// `for <ident> in <expr> do <block> end`
fn parse_for_loop(&mut self) -> ParseResult<StmtKind> {
self.expect_peek(TokenKind::Ident)?;
Expand Down Expand Up @@ -753,14 +763,14 @@ mod tests {
StmtKind::Crawl(CrawlLiteral::new(None, None, Block::default())); "empty crawl stmtddddd"
)]
#[test_case(
"crawl link, depth where true do end",
"crawl link, depth where depth < 1 do end",
StmtKind::Crawl(
CrawlLiteral::new(
Some(CrawlBindings {
link: Identifier::new("link".into()),
depth: Identifier::new("depth".into())
}),
Some(ExprKind::Boolean(true)),
Some(ExprKind::Infix(Box::new(ExprKind::Ident(Identifier::new("depth".into()))), TokenKind::LT, Box::new(ExprKind::Number(1.)))),
Block::default()
)
); "crawl stmt with bindings"
Expand All @@ -783,6 +793,21 @@ mod tests {
)
); "db colon"
)]
#[test_case(
"while a < 1 do end",
StmtKind::WhileLoop(
ExprKind::Infix(
Box::new(
ExprKind::Ident(Identifier::new("a".into()))
),
TokenKind::LT,
Box::new(
ExprKind::Number(1.)
)
),
Block::default(),
); "while loop"
)]
fn test_single_stmt(input: &str, exp: StmtKind) {
let stmt = extract_first_stmt(input);
assert_eq!(stmt, exp);
Expand Down

0 comments on commit 4fa9181

Please sign in to comment.