1use ecow::EcoString;
2use num_bigint::BigInt;
3
4use crate::ast::{self, BitArrayOption, SrcSpan};
5use crate::build::Target;
6use crate::type_::Type;
7use std::sync::Arc;
8
9//
10// Public Interface
11//
12
13pub fn type_options_for_value<TypedValue>(
14 input_options: &[BitArrayOption<TypedValue>],
15 target: Target,
16) -> Result<Arc<Type>, Error>
17where
18 TypedValue: GetLiteralValue,
19{
20 type_options(input_options, TypeOptionsMode::Expression, false, target)
21}
22
23pub fn type_options_for_pattern<TypedValue>(
24 input_options: &[BitArrayOption<TypedValue>],
25 must_have_size: bool,
26 target: Target,
27) -> Result<Arc<Type>, Error>
28where
29 TypedValue: GetLiteralValue,
30{
31 type_options(
32 input_options,
33 TypeOptionsMode::Pattern,
34 must_have_size,
35 target,
36 )
37}
38
39struct SegmentOptionCategories<'a, T> {
40 type_: Option<&'a BitArrayOption<T>>,
41 signed: Option<&'a BitArrayOption<T>>,
42 endian: Option<&'a BitArrayOption<T>>,
43 unit: Option<&'a BitArrayOption<T>>,
44 size: Option<&'a BitArrayOption<T>>,
45}
46
47impl<T> SegmentOptionCategories<'_, T> {
48 fn new() -> Self {
49 SegmentOptionCategories {
50 type_: None,
51 signed: None,
52 endian: None,
53 unit: None,
54 size: None,
55 }
56 }
57
58 fn segment_type(&self) -> Arc<Type> {
59 use BitArrayOption::*;
60 let default = Int {
61 location: SrcSpan::default(),
62 };
63
64 match self.type_.unwrap_or(&default) {
65 Int { .. } => crate::type_::int(),
66 Float { .. } => crate::type_::float(),
67 Utf8 { .. } | Utf16 { .. } | Utf32 { .. } => crate::type_::string(),
68 Bytes { .. } | Bits { .. } => crate::type_::bit_array(),
69 Utf8Codepoint { .. } | Utf16Codepoint { .. } | Utf32Codepoint { .. } => {
70 crate::type_::utf_codepoint()
71 }
72
73 Signed { .. }
74 | Unsigned { .. }
75 | Big { .. }
76 | Little { .. }
77 | Native { .. }
78 | Size { .. }
79 | Unit { .. } => panic!("Tried to type a non type kind BitArray option."),
80 }
81 }
82}
83
84#[derive(Debug, PartialEq, Eq)]
85/// Whether we're typing options for a bit array segment that's part of a pattern
86/// or an expression.
87///
88enum TypeOptionsMode {
89 Expression,
90 Pattern,
91}
92
93fn type_options<TypedValue>(
94 input_options: &[BitArrayOption<TypedValue>],
95 mode: TypeOptionsMode,
96 must_have_size: bool,
97 target: Target,
98) -> Result<Arc<Type>, Error>
99where
100 TypedValue: GetLiteralValue,
101{
102 use BitArrayOption::*;
103
104 let mut categories = SegmentOptionCategories::new();
105 // Basic category checking
106 for option in input_options {
107 match option {
108 Utf8Codepoint { .. } | Utf16Codepoint { .. } | Utf32Codepoint { .. }
109 if mode == TypeOptionsMode::Pattern && target == Target::JavaScript =>
110 {
111 return err(
112 ErrorType::OptionNotSupportedForTarget {
113 target,
114 option: UnsupportedOption::UtfCodepointPattern,
115 },
116 option.location(),
117 );
118 }
119
120 Bytes { .. }
121 | Int { .. }
122 | Float { .. }
123 | Bits { .. }
124 | Utf8 { .. }
125 | Utf16 { .. }
126 | Utf32 { .. }
127 | Utf8Codepoint { .. }
128 | Utf16Codepoint { .. }
129 | Utf32Codepoint { .. } => {
130 if let Some(previous) = categories.type_ {
131 return err(
132 ErrorType::ConflictingTypeOptions {
133 existing_type: previous.label(),
134 },
135 option.location(),
136 );
137 } else {
138 categories.type_ = Some(option);
139 }
140 }
141
142 Signed { .. } | Unsigned { .. } => {
143 if let Some(previous) = categories.signed {
144 return err(
145 ErrorType::ConflictingSignednessOptions {
146 existing_signed: previous.label(),
147 },
148 option.location(),
149 );
150 } else {
151 categories.signed = Some(option);
152 }
153 }
154
155 Native { .. } if target == Target::JavaScript => {
156 return err(
157 ErrorType::OptionNotSupportedForTarget {
158 target,
159 option: UnsupportedOption::NativeEndianness,
160 },
161 option.location(),
162 );
163 }
164
165 Big { .. } | Little { .. } | Native { .. } => {
166 if let Some(previous) = categories.endian {
167 return err(
168 ErrorType::ConflictingEndiannessOptions {
169 existing_endianness: previous.label(),
170 },
171 option.location(),
172 );
173 } else {
174 categories.endian = Some(option);
175 }
176 }
177
178 Size { .. } => {
179 if categories.size.is_some() {
180 return err(ErrorType::ConflictingSizeOptions, option.location());
181 } else {
182 categories.size = Some(option);
183 }
184 }
185
186 Unit { .. } => {
187 if categories.unit.is_some() {
188 return err(ErrorType::ConflictingUnitOptions, option.location());
189 } else {
190 categories.unit = Some(option);
191 }
192 }
193 };
194 }
195
196 // Some options are not allowed in value mode
197 if mode == TypeOptionsMode::Expression {
198 match categories {
199 SegmentOptionCategories {
200 signed: Some(opt), ..
201 }
202 | SegmentOptionCategories {
203 type_: Some(opt @ Bytes { .. }),
204 ..
205 } => return err(ErrorType::OptionNotAllowedInValue, opt.location()),
206 _ => (),
207 }
208 }
209
210 // All but the last segment in a pattern must have an exact size
211 if must_have_size {
212 if let SegmentOptionCategories {
213 type_: Some(opt @ (Bytes { .. } | Bits { .. })),
214 size: None,
215 ..
216 } = categories
217 {
218 return err(ErrorType::SegmentMustHaveSize, opt.location());
219 }
220 }
221
222 // Endianness is only valid for int, utf16, utf32 and float
223 match categories {
224 SegmentOptionCategories {
225 type_: None | Some(Int { .. } | Utf16 { .. } | Utf32 { .. } | Float { .. }),
226 ..
227 } => {}
228
229 SegmentOptionCategories {
230 endian: Some(endian),
231 ..
232 } => return err(ErrorType::InvalidEndianness, endian.location()),
233
234 _ => {}
235 }
236
237 // signed and unsigned can only be used with int types
238 match categories {
239 SegmentOptionCategories {
240 type_: None | Some(Int { .. }),
241 ..
242 } => {}
243
244 SegmentOptionCategories {
245 type_: Some(opt),
246 signed: Some(sign),
247 ..
248 } => {
249 return err(
250 ErrorType::SignednessUsedOnNonInt { type_: opt.label() },
251 sign.location(),
252 );
253 }
254
255 _ => {}
256 }
257
258 // utf8, utf16, utf32 exclude unit and size
259 match categories {
260 SegmentOptionCategories {
261 type_: Some(type_),
262 unit: Some(_),
263 ..
264 } if is_unicode(type_) => {
265 return err(
266 ErrorType::TypeDoesNotAllowUnit {
267 type_: type_.label(),
268 },
269 type_.location(),
270 );
271 }
272
273 SegmentOptionCategories {
274 type_: Some(type_),
275 size: Some(_),
276 ..
277 } if is_unicode(type_) => {
278 return err(
279 ErrorType::TypeDoesNotAllowSize {
280 type_: type_.label(),
281 },
282 type_.location(),
283 );
284 }
285
286 _ => {}
287 }
288
289 // if unit specified, size must be specified
290 if let SegmentOptionCategories {
291 unit: Some(unit),
292 size: None,
293 ..
294 } = categories
295 {
296 return err(ErrorType::UnitMustHaveSize, unit.location());
297 }
298
299 // float only 16/32/64
300 if let SegmentOptionCategories {
301 type_: Some(Float { .. }),
302 size: Some(size),
303 ..
304 } = categories
305 {
306 if let Some(abox) = size.value() {
307 match abox.as_int_literal() {
308 None => (),
309 Some(value) if value == 16.into() || value == 32.into() || value == 64.into() => (),
310 _ => return err(ErrorType::FloatWithSize, size.location()),
311 }
312 }
313 }
314
315 // Segment patterns with a zero or negative constant size must be rejected,
316 // we know they will never match!
317 // A negative size is still allowed in expressions as it will just result
318 // in an empty segment.
319 if let (Some(size @ Size { value, .. }), TypeOptionsMode::Pattern) = (categories.size, mode) {
320 match value.as_int_literal() {
321 Some(n) if n <= BigInt::ZERO => {
322 return err(ErrorType::ConstantSizeNotPositive, size.location());
323 }
324 Some(_) | None => (),
325 }
326 }
327
328 Ok(categories.segment_type())
329}
330
331pub trait GetLiteralValue {
332 fn as_int_literal(&self) -> Option<BigInt>;
333}
334
335impl GetLiteralValue for ast::TypedPattern {
336 fn as_int_literal(&self) -> Option<BigInt> {
337 if let ast::Pattern::Int { int_value, .. } = self {
338 Some(int_value.clone())
339 } else {
340 None
341 }
342 }
343}
344
345fn is_unicode<T>(opt: &BitArrayOption<T>) -> bool {
346 use BitArrayOption::*;
347
348 matches!(
349 opt,
350 Utf8 { .. }
351 | Utf16 { .. }
352 | Utf32 { .. }
353 | Utf8Codepoint { .. }
354 | Utf16Codepoint { .. }
355 | Utf32Codepoint { .. }
356 )
357}
358
359fn err<A>(error: ErrorType, location: SrcSpan) -> Result<A, Error> {
360 Err(Error { location, error })
361}
362
363#[derive(Debug)]
364pub struct Error {
365 pub location: SrcSpan,
366 pub error: ErrorType,
367}
368
369#[derive(Debug, PartialEq, Eq, Clone)]
370pub enum ErrorType {
371 ConflictingEndiannessOptions {
372 existing_endianness: EcoString,
373 },
374 ConflictingSignednessOptions {
375 existing_signed: EcoString,
376 },
377 ConflictingSizeOptions,
378 ConflictingTypeOptions {
379 existing_type: EcoString,
380 },
381 ConflictingUnitOptions,
382 FloatWithSize,
383 InvalidEndianness,
384 OptionNotAllowedInValue,
385 SegmentMustHaveSize,
386 SignednessUsedOnNonInt {
387 type_: EcoString,
388 },
389 TypeDoesNotAllowSize {
390 type_: EcoString,
391 },
392 TypeDoesNotAllowUnit {
393 type_: EcoString,
394 },
395 UnitMustHaveSize,
396 VariableUtfSegmentInPattern,
397 ConstantSizeNotPositive,
398 OptionNotSupportedForTarget {
399 target: Target,
400 option: UnsupportedOption,
401 },
402}
403
404#[derive(Debug, PartialEq, Eq, Clone, Copy)]
405pub enum UnsupportedOption {
406 UtfCodepointPattern,
407 NativeEndianness,
408}