diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index 90cbf44..d513d63 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -13,8 +13,9 @@ use crate::{ ast::{ AbstractSyntaxTree, AstError, BlockExpression, CallExpression, ElseExpression, FieldAccessExpression, IfExpression, LetStatement, ListExpression, ListIndexExpression, - LoopExpression, MapExpression, Node, OperatorExpression, RangeExpression, Span, Statement, - StructDefinition, StructExpression, TupleAccessExpression, + LiteralExpression, LoopExpression, MapExpression, Node, OperatorExpression, + PrimitiveValueExpression, RangeExpression, Span, Statement, StructDefinition, + StructExpression, TupleAccessExpression, }, core_library, parse, Context, ContextError, DustError, Expression, Identifier, StructType, Type, @@ -224,6 +225,56 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> { Expression::ListIndex(list_index_expression) => { let ListIndexExpression { list, index } = list_index_expression.inner.as_ref(); + let list_type = list.return_type(&self.context)?; + + let literal_type = if let Expression::Literal(Node { inner, .. }) = index { + Some(inner.as_ref().clone()) + } else { + None + }; + + if let Some(Type::List { length, .. }) = list_type { + if let Some(LiteralExpression::Primitive(PrimitiveValueExpression::Integer( + integer, + ))) = literal_type + { + if integer < 0 || integer >= length as i64 { + return Err(AnalysisError::IndexOutOfBounds { + index: index.clone(), + length, + list: list.clone(), + index_value: integer, + }); + } + } + } + + if let Some(Type::String { + length: Some(length), + }) = list_type + { + if let Some(LiteralExpression::Primitive(PrimitiveValueExpression::Integer( + integer, + ))) = literal_type + { + if integer < 0 || integer >= length as i64 { + return Err(AnalysisError::IndexOutOfBounds { + index: index.clone(), + length, + list: list.clone(), + index_value: integer, + }); + } + } + } + + if list_type.is_none() { + return Err(AnalysisError::ExpectedValueFromExpression { + expression: list.clone(), + found_type: list_type, + }); + } + self.analyze_expression(list)?; self.analyze_expression(index)?; } @@ -302,10 +353,60 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> { OperatorExpression::Math { left, right, .. } => { self.analyze_expression(left)?; self.analyze_expression(right)?; + + let left_type = left.return_type(&self.context)?; + let right_type = right.return_type(&self.context)?; + + if left_type.is_none() { + return Err(AnalysisError::ExpectedValueFromExpression { + expression: left.clone(), + found_type: left_type, + }); + } + + if right_type.is_none() { + return Err(AnalysisError::ExpectedValueFromExpression { + expression: right.clone(), + found_type: right_type, + }); + } + + if left_type != right_type { + return Err(AnalysisError::ExpectedType { + expected: left_type.unwrap(), + actual: right_type.unwrap(), + actual_expression: right.clone(), + }); + } } OperatorExpression::Logic { left, right, .. } => { self.analyze_expression(left)?; self.analyze_expression(right)?; + + let left_type = left.return_type(&self.context)?; + let right_type = right.return_type(&self.context)?; + + if left_type.is_none() { + return Err(AnalysisError::ExpectedValueFromExpression { + expression: left.clone(), + found_type: left_type, + }); + } + + if right_type.is_none() { + return Err(AnalysisError::ExpectedValueFromExpression { + expression: right.clone(), + found_type: right_type, + }); + } + + if left_type != right_type { + return Err(AnalysisError::ExpectedType { + expected: left_type.unwrap(), + actual: right_type.unwrap(), + actual_expression: right.clone(), + }); + } } }, Expression::Range(range_expression) => match range_expression.inner.as_ref() { @@ -325,7 +426,7 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> { .map_err(|error| AnalysisError::ContextError { error, position: name.position, - }); + })?; for (_, expression) in fields { self.analyze_expression(expression)?; @@ -401,23 +502,21 @@ pub enum AnalysisError { error: ContextError, position: Span, }, - ExpectedBoolean { - actual: Statement, + ExpectedType { + expected: Type, + actual: Type, + actual_expression: Expression, + }, + ExpectedTypeMultiple { + expected: Vec, + actual: Type, + actual_expression: Expression, }, ExpectedIdentifier { - actual: Statement, + actual: Expression, }, ExpectedIdentifierOrString { - actual: Statement, - }, - ExpectedIntegerOrRange { - actual: Statement, - }, - ExpectedList { - actual: Statement, - }, - ExpectedMap { - actual: Statement, + actual: Expression, }, ExpectedValueFromStatement { actual: Statement, @@ -432,9 +531,9 @@ pub enum AnalysisError { position: Span, }, IndexOutOfBounds { - list: Statement, - index: Statement, - index_value: usize, + list: Expression, + index: Expression, + index_value: i64, length: usize, }, TypeConflict { @@ -443,8 +542,8 @@ pub enum AnalysisError { expected: Type, }, UndefinedField { - identifier: Statement, - statement: Statement, + identifier: Expression, + statement: Expression, }, UndefinedType { identifier: Node, @@ -453,7 +552,7 @@ pub enum AnalysisError { identifier: Node, }, UnexectedString { - actual: Statement, + actual: Expression, }, UndefinedVariable { identifier: Node, @@ -471,12 +570,14 @@ impl AnalysisError { match self { AnalysisError::AstError(ast_error) => ast_error.position(), AnalysisError::ContextError { position, .. } => *position, - AnalysisError::ExpectedBoolean { actual } => actual.position(), + AnalysisError::ExpectedType { + actual_expression, .. + } => actual_expression.position(), + AnalysisError::ExpectedTypeMultiple { + actual_expression, .. + } => actual_expression.position(), AnalysisError::ExpectedIdentifier { actual } => actual.position(), AnalysisError::ExpectedIdentifierOrString { actual } => actual.position(), - AnalysisError::ExpectedIntegerOrRange { actual } => actual.position(), - AnalysisError::ExpectedList { actual } => actual.position(), - AnalysisError::ExpectedMap { actual } => actual.position(), AnalysisError::ExpectedValueFromExpression { expression, .. } => expression.position(), AnalysisError::ExpectedValueFromStatement { actual } => actual.position(), AnalysisError::ExpectedValueArgumentCount { position, .. } => *position, @@ -499,23 +600,37 @@ impl Display for AnalysisError { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { AnalysisError::AstError(ast_error) => write!(f, "{}", ast_error), - Self::ContextError { error, position } => { - write!(f, "Context error at {:?}: {}", position, error) + AnalysisError::ContextError { error, .. } => write!(f, "{}", error), + + AnalysisError::ExpectedType { + expected, + actual, + actual_expression, + } => { + write!( + f, + "Expected type {:?}, found {:?} in {}", + expected, actual, actual_expression + ) } - AnalysisError::ExpectedBoolean { actual, .. } => { - write!(f, "Expected boolean, found {}", actual) + AnalysisError::ExpectedTypeMultiple { + expected, + actual, + actual_expression, + } => { + write!( + f, + "Expected one of {:?}, found {:?} in {}", + expected, actual, actual_expression + ) } + AnalysisError::ExpectedIdentifier { actual, .. } => { write!(f, "Expected identifier, found {}", actual) } AnalysisError::ExpectedIdentifierOrString { actual } => { write!(f, "Expected identifier or string, found {}", actual) } - AnalysisError::ExpectedIntegerOrRange { actual, .. } => { - write!(f, "Expected integer or range, found {}", actual) - } - AnalysisError::ExpectedList { actual } => write!(f, "Expected list, found {}", actual), - AnalysisError::ExpectedMap { actual } => write!(f, "Expected map, found {}", actual), AnalysisError::ExpectedValueFromExpression { expression, found_type, @@ -623,18 +738,46 @@ mod tests { #[test] fn tuple_struct_with_wrong_field_types() { let source = " - struct Foo(int, float) + struct Foo(int, float); Foo(1, 2) "; - assert_eq!(analyze(source), todo!()); + assert_eq!( + analyze(source), + Err(DustError::Analysis { + analysis_error: AnalysisError::TypeConflict { + actual_expression: Expression::literal(2, (52, 53)), + actual_type: Type::Integer, + expected: Type::Float, + }, + source, + }) + ); } #[test] fn constant_list_index_out_of_bounds() { let source = "[1, 2, 3][3]"; - assert_eq!(analyze(source), todo!()); + assert_eq!( + analyze(source), + Err(DustError::Analysis { + analysis_error: AnalysisError::IndexOutOfBounds { + list: Expression::list( + vec![ + Expression::literal(1, (1, 2)), + Expression::literal(2, (4, 5)), + Expression::literal(3, (7, 8)), + ], + (0, 9) + ), + index: Expression::literal(3, (10, 11)), + index_value: 3, + length: 3, + }, + source, + }) + ); } #[test] @@ -665,18 +808,21 @@ mod tests { assert_eq!(analyze(source), todo!()); } - #[test] - fn length_no_arguments() { - let source = "length()"; - - assert_eq!(analyze(source), todo!()); - } - #[test] fn float_plus_integer() { let source = "42.0 + 2"; - assert_eq!(analyze(source), todo!()); + assert_eq!( + analyze(source), + Err(DustError::Analysis { + analysis_error: AnalysisError::ExpectedType { + expected: Type::Float, + actual: Type::Integer, + actual_expression: Expression::literal(2, (7, 8)), + }, + source, + }) + ); } #[test] diff --git a/dust-lang/src/ast/expression.rs b/dust-lang/src/ast/expression.rs index 74fbf31..54f747c 100644 --- a/dust-lang/src/ast/expression.rs +++ b/dust-lang/src/ast/expression.rs @@ -381,7 +381,9 @@ impl Expression { PrimitiveValueExpression::Integer(_) => Some(Type::Integer), PrimitiveValueExpression::Float(_) => Some(Type::Float), }, - LiteralExpression::String(_) => Some(Type::String), + LiteralExpression::String(string) => Some(Type::String { + length: Some(string.len()), + }), }, Expression::Loop(loop_expression) => match loop_expression.inner.as_ref() { LoopExpression::For { block, .. } => block.inner.return_type(context)?, diff --git a/dust-lang/src/built_in_function.rs b/dust-lang/src/built_in_function.rs index 4164a18..7f8d9d7 100644 --- a/dust-lang/src/built_in_function.rs +++ b/dust-lang/src/built_in_function.rs @@ -14,15 +14,11 @@ use crate::{Identifier, Type, Value}; pub enum BuiltInFunction { // String tools ToString, - LengthString, // Integer and float tools IsEven, IsOdd, - // List tools - LengthList, - // I/O ReadLine, WriteLine, @@ -33,8 +29,6 @@ impl BuiltInFunction { match self { BuiltInFunction::IsEven => "is_even", BuiltInFunction::IsOdd => "is_odd", - BuiltInFunction::LengthList => "length", - BuiltInFunction::LengthString => "length", BuiltInFunction::ReadLine => "read_line", BuiltInFunction::ToString { .. } => "to_string", BuiltInFunction::WriteLine => "write_line", @@ -46,8 +40,6 @@ impl BuiltInFunction { BuiltInFunction::ToString { .. } => None, BuiltInFunction::IsEven => None, BuiltInFunction::IsOdd => None, - BuiltInFunction::LengthList => None, - BuiltInFunction::LengthString => None, BuiltInFunction::ReadLine => None, BuiltInFunction::WriteLine => None, } @@ -58,13 +50,6 @@ impl BuiltInFunction { BuiltInFunction::ToString { .. } => Some(vec![("value".into(), Type::Any)]), BuiltInFunction::IsEven => Some(vec![("value".into(), Type::Number)]), BuiltInFunction::IsOdd => Some(vec![("value".into(), Type::Number)]), - BuiltInFunction::LengthList => Some(vec![( - "value".into(), - Type::ListOf { - item_type: Box::new(Type::Any), - }, - )]), - BuiltInFunction::LengthString => Some(vec![("value".into(), Type::String)]), BuiltInFunction::ReadLine => None, BuiltInFunction::WriteLine => Some(vec![("value".into(), Type::Any)]), } @@ -72,12 +57,10 @@ impl BuiltInFunction { pub fn return_type(&self) -> Option { match self { - BuiltInFunction::ToString { .. } => Some(Type::String), + BuiltInFunction::ToString { .. } => Some(Type::String { length: None }), BuiltInFunction::IsEven => Some(Type::Boolean), BuiltInFunction::IsOdd => Some(Type::Boolean), - BuiltInFunction::LengthList => Some(Type::Number), - BuiltInFunction::LengthString => Some(Type::Number), - BuiltInFunction::ReadLine => Some(Type::String), + BuiltInFunction::ReadLine => Some(Type::String { length: None }), BuiltInFunction::WriteLine => None, } } @@ -117,20 +100,6 @@ impl BuiltInFunction { Err(BuiltInFunctionError::ExpectedInteger) } } - BuiltInFunction::LengthList => { - if let Value::List(list) = &value_arguments.unwrap()[0] { - Ok(Some(Value::Integer(list.len() as i64))) - } else { - Err(BuiltInFunctionError::ExpectedList) - } - } - BuiltInFunction::LengthString => { - if let Value::String(string) = &value_arguments.unwrap()[0] { - Ok(Some(Value::Integer(string.len() as i64))) - } else { - Err(BuiltInFunctionError::ExpectedString) - } - } BuiltInFunction::ReadLine => { let mut input = String::new(); diff --git a/dust-lang/src/core_library.rs b/dust-lang/src/core_library.rs index 6cbf6b5..8b14ae4 100644 --- a/dust-lang/src/core_library.rs +++ b/dust-lang/src/core_library.rs @@ -34,15 +34,6 @@ pub fn core_library<'a>() -> &'a Context { (0, 0), ), ), - ( - Identifier::new("length"), - ( - ContextData::VariableValue(Value::Function(Function::BuiltIn( - BuiltInFunction::LengthList, - ))), - (0, 0), - ), - ), ( Identifier::new("read_line"), ( diff --git a/dust-lang/src/type.rs b/dust-lang/src/type.rs index 64bae35..d914696 100644 --- a/dust-lang/src/type.rs +++ b/dust-lang/src/type.rs @@ -54,7 +54,9 @@ pub enum Type { Range { r#type: RangeableType, }, - String, + String { + length: Option, + }, Struct(StructType), Tuple(Vec), } @@ -83,7 +85,7 @@ impl Type { | (Type::Character, Type::Character) | (Type::Float, Type::Float) | (Type::Integer, Type::Integer) - | (Type::String, Type::String) => return Ok(()), + | (Type::String { .. }, Type::String { .. }) => return Ok(()), ( Type::Generic { concrete_type: left, @@ -272,7 +274,7 @@ impl Display for Type { } Type::Number => write!(f, "num"), Type::Range { r#type } => write!(f, "{type} range"), - Type::String => write!(f, "str"), + Type::String { .. } => write!(f, "str"), Type::Struct(struct_type) => write!(f, "{struct_type}"), Type::Tuple(fields) => { write!(f, "(")?; @@ -358,8 +360,8 @@ impl Ord for Type { left_type.cmp(right_type) } (Type::Range { .. }, _) => Ordering::Greater, - (Type::String, Type::String) => Ordering::Equal, - (Type::String, _) => Ordering::Greater, + (Type::String { length: left }, Type::String { length: right }) => left.cmp(right), + (Type::String { .. }, _) => Ordering::Greater, (Type::Struct(left_struct), Type::Struct(right_struct)) => { left_struct.cmp(right_struct) } @@ -638,7 +640,7 @@ mod tests { #[test] fn errors() { let foo = Type::Integer; - let bar = Type::String; + let bar = Type::String { length: None }; assert_eq!( foo.check(&bar), @@ -666,7 +668,7 @@ mod tests { Type::Range { r#type: RangeableType::Integer, }, - Type::String, + Type::String { length: None }, ]; for left in types.clone() { diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index 391fcc7..11ac735 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -200,7 +200,9 @@ impl Value { r#type: rangeable_type, } } - Value::String(_) => Type::String, + Value::String(string) => Type::String { + length: Some(string.len()), + }, Value::Struct(r#struct) => match r#struct { Struct::Unit { name } => Type::Struct(StructType::Unit { name: name.clone() }), Struct::Tuple { name, fields } => { diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 3c91ff8..6be144d 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -1685,12 +1685,26 @@ mod tests { } #[test] - fn length() { - let input = "length([1, 2, 3])"; + fn list_length() { + let input = "[1, 2, 3].length"; assert_eq!(run(input), Ok(Some(Value::Integer(3)))); } + #[test] + fn string_length() { + let input = "\"hello\".length"; + + assert_eq!(run(input), Ok(Some(Value::Integer(5)))); + } + + #[test] + fn map_length() { + let input = "map { a = 42, b = 4.0 }.length"; + + assert_eq!(run(input), Ok(Some(Value::Integer(2)))); + } + #[test] fn add() { let input = "1 + 2";