@@ -2,12 +2,13 @@ use aether_syntax::AnyRArgumentName;
22use aether_syntax:: AnyRExpression ;
33use aether_syntax:: AnyRValue ;
44use aether_syntax:: RArgument ;
5+ use aether_syntax:: RBinaryExpression ;
56use aether_syntax:: RCall ;
67use biome_rowan:: AstNode ;
78use biome_rowan:: AstPtr ;
89use biome_rowan:: AstSeparatedList ;
910use biome_rowan:: WalkEvent ;
10- // Re-exported so consumers building an `AssignBinding` (custom `EffectHandler `s)
11+ // Re-exported so consumers building an `AssignBinding` (custom `AssignHandler `s)
1112// can name the `name_expr` field's type without depending on oak_core directly.
1213pub use oak_core:: range:: RangedAstPtr ;
1314use oak_core:: syntax_ext:: RIdentifierExt ;
@@ -53,7 +54,7 @@ pub struct EffectsHandlers {
5354 pub arguments : Option < & ' static dyn EffectHandler < Output = ResolvedArgumentEffects > > ,
5455 pub attach : Option < & ' static dyn EffectHandler < Output = String > > ,
5556 pub source : Option < & ' static dyn EffectHandler < Output = Vec < String > > > ,
56- pub assign : Option < & ' static dyn EffectHandler < Output = Vec < AssignBinding > > > ,
57+ pub assign : Option < & ' static dyn AssignHandler > ,
5758}
5859
5960/// Resolver for an effect of a call.
@@ -73,6 +74,25 @@ pub trait EffectHandler: std::fmt::Debug + Sync {
7374 fn resolve ( & self , call : & RCall , ctx : & CallContext ) -> Option < Self :: Output > ;
7475}
7576
77+ /// Where an effect is invoked. Most effects are only ever calls but an Assign
78+ /// effect can also be a binding operator (`x %<>% f`). [`AssignHandler`] takes
79+ /// this to disambiguate rather than a bare call.
80+ pub enum EffectSite < ' a > {
81+ Call ( & ' a RCall ) ,
82+ Operator ( & ' a RBinaryExpression ) ,
83+ }
84+
85+ /// Resolver for an assign-like effect.
86+ ///
87+ /// Separate from [`EffectHandler`] because an assign has two invocation shapes,
88+ /// a call (`assign("x", v)`) and a binding operator (`x %<>% f`).
89+ ///
90+ /// Contributed statically like [`EffectHandler`], so it's `Sync` for the
91+ /// registry `static`s.
92+ pub trait AssignHandler : std:: fmt:: Debug + Sync {
93+ fn resolve ( & self , site : EffectSite , ctx : & CallContext ) -> Option < Vec < AssignBinding > > ;
94+ }
95+
7696/// Context for effect handlers.
7797///
7898/// Allows querying the properties or static values of arguments. Stateless
@@ -143,6 +163,15 @@ impl CallContext {
143163 }
144164 }
145165
166+ /// Read a quoted name argument. E.g. the LHS of an Assign operator.
167+ pub fn resolve_quoted_symbol_or_string ( & self , value : & AnyRExpression ) -> Option < String > {
168+ match value {
169+ AnyRExpression :: RIdentifier ( ident) => Some ( ident. name_text ( ) ) ,
170+ AnyRExpression :: AnyRValue ( AnyRValue :: RStringValue ( s) ) => s. string_text ( ) ,
171+ _ => None ,
172+ }
173+ }
174+
146175 /// Statically evaluate an argument's value expression to a bool.
147176 pub fn resolve_static_bool ( & self , value : & AnyRExpression ) -> Option < bool > {
148177 match value {
@@ -454,10 +483,11 @@ pub struct AssignAnnotation {
454483 pub position : usize ,
455484}
456485
457- impl EffectHandler for AssignAnnotation {
458- type Output = Vec < AssignBinding > ;
459-
460- fn resolve ( & self , call : & RCall , ctx : & CallContext ) -> Option < Vec < AssignBinding > > {
486+ impl AssignHandler for AssignAnnotation {
487+ fn resolve ( & self , site : EffectSite , ctx : & CallContext ) -> Option < Vec < AssignBinding > > {
488+ let EffectSite :: Call ( call) = site else {
489+ return None ;
490+ } ;
461491 let args = call. arguments ( ) . ok ( ) ?;
462492
463493 // Matched positionally among unnamed arguments, same as `source`, so a
@@ -512,6 +542,30 @@ impl EffectHandler for AssignAnnotation {
512542 }
513543}
514544
545+ /// Handler for a binding operator (`x %<>% f()`, `x %<~% expr`, `x := expr`).
546+ ///
547+ /// The operator captures its LHS unevaluated.
548+ #[ derive( Debug , Clone , Copy ) ]
549+ pub struct BindingOperatorHandler ;
550+
551+ impl AssignHandler for BindingOperatorHandler {
552+ fn resolve ( & self , site : EffectSite , ctx : & CallContext ) -> Option < Vec < AssignBinding > > {
553+ let EffectSite :: Operator ( bin) = site else {
554+ return None ;
555+ } ;
556+ let left = bin. left ( ) . ok ( ) ?;
557+ let right = bin. right ( ) . ok ( ) ?;
558+
559+ let name = ctx. resolve_quoted_symbol_or_string ( & left) ?;
560+
561+ Some ( vec ! [ AssignBinding {
562+ name,
563+ name_expr: RangedAstPtr :: new( & left) ,
564+ value_expr: Some ( AstPtr :: new( & right) ) ,
565+ } ] )
566+ }
567+ }
568+
515569/// Match a named argument against `formals`. Returns the index of the matched
516570/// formal.
517571///
0 commit comments