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