Add block scoping and statement/expression distinction
Implement strict block scoping and distinguish between statements and expressions. This change introduces hierarchical scopes for functions and blocks, enforces that `def` is a statement with no return value, and prevents statements from appearing in expression positions or as the last element of a block. A new example `design-flaw.myc` is added to demonstrate the uninitialized variable issue solved by these changes.
This commit is contained in:
@@ -0,0 +1,45 @@
|
|||||||
|
# Development Log: Binder Refactoring & Block Scoping
|
||||||
|
|
||||||
|
## Status Quo
|
||||||
|
- Myc currently uses a flat scope per function.
|
||||||
|
- `def` is treated as a side-effect expression that "leaks" into the function scope regardless of nesting (e.g., inside `if` or `do`).
|
||||||
|
- This leads to uninitialized variables at runtime if the definition is skipped by control flow.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Implement strict **Block Scoping** and distinguish between **Statements** and **Expressions** to eliminate undefined variable states.
|
||||||
|
|
||||||
|
### Rules
|
||||||
|
1. **Block Scoping:** Every `(do ...)` and `if` branch creates a new lexical scope.
|
||||||
|
2. **Statements vs. Expressions:**
|
||||||
|
- `def` is a **Statement**.
|
||||||
|
- Statements have no return value (Void).
|
||||||
|
- Statements **cannot** be used as expressions (e.g., as arguments, RHS of assignments, or conditions).
|
||||||
|
- Statements **cannot** be the last element of a block (since the block's value is determined by its last expression).
|
||||||
|
3. **No Shadowing:** Redefining a symbol within the **same** scope level is an error. Shadowing from outer scopes is allowed (standard lexical scoping).
|
||||||
|
4. **Self-Reference:** A variable is only available in its scope **after** its definition is complete (RHS of `def` sees outer scope).
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
### Phase 1: Binder Infrastructure (In Progress)
|
||||||
|
1. [ ] Update `CompilerScope` to support hierarchical nesting via a stack in `FunctionCompiler`.
|
||||||
|
2. [ ] Implement `push_scope` and `pop_scope` in `Binder`.
|
||||||
|
3. [ ] Refactor `resolve_variable` to traverse the scope stack (inner-to-outer).
|
||||||
|
4. [ ] Update `define_variable` to enforce "No Shadowing" at the current scope level.
|
||||||
|
|
||||||
|
### Phase 2: Statement/Expression Validation
|
||||||
|
1. [ ] Introduce a mechanism to track "Context" (Expression vs. Statement) during binding.
|
||||||
|
2. [ ] Validate that `def` is only used in statement positions.
|
||||||
|
3. [ ] Enforce that the last node in a `BoundKind::Block` is an expression.
|
||||||
|
|
||||||
|
### Phase 3: VM Compatibility
|
||||||
|
1. [ ] Ensure the VM handles "Void" results from statements correctly.
|
||||||
|
2. [ ] (Optional) Optimize stack allocation based on scope depth.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Log Entries
|
||||||
|
|
||||||
|
### 2024-05-22: Initialization
|
||||||
|
- Analyzed `design-flaw.myc`.
|
||||||
|
- Defined the "Statement" concept to solve the uninitialized variable problem.
|
||||||
|
- Initialized this log.
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
(do
|
(do
|
||||||
(do
|
(do
|
||||||
(def x "hh")
|
(if false (def x "hh"))
|
||||||
)
|
)
|
||||||
(print x)
|
(print x)
|
||||||
)
|
)
|
||||||
|
|||||||
+49
-32
@@ -21,40 +21,14 @@ struct LocalInfo {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct CompilerScope {
|
struct CompilerScope {
|
||||||
locals: HashMap<Symbol, LocalInfo>,
|
locals: HashMap<Symbol, LocalInfo>,
|
||||||
slot_count: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompilerScope {
|
impl CompilerScope {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
locals: HashMap::new(),
|
locals: HashMap::new(),
|
||||||
slot_count: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn define(&mut self, sym: &Symbol, identity: Identity) -> Result<LocalSlot, String> {
|
|
||||||
if self.locals.contains_key(sym) {
|
|
||||||
return Err(format!(
|
|
||||||
"Variable '{}' is already defined in this scope level.",
|
|
||||||
sym.name
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let slot = LocalSlot(self.slot_count);
|
|
||||||
self.locals.insert(
|
|
||||||
sym.clone(),
|
|
||||||
LocalInfo {
|
|
||||||
slot,
|
|
||||||
identity,
|
|
||||||
_ty: StaticType::Any,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
self.slot_count += 1;
|
|
||||||
Ok(slot)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve(&self, sym: &Symbol) -> Option<LocalInfo> {
|
|
||||||
self.locals.get(sym).cloned()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -65,7 +39,8 @@ enum ScopeKind {
|
|||||||
|
|
||||||
struct FunctionCompiler {
|
struct FunctionCompiler {
|
||||||
identity: Identity,
|
identity: Identity,
|
||||||
scope: CompilerScope,
|
scopes: Vec<CompilerScope>,
|
||||||
|
slot_count: u32,
|
||||||
upvalues: Vec<Address>,
|
upvalues: Vec<Address>,
|
||||||
kind: ScopeKind,
|
kind: ScopeKind,
|
||||||
}
|
}
|
||||||
@@ -74,12 +49,21 @@ impl FunctionCompiler {
|
|||||||
fn new(kind: ScopeKind, identity: Identity) -> Self {
|
fn new(kind: ScopeKind, identity: Identity) -> Self {
|
||||||
Self {
|
Self {
|
||||||
identity,
|
identity,
|
||||||
scope: CompilerScope::new(),
|
scopes: vec![CompilerScope::new()],
|
||||||
|
slot_count: 0,
|
||||||
upvalues: Vec::new(),
|
upvalues: Vec::new(),
|
||||||
kind,
|
kind,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn push_scope(&mut self) {
|
||||||
|
self.scopes.push(CompilerScope::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_scope(&mut self) {
|
||||||
|
self.scopes.pop();
|
||||||
|
}
|
||||||
|
|
||||||
fn define_variable(
|
fn define_variable(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: &Symbol,
|
name: &Symbol,
|
||||||
@@ -98,14 +82,39 @@ impl FunctionCompiler {
|
|||||||
let idx = GlobalIdx(globals_map.len() as u32);
|
let idx = GlobalIdx(globals_map.len() as u32);
|
||||||
globals_map.insert(name.clone(), (idx, identity));
|
globals_map.insert(name.clone(), (idx, identity));
|
||||||
Ok(Address::Global(idx))
|
Ok(Address::Global(idx))
|
||||||
}
|
}
|
||||||
ScopeKind::Local => {
|
ScopeKind::Local => {
|
||||||
let slot = self.scope.define(name, identity)?;
|
let current_scope = self.scopes.last_mut().unwrap();
|
||||||
|
if current_scope.locals.contains_key(name) {
|
||||||
|
return Err(format!(
|
||||||
|
"Variable '{}' is already defined in this scope level.",
|
||||||
|
name.name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let slot = LocalSlot(self.slot_count);
|
||||||
|
current_scope.locals.insert(
|
||||||
|
name.clone(),
|
||||||
|
LocalInfo {
|
||||||
|
slot,
|
||||||
|
identity,
|
||||||
|
_ty: StaticType::Any,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.slot_count += 1;
|
||||||
Ok(Address::Local(slot))
|
Ok(Address::Local(slot))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_local(&self, sym: &Symbol) -> Option<LocalInfo> {
|
||||||
|
for scope in self.scopes.iter().rev() {
|
||||||
|
if let Some(info) = scope.locals.get(sym) {
|
||||||
|
return Some(info.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn add_upvalue(&mut self, addr: Address) -> UpvalueIdx {
|
fn add_upvalue(&mut self, addr: Address) -> UpvalueIdx {
|
||||||
if let Some(idx) = self.upvalues.iter().position(|&a| a == addr) {
|
if let Some(idx) = self.upvalues.iter().position(|&a| a == addr) {
|
||||||
return UpvalueIdx(idx as u32);
|
return UpvalueIdx(idx as u32);
|
||||||
@@ -207,10 +216,16 @@ impl Binder {
|
|||||||
else_br,
|
else_br,
|
||||||
} => {
|
} => {
|
||||||
let cond = self.bind(cond, diag);
|
let cond = self.bind(cond, diag);
|
||||||
|
|
||||||
|
self.functions.last_mut().unwrap().push_scope();
|
||||||
let then_br = self.bind(then_br, diag);
|
let then_br = self.bind(then_br, diag);
|
||||||
|
self.functions.last_mut().unwrap().pop_scope();
|
||||||
|
|
||||||
let mut else_br_bound = None;
|
let mut else_br_bound = None;
|
||||||
if let Some(e) = else_br {
|
if let Some(e) = else_br {
|
||||||
|
self.functions.last_mut().unwrap().push_scope();
|
||||||
else_br_bound = Some(Rc::new(self.bind(e, diag)));
|
else_br_bound = Some(Rc::new(self.bind(e, diag)));
|
||||||
|
self.functions.last_mut().unwrap().pop_scope();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.make_node(
|
self.make_node(
|
||||||
@@ -384,10 +399,12 @@ impl Binder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UntypedKind::Block { exprs } => {
|
UntypedKind::Block { exprs } => {
|
||||||
|
self.functions.last_mut().unwrap().push_scope();
|
||||||
let mut bound_exprs = Vec::new();
|
let mut bound_exprs = Vec::new();
|
||||||
for expr in exprs {
|
for expr in exprs {
|
||||||
bound_exprs.push(Rc::new(self.bind(expr, diag)));
|
bound_exprs.push(Rc::new(self.bind(expr, diag)));
|
||||||
}
|
}
|
||||||
|
self.functions.last_mut().unwrap().pop_scope();
|
||||||
self.make_node(
|
self.make_node(
|
||||||
node.identity.clone(),
|
node.identity.clone(),
|
||||||
BoundKind::Block { exprs: bound_exprs },
|
BoundKind::Block { exprs: bound_exprs },
|
||||||
@@ -485,13 +502,13 @@ impl Binder {
|
|||||||
let current_fn_idx = self.functions.len() - 1;
|
let current_fn_idx = self.functions.len() - 1;
|
||||||
|
|
||||||
// 1. Try local in current function
|
// 1. Try local in current function
|
||||||
if let Some(info) = self.functions[current_fn_idx].scope.resolve(sym) {
|
if let Some(info) = self.functions[current_fn_idx].resolve_local(sym) {
|
||||||
return Some(Address::Local(info.slot));
|
return Some(Address::Local(info.slot));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Try enclosing scopes (capture chain)
|
// 2. Try enclosing scopes (capture chain)
|
||||||
for i in (0..current_fn_idx).rev() {
|
for i in (0..current_fn_idx).rev() {
|
||||||
if let Some(info) = self.functions[i].scope.resolve(sym) {
|
if let Some(info) = self.functions[i].resolve_local(sym) {
|
||||||
let mut addr = Address::Local(info.slot);
|
let mut addr = Address::Local(info.slot);
|
||||||
|
|
||||||
// Record the capture for each lambda level in between
|
// Record the capture for each lambda level in between
|
||||||
|
|||||||
Reference in New Issue
Block a user