Skip to content

Commit 98d53fb

Browse files
committed
Implement tuple vectorization for arithmetic operators
Element-wise operations now work on numeric tuples: - (1,2) + (5,3) == (6,5) - (1,1) * 2 == (2,2) - 5 + (3,2) == (8,7) The analyser's last-resort fallback now collects all same-named bindings instead of just the last one, so operators like `-` with both unary and binary overloads can be correctly resolved at runtime. The evaluator's resolve_and_call attempts vectorization when no direct function match is found and the argument types support it.
1 parent 3f87cd4 commit 98d53fb

5 files changed

Lines changed: 97 additions & 48 deletions

File tree

ndc_lib/src/interpreter/evaluate/mod.rs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ pub(crate) fn evaluate_expression(
334334
evaluated_args.push(arg);
335335
}
336336

337-
let function_as_value = resolve_and_call(function, evaluated_args, environment, span)?;
337+
resolve_and_call(function, evaluated_args, environment, span)?
338338
}
339339
Expression::FunctionDeclaration {
340340
parameters: arguments,
@@ -991,36 +991,59 @@ fn resolve_and_call(
991991
environment: &Rc<RefCell<Environment>>,
992992
span: Span,
993993
) -> EvaluationResult {
994-
////////////////////////////
995994
let ExpressionLocation { expression, .. } = function_expression;
996995

997-
let function_as_value = if let Expression::Identifier { resolved, .. } = expression {
996+
let function_as_value = if let Expression::Identifier { name, resolved, .. } = expression {
998997
let arg_types = args.iter().map(|arg| arg.static_type()).collect::<Vec<_>>();
999998

1000999
let opt = match resolved {
10011000
Binding::None => None,
10021001
Binding::Resolved(var) => Some(environment.borrow().get(*var)),
10031002
Binding::Dynamic(dynamic_binding) => dynamic_binding
1004-
.iter() // TODO: should we consider the binding order?
1003+
.iter()
10051004
.find_map(|binding| {
10061005
let value = environment.borrow().get(*binding);
10071006

10081007
let Value::Function(fun) = &value else {
10091008
panic!("dynamic binding resolved to non-function type at runtime");
10101009
};
10111010

1012-
// Find the first function that matches
10131011
if fun.static_type().is_fn_and_matches(&arg_types) {
10141012
return Some(value);
10151013
}
10161014

10171015
None
10181016
}),
10191017
};
1018+
1019+
if opt.is_none() {
1020+
if let Binding::Dynamic(dynamic_binding) = resolved {
1021+
if let [left_type, right_type] = arg_types.as_slice() {
1022+
if left_type.supports_vectorization_with(right_type) {
1023+
let elem_types = vectorized_element_types(left_type, right_type);
1024+
let inner_fn = dynamic_binding.iter().find_map(|binding| {
1025+
let value = environment.borrow().get(*binding);
1026+
let Value::Function(fun) = &value else {
1027+
panic!("dynamic binding resolved to non-function type at runtime");
1028+
};
1029+
if fun.static_type().is_fn_and_matches(&elem_types) {
1030+
Some(Rc::clone(&fun))
1031+
} else {
1032+
None
1033+
}
1034+
});
1035+
if let Some(inner_fn) = inner_fn {
1036+
return inner_fn.call_vectorized(&mut args, environment).add_span(span);
1037+
}
1038+
}
1039+
}
1040+
}
1041+
}
1042+
10201043
opt.ok_or_else(|| {
10211044
FunctionCarrier::EvaluationError(EvaluationError::new(
10221045
format!(
1023-
"Failed to find a function that can handle the arguments ({}) at runtime",
1046+
"no function called '{name}' found matches the arguments: ({})",
10241047
arg_types.iter().join(", ")
10251048
),
10261049
function_expression.span,
@@ -1030,12 +1053,7 @@ fn resolve_and_call(
10301053
evaluate_expression(function_expression, environment)?
10311054
};
10321055

1033-
////////////////////////////
1034-
////////////////////////////
1035-
////////////////////////////
1036-
10371056
if let Value::Function(function) = function_as_value {
1038-
// Here we should be able to call without checking types
10391057
function.call(&mut args, environment).add_span(span)
10401058
} else {
10411059
Err(FunctionCarrier::EvaluationError(EvaluationError::new(
@@ -1048,6 +1066,12 @@ fn resolve_and_call(
10481066
}
10491067
}
10501068

1069+
fn vectorized_element_types(left: &StaticType, right: &StaticType) -> [StaticType; 2] {
1070+
let left_elem = left.sequence_element_type().unwrap_or_else(|| left.clone());
1071+
let right_elem = right.sequence_element_type().unwrap_or_else(|| right.clone());
1072+
[left_elem, right_elem]
1073+
}
1074+
10511075
fn resolve_dynamic_binding(
10521076
binding: &Binding,
10531077
arg_types: &[StaticType],

ndc_lib/src/interpreter/function.rs

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -122,49 +122,39 @@ impl Function {
122122
}
123123
}
124124

125-
fn call_vectorized(
125+
pub fn call_vectorized(
126126
&self,
127127
args: &mut [Value],
128128
env: &Rc<RefCell<Environment>>,
129129
) -> EvaluationResult {
130130
let [left, right] = args else {
131-
// Vectorized application only works in cases where there are two tuple arguments
132131
panic!("incorrect argument count for vectorization should have been handled by caller");
133132
};
134133

135-
// TODO: let caller handle checks?
136-
// if !left.supports_vectorization_with(right) {
137-
// return Err(FunctionCarrier::FunctionNotFound);
138-
// }
139-
140-
let (left, right) = match (left, right) {
141-
// Both are tuples
142-
(Value::Sequence(Sequence::Tuple(left)), Value::Sequence(Sequence::Tuple(right))) => {
143-
(left, right.as_slice())
134+
let result = match (left, right) {
135+
(Value::Sequence(Sequence::Tuple(left_rc)), Value::Sequence(Sequence::Tuple(right_rc))) => {
136+
left_rc
137+
.iter()
138+
.zip(right_rc.iter())
139+
.map(|(l, r)| self.call(&mut [l.clone(), r.clone()], env))
140+
.collect::<Result<Vec<_>, _>>()?
144141
}
145-
// Left is a number and right is a tuple
146-
(left @ Value::Number(_), Value::Sequence(Sequence::Tuple(right))) => (
147-
&mut Rc::new(vec![left.clone(); right.len()]),
148-
right.as_slice(),
149-
),
150-
// Left is a tuple and right is a number
151-
(Value::Sequence(Sequence::Tuple(left)), right @ Value::Number(_)) => {
152-
(left, std::slice::from_ref(right))
142+
(left @ Value::Number(_), Value::Sequence(Sequence::Tuple(right_rc))) => {
143+
right_rc
144+
.iter()
145+
.map(|r| self.call(&mut [left.clone(), r.clone()], env))
146+
.collect::<Result<Vec<_>, _>>()?
153147
}
154-
_ => {
155-
panic!("caller should handle all checks before vectorizing")
148+
(Value::Sequence(Sequence::Tuple(left_rc)), right @ Value::Number(_)) => {
149+
left_rc
150+
.iter()
151+
.map(|l| self.call(&mut [l.clone(), right.clone()], env))
152+
.collect::<Result<Vec<_>, _>>()?
156153
}
154+
_ => panic!("caller should handle all checks before vectorizing"),
157155
};
158156

159-
let left_mut: &mut Vec<Value> = Rc::make_mut(left);
160-
161-
// Zip the mutable vector with the immutable right side and perform the operations on all elements
162-
// TODO: maybe one day figure out how to get rid of all these clones
163-
for (l, r) in left_mut.iter_mut().zip(right.iter().cycle()) {
164-
*l = self.call(&mut [l.clone(), r.clone()], env)?;
165-
}
166-
167-
Ok(Value::Sequence(Sequence::Tuple(left.clone())))
157+
Ok(Value::Sequence(Sequence::Tuple(Rc::new(result))))
168158
}
169159
}
170160

ndc_lib/src/interpreter/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ impl Interpreter {
7171
self.analyser.analyse(e)?;
7272
}
7373

74-
dbg!(&expressions);
75-
dbg!(&self.analyser);
74+
//dbg!(&expressions);
75+
//dbg!(&self.analyser);
7676

7777
let final_value = self.interpret(expressions.into_iter())?;
7878

ndc_lib/src/interpreter/semantic/analyser.rs

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -637,15 +637,40 @@ impl ScopeTree {
637637

638638
Some(Binding::Dynamic(loose_bindings))
639639
})
640-
// If we can't find any function in scope that could match, we just default to an identifier.
641-
// TODO: no this fucks everything
640+
// If we can't find any function in scope that could match, fall back to all same-named
641+
// bindings so runtime dynamic dispatch (including vectorization) can pick the right one.
642642
.or_else(|| {
643-
self.get_binding_any(ident)
644-
.map(|resolved| Binding::Dynamic(vec![resolved]))
643+
let all_bindings = self.get_all_bindings_by_name(ident);
644+
if all_bindings.is_empty() {
645+
return None;
646+
}
647+
Some(Binding::Dynamic(all_bindings))
645648
})
646649
.unwrap_or(Binding::None)
647650
}
648651

652+
fn get_all_bindings_by_name(&self, ident: &str) -> Vec<ResolvedVar> {
653+
let mut results = Vec::new();
654+
let mut depth = 0;
655+
let mut scope_ptr = self.current_scope_idx;
656+
657+
loop {
658+
let slots = self.scopes[scope_ptr].find_all_slots_by_name(ident);
659+
results.extend(slots.into_iter().map(|slot| ResolvedVar::Captured { slot, depth }));
660+
661+
if let Some(parent_idx) = self.scopes[scope_ptr].parent_idx {
662+
depth += 1;
663+
scope_ptr = parent_idx;
664+
} else {
665+
let global_slots = self.global_scope.find_all_slots_by_name(ident);
666+
results.extend(global_slots.into_iter().map(|slot| ResolvedVar::Global { slot }));
667+
break;
668+
}
669+
}
670+
671+
results
672+
}
673+
649674
fn resolve_function(&mut self, ident: &str, arg_types: &[StaticType]) -> Option<ResolvedVar> {
650675
let mut depth = 0;
651676
let mut scope_ptr = self.current_scope_idx;
@@ -692,6 +717,16 @@ impl Scope {
692717
.rposition(|(ident, _)| ident == find_ident)
693718
}
694719

720+
fn find_all_slots_by_name(&self, find_ident: &str) -> Vec<usize> {
721+
self.identifiers
722+
.iter()
723+
.enumerate()
724+
.filter_map(|(slot, (ident, _))| {
725+
if ident == find_ident { Some(slot) } else { None }
726+
})
727+
.collect()
728+
}
729+
695730
fn find_function_candidates(&self, find_ident: &str, find_types: &[StaticType]) -> Vec<usize> {
696731
self.identifiers.iter()
697732
.enumerate()
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
--PROGRAM--
22
5();
33
--EXPECT-ERROR--
4-
Failed to invoke expression as function
4+
Unable to invoke Int as a function.

0 commit comments

Comments
 (0)