Fork of daniellemaywood.uk/gleam — Wasm codegen work
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}