diff --git a/Cargo.lock b/Cargo.lock index ca7c9b3..0c49be0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -46,6 +55,12 @@ version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + [[package]] name = "cfg-if" version = "1.0.0" @@ -144,6 +159,8 @@ dependencies = [ "ron", "serde", "termion", + "tree-sitter", + "tree-sitter-c", ] [[package]] @@ -282,6 +299,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + [[package]] name = "mio" version = "0.8.4" @@ -419,6 +442,23 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + [[package]] name = "ron" version = "0.8.0" @@ -514,6 +554,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "tree-sitter" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4423c784fe11398ca91e505cdc71356b07b1a924fc8735cfab5333afe3e18bc" +dependencies = [ + "cc", + "regex", +] + +[[package]] +name = "tree-sitter-c" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca211f4827d4b4dc79f388bf67b6fa3bc8a8cfa642161ef24f99f371ba34c7b" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "unicode-ident" version = "1.0.4" diff --git a/Cargo.toml b/Cargo.toml index 4f0dedb..b0c2980 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ notify-debouncer-mini = "0.2.1" ron = "0.8.0" serde = { version = "1.0.144", features = ["derive"] } termion = "1.5.6" +tree-sitter = "0.20.9" +tree-sitter-c = "0.20.2" [profile.release] lto = true diff --git a/test.h b/clib/test.h similarity index 100% rename from test.h rename to clib/test.h diff --git a/example/simple/main.c b/example/simple/main.c index a616ec4..6308544 100644 --- a/example/simple/main.c +++ b/example/simple/main.c @@ -1,15 +1,6 @@ #include -#include "../../test.h" int main() { int a; printf("hello, world!! %d\n", a); } - -void test_it_works() { - assert_eq_int(2 + 2, 4); -} - -void test_it_fails() { - assert_eq_int(2 + 2, 5); -} diff --git a/example/simple/test.c b/example/simple/test.c new file mode 100644 index 0000000..38f1f19 --- /dev/null +++ b/example/simple/test.c @@ -0,0 +1,10 @@ +#include +#include "../../clib/test.h" + +void test_it_works() { + assert_eq_int(2 + 2, 4); +} + +void test_it_fails() { + assert_eq_int(2 + 2, 5); +} diff --git a/src/config.rs b/src/config.rs index 2d29c7b..deb7177 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,21 +1,80 @@ -pub struct UserConfig { - main_file: Option, - test_file: Option, - includes: Option>, -} +use std::{ + env, fs, + path::{Path, PathBuf}, +}; +use ron::ser::PrettyConfig; +use serde::{Deserialize, Serialize}; + +use crate::utils::Apply; + +#[derive(Debug, Serialize, Deserialize)] pub struct Config { + name: String, main_file: String, test_file: String, includes: Vec, } -impl Default for Config { - fn default() -> Self { +impl Config { + const CONFIG_FILE_NAME: &'static str = "pi.ron"; + pub fn new(name: String) -> Self { Self { + name, main_file: "main.c".into(), test_file: "test.c".into(), includes: vec![], } } + + pub fn write(&self, mut path: PathBuf) { + path.extend([Self::CONFIG_FILE_NAME]); + let content = + ron::ser::to_string_pretty(self, PrettyConfig::default().struct_names(true)).unwrap(); + fs::write(path, content).unwrap(); + } + + pub fn get_current() -> Self { + let path = env::current_dir().unwrap(); + Self::get(&path) + .unwrap_or_else(|| Self::new(path.file_name().unwrap().to_str().unwrap().to_string())) + } + + pub fn get(path: &Path) -> Option { + let path = path.to_path_buf().canonicalize().unwrap(); + Self::try_get(&path).or_else(|| path.parent().and_then(Self::get)) + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn main_file(&self) -> &str { + &self.main_file + } + + pub fn test_file(&self) -> &str { + &self.test_file + } + + pub fn includes(&self) -> &Vec { + &self.includes + } + + fn try_get(path: &Path) -> Option { + let path = path.to_path_buf().apply(|p| p.push(Self::CONFIG_FILE_NAME)); + fs::read_to_string(path) + .ok() + .and_then(|content| ron::from_str(&content).ok()) + } +} + +pub fn create(path: String) { + let absolute = fs::canonicalize(&path).unwrap(); + if !absolute.is_dir() { + panic!("not a directory"); + } + let name = absolute.file_name().unwrap(); + let config = Config::new(name.to_str().unwrap().to_string()); + config.write(absolute); } diff --git a/src/main.rs b/src/main.rs index 6ba98e3..74406a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use clap::{Parser, Subcommand}; +use config::Config; pub mod check; pub mod config; @@ -14,8 +15,6 @@ pub struct Arguments { command: Commands, } -// TODO: turn files into file lists - #[allow(non_camel_case_types)] #[derive(Subcommand)] pub enum Commands { @@ -25,7 +24,7 @@ pub enum Commands { files: Vec, }, - /// Runs a file + /// Runs a set of files or the default target. run { /// Files to run. files: Vec, @@ -33,15 +32,16 @@ pub enum Commands { /// Runs tests contained within a particular test file or test { - /// W.I.P. Wether to capture standard output or not. + /// Wether to capture standard output or not. #[clap(short, long)] capture: bool, /// Files to run tests from. files: Vec, - /// Specific test to run. - test: Option, + /// Specific tests to run. + #[clap(short, long)] + tests: Vec, }, /// Watches changes to source files and re run them @@ -49,6 +49,18 @@ pub enum Commands { /// Files to run. files: Vec, }, + + /// + init { path: String }, +} + +fn append_includes(list: &mut Vec) { + list.extend( + Config::get_current() + .includes() + .iter() + .map(|f| f.to_string()), + ); } fn main() { @@ -56,14 +68,23 @@ fn main() { match args.command { Commands::check { files } => check::main(files), - Commands::run { files } => { + Commands::run { mut files } => { + append_includes(&mut files); run::main(files); } Commands::test { capture, - files, - test, - } => test::main(capture, files, test), - Commands::watch { files } => watch::main(files), + mut files, + tests, + } => { + append_includes(&mut files); + let tests = (!tests.is_empty()).then_some(tests); + test::main(capture, files, tests) + } + Commands::watch { mut files } => { + append_includes(&mut files); + watch::main(files) + } + Commands::init { path } => config::create(path), } } diff --git a/src/tasks.rs b/src/tasks.rs index 05120d8..f0ea485 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -2,10 +2,12 @@ use std::{ fs, path::PathBuf, process::{Command, ExitStatus}, + thread, + time::Duration, }; use crate::utils::{ - log_command_run, log_separator, log_separator_bottom, log_separator_top, tmp_file_path, Apply, + log_command_run, log_separator_bottom, log_separator_top, tmp_file_path, Apply, }; pub struct CompileTask { @@ -62,6 +64,7 @@ impl CompileTask { log_separator_top(); let status = command.status().unwrap(); log_separator_bottom(); + thread::sleep(Duration::from_millis(100)); status.success().then_some(output_path).ok_or(status) } } @@ -88,3 +91,19 @@ impl RunTask { } } } + +pub struct GenTask { + content: String, +} + +impl GenTask { + pub fn new(content: String) -> Self { + Self { content } + } + pub fn run(self) -> PathBuf { + let output_path = tmp_file_path().apply(|o| o.set_extension("c")); + fs::write(&output_path, &self.content).unwrap(); + dbg!(fs::read_to_string(&output_path).unwrap()); + output_path + } +} diff --git a/src/test.rs b/src/test.rs index 523b802..60c241b 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,29 +1,53 @@ -pub fn main(_capture: bool, _files: Vec, _test: Option) { - let content = todo!(); - let tests = find_tests(content); - for test in tests { - // compile - // run +use std::fs; + +use crate::tasks::{CompileTask, GenTask, RunTask}; + +pub fn main(_capture: bool, files: Vec, _test: Option>) { + // let includes = files + // .iter() + // .cloned() + // .map(|p| PathBuf::from_str(&p).unwrap()) + // .collect::>(); + + for path in files { + let content = fs::read_to_string(&path).unwrap(); + let tests = find_tests(content); + for test in tests { + let content = gen_test_main(fs::canonicalize(&path).unwrap().to_str().unwrap(), &test); + let generated_code = GenTask::new(content).run(); + + // compile with all files + //let files = includes.clone().apply(|v| v.insert(0, generated_code)); + let generated_bin = CompileTask::new(vec![generated_code]).run().unwrap(); + // run + RunTask::new(generated_bin).run().unwrap(); + } } } pub fn find_tests(source: String) -> Vec { source .split([' ', '(', ')', ';']) - .filter(|name| &name[0..5] == "test_") + .filter(|name| name.starts_with("test_")) .map(String::from) .collect() } -pub fn gen_test_main(test_file: &str, test: &str) -> String { +pub fn gen_test_main(path: &str, test: &str) -> String { format!( " -#include \"{test_file}\" +void ____test(); int main() {{ - {test}(); + ____test(); return 0; - }} +}} + +#include \"{path}\" + +void ____test() {{ + {test}(); +}} " ) }