wip:
Add type system and refator for having only epression
This commit is contained in:
@@ -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);
|
||||
|
||||
+70
-90
@@ -23,7 +23,7 @@
|
||||
//! struct IdentifierCounter { count: usize }
|
||||
//!
|
||||
//! impl Visitor for IdentifierCounter {
|
||||
//! fn visit_expr(&mut self, expr: &AstNode<Expr>) -> LoxResult<()> {
|
||||
//! fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> {
|
||||
//! if let Expr::Identifier { .. } = &expr.node {
|
||||
//! self.count += 1;
|
||||
//! }
|
||||
@@ -33,7 +33,7 @@
|
||||
//! ```
|
||||
|
||||
use crate::common::{
|
||||
ast::{AstNode, Expr, Stmt},
|
||||
ast::{AstNode, Expr},
|
||||
base_value::{BaseValue, LoxFunction},
|
||||
lox_result::LoxResult,
|
||||
};
|
||||
@@ -43,13 +43,8 @@ use crate::common::{
|
||||
/// Every hook has a default implementation that performs the standard
|
||||
/// recursive traversal, so implementors only override the cases they need.
|
||||
pub trait Visitor: Sized {
|
||||
/// Visit a statement node. Defaults to [`walk_stmt`].
|
||||
fn visit_stmt(&mut self, stmt: &AstNode<Stmt>) -> LoxResult<()> {
|
||||
walk_stmt(self, stmt)
|
||||
}
|
||||
|
||||
/// Visit an expression node. Defaults to [`walk_expr`].
|
||||
fn visit_expr(&mut self, expr: &AstNode<Expr>) -> LoxResult<()> {
|
||||
fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> {
|
||||
walk_expr(self, expr)
|
||||
}
|
||||
|
||||
@@ -62,70 +57,13 @@ pub trait Visitor: Sized {
|
||||
}
|
||||
}
|
||||
|
||||
/// Recurse into the children of `stmt`, calling back into `visitor`.
|
||||
pub fn walk_stmt<V: Visitor>(visitor: &mut V, stmt: &AstNode<Stmt>) -> LoxResult<()> {
|
||||
match &stmt.node {
|
||||
Stmt::Expression { expression, .. } => visitor.visit_expr(expression),
|
||||
Stmt::Print { expression, .. } => visitor.visit_expr(expression),
|
||||
Stmt::VarDeclaration { initializer, .. } => {
|
||||
if let Some(initializer) = initializer {
|
||||
visitor.visit_expr(initializer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Stmt::Return { expression, .. } => visitor.visit_expr(expression),
|
||||
Stmt::Block { statements, .. } => {
|
||||
for statement in statements.iter() {
|
||||
visitor.visit_stmt(statement)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Stmt::If {
|
||||
condition,
|
||||
then_branch,
|
||||
elif_branch,
|
||||
else_branch,
|
||||
..
|
||||
} => {
|
||||
visitor.visit_expr(condition)?;
|
||||
visitor.visit_stmt(then_branch)?;
|
||||
for (elif_condition, elif_body) in elif_branch.iter() {
|
||||
visitor.visit_expr(elif_condition)?;
|
||||
visitor.visit_stmt(elif_body)?;
|
||||
}
|
||||
if let Some(else_body) = else_branch {
|
||||
visitor.visit_stmt(else_body)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Stmt::While {
|
||||
condition, body, ..
|
||||
} => {
|
||||
visitor.visit_expr(condition)?;
|
||||
visitor.visit_stmt(body)
|
||||
}
|
||||
Stmt::For {
|
||||
variable,
|
||||
condition,
|
||||
increment,
|
||||
body,
|
||||
..
|
||||
} => {
|
||||
visitor.visit_stmt(variable)?;
|
||||
visitor.visit_expr(condition)?;
|
||||
visitor.visit_stmt(increment)?;
|
||||
visitor.visit_stmt(body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Recurse into the children of `expr`, calling back into `visitor`.
|
||||
pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &AstNode<Expr>) -> LoxResult<()> {
|
||||
pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &AstNode) -> LoxResult<()> {
|
||||
match &expr.node {
|
||||
// A function literal carries an entire sub-tree (its body), so it is
|
||||
// not a leaf: hand it to the dedicated function hook.
|
||||
Expr::Literal { value } => {
|
||||
if let BaseValue::Function(function) = value {
|
||||
if let BaseValue::Function(function) = &**value {
|
||||
visitor.visit_function(function)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -147,6 +85,57 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &AstNode<Expr>) -> LoxResult
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Expr::Print { expression, .. } => visitor.visit_expr(expression),
|
||||
Expr::VarDeclaration { initializer, .. } => {
|
||||
if let Some(initializer) = initializer {
|
||||
visitor.visit_expr(initializer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Expr::Return { expression, .. } => visitor.visit_expr(expression),
|
||||
Expr::Block { statements, .. } => {
|
||||
for statement in statements.iter() {
|
||||
visitor.visit_expr(statement)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Expr::If {
|
||||
condition,
|
||||
then_branch,
|
||||
elif_branches,
|
||||
else_branch,
|
||||
..
|
||||
} => {
|
||||
visitor.visit_expr(condition)?;
|
||||
visitor.visit_expr(then_branch)?;
|
||||
for (elif_condition, elif_body) in elif_branches.iter() {
|
||||
visitor.visit_expr(elif_condition)?;
|
||||
visitor.visit_expr(elif_body)?;
|
||||
}
|
||||
if let Some(else_body) = else_branch {
|
||||
visitor.visit_expr(else_body)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Expr::While {
|
||||
condition, body, ..
|
||||
} => {
|
||||
visitor.visit_expr(condition)?;
|
||||
visitor.visit_expr(body)
|
||||
}
|
||||
Expr::For {
|
||||
variable,
|
||||
condition,
|
||||
increment,
|
||||
body,
|
||||
..
|
||||
} => {
|
||||
visitor.visit_expr(variable)?;
|
||||
visitor.visit_expr(condition)?;
|
||||
visitor.visit_expr(increment)?;
|
||||
visitor.visit_expr(body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +144,7 @@ pub fn walk_function<V: Visitor>(visitor: &mut V, function: &LoxFunction) -> Lox
|
||||
if let Some(guard) = &function.guard {
|
||||
visitor.visit_expr(guard)?;
|
||||
}
|
||||
visitor.visit_stmt(&function.body)
|
||||
visitor.visit_expr(&function.body)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -165,7 +154,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");
|
||||
@@ -173,29 +162,23 @@ mod tests {
|
||||
}
|
||||
|
||||
/// A visitor that relies entirely on the default traversal and just counts
|
||||
/// how many statement and expression nodes it sees.
|
||||
/// how many nodes it sees.
|
||||
#[derive(Default)]
|
||||
struct Counter {
|
||||
stmts: usize,
|
||||
exprs: usize,
|
||||
nodes: usize,
|
||||
}
|
||||
|
||||
impl Visitor for Counter {
|
||||
fn visit_stmt(&mut self, stmt: &AstNode<Stmt>) -> LoxResult<()> {
|
||||
self.stmts += 1;
|
||||
walk_stmt(self, stmt)
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &AstNode<Expr>) -> LoxResult<()> {
|
||||
self.exprs += 1;
|
||||
walk_expr(self, expr)
|
||||
fn visit_expr(&mut self, node: &AstNode) -> LoxResult<()> {
|
||||
self.nodes += 1;
|
||||
walk_expr(self, node)
|
||||
}
|
||||
}
|
||||
|
||||
fn count(src: &str) -> Counter {
|
||||
let mut counter = Counter::default();
|
||||
for stmt in parse(src).iter() {
|
||||
counter.visit_stmt(stmt).unwrap();
|
||||
counter.visit_expr(stmt).unwrap();
|
||||
}
|
||||
counter
|
||||
}
|
||||
@@ -204,19 +187,16 @@ mod tests {
|
||||
fn counts_every_node_via_default_traversal() {
|
||||
// 1 + 2 * 3 => Binary(+){ Literal, Binary(*){ Literal, Literal } }
|
||||
let counter = count("1 + 2 * 3;");
|
||||
assert_eq!(counter.stmts, 1);
|
||||
assert_eq!(counter.exprs, 5);
|
||||
assert_eq!(counter.nodes, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn descends_into_function_bodies() {
|
||||
// The function body must be traversed through `visit_function`, so the
|
||||
// `return a;` inside it should contribute to the counts.
|
||||
let counter = count("f :: fn (a) do return a; end");
|
||||
// VarDeclaration + Block + Return
|
||||
assert_eq!(counter.stmts, 3);
|
||||
// Function literal + identifier `a`
|
||||
assert_eq!(counter.exprs, 2);
|
||||
let counter = count("f :: fn (a): Number do return a; end;");
|
||||
// VarDeclaration + Function literal + Block + Return + identifier `a`
|
||||
assert_eq!(counter.nodes, 5);
|
||||
}
|
||||
|
||||
/// A visitor that aborts as soon as it sees an identifier, used to check
|
||||
@@ -224,7 +204,7 @@ mod tests {
|
||||
struct FailOnIdentifier;
|
||||
|
||||
impl Visitor for FailOnIdentifier {
|
||||
fn visit_expr(&mut self, expr: &AstNode<Expr>) -> LoxResult<()> {
|
||||
fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> {
|
||||
if let Expr::Identifier { .. } = &expr.node {
|
||||
return runtime_error(expr.source_slice.clone(), "found an identifier");
|
||||
}
|
||||
@@ -238,7 +218,7 @@ mod tests {
|
||||
let mut visitor = FailOnIdentifier;
|
||||
let mut result = Ok(());
|
||||
for stmt in stmts.iter() {
|
||||
result = visitor.visit_stmt(stmt);
|
||||
result = visitor.visit_expr(stmt);
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user