1pub mod doc;
16pub mod lexer;
17pub mod node;
18pub mod parser;
19pub mod print;
20pub mod security;
21pub mod sexpr;
22
23pub use node::{sym, Head, Lit, Meta, Node, Scope, ScopeSet, Symbol};
24
25use beck_diag::{Diagnostics, FileId};
26
27pub fn module_ident(name: &str) -> String {
39 let stem = name
40 .rsplit(['/', '\\'])
41 .next()
42 .unwrap_or(name)
43 .trim_end_matches(".becki")
44 .trim_end_matches(".beck")
45 .trim_end_matches(".sx");
46 let mut out = String::with_capacity(stem.len() + 1);
47 for (i, c) in stem.chars().enumerate() {
48 match c {
49 'a'..='z' | 'A'..='Z' | '_' => out.push(c),
50 '0'..='9' if i > 0 => out.push(c),
51 '0'..='9' => {
52 out.push('m');
53 out.push(c);
54 }
55 _ => out.push('_'),
56 }
57 }
58 if out.is_empty() {
59 out.push_str("main");
60 }
61 out
62}
63
64pub fn parse_file(file: FileId, name: &str, src: &str, diags: &mut Diagnostics) -> Node {
65 let mut parsed = parse_forms(file, name, src, diags);
66 doc::attach(&mut parsed, &doc::collect(src, doc::marker_for(name)));
69 parsed
70}
71
72fn parse_forms(file: FileId, name: &str, src: &str, diags: &mut Diagnostics) -> Node {
73 let module_name = module_ident(name);
74
75 security::scan(file, src, diags);
78
79 if name.ends_with(".sx") {
80 let forms = sexpr::read_all(file, src, diags);
81 if forms.len() == 1 && forms[0].is_form(sym::MODULE) {
86 return forms.into_iter().next().expect("length checked");
87 }
88 let mut items = vec![Node::sym(&module_name, beck_diag::Span::NONE)];
89 items.extend(forms);
90 Node::form(sym::MODULE, items, beck_diag::Span::new(file, 0..src.len()))
91 } else {
92 parser::parse_module(file, &module_name, src, diags)
93 }
94}
95
96#[cfg(test)]
97mod module_name_tests {
98 use super::module_ident;
99
100 #[test]
101 fn a_printed_module_reads_back_as_that_module_rather_than_as_a_nested_one() {
102 use beck_diag::{Diagnostics, SourceMap};
103 let src = "def f() -> Int:\n return 1\n";
104 let mut map = SourceMap::new();
105 let file = map.add("t.beck", src);
106 let mut d = Diagnostics::new();
107 let parsed = super::parse_file(file, "t.beck", src, &mut d);
108
109 let printed = super::print::to_sexpr_pretty(&parsed);
110 let file2 = map.add("t.sx", printed.clone());
111 let reread = super::parse_file(file2, "t.sx", &printed, &mut d);
112 assert!(!d.has_errors());
113 assert_eq!(
114 super::print::to_sexpr(&parsed),
115 super::print::to_sexpr(&reread),
116 "printed:\n{printed}"
117 );
118 }
119
120 #[test]
121 fn a_file_name_becomes_an_identifier_a_reader_can_read_back() {
122 assert_eq!(module_ident("todo.beck"), "todo");
123 assert_eq!(module_ident("src/orders.beck"), "orders");
124 assert_eq!(module_ident("a/b/c.sx"), "c");
125 assert_eq!(module_ident("orders.becki"), "orders");
126 assert_eq!(module_ident("01-counter.beck"), "m01_counter");
129 assert_eq!(module_ident("my app.beck"), "my_app");
130 assert_eq!(module_ident(""), "main");
131 }
132}