Add instantiation for tuple structs
This commit is contained in:
parent
049790726a
commit
83aa53b998
@ -6,7 +6,7 @@ use std::{
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{BuiltInFunction, Context, Identifier, Span, Type, Value};
|
||||
use crate::{BuiltInFunction, Context, Identifier, Span, StructType, Type, Value};
|
||||
|
||||
/// In-memory representation of a Dust program.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
@ -50,17 +50,21 @@ pub enum Statement {
|
||||
operand: Box<Node<Statement>>,
|
||||
},
|
||||
|
||||
// Function calls
|
||||
// Type definitions
|
||||
StructDefinition(StructDefinition),
|
||||
|
||||
// Function calls and type instantiation
|
||||
BuiltInFunctionCall {
|
||||
function: BuiltInFunction,
|
||||
type_arguments: Option<Vec<Node<Statement>>>,
|
||||
value_arguments: Option<Vec<Node<Statement>>>,
|
||||
},
|
||||
FunctionCall {
|
||||
function: Box<Node<Statement>>,
|
||||
Invokation {
|
||||
invokee: Box<Node<Statement>>,
|
||||
type_arguments: Option<Vec<Node<Statement>>>,
|
||||
value_arguments: Option<Vec<Node<Statement>>>,
|
||||
},
|
||||
StructInstantiation(StructInstantiation),
|
||||
|
||||
// Loops
|
||||
While {
|
||||
@ -103,9 +107,6 @@ pub enum Statement {
|
||||
// A statement that always returns None. Created with a semicolon, it causes the preceding
|
||||
// statement to return None. This is analagous to the semicolon or unit type in Rust.
|
||||
Nil(Box<Node<Statement>>),
|
||||
|
||||
// Type definitions
|
||||
StructDefinition(StructDefinition),
|
||||
}
|
||||
|
||||
impl Statement {
|
||||
@ -159,7 +160,9 @@ impl Statement {
|
||||
},
|
||||
Statement::BuiltInFunctionCall { function, .. } => function.expected_return_type(),
|
||||
Statement::Constant(value) => Some(value.r#type()),
|
||||
Statement::FunctionCall { function, .. } => function.inner.expected_type(context),
|
||||
Statement::Invokation {
|
||||
invokee: function, ..
|
||||
} => function.inner.expected_type(context),
|
||||
Statement::Identifier(identifier) => context.get_type(identifier),
|
||||
Statement::If { .. } => None,
|
||||
Statement::IfElse { if_body, .. } => if_body.inner.expected_type(context),
|
||||
@ -190,6 +193,17 @@ impl Statement {
|
||||
UnaryOperator::Not => Some(Type::Boolean),
|
||||
},
|
||||
Statement::StructDefinition(_) => None,
|
||||
Statement::StructInstantiation(struct_instantiation) => match struct_instantiation {
|
||||
StructInstantiation::Tuple { name, fields } => {
|
||||
Some(Type::Struct(StructType::Tuple {
|
||||
name: name.inner.clone(),
|
||||
fields: fields
|
||||
.iter()
|
||||
.map(|field| field.inner.expected_type(context))
|
||||
.collect::<Option<Vec<Type>>>()?,
|
||||
}))
|
||||
}
|
||||
},
|
||||
Statement::While { .. } => None,
|
||||
}
|
||||
}
|
||||
@ -287,8 +301,8 @@ impl Display for Statement {
|
||||
write!(f, ")")
|
||||
}
|
||||
Statement::Constant(value) => write!(f, "{value}"),
|
||||
Statement::FunctionCall {
|
||||
function,
|
||||
Statement::Invokation {
|
||||
invokee: function,
|
||||
type_arguments: type_parameters,
|
||||
value_arguments: value_parameters,
|
||||
} => {
|
||||
@ -398,6 +412,9 @@ impl Display for Statement {
|
||||
Statement::StructDefinition(struct_definition) => {
|
||||
write!(f, "{struct_definition}")
|
||||
}
|
||||
Statement::StructInstantiation(struct_instantiation) => {
|
||||
write!(f, "{struct_instantiation}")
|
||||
}
|
||||
Statement::While { condition, body } => {
|
||||
write!(f, "while {condition} {body}")
|
||||
}
|
||||
@ -471,3 +488,32 @@ impl Display for StructDefinition {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum StructInstantiation {
|
||||
// The Unit variant is absent because unit structs are instantiated without any fields
|
||||
Tuple {
|
||||
name: Node<Identifier>,
|
||||
fields: Vec<Node<Statement>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Display for StructInstantiation {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
StructInstantiation::Tuple { name, fields } => {
|
||||
write!(f, "{name}(")?;
|
||||
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
|
||||
write!(f, "{field}")?;
|
||||
}
|
||||
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
abstract_tree::{BinaryOperator, UnaryOperator},
|
||||
abstract_tree::{BinaryOperator, StructInstantiation, UnaryOperator},
|
||||
parse, AbstractSyntaxTree, Context, DustError, Identifier, Node, Span, Statement,
|
||||
StructDefinition, StructType, Type,
|
||||
};
|
||||
@ -290,8 +290,8 @@ impl<'a> Analyzer<'a> {
|
||||
}
|
||||
}
|
||||
Statement::Constant(_) => {}
|
||||
Statement::FunctionCall {
|
||||
function,
|
||||
Statement::Invokation {
|
||||
invokee: function,
|
||||
value_arguments,
|
||||
..
|
||||
} => {
|
||||
@ -429,13 +429,31 @@ impl<'a> Analyzer<'a> {
|
||||
name: name.inner.clone(),
|
||||
}),
|
||||
),
|
||||
StructDefinition::Tuple { name, fields } => {
|
||||
todo!()
|
||||
}
|
||||
StructDefinition::Tuple { name, fields } => (
|
||||
name.inner.clone(),
|
||||
Type::Struct(StructType::Tuple {
|
||||
name: name.inner.clone(),
|
||||
fields: fields
|
||||
.iter()
|
||||
.map(|type_node| type_node.inner.clone())
|
||||
.collect(),
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
self.context.set_type(name, r#type, node.position);
|
||||
}
|
||||
Statement::StructInstantiation(struct_instantiation) => {
|
||||
let name = match struct_instantiation {
|
||||
StructInstantiation::Tuple { name, .. } => name,
|
||||
};
|
||||
|
||||
if self.context.get_type(&name.inner).is_none() {
|
||||
return Err(AnalyzerError::UndefinedType {
|
||||
identifier: name.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Statement::UnaryOperation { operator, operand } => {
|
||||
self.analyze_statement(operand)?;
|
||||
|
||||
@ -517,19 +535,22 @@ pub enum AnalyzerError {
|
||||
actual_type: Type,
|
||||
expected: Type,
|
||||
},
|
||||
UndefinedVariable {
|
||||
identifier: Node<Statement>,
|
||||
},
|
||||
UndefinedField {
|
||||
identifier: Node<Statement>,
|
||||
map: Node<Statement>,
|
||||
},
|
||||
UndefinedType {
|
||||
identifier: Node<Identifier>,
|
||||
},
|
||||
UnexpectedIdentifier {
|
||||
identifier: Node<Statement>,
|
||||
},
|
||||
UnexectedString {
|
||||
actual: Node<Statement>,
|
||||
},
|
||||
UndefinedVariable {
|
||||
identifier: Node<Statement>,
|
||||
},
|
||||
}
|
||||
|
||||
impl AnalyzerError {
|
||||
@ -550,6 +571,7 @@ impl AnalyzerError {
|
||||
actual_statement, ..
|
||||
} => actual_statement.position,
|
||||
AnalyzerError::UndefinedField { identifier, .. } => identifier.position,
|
||||
AnalyzerError::UndefinedType { identifier } => identifier.position,
|
||||
AnalyzerError::UndefinedVariable { identifier } => identifier.position,
|
||||
AnalyzerError::UnexpectedIdentifier { identifier } => identifier.position,
|
||||
AnalyzerError::UnexectedString { actual } => actual.position,
|
||||
@ -606,6 +628,9 @@ impl Display for AnalyzerError {
|
||||
AnalyzerError::UndefinedField { identifier, map } => {
|
||||
write!(f, "Undefined field {} in map {}", identifier, map)
|
||||
}
|
||||
AnalyzerError::UndefinedType { identifier } => {
|
||||
write!(f, "Undefined type {}", identifier)
|
||||
}
|
||||
AnalyzerError::UndefinedVariable { identifier } => {
|
||||
write!(f, "Undefined variable {}", identifier)
|
||||
}
|
||||
|
@ -15,7 +15,8 @@ pub mod value;
|
||||
pub mod vm;
|
||||
|
||||
pub use abstract_tree::{
|
||||
AbstractSyntaxTree, BinaryOperator, Node, Statement, StructDefinition, UnaryOperator,
|
||||
AbstractSyntaxTree, BinaryOperator, Node, Statement, StructDefinition, StructInstantiation,
|
||||
UnaryOperator,
|
||||
};
|
||||
pub use analyzer::{analyze, Analyzer, AnalyzerError};
|
||||
pub use built_in_function::{BuiltInFunction, BuiltInFunctionError};
|
||||
|
@ -686,8 +686,8 @@ impl<'src> Parser<'src> {
|
||||
));
|
||||
}
|
||||
|
||||
if let Statement::FunctionCall {
|
||||
function,
|
||||
if let Statement::Invokation {
|
||||
invokee: function,
|
||||
type_arguments,
|
||||
value_arguments,
|
||||
} = right.inner
|
||||
@ -701,8 +701,8 @@ impl<'src> Parser<'src> {
|
||||
};
|
||||
|
||||
return Ok(Node::new(
|
||||
Statement::FunctionCall {
|
||||
function,
|
||||
Statement::Invokation {
|
||||
invokee: function,
|
||||
type_arguments,
|
||||
value_arguments,
|
||||
},
|
||||
@ -762,48 +762,85 @@ impl<'src> Parser<'src> {
|
||||
fn parse_postfix(&mut self, left: Node<Statement>) -> Result<Node<Statement>, ParseError> {
|
||||
let left_start = left.position.0;
|
||||
|
||||
let statement = if let Token::LeftSquareBrace = &self.current.0 {
|
||||
let operator_start = self.current.1 .0;
|
||||
let statement = match &self.current.0 {
|
||||
Token::LeftParenthesis => {
|
||||
self.next_token()?;
|
||||
|
||||
self.next_token()?;
|
||||
let mut arguments = Vec::new();
|
||||
|
||||
let index = self.parse_statement(0)?;
|
||||
while self.current.0 != Token::RightParenthesis {
|
||||
let argument = self.parse_statement(0)?;
|
||||
|
||||
let operator_end = if let Token::RightSquareBrace = self.current.0 {
|
||||
let end = self.current.1 .1;
|
||||
arguments.push(argument);
|
||||
|
||||
if let Token::Comma = self.current.0 {
|
||||
self.next_token()?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let right_end = self.current.1 .1;
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
end
|
||||
} else {
|
||||
return Err(ParseError::ExpectedToken {
|
||||
expected: TokenKind::RightSquareBrace,
|
||||
Node::new(
|
||||
Statement::Invokation {
|
||||
invokee: Box::new(left),
|
||||
type_arguments: None,
|
||||
value_arguments: Some(arguments),
|
||||
},
|
||||
(left_start, right_end),
|
||||
)
|
||||
}
|
||||
Token::LeftSquareBrace => {
|
||||
let operator_start = self.current.1 .0;
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
let index = self.parse_statement(0)?;
|
||||
|
||||
let operator_end = if let Token::RightSquareBrace = self.current.0 {
|
||||
let end = self.current.1 .1;
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
end
|
||||
} else {
|
||||
return Err(ParseError::ExpectedToken {
|
||||
expected: TokenKind::RightSquareBrace,
|
||||
actual: self.current.0.to_owned(),
|
||||
position: self.current.1,
|
||||
});
|
||||
};
|
||||
|
||||
let right_end = self.current.1 .1;
|
||||
|
||||
Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(left),
|
||||
operator: Node::new(
|
||||
BinaryOperator::ListIndex,
|
||||
(operator_start, operator_end),
|
||||
),
|
||||
right: Box::new(index),
|
||||
},
|
||||
(left_start, right_end),
|
||||
)
|
||||
}
|
||||
Token::Semicolon => {
|
||||
let operator_end = self.current.1 .1;
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
Node::new(Statement::Nil(Box::new(left)), (left_start, operator_end))
|
||||
}
|
||||
_ => {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
actual: self.current.0.to_owned(),
|
||||
position: self.current.1,
|
||||
});
|
||||
};
|
||||
|
||||
let right_end = self.current.1 .1;
|
||||
|
||||
Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(left),
|
||||
operator: Node::new(BinaryOperator::ListIndex, (operator_start, operator_end)),
|
||||
right: Box::new(index),
|
||||
},
|
||||
(left_start, right_end),
|
||||
)
|
||||
} else if let Token::Semicolon = &self.current.0 {
|
||||
let operator_end = self.current.1 .1;
|
||||
|
||||
self.next_token()?;
|
||||
|
||||
Node::new(Statement::Nil(Box::new(left)), (left_start, operator_end))
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
actual: self.current.0.to_owned(),
|
||||
position: self.current.1,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if self.current.0.is_postfix() {
|
||||
@ -990,10 +1027,50 @@ impl Display for ParseError {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{BinaryOperator, Identifier, StructDefinition, Type, UnaryOperator};
|
||||
use crate::{
|
||||
BinaryOperator, Identifier, StructDefinition, StructInstantiation, Type, UnaryOperator,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tuple_struct_instantiation() {
|
||||
let input = "struct Foo(int, float) Foo(1, 2.0)";
|
||||
|
||||
assert_eq!(
|
||||
parse(input),
|
||||
Ok(AbstractSyntaxTree {
|
||||
nodes: [
|
||||
Node::new(
|
||||
Statement::StructDefinition(StructDefinition::Tuple {
|
||||
name: Node::new(Identifier::new("Foo"), (7, 10)),
|
||||
fields: vec![
|
||||
Node::new(Type::Integer, (11, 14)),
|
||||
Node::new(Type::Float, (16, 21))
|
||||
]
|
||||
}),
|
||||
(0, 22)
|
||||
),
|
||||
Node::new(
|
||||
Statement::Invokation {
|
||||
invokee: Box::new(Node::new(
|
||||
Statement::Identifier(Identifier::new("Foo")),
|
||||
(23, 26)
|
||||
)),
|
||||
type_arguments: None,
|
||||
value_arguments: Some(vec![
|
||||
Node::new(Statement::Constant(Value::integer(1)), (27, 28)),
|
||||
Node::new(Statement::Constant(Value::float(2.0)), (30, 33))
|
||||
])
|
||||
},
|
||||
(23, 34)
|
||||
)
|
||||
]
|
||||
.into()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_struct() {
|
||||
let input = "struct Foo(int, float)";
|
||||
|
@ -223,7 +223,7 @@ impl<'src> Token<'src> {
|
||||
pub fn precedence(&self) -> u8 {
|
||||
match self {
|
||||
Token::Dot => 10,
|
||||
Token::LeftSquareBrace => 9,
|
||||
Token::LeftParenthesis | Token::LeftSquareBrace => 9,
|
||||
Token::Star | Token::Slash | Token::Percent => 8,
|
||||
Token::Minus | Token::Plus => 6,
|
||||
Token::DoubleEqual
|
||||
@ -261,7 +261,10 @@ impl<'src> Token<'src> {
|
||||
}
|
||||
|
||||
pub fn is_postfix(&self) -> bool {
|
||||
matches!(self, Token::LeftSquareBrace | Token::Semicolon)
|
||||
matches!(
|
||||
self,
|
||||
Token::LeftParenthesis | Token::LeftSquareBrace | Token::Semicolon
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ use std::{
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Identifier, Struct};
|
||||
use crate::Identifier;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TypeConflict {
|
||||
@ -344,7 +344,7 @@ pub enum StructType {
|
||||
},
|
||||
Tuple {
|
||||
name: Identifier,
|
||||
types: Vec<Type>,
|
||||
fields: Vec<Type>,
|
||||
},
|
||||
Fields {
|
||||
name: Identifier,
|
||||
@ -352,26 +352,17 @@ pub enum StructType {
|
||||
},
|
||||
}
|
||||
|
||||
impl StructType {
|
||||
pub fn instantiate(&self) -> Struct {
|
||||
match self {
|
||||
StructType::Unit { name } => Struct::Unit { name: name.clone() },
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for StructType {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
StructType::Unit { name } => write!(f, "struct {name}"),
|
||||
StructType::Tuple { name, types } => {
|
||||
StructType::Tuple { name, fields } => {
|
||||
write!(f, "struct {name}(")?;
|
||||
|
||||
for (index, r#type) in types.iter().enumerate() {
|
||||
for (index, r#type) in fields.iter().enumerate() {
|
||||
write!(f, "{type}")?;
|
||||
|
||||
if index != types.len() - 1 {
|
||||
if index != fields.len() - 1 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ use std::{
|
||||
|
||||
use crate::{
|
||||
parse, value::ValueInner, AbstractSyntaxTree, Analyzer, BinaryOperator, BuiltInFunctionError,
|
||||
Context, DustError, Identifier, Node, ParseError, Span, Statement, StructDefinition,
|
||||
StructType, Type, UnaryOperator, Value, ValueError,
|
||||
Context, DustError, Identifier, Node, ParseError, Span, Statement, Struct, StructDefinition,
|
||||
StructInstantiation, StructType, Type, UnaryOperator, Value, ValueError,
|
||||
};
|
||||
|
||||
/// Run the source code and return the result.
|
||||
@ -329,17 +329,43 @@ impl Vm {
|
||||
Ok(function_call_return)
|
||||
}
|
||||
Statement::Constant(value) => Ok(Some(value.clone())),
|
||||
Statement::FunctionCall {
|
||||
function: function_node,
|
||||
Statement::Invokation {
|
||||
invokee,
|
||||
type_arguments: _,
|
||||
value_arguments: value_parameter_nodes,
|
||||
} => {
|
||||
let function_position = function_node.position;
|
||||
let function_value = if let Some(value) = self.run_statement(*function_node)? {
|
||||
let invokee_position = invokee.position;
|
||||
let invokee_type = invokee.inner.expected_type(&self.context);
|
||||
|
||||
if let Some(Type::Struct(StructType::Tuple { name, .. })) = invokee_type {
|
||||
let mut fields = Vec::new();
|
||||
|
||||
if let Some(value_parameter_nodes) = value_parameter_nodes {
|
||||
for statement in value_parameter_nodes {
|
||||
let position = statement.position;
|
||||
let value = if let Some(value) = self.run_statement(statement)? {
|
||||
value
|
||||
} else {
|
||||
return Err(VmError::ExpectedValue { position });
|
||||
};
|
||||
|
||||
fields.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
let struct_value = Value::r#struct(Struct::Tuple {
|
||||
name: name.clone(),
|
||||
fields,
|
||||
});
|
||||
|
||||
return Ok(Some(struct_value));
|
||||
}
|
||||
|
||||
let function_value = if let Some(value) = self.run_statement(*invokee)? {
|
||||
value
|
||||
} else {
|
||||
return Err(VmError::ExpectedValue {
|
||||
position: function_position,
|
||||
position: invokee_position,
|
||||
});
|
||||
};
|
||||
let function = if let Some(function) = function_value.as_function() {
|
||||
@ -347,7 +373,7 @@ impl Vm {
|
||||
} else {
|
||||
return Err(VmError::ExpectedFunction {
|
||||
actual: function_value,
|
||||
position: function_position,
|
||||
position: invokee_position,
|
||||
});
|
||||
};
|
||||
|
||||
@ -385,8 +411,8 @@ impl Vm {
|
||||
|
||||
println!("{type_option:?}");
|
||||
|
||||
if let Some(Type::Struct(struct_type)) = type_option {
|
||||
return Ok(Some(Value::r#struct(struct_type.instantiate())));
|
||||
if let Some(Type::Struct(StructType::Unit { name })) = type_option {
|
||||
return Ok(Some(Value::r#struct(Struct::Unit { name })));
|
||||
}
|
||||
|
||||
Err(VmError::UndefinedVariable {
|
||||
@ -592,15 +618,40 @@ impl Vm {
|
||||
name: name.inner.clone(),
|
||||
}),
|
||||
),
|
||||
StructDefinition::Tuple { name, fields } => {
|
||||
todo!()
|
||||
}
|
||||
StructDefinition::Tuple { name, fields } => (
|
||||
name.inner.clone(),
|
||||
Type::Struct(StructType::Tuple {
|
||||
name: name.inner.clone(),
|
||||
fields: fields
|
||||
.into_iter()
|
||||
.map(|type_node| type_node.inner)
|
||||
.collect(),
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
self.context.set_type(type_name, r#type, node.position);
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
Statement::StructInstantiation(struct_instantiation) => match struct_instantiation {
|
||||
StructInstantiation::Tuple { name, fields } => {
|
||||
Ok(Some(Value::r#struct(Struct::Tuple {
|
||||
name: name.inner,
|
||||
fields: fields
|
||||
.into_iter()
|
||||
.map(|node| {
|
||||
let position = node.position;
|
||||
if let Some(value) = self.run_statement(node)? {
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(VmError::ExpectedValue { position })
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<Value>, VmError>>()?,
|
||||
})))
|
||||
}
|
||||
},
|
||||
Statement::UnaryOperation { operator, operand } => {
|
||||
let position = operand.position;
|
||||
let value = if let Some(value) = self.run_statement(*operand)? {
|
||||
@ -815,6 +866,19 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn define_and_instantiate_tuple_struct() {
|
||||
let input = "struct Foo(int) Foo(42)";
|
||||
|
||||
assert_eq!(
|
||||
run(input),
|
||||
Ok(Some(Value::r#struct(Struct::Tuple {
|
||||
name: Identifier::new("Foo"),
|
||||
fields: vec![Value::integer(42)]
|
||||
})))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assign_unit_struct_variable() {
|
||||
let input = "
|
||||
|
Loading…
Reference in New Issue
Block a user