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        match (
261            parser
262                .parse::<At<T, _>>(T::Prec::default())
263                .map(inline_modules),
264            verbose,
265        ) {
266            (Err(ParseError::EOF(_)), Verbosity::Quiet) => break,
267            (Err(e @ ParseError::EOF(_)), _) => {
268                println!(
269                    "\x1b[92m{e} (total {} byte{}, {idx} expression{})\x1b[0m",
270                    document.len(),
271                    plural(document.len()),
272                    plural(idx),
273                );
274                break;
275            }
276            (Err(e), _) => Err(e)?,
277            (Ok(At(expr, span)), Verbosity::Pretty) => {
278                println!("\x1b[{}m{span:?}:\n{expr}", (idx + 5) % 6 + 31);
279            }
280            // (Ok(At(expr, span)), Verbosity::Frob) => {
281            //     println!("\x1b[{}m{span}:\n", (idx + 5) % 6 + 31);
282            //     let _ = expr.visit_in(&mut Collector::new());
283            // }
284            (Ok(expr), Verbosity::Debug) => {
285                println!("\x1b[{}m{expr:?}", (idx + 5) % 6 + 31);
286            }
287            (Ok(expr), Verbosity::DebugPretty) => {
288                println!("\x1b[{}m{expr:#?}", (idx + 5) % 6 + 31);
289            }
290            _ => {}
291        }
292    }
293    Ok(())
294}
295
296/// Parses and executes expressions from the input
297fn run<'env: 't, 't>(
298    env: &'env mut Environment,
299    document: &'t str,
300    verbose: Verbosity,
301) -> Result<(), Box<dyn Error>> {
302    let mut parser = Parser::new(Lexer::new("<run>".into(), document));
303    for idx in 0..6 {
304        let Some(code) = parser.parse::<At<Expr>>(0).allow_eof()? else {
305            break;
306        };
307        match (inline_modules(code).interpret(env), verbose) {
308            (Err(error), _) => {
309                println!("\x1b[{}m{error}", (idx + 5) % 6 + 31);
310            }
311            (Ok(ConValue::Empty), Verbosity::Pretty) => {}
312            (Ok(value), Verbosity::Pretty) => {
313                println!("\x1b[{}m{value}", (idx + 5) % 6 + 31);
314            }
315            (Ok(value), Verbosity::Debug) => {
316                println!("\x1b[{}m{value:?}", (idx + 5) % 6 + 31);
317            }
318            (Ok(value), Verbosity::DebugPretty) => {
319                println!("\x1b[{}m{value:#?}", (idx + 5) % 6 + 31);
320            }
321            _ => {}
322        }
323    }
324    Ok(())
325}
326
327/// Performs experimental desugaring on expressions from the input
328fn bubble<'env: 't, 't>(
329    _: &'env mut Environment,
330    document: &'t str,
331    verbose: Verbosity,
332) -> Result<(), Box<dyn Error>> {
333    let mut parser = Parser::new(Lexer::new("<bubble>".into(), document));
334    for idx in 0..6 {
335        match (
336            parser
337                .parse::<At<Expr>>(Default::default())
338                .map(inline_modules)
339                .map(|v| v.fold_in(&mut Bubbler(verbose == Verbosity::Frob)).unwrap()),
340            verbose,
341        ) {
342            (Err(ParseError::EOF(_)), _) => break,
343            (Err(e), _) => Err(e)?,
344            (Ok(pat), Verbosity::Pretty | Verbosity::Frob) => {
345                println!("\x1b[{}m{pat}", (idx + 5) % 6 + 31);
346            }
347            (Ok(pat), Verbosity::Debug) => {
348                println!("\x1b[{}m{pat:?}", (idx + 5) % 6 + 31);
349            }
350            (Ok(pat), Verbosity::DebugPretty) => {
351                println!("\x1b[{}m{pat:#?}", (idx + 5) % 6 + 31);
352            }
353            _ => {}
354        }
355    }
356    Ok(())
357}
358
359/// Inlines modules at a given `T` relative to the PWD
360fn inline_modules<T>(expr: At<T>) -> At<T::Out>
361where
362    T: AstNode + Foldable<DefaultTypes, DefaultTypes>,
363    T::Out: AstNode,
364{
365    let mut mi = ModuleInliner::new(".");
366    let At(expr, span) = expr;
367    let Ok(expr) = expr.fold_in(&mut mi);
368    if let Some((io_errs, parse_errs)) = mi.into_errs() {
369        for (path, err) in io_errs {
370            println!("{}: {err}", path.display());
371        }
372        for (path, err) in parse_errs {
373            println!("{}: {err}", path.display());
374        }
375    }
376
377    At(expr, span)
378}
379
380/// How much information to show about results
381#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
382enum Verbosity {
383    #[default]
384    Pretty,
385    Debug,
386    DebugPretty,
387    Frob,
388    Quiet,
389}
390
391impl TryFrom<&str> for Verbosity {
392    type Error = ();
393
394    fn try_from(value: &str) -> Result<Self, Self::Error> {
395        match value {
396            "quiet" => Ok(Verbosity::Quiet),
397            "debug" | "d" => Ok(Verbosity::Debug),
398            "debugpretty" | "debug_pretty" | "dp" => Ok(Verbosity::DebugPretty),
399            "frob" => Ok(Verbosity::Frob),
400            "pretty" => Ok(Verbosity::Pretty),
401            _ => Err(()),
402        }
403    }
404}
405
406impl Verbosity {
407    /// Gets a prompt string representing this verbosity
408    fn begin(self) -> &'static str {
409        match self {
410            Self::Pretty => " .> ",
411            Self::Debug => " ?> ",
412            Self::DebugPretty => " #> ",
413            Self::Frob => "🐸> ",
414            Self::Quiet => " _> ",
415        }
416    }
417}
418
419/// What the next operation should be
420#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
421enum ParseMode {
422    #[default]
423    Run,
424    Expr,
425    Pat,
426    Bind,
427    Use,
428    Tokens,
429    Bubble,
430}
431
432impl TryFrom<&str> for ParseMode {
433    type Error = ();
434
435    fn try_from(value: &str) -> Result<Self, Self::Error> {
436        match value {
437            "run" => Ok(Self::Run),
438            "fmt" | "format" | "expr" => Ok(Self::Expr),
439            "pat" => Ok(Self::Pat),
440            "bind" => Ok(Self::Bind),
441            "use" => Ok(Self::Use),
442            "tokens" => Ok(Self::Tokens),
443            "bubble" => Ok(Self::Bubble),
444            _ => Err(()),
445        }
446    }
447}
448
449impl ParseMode {
450    /// Gets a function implementing this operation
451    #[expect(clippy::type_complexity)]
452    fn with<'env: 'a, 'a>(
453        &self,
454    ) -> fn(&'env mut Environment, &'a str, Verbosity) -> Result<(), Box<dyn Error>> {
455        match self {
456            Self::Expr => parse::<'env, 'a, Expr>,
457            Self::Pat => parse::<'env, 'a, Pat>,
458            Self::Bind => parse::<'env, 'a, Bind>,
459            Self::Use => parse::<'env, 'a, Use>,
460            Self::Tokens => tokens::<'env, 'a, dyn Parse<'a, Prec = ()>>,
461            Self::Run => run::<'env, 'a>,
462            Self::Bubble => bubble::<'env, 'a>,
463        }
464    }
465
466    /// Gets an ANSI color representing this operation
467    fn color(&self) -> &'static str {
468        match self {
469            Self::Run => "\x1b[36m",
470            Self::Expr => "\x1b[35m",
471            Self::Pat => "\x1b[34m",
472            Self::Bind => "\x1b[33m",
473            Self::Use => "\x1b[32m",
474            Self::Tokens => "\x1b[31m",
475            Self::Bubble => "\x1b[90m",
476        }
477    }
478}