Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2021 The Gleam contributors 3 4use crate::{ 5 analyse::TargetSupport, 6 build::{Origin, Target}, 7 config::PackageConfig, 8 inline, 9 javascript::*, 10 uid::UniqueIdGenerator, 11 warning::{TypeWarningEmitter, WarningEmitter}, 12}; 13use camino::{Utf8Path, Utf8PathBuf}; 14use lsp_types::Position; 15 16mod assert; 17mod assignments; 18mod bit_arrays; 19mod blocks; 20mod bools; 21mod case; 22mod case_clause_guards; 23mod consts; 24mod custom_types; 25mod echo; 26mod externals; 27mod functions; 28mod generics; 29mod inlining; 30mod lists; 31mod modules; 32mod numbers; 33mod panic; 34mod prelude; 35mod records; 36mod recursion; 37mod results; 38mod sourcemaps; 39mod strings; 40mod todo; 41mod tuples; 42mod type_alias; 43mod use_; 44 45pub static CURRENT_PACKAGE: &str = "thepackage"; 46 47#[macro_export] 48macro_rules! assert_js { 49 ($(($name:literal, $module_src:literal)),+, $src:literal $(,)?) => { 50 let compiled = 51 $crate::javascript::tests::compile_js($src, vec![$(($crate::javascript::tests::CURRENT_PACKAGE, $name, $module_src)),*]); 52 let mut output = String::from("----- SOURCE CODE\n"); 53 for (name, src) in [$(($name, $module_src)),*] { 54 output.push_str(&format!("-- {name}.gleam\n{src}\n\n")); 55 } 56 output.push_str(&format!("-- main.gleam\n{}\n\n----- COMPILED JAVASCRIPT\n{compiled}", $src)); 57 insta::assert_snapshot!(insta::internals::AutoName, output, $src); 58 }; 59 60 ($(($dep_package:expr, $dep_name:expr, $dep_src:expr)),+, $src:literal $(,)?) => {{ 61 let compiled = 62 $crate::javascript::tests::compile_js($src, vec![$(($dep_package, $dep_name, $dep_src)),*]); 63 let output = format!( 64 "----- SOURCE CODE\n{}\n\n----- COMPILED JAVASCRIPT\n{}", 65 $src, compiled 66 ); 67 insta::assert_snapshot!(insta::internals::AutoName, output, $src); 68 }}; 69 70 (($dep_package:expr, $dep_name:expr, $dep_src:expr), $src:expr, $js:expr $(,)?) => {{ 71 let output = 72 $crate::javascript::tests::compile_js($src, Some(($dep_package, $dep_name, $dep_src))); 73 assert_eq!(($src, output), ($src, $js.to_string())); 74 }}; 75 76 ($src:expr $(,)?) => {{ 77 let compiled = 78 $crate::javascript::tests::compile_js($src, vec![]); 79 let output = format!( 80 "----- SOURCE CODE\n{}\n\n----- COMPILED JAVASCRIPT\n{}", 81 $src, compiled 82 ); 83 insta::assert_snapshot!(insta::internals::AutoName, output, $src); 84 }}; 85 86 ($src:expr, $js:expr $(,)?) => {{ 87 let output = 88 $crate::javascript::tests::compile_js($src, vec![]); 89 assert_eq!(($src, output), ($src, $js.to_string())); 90 }}; 91} 92 93#[macro_export] 94macro_rules! assert_source_map { 95 ($src:expr $(,)?) => {{ 96 let (compiled, source_map) = 97 $crate::javascript::tests::compile_js_with_source_map($src, vec![]); 98 99 let output = format!( 100 "----- SOURCE CODE\n{}\n\n----- COMPILED JAVASCRIPT\n{}\n\n----- SOURCE MAP\n{}", 101 $crate::javascript::tests::append_line_numbers($src), 102 $crate::javascript::tests::append_line_numbers(&compiled), 103 $crate::javascript::tests::source_map_to_string($src, &compiled, source_map) 104 ); 105 insta::assert_snapshot!(insta::internals::AutoName, output, $src); 106 }}; 107} 108 109#[macro_export] 110macro_rules! assert_ts_def { 111 (($dep_1_package:expr, $dep_1_name:expr, $dep_1_src:expr), ($dep_2_package:expr, $dep_2_name:expr, $dep_2_src:expr), $src:expr $(,)?) => {{ 112 let compiled = $crate::javascript::tests::compile_ts( 113 $src, 114 vec![ 115 ($dep_1_package, $dep_1_name, $dep_1_src), 116 ($dep_2_package, $dep_2_name, $dep_2_src), 117 ], 118 ); 119 let output = format!( 120 "----- SOURCE CODE\n{}\n\n----- TYPESCRIPT DEFINITIONS\n{}", 121 $src, compiled 122 ); 123 insta::assert_snapshot!(insta::internals::AutoName, output, $src); 124 }}; 125 126 (($dep_package:expr, $dep_name:expr, $dep_src:expr), $src:expr $(,)?) => {{ 127 let compiled = 128 $crate::javascript::tests::compile_ts($src, vec![($dep_package, $dep_name, $dep_src)]); 129 let output = format!( 130 "----- SOURCE CODE\n{}\n\n----- TYPESCRIPT DEFINITIONS\n{}", 131 $src, compiled 132 ); 133 insta::assert_snapshot!(insta::internals::AutoName, output, $src); 134 }}; 135 136 ($src:expr $(,)?) => {{ 137 let compiled = $crate::javascript::tests::compile_ts($src, vec![]); 138 let output = format!( 139 "----- SOURCE CODE\n{}\n\n----- TYPESCRIPT DEFINITIONS\n{}", 140 $src, compiled 141 ); 142 insta::assert_snapshot!(insta::internals::AutoName, output, $src); 143 }}; 144} 145 146pub fn compile(src: &str, deps: Vec<(&str, &str, &str)>) -> TypedModule { 147 let mut modules = im::HashMap::new(); 148 let ids = UniqueIdGenerator::new(); 149 // DUPE: preludeinsertion 150 // TODO: Currently we do this here and also in the tests. It would be better 151 // to have one place where we create all this required state for use in each 152 // place. 153 let _ = modules.insert( 154 PRELUDE_MODULE_NAME.into(), 155 crate::type_::build_prelude(&ids), 156 ); 157 let mut direct_dependencies = HashMap::from_iter(vec![]); 158 159 deps.iter().for_each(|(dep_package, dep_name, dep_src)| { 160 let mut dep_config = PackageConfig::default(); 161 dep_config.name = (*dep_package).into(); 162 let parsed = crate::parse::parse_module( 163 Utf8PathBuf::from("test/path"), 164 dep_src, 165 &WarningEmitter::null(), 166 ) 167 .expect("dep syntax error"); 168 let mut ast = parsed.module; 169 ast.name = (*dep_name).into(); 170 let line_numbers = LineNumbers::new(dep_src); 171 172 let dep = crate::analyse::ModuleAnalyzerConstructor::<()> { 173 target: Target::JavaScript, 174 ids: &ids, 175 origin: Origin::Src, 176 importable_modules: &modules, 177 warnings: &TypeWarningEmitter::null(), 178 direct_dependencies: &HashMap::new(), 179 dev_dependencies: &HashSet::new(), 180 target_support: TargetSupport::Enforced, 181 package_config: &dep_config, 182 } 183 .infer_module(ast, line_numbers, "".into()) 184 .expect("should successfully infer"); 185 let _ = modules.insert((*dep_name).into(), dep.type_info); 186 let _ = direct_dependencies.insert((*dep_package).into(), ()); 187 }); 188 189 let parsed = 190 crate::parse::parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null()) 191 .expect("syntax error"); 192 let mut ast = parsed.module; 193 ast.name = "my/mod".into(); 194 let line_numbers = LineNumbers::new(src); 195 let mut config = PackageConfig::default(); 196 config.name = "thepackage".into(); 197 198 let module = crate::analyse::ModuleAnalyzerConstructor::<()> { 199 target: Target::JavaScript, 200 ids: &ids, 201 origin: Origin::Src, 202 importable_modules: &modules, 203 warnings: &TypeWarningEmitter::null(), 204 direct_dependencies: &direct_dependencies, 205 dev_dependencies: &HashSet::new(), 206 target_support: TargetSupport::NotEnforced, 207 package_config: &config, 208 } 209 .infer_module(ast, line_numbers, "src/module.gleam".into()) 210 .expect("should successfully infer"); 211 212 inline::module(module, &modules) 213} 214 215pub fn compile_js(src: &str, deps: Vec<(&str, &str, &str)>) -> String { 216 let ast = compile(src, deps); 217 let line_numbers = LineNumbers::new(src); 218 let stdlib_package = StdlibPackage::Present; 219 let (output, _) = module(ModuleConfig { 220 module: &ast, 221 line_numbers: &line_numbers, 222 src: &"".into(), 223 typescript: TypeScriptDeclarations::None, 224 source_map: false, 225 stdlib_package, 226 path: Utf8Path::new("src/module.gleam"), 227 project_root: "project/root".into(), 228 }); 229 230 output.replace( 231 std::include_str!("../../templates/echo.mjs"), 232 "// ...omitted code from `templates/echo.mjs`...", 233 ) 234} 235 236pub fn compile_js_with_source_map(src: &str, deps: Vec<(&str, &str, &str)>) -> (String, SourceMap) { 237 let ast = compile(src, deps); 238 let line_numbers = LineNumbers::new(src); 239 let stdlib_package = StdlibPackage::Present; 240 let (output, source_map) = module(ModuleConfig { 241 module: &ast, 242 line_numbers: &line_numbers, 243 src: &"".into(), 244 typescript: TypeScriptDeclarations::None, 245 source_map: true, 246 stdlib_package, 247 path: Utf8Path::new("src/module.gleam"), 248 project_root: "project/root".into(), 249 }); 250 let source_map = source_map.expect("source map should always be present"); 251 252 let output = output.replace( 253 std::include_str!("../../templates/echo.mjs"), 254 "// ...omitted code from `templates/echo.mjs`...", 255 ); 256 (output, source_map) 257} 258 259pub fn compile_ts(src: &str, deps: Vec<(&str, &str, &str)>) -> String { 260 let ast = compile(src, deps); 261 ts_declaration(&ast) 262} 263 264// Append zero indexed line numbers to the code for easier reading of source maps. 265pub fn append_line_numbers(src: &str) -> String { 266 src.lines() 267 .enumerate() 268 .map(|(line, content)| format!("{} |{}", line, content)) 269 .collect::<Vec<String>>() 270 .join("\n") 271} 272 273// Pretty-print a sourcemap to a string that might be readable by humans. 274pub fn source_map_to_string(src: &str, compiled: &str, source_map: SourceMap) -> String { 275 let mut output = String::new(); 276 output.push_str("File: "); 277 output.push_str(source_map.get_file().expect("source map file")); 278 output.push_str("\nSources:"); 279 for source in source_map.sources() { 280 output.push(' '); 281 output.push_str(source); 282 } 283 output.push_str("\nMappings:\n"); 284 output.push_str("----- \n"); 285 let src_line_numbers = LineNumbers::new(&src); 286 let compiled_line_numbers = LineNumbers::new(&compiled); 287 288 // Since source maps can have multiple tokens for the same source index, we skip over the 289 // intermediate tokens and only keep tokens with a new source index. 290 // 291 // Construct a vector of tuples of (src_line, src_col, dst_line, dst_col) 292 // 293 let mut merged_tokens: Vec<((u32, u32), (u32, u32))> = Vec::new(); 294 // Push a sentinel token at 0, 0 295 merged_tokens.push(((0, 0), (0, 0))); 296 let mut prev_token_src = (0, 0); 297 for token in source_map.tokens() { 298 if prev_token_src == token.get_src() { 299 // Same source index, so extend the current token 300 // This is for making the source map more readable 301 continue; 302 } else { 303 // Different source index, so add the current token to the merged tokens 304 merged_tokens.push((token.get_src(), token.get_dst())); 305 prev_token_src = token.get_src(); 306 }; 307 } 308 let sorted_src_tokens = merged_tokens 309 .iter() 310 .sorted_by_key(|(src, _)| src) 311 .map(|(src, _)| *src) 312 .collect::<Vec<_>>(); 313 let by_dst = merged_tokens 314 .iter() 315 .sorted_by_key(|(_, dst)| dst) 316 .collect::<Vec<_>>(); 317 318 let mut prev_compiled_index = 0; 319 // iterate over all but the last mapping 320 for i in 0..by_dst.len() - 1 { 321 let ((src_line, src_col), (dst_line, dst_col)) = by_dst[i]; 322 let ((_, _), (next_dst_line, next_dst_col)) = by_dst[i + 1]; 323 let (next_src_line, next_src_col) = sorted_src_tokens 324 .iter() 325 .find(|src| **src > (*src_line, *src_col)) 326 .expect("next src token must exist"); 327 328 let src_index = src_line_numbers.byte_index(Position::new(*src_line, *src_col)) as usize; 329 let compiled_index = 330 compiled_line_numbers.byte_index(Position::new(*dst_line, *dst_col)) as usize; 331 let next_src_index = 332 src_line_numbers.byte_index(Position::new(*next_src_line, *next_src_col)) as usize; 333 let next_compiled_index = 334 compiled_line_numbers.byte_index(Position::new(*next_dst_line, *next_dst_col)) as usize; 335 output.push_str(&format!("{}\n", &src[src_index..next_src_index])); 336 output.push_str("\n"); 337 output.push_str(&format!( 338 "{}\n", 339 &compiled[compiled_index..next_compiled_index] 340 )); 341 output.push_str("----- \n"); 342 prev_compiled_index = next_compiled_index; 343 } 344 // Print the last mapping. this needs to be done separately because the last mapping 345 // will likely extend to the rest of the source code. 346 let (src_token, _) = by_dst.last().expect("last mapping must exist"); 347 let src_index: usize = 348 src_line_numbers.byte_index(Position::new(src_token.0, src_token.1)) as usize; 349 // find the next src token after the last mapping if one exists 350 let next_src_token = sorted_src_tokens.iter().find(|src| **src > *src_token); 351 match next_src_token { 352 Some(next_src_token) => { 353 let next_src_index = src_line_numbers 354 .byte_index(Position::new(next_src_token.0, next_src_token.1)) 355 as usize; 356 output.push_str(&format!("{}\n", &src[src_index..next_src_index])); 357 } 358 None => { 359 // If there is no next src token, print the rest of the source and compiled code 360 output.push_str(&format!("{}\n", &src[src_index..])); 361 } 362 } 363 output.push_str("\n"); 364 output.push_str(&format!("{}\n", &compiled[prev_compiled_index..])); 365 output.push_str("----- \n"); 366 output 367}