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