fix function scoping bug

This commit is contained in:
Scott Richmond 2025-06-20 15:35:09 -04:00
parent a2ae53f8e4
commit 121861cc8e
4 changed files with 84 additions and 30 deletions

View File

@ -1,10 +1,10 @@
& this file, uniquely, gets `base` loaded as context. See src/base.janet for exports
let base = base
& let base = base
& some forward declarations
& TODO: fix this so that we don't need (as many of) them
fn and
& fn and
fn append
fn apply_command
fn assoc
@ -738,22 +738,22 @@ fn keyword? {
& TODO: determine if Ludus should have a `keyword` function that takes a string and returns a keyword. Too many panics, it has weird memory consequences, etc.
& TODO: make `and` and `or` special forms which lazily evaluate arguments
fn and {
"Returns true if all values passed in are truthy. Note that this does not short-circuit: all arguments are evaulated before they are passed in."
() -> true
(x) -> bool (x)
(x, y) -> base :and (x, y)
(x, y, ...zs) -> fold (and, zs, base :and (x, y))
}
& & TODO: make `and` and `or` special forms which lazily evaluate arguments
& fn and {
& "Returns true if all values passed in are truthy. Note that this does not short-circuit: all arguments are evaulated before they are passed in."
& () -> true
& (x) -> bool (x)
& (x, y) -> base :and (x, y)
& (x, y, ...zs) -> fold (and, zs, base :and (x, y))
& }
fn or {
"Returns true if any value passed in is truthy. Note that this does not short-circuit: all arguments are evaluated before they are passed in."
() -> true
(x) -> bool (x)
(x, y) -> base :or (x, y)
(x, y, ...zs) -> fold (or, zs, base :or (x, y))
}
& fn or {
& "Returns true if any value passed in is truthy. Note that this does not short-circuit: all arguments are evaluated before they are passed in."
& () -> true
& (x) -> bool (x)
& (x, y) -> base :or (x, y)
& (x, y, ...zs) -> fold (or, zs, base :or (x, y))
& }
fn assoc {
"Takes a dict, key, and value, and returns a new dict with the key set to value."
@ -1308,7 +1308,7 @@ box state = nil
#{
abs & math
add & math
and & bool
& and & bool
angle & math
any? & dicts lists strings sets tuples
append & lists sets
@ -1402,7 +1402,7 @@ box state = nil
ok & results
ok? & results
& omit & set
or & bool
& or & bool
ordered? & lists tuples strings
pc! & turtles
pd! & turtles

View File

@ -332,4 +332,23 @@ And then: quality of life improvements:
* [ ] that suggests that we need a mapping from bytecodes to AST nodes
* The way I had been planning on doing this is having a vec that moves in lockstep with bytecode that's just references to ast nodes, which are `'static`, so that shouldn't be too bad. But this is per-chunk, which means we need a reference to that vec in the VM. My sense is that what we want is actually a separate data structure that holds the AST nodes--we'll only need them in the sad path, which can be slow.
### Bugs discovered while trying to compile prelude
#### 2025-06-20
Consider the following code:
```
fn one {
(x as :number) -> {
fn two () -> :number
two
}
(x as :bool) -> {
fn two () -> :bool
two
}
}
```
The second clause causes a panic in the compiler.
I'm not entirely sure what's going on.
That said, I'm pretty sure the root of it is that the fact that `two` was already bound in the first clause means that it's in the constants vector in that chunk under the name "two."

View File

@ -531,7 +531,10 @@ impl<'a> Compiler<'a> {
name,
stack_pos: binding.stack_pos,
},
None => self.enclosing.unwrap().get_upvalue(name),
None => {
println!("Getting upvalue {name}");
self.enclosing.unwrap().get_upvalue(name)
}
}
}
@ -1244,7 +1247,13 @@ impl<'a> Compiler<'a> {
if !is_anon {
let declared = self.chunk.constants.iter().any(|val| match val {
Value::Fn(lfn) => lfn.name() == *name,
Value::Fn(lfn) => {
if matches!(lfn.as_ref(), LFn::Declared { .. }) {
lfn.name() == *name
} else {
false
}
}
_ => false,
});
if !declared {
@ -1394,7 +1403,13 @@ impl<'a> Compiler<'a> {
.constants
.iter()
.position(|val| match val {
Value::Fn(declaration) => declaration.name() == *name,
Value::Fn(lfn) => {
if matches!(lfn.as_ref(), LFn::Declared { .. }) {
lfn.name() == *name
} else {
false
}
}
_ => false,
})
.unwrap();

View File

@ -27,16 +27,21 @@ use value::Value;
mod vm;
use vm::Vm;
const PRELUDE: &str = include_str!("../assets/test_prelude.ld");
const PRELUDE: &str = include_str!("../assets/prelude.ld");
pub fn prelude() -> HashMap<&'static str, Value> {
let tokens = lexer().parse(PRELUDE).into_output_errors().0.unwrap();
let parsed = parser()
let (parsed, parse_errors) = parser()
.parse(Stream::from_iter(tokens).map((0..PRELUDE.len()).into(), |(t, s)| (t, s)))
.into_output_errors()
.0
.unwrap();
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parsed));
.into_output_errors();
if !parse_errors.is_empty() {
println!("ERROR PARSING PRELUDE:");
println!("{:?}", parse_errors);
panic!();
}
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parsed.unwrap()));
let mut compiler = Compiler::new(parsed, "prelude", PRELUDE, None, HashMap::new());
let base = base::make_base();
compiler.emit_constant(base);
@ -74,9 +79,13 @@ pub fn run(src: &'static str) {
// in any event, the AST should live forever
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parse_result.unwrap()));
let prelude = prelude();
// let prelude = prelude();
let prelude = imbl::HashMap::new();
let mut compiler = Compiler::new(parsed, "test", src, None, prelude);
// let base = base::make_base();
// compiler.emit_constant(base);
// compiler.bind("base");
compiler.compile();
if DEBUG_COMPILE {
@ -105,7 +114,18 @@ pub fn run(src: &'static str) {
pub fn main() {
env::set_var("RUST_BACKTRACE", "1");
let src = "
panics! ()
fn one {
(x as :number) -> {
fn two () -> :number
two
}
(x as :bool) -> {
fn two () -> :bool
two
}
}
one (true) ()
";
run(src);
}