1#![doc = include_str!("tutorial.cl")]
14use 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};
28use repline::prebaked::*;
30use std::{
31 error::Error,
32 io::{IsTerminal, stdin, stdout},
33 marker::PhantomData,
34};
35
36mod builtin;
37
38fn banner() {
40 println!("--- conlang v{} 💪🦈 ---", env!("CARGO_PKG_VERSION"))
41}
42
43fn clear() {
45 print!("\x1b[H\x1b[2J\x1b[3J");
46}
47
48fn 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
64type Args = (Verbosity, ParseMode, String, String, bool);
66
67fn 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
162fn 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
211fn plural(count: usize) -> &'static str {
213 match count {
214 1 => "",
215 _ => "s",
216 }
217}
218
219fn 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>; 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
248fn 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(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
296fn 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
327fn 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
359fn 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#[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 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#[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 #[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 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}