Add &&, || and ! to the language; Add tests
This commit is contained in:
parent
00555785e3
commit
6ca96bc1dc
@ -99,6 +99,36 @@ impl 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 {
|
||||
let mut instruction = Instruction(Operation::Negate as u32);
|
||||
|
||||
@ -108,6 +138,15 @@ impl 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 {
|
||||
Instruction(Operation::Return as u32)
|
||||
}
|
||||
@ -306,6 +345,51 @@ impl Instruction {
|
||||
|
||||
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 => {
|
||||
let destination = self.destination();
|
||||
let argument = if self.first_argument_is_constant() {
|
||||
@ -316,6 +400,16 @@ impl Instruction {
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
@ -342,9 +436,13 @@ const SET_LOCAL: u8 = 0b0000_0101;
|
||||
const ADD: u8 = 0b0000_0110;
|
||||
const SUBTRACT: u8 = 0b0000_0111;
|
||||
const MULTIPLY: u8 = 0b0000_1000;
|
||||
const DIVIDE: u8 = 0b0000_1001;
|
||||
const NEGATE: u8 = 0b0000_1010;
|
||||
const RETURN: u8 = 0b0000_1011;
|
||||
const MODULO: u8 = 0b0000_1001;
|
||||
const AND: u8 = 0b0000_1010;
|
||||
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)]
|
||||
pub enum Operation {
|
||||
@ -365,9 +463,13 @@ pub enum Operation {
|
||||
Subtract = SUBTRACT as isize,
|
||||
Multiply = MULTIPLY as isize,
|
||||
Divide = DIVIDE as isize,
|
||||
Modulo = MODULO as isize,
|
||||
And = AND as isize,
|
||||
Or = OR as isize,
|
||||
|
||||
// Unary operations
|
||||
Negate = NEGATE as isize,
|
||||
Not = NOT as isize,
|
||||
|
||||
// Control flow
|
||||
Return = RETURN as isize,
|
||||
@ -377,7 +479,13 @@ impl Operation {
|
||||
pub fn is_binary(&self) -> bool {
|
||||
matches!(
|
||||
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,
|
||||
MULTIPLY => Operation::Multiply,
|
||||
DIVIDE => Operation::Divide,
|
||||
MODULO => Operation::Modulo,
|
||||
AND => Operation::And,
|
||||
OR => Operation::Or,
|
||||
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::Multiply => MULTIPLY,
|
||||
Operation::Divide => DIVIDE,
|
||||
Operation::Modulo => MODULO,
|
||||
Operation::And => AND,
|
||||
Operation::Or => OR,
|
||||
Operation::Negate => NEGATE,
|
||||
Operation::Not => NOT,
|
||||
Operation::Return => RETURN,
|
||||
}
|
||||
}
|
||||
@ -433,7 +550,11 @@ impl Display for Operation {
|
||||
Operation::Subtract => write!(f, "SUBTRACT"),
|
||||
Operation::Multiply => write!(f, "MULTIPLY"),
|
||||
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::Not => write!(f, "NOT"),
|
||||
Operation::Return => write!(f, "RETURN"),
|
||||
}
|
||||
}
|
||||
@ -564,6 +685,40 @@ mod tests {
|
||||
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]
|
||||
fn negate() {
|
||||
let mut instruction = Instruction::negate(0, 1);
|
||||
@ -580,6 +735,22 @@ mod tests {
|
||||
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]
|
||||
fn r#return() {
|
||||
let mut instruction = Instruction::r#return();
|
||||
|
@ -279,8 +279,8 @@ impl<'src> Parser<'src> {
|
||||
|
||||
fn parse_binary(&mut self) -> Result<(), ParseError> {
|
||||
let operator_position = self.current_position;
|
||||
let operator = self.current_token.kind();
|
||||
let rule = ParseRule::from(&operator);
|
||||
let operator = self.current_token;
|
||||
let rule = ParseRule::from(&operator.kind());
|
||||
|
||||
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::Minus => Instruction::subtract(self.current_register, left, right),
|
||||
TokenKind::Star => Instruction::multiply(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 {
|
||||
expected: vec![
|
||||
@ -343,7 +346,7 @@ impl<'src> Parser<'src> {
|
||||
TokenKind::Star,
|
||||
TokenKind::Slash,
|
||||
],
|
||||
found: self.previous_token.to_owned(),
|
||||
found: operator.to_owned(),
|
||||
position: operator_position,
|
||||
})
|
||||
}
|
||||
@ -448,6 +451,7 @@ impl<'src> Parser<'src> {
|
||||
}
|
||||
|
||||
pub fn parse_block(&mut self, _allow_assignment: bool) -> Result<(), ParseError> {
|
||||
self.advance()?;
|
||||
self.chunk.begin_scope();
|
||||
|
||||
while !self.allow(TokenKind::RightCurlyBrace)? && !self.is_eof() {
|
||||
@ -467,13 +471,11 @@ impl<'src> Parser<'src> {
|
||||
let start = self.current_position.0;
|
||||
let (is_expression_statement, contains_block) = match self.current_token {
|
||||
Token::Let => {
|
||||
self.advance()?;
|
||||
self.parse_let_statement(true)?;
|
||||
|
||||
(false, false)
|
||||
}
|
||||
Token::LeftCurlyBrace => {
|
||||
self.advance()?;
|
||||
self.parse_block(true)?;
|
||||
|
||||
(true, true)
|
||||
@ -495,7 +497,7 @@ impl<'src> Parser<'src> {
|
||||
}
|
||||
|
||||
fn parse_let_statement(&mut self, _allow_assignment: bool) -> Result<(), ParseError> {
|
||||
self.allow(TokenKind::Let)?;
|
||||
self.advance()?;
|
||||
|
||||
let position = self.current_position;
|
||||
let identifier = if let Token::Identifier(text) = self.current_token {
|
||||
@ -692,7 +694,11 @@ impl From<&TokenKind> for ParseRule<'_> {
|
||||
TokenKind::Str => todo!(),
|
||||
TokenKind::While => todo!(),
|
||||
TokenKind::BangEqual => todo!(),
|
||||
TokenKind::Bang => todo!(),
|
||||
TokenKind::Bang => ParseRule {
|
||||
prefix: Some(Parser::parse_unary),
|
||||
infix: None,
|
||||
precedence: Precedence::Unary,
|
||||
},
|
||||
TokenKind::Colon => todo!(),
|
||||
TokenKind::Comma => todo!(),
|
||||
TokenKind::Dot => todo!(),
|
||||
@ -703,7 +709,11 @@ impl From<&TokenKind> for ParseRule<'_> {
|
||||
},
|
||||
TokenKind::DoubleDot => todo!(),
|
||||
TokenKind::DoubleEqual => todo!(),
|
||||
TokenKind::DoublePipe => todo!(),
|
||||
TokenKind::DoublePipe => ParseRule {
|
||||
prefix: None,
|
||||
infix: Some(Parser::parse_binary),
|
||||
precedence: Precedence::LogicalOr,
|
||||
},
|
||||
TokenKind::Equal => ParseRule {
|
||||
prefix: None,
|
||||
infix: None,
|
||||
|
@ -148,15 +148,60 @@ fn declare_local() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constant() {
|
||||
fn and() {
|
||||
assert_eq!(
|
||||
parse("42"),
|
||||
parse("1 && 2"),
|
||||
Ok(Chunk::with_data(
|
||||
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![]
|
||||
))
|
||||
);
|
||||
@ -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![]
|
||||
))
|
||||
);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
/// Source code token.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Token<'src> {
|
||||
// End of file
|
||||
Eof,
|
||||
|
@ -149,6 +149,57 @@ impl Vm {
|
||||
|
||||
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 => {
|
||||
let value = self.take_constant_or_clone_register(
|
||||
instruction.first_argument(),
|
||||
@ -161,6 +212,18 @@ impl Vm {
|
||||
|
||||
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 => {
|
||||
let value = self.pop(position)?;
|
||||
|
||||
|
@ -1,5 +1,10 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn long_math() {
|
||||
assert_eq!(run("1 + 2 * 3 - 4 / 2"), Ok(Some(Value::integer(5))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add() {
|
||||
assert_eq!(run("1 + 2"), Ok(Some(Value::integer(3))));
|
||||
|
Loading…
Reference in New Issue
Block a user