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