From 77eede41c04e77a0321dff535e11ea27ace0b8cf Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 19 Sep 2019 19:58:13 -0700 Subject: [PATCH 1/4] witx tool: improve public api so you don't have to know about internal invariants the definitions/entries pattern involves upgrading Weak into Rc and also its not super intuitive, especially given ive written zero (0) docs. This is a bit nicer to use --- tools/witx/src/ast.rs | 79 ++++++++++++++++++++++++++++++++++++-- tools/witx/src/validate.rs | 13 +++---- 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/tools/witx/src/ast.rs b/tools/witx/src/ast.rs index 4df4d1e24..a6a4a7a90 100644 --- a/tools/witx/src/ast.rs +++ b/tools/witx/src/ast.rs @@ -18,8 +18,41 @@ impl Id { #[derive(Debug, Clone)] pub struct Document { - pub definitions: Vec, - pub entries: HashMap, + definitions: Vec, + entries: HashMap, +} + +impl Document { + pub(crate) fn new(definitions: Vec, entries: HashMap) -> Self { + Document { + definitions, + entries, + } + } + pub fn datatype(&self, name: &Id) -> Option> { + self.entries.get(name).and_then(|e| match e { + Entry::Datatype(d) => Some(d.upgrade().expect("always possible to upgrade entry")), + _ => None, + }) + } + pub fn datatypes<'a>(&'a self) -> impl Iterator> + 'a { + self.definitions.iter().filter_map(|d| match d { + Definition::Datatype(d) => Some(d.clone()), + _ => None, + }) + } + pub fn module(&self, name: &Id) -> Option> { + self.entries.get(&name).and_then(|e| match e { + Entry::Module(d) => Some(d.upgrade().expect("always possible to upgrade entry")), + _ => None, + }) + } + pub fn modules<'a>(&'a self) -> impl Iterator> + 'a { + self.definitions.iter().filter_map(|d| match d { + Definition::Module(d) => Some(d.clone()), + _ => None, + }) + } } #[derive(Debug, Clone)] @@ -122,8 +155,46 @@ pub struct UnionVariant { #[derive(Debug, Clone)] pub struct Module { pub name: Id, - pub definitions: Vec, - pub entries: HashMap, + definitions: Vec, + entries: HashMap, +} + +impl Module { + pub(crate) fn new( + name: Id, + definitions: Vec, + entries: HashMap, + ) -> Self { + Module { + name, + definitions, + entries, + } + } + pub fn import(&self, name: &str) -> Option> { + self.entries.get(&Id::new(name)).and_then(|e| match e { + ModuleEntry::Import(d) => Some(d.upgrade().expect("always possible to upgrade entry")), + _ => None, + }) + } + pub fn imports<'a>(&'a self) -> impl Iterator> + 'a { + self.definitions.iter().filter_map(|d| match d { + ModuleDefinition::Import(d) => Some(d.clone()), + _ => None, + }) + } + pub fn func(&self, name: &Id) -> Option> { + self.entries.get(name).and_then(|e| match e { + ModuleEntry::Func(d) => Some(d.upgrade().expect("always possible to upgrade entry")), + _ => None, + }) + } + pub fn funcs<'a>(&'a self) -> impl Iterator> + 'a { + self.definitions.iter().filter_map(|d| match d { + ModuleDefinition::Func(d) => Some(d.clone()), + _ => None, + }) + } } #[derive(Debug, Clone)] diff --git a/tools/witx/src/validate.rs b/tools/witx/src/validate.rs index 7041fca98..bfdaad577 100644 --- a/tools/witx/src/validate.rs +++ b/tools/witx/src/validate.rs @@ -70,10 +70,7 @@ pub fn validate_document(decls: &[DeclSyntax]) -> Result, _>>()?; - let rc_module = Rc::new(Module { - name: name.clone(), + let rc_module = Rc::new(Module::new( + name.clone(), definitions, - entries: module_validator.entries, - }); + module_validator.entries, + )); self.entries .insert(name, Entry::Module(Rc::downgrade(&rc_module))); Ok(Definition::Module(rc_module)) From 37a6ec388d7d0e7ed5098544bb1ac1ca2de15e14 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Tue, 24 Sep 2019 13:54:48 -0700 Subject: [PATCH 2/4] witx: add render trait, make sure it roundtrips by implementing Eq --- tools/witx/src/ast.rs | 90 ++++++++-- tools/witx/src/io.rs | 88 ++++++++++ tools/witx/src/lib.rs | 49 +++--- tools/witx/src/parser.rs | 10 +- tools/witx/src/render.rs | 266 ++++++++++++++++++++++++++++++ tools/witx/src/sexpr.rs | 10 +- tools/witx/src/toplevel.rs | 74 ++------- tools/witx/src/validate.rs | 14 +- tools/witx/tests/wasi_unstable.rs | 16 ++ 9 files changed, 510 insertions(+), 107 deletions(-) create mode 100644 tools/witx/src/io.rs create mode 100644 tools/witx/src/render.rs diff --git a/tools/witx/src/ast.rs b/tools/witx/src/ast.rs index a6a4a7a90..3d2fbb9c2 100644 --- a/tools/witx/src/ast.rs +++ b/tools/witx/src/ast.rs @@ -55,6 +55,34 @@ impl Document { } } +impl PartialEq for Document { + fn eq(&self, rhs: &Document) -> bool { + if self.definitions.len() != rhs.definitions.len() { + return false; + } + for d in self.datatypes() { + if let Some(d_rhs) = rhs.datatype(&d.name) { + if d != d_rhs { + return false; + } + } else { + return false; + } + } + for m in self.modules() { + if let Some(m_rhs) = rhs.module(&m.name) { + if m != m_rhs { + return false; + } + } else { + return false; + } + } + true + } +} +impl Eq for Document {} + #[derive(Debug, Clone)] pub enum Definition { Datatype(Rc), @@ -76,7 +104,7 @@ impl Entry { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum DatatypeIdent { Builtin(BuiltinType), Array(Box), @@ -85,13 +113,13 @@ pub enum DatatypeIdent { Ident(Rc), } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Datatype { pub name: Id, pub variant: DatatypeVariant, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum DatatypeVariant { Alias(AliasDatatype), Enum(EnumDatatype), @@ -100,13 +128,13 @@ pub enum DatatypeVariant { Union(UnionDatatype), } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct AliasDatatype { pub name: Id, pub to: DatatypeIdent, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum IntRepr { I8, I16, @@ -114,39 +142,39 @@ pub enum IntRepr { I64, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct EnumDatatype { pub name: Id, pub repr: IntRepr, pub variants: Vec, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct FlagsDatatype { pub name: Id, pub repr: IntRepr, pub flags: Vec, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct StructDatatype { pub name: Id, pub members: Vec, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct StructMember { pub name: Id, pub type_: DatatypeIdent, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct UnionDatatype { pub name: Id, pub variants: Vec, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct UnionVariant { pub name: Id, pub type_: DatatypeIdent, @@ -171,8 +199,8 @@ impl Module { entries, } } - pub fn import(&self, name: &str) -> Option> { - self.entries.get(&Id::new(name)).and_then(|e| match e { + pub fn import(&self, name: &Id) -> Option> { + self.entries.get(name).and_then(|e| match e { ModuleEntry::Import(d) => Some(d.upgrade().expect("always possible to upgrade entry")), _ => None, }) @@ -197,6 +225,34 @@ impl Module { } } +impl PartialEq for Module { + fn eq(&self, rhs: &Module) -> bool { + if self.definitions.len() != rhs.definitions.len() { + return false; + } + for i in self.imports() { + if let Some(i_rhs) = rhs.import(&i.name) { + if i != i_rhs { + return false; + } + } else { + return false; + } + } + for f in self.funcs() { + if let Some(f_rhs) = rhs.func(&f.name) { + if f != f_rhs { + return false; + } + } else { + return false; + } + } + true + } +} +impl Eq for Module {} + #[derive(Debug, Clone)] pub enum ModuleDefinition { Import(Rc), @@ -209,25 +265,25 @@ pub enum ModuleEntry { Func(Weak), } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ModuleImport { pub name: Id, pub variant: ModuleImportVariant, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum ModuleImportVariant { Memory, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct InterfaceFunc { pub name: Id, pub params: Vec, pub results: Vec, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct InterfaceFuncParam { pub name: Id, pub type_: DatatypeIdent, diff --git a/tools/witx/src/io.rs b/tools/witx/src/io.rs new file mode 100644 index 000000000..2ff2aef62 --- /dev/null +++ b/tools/witx/src/io.rs @@ -0,0 +1,88 @@ +use crate::WitxError; +use std::collections::HashMap; +use std::fs::{read_to_string, File}; +use std::io::{BufRead, BufReader, Error, ErrorKind}; +use std::path::{Path, PathBuf}; + +pub trait WitxIo { + fn fgets(&self, path: &Path) -> Result; + fn fget_line(&self, path: &Path, line_num: usize) -> Result; + fn canonicalize(&self, path: &Path) -> Result; +} + +pub struct Filesystem; + +impl WitxIo for Filesystem { + fn fgets(&self, path: &Path) -> Result { + read_to_string(path).map_err(|e| WitxError::Io(path.to_path_buf(), e)) + } + fn fget_line(&self, path: &Path, line_num: usize) -> Result { + let f = File::open(path).map_err(|e| WitxError::Io(path.into(), e))?; + let buf = BufReader::new(f); + let l = buf + .lines() + .skip(line_num - 1) + .next() + .ok_or_else(|| { + WitxError::Io(path.into(), Error::new(ErrorKind::Other, "Line not found")) + })? + .map_err(|e| WitxError::Io(path.into(), e))?; + + Ok(l) + } + fn canonicalize(&self, path: &Path) -> Result { + path.canonicalize() + .map_err(|e| WitxError::Io(path.to_path_buf(), e)) + } +} + +pub struct MockFs { + map: HashMap, +} + +impl MockFs { + pub fn new(strings: &[(&str, &str)]) -> Self { + MockFs { + map: strings + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(), + } + } +} + +impl WitxIo for MockFs { + fn fgets(&self, path: &Path) -> Result { + if let Some(entry) = self.map.get(path.to_str().unwrap()) { + Ok(entry.to_string()) + } else { + Err(WitxError::Io( + path.to_path_buf(), + Error::new(ErrorKind::Other, "mock fs: file not found"), + )) + } + } + fn fget_line(&self, path: &Path, line: usize) -> Result { + if let Some(entry) = self.map.get(path.to_str().unwrap()) { + entry + .lines() + .skip(line - 1) + .next() + .map(|s| s.to_string()) + .ok_or_else(|| { + WitxError::Io( + path.to_path_buf(), + Error::new(ErrorKind::Other, "mock fs: file not found"), + ) + }) + } else { + Err(WitxError::Io( + path.to_path_buf(), + Error::new(ErrorKind::Other, "mock fs: file not found"), + )) + } + } + fn canonicalize(&self, path: &Path) -> Result { + Ok(PathBuf::from(path)) + } +} diff --git a/tools/witx/src/lib.rs b/tools/witx/src/lib.rs index 7d5040d00..0cac5d6b3 100644 --- a/tools/witx/src/lib.rs +++ b/tools/witx/src/lib.rs @@ -1,9 +1,13 @@ /// Types describing a validated witx document mod ast; +/// Interface for filesystem or mock IO +mod io; /// Lexer text into tokens mod lexer; /// Witx syntax parsing from SExprs mod parser; +/// Render ast to text +mod render; /// SExpr parsing from tokens mod sexpr; /// Resolve toplevel `use` declarations across files @@ -17,13 +21,14 @@ pub use ast::{ ModuleDefinition, ModuleEntry, ModuleImport, ModuleImportVariant, StructDatatype, StructMember, UnionDatatype, UnionVariant, }; +pub use io::{Filesystem, MockFs, WitxIo}; pub use lexer::LexError; pub use parser::{DeclSyntax, ParseError}; +pub use render::{Render, SExpr as RenderSExpr}; pub use sexpr::SExprParseError; pub use validate::ValidationError; use failure::Fail; -use std::io; use std::path::{Path, PathBuf}; pub fn load>(path: P) -> Result { @@ -33,6 +38,14 @@ pub fn load>(path: P) -> Result { validate_document(&parsed_decls).map_err(WitxError::Validation) } +pub fn parse(source: &str) -> Result { + use toplevel::parse_witx_with; + use validate::validate_document; + let mockfs = MockFs::new(&[("-", source)]); + let parsed_decls = parse_witx_with(Path::new("-"), &mockfs)?; + validate_document(&parsed_decls).map_err(WitxError::Validation) +} + /// Location in the source text #[derive(Debug, PartialEq, Eq, Clone)] pub struct Location { @@ -45,8 +58,8 @@ pub struct Location { pub enum WitxError { #[fail(display = "{}", _0)] SExpr(#[cause] SExprParseError), - #[fail(display = "when resolving use declaration for {:?}: {}", _0, _1)] - UseResolution(PathBuf, #[cause] io::Error), + #[fail(display = "with file {:?}: {}", _0, _1)] + Io(PathBuf, #[cause] ::std::io::Error), #[fail(display = "{}", _0)] Parse(#[cause] ParseError), #[fail(display = "{}", _0)] @@ -54,20 +67,24 @@ pub enum WitxError { } impl WitxError { - pub fn report(&self) -> String { + pub fn report_with(&self, witxio: &dyn WitxIo) -> String { use WitxError::*; match self { - SExpr(sexpr) => sexpr.report(), - UseResolution(path, ioerr) => format!("when resolving `use {:?}`: {}", path, ioerr), - Parse(parse) => parse.report(), - Validation(validation) => validation.report(), + SExpr(sexpr) => sexpr.report_with(witxio), + Io(path, ioerr) => format!("with file {:?}: {}", path, ioerr), + Parse(parse) => parse.report_with(witxio), + Validation(validation) => validation.report_with(witxio), } } + pub fn report(&self) -> String { + self.report_with(&Filesystem) + } } + impl Location { - pub fn highlight_source(&self) -> String { + pub fn highlight_source_with(&self, witxio: &dyn WitxIo) -> String { let mut msg = format!("in {:?}:\n", self.path); - if let Ok(src_line) = self.source_line() { + if let Ok(src_line) = witxio.fget_line(&self.path, self.line) { msg += &format!( "{line_num: >5} | {src_line}\n{blank: >5} {caret: >column$}", line_num = self.line, @@ -79,15 +96,7 @@ impl Location { } msg } - pub fn source_line(&self) -> Result { - use std::fs::File; - use std::io::{BufRead, BufReader}; - let f = BufReader::new(File::open(&self.path)?); - let l = f - .lines() - .skip(self.line - 1) - .next() - .unwrap_or_else(|| Err(io::Error::new(io::ErrorKind::Other, "TODO")))?; - Ok(l) + pub fn highlight_source(&self) -> String { + self.highlight_source_with(&Filesystem) } } diff --git a/tools/witx/src/parser.rs b/tools/witx/src/parser.rs index a99df5fa4..1f627b27c 100644 --- a/tools/witx/src/parser.rs +++ b/tools/witx/src/parser.rs @@ -1,3 +1,4 @@ +use crate::io::{Filesystem, WitxIo}; use crate::sexpr::SExpr; use crate::Location; use failure::Fail; @@ -23,8 +24,15 @@ pub struct ParseError { } impl ParseError { + pub fn report_with(&self, witxio: &dyn WitxIo) -> String { + format!( + "{}\n{}", + self.location.highlight_source_with(witxio), + self.message + ) + } pub fn report(&self) -> String { - format!("{}\n{}", self.location.highlight_source(), self.message) + self.report_with(&Filesystem) } } diff --git a/tools/witx/src/render.rs b/tools/witx/src/render.rs new file mode 100644 index 000000000..3a2ece505 --- /dev/null +++ b/tools/witx/src/render.rs @@ -0,0 +1,266 @@ +use crate::ast::*; +use std::fmt; + +impl fmt::Display for Document { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for d in self.datatypes() { + write!(f, "{}\n", d.to_sexpr())?; + } + for m in self.modules() { + write!(f, "{}\n", m.to_sexpr())?; + } + Ok(()) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum SExpr { + Vec(Vec), + Word(String), + Ident(String), + Quote(String), + /// Short for Annotation + Annot(String), +} + +impl fmt::Display for SExpr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + SExpr::Vec(vs) => { + write!(f, "(")?; + let mut vss = Vec::new(); + for v in vs { + vss.push(format!("{}", v)); + } + f.write_str(&vss.join(" "))?; + write!(f, ")") + } + SExpr::Word(w) => write!(f, "{}", w), + SExpr::Ident(i) => write!(f, "${}", i), + SExpr::Quote(q) => write!(f, "\"{}\"", q), + SExpr::Annot(a) => write!(f, "@{}", a), + } + } +} + +impl SExpr { + fn word(s: &str) -> SExpr { + SExpr::Word(s.to_string()) + } + fn ident(s: &str) -> SExpr { + SExpr::Ident(s.to_string()) + } + fn quote(s: &str) -> SExpr { + SExpr::Quote(s.to_string()) + } + fn annot(s: &str) -> SExpr { + SExpr::Annot(s.to_string()) + } +} + +pub trait Render { + fn to_sexpr(&self) -> SExpr; +} + +impl Render for Id { + fn to_sexpr(&self) -> SExpr { + SExpr::ident(self.as_str()) + } +} + +impl Render for BuiltinType { + fn to_sexpr(&self) -> SExpr { + match self { + BuiltinType::String => SExpr::word("string"), + BuiltinType::Data => SExpr::word("data"), + BuiltinType::U8 => SExpr::word("u8"), + BuiltinType::U16 => SExpr::word("u16"), + BuiltinType::U32 => SExpr::word("u32"), + BuiltinType::U64 => SExpr::word("u64"), + BuiltinType::S8 => SExpr::word("s8"), + BuiltinType::S16 => SExpr::word("s16"), + BuiltinType::S32 => SExpr::word("s32"), + BuiltinType::S64 => SExpr::word("s64"), + BuiltinType::F32 => SExpr::word("f32"), + BuiltinType::F64 => SExpr::word("f64"), + } + } +} + +impl Render for DatatypeIdent { + fn to_sexpr(&self) -> SExpr { + match self { + DatatypeIdent::Builtin(b) => b.to_sexpr(), + DatatypeIdent::Array(a) => SExpr::Vec(vec![SExpr::word("array"), a.to_sexpr()]), + DatatypeIdent::Pointer(p) => SExpr::Vec(vec![ + SExpr::annot("witx"), + SExpr::word("pointer"), + p.to_sexpr(), + ]), + DatatypeIdent::ConstPointer(p) => SExpr::Vec(vec![ + SExpr::annot("witx"), + SExpr::word("const_pointer"), + p.to_sexpr(), + ]), + DatatypeIdent::Ident(i) => i.name.to_sexpr(), + } + } +} + +impl Render for Datatype { + fn to_sexpr(&self) -> SExpr { + let name = self.name.to_sexpr(); + let body = self.variant.to_sexpr(); + SExpr::Vec(vec![SExpr::word("typename"), name, body]) + } +} + +impl Render for DatatypeVariant { + fn to_sexpr(&self) -> SExpr { + match self { + DatatypeVariant::Alias(a) => a.to_sexpr(), + DatatypeVariant::Enum(a) => a.to_sexpr(), + DatatypeVariant::Flags(a) => a.to_sexpr(), + DatatypeVariant::Struct(a) => a.to_sexpr(), + DatatypeVariant::Union(a) => a.to_sexpr(), + } + } +} + +impl Render for AliasDatatype { + fn to_sexpr(&self) -> SExpr { + self.to.to_sexpr() + } +} + +impl Render for EnumDatatype { + fn to_sexpr(&self) -> SExpr { + let header = vec![SExpr::word("enum"), self.repr.to_sexpr()]; + let variants = self + .variants + .iter() + .map(|v| v.to_sexpr()) + .collect::>(); + SExpr::Vec([header, variants].concat()) + } +} + +impl Render for FlagsDatatype { + fn to_sexpr(&self) -> SExpr { + let header = vec![SExpr::word("flags"), self.repr.to_sexpr()]; + let flags = self + .flags + .iter() + .map(|f| SExpr::Vec(vec![SExpr::word("flag"), f.to_sexpr()])) + .collect::>(); + SExpr::Vec([header, flags].concat()) + } +} + +impl Render for StructDatatype { + fn to_sexpr(&self) -> SExpr { + let header = vec![SExpr::word("struct")]; + let members = self + .members + .iter() + .map(|m| { + SExpr::Vec(vec![ + SExpr::word("field"), + m.name.to_sexpr(), + m.type_.to_sexpr(), + ]) + }) + .collect::>(); + SExpr::Vec([header, members].concat()) + } +} + +impl Render for UnionDatatype { + fn to_sexpr(&self) -> SExpr { + let header = vec![SExpr::word("union")]; + let variants = self + .variants + .iter() + .map(|v| { + SExpr::Vec(vec![ + SExpr::word("field"), + v.name.to_sexpr(), + v.type_.to_sexpr(), + ]) + }) + .collect::>(); + SExpr::Vec([header, variants].concat()) + } +} + +impl Render for IntRepr { + fn to_sexpr(&self) -> SExpr { + match self { + IntRepr::I8 => SExpr::word("u8"), + IntRepr::I16 => SExpr::word("u16"), + IntRepr::I32 => SExpr::word("u32"), + IntRepr::I64 => SExpr::word("u64"), + } + } +} + +impl Render for Module { + fn to_sexpr(&self) -> SExpr { + let header = vec![SExpr::word("module"), self.name.to_sexpr()]; + let definitions = self + .imports() + .map(|i| i.to_sexpr()) + .chain(self.funcs().map(|f| f.to_sexpr())) + .collect::>(); + SExpr::Vec([header, definitions].concat()) + } +} + +impl Render for ModuleImport { + fn to_sexpr(&self) -> SExpr { + let variant = match self.variant { + ModuleImportVariant::Memory => SExpr::Vec(vec![SExpr::word("memory")]), + }; + SExpr::Vec(vec![ + SExpr::word("import"), + SExpr::quote(self.name.as_str()), + variant, + ]) + } +} + +impl Render for InterfaceFunc { + fn to_sexpr(&self) -> SExpr { + let header = vec![ + SExpr::annot("interface"), + SExpr::word("func"), + SExpr::Vec(vec![ + SExpr::word("export"), + SExpr::quote(self.name.as_str()), + ]), + ]; + let params = self + .params + .iter() + .map(|f| { + SExpr::Vec(vec![ + SExpr::word("param"), + f.name.to_sexpr(), + f.type_.to_sexpr(), + ]) + }) + .collect(); + let results = self + .results + .iter() + .map(|f| { + SExpr::Vec(vec![ + SExpr::word("result"), + f.name.to_sexpr(), + f.type_.to_sexpr(), + ]) + }) + .collect(); + SExpr::Vec([header, params, results].concat()) + } +} diff --git a/tools/witx/src/sexpr.rs b/tools/witx/src/sexpr.rs index 6a34cf83b..d87d427e3 100644 --- a/tools/witx/src/sexpr.rs +++ b/tools/witx/src/sexpr.rs @@ -1,3 +1,4 @@ +use crate::io::{Filesystem, WitxIo}; pub use crate::lexer::LexError; use crate::lexer::{Lexer, LocatedError, LocatedToken, Token}; use crate::Location; @@ -41,14 +42,17 @@ pub enum SExprParseError { } impl SExprParseError { - pub fn report(&self) -> String { + pub fn report_with(&self, witxio: &dyn WitxIo) -> String { use SExprParseError::*; match self { - Lex(lex_err, loc) => format!("{}\n{}", loc.highlight_source(), lex_err), - UnexpectedCloseParen(loc) => format!("{}\n{}", loc.highlight_source(), self), + Lex(lex_err, loc) => format!("{}\n{}", loc.highlight_source_with(witxio), lex_err), + UnexpectedCloseParen(loc) => format!("{}\n{}", loc.highlight_source_with(witxio), self), UnexpectedEof(_path) => format!("{}", self), } } + pub fn report(&self) -> String { + self.report_with(&Filesystem) + } } pub struct SExprParser<'a> { diff --git a/tools/witx/src/toplevel.rs b/tools/witx/src/toplevel.rs index cf3a5e377..98325b39c 100644 --- a/tools/witx/src/toplevel.rs +++ b/tools/witx/src/toplevel.rs @@ -1,32 +1,15 @@ +use crate::io::{Filesystem, WitxIo}; use crate::parser::{DeclSyntax, ParseError, TopLevelSyntax}; use crate::sexpr::SExprParser; use crate::WitxError; use std::collections::HashSet; -use std::fs; use std::path::{Path, PathBuf}; -trait WitxIo { - fn fgets(&self, path: &Path) -> Result; - fn canonicalize(&self, path: &Path) -> Result; -} - -struct Filesystem; - -impl WitxIo for Filesystem { - fn fgets(&self, path: &Path) -> Result { - fs::read_to_string(path).map_err(|e| WitxError::UseResolution(path.to_path_buf(), e)) - } - fn canonicalize(&self, path: &Path) -> Result { - path.canonicalize() - .map_err(|e| WitxError::UseResolution(path.to_path_buf(), e)) - } -} - pub fn parse_witx>(i: P) -> Result, WitxError> { parse_witx_with(i, &Filesystem) } -fn parse_witx_with>( +pub fn parse_witx_with>( i: P, witxio: &dyn WitxIo, ) -> Result, WitxError> { @@ -86,44 +69,14 @@ fn resolve_uses( #[cfg(test)] mod test { use super::*; + use crate::io::MockFs; use crate::parser::*; use crate::Location; - use std::collections::HashMap; - - struct MockFs { - map: HashMap<&'static str, &'static str>, - } - - impl MockFs { - pub fn new(strings: Vec<(&'static str, &'static str)>) -> Self { - MockFs { - map: strings.into_iter().collect(), - } - } - } - - impl WitxIo for MockFs { - fn fgets(&self, path: &Path) -> Result { - if let Some(entry) = self.map.get(path.to_str().unwrap()) { - Ok(entry.to_string()) - } else { - use std::io::{Error, ErrorKind}; - Err(WitxError::UseResolution( - path.to_path_buf(), - Error::new(ErrorKind::Other, "mock fs: file not found"), - )) - } - } - fn canonicalize(&self, path: &Path) -> Result { - Ok(PathBuf::from(path)) - } - } #[test] fn empty() { assert_eq!( - parse_witx_with(&Path::new("/a"), &MockFs::new(vec![("/a", ";; empty")])) - .expect("parse"), + parse_witx_with(&Path::new("/a"), &MockFs::new(&[("/a", ";; empty")])).expect("parse"), Vec::new(), ); } @@ -133,7 +86,7 @@ mod test { assert_eq!( parse_witx_with( &Path::new("/a"), - &MockFs::new(vec![("/a", "(use \"b\")"), ("/b", ";; empty")]) + &MockFs::new(&[("/a", "(use \"b\")"), ("/b", ";; empty")]) ) .expect("parse"), Vec::new(), @@ -145,7 +98,7 @@ mod test { assert_eq!( parse_witx_with( &Path::new("/a"), - &MockFs::new(vec![ + &MockFs::new(&[ ("/a", "(use \"b\")"), ("/b", "(use \"c\")\n(typename $b_float f64)"), ("/c", "(typename $c_int u32)") @@ -184,7 +137,7 @@ mod test { assert_eq!( parse_witx_with( &Path::new("/a"), - &MockFs::new(vec![ + &MockFs::new(&[ ("/a", "(use \"b\")\n(use \"c\")"), ("/b", "(use \"d\")"), ("/c", "(use \"d\")"), @@ -208,23 +161,20 @@ mod test { #[test] fn use_not_found() { - match parse_witx_with(&Path::new("/a"), &MockFs::new(vec![("/a", "(use \"b\")")])) + match parse_witx_with(&Path::new("/a"), &MockFs::new(&[("/a", "(use \"b\")")])) .err() .unwrap() { - WitxError::UseResolution(path, _error) => assert_eq!(path, PathBuf::from("/b")), + WitxError::Io(path, _error) => assert_eq!(path, PathBuf::from("/b")), e => panic!("wrong error: {:?}", e), } } #[test] fn use_invalid() { - match parse_witx_with( - &Path::new("/a"), - &MockFs::new(vec![("/a", "(use bbbbbbb)")]), - ) - .err() - .unwrap() + match parse_witx_with(&Path::new("/a"), &MockFs::new(&[("/a", "(use bbbbbbb)")])) + .err() + .unwrap() { WitxError::Parse(e) => { assert_eq!(e.message, "invalid use declaration"); diff --git a/tools/witx/src/validate.rs b/tools/witx/src/validate.rs index bfdaad577..64e4ecca9 100644 --- a/tools/witx/src/validate.rs +++ b/tools/witx/src/validate.rs @@ -1,4 +1,5 @@ use crate::{ + io::{Filesystem, WitxIo}, parser::{ DatatypeIdentSyntax, DeclSyntax, EnumSyntax, FlagsSyntax, IdentSyntax, ImportTypeSyntax, ModuleDeclSyntax, StructSyntax, TypedefSyntax, UnionSyntax, @@ -42,25 +43,30 @@ pub enum ValidationError { } impl ValidationError { - pub fn report(&self) -> String { + pub fn report_with(&self, witxio: &dyn WitxIo) -> String { use ValidationError::*; match self { UnknownName { location, .. } | WrongKindName { location, .. } | Recursive { location, .. } - | InvalidRepr { location, .. } => format!("{}\n{}", location.highlight_source(), &self), + | InvalidRepr { location, .. } => { + format!("{}\n{}", location.highlight_source_with(witxio), &self) + } NameAlreadyExists { at_location, previous_location, .. } => format!( "{}\n{}\nOriginally defined at:\n{}", - at_location.highlight_source(), + at_location.highlight_source_with(witxio), &self, - previous_location.highlight_source(), + previous_location.highlight_source_with(witxio), ), } } + pub fn report(&self) -> String { + self.report_with(&Filesystem) + } } pub fn validate_document(decls: &[DeclSyntax]) -> Result { diff --git a/tools/witx/tests/wasi_unstable.rs b/tools/witx/tests/wasi_unstable.rs index 6e196cb4a..2722e8d04 100644 --- a/tools/witx/tests/wasi_unstable.rs +++ b/tools/witx/tests/wasi_unstable.rs @@ -21,3 +21,19 @@ fn validate_wasi_ephemeral_preview0() { fn validate_wasi_old_preview0() { witx::load(Path::new("../../phases/old/witx/wasi_unstable.witx")).unwrap(); } + +#[test] +fn render_roundtrip() { + let doc = witx::load(Path::new( + "../../phases/unstable/witx/wasi_unstable_preview0.witx", + )) + .unwrap(); + + let back_to_sexprs = format!("{}", doc); + println!("{}", back_to_sexprs); + let doc2 = witx::parse(&back_to_sexprs) + .map_err(|e| e.report_with(&witx::MockFs::new(&[("-", &back_to_sexprs)]))) + .unwrap(); + + assert_eq!(doc, doc2); +} From a1abdc9515463efe1de76934e44a6261a0f534e3 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 26 Sep 2019 09:32:08 -0700 Subject: [PATCH 3/4] witx crate: IntRepr variants are U8, U16... to reflect syntax as "u8"... --- tools/witx/src/ast.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/witx/src/ast.rs b/tools/witx/src/ast.rs index 3d2fbb9c2..3713b3dd9 100644 --- a/tools/witx/src/ast.rs +++ b/tools/witx/src/ast.rs @@ -136,10 +136,10 @@ pub struct AliasDatatype { #[derive(Debug, Clone, PartialEq, Eq)] pub enum IntRepr { - I8, - I16, - I32, - I64, + U8, + U16, + U32, + U64, } #[derive(Debug, Clone, PartialEq, Eq)] From bd75cdf69f943b075ac209959570efc9e2e90de1 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 26 Sep 2019 09:52:27 -0700 Subject: [PATCH 4/4] witx crate: doc comments --- tools/witx/src/io.rs | 3 +++ tools/witx/src/lib.rs | 2 ++ tools/witx/src/render.rs | 8 ++++---- tools/witx/src/validate.rs | 8 ++++---- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/tools/witx/src/io.rs b/tools/witx/src/io.rs index 2ff2aef62..4f40d8947 100644 --- a/tools/witx/src/io.rs +++ b/tools/witx/src/io.rs @@ -5,8 +5,11 @@ use std::io::{BufRead, BufReader, Error, ErrorKind}; use std::path::{Path, PathBuf}; pub trait WitxIo { + /// Read the entire file into a String. Used to resolve `use` declarations. fn fgets(&self, path: &Path) -> Result; + /// Read a line of a file into a String. Used for error reporting. fn fget_line(&self, path: &Path, line_num: usize) -> Result; + /// Return the canonical (non-symlinked) path of a file. Used to resolve `use` declarations. fn canonicalize(&self, path: &Path) -> Result; } diff --git a/tools/witx/src/lib.rs b/tools/witx/src/lib.rs index 0cac5d6b3..34876cf7b 100644 --- a/tools/witx/src/lib.rs +++ b/tools/witx/src/lib.rs @@ -31,6 +31,7 @@ pub use validate::ValidationError; use failure::Fail; use std::path::{Path, PathBuf}; +/// Load a witx document from the filesystem pub fn load>(path: P) -> Result { use toplevel::parse_witx; use validate::validate_document; @@ -38,6 +39,7 @@ pub fn load>(path: P) -> Result { validate_document(&parsed_decls).map_err(WitxError::Validation) } +/// Parse a witx document from a str. `(use ...)` directives are not permitted. pub fn parse(source: &str) -> Result { use toplevel::parse_witx_with; use validate::validate_document; diff --git a/tools/witx/src/render.rs b/tools/witx/src/render.rs index 3a2ece505..912d59508 100644 --- a/tools/witx/src/render.rs +++ b/tools/witx/src/render.rs @@ -196,10 +196,10 @@ impl Render for UnionDatatype { impl Render for IntRepr { fn to_sexpr(&self) -> SExpr { match self { - IntRepr::I8 => SExpr::word("u8"), - IntRepr::I16 => SExpr::word("u16"), - IntRepr::I32 => SExpr::word("u32"), - IntRepr::I64 => SExpr::word("u64"), + IntRepr::U8 => SExpr::word("u8"), + IntRepr::U16 => SExpr::word("u16"), + IntRepr::U32 => SExpr::word("u32"), + IntRepr::U64 => SExpr::word("u64"), } } } diff --git a/tools/witx/src/validate.rs b/tools/witx/src/validate.rs index 64e4ecca9..0fecf1178 100644 --- a/tools/witx/src/validate.rs +++ b/tools/witx/src/validate.rs @@ -311,10 +311,10 @@ impl DocValidation { fn validate_int_repr(type_: &BuiltinType, location: &Location) -> Result { match type_ { - BuiltinType::U8 => Ok(IntRepr::I8), - BuiltinType::U16 => Ok(IntRepr::I16), - BuiltinType::U32 => Ok(IntRepr::I32), - BuiltinType::U64 => Ok(IntRepr::I64), + BuiltinType::U8 => Ok(IntRepr::U8), + BuiltinType::U16 => Ok(IntRepr::U16), + BuiltinType::U32 => Ok(IntRepr::U32), + BuiltinType::U64 => Ok(IntRepr::U64), _ => Err(ValidationError::InvalidRepr { repr: type_.clone(), location: location.clone(),