Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions implants/lib/eldritch/eldritch-core/src/interpreter/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ impl Interpreter {
self.call_stack.clear();
self.current_func_name = "<module>".to_string();

if let Err(e) = exec::hoist_functions(self, &stmts) {
return Err(self.format_error(input, e));
}

for stmt in stmts {
match &stmt.kind {
// Special case: if top-level statement is an expression, return its value
Expand Down
40 changes: 40 additions & 0 deletions implants/lib/eldritch/eldritch-core/src/interpreter/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,46 @@ pub fn execute(interp: &mut Interpreter, stmt: &Stmt) -> Result<(), EldritchErro
Ok(())
}

pub fn hoist_functions(interp: &mut Interpreter, stmts: &[Stmt]) -> Result<(), EldritchError> {
// Collect functions to hoist so we don't hold read locks while evaluating default params
// Only hoist the *first* definition of a given name in this block, to allow forward references,
// while sequential execution will properly overwrite later definitions.
let mut to_hoist = Vec::new();
let mut seen = BTreeSet::new();

for stmt in stmts {
if let StmtKind::Def(name, params, _return_annotation, body) = &stmt.kind {
if !seen.contains(name) {
seen.insert(name.clone());
to_hoist.push((name, params, body));
}
}
}

for (name, params, body) in to_hoist {
let mut runtime_params = Vec::new();
for param in params {
match param {
Param::Normal(n, _) => runtime_params.push(RuntimeParam::Normal(n.clone())),
Param::Star(n, _) => runtime_params.push(RuntimeParam::Star(n.clone())),
Param::StarStar(n, _) => runtime_params.push(RuntimeParam::StarStar(n.clone())),
Param::WithDefault(n, _, _) => {
runtime_params.push(RuntimeParam::WithDefault(n.clone(), Value::None));
}
}
}

let func = Value::Function(Function {
name: name.clone(),
params: runtime_params,
body: body.clone(),
closure: interp.env.clone(),
});
interp.env.write().values.insert(name.clone(), func);
}
Ok(())
}

pub fn execute_stmts(interp: &mut Interpreter, stmts: &[Stmt]) -> Result<(), EldritchError> {
for stmt in stmts {
execute(interp, stmt)?;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use eldritch_core::Interpreter;

#[test]
fn test_forward_reference() {
let mut interp = Interpreter::new();
let res = interp.interpret(
r#"
def b():
a()

def a():
print("A")

b()
"#,
);
if let Err(e) = res {
panic!("Failed with: {}", e);
}
}

#[test]
fn test_redefine() {
let mut interp = Interpreter::new();
let res = interp.interpret(
r#"
def b():
return a()

def a():
return "A"

res1 = b()

def a():
return "A2"

res2 = b()
"#,
);
if let Err(e) = res {
panic!("Failed with: {}", e);
}
// Verify values
let env = interp.env.read();
let res1 = env.values.get("res1").unwrap();
let res2 = env.values.get("res2").unwrap();
assert_eq!(res1, &eldritch_core::Value::String("A".to_string()));
assert_eq!(res2, &eldritch_core::Value::String("A2".to_string()));
}
18 changes: 18 additions & 0 deletions implants/lib/eldritch/eldritch-core/tests/test_eval.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use eldritch_core::Interpreter;

#[test]
fn test_manual() {
let mut interp = Interpreter::new();
let res = interp.interpret(
"
def b():
a()

def a():
print(\"A\")

b()
",
);
println!("result is {:?}", res);
}
19 changes: 19 additions & 0 deletions implants/lib/eldritch/eldritch-core/tests/test_eval_2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use eldritch_core::Interpreter;

#[test]
fn test_manual() {
let mut interp = Interpreter::new();
let res = interp.interpret(
"
def b():
a()

def a():
print(\"A\")

b()
",
);
println!("result is {:?}", res);
assert!(res.is_ok());
}
14 changes: 14 additions & 0 deletions implants/lib/eldritch/eldritch-core/tests/test_eval_split.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use eldritch_core::Interpreter;

#[test]
fn test_manual() {
let mut interp = Interpreter::new();
let res = interp.interpret("def b():\n a()");
println!("def b: {:?}", res);

let res = interp.interpret("def a():\n print(\"A\")");
println!("def a: {:?}", res);

let res = interp.interpret("b()");
println!("result is {:?}", res);
}
19 changes: 19 additions & 0 deletions implants/lib/eldritch/eldritch-core/tests/test_hoist.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use eldritch_core::Interpreter;

#[test]
fn test_hoist() {
let mut interp = Interpreter::new();
let res = interp.interpret(
"
def b():
a()

b()

def a():
print(\"A\")
",
);
println!("result is {:?}", res);
assert!(res.is_ok());
}
21 changes: 21 additions & 0 deletions implants/lib/eldritch/eldritch-core/tests/test_nested.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use eldritch_core::Interpreter;

#[test]
fn test_nested() {
let mut interp = Interpreter::new();
let res = interp.interpret(
"
def c():
def b():
a()
def a():
print(\"A\")
b()

c()
",
);
if let Err(e) = res {
panic!("Failed with: {}", e);
}
}
Loading