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 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(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
300fn 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
332fn 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
364fn 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#[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 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#[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 #[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 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}