mirror of
https://github.com/HigherOrderCO/Kind1.git
synced 2024-08-15 19:30:41 +03:00
refactor: started to refactor in order to make tests
This commit is contained in:
parent
856343b1cf
commit
c7f9db59ba
@ -9,6 +9,6 @@ edition = "2021"
|
||||
kind-tree = { path = "../kind-tree" }
|
||||
kind-span = { path = "../kind-span" }
|
||||
kind-report = { path = "../kind-report" }
|
||||
hvm = "0.1.81"
|
||||
hvm = "0.1.89"
|
||||
fxhash = "0.2.1"
|
||||
im = "15.1.0"
|
@ -19,3 +19,9 @@ kind-report = { path = "../kind-report" }
|
||||
kind-checker = { path = "../kind-checker" }
|
||||
|
||||
clap = { version = "4.0.10", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
pretty_assertions = "1.3.0"
|
||||
ntest = "0.8.1"
|
||||
walkdir = "2"
|
||||
|
230
src/kind-cli/src/lib.rs
Normal file
230
src/kind-cli/src/lib.rs
Normal file
@ -0,0 +1,230 @@
|
||||
use std::path::PathBuf;
|
||||
use std::time::Instant;
|
||||
use std::{fmt, io};
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use kind_driver::session::Session;
|
||||
use kind_report::data::{Diagnostic, DiagnosticFrame, Log};
|
||||
use kind_report::report::{FileCache, Report};
|
||||
use kind_report::RenderConfig;
|
||||
|
||||
use kind_driver as driver;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[clap(propagate_version = true)]
|
||||
pub struct Cli {
|
||||
/// Configuration file to change information about
|
||||
/// pretty printing or project root.
|
||||
#[arg(short, long, value_name = "FILE")]
|
||||
pub config: Option<PathBuf>,
|
||||
|
||||
/// Turn on the debugging information generated
|
||||
/// by the compiler.
|
||||
#[arg(short, long)]
|
||||
pub debug: bool,
|
||||
|
||||
/// Show warning messages
|
||||
#[arg(short, long)]
|
||||
pub warning: bool,
|
||||
|
||||
/// Disable colors in error messages
|
||||
#[arg(short, long)]
|
||||
pub no_color: bool,
|
||||
|
||||
/// Only ascii characters in error messages
|
||||
#[arg(short, long)]
|
||||
pub ascii: bool,
|
||||
|
||||
#[command(subcommand)]
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum Command {
|
||||
/// Check a file
|
||||
#[clap(aliases = &["c"])]
|
||||
Check { file: String },
|
||||
|
||||
/// Evaluates Main on Kind2
|
||||
#[clap(aliases = &["e"])]
|
||||
Eval { file: String },
|
||||
|
||||
#[clap(aliases = &["k"])]
|
||||
ToKindCore { file : String },
|
||||
|
||||
/// Runs Main on the HVM
|
||||
#[clap(aliases = &["r"])]
|
||||
Run { file: String },
|
||||
|
||||
/// Generates a checker (.hvm) for a file
|
||||
#[clap(aliases = &["gc"])]
|
||||
GenChecker { file: String },
|
||||
|
||||
/// Stringifies a file
|
||||
#[clap(aliases = &["s"])]
|
||||
Show { file: String },
|
||||
|
||||
/// Compiles a file to Kindelia (.kdl)
|
||||
#[clap(aliases = &["kdl"])]
|
||||
ToKDL {
|
||||
file: String,
|
||||
/// If given, a namespace that goes before each compiled name. Can be at most 10 charaters long.
|
||||
#[clap(long, aliases = &["ns"])]
|
||||
namespace: Option<String>,
|
||||
},
|
||||
|
||||
/// Compiles a file to HVM (.hvm)
|
||||
#[clap(aliases = &["hvm"])]
|
||||
ToHVM { file: String },
|
||||
|
||||
/// Watch for file changes and then
|
||||
/// check when some file change.
|
||||
#[clap(aliases = &["w"])]
|
||||
Watch { file: String },
|
||||
|
||||
/// Read eval print loop
|
||||
#[clap(aliases = &["re"])]
|
||||
Repl,
|
||||
}
|
||||
|
||||
/// Helper structure to use stderr as fmt::Write
|
||||
struct ToWriteFmt<T>(pub T);
|
||||
|
||||
impl<T> fmt::Write for ToWriteFmt<T>
|
||||
where
|
||||
T: io::Write,
|
||||
{
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
self.0.write_all(s.as_bytes()).map_err(|_| fmt::Error)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_to_stderr<T, E>(render_config: &RenderConfig, session: &T, err: &E)
|
||||
where
|
||||
T: FileCache,
|
||||
E: Report,
|
||||
{
|
||||
Report::render(
|
||||
err,
|
||||
session,
|
||||
render_config,
|
||||
&mut ToWriteFmt(std::io::stderr()),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn compile_in_session<T>(
|
||||
render_config: RenderConfig,
|
||||
root: PathBuf,
|
||||
file: String,
|
||||
fun: &mut dyn FnMut(&mut Session) -> Option<T>,
|
||||
) -> Option<T> {
|
||||
let (rx, tx) = std::sync::mpsc::channel();
|
||||
|
||||
let mut session = Session::new(root, rx);
|
||||
|
||||
eprintln!();
|
||||
|
||||
render_to_stderr(
|
||||
&render_config,
|
||||
&session,
|
||||
&Log::Checking(format!("the file '{}'", file)),
|
||||
);
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
let res = fun(&mut session);
|
||||
|
||||
let diagnostics = tx.try_iter().collect::<Vec<DiagnosticFrame>>();
|
||||
|
||||
if diagnostics.is_empty() && res.is_some() {
|
||||
render_to_stderr(&render_config, &session, &Log::Checked(start.elapsed()));
|
||||
eprintln!();
|
||||
res
|
||||
} else {
|
||||
render_to_stderr(&render_config, &session, &Log::Failed(start.elapsed()));
|
||||
eprintln!();
|
||||
for diagnostic in diagnostics {
|
||||
let diagnostic: Diagnostic = (&diagnostic).into();
|
||||
render_to_stderr(&render_config, &session, &diagnostic)
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_cli(config: Cli) {
|
||||
|
||||
kind_report::check_if_colors_are_supported(config.no_color);
|
||||
|
||||
let render_config = kind_report::check_if_utf8_is_supported(config.ascii, 2);
|
||||
let root = PathBuf::from(".");
|
||||
|
||||
match config.command {
|
||||
Command::Check { file } => {
|
||||
compile_in_session(render_config, root, file.clone(), &mut |session| {
|
||||
driver::type_check_book(session, &PathBuf::from(file.clone()))
|
||||
});
|
||||
}
|
||||
Command::ToHVM { file } => {
|
||||
compile_in_session(render_config, root, file.clone(), &mut |session| {
|
||||
driver::compile_book_to_hvm(session, &PathBuf::from(file.clone()))
|
||||
})
|
||||
.map(|res| {
|
||||
println!("{}", res);
|
||||
res
|
||||
});
|
||||
}
|
||||
Command::Run { file } => {
|
||||
compile_in_session(render_config, root, file.clone(), &mut |session| {
|
||||
driver::compile_book_to_hvm(session, &PathBuf::from(file.clone()))
|
||||
})
|
||||
.map(|res| {
|
||||
println!("{}", driver::execute_file(&res));
|
||||
res
|
||||
});
|
||||
}
|
||||
Command::Eval { file } => {
|
||||
compile_in_session(render_config, root, file.clone(), &mut |session| {
|
||||
driver::erase_book(session, &PathBuf::from(file.clone()))
|
||||
})
|
||||
.map(|res| {
|
||||
println!("{}", driver::eval_in_checker(&res));
|
||||
res
|
||||
});
|
||||
}
|
||||
Command::Show { file } => {
|
||||
compile_in_session(render_config, root, file.clone(), &mut |session| {
|
||||
driver::to_book(session, &PathBuf::from(file.clone()))
|
||||
})
|
||||
.map(|res| {
|
||||
print!("{}", res);
|
||||
res
|
||||
});
|
||||
}
|
||||
Command::ToKindCore { file } => {
|
||||
compile_in_session(render_config, root, file.clone(), &mut |session| {
|
||||
driver::desugar_book(session, &PathBuf::from(file.clone()))
|
||||
})
|
||||
.map(|res| {
|
||||
print!("{}", res);
|
||||
res
|
||||
});
|
||||
}
|
||||
Command::GenChecker { file } => {
|
||||
compile_in_session(render_config, root, file.clone(), &mut |session| {
|
||||
driver::check_erasure_book(session, &PathBuf::from(file.clone()))
|
||||
})
|
||||
.map(|res| {
|
||||
print!("{}", driver::generate_checker(&res));
|
||||
res
|
||||
});
|
||||
}
|
||||
Command::ToKDL {
|
||||
file: _,
|
||||
namespace: _,
|
||||
} => todo!(),
|
||||
Command::Watch { file: _ } => todo!(),
|
||||
Command::Repl => todo!(),
|
||||
}
|
||||
}
|
@ -1,231 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
use std::time::Instant;
|
||||
use std::{fmt, io};
|
||||
use clap::Parser;
|
||||
use kind_cli::{run_cli, Cli};
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use kind_driver::session::Session;
|
||||
use kind_report::data::{Diagnostic, DiagnosticFrame, Log};
|
||||
use kind_report::report::{FileCache, Report};
|
||||
use kind_report::RenderConfig;
|
||||
|
||||
use kind_driver as driver;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[clap(propagate_version = true)]
|
||||
struct Cli {
|
||||
/// Configuration file to change information about
|
||||
/// pretty printing or project root.
|
||||
#[arg(short, long, value_name = "FILE")]
|
||||
config: Option<PathBuf>,
|
||||
|
||||
/// Turn on the debugging information generated
|
||||
/// by the compiler.
|
||||
#[arg(short, long)]
|
||||
debug: bool,
|
||||
|
||||
/// Show warning messages
|
||||
#[arg(short, long)]
|
||||
warning: bool,
|
||||
|
||||
/// Disable colors in error messages
|
||||
#[arg(short, long)]
|
||||
no_color: bool,
|
||||
|
||||
/// Only ascii characters in error messages
|
||||
#[arg(short, long)]
|
||||
ascii: bool,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Command {
|
||||
/// Check a file
|
||||
#[clap(aliases = &["c"])]
|
||||
Check { file: String },
|
||||
|
||||
/// Evaluates Main on Kind2
|
||||
#[clap(aliases = &["e"])]
|
||||
Eval { file: String },
|
||||
|
||||
#[clap(aliases = &["k"])]
|
||||
ToKindCore { file : String },
|
||||
|
||||
/// Runs Main on the HVM
|
||||
#[clap(aliases = &["r"])]
|
||||
Run { file: String },
|
||||
|
||||
/// Generates a checker (.hvm) for a file
|
||||
#[clap(aliases = &["gc"])]
|
||||
GenChecker { file: String },
|
||||
|
||||
/// Stringifies a file
|
||||
#[clap(aliases = &["s"])]
|
||||
Show { file: String },
|
||||
|
||||
/// Compiles a file to Kindelia (.kdl)
|
||||
#[clap(aliases = &["kdl"])]
|
||||
ToKDL {
|
||||
file: String,
|
||||
/// If given, a namespace that goes before each compiled name. Can be at most 10 charaters long.
|
||||
#[clap(long, aliases = &["ns"])]
|
||||
namespace: Option<String>,
|
||||
},
|
||||
|
||||
/// Compiles a file to HVM (.hvm)
|
||||
#[clap(aliases = &["hvm"])]
|
||||
ToHVM { file: String },
|
||||
|
||||
/// Watch for file changes and then
|
||||
/// check when some file change.
|
||||
#[clap(aliases = &["w"])]
|
||||
Watch { file: String },
|
||||
|
||||
/// Read eval print loop
|
||||
#[clap(aliases = &["re"])]
|
||||
Repl,
|
||||
}
|
||||
|
||||
/// Helper structure to use stderr as fmt::Write
|
||||
struct ToWriteFmt<T>(pub T);
|
||||
|
||||
impl<T> fmt::Write for ToWriteFmt<T>
|
||||
where
|
||||
T: io::Write,
|
||||
{
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
self.0.write_all(s.as_bytes()).map_err(|_| fmt::Error)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_to_stderr<T, E>(render_config: &RenderConfig, session: &T, err: &E)
|
||||
where
|
||||
T: FileCache,
|
||||
E: Report,
|
||||
{
|
||||
Report::render(
|
||||
err,
|
||||
session,
|
||||
render_config,
|
||||
&mut ToWriteFmt(std::io::stderr()),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn compile_in_session<T>(
|
||||
render_config: RenderConfig,
|
||||
root: PathBuf,
|
||||
file: String,
|
||||
fun: &mut dyn FnMut(&mut Session) -> Option<T>,
|
||||
) -> Option<T> {
|
||||
let (rx, tx) = std::sync::mpsc::channel();
|
||||
|
||||
let mut session = Session::new(root, rx);
|
||||
|
||||
eprintln!();
|
||||
|
||||
render_to_stderr(
|
||||
&render_config,
|
||||
&session,
|
||||
&Log::Checking(format!("the file '{}'", file)),
|
||||
);
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
let res = fun(&mut session);
|
||||
|
||||
let diagnostics = tx.try_iter().collect::<Vec<DiagnosticFrame>>();
|
||||
|
||||
if diagnostics.is_empty() && res.is_some() {
|
||||
render_to_stderr(&render_config, &session, &Log::Checked(start.elapsed()));
|
||||
eprintln!();
|
||||
res
|
||||
} else {
|
||||
render_to_stderr(&render_config, &session, &Log::Failed(start.elapsed()));
|
||||
eprintln!();
|
||||
for diagnostic in diagnostics {
|
||||
let diagnostic: Diagnostic = (&diagnostic).into();
|
||||
render_to_stderr(&render_config, &session, &diagnostic)
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let config = Cli::parse();
|
||||
|
||||
kind_report::check_if_colors_are_supported(config.no_color);
|
||||
|
||||
let render_config = kind_report::check_if_utf8_is_supported(config.ascii, 2);
|
||||
let root = PathBuf::from(".");
|
||||
|
||||
match config.command {
|
||||
Command::Check { file } => {
|
||||
compile_in_session(render_config, root, file.clone(), &mut |session| {
|
||||
driver::type_check_book(session, &PathBuf::from(file.clone()))
|
||||
});
|
||||
}
|
||||
Command::ToHVM { file } => {
|
||||
compile_in_session(render_config, root, file.clone(), &mut |session| {
|
||||
driver::compile_book_to_hvm(session, &PathBuf::from(file.clone()))
|
||||
})
|
||||
.map(|res| {
|
||||
println!("{}", res);
|
||||
res
|
||||
});
|
||||
}
|
||||
Command::Run { file } => {
|
||||
compile_in_session(render_config, root, file.clone(), &mut |session| {
|
||||
driver::compile_book_to_hvm(session, &PathBuf::from(file.clone()))
|
||||
})
|
||||
.map(|res| {
|
||||
println!("{}", driver::execute_file(&res));
|
||||
res
|
||||
});
|
||||
}
|
||||
Command::Eval { file } => {
|
||||
compile_in_session(render_config, root, file.clone(), &mut |session| {
|
||||
driver::erase_book(session, &PathBuf::from(file.clone()))
|
||||
})
|
||||
.map(|res| {
|
||||
println!("{}", driver::eval_in_checker(&res));
|
||||
res
|
||||
});
|
||||
}
|
||||
Command::Show { file } => {
|
||||
compile_in_session(render_config, root, file.clone(), &mut |session| {
|
||||
driver::to_book(session, &PathBuf::from(file.clone()))
|
||||
})
|
||||
.map(|res| {
|
||||
print!("{}", res);
|
||||
res
|
||||
});
|
||||
}
|
||||
Command::ToKindCore { file } => {
|
||||
compile_in_session(render_config, root, file.clone(), &mut |session| {
|
||||
driver::desugar_book(session, &PathBuf::from(file.clone()))
|
||||
})
|
||||
.map(|res| {
|
||||
print!("{}", res);
|
||||
res
|
||||
});
|
||||
}
|
||||
Command::GenChecker { file } => {
|
||||
compile_in_session(render_config, root, file.clone(), &mut |session| {
|
||||
driver::check_erasure_book(session, &PathBuf::from(file.clone()))
|
||||
})
|
||||
.map(|res| {
|
||||
print!("{}", driver::generate_checker(&res));
|
||||
res
|
||||
});
|
||||
}
|
||||
Command::ToKDL {
|
||||
file: _,
|
||||
namespace: _,
|
||||
} => todo!(),
|
||||
Command::Watch { file: _ } => todo!(),
|
||||
Command::Repl => todo!(),
|
||||
}
|
||||
}
|
||||
pub fn main() {
|
||||
run_cli(Cli::parse())
|
||||
}
|
53
src/kind-cli/tests/mod.rs
Normal file
53
src/kind-cli/tests/mod.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use kind_cli::{run_cli, Cli};
|
||||
|
||||
use ntest::timeout;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use walkdir::{Error, WalkDir};
|
||||
|
||||
fn golden_test(path: &Path, run: fn(&Path) -> String) {
|
||||
let result = run(path);
|
||||
|
||||
let golden_path = path.with_extension("golden");
|
||||
if let Ok(to_check) = fs::read_to_string(golden_path.clone()) {
|
||||
assert_eq!(result, to_check, "Testing file '{}'", path.display());
|
||||
} else {
|
||||
let mut file = File::create(golden_path).unwrap();
|
||||
file.write_all(result.as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn test_kind2(path: &Path, run: fn(&Path) -> String) -> Result<(), Error> {
|
||||
for entry in WalkDir::new(path).follow_links(true) {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_file() && path.extension().map(|x| x == "kind2").unwrap_or(false) {
|
||||
golden_test(path, run);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
#[timeout(15000)]
|
||||
fn test_checker() -> Result<(), Error> {
|
||||
test_kind2(Path::new("./tests/suite/checker"), |path| {
|
||||
let config = Cli {
|
||||
ascii: true,
|
||||
no_color: true,
|
||||
config: None,
|
||||
command: kind_cli::Command::Check { file: path.to_str().unwrap().to_string() },
|
||||
debug: false,
|
||||
warning: true,
|
||||
};
|
||||
|
||||
// let (rx, tx) = std::sync::mpsc::channel();
|
||||
let root = PathBuf::from(".");
|
||||
run_cli(config);
|
||||
todo!()
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
@ -165,6 +165,15 @@ fn parse_and_store_book_by_path<'a>(
|
||||
path: &PathBuf,
|
||||
book: &'a mut Book,
|
||||
) -> bool {
|
||||
|
||||
if !path.exists() {
|
||||
session
|
||||
.diagnostic_sender
|
||||
.send(DriverError::CannotFindFile(path.to_str().unwrap().to_string()).into())
|
||||
.unwrap();
|
||||
return true;
|
||||
}
|
||||
|
||||
if session.loaded_paths_map.contains_key(&fs::canonicalize(path).unwrap()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
//! Crate to parse the kind2 grammar.
|
||||
|
||||
pub mod errors;
|
||||
pub mod expr;
|
||||
pub mod macros;
|
||||
|
Loading…
Reference in New Issue
Block a user