Skip to main content

conlang/
main.rs

1//! The new Conlang REPL
2//!
3//! # Introduction
4//! Conlang is a ~~statically-typed~~ expressional language in the ML language family.
5//!
6//! It aims for maximal flexibility at (almost) any cost, allowing you to use
7//! (almost) any syntax in (almost) any context, with as minimal bracketing
8//! as possible.
9//!
10//! # Syntax
11//!
12//! ```ignore
13#![doc = include_str!("tutorial.cl")]
14//! ```
15
16use cl_ast::{
17    AstNode, At, Bind, DefaultTypes, Expr, Pat, Use,
18    desugar::type_bubbler::Bubbler,
19    fold::Foldable,
20    macro_matcher::{Match, Subst},
21    visit::Walk,
22};
23use cl_interpret::{convalue::ConValue, env::Environment, interpret::Interpret};
24use cl_lexer::{EOF, LexError, Lexer};
25use cl_parser::{PResultExt, Parse, ParseError, Parser, inliner::ModuleInliner};
26use cl_structures::span::Span;
27use cl_token::{TKind, Token};
28// use cl_typeck::Collector;
29use repline::prebaked::*;
30use std::{
31    error::Error,
32    io::{IsTerminal, stdin, stdout},
33    marker::PhantomData,
34};
35
36mod builtin;
37
38/// Prints the `--- conlang version ---` banner
39fn banner() {
40    println!("--- conlang v{} 💪🦈 ---", env!("CARGO_PKG_VERSION"))
41}
42
43/// Clears the terminal
44fn clear() {
45    print!("\x1b[H\x1b[2J\x1b[3J");
46}
47
48/// Prints the usage string
49fn usage(command: &str) {
50    println!("Usage: {command} [help | clear] [PARSEMODE] [VERBOSITY] [*.cl ...] [CODE ...]");
51    println!();
52    println!("Commands:");
53    println!("    *.cl        Import a source file (by name)");
54    println!("    code        Run some Conlang code");
55    println!("    help        Print this help-text");
56    println!("    clear       Clear the terminal on startup");
57    println!();
58    println!("Flags:");
59    println!("    PARSEMODE   run, expr, pat, bind, use, tokens, or bubble");
60    println!("    VERBOSITY   pretty, debugpretty or dp, debug, or quiet");
61    println!();
62}
63
64/// The return type of [pargs]
65type Args = (Verbosity, ParseMode, String, String, bool);
66
67/// Parses [Args]
68fn pargs() -> Result<Args, Box<dyn Error>> {
69    let mut verbose = Verbosity::try_from(std::env::var("DO_VERBOSE").as_deref().unwrap_or(""))
70        .unwrap_or_default();
71    let mut parsing = ParseMode::try_from(std::env::var("DO_PARSING").as_deref().unwrap_or(""))
72        .unwrap_or_default();
73    let mut includes = String::new();
74    let mut entrypoint = String::new();
75    let mut interactive = stdin().is_terminal() && stdout().is_terminal();
76
77    let mut args = std::env::args();
78    let command = args.next();
79    for arg in args {
80        match arg.as_str() {
81            "clear" => clear(),
82            "-i" | "--interactive" => interactive = true,
83            "--" => interactive = false,
84            "help" | "-h" | "--help" => {
85                usage(command.as_deref().unwrap_or("conlang"));
86                std::process::exit(0);
87            }
88            line if let Ok(mode) = ParseMode::try_from(line) => parsing = mode,
89            line if let Ok(mode) = Verbosity::try_from(line) => verbose = mode,
90            line if line.ends_with(".cl") => includes += &format!("mod \"{line}\";\n"),
91            _ => entrypoint += &(arg + " "),
92        }
93    }
94
95    Ok((verbose, parsing, includes, entrypoint, interactive))
96}
97
98fn main() -> Result<(), Box<dyn Error>> {
99    let (mut verbose, mut parsing, includes, entrypoint, interactive) = pargs()?;
100    let mut env = builtin::get_env();
101    let color = parsing.color();
102    let begin = verbose.begin();
103
104    if !includes.is_empty() {
105        parsing.with()(&mut env, &includes, verbose)?;
106    }
107    if !entrypoint.is_empty() {
108        parsing.with()(&mut env, &entrypoint, verbose)?;
109        return Ok(());
110    }
111
112    if interactive {
113        banner();
114        read_and_mut(color, begin, "  > ", |rl, line| match line.trim_end() {
115            "" => Ok(Response::Continue),
116            "exit" => Ok(Response::Break),
117            "help" => {
118                println!("Parsing: {parsing:?} (run, expr, pat, bind, use, tokens, bubble)");
119                println!("Verbose: {verbose:?} (pretty, debugpretty (dp), debug, quiet)");
120                Ok(Response::Deny)
121            }
122            "clear" => {
123                clear();
124                banner();
125                Ok(Response::Deny)
126            }
127            "macro" => {
128                if let Err(e) = subst() {
129                    println!("\x1b[31m{e}\x1b[0m");
130                }
131                Ok(Response::Accept)
132            }
133            line if let Ok(mode) = ParseMode::try_from(line) => {
134                parsing = mode;
135                println!("Parse mode set to '{parsing:?}'");
136                rl.set_color(parsing.color());
137                Ok(Response::Accept)
138            }
139            line if let Ok(mode) = Verbosity::try_from(line) => {
140                verbose = mode;
141                println!("Verbosity set to '{verbose:?}'");
142                rl.set_begin(verbose.begin());
143                Ok(Response::Accept)
144            }
145            _ if let ParseMode::Run = parsing => {
146                parsing.with()(&mut env, line, verbose)?;
147                Ok(Response::Accept)
148            }
149            _ if line.ends_with("\n\n") => {
150                parsing.with()(&mut env, line, verbose)?;
151                Ok(Response::Accept)
152            }
153            _ => Ok(Response::Continue),
154        })?;
155    } else {
156        let doc = std::io::read_to_string(stdin())?;
157        parsing.with()(&mut env, &doc, verbose)?;
158    }
159    Ok(())
160}
161
162/// Performs macro substitution
163fn subst() -> Result<(), Box<dyn Error>> {
164    let mut rl = repline::Repline::new("\x1b[35mexp", " >", "?>");
165    let exp = rl.read()?;
166    let exp: At<Expr> = Parser::new(Lexer::new("<interactive>".into(), &exp)).parse(0)?;
167    let mut exp = inline_modules(exp).0;
168    println!("\x1b[G\x1b[J{exp}");
169
170    rl.accept();
171
172    loop {
173        rl.set_color("\x1b[36mpat");
174        let pat = rl.read()?;
175        rl.accept();
176        print!("\x1b[G\x1b[J");
177        let mut p = Parser::new(Lexer::new("<interactive>".into(), &pat));
178
179        let Ok(pat) = p.parse::<Expr>(0) else {
180            println!("{exp}");
181            continue;
182        };
183
184        if p.next_if(TKind::Arrow).is_err() {
185            let Some(Subst { exp, pat }) = exp.match_with(&pat) else {
186                println!("Match failed: {exp} <- {pat}");
187                continue;
188            };
189            let mut pats: Vec<_> = pat.into_iter().collect();
190            pats.sort_by_key(|(a, _)| a.to_ref());
191            for (name, pat) in pats {
192                println!("{name}: {pat}")
193            }
194            let mut exprs: Vec<_> = exp.into_iter().collect();
195            exprs.sort_by_key(|(a, _)| a.to_ref());
196            for (name, expr) in exprs.iter() {
197                println!("{name}: {expr}")
198            }
199            continue;
200        }
201
202        let sub: Expr = p.parse(0)?;
203        if exp.apply_rule(&pat, &sub) {
204            println!("{exp}");
205        } else {
206            println!("No match: {pat} in {exp}\n")
207        }
208    }
209}
210
211/// Gets the English-language pluralizer for `count`
212fn plural(count: usize) -> &'static str {
213    match count {
214        1 => "",
215        _ => "s",
216    }
217}
218
219/// Prints the tokenization of the input
220fn tokens<'e: 't, 't, T: Parse<'t> + ?Sized>(
221    _: &'e mut Environment,
222    document: &'t str,
223    verbose: Verbosity,
224) -> Result<(), Box<dyn Error>> {
225    let _: PhantomData<T>; // for lifetime variance
226    let mut lexer = Lexer::new("<tokens>".into(), document);
227    loop {
228        match (lexer.scan(), verbose) {
229            (Err(LexError { res: EOF, .. }), _) => {
230                break;
231            }
232            (Err(e), _) => Err(e)?,
233            (Ok(Token { lexeme, kind, span: Span { path: _, head, tail } }), Verbosity::Pretty) => {
234                println!("{kind:?}\x1b[11G {head:<4} {tail:<4} {lexeme:?}")
235            }
236            (Ok(token), Verbosity::DebugPretty) => {
237                println!("{token:#?}");
238            }
239            (Ok(token), Verbosity::Debug) => {
240                println!("{token:?}")
241            }
242            _ => {}
243        }
244    }
245    Ok(())
246}
247
248/// Parses and displays `T`s from the input
249fn parse<'env: 't, 't, T>(
250    _: &'env mut Environment,
251    document: &'t str,
252    verbose: Verbosity,
253) -> Result<(), Box<dyn Error>>
254where
255    T: Parse<'t> + AstNode + for<'a> Walk<'a, DefaultTypes> + Foldable<DefaultTypes, DefaultTypes>,
256    <T as Foldable<DefaultTypes, DefaultTypes>>::Out: AstNode,
257{
258    let mut parser = Parser::new(Lexer::new("<parse>".into(), document));
259    for idx in 0..6 {
260        let color_tag = if idx == 0 { 96 } else { (idx + 4) % 6 + 31 };
261        match (
262            parser
263                .parse::<At<T, _>>(T::Prec::default())
264                .map(inline_modules),
265            verbose,
266        ) {
267            (Err(ParseError::EOF(_)), Verbosity::Quiet) => break,
268            (Err(e @ ParseError::EOF(_)), _) => {
269                println!(
270                    "\x1b[92m{e} (total {} byte{}, {idx} expression{})\x1b[0m",
271                    document.len(),
272                    plural(document.len()),
273                    plural(idx),
274                );
275                break;
276            }
277            (Err(e), _) => Err(e)?,
278            (Ok(At(expr, span)), Verbosity::Pretty) => {
279                println!("\x1b[{color_tag}m{span:?}:\n{expr}");
280            }
281            // (Ok(At(expr, span)), Verbosity::Frob) => {
282            //     println!("\x1b[{color_tag}m{span}:\n");
283            //     let _ = expr.visit_in(&mut Collector::new());
284            // }
285            (Ok(expr), Verbosity::Debug) => {
286                println!("\x1b[{color_tag}m{expr:?}");
287            }
288            (Ok(expr), Verbosity::DebugPretty) => {
289                println!("\x1b[{color_tag}m{expr:#?}");
290            }
291            (Ok(expr), Verbosity::Quiet) => {
292                println!("{expr}");
293            }
294            _ => {}
295        }
296    }
297    Ok(())
298}
299
300/// Parses and executes expressions from the input
301fn run<'env: 't, 't>(
302    env: &'env mut Environment,
303    document: &'t str,
304    verbose: Verbosity,
305) -> Result<(), Box<dyn Error>> {
306    let mut parser = Parser::new(Lexer::new("<run>".into(), document));
307    for idx in 0..6 {
308        let color_tag = (idx + 5) % 6 + 31;
309        let Some(code) = parser.parse::<At<Expr>>(0).allow_eof()? else {
310            break;
311        };
312        match (inline_modules(code).interpret(env), verbose) {
313            (Err(error), _) => {
314                println!("\x1b[31m{error}");
315            }
316            (Ok(ConValue::Empty), Verbosity::Pretty) => {}
317            (Ok(value), Verbosity::Pretty) => {
318                println!("\x1b[{color_tag}m{value}");
319            }
320            (Ok(value), Verbosity::Debug) => {
321                println!("\x1b[{color_tag}m{value:?}");
322            }
323            (Ok(value), Verbosity::DebugPretty) => {
324                println!("\x1b[{color_tag}m{value:#?}");
325            }
326            _ => {}
327        }
328    }
329    Ok(())
330}
331
332/// Performs experimental desugaring on expressions from the input
333fn bubble<'env: 't, 't>(
334    _: &'env mut Environment,
335    document: &'t str,
336    verbose: Verbosity,
337) -> Result<(), Box<dyn Error>> {
338    let mut parser = Parser::new(Lexer::new("<bubble>".into(), document));
339    for idx in 0..6 {
340        match (
341            parser
342                .parse::<At<Expr>>(Default::default())
343                .map(inline_modules)
344                .map(|v| v.fold_in(&mut Bubbler(verbose == Verbosity::Frob)).unwrap()),
345            verbose,
346        ) {
347            (Err(ParseError::EOF(_)), _) => break,
348            (Err(e), _) => Err(e)?,
349            (Ok(pat), Verbosity::Pretty | Verbosity::Frob) => {
350                println!("\x1b[{}m{pat}", (idx + 5) % 6 + 31);
351            }
352            (Ok(pat), Verbosity::Debug) => {
353                println!("\x1b[{}m{pat:?}", (idx + 5) % 6 + 31);
354            }
355            (Ok(pat), Verbosity::DebugPretty) => {
356                println!("\x1b[{}m{pat:#?}", (idx + 5) % 6 + 31);
357            }
358            _ => {}
359        }
360    }
361    Ok(())
362}
363
364/// Inlines modules at a given `T` relative to the PWD
365fn inline_modules<T>(expr: At<T>) -> At<T::Out>
366where
367    T: AstNode + Foldable<DefaultTypes, DefaultTypes>,
368    T::Out: AstNode,
369{
370    let mut mi = ModuleInliner::new(".");
371    let At(expr, span) = expr;
372    let Ok(expr) = expr.fold_in(&mut mi);
373    if let Some((io_errs, parse_errs)) = mi.into_errs() {
374        for (path, err) in io_errs {
375            println!("{}: {err}", path.display());
376        }
377        for (path, err) in parse_errs {
378            println!("{}: {err}", path.display());
379        }
380    }
381
382    At(expr, span)
383}
384
385/// How much information to show about results
386#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
387enum Verbosity {
388    #[default]
389    Pretty,
390    Debug,
391    DebugPretty,
392    Frob,
393    Quiet,
394}
395
396impl TryFrom<&str> for Verbosity {
397    type Error = ();
398
399    fn try_from(value: &str) -> Result<Self, Self::Error> {
400        match value {
401            "quiet" => Ok(Verbosity::Quiet),
402            "debug" | "d" => Ok(Verbosity::Debug),
403            "debugpretty" | "debug_pretty" | "dp" => Ok(Verbosity::DebugPretty),
404            "frob" => Ok(Verbosity::Frob),
405            "pretty" => Ok(Verbosity::Pretty),
406            _ => Err(()),
407        }
408    }
409}
410
411impl Verbosity {
412    /// Gets a prompt string representing this verbosity
413    fn begin(self) -> &'static str {
414        match self {
415            Self::Pretty => " .> ",
416            Self::Debug => " ?> ",
417            Self::DebugPretty => " #> ",
418            Self::Frob => "🐸> ",
419            Self::Quiet => " _> ",
420        }
421    }
422}
423
424/// What the next operation should be
425#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
426enum ParseMode {
427    #[default]
428    Run,
429    Expr,
430    Pat,
431    Bind,
432    Use,
433    Tokens,
434    Bubble,
435}
436
437impl TryFrom<&str> for ParseMode {
438    type Error = ();
439
440    fn try_from(value: &str) -> Result<Self, Self::Error> {
441        match value {
442            "run" => Ok(Self::Run),
443            "fmt" | "format" | "expr" => Ok(Self::Expr),
444            "pat" => Ok(Self::Pat),
445            "bind" => Ok(Self::Bind),
446            "use" => Ok(Self::Use),
447            "tokens" => Ok(Self::Tokens),
448            "bubble" => Ok(Self::Bubble),
449            _ => Err(()),
450        }
451    }
452}
453
454impl ParseMode {
455    /// Gets a function implementing this operation
456    #[expect(clippy::type_complexity)]
457    fn with<'env: 'a, 'a>(
458        &self,
459    ) -> fn(&'env mut Environment, &'a str, Verbosity) -> Result<(), Box<dyn Error>> {
460        match self {
461            Self::Expr => parse::<'env, 'a, Expr>,
462            Self::Pat => parse::<'env, 'a, Pat>,
463            Self::Bind => parse::<'env, 'a, Bind>,
464            Self::Use => parse::<'env, 'a, Use>,
465            Self::Tokens => tokens::<'env, 'a, dyn Parse<'a, Prec = ()>>,
466            Self::Run => run::<'env, 'a>,
467            Self::Bubble => bubble::<'env, 'a>,
468        }
469    }
470
471    /// Gets an ANSI color representing this operation
472    fn color(&self) -> &'static str {
473        match self {
474            Self::Run => "\x1b[36m",
475            Self::Expr => "\x1b[35m",
476            Self::Pat => "\x1b[34m",
477            Self::Bind => "\x1b[33m",
478            Self::Use => "\x1b[32m",
479            Self::Tokens => "\x1b[31m",
480            Self::Bubble => "\x1b[90m",
481        }
482    }
483}