1
0

Add math-assignment operators

This commit is contained in:
Jeff 2024-10-05 04:33:38 -04:00
parent d857f42434
commit 437a6bf164
5 changed files with 275 additions and 23 deletions

View File

@ -159,9 +159,15 @@ impl<'src> Lexer<'src> {
}
}
'*' => {
self.position += 1;
if let Some('=') = self.peek_second_char() {
self.position += 2;
(Token::Star, Span(self.position - 1, self.position))
(Token::StarEqual, Span(self.position - 2, self.position))
} else {
self.position += 1;
(Token::Star, Span(self.position - 1, self.position))
}
}
'(' => {
self.position += 1;
@ -261,9 +267,15 @@ impl<'src> Lexer<'src> {
)
}
'/' => {
self.position += 1;
if let Some('=') = self.peek_second_char() {
self.position += 2;
(Token::Slash, Span(self.position - 1, self.position))
(Token::SlashEqual, Span(self.position - 2, self.position))
} else {
self.position += 1;
(Token::Slash, Span(self.position - 1, self.position))
}
}
'%' => {
self.position += 1;

View File

@ -348,12 +348,12 @@ impl<'src> Parser<'src> {
) -> Result<(bool, bool, bool, u8), ParseError> {
let mut push_back = false;
let mut is_constant = false;
let mut is_local = false;
let mut is_mutable_local = false;
let argument = match instruction.operation() {
Operation::GetLocal => {
is_local = true;
let local_index = instruction.b();
let local = self.chunk.get_local(local_index, self.current_position)?;
is_mutable_local = local.is_mutable;
if let Some(index) = local.register_index {
index
@ -380,24 +380,44 @@ impl<'src> Parser<'src> {
}
};
Ok((push_back, is_constant, is_local, argument))
Ok((push_back, is_constant, is_mutable_local, argument))
}
fn parse_math_binary(&mut self) -> Result<(), ParseError> {
let (left_instruction, left_position) =
self.chunk.pop_instruction(self.current_position)?;
let (push_back_left, left_is_constant, left_is_local, left) =
let (push_back_left, left_is_constant, left_is_mutable_local, left) =
self.handle_binary_argument(&left_instruction)?;
let operator = self.current_token;
let operator_position = self.current_position;
let rule = ParseRule::from(&operator.kind());
if let TokenKind::PlusEqual
| TokenKind::MinusEqual
| TokenKind::StarEqual
| TokenKind::SlashEqual = operator.kind()
{
if !left_is_mutable_local {
return Err(ParseError::ExpectedMutableVariable {
found: self.previous_token.to_owned(),
position: left_position,
});
}
}
self.advance()?;
self.parse(rule.precedence.increment())?;
let register = if left_is_local {
let (right_instruction, right_position) =
self.chunk.pop_instruction(self.current_position)?;
let (push_back_right, right_is_constant, right_is_mutable_local, right) =
self.handle_binary_argument(&right_instruction)?;
let register = if left_is_mutable_local {
left
} else if right_is_mutable_local {
right
} else {
let current = self.current_register;
@ -406,18 +426,26 @@ impl<'src> Parser<'src> {
current
};
let mut new_instruction = match operator.kind() {
TokenKind::Plus => Instruction::add(register, left, 0),
TokenKind::Minus => Instruction::subtract(register, left, 0),
TokenKind::Star => Instruction::multiply(register, left, 0),
TokenKind::Slash => Instruction::divide(register, left, 0),
TokenKind::Percent => Instruction::modulo(register, left, 0),
TokenKind::Plus => Instruction::add(register, left, right),
TokenKind::PlusEqual => Instruction::add(register, left, right),
TokenKind::Minus => Instruction::subtract(register, left, right),
TokenKind::MinusEqual => Instruction::subtract(register, left, right),
TokenKind::Star => Instruction::multiply(register, left, right),
TokenKind::StarEqual => Instruction::multiply(register, left, right),
TokenKind::Slash => Instruction::divide(register, left, right),
TokenKind::SlashEqual => Instruction::divide(register, left, right),
TokenKind::Percent => Instruction::modulo(register, left, right),
_ => {
return Err(ParseError::ExpectedTokenMultiple {
expected: &[
TokenKind::Plus,
TokenKind::PlusEqual,
TokenKind::Minus,
TokenKind::MinusEqual,
TokenKind::Star,
TokenKind::StarEqual,
TokenKind::Slash,
TokenKind::SlashEqual,
TokenKind::Percent,
],
found: operator.to_owned(),
@ -426,13 +454,6 @@ impl<'src> Parser<'src> {
}
};
let (right_instruction, right_position) =
self.chunk.pop_instruction(self.current_position)?;
let (push_back_right, right_is_constant, right_is_local, right) =
self.handle_binary_argument(&right_instruction)?;
new_instruction.set_c(right);
if left_is_constant {
new_instruction.set_b_is_constant();
}
@ -1122,7 +1143,11 @@ impl From<&TokenKind> for ParseRule<'_> {
infix: Some(Parser::parse_math_binary),
precedence: Precedence::Term,
},
TokenKind::MinusEqual => todo!(),
TokenKind::MinusEqual => ParseRule {
prefix: None,
infix: Some(Parser::parse_math_binary),
precedence: Precedence::Assignment,
},
TokenKind::Mut => ParseRule {
prefix: None,
infix: None,
@ -1138,7 +1163,11 @@ impl From<&TokenKind> for ParseRule<'_> {
infix: Some(Parser::parse_math_binary),
precedence: Precedence::Term,
},
TokenKind::PlusEqual => todo!(),
TokenKind::PlusEqual => ParseRule {
prefix: None,
infix: Some(Parser::parse_math_binary),
precedence: Precedence::Assignment,
},
TokenKind::RightCurlyBrace => ParseRule {
prefix: None,
infix: None,
@ -1164,11 +1193,21 @@ impl From<&TokenKind> for ParseRule<'_> {
infix: Some(Parser::parse_math_binary),
precedence: Precedence::Factor,
},
TokenKind::SlashEqual => ParseRule {
prefix: None,
infix: Some(Parser::parse_math_binary),
precedence: Precedence::Assignment,
},
TokenKind::Star => ParseRule {
prefix: None,
infix: Some(Parser::parse_math_binary),
precedence: Precedence::Factor,
},
TokenKind::StarEqual => ParseRule {
prefix: None,
infix: Some(Parser::parse_math_binary),
precedence: Precedence::Assignment,
},
TokenKind::Str => todo!(),
TokenKind::String => ParseRule {
prefix: Some(Parser::parse_string),
@ -1205,6 +1244,10 @@ pub enum ParseError {
found: TokenOwned,
position: Span,
},
ExpectedMutableVariable {
found: TokenOwned,
position: Span,
},
InvalidAssignmentTarget {
found: TokenOwned,
position: Span,
@ -1250,6 +1293,7 @@ impl AnnotatedError for ParseError {
Self::ExpectedExpression { .. } => "Expected an expression",
Self::ExpectedToken { .. } => "Expected a specific token",
Self::ExpectedTokenMultiple { .. } => "Expected one of multiple tokens",
Self::ExpectedMutableVariable { .. } => "Expected a mutable variable",
Self::InvalidAssignmentTarget { .. } => "Invalid assignment target",
Self::UndefinedVariable { .. } => "Undefined variable",
Self::RegisterOverflow { .. } => "Register overflow",
@ -1291,6 +1335,9 @@ impl AnnotatedError for ParseError {
Some(details)
}
Self::ExpectedMutableVariable { found, .. } => {
Some(format!("Expected mutable variable, found \"{found}\""))
}
Self::InvalidAssignmentTarget { found, .. } => {
Some(format!("Invalid assignment target, found \"{found}\""))
}
@ -1312,6 +1359,7 @@ impl AnnotatedError for ParseError {
Self::ExpectedExpression { position, .. } => *position,
Self::ExpectedToken { position, .. } => *position,
Self::ExpectedTokenMultiple { position, .. } => *position,
Self::ExpectedMutableVariable { position, .. } => *position,
Self::InvalidAssignmentTarget { position, .. } => *position,
Self::UndefinedVariable { position, .. } => *position,
Self::RegisterOverflow { position } => *position,

View File

@ -60,7 +60,9 @@ pub enum Token<'src> {
RightSquareBrace,
Semicolon,
Slash,
SlashEqual,
Star,
StarEqual,
}
impl<'src> Token<'src> {
@ -116,7 +118,9 @@ impl<'src> Token<'src> {
Token::RightSquareBrace => 1,
Token::Semicolon => 1,
Token::Slash => 1,
Token::SlashEqual => 2,
Token::Star => 1,
Token::StarEqual => 2,
}
}
@ -167,7 +171,9 @@ impl<'src> Token<'src> {
Token::RightSquareBrace => TokenOwned::RightSquareBrace,
Token::Semicolon => TokenOwned::Semicolon,
Token::Star => TokenOwned::Star,
Token::StarEqual => TokenOwned::StarEqual,
Token::Slash => TokenOwned::Slash,
Token::SlashEqual => TokenOwned::SlashEqual,
Token::String(text) => TokenOwned::String(text.to_string()),
Token::Str => TokenOwned::Str,
Token::Struct => TokenOwned::Struct,
@ -222,7 +228,9 @@ impl<'src> Token<'src> {
Token::RightSquareBrace => TokenKind::RightSquareBrace,
Token::Semicolon => TokenKind::Semicolon,
Token::Star => TokenKind::Star,
Token::StarEqual => TokenKind::StarEqual,
Token::Slash => TokenKind::Slash,
Token::SlashEqual => TokenKind::SlashEqual,
Token::Str => TokenKind::Str,
Token::String(_) => TokenKind::String,
Token::Struct => TokenKind::Struct,
@ -279,7 +287,9 @@ impl<'src> Display for Token<'src> {
Token::RightSquareBrace => write!(f, "]"),
Token::Semicolon => write!(f, ";"),
Token::Slash => write!(f, "/"),
Token::SlashEqual => write!(f, "/="),
Token::Star => write!(f, "*"),
Token::StarEqual => write!(f, "*="),
Token::Str => write!(f, "str"),
Token::String(value) => write!(f, "{value}"),
Token::Struct => write!(f, "struct"),
@ -348,8 +358,10 @@ pub enum TokenOwned {
RightSquareBrace,
Semicolon,
Star,
StarEqual,
Struct,
Slash,
SlashEqual,
}
impl Display for TokenOwned {
@ -400,7 +412,9 @@ impl Display for TokenOwned {
TokenOwned::RightSquareBrace => Token::RightSquareBrace.fmt(f),
TokenOwned::Semicolon => Token::Semicolon.fmt(f),
TokenOwned::Star => Token::Star.fmt(f),
TokenOwned::StarEqual => Token::StarEqual.fmt(f),
TokenOwned::Slash => Token::Slash.fmt(f),
TokenOwned::SlashEqual => Token::SlashEqual.fmt(f),
TokenOwned::Str => Token::Str.fmt(f),
TokenOwned::String(string) => Token::String(string).fmt(f),
TokenOwned::Struct => Token::Struct.fmt(f),
@ -467,8 +481,10 @@ pub enum TokenKind {
RightSquareBrace,
Semicolon,
Star,
StarEqual,
Struct,
Slash,
SlashEqual,
}
impl Display for TokenKind {
@ -519,8 +535,10 @@ impl Display for TokenKind {
TokenKind::RightSquareBrace => Token::RightSquareBrace.fmt(f),
TokenKind::Semicolon => Token::Semicolon.fmt(f),
TokenKind::Star => Token::Star.fmt(f),
TokenKind::StarEqual => Token::StarEqual.fmt(f),
TokenKind::Str => Token::Str.fmt(f),
TokenKind::Slash => Token::Slash.fmt(f),
TokenKind::SlashEqual => Token::SlashEqual.fmt(f),
TokenKind::String => write!(f, "string value"),
TokenKind::Struct => Token::Struct.fmt(f),
TokenKind::While => Token::While.fmt(f),

View File

@ -1,4 +1,5 @@
use dust_lang::*;
#[test]
fn add() {
let source = "1 + 2";
@ -23,6 +24,28 @@ fn add() {
assert_eq!(run(source), Ok(Some(Value::integer(3))));
}
#[test]
fn add_assign() {
let source = "let mut a = 1; a += 2; a";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(*Instruction::add(0, 0, 1).set_c_is_constant(), Span(17, 19)),
(Instruction::get_local(1, 0), Span(23, 24)),
(Instruction::r#return(), Span(24, 24))
],
vec![Value::integer(1), Value::integer(2)],
vec![Local::new(Identifier::new("a"), true, 0, Some(0))]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(3))));
}
#[test]
fn and() {
let source = "true && false";
@ -97,6 +120,7 @@ fn block_scope() {
#[test]
fn constant() {
let source = "42";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
@ -115,6 +139,7 @@ fn constant() {
#[test]
fn define_local() {
let source = "let x = 42;";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
@ -133,6 +158,7 @@ fn define_local() {
#[test]
fn divide() {
let source = "2 / 2";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
@ -153,9 +179,35 @@ fn divide() {
assert_eq!(run(source), Ok(Some(Value::integer(1))));
}
#[test]
fn divide_assign() {
let source = "let mut a = 2; a /= 2; a";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::divide(0, 0, 1).set_c_is_constant(),
Span(17, 19)
),
(Instruction::get_local(1, 0), Span(23, 24)),
(Instruction::r#return(), Span(24, 24))
],
vec![Value::integer(2), Value::integer(2)],
vec![Local::new(Identifier::new("a"), true, 0, Some(0))]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(1))));
}
#[test]
fn empty() {
let source = "";
assert_eq!(parse(source), Ok(Chunk::with_data(vec![], vec![], vec![])),);
assert_eq!(run(source), Ok(None));
}
@ -554,6 +606,7 @@ fn list_with_simple_expression() {
#[test]
fn math_operator_precedence() {
let source = "1 + 2 - 3 * 4 / 5";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
@ -594,6 +647,7 @@ fn math_operator_precedence() {
#[test]
fn multiply() {
let source = "1 * 2";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
@ -614,6 +668,31 @@ fn multiply() {
assert_eq!(run(source), Ok(Some(Value::integer(2))));
}
#[test]
fn multiply_assign() {
let source = "let mut a = 2; a *= 3 a";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::multiply(0, 0, 1).set_c_is_constant(),
Span(17, 19)
),
(Instruction::get_local(1, 0), Span(22, 23)),
(Instruction::r#return(), Span(23, 23))
],
vec![Value::integer(2), Value::integer(3)],
vec![Local::new(Identifier::new("a"), true, 0, Some(0)),]
))
);
assert_eq!(run(source), Ok(Some(Value::integer(6))));
}
#[test]
fn negate() {
let source = "-(42)";
@ -683,6 +762,7 @@ fn not_equal() {
#[test]
fn or() {
let source = "true || false";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
@ -704,6 +784,7 @@ fn or() {
#[test]
fn parentheses_precedence() {
let source = "(1 + 2) * 3";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
@ -731,6 +812,7 @@ fn parentheses_precedence() {
#[test]
fn set_local() {
let source = "let mut x = 41; x = 42; x";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
@ -753,6 +835,7 @@ fn set_local() {
#[test]
fn subtract() {
let source = "1 - 2";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
@ -773,9 +856,35 @@ fn subtract() {
assert_eq!(run(source), Ok(Some(Value::integer(-1))));
}
#[test]
fn subtract_assign() {
let source = "let mut x = 42; x -= 2; x";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
vec![
(Instruction::load_constant(0, 0, false), Span(12, 14)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::subtract(0, 0, 1).set_c_is_constant(),
Span(18, 20)
),
(Instruction::get_local(1, 0), Span(24, 25)),
(Instruction::r#return(), Span(25, 25)),
],
vec![Value::integer(42), Value::integer(2)],
vec![Local::new(Identifier::new("x"), true, 0, Some(0)),]
)),
);
assert_eq!(run(source), Ok(Some(Value::integer(40))));
}
#[test]
fn variable_and() {
let source = "let a = true; let b = false; a && b";
assert_eq!(
parse(source),
Ok(Chunk::with_data(

View File

@ -0,0 +1,65 @@
use dust_lang::*;
#[test]
fn add_assign_expects_variable() {
let source = "1 += 2";
assert_eq!(
parse(source),
Err(DustError::Parse {
error: ParseError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
},
source
})
);
}
#[test]
fn divide_assign_expects_variable() {
let source = "1 -= 2";
assert_eq!(
parse(source),
Err(DustError::Parse {
error: ParseError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
},
source
})
);
}
#[test]
fn multiply_assign_expects_variable() {
let source = "1 *= 2";
assert_eq!(
parse(source),
Err(DustError::Parse {
error: ParseError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
},
source
})
);
}
#[test]
fn subtract_assign_expects_variable() {
let source = "1 -= 2";
assert_eq!(
parse(source),
Err(DustError::Parse {
error: ParseError::ExpectedMutableVariable {
found: Token::Integer("1").to_owned(),
position: Span(0, 1)
},
source
})
);
}