Work on auto CST parser tests.
This commit is contained in:
parent
41693788fc
commit
024baf2064
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -169,6 +169,17 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cst-test-generator"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"convert_case",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deimos"
|
name = "deimos"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -176,6 +187,7 @@ dependencies = [
|
|||||||
"ast-generator",
|
"ast-generator",
|
||||||
"clap",
|
"clap",
|
||||||
"codespan-reporting",
|
"codespan-reporting",
|
||||||
|
"cst-test-generator",
|
||||||
"indoc",
|
"indoc",
|
||||||
"log",
|
"log",
|
||||||
"pest",
|
"pest",
|
||||||
|
|||||||
@ -21,7 +21,8 @@ indoc = "2.0.6"
|
|||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
ast-generator = { path = "ast-generator" }
|
ast-generator = { path = "ast-generator" }
|
||||||
|
cst-test-generator = { path = "cst-test-generator" }
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["ast-generator"]
|
members = ["ast-generator", "cst-test-generator"]
|
||||||
|
|||||||
19
build.rs
19
build.rs
@ -1,9 +1,18 @@
|
|||||||
|
use cst_test_generator::generate_test_files;
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
println!("cargo:rerun-if-changed=src/parser/deimos.pest");
|
println!("cargo:rerun-if-changed=src/parser/deimos.pest");
|
||||||
// let out_dir = env::var("OUT_DIR").unwrap();
|
let out_dir = env::var("OUT_DIR").unwrap();
|
||||||
// let out_dir_path = Path::new(&out_dir);
|
let out_dir_path = Path::new(&out_dir);
|
||||||
// let testing_txt_path = out_dir_path.join("testing.rs");
|
let parser_tests_dir = out_dir_path.join("src").join("parser").join("tests");
|
||||||
// let output = test_dump();
|
fs::create_dir_all(&parser_tests_dir)?;
|
||||||
// write(&testing_txt_path, output)?;
|
let files = generate_test_files(Path::new("src/parser/tests"))?;
|
||||||
|
for parser_test_file in &files {
|
||||||
|
let file_path = parser_tests_dir.join(&parser_test_file.file_name);
|
||||||
|
fs::write(file_path, &parser_test_file.contents)?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
11
cst-test-generator/Cargo.toml
Normal file
11
cst-test-generator/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "cst-test-generator"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
convert_case = "0.8.0"
|
||||||
|
prettyplease = "0.2.37"
|
||||||
|
proc-macro2 = "1.0.101"
|
||||||
|
quote = "1.0.40"
|
||||||
|
syn = "2.0.106"
|
||||||
115
cst-test-generator/src/lib.rs
Normal file
115
cst-test-generator/src/lib.rs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
use convert_case::{Case, Casing};
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{format_ident, quote};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::{fs, io};
|
||||||
|
use syn::File;
|
||||||
|
|
||||||
|
pub struct ParserTestFile {
|
||||||
|
pub file_name: String,
|
||||||
|
pub contents: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_test_files(tests_dir: &Path) -> io::Result<Vec<ParserTestFile>> {
|
||||||
|
let mut files: Vec<ParserTestFile> = vec![];
|
||||||
|
let mut test_module_names: Vec<String> = vec![];
|
||||||
|
|
||||||
|
// generate test file for each sub dir
|
||||||
|
for sub_dir in fs::read_dir(tests_dir)? {
|
||||||
|
let sub_dir = sub_dir?;
|
||||||
|
let sub_dir_path = sub_dir.path();
|
||||||
|
if sub_dir_path.is_dir() {
|
||||||
|
let sub_dir_file_name = sub_dir.file_name();
|
||||||
|
let sub_dir_string = sub_dir_file_name.to_string_lossy();
|
||||||
|
test_module_names.push(sub_dir_string.to_string());
|
||||||
|
|
||||||
|
let sub_dir_pascal = sub_dir_string.to_case(Case::Pascal);
|
||||||
|
let rule_ident = format_ident!("{}", sub_dir_pascal);
|
||||||
|
|
||||||
|
let mut tests: Vec<TokenStream> = vec![];
|
||||||
|
for test_file in fs::read_dir(sub_dir_path)? {
|
||||||
|
let test_file = test_file?;
|
||||||
|
let test_file_name = test_file.file_name();
|
||||||
|
let test_ident = format_ident!("{}", test_file_name.to_string_lossy());
|
||||||
|
|
||||||
|
let src_input = fs::read_to_string(test_file.path())?;
|
||||||
|
|
||||||
|
let test = quote! {
|
||||||
|
#[test]
|
||||||
|
fn #test_ident() {
|
||||||
|
parses_to(Rule::#rule_ident, #src_input)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tests.push(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tests_mod_ident = format_ident!("{}_tests", sub_dir.file_name().to_string_lossy());
|
||||||
|
let test_file_contents = quote! {
|
||||||
|
#[cfg(test)]
|
||||||
|
mod #tests_mod_ident {
|
||||||
|
use crate::parser::Rule;
|
||||||
|
use crate::parser::tests::parses_to;
|
||||||
|
|
||||||
|
#(#tests)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let parsed: File = syn::parse2(test_file_contents).unwrap();
|
||||||
|
let contents = prettyplease::unparse(&parsed);
|
||||||
|
|
||||||
|
let file_name = sub_dir.file_name().to_string_lossy().to_string() + ".rs";
|
||||||
|
files.push(ParserTestFile {
|
||||||
|
file_name,
|
||||||
|
contents,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
println!("Warning: not a directory: {:?}", sub_dir_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let test_mod_statements = test_module_names
|
||||||
|
.iter()
|
||||||
|
.map(|name| format_ident!("{}", name))
|
||||||
|
.map(|module_ident| quote! { mod #module_ident; })
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// generate main mod.rs file
|
||||||
|
let main_mod = quote! {
|
||||||
|
use crate::parser::DeimosParser;
|
||||||
|
use crate::parser::Rule;
|
||||||
|
use pest_derive::Parser;
|
||||||
|
|
||||||
|
#(#test_mod_statements)*
|
||||||
|
|
||||||
|
pub(crate) fn parses_to(rule: Rule, input: &str) {
|
||||||
|
let parse_result = DeimosParser::parse(rule, input);
|
||||||
|
if let Err(e) = parse_result {
|
||||||
|
panic!("Parsing failed.\n{}", e);
|
||||||
|
} else {
|
||||||
|
let mut pairs = parse_result.unwrap();
|
||||||
|
if input.trim() != pairs.as_str().trim() {
|
||||||
|
panic!(
|
||||||
|
"Parsing did not consume entire input. Consumed only:\n{}",
|
||||||
|
pairs.as_str().trim()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let first = pairs.next().unwrap();
|
||||||
|
if rule != pair.as_rule() {
|
||||||
|
panic!(
|
||||||
|
"Expected {} but found {:?}.",
|
||||||
|
stringify!(rule),
|
||||||
|
pair.as_rule()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mod_file_parsed: File = syn::parse2(main_mod).unwrap();
|
||||||
|
let mod_file_contents = prettyplease::unparse(&mod_file_parsed);
|
||||||
|
files.push(ParserTestFile {
|
||||||
|
file_name: String::from("mod.rs"),
|
||||||
|
contents: mod_file_contents,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(files)
|
||||||
|
}
|
||||||
@ -107,46 +107,46 @@ mod deimos_parser_tests {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
// #[test]
|
||||||
fn if_else_statement() {
|
// fn if_else_statement() {
|
||||||
parses_to(
|
// parses_to(
|
||||||
Rule::IfElseStatement,
|
// Rule::IfElseStatement,
|
||||||
indoc! {"
|
// indoc! {"
|
||||||
if (foo == 42) {
|
// if (foo == 42) {
|
||||||
bar()
|
// bar()
|
||||||
} else {
|
// } else {
|
||||||
baz()
|
// baz()
|
||||||
}"},
|
// }"},
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
#[test]
|
// #[test]
|
||||||
fn if_else_if_statement() {
|
// fn if_else_if_statement() {
|
||||||
parses_to(
|
// parses_to(
|
||||||
Rule::IfElseStatement,
|
// Rule::IfElseStatement,
|
||||||
indoc! {"
|
// indoc! {"
|
||||||
if (foo == 42) {
|
// if (foo == 42) {
|
||||||
bar()
|
// bar()
|
||||||
} else if (foo == 16) {
|
// } else if (foo == 16) {
|
||||||
baz()
|
// baz()
|
||||||
}"},
|
// }"},
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
#[test]
|
// #[test]
|
||||||
fn if_else_if_else_statement() {
|
// fn if_else_if_else_statement() {
|
||||||
parses_to(
|
// parses_to(
|
||||||
Rule::IfElseStatement,
|
// Rule::IfElseStatement,
|
||||||
indoc! {"
|
// indoc! {"
|
||||||
if (foo == 42) {
|
// if (foo == 42) {
|
||||||
foo()
|
// foo()
|
||||||
} else if (foo == 16) {
|
// } else if (foo == 16) {
|
||||||
baz()
|
// baz()
|
||||||
} else {
|
// } else {
|
||||||
fizz()
|
// fizz()
|
||||||
}"},
|
// }"},
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn while_statement() {
|
fn while_statement() {
|
||||||
|
|||||||
1
src/parser/tests/backtick_string/empty
Normal file
1
src/parser/tests/backtick_string/empty
Normal file
@ -0,0 +1 @@
|
|||||||
|
``
|
||||||
1
src/parser/tests/backtick_string/expressions
Normal file
1
src/parser/tests/backtick_string/expressions
Normal file
@ -0,0 +1 @@
|
|||||||
|
`${greeting}, ${world}!`
|
||||||
1
src/parser/tests/backtick_string/no_expressions
Normal file
1
src/parser/tests/backtick_string/no_expressions
Normal file
@ -0,0 +1 @@
|
|||||||
|
`Hello, World!`
|
||||||
Loading…
Reference in New Issue
Block a user