diff --git a/dust-lang/src/abstract_tree/identifier.rs b/dust-lang/src/abstract_tree/identifier.rs index 7bf4496..cdfc647 100644 --- a/dust-lang/src/abstract_tree/identifier.rs +++ b/dust-lang/src/abstract_tree/identifier.rs @@ -10,7 +10,7 @@ use crate::{ use super::{AbstractNode, Action, Type}; -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] pub struct Identifier(Arc); impl Identifier { diff --git a/dust-lang/src/abstract_tree/type.rs b/dust-lang/src/abstract_tree/type.rs index f5c24cd..e7ccf66 100644 --- a/dust-lang/src/abstract_tree/type.rs +++ b/dust-lang/src/abstract_tree/type.rs @@ -1,5 +1,7 @@ use std::fmt::{self, Display, Formatter}; +use clap::error::Result; + use crate::{ abstract_tree::Identifier, context::Context, @@ -22,7 +24,6 @@ pub enum Type { ListOf(Box), ListExact(Vec), Map, - Named(Identifier), None, Range, String, @@ -75,6 +76,32 @@ impl Type { return Ok(()); } + ( + Type::Structure { + name: left_name, + fields: left_fields, + }, + Type::Structure { + name: right_name, + fields: right_fields, + }, + ) => { + if left_name == right_name { + for ((left_field_name, left_type), (right_field_name, right_type)) in + left_fields.iter().zip(right_fields.iter()) + { + if left_field_name != right_field_name || left_type.node != right_type.node + { + return Err(TypeConflict { + actual: other.clone(), + expected: self.clone(), + }); + } + } + + return Ok(()); + } + } ( Type::Function { parameter_types: left_parameters, @@ -89,27 +116,6 @@ impl Type { return Ok(()); } } - (Type::Named(left), Type::Named(right)) => { - if left == right { - return Ok(()); - } - } - ( - Type::Named(named), - Type::Structure { - name: struct_name, .. - }, - ) - | ( - Type::Structure { - name: struct_name, .. - }, - Type::Named(named), - ) => { - if named == struct_name { - return Ok(()); - } - } _ => {} } @@ -172,8 +178,7 @@ impl Display for Type { write!(f, ") : {return_type}") } - Type::Structure { .. } => todo!(), - Type::Named(name) => write!(f, "{name}"), + Type::Structure { name, .. } => write!(f, "{name}"), } } } diff --git a/dust-lang/src/abstract_tree/value_node.rs b/dust-lang/src/abstract_tree/value_node.rs index e7810a4..0ff4bf3 100644 --- a/dust-lang/src/abstract_tree/value_node.rs +++ b/dust-lang/src/abstract_tree/value_node.rs @@ -1,7 +1,5 @@ use std::{cmp::Ordering, collections::BTreeMap, ops::Range}; -use chumsky::container::Container; - use crate::{ context::Context, error::{RuntimeError, ValidationError}, @@ -145,7 +143,7 @@ impl AbstractNode for ValueNode { { let types = if let Some(r#type) = context.get_type(name)? { if let Type::Structure { - name, + name: _, fields: types, } = r#type { @@ -319,8 +317,16 @@ impl Ord for ValueNode { name: right_name, fields: right_fields, }, - ) => todo!(), - (Structure { name, fields }, _) => todo!(), + ) => { + let name_cmp = left_name.cmp(right_name); + + if name_cmp.is_eq() { + left_fields.cmp(right_fields) + } else { + name_cmp + } + } + (Structure { .. }, _) => Ordering::Greater, } } } diff --git a/dust-lang/src/context.rs b/dust-lang/src/context.rs index 06df97b..1ff04e9 100644 --- a/dust-lang/src/context.rs +++ b/dust-lang/src/context.rs @@ -79,13 +79,7 @@ impl Context { pub fn get_type(&self, identifier: &Identifier) -> Result, ValidationError> { if let Some(value_data) = self.inner.read()?.get(identifier) { let r#type = match value_data { - ValueData::Type(r#type) => { - if let Type::Named(name) = r#type { - return self.get_type(name); - } - - r#type.clone() - } + ValueData::Type(r#type) => r#type.clone(), ValueData::Value(value) => value.r#type(self)?, }; diff --git a/dust-lang/src/error.rs b/dust-lang/src/error.rs index 4517cb9..388bf03 100644 --- a/dust-lang/src/error.rs +++ b/dust-lang/src/error.rs @@ -13,10 +13,12 @@ pub enum Error { Parse { expected: String, span: (usize, usize), + reason: String, }, Lex { expected: String, span: (usize, usize), + reason: String, }, Runtime { error: RuntimeError, @@ -29,11 +31,15 @@ pub enum Error { } impl Error { - pub fn build_report(self, source: &str) -> Vec { + pub fn build_report(self, source: &str) -> Result, io::Error> { let (mut builder, validation_error, error_position) = match self { - Error::Parse { expected, span } => { - let message = if expected.is_empty() { - "Invalid character.".to_string() + Error::Parse { + expected, + span, + reason, + } => { + let description = if expected.is_empty() { + "Invalid token.".to_string() } else { format!("Expected {expected}.") }; @@ -44,31 +50,37 @@ impl Error { "input", span.1, ) + .with_message(description) .with_label( Label::new(("input", span.0..span.1)) - .with_message(message) + .with_message(reason) .with_color(Color::Red), ), None, span.into(), ) } - Error::Lex { expected, span } => { - let message = if expected.is_empty() { - "Invalid token.".to_string() + Error::Lex { + expected, + span, + reason, + } => { + let description = if expected.is_empty() { + "Invalid character.".to_string() } else { format!("Expected {expected}.") }; ( Report::build( - ReportKind::Custom("Dust Error", Color::White), + ReportKind::Custom("Lexing Error", Color::White), "input", span.1, ) + .with_message(description) .with_label( Label::new(("input", span.0..span.1)) - .with_message(message) + .with_message(reason) .with_color(Color::Red), ), None, @@ -188,9 +200,9 @@ impl Error { builder .finish() - .write_for_stdout(sources([("input", source)]), &mut output); + .write_for_stdout(sources([("input", source)]), &mut output)?; - output + Ok(output) } } @@ -199,6 +211,7 @@ impl From> for Error { Error::Lex { expected: error.expected().map(|error| error.to_string()).collect(), span: (error.span().start(), error.span().end()), + reason: error.reason().to_string(), } } } @@ -208,6 +221,7 @@ impl<'src> From>> for Error { Error::Parse { expected: error.expected().map(|error| error.to_string()).collect(), span: (error.span().start(), error.span().end()), + reason: error.reason().to_string(), } } } diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index 234534f..a8872db 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, collections::HashMap}; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; use chumsky::{input::SpannedInput, pratt::*, prelude::*}; @@ -32,6 +32,8 @@ pub fn parser<'src>() -> impl Parser< extra::Err, SimpleSpan>>, > { let identifiers: RefCell> = RefCell::new(HashMap::new()); + let _custom_types: Rc>> = + Rc::new(RefCell::new(HashMap::new())); let identifier = select! { Token::Identifier(text) => { @@ -61,6 +63,7 @@ pub fn parser<'src>() -> impl Parser< } .map_with(|value, state| Expression::Value(value).with_position(state.span())); + let custom_types = _custom_types.clone(); let r#type = recursive(|r#type| { let function_type = r#type .clone() @@ -106,13 +109,22 @@ pub fn parser<'src>() -> impl Parser< just(Token::Keyword("range")).to(Type::Range), just(Token::Keyword("str")).to(Type::String), just(Token::Keyword("list")).to(Type::List), - identifier.clone().map(|identifier| Type::Named(identifier)), + identifier.clone().try_map(move |identifier, span| { + custom_types + .borrow() + .get(&identifier) + .cloned() + .ok_or_else(|| { + Rich::custom(span, format!("There is no type named {identifier}.")) + }) + }), )) }) .map_with(|r#type, state| r#type.with_position(state.span())); let type_specification = just(Token::Control(Control::Colon)).ignore_then(r#type.clone()); + let custom_types = _custom_types.clone(); let positioned_statement = recursive(|positioned_statement| { let block = positioned_statement .clone() @@ -454,15 +466,22 @@ pub fn parser<'src>() -> impl Parser< structure_field_definition .separated_by(just(Token::Control(Control::Comma))) .allow_trailing() - .collect() + .collect::)>>() .delimited_by( just(Token::Control(Control::CurlyOpen)), just(Token::Control(Control::CurlyClose)), ), ) - .map_with(|(name, fields), state| { - Statement::StructureDefinition(StructureDefinition::new(name, fields)) - .with_position(state.span()) + .map_with(move |(name, fields), state| { + let definition = StructureDefinition::new(name.clone(), fields.clone()); + let r#type = Type::Structure { + name: name.clone(), + fields, + }; + + custom_types.as_ref().borrow_mut().insert(name, r#type); + + Statement::StructureDefinition(definition).with_position(state.span()) }); choice(( diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index f82b933..8fb9cae 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -79,43 +79,7 @@ impl Value { } pub fn r#type(&self, context: &Context) -> Result { - let r#type = match self.0.as_ref() { - ValueInner::Boolean(_) => Type::Boolean, - ValueInner::Float(_) => Type::Float, - ValueInner::Integer(_) => Type::Integer, - ValueInner::List(values) => { - let mut types = Vec::with_capacity(values.len()); - - for value in values { - types.push(value.r#type(context)?); - } - - Type::ListExact(types) - } - ValueInner::Map(_) => Type::Map, - ValueInner::Range(_) => Type::Range, - ValueInner::String(_) => Type::String, - ValueInner::Function(function) => match function { - Function::Parsed(parsed_function) => Type::Function { - parameter_types: parsed_function - .parameters - .iter() - .map(|(_, r#type)| r#type.node.clone()) - .collect(), - return_type: Box::new(parsed_function.return_type.node.clone()), - }, - Function::BuiltIn(built_in_function) => built_in_function.r#type(), - }, - ValueInner::Structure { name, .. } => { - if let Some(r#type) = context.get_type(name)? { - r#type - } else { - return Err(ValidationError::TypeNotFound(name.clone())); - } - } - }; - - Ok(r#type) + self.0.r#type(context) } pub fn as_boolean(&self) -> Option { @@ -232,6 +196,48 @@ pub enum ValueInner { }, } +impl ValueInner { + pub fn r#type(&self, context: &Context) -> Result { + let r#type = match self { + ValueInner::Boolean(_) => Type::Boolean, + ValueInner::Float(_) => Type::Float, + ValueInner::Integer(_) => Type::Integer, + ValueInner::List(values) => { + let mut types = Vec::with_capacity(values.len()); + + for value in values { + types.push(value.r#type(context)?); + } + + Type::ListExact(types) + } + ValueInner::Map(_) => Type::Map, + ValueInner::Range(_) => Type::Range, + ValueInner::String(_) => Type::String, + ValueInner::Function(function) => match function { + Function::Parsed(parsed_function) => Type::Function { + parameter_types: parsed_function + .parameters + .iter() + .map(|(_, r#type)| r#type.node.clone()) + .collect(), + return_type: Box::new(parsed_function.return_type.node.clone()), + }, + Function::BuiltIn(built_in_function) => built_in_function.r#type(), + }, + ValueInner::Structure { name, .. } => { + if let Some(r#type) = context.get_type(name)? { + r#type + } else { + return Err(ValidationError::TypeNotFound(name.clone())); + } + } + }; + + Ok(r#type) + } +} + impl Eq for ValueInner {} impl PartialOrd for ValueInner { @@ -397,7 +403,7 @@ impl BuiltInFunction { BuiltInFunction::IntParse => { let string = arguments.get(0).unwrap(); - if let ValueInner::String(string) = string.inner().as_ref() { + if let ValueInner::String(_string) = string.inner().as_ref() { // let integer = string.parse(); todo!() diff --git a/dust-shell/src/cli.rs b/dust-shell/src/cli.rs index b9ac142..ee685b3 100644 --- a/dust-shell/src/cli.rs +++ b/dust-shell/src/cli.rs @@ -87,7 +87,7 @@ pub fn run_shell(context: Context) { Ok(None) => {} Err(errors) => { for error in errors { - let report = error.build_report(&buffer); + let report = error.build_report(&buffer).unwrap(); stderr().write_all(&report).unwrap(); } diff --git a/dust-shell/src/main.rs b/dust-shell/src/main.rs index 1a07c86..86daaed 100644 --- a/dust-shell/src/main.rs +++ b/dust-shell/src/main.rs @@ -58,7 +58,7 @@ fn main() { } Err(errors) => { for error in errors { - let report = error.build_report(&source); + let report = error.build_report(&source).unwrap(); stderr().write_all(&report).unwrap(); }