Skip to main content

typeck/
typeck.rs

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
22// Path to display in standard library errors
23const STDLIB_DISPLAY_PATH: &str = "stdlib/lib.cl";
24// Statically included standard library
25const PREAMBLE: &str = r"
26pub mod std;
27pub use std::preamble::*;
28";
29
30// Colors
31const 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    // This code is special - it gets loaded from a hard-coded project directory (for now)
50    let code = inline_modules(code, concat!(env!("CARGO_MANIFEST_DIR"), "/../../stdlib"));
51    // let code = cl_ast::desugar::WhileElseDesugar.fold_file(code);
52    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 code = WhileElseDesugar.fold_file(code);
114
115        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        // let code = ConstantFolder.fold_stmt(code);
126        // println!("ConstantFolder\n{C_LISTING}{code}\x1b[0m");
127
128        // let code = SquashGroups.fold_stmt(code);
129        // println!("SquashGroups\n{C_LISTING}{code}\x1b[0m");
130
131        // let code = WhileElseDesugar.fold_stmt(code);
132        // println!("WhileElseDesugar\n{C_LISTING}{code}\x1b[0m");
133
134        // let code = NormalizePaths::new().fold_stmt(code);
135        // println!("NormalizePaths\n{C_LISTING}{code}\x1b[0m");
136
137        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        // A query is comprised of a Ty and a relative Path
151        let mut p = Parser::new(Lexer::new("".into(), line));
152        let ty: cl_ast::Pat = p.parse(cl_parser::pat::Prec::Alt)?;
153        // let path: cl_ast::types::Path = p.parse(()).unwrap_or_else(|_| Path::from(""));
154        let id = ty.evaluate(prj, prj.root())?;
155        // let id = path.evaluate(prj, id)?;
156        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        // eprintln!("--> {}", prj.entry(ty));
185        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            // Err("No results.")?
220        }
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 code = cl_ast::desugar::WhileElseDesugar.fold_file(code);
285    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
436/// Interns an [Expr], returning a static reference to it.
437fn interned(expr: Expr) -> &'static Expr {
438    EXPR_INTERNER
439        .get_or_init(Default::default)
440        .get_or_insert(expr)
441        .to_ref()
442}