From 314913dbf5fb1825da2f24ee8e13502b5d22b954 Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 5 Nov 2024 23:20:58 -0500 Subject: [PATCH] Begin adding type evaluation to the parser --- dust-lang/src/chunk.rs | 68 +++++++++++++++++++--- dust-lang/src/instruction.rs | 106 +++++++++++++++++++---------------- dust-lang/src/vm.rs | 2 +- dust-lang/tests/math.rs | 16 ++++++ 4 files changed, 135 insertions(+), 57 deletions(-) diff --git a/dust-lang/src/chunk.rs b/dust-lang/src/chunk.rs index 132f3cb..dd88711 100644 --- a/dust-lang/src/chunk.rs +++ b/dust-lang/src/chunk.rs @@ -153,6 +153,46 @@ impl Chunk { } } + pub fn get_constant_type(&self, constant_index: u8) -> Option { + self.constants + .get(constant_index as usize) + .map(|value| value.r#type()) + } + + pub fn get_local_type(&self, local_index: u8) -> Option { + self.locals.get(local_index as usize)?.r#type.clone() + } + + pub fn get_register_type(&self, register_index: u8) -> Option { + let local_type_option = self + .locals + .iter() + .find(|local| local.register_index == register_index) + .map(|local| local.r#type.clone()); + + if let Some(local_type) = local_type_option { + return local_type; + } + + self.instructions.iter().find_map(|(instruction, _)| { + if instruction.yields_value() && instruction.a() == register_index { + instruction.yielded_type(self) + } else { + None + } + }) + } + + pub fn return_type(&self) -> Option { + self.instructions.iter().rev().find_map(|(instruction, _)| { + if instruction.yields_value() { + instruction.yielded_type(self) + } else { + None + } + }) + } + pub fn disassembler(&self) -> ChunkDisassembler { ChunkDisassembler::new(self) } @@ -259,8 +299,8 @@ impl<'a> ChunkDisassembler<'a> { const INSTRUCTION_HEADER: [&'static str; 4] = [ "Instructions", "------------", - "INDEX BYTECODE OPERATION INFO POSITION ", - "----- -------- --------------- ------------------------- -------------", + "INDEX BYTECODE OPERATION INFO TYPE POSITION ", + "----- -------- ------------- ------------------------- --------- -----------", ]; const CONSTANT_HEADER: [&'static str; 4] = @@ -427,10 +467,14 @@ impl<'a> ChunkDisassembler<'a> { self.push_header(&name_display); let info_line = format!( - "{} instructions, {} constants, {} locals", + "{} instructions, {} constants, {} locals, returns {}", self.chunk.instructions.len(), self.chunk.constants.len(), - self.chunk.locals.len() + self.chunk.locals.len(), + self.chunk + .return_type() + .map(|r#type| r#type.to_string()) + .unwrap_or("none".to_string()) ); self.push(&info_line, true, false, false, true); @@ -440,12 +484,18 @@ impl<'a> ChunkDisassembler<'a> { } for (index, (instruction, position)) in self.chunk.instructions.iter().enumerate() { - let position = position.to_string(); - let operation = instruction.operation().to_string(); - let info = instruction.disassembly_info(Some(self.chunk)); let bytecode = u32::from(instruction); - let instruction_display = - format!("{index:<5} {bytecode:<08X} {operation:15} {info:25} {position:13}"); + let operation = instruction.operation().to_string(); + let info = instruction.disassembly_info(self.chunk); + let type_display = instruction + .yielded_type(self.chunk) + .map(|r#type| r#type.to_string()) + .unwrap_or(String::with_capacity(0)); + let position = position.to_string(); + + let instruction_display = format!( + "{index:<5} {bytecode:08X} {operation:13} {info:25} {type_display:9} {position:11}" + ); self.push_details(&instruction_display); } diff --git a/dust-lang/src/instruction.rs b/dust-lang/src/instruction.rs index cbd82b6..85b2150 100644 --- a/dust-lang/src/instruction.rs +++ b/dust-lang/src/instruction.rs @@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize}; -use crate::{Chunk, NativeFunction, Operation}; +use crate::{Chunk, NativeFunction, Operation, Type}; /// An operation and its arguments for the Dust virtual machine. /// @@ -364,35 +364,59 @@ impl Instruction { } pub fn yields_value(&self) -> bool { - if matches!( - self.operation(), + match self.operation() { Operation::Add - | Operation::Call - | Operation::Divide - | Operation::GetLocal - | Operation::LoadBoolean - | Operation::LoadConstant - | Operation::LoadList - | Operation::LoadSelf - | Operation::Modulo - | Operation::Multiply - | Operation::Negate - | Operation::Not - | Operation::Subtract - ) { - return true; + | Operation::Call + | Operation::Divide + | Operation::GetLocal + | Operation::LoadBoolean + | Operation::LoadConstant + | Operation::LoadList + | Operation::LoadSelf + | Operation::Modulo + | Operation::Multiply + | Operation::Negate + | Operation::Not + | Operation::Subtract => true, + Operation::CallNative => { + let native_function = NativeFunction::from(self.b()); + + native_function.r#type().return_type.is_some() + } + _ => false, } - - if matches!(self.operation(), Operation::CallNative) { - let native_function = NativeFunction::from(self.b()); - - return native_function.r#type().return_type.is_some(); - } - - false } - pub fn disassembly_info(&self, chunk: Option<&Chunk>) -> String { + pub fn yielded_type(&self, chunk: &Chunk) -> Option { + use Operation::*; + + match self.operation() { + Add | Divide | Modulo | Multiply | Subtract => { + if self.b_is_constant() { + chunk.get_constant_type(self.b()) + } else { + chunk.get_register_type(self.b()) + } + } + Equal | Less | LessEqual | Test | Not | LoadBoolean => Some(Type::Boolean), + Negate => { + if self.b_is_constant() { + chunk.get_constant_type(self.b()) + } else { + chunk.get_register_type(self.b()) + } + } + GetLocal => chunk.get_local_type(self.b()), + CallNative => { + let native_function = NativeFunction::from(self.b()); + + native_function.r#type().return_type.map(|boxed| *boxed) + } + _ => None, + } + } + + pub fn disassembly_info(&self, chunk: &Chunk) -> String { let format_arguments = || { let first_argument = if self.b_is_constant() { format!("C{}", self.b()) @@ -448,26 +472,18 @@ impl Instruction { Operation::LoadSelf => { let to_register = self.a(); let name = chunk - .map(|chunk| { - chunk - .name() - .map(|idenifier| idenifier.as_str()) - .unwrap_or("self") - }) - .unwrap(); + .name() + .map(|idenifier| idenifier.as_str()) + .unwrap_or("self"); format!("R{to_register} = {name}") } Operation::DefineLocal => { let to_register = self.a(); let local_index = self.b(); - let identifier_display = if let Some(chunk) = chunk { - match chunk.get_identifier(local_index) { - Some(identifier) => identifier.to_string(), - None => "???".to_string(), - } - } else { - "???".to_string() + let identifier_display = match chunk.get_identifier(local_index) { + Some(identifier) => identifier.to_string(), + None => "???".to_string(), }; let mutable_display = if self.c_as_boolean() { "mut" } else { "" }; @@ -480,13 +496,9 @@ impl Instruction { } Operation::SetLocal => { let local_index = self.b(); - let identifier_display = if let Some(chunk) = chunk { - match chunk.get_identifier(local_index) { - Some(identifier) => identifier.to_string(), - None => "???".to_string(), - } - } else { - "???".to_string() + let identifier_display = match chunk.get_identifier(local_index) { + Some(identifier) => identifier.to_string(), + None => "???".to_string(), }; format!("L{} = R{} {}", local_index, self.a(), identifier_display) diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index aa0387b..5096e6c 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -74,7 +74,7 @@ impl Vm { self.ip - 1, position, instruction.operation(), - instruction.disassembly_info(Some(&self.chunk)) + instruction.disassembly_info(&self.chunk) ); match instruction.operation() { diff --git a/dust-lang/tests/math.rs b/dust-lang/tests/math.rs index 9e632c2..c9230f4 100644 --- a/dust-lang/tests/math.rs +++ b/dust-lang/tests/math.rs @@ -64,6 +64,22 @@ fn add_assign_expects_mutable_variable() { ); } +#[test] +fn add_expects_integer_float_or_string() { + let source = "true + false"; + + assert_eq!( + parse(source), + Err(DustError::Parse { + error: ParseError::ExpectedIntegerFloatOrString { + found: Token::True, + position: Span(0, 3) + }, + source + }) + ); +} + #[test] fn divide() { let source = "2 / 2";