diff --git a/Cargo.lock b/Cargo.lock index d418acda..f8ae2787 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1181,7 +1181,6 @@ dependencies = [ "derive_more", "factorial", "itertools 0.14.0", - "log", "md5", "miette", "ndc_macros", diff --git a/ndc_bin/src/docs.rs b/ndc_bin/src/docs.rs index 252352a7..9b9ba97d 100644 --- a/ndc_bin/src/docs.rs +++ b/ndc_bin/src/docs.rs @@ -18,8 +18,7 @@ pub fn docs(query: Option<&str>) -> anyhow::Result<()> { let matched_functions = functions .into_iter() - .flat_map(|x| x.implementations().collect::>()) - .filter(|(_, func)| { + .filter(|func| { if let Some(query) = query { string_match(query, func.name()) } else { @@ -28,7 +27,7 @@ pub fn docs(query: Option<&str>) -> anyhow::Result<()> { }) .collect::>() .tap_mut(|list| { - list.sort_by(|(_, l), (_, r)| { + list.sort_by(|l, r| { if let Some(query) = query { normalized_damerau_levenshtein(l.name(), query) .partial_cmp(&normalized_damerau_levenshtein(r.name(), query)) @@ -46,31 +45,38 @@ pub fn docs(query: Option<&str>) -> anyhow::Result<()> { skin.headers[1].align = Alignment::Left; skin.headers[2].align = Alignment::Left; - for (type_sig, function) in matched_functions { + for function in matched_functions { let mut signature = String::new(); + let type_sig = function.type_signature(); match type_sig { TypeSignature::Variadic => { - writeln!(signature, "(*args**)")?; + write!(signature, "(*args**)")?; } TypeSignature::Exact(params) => { write!(signature, "(")?; let mut param_iter = params.iter().peekable(); while let Some(Parameter { name, type_name }) = param_iter.next() { - write!(signature, "*{name}*: **{}**", type_name.as_str().green())?; + write!( + signature, + "*{name}*: **{}**", + format!("{}", type_name).green() + )?; if param_iter.peek().is_some() { write!(signature, ", ")?; } } - writeln!(signature, ")")?; + write!(signature, ")")?; } } let name = function.name(); let documentation = function.documentation().trim(); + let return_type = function.return_type(); let markdown = format!( - "---\n\n## **{}**{signature}\n{documentation}{}", + "---\n\n## **{}**{signature} -> {}\n\n{documentation}{}", name.green(), + format!("{}", return_type).green().bold(), if documentation.is_empty() { "" } else { "\n\n" } ); diff --git a/ndc_lib/Cargo.toml b/ndc_lib/Cargo.toml index 779cfb00..28b01be9 100644 --- a/ndc_lib/Cargo.toml +++ b/ndc_lib/Cargo.toml @@ -27,7 +27,6 @@ thiserror.workspace = true # Crypto md5 = { version = "0.8.0", optional = true } sha1 = { version = "0.10.6", optional = true } -log = "0.4.29" [features] default = ["ahash", "crypto"] diff --git a/ndc_lib/src/ast/expression.rs b/ndc_lib/src/ast/expression.rs index e9c07c6f..6490812a 100644 --- a/ndc_lib/src/ast/expression.rs +++ b/ndc_lib/src/ast/expression.rs @@ -1,10 +1,18 @@ use crate::ast::operator::LogicalOperator; use crate::ast::parser::Error as ParseError; use crate::interpreter::evaluate::EvaluationError; +use crate::interpreter::function::StaticType; use crate::lexer::Span; use num::BigInt; use num::complex::Complex64; +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum Binding { + None, + Resolved(ResolvedVar), + Dynamic(Vec), // figure it out at runtime +} + #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum ResolvedVar { Captured { depth: usize, slot: usize }, @@ -28,7 +36,7 @@ pub enum Expression { ComplexLiteral(Complex64), Identifier { name: String, - resolved: Option, + resolved: Binding, }, Statement(Box), Logical { @@ -49,14 +57,15 @@ pub enum Expression { l_value: Lvalue, r_value: Box, operation: String, - resolved_assign_operation: Option, - resolved_operation: Option, + resolved_assign_operation: Binding, + resolved_operation: Binding, }, FunctionDeclaration { name: Option, resolved_name: Option, - arguments: Box, + parameters: Box, body: Box, + return_type: Option, pure: bool, }, Block { @@ -332,7 +341,8 @@ impl std::fmt::Debug for ExpressionLocation { .finish(), Expression::FunctionDeclaration { name, - arguments, + parameters, + return_type, body, pure, resolved_name, @@ -340,7 +350,8 @@ impl std::fmt::Debug for ExpressionLocation { .debug_struct("FunctionDeclaration") .field("name", name) .field("resolved_name", resolved_name) - .field("arguments", arguments) + .field("parameters", parameters) + .field("return_type", return_type) .field("body", body) .field("pure", pure) .finish(), diff --git a/ndc_lib/src/ast/mod.rs b/ndc_lib/src/ast/mod.rs index 85a7b176..d8dc7c71 100644 --- a/ndc_lib/src/ast/mod.rs +++ b/ndc_lib/src/ast/mod.rs @@ -2,7 +2,9 @@ mod expression; mod operator; mod parser; -pub use expression::{Expression, ExpressionLocation, ForBody, ForIteration, Lvalue, ResolvedVar}; +pub use expression::{ + Binding, Expression, ExpressionLocation, ForBody, ForIteration, Lvalue, ResolvedVar, +}; pub use operator::{BinaryOperator, LogicalOperator, UnaryOperator}; pub use parser::Error; diff --git a/ndc_lib/src/ast/parser.rs b/ndc_lib/src/ast/parser.rs index f1778591..97a0494d 100644 --- a/ndc_lib/src/ast/parser.rs +++ b/ndc_lib/src/ast/parser.rs @@ -3,7 +3,7 @@ use std::fmt::Write; use miette::Diagnostic; use crate::ast::Expression; -use crate::ast::expression::{ExpressionLocation, ForBody, ForIteration, Lvalue}; +use crate::ast::expression::{Binding, ExpressionLocation, ForBody, ForIteration, Lvalue}; use crate::ast::operator::{BinaryOperator, LogicalOperator, UnaryOperator}; use crate::lexer::{Span, Token, TokenLocation}; @@ -201,7 +201,7 @@ impl Parser { function: Box::new( Expression::Identifier { name: operator_token_loc.token.to_string(), - resolved: None, + resolved: Binding::None, } .to_location(operator_token_loc.span), ), @@ -214,7 +214,7 @@ impl Parser { function: Box::new( Expression::Identifier { name: not_token.token.to_string(), - resolved: None, + resolved: Binding::None, } .to_location(not_token.span), ), @@ -246,7 +246,7 @@ impl Parser { function: Box::new( Expression::Identifier { name: operator.to_string(), - resolved: None, + resolved: Binding::None, } .to_location(operator_span), ), @@ -385,8 +385,8 @@ impl Parser { .expect("guaranteed to produce an lvalue"), r_value: Box::new(expression), operation: operation_identifier, - resolved_assign_operation: None, - resolved_operation: None, + resolved_assign_operation: Binding::None, + resolved_operation: Binding::None, }; Ok(op_assign.to_location(start.merge(end))) @@ -483,7 +483,7 @@ impl Parser { function: Box::new( Expression::Identifier { name: operator_token_loc.token.to_string(), - resolved: None, + resolved: Binding::None, } .to_location(operator_span), ), @@ -610,7 +610,7 @@ impl Parser { function: Box::new( Expression::Identifier { name: operator_token_loc.token.to_string(), - resolved: None, + resolved: Binding::None, } .to_location(token_span), ), @@ -689,7 +689,7 @@ impl Parser { function: Box::new( Expression::Identifier { name: identifier, - resolved: None, + resolved: Binding::None, } .to_location(identifier_span), ), @@ -961,7 +961,7 @@ impl Parser { Token::String(value) => Expression::StringLiteral(value), Token::Identifier(identifier) => Expression::Identifier { name: identifier, - resolved: None, + resolved: Binding::None, }, _ => { // TODO: this error might not be the best way to describe what's happening here @@ -1155,8 +1155,9 @@ impl Parser { Ok(ExpressionLocation { expression: Expression::FunctionDeclaration { name: identifier, - arguments: Box::new(argument_list), + parameters: Box::new(argument_list), body: Box::new(body), + return_type: None, // At some point in the future we could use type declarations here to insert the type (return type inference is cringe anyway) pure: is_pure, resolved_name: None, }, diff --git a/ndc_lib/src/interpreter/environment.rs b/ndc_lib/src/interpreter/environment.rs index aba7e14f..12ede514 100644 --- a/ndc_lib/src/interpreter/environment.rs +++ b/ndc_lib/src/interpreter/environment.rs @@ -1,7 +1,6 @@ -use crate::interpreter::function::{Function, OverloadedFunction}; +use crate::interpreter::function::{Function, StaticType}; use crate::ast::ResolvedVar; -use crate::interpreter::resolve::LexicalIdentifier; use crate::interpreter::value::Value; use std::cell::RefCell; use std::fmt; @@ -12,7 +11,7 @@ use std::rc::Rc; pub struct RootEnvironment { pub output: Box, // These are global values - global_functions: Vec, + global_functions: Vec, } pub struct Environment { @@ -68,48 +67,27 @@ impl Environment { env } - pub fn create_global_scope_mapping(&self) -> Vec { + pub fn get_global_identifiers(&self) -> Vec<(String, StaticType)> { self.root .borrow() .global_functions .iter() - .filter_map(|function| { - function.name().map(|name| LexicalIdentifier::Function { - name: name.to_string(), - arity: function.arity(), - }) - }) - .collect::>() + .map(|function| (function.name().to_string(), function.static_type())) + .collect::>() } #[must_use] - pub fn get_all_functions(&self) -> Vec { + pub fn get_all_functions(&self) -> Vec { self.root.borrow().global_functions.clone() } pub fn set(&mut self, var: ResolvedVar, value: Value) { match var { ResolvedVar::Captured { depth: 0, slot } => { - // This whole mess (special handling for functions) can be removed once we check - // types during the resolver pass - if let Value::Function(value_to_insert) = &value { - if let Some(existing_value) = self.values.get(slot) { - if let Value::Function(func) = existing_value { - func.borrow_mut().merge(&mut value_to_insert.borrow_mut()) - } - } else { - self.values.push(value); - // self.values.insert(slot, value); - } - - return; - } - if self.values.len() > slot { self.values[slot] = value } else { debug_assert!(slot == self.values.len()); - // self.values.insert(slot, value) // TODO: push should work here right self.values.push(value); } } @@ -139,18 +117,9 @@ impl Environment { pub fn declare_global_fn(&mut self, function: impl Into) { let new_function = function.into(); - let gb = &mut self.root.borrow_mut().global_functions; - - // Try to add it to an existing definition - for fun in gb.iter_mut() { - if fun.name() == Some(new_function.name()) && fun.arity() == new_function.arity() { - fun.add(new_function); - return; - } - } + let root: &mut RootEnvironment = &mut self.root.borrow_mut(); - // Create a new definition - gb.push(OverloadedFunction::from_multiple(vec![new_function])) + root.global_functions.push(new_function.clone()); } fn get_copy_from_slot(&self, depth: usize, slot: usize) -> Value { diff --git a/ndc_lib/src/interpreter/evaluate/index.rs b/ndc_lib/src/interpreter/evaluate/index.rs index 690dcd6a..c9c186fe 100644 --- a/ndc_lib/src/interpreter/evaluate/index.rs +++ b/ndc_lib/src/interpreter/evaluate/index.rs @@ -230,7 +230,7 @@ pub fn get_at_index( .clone()) } _ => Err(EvaluationError::syntax_error( - format!("cannot insert into {} at index", lhs.value_type()), + format!("cannot insert into {} at index", lhs.static_type()), span, ) .into()), @@ -294,7 +294,7 @@ pub fn set_at_index( } } else { return Err(EvaluationError::syntax_error( - format!("cannot insert {} into a string", rhs.value_type()), + format!("cannot insert {} into a string", rhs.static_type()), span, ) .into()); @@ -317,7 +317,7 @@ pub fn set_at_index( } _ => { return Err(EvaluationError::syntax_error( - format!("cannot insert into {} at index", lhs.value_type()), + format!("cannot insert into {} at index", lhs.static_type()), span, ) .into()); diff --git a/ndc_lib/src/interpreter/evaluate/mod.rs b/ndc_lib/src/interpreter/evaluate/mod.rs index 33fed2b8..51a6224d 100644 --- a/ndc_lib/src/interpreter/evaluate/mod.rs +++ b/ndc_lib/src/interpreter/evaluate/mod.rs @@ -1,16 +1,17 @@ -use crate::ast::{Expression, ExpressionLocation, ForBody, ForIteration, LogicalOperator, Lvalue}; +use crate::ast::{ + Binding, Expression, ExpressionLocation, ForBody, ForIteration, LogicalOperator, Lvalue, +}; use crate::hash_map::HashMap; use crate::interpreter::environment::Environment; -use crate::interpreter::function::{Function, FunctionBody, FunctionCarrier, OverloadedFunction}; +use crate::interpreter::function::{Function, FunctionBody, FunctionCarrier, StaticType}; use crate::interpreter::int::Int; use crate::interpreter::iterator::mut_value_to_iterator; use crate::interpreter::num::Number; use crate::interpreter::sequence::Sequence; -use crate::interpreter::value::{Value, ValueType}; +use crate::interpreter::value::Value; use crate::lexer::Span; use index::{Offset, evaluate_as_index, get_at_index, set_at_index}; use itertools::Itertools; -use log::error; use std::cell::RefCell; use std::fmt; use std::rc::Rc; @@ -37,9 +38,12 @@ pub(crate) fn evaluate_expression( if name == "None" { return Ok(Value::none()); } - environment - .borrow() - .get(resolved.expect("identifier was not resolved before execution")) + + match resolved { + Binding::None => panic!("binding not resolved at runtime"), + Binding::Resolved(resolved) => environment.borrow().get(*resolved), + Binding::Dynamic(_) => panic!("attempted to evaluate dynamic binding"), + } } Expression::VariableDeclaration { l_value, value } => { let value = evaluate_expression(value, environment)?; @@ -79,14 +83,6 @@ pub(crate) fn evaluate_expression( resolved_operation, .. } => { - let mut operations_to_try = [ - resolved_assign_operation.map(|op| (op, true)), - resolved_operation.map(|op| (op, false)), - ] - .into_iter() - .flatten() - .peekable(); - match l_value { Lvalue::Identifier { identifier, @@ -100,23 +96,36 @@ pub(crate) fn evaluate_expression( return Err(EvaluationError::undefined_variable(identifier, span).into()); }; + let types = [lhs.static_type(), rhs.static_type()]; let mut arguments = [lhs, rhs]; - while let Some((resolved_op, modified_in_place)) = operations_to_try.next() { - let operation = environment.borrow().get(resolved_op); + let mut operations_to_try = [ + ( + resolve_dynamic_binding(resolved_assign_operation, &types, environment), + true, + ), + ( + resolve_dynamic_binding(resolved_operation, &types, environment), + false, + ), + ] + .into_iter() + .filter_map(|(value, in_place)| value.map(|value| (value, in_place))) + .peekable(); + while let Some((operation, modified_in_place)) = operations_to_try.next() { let Value::Function(func) = operation else { unreachable!( "the resolver pass should have guaranteed that the operation points to a function" ); }; - - let result = match call_function(&func, &mut arguments, environment, span) { - Err(FunctionCarrier::FunctionNotFound) + // (&func, &mut arguments, environment, span) + let result = match func.call_checked(&mut arguments, environment) { + Err(FunctionCarrier::FunctionTypeMismatch) if operations_to_try.peek().is_none() => { let argument_string = - arguments.iter().map(Value::value_type).join(", "); + arguments.iter().map(Value::static_type).join(", "); return Err(FunctionCarrier::EvaluationError( EvaluationError::new( @@ -127,7 +136,10 @@ pub(crate) fn evaluate_expression( ), )); } - Err(FunctionCarrier::FunctionNotFound) => continue, + Err(FunctionCarrier::FunctionTypeMismatch) => continue, + Err(carrier @ FunctionCarrier::IntoEvaluationError(_)) => { + return Err(carrier.lift_if(span)); + } eval_result => eval_result?, }; @@ -156,41 +168,50 @@ pub(crate) fn evaluate_expression( let right_value = evaluate_expression(r_value, environment)?; - while let Some((resolved_operation, modified_in_place)) = - operations_to_try.next() - { - let operation_val = environment.borrow().get(resolved_operation); + let types = [value_at_index.static_type(), right_value.static_type()]; + let mut operations_to_try = [ + ( + resolve_dynamic_binding(resolved_assign_operation, &types, environment), + true, + ), + ( + resolve_dynamic_binding(resolved_operation, &types, environment), + false, + ), + ] + .into_iter() + .filter_map(|(value, in_place)| value.map(|value| (value, in_place))) + .peekable(); + while let Some((operation_val, modified_in_place)) = operations_to_try.next() { let Value::Function(func) = operation_val else { unreachable!( "the resolver pass should have guaranteed that the operation points to a function" ); }; - let result = match call_function( - &func, + let result = match func.call_checked( &mut [value_at_index.clone(), right_value.clone()], environment, - span, ) { - Err(FunctionCarrier::FunctionNotFound) + Err(FunctionCarrier::FunctionTypeMismatch) if operations_to_try.peek().is_none() => { - let argument_string = [value_at_index.clone(), right_value.clone()] - .iter() - .map(Value::value_type) - .join(", "); - return Err(FunctionCarrier::EvaluationError( EvaluationError::new( format!( - "no function called 'TODO FIGURE OUT NAME' found matches the arguments: ({argument_string})" + "no function called 'TODO FIGURE OUT NAME' found matches the arguments: ({}, {})", + value_at_index.static_type(), + right_value.static_type() ), span, ), )); } - Err(FunctionCarrier::FunctionNotFound) => continue, + Err(FunctionCarrier::FunctionTypeMismatch) => continue, + Err(carrier @ FunctionCarrier::IntoEvaluationError(_)) => { + return Err(carrier.lift_if(span)); + } eval_result => eval_result?, }; @@ -239,8 +260,8 @@ pub(crate) fn evaluate_expression( return Err(EvaluationError::new( format!( "mismatched types: expected {}, found {}", - ValueType::Bool, - ValueType::from(&value) + StaticType::Bool, + value.static_type() ), span, ) @@ -269,7 +290,7 @@ pub(crate) fn evaluate_expression( return Err(EvaluationError::new( format!( "Cannot apply logical operator to non bool value {}", - ValueType::from(&value) + value.static_type() ), span, ) @@ -309,49 +330,24 @@ pub(crate) fn evaluate_expression( let mut evaluated_args = Vec::new(); for argument in arguments { - evaluated_args.push(evaluate_expression(argument, environment)?); + let arg = evaluate_expression(argument, environment)?; + evaluated_args.push(arg); } - // The Expression in `function` must either be an identifier in which case it will be looked up in the - // environment, or it must be some expression that evaluates to a function. - // In case the expression is an identifier we get ALL the values that match the identifier - // ordered by the distance is the scope-hierarchy. - let function_as_value = evaluate_expression(function, environment)?; - - return if let Value::Function(function) = function_as_value { - match call_function(&function, &mut evaluated_args, environment, span) { - Err(FunctionCarrier::FunctionNotFound) => { - let argument_string = - evaluated_args.iter().map(Value::value_type).join(", "); - - return Err(FunctionCarrier::EvaluationError(EvaluationError::new( - format!( - "no function called '{}' found matches the arguments: ({argument_string})", - function.borrow().name().unwrap_or("unnamed function") - ), - span, - ))); - } - result => result, - } - } else { - Err(FunctionCarrier::EvaluationError(EvaluationError::new( - "Failed to invoke expression as function possibly because it's not a function" - .to_string(), - span, - ))) - }; + let function_as_value = resolve_and_call(function, evaluated_args, environment, span)?; } Expression::FunctionDeclaration { - arguments, + parameters: arguments, body, resolved_name, + return_type, pure, .. } => { let mut user_function = FunctionBody::Closure { parameter_names: arguments.try_into_parameters()?, body: *body.clone(), + return_type: return_type.clone().unwrap_or_else(StaticType::unit), environment: environment.clone(), }; @@ -365,12 +361,13 @@ pub(crate) fn evaluate_expression( if let Some(resolved_name) = *resolved_name { environment.borrow_mut().set( resolved_name, + // TODO: put name in declaration? Value::function(Function::from_body(user_function)), ); Value::unit() } else { - Value::function(user_function) + Value::function(Function::from_body(user_function)) } } @@ -587,7 +584,7 @@ pub(crate) fn evaluate_expression( } value => { return Err(EvaluationError::new( - format!("cannot index into {}", value.value_type()), + format!("cannot index into {}", value.static_type()), lhs_expr.span, ) .into()); @@ -656,8 +653,8 @@ fn produce_default_value( ) -> EvaluationResult { match default { Value::Function(function) => { - match call_function(function, &mut [], environment, span) { - Err(FunctionCarrier::FunctionNotFound) => { + match function.call_checked(&mut [], environment) { + Err(FunctionCarrier::FunctionTypeMismatch) => { Err(FunctionCarrier::EvaluationError(EvaluationError::new( "default function is not callable without arguments".to_string(), span, @@ -687,20 +684,14 @@ fn declare_or_assign_variable( .set(resolved.expect("must be resolved"), value.clone()); } Lvalue::Sequence(l_values) => { - let mut remaining = l_values.len(); - let mut iter = l_values.iter().zip(value.try_into_iter().ok_or_else(|| { + let r_values = value.try_into_vec().ok_or_else(|| { FunctionCarrier::EvaluationError(EvaluationError::syntax_error( "failed to unpack non iterable value into pattern".to_string(), span, )) - })?); - - for (l_value, value) in iter.by_ref() { - remaining -= 1; - declare_or_assign_variable(l_value, value, environment, span)?; - } + })?; - if remaining > 0 || iter.next().is_some() { + if l_values.len() != r_values.len() { return Err(EvaluationError::syntax_error( "failed to unpack value into pattern because the lengths do not match" .to_string(), @@ -708,6 +699,11 @@ fn declare_or_assign_variable( ) .into()); } + let mut iter = l_values.iter().zip(r_values); + + for (l_value, value) in iter.by_ref() { + declare_or_assign_variable(l_value, value, environment, span)?; + } } Lvalue::Index { value: lhs_expr, @@ -848,6 +844,16 @@ where } } +pub trait LiftEvaluationResult { + fn add_span(self, span: Span) -> Result; +} + +impl LiftEvaluationResult for Result { + fn add_span(self, span: Span) -> Self { + self.map_err(|err| err.lift_if(span)) + } +} + // NOTE: this is called `IntoEvaluationResult` but it actually only takes care of the error part of an evaluation result. // `EvaluationResult` always wants the `Ok` type to be `Value` but this converter doesn't care. pub trait IntoEvaluationResult { @@ -863,29 +869,7 @@ where } } -fn call_function( - function: &Rc>, - evaluated_args: &mut [Value], - environment: &Rc>, - span: Span, -) -> EvaluationResult { - let result = function.borrow().call(evaluated_args, environment); - - match result { - Err(FunctionCarrier::Return(value)) | Ok(value) => Ok(value), - - e @ Err( - FunctionCarrier::FunctionNotFound - | FunctionCarrier::EvaluationError(_) - // TODO: for now we just pass the break from inside the function to outside the function. This would allow some pretty funky code and might introduce weird bugs? - | FunctionCarrier::Break(_) - | FunctionCarrier::Continue - ) => e, - Err(carrier @ FunctionCarrier::IntoEvaluationError(_)) => Err(carrier.lift_if(span)), - } -} - -fn execute_body( +fn execute_for_body( body: &ForBody, environment: &Rc>, result: &mut Vec, @@ -944,7 +928,7 @@ fn execute_for_iterations( declare_or_assign_variable(l_value, r_value, &scope, span)?; if tail.is_empty() { - match execute_body(body, &scope, out_values) { + match execute_for_body(body, &scope, out_values) { Err(FunctionCarrier::Continue) => {} Err(error) => return Err(error), Ok(_value) => {} @@ -956,7 +940,7 @@ fn execute_for_iterations( } ForIteration::Guard(guard) => match evaluate_expression(guard, environment)? { Value::Bool(true) if tail.is_empty() => { - execute_body(body, environment, out_values)?; + execute_for_body(body, environment, out_values)?; } Value::Bool(true) => { execute_for_iterations(tail, body, out_values, environment, span)?; @@ -966,8 +950,8 @@ fn execute_for_iterations( return Err(EvaluationError::type_error( format!( "mismatched types: expected {}, found {}", - ValueType::Bool, - ValueType::from(&value) + StaticType::Bool, + value.static_type(), ), span, ) @@ -978,3 +962,115 @@ fn execute_for_iterations( Ok(Value::unit()) } + +// fn evaluate_as_function( +// function_expression: &ExpressionLocation, +// arg_types: &[StaticType], +// environment: &Rc>, +// ) -> EvaluationResult { +// let ExpressionLocation { expression, .. } = function_expression; +// +// if let Expression::Identifier { resolved, .. } = expression { +// resolve_dynamic_binding(resolved, arg_types, environment).ok_or_else(|| { +// FunctionCarrier::EvaluationError(EvaluationError::new( +// format!( +// "Failed to find a function that can handle the arguments ({}) at runtime", +// arg_types.iter().join(", ") +// ), +// function_expression.span, +// )) +// }) +// } else { +// evaluate_expression(function_expression, environment) +// } +// } + +fn resolve_and_call( + function_expression: &ExpressionLocation, + mut args: Vec, + environment: &Rc>, + span: Span, +) -> EvaluationResult { + //////////////////////////// + let ExpressionLocation { expression, .. } = function_expression; + + let function_as_value = if let Expression::Identifier { resolved, .. } = expression { + let arg_types = args.iter().map(|arg| arg.static_type()).collect::>(); + + let opt = match resolved { + Binding::None => None, + Binding::Resolved(var) => Some(environment.borrow().get(*var)), + Binding::Dynamic(dynamic_binding) => dynamic_binding + .iter() // TODO: should we consider the binding order? + .find_map(|binding| { + let value = environment.borrow().get(*binding); + + let Value::Function(fun) = &value else { + panic!("dynamic binding resolved to non-function type at runtime"); + }; + + // Find the first function that matches + if fun.static_type().is_fn_and_matches(&arg_types) { + return Some(value); + } + + None + }), + }; + opt.ok_or_else(|| { + FunctionCarrier::EvaluationError(EvaluationError::new( + format!( + "Failed to find a function that can handle the arguments ({}) at runtime", + arg_types.iter().join(", ") + ), + function_expression.span, + )) + })? + } else { + evaluate_expression(function_expression, environment)? + }; + + //////////////////////////// + //////////////////////////// + //////////////////////////// + + if let Value::Function(function) = function_as_value { + // Here we should be able to call without checking types + function.call(&mut args, environment).add_span(span) + } else { + Err(FunctionCarrier::EvaluationError(EvaluationError::new( + format!( + "Unable to invoke {} as a function.", + function_as_value.static_type() + ), + span, + ))) + } +} + +fn resolve_dynamic_binding( + binding: &Binding, + arg_types: &[StaticType], + environment: &Rc>, +) -> Option { + match binding { + Binding::None => None, + Binding::Resolved(var) => Some(environment.borrow().get(*var)), + Binding::Dynamic(dynamic_binding) => dynamic_binding + .iter() // TODO: should we consider the binding order? + .find_map(|binding| { + let value = environment.borrow().get(*binding); + + let Value::Function(fun) = &value else { + panic!("dynamic binding resolved to non-function type at runtime"); + }; + + // Find the first function that matches + if fun.static_type().is_fn_and_matches(arg_types) { + return Some(value); + } + + None + }), + } +} diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index 37f51613..4c46a3a0 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -4,9 +4,9 @@ use crate::interpreter::environment::Environment; use crate::interpreter::evaluate::{ ErrorConverter, EvaluationError, EvaluationResult, evaluate_expression, }; -use crate::interpreter::num::{BinaryOperatorError, Number, NumberType}; +use crate::interpreter::num::{BinaryOperatorError, Number}; use crate::interpreter::sequence::Sequence; -use crate::interpreter::value::{Value, ValueType}; +use crate::interpreter::value::Value; use crate::lexer::Span; use derive_builder::Builder; use itertools::Itertools; @@ -18,13 +18,13 @@ use std::rc::Rc; /// Callable is a wrapper around a `OverloadedFunction` pointer and the environment to make it /// easy to have an executable function as a method signature in the standard library pub struct Callable<'a> { - pub function: Rc>, + pub function: Rc, pub environment: &'a Rc>, } impl Callable<'_> { pub fn call(&self, args: &mut [Value]) -> EvaluationResult { - self.function.borrow().call(args, self.environment) + self.function.call(args, self.environment) } } @@ -37,6 +37,17 @@ pub struct Function { body: FunctionBody, } +impl fmt::Debug for Function { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Function(name={}, sig={})", + self.name(), + self.type_signature() + ) + } +} + impl Function { pub fn arity(&self) -> Option { self.body.arity() @@ -72,6 +83,89 @@ impl Function { pub fn type_signature(&self) -> TypeSignature { self.body.type_signature() } + pub fn return_type(&self) -> &StaticType { + self.body.return_type() + } + + pub fn static_type(&self) -> StaticType { + StaticType::Function { + parameters: match self.body.type_signature() { + TypeSignature::Variadic => None, + TypeSignature::Exact(types) => { + Some(types.iter().map(|x| x.type_name.clone()).collect()) + } + }, + return_type: Box::new(self.body.return_type().clone()), + } + } + + pub fn call(&self, args: &mut [Value], env: &Rc>) -> EvaluationResult { + let result = self.body.call(args, env); + + match result { + Err(FunctionCarrier::Return(value)) | Ok(value) => Ok(value), + e => e, + } + } + + pub fn call_checked( + &self, + args: &mut [Value], + env: &Rc>, + ) -> EvaluationResult { + let arg_types = args.iter().map(|arg| arg.static_type()).collect::>(); + + if self.static_type().is_fn_and_matches(&arg_types) { + self.call(args, env) + } else { + Err(FunctionCarrier::FunctionTypeMismatch) + } + } + + fn call_vectorized( + &self, + args: &mut [Value], + env: &Rc>, + ) -> EvaluationResult { + let [left, right] = args else { + // Vectorized application only works in cases where there are two tuple arguments + panic!("incorrect argument count for vectorization should have been handled by caller"); + }; + + // TODO: let caller handle checks? + // if !left.supports_vectorization_with(right) { + // return Err(FunctionCarrier::FunctionNotFound); + // } + + let (left, right) = match (left, right) { + // Both are tuples + (Value::Sequence(Sequence::Tuple(left)), Value::Sequence(Sequence::Tuple(right))) => { + (left, right.as_slice()) + } + // Left is a number and right is a tuple + (left @ Value::Number(_), Value::Sequence(Sequence::Tuple(right))) => ( + &mut Rc::new(vec![left.clone(); right.len()]), + right.as_slice(), + ), + // Left is a tuple and right is a number + (Value::Sequence(Sequence::Tuple(left)), right @ Value::Number(_)) => { + (left, std::slice::from_ref(right)) + } + _ => { + panic!("caller should handle all checks before vectorizing") + } + }; + + let left_mut: &mut Vec = Rc::make_mut(left); + + // Zip the mutable vector with the immutable right side and perform the operations on all elements + // TODO: maybe one day figure out how to get rid of all these clones + for (l, r) in left_mut.iter_mut().zip(right.iter().cycle()) { + *l = self.call(&mut [l.clone(), r.clone()], env)?; + } + + Ok(Value::Sequence(Sequence::Tuple(left.clone()))) + } } #[derive(Clone)] @@ -79,6 +173,7 @@ pub enum FunctionBody { Closure { parameter_names: Vec, body: ExpressionLocation, + return_type: StaticType, environment: Rc>, }, NumericUnaryOp { @@ -89,6 +184,7 @@ pub enum FunctionBody { }, GenericFunction { type_signature: TypeSignature, + return_type: StaticType, function: fn(&mut [Value], &Rc>) -> EvaluationResult, }, Memoized { @@ -109,15 +205,19 @@ impl FunctionBody { Self::Memoized { function, .. } => function.arity(), } } + pub fn generic( type_signature: TypeSignature, + return_type: StaticType, function: fn(&mut [Value], &Rc>) -> EvaluationResult, ) -> Self { Self::GenericFunction { type_signature, + return_type, function, } } + fn type_signature(&self) -> TypeSignature { match self { Self::Closure { @@ -125,21 +225,30 @@ impl FunctionBody { } => TypeSignature::Exact( parameter_names .iter() - .map(|name| Parameter::new(name, ParamType::Any)) + .map(|name| Parameter::new(name, StaticType::Any)) .collect(), ), Self::Memoized { cache: _, function } => function.type_signature(), Self::NumericUnaryOp { .. } => { - TypeSignature::Exact(vec![Parameter::new("num", ParamType::Number)]) + TypeSignature::Exact(vec![Parameter::new("num", StaticType::Number)]) } Self::NumericBinaryOp { .. } => TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Number), - Parameter::new("right", ParamType::Number), + Parameter::new("left", StaticType::Number), + Parameter::new("right", StaticType::Number), ]), Self::GenericFunction { type_signature, .. } => type_signature.clone(), } } + pub fn return_type(&self) -> &StaticType { + match self { + Self::Closure { return_type, .. } | Self::GenericFunction { return_type, .. } => { + return_type + } + Self::NumericUnaryOp { .. } | Self::NumericBinaryOp { .. } => &StaticType::Number, + Self::Memoized { function, .. } => function.return_type(), + } + } pub fn call(&self, args: &mut [Value], env: &Rc>) -> EvaluationResult { match self { Self::Closure { @@ -171,8 +280,8 @@ impl FunctionBody { Self::NumericUnaryOp { body } => match args { [Value::Number(num)] => Ok(Value::Number(body(num.clone()))), [v] => Err(FunctionCallError::ArgumentTypeError { - expected: ParamType::Number, - actual: v.value_type(), + expected: StaticType::Number, + actual: v.static_type(), } .into()), args => Err(FunctionCallError::ArgumentCountError { @@ -187,13 +296,13 @@ impl FunctionBody { .map_err(|err| FunctionCarrier::IntoEvaluationError(Box::new(err)))?, )), [Value::Number(_), right] => Err(FunctionCallError::ArgumentTypeError { - expected: ParamType::Number, - actual: right.value_type(), + expected: StaticType::Number, + actual: right.static_type(), } .into()), [left, _] => Err(FunctionCallError::ArgumentTypeError { - expected: ParamType::Number, - actual: left.value_type(), + expected: StaticType::Number, + actual: left.static_type(), } .into()), args => Err(FunctionCallError::ArgumentCountError { @@ -236,14 +345,20 @@ impl TypeSignature { /// Matches a list of `ValueTypes` to a type signature. It can return `None` if there is no match or /// `Some(num)` where num is the sum of the distances of the types. The type `Int`, is distance 1 /// away from `Number`, and `Number` is 1 distance from `Any`, then `Int` is distance 2 from `Any`. - fn calc_type_score(&self, types: &[ValueType]) -> Option { + pub fn calc_type_score(&self, types: &[StaticType]) -> Option { match self { Self::Variadic => Some(0), Self::Exact(signature) => { if types.len() == signature.len() { let mut acc = 0; for (a, b) in types.iter().zip(signature.iter()) { - let dist = b.type_name.distance(a)?; + let dist = if a == &b.type_name { + 0 + } else if a.is_subtype(&b.type_name) { + 1 + } else { + return None; + }; acc += dist; } @@ -262,289 +377,483 @@ impl TypeSignature { } } } - -#[derive(Clone)] -pub struct OverloadedFunction { - implementations: HashMap, +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct Parameter { + pub name: String, + pub type_name: StaticType, } -impl OverloadedFunction { - pub fn from_multiple(functions: Vec) -> Self { +impl Parameter { + pub fn new>(name: N, param_type: StaticType) -> Self { Self { - implementations: functions - .into_iter() - .map(|f| (f.type_signature(), f)) - .collect(), + name: name.into(), + type_name: param_type, } } +} - pub fn arity(&self) -> Option { - let arity = self - .implementations - .iter() - .next() - .map(|(sig, _)| sig.arity()) - .expect("OverloadedFunction cannot be empty"); - - debug_assert!( - self.implementations - .iter() - .all(|(sig, _)| sig.arity() == arity), - "failed asserting that arity of all functions in OverloadedFunction are equal: {}", - self.implementations - .values() - .map(|fun| fun.name()) - .join(", ") - ); - arity - } - - pub fn name(&self) -> Option<&str> { - if let Some((_, ff)) = (&self.implementations).into_iter().next() { - return ff.name.as_deref(); +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum StaticType { + Any, + Bool, + Function { + parameters: Option>, + return_type: Box, + }, + Option(Box), + + // Numbers + Number, + Float, + Int, + Rational, + Complex, + + // Sequences List -> List + Sequence(Box), + List(Box), + String, + Tuple(Vec), + Map { + key: Box, + value: Box, + }, + Iterator(Box), + MinHeap(Box), + MaxHeap(Box), + Deque(Box), +} +impl StaticType { + /// Checks if `self` is a subtype of `other`. + /// + /// A type S is a subtype of T (S <: T) if a value of type S can be safely + /// used wherever a value of type T is expected. + /// + /// # Key Rules + /// - `Any` is the top type (supertype of all types) + /// - `Number` > `{Float, Int, Rational, Complex}` + /// - `Sequence` > sequence types with element type T + /// - Generic types are covariant in their type parameters + /// - Function parameters are **contravariant**, returns are **covariant** + /// + /// Note: this function probably doesn't handle variadic functions correctly + pub fn is_subtype(&self, other: &Self) -> bool { + // Any is the universal supertype + if matches!(other, Self::Any) { + return true; } - None - } + // Only Any satisfies the above; all other types fail here + if matches!(self, Self::Any) { + return false; + } - pub fn add(&mut self, function: Function) { - self.implementations - .insert(function.type_signature(), function); - } + #[allow(clippy::match_same_arms)] + #[allow(clippy::unnested_or_patterns)] + match (self, other) { + // Reflexivity: every type is a subtype of itself + _ if self == other => true, + + // Number hierarchy: all numeric types are subtypes of Number + (Self::Float | Self::Int | Self::Rational | Self::Complex, Self::Number) => true, + + // Sequence hierarchy: specific sequences are subtypes of Sequence + // where T is their element type (covariant) + (Self::List(s), Self::Sequence(t)) + | (Self::Iterator(s), Self::Sequence(t)) + | (Self::MinHeap(s), Self::Sequence(t)) + | (Self::MaxHeap(s), Self::Sequence(t)) + | (Self::Deque(s), Self::Sequence(t)) => s.is_subtype(t), + + // String is Sequence + (Self::String, Self::Sequence(t)) => Self::String.is_subtype(t.as_ref()), + + // Tuple is Sequence + (Self::Tuple(elems), Self::Sequence(t)) => { + Self::compute_tuple_element_type(elems).is_subtype(t) + } - pub fn implementations(&self) -> impl Iterator { - self.implementations.clone().into_iter() - } + // Map is Sequence> + (Self::Map { key, value }, Self::Sequence(t)) => { + Self::Tuple(vec![key.as_ref().clone(), value.as_ref().clone()]) + .is_subtype(t.as_ref()) + } - /// Ads values from the other overloaded function by draining it - pub fn merge(&mut self, other: &mut Self) { - for (key, value) in other.implementations.drain() { - debug_assert!( - !self.implementations.contains_key(&key), - "conflict while merging OverloadedFunction implementations" - ); - self.implementations.insert(key, value); - } - } + // Generic types are covariant: Container <: Container if S <: T + (Self::Option(s), Self::Option(t)) + | (Self::List(s), Self::List(t)) + | (Self::Iterator(s), Self::Iterator(t)) + | (Self::MinHeap(s), Self::MinHeap(t)) + | (Self::MaxHeap(s), Self::MaxHeap(t)) + | (Self::Deque(s), Self::Deque(t)) + | (Self::Sequence(s), Self::Sequence(t)) => s.is_subtype(t), + + // Tuples are covariant pointwise and must have the same arity + (Self::Tuple(s_elems), Self::Tuple(t_elems)) => { + s_elems.len() == t_elems.len() + && s_elems.iter().zip(t_elems).all(|(s, t)| s.is_subtype(t)) + } - pub fn call(&self, args: &mut [Value], env: &Rc>) -> EvaluationResult { - let types: Vec = args.iter().map(ValueType::from).collect(); - - let mut best_function_match = None; - let mut best_distance = u32::MAX; - - for (signature, function) in &self.implementations { - let Some(cur) = signature.calc_type_score(&types) else { - continue; - }; - if cur < best_distance { - best_distance = cur; - best_function_match = Some(function); + // Maps are covariant in both key and value type parameters + (Self::Map { key: k1, value: v1 }, Self::Map { key: k2, value: v2 }) => { + k1.is_subtype(k2) && v1.is_subtype(v2) } - } - best_function_match - .ok_or(FunctionCarrier::FunctionNotFound) - .and_then(|function| function.body().call(args, env)) - // For now if we can't find a specific function we just try to vectorize as a fallback - .or_else(|err| { - if let FunctionCarrier::FunctionNotFound = err { - self.call_vectorized(args, env) - } else { - Err(err) - } - }) + // Functions: contravariant in parameters, covariant in return type + // F1 <: F2 iff params(F2) <: params(F1) AND return(F1) <: return(F2) + ( + Self::Function { + parameters: p1, + return_type: r1, + }, + Self::Function { + parameters: p2, + return_type: r2, + }, + ) => { + // Return type is covariant: must satisfy r1 <: r2 + let return_ok = r1.is_subtype(r2); + + // Parameters are contravariant: must satisfy p2 <: p1 (reversed!) + let params_ok = match (p1, p2) { + (None, None) => true, + // A function with specific params is a subtype of one with generic params + (Some(_), None) => true, + // Cannot substitute generic params with specific ones + (None, Some(_)) => false, + (Some(ps1), Some(ps2)) => { + ps1.len() == ps2.len() && + // Note the reversal: p2 must be subtype of p1! + ps1.iter() + .zip(ps2) + .all(|(p1, p2)| p2.is_subtype(p1)) + } + }; + + return_ok && params_ok + } + + _ => false, + } } - fn call_vectorized( - &self, - args: &mut [Value], - env: &Rc>, - ) -> EvaluationResult { - let [left, right] = args else { - // Vectorized application only works in cases where there are two tuple arguments - return Err(FunctionCarrier::FunctionNotFound); - }; + // + /// Computes the Least Upper Bound (join) of two types. + /// + /// The LUB is the most specific type that is a supertype of both inputs. + /// + /// # Examples + /// - `lub(Int, Float) = Number` + /// - `lub(List, List) = List` + /// - `lub(List, Iterator) = Sequence` + /// - `lub(Int, String) = Any` + pub fn lub(&self, other: &Self) -> Self { + // Any is the top type + if matches!(self, Self::Any) || matches!(other, Self::Any) { + return Self::Any; + } - if !left.supports_vectorization_with(right) { - return Err(FunctionCarrier::FunctionNotFound); + // Reflexivity: lub(T, T) = T + if self == other { + return self.clone(); } - let (left, right) = match (left, right) { - // Both are tuples - (Value::Sequence(Sequence::Tuple(left)), Value::Sequence(Sequence::Tuple(right))) => { - (left, right.as_slice()) - } - // Left is a number and right is a tuple - (left @ Value::Number(_), Value::Sequence(Sequence::Tuple(right))) => ( - &mut Rc::new(vec![left.clone(); right.len()]), - right.as_slice(), - ), - // Left is a tuple and right is a number - (Value::Sequence(Sequence::Tuple(left)), right @ Value::Number(_)) => { - (left, std::slice::from_ref(right)) + // If one is a subtype of the other, return the supertype + if self.is_subtype(other) { + return other.clone(); + } + if other.is_subtype(self) { + return self.clone(); + } + + match (self, other) { + // Number type lattice: all numeric types join to Number + (Self::Float, Self::Int | Self::Rational | Self::Complex) + | (Self::Int, Self::Float | Self::Rational | Self::Complex) + | (Self::Rational, Self::Float | Self::Int | Self::Complex) + | (Self::Complex, Self::Float | Self::Int | Self::Rational) => Self::Number, + + // Covariant generic types: compute LUB pointwise + (Self::Option(s), Self::Option(t)) => Self::Option(Box::new(s.lub(t))), + (Self::List(s), Self::List(t)) => Self::List(Box::new(s.lub(t))), + (Self::Iterator(s), Self::Iterator(t)) => Self::Iterator(Box::new(s.lub(t))), + (Self::MinHeap(s), Self::MinHeap(t)) => Self::MinHeap(Box::new(s.lub(t))), + (Self::MaxHeap(s), Self::MaxHeap(t)) => Self::MaxHeap(Box::new(s.lub(t))), + (Self::Deque(s), Self::Deque(t)) => Self::Deque(Box::new(s.lub(t))), + (Self::Sequence(s), Self::Sequence(t)) => Self::Sequence(Box::new(s.lub(t))), + + // Maps are covariant in both parameters + (Self::Map { key: k1, value: v1 }, Self::Map { key: k2, value: v2 }) => Self::Map { + key: Box::new(k1.lub(k2)), + value: Box::new(v1.lub(v2)), + }, + + // Tuples of same arity: compute LUB pointwise + (Self::Tuple(e1), Self::Tuple(e2)) if e1.len() == e2.len() => { + Self::Tuple(e1.iter().zip(e2).map(|(a, b)| a.lub(b)).collect()) } - _ => { - return Err(FunctionCarrier::FunctionNotFound); + + // Different sequence types: generalize to Sequence + _ if Self::is_sequence(self) && Self::is_sequence(other) => { + let elem1 = self.sequence_element_type().expect("must be seq"); + let elem2 = other.sequence_element_type().expect("must be seq"); + Self::Sequence(Box::new(elem1.lub(&elem2))) } - }; - let left_mut: &mut Vec = Rc::make_mut(left); + // Functions: covariant in return, contravariant in parameters + ( + Self::Function { + parameters: p1, + return_type: r1, + }, + Self::Function { + parameters: p2, + return_type: r2, + }, + ) => { + // Return type: covariant, so take LUB + let return_type = Box::new(r1.lub(r2)); + + // Parameters: contravariant, so we need GLB (greatest lower bound) + let parameters = match (p1, p2) { + (None, None) => None, + (Some(ps1), Some(ps2)) if ps1.len() == ps2.len() => { + Some(ps1.iter().zip(ps2).map(|(a, b)| a.glb(b)).collect()) + } + // Incompatible parameter lists + _ => None, + }; - // Zip the mutable vector with the immutable right side and perform the operations on all elements - // TODO: maybe one day figure out how to get rid of all these clones - for (l, r) in left_mut.iter_mut().zip(right.iter().cycle()) { - *l = self.call(&mut [l.clone(), r.clone()], env)?; - } + Self::Function { + parameters, + return_type, + } + } - Ok(Value::Sequence(Sequence::Tuple(left.clone()))) + // No common supertype found: default to Any + _ => Self::Any, + } } -} -impl From for OverloadedFunction { - fn from(value: FunctionBody) -> Self { - Function::from_body(value).into() - } -} + /// Computes the Greatest Lower Bound (meet) of two types. + /// + /// The GLB is the most general type that is a subtype of both inputs. + /// This is required for contravariant positions (e.g., function parameters). + /// + /// Note: Not all type pairs have a representable GLB in our type system. + /// In such cases, we return a conservative approximation. + fn glb(&self, other: &Self) -> Self { + // Any is the top type: glb(Any, T) = T + match (self, other) { + (Self::Any, t) | (t, Self::Any) => t.clone(), + _ if self == other => self.clone(), + _ if self.is_subtype(other) => self.clone(), + _ if other.is_subtype(self) => other.clone(), + + // Covariant types: compute GLB pointwise + (Self::Option(s), Self::Option(t)) => Self::Option(Box::new(s.glb(t))), + (Self::List(s), Self::List(t)) => Self::List(Box::new(s.glb(t))), + (Self::Iterator(s), Self::Iterator(t)) => Self::Iterator(Box::new(s.glb(t))), + (Self::MinHeap(s), Self::MinHeap(t)) => Self::MinHeap(Box::new(s.glb(t))), + (Self::MaxHeap(s), Self::MaxHeap(t)) => Self::MaxHeap(Box::new(s.glb(t))), + (Self::Deque(s), Self::Deque(t)) => Self::Deque(Box::new(s.glb(t))), + (Self::Sequence(s), Self::Sequence(t)) => Self::Sequence(Box::new(s.glb(t))), + + (Self::Map { key: k1, value: v1 }, Self::Map { key: k2, value: v2 }) => Self::Map { + key: Box::new(k1.glb(k2)), + value: Box::new(v1.glb(v2)), + }, -impl From for OverloadedFunction { - fn from(value: Function) -> Self { - let type_signature = value.type_signature(); - Self { - implementations: HashMap::from([(type_signature, value)]), + (Self::Tuple(e1), Self::Tuple(e2)) if e1.len() == e2.len() => { + Self::Tuple(e1.iter().zip(e2).map(|(a, b)| a.glb(b)).collect()) + } + + // No representable GLB: conservatively return first type + // A complete type system would have a Bottom type (⊥) here + _ => self.clone(), } } -} -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct Parameter { - pub name: String, - pub type_name: ParamType, -} + /// Computes the element type of a tuple when viewed as a sequence. + /// Returns the LUB of all tuple elements. + fn compute_tuple_element_type(elems: &[Self]) -> Self { + if elems.is_empty() { + return Self::Any; + } -impl Parameter { - pub fn new>(name: N, param_type: ParamType) -> Self { - Self { - name: name.into(), - type_name: param_type, + elems + .iter() + .skip(1) + .fold(elems[0].clone(), |acc, elem| acc.lub(elem)) + } + + /// Checks if a type is a sequence-like type. + fn is_sequence(ty: &Self) -> bool { + matches!( + ty, + Self::Sequence(_) + | Self::List(_) + | Self::String + | Self::Tuple(_) + | Self::Map { .. } + | Self::Iterator(_) + | Self::MinHeap(_) + | Self::MaxHeap(_) + | Self::Deque(_) + ) + } + + /// Gets the element type when treating a type as a sequence. + /// + /// - `List`, `Iterator`, etc. → `T` + /// - `String` → `String` + /// - `Tuple` → `lub(T1, ..., Tn)` + /// - `Map` → `Tuple` + pub fn sequence_element_type(&self) -> Option { + match self { + Self::Sequence(t) + | Self::List(t) + | Self::Iterator(t) + | Self::MinHeap(t) + | Self::MaxHeap(t) + | Self::Deque(t) => Some(t.as_ref().clone()), + Self::String => Some(Self::String), + Self::Tuple(elems) => Some(Self::compute_tuple_element_type(elems)), + Self::Map { key, value } => Some(Self::Tuple(vec![ + key.as_ref().clone(), + value.as_ref().clone(), + ])), + Self::Any => Some(Self::Any), + _ => None, } } -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum ParamType { - Any, - Bool, - Function, - Option, + #[must_use] + pub fn unit() -> Self { + Self::Tuple(vec![]) + } - // Numbers - Number, - Float, - Int, - Rational, - Complex, + #[must_use] + pub fn supports_vectorization(&self) -> bool { + match self { + Self::Tuple(values) => values.iter().all(|v| v.is_number()), + _ => false, + } + } - // Sequences - Sequence, - List, - String, - Tuple, - Map, - Iterator, - MinHeap, - MaxHeap, - Deque, -} + pub fn is_number(&self) -> bool { + matches!( + self, + Self::Number | Self::Float | Self::Int | Self::Rational | Self::Complex + ) + } -impl ParamType { - fn distance(&self, other: &ValueType) -> Option { - #[allow(clippy::match_same_arms)] + #[must_use] + pub fn supports_vectorization_with(&self, other: &Self) -> bool { match (self, other) { - (Self::Bool, ValueType::Bool) => Some(0), - (Self::Option, ValueType::Option) => Some(0), - (Self::Int, ValueType::Number(NumberType::Int)) => Some(0), - (Self::Float, ValueType::Number(NumberType::Float)) => Some(0), - (Self::Rational, ValueType::Number(NumberType::Rational)) => Some(0), - (Self::Complex, ValueType::Number(NumberType::Complex)) => Some(0), - (Self::String, ValueType::String) => Some(0), - (Self::List, ValueType::List) => Some(0), - // TODO: once ParamType supports parameters we can calculate the proper distance to Tuple - (Self::Tuple, ValueType::Tuple(_)) => Some(0), - (Self::Map, ValueType::Map) => Some(0), - (Self::Iterator, ValueType::Iterator) => Some(0), - (Self::Function, ValueType::Function) => Some(0), - (Self::Deque, ValueType::Deque) => Some(0), - (Self::MinHeap, ValueType::MinHeap) => Some(0), - (Self::MaxHeap, ValueType::MaxHeap) => Some(0), - (Self::Any, _) => Some(2), - (Self::Number, ValueType::Number(_)) => Some(1), - ( - Self::Sequence, - ValueType::List - | ValueType::String - | ValueType::Map - // Sequence is always 1 distance to tuple - | ValueType::Tuple(_) - | ValueType::Iterator - | ValueType::MinHeap - | ValueType::MaxHeap - | ValueType::Deque, - ) => Some(1), - _ => None, + (Self::Tuple(l), Self::Tuple(r)) + if { + l.len() == r.len() + && self.supports_vectorization() + && other.supports_vectorization() + } => + { + true + } + (tup @ Self::Tuple(_), maybe_num) | (maybe_num, tup @ Self::Tuple(_)) => { + tup.supports_vectorization() && maybe_num.is_number() + } + _ => false, } } - pub fn as_str(&self) -> &'static str { - match self { - Self::Any => "Any", - Self::Bool => "Bool", - Self::Function => "Function", - Self::Option => "Option", - Self::Number => "Number", - Self::Float => "Float", - Self::Int => "Int", - Self::Rational => "Rational", - Self::Complex => "Complex", - Self::Sequence => "Sequence", - Self::List => "List", - Self::String => "String", - Self::Tuple => "Tuple", - Self::Map => "Map", - Self::Iterator => "Iterator", - Self::MinHeap => "MinHeap", - Self::MaxHeap => "MaxHeap", - Self::Deque => "Deque", + // BRUH + pub fn is_incompatible_with(&self, other: &Self) -> bool { + !self.is_subtype(other) && !other.is_subtype(self) + } + + pub fn index_element_type(&self) -> Option { + if let Self::Map { value, .. } = self { + return Some(value.as_ref().clone()); } + + self.sequence_element_type() } -} -/// Converts the concrete type of a value to the specific `ParamType` -impl From<&Value> for ParamType { - fn from(value: &Value) -> Self { - match value { - Value::Option(_) => Self::Option, - Value::Number(Number::Rational(_)) => Self::Rational, - Value::Number(Number::Complex(_)) => Self::Complex, - Value::Number(Number::Int(_)) => Self::Int, - Value::Number(Number::Float(_)) => Self::Float, - Value::Bool(_) => Self::Bool, - Value::Sequence(Sequence::String(_)) => Self::String, - Value::Sequence(Sequence::List(_)) => Self::List, - Value::Sequence(Sequence::Tuple(_)) => Self::Tuple, - Value::Function(_) => Self::Function, - Value::Sequence(Sequence::Map(_, _)) => Self::Map, - Value::Sequence(Sequence::Iterator(_)) => Self::Iterator, - Value::Sequence(Sequence::MaxHeap(_)) => Self::MaxHeap, - Value::Sequence(Sequence::MinHeap(_)) => Self::MinHeap, - Value::Sequence(Sequence::Deque(_)) => Self::Deque, + pub fn is_fn_and_matches(&self, types: &[Self]) -> bool { + // If the thing is not a function we're not interested + let Self::Function { parameters, .. } = self else { + return false; + }; + + let Some(_) = parameters else { + // If this branch happens then the function we're matching against is variadic meaning it's always a match + return true; + }; + + self.is_subtype(&Self::Function { + parameters: Some(types.to_vec()), + return_type: Box::new(Self::Any), + }) + } + + pub fn unpack(&self) -> Option + '_>> { + match self { + // Any just unpacks to an infinite list of Any + Self::Any => Some(Box::new(std::iter::repeat(&Self::Any))), + Self::List(elem) + | Self::Sequence(elem) + | Self::Iterator(elem) + | Self::MinHeap(elem) + | Self::MaxHeap(elem) + | Self::Deque(elem) => Some(Box::new(std::iter::repeat(&**elem))), + Self::Tuple(types) => Some(Box::new(types.iter())), + Self::String => Some(Box::new(std::iter::repeat(&Self::String))), + Self::Bool + | Self::Function { .. } + | Self::Option(_) + | Self::Number + | Self::Float + | Self::Int + | Self::Rational + | Self::Complex + | Self::Map { .. } => None, } } } -impl fmt::Display for ParamType { +impl fmt::Display for StaticType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) + match self { + Self::Any => write!(f, "Any"), + Self::Bool => write!(f, "Bool"), + Self::Function { + parameters, + return_type, + } => write!( + f, + "Function({}) -> {return_type}", + parameters + .as_deref() + .map(|p| p.iter().join(", ")) + .unwrap_or(String::from("*")) + ), + Self::Option(elem) => write!(f, "Option<{elem}>"), + Self::Number => write!(f, "Number"), + Self::Float => write!(f, "Float"), + Self::Int => write!(f, "Int"), + Self::Rational => write!(f, "Rational"), + Self::Complex => write!(f, "Complex"), + Self::Sequence(elem) => write!(f, "Sequence<{elem}>"), + Self::List(elem) => write!(f, "List<{elem}>"), + Self::String => write!(f, "String"), + Self::Tuple(tup) if tup.is_empty() => write!(f, "()"), + Self::Tuple(tup) => write!(f, "Tuple<{}>", tup.iter().join(", ")), + Self::Map { key, value } => write!(f, "Map<{key}, {value}>"), + Self::Iterator(elem) => write!(f, "Iterator<{elem}>"), + Self::MinHeap(elem) => write!(f, "MinHeap<{elem}>"), + Self::MaxHeap(elem) => write!(f, "MaxHeap<{elem}>"), + Self::Deque(elem) => write!(f, "Deque<{elem}>"), + } } } @@ -552,8 +861,8 @@ impl fmt::Display for ParamType { pub enum FunctionCallError { #[error("invalid argument, expected {expected} got {actual}")] ArgumentTypeError { - expected: ParamType, - actual: ValueType, + expected: StaticType, + actual: StaticType, }, #[error("invalid argument count, expected {expected} arguments got {actual}")] @@ -581,7 +890,7 @@ pub enum FunctionCarrier { #[error("evaluation error {0}")] EvaluationError(#[from] EvaluationError), #[error("function does not exist")] - FunctionNotFound, // This error has specific handling behavior and needs its own variant + FunctionTypeMismatch, // This error has specific handling behavior and needs its own variant #[error("unconverted evaluation error")] IntoEvaluationError(Box), } @@ -627,9 +936,42 @@ impl fmt::Display for TypeSignature { "{}", params .iter() - .map(|p| format!("{}: {}", p.name, p.type_name.as_str())) + .map(|p| format!("{}: {}", p.name, p.type_name)) .join(", ") ), } } } + +#[allow(unused_imports)] +mod test { + use super::*; + + #[test] + fn test_list_of_type_compatibility() { + let list_of_two_tuple_int = StaticType::List(Box::new(StaticType::Tuple(vec![ + StaticType::Int, + StaticType::Int, + ]))); + let list_of_any = StaticType::List(Box::new(StaticType::Any)); + assert!(list_of_two_tuple_int.is_subtype(&list_of_any)); + } + + #[test] + fn test_function_compatibility() { + let fun = StaticType::Function { + parameters: Some(vec![ + StaticType::List(Box::new(StaticType::Any)), + StaticType::Int, + ]), + return_type: Box::new(StaticType::Any), + }; + + let list_of_two_tuple_int = StaticType::List(Box::new(StaticType::Tuple(vec![ + StaticType::Int, + StaticType::Int, + ]))); + + assert!(fun.is_fn_and_matches(&[list_of_two_tuple_int, StaticType::Int])); + } +} diff --git a/ndc_lib/src/interpreter/iterator.rs b/ndc_lib/src/interpreter/iterator.rs index d1a7238f..badae55d 100644 --- a/ndc_lib/src/interpreter/iterator.rs +++ b/ndc_lib/src/interpreter/iterator.rs @@ -3,13 +3,13 @@ //! The implementation of the various iterators in this module were heavily inspired by the ones in //! noulith which can be found [here](https://github.com/betaveros/noulith/blob/441d52ea433527b7ada5bc6cabd952f9ae8fb791/src/streams.rs) //! -use super::function::FunctionCarrier; +use super::function::{FunctionCarrier, StaticType}; use super::int::Int::Int64; use super::num::Number; use crate::hash_map::HashMap; use crate::interpreter::heap::{MaxHeap, MinHeap}; use crate::interpreter::sequence::Sequence; -use crate::interpreter::value::{Value, ValueType}; +use crate::interpreter::value::Value; use self_cell::self_cell; use std::cell::{Ref, RefCell}; use std::collections::VecDeque; @@ -67,7 +67,7 @@ impl Iterator for MutableValueIntoIterator<'_> { #[derive(thiserror::Error, Debug)] #[error("{} is not iterable", .value_type)] pub struct NotIterableError { - value_type: ValueType, + value_type: StaticType, } impl From for FunctionCarrier { @@ -82,7 +82,7 @@ pub fn mut_value_to_iterator( match value { Value::Sequence(sequence) => Ok(mut_seq_to_iterator(sequence)), value => Err(NotIterableError { - value_type: value.value_type(), + value_type: value.static_type(), }), } } diff --git a/ndc_lib/src/interpreter/mod.rs b/ndc_lib/src/interpreter/mod.rs index fdb5d5b0..5e1c9360 100644 --- a/ndc_lib/src/interpreter/mod.rs +++ b/ndc_lib/src/interpreter/mod.rs @@ -5,7 +5,7 @@ use crate::ast::ExpressionLocation; use crate::interpreter::environment::{Environment, InterpreterOutput}; use crate::interpreter::evaluate::{EvaluationError, evaluate_expression}; use crate::interpreter::function::FunctionCarrier; -use crate::interpreter::resolve::{LexicalData, resolve_pass}; +use crate::interpreter::semantic::analyser::{Analyser, ScopeTree}; use crate::interpreter::value::Value; use crate::lexer::{Lexer, TokenLocation}; use miette::Diagnostic; @@ -17,13 +17,13 @@ pub(crate) mod heap; pub mod int; pub mod iterator; pub mod num; -mod resolve; +pub mod semantic; pub mod sequence; pub mod value; pub struct Interpreter { environment: Rc>, - lexical_data: LexicalData, + analyser: Analyser, } #[allow(clippy::dbg_macro, clippy::print_stdout, clippy::print_stderr)] @@ -34,11 +34,11 @@ impl Interpreter { T: InterpreterOutput + 'static, { let environment = Environment::new_with_stdlib(Box::new(dest)); - let hash_map = environment.create_global_scope_mapping(); + let global_identifiers = environment.get_global_identifiers(); Self { environment: Rc::new(RefCell::new(environment)), - lexical_data: LexicalData::from_global_scope(hash_map), + analyser: Analyser::from_scope_tree(ScopeTree::from_global_scope(global_identifiers)), } } @@ -68,16 +68,16 @@ impl Interpreter { } for e in &mut expressions { - resolve_pass(e, &mut self.lexical_data)? + self.analyser.analyse(e)?; } - // dbg!(&expressions); - // dbg!(&self.lexical_data); + dbg!(&expressions); + dbg!(&self.analyser); let final_value = self.interpret(expressions.into_iter())?; if debug { - dbg!(&final_value, final_value.value_type()); + dbg!(&final_value, final_value.static_type()); } Ok(format!("{final_value}")) @@ -89,7 +89,7 @@ impl Interpreter { ) -> Result { let mut value = Value::unit(); for expr in expressions { - match evaluate_expression(&expr, &mut self.environment) { + match evaluate_expression(&expr, &self.environment) { Ok(val) => value = val, Err(FunctionCarrier::Return(_)) => { Err(EvaluationError::syntax_error( @@ -110,7 +110,7 @@ impl Interpreter { ))?; } Err(FunctionCarrier::EvaluationError(e)) => return Err(InterpreterError::from(e)), - _ => { + r => { panic!( "internal error: unhandled function carrier variant returned from evaluate_expression" ); @@ -135,11 +135,11 @@ pub enum InterpreterError { #[from] cause: crate::ast::Error, }, - #[error("Error during resolver pass")] + #[error("Error during static analysis")] #[diagnostic(transparent)] Resolver { #[from] - cause: resolve::ResolveError, + cause: semantic::analyser::AnalysisError, }, #[error("Error while executing code")] #[diagnostic(transparent)] diff --git a/ndc_lib/src/interpreter/num.rs b/ndc_lib/src/interpreter/num.rs index 53f7abc7..de664006 100644 --- a/ndc_lib/src/interpreter/num.rs +++ b/ndc_lib/src/interpreter/num.rs @@ -4,9 +4,9 @@ use std::hash::{Hash, Hasher}; use std::num::TryFromIntError; use std::ops::{Add, Div, Mul, Neg, Not, Rem, Sub}; -use super::value::ValueType; use crate::ast::BinaryOperator; use crate::interpreter::evaluate::EvaluationError; +use crate::interpreter::function::StaticType; use crate::interpreter::int::Int; use crate::lexer::Span; use num::bigint::TryFromBigIntError; @@ -224,8 +224,8 @@ impl BinaryOperatorError { pub fn undefined_operation( operator: BinaryOperator, - left: &ValueType, - right: &ValueType, + left: &StaticType, + right: &StaticType, ) -> Self { Self(format!( "operator {operator} is not defined for {left} and {right}" @@ -377,13 +377,12 @@ impl Number { Self::Rational(Box::new(rat)) } - // TODO: change this to &'static str - fn type_name(&self) -> &'static str { + pub fn static_type(&self) -> StaticType { match self { - Self::Int(_) => "int", - Self::Float(_) => "float", - Self::Rational(_) => "rational", - Self::Complex(_) => "complex", + Self::Int(_) => StaticType::Int, + Self::Float(_) => StaticType::Float, + Self::Rational(_) => StaticType::Rational, + Self::Complex(_) => StaticType::Complex, } } @@ -397,8 +396,8 @@ impl Number { (Self::Float(p1), Self::Float(p2)) => Ok(Self::Float(p1.rem_euclid(p2))), (left, right) => Err(BinaryOperatorError::undefined_operation( BinaryOperator::EuclideanModulo, - &ValueType::from(left), - &ValueType::from(right), + &left.static_type(), + &right.static_type(), )), } } @@ -609,7 +608,7 @@ implement_rounding!(round); #[derive(thiserror::Error, Debug)] pub enum NumberToUsizeError { #[error("cannot convert from {0} to usize")] - UnsupportedVariant(&'static str), + UnsupportedVariant(StaticType), #[error("expected non-negative integer for indexing")] FromIntError(#[from] TryFromIntError), #[error("failed to convert from bigint to number because of: '{0}'")] @@ -623,7 +622,7 @@ impl TryFrom for usize { match value { Number::Int(Int::Int64(i)) => Ok(Self::try_from(i)?), Number::Int(Int::BigInt(b)) => Ok(Self::try_from(b)?), - n => Err(NumberToUsizeError::UnsupportedVariant(n.type_name())), + n => Err(NumberToUsizeError::UnsupportedVariant(n.static_type())), } } } @@ -631,7 +630,7 @@ impl TryFrom for usize { #[derive(thiserror::Error, Debug)] pub enum NumberToFloatError { #[error("cannot convert {0} to float")] - UnsupportedType(NumberType), + UnsupportedType(StaticType), #[error("cannot convert {0} to float")] UnsupportedValue(Number), } @@ -645,7 +644,7 @@ impl TryFrom<&Number> for f64 { Number::Int(Int::Int64(i)) => i.to_f64(), Number::Float(f) => Some(*f), Number::Rational(r) => r.to_f64(), - _ => return Err(Self::Error::UnsupportedType(NumberType::from(value))), + _ => return Err(Self::Error::UnsupportedType(value.static_type())), } .ok_or_else(|| Self::Error::UnsupportedValue(value.clone())) } @@ -654,7 +653,7 @@ impl TryFrom<&Number> for f64 { #[derive(thiserror::Error, Debug)] pub enum NumberToIntError { #[error("cannot convert {0} to int")] - UnsupportedType(NumberType), + UnsupportedType(StaticType), #[error("cannot convert {0} to int")] UnsupportedValue(Number), } @@ -668,7 +667,7 @@ impl TryFrom<&Number> for i64 { .try_into() .map_err(|_err| NumberToIntError::UnsupportedValue(value.clone())), Number::Int(Int::Int64(i)) => Ok(*i), - _ => Err(Self::Error::UnsupportedType(NumberType::from(value))), + _ => Err(Self::Error::UnsupportedType(value.static_type())), } } } @@ -696,37 +695,10 @@ fn rational_to_complex(r: &BigRational) -> Complex { } #[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[deprecated = "use static type instead?"] pub enum NumberType { Int, Float, Rational, Complex, } - -impl From for ValueType { - fn from(value: NumberType) -> Self { - Self::Number(value) - } -} - -impl From<&Number> for NumberType { - fn from(value: &Number) -> Self { - match value { - Number::Int(_) => Self::Int, - Number::Float(_) => Self::Float, - Number::Rational(_) => Self::Rational, - Number::Complex(_) => Self::Complex, - } - } -} - -impl fmt::Display for NumberType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Int => write!(f, "Int"), - Self::Float => write!(f, "Float"), - Self::Rational => write!(f, "Rational"), - Self::Complex => write!(f, "Complex"), - } - } -} diff --git a/ndc_lib/src/interpreter/resolve.rs b/ndc_lib/src/interpreter/resolve.rs deleted file mode 100644 index c6afad09..00000000 --- a/ndc_lib/src/interpreter/resolve.rs +++ /dev/null @@ -1,553 +0,0 @@ -use crate::ast::{Expression, ExpressionLocation, ForBody, ForIteration, Lvalue, ResolvedVar}; -use crate::lexer::Span; - -#[derive(thiserror::Error, miette::Diagnostic, Debug)] -#[error("{text}")] -pub struct ResolveError { - text: String, - #[label("related to this")] - span: Span, -} - -impl ResolveError { - fn identifier_not_previously_declared(ident: &str, span: Span) -> Self { - Self { - text: format!("Identifier {ident} has not previously been declared"), - span, - } - } -} - -pub fn resolve_pass( - ExpressionLocation { expression, span }: &mut ExpressionLocation, - lexical_data: &mut LexicalData, -) -> Result<(), ResolveError> { - match expression { - Expression::BoolLiteral(_) - | Expression::StringLiteral(_) - | Expression::Int64Literal(_) - | Expression::Float64Literal(_) - | Expression::BigIntLiteral(_) - | Expression::ComplexLiteral(_) - | Expression::Continue - | Expression::Break => { /* nothing to do here */ } - Expression::Identifier { - name: ident, - resolved, - } => { - if ident == "None" { - // THIS IS VERY UNHINGED - return Ok(()); - } - let binding = lexical_data - .get_binding_any(ident) - .ok_or_else(|| ResolveError::identifier_not_previously_declared(ident, *span))?; - - *resolved = Some(binding) - } - Expression::Statement(inner) => { - resolve_pass(inner, lexical_data)?; - } - Expression::Logical { left, right, .. } => { - resolve_pass(left, lexical_data)?; - resolve_pass(right, lexical_data)?; - } - Expression::Grouping(expr) => { - resolve_pass(expr, lexical_data)?; - } - Expression::VariableDeclaration { l_value, value } => { - resolve_pass(value, lexical_data)?; - resolve_lvalue_declarative(l_value, lexical_data)?; - } - Expression::Assignment { l_value, r_value } => { - resolve_lvalue(l_value, *span, lexical_data)?; - resolve_pass(r_value, lexical_data)?; - } - Expression::OpAssignment { - l_value, - r_value, - operation, - resolved_assign_operation, - resolved_operation, - } => { - resolve_lvalue(l_value, *span, lexical_data)?; - resolve_pass(r_value, lexical_data)?; - - // In this future should be able to use our type system to figure out which specific - // instance of the function we need. When this is the case we can stop inserting both - // versions in the OpAssignment expression and just figure out the correct one or error - // if there is none - let op_assign_ident = LexicalIdentifier::Function { - name: format!("{operation}="), - arity: Some(2), - }; - - if let Some(binding) = lexical_data.get_binding(&op_assign_ident) { - *resolved_assign_operation = Some(binding); - } - - let operation_ident = LexicalIdentifier::Function { - name: (*operation).clone(), - arity: Some(2), - }; - - if let Some(binding) = lexical_data.get_binding(&operation_ident) { - *resolved_operation = Some(binding); - } else { - // For now, we require that the normal operation is present and the special assignment operation is optional - return Err(ResolveError::identifier_not_previously_declared( - operation, *span, - )); - } - } - Expression::FunctionDeclaration { - name, - resolved_name, - arguments, - body, - .. - } => { - if let Some(name) = name { - let function_ident = LexicalIdentifier::Function { - name: (*name).clone(), - arity: Some(extract_argument_arity(arguments)), - }; - - *resolved_name = if let Some(binding) = - lexical_data.get_or_create_local_binding(function_ident.clone()) - { - Some(binding) - } else { - Some(lexical_data.create_local_binding(function_ident)) - } - } - - lexical_data.new_scope(); - resolve_arguments_declarative(arguments, lexical_data); - - resolve_pass(body, lexical_data)?; - lexical_data.destroy_scope(); - } - Expression::Block { statements } => { - lexical_data.new_scope(); - for s in statements { - resolve_pass(s, lexical_data)?; - } - lexical_data.destroy_scope(); - } - Expression::If { - condition, - on_true, - on_false, - } => { - resolve_pass(condition, lexical_data)?; - resolve_pass(on_true, lexical_data)?; - if let Some(on_false) = on_false { - resolve_pass(on_false, lexical_data)?; - } - } - Expression::While { - expression, - loop_body, - } => { - resolve_pass(expression, lexical_data)?; - resolve_pass(loop_body, lexical_data)?; - } - Expression::For { iterations, body } => { - resolve_for_iterations(iterations, body, lexical_data)?; - } - Expression::Call { - function, - arguments, - } => { - resolve_function_ident_arity(function, arguments.len(), *span, lexical_data)?; - for a in arguments { - resolve_pass(a, lexical_data)?; - } - } - Expression::Index { index, value } => { - resolve_pass(index, lexical_data)?; - resolve_pass(value, lexical_data)?; - } - Expression::Tuple { values } | Expression::List { values } => { - for v in values { - resolve_pass(v, lexical_data)?; - } - } - Expression::Map { values, default } => { - for (key, value) in values { - resolve_pass(key, lexical_data)?; - if let Some(value) = value { - resolve_pass(value, lexical_data)?; - } - } - - if let Some(default) = default { - resolve_pass(default, lexical_data)?; - } - } - Expression::Return { value } => { - resolve_pass(value, lexical_data)?; - } - Expression::RangeInclusive { start, end } | Expression::RangeExclusive { start, end } => { - if let Some(start) = start { - resolve_pass(start, lexical_data)?; - } - if let Some(end) = end { - resolve_pass(end, lexical_data)?; - } - } - } - - Ok(()) -} -fn resolve_function_ident_arity( - ident: &mut ExpressionLocation, - arity: usize, - span: Span, - lexical_data: &mut LexicalData, -) -> Result<(), ResolveError> { - let ExpressionLocation { - expression: Expression::Identifier { name, resolved }, - .. - } = ident - else { - // It's possible that we're not trying to invoke an identifier `foo()` but instead we're - // invoking a value like `get_function()()` so in this case we just continue like normal? - return resolve_pass(ident, lexical_data); - }; - - let binding = lexical_data - .get_binding(&LexicalIdentifier::Function { - name: name.clone(), - arity: Some(arity), - }) - .or_else(|| { - lexical_data.get_binding(&LexicalIdentifier::Function { - name: name.clone(), - arity: None, - }) - }) - // NOTE: for now if we can't find a function that matches we just bind to a variable with that name (if possible) - // this fixes some cases until we have full type checking - .or_else(|| (lexical_data).get_binding_any(name)) - .ok_or_else(|| ResolveError::identifier_not_previously_declared(name, span))?; - - *resolved = Some(binding); - - Ok(()) -} -fn resolve_for_iterations( - iterations: &mut [ForIteration], - body: &mut ForBody, - lexical_data: &mut LexicalData, -) -> Result<(), ResolveError> { - let Some((iteration, tail)) = iterations.split_first_mut() else { - unreachable!("because this function is never called with an empty slice"); - }; - - let mut do_destroy = false; - match iteration { - ForIteration::Iteration { l_value, sequence } => { - resolve_pass(sequence, lexical_data)?; - lexical_data.new_scope(); - resolve_lvalue_declarative(l_value, lexical_data)?; - do_destroy = true; - } - ForIteration::Guard(expr) => { - resolve_pass(expr, lexical_data)?; - } - } - - if !tail.is_empty() { - resolve_for_iterations(tail, body, lexical_data)? - } else { - match body { - ForBody::Block(block) => { - resolve_pass(block, lexical_data)?; - } - ForBody::List(list) => { - resolve_pass(list, lexical_data)?; - } - ForBody::Map { - key, - value, - default, - } => { - resolve_pass(key, lexical_data)?; - if let Some(value) = value { - resolve_pass(value, lexical_data)?; - } - - if let Some(default) = default { - resolve_pass(default, lexical_data)?; - } - } - } - } - - if do_destroy { - lexical_data.destroy_scope(); - } - - Ok(()) -} - -fn resolve_lvalue( - lvalue: &mut Lvalue, - span: Span, - lexical_data: &mut LexicalData, -) -> Result<(), ResolveError> { - match lvalue { - Lvalue::Identifier { - identifier, - resolved, - } => { - let Some(target) = lexical_data.get_binding(&identifier.clone().into()) else { - return Err(ResolveError::identifier_not_previously_declared( - identifier, span, - )); - }; - - *resolved = Some(target); - } - Lvalue::Index { index, value } => { - resolve_pass(index, lexical_data)?; - resolve_pass(value, lexical_data)?; - } - Lvalue::Sequence(seq) => { - for sub_lvalue in seq { - resolve_lvalue(sub_lvalue, span, lexical_data)? - } - } - } - - Ok(()) -} - -fn extract_argument_arity(arguments: &ExpressionLocation) -> usize { - let ExpressionLocation { - expression: Expression::Tuple { values }, - .. - } = arguments - else { - panic!("expected arguments to be tuple"); - }; - - values.len() -} -/// Resolve expressions as arguments to a function and return the function arity -fn resolve_arguments_declarative( - arguments: &mut ExpressionLocation, - lexical_data: &mut LexicalData, -) { - let ExpressionLocation { - expression: Expression::Tuple { values }, - .. - } = arguments - else { - panic!("expected arguments to be tuple"); - }; - - for arg in values { - let ExpressionLocation { - expression: Expression::Identifier { name, resolved }, - .. - } = arg - else { - panic!("expected tuple values to be ident"); - }; - - *resolved = Some( - lexical_data.create_local_binding(LexicalIdentifier::Variable { - name: (*name).clone(), - }), - ); - } -} -fn resolve_lvalue_declarative( - lvalue: &mut Lvalue, - lexical_data: &mut LexicalData, -) -> Result<(), ResolveError> { - match lvalue { - Lvalue::Identifier { - identifier, - resolved, - } => { - *resolved = Some( - lexical_data.create_local_binding(LexicalIdentifier::Variable { - name: (*identifier).clone(), - }), - ); - } - Lvalue::Index { index, value } => { - resolve_pass(index, lexical_data)?; - resolve_pass(value, lexical_data)?; - } - Lvalue::Sequence(seq) => { - for sub_lvalue in seq { - resolve_lvalue_declarative(sub_lvalue, lexical_data)? - } - } - } - - Ok(()) -} - -#[derive(Debug, Hash, Eq, PartialEq, Clone)] -pub enum LexicalIdentifier { - Variable { name: String }, - Function { name: String, arity: Option }, -} - -impl From<&str> for LexicalIdentifier { - fn from(value: &str) -> Self { - Self::Variable { name: value.into() } - } -} -impl From for LexicalIdentifier { - fn from(value: String) -> Self { - Self::Variable { name: value } - } -} - -#[derive(Debug)] -pub struct LexicalData { - current_scope_idx: usize, - global_scope: LexicalScope, - scopes: Vec, -} - -impl LexicalData { - pub fn from_global_scope(global_scope_map: Vec) -> Self { - Self { - current_scope_idx: 0, - global_scope: LexicalScope { - parent_idx: None, - identifiers: global_scope_map, - }, - scopes: vec![LexicalScope::new(None)], - } - } - - fn new_scope(&mut self) -> &LexicalScope { - let old_scope_idx = self.current_scope_idx; - self.current_scope_idx = self.scopes.len(); - let new_scope = LexicalScope::new(Some(old_scope_idx)); - self.scopes.push(new_scope); - &self.scopes[self.current_scope_idx] - } - - fn destroy_scope(&mut self) { - let next = self.scopes[self.current_scope_idx] - .parent_idx - .expect("tried to destroy scope while there were none"); - self.current_scope_idx = next; - } - - fn get_or_create_local_binding(&mut self, ident: LexicalIdentifier) -> Option { - Some(ResolvedVar::Captured { - slot: self.scopes[self.current_scope_idx].get_or_allocate(ident), - depth: 0, - }) - } - - fn get_binding_any(&mut self, ident: &str) -> Option { - let mut depth = 0; - let mut scope_ptr = self.current_scope_idx; - - loop { - if let Some(slot) = self.scopes[scope_ptr].get_slot_by_name(ident) { - return Some(ResolvedVar::Captured { slot, depth }); - } else if let Some(parent_idx) = self.scopes[scope_ptr].parent_idx { - depth += 1; - scope_ptr = parent_idx; - } else { - return Some(ResolvedVar::Global { - slot: self.global_scope.get_slot_by_name(ident)?, - }); - } - } - } - - fn get_binding(&mut self, name: &LexicalIdentifier) -> Option { - let mut depth = 0; - let mut scope_ptr = self.current_scope_idx; - - loop { - if let Some(slot) = self.scopes[scope_ptr].get_slot_by_identifier(name) { - return Some(ResolvedVar::Captured { slot, depth }); - } else if let Some(parent_idx) = self.scopes[scope_ptr].parent_idx { - depth += 1; - scope_ptr = parent_idx; - } else { - return Some(ResolvedVar::Global { - slot: self.global_scope.get_slot_by_identifier(name)?, - }); - } - } - } - - fn create_local_binding(&mut self, ident: LexicalIdentifier) -> ResolvedVar { - ResolvedVar::Captured { - slot: self.scopes[self.current_scope_idx].allocate(ident), - depth: 0, - } - } -} - -#[derive(Debug)] -struct LexicalScope { - parent_idx: Option, - identifiers: Vec, -} - -impl LexicalScope { - fn new(parent_idx: Option) -> Self { - Self { - parent_idx, - identifiers: Default::default(), - } - } - - pub fn get_slot_by_name(&self, find_ident: &str) -> Option { - for (slot, ident_in_scope) in self.identifiers.iter().enumerate().rev() { - let name_in_scope = match ident_in_scope { - LexicalIdentifier::Variable { name } | LexicalIdentifier::Function { name, .. } => { - name - } - }; - - if name_in_scope == find_ident { - return Some(slot); - } - } - - None - } - - /// Either returns the slot in this current scope or creates a new one - pub fn get_or_allocate(&mut self, ident: LexicalIdentifier) -> usize { - if let Some(idx) = self.get_slot_by_identifier(&ident) { - idx - } else { - self.allocate(ident) - } - } - - fn get_slot_by_identifier(&mut self, find_ident: &LexicalIdentifier) -> Option { - for (slot, ident_in_scope) in self.identifiers.iter().enumerate().rev() { - if ident_in_scope == find_ident { - return Some(slot); - } - } - - None - } - - fn allocate(&mut self, name: LexicalIdentifier) -> usize { - self.identifiers.push(name); - // Slot is just the length of the list - self.identifiers.len() - 1 - } -} diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs new file mode 100644 index 00000000..b9a9b164 --- /dev/null +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -0,0 +1,792 @@ +use crate::ast::{ + Binding, Expression, ExpressionLocation, ForBody, ForIteration, Lvalue, ResolvedVar, +}; +use crate::interpreter::function::StaticType; +use crate::lexer::Span; +use itertools::Itertools; +use std::fmt::{Debug, Formatter}; + +pub struct Analyser { + scope_tree: ScopeTree, +} + +impl Analyser { + pub fn from_scope_tree(scope_tree: ScopeTree) -> Self { + Self { scope_tree } + } +} + +impl Analyser { + pub fn analyse( + &mut self, + ExpressionLocation { expression, span }: &mut ExpressionLocation, + ) -> Result { + match expression { + Expression::BoolLiteral(_) => Ok(StaticType::Bool), + Expression::StringLiteral(_) => Ok(StaticType::String), + Expression::Int64Literal(_) | Expression::BigIntLiteral(_) => Ok(StaticType::Int), + Expression::Float64Literal(_) => Ok(StaticType::Float), + Expression::ComplexLiteral(_) => Ok(StaticType::Complex), + Expression::Continue | Expression::Break => Ok(StaticType::unit()), // TODO: change to never type? + Expression::Identifier { + name: ident, + resolved, + } => { + if ident == "None" { + // TODO: we're going to need something like HM to infer the type of option here, maybe force type annotations? + return Ok(StaticType::Option(Box::new(StaticType::Any))); + } + let binding = self.scope_tree.get_binding_any(ident).ok_or_else(|| { + AnalysisError::identifier_not_previously_declared(ident, *span) + })?; + + *resolved = Binding::Resolved(binding); + + Ok(self.scope_tree.get_type(binding).clone()) + } + Expression::Statement(inner) => { + self.analyse(inner)?; + Ok(StaticType::unit()) + } + Expression::Logical { left, right, .. } => { + self.analyse(left)?; // TODO: throw error if type does not match bool? + self.analyse(right)?; // TODO: throw error if type does not match bool? + Ok(StaticType::Bool) + } + Expression::Grouping(expr) => self.analyse(expr), + Expression::VariableDeclaration { l_value, value } => { + let typ = self.analyse(value)?; + self.resolve_lvalue_declarative(l_value, typ, *span)?; + Ok(StaticType::unit()) // TODO: never type here? + } + Expression::Assignment { l_value, r_value } => { + self.resolve_lvalue(l_value, *span)?; + self.analyse(r_value)?; + Ok(StaticType::unit()) + } + Expression::OpAssignment { + l_value, + r_value, + operation, + resolved_assign_operation, + resolved_operation, + } => { + let left_type = self.resolve_single_lvalue(l_value, *span)?; + let right_type = self.analyse(r_value)?; + let arg_types = vec![left_type, right_type]; + + *resolved_assign_operation = self + .scope_tree + .resolve_function2(&format!("{operation}="), &arg_types); + *resolved_operation = self.scope_tree.resolve_function2(operation, &arg_types); + + if let Binding::None = resolved_operation { + return Err(AnalysisError::function_not_found( + operation, &arg_types, *span, + )); + } + + Ok(StaticType::unit()) + } + Expression::FunctionDeclaration { + name, + resolved_name, + parameters, + body, + .. + } => { + // TODO: figuring out the type signature of function declarations is the rest of the owl + let param_types: Vec = + std::iter::repeat_n(StaticType::Any, extract_argument_arity(parameters)) + .collect(); + + self.scope_tree.new_scope(); + self.resolve_parameters_declarative(parameters); + + // TODO: instead of just hardcoding the return type of every function to StaticType::Any + // we should somehow collect all the returns that were encountered while analysing + // the body and then figuring out the LUB. + self.analyse(body)?; + let return_type = StaticType::Any; + self.scope_tree.destroy_scope(); + + let function_type = StaticType::Function { + parameters: Some(param_types.clone()), + return_type: Box::new(return_type), + }; + + if let Some(name) = name { + // TODO: is this correct, for now we just always create a new binding, we could + // also produce an error if we are generating a conflicting binding + *resolved_name = Some( + self.scope_tree + .create_local_binding(name.clone(), function_type.clone()), + ); + } + + Ok(function_type) + } + Expression::Block { statements } => { + self.scope_tree.new_scope(); + let mut last = None; + for s in statements { + last = Some(self.analyse(s)?); + } + self.scope_tree.destroy_scope(); + + Ok(last.unwrap_or_else(StaticType::unit)) + } + Expression::If { + condition, + on_true, + on_false, + } => { + self.analyse(condition)?; + let true_type = self.analyse(on_true)?; + let false_type = if let Some(on_false) = on_false { + self.analyse(on_false)? + } else { + StaticType::unit() + }; + + if true_type != StaticType::unit() { + // TODO: Emit warning for not using a semicolon in this if + } + + if true_type != false_type { + // TODO maybe show warning? + } + + Ok(true_type.lub(&false_type)) + } + Expression::While { + expression, + loop_body, + } => { + self.analyse(expression)?; + self.analyse(loop_body)?; + Ok(StaticType::unit()) + } + Expression::For { iterations, body } => { + Ok(self.resolve_for_iterations(iterations, body, *span)?) + } + Expression::Call { + function, + arguments, + } => { + let mut type_sig = Vec::with_capacity(arguments.len()); + for a in arguments { + type_sig.push(self.analyse(a)?); + } + + let StaticType::Function { return_type, .. } = + self.resolve_function_with_argument_types(function, &type_sig, *span)? + else { + // If we couldn't resolve the identifier to a function we have to just assume that + // whatever identifier we did find is a function at runtime and will return Any + return Ok(StaticType::Any); + }; + + Ok(*return_type) + } + Expression::Index { index, value } => { + self.analyse(index)?; + let container_type = self.analyse(value)?; + + container_type + .index_element_type() + .ok_or_else(|| AnalysisError::unable_to_index_into(&container_type, *span)) + } + Expression::Tuple { values } => { + let mut types = Vec::with_capacity(values.len()); + for v in values { + types.push(self.analyse(v)?); + } + + Ok(StaticType::Tuple(types)) + } + Expression::List { values } => { + let element_type = self.analyse_multiple_expression_with_same_type(values)?; + + // TODO: for now if we encounter an empty list expression we say the list is generic over Any but this clearly is not a good solution + Ok(StaticType::List(Box::new( + element_type.unwrap_or(StaticType::Any), + ))) + } + Expression::Map { values, default } => { + let mut key_type: Option = None; + let mut value_type: Option = None; + for (key, value) in values { + // let map = %{ + // "key": 1, + // 10: 1, + // } + if let Some(key_type) = &mut key_type { + let next_type = self.analyse(key)?; + *key_type = key_type.lub(&next_type); + } else { + key_type = Some(self.analyse(key)?); + } + if let Some(value) = value { + if let Some(value_type) = &mut value_type { + let next_type = self.analyse(value)?; + if &next_type != value_type { + *value_type = value_type.lub(&next_type); + } + } else { + value_type = Some(self.analyse(value)?); + } + } + } + + if let Some(default) = default { + self.analyse(default)?; + } + + // TODO: defaulting to Any here is surely going to bite us later + Ok(StaticType::Map { + key: Box::new(key_type.unwrap_or(StaticType::Any)), + value: Box::new(value_type.unwrap_or_else(StaticType::unit)), + }) + } + // Return evaluates to the type of the expression it returns, which makes type checking easier! + // Actually it doesn't seem to make it any easier + Expression::Return { value } => self.analyse(value), + Expression::RangeInclusive { start, end } + | Expression::RangeExclusive { start, end } => { + if let Some(start) = start { + self.analyse(start)?; + } + if let Some(end) = end { + self.analyse(end)?; + } + + Ok(StaticType::Iterator(Box::new(StaticType::Int))) + } + } + } + fn resolve_function_with_argument_types( + &mut self, + ident: &mut ExpressionLocation, + argument_types: &[StaticType], + span: Span, + ) -> Result { + let ExpressionLocation { + expression: Expression::Identifier { name, resolved }, + .. + } = ident + else { + // It's possible that we're not trying to invoke an identifier `foo()` but instead we're + // invoking a value like `get_function()()` so in this case we just continue like normal? + return self.analyse(ident); + }; + + // println!("resolve fn {name} {}", argument_types.iter().join(", ")); + + let binding = self.scope_tree.resolve_function2(name, argument_types); + + let out_type = match &binding { + Binding::None => { + return Err(AnalysisError::function_not_found( + name, + argument_types, + span, + )); + } + Binding::Resolved(res) => self.scope_tree.get_type(*res).clone(), + + // TODO: are we just going to lie about the type or is this just how truthful we can be + Binding::Dynamic(_) => StaticType::Function { + parameters: None, + return_type: Box::new(StaticType::Any), + }, + }; + + *resolved = binding; + + Ok(out_type) + } + fn resolve_for_iterations( + &mut self, + iterations: &mut [ForIteration], + body: &mut ForBody, + span: Span, + ) -> Result { + let Some((iteration, tail)) = iterations.split_first_mut() else { + unreachable!("because this function is never called with an empty slice"); + }; + + let mut do_destroy = false; + match iteration { + ForIteration::Iteration { l_value, sequence } => { + self.analyse(sequence)?; + + self.scope_tree.new_scope(); + + // TODO: when we give type parameters to all instances of sequence we can correctly infer StaticType::Any in this postition + self.resolve_lvalue_declarative(l_value, StaticType::Any, span)?; + do_destroy = true; // TODO: why is this correct + } + ForIteration::Guard(expr) => { + self.analyse(expr)?; + } + } + + let out_type = if !tail.is_empty() { + self.resolve_for_iterations(tail, body, span)? + } else { + match body { + ForBody::Block(block) => { + self.analyse(block)?; + StaticType::unit() + } + ForBody::List(list) => StaticType::List(Box::new(self.analyse(list)?)), + ForBody::Map { + key, + value, + default, + } => { + let key_type = self.analyse(key)?; + let value_type = if let Some(value) = value { + self.analyse(value)? + } else { + StaticType::unit() + }; + + if let Some(default) = default { + self.analyse(default)?; + } + + StaticType::Map { + key: Box::new(key_type), + value: Box::new(value_type), + } + } + } + }; + + if do_destroy { + self.scope_tree.destroy_scope(); + } + + Ok(out_type) + } + + fn resolve_single_lvalue( + &mut self, + lvalue: &mut Lvalue, + span: Span, + ) -> Result { + match lvalue { + Lvalue::Identifier { + identifier, + resolved, + } => { + let Some(target) = self.scope_tree.get_binding_any(identifier) else { + return Err(AnalysisError::identifier_not_previously_declared( + identifier, span, + )); + }; + + *resolved = Some(target); + + Ok(self.scope_tree.get_type(target).clone()) + } + Lvalue::Index { index, value } => { + self.analyse(index)?; + let type_of_index_target = self.analyse(value)?; + + type_of_index_target + .index_element_type() + .ok_or_else(|| AnalysisError::unable_to_index_into(&type_of_index_target, span)) + } + Lvalue::Sequence(_) => { + Err(AnalysisError::lvalue_required_to_be_single_identifier(span)) + } + } + } + + fn resolve_lvalue(&mut self, lvalue: &mut Lvalue, span: Span) -> Result<(), AnalysisError> { + match lvalue { + Lvalue::Identifier { + identifier, + resolved, + } => { + let Some(target) = self.scope_tree.get_binding_any(identifier) else { + return Err(AnalysisError::identifier_not_previously_declared( + identifier, span, + )); + }; + + *resolved = Some(target); + } + Lvalue::Index { index, value } => { + self.analyse(index)?; + self.analyse(value)?; + } + Lvalue::Sequence(seq) => { + for sub_lvalue in seq { + self.resolve_lvalue(sub_lvalue, span)? + } + } + } + + Ok(()) + } + + /// Resolve expressions as arguments to a function and return the function arity + fn resolve_parameters_declarative(&mut self, arguments: &mut ExpressionLocation) { + let ExpressionLocation { + expression: Expression::Tuple { values }, + .. + } = arguments + else { + panic!("expected arguments to be tuple"); + }; + + for arg in values { + let ExpressionLocation { + expression: Expression::Identifier { name, resolved }, + .. + } = arg + else { + panic!("expected tuple values to be ident"); + }; + + // TODO: big challenge how do we figure out the function parameter types? + // it seems like this is something we need an HM like system for!? + *resolved = Binding::Resolved( + self.scope_tree + .create_local_binding((*name).to_string(), StaticType::Any), + ); + } + } + fn resolve_lvalue_declarative( + &mut self, + lvalue: &mut Lvalue, + typ: StaticType, + span: Span, + ) -> Result<(), AnalysisError> { + match lvalue { + Lvalue::Identifier { + identifier, + resolved, + } => { + *resolved = Some( + self.scope_tree + .create_local_binding(identifier.clone(), typ), + ); + } + Lvalue::Index { index, value } => { + self.analyse(index)?; + self.analyse(value)?; + } + Lvalue::Sequence(seq) => { + let sub_types = typ + .unpack() + .ok_or_else(|| AnalysisError::unable_to_unpack_type(&typ, span))?; + + for (sub_lvalue, sub_lvalue_type) in seq.iter_mut().zip(sub_types) { + self.resolve_lvalue_declarative( + sub_lvalue, + sub_lvalue_type.clone(), + /* todo: figure out how to narrow this span */ span, + )? + } + } + } + + Ok(()) + } + fn analyse_multiple_expression_with_same_type( + &mut self, + expressions: &mut Vec, + ) -> Result, AnalysisError> { + let mut element_type: Option = None; + + for expression in expressions { + if let Some(element_type) = &mut element_type { + let following_type = self.analyse(expression)?; + + *element_type = element_type.lub(&following_type); + } else { + element_type = Some(self.analyse(expression)?); + } + } + + Ok(element_type) + } +} + +fn extract_argument_arity(arguments: &ExpressionLocation) -> usize { + let ExpressionLocation { + expression: Expression::Tuple { values }, + .. + } = arguments + else { + panic!("expected arguments to be tuple"); + }; + + values.len() +} + +#[derive(Debug)] +pub struct ScopeTree { + current_scope_idx: usize, + global_scope: Scope, + scopes: Vec, +} + +impl ScopeTree { + pub fn from_global_scope(global_scope_map: Vec<(String, StaticType)>) -> Self { + Self { + current_scope_idx: 0, + global_scope: Scope { + parent_idx: None, + identifiers: global_scope_map, + }, + scopes: vec![Scope::new(None)], + } + } + + fn get_type(&self, res: ResolvedVar) -> &StaticType { + match res { + ResolvedVar::Captured { slot, depth } => { + let mut scope_idx = self.current_scope_idx; + let mut depth = depth; + while depth > 0 { + depth -= 1; + scope_idx = self.scopes[scope_idx] + .parent_idx + .expect("parent_idx was None while traversing the scope tree"); + } + &self.scopes[scope_idx].identifiers[slot].1 + } + // for now all globals are functions + ResolvedVar::Global { slot } => &self.global_scope.identifiers[slot].1, + } + } + + fn new_scope(&mut self) -> &Scope { + let old_scope_idx = self.current_scope_idx; + self.current_scope_idx = self.scopes.len(); + let new_scope = Scope::new(Some(old_scope_idx)); + self.scopes.push(new_scope); + &self.scopes[self.current_scope_idx] + } + + fn destroy_scope(&mut self) { + let next = self.scopes[self.current_scope_idx] + .parent_idx + .expect("tried to destroy scope while there were none"); + self.current_scope_idx = next; + } + + fn get_binding_any(&mut self, ident: &str) -> Option { + let mut depth = 0; + let mut scope_ptr = self.current_scope_idx; + + loop { + if let Some(slot) = self.scopes[scope_ptr].find_slot_by_name(ident) { + return Some(ResolvedVar::Captured { slot, depth }); + } else if let Some(parent_idx) = self.scopes[scope_ptr].parent_idx { + depth += 1; + scope_ptr = parent_idx; + } else { + return Some(ResolvedVar::Global { + slot: self.global_scope.find_slot_by_name(ident)?, + }); + } + } + } + + fn resolve_function_dynamic(&mut self, ident: &str, sig: &[StaticType]) -> Vec { + let mut depth = 0; + let mut scope_ptr = self.current_scope_idx; + + loop { + let candidates = self.scopes[scope_ptr].find_function_candidates(ident, sig); + if !candidates.is_empty() { + return candidates + .into_iter() + .map(|slot| ResolvedVar::Captured { slot, depth }) + .collect(); + } else if let Some(parent_idx) = self.scopes[scope_ptr].parent_idx { + depth += 1; + scope_ptr = parent_idx; + } else { + return self + .global_scope + .find_function_candidates(ident, sig) + .into_iter() + .map(|slot| ResolvedVar::Global { slot }) + .collect(); + } + } + } + + fn resolve_function2(&mut self, ident: &str, sig: &[StaticType]) -> Binding { + self.resolve_function(ident, sig) + .map(Binding::Resolved) + .or_else(|| { + let loose_bindings = self.resolve_function_dynamic(ident, sig); + + if loose_bindings.is_empty() { + return None; + } + + Some(Binding::Dynamic(loose_bindings)) + }) + // If we can't find any function in scope that could match, we just default to an identifier. + // TODO: no this fucks everything + .or_else(|| { + self.get_binding_any(ident) + .map(|resolved| Binding::Dynamic(vec![resolved])) + }) + .unwrap_or(Binding::None) + } + + fn resolve_function(&mut self, ident: &str, arg_types: &[StaticType]) -> Option { + let mut depth = 0; + let mut scope_ptr = self.current_scope_idx; + + loop { + if let Some(slot) = self.scopes[scope_ptr].find_function(ident, arg_types) { + return Some(ResolvedVar::Captured { slot, depth }); + } else if let Some(parent_idx) = self.scopes[scope_ptr].parent_idx { + depth += 1; + scope_ptr = parent_idx; + } else { + return Some(ResolvedVar::Global { + slot: self.global_scope.find_function(ident, arg_types)?, + }); + } + } + } + + fn create_local_binding(&mut self, ident: String, typ: StaticType) -> ResolvedVar { + ResolvedVar::Captured { + slot: self.scopes[self.current_scope_idx].allocate(ident, typ), + depth: 0, + } + } +} + +#[derive(Debug)] +struct Scope { + parent_idx: Option, + identifiers: Vec<(String, StaticType)>, +} + +impl Scope { + fn new(parent_idx: Option) -> Self { + Self { + parent_idx, + identifiers: Default::default(), + } + } + + pub fn find_slot_by_name(&self, find_ident: &str) -> Option { + self.identifiers + .iter() + .rposition(|(ident, _)| ident == find_ident) + } + + fn find_function_candidates(&self, find_ident: &str, find_types: &[StaticType]) -> Vec { + self.identifiers.iter() + .enumerate() + .rev() + .filter_map(|(slot, (ident, typ))| { + if ident != find_ident { + return None; + } + + // If the thing is not a function we're not interested + let StaticType::Function { parameters, .. } = typ else { + return None; + }; + + let Some(param_types) = parameters else { + // If this branch happens then the function we're matching against is variadic meaning it's always a match + debug_assert!(false, "we should never be calling find_function_candidates if there were variadic matches"); + // TODO: Change to unreachable? + return Some(slot); + }; + + let is_good = param_types.len() == find_types.len() + && param_types.iter().zip(find_types.iter()).all(|(typ_1, typ_2)| !typ_1.is_incompatible_with(typ_2)); + + is_good.then_some(slot) + }) + .collect() + } + fn find_function(&self, find_ident: &str, find_types: &[StaticType]) -> Option { + self.identifiers + .iter() + .rposition(|(ident, typ)| ident == find_ident && typ.is_fn_and_matches(find_types)) + } + + fn allocate(&mut self, name: String, typ: StaticType) -> usize { + self.identifiers.push((name, typ)); + // Slot is just the length of the list minus one + self.identifiers.len() - 1 + } +} +#[derive(thiserror::Error, miette::Diagnostic, Debug)] +#[error("{text}")] +pub struct AnalysisError { + text: String, + #[label("related to this")] + span: Span, +} + +impl AnalysisError {} + +impl AnalysisError { + fn unable_to_index_into(typ: &StaticType, span: Span) -> Self { + Self { + text: format!("Unable to index into {typ}"), + span, + } + } + fn unable_to_unpack_type(typ: &StaticType, span: Span) -> Self { + Self { + text: format!("Invalid unpacking of {typ}"), + span, + } + } + fn lvalue_required_to_be_single_identifier(span: Span) -> Self { + Self { + text: "This lvalue is required to be a single identifier".to_string(), + span, + } + } + + fn function_not_found(ident: &str, types: &[StaticType], span: Span) -> Self { + Self { + text: format!( + "No function called '{ident}' found that matches the arguments '{}'", + types.iter().join(", ") + ), + span, + } + } + + fn identifier_not_previously_declared(ident: &str, span: Span) -> Self { + Self { + text: format!("Identifier {ident} has not previously been declared"), + span, + } + } +} + +impl Debug for Analyser { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f)?; + for (id, scope) in self.scope_tree.scopes.iter().enumerate() { + writeln!(f, "{id}: {scope:?}")?; + } + + Ok(()) + } +} diff --git a/ndc_lib/src/interpreter/semantic/mod.rs b/ndc_lib/src/interpreter/semantic/mod.rs new file mode 100644 index 00000000..32c1c0de --- /dev/null +++ b/ndc_lib/src/interpreter/semantic/mod.rs @@ -0,0 +1 @@ +pub mod analyser; diff --git a/ndc_lib/src/interpreter/sequence.rs b/ndc_lib/src/interpreter/sequence.rs index a92cc30c..83882d53 100644 --- a/ndc_lib/src/interpreter/sequence.rs +++ b/ndc_lib/src/interpreter/sequence.rs @@ -1,7 +1,8 @@ use crate::hash_map::HashMap; +use crate::interpreter::function::StaticType; use crate::interpreter::heap::{MaxHeap, MinHeap}; use crate::interpreter::iterator::ValueIterator; -use crate::interpreter::value::{Value, ValueType}; +use crate::interpreter::value::Value; use std::cell::RefCell; use std::cmp::Ordering; use std::collections::VecDeque; @@ -42,6 +43,60 @@ impl Sequence { } } + pub fn static_type(&self) -> StaticType { + match self { + Self::String(_) => StaticType::String, + // I predict that defaulting to Any is going to make us very sad one day + Self::List(l) => StaticType::List(Box::new( + l.borrow() + .iter() + .next() + .map(|i| i.static_type()) + .unwrap_or(StaticType::Any), + )), + Self::Tuple(t) => StaticType::Tuple(t.iter().map(Value::static_type).collect()), + Self::Map(map, _) => StaticType::Map { + key: Box::new( + map.borrow() + .keys() + .next() + .map(Value::static_type) + .unwrap_or(StaticType::Any), + ), + value: Box::new( + map.borrow() + .values() + .next() + .map(Value::static_type) + .unwrap_or(StaticType::Any), + ), + }, + // TODO: we can't infer the type of iterators at runtime, unless we implement peek (CNA WE?) + Self::Iterator(_) => StaticType::Iterator(Box::new(StaticType::Any)), + + Self::MaxHeap(heap) => StaticType::MaxHeap(Box::new( + heap.borrow() + .iter() + .max() + .map(|elem| elem.0.static_type()) + .unwrap_or(StaticType::Any), + )), + Self::MinHeap(heap) => StaticType::MinHeap(Box::new( + heap.borrow() + .iter() + .min() + .map(|elem| elem.0.0.static_type()) + .unwrap_or(StaticType::Any), + )), + Self::Deque(d) => StaticType::Deque(Box::new( + d.borrow() + .iter() + .next() + .map(Value::static_type) + .unwrap_or(StaticType::Any), + )), + } + } #[must_use] pub fn deepcopy(&self) -> Self { match self { @@ -88,19 +143,6 @@ impl Sequence { } } - pub fn value_type(&self) -> ValueType { - match self { - Self::String(_) => ValueType::String, - Self::List(_) => ValueType::List, - Self::Tuple(t) => ValueType::Tuple(t.iter().map(Value::value_type).collect()), - Self::Map(_, _) => ValueType::Map, - Self::Iterator(_) => ValueType::Iterator, - Self::MaxHeap(_) => ValueType::MaxHeap, - Self::MinHeap(_) => ValueType::MinHeap, - Self::Deque(_) => ValueType::Deque, - } - } - #[must_use] pub fn contains(&self, needle: &Value) -> bool { match self { diff --git a/ndc_lib/src/interpreter/value.rs b/ndc_lib/src/interpreter/value.rs index 473e0be6..a2a170ca 100644 --- a/ndc_lib/src/interpreter/value.rs +++ b/ndc_lib/src/interpreter/value.rs @@ -10,9 +10,9 @@ use num::BigInt; use crate::compare::FallibleOrd; use crate::hash_map::DefaultHasher; -use crate::interpreter::function::OverloadedFunction; +use crate::interpreter::function::{Function, StaticType}; use crate::interpreter::int::Int; -use crate::interpreter::num::{Number, NumberToFloatError, NumberToUsizeError, NumberType}; +use crate::interpreter::num::{Number, NumberToFloatError, NumberToUsizeError}; use crate::interpreter::sequence::Sequence; use super::iterator::{ValueIterator, ValueRange, ValueRangeFrom, ValueRangeInclusive}; @@ -25,10 +25,13 @@ pub enum Value { Number(Number), Bool(bool), Sequence(Sequence), - Function(Rc>), + Function(Rc), } impl Value { + pub(crate) fn function(function: Function) -> Self { + Self::Function(Rc::new(function)) + } pub(crate) fn string>(string: S) -> Self { Self::Sequence(Sequence::String(Rc::new(RefCell::new(string.into())))) } @@ -61,10 +64,6 @@ impl Value { Self::Option(Some(Box::new(value))) } - pub(crate) fn function>(source: T) -> Self { - Self::Function(Rc::new(RefCell::new(source.into()))) - } - pub(crate) fn number>(source: T) -> Self { Self::Number(source.into()) } @@ -85,6 +84,7 @@ impl Value { // Note: this method is called `try_into_iter` but it doesn't always create an iterator over // the original value. In most cases you get an iterator over a copy of the data. #[must_use] + #[deprecated = "use try_into_vec"] pub fn try_into_iter(self) -> Option> { match self { Self::Sequence(Sequence::List(list)) => match Rc::try_unwrap(list) { @@ -124,17 +124,42 @@ impl Value { } } + pub fn try_into_vec(self) -> Option> { + match self { + Self::Sequence(Sequence::List(list)) => match Rc::try_unwrap(list) { + // This short circuit is almost certainly wrong because take will panic if list is borrowed + Ok(list) => Some(list.into_inner()), + Err(list) => Some(Vec::clone(&*list.borrow())), + }, + Self::Sequence(Sequence::Tuple(list)) => match Rc::try_unwrap(list) { + Ok(list) => Some(list), + Err(list) => Some(Vec::clone(&list)), + }, + Self::Sequence(Sequence::Map(map, _)) => { + Some(map.borrow().keys().cloned().collect_vec()) + } + Self::Sequence(Sequence::String(string)) => match Rc::try_unwrap(string) { + // This implementation is peak retard, we don't want collect_vec here + // ^-- WTF: is this comment, we collect_vec here anyways? + Ok(string) => Some(string.into_inner().chars().map(Self::from).collect_vec()), + Err(string) => Some(string.borrow().chars().map(Self::from).collect_vec()), + }, + _ => None, + } + } + #[must_use] - /// Returns the `ValueType` associated with this value - /// ``` - /// # use ndc_lib::interpreter::value::Value; - /// # use ndc_lib::interpreter::value::ValueType; - /// # use ndc_lib::interpreter::num::NumberType; - /// let val = Value::from(1); - /// assert_eq!(val.value_type(), NumberType::Int.into()); - /// ``` - pub fn value_type(&self) -> ValueType { - self.into() + pub fn static_type(&self) -> StaticType { + match self { + Self::Option(c) => StaticType::Option(Box::new( + c.as_deref().map_or(StaticType::Any, Self::static_type), + )), + Self::Number(number) => number.static_type(), + Self::Bool(_) => StaticType::Bool, + + Self::Sequence(s) => s.static_type(), + Self::Function(fun) => fun.static_type(), + } } #[must_use] @@ -153,8 +178,8 @@ impl Value { #[must_use] pub fn supports_vectorization_with(&self, other: &Self) -> bool { - self.value_type() - .supports_vectorization_with(&other.value_type()) + self.static_type() + .supports_vectorization_with(&other.static_type()) } } @@ -166,8 +191,8 @@ impl FallibleOrd for Value { self.partial_cmp(other).ok_or_else(|| { anyhow::anyhow!( "{} cannot be compared to {}", - self.value_type(), - other.value_type() + self.static_type(), + other.static_type() ) }) } @@ -181,8 +206,8 @@ impl FallibleOrd for &Value { self.partial_cmp(other).ok_or_else(|| { anyhow::anyhow!( "{} cannot be compared to {}", - self.value_type(), - other.value_type() + self.static_type(), + other.static_type() ) }) } @@ -399,12 +424,6 @@ impl From for Value { } } -impl From for Value { - fn from(value: OverloadedFunction) -> Self { - Self::Function(Rc::new(RefCell::new(value))) - } -} - impl From> for Value { fn from(value: RangeInclusive) -> Self { Self::from(ValueIterator::ValueRangeInclusive(ValueRangeInclusive( @@ -432,7 +451,7 @@ impl From> for Value { #[derive(thiserror::Error, Debug)] pub enum ConversionError { #[error("Cannot convert {0} into {1}")] - UnsupportedVariant(ValueType, &'static str), + UnsupportedVariant(StaticType, &'static str), #[error("Cannot into {0} because the length is incorrect")] IncorrectLength(&'static str), @@ -452,7 +471,7 @@ impl TryFrom for (Value, Value) { fn try_from(value: Value) -> Result { let Value::Sequence(Sequence::Tuple(tuple)) = value else { return Err(ConversionError::UnsupportedVariant( - value.value_type(), + value.static_type(), stringify!((Value, Value)), )); }; @@ -475,7 +494,7 @@ impl TryFrom for i64 { type Error = ConversionError; fn try_from(value: Value) -> Result { - let typ = value.value_type(); + let typ = value.static_type(); if let Value::Number(Number::Int(Int::Int64(i))) = value { return Ok(i); } @@ -497,7 +516,7 @@ impl TryFrom<&mut Value> for f64 { match value { Value::Number(n) => Ok((&*n).try_into()?), v => Err(Self::Error::UnsupportedVariant( - v.value_type(), + v.static_type(), stringify!(f64), )), } @@ -511,7 +530,7 @@ impl TryFrom<&mut Value> for i64 { match value { Value::Number(Number::Int(Int::Int64(i))) => Ok(*i), v => Err(Self::Error::UnsupportedVariant( - v.value_type(), + v.static_type(), stringify!(i64), )), } @@ -525,7 +544,7 @@ impl TryFrom<&mut Value> for bool { match value { Value::Bool(bool) => Ok(*bool), v => Err(Self::Error::UnsupportedVariant( - v.value_type(), + v.static_type(), stringify!(bool), )), } @@ -539,7 +558,7 @@ impl TryFrom for usize { match value { Value::Number(n) => Ok(Self::try_from(n)?), v => Err(Self::Error::UnsupportedVariant( - v.value_type(), + v.static_type(), stringify!(usize), )), } @@ -553,7 +572,7 @@ impl TryFrom for Number { match value { Value::Number(n) => Ok(n), v => Err(ConversionError::UnsupportedVariant( - v.value_type(), + v.static_type(), stringify!(Number), )), } @@ -567,7 +586,7 @@ impl TryFrom for BigInt { Value::Number(Number::Int(Int::BigInt(b))) => Ok(b), Value::Number(Number::Int(Int::Int64(i))) => Ok(Self::from(i)), v => Err(ConversionError::UnsupportedVariant( - v.value_type(), + v.static_type(), stringify!(BigInt), )), } @@ -581,7 +600,7 @@ impl TryFrom<&mut Value> for usize { match value { Value::Number(number) => Ok(Self::try_from(number.clone())?), v => Err(ConversionError::UnsupportedVariant( - v.value_type(), + v.static_type(), stringify!(usize), )), } @@ -595,7 +614,7 @@ impl<'a> TryFrom<&'a mut Value> for &'a Sequence { match value { Value::Sequence(seq) => Ok(seq), v => Err(ConversionError::UnsupportedVariant( - v.value_type(), + v.static_type(), stringify!(&Sequence), )), } @@ -609,100 +628,13 @@ impl<'a> TryFrom<&'a mut Value> for &'a Number { match value { Value::Number(n) => Ok(n), v => Err(ConversionError::UnsupportedVariant( - v.value_type(), + v.static_type(), "&Number", )), } } } -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum ValueType { - Option, // TODO: add type param - Number(NumberType), - Bool, - String, - List, - Tuple(Vec), - Function, - Map, - Iterator, - MinHeap, - MaxHeap, - Deque, -} - -impl ValueType { - #[must_use] - pub fn supports_vectorization(&self) -> bool { - matches!(self, Self::Tuple(values) if values.iter().all(|x| matches!(x, Self::Number(_)))) - } - - #[must_use] - pub fn supports_vectorization_with(&self, other: &Self) -> bool { - match (self, other) { - (Self::Tuple(l), Self::Tuple(r)) - if { - l.len() == r.len() - && self.supports_vectorization() - && other.supports_vectorization() - } => - { - true - } - (Self::Tuple(_), Self::Number(_)) if self.supports_vectorization() => true, - (Self::Number(_), Self::Tuple(_)) if other.supports_vectorization() => true, - _ => false, - } - } -} - -impl From<&Value> for ValueType { - fn from(value: &Value) -> Self { - match value { - Value::Option(_) => Self::Option, - Value::Number(n) => Self::Number(n.into()), - Value::Bool(_) => Self::Bool, - Value::Sequence(Sequence::String(_)) => Self::String, - Value::Sequence(Sequence::List(_)) => Self::List, - Value::Sequence(Sequence::Tuple(t)) => Self::Tuple(t.iter().map(Into::into).collect()), - Value::Function(_) => Self::Function, - Value::Sequence(Sequence::Map(_, _)) => Self::Map, - Value::Sequence(Sequence::Iterator(_)) => Self::Iterator, - Value::Sequence(Sequence::MaxHeap(_)) => Self::MaxHeap, - Value::Sequence(Sequence::MinHeap(_)) => Self::MinHeap, - Value::Sequence(Sequence::Deque(_)) => Self::Deque, - } - } -} - -impl From for ValueType { - fn from(value: Number) -> Self { - Self::Number((&value).into()) - } -} - -impl fmt::Display for ValueType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Option => write!(f, "Option"), - Self::Number(n) => write!(f, "{n}"), - Self::Bool => write!(f, "Bool"), - Self::String => write!(f, "String"), - Self::List => write!(f, "List"), - Self::Tuple(t) => { - write!(f, "tuple<{}>", t.iter().join(", ")) - } - Self::Function => write!(f, "Function"), - Self::Map => write!(f, "Map"), - Self::Iterator => write!(f, "Iterator"), - Self::MinHeap => write!(f, "MinHeap"), - Self::MaxHeap => write!(f, "MaxHeap"), - Self::Deque => write!(f, "Deque"), - } - } -} - impl fmt::Debug for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/ndc_lib/src/stdlib/aoc.rs b/ndc_lib/src/stdlib/aoc.rs index 394d3f70..2d0430ad 100644 --- a/ndc_lib/src/stdlib/aoc.rs +++ b/ndc_lib/src/stdlib/aoc.rs @@ -10,6 +10,7 @@ mod inner { use crate::interpreter::value::Value; /// Counts the occurrences of each item in a sequence and returns a map with the frequencies. + #[function(return_type = HashMap<_, _>)] pub fn frequencies(seq: &mut Sequence) -> Value { let mut out_map = HashMap::new(); diff --git a/ndc_lib/src/stdlib/cmp.rs b/ndc_lib/src/stdlib/cmp.rs index 32d7dd88..47feef7b 100644 --- a/ndc_lib/src/stdlib/cmp.rs +++ b/ndc_lib/src/stdlib/cmp.rs @@ -7,18 +7,18 @@ mod inner { use std::cmp::Ordering; /// Produces an error if the argument is not true. - pub fn assert(value: bool) -> anyhow::Result { + pub fn assert(value: bool) -> anyhow::Result<()> { if value { - Ok(Value::unit()) + Ok(()) } else { Err(anyhow!("failed asserting that argument is true")) } } /// Produces an error if the arguments aren't equal to each other. - pub fn assert_eq(left: &Value, right: &Value) -> anyhow::Result { + pub fn assert_eq(left: &Value, right: &Value) -> anyhow::Result<()> { if left == right { - Ok(Value::unit()) + Ok(()) } else { Err(anyhow!(format!( "failed asserting that {left} equals {right}" @@ -27,21 +27,21 @@ mod inner { } /// Produces an error if the arguments are equal to each other. - pub fn assert_ne(left: &Value, right: &Value) -> anyhow::Result { + pub fn assert_ne(left: &Value, right: &Value) -> anyhow::Result<()> { if left == right { Err(anyhow!(format!( "failed asserting that {left} does not equal {right}" ))) } else { - Ok(Value::unit()) + Ok(()) } } /// Produces the error specified by the `message` parameter if the `value` argument is not true. #[function(name = "assert")] - pub fn assert_with_message(value: bool, message: &str) -> anyhow::Result { + pub fn assert_with_message(value: bool, message: &str) -> anyhow::Result<()> { if value { - Ok(Value::unit()) + Ok(()) } else { Err(anyhow!(message.to_string())) } diff --git a/ndc_lib/src/stdlib/deque.rs b/ndc_lib/src/stdlib/deque.rs index d9d165f0..02afff20 100644 --- a/ndc_lib/src/stdlib/deque.rs +++ b/ndc_lib/src/stdlib/deque.rs @@ -11,7 +11,7 @@ mod inner { /// Creates a new `Deque` type. /// /// The `Deque` type allows the user to quickly append and remove elements from both the start and the end of the list. - #[function(name = "Deque")] + #[function(name = "Deque", return_type = VecDeque)] pub fn create_deque() -> Value { Value::Sequence(Sequence::Deque(Rc::new(RefCell::new(VecDeque::new())))) } @@ -34,7 +34,7 @@ mod inner { } /// Removes and returns the first element of the `deque` as an `Option` returning `None` if the queue is empty. - #[function(name = "pop_front?")] + #[function(name = "pop_front?", return_type = Option)] pub fn maybe_pop_front(deque: &mut VecDeque) -> Value { deque.pop_front().map_or_else(Value::none, Value::some) } @@ -47,7 +47,7 @@ mod inner { } /// Removes and returns the last element of the `deque` as an `Option` returning `None` if the queue is empty. - #[function(name = "pop_back?")] + #[function(name = "pop_back?", return_type = Option)] pub fn maybe_pop_back(deque: &mut VecDeque) -> Value { deque.pop_back().map_or_else(Value::none, Value::some) } @@ -61,7 +61,7 @@ mod inner { } /// Returns (but does not remove) the first element of the `deque` as an `Option` returning `None` if the queue is empty. - #[function(name = "front?")] + #[function(name = "front?", return_type = Option)] pub fn maybe_front(deque: &VecDeque) -> Value { deque.front().cloned().map_or_else(Value::none, Value::some) } @@ -75,7 +75,7 @@ mod inner { } /// Returns (but does not remove) the last element of the `deque` as an `Option` returning `None` if the queue is empty. - #[function(name = "back?")] + #[function(name = "back?", return_type = Option)] pub fn maybe_back(deque: &VecDeque) -> Value { deque.back().cloned().map_or_else(Value::none, Value::some) } @@ -91,8 +91,7 @@ mod inner { } /// Removes all elements from the `deque` - pub fn clear(deque: &mut VecDeque) -> Value { + pub fn clear(deque: &mut VecDeque) { deque.clear(); - Value::unit() } } diff --git a/ndc_lib/src/stdlib/file.rs b/ndc_lib/src/stdlib/file.rs index aca465d4..277b4bd7 100644 --- a/ndc_lib/src/stdlib/file.rs +++ b/ndc_lib/src/stdlib/file.rs @@ -1,5 +1,7 @@ use crate::interpreter::environment::Environment; -use crate::interpreter::function::{FunctionBody, FunctionBuilder, FunctionCarrier, TypeSignature}; +use crate::interpreter::function::{ + FunctionBody, FunctionBuilder, FunctionCarrier, StaticType, TypeSignature, +}; use crate::interpreter::value::Value; use ndc_macros::export_module; use std::fs::read_to_string; @@ -46,6 +48,7 @@ pub fn register_variadic(env: &mut Environment) { Ok(Value::unit()) }, type_signature: TypeSignature::Variadic, + return_type: StaticType::unit(), }) .build() .expect("function definition defined in code must be valid"), @@ -73,6 +76,7 @@ pub fn register_variadic(env: &mut Environment) { Ok(Value::unit()) }, type_signature: TypeSignature::Variadic, + return_type: StaticType::unit(), }) .build() .expect("function definition defined in code must be valid"), diff --git a/ndc_lib/src/stdlib/hash_map.rs b/ndc_lib/src/stdlib/hash_map.rs index c7afbdf8..4d448ad0 100644 --- a/ndc_lib/src/stdlib/hash_map.rs +++ b/ndc_lib/src/stdlib/hash_map.rs @@ -12,6 +12,7 @@ mod inner { /// Returns a list of all the keys in the map or set. /// /// Note that for a set this will return the values in the set. + #[function(return_type = Vec<_>)] pub fn keys(map: &mut HashMap) -> Value { Value::list(map.keys().cloned().collect::>()) } @@ -19,6 +20,7 @@ mod inner { /// Returns a list of all the values in the map. /// /// Note that for sets this will return a list of unit types, you should use keys if you want the values in the set. + #[function(return_type = Vec<_>)] pub fn values(map: &mut HashMap) -> Value { Value::list(map.values().cloned().collect::>()) } @@ -106,7 +108,7 @@ mod inner { /// Returns the union (elements that are in either `left` or `right`) of two maps or sets. /// /// This is the same as evaluating the expression `left | right` - #[function(alias = "|")] + #[function(alias = "|", return_type = DefaultMap<'_>)] pub fn union(left: DefaultMap<'_>, right: &HashMap) -> Value { Value::Sequence(Sequence::Map( Rc::new(RefCell::new(hash_map::union(left.0, right))), @@ -117,7 +119,7 @@ mod inner { /// Returns the intersection (elements that are in both `left and `right`) of two maps or sets. /// /// This is the same as evaluating the expression `left & right`. - #[function(alias = "&")] + #[function(alias = "&", return_type = DefaultMap<'_>)] pub fn intersection(left: DefaultMap<'_>, right: &HashMap) -> Value { Value::Sequence(Sequence::Map( Rc::new(RefCell::new(hash_map::intersection(left.0, right))), @@ -128,7 +130,7 @@ mod inner { /// Returns the symmetric difference (elements that are either in `left` or `right` but not both) of two maps or sets. /// /// This is the same as evaluating the expression `left ~ right`. - #[function(alias = "~")] + #[function(alias = "~", return_type = DefaultMap<'_>)] pub fn symmetric_difference(left: DefaultMap<'_>, right: &HashMap) -> Value { Value::Sequence(Sequence::Map( Rc::new(RefCell::new(hash_map::symmetric_difference(left.0, right))), @@ -137,6 +139,7 @@ mod inner { } /// Converts the given sequence to set. + #[function(return_type = DefaultMap<'_>)] pub fn set(seq: &mut Sequence) -> Value { let out: HashMap = match seq { Sequence::String(rc) => rc diff --git a/ndc_lib/src/stdlib/heap.rs b/ndc_lib/src/stdlib/heap.rs index 06c6b557..9c6a2d5e 100644 --- a/ndc_lib/src/stdlib/heap.rs +++ b/ndc_lib/src/stdlib/heap.rs @@ -8,22 +8,22 @@ mod inner { use std::cell::RefCell; use std::rc::Rc; - #[function(name = "MinHeap")] + #[function(name = "MinHeap", return_type = MinHeap<_>)] pub fn create_min_heap() -> Value { Value::Sequence(Sequence::MinHeap(Rc::new(RefCell::new(MinHeap::new())))) } - #[function(name = "MaxHeap")] + #[function(name = "MaxHeap", return_type = MaxHeap<_>)] pub fn create_max_heap() -> Value { Value::Sequence(Sequence::MaxHeap(Rc::new(RefCell::new(MaxHeap::new())))) } - #[function(name = "pop?")] + #[function(name = "pop?", return_type = Option)] pub fn maybe_min_pop(heap: &mut MinHeap) -> Value { heap.pop().map_or_else(Value::none, Value::some) } - #[function(name = "pop?")] + #[function(name = "pop?", return_type = Option)] pub fn maybe_max_pop(heap: &mut MaxHeap) -> Value { heap.pop().map_or_else(Value::none, Value::some) } diff --git a/ndc_lib/src/stdlib/list.rs b/ndc_lib/src/stdlib/list.rs index 0d2b2894..66fc5b74 100644 --- a/ndc_lib/src/stdlib/list.rs +++ b/ndc_lib/src/stdlib/list.rs @@ -1,7 +1,7 @@ #[ndc_macros::export_module] mod inner { use crate::interpreter::iterator::mut_seq_to_iterator; - use crate::interpreter::sequence::{ListRepr, Sequence, TupleRepr}; + use crate::interpreter::sequence::{ListRepr, Sequence}; use crate::interpreter::value::Value; use itertools::Itertools; use std::rc::Rc; @@ -9,6 +9,7 @@ mod inner { use anyhow::anyhow; /// Converts any sequence into a list + #[function(return_type = Vec)] pub fn list(seq: &mut Sequence) -> Value { Value::list(mut_seq_to_iterator(seq).collect::>()) } @@ -34,12 +35,12 @@ mod inner { } } - pub fn insert(list: &mut Vec, index: usize, elem: Value) -> anyhow::Result { + pub fn insert(list: &mut Vec, index: usize, elem: Value) -> anyhow::Result<()> { if index > list.len() { return Err(anyhow!("index {index} is out of bounds")); } list.insert(index, elem); - Ok(Value::unit()) + Ok(()) } /// Removes and returns the element at position `index` within the list, shifting all elements after it to the left. @@ -66,21 +67,22 @@ mod inner { list.append(other); } - #[function(name = "++")] - pub fn tup_concat(left: TupleRepr, mut right: TupleRepr) -> Value { - match Rc::try_unwrap(left) { - Ok(mut left) => { - left.append(Rc::make_mut(&mut right)); - Value::tuple(left) - } - Err(left) => Value::tuple( - left.iter() - .chain(right.iter()) - .cloned() - .collect::>(), - ), - } - } + // A price we have to pay for the type system + // #[function(name = "++")] + // pub fn tup_concat(left: TupleRepr, mut right: TupleRepr) -> Value { + // match Rc::try_unwrap(left) { + // Ok(mut left) => { + // left.append(Rc::make_mut(&mut right)); + // Value::tuple(left) + // } + // Err(left) => Value::tuple( + // left.iter() + // .chain(right.iter()) + // .cloned() + // .collect::>(), + // ), + // } + // } #[function(name = "++")] pub fn list_concat(left: &mut ListRepr, right: &mut ListRepr) -> Value { @@ -142,7 +144,7 @@ mod inner { list.pop().unwrap_or(Value::unit()) } - #[function(name = "pop_left?")] + #[function(name = "pop_left?", return_type = Option)] pub fn maybe_pop_left(list: &mut Vec) -> Value { if list.is_empty() { return Value::none(); @@ -160,7 +162,7 @@ mod inner { } /// Creates a copy of the list with its elements in reverse order - /// TODO: how to deal with tuples? + #[function(return_type = Vec)] pub fn reversed(list: &[Value]) -> Value { Value::list(list.iter().rev().cloned().collect::>()) } @@ -216,6 +218,7 @@ mod inner { list.last().cloned().map_or_else(Value::none, Value::some) } + #[function(return_type = Vec<(Value, Value)>)] pub fn cartesian_product(list_a: &[Value], list_b: &[Value]) -> Value { Value::list( list_a diff --git a/ndc_lib/src/stdlib/math.rs b/ndc_lib/src/stdlib/math.rs index c7973b73..40f028cf 100644 --- a/ndc_lib/src/stdlib/math.rs +++ b/ndc_lib/src/stdlib/math.rs @@ -21,7 +21,7 @@ where Value::Number(n) => acc.add(n), value => Err(BinaryOperatorError::new(format!( "cannot sum {} and number", - value.value_type() + value.static_type() ))), }) } @@ -41,7 +41,7 @@ where Value::Number(n) => acc.mul(n), value => Err(BinaryOperatorError::new(format!( "cannot multiply {} and number", - value.value_type() + value.static_type() ))), }) } @@ -172,7 +172,7 @@ mod inner { } value => Err(anyhow::anyhow!( "cannot convert {} to float", - value.value_type() + value.static_type() )), } } @@ -201,7 +201,7 @@ mod inner { } value => Err(anyhow::anyhow!( "cannot convert {} to int", - value.value_type() + value.static_type() )), } } @@ -210,7 +210,7 @@ mod inner { pub mod f64 { use super::{Environment, Number, ToPrimitive, f64}; use crate::interpreter::function::{ - FunctionBody, FunctionBuilder, FunctionCarrier, ParamType, Parameter, TypeSignature, + FunctionBody, FunctionBuilder, FunctionCarrier, Parameter, StaticType, TypeSignature, }; use crate::interpreter::num::BinaryOperatorError; use crate::interpreter::value::Value; @@ -256,17 +256,18 @@ pub mod f64 { .name($operator.to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Any), - Parameter::new("right", ParamType::Any), + Parameter::new("left", StaticType::Any), + Parameter::new("right", StaticType::Any), ]), function: |values, _env| match values { [left, right] => match left.partial_cmp(&right) { Some($expected) => Ok(Value::Bool(true)), Some(_) => Ok(Value::Bool(false)), - None => Err(anyhow::anyhow!("cannot compare {} and {}",left.value_type(),right.value_type()).into()), + None => Err(anyhow::anyhow!("cannot compare {} and {}",left.static_type(),right.static_type()).into()), }, _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Bool, }) .build() .expect("must succeed") @@ -284,13 +285,14 @@ pub mod f64 { .name("==".to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Any), - Parameter::new("right", ParamType::Any), + Parameter::new("left", StaticType::Any), + Parameter::new("right", StaticType::Any), ]), function: |values, _env| match values { [left, right] => Ok(Value::Bool(left == right)), _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Bool, }) .build() .expect("must succeed") @@ -301,13 +303,14 @@ pub mod f64 { .name("!=".to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Any), - Parameter::new("right", ParamType::Any), + Parameter::new("left", StaticType::Any), + Parameter::new("right", StaticType::Any), ]), function: |values, _env| match values { [left, right] => Ok(Value::Bool(left != right)), _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Bool, }) .build() .expect("must succeed") @@ -318,18 +321,19 @@ pub mod f64 { .name("<=>".to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Any), - Parameter::new("right", ParamType::Any), + Parameter::new("left", StaticType::Any), + Parameter::new("right", StaticType::Any), ]), function: |values, _env| match values { [left, right] => match left.partial_cmp(&right) { Some(Ordering::Equal) => Ok(Value::from(0)), Some(Ordering::Less) => Ok(Value::from(-1)), Some(Ordering::Greater) => Ok(Value::from(1)), - None => Err(anyhow::anyhow!("cannot compare {} and {}",left.value_type(),right.value_type()).into()), + None => Err(anyhow::anyhow!("cannot compare {} and {}",left.static_type(),right.static_type()).into()), }, _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Int, }) .build() .expect("must succeed") @@ -340,18 +344,19 @@ pub mod f64 { .name(">=<".to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Any), - Parameter::new("right", ParamType::Any), + Parameter::new("left", StaticType::Any), + Parameter::new("right", StaticType::Any), ]), function: |values, _env| match values { [left, right] => match left.partial_cmp(&right) { Some(Ordering::Equal) => Ok(Value::from(0)), Some(Ordering::Less) => Ok(Value::from(1)), Some(Ordering::Greater) => Ok(Value::from(-1)), - None => Err(anyhow::anyhow!("cannot compare {} and {}",left.value_type(),right.value_type()).into()), + None => Err(anyhow::anyhow!("cannot compare {} and {}",left.static_type(),right.static_type()).into()), }, _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Int, }) .build() .expect("must succeed") @@ -364,13 +369,14 @@ pub mod f64 { .name($operator.to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Bool), - Parameter::new("right", ParamType::Bool), + Parameter::new("left", StaticType::Bool), + Parameter::new("right", StaticType::Bool), ]), function: |values, _env| match values { [Value::Bool(left), Value::Bool(right)] => Ok(Value::Bool($operation(*left, *right))), _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Bool, }) .build() .expect("must succeed") @@ -380,14 +386,15 @@ pub mod f64 { .name($operator.to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Int), - Parameter::new("right", ParamType::Int), + Parameter::new("left", StaticType::Int), + Parameter::new("right", StaticType::Int), ]), function: |values, _env| match values { // TODO: remove this clone [Value::Number(Number::Int(left)), Value::Number(Number::Int(right))] => Ok(Value::Number(Number::Int($operation(left.clone(), right.clone())))), _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Int, }) .build() .expect("must succeed"), @@ -411,11 +418,12 @@ pub mod f64 { env.declare_global_fn( FunctionBuilder::default() .body(FunctionBody::GenericFunction { - type_signature: TypeSignature::Exact(vec![Parameter::new("value", ParamType::Bool)]), + type_signature: TypeSignature::Exact(vec![Parameter::new("value", StaticType::Bool)]), function: |values, _env| match values { [Value::Bool(b)] => Ok(Value::Bool(b.not())), _ => unreachable!("the type checker should never invoke this function if the argument count does not match"), }, + return_type: StaticType::Bool, }) .name(ident.to_string()) .build() @@ -428,8 +436,8 @@ pub mod f64 { .name(">>".to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Int), - Parameter::new("right", ParamType::Int), + Parameter::new("left", StaticType::Int), + Parameter::new("right", StaticType::Int), ]), function: |values, _env| match values { [Value::Number(Number::Int(left)), Value::Number(Number::Int(right))] => left.clone().checked_shr(right.clone()) @@ -437,6 +445,7 @@ pub mod f64 { .map(|x| Value::Number(Number::Int(x))), _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Int, }) .build() .expect("must succeed") @@ -447,8 +456,8 @@ pub mod f64 { .name("<<".to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Int), - Parameter::new("right", ParamType::Int), + Parameter::new("left", StaticType::Int), + Parameter::new("right", StaticType::Int), ]), function: |values, _env| match values { [Value::Number(Number::Int(left)), Value::Number(Number::Int(right))] => left.clone().checked_shl(right.clone()) @@ -456,6 +465,7 @@ pub mod f64 { .map(|x| Value::Number(Number::Int(x))), _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Int, }) .build() .expect("must succeed") diff --git a/ndc_lib/src/stdlib/rand.rs b/ndc_lib/src/stdlib/rand.rs index 6b7506c5..a88f9420 100644 --- a/ndc_lib/src/stdlib/rand.rs +++ b/ndc_lib/src/stdlib/rand.rs @@ -33,6 +33,7 @@ mod inner { /// Returns a copy of the input sequence converted to a list with the elements shuffled in random order. /// /// Note: this currently does consume iterators + #[function(return_type = Vec)] pub fn shuffled(list: &mut Sequence) -> Value { Value::list( mut_seq_to_iterator(list) diff --git a/ndc_lib/src/stdlib/regex.rs b/ndc_lib/src/stdlib/regex.rs index ad161574..5ea2aeee 100644 --- a/ndc_lib/src/stdlib/regex.rs +++ b/ndc_lib/src/stdlib/regex.rs @@ -6,6 +6,7 @@ use regex::Regex; mod inner { /// Extracts all signed integers from the given string. + #[function(return_type = Vec)] pub fn nums(haystack: &str) -> Value { static RE: Lazy = Lazy::new(|| Regex::new(r"-?\d+").unwrap()); @@ -16,6 +17,7 @@ mod inner { } /// Extracts all unsigned integers from the given string. + #[function(return_type = Vec)] pub fn unsigned_nums(haystack: &str) -> Value { static RE: Lazy = Lazy::new(|| Regex::new(r"\d+").unwrap()); @@ -32,6 +34,7 @@ mod inner { } /// Returns all capture groups from the first match of the regular expression. + #[function(return_type = Vec)] pub fn captures(haystack: &str, regex: &str) -> Result { let r = Regex::new(regex)?; @@ -51,6 +54,7 @@ mod inner { } /// Returns the first capture group from the first match of the regular expression. + #[function(return_type = Vec)] pub fn capture_once(haystack: &str, regex: &str) -> Result { let r = Regex::new(regex)?; diff --git a/ndc_lib/src/stdlib/sequence.rs b/ndc_lib/src/stdlib/sequence.rs index b8db0733..2ce692a2 100644 --- a/ndc_lib/src/stdlib/sequence.rs +++ b/ndc_lib/src/stdlib/sequence.rs @@ -139,24 +139,26 @@ mod inner { /// Sorts the input sequence in place. /// /// This function only works for strings and lists and will throw errors otherwise. - pub fn sort(seq: &mut Sequence) -> anyhow::Result { + pub fn sort(seq: &mut Sequence) -> anyhow::Result<()> { match seq { Sequence::String(str) => { let r = &mut *str.borrow_mut(); *r = r.chars().sorted().collect::(); - Ok(Value::unit()) } Sequence::List(list) => { let mut m = list.borrow_mut(); try_sort_by(&mut m, Value::try_cmp)?; - Ok(Value::unit()) } - Sequence::Tuple(_) => Err(anyhow!("tuple cannot be sorted in place")), - Sequence::Map(_, _) => Err(anyhow!("map cannot be sorted in place")), - Sequence::Iterator(_) => Err(anyhow!("iterator cannot be sorted in place")), - Sequence::MaxHeap(_) | Sequence::MinHeap(_) => Err(anyhow!("heap is already sorted")), - Sequence::Deque(_) => Err(anyhow!("deque cannot be sorted in place")), + Sequence::Tuple(_) => return Err(anyhow!("tuple cannot be sorted in place")), + Sequence::Map(_, _) => return Err(anyhow!("map cannot be sorted in place")), + Sequence::Iterator(_) => return Err(anyhow!("iterator cannot be sorted in place")), + Sequence::MaxHeap(_) | Sequence::MinHeap(_) => { + return Err(anyhow!("heap is already sorted")); + } + Sequence::Deque(_) => return Err(anyhow!("deque cannot be sorted in place")), } + + Ok(()) } /// Sorts the given sequence using a comparing function in place. @@ -167,6 +169,7 @@ mod inner { /// - for values equal to `0` the first argument is equal to the second argument /// /// This function only works for strings and lists and will throw errors otherwise. + #[function(return_type = ())] pub fn sort_by(list: &mut Vec, comp: &Callable<'_>) -> EvaluationResult { try_sort_by::(list, |left, right| { let ret = comp.call(&mut [left.clone(), right.clone()])?; @@ -177,6 +180,7 @@ mod inner { } /// Returns a sorted copy of the input sequence as a list. + #[function(return_type = Vec)] pub fn sorted(seq: &mut Sequence) -> anyhow::Result { let mut list = mut_seq_to_iterator(seq).collect::>(); try_sort_by(&mut list, Value::try_cmp)?; @@ -189,6 +193,7 @@ mod inner { /// - for values lower than `0` the first argument is smaller than the second argument /// - for values higher than `0` the first argument is greater than the second argument /// - for values equal to `0` the first argument is equal to the second argument + #[function(return_type = Vec)] pub fn sorted_by(seq: &mut Sequence, comp: &Callable<'_>) -> EvaluationResult { let mut list = mut_seq_to_iterator(seq).collect::>(); try_sort_by::(&mut list, |left, right| { @@ -210,12 +215,14 @@ mod inner { Some(n) => Ok(n), None => Err(anyhow!( "cannot determine the length of {}", - seq.value_type() + seq.static_type() )), } } /// Enumerates the given sequence returning a list of tuples where the first element of the tuple is the index of the element in the input sequence. + + #[function(return_type = Vec<(i64, Value)>)] pub fn enumerate(seq: &mut Sequence) -> Value { match seq { Sequence::String(s) => Value::list( @@ -263,11 +270,13 @@ mod inner { } /// Reduces/folds the given sequence using the given combining function and a custom initial value. + #[function(return_type = Vec<_>)] pub fn fold(seq: &mut Sequence, initial: Value, function: &Callable<'_>) -> EvaluationResult { fold_iterator(mut_seq_to_iterator(seq), initial, function) } /// Reduces/folds the given sequence using the given combining function. + #[function(return_type = Value)] pub fn reduce(seq: &mut Sequence, function: &Callable<'_>) -> EvaluationResult { let mut iterator = mut_seq_to_iterator(seq); let fst = iterator @@ -278,6 +287,7 @@ mod inner { } /// Filters the given sequence using the `predicate`. + #[function(return_type = Vec<_>)] pub fn filter(seq: &mut Sequence, predicate: &Callable<'_>) -> EvaluationResult { let iterator = mut_seq_to_iterator(seq); let mut out = Vec::new(); @@ -298,6 +308,7 @@ mod inner { } /// Returns the number of elements in the input sequence for which the given `predicate` returns `true`. + #[function(return_type = i64)] pub fn count(seq: &mut Sequence, predicate: &Callable<'_>) -> EvaluationResult { let iterator = mut_seq_to_iterator(seq); let mut out = 0; @@ -316,6 +327,7 @@ mod inner { } /// Returns the value of the first element for which the `predicate` is true for the given input sequence. + #[function(return_type = Value)] pub fn find(seq: &mut Sequence, predicate: &Callable<'_>) -> EvaluationResult { let iterator = mut_seq_to_iterator(seq); for element in iterator { @@ -331,6 +343,7 @@ mod inner { } /// Returns the first index of the element for which the `predicate` is true in the input sequence. + #[function(return_type = usize)] pub fn locate(seq: &mut Sequence, predicate: &Callable<'_>) -> EvaluationResult { let iterator = mut_seq_to_iterator(seq); for (idx, element) in iterator.enumerate() { @@ -346,7 +359,7 @@ mod inner { } /// Returns the first index of the element or produces an error - #[function(name = "locate")] + #[function(name = "locate", return_type = usize)] pub fn locate_element(seq: &mut Sequence, element: &Value) -> EvaluationResult { let iterator = mut_seq_to_iterator(seq); for (idx, el) in iterator.enumerate() { @@ -359,6 +372,7 @@ mod inner { } /// Returns `true` if the `predicate` is true for none of the elements in `seq`. + #[function(return_type = bool)] pub fn none(seq: &mut Sequence, function: &Callable<'_>) -> EvaluationResult { for item in mut_seq_to_iterator(seq) { match function.call(&mut [item])? { @@ -367,7 +381,7 @@ mod inner { v => { return Err(anyhow!(format!( "invalid return type, predicate returned {}", - v.value_type() + v.static_type() )) .into()); } @@ -377,6 +391,7 @@ mod inner { Ok(Value::Bool(true)) } /// Returns `true` if the `predicate` is true for all the elements in `seq`. + #[function(return_type = bool)] pub fn all(seq: &mut Sequence, function: &Callable<'_>) -> EvaluationResult { for item in mut_seq_to_iterator(seq) { match function.call(&mut [item])? { @@ -385,7 +400,7 @@ mod inner { v => { return Err(anyhow!(format!( "invalid return type, predicate returned {}", - v.value_type() + v.static_type() )) .into()); } @@ -396,6 +411,7 @@ mod inner { } /// Returns `true` if the `predicate` is true for any of the elements in `seq`. + #[function(return_type = bool)] pub fn any(seq: &mut Sequence, predicate: &Callable<'_>) -> EvaluationResult { for item in mut_seq_to_iterator(seq) { match predicate.call(&mut [item])? { @@ -404,7 +420,7 @@ mod inner { v => { return Err(anyhow!(format!( "invalid return type, predicate returned {}", - v.value_type() + v.static_type() )) .into()); } @@ -415,6 +431,7 @@ mod inner { } /// Applies the function to each element in a sequence returning the result as a list. + #[function(return_type = Vec<_>)] pub fn map(seq: &mut Sequence, function: &Callable<'_>) -> EvaluationResult { let iterator = mut_seq_to_iterator(seq); let mut out = Vec::new(); @@ -427,6 +444,7 @@ mod inner { } /// Applies a function to each item in a sequence, flattens the resulting sequences, and returns a single combined sequence. + #[function(return_type = Vec<_>)] pub fn flat_map(seq: &mut Sequence, function: &Callable<'_>) -> EvaluationResult { // let iterator = ; let mut out = Vec::new(); @@ -469,6 +487,7 @@ mod inner { } /// Returns the `k` sized combinations of the given sequence `seq` as a list of tuples. + #[function(return_type = Vec<_>)] pub fn combinations(seq: &mut Sequence, k: usize) -> Value { Value::list( mut_seq_to_iterator(seq) @@ -479,6 +498,7 @@ mod inner { } /// Returns the `k` sized permutations of the given sequence `seq` as a list of tuples. + #[function(return_type = Vec<_>)] pub fn permutations(seq: &mut Sequence, k: usize) -> Value { Value::list( mut_seq_to_iterator(seq) @@ -489,6 +509,7 @@ mod inner { } /// Returns al prefixes of a sequence, each as a list. + #[function(return_type = Vec<_>)] pub fn prefixes(seq: &mut Sequence) -> Value { // Special case for string which is more efficient and doesn't produce lists of characters if let Sequence::String(string) = &seq { @@ -518,6 +539,7 @@ mod inner { } /// Returns all suffixes of a sequence, each as a list; for strings, returns all trailing substrings. + #[function(return_type = Vec<_>)] pub fn suffixes(seq: &mut Sequence) -> Value { // Special case for string which is more efficient and doesn't produce lists of characters if let Sequence::String(string) = &seq { @@ -543,6 +565,7 @@ mod inner { /// Transposes a sequence of sequences, turning rows into columns, and returns the result as a list of lists. // TODO: right now transposed always produces a list, it probably should produce whatever the input type was (if possible) // TODO: this might not be the expected result for sets (since iterators over sets yield tuples) + #[function(return_type = Vec<_>)] pub fn transposed(seq: &mut Sequence) -> EvaluationResult { let mut main = mut_seq_to_iterator(seq).collect::>(); let mut iterators = Vec::new(); @@ -563,6 +586,7 @@ mod inner { } /// Return a list of all windows, wrapping back to the first elements when the window would otherwise exceed the length of source list, producing tuples of size 2. + #[function(return_type = Vec<(Value, Value)>)] pub fn circular_tuple_windows(seq: &mut Sequence) -> Value { // TODO: this implementation probably clones a bit more than it needs to, but it's better tol // have something than nothing @@ -577,6 +601,7 @@ mod inner { } /// Returns a list of all size-2 windows in `seq`. + #[function(return_type = Vec<(Value, Value)>)] pub fn pairwise(seq: &mut Sequence) -> Value { Value::list( mut_seq_to_iterator(seq) @@ -589,6 +614,7 @@ mod inner { /// Applies a function to each pair of consecutive elements in a sequence and returns the results as a list. #[function(name = "pairwise")] + #[function(return_type = Vec<(Value, Value)>)] pub fn pairwise_map(seq: &mut Sequence, function: &Callable<'_>) -> EvaluationResult { let main = mut_seq_to_iterator(seq).collect::>(); @@ -601,6 +627,7 @@ mod inner { } /// Returns a list of all contiguous windows of `length` size. The windows overlap. If the `seq` is shorter than size, the iterator returns no values. + #[function(return_type = Vec>)] pub fn windows(seq: &mut Sequence, length: usize) -> Value { Value::list( mut_seq_to_iterator(seq) @@ -615,6 +642,7 @@ mod inner { /// /// The powerset of a set contains all subsets including the empty set and the full input set. A powerset has length `2^n` where `n` is the length of the input set. /// Each list produced by this function represents a subset of the elements in the source sequence. + #[function(return_type = Vec>)] pub fn subsequences(seq: &mut Sequence) -> Value { Value::list( mut_seq_to_iterator(seq) @@ -626,6 +654,7 @@ mod inner { /// Return a list that represents the powerset of the elements of `seq` that are exactly `length` long. #[function(name = "subsequences")] + #[function(return_type = Vec>)] pub fn subsequences_len(seq: &mut Sequence, length: usize) -> Value { Value::list( mut_seq_to_iterator(seq) @@ -662,6 +691,7 @@ mod inner { /// [3,"c",false] /// ] /// ``` + #[function(return_type = Vec>)] pub fn multi_cartesian_product(seq: &mut Sequence) -> anyhow::Result { let mut iterators = Vec::new(); @@ -681,6 +711,7 @@ mod inner { /// Split the input sequence into evenly sized chunks. If the input length of the sequence /// is not dividable by the chunk_size the last chunk will contain fewer elements. + #[function(return_type = Vec>)] pub fn chunks(seq: &mut Sequence, chunk_size: usize) -> anyhow::Result { if chunk_size == 0 { return Err(anyhow!("chunk size must be non-zero")); @@ -696,6 +727,7 @@ mod inner { )) } + #[function(return_type = Iterator)] pub fn repeat(value: Value) -> Value { Value::Sequence(Sequence::Iterator(Rc::new(RefCell::new( ValueIterator::Repeat(Repeat { @@ -706,7 +738,7 @@ mod inner { )))) } - #[function(name = "repeat")] + #[function(name = "repeat", return_type = Iterator)] pub fn repeat_times(value: Value, times: usize) -> Value { Value::Sequence(Sequence::Iterator(Rc::new(RefCell::new( ValueIterator::Repeat(Repeat { @@ -735,7 +767,7 @@ pub mod extra { use anyhow::anyhow; use itertools::izip; - use crate::interpreter::function::FunctionBuilder; + use crate::interpreter::function::{FunctionBuilder, StaticType}; use crate::interpreter::{ environment::Environment, function::FunctionBody, iterator::mut_value_to_iterator, value::Value, @@ -748,6 +780,7 @@ pub mod extra { .documentation("Combines multiple sequences (or iterables) into a single sequence of tuples, where the ith tuple contains the ith element from each input sequence.\n\nIf the input sequences are of different lengths, the resulting sequence is truncated to the length of the shortest input.".to_string()) .body(FunctionBody::generic( crate::interpreter::function::TypeSignature::Variadic, + StaticType::List(Box::new(StaticType::Tuple(vec![StaticType::Any, StaticType::Any]))), |args, _env| match args { [_] => { Err(anyhow!("zip must be called with 2 or more arguments").into()) diff --git a/ndc_lib/src/stdlib/serde.rs b/ndc_lib/src/stdlib/serde.rs index 60903e27..1e318f36 100644 --- a/ndc_lib/src/stdlib/serde.rs +++ b/ndc_lib/src/stdlib/serde.rs @@ -134,9 +134,9 @@ mod inner { serde_json::from_str::(input)?.try_into() } - /// Converts the input value to json - pub fn json_encode(input: Value) -> anyhow::Result { + /// Converts the input value to JSON + pub fn json_encode(input: Value) -> anyhow::Result { let v: JsonValue = input.try_into()?; - Ok(Value::string(v.to_string())) + Ok(v.to_string()) } } diff --git a/ndc_lib/src/stdlib/string.rs b/ndc_lib/src/stdlib/string.rs index 0f2f0b06..9c3b4d3f 100644 --- a/ndc_lib/src/stdlib/string.rs +++ b/ndc_lib/src/stdlib/string.rs @@ -112,6 +112,7 @@ mod inner { } /// Splits the string into paragraphs, using blank lines as separators. + #[function(return_type = Vec)] pub fn paragraphs(string: &str) -> Value { Value::collect_list(string.split("\n\n").map(ToString::to_string)) } @@ -122,6 +123,7 @@ mod inner { } /// Splits the string into lines, using newline characters as separators. + #[function(return_type = Vec)] pub fn lines(string: &str) -> Value { Value::collect_list(string.lines().map(ToString::to_string)) } @@ -132,6 +134,7 @@ mod inner { } /// Splits the string into words, using whitespace as the separator. + #[function(return_type = Vec)] pub fn words(string: &str) -> Value { Value::collect_list(string.split_whitespace().map(ToString::to_string)) } @@ -142,6 +145,7 @@ mod inner { } /// Splits the string by whitespace into a list of substrings. + #[function(return_type = Vec)] pub fn split(string: &str) -> Value { Value::collect_list(string.split_whitespace().map(ToString::to_string)) } @@ -157,7 +161,7 @@ mod inner { } /// Splits the string using a given pattern as the delimiter. - #[function(name = "split")] + #[function(name = "split", return_type = Vec)] pub fn split_with_pattern(string: &str, pattern: &str) -> Value { Value::list( string @@ -169,6 +173,7 @@ mod inner { } /// Splits the string at the first occurrence of `pattern`, returning a tuple-like value. + #[function(return_type = (String, String))] pub fn split_once(string: &str, pattern: &str) -> Value { match string.split_once(pattern) { Some((fst, snd)) => Value::tuple(vec![Value::string(fst), Value::string(snd)]), diff --git a/ndc_lib/src/stdlib/value.rs b/ndc_lib/src/stdlib/value.rs index 80e5dd51..b8f8c80a 100644 --- a/ndc_lib/src/stdlib/value.rs +++ b/ndc_lib/src/stdlib/value.rs @@ -16,18 +16,19 @@ mod inner { pub fn docs(func: &Callable<'_>) -> anyhow::Result { let mut buf = String::new(); - for (sig, fun) in func.function.borrow().implementations() { - if fun.name().is_empty() { - write!(buf, "fn({sig})")?; - } else { - write!(buf, "fn {}({sig})", fun.name())?; - } + let sig = func.function.type_signature(); + let fun = &func.function; - if !fun.short_documentation().is_empty() { - writeln!(buf, " -> {}", fun.short_documentation())?; - } else { - writeln!(buf)?; - } + if fun.name().is_empty() { + write!(buf, "fn({sig})")?; + } else { + write!(buf, "fn {}({sig})", fun.name())?; + } + + if !fun.short_documentation().is_empty() { + writeln!(buf, " -> {}", fun.short_documentation())?; + } else { + writeln!(buf)?; } buf.pop(); // Remove last newline @@ -56,29 +57,31 @@ mod inner { } /// Creates a new instance of `Some` - #[function(name = "Some")] // <-- fake type constructor + #[function(name = "Some", return_type = Option)] // <-- fake type constructor pub fn some(value: Value) -> Value { Value::Option(Some(Box::new(value))) } /// Creates a new instance of `None` + #[function(return_type = Option<_>)] pub fn none() -> Value { Value::Option(None) } /// Returns true if the argument is Some - pub fn is_some(value: &Value) -> Value { - Value::Bool(matches!(value, Value::Option(Some(_)))) + pub fn is_some(value: &Value) -> bool { + matches!(value, Value::Option(Some(_))) } /// Returns true if the argument is None - pub fn is_none(value: &Value) -> Value { - Value::Bool(matches!(value, Value::Option(None))) + pub fn is_none(value: &Value) -> bool { + matches!(value, Value::Option(None)) } /// Extracts the value from an Option or errors if it's either None or a non-Option type /// /// Note: this function should take an Option as parameter + // TODO: the type of value should be `Option` but the macro crate probably doesn't support that yet pub fn unwrap(value: Value) -> anyhow::Result { match value { Value::Option(Some(val)) => Ok(*val), @@ -116,7 +119,8 @@ mod inner { Value::Sequence(Sequence::Deque(deque)) => Value::Sequence(Sequence::Deque(Rc::new( RefCell::new(deque.borrow().to_owned()), ))), - Value::Function(f) => Value::from(f.borrow().to_owned()), + // TODO: for function should deepcopy have some special behavior + Value::Function(f) => Value::Function(f.clone()), } } diff --git a/ndc_lsp/src/backend.rs b/ndc_lsp/src/backend.rs index 677bdb93..08bf3e50 100644 --- a/ndc_lsp/src/backend.rs +++ b/ndc_lsp/src/backend.rs @@ -115,30 +115,24 @@ impl LanguageServer for Backend { let env = interpreter.environment(); let functions = env.borrow().get_all_functions(); - let items = functions.iter().flat_map(|f| { - f.implementations().filter_map(|(sig, fun)| { - // Ignore operators - if fun - .name() - .chars() - .all(|c| c.is_alphanumeric() || c == '?' || c == '_') - { - Some(CompletionItem { - label: fun.name().to_string(), - label_details: Some(CompletionItemLabelDetails { - detail: Some(format!("({sig})")), - description: None, - }), - kind: Some(CompletionItemKind::FUNCTION), - documentation: Some(Documentation::MarkupContent(MarkupContent { - kind: MarkupKind::Markdown, - value: fun.documentation().to_string(), - })), - ..Default::default() - }) - } else { - None - } + let items = functions.iter().filter_map(|fun| { + // Ignore operators + if !is_normal_ident(fun.name()) { + return None; + } + + Some(CompletionItem { + label: fun.name().to_string(), + label_details: Some(CompletionItemLabelDetails { + detail: Some(format!("({})", fun.type_signature())), + description: Some(fun.return_type().to_string()), + }), + kind: Some(CompletionItemKind::FUNCTION), + documentation: Some(Documentation::MarkupContent(MarkupContent { + kind: MarkupKind::Markdown, + value: fun.documentation().to_string(), + })), + ..Default::default() }) }); @@ -220,3 +214,9 @@ fn position_from_offset(text: &str, offset: usize) -> Position { character: col, } } + +fn is_normal_ident(input: &str) -> bool { + input + .chars() + .all(|c| c.is_alphanumeric() || c == '?' || c == '_') +} diff --git a/ndc_macros/src/convert.rs b/ndc_macros/src/convert.rs index c557530a..769f76e7 100644 --- a/ndc_macros/src/convert.rs +++ b/ndc_macros/src/convert.rs @@ -1,3 +1,4 @@ +use crate::function::temp_create_map_any; use crate::r#match::{is_ref_mut, is_string, path_ends_with}; use proc_macro2::TokenStream; use quote::quote; @@ -12,6 +13,7 @@ pub struct Argument { } pub trait TypeConverter { fn matches(&self, ty: &syn::Type) -> bool; + fn static_type(&self) -> TokenStream; fn convert( &self, temp_var: syn::Ident, @@ -26,6 +28,10 @@ impl TypeConverter for MutRefString { is_ref_mut(ty) && is_string(ty) } + fn static_type(&self) -> TokenStream { + quote! { crate::interpreter::function::StaticType::String } + } + fn convert( &self, temp_var: syn::Ident, @@ -33,7 +39,7 @@ impl TypeConverter for MutRefString { argument_var_name: syn::Ident, ) -> Vec { vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::String }, + param_type: self.static_type(), param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -53,6 +59,10 @@ impl TypeConverter for InternalMap { path_ends_with(ty, "MapRepr") } + fn static_type(&self) -> TokenStream { + temp_create_map_any() + } + fn convert( &self, temp_var: syn::Ident, @@ -60,7 +70,7 @@ impl TypeConverter for InternalMap { argument_var_name: syn::Ident, ) -> Vec { vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Map }, + param_type: self.static_type(), param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -79,6 +89,10 @@ impl TypeConverter for InternalString { path_ends_with(ty, "StringRepr") } + fn static_type(&self) -> TokenStream { + quote! { crate::interpreter::function::StaticType::String } + } + fn convert( &self, temp_var: syn::Ident, @@ -86,7 +100,7 @@ impl TypeConverter for InternalString { argument_var_name: syn::Ident, ) -> Vec { vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::String }, + param_type: self.static_type(), param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -106,6 +120,15 @@ impl TypeConverter for InternalList { path_ends_with(ty, "ListRepr") } + fn static_type(&self) -> TokenStream { + // TODO: just hardcoding Any here is lazy + quote! { + crate::interpreter::function::StaticType::List(Box::new( + crate::interpreter::function::StaticType::Any + )) + } + } + fn convert( &self, temp_var: syn::Ident, @@ -113,7 +136,7 @@ impl TypeConverter for InternalList { argument_var_name: syn::Ident, ) -> Vec { vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::List }, + param_type: self.static_type(), param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -127,40 +150,41 @@ impl TypeConverter for InternalList { } } -/// Matches `Rc>>` -struct InternalTuple; -impl TypeConverter for InternalTuple { - fn matches(&self, ty: &syn::Type) -> bool { - path_ends_with(ty, "TupleRepr") - } - - fn convert( - &self, - temp_var: syn::Ident, - original_name: &str, - argument_var_name: syn::Ident, - ) -> Vec { - vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Tuple }, - param_name: quote! { #original_name }, - argument: quote! { #argument_var_name }, - initialize_code: quote! { - let crate::interpreter::value::Value::Sequence(crate::interpreter::sequence::Sequence::Tuple(#temp_var)) = #argument_var_name else { - panic!("Value #position needed to be Sequence::Tuple but wasn't"); - }; - - // TODO: is std::mem::take appropriate here? - let #argument_var_name = std::mem::take(#temp_var); - }, - }] - } -} +// Losing tuple concatenation is a price we might have to pay +// /// Matches `Rc>>` +// struct InternalTuple; +// impl TypeConverter for InternalTuple { +// fn matches(&self, ty: &syn::Type) -> bool { +// path_ends_with(ty, "TupleRepr") +// } +// +// fn convert( +// &self, +// temp_var: syn::Ident, +// original_name: &str, +// argument_var_name: syn::Ident, +// ) -> Vec { +// vec![Argument { +// param_type: quote! { crate::interpreter::function::StaticType::Tuple }, +// param_name: quote! { #original_name }, +// argument: quote! { #argument_var_name }, +// initialize_code: quote! { +// let crate::interpreter::value::Value::Sequence(crate::interpreter::sequence::Sequence::Tuple(#temp_var)) = #argument_var_name else { +// panic!("Value #position needed to be Sequence::Tuple but wasn't"); +// }; +// +// // TODO: is std::mem::take appropriate here? +// let #argument_var_name = std::mem::take(#temp_var); +// }, +// }] +// } +// } pub fn build() -> Vec> { vec![ Box::new(InternalList), Box::new(MutRefString), - Box::new(InternalTuple), + // Box::new(InternalTuple), Box::new(InternalMap), Box::new(InternalString), ] diff --git a/ndc_macros/src/function.rs b/ndc_macros/src/function.rs index 4887c035..6a3da976 100644 --- a/ndc_macros/src/function.rs +++ b/ndc_macros/src/function.rs @@ -20,7 +20,9 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { &original_identifier.to_string(), )]; - let mut docs_buf = String::new(); + let mut return_type = None; + + let mut documentation_buffer = String::new(); for attr in &function.attrs { if attr.path().is_ident("function") { attr.parse_nested_meta(|meta| { @@ -32,6 +34,10 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { } else if meta.path.is_ident("alias") { function_names.push(meta.value()?.parse()?); Ok(()) + } else if meta.path.is_ident("return_type") { + let value: syn::Type = meta.value()?.parse()?; + return_type = Some(map_type(&value)); + Ok(()) } else { Err(meta.error("unsupported property on function")) } @@ -42,10 +48,13 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { && let syn::Expr::Lit(expr) = &meta.value && let syn::Lit::Str(lit_str) = &expr.lit { - writeln!(docs_buf, "{}", lit_str.value().trim()).expect("failed to write docs"); + writeln!(documentation_buffer, "{}", lit_str.value().trim()) + .expect("failed to write docs"); } } + let return_type = return_type.unwrap_or_else(|| map_return_type(&function.sig.output)); + match &function.vis { syn::Visibility::Public(_) => {} syn::Visibility::Restricted(_) | syn::Visibility::Inherited => { @@ -63,7 +72,8 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { &original_identifier, function_name, vec![], - &docs_buf, + &return_type, + &documentation_buffer, ) }) .collect(); @@ -71,7 +81,6 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { // When we call create_temp_variable we can get multiple definitions for a variable // For instance when a rust function is `fn foo(list: &[Value])` we can define two internal functions for both Tuple and List - let mut variation_id = 0usize; function_names .iter() @@ -98,7 +107,8 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { &format_ident!("{original_identifier}_{variation_id}"), function_name, args, - &docs_buf, + &return_type, + &documentation_buffer, ); variation_id += 1; wrapped @@ -108,6 +118,179 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { .collect() } +fn map_return_type(output: &syn::ReturnType) -> TokenStream { + match output { + syn::ReturnType::Default => { + // in case return type is not specified (for closures rust defaults to type inference which doesn't help us here) + quote! { crate::interpreter::function::StaticType::Tuple(vec![]) } + } + syn::ReturnType::Type(_, ty) => map_type(ty), + } +} + +fn map_type(ty: &syn::Type) -> TokenStream { + match ty { + syn::Type::Path(p) => map_type_path(p), + syn::Type::Reference(r) => map_type(r.elem.as_ref()), + syn::Type::Tuple(t) => { + let inner = t.elems.iter().map(map_type); + quote::quote! { + crate::interpreter::function::StaticType::Tuple(vec![ + #(#inner),* + ]) + } + } + syn::Type::Infer(_) => { + quote::quote! { crate::interpreter::function::StaticType::Any } + } + _ => { + panic!("unmapped type: {ty:?}"); + } + } +} + +#[allow(clippy::single_match_else)] +fn map_type_path(p: &syn::TypePath) -> TokenStream { + let segment = p.path.segments.last().unwrap(); + + match segment.ident.to_string().as_str() { + "i32" | "i64" | "isize" | "u32" | "u64" | "usize" | "BigInt" => { + quote::quote! { crate::interpreter::function::StaticType::Int } + } + "f32" | "f64" => { + quote::quote! { crate::interpreter::function::StaticType::Float } + } + "bool" => { + quote::quote! { crate::interpreter::function::StaticType::Bool } + } + "String" | "str" => { + quote::quote! { crate::interpreter::function::StaticType::String } + } + "Vec" | "List" => match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => { + let inner = args.args.first().expect("Vec<> requires inner type"); + if let syn::GenericArgument::Type(inner_ty) = inner { + let mapped = map_type(inner_ty); + quote::quote! { crate::interpreter::function::StaticType::List(Box::new(#mapped)) } + } else { + panic!("Vec inner not a type"); + } + } + _ => { + quote::quote! { crate::interpreter::function::StaticType::List(Box::new(crate::interpreter::function::StaticType::Any)) } + } + }, + "VecDeque" | "Deque" => match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => { + let inner = args.args.first().expect("VecDeque<> requires inner type"); + if let syn::GenericArgument::Type(inner_ty) = inner { + let mapped = map_type(inner_ty); + quote::quote! { crate::interpreter::function::StaticType::Deque(Box::new(#mapped)) } + } else { + panic!("VecDeque inner not a type"); + } + } + _ => quote::quote! { + crate::interpreter::function::StaticType::Deque(Box::new( + crate::interpreter::function::StaticType::Any + )) + }, + }, + "DefaultMap" | "HashMap" | "Map" => match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => { + let mut iter = args.args.iter(); + let Some(key) = iter.next() else { + return temp_create_map_any(); + }; + let Some(val) = iter.next() else { + return temp_create_map_any(); + }; + let key_ty = match key { + syn::GenericArgument::Type(t) => t, + _ => panic!("Invalid map key"), + }; + let val_ty = match val { + syn::GenericArgument::Type(t) => t, + _ => panic!("Invalid map value"), + }; + let key_mapped = map_type(key_ty); + let val_mapped = map_type(val_ty); + quote::quote! { crate::interpreter::function::StaticType::Map { key: Box::new(#key_mapped), value: Box::new(#val_mapped) } } + } + _ => temp_create_map_any(), + }, + "MinHeap" => match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => { + let inner = args.args.first().expect("MinHeap requires inner"); + if let syn::GenericArgument::Type(inner_ty) = inner { + let mapped = map_type(inner_ty); + quote::quote! { crate::interpreter::function::StaticType::MinHeap(Box::new(#mapped)) } + } else { + panic!("MinHeap inner invalid"); + } + } + _ => quote::quote! { + crate::interpreter::function::StaticType::MinHeap(Box::new( + crate::interpreter::function::StaticType::Any + )) + }, + }, + "MaxHeap" => match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => { + let inner = args.args.first().expect("MaxHeap requires inner"); + if let syn::GenericArgument::Type(inner_ty) = inner { + let mapped = map_type(inner_ty); + quote::quote! { crate::interpreter::function::StaticType::MaxHeap(Box::new(#mapped)) } + } else { + panic!("MaxHeap inner invalid"); + } + } + _ => panic!("MaxHeap without generics"), + }, + "Iterator" => match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => { + let inner = args.args.first().expect("Iterator requires inner"); + if let syn::GenericArgument::Type(inner_ty) = inner { + let mapped = map_type(inner_ty); + quote::quote! { crate::interpreter::function::StaticType::Iterator(Box::new(#mapped)) } + } else { + panic!("Iterator inner invalid"); + } + } + _ => { + quote::quote! { crate::interpreter::function::StaticType::Iterator(Box::new(crate::interpreter::function::StaticType::Any)) } + } + }, + "Option" => match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => { + let inner = args.args.first().expect("Option requires inner type"); + if let syn::GenericArgument::Type(inner_ty) = inner { + let mapped = map_type(inner_ty); + quote::quote! { crate::interpreter::function::StaticType::Option(Box::new(#mapped)) } + } else { + panic!("Option inner invalid"); + } + } + _ => panic!("Option without generics"), + }, + "Result" => match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => { + if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() { + map_type(inner_ty) + } else { + panic!("Result without generic arguments"); + } + } + _ => panic!("Result without angle bracketed args"), + }, + "Number" => quote::quote! { crate::interpreter::function::StaticType::Number }, + "Value" | "EvaluationResult" => { + quote::quote! { crate::interpreter::function::StaticType::Any } + } + unmatched => panic!("Cannot map type string '{unmatched}' to StaticType"), + } +} + /// Wraps an original rust function `function` in an outer function with the identifier `identifier` /// It's registered with the environment as `register_as_function_name` /// The argument translations mapping is defined by `input_arguments` @@ -116,6 +299,7 @@ fn wrap_single( identifier: &syn::Ident, register_as_function_name: &proc_macro2::Literal, input_arguments: Vec, + return_type: &TokenStream, docs: &str, ) -> WrappedFunction { let inner_ident = format_ident!("{}_inner", identifier); @@ -206,6 +390,7 @@ fn wrap_single( type_signature: crate::interpreter::function::TypeSignature::Exact(vec![ #( crate::interpreter::function::Parameter::new(#param_names, #param_types,) ),* ]), + return_type: #return_type, }) .name(String::from(#register_as_function_name)) .documentation(String::from(#docs)) @@ -223,53 +408,57 @@ fn wrap_single( fn into_param_type(ty: &syn::Type) -> TokenStream { match ty { - ty if path_ends_with(ty, "Vec") => quote! { crate::interpreter::function::ParamType::List }, + ty if path_ends_with(ty, "Vec") => { + quote! { crate::interpreter::function::StaticType::List(Box::new(crate::interpreter::function::StaticType::Any)) } + } ty if path_ends_with(ty, "VecDeque") => { - quote! { crate::interpreter::function::ParamType::Deque } + quote! { crate::interpreter::function::StaticType::Deque(Box::new(crate::interpreter::function::StaticType::Any)) } } ty if path_ends_with(ty, "DefaultMap") || path_ends_with(ty, "DefaultMapMut") || path_ends_with(ty, "HashMap") => { - quote! { crate::interpreter::function::ParamType::Map } + temp_create_map_any() } ty if path_ends_with(ty, "MinHeap") => { - quote! { crate::interpreter::function::ParamType::MinHeap } + quote! { crate::interpreter::function::StaticType::MinHeap(Box::new(crate::interpreter::function::StaticType::Any)) } } ty if path_ends_with(ty, "MaxHeap") => { - quote! { crate::interpreter::function::ParamType::MaxHeap } + quote! { crate::interpreter::function::StaticType::MaxHeap(Box::new(crate::interpreter::function::StaticType::Any)) } } ty if path_ends_with(ty, "ListRepr") => { - quote! { crate::interpreter::function::ParamType::List } - } - ty if path_ends_with(ty, "TupleRepr") => { - quote! { crate::interpreter::function::ParamType::Tuple } - } - ty if path_ends_with(ty, "MapRepr") => { - quote! { crate::interpreter::function::ParamType::Map } + quote! { crate::interpreter::function::StaticType::List(Box::new(crate::interpreter::function::StaticType::Any)) } } + ty if path_ends_with(ty, "MapRepr") => temp_create_map_any(), syn::Type::Reference(syn::TypeReference { elem, .. }) => into_param_type(elem), syn::Type::Path(syn::TypePath { path, .. }) => match path { - _ if path.is_ident("i64") => quote! { crate::interpreter::function::ParamType::Int }, - _ if path.is_ident("usize") => quote! { crate::interpreter::function::ParamType::Int }, - _ if path.is_ident("f64") => quote! { crate::interpreter::function::ParamType::Float }, - _ if path.is_ident("bool") => quote! { crate::interpreter::function::ParamType::Bool }, + _ if path.is_ident("i64") => quote! { crate::interpreter::function::StaticType::Int }, + _ if path.is_ident("usize") => quote! { crate::interpreter::function::StaticType::Int }, + _ if path.is_ident("f64") => quote! { crate::interpreter::function::StaticType::Float }, + _ if path.is_ident("bool") => quote! { crate::interpreter::function::StaticType::Bool }, _ if path.is_ident("Value") => { - quote! { crate::interpreter::function::ParamType::Any } + quote! { crate::interpreter::function::StaticType::Any } } _ if path.is_ident("Number") => { - quote! { crate::interpreter::function::ParamType::Number } + quote! { crate::interpreter::function::StaticType::Number } } _ if path.is_ident("Sequence") => { - quote! { crate::interpreter::function::ParamType::Sequence } + quote! { crate::interpreter::function::StaticType::Sequence(Box::new(crate::interpreter::function::StaticType::Any)) } } _ if path.is_ident("Callable") => { - quote! { crate::interpreter::function::ParamType::Function } + quote! { + crate::interpreter::function::StaticType::Function { + parameters: None, + return_type: Box::new(crate::interpreter::function::StaticType::Any) + } + } } - _ => panic!("Don't know how to convert Path into ParamType\n\n{path:?}"), + _ => panic!("Don't know how to convert Path into StaticType\n\n{path:?}"), }, - syn::Type::ImplTrait(_) => quote! { crate::interpreter::function::ParamType::Iterator }, - x => panic!("Don't know how to convert {x:?} into ParamType"), + syn::Type::ImplTrait(_) => { + quote! { crate::interpreter::function::StaticType::Iterator(Box::new(crate::interpreter::function::StaticType::Any)) } + } + x => panic!("Don't know how to convert {x:?} into StaticType"), } } @@ -297,7 +486,13 @@ fn create_temp_variable( if path_ends_with(ty, "Callable") { let temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Function }, + param_type: quote! { + // TODO: how are we going to figure out the exact type of function here + crate::interpreter::function::StaticType::Function { + parameters: None, + return_type: Box::new(crate::interpreter::function::StaticType::Any) + } + }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -316,7 +511,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Map }, + param_type: temp_create_map_any(), param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -332,7 +527,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Map }, + param_type: temp_create_map_any(), param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -348,7 +543,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Map }, + param_type: temp_create_map_any(), param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -364,7 +559,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Map }, + param_type: temp_create_map_any(), param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -381,7 +576,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::List }, + param_type: quote! { crate::interpreter::function::StaticType::List(Box::new(crate::interpreter::function::StaticType::Any)) }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -493,7 +688,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::String }, + param_type: quote! { crate::interpreter::function::StaticType::String }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -509,7 +704,7 @@ fn create_temp_variable( else if is_ref_of_bigint(ty) { let big_int = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Int }, + param_type: quote! { crate::interpreter::function::StaticType::Int }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -530,7 +725,7 @@ fn create_temp_variable( // If we need an owned Value else if path_ends_with(ty, "Value") && !is_ref(ty) { return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Any }, + param_type: quote! { crate::interpreter::function::StaticType::Any }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -543,7 +738,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::List }, + param_type: quote! { crate::interpreter::function::StaticType::List(Box::new(crate::interpreter::function::StaticType::Any)) }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -560,7 +755,7 @@ fn create_temp_variable( syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![ Argument { - param_type: quote! { crate::interpreter::function::ParamType::List }, + param_type: quote! { crate::interpreter::function::StaticType::List(Box::new(crate::interpreter::function::StaticType::Any)) }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -570,23 +765,23 @@ fn create_temp_variable( let #argument_var_name = &*#rc_temp_var.borrow(); }, }, - Argument { - param_type: quote! { crate::interpreter::function::ParamType::Tuple }, - param_name: quote! { #original_name }, - argument: quote! { #argument_var_name }, - initialize_code: quote! { - let crate::interpreter::value::Value::Sequence(crate::interpreter::sequence::Sequence::Tuple(#rc_temp_var)) = #argument_var_name else { - panic!("Value #position needed to be a Sequence::List but wasn't"); - }; - let #argument_var_name = &#rc_temp_var; - }, - }, + // Argument { + // param_type: quote! { crate::interpreter::function::StaticType::Tuple }, + // param_name: quote! { #original_name }, + // argument: quote! { #argument_var_name }, + // initialize_code: quote! { + // let crate::interpreter::value::Value::Sequence(crate::interpreter::sequence::Sequence::Tuple(#rc_temp_var)) = #argument_var_name else { + // panic!("Value #position needed to be a Sequence::List but wasn't"); + // }; + // let #argument_var_name = &#rc_temp_var; + // }, + // }, ]; } // The pattern is &BigRational else if path_ends_with(ty, "BigRational") && is_ref(ty) { return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Rational }, + param_type: quote! { crate::interpreter::function::StaticType::Rational }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -601,7 +796,7 @@ fn create_temp_variable( // The pattern is BigRational else if path_ends_with(ty, "BigRational") && !is_ref(ty) { return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Rational }, + param_type: quote! { crate::interpreter::function::StaticType::Rational }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -616,7 +811,7 @@ fn create_temp_variable( // The pattern is Complex64 else if path_ends_with(ty, "Complex64") && !is_ref(ty) { return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Complex }, + param_type: quote! { crate::interpreter::function::StaticType::Complex }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -675,3 +870,13 @@ fn create_temp_variable( panic!("Not sure how to handle receivers"); } + +// TODO: just adding Any as type here is lazy AF but CBA fixing generics +pub fn temp_create_map_any() -> TokenStream { + quote! { + crate::interpreter::function::StaticType::Map { + key: Box::new(crate::interpreter::function::StaticType::Any), + value: Box::new(crate::interpreter::function::StaticType::Any) + } + } +} diff --git a/tests/programs/006_lists/019_tuple_cow.ndct b/tests/programs/006_lists/019_tuple_cow.ndct index a38454f9..a110e587 100644 --- a/tests/programs/006_lists/019_tuple_cow.ndct +++ b/tests/programs/006_lists/019_tuple_cow.ndct @@ -1,7 +1,9 @@ --PROGRAM-- -let x = (1,2); -let y = x; -x ++= (3,4); -print(x == (1,2,3,4) and y == (1,2)) +// We had to sacrifice this feature to improve the typesystem +// let x = (1,2); +// let y = x; +// x ++= (3,4); +// print(x == (1,2,3,4) and y == (1,2)) +print(true); --EXPECT-- true \ No newline at end of file diff --git a/tests/programs/006_lists/030_augmented_assign_to_pattern.ndct b/tests/programs/006_lists/030_augmented_assign_to_pattern.ndct index ffff43ac..a43dfce1 100644 --- a/tests/programs/006_lists/030_augmented_assign_to_pattern.ndct +++ b/tests/programs/006_lists/030_augmented_assign_to_pattern.ndct @@ -2,4 +2,4 @@ let x, y = 0, 0; x, y += 1; // ERROR --EXPECT-ERROR-- -cannot use augmented assignment in combination with destructuring +This lvalue is required to be a single identifier diff --git a/tests/programs/006_lists/039_tuple_concat.ndct b/tests/programs/006_lists/039_tuple_concat.ndct index c8bd0c5c..f43a08c9 100644 --- a/tests/programs/006_lists/039_tuple_concat.ndct +++ b/tests/programs/006_lists/039_tuple_concat.ndct @@ -1,11 +1,12 @@ --PROGRAM-- -let tup = (1,2,3); -let out = tup ++ (4,5); -assert_eq(out, (1,2,3,4,5)); - -out ++= (6,7); -assert_eq(out, (1,2,3,4,5,6,7)); - +// Tuple concat is no longer a feature +// let tup = (1,2,3); +// let out = tup ++ (4,5); +// assert_eq(out, (1,2,3,4,5)); +// +// out ++= (6,7); +// assert_eq(out, (1,2,3,4,5,6,7)); +// print("ok"); --EXPECT-- ok diff --git a/tests/programs/006_lists/040_tuple_cow.ndct b/tests/programs/006_lists/040_tuple_cow.ndct index 29f224cb..d0c52df6 100644 --- a/tests/programs/006_lists/040_tuple_cow.ndct +++ b/tests/programs/006_lists/040_tuple_cow.ndct @@ -1,13 +1,14 @@ --PROGRAM-- -let a = (1,2,3); -let b = a; -b ++= (4,5); - -// A remains the same -assert_eq(a, (1,2,3)); - -// B was copied and (4,5) was appended -assert_eq(b, (1,2,3,4,5)); +// No longer a feature +// let a = (1,2,3); +// let b = a; +// b ++= (4,5); +// +// // A remains the same +// assert_eq(a, (1,2,3)); +// +// // B was copied and (4,5) was appended +// assert_eq(b, (1,2,3,4,5)); print("ok"); --EXPECT-- ok diff --git a/tests/programs/011_heap/007_max_heap_min .ndct b/tests/programs/011_heap/007_max_heap_min .ndct index 887e07ce..e2aa6976 100644 --- a/tests/programs/011_heap/007_max_heap_min .ndct +++ b/tests/programs/011_heap/007_max_heap_min .ndct @@ -1,9 +1,9 @@ --PROGRAM-- -let heap = MinHeap(); +let heap = MaxHeap(); heap.push(5); heap.push(4); heap.push(6); assert_eq(heap.min, 4); print("ok"); ---EXPECT-- -ok \ No newline at end of file +--EXPECT-ERROR-- +not supported for MaxHeap diff --git a/tests/programs/603_stdlib_seq/004_zip.ndct b/tests/programs/603_stdlib_seq/004_zip.ndct new file mode 100644 index 00000000..e2a07fee --- /dev/null +++ b/tests/programs/603_stdlib_seq/004_zip.ndct @@ -0,0 +1,5 @@ +--PROGRAM-- +assert_eq((1,2).zip((3,4)), [(1,3),(2,4)]); +print("ok"); +--EXPECT-- +ok \ No newline at end of file diff --git a/tests/programs/998_not_desired/big_int_ranges.ndct b/tests/programs/998_not_desired/big_int_ranges.ndct new file mode 100644 index 00000000..5a8925c0 --- /dev/null +++ b/tests/programs/998_not_desired/big_int_ranges.ndct @@ -0,0 +1,6 @@ +--PROGRAM-- +// Not sure what to think of this one, but right now you cannot construct ranges with ints outside of the i64 range +// it does make type analysis a tiny bit easier though +(2^1024)..((2^1024)+1) +--EXPECT-ERROR-- +Cannot convert Int into i64 \ No newline at end of file diff --git a/tests/programs/998_not_desired/weird_iterator_cloning.ndct b/tests/programs/998_not_desired/weird_iterator_cloning.ndct new file mode 100644 index 00000000..1faabe6b --- /dev/null +++ b/tests/programs/998_not_desired/weird_iterator_cloning.ndct @@ -0,0 +1,7 @@ +--PROGRAM-- +let a = (1..10).repeat(5).transposed; +let b = [[1,2,3,4,5],[6,7,8,9]]; +assert_eq(a, b); +print("ok"); +--EXPECT-- +ok \ No newline at end of file