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::{
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
24// Path to display in standard library errors
25const STDLIB_DISPLAY_PATH: &str = "stdlib/lib.cl";
26// Statically included standard library
27const PREAMBLE: &str = r"
28pub mod std;
29pub use std::preamble::*;
30";
31
32// Colors
33const 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    // This code is special - it gets loaded from a hard-coded project directory (for now)
52    let code = inline_modules(code, concat!(env!("CARGO_MANIFEST_DIR"), "/../../stdlib"));
53    // let code = cl_ast::desugar::WhileElseDesugar.fold_file(code);
54    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 code = WhileElseDesugar.fold_file(code);
116
117        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 = ConstantFolder.fold_stmt(code);
128        // println!("ConstantFolder\n{C_LISTING}{code}\x1b[0m");
129
130        // let code = SquashGroups.fold_stmt(code);
131        // println!("SquashGroups\n{C_LISTING}{code}\x1b[0m");
132
133        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        // let code = NormalizePaths::new().fold_stmt(code);
140        // println!("NormalizePaths\n{C_LISTING}{code}\x1b[0m");
141
142        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        // A query is comprised of a Ty and a relative Path
156        let mut p = Parser::new(Lexer::new("".into(), line));
157        let ty: cl_ast::Pat = p.parse(cl_parser::pat::Prec::Alt)?;
158        // let path: cl_ast::types::Path = p.parse(()).unwrap_or_else(|_| Path::from(""));
159        let id = ty.evaluate(prj, prj.root())?;
160        // let id = path.evaluate(prj, id)?;
161        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        // eprintln!("--> {}", prj.entry(ty));
190        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            // Err("No results.")?
225        }
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 code = cl_ast::desugar::WhileElseDesugar.fold_file(code);
290    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
441/// Interns an [Expr], returning a static reference to it.
442fn interned(expr: Expr) -> &'static Expr {
443    EXPR_INTERNER
444        .get_or_init(Default::default)
445        .get_or_insert(expr)
446        .to_ref()
447}