diff --git a/src/context.rs b/src/context.rs index f1c817a4..f3904777 100644 --- a/src/context.rs +++ b/src/context.rs @@ -192,7 +192,8 @@ impl Context { } /// Get a reference on an existing variable - pub fn get_variable(&self, name: &str) -> Option<&Var> { + // Should we get_mut or should we replace the variable?? + pub fn get_variable(&mut self, name: &str) -> Option<&mut Var> { self.scope_map.get_variable(name) } diff --git a/src/context/scope_map.rs b/src/context/scope_map.rs index 596b97da..8746d6d8 100644 --- a/src/context/scope_map.rs +++ b/src/context/scope_map.rs @@ -134,7 +134,7 @@ impl ScopeMap { /// Maybe get a variable in any available scopes pub fn get_variable(&self, name: &str) -> Option<&V> { // FIXME: Use find for code quality? - for scope in self.scopes.iter() { + for scope in self.scopes.iter_mut() { match scope.get_variable(name) { Some(v) => return Some(v), None => continue, diff --git a/src/instance/mod.rs b/src/instance/mod.rs index d0bdd76e..b5cb2d97 100644 --- a/src/instance/mod.rs +++ b/src/instance/mod.rs @@ -47,6 +47,10 @@ impl ObjectInstance { ObjectInstance::new(CheckedType::Unknown, 0, vec![], None) } + pub fn empty_with_fields() -> ObjectInstance { + ObjectInstance::new(None, 0, vec![], Some(vec![])) + } + /// Create a new instance pub fn new( ty: CheckedType, @@ -106,6 +110,50 @@ impl ObjectInstance { } } + fn add_field(&mut self, name: &str, value: ObjectInstance) -> Result<(), Error> { + self.size += value.size(); + self.data.append(&mut value.data().to_vec()); + + // We can unwrap safely here since we already checked if the instance contained fields + // in `set_field()` + self.fields.as_mut().unwrap().insert( + name.to_string(), + FieldInstance(self.size - value.size(), value), + ); + + Ok(()) + } + + fn mutate_field(&mut self, name: &str, value: ObjectInstance) -> Result<(), Error> { + // We can unwrap safely here since we already checked if the instance contained fields + // in `set_field()`, and that the field was present + let FieldInstance(offset, instance) = self.fields.as_mut().unwrap().get(name).unwrap(); + + for i in *offset..(offset + instance.size()) { + if let Some(data) = self.data.get_mut(offset + i) { + *data = *value.data().get(i).unwrap(); + } + } + + Ok(()) + } + + pub fn set_field( + &mut self, + field_name: &str, + field_value: ObjectInstance, + ) -> Result<(), Error> { + match &mut self.fields { + None => { + Err(Error::new(ErrKind::Context).with_msg(String::from("no fields on instance"))) + } + Some(field_map) => match field_map.contains_key(field_name) { + false => self.add_field(field_name, field_value), + true => self.mutate_field(field_name, field_value), + }, + } + } + pub fn fields(&self) -> &Option { &self.fields } diff --git a/src/instruction/field_access.rs b/src/instruction/field_access.rs index 3f6b8ccc..eee9ad72 100644 --- a/src/instruction/field_access.rs +++ b/src/instruction/field_access.rs @@ -8,8 +8,8 @@ use crate::{ #[derive(Clone)] pub struct FieldAccess { - instance: Box, - field_name: String, + pub(crate) instance: Box, + pub(crate) field_name: String, } impl FieldAccess { diff --git a/src/instruction/field_assign.rs b/src/instruction/field_assign.rs new file mode 100644 index 00000000..8e3e468a --- /dev/null +++ b/src/instruction/field_assign.rs @@ -0,0 +1,112 @@ +//! FieldAssigns represent the assignment of a value to a given field on an instance +//! This is what is used by the interpreter when modifying an attribute on a given type. +//! Just like variable assignments, the original instance needs to be mutable in order to +//! be assigned a new value. However, unlike variable assignments, there is no "first +//! assignment" for fields, as they should be initialized on the type's instantiation. + +use super::FieldAccess; +use crate::{ + CheckedType, Context, ErrKind, Error, InstrKind, Instruction, ObjectInstance, TypeCheck, + TypeCtx, +}; + +#[derive(Clone)] +pub struct FieldAssign { + // FIXME: This should actually be a variable and be kinda similar to VarAssign + // in that regard + field: FieldAccess, + value: Box, +} + +impl FieldAssign { + /// Create a new FieldAssign from the FieldAccess you're trying to modify and the value + /// you're trying to assign to it. + pub fn new(field: FieldAccess, value: Box) -> FieldAssign { + FieldAssign { field, value } + } +} + +impl Instruction for FieldAssign { + fn kind(&self) -> InstrKind { + InstrKind::Statement + } + + fn print(&self) -> String { + format!("{} = {}", self.field.print(), self.value.print()) + } + + fn execute(&self, ctx: &mut Context) -> Option { + // FIXME: We probably need to keep track of all the instances created somewhere + // in the context, to garbage collect them later for exemple + let value = self.value.execute(ctx)?; + + // FIXME: How does this work when we're not dealing with a variable as an instance? + // Say, `fn_call().attribute = some_other_value` (Hint: It doesn't) + let instance = match ctx.get_variable(&self.field.instance.print()) { + Some(var) => &mut var.instance, + None => { + ctx.error(Error::new(ErrKind::Context).with_msg(format!( + "cannot find variable: `{}`", + self.field.instance.print() + ))); + return None; + } + }; + + // FIXME: Should we check if this is the first time we're setting the field? In + // that case, error out! + if let Err(e) = instance.set_field(&self.field.field_name, value) { + ctx.error(e); + } + + None + } +} + +impl TypeCheck for FieldAssign { + fn resolve_type(&self, ctx: &mut TypeCtx) -> CheckedType { + // FIXME: + CheckedType::Void + } +} + +#[cfg(test)] +mod tests { + use crate::instance::ToObjectInstance; + use crate::{parser::Construct, Context, JkInt}; + + fn setup() -> Context { + let mut ctx = Context::new(); + + let inst = Construct::instruction("type Point(x: int, y: int);") + .unwrap() + .1; + inst.execute(&mut ctx); + + let inst = Construct::instruction("point = Point { x = 15, y = 14 }") + .unwrap() + .1; + inst.execute(&mut ctx); + + assert!(!ctx.error_handler.has_errors()); + + ctx + } + + #[test] + fn t_valid_field_assign() { + let mut ctx = setup(); + + let f_a = Construct::instruction("point.x = 99").unwrap().1; + f_a.execute(&mut ctx); + + let f_a_result = Construct::instruction("point.x").unwrap().1; + let x_value = f_a_result.execute(&mut ctx).unwrap(); + + assert!(!ctx.error_handler.has_errors()); + assert_eq!(x_value, JkInt::from(99).to_instance()); + } + + // FIXME: Add tests making sure that we can't modify the fields on something that + // isn't a variable +} diff --git a/src/instruction/mod.rs b/src/instruction/mod.rs index 2ae65a94..13844e76 100644 --- a/src/instruction/mod.rs +++ b/src/instruction/mod.rs @@ -12,6 +12,7 @@ mod block; mod dec_arg; mod extra_content; mod field_access; +mod field_assign; mod function_call; mod function_declaration; mod if_else; @@ -33,6 +34,7 @@ pub use block::Block; pub use dec_arg::DecArg; pub use extra_content::{CommentKind, ExtraContent, ExtraKind}; pub use field_access::FieldAccess; +pub use field_assign::FieldAssign; pub use function_call::FunctionCall; pub use function_declaration::{FunctionDec, FunctionKind}; pub use if_else::IfElse; @@ -67,6 +69,7 @@ pub trait Instruction: InstructionClone + Downcast + TypeCheck { // FIXME: Add Rename here /// Execute the instruction, altering the state of the context. Executing /// this method may return an object instance + // FIXME: Should this return a mutable ref instead?? On an instance kept in the context? fn execute(&self, _ctx: &mut Context) -> Option { unreachable!( "\n{}\n --> {}", diff --git a/src/instruction/type_instantiation.rs b/src/instruction/type_instantiation.rs index 0dfb5ee1..04ab8130 100644 --- a/src/instruction/type_instantiation.rs +++ b/src/instruction/type_instantiation.rs @@ -4,7 +4,6 @@ use super::{ Context, ErrKind, Error, InstrKind, Instruction, ObjectInstance, TypeDec, TypeId, VarAssign, }; -use crate::instance::Name; use crate::typechecker::TypeCtx; use crate::{typechecker::CheckedType, TypeCheck}; @@ -117,29 +116,22 @@ impl Instruction for TypeInstantiation { return None; } - let mut size: usize = 0; - let mut data: Vec = Vec::new(); - let mut fields: Vec<(Name, ObjectInstance)> = Vec::new(); - for (_, named_arg) in self.fields.iter().enumerate() { - // FIXME: Need to assign the correct field to the field that corresponds - // in the typedec + let mut instance = ObjectInstance::empty_with_fields(); + + for named_arg in self.fields.iter() { let field_instr = named_arg.value(); let field_name = named_arg.symbol(); + let field_instance = field_instr.execute_expression(ctx)?; - let instance = field_instr.execute_expression(ctx)?; - size += instance.size(); - - data.append(&mut instance.data().to_vec()); - fields.push((field_name.to_string(), instance)); + if let Err(e) = instance.set_field(field_name, field_instance) { + ctx.error(e); + return None; + } } - Some(ObjectInstance::new( - // FIXME: Disgusting, maybe do not use Rc for TypeId? - CheckedType::Resolved((*type_dec).clone().into()), - size, - data, - Some(fields), - )) + instance.set_ty(Some((*type_dec).clone())); + + Some(instance) } } diff --git a/src/instruction/var.rs b/src/instruction/var.rs index a0805d78..9c701af9 100644 --- a/src/instruction/var.rs +++ b/src/instruction/var.rs @@ -11,7 +11,7 @@ use crate::{Context, ErrKind, Error, InstrKind, Instruction, JkBool, ObjectInsta pub struct Var { name: String, mutable: bool, - instance: ObjectInstance, + pub(crate) instance: ObjectInstance, // FIXME: Maybe we can refactor this using the instance's type? ty: CheckedType, } @@ -32,11 +32,6 @@ impl Var { &self.name } - /// Return a copy of the variable's instance - pub fn instance(&self) -> ObjectInstance { - self.instance.clone() - } - /// Is a variable mutable or not pub fn mutable(&self) -> bool { self.mutable @@ -118,9 +113,10 @@ impl Instruction for Var { } }; - ctx.debug("VAR", var.print().as_ref()); + // FIXME: Re-add once debugging is separate from context #210 + // ctx.debug("VAR", var.print().as_ref()); - Some(var.instance()) + Some(var.instance.clone()) } } diff --git a/src/parser/box_construct.rs b/src/parser/box_construct.rs index df2ed625..3cbb9564 100644 --- a/src/parser/box_construct.rs +++ b/src/parser/box_construct.rs @@ -50,6 +50,9 @@ impl BoxConstruct { box_construct! {test_declaration} box_construct! {mock_declaration} box_construct! {incl} + box_construct! {method_call} + box_construct! {field_access} + box_construct! {field_assign} box_construct! {extra} box_construct! {jk_return} diff --git a/src/parser/constructs.rs b/src/parser/constructs.rs index be1adf71..7befe7d3 100644 --- a/src/parser/constructs.rs +++ b/src/parser/constructs.rs @@ -18,8 +18,8 @@ use nom::{branch::alt, combinator::opt, multi::many0, sequence::preceded}; use crate::error::{ErrKind, Error}; use crate::instruction::{ - Block, DecArg, ExtraContent, FieldAccess, FunctionCall, FunctionDec, FunctionKind, IfElse, - Incl, Instruction, JkInst, Loop, LoopKind, MethodCall, Return, TypeDec, TypeId, + Block, DecArg, ExtraContent, FieldAccess, FieldAssign, FunctionCall, FunctionDec, FunctionKind, + IfElse, Incl, Instruction, JkInst, Loop, LoopKind, MethodCall, Return, TypeDec, TypeId, TypeInstantiation, Var, VarAssign, }; use crate::parser::{BoxConstruct, ConstantConstruct, ParseResult, ShuntingYard, Token}; @@ -37,7 +37,8 @@ impl Construct { // FIXME: We need to parse the remaining input after a correct instruction // has been parsed let (input, value) = alt(( - BoxConstruct::extra, + Construct::binary_op, + BoxConstruct::field_assign, BoxConstruct::function_declaration, BoxConstruct::type_declaration, BoxConstruct::ext_declaration, @@ -930,6 +931,52 @@ impl Construct { Ok(instance) } + fn dot_field(input: &str) -> ParseResult<&str, String> { + let (input, _) = Token::dot(input)?; + + Token::identifier(input) + } + + fn inner_field_access(input: &str) -> ParseResult<&str, FieldAccess> { + let (input, instance) = Construct::instance(input)?; + let (input, field_name) = Construct::dot_field(input)?; + + Ok((input, FieldAccess::new(instance, field_name))) + } + + fn multi_field_access(input: &str) -> ParseResult<&str, FieldAccess> { + let (input, first_fa) = Construct::inner_field_access(input)?; + + let (input, dot_field_vec) = many0(Construct::dot_field)(input)?; + + let mut current_fa = first_fa; + + for field_name in dot_field_vec { + let fa = FieldAccess::new(Box::new(current_fa), field_name); + current_fa = fa; + } + + Ok((input, current_fa)) + } + + pub fn field_assign(input: &str) -> ParseResult<&str, FieldAssign> { + let (input, field_access) = Construct::field_access(input)?; + let (input, _) = Token::maybe_consume_extra(input)?; + let (input, _) = Token::equal(input)?; + let (input, _) = Token::maybe_consume_extra(input)?; + let (input, value) = Construct::instruction(input)?; + + Ok((input, FieldAssign::new(field_access, value))) + } + + /// Parse a field access on a custom type. This is very similar to a method call: The + /// only difference is that the method call shall have parentheses + /// + /// `.[.]*` + // pub fn field_access(input: &str) -> ParseResult<&str, FieldAccess> { + // Construct::multi_field_access(input) + // } + pub fn function_call_or_var(input: &str) -> ParseResult<&str, Box> { let (input, id) = Token::identifier(input)?; BoxConstruct::function_call(input, &id).or_else(|_| Ok((input, Box::new(Var::new(id)))))