Fork of daniellemaywood.uk/gleam — Wasm codegen work
9.3 kB
334 lines
1use ecow::EcoString;
2
3use crate::ast::{BitArrayOption, SrcSpan};
4use crate::type_::Type;
5use std::sync::Arc;
6
7//
8// Public Interface
9//
10
11pub fn type_options_for_value<TypedValue>(
12 input_options: &[BitArrayOption<TypedValue>],
13) -> Result<Arc<Type>, Error>
14where
15 TypedValue: GetLiteralValue,
16{
17 type_options(input_options, true, false)
18}
19
20pub fn type_options_for_pattern<TypedValue>(
21 input_options: &[BitArrayOption<TypedValue>],
22 must_have_size: bool,
23) -> Result<Arc<Type>, Error>
24where
25 TypedValue: GetLiteralValue,
26{
27 type_options(input_options, false, must_have_size)
28}
29
30struct SegmentOptionCategories<'a, T> {
31 typ: Option<&'a BitArrayOption<T>>,
32 signed: Option<&'a BitArrayOption<T>>,
33 endian: Option<&'a BitArrayOption<T>>,
34 unit: Option<&'a BitArrayOption<T>>,
35 size: Option<&'a BitArrayOption<T>>,
36}
37
38impl<T> SegmentOptionCategories<'_, T> {
39 fn new() -> Self {
40 SegmentOptionCategories {
41 typ: None,
42 signed: None,
43 endian: None,
44 unit: None,
45 size: None,
46 }
47 }
48
49 fn segment_type(&self) -> Arc<Type> {
50 use BitArrayOption::*;
51 let default = Int {
52 location: SrcSpan::default(),
53 };
54
55 match self.typ.unwrap_or(&default) {
56 Int { .. } => crate::type_::int(),
57 Float { .. } => crate::type_::float(),
58 Utf8 { .. } | Utf16 { .. } | Utf32 { .. } => crate::type_::string(),
59 Bytes { .. } | Bits { .. } => crate::type_::bits(),
60 Utf8Codepoint { .. } | Utf16Codepoint { .. } | Utf32Codepoint { .. } => {
61 crate::type_::utf_codepoint()
62 }
63
64 Signed { .. }
65 | Unsigned { .. }
66 | Big { .. }
67 | Little { .. }
68 | Native { .. }
69 | Size { .. }
70 | Unit { .. } => panic!("Tried to type a non type kind BitArray option."),
71 }
72 }
73}
74
75fn type_options<TypedValue>(
76 input_options: &[BitArrayOption<TypedValue>],
77 value_mode: bool,
78 must_have_size: bool,
79) -> Result<Arc<Type>, Error>
80where
81 TypedValue: GetLiteralValue,
82{
83 use BitArrayOption::*;
84
85 let mut categories = SegmentOptionCategories::new();
86 // Basic category checking
87 for option in input_options {
88 match option {
89 Bytes { .. }
90 | Int { .. }
91 | Float { .. }
92 | Bits { .. }
93 | Utf8 { .. }
94 | Utf16 { .. }
95 | Utf32 { .. }
96 | Utf8Codepoint { .. }
97 | Utf16Codepoint { .. }
98 | Utf32Codepoint { .. } => {
99 if let Some(previous) = categories.typ {
100 return err(
101 ErrorType::ConflictingTypeOptions {
102 existing_type: previous.label(),
103 },
104 option.location(),
105 );
106 } else {
107 categories.typ = Some(option);
108 }
109 }
110
111 Signed { .. } | Unsigned { .. } => {
112 if let Some(previous) = categories.signed {
113 return err(
114 ErrorType::ConflictingSignednessOptions {
115 existing_signed: previous.label(),
116 },
117 option.location(),
118 );
119 } else {
120 categories.signed = Some(option);
121 }
122 }
123
124 Big { .. } | Little { .. } | Native { .. } => {
125 if let Some(previous) = categories.endian {
126 return err(
127 ErrorType::ConflictingEndiannessOptions {
128 existing_endianness: previous.label(),
129 },
130 option.location(),
131 );
132 } else {
133 categories.endian = Some(option);
134 }
135 }
136
137 Size { .. } => {
138 if categories.size.is_some() {
139 return err(ErrorType::ConflictingSizeOptions, option.location());
140 } else {
141 categories.size = Some(option);
142 }
143 }
144
145 Unit { .. } => {
146 if categories.unit.is_some() {
147 return err(ErrorType::ConflictingUnitOptions, option.location());
148 } else {
149 categories.unit = Some(option);
150 }
151 }
152 };
153 }
154
155 // Some options are not allowed in value mode
156 if value_mode {
157 match categories {
158 SegmentOptionCategories {
159 signed: Some(opt), ..
160 }
161 | SegmentOptionCategories {
162 typ: Some(opt @ Bytes { .. }),
163 ..
164 } => return err(ErrorType::OptionNotAllowedInValue, opt.location()),
165 _ => (),
166 }
167 }
168
169 // All but the last segment in a pattern must have an exact size
170 if must_have_size {
171 if let SegmentOptionCategories {
172 typ: Some(opt @ (Bytes { .. } | Bits { .. })),
173 size: None,
174 ..
175 } = categories
176 {
177 return err(ErrorType::SegmentMustHaveSize, opt.location());
178 }
179 }
180
181 // Endianness is only valid for int, utf6, utf32 and float
182 match categories {
183 SegmentOptionCategories {
184 typ: None | Some(Int { .. } | Utf16 { .. } | Utf32 { .. } | Float { .. }),
185 ..
186 } => {}
187
188 SegmentOptionCategories {
189 endian: Some(endian),
190 ..
191 } => return err(ErrorType::InvalidEndianness, endian.location()),
192
193 _ => {}
194 }
195
196 // signed and unsigned can only be used with int types
197 match categories {
198 SegmentOptionCategories {
199 typ: None | Some(Int { .. }),
200 ..
201 } => {}
202
203 SegmentOptionCategories {
204 typ: Some(opt),
205 signed: Some(sign),
206 ..
207 } => {
208 return err(
209 ErrorType::SignednessUsedOnNonInt { typ: opt.label() },
210 sign.location(),
211 );
212 }
213
214 _ => {}
215 }
216
217 // utf8, utf16, utf32 exclude unit and size
218 match categories {
219 SegmentOptionCategories {
220 typ: Some(typ),
221 unit: Some(_),
222 ..
223 } if is_unicode(typ) => {
224 return err(
225 ErrorType::TypeDoesNotAllowUnit { typ: typ.label() },
226 typ.location(),
227 );
228 }
229
230 SegmentOptionCategories {
231 typ: Some(typ),
232 size: Some(_),
233 ..
234 } if is_unicode(typ) => {
235 return err(
236 ErrorType::TypeDoesNotAllowSize { typ: typ.label() },
237 typ.location(),
238 );
239 }
240
241 _ => {}
242 }
243
244 // if unit specified, size must be specified
245 if let SegmentOptionCategories {
246 unit: Some(unit),
247 size: None,
248 ..
249 } = categories
250 {
251 return err(ErrorType::UnitMustHaveSize, unit.location());
252 }
253
254 // float only 16/32/64
255 if let SegmentOptionCategories {
256 typ: Some(Float { .. }),
257 size: Some(size),
258 ..
259 } = categories
260 {
261 if let Some(abox) = size.value() {
262 match abox.as_int_literal() {
263 None => (),
264 Some(16) => (),
265 Some(32) => (),
266 Some(64) => (),
267 _ => return err(ErrorType::FloatWithSize, size.location()),
268 }
269 }
270 }
271
272 Ok(categories.segment_type())
273}
274
275pub trait GetLiteralValue {
276 fn as_int_literal(&self) -> Option<i64>;
277}
278
279impl GetLiteralValue for crate::ast::Pattern<Arc<Type>> {
280 fn as_int_literal(&self) -> Option<i64> {
281 match self {
282 crate::ast::Pattern::Int { value, .. } => {
283 if let Ok(val) = value.parse::<i64>() {
284 return Some(val);
285 }
286 }
287 crate::ast::Pattern::VarUsage { .. } => return None,
288 _ => (),
289 }
290 None
291 }
292}
293
294fn is_unicode<T>(opt: &BitArrayOption<T>) -> bool {
295 use BitArrayOption::*;
296
297 matches!(
298 opt,
299 Utf8 { .. }
300 | Utf16 { .. }
301 | Utf32 { .. }
302 | Utf8Codepoint { .. }
303 | Utf16Codepoint { .. }
304 | Utf32Codepoint { .. }
305 )
306}
307
308fn err<A>(error: ErrorType, location: SrcSpan) -> Result<A, Error> {
309 Err(Error { location, error })
310}
311
312#[derive(Debug)]
313pub struct Error {
314 pub location: SrcSpan,
315 pub error: ErrorType,
316}
317
318#[derive(Debug, PartialEq, Eq, Clone)]
319pub enum ErrorType {
320 ConflictingEndiannessOptions { existing_endianness: EcoString },
321 ConflictingSignednessOptions { existing_signed: EcoString },
322 ConflictingSizeOptions,
323 ConflictingTypeOptions { existing_type: EcoString },
324 ConflictingUnitOptions,
325 FloatWithSize,
326 InvalidEndianness,
327 OptionNotAllowedInValue,
328 SegmentMustHaveSize,
329 SignednessUsedOnNonInt { typ: EcoString },
330 TypeDoesNotAllowSize { typ: EcoString },
331 TypeDoesNotAllowUnit { typ: EcoString },
332 UnitMustHaveSize,
333 VariableUtfSegmentInPattern,
334}