Add type system and refator for having only epression
This commit is contained in:
Giulio Agostini
2026-07-06 10:43:17 +02:00
parent 9f15a00b98
commit 842216729b
23 changed files with 940 additions and 979 deletions
+43 -52
View File
@@ -17,13 +17,13 @@ use std::collections::HashMap;
use crate::{
common::{
ast::{AstNode, Expr, NodeId, Stmt},
ast::{AstNode, Expr, NodeId},
base_value::{BaseValue, LoxFunction},
lox_result::{ErrorSink, LoxError, LoxResult},
},
middleend::{
scope_stack::ScopeStack,
visit_ast::{walk_expr, walk_function, walk_stmt, Visitor},
visit_ast::{walk_expr, walk_function, Visitor},
},
};
@@ -55,13 +55,13 @@ impl Resolver {
/// Resolve a whole program, returning the per-reference scope distances or
/// the accumulated resolution errors.
pub fn resolve_program(
statements: &[AstNode<Stmt>],
statements: &[AstNode],
) -> Result<HashMap<NodeId, usize>, Vec<LoxError>> {
let mut resolver = Resolver::new();
for statement in statements {
// Visiting only fails for fatal/internal errors, which this pass
// never produces; user-facing diagnostics go to `errors`.
let _ = resolver.visit_stmt(statement);
let _ = resolver.visit_expr(statement);
}
if resolver.errors.has_errors() {
Err(resolver.errors.into_errors())
@@ -105,52 +105,7 @@ impl Default for Resolver {
}
impl Visitor for Resolver {
fn visit_stmt(&mut self, stmt: &AstNode<Stmt>) -> LoxResult<()> {
match &stmt.node {
Stmt::VarDeclaration {
name, initializer, ..
} => {
// A function declaration is a var bound to a function literal.
// Define its name *before* resolving the body so it can recurse;
// a plain variable is defined *after* its initializer so that
// `var a = a;` is caught.
let is_function = matches!(
initializer.as_deref().map(|node| &node.node),
Some(Expr::Literal {
value: BaseValue::Function(_)
})
);
self.declare(name, &stmt.source_slice);
if is_function {
self.define(name);
walk_stmt(self, stmt)?;
} else {
walk_stmt(self, stmt)?; // resolves the initializer, if any
self.define(name);
}
Ok(())
}
Stmt::Block { .. } => {
self.scopes.begin_scope();
walk_stmt(self, stmt)?;
self.scopes.end_scope();
Ok(())
}
Stmt::Return { .. } => {
if self.current_function == FunctionType::None {
self.errors.report(LoxError::ParseError {
source_slice: stmt.source_slice.clone(),
message: "Can't return from top-level code.".to_string(),
});
}
walk_stmt(self, stmt) // resolve the returned expression
}
// Expression, Print, If, While, For: default traversal.
_ => walk_stmt(self, stmt),
}
}
fn visit_expr(&mut self, expr: &AstNode<Expr>) -> LoxResult<()> {
fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> {
match &expr.node {
Expr::Identifier { name } => {
if self.scopes.get_local(name) == Some(&false) {
@@ -167,6 +122,42 @@ impl Visitor for Resolver {
self.resolve_local(expr.id, name);
Ok(())
}
Expr::VarDeclaration {
name, initializer, ..
} => {
// A function declaration is a var bound to a function literal.
// Define its name *before* resolving the body so it can recurse;
// a plain variable is defined *after* its initializer so that
// `var a = a;` is caught.
let is_function = matches!(
initializer.as_deref().map(|node| &node.node),
Some(Expr::Literal { value }) if matches!(**value, BaseValue::Function(_))
);
self.declare(name, &expr.source_slice);
if is_function {
self.define(name);
walk_expr(self, expr)?;
} else {
walk_expr(self, expr)?; // resolves the initializer, if any
self.define(name);
}
Ok(())
}
Expr::Block { .. } => {
self.scopes.begin_scope();
walk_expr(self, expr)?;
self.scopes.end_scope();
Ok(())
}
Expr::Return { .. } => {
if self.current_function == FunctionType::None {
self.errors.report(LoxError::ParseError {
source_slice: expr.source_slice.clone(),
message: "Can't return from top-level code.".to_string(),
});
}
walk_expr(self, expr) // resolve the returned expression
}
_ => walk_expr(self, expr),
}
}
@@ -193,7 +184,7 @@ mod tests {
use crate::frontend::lexer::Lexer;
use crate::frontend::parser::Parser;
fn parse(src: &str) -> Vec<AstNode<Stmt>> {
fn parse(src: &str) -> Vec<AstNode> {
let tokens = Lexer::new(src.to_string(), 0)
.scans_tokens()
.expect("source should lex");
@@ -251,7 +242,7 @@ mod tests {
#[test]
fn return_inside_a_function_is_allowed() {
let locals = resolve("f :: fn (n) do return n; end").expect("should resolve");
let locals = resolve("f :: fn (n): Number do return n; end;").expect("should resolve");
// `n` resolves from the body block up to the parameter scope.
assert_eq!(locals.len(), 1);
assert_eq!(*locals.values().next().unwrap(), 1);