#![feature(hash_extract_if)]
#![allow(warnings)]
use mech::*;
use mech_core::*;
use mech_syntax::parser;
#[cfg(feature = "formatter")]
use mech_syntax::formatter::*;
use mech_interpreter::interpreter::*;
use std::time::Instant;
use std::fs;
use std::env;
use std::io;
use colored::*;
use std::io::{Write, BufReader, BufWriter, stdout};
use crossterm::{
ExecutableCommand, QueueableCommand,
terminal, cursor, style::Print,
};
use ariadne::{Report, ReportKind, Label, Color, sources};
use clap::{arg, command, value_parser, Arg, ArgAction, Command};
use std::path::PathBuf;
use tabled::{
builder::Builder,
settings::{object::Rows,Panel, Span, Alignment, Modify, Style},
Tabled,
};
use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
use serde_json;
use std::panic;
use std::sync::{Arc, Mutex};
use std::path::Path;
use std::ffi::OsStr;
use std::time::Duration;
use std::thread;
const VERSION: &str = env!("CARGO_PKG_VERSION");
#[cfg(has_file_wasm)]
static MECHWASM: &[u8] = include_bytes!("../../src/wasm/pkg/mech_wasm_bg.wasm.br");
#[cfg(not(has_file_wasm))]
static MECHWASM: &[u8] = b"No Embedded WASM";
#[cfg(has_file_js)]
static MECHJS: &[u8] = include_bytes!("../../src/wasm/pkg/mech_wasm.js");
#[cfg(not(has_file_js))]
static MECHJS: &[u8] = b"No Embedded JS";
#[cfg(has_file_shim)]
static SHIMHTML: &str = include_str!("../../include/index.html");
#[cfg(not(has_file_shim))]
static SHIMHTML: &str = "No Embedded Shim";
#[cfg(has_file_stylesheet)]
static STYLESHEET: &str = include_str!("../../include/style.css");
#[cfg(not(has_file_stylesheet))]
static STYLESHEET: &str = "No Embedded Stylesheet";
#[tokio::main]
async fn main() -> Result<(), MechError> {
let text_logo = r#"
┌─────────┐ ┌──────┐ ┌─┐ ┌──┐ ┌─┐ ┌─┐
└───┐ ┌───┘ └──────┘ │ │ └┐ │ │ │ │ │
┌─┐ │ │ ┌─┐ ┌──────┐ │ │ └─┘ │ └─┐│ │
│ │ │ │ │ │ │ ┌────┘ │ │ ┌─┐ │ ┌─┘│ │
│ │ └─┘ │ │ │ └────┐ │ └──┘ │ │ │ │ │
└─┘ └─┘ └──────┘ └──────┘ └─┘ └─┘"#.truecolor(246,192,78);
let super_3D_logo = r#"
_____ _____ _____ _____
╱╲ ╲ ╱╲ ╲ ╱╲ ╲ ╱╲ ╲
╱┊┊╲ ╲ ╱┊┊╲ ╲ ╱┊┊╲____╲ ╱┊┊╲____╲
╲┊┊┊╲ ╲ ╲┊┊┊╲ ╲ ╱┊┊┊╱ ╱ ╱┊┊┊╱ ╱
___╲┊┊┊╲ ╲ ___╲┊┊┊╲ ╲ ╱┊┊┊╱ _╱___ ╱┊┊┊╱ ╱
╱╲ ╲┊┊┊╲ ╲ ╱╲ ╲┊┊┊╲ ╲ ╱┊┊┊╱ ╱╲ ╲ ╱┊┊┊╱ ╱
╱┊┊╲___╲┊┊┊╲ ╲ ╱┊┊╲ ╲┊┊┊╲ ╲ ╱┊┊┊╱ ╱┊┊╲ ╲ ╱┊┊┊╱___ ╱
╱┊┊┊╱ ╱┊┊┊┊╲ ╲ ╱┊┊┊┊╲ ╲┊┊┊╲ ╲ ╱┊┊┊╱ ╲┊┊┊╲ ╲ ╱┊┊┊┊╲ ╲ _____
╱┊┊┊╱ ╱┊┊┊┊┊┊╲ ╲ ╱┊┊┊┊┊┊╲ ╲┊┊┊╲ ╲ ╱┊┊┊╱ ╱ ╲┊┊┊╲ ╲ ╱┊┊┊┊┊┊╲ ╲ ╱╲ ╲
╱┊┊┊╱ ╱┊┊┊╱╲┊┊┊╲ ╲ ╱┊┊┊╱╲┊┊┊╲ ╲┊┊┊╲____╲ ╱┊┊┊╱ ╱ ╲┊┊┊╲____╲ ╱┊┊┊╱╲┊┊┊╲____╱┊┊╲____╲
╱┊┊┊╱ ╱┊┊┊╱ ╲┊┊┊╲____╲ ╱┊┊┊╱__╲┊┊┊╲ ╲┊┊╱ ╱ ╱┊┊┊╱____╱ ╱┊┊┊╱ ╱ ╱┊┊┊╱ ╲┊┊╱ ╱┊┊┊╱ ╱
╲┊┊╱ ╱┊┊┊╱ ╲┊┊╱ ╱ ╲┊┊┊╲ ╲┊┊┊╲ ╲╱____╱ ╲┊┊┊╲ ╲ ╲┊┊╱ ╱ ╲┊┊╱ ╲╱___╱┊┊┊╱ ╱
╲╱___╱┊┊┊╱ ___╲╱____╱ ╲┊┊┊╲ ╲┊┊┊╲ ╲ ╲┊┊┊╲ ╲ ╲╱____╱ ╲╱____╱ ╱┊┊┊╱ ╱
╱┊┊┊╱ ╱╲ ╲ ╲┊┊┊╲ ╲┊┊┊╲____╲ ╲┊┊┊╲ ╲____ ╱┊┊┊╱ ╱
╲┊┊╱ ╱┊┊╲____╲ ╲┊┊┊╲ ╲┊┊╱ ╱ ╲┊┊┊╲ ╱╲ ╲ ╱┊┊┊╱ ╱
╲╱___╱┊┊┊╱ ╱ ╲┊┊┊╲ ╲╱____╱ ╲┊┊┊╲╱┊┊╲____╲ ╱┊┊┊╱ ╱
╱┊┊┊╱ ╱ ╲┊┊┊╲ ╲ ╲┊┊┊┊┊┊╱ ╱ ╱┊┊┊╱ ╱
╱┊┊┊╱ ╱ ╲┊┊┊╲____╲ ╲┊┊┊┊╱ ╱ ╱┊┊┊╱ ╱
╱┊┊┊╱ ╱ ╲┊┊╱ ╱ ╲┊┊╱ ╱ ╲┊┊╱ ╱
╲┊┊╱ ╱ ╲╱____╱ ╲╱____╱ ╲╱____╱
╲╱____╱"#.truecolor(246,192,78);
let micromika = "╭◉╮".truecolor(246,192,78);
let micromika_point = "╭◉─".truecolor(246,192,78);
let micromika_hello = "╭◉╯".truecolor(246,192,78);
let help_cmd = ":help".bright_yellow();
let quit_cmd = ":quit".bright_yellow();
let ctrlc_cmd = ":ctrl+c".bright_yellow();
let mika_open = "⸢".bright_yellow();
let mika_close = "⸥".bright_yellow();
let about = format!("{}", text_logo);
let matches = Command::new("Mech")
.version(VERSION)
.author("Corey Montella corey@mech-lang.org")
.about(about)
.arg(Arg::new("mech_paths")
.help("Source .mec and files")
.required(false)
.action(ArgAction::Append))
.arg(Arg::new("debug")
.short('d')
.long("debug")
.help("Print debug info")
.action(ArgAction::SetTrue))
.subcommand(Command::new("format")
.about("Format Mech source code into standard format.")
.arg(Arg::new("mech_format_file_paths")
.help("Source .mec and .mecb files")
.required(false)
.action(ArgAction::Append))
.arg(Arg::new("output_path")
.short('o')
.long("out")
.help("Destination folder.")
.required(false))
.arg(Arg::new("stylesheet")
.short('s')
.long("stylesheet")
.value_name("STYLESHEET")
.help("Sets the stylesheet for the HTML output"))
.arg(Arg::new("shim")
.short('m')
.long("shim")
.value_name("SHIM")
.help("Sets the shim for the HTML output"))
.arg(Arg::new("html")
.short('t')
.long("html")
.required(false)
.help("Output as HTML")
.action(ArgAction::SetTrue)))
.subcommand(Command::new("build")
.about("Build Mech program into a binary.")
.arg(Arg::new("mech_build_file_paths")
.help("Source .mec and .mecb files")
.required(false)
.action(ArgAction::Append))
.arg(Arg::new("debug")
.short('d')
.long("debug")
.help("Print debug info")
.action(ArgAction::SetTrue))
.arg(Arg::new("output_path")
.short('o')
.long("out")
.help("Destination folder.")
.required(false)))
.subcommand(Command::new("serve")
.about("Serve Mech program over an HTTP server.")
.arg(Arg::new("mech_serve_file_paths")
.help("Source .mec and .mecb files")
.required(false)
.action(ArgAction::Append))
.arg(Arg::new("port")
.short('p')
.long("port")
.value_name("PORT")
.help("Sets the port for the server (8081)"))
.arg(Arg::new("stylesheet")
.short('s')
.long("stylesheet")
.value_name("STYLESHEET")
.help("Sets the stylesheet for the HTML output"))
.arg(Arg::new("shim")
.short('m')
.long("shim")
.value_name("SHIM")
.help("Sets the shim for the HTML output"))
.arg(Arg::new("wasm")
.short('w')
.long("wasm")
.value_name("WASM")
.help("Sets the the path to the wasm package"))
.arg(Arg::new("address")
.short('a')
.long("address")
.value_name("ADDRESS")
.help("Sets the address of the server (127.0.0.1)")))
.arg(Arg::new("tree")
.short('e')
.long("tree")
.help("Print parse tree")
.action(ArgAction::SetTrue))
.arg(Arg::new("time")
.short('t')
.long("time")
.help("Measure how long the programs takes to execute.")
.action(ArgAction::SetTrue))
.arg(Arg::new("trace")
.long("trace")
.help("Print trace output for state-machine arms and function calls")
.action(ArgAction::SetTrue))
.arg(Arg::new("repl")
.short('r')
.long("repl")
.help("Start REPL")
.action(ArgAction::SetTrue))
.get_matches();
let debug_flag = matches.get_flag("debug");
let tree_flag = matches.get_flag("tree");
let mut repl_flag = matches.get_flag("repl");
let time_flag = matches.get_flag("time");
let trace_flag = matches.get_flag("trace");
let shim_backup_url = "https://raw.githubusercontent.com/mech-lang/mech/refs/heads/main/include/shim.html".to_string();
let stylesheet_backup_url = "https://raw.githubusercontent.com/mech-lang/mech/refs/heads/main/include/style.css".to_string();
let wasm_backup_url = format!("https://github.com/mech-lang/mech/releases/download/v{}-beta/mech_wasm_bg.wasm.br", VERSION);
let js_backup_url = format!("https://github.com/mech-lang/mech/releases/download/v{}-beta/mech_wasm.js", VERSION);
#[cfg(feature = "serve")]
if let Some(matches) = matches.subcommand_matches("serve") {
let badge = "[Mech Server]".truecolor(34, 204, 187);
let port: String = matches.get_one::<String>("port").cloned().unwrap_or("8081".to_string());
let address = matches.get_one::<String>("address").cloned().unwrap_or("127.0.0.1".to_string());
let full_address: String = format!("{}:{}",address,port);
let mech_paths: Vec<String> = matches.get_many::<String>("mech_serve_file_paths").map_or(vec![], |files| files.map(|file| file.to_string()).collect());
let stylesheet_path = matches.get_one::<String>("stylesheet").cloned().unwrap_or("".to_string());
let wasm_pkg = matches.get_one::<String>("wasm").cloned().unwrap_or("".to_string());
let shim_path = matches.get_one::<String>("shim").cloned().unwrap_or("".to_string());
let wasm_path = format!("{}/mech_wasm_bg.wasm.br", wasm_pkg);
let js_path = format!("{}/mech_wasm.js", wasm_pkg);
println!("{} Loading resources...", badge);
print!("{} Loading stylesheet...", badge);
let stylesheet = read_or_download(&stylesheet_path, &stylesheet_backup_url, Some(STYLESHEET.as_bytes()))
.await?;
let stylesheet_str = String::from_utf8(stylesheet)
.map_err(|e| MechError::new(Utf8ConversionError { source_error: e.to_string() }, None).with_compiler_loc())?;
print!("{} Loading HTML shim...", badge);
let shim = read_or_download(&shim_path, &shim_backup_url, Some(SHIMHTML.as_bytes()))
.await?;
let shim_str = String::from_utf8(shim)
.map_err(|e| MechError::new(Utf8ConversionError { source_error: e.to_string() }, None).with_compiler_loc())?;
print!("{} Loading WASM...", badge);
let wasm = read_or_download(&wasm_path, &wasm_backup_url, Some(MECHWASM))
.await?;
print!("{} Loading JS...", badge);
let js = read_or_download(&js_path, &js_backup_url, Some(MECHJS))
.await?;
if cfg!(feature = "serve") {
#[cfg(feature = "serve")]
let mut server = MechServer::new("Mech Server".to_string(), full_address, stylesheet_str, shim_str, wasm, js);
#[cfg(feature = "serve")]
server.init().await?;
#[cfg(feature = "serve")]
match server.load_sources(&mech_paths) {
Ok(_) => println!("{} Sources loaded.", badge),
Err(err) => {
println!("{} {:#?}", "[Error]".truecolor(246,98,78), err);
std::process::exit(1);
}
}
#[cfg(feature = "serve")]
server.serve().await?;
}
}
#[cfg(feature = "build")]
if let Some(matches) = matches.subcommand_matches("build") {
let mech_paths: Vec<String> = matches.get_many::<String>("mech_build_file_paths").map_or(vec![], |files| files.map(|file| file.to_string()).collect());
let output_path = PathBuf::from(matches.get_one::<String>("output_path").cloned().unwrap_or(".".to_string()));
let debug_flag = matches.get_flag("debug");
let mut mechfs = MechFileSystem::new();
for path in mech_paths {
mechfs.watch_source(&path)?;
}
let sources = mechfs.sources();
let read_sources = sources.read().unwrap();
if output_path != PathBuf::from(".") {
match fs::create_dir_all(&output_path) {
Ok(_) => {
println!("{} Directory created: {}", "[Created]".truecolor(153,221,85), output_path.display());
}
Err(err) => {
println!("Error creating directory: {:?}", err);
}
}
}
let uuid = generate_uuid();
let mut intrp = Interpreter::new(uuid);
let result = run_mech_code(&mut intrp, &mechfs, tree_flag, debug_flag, time_flag, trace_flag);
let bytecode = intrp.compile()?;
let mut output_file = output_path.join("output.mecb");
let mut f = std::fs::File::create(&output_file)?;
f.write_all(&bytecode)?;
f.flush()?;
if debug_flag {
println!("{} Bytecode Size: {:#?} bytes", "[Debug]".truecolor(246,192,78), intrp.context);
}
println!("{} Mech bytecode written to: {}", "[Output]".truecolor(153,221,85), output_file.display());
return Ok(());
}
#[cfg(feature = "formatter")]
if let Some(matches) = matches.subcommand_matches("format") {
let badge = "[Mech Formatter]".truecolor(34, 204, 187);
let html_flag = matches.get_flag("html");
let stylesheet_path = matches
.get_one::<String>("stylesheet")
.cloned()
.unwrap_or("".to_string());
let shim_path = matches
.get_one::<String>("shim")
.cloned()
.unwrap_or("".to_string());
let output_path =
PathBuf::from(matches.get_one::<String>("output_path").cloned().unwrap_or(".".to_string()));
let is_output_file = output_path.extension().is_some();
let mech_paths: Vec<String> = matches
.get_many::<String>("mech_format_file_paths")
.map_or(vec![], |files| files.map(|file| file.to_string()).collect());
if mech_paths.len() == 1 {
let input_path = PathBuf::from(&mech_paths[0]);
if input_path.is_dir() && is_output_file {
eprintln!(
"{} Cannot write directory `{}` into single output file `{}`. Provide a directory for --out instead.",
"[Error]".truecolor(246,98,78),
input_path.display(),
output_path.display()
);
return Ok(());
}
}
let mut mechfs = MechFileSystem::new();
println!("{} Loading resources...", badge);
print!("{} Loading stylesheet...", badge);
let stylesheet = read_or_download(&stylesheet_path, &stylesheet_backup_url, Some(STYLESHEET.as_bytes()))
.await?;
let stylesheet_str = String::from_utf8(stylesheet).map_err(|e| {
MechError::new(
Utf8ConversionError {
source_error: e.to_string(),
},
None,
)
.with_compiler_loc()
})?;
print!("{} Loading HTML shim...", badge);
let shim = read_or_download(&shim_path, &shim_backup_url, Some(SHIMHTML.as_bytes())).await?;
let shim_str = String::from_utf8(shim).map_err(|e| {
MechError::new(
Utf8ConversionError {
source_error: e.to_string(),
},
None,
)
.with_compiler_loc()
})?;
mechfs.set_stylesheet(&stylesheet_str);
mechfs.set_shim(&shim_str);
for path in mech_paths {
mechfs.watch_source(&path)?;
}
let sources = mechfs.sources();
let read_sources = sources.read().unwrap();
if !is_output_file && output_path != PathBuf::from(".") {
match fs::create_dir_all(&output_path) {
Ok(_) => println!(
"{} Directory created: {}",
"[Created]".truecolor(153, 221, 85),
output_path.display()
),
Err(err) => println!("Error creating directory: {:?}", err),
}
}
if html_flag {
let html_items: Vec<_> = read_sources.html_iter().collect();
let is_single_html = html_items.len() == 1;
if is_output_file && is_single_html {
let (_, mech_src) = html_items[0];
if let MechSourceCode::Html(content) = mech_src {
save_to_file(output_path, content)?;
}
} else {
for (fid, mech_src) in html_items {
if let MechSourceCode::Html(content) = mech_src {
let mut filename = read_sources.get_path_from_id(*fid).unwrap().clone();
filename = filename.with_extension("html");
let output_file = if is_output_file { output_path.clone() }
else { output_path.join(filename) };
save_to_file(output_file, content)?;
}
}
}
} else {
for (fid, mech_src) in read_sources.sources_iter() {
let content = mech_src.to_string();
let filename = read_sources.get_path_from_id(*fid).unwrap().clone();
let output_file = if is_output_file { output_path.clone() }
else { output_path.join(filename) };
save_to_file(output_file, &content)?;
}
}
return Ok(());
}
let mut caught_inturrupts = Arc::new(Mutex::new(0));
let uuid = generate_uuid();
let mut intrp = Interpreter::new(uuid);
#[cfg(feature = "run")]
{
let mut paths = if let Some(m) = matches.get_many::<String>("mech_paths") {
m.map(|s| s.to_string()).collect()
} else { repl_flag = true; vec![] };
let mut mechfs = MechFileSystem::new();
let any_look_like_paths = paths.iter().any(|p| {
is_intended_path(p)
});
if !paths.is_empty() {
if any_look_like_paths {
let mut watch_errors = Vec::new();
for p in &paths {
match mechfs.watch_source(p) {
Ok(r) => {}
Err(err) => watch_errors.push(err),
}
}
if !watch_errors.is_empty() {
for err in &watch_errors {
print_mech_error(err);
}
std::process::exit(1);
}
} else {
intrp.clear();
let joined = paths.join(" ");
let parse_result = parser::parse(joined.trim());
match parse_result {
Ok(tree) => match intrp.interpret(&tree) {
Ok(r) => {
println!("{}", r.kind());
#[cfg(feature = "pretty_print")]
println!("{}", r.pretty_print());
#[cfg(not(feature = "pretty_print"))]
println!("{:#?}", r);
std::process::exit(0);
}
Err(err) => {
println!("{} {:#?}",
"[Error]".truecolor(246,98,78),
err
);
std::process::exit(1);
}
},
Err(err) => {
println!("{} {:#?}",
"[Parse Error]".truecolor(246,98,78),
err
);
std::process::exit(1);
}
}
}
}
let result = run_mech_code(&mut intrp, &mechfs, tree_flag, debug_flag, time_flag, trace_flag);
if !repl_flag {
match &result {
Ok(r) => {
println!("{}", r.kind());
#[cfg(feature = "pretty_print")]
println!("{}", r.pretty_print());
#[cfg(not(feature = "pretty_print"))]
println!("{:#?}", r);
std::process::exit(0);
}
Err(err) => {
print_mech_error(err);
std::process::exit(1);
}
};
}
#[cfg(windows)]
control::set_virtual_terminal(true).unwrap();
clc();
let mut stdo = stdout();
stdo.execute(Print(text_logo));
stdo.execute(cursor::MoveToNextLine(1));
println!("\n {} ",format!("v{}",VERSION).truecolor(246,192,78));
println!(" {} \n", "www.mech-lang.org");
let intro_message = format!("{}Enter {} for a list of all commands.{}\n", mika_open, help_cmd, mika_close);
println!("{} {}", micromika, intro_message);
let mut ci = caught_inturrupts.clone();
ctrlc::set_handler(move || {
println!("{}", ctrlc_cmd);
let mut caught_inturrupts = ci.lock().unwrap();
*caught_inturrupts += 1;
if *caught_inturrupts >= 3 {
let final_state = ProgressBar::new_spinner();
let completed_style = ProgressStyle::with_template(
"\n{spinner:.yellow} {msg}"
).unwrap().tick_strings(MICROMIKA_WAVE);
final_state.set_style(completed_style);
final_state.set_message(format!("{}Okay cya!{}\n", mika_open, mika_close));
for _ in 0..MICROMIKA_WAVE.len() - 1 {
thread::sleep(Duration::from_millis(100));
final_state.tick();
}
std::process::exit(0);
}
println!("\n{} {}Enter {} to terminate this REPL session.{}\n", micromika_point, mika_open, quit_cmd, mika_close);
print_prompt();
}).expect("Error setting Ctrl+C handler");
}
#[cfg(feature = "repl")]
let mut repl = MechRepl::from(intrp);
#[cfg(feature = "repl")]
'REPL: loop {
{
let mut ci = caught_inturrupts.lock().unwrap();
*ci = 0;
}
print_prompt();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
if input.chars().nth(0) == Some(':') {
match parse_repl_command(&input.as_str()) {
Ok((_, repl_command)) => {
match repl.execute_repl_command(repl_command) {
Ok(output) => {
println!("{}", output);
}
Err(err) => {
println!("!{:?}", err);
}
}
}
Err(x) => {
println!("{} Unrecognized command: {}", "[Error]".truecolor(246,98,78), x);
}
}
} else if input.trim() == "" {
continue;
} else {
let cmd = ReplCommand::Code(vec![("repl".to_string(),MechSourceCode::String(input))]);
match repl.execute_repl_command(cmd) {
Ok(output) => {
println!("{}", output);
}
Err(err) => {
println!("(x)> {:#?}", err);
}
}
}
}
Ok(())
}
#[cfg(feature = "async")]
pub async fn load_resource(resource_path: &str) -> String {
if resource_path.starts_with("http") {
match reqwest::get(resource_path).await {
Ok(response) => match response.text().await {
Ok(text) => text,
Err(err) => {
eprintln!("Error fetching resource text: {:?}", err);
String::new()
}
},
Err(err) => {
eprintln!("Error fetching resource: {:?}", err);
String::new()
}
}
} else {
match tokio::fs::read_to_string(resource_path).await {
Ok(content) => content,
Err(err) => {
eprintln!("Error reading resource file: {:?}", err);
String::new()
}
}
}
}
#[cfg(not(feature = "async"))]
pub fn load_resource(resource_path: &str) -> String {
if resource_path.starts_with("http") {
match reqwest::blocking::get(resource_path) {
Ok(response) => match response.text() {
Ok(text) => text,
Err(err) => {
eprintln!("Error fetching resource text: {:?}", err);
String::new()
}
},
Err(err) => {
eprintln!("Error fetching resource: {:?}", err);
String::new()
}
}
} else {
match std::fs::read_to_string(resource_path) {
Ok(content) => content,
Err(err) => {
eprintln!("Error reading resource file: {:?}", err);
String::new()
}
}
}
}
fn is_intended_path(s: &str) -> bool {
if s.trim().is_empty() { return false; }
let path = Path::new(s);
if s.starts_with("./") || s.starts_with(".\\") ||
s.starts_with("../") || s.starts_with("..\\") ||
s.starts_with('/') || s.starts_with('\\') {
return true;
}
if s.len() > 2 && s.as_bytes()[1] == b':' {
return true;
}
if s.contains('/') || s.contains('\\') {
return true;
}
if let Some(ext) = path.extension().and_then(OsStr::to_str) {
match ext {
"mec" | "🤖" | "mecb" | "mdoc" | "mpkg" => true,
"m" | "csv" | "tsv" | "txt" | "md" | "json" | "toml" | "yaml" => true,
"html" | "htm" | "css" | "js" | "wasm" => true,
"png" | "jpg" | "jpeg" | "gif" | "svg" | "bmp" | "ico" => true,
_ => false,
}
} else {
false
}
}
fn source_range_to_offset_range(file_content: &str, range: &SourceRange) -> (usize, usize) {
let mut offset = 0;
let mut start_offset = 0;
let mut end_offset = 0;
for (line_index, line) in file_content.split_inclusive('\n').enumerate() {
let row = line_index + 1;
let line_len = line.len();
if row == range.start.row {
start_offset = offset + (range.start.col - 1);
}
if row == range.end.row {
end_offset = offset + (range.end.col - 1);
break;
}
offset += line_len;
}
end_offset = end_offset.min(file_content.len());
while start_offset < end_offset
&& file_content.as_bytes()[start_offset].is_ascii_whitespace()
{
start_offset += 1;
}
while end_offset > start_offset
&& file_content.as_bytes()[end_offset - 1].is_ascii_whitespace()
{
end_offset -= 1;
}
if end_offset <= start_offset {
end_offset = start_offset + 1;
end_offset = end_offset.min(file_content.len());
}
(start_offset, end_offset)
}
pub fn print_mech_error(err: &MechError) {
if let Some(watch_error) = err.kind_as::<WatchPathFailed>() {
let src_file_path = watch_error.file_path.to_string();
match &err.source {
Some(src_err) => {
if let Some(report) = &src_err.kind_as::<ParserErrorReport>() {
let first_error_range = report.1.first().map(|e| e.cause_rng.clone()).unwrap_or(SourceRange::default());
let (first_start, first_end) = source_range_to_offset_range(&report.0, &first_error_range);
let mut error_report = Report::build(ReportKind::Error, (src_file_path.clone(), first_start..first_end))
.with_message(format!("Syntax Errors Found: {}", report.1.len()));
for (err_num, err_ctx) in report.1.iter().enumerate() {
let (start, end) = source_range_to_offset_range(&report.0, &err_ctx.cause_rng);
if let Some(annotation_rng) = err_ctx.annotation_rngs.first() {
let (ann_start, ann_end) = source_range_to_offset_range(&report.0, annotation_rng);
error_report = error_report.with_label(
Label::new((src_file_path.clone(), ann_start..ann_end))
.with_message(format!(
"#{}: {} [{}:{}]",
err_num + 1,
err_ctx.err_message,
annotation_rng.start.row,
annotation_rng.start.col
))
.with_color(Color::Yellow),
);
} else {
error_report = error_report.with_label(
Label::new((src_file_path.clone(), start..end))
.with_message(format!(
"#{}: {} [{}:{}]",
err_num + 1,
err_ctx.err_message,
err_ctx.cause_rng.start.row,
err_ctx.cause_rng.start.col
))
.with_color(Color::Yellow),
);
}
}
let cache = sources([(src_file_path.clone(), report.0.clone())]);
error_report.finish().print(cache).unwrap_or_else(|e| {
println!("Error printing report: {:?}", e);
});
} else {
println!("Error:");
println!("{:#?}", err);
}
}
None => {
println!("Error:");
println!("{:#?}", err);
}
}
} else {
println!("Error:");
println!("{:#?}", err);
}
}