extern crate go_parser as fe;
extern crate go_types as types;
use go_parser::Map;
use regex::Regex;
use std::fs;
use std::fs::File;
use std::io::{self, BufRead};
use std::path::{Path, PathBuf};
use types::ImportKey;
use types::SourceRead;
pub struct FsReader<'a> {
base_dir: Option<&'a str>,
temp_file: Option<&'a str>,
}
impl<'a> FsReader<'a> {
pub fn new(base_dir: Option<&'a str>, temp_file: Option<&'a str>) -> FsReader<'a> {
FsReader {
base_dir,
temp_file,
}
}
pub fn temp_file_path() -> &'static str {
&"./temp_file_in_memory_for_testing_and_you_can_only_have_one.gos"
}
fn canonicalize_path(&self, path: &PathBuf) -> io::Result<PathBuf> {
if path.ends_with(Self::temp_file_path()) {
Ok(path.clone())
} else if !path.exists() {
Err(io::Error::from(io::ErrorKind::NotFound))
} else {
path.canonicalize()
}
}
fn is_local(path: &str) -> bool {
path == "." || path == ".." || path.starts_with("./") || path.starts_with("../")
}
}
impl<'a> SourceRead for FsReader<'a> {
fn working_dir(&self) -> &Path {
Path::new("./")
}
fn base_dir(&self) -> Option<&Path> {
self.base_dir.map(|x| Path::new(x))
}
fn read_file(&self, path: &Path) -> io::Result<String> {
if path.ends_with(Self::temp_file_path()) {
self.temp_file
.map(|x| x.to_owned())
.ok_or(io::Error::from(io::ErrorKind::NotFound))
} else {
fs::read_to_string(path)
}
}
fn read_dir(&self, path: &Path) -> io::Result<Vec<PathBuf>> {
Ok(fs::read_dir(path)?
.filter_map(|x| {
x.map_or(None, |e| {
let path = e.path();
(!path.is_dir()).then(|| path)
})
})
.collect())
}
fn is_file(&self, path: &Path) -> bool {
if path.ends_with(Self::temp_file_path()) {
true
} else {
path.is_file()
}
}
fn is_dir(&self, path: &Path) -> bool {
path.is_dir()
}
fn canonicalize_import(&self, key: &ImportKey) -> io::Result<(PathBuf, String)> {
let mut import_path = key.path.clone();
let path = if Self::is_local(&key.path) {
let mut wd = self.working_dir().to_owned();
wd.push(&key.dir);
wd.push(&key.path);
if let Some(base) = &self.base_dir() {
if let Ok(rel) = wd.as_path().strip_prefix(base) {
import_path = rel.to_string_lossy().to_string()
}
}
wd
} else {
if let Some(base) = &self.base_dir() {
let mut p = PathBuf::new();
p.push(base);
p.push(&key.path);
p
} else {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("base dir required for path: {}", key.path),
));
}
};
self.canonicalize_path(&path).map(|p| (p, import_path))
}
}
#[derive(Debug)]
struct ErrInfo {
text: String,
regex: Regex,
checked: bool,
line: usize,
}
impl ErrInfo {
fn new(txt: String, line: usize) -> ErrInfo {
let txt = txt.trim().trim_matches('"').to_string();
let regex = Regex::new(&txt).unwrap();
ErrInfo {
text: txt,
regex: regex,
checked: false,
line: line,
}
}
}
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
P: AsRef<Path>,
{
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
fn parse_error(s: &str, line: usize) -> io::Result<Vec<ErrInfo>> {
let mut texts = vec![];
let mut cursor = 0;
loop {
if let Some(index) = s[cursor..].find("/* ERROR ") {
if let Some(end) = s[cursor + index..].find(" */") {
let txt = s[cursor + index + "/* ERROR ".len()..cursor + index + end].to_string();
texts.push(ErrInfo::new(txt, line));
cursor = cursor + index + end
} else {
return Err(io::Error::new(io::ErrorKind::Other, "invalid comment"));
}
} else if let Some(index) = s[cursor..].find("// ERROR ") {
let txt = s[cursor + index + "// ERROR ".len()..].to_string();
texts.push(ErrInfo::new(txt, line));
break;
} else {
break;
}
}
Ok(texts)
}
fn test_file(path: &str, trace: bool) {
dbg!(path);
let pkgs = &mut Map::new();
let config = types::TraceConfig {
trace_parser: trace,
trace_checker: trace,
};
let reader = FsReader::new(None, None);
let fs = &mut fe::FileSet::new();
let asto = &mut fe::AstObjects::new();
let el = &mut fe::ErrorList::new();
let tco = &mut types::TCObjects::new();
let results = &mut Map::new();
let importer = &mut types::Importer::new(&config, &reader, fs, pkgs, results, asto, tco, el, 0);
let key = types::ImportKey::new(path, "./");
let _ = importer.import(&key);
if trace {
el.sort();
print!("{}", el);
}
let mut expected_errs = parse_comment_errors(path).unwrap();
for e in el.borrow().iter() {
if e.msg.starts_with('\t') || e.by_parser {
continue;
}
if let Some(errs) = expected_errs.get_mut(&e.pos.line) {
let mut found = false;
for info in errs.iter_mut() {
if !info.checked {
if info.text == e.msg || info.regex.is_match(&e.msg) {
info.checked = true;
found = true;
break;
}
}
}
if !found {
panic!("unexpected error(1): {}", e);
}
} else {
panic!("unexpected error(2): {}", e);
}
}
for (_, errs) in expected_errs.iter() {
for info in errs.iter() {
if !info.checked {
panic!(
"expected error at line {} not reported: {}",
info.line, info.text
);
}
}
}
}
fn parse_comment_errors<P>(path: P) -> io::Result<Map<usize, Vec<ErrInfo>>>
where
P: AsRef<Path>,
{
let mut result = Map::new();
let mut parse_file = |lines: io::Lines<io::BufReader<File>>| -> io::Result<()> {
for (i, x) in lines.enumerate() {
let t = x?;
let mut errors = parse_error(&t, i + 1)?;
if !errors.is_empty() {
let entry = result.entry(i + 1).or_insert(vec![]);
entry.append(&mut errors);
}
}
Ok(())
};
if path.as_ref().is_file() {
let lines = read_lines(path)?;
parse_file(lines)?;
} else if path.as_ref().is_dir() {
for entry in fs::read_dir(path)? {
let entry = entry?;
let path = entry.path();
if !path.is_dir() {
let lines = read_lines(path)?;
parse_file(lines)?;
}
}
}
Ok(result)
}
#[test]
fn test_auto() {
let trace = false;
test_file("./tests/data/builtins.gos", trace);
test_file("./tests/data/const0.gos", trace);
test_file("./tests/data/constdecl.gos", trace);
test_file("./tests/data/conversions.gos", trace);
test_file("./tests/data/conversions2.gos", trace);
test_file("./tests/data/cycles.gos", trace);
test_file("./tests/data/cycles1.gos", trace);
test_file("./tests/data/cycles2.gos", trace);
test_file("./tests/data/cycles3.gos", trace);
test_file("./tests/data/cycles4.gos", trace);
test_file("./tests/data/cycles5.gos", trace);
test_file("./tests/data/decls0.src", trace);
test_file("./tests/data/decls1.src", trace);
test_file("./tests/data/decls2", trace);
test_file("./tests/data/decls3.src", trace);
test_file("./tests/data/decls4.src", trace);
test_file("./tests/data/decls5.src", trace);
test_file("./tests/data/errors.src", trace);
test_file("./tests/data/expr0.src", trace);
test_file("./tests/data/expr2.src", trace);
test_file("./tests/data/expr3.src", trace);
test_file("./tests/data/gotos.src", trace);
test_file("./tests/data/importdecl0", trace);
test_file("./tests/data/importdecl1", trace);
test_file("./tests/data/init0.src", trace);
test_file("./tests/data/init1.src", trace);
test_file("./tests/data/init2.src", trace);
test_file("./tests/data/issues.src", trace);
test_file("./tests/data/labels.src", trace);
test_file("./tests/data/methodsets.src", trace);
test_file("./tests/data/shifts.src", trace);
test_file("./tests/data/stmt0.src", trace);
test_file("./tests/data/stmt1.src", trace);
test_file("./tests/data/vardecl.src", trace);
}
#[test]
fn test_temp() {
test_file("./tests/data/temp.gos", true);
}