1use cl_typeck::{
2 entry::Entry,
3 stage::{
4 infer::{engine::InferenceEngine, error::InferenceError, inference::Inference},
5 *,
6 },
7 table::Table,
8 type_expression::TypeExpression,
9};
10
11use cl_ast::{
12 At, Expr, desugar::{type_bubbler::Bubbler, while_else::WhileElseDesugar}, fold::Fold, types::Path, visit::Visit,
13};
14use cl_lexer::Lexer;
15use cl_parser::{Parser, inliner::ModuleInliner};
16use cl_structures::intern::{leaky_interner::LeakyInterner, string_interner::StringInterner};
17use repline::{error::Error as RlError, prebaked::*};
18use std::{
19 error::Error,
20 path::{self, PathBuf},
21 sync::OnceLock,
22};
23
24const STDLIB_DISPLAY_PATH: &str = "stdlib/lib.cl";
26const PREAMBLE: &str = r"
28pub mod std;
29pub use std::preamble::*;
30";
31
32const C_MAIN: &str = C_LISTING;
34const C_RESV: &str = "\x1b[35m";
35const C_CODE: &str = "\x1b[36m";
36const C_BYID: &str = "\x1b[95m";
37const C_ERROR: &str = "\x1b[31m";
38const C_LISTING: &str = "\x1b[38;5;117m";
39
40fn main() -> Result<(), Box<dyn Error>> {
41 let mut prj = Table::default();
42
43 let mut parser = Parser::new(Lexer::new(STDLIB_DISPLAY_PATH.into(), PREAMBLE));
44 let code = match parser.parse(0) {
45 Ok(code) => code,
46 Err(e) => {
47 eprintln!("{STDLIB_DISPLAY_PATH}:{e}");
48 Err(e)?
49 }
50 };
51 let code = inline_modules(code, concat!(env!("CARGO_MANIFEST_DIR"), "/../../stdlib"));
53 let _ = Populator::new(&mut prj).visit_expr(interned(code));
55
56 for arg in std::env::args().skip(1) {
57 import_file(&mut prj, arg)?;
58 }
59
60 resolve_all(&mut prj)?;
61
62 main_menu(&mut prj)?;
63 Ok(())
64}
65
66fn main_menu(prj: &mut Table) -> Result<(), RlError> {
67 banner();
68 read_and(C_MAIN, "mu> ", "? > ", |line| {
69 for line in line.trim().split_ascii_whitespace() {
70 match line {
71 "c" | "code" => enter_code(prj)?,
72 "clear" => clear()?,
73 "dump" => dump_to(prj, &mut std::io::stdout().lock())?,
74 "dump-file" => dump(prj)?,
75 "d" | "desugar" => live_desugar()?,
76 "e" | "exit" => return Ok(Response::Break),
77 "f" | "file" => import_files(prj)?,
78 "i" | "id" => get_by_id(prj)?,
79 "l" | "list" => list_types(prj),
80 "q" | "query" => query_type_expression(prj)?,
81 "r" | "resolve" => resolve_all(prj)?,
82 "s" | "strings" => print_strings(),
83 "a" | "all" => infer_all(prj)?,
84 "t" | "test" => infer_expression(prj)?,
85 "h" | "help" | "" => {
86 println!(
87 "Valid commands are:
88 clear : Clear the screen
89 code (c): Enter code to type-check
90 desugar (d): WIP: Test the experimental desugaring passes
91 file (f): Load files from disk
92 id (i): Get a type by its type ID
93 list (l): List all known types
94 query (q): Query the type system
95 resolve (r): Perform type resolution
96 help (h): Print this list
97 exit (e): Exit the program"
98 );
99 return Ok(Response::Deny);
100 }
101 _ => Err(r#"Invalid command. Type "help" to see the list of valid commands."#)?,
102 }
103 }
104 Ok(Response::Accept)
105 })
106}
107
108fn enter_code(prj: &mut Table) -> Result<(), RlError> {
109 read_and(C_CODE, "cl> ", "? > ", |line| {
110 if line.trim().is_empty() {
111 return Ok(Response::Break);
112 }
113 let code = Parser::new(Lexer::new("".into(), line)).parse(0)?;
114 let code = inline_modules(code, "");
115 let _ = Populator::new(prj).visit_expr(interned(code));
118 Ok(Response::Accept)
119 })
120}
121
122fn live_desugar() -> Result<(), RlError> {
123 read_and(C_RESV, "se> ", "? > ", |line| {
124 let code = Parser::new(Lexer::new("".into(), line)).parse::<At<Expr>>(0)?;
125 println!("Raw, as parsed:\n{C_LISTING}{code}\x1b[0m");
126
127 let code = WhileElseDesugar.fold_at_expr(code).unwrap();
134 println!("WhileElseDesugar\n{C_LISTING}{code}\x1b[0m");
135
136 let code = Bubbler(false).fold_at_expr(code).unwrap();
137 println!("Bubbler\n{C_LISTING}{code}\x1b[0m");
138
139 Ok(Response::Accept)
143 })
144}
145
146fn print_strings() {
147 println!("{}", StringInterner::global());
148}
149
150fn query_type_expression(prj: &mut Table) -> Result<(), RlError> {
151 read_and(C_RESV, "ty> ", "? > ", |line| {
152 if line.trim().is_empty() {
153 return Ok(Response::Break);
154 }
155 let mut p = Parser::new(Lexer::new("".into(), line));
157 let ty: cl_ast::Pat = p.parse(cl_parser::pat::Prec::Alt)?;
158 let id = ty.evaluate(prj, prj.root())?;
160 pretty_handle(id.to_entry(prj))?;
162 Ok(Response::Accept)
163 })
164}
165
166#[allow(dead_code)]
167fn infer_expression(prj: &mut Table) -> Result<(), RlError> {
168 read_and(C_RESV, "ex> ", "!?> ", |line| {
169 if line.trim().is_empty() {
170 return Ok(Response::Break);
171 }
172 let mut p = Parser::new(Lexer::new("".into(), line));
173 let e: Expr = p.parse(0)?;
174 let mut inf = InferenceEngine::new(prj, prj.root());
175 let ty = match interned(e).infer(&mut inf) {
176 Ok(ty) => ty,
177 Err(e) => match e {
178 InferenceError::Mismatch(a, b) => {
179 eprintln!("Mismatched types: {}, {}", prj.entry(a), prj.entry(b));
180 return Ok(Response::Deny);
181 }
182 InferenceError::Recursive(a, b) => {
183 eprintln!("Recursive types: {}, {}", prj.entry(a), prj.entry(b));
184 return Ok(Response::Deny);
185 }
186 e => Err(e)?,
187 },
188 };
189 pretty_handle(ty.to_entry(prj))?;
191 Ok(Response::Accept)
192 })
193}
194
195fn get_by_id(prj: &mut Table) -> Result<(), RlError> {
196 use cl_parser::Parse;
197 use cl_structures::index_map::MapIndex;
198 use cl_typeck::handle::Handle;
199 read_and(C_BYID, "id> ", "? > ", |line| {
200 if line.trim().is_empty() {
201 return Ok(Response::Break);
202 }
203 let mut parser = Parser::new(Lexer::new("".into(), line));
204 let def_id = match Parse::parse(&mut parser, ())? {
205 cl_ast::types::Literal::Int(int, _) => int as _,
206 other => Err(format!("Expected integer, got {other}"))?,
207 };
208 let path = parser
209 .parse::<cl_ast::types::Path>(())
210 .unwrap_or_else(|_| Path::from(""));
211
212 let handle = Handle::from_usize(def_id).to_entry(prj);
213
214 print!(" > {{{C_LISTING}{handle}\x1b[0m}}");
215 if !path.parts.is_empty() {
216 print!("::{path}")
217 }
218 println!();
219
220 if let Some(entry) = handle.nav(&path.parts) {
221 pretty_handle(entry)?;
222 } else {
223 pretty_handle(handle)?;
224 }
226
227 Ok(Response::Accept)
228 })
229}
230
231fn resolve_all(table: &mut Table) -> Result<(), Box<dyn Error>> {
232 for handle in table.handle_iter() {
233 if let Err(error) = handle.to_entry_mut(table).categorize() {
234 eprintln!("{error}");
235 }
236 }
237
238 for handle in implement(table) {
239 eprintln!("Unable to reparent {} ({handle})", handle.to_entry(table))
240 }
241
242 println!("...Resolved!");
243 Ok(())
244}
245
246fn infer_all(table: &mut Table) -> Result<(), Box<dyn Error>> {
247 let mut ie = InferenceEngine::new(table, table.root());
248 let mut results = vec![];
249 EXPR_INTERNER
250 .get_or_init(Default::default)
251 .foreach(|expr| match ie.infer(expr) {
252 Ok(v) => println!("{expr}: {}", ie.entry(v)),
253 Err(e) => results.push(e),
254 });
255
256 println!("...Inferred!");
257 Ok(())
258}
259
260fn list_types(table: &mut Table) {
261 for handle in table.debug_entry_iter() {
262 let id = handle.id();
263 let kind = handle.kind().unwrap();
264 let name = handle.name().unwrap_or("".into());
265 println!("{id:3}: {name:16}| {kind}: {handle}");
266 }
267}
268
269fn import_file(table: &mut Table, path: impl AsRef<std::path::Path>) -> Result<(), Box<dyn Error>> {
270 let Ok(file) = std::fs::read_to_string(path.as_ref()) else {
271 for file in std::fs::read_dir(path)? {
272 println!("{}", file?.path().display())
273 }
274 return Ok(());
275 };
276
277 let mut parser = Parser::new(Lexer::new("".into(), &file));
278 let code = match parser.parse(0) {
279 Ok(code) => inline_modules(
280 code,
281 PathBuf::from(path.as_ref()).parent().unwrap_or("".as_ref()),
282 ),
283 Err(e) => {
284 eprintln!("{C_ERROR}{}:{e}\x1b[0m", path.as_ref().display());
285 return Ok(());
286 }
287 };
288
289 let _ = Populator::new(table).visit_expr(interned(code));
291
292 Ok(())
293}
294
295fn import_files(table: &mut Table) -> Result<(), RlError> {
296 read_and(C_RESV, "fi> ", "? > ", |line| {
297 let line = line.trim();
298 if line.is_empty() {
299 return Ok(Response::Break);
300 }
301 let Ok(file) = std::fs::read_to_string(line) else {
302 for file in std::fs::read_dir(line)? {
303 println!("{}", file?.path().display())
304 }
305 return Ok(Response::Accept);
306 };
307
308 let mut parser = Parser::new(Lexer::new("".into(), &file));
309 let code = match parser.parse(0) {
310 Ok(code) => inline_modules(code, PathBuf::from(line).parent().unwrap_or("".as_ref())),
311 Err(e) => {
312 eprintln!("{C_ERROR}{line}:{e}\x1b[0m");
313 return Ok(Response::Deny);
314 }
315 };
316
317 let _ = Populator::new(table).visit_expr(interned(code));
318
319 println!("...Imported!");
320 Ok(Response::Accept)
321 })
322}
323
324fn pretty_handle(entry: Entry) -> Result<(), std::io::Error> {
325 use std::io::Write;
326 let mut out = std::io::stdout().lock();
327 let Some(kind) = entry.kind() else {
328 return writeln!(out, "{entry}");
329 };
330 write!(out, "{C_LISTING}{kind}")?;
331
332 if let Some(name) = entry.name() {
333 write!(out, " {name}")?;
334 }
335 writeln!(out, "\x1b[0m ({}): {entry}", entry.id())?;
336
337 if let Some(parent) = entry.parent() {
338 writeln!(
339 out,
340 "- {C_LISTING}Parent\x1b[0m: {parent} ({})",
341 parent.id()
342 )?;
343 }
344
345 match entry.meta() {
346 Some(meta) if !meta.is_empty() => {
347 writeln!(out, "- {C_LISTING}Meta:\x1b[0m")?;
348 for meta in meta {
349 writeln!(out, " - {meta}")?;
350 }
351 }
352 _ => {}
353 }
354
355 if let Some(children) = entry.children() {
356 writeln!(out, "- {C_LISTING}Children:\x1b[0m")?;
357 for (name, child) in children {
358 writeln!(
359 out,
360 " - {C_LISTING}{name}\x1b[0m ({child}): {}",
361 entry.with_id(*child)
362 )?
363 }
364 }
365
366 if let Some(lazy_imports) = entry.lazy_imports() {
367 writeln!(out, "- {C_LISTING}Lazy imports:\x1b[0m")?;
368 for (name, child) in lazy_imports {
369 writeln!(out, " - {C_LISTING}{name}\x1b[0m: {child}",)?
370 }
371 }
372
373 if let Some(glob_imports) = entry.glob_imports() {
374 writeln!(out, "- {C_LISTING}Glob imports:\x1b[0m")?;
375 for path in glob_imports {
376 writeln!(out, " - {C_LISTING}{path}\x1b[0m",)?
377 }
378 }
379
380 Ok(())
381}
382
383fn inline_modules(code: Expr, path: impl AsRef<path::Path>) -> Expr {
384 match ModuleInliner::new(path).inline(code) {
385 Err((code, io, parse)) => {
386 for (file, error) in io {
387 eprintln!("{}:{error}", file.display());
388 }
389 for (file, error) in parse {
390 eprintln!("{}:{error}", file.display());
391 }
392 code
393 }
394 Ok(code) => code,
395 }
396}
397
398fn dump_to(table: &Table, output: &mut impl std::io::Write) -> Result<(), Box<dyn Error>> {
399 fn dump_recursive(
400 name: cl_ast::types::Symbol,
401 entry: Entry,
402 depth: usize,
403 to_file: &mut impl std::io::Write,
404 ) -> std::io::Result<()> {
405 write!(to_file, "{:w$}{name}: {entry}", "", w = depth)?;
406 if let Some(children) = entry.children() {
407 writeln!(to_file, " {{")?;
408 for (name, child) in children {
409 dump_recursive(*name, entry.with_id(*child), depth + 2, to_file)?;
410 }
411 write!(to_file, "{:w$}}}", "", w = depth)?;
412 }
413 writeln!(to_file)
414 }
415 dump_recursive("root".into(), table.root_entry(), 0, output)?;
416 Ok(())
417}
418
419fn dump(table: &Table) -> Result<(), Box<dyn Error>> {
420 let mut file = std::fs::File::create("typeck-table.ron")?;
421 dump_to(table, &mut file)?;
422 Ok(())
423}
424
425fn clear() -> Result<(), Box<dyn Error>> {
426 println!("\x1b[H\x1b[2J");
427 banner();
428 Ok(())
429}
430
431fn banner() {
432 println!(
433 "--- {} v{} 💪🦈 ---",
434 env!("CARGO_BIN_NAME"),
435 env!("CARGO_PKG_VERSION"),
436 );
437}
438
439static EXPR_INTERNER: OnceLock<LeakyInterner<'static, Expr>> = OnceLock::new();
440
441fn interned(expr: Expr) -> &'static Expr {
443 EXPR_INTERNER
444 .get_or_init(Default::default)
445 .get_or_insert(expr)
446 .to_ref()
447}