Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / bit_array.rs
12 kB 420 lines
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, utf16_codepoint, utf32, 223 // utf32_codepoint and float 224 match categories { 225 SegmentOptionCategories { 226 type_: 227 None 228 | Some( 229 Int { .. } 230 | Utf16 { .. } 231 | Utf32 { .. } 232 | Utf16Codepoint { .. } 233 | Utf32Codepoint { .. } 234 | Float { .. }, 235 ), 236 .. 237 } => {} 238 239 SegmentOptionCategories { 240 endian: Some(endian), 241 .. 242 } => return err(ErrorType::InvalidEndianness, endian.location()), 243 244 _ => {} 245 } 246 247 // signed and unsigned can only be used with int types 248 match categories { 249 SegmentOptionCategories { 250 type_: None | Some(Int { .. }), 251 .. 252 } => {} 253 254 SegmentOptionCategories { 255 type_: Some(opt), 256 signed: Some(sign), 257 .. 258 } => { 259 return err( 260 ErrorType::SignednessUsedOnNonInt { type_: opt.label() }, 261 sign.location(), 262 ); 263 } 264 265 _ => {} 266 } 267 268 // utf8, utf16, utf32 exclude unit and size 269 match categories { 270 SegmentOptionCategories { 271 type_: Some(type_), 272 unit: Some(_), 273 .. 274 } if is_unicode(type_) => { 275 return err( 276 ErrorType::TypeDoesNotAllowUnit { 277 type_: type_.label(), 278 }, 279 type_.location(), 280 ); 281 } 282 283 SegmentOptionCategories { 284 type_: Some(type_), 285 size: Some(_), 286 .. 287 } if is_unicode(type_) => { 288 return err( 289 ErrorType::TypeDoesNotAllowSize { 290 type_: type_.label(), 291 }, 292 type_.location(), 293 ); 294 } 295 296 _ => {} 297 } 298 299 // if unit specified, size must be specified 300 if let SegmentOptionCategories { 301 unit: Some(unit), 302 size: None, 303 .. 304 } = categories 305 { 306 return err(ErrorType::UnitMustHaveSize, unit.location()); 307 } 308 309 // float only 16/32/64 310 if let SegmentOptionCategories { 311 type_: Some(Float { .. }), 312 size: Some(size), 313 .. 314 } = categories 315 { 316 if let Some(abox) = size.value() { 317 match abox.as_int_literal() { 318 None => (), 319 Some(value) if value == 16.into() || value == 32.into() || value == 64.into() => (), 320 _ => return err(ErrorType::FloatWithSize, size.location()), 321 } 322 } 323 } 324 325 // Segment patterns with a zero or negative constant size must be rejected, 326 // we know they will never match! 327 // A negative size is still allowed in expressions as it will just result 328 // in an empty segment. 329 if let (Some(size @ Size { value, .. }), TypeOptionsMode::Pattern) = (categories.size, mode) { 330 match value.as_int_literal() { 331 Some(n) if n <= BigInt::ZERO => { 332 return err(ErrorType::ConstantSizeNotPositive, size.location()); 333 } 334 Some(_) | None => (), 335 } 336 } 337 338 Ok(categories.segment_type()) 339} 340 341pub trait GetLiteralValue { 342 fn as_int_literal(&self) -> Option<BigInt>; 343} 344 345impl GetLiteralValue for ast::TypedPattern { 346 fn as_int_literal(&self) -> Option<BigInt> { 347 match self { 348 ast::Pattern::Int { int_value, .. } 349 | ast::Pattern::BitArraySize(ast::BitArraySize::Int { int_value, .. }) => { 350 Some(int_value.clone()) 351 } 352 _ => None, 353 } 354 } 355} 356 357fn is_unicode<T>(opt: &BitArrayOption<T>) -> bool { 358 use BitArrayOption::*; 359 360 matches!( 361 opt, 362 Utf8 { .. } 363 | Utf16 { .. } 364 | Utf32 { .. } 365 | Utf8Codepoint { .. } 366 | Utf16Codepoint { .. } 367 | Utf32Codepoint { .. } 368 ) 369} 370 371fn err<A>(error: ErrorType, location: SrcSpan) -> Result<A, Error> { 372 Err(Error { location, error }) 373} 374 375#[derive(Debug)] 376pub struct Error { 377 pub location: SrcSpan, 378 pub error: ErrorType, 379} 380 381#[derive(Debug, PartialEq, Eq, Clone)] 382pub enum ErrorType { 383 ConflictingEndiannessOptions { 384 existing_endianness: EcoString, 385 }, 386 ConflictingSignednessOptions { 387 existing_signed: EcoString, 388 }, 389 ConflictingSizeOptions, 390 ConflictingTypeOptions { 391 existing_type: EcoString, 392 }, 393 ConflictingUnitOptions, 394 FloatWithSize, 395 InvalidEndianness, 396 OptionNotAllowedInValue, 397 SegmentMustHaveSize, 398 SignednessUsedOnNonInt { 399 type_: EcoString, 400 }, 401 TypeDoesNotAllowSize { 402 type_: EcoString, 403 }, 404 TypeDoesNotAllowUnit { 405 type_: EcoString, 406 }, 407 UnitMustHaveSize, 408 VariableUtfSegmentInPattern, 409 ConstantSizeNotPositive, 410 OptionNotSupportedForTarget { 411 target: Target, 412 option: UnsupportedOption, 413 }, 414} 415 416#[derive(Debug, PartialEq, Eq, Clone, Copy)] 417pub enum UnsupportedOption { 418 UtfCodepointPattern, 419 NativeEndianness, 420}