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