Fork of daniellemaywood.uk/gleam — Wasm codegen work
3.7 kB
118 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2024 The Gleam contributors
3
4use std::sync::OnceLock;
5
6use ecow::{EcoString, eco_format};
7use regex::Regex;
8
9use crate::{
10 ast::{ArgNames, SrcSpan},
11 strings::{to_snake_case, to_upper_camel_case},
12 type_::Problems,
13};
14
15use super::{Error, Named};
16
17static VALID_NAME_PATTERN: OnceLock<Regex> = OnceLock::new();
18
19fn valid_name(name: &EcoString) -> bool {
20 // Some of the internally generated variables (such as `_capture` and `_use0`)
21 // start with underscores, so we allow underscores here.
22 let valid_name_pattern = VALID_NAME_PATTERN
23 .get_or_init(|| Regex::new("^_?[a-z][a-z0-9_]*$").expect("Regex is correct"));
24 valid_name_pattern.is_match(name)
25}
26
27static VALID_DISCARD_PATTERN: OnceLock<Regex> = OnceLock::new();
28
29fn valid_discard_name(name: &EcoString) -> bool {
30 let valid_discard_pattern = VALID_DISCARD_PATTERN
31 .get_or_init(|| Regex::new("^_[a-z0-9_]*$").expect("Regex is correct"));
32 valid_discard_pattern.is_match(name)
33}
34
35static VALID_UPNAME_PATTERN: OnceLock<Regex> = OnceLock::new();
36
37fn valid_upname(name: &EcoString) -> bool {
38 let valid_upname_pattern = VALID_UPNAME_PATTERN
39 .get_or_init(|| Regex::new("^[A-Z][A-Za-z0-9]*$").expect("Regex is correct"));
40 valid_upname_pattern.is_match(name)
41}
42
43pub fn check_name_case(location: SrcSpan, name: &EcoString, kind: Named) -> Result<(), Error> {
44 let valid = match kind {
45 Named::Type | Named::TypeAlias | Named::CustomTypeVariant => valid_upname(name),
46 Named::Variable
47 | Named::TypeVariable
48 | Named::Argument
49 | Named::Label
50 | Named::Constant
51 | Named::Function => valid_name(name),
52 Named::Discard => valid_discard_name(name),
53 };
54
55 if valid {
56 return Ok(());
57 }
58
59 Err(Error::BadName {
60 location,
61 kind,
62 name: name.clone(),
63 })
64}
65
66pub fn correct_name_case(name: &EcoString, kind: Named) -> EcoString {
67 match kind {
68 Named::Type | Named::TypeAlias | Named::CustomTypeVariant => to_upper_camel_case(name),
69 Named::Variable
70 | Named::TypeVariable
71 | Named::Argument
72 | Named::Label
73 | Named::Constant
74 | Named::Function => to_snake_case(name),
75 Named::Discard => eco_format!("_{}", to_snake_case(name)),
76 }
77}
78
79pub fn check_argument_names(names: &ArgNames, problems: &mut Problems) {
80 match names {
81 ArgNames::Discard { name, location } => {
82 if let Err(error) = check_name_case(*location, name, Named::Discard) {
83 problems.error(error);
84 }
85 }
86 ArgNames::LabelledDiscard {
87 label,
88 label_location,
89 name,
90 name_location,
91 } => {
92 if let Err(error) = check_name_case(*label_location, label, Named::Label) {
93 problems.error(error);
94 }
95 if let Err(error) = check_name_case(*name_location, name, Named::Discard) {
96 problems.error(error);
97 }
98 }
99 ArgNames::Named { name, location } => {
100 if let Err(error) = check_name_case(*location, name, Named::Argument) {
101 problems.error(error);
102 }
103 }
104 ArgNames::NamedLabelled {
105 name,
106 name_location,
107 label,
108 label_location,
109 } => {
110 if let Err(error) = check_name_case(*label_location, label, Named::Label) {
111 problems.error(error);
112 }
113 if let Err(error) = check_name_case(*name_location, name, Named::Argument) {
114 problems.error(error);
115 }
116 }
117 }
118}