Begin implementing control flow with if/else expressions

This commit is contained in:
Jeff 2024-09-18 16:43:34 -04:00
parent 915340fbdb
commit 413cb70731
4 changed files with 264 additions and 61 deletions

View File

@ -150,6 +150,16 @@ impl Instruction {
instruction instruction
} }
pub fn equal(comparison_boolean: bool, left_index: u8, right_index: u8) -> Instruction {
let mut instruction = Instruction(Operation::Equal as u32);
instruction.set_destination(if comparison_boolean { 1 } else { 0 });
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);
@ -168,34 +178,35 @@ impl Instruction {
instruction instruction
} }
pub fn jump(offset: u8, is_positive: bool) -> Instruction {
let mut instruction = Instruction(Operation::Jump as u32);
instruction.set_first_argument(offset);
instruction.set_second_argument(if is_positive { 1 } else { 0 });
instruction
}
pub fn r#return() -> Instruction { pub fn r#return() -> Instruction {
Instruction(Operation::Return as u32) Instruction(Operation::Return as u32)
} }
pub fn set_first_argument_to_constant(&mut self) -> &mut Self { pub fn operation(&self) -> Operation {
self.0 |= 0b1000_0000; Operation::from((self.0 & 0b0000_0000_0011_1111) as u8)
self
} }
pub fn first_argument_is_constant(&self) -> bool { pub fn set_operation(&mut self, operation: Operation) {
self.0 & 0b1000_0000 != 0 self.0 |= u8::from(operation) as u32;
}
pub fn set_second_argument_to_constant(&mut self) -> &mut Self {
self.0 |= 0b0100_0000;
self
}
pub fn second_argument_is_constant(&self) -> bool {
self.0 & 0b0100_0000 != 0
} }
pub fn destination(&self) -> u8 { pub fn destination(&self) -> u8 {
(self.0 >> 24) as u8 (self.0 >> 24) as u8
} }
pub fn destination_as_boolean(&self) -> bool {
(self.0 >> 24) != 0
}
pub fn set_destination(&mut self, destination: u8) { pub fn set_destination(&mut self, destination: u8) {
self.0 &= 0x00FFFFFF; self.0 &= 0x00FFFFFF;
self.0 |= (destination as u32) << 24; self.0 |= (destination as u32) << 24;
@ -205,6 +216,20 @@ impl Instruction {
(self.0 >> 16) as u8 (self.0 >> 16) as u8
} }
pub fn first_argument_is_constant(&self) -> bool {
self.0 & 0b1000_0000 != 0
}
pub fn first_argument_as_boolean(&self) -> bool {
self.first_argument() != 0
}
pub fn set_first_argument_to_constant(&mut self) -> &mut Self {
self.0 |= 0b1000_0000;
self
}
pub fn set_first_argument(&mut self, argument: u8) { pub fn set_first_argument(&mut self, argument: u8) {
self.0 |= (argument as u32) << 16; self.0 |= (argument as u32) << 16;
} }
@ -213,18 +238,24 @@ impl Instruction {
(self.0 >> 8) as u8 (self.0 >> 8) as u8
} }
pub fn second_argument_is_constant(&self) -> bool {
self.0 & 0b0100_0000 != 0
}
pub fn second_argument_as_boolean(&self) -> bool {
self.second_argument() != 0
}
pub fn set_second_argument_to_constant(&mut self) -> &mut Self {
self.0 |= 0b0100_0000;
self
}
pub fn set_second_argument(&mut self, argument: u8) { pub fn set_second_argument(&mut self, argument: u8) {
self.0 |= (argument as u32) << 8; self.0 |= (argument as u32) << 8;
} }
pub fn operation(&self) -> Operation {
Operation::from((self.0 & 0b0000_0000_0011_1111) as u8)
}
pub fn set_operation(&mut self, operation: Operation) {
self.0 |= u8::from(operation) as u32;
}
pub fn disassemble(&self, chunk: &Chunk) -> String { pub fn disassemble(&self, chunk: &Chunk) -> String {
let mut disassembled = format!("{:16} ", self.operation().to_string()); let mut disassembled = format!("{:16} ", self.operation().to_string());
@ -263,14 +294,14 @@ impl Instruction {
} }
Operation::LoadBoolean => { Operation::LoadBoolean => {
let to_register = self.destination(); let to_register = self.destination();
let boolean = if self.first_argument() == 0 { let boolean = self.first_argument_as_boolean();
"false" let skip_display = if self.second_argument_as_boolean() {
"IP++"
} else { } else {
"true" ""
}; };
let skip = self.second_argument() != 0;
format!("R({to_register}) = {boolean}; if {skip} ip++",) format!("R({to_register}) = {boolean} {skip_display}",)
} }
Operation::LoadConstant => { Operation::LoadConstant => {
let constant_index = self.first_argument(); let constant_index = self.first_argument();
@ -389,6 +420,37 @@ impl Instruction {
format!("R({destination}) = {first_argument} || {second_argument}",) format!("R({destination}) = {first_argument} || {second_argument}",)
} }
Operation::Equal => {
let comparison_symbol = if self.destination_as_boolean() {
"=="
} else {
"!="
};
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} IP++",)
}
Operation::Less => {
let comparison_symbol = if self.destination_as_boolean() {
"<"
} else {
">="
};
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} IP++",)
}
Operation::LessEqual => {
let comparison_symbol = if self.destination_as_boolean() {
"<="
} else {
">"
};
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} IP++",)
}
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() {
@ -409,6 +471,16 @@ impl Instruction {
format!("R({destination}) = !{argument}") format!("R({destination}) = !{argument}")
} }
Operation::Jump => {
let offset = self.first_argument();
let positive = self.second_argument() != 0;
if positive {
format!("IP += {}", offset)
} else {
format!("IP -= {}", offset)
}
}
Operation::Return => return None, Operation::Return => return None,
}; };
@ -453,6 +525,16 @@ mod tests {
assert_eq!(instruction.second_argument(), 2); assert_eq!(instruction.second_argument(), 2);
} }
#[test]
fn load_boolean() {
let instruction = Instruction::load_boolean(4, true, true);
assert_eq!(instruction.operation(), Operation::LoadBoolean);
assert_eq!(instruction.destination(), 4);
assert!(instruction.first_argument_as_boolean());
assert!(instruction.second_argument_as_boolean());
}
#[test] #[test]
fn load_constant() { fn load_constant() {
let mut instruction = Instruction::load_constant(0, 1); let mut instruction = Instruction::load_constant(0, 1);
@ -484,8 +566,6 @@ mod tests {
fn add() { fn add() {
let mut instruction = Instruction::add(1, 1, 0); let mut instruction = Instruction::add(1, 1, 0);
instruction.set_operation(Operation::Add);
instruction.set_first_argument_to_constant(); instruction.set_first_argument_to_constant();
assert_eq!(instruction.operation(), Operation::Add); assert_eq!(instruction.operation(), Operation::Add);
@ -499,8 +579,6 @@ mod tests {
fn subtract() { fn subtract() {
let mut instruction = Instruction::subtract(0, 1, 2); let mut instruction = Instruction::subtract(0, 1, 2);
instruction.set_operation(Operation::Subtract);
instruction.set_first_argument_to_constant(); instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant(); instruction.set_second_argument_to_constant();
@ -516,8 +594,6 @@ mod tests {
fn multiply() { fn multiply() {
let mut instruction = Instruction::multiply(0, 1, 2); let mut instruction = Instruction::multiply(0, 1, 2);
instruction.set_operation(Operation::Multiply);
instruction.set_first_argument_to_constant(); instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant(); instruction.set_second_argument_to_constant();
@ -533,8 +609,6 @@ mod tests {
fn divide() { fn divide() {
let mut instruction = Instruction::divide(0, 1, 2); let mut instruction = Instruction::divide(0, 1, 2);
instruction.set_operation(Operation::Divide);
instruction.set_first_argument_to_constant(); instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant(); instruction.set_second_argument_to_constant();
@ -550,8 +624,6 @@ mod tests {
fn and() { fn and() {
let mut instruction = Instruction::and(0, 1, 2); let mut instruction = Instruction::and(0, 1, 2);
instruction.set_operation(Operation::And);
instruction.set_first_argument_to_constant(); instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant(); instruction.set_second_argument_to_constant();
@ -567,8 +639,6 @@ mod tests {
fn or() { fn or() {
let mut instruction = Instruction::or(0, 1, 2); let mut instruction = Instruction::or(0, 1, 2);
instruction.set_operation(Operation::Or);
instruction.set_first_argument_to_constant(); instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant(); instruction.set_second_argument_to_constant();
@ -580,12 +650,25 @@ mod tests {
assert!(instruction.second_argument_is_constant()); assert!(instruction.second_argument_is_constant());
} }
#[test]
fn equal() {
let mut instruction = Instruction::equal(true, 1, 2);
instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant();
assert_eq!(instruction.operation(), Operation::Equal);
assert!(instruction.destination_as_boolean());
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);
instruction.set_operation(Operation::Negate);
instruction.set_first_argument_to_constant(); instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant(); instruction.set_second_argument_to_constant();
@ -600,8 +683,6 @@ mod tests {
fn not() { fn not() {
let mut instruction = Instruction::not(0, 1); let mut instruction = Instruction::not(0, 1);
instruction.set_operation(Operation::Not);
instruction.set_first_argument_to_constant(); instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant(); instruction.set_second_argument_to_constant();
@ -612,12 +693,22 @@ mod tests {
assert!(instruction.second_argument_is_constant()); assert!(instruction.second_argument_is_constant());
} }
#[test]
fn jump() {
let mut instruction = Instruction::jump(4, true);
instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant();
assert_eq!(instruction.operation(), Operation::Jump);
assert_eq!(instruction.first_argument(), 4);
assert!(instruction.first_argument_as_boolean());
}
#[test] #[test]
fn r#return() { fn r#return() {
let mut instruction = Instruction::r#return(); let mut instruction = Instruction::r#return();
instruction.set_operation(Operation::Return);
instruction.set_first_argument_to_constant(); instruction.set_first_argument_to_constant();
instruction.set_second_argument_to_constant(); instruction.set_second_argument_to_constant();

View File

@ -2,22 +2,32 @@ use std::fmt::{self, Display, Formatter};
const MOVE: u8 = 0b0000_0000; const MOVE: u8 = 0b0000_0000;
const CLOSE: u8 = 0b000_0001; const CLOSE: u8 = 0b000_0001;
const LOAD_BOOLEAN: u8 = 0b0000_0010; const LOAD_BOOLEAN: u8 = 0b0000_0010;
const LOAD_CONSTANT: u8 = 0b0000_0011; const LOAD_CONSTANT: u8 = 0b0000_0011;
const LOAD_LIST: u8 = 0b0000_0100; const LOAD_LIST: u8 = 0b0000_0100;
const DECLARE_LOCAL: u8 = 0b0000_0101; const DECLARE_LOCAL: u8 = 0b0000_0101;
const GET_LOCAL: u8 = 0b0000_0110; const GET_LOCAL: u8 = 0b0000_0110;
const SET_LOCAL: u8 = 0b0000_0111; const SET_LOCAL: u8 = 0b0000_0111;
const ADD: u8 = 0b0000_1000; const ADD: u8 = 0b0000_1000;
const SUBTRACT: u8 = 0b0000_1001; const SUBTRACT: u8 = 0b0000_1001;
const MULTIPLY: u8 = 0b0000_1010; const MULTIPLY: u8 = 0b0000_1010;
const MODULO: u8 = 0b0000_1011; const DIVIDE: u8 = 0b0000_1011;
const AND: u8 = 0b0000_1100; const MODULO: u8 = 0b0000_1100;
const OR: u8 = 0b0000_1101; const AND: u8 = 0b0000_1101;
const DIVIDE: u8 = 0b0000_1110; const OR: u8 = 0b0000_1110;
const NEGATE: u8 = 0b0000_1111;
const NOT: u8 = 0b0001_0000; const EQUAL: u8 = 0b0000_1111;
const RETURN: u8 = 0b0001_0001; const LESS: u8 = 0b0001_0000;
const LESS_EQUAL: u8 = 0b0001_0001;
const NEGATE: u8 = 0b0001_0010;
const NOT: u8 = 0b0001_0011;
const JUMP: u8 = 0b0001_0100;
const RETURN: u8 = 0b0001_0101;
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum Operation { pub enum Operation {
@ -44,11 +54,17 @@ pub enum Operation {
And = AND as isize, And = AND as isize,
Or = OR as isize, Or = OR as isize,
// Relational operations
Equal = EQUAL as isize,
Less = LESS as isize,
LessEqual = LESS_EQUAL as isize,
// Unary operations // Unary operations
Negate = NEGATE as isize, Negate = NEGATE as isize,
Not = NOT as isize, Not = NOT as isize,
// Control flow // Control flow
Jump = JUMP as isize,
Return = RETURN as isize, Return = RETURN as isize,
} }
@ -85,8 +101,12 @@ impl From<u8> for Operation {
MODULO => Operation::Modulo, MODULO => Operation::Modulo,
AND => Operation::And, AND => Operation::And,
OR => Operation::Or, OR => Operation::Or,
EQUAL => Operation::Equal,
LESS => Operation::Less,
LESS_EQUAL => Operation::LessEqual,
NEGATE => Operation::Negate, NEGATE => Operation::Negate,
NOT => Operation::Not, NOT => Operation::Not,
JUMP => Operation::Jump,
RETURN => Operation::Return, RETURN => Operation::Return,
_ => { _ => {
if cfg!(test) { if cfg!(test) {
@ -117,8 +137,12 @@ impl From<Operation> for u8 {
Operation::Modulo => MODULO, Operation::Modulo => MODULO,
Operation::And => AND, Operation::And => AND,
Operation::Or => OR, Operation::Or => OR,
Operation::Equal => EQUAL,
Operation::Less => LESS,
Operation::LessEqual => LESS_EQUAL,
Operation::Negate => NEGATE, Operation::Negate => NEGATE,
Operation::Not => NOT, Operation::Not => NOT,
Operation::Jump => JUMP,
Operation::Return => RETURN, Operation::Return => RETURN,
} }
} }
@ -142,8 +166,12 @@ impl Display for Operation {
Operation::Modulo => write!(f, "MODULO"), Operation::Modulo => write!(f, "MODULO"),
Operation::And => write!(f, "AND"), Operation::And => write!(f, "AND"),
Operation::Or => write!(f, "OR"), Operation::Or => write!(f, "OR"),
Operation::Equal => write!(f, "EQUAL"),
Operation::Less => write!(f, "LESS"),
Operation::LessEqual => write!(f, "LESS_EQUAL"),
Operation::Negate => write!(f, "NEGATE"), Operation::Negate => write!(f, "NEGATE"),
Operation::Not => write!(f, "NOT"), Operation::Not => write!(f, "NOT"),
Operation::Jump => write!(f, "JUMP"),
Operation::Return => write!(f, "RETURN"), Operation::Return => write!(f, "RETURN"),
} }
} }

View File

@ -153,6 +153,7 @@ impl<'src> Parser<'src> {
Instruction::load_boolean(self.current_register, boolean, false), Instruction::load_boolean(self.current_register, boolean, false),
self.previous_position, self.previous_position,
); );
self.increment_register()?;
} }
Ok(()) Ok(())
@ -382,6 +383,7 @@ impl<'src> Parser<'src> {
TokenKind::Percent => Instruction::modulo(self.current_register, left, right), TokenKind::Percent => Instruction::modulo(self.current_register, left, right),
TokenKind::DoubleAmpersand => Instruction::and(self.current_register, left, right), TokenKind::DoubleAmpersand => Instruction::and(self.current_register, left, right),
TokenKind::DoublePipe => Instruction::or(self.current_register, left, right), TokenKind::DoublePipe => Instruction::or(self.current_register, left, right),
TokenKind::DoubleEqual => Instruction::equal(true, left, right),
_ => { _ => {
return Err(ParseError::ExpectedTokenMultiple { return Err(ParseError::ExpectedTokenMultiple {
expected: &[ expected: &[
@ -392,6 +394,7 @@ impl<'src> Parser<'src> {
TokenKind::Percent, TokenKind::Percent,
TokenKind::DoubleAmpersand, TokenKind::DoubleAmpersand,
TokenKind::DoublePipe, TokenKind::DoublePipe,
TokenKind::DoubleEqual,
], ],
found: operator.to_owned(), found: operator.to_owned(),
position: operator_position, position: operator_position,
@ -418,6 +421,10 @@ impl<'src> Parser<'src> {
self.emit_instruction(instruction, operator_position); self.emit_instruction(instruction, operator_position);
self.increment_register()?; self.increment_register()?;
if let TokenKind::DoubleEqual = operator.kind() {
self.emit_instruction(Instruction::jump(2, true), operator_position);
}
Ok(()) Ok(())
} }
@ -521,14 +528,17 @@ impl<'src> Parser<'src> {
ends_with_semicolon = true; ends_with_semicolon = true;
} }
let end = self.current_position.1;
if ends_with_semicolon { if ends_with_semicolon {
let end = self.current_position.1;
let end_register = self.current_register; let end_register = self.current_register;
self.emit_instruction( self.emit_instruction(
Instruction::close(start_register, end_register), Instruction::close(start_register, end_register),
Span(start, end), Span(start, end),
); );
} else {
self.emit_instruction(Instruction::r#return(), Span(start, end));
} }
Ok(()) Ok(())
@ -572,6 +582,18 @@ impl<'src> Parser<'src> {
Ok(()) Ok(())
} }
fn parse_if(&mut self, _allow_assignment: bool) -> Result<(), ParseError> {
self.advance()?;
self.parse_expression()?;
self.parse_block(false)?;
if self.allow(TokenKind::Else)? {
self.parse_block(false)?;
}
Ok(())
}
fn parse_expression(&mut self) -> Result<(), ParseError> { fn parse_expression(&mut self) -> Result<(), ParseError> {
self.parse(Precedence::None) self.parse(Precedence::None)
} }
@ -784,9 +806,17 @@ impl From<&TokenKind> for ParseRule<'_> {
TokenKind::Async => todo!(), TokenKind::Async => todo!(),
TokenKind::Bool => todo!(), TokenKind::Bool => todo!(),
TokenKind::Break => todo!(), TokenKind::Break => todo!(),
TokenKind::Else => todo!(), TokenKind::Else => ParseRule {
prefix: None,
infix: None,
precedence: Precedence::None,
},
TokenKind::FloatKeyword => todo!(), TokenKind::FloatKeyword => todo!(),
TokenKind::If => todo!(), TokenKind::If => ParseRule {
prefix: Some(Parser::parse_if),
infix: None,
precedence: Precedence::None,
},
TokenKind::Int => todo!(), TokenKind::Int => todo!(),
TokenKind::Let => ParseRule { TokenKind::Let => ParseRule {
prefix: Some(Parser::parse_let_statement), prefix: Some(Parser::parse_let_statement),
@ -816,7 +846,11 @@ impl From<&TokenKind> for ParseRule<'_> {
precedence: Precedence::LogicalAnd, precedence: Precedence::LogicalAnd,
}, },
TokenKind::DoubleDot => todo!(), TokenKind::DoubleDot => todo!(),
TokenKind::DoubleEqual => todo!(), TokenKind::DoubleEqual => ParseRule {
prefix: None,
infix: Some(Parser::parse_binary),
precedence: Precedence::Equality,
},
TokenKind::DoublePipe => ParseRule { TokenKind::DoublePipe => ParseRule {
prefix: None, prefix: None,
infix: Some(Parser::parse_binary), infix: Some(Parser::parse_binary),

View File

@ -77,13 +77,13 @@ impl Vm {
} }
Operation::LoadBoolean => { Operation::LoadBoolean => {
let to_register = instruction.destination(); let to_register = instruction.destination();
let boolean = instruction.first_argument() != 0; let boolean = instruction.first_argument_as_boolean();
let skip = instruction.second_argument() != 0; let skip = instruction.second_argument_as_boolean();
let value = Value::boolean(boolean); let value = Value::boolean(boolean);
self.insert(value, to_register, position)?; self.insert(value, to_register, position)?;
if skip { if boolean && skip {
self.ip += 1; self.ip += 1;
} }
} }
@ -201,6 +201,45 @@ impl Vm {
self.insert(or, instruction.destination(), position)?; self.insert(or, instruction.destination(), position)?;
} }
Operation::Equal => {
let (left, right) = take_constants_or_clone(self, instruction, position)?;
let equal = left
.equal(&right)
.map_err(|error| VmError::Value { error, position })?;
let compare_to = instruction.destination_as_boolean();
if let Some(boolean) = equal.as_boolean() {
if boolean == compare_to {
self.ip += 1;
}
}
}
Operation::Less => {
let (left, right) = take_constants_or_clone(self, instruction, position)?;
let less = left
.less_than(&right)
.map_err(|error| VmError::Value { error, position })?;
let compare_to = instruction.destination() != 0;
if let Some(boolean) = less.as_boolean() {
if boolean != compare_to {
self.ip += 1;
}
}
}
Operation::LessEqual => {
let (left, right) = take_constants_or_clone(self, instruction, position)?;
let less_equal = left
.less_than_or_equal(&right)
.map_err(|error| VmError::Value { error, position })?;
let compare_to = instruction.destination() != 0;
if let Some(boolean) = less_equal.as_boolean() {
if boolean != compare_to {
self.ip += 1;
}
}
}
Operation::Negate => { Operation::Negate => {
let value = if instruction.first_argument_is_constant() { let value = if instruction.first_argument_is_constant() {
self.chunk self.chunk
@ -227,6 +266,17 @@ impl Vm {
self.insert(not, instruction.destination(), position)?; self.insert(not, instruction.destination(), position)?;
} }
Operation::Jump => {
let offset = instruction.first_argument();
let is_positive = instruction.second_argument_as_boolean();
let new_ip = if is_positive {
self.ip + offset as usize
} else {
self.ip - offset as usize
};
self.ip = new_ip;
}
Operation::Return => { Operation::Return => {
let value = self.pop(position)?; let value = self.pop(position)?;