Skip to content
Draft
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