mirror of
https://github.com/MorizzG/rlox.git
synced 2025-12-06 04:12:42 +00:00
Lox Interpreter done (Chapter 13)
This commit is contained in:
parent
10540708d4
commit
719a014977
16 changed files with 707 additions and 196 deletions
11
GRAMMAR
11
GRAMMAR
|
|
@ -23,25 +23,26 @@ declaration -> var_decl | fun_decl | class_decl ;
|
||||||
|
|
||||||
var_decl -> "var" IDENTIFIER ( "=" expression )? ";"
|
var_decl -> "var" IDENTIFIER ( "=" expression )? ";"
|
||||||
fun_decl -> "fun" IDENTIFIER "(" parameters ")" block ;
|
fun_decl -> "fun" IDENTIFIER "(" parameters ")" block ;
|
||||||
class_decl -> "class" IDENTIFIER "{" method* "}" ;
|
class_decl -> "class" IDENTIFIER ( "<" IDENTIFIER ) "{" method* "}" ;
|
||||||
|
|
||||||
method -> IDENTIFIER "(" parameters ")" block ;
|
method -> IDENTIFIER "(" parameters ")" block ;
|
||||||
|
|
||||||
expression -> assignment
|
expression -> assignment
|
||||||
|
|
||||||
assignment -> IDENTIFIER "=" assignment | logic_or ;
|
assignment -> ( call_or_get "." )? IDENTIFIER "=" assignment | logic_or ;
|
||||||
logic_or -> logic_and ( "or" logic_and )* ;
|
logic_or -> logic_and ( "or" logic_and )* ;
|
||||||
logic_and -> equality ( "and" equality )* ;
|
logic_and -> equality ( "and" equality )* ;
|
||||||
equality -> comparison ( ( "==" | "!=" ) comparison )* ;
|
equality -> comparison ( ( "==" | "!=" ) comparison )* ;
|
||||||
comparison -> term ( ">" | ">=" | "<" | "<=" term )* ;
|
comparison -> term ( ">" | ">=" | "<" | "<=" term )* ;
|
||||||
term -> factor ( ( "+" | "-" ) factor )*
|
term -> factor ( ( "+" | "-" ) factor )*
|
||||||
factor -> unary ( ( "*" | "/" ) unary )* ;
|
factor -> unary ( ( "*" | "/" ) unary )* ;
|
||||||
unary -> ( "!" | "-" ) unary | call ;
|
unary -> ( "!" | "-" ) unary | call_or_get ;
|
||||||
|
|
||||||
call -> primary ( "(" arguments? ")" )* ;
|
call_or_get -> primary ( "(" arguments? ")" | "." IDENTIFIER )* ;
|
||||||
arguments -> expression ( "," expression )* ;
|
arguments -> expression ( "," expression )* ;
|
||||||
|
|
||||||
primary -> "(" expression ")" | IDENTIFIER | lambda | NUMBER | STRING | "true" | "false" | "nil" ;
|
primary -> "(" expression ")" | IDENTIFIER | lambda | NUMBER | STRING | "true" | "false" | "nil"
|
||||||
|
| "this" | "super" "." IDENTIFIER ;
|
||||||
|
|
||||||
lambda -> "fun" "(" parameters ")" block ;
|
lambda -> "fun" "(" parameters ")" block ;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,13 @@ pub enum ParserError {
|
||||||
#[error("Function declaration at {code_pos} has too many parameters")]
|
#[error("Function declaration at {code_pos} has too many parameters")]
|
||||||
TooManyParams { code_pos: CodePos },
|
TooManyParams { code_pos: CodePos },
|
||||||
#[error("Return statement outside of function definition")]
|
#[error("Return statement outside of function definition")]
|
||||||
InvalidReturn { code_pos: CodePos },
|
ReturnOutsideFunction { code_pos: CodePos },
|
||||||
#[error("Break statement outside of loop")]
|
#[error("Break statement outside of loop")]
|
||||||
InvalidBreak { code_pos: CodePos },
|
InvalidBreak { code_pos: CodePos },
|
||||||
#[error("Missing class body at {code_pos}")]
|
#[error("Missing class body at {code_pos}")]
|
||||||
MissingClassBody { code_pos: CodePos },
|
MissingClassBody { code_pos: CodePos },
|
||||||
|
#[error("Return statement in init")]
|
||||||
|
ReturnInInit { code_pos: CodePos },
|
||||||
|
#[error("Missing method name after super")]
|
||||||
|
MissingMethodAfterSuper { code_pos: CodePos },
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,12 +45,32 @@ pub enum Expr {
|
||||||
callee: Box<Expr>,
|
callee: Box<Expr>,
|
||||||
args: Vec<Expr>,
|
args: Vec<Expr>,
|
||||||
},
|
},
|
||||||
Function {
|
Get {
|
||||||
|
target: Box<Expr>,
|
||||||
name: String,
|
name: String,
|
||||||
param_names: Vec<String>,
|
},
|
||||||
closure_vars: Vec<(String, usize)>,
|
Set {
|
||||||
|
target: Box<Expr>,
|
||||||
|
name: String,
|
||||||
|
value: Box<Expr>,
|
||||||
|
},
|
||||||
|
Function {
|
||||||
|
name: Box<String>,
|
||||||
|
param_names: Box<Vec<String>>,
|
||||||
|
closure_vars: Box<Vec<(String, usize)>>,
|
||||||
body: Box<Stmt>,
|
body: Box<Stmt>,
|
||||||
},
|
},
|
||||||
|
Class {
|
||||||
|
superclass: Option<Box<Expr>>,
|
||||||
|
name: String,
|
||||||
|
methods: Box<Vec<Expr>>,
|
||||||
|
},
|
||||||
|
This,
|
||||||
|
Super {
|
||||||
|
super_var: Box<Expr>,
|
||||||
|
this_var: Box<Expr>,
|
||||||
|
method: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Expr {
|
impl Expr {
|
||||||
|
|
@ -110,15 +130,36 @@ impl Expr {
|
||||||
Expr::Call { callee, args }
|
Expr::Call { callee, args }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get(target: Expr, name: impl Into<String>) -> Self {
|
||||||
|
let target = Box::new(target);
|
||||||
|
let name = name.into();
|
||||||
|
Expr::Get { target, name }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn function(name: String, param_names: Vec<String>, body: Stmt) -> Self {
|
pub fn function(name: String, param_names: Vec<String>, body: Stmt) -> Self {
|
||||||
|
let name = Box::new(name);
|
||||||
|
let param_names = Box::new(param_names);
|
||||||
|
#[allow(clippy::box_default)]
|
||||||
|
let closure_vars = Box::new(Vec::new());
|
||||||
let body = Box::new(body);
|
let body = Box::new(body);
|
||||||
Self::Function {
|
Self::Function {
|
||||||
name,
|
name,
|
||||||
param_names,
|
param_names,
|
||||||
closure_vars: Vec::new(),
|
closure_vars,
|
||||||
body,
|
body,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn class(name: impl Into<String>, methods: Vec<Expr>, superclass: Option<Expr>) -> Self {
|
||||||
|
let superclass = superclass.map(Box::new);
|
||||||
|
let name = name.into();
|
||||||
|
let methods = Box::new(methods);
|
||||||
|
Expr::Class {
|
||||||
|
superclass,
|
||||||
|
name,
|
||||||
|
methods,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Expr {
|
impl Display for Expr {
|
||||||
|
|
@ -136,6 +177,8 @@ impl Display for Expr {
|
||||||
Expr::GlobalVariable { name } => write!(f, "(var {name} global)"),
|
Expr::GlobalVariable { name } => write!(f, "(var {name} global)"),
|
||||||
Expr::Assignment { target, value } => write!(f, "{target} = {value}"),
|
Expr::Assignment { target, value } => write!(f, "{target} = {value}"),
|
||||||
Expr::Call { callee, args } => write!(f, "({callee} {})", args.iter().map(|arg| arg.to_string()).join(" ")),
|
Expr::Call { callee, args } => write!(f, "({callee} {})", args.iter().map(|arg| arg.to_string()).join(" ")),
|
||||||
|
Expr::Get { target, name } => write!(f, "(get {name} {target})"),
|
||||||
|
Expr::Set { target, name, value } => write!(f, "(set {name} {target} {value})"),
|
||||||
Expr::Function {
|
Expr::Function {
|
||||||
name,
|
name,
|
||||||
param_names,
|
param_names,
|
||||||
|
|
@ -145,11 +188,32 @@ impl Display for Expr {
|
||||||
if !closure_vars.is_empty() {
|
if !closure_vars.is_empty() {
|
||||||
let closure_fmt = closure_vars.iter().map(|(name, _level)| name).join(", ");
|
let closure_fmt = closure_vars.iter().map(|(name, _level)| name).join(", ");
|
||||||
|
|
||||||
write!(f, "fun [{closure_fmt}] {name}({}) {body}", param_names.join(", "))
|
write!(f, "fun [{closure_fmt}] {name}({}) => {body}", param_names.join(", "))
|
||||||
} else {
|
} else {
|
||||||
write!(f, "fun {name}({}) {body}", param_names.join(", "))
|
write!(f, "fun {name}({}) => {body}", param_names.join(", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Expr::Class {
|
||||||
|
superclass,
|
||||||
|
name,
|
||||||
|
methods,
|
||||||
|
} => {
|
||||||
|
if let Some(superclass) = superclass {
|
||||||
|
writeln!(f, "class {name} < {superclass} {{")?;
|
||||||
|
} else {
|
||||||
|
writeln!(f, "class {name} {{")?;
|
||||||
|
}
|
||||||
|
for method in methods.iter() {
|
||||||
|
writeln!(f, "{method}")?;
|
||||||
|
}
|
||||||
|
write!(f, "}}")
|
||||||
|
}
|
||||||
|
Expr::This => write!(f, "this"),
|
||||||
|
Expr::Super {
|
||||||
|
super_var: _,
|
||||||
|
this_var: _,
|
||||||
|
method,
|
||||||
|
} => write!(f, "super.{method}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,10 @@ struct Parser {
|
||||||
|
|
||||||
parse_errors: Vec<ParserError>,
|
parse_errors: Vec<ParserError>,
|
||||||
|
|
||||||
is_in_function: bool,
|
|
||||||
is_in_loop: bool,
|
is_in_loop: bool,
|
||||||
|
is_in_class: bool,
|
||||||
|
is_in_function: bool,
|
||||||
|
is_in_init: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parser {
|
impl Parser {
|
||||||
|
|
@ -71,8 +73,10 @@ impl Parser {
|
||||||
Parser {
|
Parser {
|
||||||
token_iter: TokenIter::new(tokens),
|
token_iter: TokenIter::new(tokens),
|
||||||
parse_errors: Vec::new(),
|
parse_errors: Vec::new(),
|
||||||
is_in_function: false,
|
|
||||||
is_in_loop: false,
|
is_in_loop: false,
|
||||||
|
is_in_class: false,
|
||||||
|
is_in_function: false,
|
||||||
|
is_in_init: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,6 +116,12 @@ impl Parser {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when synchronising: assume all false
|
||||||
|
self.is_in_loop = false;
|
||||||
|
self.is_in_class = false;
|
||||||
|
self.is_in_function = false;
|
||||||
|
self.is_in_init = false;
|
||||||
|
|
||||||
let peek_token = self.peek_token();
|
let peek_token = self.peek_token();
|
||||||
|
|
||||||
// if we match a synchronisation point: return
|
// if we match a synchronisation point: return
|
||||||
|
|
@ -158,23 +168,43 @@ impl Parser {
|
||||||
}
|
}
|
||||||
Ok(Stmt::Break)
|
Ok(Stmt::Break)
|
||||||
}
|
}
|
||||||
TokenType::Return => {
|
TokenType::Return => self.return_statement(),
|
||||||
let code_pos = self.peek_token().code_pos;
|
|
||||||
assert_eq!(self.next_token().token_type, TokenType::Return);
|
|
||||||
let expr = match self.peek_token().token_type {
|
|
||||||
TokenType::Semicolon => Expr::nil(),
|
|
||||||
_ => self.expression()?,
|
|
||||||
};
|
|
||||||
self.semicolon()?;
|
|
||||||
if !self.is_in_function {
|
|
||||||
return Err(ParserError::InvalidReturn { code_pos });
|
|
||||||
}
|
|
||||||
Ok(Stmt::return_stmt(expr))
|
|
||||||
}
|
|
||||||
_ => self.expression_statement(),
|
_ => self.expression_statement(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn return_statement(&mut self) -> ParserResult<Stmt> {
|
||||||
|
let code_pos = self.peek_token().code_pos;
|
||||||
|
assert_eq!(self.next_token().token_type, TokenType::Return);
|
||||||
|
|
||||||
|
let expr = match self.peek_token().token_type {
|
||||||
|
TokenType::Semicolon => {
|
||||||
|
if !self.is_in_init {
|
||||||
|
Expr::nil()
|
||||||
|
} else {
|
||||||
|
Expr::Variable {
|
||||||
|
name: "this".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if !self.is_in_init {
|
||||||
|
self.expression()?
|
||||||
|
} else {
|
||||||
|
return Err(ParserError::ReturnInInit { code_pos });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.semicolon()?;
|
||||||
|
|
||||||
|
if !self.is_in_function {
|
||||||
|
return Err(ParserError::ReturnOutsideFunction { code_pos });
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Stmt::return_stmt(expr))
|
||||||
|
}
|
||||||
|
|
||||||
fn if_statement(&mut self) -> ParserResult<Stmt> {
|
fn if_statement(&mut self) -> ParserResult<Stmt> {
|
||||||
assert_eq!(self.next_token().token_type, TokenType::If);
|
assert_eq!(self.next_token().token_type, TokenType::If);
|
||||||
|
|
||||||
|
|
@ -217,13 +247,7 @@ impl Parser {
|
||||||
|
|
||||||
let is_in_loop = std::mem::replace(&mut self.is_in_loop, true);
|
let is_in_loop = std::mem::replace(&mut self.is_in_loop, true);
|
||||||
|
|
||||||
let body = match self.statement() {
|
let body = self.statement()?;
|
||||||
Ok(body) => body,
|
|
||||||
Err(err) => {
|
|
||||||
self.is_in_loop = is_in_loop;
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.is_in_loop = is_in_loop;
|
self.is_in_loop = is_in_loop;
|
||||||
|
|
||||||
|
|
@ -264,13 +288,7 @@ impl Parser {
|
||||||
|
|
||||||
let is_in_loop = std::mem::replace(&mut self.is_in_loop, true);
|
let is_in_loop = std::mem::replace(&mut self.is_in_loop, true);
|
||||||
|
|
||||||
let mut body = match self.statement() {
|
let mut body = self.statement()?;
|
||||||
Ok(body) => body,
|
|
||||||
Err(err) => {
|
|
||||||
self.is_in_loop = is_in_loop;
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.is_in_loop = is_in_loop;
|
self.is_in_loop = is_in_loop;
|
||||||
|
|
||||||
|
|
@ -333,10 +351,24 @@ impl Parser {
|
||||||
|
|
||||||
let name = self.identifier("Missing class name")?;
|
let name = self.identifier("Missing class name")?;
|
||||||
|
|
||||||
|
let superclass = if self.peek_token().token_type == TokenType::Less {
|
||||||
|
assert_eq!(self.next_token().token_type, TokenType::Less);
|
||||||
|
|
||||||
|
let superclass_name = self.identifier("Expected superclass")?;
|
||||||
|
|
||||||
|
Some(Expr::Variable { name: superclass_name })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
self.consume_token(TokenType::LeftBrace, |token| ParserError::MissingClassBody {
|
self.consume_token(TokenType::LeftBrace, |token| ParserError::MissingClassBody {
|
||||||
code_pos: token.code_pos,
|
code_pos: token.code_pos,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let is_in_loop = std::mem::replace(&mut self.is_in_loop, false);
|
||||||
|
let is_in_class = std::mem::replace(&mut self.is_in_class, true);
|
||||||
|
let is_in_function = std::mem::replace(&mut self.is_in_function, false);
|
||||||
|
|
||||||
let mut methods = Vec::new();
|
let mut methods = Vec::new();
|
||||||
|
|
||||||
while self.peek_token().token_type != TokenType::RightBrace {
|
while self.peek_token().token_type != TokenType::RightBrace {
|
||||||
|
|
@ -350,14 +382,27 @@ impl Parser {
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let is_in_init = self.is_in_init;
|
||||||
|
if method_name == "init" {
|
||||||
|
self.is_in_init = true;
|
||||||
|
}
|
||||||
|
|
||||||
let method = self.fun_params_and_body(method_name)?;
|
let method = self.fun_params_and_body(method_name)?;
|
||||||
|
|
||||||
|
self.is_in_init = is_in_init;
|
||||||
|
|
||||||
methods.push(method);
|
methods.push(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(self.next_token().token_type, TokenType::RightBrace);
|
assert_eq!(self.next_token().token_type, TokenType::RightBrace);
|
||||||
|
|
||||||
Ok(Stmt::Class { name, methods })
|
self.is_in_loop = is_in_loop;
|
||||||
|
self.is_in_class = is_in_class;
|
||||||
|
self.is_in_function = is_in_function;
|
||||||
|
|
||||||
|
let class = Expr::class(&name, methods, superclass);
|
||||||
|
|
||||||
|
Ok(Stmt::var_decl(name, class))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fun_declaration(&mut self) -> ParserResult<Stmt> {
|
fn fun_declaration(&mut self) -> ParserResult<Stmt> {
|
||||||
|
|
@ -399,14 +444,7 @@ impl Parser {
|
||||||
let is_in_function = std::mem::replace(&mut self.is_in_function, true);
|
let is_in_function = std::mem::replace(&mut self.is_in_function, true);
|
||||||
let is_in_loop = std::mem::replace(&mut self.is_in_loop, false);
|
let is_in_loop = std::mem::replace(&mut self.is_in_loop, false);
|
||||||
|
|
||||||
let body = match self.block() {
|
let body = self.block()?;
|
||||||
Ok(body) => body,
|
|
||||||
Err(err) => {
|
|
||||||
self.is_in_function = is_in_function;
|
|
||||||
self.is_in_loop = is_in_loop;
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.is_in_function = is_in_function;
|
self.is_in_function = is_in_function;
|
||||||
self.is_in_loop = is_in_loop;
|
self.is_in_loop = is_in_loop;
|
||||||
|
|
@ -494,6 +532,10 @@ impl Parser {
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Variable { name } => Ok(Expr::assignment(Expr::Variable { name }, value)),
|
Expr::Variable { name } => Ok(Expr::assignment(Expr::Variable { name }, value)),
|
||||||
|
Expr::Get { target, name } => {
|
||||||
|
let value = Box::new(value);
|
||||||
|
Ok(Expr::Set { target, name, value })
|
||||||
|
}
|
||||||
_ => Err(ParserError::InvalidAssignment { expr, code_pos }),
|
_ => Err(ParserError::InvalidAssignment { expr, code_pos }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -625,11 +667,11 @@ impl Parser {
|
||||||
let _ = self.next_token();
|
let _ = self.next_token();
|
||||||
Ok(Expr::unary(UnaryOp::Negate, self.unary()?))
|
Ok(Expr::unary(UnaryOp::Negate, self.unary()?))
|
||||||
}
|
}
|
||||||
_ => self.call(),
|
_ => self.call_or_get(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self) -> ParserResult<Expr> {
|
fn call_or_get(&mut self) -> ParserResult<Expr> {
|
||||||
let mut expr = self.primary()?;
|
let mut expr = self.primary()?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -647,7 +689,13 @@ impl Parser {
|
||||||
|
|
||||||
expr = Expr::call(expr, args);
|
expr = Expr::call(expr, args);
|
||||||
}
|
}
|
||||||
TokenType::Dot => todo!(),
|
TokenType::Dot => {
|
||||||
|
assert_eq!(self.next_token().token_type, TokenType::Dot);
|
||||||
|
|
||||||
|
let name = self.identifier("Expected property name after dot")?;
|
||||||
|
|
||||||
|
expr = Expr::get(expr, name);
|
||||||
|
}
|
||||||
_ => break,
|
_ => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -695,6 +743,22 @@ impl Parser {
|
||||||
TokenType::False => Ok(Expr::bool(false)),
|
TokenType::False => Ok(Expr::bool(false)),
|
||||||
TokenType::True => Ok(Expr::bool(true)),
|
TokenType::True => Ok(Expr::bool(true)),
|
||||||
TokenType::Nil => Ok(Expr::nil()),
|
TokenType::Nil => Ok(Expr::nil()),
|
||||||
|
TokenType::This => Ok(Expr::This),
|
||||||
|
TokenType::Super => {
|
||||||
|
self.consume_token(TokenType::Dot, |token| ParserError::MissingMethodAfterSuper {
|
||||||
|
code_pos: token.code_pos,
|
||||||
|
})?;
|
||||||
|
let method = self.identifier("Expected method name after super")?;
|
||||||
|
let super_var = Box::new(Expr::Variable {
|
||||||
|
name: "super".to_owned(),
|
||||||
|
});
|
||||||
|
let this_var = Box::new(Expr::This);
|
||||||
|
Ok(Expr::Super {
|
||||||
|
super_var,
|
||||||
|
this_var,
|
||||||
|
method,
|
||||||
|
})
|
||||||
|
}
|
||||||
TokenType::LeftParen => {
|
TokenType::LeftParen => {
|
||||||
let expr = self.expression()?;
|
let expr = self.expression()?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,6 @@ pub enum Stmt {
|
||||||
Block {
|
Block {
|
||||||
statements: Vec<Stmt>,
|
statements: Vec<Stmt>,
|
||||||
},
|
},
|
||||||
Class {
|
|
||||||
name: String,
|
|
||||||
methods: Vec<Expr>,
|
|
||||||
},
|
|
||||||
ExprStmt {
|
ExprStmt {
|
||||||
expr: Box<Expr>,
|
expr: Box<Expr>,
|
||||||
},
|
},
|
||||||
|
|
@ -122,13 +118,6 @@ impl Display for Stmt {
|
||||||
}
|
}
|
||||||
write!(f, "}}")
|
write!(f, "}}")
|
||||||
}
|
}
|
||||||
Stmt::Class { name, methods } => {
|
|
||||||
writeln!(f, "class {name} {{")?;
|
|
||||||
for method in methods {
|
|
||||||
writeln!(f, "{method}")?;
|
|
||||||
}
|
|
||||||
write!(f, "}}")
|
|
||||||
}
|
|
||||||
Stmt::ExprStmt { expr } => write!(f, "{expr};"),
|
Stmt::ExprStmt { expr } => write!(f, "{expr};"),
|
||||||
Stmt::Break => write!(f, "break;"),
|
Stmt::Break => write!(f, "break;"),
|
||||||
Stmt::Return { expr } => {
|
Stmt::Return { expr } => {
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,141 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
#[derive(Debug)]
|
use rlox2_frontend::parser::{Expr, Stmt};
|
||||||
|
|
||||||
|
use crate::{LoxFunction, LoxReference, Value};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct LoxClass {
|
pub struct LoxClass {
|
||||||
|
superclass: Option<Rc<LoxClass>>,
|
||||||
|
|
||||||
name: String,
|
name: String,
|
||||||
|
|
||||||
|
methods: HashMap<String, Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Representation of a class in Lox. Always behind an Rc to ensure uniqueness. Should never be handled raw.
|
||||||
impl LoxClass {
|
impl LoxClass {
|
||||||
pub fn new(name: impl Into<String>) -> Self {
|
pub fn new(name: impl Into<String>, methods: HashMap<String, Value>, superclass: Option<Rc<LoxClass>>) -> Rc<Self> {
|
||||||
let name = name.into();
|
let name = name.into();
|
||||||
LoxClass { name }
|
let mut methods = methods;
|
||||||
|
|
||||||
|
// if class has an init method: insert an implicit "return this;" at its end
|
||||||
|
if let Some(Value::Function(init)) = methods.get("init") {
|
||||||
|
let name = init.name().to_owned();
|
||||||
|
let closure = init.closure().clone();
|
||||||
|
let param_names = init.param_names().to_vec();
|
||||||
|
let mut body = init.body().clone();
|
||||||
|
|
||||||
|
if let Stmt::Block { ref mut statements } = body {
|
||||||
|
statements.push(Stmt::return_stmt(Expr::LocalVariable {
|
||||||
|
name: "this".to_owned(),
|
||||||
|
level: 1,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
panic!("Body of init method of class {name} wasn't a block");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let new_init = Value::function(LoxFunction::new(name, closure, param_names, body));
|
||||||
|
methods.insert("init".to_owned(), new_init);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref superclass) = superclass {
|
||||||
|
let mut new_methods: HashMap<String, Value> = HashMap::new();
|
||||||
|
|
||||||
|
// Rc<LoxFunction> is immutable, so we need to drain, change, and replace
|
||||||
|
for (name, value) in methods {
|
||||||
|
if let Value::Function(method_ref) = value {
|
||||||
|
let superclass = superclass.clone();
|
||||||
|
let mut method = method_ref.as_ref().clone();
|
||||||
|
|
||||||
|
method.inject_closed_var("super", Value::Class(superclass));
|
||||||
|
|
||||||
|
new_methods.insert(name, Value::function(method));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
methods = new_methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rc::new(LoxClass {
|
||||||
|
superclass,
|
||||||
|
name,
|
||||||
|
methods,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn superclass(&self) -> Option<Rc<LoxClass>> {
|
||||||
|
self.superclass.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arity(&self) -> usize {
|
||||||
|
if let Some(Value::Function(method)) = self.methods.get("init") {
|
||||||
|
return method.arity();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if let Some(superclass) = self.superclass {
|
||||||
|
return superclass.arity();
|
||||||
|
} */
|
||||||
|
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_method(&self, name: &str, this: LoxReference) -> Option<Value> {
|
||||||
|
/* self.methods
|
||||||
|
.get(name)
|
||||||
|
.cloned()
|
||||||
|
.or_else(|| self.superclass.as_ref().and_then(|cls| cls.get_method(name))) */
|
||||||
|
|
||||||
|
if let Some(method) = self.methods.get(name) {
|
||||||
|
match method {
|
||||||
|
Value::Function(method) => {
|
||||||
|
let mut method = method.as_ref().clone();
|
||||||
|
method.inject_closed_var("this", Value::Object(this));
|
||||||
|
return Some(Value::function(method));
|
||||||
|
}
|
||||||
|
method => panic!("method {name} on class {self} is {method}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref superclass) = self.superclass {
|
||||||
|
return superclass.get_method(name, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pub fn init(&self, args: Vec<Value>, env: &mut Environment) -> EvalResult<()> {
|
||||||
|
if let Some(ref superclass) = self.superclass {
|
||||||
|
let args = args.clone();
|
||||||
|
superclass.init(args, env)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Value::Function(method)) = self.get_method("init") {
|
||||||
|
method.call(args, env)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for LoxClass {
|
impl Display for LoxClass {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "<class {}>", self.name)
|
write!(f, "<class {} at {:#X}>", self.name, (self as *const LoxClass) as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// slightly hacky: two classes are equal if they are stored at the same location
|
||||||
|
// since a LoxClass is always (supposed to be) behind an Rc anyways, we might as well compare their pointers
|
||||||
|
impl PartialEq for LoxClass {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
let self_ptr = self as *const LoxClass;
|
||||||
|
let other_ptr = other as *const LoxClass;
|
||||||
|
self_ptr == other_ptr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,10 +56,6 @@ impl<'a> Environment<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DEBUG
|
|
||||||
println!("Name {name} not defined at level {level}");
|
|
||||||
println!("{self}");
|
|
||||||
|
|
||||||
Err(RuntimeError::NameNotDefined { name: name.to_owned() })
|
Err(RuntimeError::NameNotDefined { name: name.to_owned() })
|
||||||
// self.runtime.get_global(name)
|
// self.runtime.get_global(name)
|
||||||
}
|
}
|
||||||
|
|
@ -101,14 +97,19 @@ impl<'a> Environment<'a> {
|
||||||
let mut closure_scope = Scope::new();
|
let mut closure_scope = Scope::new();
|
||||||
|
|
||||||
for (name, level) in closure_vars {
|
for (name, level) in closure_vars {
|
||||||
|
// special injected variables
|
||||||
|
if name == "this" || name == "super" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let heap_value = self
|
let heap_value = self
|
||||||
.local_scopes
|
.local_scopes
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.nth(*level)
|
.nth(*level)
|
||||||
.unwrap()
|
.expect("Closure variable got resolved to level that doesn't exist")
|
||||||
.get(name)
|
.get(name)
|
||||||
.unwrap()
|
.expect("Closure variable was resolved, but could not be found")
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
let name = name.to_owned();
|
let name = name.to_owned();
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rlox2_frontend::lexer::LexerError;
|
use rlox2_frontend::lexer::LexerError;
|
||||||
use rlox2_frontend::parser::{BinaryOp, ParserError, UnaryOp};
|
use rlox2_frontend::parser::{BinaryOp, ParserError, UnaryOp};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::ResolverError;
|
|
||||||
use crate::Value;
|
use crate::Value;
|
||||||
|
use crate::{LoxClass, ResolverError};
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum RuntimeError {
|
pub enum RuntimeError {
|
||||||
|
|
@ -30,6 +32,14 @@ pub enum RuntimeError {
|
||||||
Return { value: Value },
|
Return { value: Value },
|
||||||
#[error("Exit with exit code {exit_code}")]
|
#[error("Exit with exit code {exit_code}")]
|
||||||
Exit { exit_code: i32 },
|
Exit { exit_code: i32 },
|
||||||
|
#[error("Only objects have attributes")]
|
||||||
|
InvalidGetTarget,
|
||||||
|
#[error("Only objects have attributes")]
|
||||||
|
InvalidSetTarget,
|
||||||
|
#[error("Class {0} has no property {name}", class.name())]
|
||||||
|
UndefinedAttribute { class: Rc<LoxClass>, name: String },
|
||||||
|
#[error("{value} is not a valid superclass")]
|
||||||
|
InvalidSuperclass { value: Value },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use rlox2_frontend::parser::Stmt;
|
use rlox2_frontend::parser::Stmt;
|
||||||
|
|
||||||
|
use crate::value::HeapedValue;
|
||||||
|
|
||||||
use super::environment::{Environment, Scope};
|
use super::environment::{Environment, Scope};
|
||||||
use super::interpret::EvalResult;
|
use super::interpret::EvalResult;
|
||||||
use super::{Runtime, Value};
|
use super::{Runtime, Value};
|
||||||
|
|
@ -15,19 +18,16 @@ pub struct LoxFunction {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LoxFunction {
|
impl LoxFunction {
|
||||||
pub fn new(name: impl Into<String>, closure: Scope, param_names: Vec<String>, body: Stmt) -> Self {
|
pub fn new(name: impl Into<String>, closure: Scope, param_names: Vec<String>, body: Stmt) -> Rc<Self> {
|
||||||
let name = name.into();
|
let name = name.into();
|
||||||
let body = Box::new(body);
|
let body = Box::new(body);
|
||||||
LoxFunction {
|
let fun = LoxFunction {
|
||||||
name,
|
name,
|
||||||
closure,
|
closure,
|
||||||
param_names,
|
param_names,
|
||||||
body,
|
body,
|
||||||
}
|
};
|
||||||
}
|
Rc::new(fun)
|
||||||
|
|
||||||
pub fn arity(&self) -> usize {
|
|
||||||
self.param_names.len()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> &str {
|
pub fn name(&self) -> &str {
|
||||||
|
|
@ -38,13 +38,23 @@ impl LoxFunction {
|
||||||
&self.closure
|
&self.closure
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn param_names(&self) -> impl Iterator<Item = &String> {
|
pub fn param_names(&self) -> &[String] {
|
||||||
self.param_names.iter()
|
&self.param_names
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn body(&self) -> &Stmt {
|
pub fn body(&self) -> &Stmt {
|
||||||
&self.body
|
&self.body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn arity(&self) -> usize {
|
||||||
|
self.param_names().len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inject_closed_var(&mut self, name: impl Into<String>, value: Value) {
|
||||||
|
let name = name.into();
|
||||||
|
let heaped_value = HeapedValue::new(value);
|
||||||
|
self.closure.insert(name, heaped_value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for LoxFunction {
|
impl Display for LoxFunction {
|
||||||
|
|
@ -53,6 +63,16 @@ impl Display for LoxFunction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// slightly hacky: two functions are equal if they are stored at the same location
|
||||||
|
// since a LoxFunction is always (supposed to be) behind an Rc anyways, we might as well compare their pointers
|
||||||
|
impl PartialEq for LoxFunction {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
let self_ptr = self as *const LoxFunction;
|
||||||
|
let other_ptr = other as *const LoxFunction;
|
||||||
|
self_ptr == other_ptr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*====================================================================================================================*/
|
/*====================================================================================================================*/
|
||||||
|
|
||||||
pub type ExternFunClosure = fn(Vec<Value>, &mut Environment) -> EvalResult<Value>;
|
pub type ExternFunClosure = fn(Vec<Value>, &mut Environment) -> EvalResult<Value>;
|
||||||
|
|
@ -60,15 +80,27 @@ pub type ExternFunClosure = fn(Vec<Value>, &mut Environment) -> EvalResult<Value
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LoxExternFunction {
|
pub struct LoxExternFunction {
|
||||||
name: String,
|
name: String,
|
||||||
|
arity: usize,
|
||||||
closure: ExternFunClosure,
|
closure: ExternFunClosure,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LoxExternFunction {
|
impl LoxExternFunction {
|
||||||
pub fn new(name: impl Into<String>, closure: ExternFunClosure) -> Self {
|
pub fn new(name: impl Into<String>, arity: usize, closure: ExternFunClosure) -> Self {
|
||||||
let name = name.into();
|
let name = name.into();
|
||||||
|
|
||||||
LoxExternFunction { name, closure }
|
LoxExternFunction { name, arity, closure }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arity(&self) -> usize {
|
||||||
|
self.arity
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn closure(&self) -> ExternFunClosure {
|
||||||
|
self.closure
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register(self, env: &mut Runtime) {
|
pub fn register(self, env: &mut Runtime) {
|
||||||
|
|
@ -76,10 +108,6 @@ impl LoxExternFunction {
|
||||||
let fun = Value::extern_function(self);
|
let fun = Value::extern_function(self);
|
||||||
env.define_global(name, fun);
|
env.define_global(name, fun);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn call(&self, args: Vec<Value>, env: &mut Environment) -> EvalResult<Value> {
|
|
||||||
(self.closure)(args, env)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for LoxExternFunction {
|
impl Display for LoxExternFunction {
|
||||||
|
|
@ -97,8 +125,12 @@ impl Debug for LoxExternFunction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// slightly hacky: two extern functions are equal if they are stored at the same location
|
||||||
|
// since a LoxExternFunction is always (supposed to be) behind an Rc anyways, we might as well compare their pointers
|
||||||
impl PartialEq for LoxExternFunction {
|
impl PartialEq for LoxExternFunction {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.name == other.name
|
let self_ptr = self as *const LoxExternFunction;
|
||||||
|
let other_ptr = other as *const LoxExternFunction;
|
||||||
|
self_ptr == other_ptr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use rlox2_frontend::parser::{BinaryOp, Expr, Literal, LogicalOp, Stmt, UnaryOp};
|
use rlox2_frontend::parser::{BinaryOp, Expr, Literal, LogicalOp, Stmt, UnaryOp};
|
||||||
|
|
||||||
use crate::error::RuntimeError;
|
use crate::error::RuntimeError;
|
||||||
use crate::LoxClass;
|
use crate::function::LoxExternFunction;
|
||||||
|
use crate::{LoxClass, LoxReference};
|
||||||
|
|
||||||
use super::environment::Environment;
|
use super::environment::Environment;
|
||||||
use super::{LoxFunction, Runtime, Value};
|
use super::{LoxFunction, Runtime, Value};
|
||||||
|
|
@ -133,20 +135,89 @@ impl Eval for Expr {
|
||||||
match callee {
|
match callee {
|
||||||
Value::Function(fun) => fun.call(args, env),
|
Value::Function(fun) => fun.call(args, env),
|
||||||
Value::ExternFunction(ext_fun) => ext_fun.call(args, env),
|
Value::ExternFunction(ext_fun) => ext_fun.call(args, env),
|
||||||
|
Value::Class(class) => LoxClass::call(class, args, env),
|
||||||
_ => Err(RuntimeError::NotCallable { callee }),
|
_ => Err(RuntimeError::NotCallable { callee }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Expr::Get { target, name } => {
|
||||||
|
let target = target.eval(env)?;
|
||||||
|
|
||||||
|
if let Value::Object(object) = target {
|
||||||
|
object.get(name).ok_or_else(|| {
|
||||||
|
let class = object.class();
|
||||||
|
let name = name.to_owned();
|
||||||
|
RuntimeError::UndefinedAttribute { class, name }
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(RuntimeError::InvalidGetTarget)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Set { target, name, value } => {
|
||||||
|
let target = target.eval(env)?;
|
||||||
|
|
||||||
|
if let Value::Object(mut object) = target {
|
||||||
|
let value = value.eval(env)?;
|
||||||
|
object.set(name, value);
|
||||||
|
Ok(Value::Nil)
|
||||||
|
} else {
|
||||||
|
Err(RuntimeError::InvalidSetTarget)
|
||||||
|
}
|
||||||
|
}
|
||||||
Expr::Function {
|
Expr::Function {
|
||||||
name,
|
name,
|
||||||
param_names,
|
param_names,
|
||||||
closure_vars,
|
closure_vars,
|
||||||
body,
|
body,
|
||||||
} => Ok(Value::function(LoxFunction::new(
|
} => Ok(Value::function(LoxFunction::new(
|
||||||
name,
|
name.as_ref(),
|
||||||
env.collect_closure(closure_vars),
|
env.collect_closure(closure_vars),
|
||||||
param_names.clone(),
|
param_names.as_ref().clone(),
|
||||||
body.as_ref().clone(),
|
body.as_ref().clone(),
|
||||||
))),
|
))),
|
||||||
|
Expr::Class {
|
||||||
|
superclass,
|
||||||
|
name,
|
||||||
|
methods: method_exprs,
|
||||||
|
} => {
|
||||||
|
let superclass = match superclass.as_ref().map(|expr| expr.eval(env)) {
|
||||||
|
Some(Ok(Value::Class(superclass))) => Some(superclass),
|
||||||
|
Some(Ok(value)) => return Err(RuntimeError::InvalidSuperclass { value }),
|
||||||
|
Some(Err(err)) => return Err(err),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut methods: HashMap<String, Value> = HashMap::new();
|
||||||
|
|
||||||
|
// this is the scope "this" will get injected in
|
||||||
|
env.enter_scope();
|
||||||
|
|
||||||
|
for method_expr in method_exprs.iter() {
|
||||||
|
let method = method_expr.eval(env)?;
|
||||||
|
if let Value::Function(ref fun) = method {
|
||||||
|
let name = fun.name().to_owned();
|
||||||
|
methods.insert(name, method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
env.exit_scope();
|
||||||
|
|
||||||
|
Ok(Value::class(LoxClass::new(name, methods, superclass)))
|
||||||
|
}
|
||||||
|
Expr::This => panic!("Unresolved this"),
|
||||||
|
Expr::Super {
|
||||||
|
super_var,
|
||||||
|
this_var,
|
||||||
|
method,
|
||||||
|
} => match (super_var.eval(env)?, this_var.eval(env)?) {
|
||||||
|
(Value::Class(superclass), Value::Object(this)) => {
|
||||||
|
if let Some(method) = superclass.get_method(method, this) {
|
||||||
|
Ok(method)
|
||||||
|
} else {
|
||||||
|
Err(RuntimeError::InvalidGetTarget)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(super_val, this_val) => panic!("super evaluated to {super_val} and this evaluated to {this_val}"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -196,13 +267,6 @@ impl Eval for Stmt {
|
||||||
}
|
}
|
||||||
env.exit_scope();
|
env.exit_scope();
|
||||||
}
|
}
|
||||||
Stmt::Class { name, methods: _ } => {
|
|
||||||
env.define(name, Value::Nil);
|
|
||||||
|
|
||||||
let class = Value::class(LoxClass::new(name));
|
|
||||||
|
|
||||||
env.assign(name, class, 0)?;
|
|
||||||
}
|
|
||||||
Stmt::ExprStmt { expr } => {
|
Stmt::ExprStmt { expr } => {
|
||||||
// expr.eval(env)?;
|
// expr.eval(env)?;
|
||||||
// Ok(Value::Nil)
|
// Ok(Value::Nil)
|
||||||
|
|
@ -248,3 +312,40 @@ impl LoxFunction {
|
||||||
ret_val
|
ret_val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LoxExternFunction {
|
||||||
|
pub fn call(&self, args: Vec<Value>, env: &mut Environment) -> EvalResult<Value> {
|
||||||
|
if args.len() != self.arity() {
|
||||||
|
return Err(RuntimeError::WrongArity {
|
||||||
|
name: self.name().to_owned(),
|
||||||
|
arity: self.arity(),
|
||||||
|
given: args.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
(self.closure())(args, env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoxClass {
|
||||||
|
// has to take class as an argument instead of as self to leave it behind its Rc
|
||||||
|
pub fn call(class: Rc<LoxClass>, args: Vec<Value>, env: &mut Environment) -> EvalResult<Value> {
|
||||||
|
if args.len() != class.arity() {
|
||||||
|
return Err(RuntimeError::WrongArity {
|
||||||
|
name: class.name().to_owned(),
|
||||||
|
arity: class.arity(),
|
||||||
|
given: args.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let object = LoxReference::new(class);
|
||||||
|
|
||||||
|
// object.init(args, env)?;
|
||||||
|
|
||||||
|
if let Some(Value::Function(method)) = object.get("init") {
|
||||||
|
method.call(args, env)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::Object(object))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ mod error;
|
||||||
mod function;
|
mod function;
|
||||||
mod interpret;
|
mod interpret;
|
||||||
mod lox_std;
|
mod lox_std;
|
||||||
|
mod object;
|
||||||
mod resolver;
|
mod resolver;
|
||||||
mod run;
|
mod run;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
|
|
@ -12,6 +13,7 @@ mod value;
|
||||||
pub use class::LoxClass;
|
pub use class::LoxClass;
|
||||||
pub use error::{LoxError, RuntimeError};
|
pub use error::{LoxError, RuntimeError};
|
||||||
pub use function::LoxFunction;
|
pub use function::LoxFunction;
|
||||||
|
pub use object::LoxReference;
|
||||||
pub use resolver::{resolve, ResolverError};
|
pub use resolver::{resolve, ResolverError};
|
||||||
pub use run::{run_file, run_repl};
|
pub use run::{run_file, run_repl};
|
||||||
pub use runtime::Runtime;
|
pub use runtime::Runtime;
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ pub fn init_std(env: &mut Runtime) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn input() -> LoxExternFunction {
|
fn input() -> LoxExternFunction {
|
||||||
let closure: ExternFunClosure = |args, _env| match *args {
|
let closure: ExternFunClosure = |args, _env| {
|
||||||
[] => {
|
assert_eq!(args.len(), 0);
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
std::io::stdin()
|
std::io::stdin()
|
||||||
.read_line(&mut buf)
|
.read_line(&mut buf)
|
||||||
|
|
@ -25,38 +25,28 @@ fn input() -> LoxExternFunction {
|
||||||
assert_eq!(buf.pop(), Some('\n'));
|
assert_eq!(buf.pop(), Some('\n'));
|
||||||
|
|
||||||
Ok(Value::string(buf))
|
Ok(Value::string(buf))
|
||||||
}
|
|
||||||
_ => Err(RuntimeError::ExtFunCallFailed {
|
|
||||||
name: "input".to_owned(),
|
|
||||||
msg: "input() takes no arguments".to_owned(),
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
LoxExternFunction::new("input", closure)
|
LoxExternFunction::new("input", 0, closure)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clock() -> LoxExternFunction {
|
fn clock() -> LoxExternFunction {
|
||||||
let closure: ExternFunClosure = |args, _env| match *args {
|
let closure: ExternFunClosure = |args, _env| {
|
||||||
[] => {
|
assert_eq!(args.len(), 0);
|
||||||
let time = std::time::SystemTime::now()
|
let time = std::time::SystemTime::now()
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_secs_f64();
|
.as_secs_f64();
|
||||||
|
|
||||||
Ok(Value::Number(time))
|
Ok(Value::Number(time))
|
||||||
}
|
|
||||||
_ => Err(RuntimeError::ExtFunCallFailed {
|
|
||||||
name: "clock".to_owned(),
|
|
||||||
msg: "clock() takes no arguments".to_owned(),
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
LoxExternFunction::new("clock", closure)
|
LoxExternFunction::new("clock", 0, closure)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_globals() -> LoxExternFunction {
|
fn print_globals() -> LoxExternFunction {
|
||||||
let closure: ExternFunClosure = |args, env| match *args {
|
let closure: ExternFunClosure = |args, env| {
|
||||||
[] => {
|
assert_eq!(args.len(), 0);
|
||||||
let mut globals: Vec<(&String, &Value)> = env.globals().iter().collect();
|
let mut globals: Vec<(&String, &Value)> = env.globals().iter().collect();
|
||||||
|
|
||||||
globals.sort_by_key(|&(name, _value)| name);
|
globals.sort_by_key(|&(name, _value)| name);
|
||||||
|
|
@ -66,48 +56,35 @@ fn print_globals() -> LoxExternFunction {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::Nil)
|
Ok(Value::Nil)
|
||||||
}
|
|
||||||
_ => Err(RuntimeError::ExtFunCallFailed {
|
|
||||||
name: "print_globals".to_owned(),
|
|
||||||
msg: "print_globals() takes no arguments".to_owned(),
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
LoxExternFunction::new("print_globals", closure)
|
LoxExternFunction::new("print_globals", 0, closure)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_env() -> LoxExternFunction {
|
fn print_env() -> LoxExternFunction {
|
||||||
let closure: ExternFunClosure = |args, env| match *args {
|
let closure: ExternFunClosure = |args, env| {
|
||||||
[] => {
|
assert_eq!(args.len(), 0);
|
||||||
println!("{env}");
|
println!("{env}");
|
||||||
Ok(Value::Nil)
|
Ok(Value::Nil)
|
||||||
}
|
|
||||||
_ => Err(RuntimeError::ExtFunCallFailed {
|
|
||||||
name: "print_env".to_owned(),
|
|
||||||
msg: "print_env() takes no arguments".to_owned(),
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
LoxExternFunction::new("print_env", closure)
|
LoxExternFunction::new("print_env", 0, closure)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit() -> LoxExternFunction {
|
fn exit() -> LoxExternFunction {
|
||||||
let closure: ExternFunClosure = |args, _env| {
|
let closure: ExternFunClosure = |args, _env| {
|
||||||
match &*args {
|
assert_eq!(args.len(), 1);
|
||||||
[] => return Err(RuntimeError::Exit { exit_code: 0 }),
|
if let Value::Number(exit_code) = args[0] {
|
||||||
[Value::Number(exit_code)] => {
|
|
||||||
if exit_code.fract() == 0.0 {
|
if exit_code.fract() == 0.0 {
|
||||||
let exit_code = *exit_code as i32;
|
let exit_code = exit_code as i32;
|
||||||
return Err(RuntimeError::Exit { exit_code });
|
return Err(RuntimeError::Exit { exit_code });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Err(RuntimeError::ExtFunCallFailed {
|
Err(RuntimeError::ExtFunCallFailed {
|
||||||
name: "exit".to_owned(),
|
name: "exit".to_owned(),
|
||||||
msg: "Arguments to exit() must an integer or nothing for exit code 0".to_owned(),
|
msg: "Arguments to exit() must an integer".to_owned(),
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
LoxExternFunction::new("exit", closure)
|
LoxExternFunction::new("exit", 1, closure)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
104
interpreter/src/object.rs
Normal file
104
interpreter/src/object.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
use std::cell::UnsafeCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::{LoxClass, Value};
|
||||||
|
|
||||||
|
/// This struct is private, since *nothing* is supposed to be handling an object directly,
|
||||||
|
/// but *only* through a LoxReference, which is basically a pointer to an object
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct LoxObject {
|
||||||
|
class: Rc<LoxClass>,
|
||||||
|
|
||||||
|
attrs: HashMap<String, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoxObject {
|
||||||
|
fn new(class: Rc<LoxClass>) -> Self {
|
||||||
|
LoxObject {
|
||||||
|
class,
|
||||||
|
attrs: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn class(&self) -> Rc<LoxClass> {
|
||||||
|
Rc::clone(&self.class)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, name: &str, this: LoxReference) -> Option<Value> {
|
||||||
|
if let Some(value) = self.attrs.get(name).cloned() {
|
||||||
|
return Some(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.class.get_method(name, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(&mut self, name: impl Into<String>, value: Value) {
|
||||||
|
let name = name.into();
|
||||||
|
self.attrs.insert(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*====================================================================================================================*/
|
||||||
|
|
||||||
|
/// Pointer-like to LoxObject. Has interior mutability, which means it can't give out references to its interior.
|
||||||
|
/// Also includes the possibility of weird, self-referential constructions that lead to unsafe beheaviour,
|
||||||
|
/// so make sure not to do any (too) weird shit here.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LoxReference {
|
||||||
|
inner: Rc<UnsafeCell<LoxObject>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoxReference {
|
||||||
|
pub fn new(class: Rc<LoxClass>) -> Self {
|
||||||
|
let object = LoxObject::new(class);
|
||||||
|
let inner = Rc::new(UnsafeCell::new(object));
|
||||||
|
LoxReference { inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn class(&self) -> Rc<LoxClass> {
|
||||||
|
unsafe {
|
||||||
|
let ptr = self.inner.get();
|
||||||
|
let object = &*ptr;
|
||||||
|
object.class()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, name: &str) -> Option<Value> {
|
||||||
|
unsafe {
|
||||||
|
let ptr = self.inner.get();
|
||||||
|
let object = &*ptr;
|
||||||
|
let this = self.clone();
|
||||||
|
object.get(name, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, name: impl Into<String>, value: Value) {
|
||||||
|
unsafe {
|
||||||
|
let ptr = self.inner.get();
|
||||||
|
let object = &mut *ptr;
|
||||||
|
object.set(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pub fn init(&self, args: Vec<Value>, env: &mut Environment) -> EvalResult<()> {
|
||||||
|
self.class().init(self.clone(), args, env)
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for LoxReference {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
Rc::ptr_eq(&self.inner, &other.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for LoxReference {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
unsafe {
|
||||||
|
let ptr = self.inner.get();
|
||||||
|
let object = &*ptr;
|
||||||
|
write!(f, "<{} object at {:#X}>", object.class().name(), ptr as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,8 +4,12 @@ use thiserror::Error;
|
||||||
pub enum ResolverError {
|
pub enum ResolverError {
|
||||||
#[error("Can't read variable {name} in its own initializer.")]
|
#[error("Can't read variable {name} in its own initializer.")]
|
||||||
VarInOwnInitializer { name: String },
|
VarInOwnInitializer { name: String },
|
||||||
#[error("Could not resolve variable {name}")]
|
#[error("Variable {name} not defined")]
|
||||||
UnresolvableVariable { name: String },
|
UnresolvableVariable { name: String },
|
||||||
#[error("Return outside of function definition")]
|
#[error("Return outside of function definition")]
|
||||||
ReturnOutsideFunction,
|
ReturnOutsideFunction,
|
||||||
|
#[error("this outside of method")]
|
||||||
|
ThisOutsideMethod,
|
||||||
|
#[error("super outside of subclass method")]
|
||||||
|
SuperOutsideMethod,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,6 @@ impl Resolver {
|
||||||
if scope.contains_key(&name) {
|
if scope.contains_key(&name) {
|
||||||
return Ok(Expr::LocalVariable { name, level });
|
return Ok(Expr::LocalVariable { name, level });
|
||||||
}
|
}
|
||||||
// DEBUG
|
|
||||||
level += 1;
|
level += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,8 +141,14 @@ impl Resolver {
|
||||||
return Ok(Expr::GlobalVariable { name });
|
return Ok(Expr::GlobalVariable { name });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if name == "this" {
|
||||||
|
Err(ResolverError::ThisOutsideMethod)
|
||||||
|
} else if name == "super" {
|
||||||
|
Err(ResolverError::SuperOutsideMethod)
|
||||||
|
} else {
|
||||||
Err(ResolverError::UnresolvableVariable { name })
|
Err(ResolverError::UnresolvableVariable { name })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_stmt(&mut self, stmt: &mut Stmt) -> Result<(), ResolverError> {
|
fn resolve_stmt(&mut self, stmt: &mut Stmt) -> Result<(), ResolverError> {
|
||||||
match stmt {
|
match stmt {
|
||||||
|
|
@ -179,11 +184,6 @@ impl Resolver {
|
||||||
self.exit_scope();
|
self.exit_scope();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Stmt::Class { name, methods: _ } => {
|
|
||||||
self.declare(name);
|
|
||||||
self.define(name);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Stmt::ExprStmt { expr } => self.resolve_expr(expr),
|
Stmt::ExprStmt { expr } => self.resolve_expr(expr),
|
||||||
Stmt::Break => Ok(()),
|
Stmt::Break => Ok(()),
|
||||||
Stmt::Return { expr } => self.resolve_expr(expr),
|
Stmt::Return { expr } => self.resolve_expr(expr),
|
||||||
|
|
@ -241,6 +241,15 @@ impl Resolver {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Expr::Get { target, name: _ } => {
|
||||||
|
self.resolve_expr(target)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Expr::Set { target, name: _, value } => {
|
||||||
|
self.resolve_expr(target)?;
|
||||||
|
self.resolve_expr(value)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Expr::Function {
|
Expr::Function {
|
||||||
name,
|
name,
|
||||||
param_names,
|
param_names,
|
||||||
|
|
@ -254,7 +263,7 @@ impl Resolver {
|
||||||
self.declare(name);
|
self.declare(name);
|
||||||
self.define(name);
|
self.define(name);
|
||||||
|
|
||||||
for param_name in param_names {
|
for param_name in param_names.iter() {
|
||||||
self.declare(param_name);
|
self.declare(param_name);
|
||||||
self.define(param_name);
|
self.define(param_name);
|
||||||
}
|
}
|
||||||
|
|
@ -271,6 +280,47 @@ impl Resolver {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Expr::Class {
|
||||||
|
superclass,
|
||||||
|
name,
|
||||||
|
methods,
|
||||||
|
} => {
|
||||||
|
self.declare(name);
|
||||||
|
|
||||||
|
if let Some(superclass) = superclass {
|
||||||
|
self.resolve_expr(superclass)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.define(name);
|
||||||
|
|
||||||
|
// this is the scope "this" is defined in
|
||||||
|
self.enter_scope();
|
||||||
|
self.declare("this");
|
||||||
|
|
||||||
|
if superclass.is_some() {
|
||||||
|
self.declare("super");
|
||||||
|
}
|
||||||
|
|
||||||
|
for method in methods.iter_mut() {
|
||||||
|
self.resolve_expr(method)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.exit_scope();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Expr::This => {
|
||||||
|
*expr = self.resolve_var("this")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Expr::Super {
|
||||||
|
super_var,
|
||||||
|
this_var,
|
||||||
|
method: _,
|
||||||
|
} => {
|
||||||
|
self.resolve_expr(super_var)?;
|
||||||
|
self.resolve_expr(this_var)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@ use std::cell::UnsafeCell;
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::LoxClass;
|
use crate::{LoxClass, LoxReference};
|
||||||
|
|
||||||
use super::function::LoxExternFunction;
|
use super::function::LoxExternFunction;
|
||||||
use super::LoxFunction;
|
use super::LoxFunction;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
|
Object(LoxReference),
|
||||||
Class(Rc<LoxClass>),
|
Class(Rc<LoxClass>),
|
||||||
Function(Rc<LoxFunction>),
|
Function(Rc<LoxFunction>),
|
||||||
ExternFunction(Rc<LoxExternFunction>),
|
ExternFunction(Rc<LoxExternFunction>),
|
||||||
|
|
@ -17,15 +18,13 @@ pub enum Value {
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
Nil,
|
Nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
pub fn class(class: LoxClass) -> Self {
|
pub fn class(class: Rc<LoxClass>) -> Self {
|
||||||
let class = Rc::new(class);
|
|
||||||
Value::Class(class)
|
Value::Class(class)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn function(fun: LoxFunction) -> Self {
|
pub fn function(fun: impl Into<Rc<LoxFunction>>) -> Self {
|
||||||
let fun = Rc::new(fun);
|
let fun = fun.into();
|
||||||
Value::Function(fun)
|
Value::Function(fun)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,6 +46,7 @@ impl Value {
|
||||||
impl Display for Value {
|
impl Display for Value {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
Value::Object(object) => write!(f, "{object}"),
|
||||||
Value::Class(class) => write!(f, "{class}"),
|
Value::Class(class) => write!(f, "{class}"),
|
||||||
Value::Function(fun) => write!(f, "{fun}"),
|
Value::Function(fun) => write!(f, "{fun}"),
|
||||||
Value::ExternFunction(ext_fun) => write!(f, "{ext_fun}"),
|
Value::ExternFunction(ext_fun) => write!(f, "{ext_fun}"),
|
||||||
|
|
@ -58,20 +58,6 @@ impl Display for Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Value {
|
|
||||||
fn eq(&self, other: &Value) -> bool {
|
|
||||||
match (self, other) {
|
|
||||||
(Value::Function(l0), Value::Function(r0)) => Rc::ptr_eq(l0, r0),
|
|
||||||
(Value::ExternFunction(l0), Value::ExternFunction(r0)) => Rc::ptr_eq(l0, r0),
|
|
||||||
(Value::String(l0), Value::String(r0)) => l0 == r0,
|
|
||||||
(Value::Number(l0), Value::Number(r0)) => l0 == r0,
|
|
||||||
(Value::Bool(l0), Value::Bool(r0)) => l0 == r0,
|
|
||||||
(Value::Nil, Value::Nil) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*====================================================================================================================*/
|
/*====================================================================================================================*/
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue