1
0

Fix some aspects of the compiler for logic and semicolons

This commit is contained in:
Jeff 2024-12-17 18:11:06 -05:00
parent 72365cd399
commit d7289414f4
5 changed files with 69 additions and 115 deletions

View File

@ -27,7 +27,7 @@ use std::io::Write;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use crate::vm::{Record, Register, RunAction};
use crate::vm::{Record, RunAction};
use crate::{DustString, Function, FunctionType, Instruction, Span, Value};
/// Representation of a Dust program or function.

View File

@ -7,6 +7,17 @@
//! - [`Compiler`] is created with a [`Lexer`] and protentially emits a [`CompileError`] or
//! [`LexError`] if the input is invalid. Allows passing a name for the main chunk when
//! [`Compiler::finish`] is called.
//!
//! # Errors
//!
//! The compiler can return errors due to:
//! - Lexing errors
//! - Parsing errors
//! - Type conflicts
//!
//! It is a logic error to call [`Compiler::finish`] on a compiler that has emitted an error and
//! pass that chunk to the VM. Otherwise, if the compiler gives no errors and the VM encounters a
//! runtime error, it is the compiler's fault and the error should be fixed here.
mod error;
mod optimize;
@ -22,9 +33,7 @@ use optimize::control_flow_register_consolidation;
use smallvec::{smallvec, SmallVec};
use crate::{
instruction::{
CallNative, Close, GetLocal, Jump, LoadList, Negate, Not, Return, SetLocal, Test,
},
instruction::{CallNative, Close, GetLocal, Jump, LoadList, Negate, Not, Return, SetLocal},
Argument, Chunk, ConcreteValue, DustError, DustString, FunctionType, Instruction, Lexer, Local,
NativeFunction, Operation, Scope, Span, Token, TokenKind, Type, Value,
};
@ -192,9 +201,9 @@ impl<'src> Compiler<'src> {
)
}
/// Compiles the source while checking for errors and returning a [`CompileError`] if any are
/// found. After calling this function, check its return value for an error, then call
/// [`Compiler::finish`] to get the compiled chunk.
/// Compiles the source (which is in the lexer) while checking for errors and returning a
/// [`CompileError`] if any are found. After calling this function, check its return value for
/// an error, then call [`Compiler::finish`] to get the compiled chunk.
pub fn compile(&mut self) -> Result<(), CompileError> {
loop {
self.parse(Precedence::None)?;
@ -351,15 +360,6 @@ impl<'src> Compiler<'src> {
}
}
fn pop_last_instruction(&mut self) -> Result<(Instruction, Type, Span), CompileError> {
self.instructions
.pop()
.ok_or_else(|| CompileError::ExpectedExpression {
found: self.previous_token.to_owned(),
position: self.previous_position,
})
}
fn get_last_operations<const COUNT: usize>(&self) -> Option<[Operation; COUNT]> {
let mut n_operations = [Operation::RETURN; COUNT];
@ -628,7 +628,7 @@ impl<'src> Compiler<'src> {
self.parse_expression()?;
let (previous_instruction, previous_type, previous_position) =
self.pop_last_instruction()?;
self.instructions.pop().unwrap();
let (argument, push_back) = self.handle_binary_argument(&previous_instruction)?;
if push_back {
@ -759,7 +759,7 @@ impl<'src> Compiler<'src> {
self.advance()?;
self.parse_sub_expression(&rule.precedence)?;
let (right_instruction, right_type, right_position) = self.pop_last_instruction()?;
let (right_instruction, right_type, right_position) = self.instructions.pop().unwrap();
let (right, push_back_right) = self.handle_binary_argument(&right_instruction)?;
match operator {
@ -948,44 +948,17 @@ impl<'src> Compiler<'src> {
fn parse_logical_binary(&mut self) -> Result<(), CompileError> {
let is_logic_chain = matches!(
self.get_last_operations(),
Some([Operation::TEST, Operation::JUMP, _])
Some([_, Operation::TEST, Operation::JUMP, _])
);
let jump_over_index = self.instructions.len() - 2;
let jump_over = if is_logic_chain {
let (instruction, _, _) = self.instructions.remove(jump_over_index);
let (left_instruction, left_type, left_position) = self.pop_last_instruction()?;
let (left, _) = self.handle_binary_argument(&left_instruction)?;
// if is_logic_chain {
// let destination = self
// .instructions
// .iter()
// .rev()
// .nth(2)
// .map_or(0, |(instruction, _, _)| instruction.a_field());
// left_instruction.set_a_field(destination);
// }
if !left_instruction.yields_value() {
return Err(CompileError::ExpectedExpression {
found: self.previous_token.to_owned(),
position: self.previous_position,
});
}
// self.instructions
// .push((left_instruction, left_type.clone(), left_position));
// let short_circuit_jump_index = self.instructions.len();
// let mut short_circuit_jump_distance = if is_logic_chain {
// self.instructions.pop().map_or(0, |(jump, _, _)| {
// let Jump { offset, .. } = Jump::from(&jump);
// offset
// })
// } else {
// 1
// };
Some(instruction)
} else {
None
};
let operand_register = self.next_register() - 1;
let operator = self.current_token;
let operator_position = self.current_position;
let rule = ParseRule::from(&operator);
@ -1000,7 +973,7 @@ impl<'src> Compiler<'src> {
})
}
};
let test = Instruction::test(left, test_boolean);
let test = Instruction::test(operand_register, test_boolean);
self.advance()?;
self.emit_instruction(test, Type::None, operator_position);
@ -1015,9 +988,21 @@ impl<'src> Compiler<'src> {
self.instructions
.insert(jump_index, (jump, Type::None, operator_position));
let (mut right_instruction, _, _) = self.instructions.last_mut().unwrap();
if let Some(jump_over) = jump_over {
let is_end_of_context = matches!(self.current_token, Token::RightBrace | Token::Eof);
let jump_over_distance =
jump_over.b_field() + (self.instructions.len() - jump_over_index) as u8 - {
if is_end_of_context {
1
} else {
0
}
};
let jump_over = Instruction::jump(jump_over_distance, true);
right_instruction.set_a_field(left_instruction.a_field());
self.instructions
.insert(jump_over_index, (jump_over, Type::None, operator_position));
}
// short_circuit_jump_distance += (self.instructions.len() - short_circuit_jump_index) as u8;
// let jump = Instruction::jump(short_circuit_jump_distance, true);
@ -1201,20 +1186,8 @@ impl<'src> Compiler<'src> {
self.advance()?;
self.parse_expression()?;
let (last_instruction, _, _) = self.instructions.last().unwrap();
let argument = match last_instruction.as_argument() {
Some(argument) => argument,
None => {
return Err(CompileError::ExpectedExpression {
found: self.previous_token.to_owned(),
position: self.previous_position,
});
}
};
let test = Instruction::from(Test {
argument,
test_value: true,
});
let operand_register = self.next_register() - 1;
let test = Instruction::test(operand_register, true);
self.emit_instruction(test, Type::None, self.current_position);
@ -1334,27 +1307,8 @@ impl<'src> Compiler<'src> {
self.instructions.pop();
self.instructions.pop();
} else {
let (expression_instruction, expression_type, expression_position) =
self.instructions.last().unwrap();
if expression_type != &Type::Boolean {
return Err(CompileError::ExpectedFunction {
found: self.previous_token.to_owned(),
actual_type: expression_type.clone(),
position: *expression_position,
});
}
let test_argument = match expression_instruction.as_argument() {
Some(argument) => argument,
None => {
return Err(CompileError::ExpectedExpression {
found: self.previous_token.to_owned(),
position: *expression_position,
})
}
};
let test = Instruction::test(test_argument, true);
let operand_register = self.next_register() - 1;
let test = Instruction::test(operand_register, true);
self.emit_instruction(test, Type::None, self.current_position);
}
@ -1427,8 +1381,7 @@ impl<'src> Compiler<'src> {
fn parse_semicolon(&mut self) -> Result<(), CompileError> {
self.advance()?;
Ok(())
self.parse(Precedence::None)
}
fn parse_expression(&mut self) -> Result<(), CompileError> {

View File

@ -327,9 +327,9 @@ impl Instruction {
})
}
pub fn test(argument: Argument, value: bool) -> Instruction {
pub fn test(operand_register: u8, value: bool) -> Instruction {
Instruction::from(Test {
argument,
operand_register,
test_value: value,
})
}
@ -622,12 +622,12 @@ impl Instruction {
}
Operation::TEST => {
let Test {
argument,
test_value: value,
operand_register,
test_value,
} = Test::from(self);
let bang = if value { "" } else { "!" };
let bang = if test_value { "" } else { "!" };
format!("if {bang}{argument} {{ JUMP +1 }}",)
format!("if {bang}R{operand_register} {{ JUMP +1 }}",)
}
Operation::TEST_SET => {
let TestSet {

View File

@ -1,17 +1,17 @@
use crate::{Argument, Instruction, Operation};
use crate::{Instruction, Operation};
pub struct Test {
pub argument: Argument,
pub operand_register: u8,
pub test_value: bool,
}
impl From<&Instruction> for Test {
fn from(instruction: &Instruction) -> Self {
let argument = instruction.b_as_argument();
let operand_register = instruction.b_field();
let test_value = instruction.c_field() != 0;
Test {
argument,
operand_register,
test_value,
}
}
@ -19,10 +19,14 @@ impl From<&Instruction> for Test {
impl From<Test> for Instruction {
fn from(test: Test) -> Self {
let operation = Operation::TEST;
let (b, b_is_constant) = test.argument.as_index_and_constant_flag();
let c = test.test_value as u8;
Instruction::new(operation, 0, b, c, b_is_constant, false, false)
Instruction::new(
Operation::TEST,
0,
test.operand_register,
test.test_value as u8,
false,
false,
false,
)
}
}

View File

@ -356,18 +356,15 @@ pub fn modulo(instruction_data: InstructionData, record: &mut Record) -> ThreadS
pub fn test(instruction_data: InstructionData, record: &mut Record) -> ThreadSignal {
let InstructionData {
b_field: b,
b_is_constant,
c_field: c,
..
b_field, c_field, ..
} = instruction_data;
let value = record.get_argument(b, b_is_constant);
let value = record.open_register(b_field);
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) = value {
*boolean
} else {
panic!("VM Error: Expected boolean value for TEST operation",);
};
let test_value = c != 0;
let test_value = c_field != 0;
if boolean == test_value {
record.ip += 1;