1
0

Pass all tests

This commit is contained in:
Jeff 2024-03-17 13:36:31 -04:00
parent 199e1c9184
commit fed119f38b
14 changed files with 235 additions and 238 deletions

View File

@ -80,7 +80,7 @@ impl AbstractTree for Assignment {
context.set_value(self.identifier, value)?; context.set_value(self.identifier, value)?;
} }
AssignmentOperator::AddAssign => { AssignmentOperator::AddAssign => {
if let Some(previous_value) = context.use_value(&self.identifier)? { if let Some(previous_value) = context.get_value(&self.identifier)? {
let new_value = previous_value.add(&value)?; let new_value = previous_value.add(&value)?;
context.set_value(self.identifier, new_value)?; context.set_value(self.identifier, new_value)?;
@ -91,7 +91,7 @@ impl AbstractTree for Assignment {
} }
} }
AssignmentOperator::SubAssign => { AssignmentOperator::SubAssign => {
if let Some(previous_value) = context.use_value(&self.identifier)? { if let Some(previous_value) = context.get_value(&self.identifier)? {
let new_value = previous_value.subtract(&value)?; let new_value = previous_value.subtract(&value)?;
context.set_value(self.identifier, new_value)?; context.set_value(self.identifier, new_value)?;
@ -131,7 +131,7 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
context.use_value(&Identifier::new("foobar")), context.get_value(&Identifier::new("foobar")),
Ok(Some(Value::integer(42))) Ok(Some(Value::integer(42)))
) )
} }
@ -154,7 +154,7 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
context.use_value(&Identifier::new("foobar")), context.get_value(&Identifier::new("foobar")),
Ok(Some(Value::integer(42))) Ok(Some(Value::integer(42)))
) )
} }
@ -177,7 +177,7 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
context.use_value(&Identifier::new("foobar")), context.get_value(&Identifier::new("foobar")),
Ok(Some(Value::integer(42))) Ok(Some(Value::integer(42)))
) )
} }

View File

@ -41,12 +41,7 @@ impl AbstractTree for Block {
let mut previous = Action::None; let mut previous = Action::None;
for statement in self.statements { for statement in self.statements {
let action = statement.node.run(_context)?; previous = statement.node.run(_context)?;
previous = match action {
Action::Return(value) => Action::Return(value),
Action::None => Action::None,
Action::Break => return Ok(action),
};
} }
Ok(previous) Ok(previous)

View File

@ -51,8 +51,9 @@ impl AbstractTree for FunctionCall {
arguments.push(value); arguments.push(value);
} }
let function_context = Context::inherit_data_from(context)?; let function_context = Context::new();
function_context.inherit_data_from(&context)?;
function.clone().call(arguments, function_context) function.clone().call(arguments, function_context)
} }
} }

View File

@ -25,7 +25,7 @@ impl Identifier {
impl AbstractTree for Identifier { impl AbstractTree for Identifier {
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> { fn expected_type(&self, context: &Context) -> Result<Type, ValidationError> {
if let Some(r#type) = context.use_type(self)? { if let Some(r#type) = context.get_type(self)? {
Ok(r#type) Ok(r#type)
} else { } else {
Err(ValidationError::VariableNotFound(self.clone())) Err(ValidationError::VariableNotFound(self.clone()))
@ -33,7 +33,7 @@ impl AbstractTree for Identifier {
} }
fn validate(&self, context: &Context) -> Result<(), ValidationError> { fn validate(&self, context: &Context) -> Result<(), ValidationError> {
if context.add_allowance(self)? { if context.contains(self)? {
Ok(()) Ok(())
} else { } else {
Err(ValidationError::VariableNotFound(self.clone())) Err(ValidationError::VariableNotFound(self.clone()))
@ -41,7 +41,7 @@ impl AbstractTree for Identifier {
} }
fn run(self, context: &Context) -> Result<Action, RuntimeError> { fn run(self, context: &Context) -> Result<Action, RuntimeError> {
let return_action = context.use_value(&self)?.map(|value| Action::Return(value)); let return_action = context.get_value(&self)?.map(|value| Action::Return(value));
if let Some(action) = return_action { if let Some(action) = return_action {
Ok(action) Ok(action)

View File

@ -32,8 +32,13 @@ impl AbstractTree for IfElse {
} }
fn validate(&self, context: &Context) -> Result<(), ValidationError> { fn validate(&self, context: &Context) -> Result<(), ValidationError> {
self.if_expression.node.validate(context)?;
self.if_block.validate(context)?;
if let Type::Boolean = self.if_expression.node.expected_type(context)? { if let Type::Boolean = self.if_expression.node.expected_type(context)? {
if let Some(else_block) = &self.else_block { if let Some(else_block) = &self.else_block {
else_block.validate(context)?;
let expected = self.if_block.expected_type(context)?; let expected = self.if_block.expected_type(context)?;
let actual = else_block.expected_type(context)?; let actual = else_block.expected_type(context)?;

View File

@ -1,3 +1,5 @@
use std::cmp::Ordering;
use crate::{ use crate::{
context::Context, context::Context,
error::{RuntimeError, ValidationError}, error::{RuntimeError, ValidationError},
@ -5,7 +7,7 @@ use crate::{
use super::{AbstractTree, Action, Statement, Type, WithPosition}; use super::{AbstractTree, Action, Statement, Type, WithPosition};
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] #[derive(Clone, Debug)]
pub struct Loop { pub struct Loop {
statements: Vec<WithPosition<Statement>>, statements: Vec<WithPosition<Statement>>,
} }
@ -30,38 +32,92 @@ impl AbstractTree for Loop {
} }
fn run(self, _context: &Context) -> Result<Action, RuntimeError> { fn run(self, _context: &Context) -> Result<Action, RuntimeError> {
let mut index = 0;
loop { loop {
if index == self.statements.len() - 1 { for statement in &self.statements {
index = 0; let action = statement.node.clone().run(_context)?;
} else {
index += 1;
}
let statement = self.statements[index].clone();
let action = statement.node.run(_context)?;
match action { match action {
Action::Return(_) => {} Action::Return(_) => {}
Action::None => {} Action::None => {}
r#break => return Ok(r#break), Action::Break => return Ok(Action::Break),
} }
} }
} }
}
}
impl Eq for Loop {}
impl PartialEq for Loop {
fn eq(&self, other: &Self) -> bool {
self.statements.eq(&other.statements)
}
}
impl PartialOrd for Loop {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Loop {
fn cmp(&self, other: &Self) -> Ordering {
self.statements.cmp(&other.statements)
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{
abstract_tree::{
Assignment, AssignmentOperator, Block, Expression, Identifier, IfElse, Logic, ValueNode,
},
Value,
};
use super::*; use super::*;
#[test] #[test]
fn basic_loop() { fn basic_loop() {
let result = Loop { let result = Loop::new(vec![Statement::Break.with_position((0, 0))]).run(&Context::new());
statements: vec![Statement::Break.with_position((0, 0))],
}
.run(&Context::new());
assert_eq!(result, Ok(Action::Break)) assert_eq!(result, Ok(Action::Break))
} }
#[test]
fn complex_loop() {
let result = Block::new(vec![
Statement::Assignment(Assignment::new(
Identifier::new("i"),
None,
AssignmentOperator::Assign,
Statement::Expression(Expression::Value(ValueNode::Integer(1)))
.with_position((0, 0)),
))
.with_position((0, 0)),
Statement::Loop(Loop::new(vec![Statement::IfElse(IfElse::new(
Expression::Logic(Box::new(Logic::Greater(
Expression::Identifier(Identifier::new("i")).with_position((10, 11)),
Expression::Value(ValueNode::Integer(2)).with_position((14, 15)),
)))
.with_position((10, 15)),
Block::new(vec![Statement::Break.with_position((18, 24))]),
Some(Block::new(vec![Statement::Assignment(Assignment::new(
Identifier::new("i"),
None,
AssignmentOperator::AddAssign,
Statement::Expression(Expression::Value(ValueNode::Integer(1)))
.with_position((38, 39)),
))
.with_position((33, 39))])),
))
.with_position((0, 0))]))
.with_position((0, 0)),
Statement::Expression(Expression::Identifier(Identifier::new("i")))
.with_position((0, 0)),
])
.run(&Context::new());
assert_eq!(result, Ok(Action::Return(Value::integer(3))))
}
} }

View File

@ -87,7 +87,9 @@ impl AbstractTree for ValueNode {
body, body,
} = self } = self
{ {
let function_context = Context::inherit_types_from(context)?; let function_context = Context::new();
function_context.inherit_types_from(context)?;
for (identifier, r#type) in parameters { for (identifier, r#type) in parameters {
function_context.set_type(identifier.clone(), r#type.node.clone())?; function_context.set_type(identifier.clone(), r#type.node.clone())?;

View File

@ -6,36 +6,13 @@ use std::{
use crate::{ use crate::{
abstract_tree::{Identifier, Type}, abstract_tree::{Identifier, Type},
error::RwLockPoisonError, error::RwLockPoisonError,
value::{BuiltInFunction, ValueInner}, value::BuiltInFunction,
Value, Value,
}; };
#[derive(Clone, Debug)]
pub struct Context { pub struct Context {
inner: Arc<RwLock<BTreeMap<Identifier, (ValueData, UsageData)>>>, inner: Arc<RwLock<BTreeMap<Identifier, ValueData>>>,
}
#[derive(Clone, Debug)]
pub struct UsageData(Arc<RwLock<UsageDataInner>>);
#[derive(Clone, Debug)]
pub struct UsageDataInner {
pub allowances: usize,
pub uses: usize,
}
impl Default for UsageData {
fn default() -> Self {
UsageData(Arc::new(RwLock::new(UsageDataInner {
allowances: 0,
uses: 0,
})))
}
}
#[derive(Clone, Debug)]
pub enum ValueData {
Type(Type),
Value(Value),
} }
impl Context { impl Context {
@ -45,96 +22,47 @@ impl Context {
} }
} }
pub fn with_data(data: BTreeMap<Identifier, (ValueData, UsageData)>) -> Self { pub fn with_data(data: BTreeMap<Identifier, ValueData>) -> Self {
Self { Self {
inner: Arc::new(RwLock::new(data)), inner: Arc::new(RwLock::new(data)),
} }
} }
pub fn inherit_types_from(other: &Context) -> Result<Self, RwLockPoisonError> { pub fn inherit_types_from(&self, other: &Context) -> Result<(), RwLockPoisonError> {
let mut new_data = BTreeMap::new(); let mut self_data = self.inner.write()?;
for (identifier, (value_data, usage_data)) in other.inner.read()?.iter() { for (identifier, value_data) in other.inner.read()?.iter() {
if let ValueData::Type(r#type) = value_data { if let ValueData::Type(r#type) = value_data {
if let Type::Function { .. } = r#type { if let Type::Function { .. } = r#type {
new_data.insert(identifier.clone(), (value_data.clone(), usage_data.clone())); self_data.insert(identifier.clone(), value_data.clone());
} }
} }
} }
Ok(Self::with_data(new_data)) Ok(())
} }
pub fn inherit_data_from(other: &Context) -> Result<Self, RwLockPoisonError> { pub fn inherit_data_from(&self, other: &Context) -> Result<(), RwLockPoisonError> {
let mut new_data = BTreeMap::new(); let mut self_data = self.inner.write()?;
for (identifier, (value_data, usage_data)) in other.inner.read()?.iter() { for (identifier, value_data) in other.inner.read()?.iter() {
if let ValueData::Type(r#type) = value_data { self_data.insert(identifier.clone(), value_data.clone());
if let Type::Function { .. } = r#type {
new_data.insert(identifier.clone(), (value_data.clone(), usage_data.clone()));
}
}
if let ValueData::Value(value) = value_data {
if let ValueInner::Function { .. } = value.inner().as_ref() {
new_data.insert(identifier.clone(), (value_data.clone(), usage_data.clone()));
}
}
} }
Ok(Self::with_data(new_data)) Ok(())
} }
pub fn add_allowance(&self, identifier: &Identifier) -> Result<bool, RwLockPoisonError> { pub fn contains(&self, identifier: &Identifier) -> Result<bool, RwLockPoisonError> {
if let Some((_, usage_data)) = self.inner.read()?.get(identifier) { Ok(self.inner.read()?.contains_key(identifier))
usage_data.0.write()?.allowances += 1;
Ok(true)
} else {
Ok(false)
}
} }
pub fn use_data( pub fn get_type(&self, identifier: &Identifier) -> Result<Option<Type>, RwLockPoisonError> {
&self, if let Some(value_data) = self.inner.read()?.get(identifier) {
identifier: &Identifier, let r#type = match value_data {
) -> Result<Option<ValueData>, RwLockPoisonError> { ValueData::Type(r#type) => r#type.clone(),
let should_remove = ValueData::Value(value) => value.r#type(),
if let Some((value_data, usage_data)) = self.inner.read()?.get(identifier) {
let mut usage_data = usage_data.0.write()?;
log::trace!("Adding use for variable: {identifier}");
usage_data.uses += 1;
if usage_data.uses == usage_data.allowances {
true
} else {
return Ok(Some(value_data.clone()));
}
} else {
false
}; };
if should_remove {
log::trace!("Removing varialble: {identifier}");
self.inner.write()?.remove(identifier);
}
let value_data = match identifier.as_str() {
"output" => ValueData::Value(BuiltInFunction::output()),
_ => return Ok(None),
};
Ok(Some(value_data))
}
pub fn use_type(&self, identifier: &Identifier) -> Result<Option<Type>, RwLockPoisonError> {
if let Some((ValueData::Type(r#type), usage_data)) = self.inner.read()?.get(identifier) {
log::trace!("Adding use for variable: {identifier}");
usage_data.0.write()?.uses += 1;
return Ok(Some(r#type.clone())); return Ok(Some(r#type.clone()));
} }
@ -146,43 +74,23 @@ impl Context {
Ok(Some(r#type)) Ok(Some(r#type))
} }
pub fn use_value(&self, identifier: &Identifier) -> Result<Option<Value>, RwLockPoisonError> { pub fn get_value(&self, identifier: &Identifier) -> Result<Option<Value>, RwLockPoisonError> {
let should_remove = if let Some((ValueData::Value(value), usage_data)) = if let Some(ValueData::Value(value)) = self.inner.read()?.get(identifier) {
self.inner.read()?.get(identifier) Ok(Some(value.clone()))
{
let mut usage_data = usage_data.0.write()?;
log::trace!("Adding use for variable: {identifier}");
usage_data.uses += 1;
if usage_data.uses == usage_data.allowances {
true
} else { } else {
return Ok(Some(value.clone()));
}
} else {
false
};
if should_remove {
log::trace!("Removing varialble: {identifier}");
self.inner.write()?.remove(identifier);
}
let value = match identifier.as_str() { let value = match identifier.as_str() {
"output" => BuiltInFunction::output(), "output" => Value::built_in_function(BuiltInFunction::Output),
_ => return Ok(None), _ => return Ok(None),
}; };
Ok(Some(value)) Ok(Some(value))
} }
}
pub fn set_type(&self, identifier: Identifier, r#type: Type) -> Result<(), RwLockPoisonError> { pub fn set_type(&self, identifier: Identifier, r#type: Type) -> Result<(), RwLockPoisonError> {
self.inner self.inner
.write()? .write()?
.insert(identifier, (ValueData::Type(r#type), UsageData::default())); .insert(identifier, ValueData::Type(r#type));
Ok(()) Ok(())
} }
@ -190,12 +98,14 @@ impl Context {
pub fn set_value(&self, identifier: Identifier, value: Value) -> Result<(), RwLockPoisonError> { pub fn set_value(&self, identifier: Identifier, value: Value) -> Result<(), RwLockPoisonError> {
let mut inner = self.inner.write()?; let mut inner = self.inner.write()?;
if let Some((_value_data, usage_data)) = inner.remove(&identifier) { inner.insert(identifier, ValueData::Value(value));
inner.insert(identifier, (ValueData::Value(value), usage_data));
} else {
inner.insert(identifier, (ValueData::Value(value), UsageData::default()));
}
Ok(()) Ok(())
} }
} }
#[derive(Clone, Debug, PartialEq)]
pub enum ValueData {
Type(Type),
Value(Value),
}

View File

@ -1,6 +1,6 @@
use std::sync::PoisonError; use std::{ops::Range, sync::PoisonError};
use ariadne::{Color, Label, Report, ReportKind}; use ariadne::{Label, ReportBuilder};
use chumsky::{prelude::Rich, span::Span}; use chumsky::{prelude::Rich, span::Span};
use crate::{ use crate::{
@ -18,7 +18,10 @@ pub enum Error {
expected: String, expected: String,
span: (usize, usize), span: (usize, usize),
}, },
Runtime(RuntimeError), Runtime {
error: RuntimeError,
position: SourcePosition,
},
Validation { Validation {
error: ValidationError, error: ValidationError,
position: SourcePosition, position: SourcePosition,
@ -26,7 +29,10 @@ pub enum Error {
} }
impl Error { impl Error {
pub fn report(&self) -> Report { pub fn build_report(
self,
mut builder: ReportBuilder<'_, Range<usize>>,
) -> ReportBuilder<'_, Range<usize>> {
match self { match self {
Error::Parse { expected, span } => { Error::Parse { expected, span } => {
let message = match expected.as_str() { let message = match expected.as_str() {
@ -34,9 +40,7 @@ impl Error {
expected => format!("Expected {expected}."), expected => format!("Expected {expected}."),
}; };
Report::build(ReportKind::Custom("Lexing Error", Color::White), (), span.0) builder.add_label(Label::new(span.0..span.1).with_message(message));
.with_label(Label::new(span.0..span.1).with_message(message))
.finish()
} }
Error::Lex { expected, span } => { Error::Lex { expected, span } => {
let message = match expected.as_str() { let message = match expected.as_str() {
@ -44,30 +48,27 @@ impl Error {
expected => format!("Expected {expected}."), expected => format!("Expected {expected}."),
}; };
Report::build(ReportKind::Custom("Lexing Error", Color::White), (), span.0) builder.add_label(Label::new(span.0..span.1).with_message(message));
.with_label(Label::new(span.0..span.1).with_message(message))
.finish()
} }
Error::Runtime(_) => todo!(), Error::Runtime { error, position } => match error {
Error::Validation { error, position } => { RuntimeError::RwLockPoison(_) => todo!(),
let mut report = Report::build( RuntimeError::ValidationFailure(validation_error) => {
ReportKind::Custom("Validation Error: The code was not run.", Color::White), builder =
(), Error::Validation {
0, error: validation_error,
) position,
.with_label( }
Label::new(position.0..position.1).with_message("Error found in this item."), .build_report(builder.with_note(
); "The interpreter failed to catch this error during validation.",
));
match error { }
},
Error::Validation { error, position } => match error {
ValidationError::ExpectedBoolean => { ValidationError::ExpectedBoolean => {
report = builder.add_label(Label::new(0..0).with_message("Expected boolean."));
report.with_label(Label::new(0..0).with_message("Expected boolean."));
} }
ValidationError::ExpectedIntegerOrFloat => { ValidationError::ExpectedIntegerOrFloat => {
report = report.with_label( builder.add_label(Label::new(0..0).with_message("Expected integer or float."));
Label::new(0..0).with_message("Expected integer or float."),
);
} }
ValidationError::RwLockPoison(_) => todo!(), ValidationError::RwLockPoison(_) => todo!(),
ValidationError::TypeCheck { ValidationError::TypeCheck {
@ -77,7 +78,7 @@ impl Error {
} => { } => {
let TypeConflict { actual, expected } = conflict; let TypeConflict { actual, expected } = conflict;
report = report.with_labels([ builder.add_labels([
Label::new(expected_postion.0..expected_postion.1) Label::new(expected_postion.0..expected_postion.1)
.with_message(format!("Type {expected} established here.")), .with_message(format!("Type {expected} established here.")),
Label::new(actual_position.0..actual_position.1) Label::new(actual_position.0..actual_position.1)
@ -85,21 +86,21 @@ impl Error {
]); ]);
} }
ValidationError::VariableNotFound(identifier) => { ValidationError::VariableNotFound(identifier) => {
report = report builder.add_label(
.with_label(Label::new(0..0).with_message(format!( Label::new(position.0..position.1)
"The variable {identifier} does not exist." .with_message(format!("The variable {identifier} does not exist."))
))); .with_priority(1),
);
} }
ValidationError::CannotIndex(_) => todo!(), ValidationError::CannotIndex(_) => todo!(),
ValidationError::CannotIndexWith(_, _) => todo!(), ValidationError::CannotIndexWith(_, _) => todo!(),
ValidationError::InterpreterExpectedReturn => todo!(), ValidationError::InterpreterExpectedReturn => todo!(),
ValidationError::ExpectedFunction => todo!(), ValidationError::ExpectedFunction => todo!(),
ValidationError::ExpectedValue => todo!(), ValidationError::ExpectedValue => todo!(),
},
} }
report.finish() builder
}
}
} }
} }
@ -121,12 +122,6 @@ impl<'src> From<Rich<'_, Token<'src>>> for Error {
} }
} }
impl From<RuntimeError> for Error {
fn from(error: RuntimeError) -> Self {
Error::Runtime(error)
}
}
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum RuntimeError { pub enum RuntimeError {
RwLockPoison(RwLockPoisonError), RwLockPoison(RwLockPoisonError),

View File

@ -56,7 +56,12 @@ impl Interpreter {
Action::Return(value) => Some(value), Action::Return(value) => Some(value),
Action::None => continue, Action::None => continue,
}, },
Err(runtime_error) => return Err(vec![Error::Runtime(runtime_error)]), Err(runtime_error) => {
return Err(vec![Error::Runtime {
error: runtime_error,
position: statement.position,
}])
}
} }
} }

View File

@ -1,6 +1,6 @@
//! Command line interface for the dust programming language. //! Command line interface for the dust programming language.
use ariadne::Source; use ariadne::{Color, Report, ReportKind, Source};
use clap::Parser; use clap::Parser;
use colored::Colorize; use colored::Colorize;
@ -53,9 +53,17 @@ fn main() {
} }
} }
Err(errors) => { Err(errors) => {
let mut report_builder =
Report::build(ReportKind::Custom("Dust Error", Color::White), (), 5);
for error in errors { for error in errors {
error.report().eprint(Source::from(&source)).unwrap(); report_builder = error.build_report(report_builder);
} }
report_builder
.finish()
.eprint(Source::from(source))
.unwrap()
} }
} }
} }

View File

@ -656,6 +656,26 @@ mod tests {
),) ),)
.with_position((7, 9))]),) .with_position((7, 9))]),)
); );
assert_eq!(
parse(&lex("loop { if i > 2 { break } else { i += 1 } }").unwrap()).unwrap()[0].node,
Statement::Loop(Loop::new(vec![Statement::IfElse(IfElse::new(
Expression::Logic(Box::new(Logic::Greater(
Expression::Identifier(Identifier::new("i")).with_position((10, 11)),
Expression::Value(ValueNode::Integer(2)).with_position((14, 15))
)))
.with_position((10, 15)),
Block::new(vec![Statement::Break.with_position((18, 24))]),
Some(Block::new(vec![Statement::Assignment(Assignment::new(
Identifier::new("i"),
None,
AssignmentOperator::AddAssign,
Statement::Expression(Expression::Value(ValueNode::Integer(1)))
.with_position((38, 39))
))
.with_position((33, 39))]))
),)
.with_position((7, 42))]))
);
} }
#[test] #[test]

View File

@ -141,8 +141,8 @@ fn map_type_errors() {
actual: Type::String, actual: Type::String,
expected: Type::Boolean expected: Type::Boolean
}, },
actual_position: (0, 0).into(), actual_position: (15, 20).into(),
expected_position: (0, 0).into(), expected_position: (8, 13).into(),
}, },
position: (0, 22).into() position: (0, 22).into()
}]) }])

View File

@ -30,8 +30,8 @@ fn set_variable_with_type_error() {
actual: Type::Boolean, actual: Type::Boolean,
expected: Type::String expected: Type::String
}, },
actual_position: (0, 0).into(), actual_position: (14, 18).into(),
expected_position: (0, 0).into() expected_position: (8, 12).into()
}, },
position: (0, 18).into() position: (0, 18).into()
}]) }])
@ -43,13 +43,13 @@ fn function_variable() {
assert_eq!( assert_eq!(
interpret("foobar = (x: int): int { x }; foobar"), interpret("foobar = (x: int): int { x }; foobar"),
Ok(Some(Value::function( Ok(Some(Value::function(
vec![(Identifier::new("x"), Type::Integer.with_position((0, 0)))], vec![(Identifier::new("x"), Type::Integer.with_position((13, 16)))],
Type::Integer.with_position((0, 0)), Type::Integer.with_position((19, 23)),
Block::new(vec![Statement::Expression(Expression::Identifier( Block::new(vec![Statement::Expression(Expression::Identifier(
Identifier::new("x") Identifier::new("x")
)) ))
.with_position((0, 0))]) .with_position((25, 26))])
.with_position((0, 0)) .with_position((9, 28))
))) )))
); );
} }