@@ -48,99 +48,141 @@ pub(crate) fn parse_expr(expr: &str) -> CranexprResult<Vec<Expr>> {
4848 stack. push ( Expr :: Lit ( text. parse :: < f32 > ( ) . unwrap ( ) ) ) ;
4949 }
5050 TokenKind :: Ident => {
51- // An identifier followed by an open bracket is relative pixel access.
51+ // An identifier followed by an open bracket is relative or absolute pixel access.
5252 if tokens
5353 . peek ( )
5454 . is_some_and ( |( k, _) | * k == TokenKind :: OpenBracket )
5555 {
5656 tokens. next ( ) ; // Consume `[`.
5757
58- // Parse relX (must be an integer literal).
59- let ( rel_x_kind, rel_x_text) = tokens. next ( ) . ok_or ( CranexprError :: StackUnderflow ) ?;
60- let rel_x = match rel_x_kind {
61- TokenKind :: Literal { .. } => rel_x_text
62- . parse :: < i32 > ( )
63- . map_err ( |_| CranexprError :: StackUnderflow ) ?,
64- TokenKind :: Minus => {
65- // Negative integer literal.
66- let ( lit_kind, lit_text) = tokens. next ( ) . ok_or ( CranexprError :: StackUnderflow ) ?;
67- if !matches ! ( lit_kind, TokenKind :: Literal { .. } ) {
68- return Err ( CranexprError :: StackUnderflow ) ;
69- }
70- -( lit_text
71- . parse :: < i32 > ( )
72- . map_err ( |_| CranexprError :: StackUnderflow ) ?)
73- }
74- _ => return Err ( CranexprError :: StackUnderflow ) ,
75- } ;
76-
77- // Skip whitespace and optional comma.
78- while tokens
58+ // Check for empty brackets `[]` which indicates absolute access.
59+ if tokens
7960 . peek ( )
80- . is_some_and ( |( k, _) | matches ! ( k , TokenKind :: Whitespace | TokenKind :: Comma ) )
61+ . is_some_and ( |( k, _) | * k == TokenKind :: CloseBracket )
8162 {
82- tokens. next ( ) ;
83- }
63+ tokens. next ( ) ; // Consume `]`.
8464
85- // Parse relY.
86- let ( rel_y_kind, rel_y_text) = tokens. next ( ) . ok_or ( CranexprError :: StackUnderflow ) ?;
87- let rel_y = match rel_y_kind {
88- TokenKind :: Literal { .. } => rel_y_text
89- . parse :: < i32 > ( )
90- . map_err ( |_| CranexprError :: StackUnderflow ) ?,
91- TokenKind :: Minus => {
92- let ( lit_kind, lit_text) = tokens. next ( ) . ok_or ( CranexprError :: StackUnderflow ) ?;
93- if !matches ! ( lit_kind, TokenKind :: Literal { .. } ) {
65+ // Parse optional boundary mode suffix.
66+ let boundary_mode = if tokens. peek ( ) . is_some_and ( |( k, _) | * k == TokenKind :: Colon ) {
67+ tokens. next ( ) ; // Consume `:`.
68+ // Skip whitespace.
69+ while tokens
70+ . peek ( )
71+ . is_some_and ( |( k, _) | * k == TokenKind :: Whitespace )
72+ {
73+ tokens. next ( ) ;
74+ }
75+ let ( mode_kind, mode_text) = tokens. next ( ) . ok_or ( CranexprError :: StackUnderflow ) ?;
76+ if mode_kind != TokenKind :: Ident {
9477 return Err ( CranexprError :: StackUnderflow ) ;
9578 }
96- -( lit_text
79+ match mode_text {
80+ "c" => Some ( BoundaryMode :: Clamp ) ,
81+ "m" => Some ( BoundaryMode :: Mirror ) ,
82+ _ => return Err ( CranexprError :: StackUnderflow ) ,
83+ }
84+ } else {
85+ Some ( BoundaryMode :: Clamp ) // Default to clamp if no suffix
86+ } ;
87+
88+ let y = stack. pop ( ) . ok_or ( CranexprError :: StackUnderflow ) ?;
89+ let x = stack. pop ( ) . ok_or ( CranexprError :: StackUnderflow ) ?;
90+
91+ stack. push ( Expr :: AbsAccess {
92+ clip : text. to_string ( ) ,
93+ x : Box :: new ( x) ,
94+ y : Box :: new ( y) ,
95+ boundary_mode,
96+ } ) ;
97+ } else {
98+ // Relative access parsing
99+ // Parse relX (must be an integer literal).
100+ let ( rel_x_kind, rel_x_text) = tokens. next ( ) . ok_or ( CranexprError :: StackUnderflow ) ?;
101+ let rel_x = match rel_x_kind {
102+ TokenKind :: Literal { .. } => rel_x_text
97103 . parse :: < i32 > ( )
98- . map_err ( |_| CranexprError :: StackUnderflow ) ?)
104+ . map_err ( |_| CranexprError :: StackUnderflow ) ?,
105+ TokenKind :: Minus => {
106+ // Negative integer literal.
107+ let ( lit_kind, lit_text) = tokens. next ( ) . ok_or ( CranexprError :: StackUnderflow ) ?;
108+ if !matches ! ( lit_kind, TokenKind :: Literal { .. } ) {
109+ return Err ( CranexprError :: StackUnderflow ) ;
110+ }
111+ -( lit_text
112+ . parse :: < i32 > ( )
113+ . map_err ( |_| CranexprError :: StackUnderflow ) ?)
114+ }
115+ _ => return Err ( CranexprError :: StackUnderflow ) ,
116+ } ;
117+
118+ // Skip whitespace and optional comma.
119+ while tokens
120+ . peek ( )
121+ . is_some_and ( |( k, _) | matches ! ( k, TokenKind :: Whitespace | TokenKind :: Comma ) )
122+ {
123+ tokens. next ( ) ;
99124 }
100- _ => return Err ( CranexprError :: StackUnderflow ) ,
101- } ;
102125
103- // Expect close bracket, but skip whitespace first.
104- while tokens
105- . peek ( )
106- . is_some_and ( |( k, _) | * k == TokenKind :: Whitespace )
107- {
108- tokens. next ( ) ;
109- }
110- let ( rbracket_kind, _) = tokens. next ( ) . ok_or ( CranexprError :: StackUnderflow ) ?;
111- if rbracket_kind != TokenKind :: CloseBracket {
112- return Err ( CranexprError :: StackUnderflow ) ;
113- }
126+ // Parse relY.
127+ let ( rel_y_kind, rel_y_text) = tokens. next ( ) . ok_or ( CranexprError :: StackUnderflow ) ?;
128+ let rel_y = match rel_y_kind {
129+ TokenKind :: Literal { .. } => rel_y_text
130+ . parse :: < i32 > ( )
131+ . map_err ( |_| CranexprError :: StackUnderflow ) ?,
132+ TokenKind :: Minus => {
133+ let ( lit_kind, lit_text) = tokens. next ( ) . ok_or ( CranexprError :: StackUnderflow ) ?;
134+ if !matches ! ( lit_kind, TokenKind :: Literal { .. } ) {
135+ return Err ( CranexprError :: StackUnderflow ) ;
136+ }
137+ -( lit_text
138+ . parse :: < i32 > ( )
139+ . map_err ( |_| CranexprError :: StackUnderflow ) ?)
140+ }
141+ _ => return Err ( CranexprError :: StackUnderflow ) ,
142+ } ;
114143
115- // Parse optional boundary mode suffix.
116- let boundary_override = if tokens. peek ( ) . is_some_and ( |( k, _) | * k == TokenKind :: Colon ) {
117- tokens. next ( ) ; // Consume `:`.
118- // Skip whitespace.
144+ // Expect close bracket, but skip whitespace first.
119145 while tokens
120146 . peek ( )
121147 . is_some_and ( |( k, _) | * k == TokenKind :: Whitespace )
122148 {
123149 tokens. next ( ) ;
124150 }
125- let ( mode_kind , mode_text ) = tokens. next ( ) . ok_or ( CranexprError :: StackUnderflow ) ?;
126- if mode_kind != TokenKind :: Ident {
151+ let ( rbracket_kind , _ ) = tokens. next ( ) . ok_or ( CranexprError :: StackUnderflow ) ?;
152+ if rbracket_kind != TokenKind :: CloseBracket {
127153 return Err ( CranexprError :: StackUnderflow ) ;
128154 }
129- match mode_text {
130- "c" => Some ( BoundaryMode :: Clamp ) ,
131- "m" => Some ( BoundaryMode :: Mirror ) ,
132- _ => return Err ( CranexprError :: StackUnderflow ) ,
133- }
134- } else {
135- None
136- } ;
137-
138- stack. push ( Expr :: RelAccess {
139- clip : text. to_string ( ) ,
140- rel_x,
141- rel_y,
142- boundary_mode : boundary_override,
143- } ) ;
155+
156+ // Parse optional boundary mode suffix.
157+ let boundary_override = if tokens. peek ( ) . is_some_and ( |( k, _) | * k == TokenKind :: Colon ) {
158+ tokens. next ( ) ; // Consume `:`.
159+ // Skip whitespace.
160+ while tokens
161+ . peek ( )
162+ . is_some_and ( |( k, _) | * k == TokenKind :: Whitespace )
163+ {
164+ tokens. next ( ) ;
165+ }
166+ let ( mode_kind, mode_text) = tokens. next ( ) . ok_or ( CranexprError :: StackUnderflow ) ?;
167+ if mode_kind != TokenKind :: Ident {
168+ return Err ( CranexprError :: StackUnderflow ) ;
169+ }
170+ match mode_text {
171+ "c" => Some ( BoundaryMode :: Clamp ) ,
172+ "m" => Some ( BoundaryMode :: Mirror ) ,
173+ _ => return Err ( CranexprError :: StackUnderflow ) ,
174+ }
175+ } else {
176+ None
177+ } ;
178+
179+ stack. push ( Expr :: RelAccess {
180+ clip : text. to_string ( ) ,
181+ rel_x,
182+ rel_y,
183+ boundary_mode : boundary_override,
184+ } ) ;
185+ }
144186 }
145187 // An identifier followed by a dot is the start of frame property access.
146188 else if tokens. peek ( ) . is_some_and ( |( k, _) | * k == TokenKind :: Dot ) {
0 commit comments