diff --git a/dust-lang/src/chunk.rs b/dust-lang/src/chunk.rs index 2a10247..4f38524 100644 --- a/dust-lang/src/chunk.rs +++ b/dust-lang/src/chunk.rs @@ -271,8 +271,8 @@ impl<'a> ChunkDisassembler<'a> { "", "Instructions", "------------", - "INDEX OPERATION INFO POSITION", - "----- -------------- ------------------------- --------", + "INDEX OPERATION INFO POSITION", + "----- --------------- ------------------------------ --------", ]; const CONSTANT_HEADER: [&'static str; 5] = [ @@ -293,14 +293,10 @@ impl<'a> ChunkDisassembler<'a> { /// The default width of the disassembly output. To correctly align the output, this should /// return the width of the longest line that the disassembler is guaranteed to produce. - pub fn default_width(styled: bool) -> usize { + pub fn default_width() -> usize { let longest_line = Self::INSTRUCTION_HEADER[4]; - if styled { - longest_line.bold().chars().count() - } else { - longest_line.chars().count() - } + longest_line.chars().count() } pub fn new(name: &'a str, chunk: &'a Chunk) -> Self { @@ -325,10 +321,7 @@ impl<'a> ChunkDisassembler<'a> { } pub fn disassemble(&self) -> String { - let width = self - .width - .unwrap_or_else(|| Self::default_width(self.styled)) - + 1; + let width = self.width.unwrap_or_else(Self::default_width); let center = |line: &str| format!("{line:^width$}\n"); let style = |line: String| { if self.styled { @@ -369,9 +362,9 @@ impl<'a> ChunkDisassembler<'a> { let info_option = instruction.disassembly_info(Some(self.chunk)); let instruction_display = if let Some(info) = info_option { - format!("{index:<5} {operation:14} {info:25} {position:8}") + format!("{index:<5} {operation:15} {info:30} {position:8}") } else { - format!("{index:<5} {operation:14} {:25} {position:8}", " ") + format!("{index:<5} {operation:15} {:30} {position:8}", " ") }; disassembly.push_str(¢er(&instruction_display)); @@ -462,10 +455,7 @@ impl<'a> ChunkDisassembler<'a> { let dynamic_line_count = self.chunk.instructions.len() + self.chunk.constants.len() + self.chunk.locals.len(); let total_line_count = static_line_count + dynamic_line_count; - let width = self - .width - .unwrap_or_else(|| Self::default_width(self.styled)) - + 1; + let width = self.width.unwrap_or_else(Self::default_width) + 1; total_line_count * width } diff --git a/dust-lang/src/instruction.rs b/dust-lang/src/instruction.rs index d87e190..b532795 100644 --- a/dust-lang/src/instruction.rs +++ b/dust-lang/src/instruction.rs @@ -207,6 +207,12 @@ impl Instruction { (self.0 >> 24) != 0 } + pub fn set_destination_to_boolean(&mut self, boolean: bool) -> &mut Self { + self.set_destination(if boolean { 1 } else { 0 }); + + self + } + pub fn set_destination(&mut self, destination: u8) { self.0 &= 0x00FFFFFF; self.0 |= (destination as u32) << 24; @@ -224,6 +230,12 @@ impl Instruction { self.first_argument() != 0 } + pub fn set_first_argument_to_boolean(&mut self, boolean: bool) -> &mut Self { + self.set_first_argument(if boolean { 1 } else { 0 }); + + self + } + pub fn set_first_argument_to_constant(&mut self) -> &mut Self { self.0 |= 0b1000_0000; @@ -246,6 +258,12 @@ impl Instruction { self.second_argument() != 0 } + pub fn set_second_argument_to_boolean(&mut self, boolean: bool) -> &mut Self { + self.set_second_argument(if boolean { 1 } else { 0 }); + + self + } + pub fn set_second_argument_to_constant(&mut self) -> &mut Self { self.0 |= 0b0100_0000; @@ -269,14 +287,14 @@ impl Instruction { pub fn disassembly_info(&self, chunk: Option<&Chunk>) -> Option { let format_arguments = || { let first_argument = if self.first_argument_is_constant() { - format!("C({})", self.first_argument()) + format!("C{}", self.first_argument()) } else { - format!("R({})", self.first_argument()) + format!("R{}", self.first_argument()) }; let second_argument = if self.second_argument_is_constant() { - format!("C({})", self.second_argument()) + format!("C{}", self.second_argument()) } else { - format!("R({})", self.second_argument()) + format!("R{}", self.second_argument()) }; (first_argument, second_argument) @@ -284,13 +302,13 @@ impl Instruction { let info = match self.operation() { Operation::Move => { - format!("R({}) = R({})", self.destination(), self.first_argument()) + format!("R{} = R{}", self.destination(), self.first_argument()) } Operation::Close => { let from_register = self.first_argument(); let to_register = self.second_argument().saturating_sub(1); - format!("R({from_register})..=R({to_register})") + format!("R{from_register}..=R{to_register}") } Operation::LoadBoolean => { let to_register = self.destination(); @@ -301,7 +319,7 @@ impl Instruction { "" }; - format!("R({to_register}) = {boolean} {skip_display}",) + format!("R{to_register} = {boolean} {skip_display}",) } Operation::LoadConstant => { let constant_index = self.first_argument(); @@ -309,22 +327,14 @@ impl Instruction { if let Some(chunk) = chunk { match chunk.get_constant(constant_index, Span(0, 0)) { Ok(value) => { - format!( - "R({}) = C({}) {}", - self.destination(), - constant_index, - value - ) + format!("R{} = C{} {}", self.destination(), constant_index, value) + } + Err(error) => { + format!("R{} = C{} {:?}", self.destination(), constant_index, error) } - Err(error) => format!( - "R({}) = C({}) {:?}", - self.destination(), - constant_index, - error - ), } } else { - format!("R({}) = C({})", self.destination(), constant_index) + format!("R{} = C{}", self.destination(), constant_index) } } Operation::LoadList => { @@ -332,12 +342,10 @@ impl Instruction { let first_index = destination - self.first_argument(); let last_index = destination - 1; - format!( - "R({}) = [R({})..=R({})]", - destination, first_index, last_index - ) + format!("R{} = [R{}..=R{}]", destination, first_index, last_index) } Operation::DefineLocal => { + let destination = self.destination(); let local_index = self.first_argument(); let identifier_display = if let Some(chunk) = chunk { match chunk.get_identifier(local_index) { @@ -347,18 +355,18 @@ impl Instruction { } else { "???".to_string() }; + let mutable_display = if self.second_argument_as_boolean() { + "mut " + } else { + "" + }; - format!( - "L({}) = R({}) {}", - local_index, - self.destination(), - identifier_display - ) + format!("L{local_index} = R{destination} {mutable_display}{identifier_display}") } Operation::GetLocal => { let local_index = self.first_argument(); - format!("R({}) = L({})", self.destination(), local_index) + format!("R{} = L{}", self.destination(), local_index) } Operation::SetLocal => { let local_index = self.first_argument(); @@ -372,7 +380,7 @@ impl Instruction { }; format!( - "L({}) = R({}) {}", + "L{} = R{} {}", local_index, self.destination(), identifier_display @@ -429,7 +437,7 @@ impl Instruction { let (first_argument, second_argument) = format_arguments(); - format!("if {first_argument} {comparison_symbol} {second_argument} IP++",) + format!("if {first_argument} {comparison_symbol} {second_argument} {{ IP += 1 }}",) } Operation::Less => { let comparison_symbol = if self.destination_as_boolean() { @@ -454,22 +462,22 @@ impl Instruction { Operation::Negate => { let destination = self.destination(); let argument = if self.first_argument_is_constant() { - format!("C({})", self.first_argument()) + format!("C{}", self.first_argument()) } else { - format!("R({})", self.first_argument()) + format!("R{}", self.first_argument()) }; - 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()) + format!("C{}", self.first_argument()) } else { - format!("R({})", self.first_argument()) + format!("R{}", self.first_argument()) }; - format!("R({destination}) = !{argument}") + format!("R{destination} = !{argument}") } Operation::Jump => { let offset = self.first_argument(); @@ -483,8 +491,15 @@ impl Instruction { } Operation::Return => return None, }; + let trucated_length = 30; + let with_elipsis = trucated_length - 3; + let truncated_info = if info.len() > with_elipsis { + format!("{info:. Parser<'src> { let boolean = text.parse::().unwrap(); - self.emit_instruction( - Instruction::load_boolean(self.current_register, boolean, false), - self.previous_position, - ); - self.increment_register()?; + if let Ok((last_instruction, _)) = + self.chunk.get_last_instruction(self.current_position) + { + let skip = last_instruction.operation() == Operation::Jump; + let destination = if let Operation::LoadBoolean = last_instruction.operation() { + last_instruction.destination() + } else { + self.current_register + }; + + self.emit_instruction( + Instruction::load_boolean(destination, boolean, skip), + self.previous_position, + ); + } else { + self.emit_instruction( + Instruction::load_boolean(self.current_register, boolean, false), + self.previous_position, + ); + self.increment_register()?; + } } Ok(()) @@ -419,10 +435,19 @@ impl<'src> Parser<'src> { } self.emit_instruction(instruction, operator_position); - self.increment_register()?; - if let TokenKind::DoubleEqual = operator.kind() { - self.emit_instruction(Instruction::jump(2, true), operator_position); + if operator == Token::DoubleEqual { + let distance = if push_back_left && push_back_right { + 3 + } else if push_back_left || push_back_right { + 2 + } else { + 1 + }; + + self.emit_instruction(Instruction::jump(distance, true), operator_position); + } else { + self.increment_register()?; } Ok(()) @@ -436,10 +461,20 @@ impl<'src> Parser<'src> { let token = self.current_token.to_owned(); let start_position = self.current_position; let local_index = self.parse_identifier_from(token, start_position)?; + let is_mutable = self.chunk.get_local(local_index, start_position)?.mutable; self.advance()?; if allow_assignment && self.allow(TokenKind::Equal)? { + if !is_mutable { + let identifier = self.chunk.get_identifier(local_index).cloned().unwrap(); + + return Err(ParseError::CannotMutateImmutableVariable { + identifier, + position: start_position, + }); + } + self.parse_expression()?; let (mut previous_instruction, previous_position) = @@ -510,37 +545,12 @@ impl<'src> Parser<'src> { self.advance()?; self.chunk.begin_scope(); - let start = self.current_position.0; - let start_register = self.current_register; - let mut ends_with_semicolon = false; - while !self.allow(TokenKind::RightCurlyBrace)? && !self.is_eof() { self.parse_statement()?; - - if self.previous_token == Token::Semicolon { - ends_with_semicolon = true; - } } self.chunk.end_scope(); - if self.current_token == Token::Semicolon { - ends_with_semicolon = true; - } - - let end = self.current_position.1; - - if ends_with_semicolon { - let end_register = self.current_register; - - self.emit_instruction( - Instruction::close(start_register, end_register), - Span(start, end), - ); - } else { - self.emit_instruction(Instruction::r#return(), Span(start, end)); - } - Ok(()) } @@ -582,13 +592,13 @@ impl<'src> Parser<'src> { Ok(()) } - fn parse_if(&mut self, _allow_assignment: bool) -> Result<(), ParseError> { + fn parse_if(&mut self, allow_assignment: bool) -> Result<(), ParseError> { self.advance()?; self.parse_expression()?; - self.parse_block(false)?; + self.parse_block(allow_assignment)?; if self.allow(TokenKind::Else)? { - self.parse_block(false)?; + self.parse_block(allow_assignment)?; } Ok(()) @@ -939,6 +949,10 @@ impl From<&TokenKind> for ParseRule<'_> { #[derive(Debug, PartialEq)] pub enum ParseError { + CannotMutateImmutableVariable { + identifier: Identifier, + position: Span, + }, ExpectedExpression { found: TokenOwned, position: Span, @@ -994,6 +1008,7 @@ impl AnnotatedError for ParseError { fn description(&self) -> &'static str { match self { + Self::CannotMutateImmutableVariable { .. } => "Cannot mutate immutable variable", Self::ExpectedExpression { .. } => "Expected an expression", Self::ExpectedToken { .. } => "Expected a specific token", Self::ExpectedTokenMultiple { .. } => "Expected one of multiple tokens", @@ -1010,6 +1025,9 @@ impl AnnotatedError for ParseError { fn details(&self) -> Option { match self { + Self::CannotMutateImmutableVariable { identifier, .. } => { + Some(format!("Cannot mutate immutable variable \"{identifier}\"")) + } Self::ExpectedExpression { found, .. } => Some(format!("Found \"{found}\"")), Self::ExpectedToken { expected, found, .. @@ -1034,6 +1052,7 @@ impl AnnotatedError for ParseError { fn position(&self) -> Span { match self { + Self::CannotMutateImmutableVariable { position, .. } => *position, Self::ExpectedExpression { position, .. } => *position, Self::ExpectedToken { position, .. } => *position, Self::ExpectedTokenMultiple { position, .. } => *position, diff --git a/dust-lang/src/parser/tests.rs b/dust-lang/src/parser/tests.rs index 6c93974..4cdce4f 100644 --- a/dust-lang/src/parser/tests.rs +++ b/dust-lang/src/parser/tests.rs @@ -76,10 +76,8 @@ fn block_scope() { (Instruction::define_local(1, 1, false), Span(46, 47)), (Instruction::load_constant(2, 2), Span(92, 93)), (Instruction::define_local(2, 2, false), Span(88, 89)), - (Instruction::close(2, 3), Span(84, 124)), (Instruction::load_constant(3, 3), Span(129, 130)), (Instruction::define_local(3, 3, false), Span(125, 126)), - (Instruction::close(1, 4), Span(42, 153)), (Instruction::load_constant(4, 4), Span(158, 159)), (Instruction::define_local(4, 4, false), Span(154, 155)), ], @@ -109,16 +107,16 @@ fn empty() { #[test] fn set_local() { assert_eq!( - parse("let x = 41; x = 42;"), + parse("let mut x = 41; x = 42;"), Ok(Chunk::with_data( vec![ - (Instruction::load_constant(0, 0), Span(8, 10)), - (Instruction::define_local(0, 0, false), Span(4, 5)), - (Instruction::load_constant(1, 1), Span(16, 18)), - (Instruction::set_local(1, 0), Span(12, 13)), + (Instruction::load_constant(0, 0), Span(12, 14)), + (Instruction::define_local(0, 0, true), Span(8, 9)), + (Instruction::load_constant(1, 1), Span(20, 22)), + (Instruction::set_local(1, 0), Span(16, 17)), ], vec![Value::integer(41), Value::integer(42)], - vec![Local::new(Identifier::new("x"), false, 0, Some(0)),] + vec![Local::new(Identifier::new("x"), true, 0, Some(0)),] )), ); }