Add &&, || and ! to the language; Add tests

This commit is contained in:
Jeff 2024-09-17 17:51:39 -04:00
parent 00555785e3
commit 6ca96bc1dc
6 changed files with 329 additions and 20 deletions

View File

@ -99,6 +99,36 @@ impl Instruction {
instruction instruction
} }
pub fn modulo(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Modulo as u32);
instruction.set_destination(to_register);
instruction.set_first_argument(left_index);
instruction.set_second_argument(right_index);
instruction
}
pub fn and(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::And as u32);
instruction.set_destination(to_register);
instruction.set_first_argument(left_index);
instruction.set_second_argument(right_index);
instruction
}
pub fn or(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Or as u32);
instruction.set_destination(to_register);
instruction.set_first_argument(left_index);
instruction.set_second_argument(right_index);
instruction
}
pub fn negate(to_register: u8, from_index: u8) -> Instruction { pub fn negate(to_register: u8, from_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Negate as u32); let mut instruction = Instruction(Operation::Negate as u32);
@ -108,6 +138,15 @@ impl Instruction {
instruction instruction
} }
pub fn not(to_register: u8, from_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Not as u32);
instruction.set_destination(to_register);
instruction.set_first_argument(from_index);
instruction
}
pub fn r#return() -> Instruction { pub fn r#return() -> Instruction {
Instruction(Operation::Return as u32) Instruction(Operation::Return as u32)
} }
@ -306,6 +345,51 @@ impl Instruction {
format!("R({destination}) = {first_argument} / {second_argument}",) format!("R({destination}) = {first_argument} / {second_argument}",)
} }
Operation::Modulo => {
let destination = self.destination();
let first_argument = if self.first_argument_is_constant() {
format!("C({})", self.first_argument())
} else {
format!("R({})", self.first_argument())
};
let second_argument = if self.second_argument_is_constant() {
format!("C({})", self.second_argument())
} else {
format!("R({})", self.second_argument())
};
format!("R({destination}) = {first_argument} % {second_argument}",)
}
Operation::And => {
let destination = self.destination();
let first_argument = if self.first_argument_is_constant() {
format!("C({})", self.first_argument())
} else {
format!("R({})", self.first_argument())
};
let second_argument = if self.second_argument_is_constant() {
format!("C({})", self.second_argument())
} else {
format!("R({})", self.second_argument())
};
format!("R({destination}) = {first_argument} && {second_argument}",)
}
Operation::Or => {
let destination = self.destination();
let first_argument = if self.first_argument_is_constant() {
format!("C({})", self.first_argument())
} else {
format!("R({})", self.first_argument())
};
let second_argument = if self.second_argument_is_constant() {
format!("C({})", self.second_argument())
} else {
format!("R({})", self.second_argument())
};
format!("R({destination}) = {first_argument} || {second_argument}",)
}
Operation::Negate => { Operation::Negate => {
let destination = self.destination(); let destination = self.destination();
let argument = if self.first_argument_is_constant() { let argument = if self.first_argument_is_constant() {
@ -316,6 +400,16 @@ impl Instruction {
format!("R({destination}) = -{argument}") format!("R({destination}) = -{argument}")
} }
Operation::Not => {
let destination = self.destination();
let argument = if self.first_argument_is_constant() {
format!("C({})", self.first_argument())
} else {
format!("R({})", self.first_argument())
};
format!("R({destination}) = !{argument}")
}
Operation::Return => return None, Operation::Return => return None,
}; };
@ -342,9 +436,13 @@ const SET_LOCAL: u8 = 0b0000_0101;
const ADD: u8 = 0b0000_0110; const ADD: u8 = 0b0000_0110;
const SUBTRACT: u8 = 0b0000_0111; const SUBTRACT: u8 = 0b0000_0111;
const MULTIPLY: u8 = 0b0000_1000; const MULTIPLY: u8 = 0b0000_1000;
const DIVIDE: u8 = 0b0000_1001; const MODULO: u8 = 0b0000_1001;
const NEGATE: u8 = 0b0000_1010; const AND: u8 = 0b0000_1010;
const RETURN: u8 = 0b0000_1011; const OR: u8 = 0b0000_1011;
const DIVIDE: u8 = 0b0000_1100;
const NEGATE: u8 = 0b0000_1101;
const NOT: u8 = 0b0000_1110;
const RETURN: u8 = 0b0000_1111;
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum Operation { pub enum Operation {
@ -365,9 +463,13 @@ pub enum Operation {
Subtract = SUBTRACT as isize, Subtract = SUBTRACT as isize,
Multiply = MULTIPLY as isize, Multiply = MULTIPLY as isize,
Divide = DIVIDE as isize, Divide = DIVIDE as isize,
Modulo = MODULO as isize,
And = AND as isize,
Or = OR as isize,
// Unary operations // Unary operations
Negate = NEGATE as isize, Negate = NEGATE as isize,
Not = NOT as isize,
// Control flow // Control flow
Return = RETURN as isize, Return = RETURN as isize,
@ -377,7 +479,13 @@ impl Operation {
pub fn is_binary(&self) -> bool { pub fn is_binary(&self) -> bool {
matches!( matches!(
self, self,
Operation::Add | Operation::Subtract | Operation::Multiply | Operation::Divide Operation::Add
| Operation::Subtract
| Operation::Multiply
| Operation::Divide
| Operation::Modulo
| Operation::And
| Operation::Or
) )
} }
} }
@ -395,8 +503,13 @@ impl From<u8> for Operation {
SUBTRACT => Operation::Subtract, SUBTRACT => Operation::Subtract,
MULTIPLY => Operation::Multiply, MULTIPLY => Operation::Multiply,
DIVIDE => Operation::Divide, DIVIDE => Operation::Divide,
MODULO => Operation::Modulo,
AND => Operation::And,
OR => Operation::Or,
NEGATE => Operation::Negate, NEGATE => Operation::Negate,
_ => Operation::Return, NOT => Operation::Not,
RETURN => Operation::Return,
_ => panic!("Invalid operation byte: {}", byte),
} }
} }
} }
@ -414,7 +527,11 @@ impl From<Operation> for u8 {
Operation::Subtract => SUBTRACT, Operation::Subtract => SUBTRACT,
Operation::Multiply => MULTIPLY, Operation::Multiply => MULTIPLY,
Operation::Divide => DIVIDE, Operation::Divide => DIVIDE,
Operation::Modulo => MODULO,
Operation::And => AND,
Operation::Or => OR,
Operation::Negate => NEGATE, Operation::Negate => NEGATE,
Operation::Not => NOT,
Operation::Return => RETURN, Operation::Return => RETURN,
} }
} }
@ -433,7 +550,11 @@ impl Display for Operation {
Operation::Subtract => write!(f, "SUBTRACT"), Operation::Subtract => write!(f, "SUBTRACT"),
Operation::Multiply => write!(f, "MULTIPLY"), Operation::Multiply => write!(f, "MULTIPLY"),
Operation::Divide => write!(f, "DIVIDE"), Operation::Divide => write!(f, "DIVIDE"),
Operation::Modulo => write!(f, "MODULO"),
Operation::And => write!(f, "AND"),
Operation::Or => write!(f, "OR"),
Operation::Negate => write!(f, "NEGATE"), Operation::Negate => write!(f, "NEGATE"),
Operation::Not => write!(f, "NOT"),
Operation::Return => write!(f, "RETURN"), Operation::Return => write!(f, "RETURN"),
} }
} }
@ -564,6 +685,40 @@ mod tests {
assert!(instruction.second_argument_is_constant()); assert!(instruction.second_argument_is_constant());
} }
#[test]
fn and() {
let mut instruction = Instruction::and(0, 1, 2);
instruction.set_operation(Operation::And);
instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant();
assert_eq!(instruction.operation(), Operation::And);
assert_eq!(instruction.destination(), 0);
assert_eq!(instruction.first_argument(), 1);
assert_eq!(instruction.second_argument(), 2);
assert!(instruction.first_argument_is_constant());
assert!(instruction.second_argument_is_constant());
}
#[test]
fn or() {
let mut instruction = Instruction::or(0, 1, 2);
instruction.set_operation(Operation::Or);
instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant();
assert_eq!(instruction.operation(), Operation::Or);
assert_eq!(instruction.destination(), 0);
assert_eq!(instruction.first_argument(), 1);
assert_eq!(instruction.second_argument(), 2);
assert!(instruction.first_argument_is_constant());
assert!(instruction.second_argument_is_constant());
}
#[test] #[test]
fn negate() { fn negate() {
let mut instruction = Instruction::negate(0, 1); let mut instruction = Instruction::negate(0, 1);
@ -580,6 +735,22 @@ mod tests {
assert!(instruction.second_argument_is_constant()); assert!(instruction.second_argument_is_constant());
} }
#[test]
fn not() {
let mut instruction = Instruction::not(0, 1);
instruction.set_operation(Operation::Not);
instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant();
assert_eq!(instruction.operation(), Operation::Not);
assert_eq!(instruction.destination(), 0);
assert_eq!(instruction.first_argument(), 1);
assert!(instruction.first_argument_is_constant());
assert!(instruction.second_argument_is_constant());
}
#[test] #[test]
fn r#return() { fn r#return() {
let mut instruction = Instruction::r#return(); let mut instruction = Instruction::r#return();

View File

@ -279,8 +279,8 @@ impl<'src> Parser<'src> {
fn parse_binary(&mut self) -> Result<(), ParseError> { fn parse_binary(&mut self) -> Result<(), ParseError> {
let operator_position = self.current_position; let operator_position = self.current_position;
let operator = self.current_token.kind(); let operator = self.current_token;
let rule = ParseRule::from(&operator); let rule = ParseRule::from(&operator.kind());
self.advance()?; self.advance()?;
@ -330,11 +330,14 @@ impl<'src> Parser<'src> {
} }
}; };
let mut instruction = match operator { let mut instruction = match operator.kind() {
TokenKind::Plus => Instruction::add(self.current_register, left, right), TokenKind::Plus => Instruction::add(self.current_register, left, right),
TokenKind::Minus => Instruction::subtract(self.current_register, left, right), TokenKind::Minus => Instruction::subtract(self.current_register, left, right),
TokenKind::Star => Instruction::multiply(self.current_register, left, right), TokenKind::Star => Instruction::multiply(self.current_register, left, right),
TokenKind::Slash => Instruction::divide(self.current_register, left, right), TokenKind::Slash => Instruction::divide(self.current_register, left, right),
TokenKind::Percent => Instruction::modulo(self.current_register, left, right),
TokenKind::DoubleAmpersand => Instruction::and(self.current_register, left, right),
TokenKind::DoublePipe => Instruction::or(self.current_register, left, right),
_ => { _ => {
return Err(ParseError::ExpectedTokenMultiple { return Err(ParseError::ExpectedTokenMultiple {
expected: vec![ expected: vec![
@ -343,7 +346,7 @@ impl<'src> Parser<'src> {
TokenKind::Star, TokenKind::Star,
TokenKind::Slash, TokenKind::Slash,
], ],
found: self.previous_token.to_owned(), found: operator.to_owned(),
position: operator_position, position: operator_position,
}) })
} }
@ -448,6 +451,7 @@ impl<'src> Parser<'src> {
} }
pub fn parse_block(&mut self, _allow_assignment: bool) -> Result<(), ParseError> { pub fn parse_block(&mut self, _allow_assignment: bool) -> Result<(), ParseError> {
self.advance()?;
self.chunk.begin_scope(); self.chunk.begin_scope();
while !self.allow(TokenKind::RightCurlyBrace)? && !self.is_eof() { while !self.allow(TokenKind::RightCurlyBrace)? && !self.is_eof() {
@ -467,13 +471,11 @@ impl<'src> Parser<'src> {
let start = self.current_position.0; let start = self.current_position.0;
let (is_expression_statement, contains_block) = match self.current_token { let (is_expression_statement, contains_block) = match self.current_token {
Token::Let => { Token::Let => {
self.advance()?;
self.parse_let_statement(true)?; self.parse_let_statement(true)?;
(false, false) (false, false)
} }
Token::LeftCurlyBrace => { Token::LeftCurlyBrace => {
self.advance()?;
self.parse_block(true)?; self.parse_block(true)?;
(true, true) (true, true)
@ -495,7 +497,7 @@ impl<'src> Parser<'src> {
} }
fn parse_let_statement(&mut self, _allow_assignment: bool) -> Result<(), ParseError> { fn parse_let_statement(&mut self, _allow_assignment: bool) -> Result<(), ParseError> {
self.allow(TokenKind::Let)?; self.advance()?;
let position = self.current_position; let position = self.current_position;
let identifier = if let Token::Identifier(text) = self.current_token { let identifier = if let Token::Identifier(text) = self.current_token {
@ -692,7 +694,11 @@ impl From<&TokenKind> for ParseRule<'_> {
TokenKind::Str => todo!(), TokenKind::Str => todo!(),
TokenKind::While => todo!(), TokenKind::While => todo!(),
TokenKind::BangEqual => todo!(), TokenKind::BangEqual => todo!(),
TokenKind::Bang => todo!(), TokenKind::Bang => ParseRule {
prefix: Some(Parser::parse_unary),
infix: None,
precedence: Precedence::Unary,
},
TokenKind::Colon => todo!(), TokenKind::Colon => todo!(),
TokenKind::Comma => todo!(), TokenKind::Comma => todo!(),
TokenKind::Dot => todo!(), TokenKind::Dot => todo!(),
@ -703,7 +709,11 @@ impl From<&TokenKind> for ParseRule<'_> {
}, },
TokenKind::DoubleDot => todo!(), TokenKind::DoubleDot => todo!(),
TokenKind::DoubleEqual => todo!(), TokenKind::DoubleEqual => todo!(),
TokenKind::DoublePipe => todo!(), TokenKind::DoublePipe => ParseRule {
prefix: None,
infix: Some(Parser::parse_binary),
precedence: Precedence::LogicalOr,
},
TokenKind::Equal => ParseRule { TokenKind::Equal => ParseRule {
prefix: None, prefix: None,
infix: None, infix: None,

View File

@ -148,15 +148,60 @@ fn declare_local() {
} }
#[test] #[test]
fn constant() { fn and() {
assert_eq!( assert_eq!(
parse("42"), parse("1 && 2"),
Ok(Chunk::with_data( Ok(Chunk::with_data(
vec![ vec![
(Instruction::load_constant(0, 0), Span(0, 2)), (
(Instruction::r#return(), Span(0, 2)), *Instruction::and(0, 0, 1)
.set_first_argument_to_constant()
.set_second_argument_to_constant(),
Span(2, 4)
),
(Instruction::r#return(), Span(0, 6)),
], ],
vec![Value::integer(42)], vec![Value::integer(1), Value::integer(2)],
vec![]
))
);
}
#[test]
fn divide() {
assert_eq!(
parse("1 / 2"),
Ok(Chunk::with_data(
vec![
(
*Instruction::divide(0, 0, 1)
.set_first_argument_to_constant()
.set_second_argument_to_constant(),
Span(2, 3)
),
(Instruction::r#return(), Span(0, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![]
))
);
}
#[test]
fn multiply() {
assert_eq!(
parse("1 * 2"),
Ok(Chunk::with_data(
vec![
(
*Instruction::multiply(0, 0, 1)
.set_first_argument_to_constant()
.set_second_argument_to_constant(),
Span(2, 3)
),
(Instruction::r#return(), Span(0, 5)),
],
vec![Value::integer(1), Value::integer(2)],
vec![] vec![]
)) ))
); );
@ -201,3 +246,18 @@ fn subtract() {
)) ))
); );
} }
#[test]
fn constant() {
assert_eq!(
parse("42"),
Ok(Chunk::with_data(
vec![
(Instruction::load_constant(0, 0), Span(0, 2)),
(Instruction::r#return(), Span(0, 2)),
],
vec![Value::integer(42)],
vec![]
))
);
}

View File

@ -2,7 +2,7 @@
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
/// Source code token. /// Source code token.
#[derive(Debug, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum Token<'src> { pub enum Token<'src> {
// End of file // End of file
Eof, Eof,

View File

@ -149,6 +149,57 @@ impl Vm {
self.insert(quotient, instruction.destination(), position)?; self.insert(quotient, instruction.destination(), position)?;
} }
Operation::Modulo => {
let left = self.take_constant_or_clone_register(
instruction.first_argument(),
instruction.first_argument_is_constant(),
position,
)?;
let right = self.take_constant_or_clone_register(
instruction.second_argument(),
instruction.second_argument_is_constant(),
position,
)?;
let remainder = left
.modulo(&right)
.map_err(|error| VmError::Value { error, position })?;
self.insert(remainder, instruction.destination(), position)?;
}
Operation::And => {
let left = self.take_constant_or_clone_register(
instruction.first_argument(),
instruction.first_argument_is_constant(),
position,
)?;
let right = self.take_constant_or_clone_register(
instruction.second_argument(),
instruction.second_argument_is_constant(),
position,
)?;
let result = left
.and(&right)
.map_err(|error| VmError::Value { error, position })?;
self.insert(result, instruction.destination(), position)?;
}
Operation::Or => {
let left = self.take_constant_or_clone_register(
instruction.first_argument(),
instruction.first_argument_is_constant(),
position,
)?;
let right = self.take_constant_or_clone_register(
instruction.second_argument(),
instruction.second_argument_is_constant(),
position,
)?;
let result = left
.or(&right)
.map_err(|error| VmError::Value { error, position })?;
self.insert(result, instruction.destination(), position)?;
}
Operation::Negate => { Operation::Negate => {
let value = self.take_constant_or_clone_register( let value = self.take_constant_or_clone_register(
instruction.first_argument(), instruction.first_argument(),
@ -161,6 +212,18 @@ impl Vm {
self.insert(negated, instruction.destination(), position)?; self.insert(negated, instruction.destination(), position)?;
} }
Operation::Not => {
let value = self.take_constant_or_clone_register(
instruction.first_argument(),
instruction.first_argument_is_constant(),
position,
)?;
let result = value
.not()
.map_err(|error| VmError::Value { error, position })?;
self.insert(result, instruction.destination(), position)?;
}
Operation::Return => { Operation::Return => {
let value = self.pop(position)?; let value = self.pop(position)?;

View File

@ -1,5 +1,10 @@
use dust_lang::*; use dust_lang::*;
#[test]
fn long_math() {
assert_eq!(run("1 + 2 * 3 - 4 / 2"), Ok(Some(Value::integer(5))));
}
#[test] #[test]
fn add() { fn add() {
assert_eq!(run("1 + 2"), Ok(Some(Value::integer(3)))); assert_eq!(run("1 + 2"), Ok(Some(Value::integer(3))));