// Copyright 2022 The Goscript Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. 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 { 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 { 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> { 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

(filename: P) -> io::Result>> where P: AsRef, { let file = File::open(filename)?; Ok(io::BufReader::new(file).lines()) } fn parse_error(s: &str, line: usize) -> io::Result> { 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); //dbg!(results); } 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

(path: P) -> io::Result>> where P: AsRef, { let mut result = Map::new(); let mut parse_file = |lines: io::Lines>| -> 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/const1.gos", true); //todo: this test case not passing!!! 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); }