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