feat: add a histrical wit-bindgen
This commit is contained in:
2251
__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/abi.rs
Normal file
2251
__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/abi.rs
Normal file
File diff suppressed because it is too large
Load Diff
711
__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/ast.rs
Normal file
711
__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/ast.rs
Normal file
@@ -0,0 +1,711 @@
|
||||
use anyhow::Result;
|
||||
use lex::{Span, Token, Tokenizer};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
|
||||
mod lex;
|
||||
mod resolve;
|
||||
|
||||
pub use lex::validate_id;
|
||||
|
||||
pub struct Ast<'a> {
|
||||
pub items: Vec<Item<'a>>,
|
||||
}
|
||||
|
||||
pub enum Item<'a> {
|
||||
Use(Use<'a>),
|
||||
Resource(Resource<'a>),
|
||||
TypeDef(TypeDef<'a>),
|
||||
Value(Value<'a>),
|
||||
Interface(Interface<'a>),
|
||||
}
|
||||
|
||||
pub struct Id<'a> {
|
||||
pub name: Cow<'a, str>,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Id<'a> {
|
||||
fn from(s: &'a str) -> Id<'a> {
|
||||
Id {
|
||||
name: s.into(),
|
||||
span: Span { start: 0, end: 0 },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<String> for Id<'a> {
|
||||
fn from(s: String) -> Id<'a> {
|
||||
Id {
|
||||
name: s.into(),
|
||||
span: Span { start: 0, end: 0 },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Use<'a> {
|
||||
pub from: Vec<Id<'a>>,
|
||||
names: Option<Vec<UseName<'a>>>,
|
||||
}
|
||||
|
||||
struct UseName<'a> {
|
||||
name: Id<'a>,
|
||||
as_: Option<Id<'a>>,
|
||||
}
|
||||
|
||||
pub struct Resource<'a> {
|
||||
docs: Docs<'a>,
|
||||
name: Id<'a>,
|
||||
supertype: Option<Id<'a>>,
|
||||
values: Vec<(bool, Value<'a>)>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Docs<'a> {
|
||||
docs: Vec<Cow<'a, str>>,
|
||||
}
|
||||
|
||||
pub struct TypeDef<'a> {
|
||||
docs: Docs<'a>,
|
||||
name: Id<'a>,
|
||||
ty: Type<'a>,
|
||||
}
|
||||
|
||||
enum Type<'a> {
|
||||
Unit,
|
||||
Bool,
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
S8,
|
||||
S16,
|
||||
S32,
|
||||
S64,
|
||||
Float32,
|
||||
Float64,
|
||||
Char,
|
||||
String,
|
||||
Handle(Id<'a>),
|
||||
Name(Id<'a>),
|
||||
List(Box<Type<'a>>),
|
||||
Record(Record<'a>),
|
||||
Flags(Flags<'a>),
|
||||
Variant(Variant<'a>),
|
||||
Tuple(Vec<Type<'a>>),
|
||||
Enum(Enum<'a>),
|
||||
Option(Box<Type<'a>>),
|
||||
Expected(Expected<'a>),
|
||||
Future(Box<Type<'a>>),
|
||||
Stream(Stream<'a>),
|
||||
Union(Union<'a>),
|
||||
}
|
||||
|
||||
struct Record<'a> {
|
||||
fields: Vec<Field<'a>>,
|
||||
}
|
||||
|
||||
struct Field<'a> {
|
||||
docs: Docs<'a>,
|
||||
name: Id<'a>,
|
||||
ty: Type<'a>,
|
||||
}
|
||||
|
||||
struct Flags<'a> {
|
||||
flags: Vec<Flag<'a>>,
|
||||
}
|
||||
|
||||
struct Flag<'a> {
|
||||
docs: Docs<'a>,
|
||||
name: Id<'a>,
|
||||
}
|
||||
|
||||
struct Variant<'a> {
|
||||
span: Span,
|
||||
cases: Vec<Case<'a>>,
|
||||
}
|
||||
|
||||
struct Case<'a> {
|
||||
docs: Docs<'a>,
|
||||
name: Id<'a>,
|
||||
ty: Option<Type<'a>>,
|
||||
}
|
||||
|
||||
struct Enum<'a> {
|
||||
span: Span,
|
||||
cases: Vec<EnumCase<'a>>,
|
||||
}
|
||||
|
||||
struct EnumCase<'a> {
|
||||
docs: Docs<'a>,
|
||||
name: Id<'a>,
|
||||
}
|
||||
|
||||
struct Expected<'a> {
|
||||
ok: Box<Type<'a>>,
|
||||
err: Box<Type<'a>>,
|
||||
}
|
||||
|
||||
struct Stream<'a> {
|
||||
element: Box<Type<'a>>,
|
||||
end: Box<Type<'a>>,
|
||||
}
|
||||
|
||||
pub struct Value<'a> {
|
||||
docs: Docs<'a>,
|
||||
name: Id<'a>,
|
||||
kind: ValueKind<'a>,
|
||||
}
|
||||
|
||||
struct Union<'a> {
|
||||
span: Span,
|
||||
cases: Vec<UnionCase<'a>>,
|
||||
}
|
||||
|
||||
struct UnionCase<'a> {
|
||||
docs: Docs<'a>,
|
||||
ty: Type<'a>,
|
||||
}
|
||||
|
||||
enum ValueKind<'a> {
|
||||
Function {
|
||||
is_async: bool,
|
||||
params: Vec<(Id<'a>, Type<'a>)>,
|
||||
result: Type<'a>,
|
||||
},
|
||||
Global(Type<'a>),
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // TODO
|
||||
pub struct Interface<'a> {
|
||||
docs: Docs<'a>,
|
||||
name: Id<'a>,
|
||||
items: Vec<Item<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Ast<'a> {
|
||||
pub fn parse(input: &'a str) -> Result<Ast<'a>> {
|
||||
let mut lexer = Tokenizer::new(input)?;
|
||||
let mut items = Vec::new();
|
||||
while lexer.clone().next()?.is_some() {
|
||||
let docs = parse_docs(&mut lexer)?;
|
||||
items.push(Item::parse(&mut lexer, docs)?);
|
||||
}
|
||||
Ok(Ast { items })
|
||||
}
|
||||
|
||||
pub fn resolve(
|
||||
&self,
|
||||
name: &str,
|
||||
map: &HashMap<String, crate::Interface>,
|
||||
) -> Result<crate::Interface> {
|
||||
let mut resolver = resolve::Resolver::default();
|
||||
let instance = resolver.resolve(name, &self.items, map)?;
|
||||
Ok(instance)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Item<'a> {
|
||||
fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Item<'a>> {
|
||||
match tokens.clone().next()? {
|
||||
Some((_span, Token::Use)) => Use::parse(tokens, docs).map(Item::Use),
|
||||
Some((_span, Token::Type)) => TypeDef::parse(tokens, docs).map(Item::TypeDef),
|
||||
Some((_span, Token::Flags)) => TypeDef::parse_flags(tokens, docs).map(Item::TypeDef),
|
||||
Some((_span, Token::Enum)) => TypeDef::parse_enum(tokens, docs).map(Item::TypeDef),
|
||||
Some((_span, Token::Variant)) => {
|
||||
TypeDef::parse_variant(tokens, docs).map(Item::TypeDef)
|
||||
}
|
||||
Some((_span, Token::Record)) => TypeDef::parse_record(tokens, docs).map(Item::TypeDef),
|
||||
Some((_span, Token::Union)) => TypeDef::parse_union(tokens, docs).map(Item::TypeDef),
|
||||
Some((_span, Token::Resource)) => Resource::parse(tokens, docs).map(Item::Resource),
|
||||
Some((_span, Token::Interface)) => Interface::parse(tokens, docs).map(Item::Interface),
|
||||
Some((_span, Token::Id)) | Some((_span, Token::ExplicitId)) => {
|
||||
Value::parse(tokens, docs).map(Item::Value)
|
||||
}
|
||||
other => Err(err_expected(tokens, "`type`, `resource`, or `func`", other).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Use<'a> {
|
||||
fn parse(tokens: &mut Tokenizer<'a>, _docs: Docs<'a>) -> Result<Self> {
|
||||
tokens.expect(Token::Use)?;
|
||||
let mut names = None;
|
||||
loop {
|
||||
if names.is_none() {
|
||||
if tokens.eat(Token::Star)? {
|
||||
break;
|
||||
}
|
||||
tokens.expect(Token::LeftBrace)?;
|
||||
names = Some(Vec::new());
|
||||
}
|
||||
let names = names.as_mut().unwrap();
|
||||
let mut name = UseName {
|
||||
name: parse_id(tokens)?,
|
||||
as_: None,
|
||||
};
|
||||
if tokens.eat(Token::As)? {
|
||||
name.as_ = Some(parse_id(tokens)?);
|
||||
}
|
||||
names.push(name);
|
||||
if !tokens.eat(Token::Comma)? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if names.is_some() {
|
||||
tokens.expect(Token::RightBrace)?;
|
||||
}
|
||||
tokens.expect(Token::From_)?;
|
||||
let mut from = vec![parse_id(tokens)?];
|
||||
while tokens.eat(Token::Colon)? {
|
||||
tokens.expect_raw(Token::Colon)?;
|
||||
from.push(parse_id(tokens)?);
|
||||
}
|
||||
Ok(Use { from, names })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TypeDef<'a> {
|
||||
fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
|
||||
tokens.expect(Token::Type)?;
|
||||
let name = parse_id(tokens)?;
|
||||
tokens.expect(Token::Equals)?;
|
||||
let ty = Type::parse(tokens)?;
|
||||
Ok(TypeDef { docs, name, ty })
|
||||
}
|
||||
|
||||
fn parse_flags(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
|
||||
tokens.expect(Token::Flags)?;
|
||||
let name = parse_id(tokens)?;
|
||||
let ty = Type::Flags(Flags {
|
||||
flags: parse_list(
|
||||
tokens,
|
||||
Token::LeftBrace,
|
||||
Token::RightBrace,
|
||||
|docs, tokens| {
|
||||
let name = parse_id(tokens)?;
|
||||
Ok(Flag { docs, name })
|
||||
},
|
||||
)?,
|
||||
});
|
||||
Ok(TypeDef { docs, name, ty })
|
||||
}
|
||||
|
||||
fn parse_record(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
|
||||
tokens.expect(Token::Record)?;
|
||||
let name = parse_id(tokens)?;
|
||||
let ty = Type::Record(Record {
|
||||
fields: parse_list(
|
||||
tokens,
|
||||
Token::LeftBrace,
|
||||
Token::RightBrace,
|
||||
|docs, tokens| {
|
||||
let name = parse_id(tokens)?;
|
||||
tokens.expect(Token::Colon)?;
|
||||
let ty = Type::parse(tokens)?;
|
||||
Ok(Field { docs, name, ty })
|
||||
},
|
||||
)?,
|
||||
});
|
||||
Ok(TypeDef { docs, name, ty })
|
||||
}
|
||||
|
||||
fn parse_variant(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
|
||||
tokens.expect(Token::Variant)?;
|
||||
let name = parse_id(tokens)?;
|
||||
let ty = Type::Variant(Variant {
|
||||
span: name.span,
|
||||
cases: parse_list(
|
||||
tokens,
|
||||
Token::LeftBrace,
|
||||
Token::RightBrace,
|
||||
|docs, tokens| {
|
||||
let name = parse_id(tokens)?;
|
||||
let ty = if tokens.eat(Token::LeftParen)? {
|
||||
let ty = Type::parse(tokens)?;
|
||||
tokens.expect(Token::RightParen)?;
|
||||
Some(ty)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Case { docs, name, ty })
|
||||
},
|
||||
)?,
|
||||
});
|
||||
Ok(TypeDef { docs, name, ty })
|
||||
}
|
||||
|
||||
fn parse_union(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
|
||||
tokens.expect(Token::Union)?;
|
||||
let name = parse_id(tokens)?;
|
||||
let ty = Type::Union(Union {
|
||||
span: name.span,
|
||||
cases: parse_list(
|
||||
tokens,
|
||||
Token::LeftBrace,
|
||||
Token::RightBrace,
|
||||
|docs, tokens| {
|
||||
let ty = Type::parse(tokens)?;
|
||||
Ok(UnionCase { docs, ty })
|
||||
},
|
||||
)?,
|
||||
});
|
||||
Ok(TypeDef { docs, name, ty })
|
||||
}
|
||||
|
||||
fn parse_enum(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
|
||||
tokens.expect(Token::Enum)?;
|
||||
let name = parse_id(tokens)?;
|
||||
let ty = Type::Enum(Enum {
|
||||
span: name.span,
|
||||
cases: parse_list(
|
||||
tokens,
|
||||
Token::LeftBrace,
|
||||
Token::RightBrace,
|
||||
|docs, tokens| {
|
||||
let name = parse_id(tokens)?;
|
||||
Ok(EnumCase { docs, name })
|
||||
},
|
||||
)?,
|
||||
});
|
||||
Ok(TypeDef { docs, name, ty })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Resource<'a> {
|
||||
fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
|
||||
tokens.expect(Token::Resource)?;
|
||||
let name = parse_id(tokens)?;
|
||||
let supertype = if tokens.eat(Token::Implements)? {
|
||||
Some(parse_id(tokens)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut values = Vec::new();
|
||||
if tokens.eat(Token::LeftBrace)? {
|
||||
loop {
|
||||
let docs = parse_docs(tokens)?;
|
||||
if tokens.eat(Token::RightBrace)? {
|
||||
break;
|
||||
}
|
||||
let statik = tokens.eat(Token::Static)?;
|
||||
values.push((statik, Value::parse(tokens, docs)?));
|
||||
}
|
||||
}
|
||||
Ok(Resource {
|
||||
docs,
|
||||
name,
|
||||
supertype,
|
||||
values,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Value<'a> {
|
||||
fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
|
||||
let name = parse_id(tokens)?;
|
||||
tokens.expect(Token::Colon)?;
|
||||
|
||||
let kind = if tokens.eat(Token::Func)? {
|
||||
parse_func(tokens, false)?
|
||||
} else if tokens.eat(Token::Async)? {
|
||||
tokens.expect(Token::Func)?;
|
||||
parse_func(tokens, true)?
|
||||
} else {
|
||||
ValueKind::Global(Type::parse(tokens)?)
|
||||
};
|
||||
return Ok(Value { docs, name, kind });
|
||||
|
||||
fn parse_func<'a>(tokens: &mut Tokenizer<'a>, is_async: bool) -> Result<ValueKind<'a>> {
|
||||
let params = parse_list(
|
||||
tokens,
|
||||
Token::LeftParen,
|
||||
Token::RightParen,
|
||||
|_docs, tokens| {
|
||||
let name = parse_id(tokens)?;
|
||||
tokens.expect(Token::Colon)?;
|
||||
let ty = Type::parse(tokens)?;
|
||||
Ok((name, ty))
|
||||
},
|
||||
)?;
|
||||
let result = if tokens.eat(Token::RArrow)? {
|
||||
Type::parse(tokens)?
|
||||
} else {
|
||||
Type::Unit
|
||||
};
|
||||
Ok(ValueKind::Function {
|
||||
is_async,
|
||||
params,
|
||||
result,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_id<'a>(tokens: &mut Tokenizer<'a>) -> Result<Id<'a>> {
|
||||
match tokens.next()? {
|
||||
Some((span, Token::Id)) => Ok(Id {
|
||||
name: tokens.parse_id(span)?.into(),
|
||||
span,
|
||||
}),
|
||||
Some((span, Token::ExplicitId)) => Ok(Id {
|
||||
name: tokens.parse_explicit_id(span)?.into(),
|
||||
span,
|
||||
}),
|
||||
other => Err(err_expected(tokens, "an identifier or string", other).into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_docs<'a>(tokens: &mut Tokenizer<'a>) -> Result<Docs<'a>> {
|
||||
let mut docs = Docs::default();
|
||||
let mut clone = tokens.clone();
|
||||
while let Some((span, token)) = clone.next_raw()? {
|
||||
match token {
|
||||
Token::Whitespace => {}
|
||||
Token::Comment => docs.docs.push(tokens.get_span(span).into()),
|
||||
_ => break,
|
||||
};
|
||||
*tokens = clone.clone();
|
||||
}
|
||||
Ok(docs)
|
||||
}
|
||||
|
||||
impl<'a> Type<'a> {
|
||||
fn parse(tokens: &mut Tokenizer<'a>) -> Result<Self> {
|
||||
match tokens.next()? {
|
||||
Some((_span, Token::U8)) => Ok(Type::U8),
|
||||
Some((_span, Token::U16)) => Ok(Type::U16),
|
||||
Some((_span, Token::U32)) => Ok(Type::U32),
|
||||
Some((_span, Token::U64)) => Ok(Type::U64),
|
||||
Some((_span, Token::S8)) => Ok(Type::S8),
|
||||
Some((_span, Token::S16)) => Ok(Type::S16),
|
||||
Some((_span, Token::S32)) => Ok(Type::S32),
|
||||
Some((_span, Token::S64)) => Ok(Type::S64),
|
||||
Some((_span, Token::Float32)) => Ok(Type::Float32),
|
||||
Some((_span, Token::Float64)) => Ok(Type::Float64),
|
||||
Some((_span, Token::Char)) => Ok(Type::Char),
|
||||
Some((_span, Token::Handle)) => {
|
||||
let name = parse_id(tokens)?;
|
||||
Ok(Type::Handle(name))
|
||||
}
|
||||
|
||||
// tuple<T, U, ...>
|
||||
Some((_span, Token::Tuple)) => {
|
||||
let types = parse_list(
|
||||
tokens,
|
||||
Token::LessThan,
|
||||
Token::GreaterThan,
|
||||
|_docs, tokens| Type::parse(tokens),
|
||||
)?;
|
||||
Ok(Type::Tuple(types))
|
||||
}
|
||||
|
||||
Some((_span, Token::Unit)) => Ok(Type::Unit),
|
||||
Some((_span, Token::Bool)) => Ok(Type::Bool),
|
||||
Some((_span, Token::String_)) => Ok(Type::String),
|
||||
|
||||
// list<T>
|
||||
Some((_span, Token::List)) => {
|
||||
tokens.expect(Token::LessThan)?;
|
||||
let ty = Type::parse(tokens)?;
|
||||
tokens.expect(Token::GreaterThan)?;
|
||||
Ok(Type::List(Box::new(ty)))
|
||||
}
|
||||
|
||||
// option<T>
|
||||
Some((_span, Token::Option_)) => {
|
||||
tokens.expect(Token::LessThan)?;
|
||||
let ty = Type::parse(tokens)?;
|
||||
tokens.expect(Token::GreaterThan)?;
|
||||
Ok(Type::Option(Box::new(ty)))
|
||||
}
|
||||
|
||||
// expected<T, E>
|
||||
Some((_span, Token::Expected)) => {
|
||||
tokens.expect(Token::LessThan)?;
|
||||
let ok = Box::new(Type::parse(tokens)?);
|
||||
tokens.expect(Token::Comma)?;
|
||||
let err = Box::new(Type::parse(tokens)?);
|
||||
tokens.expect(Token::GreaterThan)?;
|
||||
Ok(Type::Expected(Expected { ok, err }))
|
||||
}
|
||||
|
||||
// future<T>
|
||||
Some((_span, Token::Future)) => {
|
||||
tokens.expect(Token::LessThan)?;
|
||||
let ty = Box::new(Type::parse(tokens)?);
|
||||
tokens.expect(Token::GreaterThan)?;
|
||||
Ok(Type::Future(ty))
|
||||
}
|
||||
|
||||
// stream<T, Z>
|
||||
Some((_span, Token::Stream)) => {
|
||||
tokens.expect(Token::LessThan)?;
|
||||
let element = Box::new(Type::parse(tokens)?);
|
||||
tokens.expect(Token::Comma)?;
|
||||
let end = Box::new(Type::parse(tokens)?);
|
||||
tokens.expect(Token::GreaterThan)?;
|
||||
Ok(Type::Stream(Stream { element, end }))
|
||||
}
|
||||
|
||||
// `foo`
|
||||
Some((span, Token::Id)) => Ok(Type::Name(Id {
|
||||
name: tokens.parse_id(span)?.into(),
|
||||
span,
|
||||
})),
|
||||
// `@foo`
|
||||
Some((span, Token::ExplicitId)) => Ok(Type::Name(Id {
|
||||
name: tokens.parse_explicit_id(span)?.into(),
|
||||
span,
|
||||
})),
|
||||
|
||||
other => Err(err_expected(tokens, "a type", other).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Interface<'a> {
|
||||
fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
|
||||
tokens.expect(Token::Interface)?;
|
||||
let name = parse_id(tokens)?;
|
||||
tokens.expect(Token::LeftBrace)?;
|
||||
let mut items = Vec::new();
|
||||
loop {
|
||||
let docs = parse_docs(tokens)?;
|
||||
if tokens.eat(Token::RightBrace)? {
|
||||
break;
|
||||
}
|
||||
items.push(Item::parse(tokens, docs)?);
|
||||
}
|
||||
Ok(Interface { docs, name, items })
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_list<'a, T>(
|
||||
tokens: &mut Tokenizer<'a>,
|
||||
start: Token,
|
||||
end: Token,
|
||||
mut parse: impl FnMut(Docs<'a>, &mut Tokenizer<'a>) -> Result<T>,
|
||||
) -> Result<Vec<T>> {
|
||||
tokens.expect(start)?;
|
||||
let mut items = Vec::new();
|
||||
loop {
|
||||
// get docs before we skip them to try to eat the end token
|
||||
let docs = parse_docs(tokens)?;
|
||||
|
||||
// if we found an end token then we're done
|
||||
if tokens.eat(end)? {
|
||||
break;
|
||||
}
|
||||
|
||||
let item = parse(docs, tokens)?;
|
||||
items.push(item);
|
||||
|
||||
// if there's no trailing comma then this is required to be the end,
|
||||
// otherwise we go through the loop to try to get another item
|
||||
if !tokens.eat(Token::Comma)? {
|
||||
tokens.expect(end)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
fn err_expected(
|
||||
tokens: &Tokenizer<'_>,
|
||||
expected: &'static str,
|
||||
found: Option<(Span, Token)>,
|
||||
) -> Error {
|
||||
match found {
|
||||
Some((span, token)) => Error {
|
||||
span,
|
||||
msg: format!("expected {}, found {}", expected, token.describe()),
|
||||
},
|
||||
None => Error {
|
||||
span: Span {
|
||||
start: u32::try_from(tokens.input().len()).unwrap(),
|
||||
end: u32::try_from(tokens.input().len()).unwrap(),
|
||||
},
|
||||
msg: format!("expected {}, found eof", expected),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Error {
|
||||
span: Span,
|
||||
msg: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.msg.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
pub fn rewrite_error(err: &mut anyhow::Error, file: &str, contents: &str) {
|
||||
let parse = match err.downcast_mut::<Error>() {
|
||||
Some(err) => err,
|
||||
None => return lex::rewrite_error(err, file, contents),
|
||||
};
|
||||
let msg = highlight_err(
|
||||
parse.span.start as usize,
|
||||
Some(parse.span.end as usize),
|
||||
file,
|
||||
contents,
|
||||
&parse.msg,
|
||||
);
|
||||
*err = anyhow::anyhow!("{}", msg);
|
||||
}
|
||||
|
||||
fn highlight_err(
|
||||
start: usize,
|
||||
end: Option<usize>,
|
||||
file: &str,
|
||||
input: &str,
|
||||
err: impl fmt::Display,
|
||||
) -> String {
|
||||
let (line, col) = linecol_in(start, input);
|
||||
let snippet = input.lines().nth(line).unwrap_or("");
|
||||
let mut msg = format!(
|
||||
"\
|
||||
{err}
|
||||
--> {file}:{line}:{col}
|
||||
|
|
||||
{line:4} | {snippet}
|
||||
| {marker:>0$}",
|
||||
col + 1,
|
||||
file = file,
|
||||
line = line + 1,
|
||||
col = col + 1,
|
||||
err = err,
|
||||
snippet = snippet,
|
||||
marker = "^",
|
||||
);
|
||||
if let Some(end) = end {
|
||||
if let Some(s) = input.get(start..end) {
|
||||
for _ in s.chars().skip(1) {
|
||||
msg.push('-');
|
||||
}
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
|
||||
fn linecol_in(pos: usize, text: &str) -> (usize, usize) {
|
||||
let mut cur = 0;
|
||||
// Use split_terminator instead of lines so that if there is a `\r`,
|
||||
// it is included in the offset calculation. The `+1` values below
|
||||
// account for the `\n`.
|
||||
for (i, line) in text.split_terminator('\n').enumerate() {
|
||||
if cur + line.len() + 1 > pos {
|
||||
return (i, pos - cur);
|
||||
}
|
||||
cur += line.len() + 1;
|
||||
}
|
||||
(text.lines().count(), 0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,709 @@
|
||||
use anyhow::{bail, Result};
|
||||
use std::char;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::str;
|
||||
use unicode_normalization::char::canonical_combining_class;
|
||||
use unicode_xid::UnicodeXID;
|
||||
|
||||
use self::Token::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Tokenizer<'a> {
|
||||
input: &'a str,
|
||||
chars: CrlfFold<'a>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CrlfFold<'a> {
|
||||
chars: str::CharIndices<'a>,
|
||||
}
|
||||
|
||||
/// A span, designating a range of bytes where a token is located.
|
||||
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
|
||||
pub struct Span {
|
||||
/// The start of the range.
|
||||
pub start: u32,
|
||||
/// The end of the range (exclusive).
|
||||
pub end: u32,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
|
||||
pub enum Token {
|
||||
Whitespace,
|
||||
Comment,
|
||||
|
||||
Equals,
|
||||
Comma,
|
||||
Colon,
|
||||
Semicolon,
|
||||
LeftParen,
|
||||
RightParen,
|
||||
LeftBrace,
|
||||
RightBrace,
|
||||
LessThan,
|
||||
GreaterThan,
|
||||
RArrow,
|
||||
Star,
|
||||
|
||||
Use,
|
||||
Type,
|
||||
Resource,
|
||||
Func,
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
S8,
|
||||
S16,
|
||||
S32,
|
||||
S64,
|
||||
Float32,
|
||||
Float64,
|
||||
Char,
|
||||
Handle,
|
||||
Record,
|
||||
Flags,
|
||||
Variant,
|
||||
Enum,
|
||||
Union,
|
||||
Bool,
|
||||
String_,
|
||||
Option_,
|
||||
Expected,
|
||||
Future,
|
||||
Stream,
|
||||
List,
|
||||
Underscore,
|
||||
As,
|
||||
From_,
|
||||
Static,
|
||||
Interface,
|
||||
Tuple,
|
||||
Async,
|
||||
Unit,
|
||||
Implements,
|
||||
|
||||
Id,
|
||||
ExplicitId,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Error {
|
||||
InvalidCharInString(usize, char),
|
||||
InvalidCharInId(usize, char),
|
||||
IdNotSSNFC(usize),
|
||||
IdPartEmpty(usize),
|
||||
InvalidEscape(usize, char),
|
||||
// InvalidHexEscape(usize, char),
|
||||
// InvalidEscapeValue(usize, u32),
|
||||
Unexpected(usize, char),
|
||||
UnterminatedComment(usize),
|
||||
UnterminatedString(usize),
|
||||
NewlineInString(usize),
|
||||
Wanted {
|
||||
at: usize,
|
||||
expected: &'static str,
|
||||
found: &'static str,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> Tokenizer<'a> {
|
||||
pub fn new(input: &'a str) -> Result<Tokenizer<'a>> {
|
||||
detect_invalid_input(input)?;
|
||||
|
||||
let mut t = Tokenizer {
|
||||
input,
|
||||
chars: CrlfFold {
|
||||
chars: input.char_indices(),
|
||||
},
|
||||
};
|
||||
// Eat utf-8 BOM
|
||||
t.eatc('\u{feff}');
|
||||
Ok(t)
|
||||
}
|
||||
|
||||
pub fn input(&self) -> &'a str {
|
||||
self.input
|
||||
}
|
||||
|
||||
pub fn get_span(&self, span: Span) -> &'a str {
|
||||
&self.input[span.start as usize..span.end as usize]
|
||||
}
|
||||
|
||||
pub fn parse_id(&self, span: Span) -> Result<String> {
|
||||
let ret = self.get_span(span).to_owned();
|
||||
validate_id(span.start as usize, &ret)?;
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
pub fn parse_explicit_id(&self, span: Span) -> Result<String> {
|
||||
let token = self.get_span(span);
|
||||
let id_part = token.strip_prefix('%').unwrap();
|
||||
validate_id(span.start as usize, id_part)?;
|
||||
Ok(id_part.to_owned())
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Result<Option<(Span, Token)>, Error> {
|
||||
loop {
|
||||
match self.next_raw()? {
|
||||
Some((_, Token::Whitespace)) | Some((_, Token::Comment)) => {}
|
||||
other => break Ok(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_raw(&mut self) -> Result<Option<(Span, Token)>, Error> {
|
||||
let (start, ch) = match self.chars.next() {
|
||||
Some(pair) => pair,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let token = match ch {
|
||||
'\n' | '\t' | ' ' => {
|
||||
// Eat all contiguous whitespace tokens
|
||||
while self.eatc(' ') || self.eatc('\t') || self.eatc('\n') {}
|
||||
Whitespace
|
||||
}
|
||||
'/' => {
|
||||
// Eat a line comment if it's `//...`
|
||||
if self.eatc('/') {
|
||||
for (_, ch) in &mut self.chars {
|
||||
if ch == '\n' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// eat a block comment if it's `/*...`
|
||||
} else if self.eatc('*') {
|
||||
let mut depth = 1;
|
||||
while depth > 0 {
|
||||
let (_, ch) = match self.chars.next() {
|
||||
Some(pair) => pair,
|
||||
None => return Err(Error::UnterminatedComment(start)),
|
||||
};
|
||||
match ch {
|
||||
'/' if self.eatc('*') => depth += 1,
|
||||
'*' if self.eatc('/') => depth -= 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(Error::Unexpected(start, ch));
|
||||
}
|
||||
|
||||
Comment
|
||||
}
|
||||
'=' => Equals,
|
||||
',' => Comma,
|
||||
':' => Colon,
|
||||
';' => Semicolon,
|
||||
'(' => LeftParen,
|
||||
')' => RightParen,
|
||||
'{' => LeftBrace,
|
||||
'}' => RightBrace,
|
||||
'<' => LessThan,
|
||||
'>' => GreaterThan,
|
||||
'*' => Star,
|
||||
'-' => {
|
||||
if self.eatc('>') {
|
||||
RArrow
|
||||
} else {
|
||||
return Err(Error::Unexpected(start, '-'));
|
||||
}
|
||||
}
|
||||
'%' => {
|
||||
let mut iter = self.chars.clone();
|
||||
if let Some((_, ch)) = iter.next() {
|
||||
if is_keylike_start(ch) {
|
||||
self.chars = iter.clone();
|
||||
while let Some((_, ch)) = iter.next() {
|
||||
if !is_keylike_continue(ch) {
|
||||
break;
|
||||
}
|
||||
self.chars = iter.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
ExplicitId
|
||||
}
|
||||
ch if is_keylike_start(ch) => {
|
||||
let remaining = self.chars.chars.as_str().len();
|
||||
let mut iter = self.chars.clone();
|
||||
while let Some((_, ch)) = iter.next() {
|
||||
if !is_keylike_continue(ch) {
|
||||
break;
|
||||
}
|
||||
self.chars = iter.clone();
|
||||
}
|
||||
let end = start + ch.len_utf8() + (remaining - self.chars.chars.as_str().len());
|
||||
match &self.input[start..end] {
|
||||
"use" => Use,
|
||||
"type" => Type,
|
||||
"resource" => Resource,
|
||||
"func" => Func,
|
||||
"u8" => U8,
|
||||
"u16" => U16,
|
||||
"u32" => U32,
|
||||
"u64" => U64,
|
||||
"s8" => S8,
|
||||
"s16" => S16,
|
||||
"s32" => S32,
|
||||
"s64" => S64,
|
||||
"float32" => Float32,
|
||||
"float64" => Float64,
|
||||
"char" => Char,
|
||||
"handle" => Handle,
|
||||
"record" => Record,
|
||||
"flags" => Flags,
|
||||
"variant" => Variant,
|
||||
"enum" => Enum,
|
||||
"union" => Union,
|
||||
"bool" => Bool,
|
||||
"string" => String_,
|
||||
"option" => Option_,
|
||||
"expected" => Expected,
|
||||
"future" => Future,
|
||||
"stream" => Stream,
|
||||
"list" => List,
|
||||
"_" => Underscore,
|
||||
"as" => As,
|
||||
"from" => From_,
|
||||
"static" => Static,
|
||||
"interface" => Interface,
|
||||
"tuple" => Tuple,
|
||||
"async" => Async,
|
||||
"unit" => Unit,
|
||||
"implements" => Implements,
|
||||
_ => Id,
|
||||
}
|
||||
}
|
||||
ch => return Err(Error::Unexpected(start, ch)),
|
||||
};
|
||||
let end = match self.chars.clone().next() {
|
||||
Some((i, _)) => i,
|
||||
None => self.input.len(),
|
||||
};
|
||||
|
||||
let start = u32::try_from(start).unwrap();
|
||||
let end = u32::try_from(end).unwrap();
|
||||
Ok(Some((Span { start, end }, token)))
|
||||
}
|
||||
|
||||
pub fn eat(&mut self, expected: Token) -> Result<bool, Error> {
|
||||
let mut other = self.clone();
|
||||
match other.next()? {
|
||||
Some((_span, found)) if expected == found => {
|
||||
*self = other;
|
||||
Ok(true)
|
||||
}
|
||||
Some(_) => Ok(false),
|
||||
None => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect(&mut self, expected: Token) -> Result<Span, Error> {
|
||||
match self.next()? {
|
||||
Some((span, found)) => {
|
||||
if expected == found {
|
||||
Ok(span)
|
||||
} else {
|
||||
Err(Error::Wanted {
|
||||
at: usize::try_from(span.start).unwrap(),
|
||||
expected: expected.describe(),
|
||||
found: found.describe(),
|
||||
})
|
||||
}
|
||||
}
|
||||
None => Err(Error::Wanted {
|
||||
at: self.input.len(),
|
||||
expected: expected.describe(),
|
||||
found: "eof",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_raw(&mut self, expected: Token) -> Result<Span, Error> {
|
||||
match self.next_raw()? {
|
||||
Some((span, found)) => {
|
||||
if expected == found {
|
||||
Ok(span)
|
||||
} else {
|
||||
Err(Error::Wanted {
|
||||
at: usize::try_from(span.start).unwrap(),
|
||||
expected: expected.describe(),
|
||||
found: found.describe(),
|
||||
})
|
||||
}
|
||||
}
|
||||
None => Err(Error::Wanted {
|
||||
at: self.input.len(),
|
||||
expected: expected.describe(),
|
||||
found: "eof",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn eatc(&mut self, ch: char) -> bool {
|
||||
let mut iter = self.chars.clone();
|
||||
match iter.next() {
|
||||
Some((_, ch2)) if ch == ch2 => {
|
||||
self.chars = iter;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for CrlfFold<'a> {
|
||||
type Item = (usize, char);
|
||||
|
||||
fn next(&mut self) -> Option<(usize, char)> {
|
||||
self.chars.next().map(|(i, c)| {
|
||||
if c == '\r' {
|
||||
let mut attempt = self.chars.clone();
|
||||
if let Some((_, '\n')) = attempt.next() {
|
||||
self.chars = attempt;
|
||||
return (i, '\n');
|
||||
}
|
||||
}
|
||||
(i, c)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_invalid_input(input: &str) -> Result<()> {
|
||||
// Disallow specific codepoints.
|
||||
let mut line = 1;
|
||||
for ch in input.chars() {
|
||||
match ch {
|
||||
'\n' => line += 1,
|
||||
'\r' | '\t' => {}
|
||||
|
||||
// Bidirectional override codepoints can be used to craft source code that
|
||||
// appears to have a different meaning than its actual meaning. See
|
||||
// [CVE-2021-42574] for background and motivation.
|
||||
//
|
||||
// [CVE-2021-42574]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-42574
|
||||
'\u{202a}' | '\u{202b}' | '\u{202c}' | '\u{202d}' | '\u{202e}' | '\u{2066}'
|
||||
| '\u{2067}' | '\u{2068}' | '\u{2069}' => {
|
||||
bail!(
|
||||
"Input contains bidirectional override codepoint {:?} at line {}",
|
||||
ch.escape_unicode(),
|
||||
line
|
||||
);
|
||||
}
|
||||
|
||||
// Disallow several characters which are deprecated or discouraged in Unicode.
|
||||
//
|
||||
// U+149 deprecated; see Unicode 13.0.0, sec. 7.1 Latin, Compatibility Digraphs.
|
||||
// U+673 deprecated; see Unicode 13.0.0, sec. 9.2 Arabic, Additional Vowel Marks.
|
||||
// U+F77 and U+F79 deprecated; see Unicode 13.0.0, sec. 13.4 Tibetan, Vowels.
|
||||
// U+17A3 and U+17A4 deprecated, and U+17B4 and U+17B5 discouraged; see
|
||||
// Unicode 13.0.0, sec. 16.4 Khmer, Characters Whose Use Is Discouraged.
|
||||
'\u{149}' | '\u{673}' | '\u{f77}' | '\u{f79}' | '\u{17a3}' | '\u{17a4}'
|
||||
| '\u{17b4}' | '\u{17b5}' => {
|
||||
bail!(
|
||||
"Codepoint {:?} at line {} is discouraged by Unicode",
|
||||
ch.escape_unicode(),
|
||||
line
|
||||
);
|
||||
}
|
||||
|
||||
// Disallow control codes other than the ones explicitly recognized above,
|
||||
// so that viewing a wit file on a terminal doesn't have surprising side
|
||||
// effects or appear to have a different meaning than its actual meaning.
|
||||
ch if ch.is_control() => {
|
||||
bail!("Control code '{}' at line {}", ch.escape_unicode(), line);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_keylike_start(ch: char) -> bool {
|
||||
// Lex any XID start, `_`, or '-'. These aren't all valid identifier chars,
|
||||
// but we'll diagnose that after we've lexed the full string.
|
||||
UnicodeXID::is_xid_start(ch) || ch == '_' || ch == '-'
|
||||
}
|
||||
|
||||
fn is_keylike_continue(ch: char) -> bool {
|
||||
// Lex any XID continue (which includes `_`) or '-'.
|
||||
UnicodeXID::is_xid_continue(ch) || ch == '-'
|
||||
}
|
||||
|
||||
pub fn validate_id(start: usize, id: &str) -> Result<(), Error> {
|
||||
// Ids must be in stream-safe NFC.
|
||||
if !unicode_normalization::is_nfc_stream_safe(&id) {
|
||||
return Err(Error::IdNotSSNFC(start));
|
||||
}
|
||||
|
||||
// IDs must have at least one part.
|
||||
if id.is_empty() {
|
||||
return Err(Error::IdPartEmpty(start));
|
||||
}
|
||||
|
||||
// Ids consist of parts separated by '-'s.
|
||||
for part in id.split("-") {
|
||||
// Parts must be non-empty and start with a non-combining XID start.
|
||||
match part.chars().next() {
|
||||
None => return Err(Error::IdPartEmpty(start)),
|
||||
Some(first) => {
|
||||
// Require the first character of each part to be non-combining,
|
||||
// so that if a source langauge uses `CamelCase`, they won't
|
||||
// combine with the last character of the previous part.
|
||||
if canonical_combining_class(first) != 0 {
|
||||
return Err(Error::InvalidCharInId(start, first));
|
||||
}
|
||||
|
||||
// Require the first character to be a XID start.
|
||||
if !UnicodeXID::is_xid_start(first) {
|
||||
return Err(Error::InvalidCharInId(start, first));
|
||||
}
|
||||
|
||||
// TODO: Disallow values with 'Grapheme_Extend = Yes', to
|
||||
// prevent them from combining with previous parts?
|
||||
|
||||
// TODO: Disallow values with 'Grapheme_Cluster_Break = SpacingMark'?
|
||||
}
|
||||
};
|
||||
|
||||
// Some XID values are not valid ID part values.
|
||||
for ch in part.chars() {
|
||||
// Disallow uppercase and underscore, so that identifiers
|
||||
// consistently use `kebab-case`, and source languages can map
|
||||
// identifiers according to their own conventions (which might use
|
||||
// `CamelCase` or `snake_case` or something else) without worrying
|
||||
// about collisions.
|
||||
if ch.is_uppercase() || ch == '_' || !UnicodeXID::is_xid_continue(ch) {
|
||||
return Err(Error::InvalidCharInId(start, ch));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn describe(&self) -> &'static str {
|
||||
match self {
|
||||
Whitespace => "whitespace",
|
||||
Comment => "a comment",
|
||||
Equals => "'='",
|
||||
Comma => "','",
|
||||
Colon => "':'",
|
||||
Semicolon => "';'",
|
||||
LeftParen => "'('",
|
||||
RightParen => "')'",
|
||||
LeftBrace => "'{'",
|
||||
RightBrace => "'}'",
|
||||
LessThan => "'<'",
|
||||
GreaterThan => "'>'",
|
||||
Use => "keyword `use`",
|
||||
Type => "keyword `type`",
|
||||
Resource => "keyword `resource`",
|
||||
Func => "keyword `func`",
|
||||
U8 => "keyword `u8`",
|
||||
U16 => "keyword `u16`",
|
||||
U32 => "keyword `u32`",
|
||||
U64 => "keyword `u64`",
|
||||
S8 => "keyword `s8`",
|
||||
S16 => "keyword `s16`",
|
||||
S32 => "keyword `s32`",
|
||||
S64 => "keyword `s64`",
|
||||
Float32 => "keyword `float32`",
|
||||
Float64 => "keyword `float64`",
|
||||
Char => "keyword `char`",
|
||||
Handle => "keyword `handle`",
|
||||
Record => "keyword `record`",
|
||||
Flags => "keyword `flags`",
|
||||
Variant => "keyword `variant`",
|
||||
Enum => "keyword `enum`",
|
||||
Union => "keyword `union`",
|
||||
Bool => "keyword `bool`",
|
||||
String_ => "keyword `string`",
|
||||
Option_ => "keyword `option`",
|
||||
Expected => "keyword `expected`",
|
||||
Future => "keyword `future`",
|
||||
Stream => "keyword `stream`",
|
||||
List => "keyword `list`",
|
||||
Underscore => "keyword `_`",
|
||||
Id => "an identifier",
|
||||
ExplicitId => "an '%' identifier",
|
||||
RArrow => "`->`",
|
||||
Star => "`*`",
|
||||
As => "keyword `as`",
|
||||
From_ => "keyword `from`",
|
||||
Static => "keyword `static`",
|
||||
Interface => "keyword `interface`",
|
||||
Tuple => "keyword `tuple`",
|
||||
Async => "keyword `async`",
|
||||
Unit => "keyword `unit`",
|
||||
Implements => "keyword `implements`",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::Unexpected(_, ch) => write!(f, "unexpected character {:?}", ch),
|
||||
Error::UnterminatedComment(_) => write!(f, "unterminated block comment"),
|
||||
Error::Wanted {
|
||||
expected, found, ..
|
||||
} => write!(f, "expected {}, found {}", expected, found),
|
||||
Error::UnterminatedString(_) => write!(f, "unterminated string literal"),
|
||||
Error::NewlineInString(_) => write!(f, "newline in string literal"),
|
||||
Error::InvalidCharInString(_, ch) => write!(f, "invalid character in string {:?}", ch),
|
||||
Error::InvalidCharInId(_, ch) => write!(f, "invalid character in identifier {:?}", ch),
|
||||
Error::IdPartEmpty(_) => write!(f, "identifiers must have characters between '-'s"),
|
||||
Error::IdNotSSNFC(_) => write!(f, "identifiers must be in stream-safe NFC"),
|
||||
Error::InvalidEscape(_, ch) => write!(f, "invalid escape in string {:?}", ch),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewrite_error(err: &mut anyhow::Error, file: &str, contents: &str) {
|
||||
let lex = match err.downcast_mut::<Error>() {
|
||||
Some(err) => err,
|
||||
None => return,
|
||||
};
|
||||
let pos = match lex {
|
||||
Error::Unexpected(at, _)
|
||||
| Error::UnterminatedComment(at)
|
||||
| Error::Wanted { at, .. }
|
||||
| Error::UnterminatedString(at)
|
||||
| Error::NewlineInString(at)
|
||||
| Error::InvalidCharInString(at, _)
|
||||
| Error::InvalidCharInId(at, _)
|
||||
| Error::IdNotSSNFC(at)
|
||||
| Error::IdPartEmpty(at)
|
||||
| Error::InvalidEscape(at, _) => *at,
|
||||
};
|
||||
let msg = super::highlight_err(pos, None, file, contents, lex);
|
||||
*err = anyhow::anyhow!("{}", msg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_id() {
|
||||
validate_id(0, "apple").unwrap();
|
||||
validate_id(0, "apple-pear").unwrap();
|
||||
validate_id(0, "apple-pear-grape").unwrap();
|
||||
validate_id(0, "garçon").unwrap();
|
||||
validate_id(0, "hühnervögel").unwrap();
|
||||
validate_id(0, "москва").unwrap();
|
||||
validate_id(0, "東京").unwrap();
|
||||
validate_id(0, "東-京").unwrap();
|
||||
validate_id(0, "garçon-hühnervögel-москва-東京").unwrap();
|
||||
validate_id(0, "garçon-hühnervögel-москва-東-京").unwrap();
|
||||
validate_id(0, "a0").unwrap();
|
||||
validate_id(0, "a").unwrap();
|
||||
validate_id(0, "a-a").unwrap();
|
||||
validate_id(0, "bool").unwrap();
|
||||
|
||||
assert!(validate_id(0, "").is_err());
|
||||
assert!(validate_id(0, "0").is_err());
|
||||
assert!(validate_id(0, "%").is_err());
|
||||
assert!(validate_id(0, "$").is_err());
|
||||
assert!(validate_id(0, "0a").is_err());
|
||||
assert!(validate_id(0, ".").is_err());
|
||||
assert!(validate_id(0, "·").is_err());
|
||||
assert!(validate_id(0, "a a").is_err());
|
||||
assert!(validate_id(0, "_").is_err());
|
||||
assert!(validate_id(0, "-").is_err());
|
||||
assert!(validate_id(0, "a-").is_err());
|
||||
assert!(validate_id(0, "-a").is_err());
|
||||
assert!(validate_id(0, "Apple").is_err());
|
||||
assert!(validate_id(0, "APPLE").is_err());
|
||||
assert!(validate_id(0, "applE").is_err());
|
||||
assert!(validate_id(0, "-apple-pear").is_err());
|
||||
assert!(validate_id(0, "apple-pear-").is_err());
|
||||
assert!(validate_id(0, "apple_pear").is_err());
|
||||
assert!(validate_id(0, "apple.pear").is_err());
|
||||
assert!(validate_id(0, "apple pear").is_err());
|
||||
assert!(validate_id(0, "apple/pear").is_err());
|
||||
assert!(validate_id(0, "apple|pear").is_err());
|
||||
assert!(validate_id(0, "apple-Pear").is_err());
|
||||
assert!(validate_id(0, "apple-0").is_err());
|
||||
assert!(validate_id(0, "()()").is_err());
|
||||
assert!(validate_id(0, "").is_err());
|
||||
assert!(validate_id(0, "*").is_err());
|
||||
assert!(validate_id(0, "apple\u{5f3}pear").is_err());
|
||||
assert!(validate_id(0, "apple\u{200c}pear").is_err());
|
||||
assert!(validate_id(0, "apple\u{200d}pear").is_err());
|
||||
assert!(validate_id(0, "apple--pear").is_err());
|
||||
assert!(validate_id(0, "_apple").is_err());
|
||||
assert!(validate_id(0, "apple_").is_err());
|
||||
assert!(validate_id(0, "_Znwj").is_err());
|
||||
assert!(validate_id(0, "__i386").is_err());
|
||||
assert!(validate_id(0, "__i386__").is_err());
|
||||
assert!(validate_id(0, "ENOENT").is_err());
|
||||
assert!(validate_id(0, "Москва").is_err());
|
||||
assert!(validate_id(0, "garçon-hühnervögel-Москва-東京").is_err());
|
||||
assert!(validate_id(0, "😼").is_err(), "non-identifier");
|
||||
assert!(validate_id(0, "\u{212b}").is_err(), "not NFC");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tokenizer() {
|
||||
fn collect(s: &str) -> Result<Vec<Token>> {
|
||||
let mut t = Tokenizer::new(s)?;
|
||||
let mut tokens = Vec::new();
|
||||
while let Some(token) = t.next()? {
|
||||
tokens.push(token.1);
|
||||
}
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
assert_eq!(collect("").unwrap(), vec![]);
|
||||
assert_eq!(collect("_").unwrap(), vec![Token::Underscore]);
|
||||
assert_eq!(collect("apple").unwrap(), vec![Token::Id]);
|
||||
assert_eq!(collect("apple-pear").unwrap(), vec![Token::Id]);
|
||||
assert_eq!(collect("apple--pear").unwrap(), vec![Token::Id]);
|
||||
assert_eq!(collect("apple-Pear").unwrap(), vec![Token::Id]);
|
||||
assert_eq!(collect("apple-pear-grape").unwrap(), vec![Token::Id]);
|
||||
assert_eq!(collect("apple pear").unwrap(), vec![Token::Id, Token::Id]);
|
||||
assert_eq!(collect("_a_p_p_l_e_").unwrap(), vec![Token::Id]);
|
||||
assert_eq!(collect("garçon").unwrap(), vec![Token::Id]);
|
||||
assert_eq!(collect("hühnervögel").unwrap(), vec![Token::Id]);
|
||||
assert_eq!(collect("москва").unwrap(), vec![Token::Id]);
|
||||
assert_eq!(collect("東京").unwrap(), vec![Token::Id]);
|
||||
assert_eq!(
|
||||
collect("garçon-hühnervögel-москва-東京").unwrap(),
|
||||
vec![Token::Id]
|
||||
);
|
||||
assert_eq!(collect("a0").unwrap(), vec![Token::Id]);
|
||||
assert_eq!(collect("a").unwrap(), vec![Token::Id]);
|
||||
assert_eq!(collect("%a").unwrap(), vec![Token::ExplicitId]);
|
||||
assert_eq!(collect("%a-a").unwrap(), vec![Token::ExplicitId]);
|
||||
assert_eq!(collect("%bool").unwrap(), vec![Token::ExplicitId]);
|
||||
assert_eq!(collect("%").unwrap(), vec![Token::ExplicitId]);
|
||||
|
||||
assert_eq!(collect("func").unwrap(), vec![Token::Func]);
|
||||
assert_eq!(
|
||||
collect("a: func()").unwrap(),
|
||||
vec![
|
||||
Token::Id,
|
||||
Token::Colon,
|
||||
Token::Func,
|
||||
Token::LeftParen,
|
||||
Token::RightParen
|
||||
]
|
||||
);
|
||||
|
||||
assert!(collect("\u{149}").is_err(), "strongly discouraged");
|
||||
assert!(collect("\u{673}").is_err(), "strongly discouraged");
|
||||
assert!(collect("\u{17a3}").is_err(), "strongly discouraged");
|
||||
assert!(collect("\u{17a4}").is_err(), "strongly discouraged");
|
||||
assert!(collect("\u{202a}").is_err(), "bidirectional override");
|
||||
assert!(collect("\u{2068}").is_err(), "bidirectional override");
|
||||
assert!(collect("\u{0}").is_err(), "control code");
|
||||
assert!(collect("\u{b}").is_err(), "control code");
|
||||
assert!(collect("\u{c}").is_err(), "control code");
|
||||
assert!(collect("\u{85}").is_err(), "control code");
|
||||
}
|
||||
@@ -0,0 +1,751 @@
|
||||
use super::{Error, Item, Span, Value, ValueKind};
|
||||
use crate::*;
|
||||
use anyhow::Result;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::mem;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Resolver {
|
||||
type_lookup: HashMap<String, TypeId>,
|
||||
types: Arena<TypeDef>,
|
||||
resource_lookup: HashMap<String, ResourceId>,
|
||||
resources_copied: HashMap<(String, ResourceId), ResourceId>,
|
||||
types_copied: HashMap<(String, TypeId), TypeId>,
|
||||
resources: Arena<Resource>,
|
||||
anon_types: HashMap<Key, TypeId>,
|
||||
functions: Vec<Function>,
|
||||
globals: Vec<Global>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
enum Key {
|
||||
Variant(Vec<(String, Type)>),
|
||||
Record(Vec<(String, Type)>),
|
||||
Flags(Vec<String>),
|
||||
Tuple(Vec<Type>),
|
||||
Enum(Vec<String>),
|
||||
List(Type),
|
||||
Option(Type),
|
||||
Expected(Type, Type),
|
||||
Union(Vec<Type>),
|
||||
Future(Type),
|
||||
Stream(Type, Type),
|
||||
}
|
||||
|
||||
impl Resolver {
|
||||
pub(super) fn resolve(
|
||||
&mut self,
|
||||
name: &str,
|
||||
fields: &[Item<'_>],
|
||||
deps: &HashMap<String, Interface>,
|
||||
) -> Result<Interface> {
|
||||
// First pull in any names from our dependencies
|
||||
self.process_use(fields, deps)?;
|
||||
// ... then register our own names
|
||||
self.register_names(fields)?;
|
||||
|
||||
// With all names registered we can now fully expand and translate all
|
||||
// types.
|
||||
for field in fields {
|
||||
let t = match field {
|
||||
Item::TypeDef(t) => t,
|
||||
_ => continue,
|
||||
};
|
||||
let id = self.type_lookup[&*t.name.name];
|
||||
let kind = self.resolve_type_def(&t.ty)?;
|
||||
self.types.get_mut(id).unwrap().kind = kind;
|
||||
}
|
||||
|
||||
// And finally we can resolve all type references in functions/globals
|
||||
// and additionally validate that types thesmelves are not recursive
|
||||
let mut valid_types = HashSet::new();
|
||||
let mut visiting = HashSet::new();
|
||||
for field in fields {
|
||||
match field {
|
||||
Item::Value(v) => self.resolve_value(v)?,
|
||||
Item::Resource(r) => self.resolve_resource(r)?,
|
||||
Item::TypeDef(t) => {
|
||||
self.validate_type_not_recursive(
|
||||
t.name.span,
|
||||
self.type_lookup[&*t.name.name],
|
||||
&mut visiting,
|
||||
&mut valid_types,
|
||||
)?;
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Interface {
|
||||
name: name.to_string(),
|
||||
module: None,
|
||||
types: mem::take(&mut self.types),
|
||||
type_lookup: mem::take(&mut self.type_lookup),
|
||||
resources: mem::take(&mut self.resources),
|
||||
resource_lookup: mem::take(&mut self.resource_lookup),
|
||||
interface_lookup: Default::default(),
|
||||
interfaces: Default::default(),
|
||||
functions: mem::take(&mut self.functions),
|
||||
globals: mem::take(&mut self.globals),
|
||||
})
|
||||
}
|
||||
|
||||
fn process_use<'a>(
|
||||
&mut self,
|
||||
fields: &[Item<'a>],
|
||||
deps: &'a HashMap<String, Interface>,
|
||||
) -> Result<()> {
|
||||
for field in fields {
|
||||
let u = match field {
|
||||
Item::Use(u) => u,
|
||||
_ => continue,
|
||||
};
|
||||
let mut dep = &deps[&*u.from[0].name];
|
||||
let mut prev = &*u.from[0].name;
|
||||
for name in u.from[1..].iter() {
|
||||
dep = match dep.interface_lookup.get(&*name.name) {
|
||||
Some(i) => &dep.interfaces[*i],
|
||||
None => {
|
||||
return Err(Error {
|
||||
span: name.span,
|
||||
msg: format!("`{}` not defined in `{}`", name.name, prev),
|
||||
}
|
||||
.into())
|
||||
}
|
||||
};
|
||||
prev = &*name.name;
|
||||
}
|
||||
|
||||
let mod_name = &u.from[0];
|
||||
|
||||
match &u.names {
|
||||
Some(names) => {
|
||||
for name in names {
|
||||
let (my_name, span) = match &name.as_ {
|
||||
Some(id) => (&id.name, id.span),
|
||||
None => (&name.name.name, name.name.span),
|
||||
};
|
||||
let mut found = false;
|
||||
|
||||
if let Some(id) = dep.resource_lookup.get(&*name.name.name) {
|
||||
let resource = self.copy_resource(&mod_name.name, dep, *id);
|
||||
self.define_resource(my_name, span, resource)?;
|
||||
found = true;
|
||||
}
|
||||
|
||||
if let Some(id) = dep.type_lookup.get(&*name.name.name) {
|
||||
let ty = self.copy_type_def(&mod_name.name, dep, *id);
|
||||
self.define_type(my_name, span, ty)?;
|
||||
found = true;
|
||||
}
|
||||
|
||||
if !found {
|
||||
return Err(Error {
|
||||
span: name.name.span,
|
||||
msg: "name not defined in submodule".to_string(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
for (id, resource) in dep.resources.iter() {
|
||||
let id = self.copy_resource(&mod_name.name, dep, id);
|
||||
self.define_resource(&resource.name, mod_name.span, id)?;
|
||||
}
|
||||
let mut names = dep.type_lookup.iter().collect::<Vec<_>>();
|
||||
names.sort(); // produce a stable order by which to add names
|
||||
for (name, id) in names {
|
||||
let ty = self.copy_type_def(&mod_name.name, dep, *id);
|
||||
self.define_type(name, mod_name.span, ty)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_resource(&mut self, dep_name: &str, dep: &Interface, r: ResourceId) -> ResourceId {
|
||||
let resources = &mut self.resources;
|
||||
*self
|
||||
.resources_copied
|
||||
.entry((dep_name.to_string(), r))
|
||||
.or_insert_with(|| {
|
||||
let r = &dep.resources[r];
|
||||
let resource = Resource {
|
||||
docs: r.docs.clone(),
|
||||
name: r.name.clone(),
|
||||
supertype: r.supertype.clone(),
|
||||
foreign_module: Some(
|
||||
r.foreign_module
|
||||
.clone()
|
||||
.unwrap_or_else(|| dep_name.to_string()),
|
||||
),
|
||||
};
|
||||
resources.alloc(resource)
|
||||
})
|
||||
}
|
||||
|
||||
fn copy_type_def(&mut self, dep_name: &str, dep: &Interface, dep_id: TypeId) -> TypeId {
|
||||
if let Some(id) = self.types_copied.get(&(dep_name.to_string(), dep_id)) {
|
||||
return *id;
|
||||
}
|
||||
let ty = &dep.types[dep_id];
|
||||
|
||||
let ty = TypeDef {
|
||||
docs: ty.docs.clone(),
|
||||
name: ty.name.clone(),
|
||||
foreign_module: Some(
|
||||
ty.foreign_module
|
||||
.clone()
|
||||
.unwrap_or_else(|| dep_name.to_string()),
|
||||
),
|
||||
kind: match &ty.kind {
|
||||
TypeDefKind::Type(t) => TypeDefKind::Type(self.copy_type(dep_name, dep, *t)),
|
||||
TypeDefKind::Record(r) => TypeDefKind::Record(Record {
|
||||
fields: r
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| Field {
|
||||
docs: field.docs.clone(),
|
||||
name: field.name.clone(),
|
||||
ty: self.copy_type(dep_name, dep, field.ty),
|
||||
})
|
||||
.collect(),
|
||||
}),
|
||||
TypeDefKind::Flags(f) => TypeDefKind::Flags(f.clone()),
|
||||
TypeDefKind::Tuple(t) => TypeDefKind::Tuple(Tuple {
|
||||
types: t
|
||||
.types
|
||||
.iter()
|
||||
.map(|ty| self.copy_type(dep_name, dep, *ty))
|
||||
.collect(),
|
||||
}),
|
||||
TypeDefKind::Variant(v) => TypeDefKind::Variant(Variant {
|
||||
cases: v
|
||||
.cases
|
||||
.iter()
|
||||
.map(|case| Case {
|
||||
docs: case.docs.clone(),
|
||||
name: case.name.clone(),
|
||||
ty: self.copy_type(dep_name, dep, case.ty),
|
||||
})
|
||||
.collect(),
|
||||
}),
|
||||
TypeDefKind::Enum(e) => TypeDefKind::Enum(Enum {
|
||||
cases: e.cases.clone(),
|
||||
}),
|
||||
TypeDefKind::List(t) => TypeDefKind::List(self.copy_type(dep_name, dep, *t)),
|
||||
TypeDefKind::Option(t) => TypeDefKind::Option(self.copy_type(dep_name, dep, *t)),
|
||||
TypeDefKind::Expected(e) => TypeDefKind::Expected(Expected {
|
||||
ok: self.copy_type(dep_name, dep, e.ok),
|
||||
err: self.copy_type(dep_name, dep, e.err),
|
||||
}),
|
||||
TypeDefKind::Union(u) => TypeDefKind::Union(Union {
|
||||
cases: u
|
||||
.cases
|
||||
.iter()
|
||||
.map(|c| UnionCase {
|
||||
docs: c.docs.clone(),
|
||||
ty: self.copy_type(dep_name, dep, c.ty),
|
||||
})
|
||||
.collect(),
|
||||
}),
|
||||
TypeDefKind::Future(t) => TypeDefKind::Future(self.copy_type(dep_name, dep, *t)),
|
||||
TypeDefKind::Stream(e) => TypeDefKind::Stream(Stream {
|
||||
element: self.copy_type(dep_name, dep, e.element),
|
||||
end: self.copy_type(dep_name, dep, e.end),
|
||||
}),
|
||||
},
|
||||
};
|
||||
let id = self.types.alloc(ty);
|
||||
self.types_copied.insert((dep_name.to_string(), dep_id), id);
|
||||
id
|
||||
}
|
||||
|
||||
fn copy_type(&mut self, dep_name: &str, dep: &Interface, ty: Type) -> Type {
|
||||
match ty {
|
||||
Type::Id(id) => Type::Id(self.copy_type_def(dep_name, dep, id)),
|
||||
Type::Handle(id) => Type::Handle(self.copy_resource(dep_name, dep, id)),
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
fn register_names(&mut self, fields: &[Item<'_>]) -> Result<()> {
|
||||
let mut values = HashSet::new();
|
||||
for field in fields {
|
||||
match field {
|
||||
Item::Resource(r) => {
|
||||
let docs = self.docs(&r.docs);
|
||||
let id = self.resources.alloc(Resource {
|
||||
docs,
|
||||
name: r.name.name.to_string(),
|
||||
supertype: r
|
||||
.supertype
|
||||
.as_ref()
|
||||
.map(|supertype| supertype.name.to_string()),
|
||||
foreign_module: None,
|
||||
});
|
||||
self.define_resource(&r.name.name, r.name.span, id)?;
|
||||
let type_id = self.types.alloc(TypeDef {
|
||||
docs: Docs::default(),
|
||||
kind: TypeDefKind::Type(Type::Handle(id)),
|
||||
name: None,
|
||||
foreign_module: None,
|
||||
});
|
||||
self.define_type(&r.name.name, r.name.span, type_id)?;
|
||||
}
|
||||
Item::TypeDef(t) => {
|
||||
let docs = self.docs(&t.docs);
|
||||
let id = self.types.alloc(TypeDef {
|
||||
docs,
|
||||
// a dummy kind is used for now which will get filled in
|
||||
// later with the actual desired contents.
|
||||
kind: TypeDefKind::List(Type::U8),
|
||||
name: Some(t.name.name.to_string()),
|
||||
foreign_module: None,
|
||||
});
|
||||
self.define_type(&t.name.name, t.name.span, id)?;
|
||||
}
|
||||
Item::Value(f) => {
|
||||
if !values.insert(&f.name.name) {
|
||||
return Err(Error {
|
||||
span: f.name.span,
|
||||
msg: format!("{:?} defined twice", f.name.name),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
Item::Use(_) => {}
|
||||
|
||||
Item::Interface(_) => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn define_resource(&mut self, name: &str, span: Span, id: ResourceId) -> Result<()> {
|
||||
if self.resource_lookup.insert(name.to_string(), id).is_some() {
|
||||
Err(Error {
|
||||
span,
|
||||
msg: format!("resource {:?} defined twice", name),
|
||||
}
|
||||
.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn define_type(&mut self, name: &str, span: Span, id: TypeId) -> Result<()> {
|
||||
if self.type_lookup.insert(name.to_string(), id).is_some() {
|
||||
Err(Error {
|
||||
span,
|
||||
msg: format!("type {:?} defined twice", name),
|
||||
}
|
||||
.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_type_def(&mut self, ty: &super::Type<'_>) -> Result<TypeDefKind> {
|
||||
Ok(match ty {
|
||||
super::Type::Unit => TypeDefKind::Type(Type::Unit),
|
||||
super::Type::Bool => TypeDefKind::Type(Type::Bool),
|
||||
super::Type::U8 => TypeDefKind::Type(Type::U8),
|
||||
super::Type::U16 => TypeDefKind::Type(Type::U16),
|
||||
super::Type::U32 => TypeDefKind::Type(Type::U32),
|
||||
super::Type::U64 => TypeDefKind::Type(Type::U64),
|
||||
super::Type::S8 => TypeDefKind::Type(Type::S8),
|
||||
super::Type::S16 => TypeDefKind::Type(Type::S16),
|
||||
super::Type::S32 => TypeDefKind::Type(Type::S32),
|
||||
super::Type::S64 => TypeDefKind::Type(Type::S64),
|
||||
super::Type::Float32 => TypeDefKind::Type(Type::Float32),
|
||||
super::Type::Float64 => TypeDefKind::Type(Type::Float64),
|
||||
super::Type::Char => TypeDefKind::Type(Type::Char),
|
||||
super::Type::String => TypeDefKind::Type(Type::String),
|
||||
super::Type::Handle(resource) => {
|
||||
let id = match self.resource_lookup.get(&*resource.name) {
|
||||
Some(id) => *id,
|
||||
None => {
|
||||
return Err(Error {
|
||||
span: resource.span,
|
||||
msg: format!("no resource named `{}`", resource.name),
|
||||
}
|
||||
.into())
|
||||
}
|
||||
};
|
||||
TypeDefKind::Type(Type::Handle(id))
|
||||
}
|
||||
super::Type::Name(name) => {
|
||||
let id = match self.type_lookup.get(&*name.name) {
|
||||
Some(id) => *id,
|
||||
None => {
|
||||
return Err(Error {
|
||||
span: name.span,
|
||||
msg: format!("no type named `{}`", name.name),
|
||||
}
|
||||
.into())
|
||||
}
|
||||
};
|
||||
TypeDefKind::Type(Type::Id(id))
|
||||
}
|
||||
super::Type::List(list) => {
|
||||
let ty = self.resolve_type(list)?;
|
||||
TypeDefKind::List(ty)
|
||||
}
|
||||
super::Type::Record(record) => {
|
||||
let fields = record
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
Ok(Field {
|
||||
docs: self.docs(&field.docs),
|
||||
name: field.name.name.to_string(),
|
||||
ty: self.resolve_type(&field.ty)?,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
TypeDefKind::Record(Record { fields })
|
||||
}
|
||||
super::Type::Flags(flags) => {
|
||||
let flags = flags
|
||||
.flags
|
||||
.iter()
|
||||
.map(|flag| Flag {
|
||||
docs: self.docs(&flag.docs),
|
||||
name: flag.name.name.to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
TypeDefKind::Flags(Flags { flags })
|
||||
}
|
||||
super::Type::Tuple(types) => {
|
||||
let types = types
|
||||
.iter()
|
||||
.map(|ty| self.resolve_type(ty))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
TypeDefKind::Tuple(Tuple { types })
|
||||
}
|
||||
super::Type::Variant(variant) => {
|
||||
if variant.cases.is_empty() {
|
||||
return Err(Error {
|
||||
span: variant.span,
|
||||
msg: "empty variant".to_string(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
let cases = variant
|
||||
.cases
|
||||
.iter()
|
||||
.map(|case| {
|
||||
Ok(Case {
|
||||
docs: self.docs(&case.docs),
|
||||
name: case.name.name.to_string(),
|
||||
ty: match &case.ty {
|
||||
Some(ty) => self.resolve_type(ty)?,
|
||||
None => Type::Unit,
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
TypeDefKind::Variant(Variant { cases })
|
||||
}
|
||||
super::Type::Enum(e) => {
|
||||
if e.cases.is_empty() {
|
||||
return Err(Error {
|
||||
span: e.span,
|
||||
msg: "empty enum".to_string(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
let cases = e
|
||||
.cases
|
||||
.iter()
|
||||
.map(|case| {
|
||||
Ok(EnumCase {
|
||||
docs: self.docs(&case.docs),
|
||||
name: case.name.name.to_string(),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
TypeDefKind::Enum(Enum { cases })
|
||||
}
|
||||
super::Type::Option(ty) => TypeDefKind::Option(self.resolve_type(ty)?),
|
||||
super::Type::Expected(e) => TypeDefKind::Expected(Expected {
|
||||
ok: self.resolve_type(&e.ok)?,
|
||||
err: self.resolve_type(&e.err)?,
|
||||
}),
|
||||
super::Type::Union(e) => {
|
||||
if e.cases.is_empty() {
|
||||
return Err(Error {
|
||||
span: e.span,
|
||||
msg: "empty union".to_string(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
let cases = e
|
||||
.cases
|
||||
.iter()
|
||||
.map(|case| {
|
||||
Ok(UnionCase {
|
||||
docs: self.docs(&case.docs),
|
||||
ty: self.resolve_type(&case.ty)?,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
TypeDefKind::Union(Union { cases })
|
||||
}
|
||||
super::Type::Future(t) => TypeDefKind::Future(self.resolve_type(t)?),
|
||||
super::Type::Stream(s) => TypeDefKind::Stream(Stream {
|
||||
element: self.resolve_type(&s.element)?,
|
||||
end: self.resolve_type(&s.end)?,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_type(&mut self, ty: &super::Type<'_>) -> Result<Type> {
|
||||
let kind = self.resolve_type_def(ty)?;
|
||||
Ok(self.anon_type_def(TypeDef {
|
||||
kind,
|
||||
name: None,
|
||||
docs: Docs::default(),
|
||||
foreign_module: None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn anon_type_def(&mut self, ty: TypeDef) -> Type {
|
||||
let key = match &ty.kind {
|
||||
TypeDefKind::Type(t) => return *t,
|
||||
TypeDefKind::Variant(v) => Key::Variant(
|
||||
v.cases
|
||||
.iter()
|
||||
.map(|case| (case.name.clone(), case.ty))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
TypeDefKind::Record(r) => Key::Record(
|
||||
r.fields
|
||||
.iter()
|
||||
.map(|case| (case.name.clone(), case.ty))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
TypeDefKind::Flags(r) => {
|
||||
Key::Flags(r.flags.iter().map(|f| f.name.clone()).collect::<Vec<_>>())
|
||||
}
|
||||
TypeDefKind::Tuple(t) => Key::Tuple(t.types.clone()),
|
||||
TypeDefKind::Enum(r) => {
|
||||
Key::Enum(r.cases.iter().map(|f| f.name.clone()).collect::<Vec<_>>())
|
||||
}
|
||||
TypeDefKind::List(ty) => Key::List(*ty),
|
||||
TypeDefKind::Option(t) => Key::Option(*t),
|
||||
TypeDefKind::Expected(e) => Key::Expected(e.ok, e.err),
|
||||
TypeDefKind::Union(u) => Key::Union(u.cases.iter().map(|c| c.ty).collect()),
|
||||
TypeDefKind::Future(ty) => Key::Future(*ty),
|
||||
TypeDefKind::Stream(s) => Key::Stream(s.element, s.end),
|
||||
};
|
||||
let types = &mut self.types;
|
||||
let id = self
|
||||
.anon_types
|
||||
.entry(key)
|
||||
.or_insert_with(|| types.alloc(ty));
|
||||
Type::Id(*id)
|
||||
}
|
||||
|
||||
fn docs(&mut self, doc: &super::Docs<'_>) -> Docs {
|
||||
let mut docs = None;
|
||||
for doc in doc.docs.iter() {
|
||||
// Comments which are not doc-comments are silently ignored
|
||||
if let Some(doc) = doc.strip_prefix("///") {
|
||||
let docs = docs.get_or_insert_with(String::new);
|
||||
docs.push_str(doc.trim_start_matches('/').trim());
|
||||
docs.push('\n');
|
||||
} else if let Some(doc) = doc.strip_prefix("/**") {
|
||||
let docs = docs.get_or_insert_with(String::new);
|
||||
assert!(doc.ends_with("*/"));
|
||||
for line in doc[..doc.len() - 2].lines() {
|
||||
docs.push_str(line);
|
||||
docs.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
Docs { contents: docs }
|
||||
}
|
||||
|
||||
fn resolve_value(&mut self, value: &Value<'_>) -> Result<()> {
|
||||
let docs = self.docs(&value.docs);
|
||||
match &value.kind {
|
||||
ValueKind::Function {
|
||||
is_async,
|
||||
params,
|
||||
result,
|
||||
} => {
|
||||
let params = params
|
||||
.iter()
|
||||
.map(|(name, ty)| Ok((name.name.to_string(), self.resolve_type(ty)?)))
|
||||
.collect::<Result<_>>()?;
|
||||
let result = self.resolve_type(result)?;
|
||||
self.functions.push(Function {
|
||||
docs,
|
||||
name: value.name.name.to_string(),
|
||||
kind: FunctionKind::Freestanding,
|
||||
params,
|
||||
result,
|
||||
is_async: *is_async,
|
||||
});
|
||||
}
|
||||
ValueKind::Global(ty) => {
|
||||
let ty = self.resolve_type(ty)?;
|
||||
self.globals.push(Global {
|
||||
docs,
|
||||
name: value.name.name.to_string(),
|
||||
ty,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_resource(&mut self, resource: &super::Resource<'_>) -> Result<()> {
|
||||
let mut names = HashSet::new();
|
||||
let id = self.resource_lookup[&*resource.name.name];
|
||||
for (statik, value) in resource.values.iter() {
|
||||
let (is_async, params, result) = match &value.kind {
|
||||
ValueKind::Function {
|
||||
is_async,
|
||||
params,
|
||||
result,
|
||||
} => (*is_async, params, result),
|
||||
ValueKind::Global(_) => {
|
||||
return Err(Error {
|
||||
span: value.name.span,
|
||||
msg: "globals not allowed in resources".to_string(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
};
|
||||
if !names.insert(&value.name.name) {
|
||||
return Err(Error {
|
||||
span: value.name.span,
|
||||
msg: format!("{:?} defined twice in this resource", value.name.name),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
let docs = self.docs(&value.docs);
|
||||
let mut params = params
|
||||
.iter()
|
||||
.map(|(name, ty)| Ok((name.name.to_string(), self.resolve_type(ty)?)))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let result = self.resolve_type(result)?;
|
||||
let kind = if *statik {
|
||||
FunctionKind::Static {
|
||||
resource: id,
|
||||
name: value.name.name.to_string(),
|
||||
}
|
||||
} else {
|
||||
params.insert(0, ("self".to_string(), Type::Handle(id)));
|
||||
FunctionKind::Method {
|
||||
resource: id,
|
||||
name: value.name.name.to_string(),
|
||||
}
|
||||
};
|
||||
self.functions.push(Function {
|
||||
is_async,
|
||||
docs,
|
||||
name: format!("{}::{}", resource.name.name, value.name.name),
|
||||
kind,
|
||||
params,
|
||||
result,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_type_not_recursive(
|
||||
&self,
|
||||
span: Span,
|
||||
ty: TypeId,
|
||||
visiting: &mut HashSet<TypeId>,
|
||||
valid: &mut HashSet<TypeId>,
|
||||
) -> Result<()> {
|
||||
if valid.contains(&ty) {
|
||||
return Ok(());
|
||||
}
|
||||
if !visiting.insert(ty) {
|
||||
return Err(Error {
|
||||
span,
|
||||
msg: "type can recursively refer to itself".to_string(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
match &self.types[ty].kind {
|
||||
TypeDefKind::List(Type::Id(id)) | TypeDefKind::Type(Type::Id(id)) => {
|
||||
self.validate_type_not_recursive(span, *id, visiting, valid)?
|
||||
}
|
||||
TypeDefKind::Variant(v) => {
|
||||
for case in v.cases.iter() {
|
||||
if let Type::Id(id) = case.ty {
|
||||
self.validate_type_not_recursive(span, id, visiting, valid)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
TypeDefKind::Record(r) => {
|
||||
for case in r.fields.iter() {
|
||||
if let Type::Id(id) = case.ty {
|
||||
self.validate_type_not_recursive(span, id, visiting, valid)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
TypeDefKind::Tuple(t) => {
|
||||
for ty in t.types.iter() {
|
||||
if let Type::Id(id) = *ty {
|
||||
self.validate_type_not_recursive(span, id, visiting, valid)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TypeDefKind::Option(t) => {
|
||||
if let Type::Id(id) = *t {
|
||||
self.validate_type_not_recursive(span, id, visiting, valid)?
|
||||
}
|
||||
}
|
||||
TypeDefKind::Expected(e) => {
|
||||
if let Type::Id(id) = e.ok {
|
||||
self.validate_type_not_recursive(span, id, visiting, valid)?
|
||||
}
|
||||
if let Type::Id(id) = e.err {
|
||||
self.validate_type_not_recursive(span, id, visiting, valid)?
|
||||
}
|
||||
}
|
||||
TypeDefKind::Future(t) => {
|
||||
if let Type::Id(id) = *t {
|
||||
self.validate_type_not_recursive(span, id, visiting, valid)?
|
||||
}
|
||||
}
|
||||
TypeDefKind::Stream(s) => {
|
||||
if let Type::Id(id) = s.element {
|
||||
self.validate_type_not_recursive(span, id, visiting, valid)?
|
||||
}
|
||||
if let Type::Id(id) = s.end {
|
||||
self.validate_type_not_recursive(span, id, visiting, valid)?
|
||||
}
|
||||
}
|
||||
TypeDefKind::Union(u) => {
|
||||
for c in u.cases.iter() {
|
||||
if let Type::Id(id) = c.ty {
|
||||
self.validate_type_not_recursive(span, id, visiting, valid)?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TypeDefKind::Flags(_)
|
||||
| TypeDefKind::List(_)
|
||||
| TypeDefKind::Type(_)
|
||||
| TypeDefKind::Enum(_) => {}
|
||||
}
|
||||
|
||||
valid.insert(ty);
|
||||
visiting.remove(&ty);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
524
__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/lib.rs
Normal file
524
__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/lib.rs
Normal file
@@ -0,0 +1,524 @@
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use id_arena::{Arena, Id};
|
||||
use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub mod abi;
|
||||
mod ast;
|
||||
mod sizealign;
|
||||
pub use sizealign::*;
|
||||
|
||||
/// Checks if the given string is a legal identifier in wit.
|
||||
pub fn validate_id(s: &str) -> Result<()> {
|
||||
ast::validate_id(0, s)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Interface {
|
||||
pub name: String,
|
||||
/// The module name to use for bindings generation.
|
||||
///
|
||||
/// If `None`, then the interface name will be used.
|
||||
///
|
||||
/// If `Some`, then this value is used to format an export
|
||||
/// name of `<module>#<name>` for exports or an import module
|
||||
/// name of `<module>` for imports.
|
||||
pub module: Option<String>,
|
||||
pub types: Arena<TypeDef>,
|
||||
pub type_lookup: HashMap<String, TypeId>,
|
||||
pub resources: Arena<Resource>,
|
||||
pub resource_lookup: HashMap<String, ResourceId>,
|
||||
pub interfaces: Arena<Interface>,
|
||||
pub interface_lookup: HashMap<String, InterfaceId>,
|
||||
pub functions: Vec<Function>,
|
||||
pub globals: Vec<Global>,
|
||||
}
|
||||
|
||||
pub type TypeId = Id<TypeDef>;
|
||||
pub type ResourceId = Id<Resource>;
|
||||
pub type InterfaceId = Id<Interface>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TypeDef {
|
||||
pub docs: Docs,
|
||||
pub kind: TypeDefKind,
|
||||
pub name: Option<String>,
|
||||
/// `None` if this type is originally declared in this instance or
|
||||
/// otherwise `Some` if it was originally defined in a different module.
|
||||
pub foreign_module: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TypeDefKind {
|
||||
Record(Record),
|
||||
Flags(Flags),
|
||||
Tuple(Tuple),
|
||||
Variant(Variant),
|
||||
Enum(Enum),
|
||||
Option(Type),
|
||||
Expected(Expected),
|
||||
Union(Union),
|
||||
List(Type),
|
||||
Future(Type),
|
||||
Stream(Stream),
|
||||
Type(Type),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub enum Type {
|
||||
Unit,
|
||||
Bool,
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
S8,
|
||||
S16,
|
||||
S32,
|
||||
S64,
|
||||
Float32,
|
||||
Float64,
|
||||
Char,
|
||||
String,
|
||||
Handle(ResourceId),
|
||||
Id(TypeId),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub enum Int {
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Record {
|
||||
pub fields: Vec<Field>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Field {
|
||||
pub docs: Docs,
|
||||
pub name: String,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Flags {
|
||||
pub flags: Vec<Flag>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Flag {
|
||||
pub docs: Docs,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FlagsRepr {
|
||||
U8,
|
||||
U16,
|
||||
U32(usize),
|
||||
}
|
||||
|
||||
impl Flags {
|
||||
pub fn repr(&self) -> FlagsRepr {
|
||||
match self.flags.len() {
|
||||
n if n <= 8 => FlagsRepr::U8,
|
||||
n if n <= 16 => FlagsRepr::U16,
|
||||
n => FlagsRepr::U32(sizealign::align_to(n, 32) / 32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FlagsRepr {
|
||||
pub fn count(&self) -> usize {
|
||||
match self {
|
||||
FlagsRepr::U8 => 1,
|
||||
FlagsRepr::U16 => 1,
|
||||
FlagsRepr::U32(n) => *n,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Tuple {
|
||||
pub types: Vec<Type>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Variant {
|
||||
pub cases: Vec<Case>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Case {
|
||||
pub docs: Docs,
|
||||
pub name: String,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
impl Variant {
|
||||
pub fn tag(&self) -> Int {
|
||||
match self.cases.len() {
|
||||
n if n <= u8::max_value() as usize => Int::U8,
|
||||
n if n <= u16::max_value() as usize => Int::U16,
|
||||
n if n <= u32::max_value() as usize => Int::U32,
|
||||
_ => panic!("too many cases to fit in a repr"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Enum {
|
||||
pub cases: Vec<EnumCase>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EnumCase {
|
||||
pub docs: Docs,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl Enum {
|
||||
pub fn tag(&self) -> Int {
|
||||
match self.cases.len() {
|
||||
n if n <= u8::max_value() as usize => Int::U8,
|
||||
n if n <= u16::max_value() as usize => Int::U16,
|
||||
n if n <= u32::max_value() as usize => Int::U32,
|
||||
_ => panic!("too many cases to fit in a repr"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Expected {
|
||||
pub ok: Type,
|
||||
pub err: Type,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Union {
|
||||
pub cases: Vec<UnionCase>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UnionCase {
|
||||
pub docs: Docs,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
impl Union {
|
||||
pub fn tag(&self) -> Int {
|
||||
match self.cases.len() {
|
||||
n if n <= u8::max_value() as usize => Int::U8,
|
||||
n if n <= u16::max_value() as usize => Int::U16,
|
||||
n if n <= u32::max_value() as usize => Int::U32,
|
||||
_ => panic!("too many cases to fit in a repr"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Stream {
|
||||
pub element: Type,
|
||||
pub end: Type,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct Docs {
|
||||
pub contents: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Resource {
|
||||
pub docs: Docs,
|
||||
pub name: String,
|
||||
pub supertype: Option<String>,
|
||||
/// `None` if this resource is defined within the containing instance,
|
||||
/// otherwise `Some` if it's defined in an instance named here.
|
||||
pub foreign_module: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Global {
|
||||
pub docs: Docs,
|
||||
pub name: String,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Function {
|
||||
pub is_async: bool,
|
||||
pub docs: Docs,
|
||||
pub name: String,
|
||||
pub kind: FunctionKind,
|
||||
pub params: Vec<(String, Type)>,
|
||||
pub result: Type,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FunctionKind {
|
||||
Freestanding,
|
||||
Static { resource: ResourceId, name: String },
|
||||
Method { resource: ResourceId, name: String },
|
||||
}
|
||||
|
||||
impl Function {
|
||||
pub fn item_name(&self) -> &str {
|
||||
match &self.kind {
|
||||
FunctionKind::Freestanding => &self.name,
|
||||
FunctionKind::Static { name, .. } => name,
|
||||
FunctionKind::Method { name, .. } => name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_md(contents: &str) -> String {
|
||||
let mut wit = String::new();
|
||||
let mut last_pos = 0;
|
||||
let mut in_wit_code_block = false;
|
||||
Parser::new_ext(contents, Options::empty())
|
||||
.into_offset_iter()
|
||||
.for_each(|(event, range)| match (event, range) {
|
||||
(Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::Borrowed("wit")))), _) => {
|
||||
in_wit_code_block = true;
|
||||
}
|
||||
(Event::Text(text), range) if in_wit_code_block => {
|
||||
// Ensure that offsets are correct by inserting newlines to
|
||||
// cover the Markdown content outside of wit code blocks.
|
||||
for _ in contents[last_pos..range.start].lines() {
|
||||
wit.push_str("\n");
|
||||
}
|
||||
wit.push_str(&text);
|
||||
last_pos = range.end;
|
||||
}
|
||||
(Event::End(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::Borrowed("wit")))), _) => {
|
||||
in_wit_code_block = false;
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
wit
|
||||
}
|
||||
|
||||
impl Interface {
|
||||
pub fn parse(name: &str, input: &str) -> Result<Interface> {
|
||||
Interface::parse_with(name, input, |f| {
|
||||
Err(anyhow!("cannot load submodule `{}`", f))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_file(path: impl AsRef<Path>) -> Result<Interface> {
|
||||
let path = path.as_ref();
|
||||
let parent = path.parent().unwrap();
|
||||
let contents = std::fs::read_to_string(&path)
|
||||
.with_context(|| format!("failed to read: {}", path.display()))?;
|
||||
Interface::parse_with(path, &contents, |path| load_fs(parent, path))
|
||||
}
|
||||
|
||||
pub fn parse_with(
|
||||
filename: impl AsRef<Path>,
|
||||
contents: &str,
|
||||
mut load: impl FnMut(&str) -> Result<(PathBuf, String)>,
|
||||
) -> Result<Interface> {
|
||||
Interface::_parse_with(
|
||||
filename.as_ref(),
|
||||
contents,
|
||||
&mut load,
|
||||
&mut HashSet::new(),
|
||||
&mut HashMap::new(),
|
||||
)
|
||||
}
|
||||
|
||||
fn _parse_with(
|
||||
filename: &Path,
|
||||
contents: &str,
|
||||
load: &mut dyn FnMut(&str) -> Result<(PathBuf, String)>,
|
||||
visiting: &mut HashSet<PathBuf>,
|
||||
map: &mut HashMap<String, Interface>,
|
||||
) -> Result<Interface> {
|
||||
let mut name = filename.file_stem().unwrap();
|
||||
let mut contents = contents;
|
||||
|
||||
// If we have a ".md" file, it's a wit file wrapped in a markdown file;
|
||||
// parse the markdown to extract the `wit` code blocks.
|
||||
let md_contents;
|
||||
if filename.extension().and_then(|s| s.to_str()) == Some("md") {
|
||||
md_contents = unwrap_md(contents);
|
||||
contents = &md_contents[..];
|
||||
|
||||
// Also strip the inner ".wit" extension.
|
||||
name = Path::new(name).file_stem().unwrap();
|
||||
}
|
||||
|
||||
// Parse the `contents `into an AST
|
||||
let ast = match ast::Ast::parse(contents) {
|
||||
Ok(ast) => ast,
|
||||
Err(mut e) => {
|
||||
let file = filename.display().to_string();
|
||||
ast::rewrite_error(&mut e, &file, contents);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
// Load up any modules into our `map` that have not yet been parsed.
|
||||
if !visiting.insert(filename.to_path_buf()) {
|
||||
bail!("file `{}` recursively imports itself", filename.display())
|
||||
}
|
||||
for item in ast.items.iter() {
|
||||
let u = match item {
|
||||
ast::Item::Use(u) => u,
|
||||
_ => continue,
|
||||
};
|
||||
if map.contains_key(&*u.from[0].name) {
|
||||
continue;
|
||||
}
|
||||
let (filename, contents) = load(&u.from[0].name)
|
||||
// TODO: insert context here about `u.name.span` and `filename`
|
||||
?;
|
||||
let instance = Interface::_parse_with(&filename, &contents, load, visiting, map)?;
|
||||
map.insert(u.from[0].name.to_string(), instance);
|
||||
}
|
||||
visiting.remove(filename);
|
||||
|
||||
// and finally resolve everything into our final instance
|
||||
match ast.resolve(name.to_str().unwrap(), map) {
|
||||
Ok(i) => Ok(i),
|
||||
Err(mut e) => {
|
||||
let file = filename.display().to_string();
|
||||
ast::rewrite_error(&mut e, &file, contents);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn topological_types(&self) -> Vec<TypeId> {
|
||||
let mut ret = Vec::new();
|
||||
let mut visited = HashSet::new();
|
||||
for (id, _) in self.types.iter() {
|
||||
self.topo_visit(id, &mut ret, &mut visited);
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn topo_visit(&self, id: TypeId, list: &mut Vec<TypeId>, visited: &mut HashSet<TypeId>) {
|
||||
if !visited.insert(id) {
|
||||
return;
|
||||
}
|
||||
match &self.types[id].kind {
|
||||
TypeDefKind::Flags(_) | TypeDefKind::Enum(_) => {}
|
||||
TypeDefKind::Type(t) | TypeDefKind::List(t) => self.topo_visit_ty(t, list, visited),
|
||||
TypeDefKind::Record(r) => {
|
||||
for f in r.fields.iter() {
|
||||
self.topo_visit_ty(&f.ty, list, visited);
|
||||
}
|
||||
}
|
||||
TypeDefKind::Tuple(t) => {
|
||||
for t in t.types.iter() {
|
||||
self.topo_visit_ty(t, list, visited);
|
||||
}
|
||||
}
|
||||
TypeDefKind::Variant(v) => {
|
||||
for v in v.cases.iter() {
|
||||
self.topo_visit_ty(&v.ty, list, visited);
|
||||
}
|
||||
}
|
||||
TypeDefKind::Option(ty) => self.topo_visit_ty(ty, list, visited),
|
||||
TypeDefKind::Expected(e) => {
|
||||
self.topo_visit_ty(&e.ok, list, visited);
|
||||
self.topo_visit_ty(&e.err, list, visited);
|
||||
}
|
||||
TypeDefKind::Union(u) => {
|
||||
for t in u.cases.iter() {
|
||||
self.topo_visit_ty(&t.ty, list, visited);
|
||||
}
|
||||
}
|
||||
TypeDefKind::Future(ty) => {
|
||||
self.topo_visit_ty(ty, list, visited);
|
||||
}
|
||||
TypeDefKind::Stream(s) => {
|
||||
self.topo_visit_ty(&s.element, list, visited);
|
||||
self.topo_visit_ty(&s.end, list, visited);
|
||||
}
|
||||
}
|
||||
list.push(id);
|
||||
}
|
||||
|
||||
fn topo_visit_ty(&self, ty: &Type, list: &mut Vec<TypeId>, visited: &mut HashSet<TypeId>) {
|
||||
if let Type::Id(id) = ty {
|
||||
self.topo_visit(*id, list, visited);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_bits_valid(&self, ty: &Type) -> bool {
|
||||
match ty {
|
||||
Type::Unit
|
||||
| Type::U8
|
||||
| Type::S8
|
||||
| Type::U16
|
||||
| Type::S16
|
||||
| Type::U32
|
||||
| Type::S32
|
||||
| Type::U64
|
||||
| Type::S64
|
||||
| Type::Float32
|
||||
| Type::Float64 => true,
|
||||
|
||||
Type::Bool | Type::Char | Type::Handle(_) | Type::String => false,
|
||||
|
||||
Type::Id(id) => match &self.types[*id].kind {
|
||||
TypeDefKind::List(_)
|
||||
| TypeDefKind::Variant(_)
|
||||
| TypeDefKind::Enum(_)
|
||||
| TypeDefKind::Option(_)
|
||||
| TypeDefKind::Expected(_)
|
||||
| TypeDefKind::Future(_)
|
||||
| TypeDefKind::Stream(_)
|
||||
| TypeDefKind::Union(_) => false,
|
||||
TypeDefKind::Type(t) => self.all_bits_valid(t),
|
||||
TypeDefKind::Record(r) => r.fields.iter().all(|f| self.all_bits_valid(&f.ty)),
|
||||
TypeDefKind::Tuple(t) => t.types.iter().all(|t| self.all_bits_valid(t)),
|
||||
|
||||
// FIXME: this could perhaps be `true` for multiples-of-32 but
|
||||
// seems better to probably leave this as unconditionally
|
||||
// `false` for now, may want to reconsider later?
|
||||
TypeDefKind::Flags(_) => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_variant(&self, ty: &Type) -> Option<&Variant> {
|
||||
if let Type::Id(id) = ty {
|
||||
match &self.types[*id].kind {
|
||||
TypeDefKind::Variant(v) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_fs(root: &Path, name: &str) -> Result<(PathBuf, String)> {
|
||||
let wit = root.join(name).with_extension("wit");
|
||||
|
||||
// Attempt to read a ".wit" file.
|
||||
match fs::read_to_string(&wit) {
|
||||
Ok(contents) => Ok((wit, contents)),
|
||||
|
||||
// If no such file was found, attempt to read a ".wit.md" file.
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
let wit_md = wit.with_extension("wit.md");
|
||||
match fs::read_to_string(&wit_md) {
|
||||
Ok(contents) => Ok((wit_md, contents)),
|
||||
Err(_err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
Err(err) => return Err(err.into()),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
use crate::{FlagsRepr, Int, Interface, Type, TypeDef, TypeDefKind};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SizeAlign {
|
||||
map: Vec<(usize, usize)>,
|
||||
}
|
||||
|
||||
impl SizeAlign {
|
||||
pub fn fill(&mut self, iface: &Interface) {
|
||||
self.map = vec![(0, 0); iface.types.len()];
|
||||
for ty in iface.topological_types() {
|
||||
let pair = self.calculate(&iface.types[ty]);
|
||||
self.map[ty.index()] = pair;
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate(&self, ty: &TypeDef) -> (usize, usize) {
|
||||
match &ty.kind {
|
||||
TypeDefKind::Type(t) => (self.size(t), self.align(t)),
|
||||
TypeDefKind::List(_) => (8, 4),
|
||||
TypeDefKind::Record(r) => self.record(r.fields.iter().map(|f| &f.ty)),
|
||||
TypeDefKind::Tuple(t) => self.record(t.types.iter()),
|
||||
TypeDefKind::Flags(f) => match f.repr() {
|
||||
FlagsRepr::U8 => (1, 1),
|
||||
FlagsRepr::U16 => (2, 2),
|
||||
FlagsRepr::U32(n) => (n * 4, 4),
|
||||
},
|
||||
TypeDefKind::Variant(v) => self.variant(v.tag(), v.cases.iter().map(|c| &c.ty)),
|
||||
TypeDefKind::Enum(e) => self.variant(e.tag(), []),
|
||||
TypeDefKind::Option(t) => self.variant(Int::U8, [&Type::Unit, t]),
|
||||
TypeDefKind::Expected(e) => self.variant(Int::U8, [&e.ok, &e.err]),
|
||||
TypeDefKind::Union(u) => self.variant(u.tag(), u.cases.iter().map(|c| &c.ty)),
|
||||
// A future is represented as an index.
|
||||
TypeDefKind::Future(_) => (4, 4),
|
||||
// A stream is represented as an index.
|
||||
TypeDefKind::Stream(_) => (4, 4),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self, ty: &Type) -> usize {
|
||||
match ty {
|
||||
Type::Unit => 0,
|
||||
Type::Bool | Type::U8 | Type::S8 => 1,
|
||||
Type::U16 | Type::S16 => 2,
|
||||
Type::U32 | Type::S32 | Type::Float32 | Type::Char | Type::Handle(_) => 4,
|
||||
Type::U64 | Type::S64 | Type::Float64 | Type::String => 8,
|
||||
Type::Id(id) => self.map[id.index()].0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn align(&self, ty: &Type) -> usize {
|
||||
match ty {
|
||||
Type::Unit | Type::Bool | Type::U8 | Type::S8 => 1,
|
||||
Type::U16 | Type::S16 => 2,
|
||||
Type::U32 | Type::S32 | Type::Float32 | Type::Char | Type::Handle(_) | Type::String => {
|
||||
4
|
||||
}
|
||||
Type::U64 | Type::S64 | Type::Float64 => 8,
|
||||
Type::Id(id) => self.map[id.index()].1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn field_offsets<'a>(&self, types: impl IntoIterator<Item = &'a Type>) -> Vec<usize> {
|
||||
let mut cur = 0;
|
||||
types
|
||||
.into_iter()
|
||||
.map(|ty| {
|
||||
let ret = align_to(cur, self.align(ty));
|
||||
cur = ret + self.size(ty);
|
||||
ret
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn payload_offset<'a>(&self, tag: Int, cases: impl IntoIterator<Item = &'a Type>) -> usize {
|
||||
let mut max_align = 1;
|
||||
for ty in cases {
|
||||
max_align = max_align.max(self.align(ty));
|
||||
}
|
||||
let tag_size = int_size_align(tag).0;
|
||||
align_to(tag_size, max_align)
|
||||
}
|
||||
|
||||
pub fn record<'a>(&self, types: impl Iterator<Item = &'a Type>) -> (usize, usize) {
|
||||
let mut size = 0;
|
||||
let mut align = 1;
|
||||
for ty in types {
|
||||
let field_size = self.size(ty);
|
||||
let field_align = self.align(ty);
|
||||
size = align_to(size, field_align) + field_size;
|
||||
align = align.max(field_align);
|
||||
}
|
||||
(align_to(size, align), align)
|
||||
}
|
||||
|
||||
fn variant<'a>(&self, tag: Int, types: impl IntoIterator<Item = &'a Type>) -> (usize, usize) {
|
||||
let (discrim_size, discrim_align) = int_size_align(tag);
|
||||
let mut size = discrim_size;
|
||||
let mut align = discrim_align;
|
||||
for ty in types {
|
||||
let case_size = self.size(ty);
|
||||
let case_align = self.align(ty);
|
||||
align = align.max(case_align);
|
||||
size = size.max(align_to(discrim_size, case_align) + case_size);
|
||||
}
|
||||
(size, align)
|
||||
}
|
||||
}
|
||||
|
||||
fn int_size_align(i: Int) -> (usize, usize) {
|
||||
match i {
|
||||
Int::U8 => (1, 1),
|
||||
Int::U16 => (2, 2),
|
||||
Int::U32 => (4, 4),
|
||||
Int::U64 => (8, 8),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn align_to(val: usize, align: usize) -> usize {
|
||||
(val + align - 1) & !(align - 1)
|
||||
}
|
||||
Reference in New Issue
Block a user