1use std::collections::{BTreeMap, BTreeSet};
17
18use lex_vcs::{default_import_alias, OpLog, OperationKind};
19
20use crate::store::{Store, StoreError};
21
22#[derive(Debug, Default, Clone)]
25pub struct PackageHead {
26 pub map: BTreeMap<String, String>,
28 pub sig_files: BTreeMap<String, String>,
30 pub flat_imports: BTreeMap<String, String>,
32 pub file_imports: BTreeMap<String, BTreeMap<String, String>>,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum RenderedSource {
39 Single(String),
40 Multi(BTreeMap<String, String>),
41}
42
43pub fn package_head_at_op(store: &Store, head_op: &str) -> Result<PackageHead, StoreError> {
47 let log = OpLog::open(store.root())?;
48 let mut head = PackageHead::default();
49 for rec in log.walk_forward(&head_op.to_string(), None)? {
50 crate::branches::apply_transition(&mut head.map, &rec.produces);
51 match &rec.op.kind {
52 OperationKind::AddFunction { sig_id, in_file: Some(f), .. }
53 | OperationKind::AddType { sig_id, in_file: Some(f), .. } => {
54 head.sig_files.insert(sig_id.clone(), f.clone());
55 }
56 OperationKind::AddImport { in_file, module, alias } => {
57 let alias = alias.clone().unwrap_or_else(|| default_import_alias(module));
58 head.flat_imports.insert(module.clone(), alias.clone());
59 head.file_imports.entry(in_file.clone()).or_default().insert(module.clone(), alias);
60 }
61 OperationKind::RemoveImport { in_file, module } => {
62 head.flat_imports.remove(module);
63 if let Some(m) = head.file_imports.get_mut(in_file) {
64 m.remove(module);
65 }
66 }
67 OperationKind::RenameSymbol { from, to, .. } => {
68 if let Some(f) = head.sig_files.remove(from) {
69 head.sig_files.insert(to.clone(), f);
70 }
71 }
72 _ => {}
73 }
74 }
75 Ok(head)
76}
77
78pub fn render_source(store: &Store, head: &PackageHead) -> Result<RenderedSource, StoreError> {
81 let multi = !head.map.is_empty() && head.map.keys().all(|s| head.sig_files.contains_key(s));
82 if multi {
83 Ok(RenderedSource::Multi(render_multifile(store, head)?))
84 } else {
85 Ok(RenderedSource::Single(render_singlefile(store, head)?))
86 }
87}
88
89pub fn module_record_at_op(store: &Store, head_op: &str) -> Result<lex_types::Ty, StoreError> {
106 let stages = demangled_head_stages(store, head_op)?;
107 let types = lex_types::check_program(&stages).map_err(StoreError::TypeError)?;
108 let fields = types
110 .fn_signatures
111 .iter()
112 .map(|(name, scheme)| (name.clone(), scheme.ty.clone()));
113 Ok(lex_types::module_record_from_fields(fields))
114}
115
116pub(crate) fn demangled_head_stages(
132 store: &Store,
133 head_op: &str,
134) -> Result<Vec<lex_ast::Stage>, StoreError> {
135 let head = package_head_at_op(store, head_op)?;
136 let distinct_files: BTreeSet<&String> = head.sig_files.values().collect();
137 if distinct_files.len() > 1 {
138 return Err(StoreError::UnsupportedMultiModuleDependency);
139 }
140 let pairs: Vec<(String, String)> =
141 head.map.iter().map(|(s, st)| (s.clone(), st.clone())).collect();
142 let mut decls: Vec<lex_ast::Stage> = Vec::new();
143 for ast in store.get_asts_for_sigs_bulk(&pairs) {
144 decls.push(ast?);
145 }
146 let own_prefix = decls.iter().find_map(stage_prefix).unwrap_or_default();
148 let mut bound_locals = BTreeSet::new();
149 for s in &decls {
150 collect_bound_locals(s, &mut bound_locals);
151 }
152 let mut rw = FileRewrite {
153 own_prefix: &own_prefix,
154 own_file: "",
155 prefix_to_file: &BTreeMap::new(),
156 bound_locals: &bound_locals,
157 local_imports: BTreeMap::new(),
158 };
159 for s in &mut decls {
160 rw.rewrite_stage(s);
161 }
162 let mut stages: Vec<lex_ast::Stage> = Vec::new();
163 for (reference, alias) in &head.flat_imports {
164 stages.push(lex_ast::Stage::Import(lex_ast::Import {
165 reference: reference.clone(),
166 alias: alias.clone(),
167 }));
168 }
169 stages.extend(decls);
170 Ok(stages)
171}
172
173fn render_singlefile(store: &Store, head: &PackageHead) -> Result<String, StoreError> {
177 let pairs: Vec<(String, String)> = head.map.iter().map(|(s, st)| (s.clone(), st.clone())).collect();
178 let mut decls: Vec<lex_ast::Stage> = Vec::new();
179 for ast in store.get_asts_for_sigs_bulk(&pairs) {
180 decls.push(ast?);
181 }
182 let own_prefix = decls.iter().find_map(stage_prefix).unwrap_or_default();
191 let mut bound_locals = BTreeSet::new();
192 for s in &decls {
193 collect_bound_locals(s, &mut bound_locals);
194 }
195 let mut rw = FileRewrite {
196 own_prefix: &own_prefix,
197 own_file: "",
198 prefix_to_file: &BTreeMap::new(),
199 bound_locals: &bound_locals,
200 local_imports: BTreeMap::new(),
201 };
202 for s in &mut decls {
203 rw.rewrite_stage(s);
204 }
205
206 let mut stages: Vec<lex_ast::Stage> = Vec::new();
207 for (reference, alias) in &head.flat_imports {
208 stages.push(lex_ast::Stage::Import(lex_ast::Import {
209 reference: reference.clone(),
210 alias: alias.clone(),
211 }));
212 }
213 stages.extend(decls);
214 Ok(lex_ast::print_stages(&stages))
215}
216
217fn render_multifile(store: &Store, head: &PackageHead) -> Result<BTreeMap<String, String>, StoreError> {
219 let mut prefix_to_file: BTreeMap<String, String> = BTreeMap::new();
220 let mut by_file: BTreeMap<String, Vec<lex_ast::Stage>> = BTreeMap::new();
221 let pairs: Vec<(String, String)> = head.map.iter().map(|(s, st)| (s.clone(), st.clone())).collect();
225 let asts = store.get_asts_for_sigs_bulk(&pairs);
226 for ((sig, _), ast) in pairs.iter().zip(asts) {
227 let stage = ast?;
228 let file = head.sig_files.get(sig).cloned().unwrap_or_default();
229 if let Some(prefix) = stage_prefix(&stage) {
230 prefix_to_file.insert(prefix, file.clone());
231 }
232 by_file.entry(file).or_default().push(stage);
233 }
234
235 let mut out: BTreeMap<String, String> = BTreeMap::new();
236 for (file, stages) in &by_file {
237 let own_prefix = stages.iter().find_map(stage_prefix).unwrap_or_default();
238 let mut bound_locals = BTreeSet::new();
239 for s in stages {
240 collect_bound_locals(s, &mut bound_locals);
241 }
242 let mut rw = FileRewrite {
243 own_prefix: &own_prefix,
244 own_file: file,
245 prefix_to_file: &prefix_to_file,
246 bound_locals: &bound_locals,
247 local_imports: BTreeMap::new(),
248 };
249 let rewritten: Vec<lex_ast::Stage> = stages
250 .iter()
251 .cloned()
252 .map(|mut s| {
253 rw.rewrite_stage(&mut s);
254 s
255 })
256 .collect();
257
258 let mut imports: BTreeMap<String, String> = head.file_imports.get(file).cloned().unwrap_or_default();
259 imports.extend(rw.local_imports);
260
261 let mut out_stages: Vec<lex_ast::Stage> = Vec::new();
262 for (reference, alias) in &imports {
263 out_stages.push(lex_ast::Stage::Import(lex_ast::Import {
264 reference: reference.clone(),
265 alias: alias.clone(),
266 }));
267 }
268 out_stages.extend(rewritten);
269 out.insert(file.clone(), lex_ast::print_stages(&out_stages));
270 }
271 Ok(out)
272}
273
274fn is_mangle_prefix(q: &str) -> bool {
278 match q.rsplit_once('_') {
279 Some((stem, hex)) => {
280 !stem.is_empty()
281 && hex.len() >= 6
282 && hex.chars().all(|c| c.is_ascii_hexdigit())
283 }
284 None => false,
285 }
286}
287
288fn stage_prefix(s: &lex_ast::Stage) -> Option<String> {
291 let name = match s {
292 lex_ast::Stage::FnDecl(fd) => &fd.name,
293 lex_ast::Stage::TypeDecl(td) => &td.name,
294 lex_ast::Stage::Import(_) => return None,
295 };
296 name.split_once('.').map(|(p, _)| p.to_string())
297}
298
299fn relative_import(from: &str, to: &str) -> (String, String) {
301 let from_dir: Vec<&str> = from
302 .rsplit_once('/')
303 .map(|(d, _)| d)
304 .unwrap_or("")
305 .split('/')
306 .filter(|s| !s.is_empty())
307 .collect();
308 let to_noext = to.strip_suffix(".lex").unwrap_or(to);
309 let to_parts: Vec<&str> = to_noext.split('/').filter(|s| !s.is_empty()).collect();
310 let alias = to_parts.last().copied().unwrap_or("mod").to_string();
311 let mut i = 0;
312 while i < from_dir.len() && i + 1 < to_parts.len() && from_dir[i] == to_parts[i] {
313 i += 1;
314 }
315 let ups = from_dir.len() - i;
316 let mut rel = String::new();
317 if ups == 0 {
318 rel.push_str("./");
319 } else {
320 for _ in 0..ups {
321 rel.push_str("../");
322 }
323 }
324 rel.push_str(&to_parts[i..].join("/"));
325 (rel, alias)
326}
327
328fn collect_bound_locals(s: &lex_ast::Stage, out: &mut BTreeSet<String>) {
329 if let lex_ast::Stage::FnDecl(fd) = s {
330 for p in &fd.params {
331 out.insert(p.name.clone());
332 }
333 collect_expr_locals(&fd.body, out);
334 for ex in &fd.examples {
335 for a in &ex.args {
336 collect_expr_locals(a, out);
337 }
338 collect_expr_locals(&ex.expected, out);
339 }
340 }
341}
342
343fn collect_expr_locals(e: &lex_ast::CExpr, out: &mut BTreeSet<String>) {
344 use lex_ast::CExpr::*;
345 match e {
346 Let { name, value, body, .. } => {
347 out.insert(name.clone());
348 collect_expr_locals(value, out);
349 collect_expr_locals(body, out);
350 }
351 Lambda { params, body, .. } => {
352 for p in params {
353 out.insert(p.name.clone());
354 }
355 collect_expr_locals(body, out);
356 }
357 Match { scrutinee, arms } => {
358 collect_expr_locals(scrutinee, out);
359 for arm in arms {
360 collect_pattern_locals(&arm.pattern, out);
361 collect_expr_locals(&arm.body, out);
362 }
363 }
364 Call { callee, args } => {
365 collect_expr_locals(callee, out);
366 for a in args {
367 collect_expr_locals(a, out);
368 }
369 }
370 Block { statements, result } => {
371 for s in statements {
372 collect_expr_locals(s, out);
373 }
374 collect_expr_locals(result, out);
375 }
376 Constructor { args, .. } => {
377 for a in args {
378 collect_expr_locals(a, out);
379 }
380 }
381 RecordLit { fields } => {
382 for f in fields {
383 collect_expr_locals(&f.value, out);
384 }
385 }
386 TupleLit { items } | ListLit { items } => {
387 for i in items {
388 collect_expr_locals(i, out);
389 }
390 }
391 FieldAccess { value, .. } => collect_expr_locals(value, out),
392 BinOp { lhs, rhs, .. } => {
393 collect_expr_locals(lhs, out);
394 collect_expr_locals(rhs, out);
395 }
396 UnaryOp { expr, .. } => collect_expr_locals(expr, out),
397 Return { value } => collect_expr_locals(value, out),
398 Var { .. } | Literal { .. } => {}
399 }
400}
401
402fn collect_pattern_locals(p: &lex_ast::Pattern, out: &mut BTreeSet<String>) {
403 use lex_ast::Pattern::*;
404 match p {
405 PVar { name } => {
406 out.insert(name.clone());
407 }
408 PConstructor { args, .. } => {
409 for a in args {
410 collect_pattern_locals(a, out);
411 }
412 }
413 PRecord { fields } => {
414 for f in fields {
415 collect_pattern_locals(&f.pattern, out);
416 }
417 }
418 PTuple { items } => {
419 for i in items {
420 collect_pattern_locals(i, out);
421 }
422 }
423 PLiteral { .. } | PWild => {}
424 }
425}
426
427struct FileRewrite<'a> {
428 own_prefix: &'a str,
429 own_file: &'a str,
430 prefix_to_file: &'a BTreeMap<String, String>,
431 bound_locals: &'a BTreeSet<String>,
432 local_imports: BTreeMap<String, String>,
433}
434
435impl FileRewrite<'_> {
436 fn rename(&mut self, name: &str) -> String {
440 if let Some(rest) = name.strip_prefix(&format!("{}.", self.own_prefix)) {
441 return rest.to_string();
442 }
443 if let Some((q, rest)) = name.split_once('.') {
444 if q != self.own_prefix {
445 if let Some(other_file) = self.prefix_to_file.get(q) {
446 let (import_ref, stem) = relative_import(self.own_file, other_file);
447 let alias = if self.bound_locals.contains(&stem) {
448 q.to_string()
449 } else {
450 stem
451 };
452 self.local_imports.insert(import_ref, alias.clone());
453 return format!("{alias}.{rest}");
454 }
455 if is_mangle_prefix(q) {
463 return rest.to_string();
464 }
465 }
466 }
467 name.to_string()
468 }
469
470 fn rewrite_stage(&mut self, s: &mut lex_ast::Stage) {
471 match s {
472 lex_ast::Stage::FnDecl(fd) => {
473 fd.name = self.rename(&fd.name);
474 for p in &mut fd.params {
475 self.rewrite_type(&mut p.ty);
476 }
477 self.rewrite_type(&mut fd.return_type);
478 self.rewrite_expr(&mut fd.body);
479 for ex in &mut fd.examples {
480 for a in &mut ex.args {
481 self.rewrite_expr(a);
482 }
483 self.rewrite_expr(&mut ex.expected);
484 }
485 }
486 lex_ast::Stage::TypeDecl(td) => {
487 td.name = self.rename(&td.name);
488 self.rewrite_type(&mut td.definition);
489 }
490 lex_ast::Stage::Import(_) => {}
491 }
492 }
493
494 fn rewrite_expr(&mut self, e: &mut lex_ast::CExpr) {
495 use lex_ast::CExpr::*;
496 match e {
497 Var { name } => *name = self.rename(name),
498 Literal { .. } => {}
499 Call { callee, args } => {
500 self.rewrite_expr(callee);
501 for a in args {
502 self.rewrite_expr(a);
503 }
504 }
505 Let { value, body, ty, .. } => {
506 if let Some(t) = ty {
507 self.rewrite_type(t);
508 }
509 self.rewrite_expr(value);
510 self.rewrite_expr(body);
511 }
512 Match { scrutinee, arms } => {
513 self.rewrite_expr(scrutinee);
514 for arm in arms {
515 self.rewrite_expr(&mut arm.body);
516 }
517 }
518 Block { statements, result } => {
519 for s in statements {
520 self.rewrite_expr(s);
521 }
522 self.rewrite_expr(result);
523 }
524 Constructor { args, .. } => {
525 for a in args {
526 self.rewrite_expr(a);
527 }
528 }
529 RecordLit { fields } => {
530 for f in fields {
531 self.rewrite_expr(&mut f.value);
532 }
533 }
534 TupleLit { items } | ListLit { items } => {
535 for i in items {
536 self.rewrite_expr(i);
537 }
538 }
539 FieldAccess { value, .. } => self.rewrite_expr(value),
540 Lambda { params, return_type, body, .. } => {
541 for p in params {
542 self.rewrite_type(&mut p.ty);
543 }
544 self.rewrite_type(return_type);
545 self.rewrite_expr(body);
546 }
547 BinOp { lhs, rhs, .. } => {
548 self.rewrite_expr(lhs);
549 self.rewrite_expr(rhs);
550 }
551 UnaryOp { expr, .. } => self.rewrite_expr(expr),
552 Return { value } => self.rewrite_expr(value),
553 }
554 }
555
556 fn rewrite_type(&mut self, t: &mut lex_ast::TypeExpr) {
557 use lex_ast::TypeExpr::*;
558 match t {
559 Named { name, args } => {
560 *name = self.rename(name);
561 for a in args {
562 self.rewrite_type(a);
563 }
564 }
565 Record { fields } => {
566 for f in fields {
567 self.rewrite_type(&mut f.ty);
568 }
569 }
570 Tuple { items } => {
571 for i in items {
572 self.rewrite_type(i);
573 }
574 }
575 Function { params, ret, .. } => {
576 for p in params {
577 self.rewrite_type(p);
578 }
579 self.rewrite_type(ret);
580 }
581 Union { variants } => {
582 for v in variants {
583 if let Some(pl) = &mut v.payload {
584 self.rewrite_type(pl);
585 }
586 }
587 }
588 RecordWithSpreads { spreads, fields } => {
589 for s in spreads {
590 *s = self.rename(s);
591 }
592 for f in fields {
593 self.rewrite_type(&mut f.ty);
594 }
595 }
596 Refined { base, predicate, .. } => {
597 self.rewrite_type(base);
598 self.rewrite_expr(predicate);
599 }
600 }
601 }
602}
603
604#[cfg(test)]
605mod prefix_tests {
606 use super::is_mangle_prefix;
607
608 #[test]
609 fn recognizes_mangle_prefixes_not_stdlib_aliases() {
610 assert!(is_mangle_prefix("lib_56ce0533"));
612 assert!(is_mangle_prefix("schema_a1b2c3"));
613 assert!(!is_mangle_prefix("int"));
615 assert!(!is_mangle_prefix("str"));
616 assert!(!is_mangle_prefix("map_reduce")); assert!(!is_mangle_prefix("nt"));
618 assert!(!is_mangle_prefix("lib_xyz")); }
620}