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