Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add field assignment #218

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
3 changes: 2 additions & 1 deletion src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
2 changes: 1 addition & 1 deletion src/context/scope_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ impl<V, F, T> ScopeMap<V, F, T> {
/// 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,
Expand Down
48 changes: 48 additions & 0 deletions src/instance/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<FieldsMap> {
&self.fields
}
Expand Down
4 changes: 2 additions & 2 deletions src/instruction/field_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use crate::{

#[derive(Clone)]
pub struct FieldAccess {
instance: Box<dyn Instruction>,
field_name: String,
pub(crate) instance: Box<dyn Instruction>,
pub(crate) field_name: String,
}

impl FieldAccess {
Expand Down
112 changes: 112 additions & 0 deletions src/instruction/field_assign.rs
Original file line number Diff line number Diff line change
@@ -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<dyn Instruction>,
}

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<dyn Instruction>) -> 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<ObjectInstance> {
// 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
}
3 changes: 3 additions & 0 deletions src/instruction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<ObjectInstance> {
unreachable!(
"\n{}\n --> {}",
Expand Down
30 changes: 11 additions & 19 deletions src/instruction/type_instantiation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -117,29 +116,22 @@ impl Instruction for TypeInstantiation {
return None;
}

let mut size: usize = 0;
let mut data: Vec<u8> = 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)
}
}

Expand Down
12 changes: 4 additions & 8 deletions src/instruction/var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand All @@ -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
Expand Down Expand Up @@ -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())
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/parser/box_construct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down
53 changes: 50 additions & 3 deletions src/parser/constructs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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,
Expand Down Expand Up @@ -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
///
/// `<identifier>.<identifier>[.<identifier>]*`
// 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<dyn Instruction>> {
let (input, id) = Token::identifier(input)?;
BoxConstruct::function_call(input, &id).or_else(|_| Ok((input, Box::new(Var::new(id)))))
Expand Down