Implement augmented assignment operators
Add support for +=, -=, *=, and /= operators. This is basically just
syntactic sugar, but it's still nice to have
a += 1
compiles to the equivalent of
a = a + 1
with all the same implications of scoping rules.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
// This is an auto-generated file. Any changes made to this file may be overwritten.
|
// This is an auto-generated file. Any changes made to this file may be overwritten.
|
||||||
// This file was created at: 2024-10-03 12:06:28
|
// This file was created at: 2024-10-07 09:59:23
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
@@ -204,6 +204,7 @@ impl Stmt for ExprStmt {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AssignStmt {
|
pub struct AssignStmt {
|
||||||
pub lhs: Token,
|
pub lhs: Token,
|
||||||
|
pub op: Token,
|
||||||
pub rhs: ExprP,
|
pub rhs: ExprP,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -417,6 +417,29 @@ impl<'c> Compiler<'c> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emit a name lookup. This will either emit `Op::GetLocal` or `Op::GetGlobal`, depending on
|
||||||
|
/// the context. If the local or global is not found in the current scope, we error out
|
||||||
|
/// instead.
|
||||||
|
fn emit_lookup(&mut self, line: LineRange, name: &str) -> Result<()> {
|
||||||
|
// check if there's a local with this name, otherwise check globals
|
||||||
|
if let Some(local) = self.get_local(name) {
|
||||||
|
self.emit(line, Op::GetLocal(local.index));
|
||||||
|
} else {
|
||||||
|
let global = self.get_global(name).ok_or_else(|| CompileError::Error {
|
||||||
|
line: Some(line),
|
||||||
|
source_path: self.path.display().to_string(),
|
||||||
|
message: if self.is_global_scope() {
|
||||||
|
format!("unknown global {}", name)
|
||||||
|
} else {
|
||||||
|
format!("unknown local {}", name)
|
||||||
|
},
|
||||||
|
})?;
|
||||||
|
self.emit(line, Op::GetGlobal(global));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn search_dir(&self) -> &Path {
|
fn search_dir(&self) -> &Path {
|
||||||
self.path.parent().unwrap()
|
self.path.parent().unwrap()
|
||||||
}
|
}
|
||||||
@@ -524,23 +547,52 @@ impl StmtVisitor for Compiler<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn visit_assign_stmt(&mut self, stmt: &AssignStmt) -> Result<()> {
|
fn visit_assign_stmt(&mut self, stmt: &AssignStmt) -> Result<()> {
|
||||||
// compile RHS
|
|
||||||
self.compile_expr(&stmt.rhs)?;
|
|
||||||
|
|
||||||
let name = &stmt.lhs.text;
|
let name = &stmt.lhs.text;
|
||||||
|
|
||||||
// If the last value that was assigned to is a function, set its name here
|
let line = stmt_line_number(stmt);
|
||||||
// TODO - maybe this would be smarter to set up in the AST. I'm 99% sure that the last
|
|
||||||
// object created, if it were a function object, will be what we're assigning it to, but I
|
|
||||||
// want to be 100% sure instead of 99%.
|
|
||||||
if let Some(obj) = self.constants.last() {
|
|
||||||
if let Some(fun) = obj.borrow_mut().as_any_mut().downcast_mut::<UserFunction>() {
|
|
||||||
fun.set_name(Rc::new(name.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// compile LHS
|
match stmt.op.kind {
|
||||||
self.emit_assign(stmt_line_number(stmt), name)?;
|
// normal assignment
|
||||||
|
TokenKind::Eq => {
|
||||||
|
// compile RHS
|
||||||
|
self.compile_expr(&stmt.rhs)?;
|
||||||
|
|
||||||
|
// If the last value that was assigned to is a function, set its name here
|
||||||
|
// TODO - maybe this would be smarter to set up in the AST. I'm 99% sure that the last
|
||||||
|
// object created, if it were a function object, will be what we're assigning it to, but I
|
||||||
|
// want to be 100% sure instead of 99%.
|
||||||
|
if let Some(obj) = self.constants.last() {
|
||||||
|
if let Some(fun) = obj.borrow_mut().as_any_mut().downcast_mut::<UserFunction>()
|
||||||
|
{
|
||||||
|
fun.set_name(Rc::new(name.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compile LHS
|
||||||
|
self.emit_assign(line, name)?;
|
||||||
|
}
|
||||||
|
// augmented assignment
|
||||||
|
TokenKind::PlusEq | TokenKind::MinusEq | TokenKind::StarEq | TokenKind::SlashEq => {
|
||||||
|
static OP_NAMES: LazyLock<HashMap<TokenKind, &'static str>> = LazyLock::new(|| {
|
||||||
|
hash_map! {
|
||||||
|
TokenKind::PlusEq => "__add__",
|
||||||
|
TokenKind::MinusEq => "__sub__",
|
||||||
|
TokenKind::StarEq => "__mul__",
|
||||||
|
TokenKind::SlashEq => "__div__",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.emit_lookup(line, name)?;
|
||||||
|
let op_name = OP_NAMES
|
||||||
|
.get(&stmt.op.kind)
|
||||||
|
.expect("invalid augmented assignment operator");
|
||||||
|
let op_constant = self.insert_constant(Str::create(op_name))?;
|
||||||
|
self.emit(line, Op::GetAttr(op_constant));
|
||||||
|
self.compile_expr(&stmt.rhs)?;
|
||||||
|
self.emit(line, Op::Call(1));
|
||||||
|
self.emit_assign(line, name)?;
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -750,21 +802,8 @@ impl ExprVisitor for Compiler<'_> {
|
|||||||
match expr.token.kind {
|
match expr.token.kind {
|
||||||
TokenKind::Name => {
|
TokenKind::Name => {
|
||||||
let name = &expr.token.text;
|
let name = &expr.token.text;
|
||||||
// check if there's a local with this name, otherwise check globals
|
let line = expr_line_number(expr);
|
||||||
if let Some(local) = self.get_local(name) {
|
self.emit_lookup(line, name)?;
|
||||||
self.emit(expr_line_number(expr), Op::GetLocal(local.index));
|
|
||||||
} else {
|
|
||||||
let global = self.get_global(name).ok_or_else(|| CompileError::Error {
|
|
||||||
line: Some(expr_line_number(expr)),
|
|
||||||
source_path: self.path.display().to_string(),
|
|
||||||
message: if self.is_global_scope() {
|
|
||||||
format!("unknown global {}", name)
|
|
||||||
} else {
|
|
||||||
format!("unknown local {}", name)
|
|
||||||
},
|
|
||||||
})?;
|
|
||||||
self.emit(expr_line_number(expr), Op::GetGlobal(global));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
TokenKind::Number => {
|
TokenKind::Number => {
|
||||||
let obj = if expr.token.text.contains('.') {
|
let obj = if expr.token.text.contains('.') {
|
||||||
|
|||||||
@@ -178,17 +178,31 @@ impl Lexer {
|
|||||||
} else if STRING_START_CHARS.contains(self.current()) {
|
} else if STRING_START_CHARS.contains(self.current()) {
|
||||||
return self.string();
|
return self.string();
|
||||||
} else if self.mat('+') {
|
} else if self.mat('+') {
|
||||||
return Ok(self.make_token(TokenKind::Plus));
|
if self.mat('=') {
|
||||||
|
return Ok(self.make_token(TokenKind::PlusEq));
|
||||||
|
} else {
|
||||||
|
return Ok(self.make_token(TokenKind::Plus));
|
||||||
|
}
|
||||||
} else if self.mat('-') {
|
} else if self.mat('-') {
|
||||||
if self.mat('>') {
|
if self.mat('>') {
|
||||||
return Ok(self.make_token(TokenKind::Arrow));
|
return Ok(self.make_token(TokenKind::Arrow));
|
||||||
|
} else if self.mat('=') {
|
||||||
|
return Ok(self.make_token(TokenKind::MinusEq));
|
||||||
} else {
|
} else {
|
||||||
return Ok(self.make_token(TokenKind::Minus));
|
return Ok(self.make_token(TokenKind::Minus));
|
||||||
}
|
}
|
||||||
} else if self.mat('*') {
|
} else if self.mat('*') {
|
||||||
return Ok(self.make_token(TokenKind::Star));
|
if self.mat('=') {
|
||||||
|
return Ok(self.make_token(TokenKind::StarEq));
|
||||||
|
} else {
|
||||||
|
return Ok(self.make_token(TokenKind::Star));
|
||||||
|
}
|
||||||
} else if self.mat('/') {
|
} else if self.mat('/') {
|
||||||
return Ok(self.make_token(TokenKind::Slash));
|
if self.mat('=') {
|
||||||
|
return Ok(self.make_token(TokenKind::SlashEq));
|
||||||
|
} else {
|
||||||
|
return Ok(self.make_token(TokenKind::Slash));
|
||||||
|
}
|
||||||
} else if self.mat('&') {
|
} else if self.mat('&') {
|
||||||
if self.mat('&') {
|
if self.mat('&') {
|
||||||
return Ok(self.make_token(TokenKind::And));
|
return Ok(self.make_token(TokenKind::And));
|
||||||
@@ -561,7 +575,13 @@ impl Parser {
|
|||||||
stmts,
|
stmts,
|
||||||
rbrace,
|
rbrace,
|
||||||
}) as Box<dyn Stmt + 'static>)
|
}) as Box<dyn Stmt + 'static>)
|
||||||
} else if self.current.kind == TokenKind::Name && self.next.kind == TokenKind::Eq {
|
} else if self.current.kind == TokenKind::Name
|
||||||
|
&& (self.next.kind == TokenKind::Eq
|
||||||
|
|| self.next.kind == TokenKind::PlusEq
|
||||||
|
|| self.next.kind == TokenKind::MinusEq
|
||||||
|
|| self.next.kind == TokenKind::StarEq
|
||||||
|
|| self.next.kind == TokenKind::SlashEq)
|
||||||
|
{
|
||||||
self.assign_stmt()
|
self.assign_stmt()
|
||||||
} else {
|
} else {
|
||||||
let expr = self.expr()?;
|
let expr = self.expr()?;
|
||||||
@@ -745,7 +765,15 @@ impl Parser {
|
|||||||
let name = self
|
let name = self
|
||||||
.expect("expect name for assign statement", TokenKind::Name)?
|
.expect("expect name for assign statement", TokenKind::Name)?
|
||||||
.clone();
|
.clone();
|
||||||
self.expect("expect '=' after name", TokenKind::Eq)?;
|
let op = expect!(
|
||||||
|
self,
|
||||||
|
"expected '=' or augmented assign after name",
|
||||||
|
TokenKind::Eq,
|
||||||
|
TokenKind::PlusEq,
|
||||||
|
TokenKind::MinusEq,
|
||||||
|
TokenKind::StarEq,
|
||||||
|
TokenKind::SlashEq
|
||||||
|
)?;
|
||||||
let expr = self.expr()?;
|
let expr = self.expr()?;
|
||||||
if !self.check(TokenKind::RBrace) {
|
if !self.check(TokenKind::RBrace) {
|
||||||
expect!(
|
expect!(
|
||||||
@@ -757,6 +785,7 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
Ok(Box::new(AssignStmt {
|
Ok(Box::new(AssignStmt {
|
||||||
lhs: name,
|
lhs: name,
|
||||||
|
op,
|
||||||
rhs: expr,
|
rhs: expr,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ pub enum TokenKind {
|
|||||||
Star,
|
Star,
|
||||||
Slash,
|
Slash,
|
||||||
|
|
||||||
|
// Augmented assignment operators
|
||||||
|
PlusEq,
|
||||||
|
MinusEq,
|
||||||
|
StarEq,
|
||||||
|
SlashEq,
|
||||||
|
|
||||||
// Unary operators (not already covered)
|
// Unary operators (not already covered)
|
||||||
Bang,
|
Bang,
|
||||||
|
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ GENERATE = [
|
|||||||
GenerateGroup("Stmt")
|
GenerateGroup("Stmt")
|
||||||
.add_struct("Import", ["import_kw: Token", "what: Vec<Token>", "module: Token"])
|
.add_struct("Import", ["import_kw: Token", "what: Vec<Token>", "module: Token"])
|
||||||
.add_struct("Expr", ["expr: ExprP"])
|
.add_struct("Expr", ["expr: ExprP"])
|
||||||
.add_struct("Assign", ["lhs: Token", "rhs: ExprP"])
|
.add_struct("Assign", ["lhs: Token", "op: Token", "rhs: ExprP"])
|
||||||
.add_struct("Set", ["expr: ExprP", "name: Token", "rhs: ExprP"])
|
.add_struct("Set", ["expr: ExprP", "name: Token", "rhs: ExprP"])
|
||||||
.add_struct("Block", ["lbrace: Token", "stmts: Vec<StmtP>", "rbrace: Token"])
|
.add_struct("Block", ["lbrace: Token", "stmts: Vec<StmtP>", "rbrace: Token"])
|
||||||
.add_struct("Return", ["return_kw: Token", "expr: Option<ExprP>"])
|
.add_struct("Return", ["return_kw: Token", "expr: Option<ExprP>"])
|
||||||
|
|||||||
Reference in New Issue
Block a user