feat: add a histrical wit-bindgen

This commit is contained in:
2023-01-01 00:25:48 +08:00
parent 01e8f5a959
commit aa50d63aec
419 changed files with 45283 additions and 1 deletions

View File

@@ -0,0 +1,17 @@
[package]
name = "wit-bindgen-gen-c"
version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
edition = "2018"
[lib]
doctest = false
test = false
[dependencies]
wit-bindgen-gen-core = { path = '../gen-core', version = '0.1.0' }
heck = "0.3"
structopt = { version = "0.3", default-features = false, optional = true }
[dev-dependencies]
test-helpers = { path = '../test-helpers', features = ['wit-bindgen-gen-c'] }

View File

@@ -0,0 +1,4 @@
fn main() {
println!("cargo:rerun-if-changed=build.rs");
// this build script is currently only here so OUT_DIR is set for testing.
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
mod imports {
test_helpers::codegen_c_import!(
// ...
"*.wit"
// TODO: implement async support
"!async-functions.wit"
);
}
mod exports {
test_helpers::codegen_c_export!(
"*.wit"
// TODO: implement async support
"!async-functions.wit"
// TODO: these use push/pull buffer in exports which isn't implemented
// yet
"!wasi-next.wit"
"!host.wit"
);
}
fn verify(dir: &str, name: &str) {
let dir = Path::new(dir);
let path = PathBuf::from(env::var_os("WASI_SDK_PATH").unwrap());
let mut cmd = Command::new(path.join("bin/clang"));
cmd.arg("--sysroot").arg(path.join("share/wasi-sysroot"));
cmd.arg(dir.join(format!("{}.c", name)));
cmd.arg("-I").arg(dir);
cmd.arg("-Wall")
.arg("-Wextra")
.arg("-Werror")
.arg("-Wno-unused-parameter");
cmd.arg("-c");
cmd.arg("-o").arg(dir.join("obj.o"));
println!("{:?}", cmd);
let output = match cmd.output() {
Ok(output) => output,
Err(e) => panic!("failed to spawn compiler: {}", e),
};
if output.status.success() {
return;
}
println!("status: {}", output.status);
println!("stdout: ------------------------------------------");
println!("{}", String::from_utf8_lossy(&output.stdout));
println!("stderr: ------------------------------------------");
println!("{}", String::from_utf8_lossy(&output.stderr));
panic!("failed to compile");
}

View File

@@ -0,0 +1,12 @@
[package]
name = "wit-bindgen-gen-core"
version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
edition = "2018"
[lib]
doctest = false
[dependencies]
wit-parser = { path = '../parser' }
anyhow = "1"

View File

@@ -0,0 +1,505 @@
use anyhow::Result;
use std::collections::{btree_map::Entry, BTreeMap, HashMap};
use std::fmt::{self, Write};
use std::ops::Deref;
use std::path::Path;
use wit_parser::*;
pub use wit_parser;
mod ns;
pub use ns::Ns;
/// This is the direction from the user's perspective. Are we importing
/// functions to call, or defining functions and exporting them to be called?
///
/// This is only used outside of `Generator` implementations. Inside of
/// `Generator` implementations, the `Direction` is translated to an
/// `AbiVariant` instead. The ABI variant is usually the same as the
/// `Direction`, but it's different in the case of the Wasmtime host bindings:
///
/// In a wasm-calling-wasm use case, one wasm module would use the `Import`
/// ABI, the other would use the `Export` ABI, and there would be an adapter
/// layer between the two that translates from one ABI to the other.
///
/// But with wasm-calling-host, we don't go through a separate adapter layer;
/// the binding code we generate on the host side just does everything itself.
/// So when the host is conceptually "exporting" a function to wasm, it uses
/// the `Import` ABI so that wasm can also use the `Import` ABI and import it
/// directly from the host.
///
/// These are all implementation details; from the user perspective, and
/// from the perspective of everything outside of `Generator` implementations,
/// `export` means I'm exporting functions to be called, and `import` means I'm
/// importing functions that I'm going to call, in both wasm modules and host
/// code. The enum here represents this user perspective.
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Direction {
Import,
Export,
}
pub trait Generator {
fn preprocess_all(&mut self, imports: &[Interface], exports: &[Interface]) {
drop((imports, exports));
}
fn preprocess_one(&mut self, iface: &Interface, dir: Direction) {
drop((iface, dir));
}
fn type_record(
&mut self,
iface: &Interface,
id: TypeId,
name: &str,
record: &Record,
docs: &Docs,
);
fn type_flags(&mut self, iface: &Interface, id: TypeId, name: &str, flags: &Flags, docs: &Docs);
fn type_tuple(&mut self, iface: &Interface, id: TypeId, name: &str, flags: &Tuple, docs: &Docs);
fn type_variant(
&mut self,
iface: &Interface,
id: TypeId,
name: &str,
variant: &Variant,
docs: &Docs,
);
fn type_option(
&mut self,
iface: &Interface,
id: TypeId,
name: &str,
payload: &Type,
docs: &Docs,
);
fn type_expected(
&mut self,
iface: &Interface,
id: TypeId,
name: &str,
expected: &Expected,
docs: &Docs,
);
fn type_union(&mut self, iface: &Interface, id: TypeId, name: &str, union: &Union, docs: &Docs);
fn type_enum(&mut self, iface: &Interface, id: TypeId, name: &str, enum_: &Enum, docs: &Docs);
fn type_resource(&mut self, iface: &Interface, ty: ResourceId);
fn type_alias(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs);
fn type_list(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs);
fn type_builtin(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs);
fn preprocess_functions(&mut self, iface: &Interface, dir: Direction) {
drop((iface, dir));
}
fn import(&mut self, iface: &Interface, func: &Function);
fn export(&mut self, iface: &Interface, func: &Function);
fn finish_functions(&mut self, iface: &Interface, dir: Direction) {
drop((iface, dir));
}
fn finish_one(&mut self, iface: &Interface, files: &mut Files);
fn finish_all(&mut self, files: &mut Files) {
drop(files);
}
fn generate_one(&mut self, iface: &Interface, dir: Direction, files: &mut Files) {
self.preprocess_one(iface, dir);
for (id, ty) in iface.types.iter() {
// assert!(ty.foreign_module.is_none()); // TODO
let name = match &ty.name {
Some(name) => name,
None => continue,
};
match &ty.kind {
TypeDefKind::Record(record) => self.type_record(iface, id, name, record, &ty.docs),
TypeDefKind::Flags(flags) => self.type_flags(iface, id, name, flags, &ty.docs),
TypeDefKind::Tuple(tuple) => self.type_tuple(iface, id, name, tuple, &ty.docs),
TypeDefKind::Enum(enum_) => self.type_enum(iface, id, name, enum_, &ty.docs),
TypeDefKind::Variant(variant) => {
self.type_variant(iface, id, name, variant, &ty.docs)
}
TypeDefKind::Option(t) => self.type_option(iface, id, name, t, &ty.docs),
TypeDefKind::Expected(e) => self.type_expected(iface, id, name, e, &ty.docs),
TypeDefKind::Union(u) => self.type_union(iface, id, name, u, &ty.docs),
TypeDefKind::List(t) => self.type_list(iface, id, name, t, &ty.docs),
TypeDefKind::Type(t) => self.type_alias(iface, id, name, t, &ty.docs),
TypeDefKind::Future(_) => todo!("generate for future"),
TypeDefKind::Stream(_) => todo!("generate for stream"),
}
}
for (id, _resource) in iface.resources.iter() {
self.type_resource(iface, id);
}
self.preprocess_functions(iface, dir);
for f in iface.functions.iter() {
match dir {
Direction::Import => self.import(iface, &f),
Direction::Export => self.export(iface, &f),
}
}
self.finish_functions(iface, dir);
self.finish_one(iface, files)
}
fn generate_all(&mut self, imports: &[Interface], exports: &[Interface], files: &mut Files) {
self.preprocess_all(imports, exports);
for imp in imports {
self.generate_one(imp, Direction::Import, files);
}
for exp in exports {
self.generate_one(exp, Direction::Export, files);
}
self.finish_all(files);
}
}
#[derive(Default)]
pub struct Types {
type_info: HashMap<TypeId, TypeInfo>,
}
#[derive(Default, Clone, Copy)]
pub struct TypeInfo {
/// Whether or not this type is ever used (transitively) within the
/// parameter of a function.
pub param: bool,
/// Whether or not this type is ever used (transitively) within the
/// result of a function.
pub result: bool,
/// Whether or not this type (transitively) has a list.
pub has_list: bool,
/// Whether or not this type (transitively) has a handle.
pub has_handle: bool,
}
impl std::ops::BitOrAssign for TypeInfo {
fn bitor_assign(&mut self, rhs: Self) {
self.param |= rhs.param;
self.result |= rhs.result;
self.has_list |= rhs.has_list;
self.has_handle |= rhs.has_handle;
}
}
impl Types {
pub fn analyze(&mut self, iface: &Interface) {
for (t, _) in iface.types.iter() {
self.type_id_info(iface, t);
}
for f in iface.functions.iter() {
for (_, ty) in f.params.iter() {
self.set_param_result_ty(iface, ty, true, false);
}
self.set_param_result_ty(iface, &f.result, false, true);
}
}
pub fn get(&self, id: TypeId) -> TypeInfo {
self.type_info[&id]
}
pub fn type_id_info(&mut self, iface: &Interface, ty: TypeId) -> TypeInfo {
if let Some(info) = self.type_info.get(&ty) {
return *info;
}
let mut info = TypeInfo::default();
match &iface.types[ty].kind {
TypeDefKind::Record(r) => {
for field in r.fields.iter() {
info |= self.type_info(iface, &field.ty);
}
}
TypeDefKind::Tuple(t) => {
for ty in t.types.iter() {
info |= self.type_info(iface, ty);
}
}
TypeDefKind::Flags(_) => {}
TypeDefKind::Enum(_) => {}
TypeDefKind::Variant(v) => {
for case in v.cases.iter() {
info |= self.type_info(iface, &case.ty);
}
}
TypeDefKind::List(ty) => {
info = self.type_info(iface, ty);
info.has_list = true;
}
TypeDefKind::Type(ty) => {
info = self.type_info(iface, ty);
}
TypeDefKind::Option(ty) => {
info = self.type_info(iface, ty);
}
TypeDefKind::Expected(e) => {
info = self.type_info(iface, &e.ok);
info |= self.type_info(iface, &e.err);
}
TypeDefKind::Union(u) => {
for case in u.cases.iter() {
info |= self.type_info(iface, &case.ty);
}
}
TypeDefKind::Future(_) => todo!("type_id_info for future"),
TypeDefKind::Stream(_) => todo!("type_id_info for stream"),
}
self.type_info.insert(ty, info);
return info;
}
pub fn type_info(&mut self, iface: &Interface, ty: &Type) -> TypeInfo {
let mut info = TypeInfo::default();
match ty {
Type::Handle(_) => info.has_handle = true,
Type::String => info.has_list = true,
Type::Id(id) => return self.type_id_info(iface, *id),
_ => {}
}
info
}
fn set_param_result_id(&mut self, iface: &Interface, ty: TypeId, param: bool, result: bool) {
match &iface.types[ty].kind {
TypeDefKind::Record(r) => {
for field in r.fields.iter() {
self.set_param_result_ty(iface, &field.ty, param, result)
}
}
TypeDefKind::Tuple(t) => {
for ty in t.types.iter() {
self.set_param_result_ty(iface, ty, param, result)
}
}
TypeDefKind::Flags(_) => {}
TypeDefKind::Enum(_) => {}
TypeDefKind::Variant(v) => {
for case in v.cases.iter() {
self.set_param_result_ty(iface, &case.ty, param, result)
}
}
TypeDefKind::List(ty) | TypeDefKind::Type(ty) | TypeDefKind::Option(ty) => {
self.set_param_result_ty(iface, ty, param, result)
}
TypeDefKind::Expected(e) => {
self.set_param_result_ty(iface, &e.ok, param, result);
self.set_param_result_ty(iface, &e.err, param, result);
}
TypeDefKind::Union(u) => {
for case in u.cases.iter() {
self.set_param_result_ty(iface, &case.ty, param, result)
}
}
TypeDefKind::Future(_) => todo!("set_param_result_id for future"),
TypeDefKind::Stream(_) => todo!("set_param_result_id for stream"),
}
}
fn set_param_result_ty(&mut self, iface: &Interface, ty: &Type, param: bool, result: bool) {
match ty {
Type::Id(id) => {
self.type_id_info(iface, *id);
let info = self.type_info.get_mut(id).unwrap();
if (param && !info.param) || (result && !info.result) {
info.param = info.param || param;
info.result = info.result || result;
self.set_param_result_id(iface, *id, param, result);
}
}
_ => {}
}
}
}
#[derive(Default)]
pub struct Files {
files: BTreeMap<String, Vec<u8>>,
}
impl Files {
pub fn push(&mut self, name: &str, contents: &[u8]) {
match self.files.entry(name.to_owned()) {
Entry::Vacant(entry) => {
entry.insert(contents.to_owned());
}
Entry::Occupied(ref mut entry) => {
entry.get_mut().extend_from_slice(contents);
}
}
}
pub fn iter(&self) -> impl Iterator<Item = (&'_ str, &'_ [u8])> {
self.files.iter().map(|p| (p.0.as_str(), p.1.as_slice()))
}
}
pub fn load(path: impl AsRef<Path>) -> Result<Interface> {
Interface::parse_file(path)
}
#[derive(Default)]
pub struct Source {
s: String,
indent: usize,
}
impl Source {
pub fn push_str(&mut self, src: &str) {
let lines = src.lines().collect::<Vec<_>>();
for (i, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.starts_with("}") && self.s.ends_with(" ") {
self.s.pop();
self.s.pop();
}
self.s.push_str(if lines.len() == 1 {
line
} else {
line.trim_start()
});
if trimmed.ends_with('{') {
self.indent += 1;
}
if trimmed.starts_with('}') {
self.indent -= 1;
}
if i != lines.len() - 1 || src.ends_with("\n") {
self.newline();
}
}
}
pub fn indent(&mut self, amt: usize) {
self.indent += amt;
}
pub fn deindent(&mut self, amt: usize) {
self.indent -= amt;
}
fn newline(&mut self) {
self.s.push_str("\n");
for _ in 0..self.indent {
self.s.push_str(" ");
}
}
pub fn as_mut_string(&mut self) -> &mut String {
&mut self.s
}
}
impl Write for Source {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.push_str(s);
Ok(())
}
}
impl Deref for Source {
type Target = str;
fn deref(&self) -> &str {
&self.s
}
}
impl From<Source> for String {
fn from(s: Source) -> String {
s.s
}
}
/// Calls [`write!`] with the passed arguments and unwraps the result.
///
/// Useful for writing to things with infallible `Write` implementations like
/// `Source` and `String`.
///
/// [`write!`]: std::write
#[macro_export]
macro_rules! uwrite {
($dst:expr, $($arg:tt)*) => {
write!($dst, $($arg)*).unwrap()
};
}
/// Calls [`writeln!`] with the passed arguments and unwraps the result.
///
/// Useful for writing to things with infallible `Write` implementations like
/// `Source` and `String`.
///
/// [`writeln!`]: std::writeln
#[macro_export]
macro_rules! uwriteln {
($dst:expr, $($arg:tt)*) => {
writeln!($dst, $($arg)*).unwrap()
};
}
#[cfg(test)]
mod tests {
use super::{Generator, Source};
#[test]
fn simple_append() {
let mut s = Source::default();
s.push_str("x");
assert_eq!(s.s, "x");
s.push_str("y");
assert_eq!(s.s, "xy");
s.push_str("z ");
assert_eq!(s.s, "xyz ");
s.push_str(" a ");
assert_eq!(s.s, "xyz a ");
s.push_str("\na");
assert_eq!(s.s, "xyz a \na");
}
#[test]
fn newline_remap() {
let mut s = Source::default();
s.push_str("function() {\n");
s.push_str("y\n");
s.push_str("}\n");
assert_eq!(s.s, "function() {\n y\n}\n");
}
#[test]
fn if_else() {
let mut s = Source::default();
s.push_str("if() {\n");
s.push_str("y\n");
s.push_str("} else if () {\n");
s.push_str("z\n");
s.push_str("}\n");
assert_eq!(s.s, "if() {\n y\n} else if () {\n z\n}\n");
}
#[test]
fn trim_ws() {
let mut s = Source::default();
s.push_str(
"function() {
x
}",
);
assert_eq!(s.s, "function() {\n x\n}");
}
#[test]
fn generator_is_object_safe() {
fn _assert(_: &dyn Generator) {}
}
}

View File

@@ -0,0 +1,27 @@
use std::collections::HashSet;
#[derive(Default)]
pub struct Ns {
defined: HashSet<String>,
tmp: usize,
}
impl Ns {
pub fn insert(&mut self, name: &str) -> Result<(), String> {
if self.defined.insert(name.to_string()) {
Ok(())
} else {
Err(format!("name `{}` already defined", name))
}
}
pub fn tmp(&mut self, name: &str) -> String {
let mut ret = name.to_string();
while self.defined.contains(&ret) {
ret = format!("{}{}", name, self.tmp);
self.tmp += 1;
}
self.defined.insert(ret.clone());
return ret;
}
}

View File

@@ -0,0 +1,18 @@
module.exports = {
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"rules": {
// allow this since we generate `const {} = e;` for empty structs
"no-empty-pattern": 0,
// TODO: we generate some unused functions by accident, let's fix that later
"no-unused-vars": 0,
}
};

View File

@@ -0,0 +1,17 @@
[package]
name = "wit-bindgen-gen-js"
version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
edition = "2018"
[lib]
doctest = false
test = false
[dependencies]
wit-bindgen-gen-core = { path = '../gen-core', version = '0.1.0' }
heck = "0.3"
structopt = { version = "0.3", default-features = false, optional = true }
[dev-dependencies]
test-helpers = { path = '../test-helpers', features = ['wit-bindgen-gen-js'] }

View File

@@ -0,0 +1,4 @@
fn main() {
println!("cargo:rerun-if-changed=build.rs");
// this build script is currently only here so OUT_DIR is set for testing.
}

View File

@@ -0,0 +1,9 @@
{
"devDependencies": {
"@types/node": "^15.12.2",
"@typescript-eslint/eslint-plugin": "^4.27.0",
"@typescript-eslint/parser": "^4.27.0",
"eslint": "^7.28.0",
"typescript": "^4.3.2"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
use std::path::Path;
use std::process::Command;
mod exports {
test_helpers::codegen_js_export!(
// ...
"*.wit"
);
}
mod imports {
test_helpers::codegen_js_import!(
"*.wit"
// This uses buffers, which we don't support in imports just yet
// TODO: should support this
"!wasi-next.wit"
"!host.wit"
);
}
fn verify(dir: &str, name: &str) {
let (cmd, args) = if cfg!(windows) {
("cmd.exe", &["/c", "npx.cmd"] as &[&str])
} else {
("npx", &[] as &[&str])
};
let status = Command::new(cmd)
.args(args)
.arg("eslint")
.arg("-c")
.arg(".eslintrc.js")
.arg(Path::new(dir).join(&format!("{}.js", name)))
.status()
.unwrap();
assert!(status.success());
}

View File

@@ -0,0 +1,7 @@
export function getWasm(): Uint8Array;
export interface Wasi {
start(instance: WebAssembly.Instance): void;
}
export function addWasiToImports(importObj: any): Wasi;

View File

@@ -0,0 +1,26 @@
import { readFileSync } from 'fs';
import { WASI } from 'wasi';
export function getWasm() {
return readFileSync(process.argv[2]);
}
class MyWasi {
constructor(wasi) {
this.wasi = wasi;
}
start(instance) {
if ('_start' in instance.exports) {
this.wasi.start(instance);
} else {
this.wasi.initialize(instance);
}
}
}
export function addWasiToImports(importObj) {
const wasi = new WASI();
importObj.wasi_snapshot_preview1 = wasi.wasiImport;
return new MyWasi(wasi);
}

View File

@@ -0,0 +1,99 @@
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use wit_bindgen_gen_core::Generator;
test_helpers::runtime_tests!("ts");
fn execute(name: &str, wasm: &Path, ts: &Path, imports: &Path, exports: &Path) {
let mut dir = PathBuf::from(env!("OUT_DIR"));
dir.push(name);
drop(fs::remove_dir_all(&dir));
fs::create_dir_all(&dir).unwrap();
println!("OUT_DIR = {:?}", dir);
println!("Generating bindings...");
// We call `generate_all` with exports from the imports.wit file, and
// imports from the exports.wit wit file. It's reversed because we're
// implementing the host side of these APIs.
let imports = wit_bindgen_gen_core::wit_parser::Interface::parse_file(imports).unwrap();
let exports = wit_bindgen_gen_core::wit_parser::Interface::parse_file(exports).unwrap();
let mut files = Default::default();
wit_bindgen_gen_js::Opts::default()
.build()
.generate_all(&[exports], &[imports], &mut files);
for (file, contents) in files.iter() {
fs::write(dir.join(file), contents).unwrap();
}
let (cmd, args) = if cfg!(windows) {
("cmd.exe", &["/c", "npx.cmd"] as &[&str])
} else {
("npx", &[] as &[&str])
};
fs::copy(ts, dir.join("host.ts")).unwrap();
fs::copy("tests/helpers.d.ts", dir.join("helpers.d.ts")).unwrap();
fs::copy("tests/helpers.js", dir.join("helpers.js")).unwrap();
let config = dir.join("tsconfig.json");
fs::write(
&config,
format!(
r#"
{{
"files": ["host.ts"],
"compilerOptions": {{
"module": "esnext",
"target": "es2020",
"strict": true,
"strictNullChecks": true,
"baseUrl": {0:?},
"outDir": {0:?}
}}
}}
"#,
dir,
),
)
.unwrap();
run(Command::new(cmd)
.args(args)
.arg("tsc")
.arg("--project")
.arg(&config));
// Currently there's mysterious uvwasi errors creating a `WASI` on Windows.
// Unsure what's happening so let's ignore these tests for now since there's
// not much Windows-specific here anyway.
if cfg!(windows) {
return;
}
fs::write(dir.join("package.json"), "{\"type\":\"module\"}").unwrap();
let mut path = Vec::new();
path.push(env::current_dir().unwrap());
path.push(dir.clone());
println!("{:?}", std::env::join_paths(&path));
run(Command::new("node")
.arg("--experimental-wasi-unstable-preview1")
.arg(dir.join("host.js"))
.env("NODE_PATH", std::env::join_paths(&path).unwrap())
.arg(wasm));
}
fn run(cmd: &mut Command) {
println!("running {:?}", cmd);
let output = cmd.output().expect("failed to executed");
println!("status: {}", output.status);
println!(
"stdout:\n {}",
String::from_utf8_lossy(&output.stdout).replace("\n", "\n ")
);
println!(
"stderr:\n {}",
String::from_utf8_lossy(&output.stderr).replace("\n", "\n ")
);
assert!(output.status.success());
}

View File

@@ -0,0 +1,14 @@
[package]
name = "wit-bindgen-gen-markdown"
version = "0.1.0"
edition = "2018"
[lib]
doctest = false
test = false
[dependencies]
heck = "0.3"
pulldown-cmark = { version = "0.8", default-features = false }
structopt = { version = "0.3", default-features = false, optional = true }
wit-bindgen-gen-core = { path = '../gen-core', version = '0.1.0' }

View File

@@ -0,0 +1,466 @@
use heck::*;
use pulldown_cmark::{html, Event, LinkType, Parser, Tag};
use std::collections::HashMap;
use wit_bindgen_gen_core::{wit_parser, Direction, Files, Generator, Source};
use wit_parser::*;
#[derive(Default)]
pub struct Markdown {
src: Source,
opts: Opts,
sizes: SizeAlign,
hrefs: HashMap<String, String>,
funcs: usize,
types: usize,
}
#[derive(Default, Debug, Clone)]
#[cfg_attr(feature = "structopt", derive(structopt::StructOpt))]
pub struct Opts {
// ...
}
impl Opts {
pub fn build(&self) -> Markdown {
let mut r = Markdown::new();
r.opts = self.clone();
r
}
}
impl Markdown {
pub fn new() -> Markdown {
Markdown::default()
}
fn print_ty(&mut self, iface: &Interface, ty: &Type, skip_name: bool) {
match ty {
Type::Unit => self.src.push_str("`unit`"),
Type::Bool => self.src.push_str("`bool`"),
Type::U8 => self.src.push_str("`u8`"),
Type::S8 => self.src.push_str("`s8`"),
Type::U16 => self.src.push_str("`u16`"),
Type::S16 => self.src.push_str("`s16`"),
Type::U32 => self.src.push_str("`u32`"),
Type::S32 => self.src.push_str("`s32`"),
Type::U64 => self.src.push_str("`u64`"),
Type::S64 => self.src.push_str("`s64`"),
Type::Float32 => self.src.push_str("`float32`"),
Type::Float64 => self.src.push_str("`float64`"),
Type::Char => self.src.push_str("`char`"),
Type::String => self.src.push_str("`string`"),
Type::Handle(id) => {
self.src.push_str("handle<");
self.src.push_str(&iface.resources[*id].name);
self.src.push_str(">");
}
Type::Id(id) => {
let ty = &iface.types[*id];
if !skip_name {
if let Some(name) = &ty.name {
self.src.push_str("[`");
self.src.push_str(name);
self.src.push_str("`](#");
self.src.push_str(&name.to_snake_case());
self.src.push_str(")");
return;
}
}
match &ty.kind {
TypeDefKind::Type(t) => self.print_ty(iface, t, false),
TypeDefKind::Tuple(t) => {
self.src.push_str("(");
for (i, t) in t.types.iter().enumerate() {
if i > 0 {
self.src.push_str(", ");
}
self.print_ty(iface, t, false);
}
self.src.push_str(")");
}
TypeDefKind::Record(_)
| TypeDefKind::Flags(_)
| TypeDefKind::Enum(_)
| TypeDefKind::Variant(_)
| TypeDefKind::Union(_) => {
unreachable!()
}
TypeDefKind::Option(t) => {
self.src.push_str("option<");
self.print_ty(iface, t, false);
self.src.push_str(">");
}
TypeDefKind::Expected(e) => {
self.src.push_str("expected<");
self.print_ty(iface, &e.ok, false);
self.src.push_str(", ");
self.print_ty(iface, &e.err, false);
self.src.push_str(">");
}
TypeDefKind::List(t) => {
self.src.push_str("list<");
self.print_ty(iface, t, false);
self.src.push_str(">");
}
TypeDefKind::Future(t) => {
self.src.push_str("future<");
self.print_ty(iface, t, false);
self.src.push_str(">");
}
TypeDefKind::Stream(s) => {
self.src.push_str("stream<");
self.print_ty(iface, &s.element, false);
self.src.push_str(", ");
self.print_ty(iface, &s.end, false);
self.src.push_str(">");
}
}
}
}
}
fn docs(&mut self, docs: &Docs) {
let docs = match &docs.contents {
Some(docs) => docs,
None => return,
};
for line in docs.lines() {
self.src.push_str(line.trim());
self.src.push_str("\n");
}
}
fn print_type_header(&mut self, name: &str) {
if self.types == 0 {
self.src.push_str("# Types\n\n");
}
self.types += 1;
self.src.push_str(&format!(
"## <a href=\"#{}\" name=\"{0}\"></a> `{}`: ",
name.to_snake_case(),
name,
));
self.hrefs
.insert(name.to_string(), format!("#{}", name.to_snake_case()));
}
fn print_type_info(&mut self, ty: TypeId, docs: &Docs) {
self.docs(docs);
self.src.push_str("\n");
self.src
.push_str(&format!("Size: {}, ", self.sizes.size(&Type::Id(ty))));
self.src
.push_str(&format!("Alignment: {}\n", self.sizes.align(&Type::Id(ty))));
}
}
impl Generator for Markdown {
fn preprocess_one(&mut self, iface: &Interface, _dir: Direction) {
self.sizes.fill(iface);
}
fn type_record(
&mut self,
iface: &Interface,
id: TypeId,
name: &str,
record: &Record,
docs: &Docs,
) {
self.print_type_header(name);
self.src.push_str("record\n\n");
self.print_type_info(id, docs);
self.src.push_str("\n### Record Fields\n\n");
for field in record.fields.iter() {
self.src.push_str(&format!(
"- <a href=\"{r}.{f}\" name=\"{r}.{f}\"></a> [`{name}`](#{r}.{f}): ",
r = name.to_snake_case(),
f = field.name.to_snake_case(),
name = field.name,
));
self.hrefs.insert(
format!("{}::{}", name, field.name),
format!("#{}.{}", name.to_snake_case(), field.name.to_snake_case()),
);
self.print_ty(iface, &field.ty, false);
self.src.indent(1);
self.src.push_str("\n\n");
self.docs(&field.docs);
self.src.deindent(1);
self.src.push_str("\n");
}
}
fn type_tuple(
&mut self,
iface: &Interface,
id: TypeId,
name: &str,
tuple: &Tuple,
docs: &Docs,
) {
self.print_type_header(name);
self.src.push_str("tuple\n\n");
self.print_type_info(id, docs);
self.src.push_str("\n### Tuple Fields\n\n");
for (i, ty) in tuple.types.iter().enumerate() {
self.src.push_str(&format!(
"- <a href=\"{r}.{f}\" name=\"{r}.{f}\"></a> [`{name}`](#{r}.{f}): ",
r = name.to_snake_case(),
f = i,
name = i,
));
self.hrefs.insert(
format!("{}::{}", name, i),
format!("#{}.{}", name.to_snake_case(), i),
);
self.print_ty(iface, ty, false);
self.src.push_str("\n");
}
}
fn type_flags(
&mut self,
_iface: &Interface,
id: TypeId,
name: &str,
flags: &Flags,
docs: &Docs,
) {
self.print_type_header(name);
self.src.push_str("record\n\n");
self.print_type_info(id, docs);
self.src.push_str("\n### Record Fields\n\n");
for (i, flag) in flags.flags.iter().enumerate() {
self.src.push_str(&format!(
"- <a href=\"{r}.{f}\" name=\"{r}.{f}\"></a> [`{name}`](#{r}.{f}): ",
r = name.to_snake_case(),
f = flag.name.to_snake_case(),
name = flag.name,
));
self.hrefs.insert(
format!("{}::{}", name, flag.name),
format!("#{}.{}", name.to_snake_case(), flag.name.to_snake_case()),
);
self.src.indent(1);
self.src.push_str("\n\n");
self.docs(&flag.docs);
self.src.deindent(1);
self.src.push_str(&format!("Bit: {}\n", i));
self.src.push_str("\n");
}
}
fn type_variant(
&mut self,
iface: &Interface,
id: TypeId,
name: &str,
variant: &Variant,
docs: &Docs,
) {
self.print_type_header(name);
self.src.push_str("variant\n\n");
self.print_type_info(id, docs);
self.src.push_str("\n### Variant Cases\n\n");
for case in variant.cases.iter() {
self.src.push_str(&format!(
"- <a href=\"{v}.{c}\" name=\"{v}.{c}\"></a> [`{name}`](#{v}.{c})",
v = name.to_snake_case(),
c = case.name.to_snake_case(),
name = case.name,
));
self.hrefs.insert(
format!("{}::{}", name, case.name),
format!("#{}.{}", name.to_snake_case(), case.name.to_snake_case()),
);
self.src.push_str(": ");
self.print_ty(iface, &case.ty, false);
self.src.indent(1);
self.src.push_str("\n\n");
self.docs(&case.docs);
self.src.deindent(1);
self.src.push_str("\n");
}
}
fn type_union(
&mut self,
iface: &Interface,
id: TypeId,
name: &str,
union: &Union,
docs: &Docs,
) {
self.print_type_header(name);
self.src.push_str("union\n\n");
self.print_type_info(id, docs);
self.src.push_str("\n### Union Cases\n\n");
let snake = name.to_snake_case();
for (i, case) in union.cases.iter().enumerate() {
self.src.push_str(&format!(
"- <a href=\"{snake}.{i}\" name=\"{snake}.{i}\"></a> [`{i}`](#{snake}.{i})",
));
self.hrefs
.insert(format!("{name}::{i}"), format!("#{snake}.{i}"));
self.src.push_str(": ");
self.print_ty(iface, &case.ty, false);
self.src.indent(1);
self.src.push_str("\n\n");
self.docs(&case.docs);
self.src.deindent(1);
self.src.push_str("\n");
}
}
fn type_enum(&mut self, _iface: &Interface, id: TypeId, name: &str, enum_: &Enum, docs: &Docs) {
self.print_type_header(name);
self.src.push_str("enum\n\n");
self.print_type_info(id, docs);
self.src.push_str("\n### Enum Cases\n\n");
for case in enum_.cases.iter() {
self.src.push_str(&format!(
"- <a href=\"{v}.{c}\" name=\"{v}.{c}\"></a> [`{name}`](#{v}.{c})",
v = name.to_snake_case(),
c = case.name.to_snake_case(),
name = case.name,
));
self.hrefs.insert(
format!("{}::{}", name, case.name),
format!("#{}.{}", name.to_snake_case(), case.name.to_snake_case()),
);
self.src.indent(1);
self.src.push_str("\n\n");
self.docs(&case.docs);
self.src.deindent(1);
self.src.push_str("\n");
}
}
fn type_option(
&mut self,
iface: &Interface,
id: TypeId,
name: &str,
payload: &Type,
docs: &Docs,
) {
self.print_type_header(name);
self.src.push_str("option<");
self.print_ty(iface, payload, false);
self.src.push_str(">");
self.print_type_info(id, docs);
}
fn type_expected(
&mut self,
iface: &Interface,
id: TypeId,
name: &str,
expected: &Expected,
docs: &Docs,
) {
self.print_type_header(name);
self.src.push_str("expected<");
self.print_ty(iface, &expected.ok, false);
self.src.push_str(", ");
self.print_ty(iface, &expected.err, false);
self.src.push_str(">");
self.print_type_info(id, docs);
}
fn type_resource(&mut self, iface: &Interface, ty: ResourceId) {
drop((iface, ty));
}
fn type_alias(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs) {
self.print_type_header(name);
self.print_ty(iface, ty, true);
self.src.push_str("\n\n");
self.print_type_info(id, docs);
self.src.push_str("\n");
}
fn type_list(&mut self, iface: &Interface, id: TypeId, name: &str, _ty: &Type, docs: &Docs) {
self.type_alias(iface, id, name, &Type::Id(id), docs);
}
fn type_builtin(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs) {
self.type_alias(iface, id, name, ty, docs)
}
fn import(&mut self, iface: &Interface, func: &Function) {
if self.funcs == 0 {
self.src.push_str("# Functions\n\n");
}
self.funcs += 1;
self.src.push_str("----\n\n");
self.src.push_str(&format!(
"#### <a href=\"#{0}\" name=\"{0}\"></a> `",
func.name.to_snake_case()
));
self.hrefs
.insert(func.name.clone(), format!("#{}", func.name.to_snake_case()));
self.src.push_str(&func.name);
self.src.push_str("` ");
self.src.push_str("\n\n");
self.docs(&func.docs);
if func.params.len() > 0 {
self.src.push_str("##### Params\n\n");
for (name, ty) in func.params.iter() {
self.src.push_str(&format!(
"- <a href=\"#{f}.{p}\" name=\"{f}.{p}\"></a> `{}`: ",
name,
f = func.name.to_snake_case(),
p = name.to_snake_case(),
));
self.print_ty(iface, ty, false);
self.src.push_str("\n");
}
}
match &func.result {
Type::Unit => {}
ty => {
self.src.push_str("##### Results\n\n");
self.src.push_str(&format!(
"- <a href=\"#{f}.{p}\" name=\"{f}.{p}\"></a> `{}`: ",
"result",
f = func.name.to_snake_case(),
p = "result",
));
self.print_ty(iface, ty, false);
self.src.push_str("\n");
}
}
self.src.push_str("\n");
}
fn export(&mut self, iface: &Interface, func: &Function) {
self.import(iface, func);
}
fn finish_one(&mut self, _iface: &Interface, files: &mut Files) {
let parser = Parser::new(&self.src);
let mut events = Vec::new();
for event in parser {
if let Event::Code(code) = &event {
if let Some(dst) = self.hrefs.get(code.as_ref()) {
let tag = Tag::Link(LinkType::Inline, dst.as_str().into(), "".into());
events.push(Event::Start(tag.clone()));
events.push(event.clone());
events.push(Event::End(tag));
continue;
}
}
events.push(event);
}
let mut html_output = String::new();
html::push_html(&mut html_output, events.into_iter());
files.push("bindings.md", self.src.as_bytes());
files.push("bindings.html", html_output.as_bytes());
}
}

View File

@@ -0,0 +1,19 @@
[package]
name = "wit-bindgen-gen-rust-wasm"
version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
edition = "2018"
[lib]
test = false
doctest = false
[dependencies]
wit-bindgen-gen-core = { path = '../gen-core', version = '0.1.0' }
wit-bindgen-gen-rust = { path = '../gen-rust', version = '0.1.0' }
heck = "0.3"
structopt = { version = "0.3", default-features = false, optional = true }
[dev-dependencies]
wit-bindgen-rust = { path = '../rust-wasm' }
test-helpers = { path = '../test-helpers', features = ['wit-bindgen-gen-rust-wasm'] }

View File

@@ -0,0 +1,4 @@
fn main() {
println!("cargo:rerun-if-changed=build.rs");
// this build script is currently only here so OUT_DIR is set for testing.
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
#![allow(dead_code, type_alias_bounds)]
#[test]
fn ok() {}
#[rustfmt::skip]
mod imports {
test_helpers::codegen_rust_wasm_import!(
"*.wit"
// If you want to exclude a specific test you can include it here with
// gitignore glob syntax:
//
// "!wasm.wit"
// "!host.wit"
//
//
// Similarly you can also just remove the `*.wit` glob and list tests
// individually if you're debugging.
);
}
mod exports {
test_helpers::codegen_rust_wasm_export!(
"*.wit"
// TODO: these use push/pull buffer which isn't implemented in the test
// generator just yet
"!wasi-next.wit"
"!host.wit"
);
}

View File

@@ -0,0 +1,13 @@
[package]
name = "wit-bindgen-gen-rust"
version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
edition = "2018"
[lib]
doctest = false
test = false
[dependencies]
wit-bindgen-gen-core = { path = '../gen-core' }
heck = "0.3"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
[package]
name = "wit-bindgen-gen-spidermonkey"
version = "0.1.0"
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
edition = "2018"
[lib]
test = false
doctest = false
[dependencies]
lazy_static = "1.4.0"
structopt = { version = "0.3", optional = true }
wasm-encoder = "0.8.0"
wit-bindgen-gen-core = { path = "../gen-core" }
heck = "0.3"
[dev-dependencies]
test-helpers = { path = '../test-helpers', features = ['wit-bindgen-gen-spidermonkey'] }
wasmparser = "0.80"

View File

@@ -0,0 +1,4 @@
fn main() {
println!("cargo:rerun-if-changed=build.rs");
// this build script is currently only here so OUT_DIR is set for testing.
}

View File

@@ -0,0 +1,7 @@
spidermonkey-*
lib/*
*.o
spidermonkey.initial.wasm
wasi-sdk-12.0
mozbuild
.exports

View File

@@ -0,0 +1,142 @@
SM_REPO := https://github.com/fitzgen/gecko-dev
SM_COMMIT := dafd3165f45c55023ece4787a86444029e4f475e
# TODO: support building `spidermonkey.wasm` on other OSes. But for some reason
# the resulting `.wasm` binary is slower when the host compiler is on macOS.
WASI_SDK_URL := https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-12/wasi-sdk-12.0-linux.tar.gz
CC := $(CURDIR)/wasi-sdk-12.0/bin/clang
CXX := $(CURDIR)/wasi-sdk-12.0/bin/clang++
# Set this to `1` to enable logging via all the `SMW_LOG(...)` calls.
LOGGING := 0
# Set this to `-DDEBUG` and uncomment the `--enable-debug` line in `mozconfig`
# to enable debug builds of SpiderMonkey.
DEBUG := ""
# Set this to `""` in debug mode for better debugging.
OPT := "-O2"
CFLAGS := \
--sysroot=$(CURDIR)/wasi-sdk-12.0/share/wasi-sysroot \
-Wall \
--target=wasm32-unknown-wasi \
-Ispidermonkey-$(SM_COMMIT)/obj-wasm32-unknown-wasi/dist/include \
-I$(CURDIR)/include \
$(DEBUG) \
$(OPT) \
-DLOGGING=$(LOGGING)
CXXFLAGS := \
$(CFLAGS) \
-fno-exceptions \
-std=c++17
# Local object files.
LOCAL_OBJECTS := $(patsubst %.cpp,%.o,$(wildcard *.cpp))
# Object files needed within SpiderMonkey's obj dir.
SM_OBJECTS := \
js/src/build/libjs_static.a \
memory/build/Unified_cpp_memory_build0.o \
memory/mozalloc/mozalloc_abort.o \
memory/mozalloc/Unified_cpp_memory_mozalloc0.o \
mfbt/Unified_cpp_mfbt0.o \
mfbt/Unified_cpp_mfbt1.o \
mfbt/lz4.o \
mfbt/lz4frame.o \
mfbt/lz4hc.o \
mfbt/xxhash.o \
mozglue/misc/AutoProfilerLabel.o \
mozglue/misc/ConditionVariable_noop.o \
mozglue/misc/Decimal.o \
mozglue/misc/MmapFaultHandler.o \
mozglue/misc/Mutex_noop.o \
mozglue/misc/Printf.o \
mozglue/misc/StackWalk.o \
mozglue/misc/TimeStamp.o \
mozglue/misc/TimeStamp_posix.o \
mozglue/misc/Uptime.o \
modules/zlib/src/compress.o \
modules/zlib/src/gzclose.o \
modules/zlib/src/infback.o \
modules/zlib/src/uncompr.o \
wasm32-wasi/release/libjsrust.a
# The `./lib/*` copies of SpiderMonkey's object files that we check into the
# repo.
SM_LIB_OBJECTS := $(shell echo $(SM_OBJECTS) | xargs -d' ' -I{} basename {} | xargs -I{} echo lib/{})
.PHONY: all clean clean-all clean-spidermonkey clean-wasi-sdk
all: spidermonkey.wasm
@echo "Done!"
spidermonkey.initial.wasm: $(SM_LIB_OBJECTS) $(LOCAL_OBJECTS)
$(CXX) $(CXXFLAGS) \
-mexec-model=reactor \
$(LOCAL_OBJECTS) \
$(SM_LIB_OBJECTS) \
-o spidermonkey.initial.wasm \
-Wl,--export-dynamic \
-Wl,--growable-table \
-Wl,--export-table \
-Wl,--gc-sections
spidermonkey.wasm: spidermonkey.initial.wasm
# Uncomment this `wasm-opt` invocation and comment the following one out to
# enable better debugging.
#
# wasm-opt -g --duplicate-import-elimination spidermonkey.initial.wasm -o spidermonkey.wasm
wasm-opt -O2 --strip-dwarf --duplicate-import-elimination spidermonkey.initial.wasm -o spidermonkey.wasm
# Build all `*.cpp` files into `*.o` files.
%.o: %.cpp $(SM_LIB_OBJECTS)
$(CXX) $(CXXFLAGS) -c $< -o $@
# Actually build SpiderMonkey.
$(SM_LIB_OBJECTS): spidermonkey-$(SM_COMMIT) mozbuild wasi-sdk-12.0 mozconfig
cd spidermonkey-$(SM_COMMIT) \
&& MOZBUILD_STATE_PATH=$(CURDIR)/mozbuild MOZCONFIG=$(CURDIR)/mozconfig ./mach build
mkdir -p lib
for x in $(SM_OBJECTS); do \
cp spidermonkey-$(SM_COMMIT)/obj-wasm32-unknown-wasi/$$x lib/; \
done
# Clone `mozilla-central` at the `SM_COMMIT` commit.
spidermonkey-$(SM_COMMIT):
-rm -rf spidermonkey-temp
mkdir spidermonkey-temp
cd spidermonkey-temp \
&& git init \
&& git remote add origin $(SM_REPO) \
&& git fetch origin $(SM_COMMIT) \
&& git checkout $(SM_COMMIT)
mv spidermonkey-temp spidermonkey-$(SM_COMMIT)
mozbuild: spidermonkey-$(SM_COMMIT)
-mkdir mozbuild
cd spidermonkey-$(SM_COMMIT) \
&& MOZBUILD_STATE_PATH=$(CURDIR)/mozbuild ./mach bootstrap --application-choice js --no-system-changes \
|| rm -rf $(CURDIR)/mozbuild
wasi-sdk-12.0:
curl -L $(WASI_SDK_URL) | tar -x -z
clean-all: clean clean-spidermonkey clean-wasi-sdk
clean-wasi-sdk:
-rm -rf wasi-sdk-12.0
clean-spidermonkey:
-rm -rf spidermonkey-$(SM_COMMIT)
-rm -rf spidermonkey-$(SM_COMMIT)/obj-wasm32-unknown-wasi/
-rm -rf mozbuild
clean:
@echo 'Only cleaning our own artifacts, not upstream deps. Run `make clean-{all,spidermonkey,wasi-sdk}` to clean others.'
-rm -rf spidermonkey-temp
-rm -rf ./*.o
-rm -rf spidermonkey.wasm

View File

@@ -0,0 +1,13 @@
# `spidermonkey.wasm`
This directory contains the source code for `spidermonkey.wasm`, which is an
embedding of the SpiderMonkey JavaScript engine for targeting `wasm32-wasi` and
use with `wit-bindgen-gen-spidermonkey`. It exports a variety of helper
functions that are used by `wit-bindgen-gen-spidermonkey`'s generated glue
code. These helpers are typically named something like `SMW_whatever_function`.
## Building `spidermonkey.wasm`
```
make
```

View File

@@ -0,0 +1,46 @@
#include <stdlib.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winvalid-offsetof"
#include "js/Exception.h"
#pragma clang diagnostic pop
#include "smw/abort.h"
#include "smw/cx.h"
#include "smw/dump.h"
namespace smw {
void abort(const char* msg) {
abort(get_js_context(), msg);
}
void abort(JSContext *cx, const char* msg) {
fprintf(stderr, "Error: %s", msg);
if (JS_IsExceptionPending(cx)) {
fprintf(stderr, ":");
JS::ExceptionStack exception(cx);
if (!JS::GetPendingExceptionStack(cx, &exception)) {
fprintf(stderr, " failed to get pending exception value and stack\n");
} else {
fprintf(stderr, "\n exception value: ");
if (!dump_value(cx, exception.exception(), stderr)) {
fprintf(stderr, "<failed to dump value>");
}
fprintf(stderr, "\n exception stack:\n");
if (!dump_stack(cx, exception.stack(), stderr)) {
fprintf(stderr, "<failed to dump stack>\n");
}
}
} else {
fprintf(stderr, "\n");
}
// TODO: check for unhandled promise rejections.
fflush(stderr);
::abort();
}
} // namespace smw

View File

@@ -0,0 +1,452 @@
/*!
* This module implements the intrinsics used by code emitted in the
* `wit_bindgen_gen_spidermonkey::Bindgen` trait implementation.
*/
#include <assert.h>
#include <cmath>
#include <stdlib.h>
#include "smw/abort.h"
#include "smw/cx.h"
#include "smw/logging.h"
#include "smw/wasm.h"
#include "mozilla/UniquePtr.h"
#include "jsapi.h"
#include "js/Array.h"
#include "js/Conversions.h"
#include "js/ForOfIterator.h"
#include "js/Modules.h"
#ifdef LOGGING
#include "js/friend/DumpFunctions.h"
#endif
namespace smw {
using UniqueChars = mozilla::UniquePtr<char[]>;
using PersistentRootedValueVector = JS::PersistentRooted<JS::GCVector<JS::Value>>;
// Used for general Wasm<-->JS conversions.
static PersistentRootedValueVector* OPERANDS;
// Used for holding arguments to JS calls.
static PersistentRootedValueVector* ARGS;
// Used for holding returns from Wasm calls.
static PersistentRootedValueVector* RETS;
void init_operands(JSContext* cx) {
assert(!OPERANDS && "OPERANDS must only be initialized once");
OPERANDS = new PersistentRootedValueVector(cx, cx);
if (!OPERANDS) {
abort(cx, "failed to allocate OPERANDS");
}
assert(!ARGS && "ARGS must only be initialized once");
ARGS = new PersistentRootedValueVector(cx, cx);
if (!ARGS) {
abort(cx, "failed to allocate ARGS");
}
assert(!RETS && "RETS must only be initialized once");
RETS = new PersistentRootedValueVector(cx, cx);
if (!RETS) {
abort(cx, "failed to allocate RETS");
}
}
PersistentRootedValueVector& operands() {
assert(OPERANDS && OPERANDS->initialized() && "OPERANDS must be initialized");
return *OPERANDS;
}
void save_operand(size_t dest, JS::HandleValue val) {
#if LOGGING==1
SMW_LOG("operands[%zu] = ", dest);
js::DumpValue(val, stderr);
#endif // LOGGING==1
JSContext* cx = get_js_context();
if (operands().length() <= dest) {
size_t needed_capacity = 1 + dest - operands().length();
if (!operands().reserve(needed_capacity)) {
abort("failed to reserve capacity for the OPERANDS vector");
}
if (dest == operands().length()) {
bool ok = operands().append(val);
assert(ok && "already reserved space");
return;
}
JS::RootedValue placeholder(cx, JS::UndefinedValue());
for (size_t i = 0; i < needed_capacity; i++) {
bool ok = operands().append(placeholder);
assert(ok && "already reserved space");
}
}
operands()[dest].set(val);
}
PersistentRootedValueVector& args() {
assert(ARGS && ARGS->initialized() && "ARGS must be initialized");
return *ARGS;
}
PersistentRootedValueVector& rets() {
assert(RETS && RETS->initialized() && "RETS must be initialized");
return *RETS;
}
WASM_EXPORT
void canonical_abi_free(void* ptr, size_t size, size_t align) {
(void) size;
(void) align;
free(ptr);
}
WASM_EXPORT
void* canonical_abi_realloc(void* ptr, size_t old_size, size_t align, size_t new_size) {
(void) old_size;
(void) align;
return realloc(ptr, new_size);
}
WASM_EXPORT
void SMW_fill_operands(unsigned argc, JS::Value* vp) {
SMW_LOG("SMW_fill_operands(argc = %d, vp = %p)\n", argc, vp);
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
if (!operands().reserve(size_t(args.length()))) {
abort(get_js_context(), "failed to reserve space in the operands vector");
}
for (unsigned i = 0; i < args.length(); i++) {
#if LOGGING==1
SMW_LOG("operands[%d] = ", i);
js::DumpValue(args.get(i), stderr);
#endif // LOGGING==1
bool ok = operands().append(args.get(i));
assert(ok && "already reserved space");
}
}
WASM_EXPORT
void SMW_clear_operands() {
SMW_LOG("SMW_clear_operands\n");
operands().clear();
}
WASM_EXPORT
void SMW_push_arg(size_t i) {
SMW_LOG("SMW_push_arg(i = %zu)\n", i);
if (!args().append(operands()[i])) {
abort("failed to push arg");
}
}
WASM_EXPORT
void SMW_call(char *funcName, size_t funcNameLen, size_t numResults, size_t dest) {
#ifdef LOGGING
SMW_LOG("SMW_call(funcName = %p \"", funcName);
for (size_t i = 0; i < funcNameLen; i++) {
SMW_LOG("%c", funcName[i]);
}
SMW_LOG("\", funcNameLen = %zu, numResults = %zu, dest = %zu)\n",
funcNameLen,
numResults,
dest);
#endif
UniqueChars uniqFuncName(funcName);
JSContext *cx = get_js_context();
JS::RootedString funcNameAtom(cx, JS_AtomizeStringN(cx, uniqFuncName.get(), funcNameLen));
if (!funcNameAtom) {
abort(cx, "failed to atomize function name");
}
JS::RootedObject module(cx, get_user_module());
JS::RootedValue exportVal(cx);
bool hasExport = false;
if (!JS::GetModuleExport(cx, module, funcNameAtom, &exportVal, &hasExport)) {
abort(cx, "failed to get module export");
}
if (!hasExport) {
// TODO: include the export name in this message to help users debug
// which export they're missing.
abort(cx, "user module does not have the requested export");
}
JS::RootedFunction exportFunc(cx, JS_ValueToFunction(cx, exportVal));
if (!exportFunc) {
// TODO: include the export name in this message.
abort(cx, "exported value is not a function");
}
// XXX: we have to copy ARGS into a `JS::RootedVector<JS::Value>` because
// `JS::Call` takes a `JS::HandleValueArray` and you can't construct that
// from a `JS::PersistentRooted<JS::GCVector<JS::Value>>`, only a
// `JS::RootedVector<JS::Value>`. And we can't make `ARGS` a
// `JS::RootedVector<JS::Value>` because it is a global, not an on-stack
// RAII value as required by `JS::RootedVector<JS::Value>`. Gross!
JS::RootedVector<JS::Value> argsVector(cx);
if (!argsVector.reserve(args().length())) {
abort(cx, "failed to reserve space for arguments vector");
}
for (size_t i = 0; i < args().length(); i++) {
bool ok = argsVector.append(args()[i]);
assert(ok && "already reserved space");
}
JS::RootedObject thisObj(cx);
JS::RootedValue result(cx);
if (!JS::Call(cx, thisObj, exportFunc, argsVector, &result)) {
// TODO: include the export name in this message.
abort(cx, "calling export function failed");
}
args().clear();
if (numResults == 0) {
// Nothing to push onto the operands vector.
} else if (numResults == 1) {
save_operand(dest, result);
} else {
// Treat the "physical" return value as an iterator and unpack the
// "logical" return values from within it. This allows JS to return
// multiple WIT values as an array or any other iterable.
JS::ForOfIterator iter(cx);
if (!iter.init(result)) {
// TODO: include the export name in this message.
abort(cx, "failed to convert return value to iterable");
}
JS::RootedValue val(cx);
bool done = false;
for (size_t i = 0; i < numResults; i++) {
if (done) {
// TODO: include the export name in this message.
abort(cx, "function's returned iterator did not yield enough return values");
}
if (!iter.next(&val, &done)) {
// TODO: include the export name in this message.
abort(cx, "failed to get the next value out of the return values iterator");
}
save_operand(dest + i, val);
}
}
}
WASM_EXPORT
void SMW_push_return_value(size_t i) {
SMW_LOG("SMW_push_return_value(i = %zu)\n", i);
if (!rets().append(operands()[i])) {
abort(get_js_context(), "failed to push arg");
}
}
WASM_EXPORT
void SMW_finish_returns(unsigned argc, JS::Value* vp) {
SMW_LOG("SMW_finish_returns(argc = %d, vp = %p)\n", argc, vp);
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
switch (rets().length()) {
case 0: {
break;
}
case 1: {
args.rval().set(rets().back());
break;
}
default: {
JSContext* cx = get_js_context();
JS::RootedVector<JS::Value> elems(cx);
if (!elems.reserve(rets().length())) {
abort(cx, "failed to reserve space for results vector");
}
for (size_t i = 0; i < rets().length(); i++) {
bool ok = elems.append(rets()[i]);
assert(ok && "already reserved space");
}
JS::RootedObject arr(cx, JS::NewArrayObject(cx, elems));
if (!arr) {
abort(cx, "failed to allocate array for function's return values");
}
args.rval().setObject(*arr.get());
break;
}
}
rets().clear();
}
WASM_EXPORT
uint32_t SMW_i32_from_u32(size_t i) {
SMW_LOG("SMW_i32_from_u32(i = %zu)\n", i);
JSContext* cx = get_js_context();
JS::RootedValue val(cx, operands()[i]);
double number = 0.0;
if (!JS::ToNumber(cx, val, &number)) {
abort(cx, "failed to convert value to number");
}
number = std::round(number);
return uint32_t(number);
}
WASM_EXPORT
void SMW_u32_from_i32(uint32_t x, size_t dest) {
SMW_LOG("SMW_u32_from_i32(x = %ull, dest = %zu)\n", x, dest);
JSContext* cx = get_js_context();
JS::RootedValue val(cx, JS::NumberValue(x));
save_operand(dest, val);
}
WASM_EXPORT
void SMW_string_canon_lower(uint32_t* ret_ptr, size_t i) {
SMW_LOG("SMW_string_canon_lower(ret_ptr = %p, i = %zu)\n", ret_ptr, i);
JSContext* cx = get_js_context();
JS::RootedValue strVal(cx, operands()[i]);
if (!strVal.isString()) {
abort(cx, "value is not a string");
}
JS::RootedString str(cx, strVal.toString());
JS::Rooted<JSLinearString*> linearStr(cx, JS_EnsureLinearString(cx, str));
if (!linearStr) {
abort(cx, "failed to linearize JS string");
}
size_t len = JS::GetDeflatedUTF8StringLength(linearStr);
char* ptr = static_cast<char*>(malloc(len));
if (!ptr) {
abort(cx, "out of memory");
}
size_t num_written = JS::DeflateStringToUTF8Buffer(linearStr, mozilla::Span(ptr, len));
assert(num_written == len);
ret_ptr[0] = reinterpret_cast<uint32_t>(ptr);
ret_ptr[1] = static_cast<uint32_t>(len);
}
WASM_EXPORT
void SMW_string_canon_lift(char* ptr, size_t len, size_t dest) {
SMW_LOG("SMW_string_canon_lift(ptr = %p, len = %zu, dest = %zu)\n", ptr, len, dest);
JSContext* cx = get_js_context();
JS::RootedString str(cx, JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(ptr, len)));
if (!str) {
abort(cx, "failed to create JS string from UTF-8 buffer");
}
JS::RootedValue strVal(cx, JS::StringValue(str));
save_operand(dest, strVal);
}
WASM_EXPORT
uint32_t SMW_spread_into_array(size_t i) {
SMW_LOG("SMW_spread_into_array; i = %zu\n", i);
JSContext* cx = get_js_context();
JS::RootedValue iterable(cx, operands()[i]);
bool is_array = false;
if (!JS::IsArrayObject(cx, iterable, &is_array)) {
abort(cx, "failed to check if object is an array");
}
if (is_array) {
JS::RootedObject arr(cx, &iterable.toObject());
uint32_t length = 0;
if (!JS::GetArrayLength(cx, arr, &length)) {
abort(cx, "failed to get array length");
}
return length;
}
JS::RootedVector<JS::Value> elems(cx);
JS::ForOfIterator iter(cx);
if (!iter.init(iterable)) {
abort(cx, "failed to convert operand value to iterable");
}
JS::RootedValue val(cx);
bool done = false;
while (!done) {
if (!iter.next(&val, &done)) {
abort(cx, "failed to get the next value out of iterator");
}
if (done) {
break;
}
if (!elems.append(val)) {
abort(cx, "failed to append value to vector");
}
}
JS::RootedObject arr(cx, JS::NewArrayObject(cx, elems));
if (!arr) {
abort(cx, "failed to allocate JS array object");
}
operands()[i].setObject(*arr);
return elems.length();
}
WASM_EXPORT
void SMW_get_array_element(size_t array, size_t index, size_t dest) {
SMW_LOG("SMW_get_array_element(array = %zu, index = %zu, dest = %zu)\n", array, index, dest);
JSContext* cx = get_js_context();
JS::RootedValue array_val(cx, operands()[array]);
assert(array_val.isObject());
JS::RootedObject array_obj(cx, &array_val.toObject());
JS::RootedValue elem(cx);
if (!JS_GetElement(cx, array_obj, index, &elem)) {
abort(cx, "failed to get array element");
}
save_operand(dest, elem);
}
WASM_EXPORT
void SMW_new_array(size_t dest) {
SMW_LOG("SMW_new_array(dest = %zu)\n", dest);
JSContext* cx = get_js_context();
JS::RootedObject arr(cx, JS::NewArrayObject(cx, 0));
if (!arr) {
abort(cx, "failed to allocate a new JS array object");
}
JS::RootedValue arr_val(cx, JS::ObjectValue(*arr));
save_operand(dest, arr_val);
}
WASM_EXPORT
void SMW_array_push(size_t array, size_t elem) {
SMW_LOG("SMW_array_push(array = %zu, elem = %zu)\n", array, elem);
JSContext* cx = get_js_context();
JS::RootedValue array_val(cx, operands()[array]);
assert(array_val.isObject());
JS::RootedObject array_obj(cx, &array_val.toObject());
uint32_t length = 0;
if (!JS::GetArrayLength(cx, array_obj, &length)) {
abort(cx, "failed to get JS array object length");
}
JS::RootedValue elem_val(cx, operands()[elem]);
if (!JS_SetElement(cx, array_obj, length, elem_val)) {
abort(cx, "failed to set JS array element");
}
}
} // namespace smw

View File

@@ -0,0 +1,33 @@
#include <assert.h>
#include "smw/cx.h"
#include "jsapi.h"
namespace smw {
static JSContext* CONTEXT = nullptr;
void init_js_context(JSContext *cx) {
assert(!CONTEXT && "CONTEXT should only be initialized once");
CONTEXT = cx;
}
JSContext *get_js_context() {
assert(CONTEXT && "CONTEXT should be initialized");
return CONTEXT;
}
static JS::PersistentRooted<JSObject*> USER_MODULE;
void init_user_module(JSContext* cx, JSObject* user_module) {
assert(!USER_MODULE && "USER_MODULE should only be initialized once");
USER_MODULE.init(cx, user_module);
}
JSObject* get_user_module() {
assert(USER_MODULE && "USER_MODULE should be initialized");
return USER_MODULE;
}
} // namespace smw

View File

@@ -0,0 +1,48 @@
#include "smw/dump.h"
#include <assert.h>
#include "jsapi.h"
#include "smw/wasm.h"
namespace smw {
static JS::UniqueChars stringify_value(JSContext *cx, JS::HandleValue val) {
JS::RootedString str(cx, JS_ValueToSource(cx, val));
if (!str) {
return nullptr;
}
return JS_EncodeStringToUTF8(cx, str);
}
bool dump_value(JSContext *cx, JS::HandleValue val, FILE* fp) {
JS::UniqueChars str = stringify_value(cx, val);
if (!str) {
return false;
}
fprintf(fp, "%s\n", str.get());
return true;
}
bool dump_stack(JSContext *cx, JS::HandleObject stack, FILE* fp) {
JS::RootedString str(cx);
size_t indent = 4;
if (!JS::BuildStackString(cx, nullptr, stack, &str, indent)) {
return false;
}
JS::UniqueChars utf8 = JS_EncodeStringToUTF8(cx, str);
if (!utf8) {
return false;
}
fprintf(fp, "%s\n", utf8.get());
return true;
}
WASM_EXPORT
int32_t dump_i32(int32_t x) {
fprintf(stderr, "dump_i32: %d\n", x);
return x;
}
} // namespace smw

View File

@@ -0,0 +1,16 @@
#ifndef _smw_abort_h
#define _smw_abort_h
struct JSContext;
namespace smw {
/**
* Print the given error message and abort.
*/
void abort(const char* msg);
void abort(JSContext *cx, const char* msg);
}
#endif // _smw_abort_h

View File

@@ -0,0 +1,12 @@
#ifndef _smw_bindgen_h
#define _smw_bindgen_h
struct JSContext;
namespace smw {
void init_operands(JSContext* cx);
}
#endif // _smw_bindgen_h

View File

@@ -0,0 +1,17 @@
#ifndef _smw_cx_h
#define _smw_cx_h
struct JSContext;
class JSObject;
namespace smw {
void init_js_context(JSContext* cx);
JSContext* get_js_context();
void init_user_module(JSContext* cx, JSObject* user_module);
JSObject* get_user_module();
}
#endif // _smw_cx_h

View File

@@ -0,0 +1,27 @@
#ifndef _smw_dump_h
#define _smw_dump_h
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winvalid-offsetof"
#include "js/TypeDecls.h"
#include "js/Value.h"
#pragma clang diagnostic pop
namespace smw {
/**
* Dump a human-readable representation of the given JS value to the given file.
*/
bool dump_value(JSContext *cx, JS::HandleValue val, FILE* fp);
/**
* Dump a human-readable representation of the given JS exception stack to the
* given file.
*/
bool dump_stack(JSContext *cx, JS::HandleObject stack, FILE* fp);
}
#endif // _smw_dump_h

View File

@@ -0,0 +1,15 @@
#ifndef _smw_logging_h
#define _smw_logging_h
#if LOGGING==1
#include <stdio.h>
#define SMW_LOG(msg, ...) fprintf(stderr, msg, ##__VA_ARGS__)
#else // LOGGING==1
#define SMW_LOG(msg, ...) do { } while(false)
#endif // LOGGING==1
#endif // _smw_logging_h

View File

@@ -0,0 +1,18 @@
#ifndef _smw_wasm_h
#define _smw_wasm_h
/**
* An attribute for making a function exported from the final Wasm binary.
*
* Example usage:
*
* WASM_EXPORT
* int add(int a, int b) {
* return a + b;
* }
*/
#define WASM_EXPORT \
__attribute__((visibility("default"))) \
extern "C"
#endif // _smw_wasm_h

View File

@@ -0,0 +1,380 @@
/*!
* JS engine initialization and JS top-level evaluation.
*
* This file contains the code to start up the JS engine, define import-able
* modules from VM functions, and evaluate the user JS.
*/
#include <assert.h>
#include <stdlib.h>
#include "smw/abort.h"
#include "smw/bindgen.h"
#include "smw/cx.h"
#include "smw/wasm.h"
#include "js/AllocPolicy.h"
#include "js/CompilationAndEvaluation.h"
#include "js/GCAPI.h"
#include "js/GCVector.h"
#include "js/Initialization.h"
#include "js/Modules.h"
#include "js/Promise.h"
#include "js/Realm.h"
#include "js/SourceText.h"
#include "js/TypeDecls.h"
#include "js/Warnings.h"
#include "jsapi.h"
#include "jsfriendapi.h"
namespace smw {
using UniqueChars = mozilla::UniquePtr<char[]>;
bool INITIALIZED = false;
JS::PersistentRootedObject GLOBAL;
static JSClass global_class = {
"global",
JSCLASS_GLOBAL_FLAGS,
&JS::DefaultGlobalClassOps
};
/**
* Compile the given JS source as a module in the context of the given global.
*
* Takes ownership of `jsSource`.
*
* Does not take ownership of `jsFileName`.
*
* Sets `outModule` to the resulting source text module record object.
*/
bool compile_js_module(JSContext *cx,
const char *jsFileName,
char *jsSource,
size_t jsSourceLen,
JS::MutableHandleObject outModule) {
JS::CompileOptions copts(cx);
copts
.setFileAndLine(jsFileName, 1)
.setNoScriptRval(true)
.setForceFullParse();
JS::SourceText<mozilla::Utf8Unit> srcBuf;
if (!srcBuf.init(cx, jsSource, jsSourceLen, JS::SourceOwnership::TakeOwnership)) {
return false;
}
JS::RootedObject module(cx);
// Disabling generational GC during compilation seems to slightly reduce
// the number of pages touched post-wizening. (Whereas disabling it
// during execution meaningfully increases it, which is why this is
// scoped to just compilation.)
JS::AutoDisableGenerationalGC noGgc(cx);
module = JS::CompileModule(cx, copts, srcBuf);
if (!module) {
return false;
}
outModule.set(module);
return true;
}
/**
* A synthesized module that exports `JSNative` functions.
*/
struct SynthesizedModule {
JS::Heap<JSString*> moduleName;
JS::Heap<JSObject*> moduleObject;
SynthesizedModule(JS::HandleString moduleName, JS::HandleObject moduleObject)
: moduleName(moduleName)
, moduleObject(moduleObject)
{ }
void trace(JSTracer* tracer) {
JS::TraceEdge(tracer, &moduleObject, "SynthesizedModule.moduleObject");
}
};
JS::PersistentRooted<JS::GCVector<SynthesizedModule, 0, js::SystemAllocPolicy>> MODULES;
JSObject* module_resolve_hook(JSContext *cx,
JS::HandleValue referencing_private,
JS::HandleObject module_request) {
JS::RootedString specifier(cx, JS::GetModuleRequestSpecifier(cx, module_request));
if (!specifier) {
abort(cx, "failed to get module request specifier");
}
size_t len = MODULES.length();
for (size_t i = 0; i < len; i++) {
JS::RootedObject it_module(cx, MODULES[i].get().moduleObject);
JS::RootedString it_name(cx, MODULES[i].get().moduleName);
int32_t result = 0;
if (!JS_CompareStrings(cx, it_name, specifier, &result)) {
abort(cx, "failed to compare module specifier to registered module name");
}
if (result == 0) {
return it_module.get();
}
}
JS::UniqueChars utf8 = JS_EncodeStringToUTF8(cx, specifier);
if (!utf8) {
JS_ReportErrorASCII(cx, "failed to find module import");
return nullptr;
}
JS_ReportErrorASCII(cx, "failed to find module import: `%s`", utf8.get());
return nullptr;
}
JS::RealmOptions make_realm_options() {
JS::RealmOptions options;
options
.creationOptions()
.setStreamsEnabled(true)
.setReadableByteStreamsEnabled(true)
.setBYOBStreamReadersEnabled(true)
.setReadableStreamPipeToEnabled(true)
.setWritableStreamsEnabled(true)
.setIteratorHelpersEnabled(true)
.setWeakRefsEnabled(JS::WeakRefSpecifier::EnabledWithoutCleanupSome);
return options;
}
bool init_js(JSContext *cx) {
if (!js::UseInternalJobQueues(cx)) {
return false;
}
if (!JS::InitSelfHostedCode(cx)) {
return false;
}
JS::RealmOptions options = make_realm_options();
JS::DisableIncrementalGC(cx);
JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr,
JS::FireOnNewGlobalHook, options));
if (!global) {
return false;
}
JS::EnterRealm(cx, global);
if (!JS::InitRealmStandardClasses(cx)) {
return false;
}
// JS::SetPromiseRejectionTrackerCallback(cx, rejection_tracker);
JS::SetModuleResolveHook(JS_GetRuntime(cx), module_resolve_hook);
GLOBAL.init(cx, global);
return true;
}
// static void report_warning(JSContext *cx, JSErrorReport *report) {
// JS::PrintError(stderr, report, true);
// if (!report->isWarning()) {
// ::abort();
// }
// }
/**
* Initialize the JS engine and evaluate the top-level of the given JavaScript
* source.
*
* Takes ownership of its parameters.
*/
WASM_EXPORT
void SMW_initialize_engine() {
assert(!INITIALIZED);
bool ok = true;
ok = JS_Init();
assert(ok && "JS_Init failed");
JSContext *cx = JS_NewContext(JS::DefaultHeapMaxBytes);
assert(cx != nullptr && "JS_NewContext failed");
init_js_context(cx);
// JS::SetWarningReporter(cx, report_warning);
if (!init_js(cx)) {
abort(cx, "initializing the JavaScript engine failed");
}
init_operands(cx);
MODULES.init(cx);
INITIALIZED = true;
}
class ModuleBuilder {
JS::PersistentRootedString moduleName;
JS::PersistentRooted<JS::IdValueVector> exports;
public:
/**
* Construct a new `ModuleBuilder` and take ownership of `moduleName`.
*/
ModuleBuilder(JSContext *cx, JS::HandleString moduleName)
: moduleName(cx, moduleName)
, exports(cx, cx)
{
assert(moduleName && "moduleName must not be nullptr");
}
/**
* Add an exported function to this module and take ownership of `funcName`.
*/
void add_export(const char *funcName, size_t funcNameLen, JSNative func, unsigned numArgs) {
assert(funcName && "function name must not be nullptr");
assert(funcNameLen > 0 && "function name length must be greater than zero");
assert(func && "the function must not be nullptr");
JSContext *cx = get_js_context();
JS::RootedString jsFuncName(cx, JS_NewStringCopyN(cx, funcName, funcNameLen));
if (!jsFuncName) {
abort(cx, "failed to create new JS string");
}
JS::RootedId funcNameId(cx);
if (!JS_StringToId(cx, jsFuncName, &funcNameId)) {
abort(cx, "failed to convert string to id");
}
JS::RootedFunction jsFunc(cx, JS_NewFunction(cx, func, numArgs, 0, funcName));
if (!jsFunc) {
abort(cx, "failed to create new JS function");
}
JS::RootedObject jsFuncObj(cx, JS_GetFunctionObject(jsFunc));
assert(jsFuncObj && "getting function object is infallible");
JS::RootedValue jsFuncVal(cx, JS::ObjectValue(*jsFuncObj));
if (!exports.append(JS::IdValuePair(funcNameId, jsFuncVal))) {
abort(cx, "failed to append export to exports list");
}
}
void finish() {
JSContext *cx = get_js_context();
JS::RootedObject module(cx, JS::CreateModule(cx, exports));
if (!module) {
abort(cx, "failed to create synthetic module");
}
if (!MODULES.append(SynthesizedModule(moduleName, module))) {
abort(cx, "failed to append to MODULES");
}
delete this;
}
};
WASM_EXPORT
ModuleBuilder *SMW_new_module_builder(char *module_name, size_t module_name_len) {
auto unique_module_name = UniqueChars(module_name);
JSContext *cx = get_js_context();
JS::RootedString js_module_name(cx, JS_NewStringCopyN(cx, unique_module_name.get(), module_name_len));
if (!js_module_name) {
abort(cx, "failed to allocate JS string");
}
auto b = new ModuleBuilder(cx, js_module_name);
if (!b) {
abort(cx, "failed to create new ModuleBuilder");
}
return b;
}
WASM_EXPORT
void SMW_module_builder_add_export(ModuleBuilder *builder,
char *funcName,
size_t funcNameLen,
JSNative func,
unsigned numArgs) {
assert(builder && "builder must not be nullptr");
assert(funcName && "funcName must not be nullptr");
assert(funcNameLen > 0 && "funcNameLen must be greater than 0");
assert(func && "func must not be nullptr");
auto uniqFuncName = UniqueChars(funcName);
builder->add_export(uniqFuncName.get(), funcNameLen, func, numArgs);
}
WASM_EXPORT
void SMW_finish_module_builder(ModuleBuilder *builder) {
builder->finish();
}
WASM_EXPORT
void SMW_eval_module(char *jsFileName, char *jsSource, size_t jsSourceLen) {
JSContext *cx = get_js_context();
assert(GLOBAL && "GLOBAL should be initialized");
JS::RootedObject global(cx, GLOBAL);
JSAutoRealm autoRealm(cx, global);
JS::RootedObject module(cx);
if (!compile_js_module(cx, jsFileName, jsSource, jsSourceLen, &module)) {
abort(cx, "module compilation failed");
}
if (!JS::ModuleInstantiate(cx, module)) {
abort(cx, "failed to instantiate module");
}
JS::RootedValue result(cx);
if (!JS::ModuleEvaluate(cx, module, &result)) {
abort(cx, "failed to evaluate module");
}
// TODO: if `result` is a promise because of top-level await, then don't
// return until the micro task queue is empty.
if (result.isObject()) {
JS::RootedObject resultObj(cx, &result.toObject());
if (!JS::IsPromiseObject(resultObj)) {
goto done_handling_promise;
}
switch (JS::GetPromiseState(resultObj)) {
case JS::PromiseState::Fulfilled: {
JS::RootedValue promiseResolution(cx, JS::GetPromiseResult(resultObj));
break;
}
case JS::PromiseState::Rejected: {
JS::RootedValue promiseRejection(cx, JS::GetPromiseResult(resultObj));
JS_SetPendingException(cx, promiseRejection);
abort(cx, "module evaluation failed");
}
case JS::PromiseState::Pending: {
abort(cx, "module evaluation returned a pending promise, but top-level await isn't enabled yet");
}
default:
abort(cx, "module evaluation returned a promise in an unknown promise state");
}
}
done_handling_promise:
init_user_module(cx, module);
JS::PrepareForFullGC(cx);
JS::NonIncrementalGC(cx, JS::GCOptions::Shrink, JS::GCReason::API);
free(jsFileName);
}
} // namespace smw

View File

@@ -0,0 +1,18 @@
#include <stdint.h>
#include <stdlib.h>
#include "smw/abort.h"
#include "smw/wasm.h"
namespace smw {
WASM_EXPORT
void* SMW_malloc(size_t size) {
auto p = malloc(size);
if (p == nullptr) {
abort("out of memory");
}
return p;
}
} // namespace smw

View File

@@ -0,0 +1,27 @@
WASI_SDK="$(pwd)/../wasi-sdk-12.0"
CC="$WASI_SDK/bin/clang --sysroot=$WASI_SDK/share/wasi-sysroot"
CXX="$WASI_SDK/bin/clang++ --sysroot=$WASI_SDK/share/wasi-sysroot"
AR="$WASI_SDK/bin/ar"
HOST_CC=gcc
HOST_CXX=g++
ac_add_options --enable-application=js
ac_add_options --target=wasm32-unknown-wasi
ac_add_options --without-system-zlib
ac_add_options --without-intl-api
ac_add_options --disable-jit
ac_add_options --disable-shared-js
ac_add_options --disable-shared-memory
# Comment out `--enable-optimize` and `--disable-debug` and then uncomment
# `--enable-debug` to switch to debug builds of SpiderMonkey. Also update
# `DEFINE=` in the `Makefile`.
ac_add_options --enable-optimize
ac_add_options --disable-debug
# ac_add_options --enable-debug
mk_add_options AUTOCLOBBER=1

View File

@@ -0,0 +1,72 @@
//! Assigning static locations for data segments we will emit in the glue Wasm
//! module.
use std::convert::TryFrom;
#[derive(Debug)]
pub struct DataSegments {
data: wasm_encoder::DataSection,
next_offset: u32,
memory: u32,
}
impl DataSegments {
/// Create a new collection of data segments for the given memory.
pub fn new(memory: u32) -> DataSegments {
DataSegments {
data: wasm_encoder::DataSection::new(),
next_offset: 0,
memory,
}
}
/// Add a new segment to this `DataSegments`, returning the assigned offset
/// in memory.
pub fn add<S>(&mut self, segment: S) -> u32
where
S: IntoIterator<Item = u8>,
S::IntoIter: ExactSizeIterator,
{
let segment = segment.into_iter();
let offset = self.reserve_space(u32::try_from(segment.len()).unwrap());
self.data.active(
self.memory,
&wasm_encoder::Instruction::I32Const(offset as i32),
segment,
);
offset
}
/// Reserve space in memory but don't emit any data segment to initialize
/// it.
///
/// This effectively lets you add zero-initialized data segments, reserve
/// space for return pointer areas, or define shadow stack regions.
pub fn reserve_space(&mut self, num_bytes: u32) -> u32 {
// Leave an empty byte between each data segment. This helps when
// staring at disassemblies and heap dumps.
self.next_offset += 1;
let offset = self.next_offset;
self.next_offset += num_bytes;
offset
}
/// Get the memory type required to hold these data segments.
pub fn memory_type(&self) -> wasm_encoder::MemoryType {
const WASM_PAGE_SIZE: u32 = 65_536;
wasm_encoder::MemoryType {
minimum: ((self.next_offset + WASM_PAGE_SIZE - 1) / WASM_PAGE_SIZE).into(),
maximum: None,
memory64: false,
}
}
/// Take the constructed data section.
///
/// No more data segments should be added after this is called.
pub fn take_data(&mut self) -> wasm_encoder::DataSection {
std::mem::replace(&mut self.data, wasm_encoder::DataSection::new())
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,31 @@
use std::path::Path;
mod imports {
test_helpers::codegen_spidermonkey_import!(
// TODO: should support more of the `*.wit` test suite
"strings.wit"
"simple-lists.wit"
"simple-functions.wit"
);
}
mod exports {
test_helpers::codegen_spidermonkey_export!(
// TODO: should support more of the `*.wit` test suite
"strings.wit"
"simple-lists.wit"
"simple-functions.wit"
);
}
fn verify(dir: &str, _name: &str) {
let wasm = std::fs::read(Path::new(dir).join("foo.wasm")).unwrap();
let mut validator = wasmparser::Validator::new();
validator.wasm_features(wasmparser::WasmFeatures {
bulk_memory: true,
module_linking: true,
multi_memory: true,
..wasmparser::WasmFeatures::default()
});
validator.validate_all(&wasm).expect("wasm isn't valid");
}

View File

@@ -0,0 +1,13 @@
[package]
name = "wit-bindgen-gen-wasmtime-py"
version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
edition = "2018"
[dependencies]
wit-bindgen-gen-core = { path = '../gen-core', version = '0.1.0' }
heck = "0.3"
structopt = { version = "0.3", default-features = false, optional = true }
[dev-dependencies]
test-helpers = { path = '../test-helpers', features = ['wit-bindgen-gen-wasmtime-py'] }

View File

@@ -0,0 +1,4 @@
fn main() {
println!("cargo:rerun-if-changed=build.rs");
// this build script is currently only here so OUT_DIR is set for testing.
}

View File

@@ -0,0 +1,13 @@
[mypy]
disallow_any_unimported = True
disallow_untyped_calls = True
disallow_untyped_defs = True
disallow_incomplete_defs = True
check_untyped_defs = True
disallow_untyped_decorators = True
strict_optional = True
warn_return_any = True
warn_unused_configs = True

View File

@@ -0,0 +1,368 @@
use crate::Source;
use std::collections::{BTreeMap, BTreeSet};
/// Tracks all of the import and intrinsics that a given codegen
/// requires and how to generate them when needed.
#[derive(Default)]
pub struct Dependencies {
pub needs_clamp: bool,
pub needs_store: bool,
pub needs_load: bool,
pub needs_validate_guest_char: bool,
pub needs_expected: bool,
pub needs_i32_to_f32: bool,
pub needs_f32_to_i32: bool,
pub needs_i64_to_f64: bool,
pub needs_f64_to_i64: bool,
pub needs_decode_utf8: bool,
pub needs_encode_utf8: bool,
pub needs_list_canon_lift: bool,
pub needs_list_canon_lower: bool,
pub needs_t_typevar: bool,
pub needs_resources: bool,
pub pyimports: BTreeMap<String, Option<BTreeSet<String>>>,
}
impl Dependencies {
/// Record that a Python import is required
///
/// Examples
/// ```
/// # use wit_bindgen_gen_wasmtime_py::dependencies::Dependencies;
/// # let mut deps = Dependencies::default();
/// // Import a specific item from a module
/// deps.pyimport("typing", "NamedTuple");
/// // Import an entire module
/// deps.pyimport("collections", None);
/// ```
pub fn pyimport<'a>(&mut self, module: &str, name: impl Into<Option<&'a str>>) {
let name = name.into();
let list = self
.pyimports
.entry(module.to_string())
.or_insert(match name {
Some(_) => Some(BTreeSet::new()),
None => None,
});
match name {
Some(name) => {
assert!(list.is_some());
list.as_mut().unwrap().insert(name.to_string());
}
None => assert!(list.is_none()),
}
}
/// Create a `Source` containing all of the intrinsics
/// required according to this `Dependencies` struct.
pub fn intrinsics(&mut self) -> Source {
let mut src = Source::default();
if self.needs_clamp {
src.push_str(
"
def _clamp(i: int, min: int, max: int) -> int:
if i < min or i > max:
raise OverflowError(f'must be between {min} and {max}')
return i
",
);
}
if self.needs_store {
// TODO: this uses native endianness
self.pyimport("ctypes", None);
src.push_str(
"
def _store(ty: Any, mem: wasmtime.Memory, store: wasmtime.Storelike, base: int, offset: int, val: Any) -> None:
ptr = (base & 0xffffffff) + offset
if ptr + ctypes.sizeof(ty) > mem.data_len(store):
raise IndexError('out-of-bounds store')
raw_base = mem.data_ptr(store)
c_ptr = ctypes.POINTER(ty)(
ty.from_address(ctypes.addressof(raw_base.contents) + ptr)
)
c_ptr[0] = val
",
);
}
if self.needs_load {
// TODO: this uses native endianness
self.pyimport("ctypes", None);
src.push_str(
"
def _load(ty: Any, mem: wasmtime.Memory, store: wasmtime.Storelike, base: int, offset: int) -> Any:
ptr = (base & 0xffffffff) + offset
if ptr + ctypes.sizeof(ty) > mem.data_len(store):
raise IndexError('out-of-bounds store')
raw_base = mem.data_ptr(store)
c_ptr = ctypes.POINTER(ty)(
ty.from_address(ctypes.addressof(raw_base.contents) + ptr)
)
return c_ptr[0]
",
);
}
if self.needs_validate_guest_char {
src.push_str(
"
def _validate_guest_char(i: int) -> str:
if i > 0x10ffff or (i >= 0xd800 and i <= 0xdfff):
raise TypeError('not a valid char')
return chr(i)
",
);
}
if self.needs_expected {
self.pyimport("dataclasses", "dataclass");
self.pyimport("typing", "TypeVar");
self.pyimport("typing", "Generic");
self.pyimport("typing", "Union");
self.needs_t_typevar = true;
src.push_str(
"
@dataclass
class Ok(Generic[T]):
value: T
E = TypeVar('E')
@dataclass
class Err(Generic[E]):
value: E
Expected = Union[Ok[T], Err[E]]
",
);
}
if self.needs_i32_to_f32 || self.needs_f32_to_i32 {
self.pyimport("ctypes", None);
src.push_str("_i32_to_f32_i32 = ctypes.pointer(ctypes.c_int32(0))\n");
src.push_str(
"_i32_to_f32_f32 = ctypes.cast(_i32_to_f32_i32, ctypes.POINTER(ctypes.c_float))\n",
);
if self.needs_i32_to_f32 {
src.push_str(
"
def _i32_to_f32(i: int) -> float:
_i32_to_f32_i32[0] = i # type: ignore
return _i32_to_f32_f32[0] # type: ignore
",
);
}
if self.needs_f32_to_i32 {
src.push_str(
"
def _f32_to_i32(i: float) -> int:
_i32_to_f32_f32[0] = i # type: ignore
return _i32_to_f32_i32[0] # type: ignore
",
);
}
}
if self.needs_i64_to_f64 || self.needs_f64_to_i64 {
self.pyimport("ctypes", None);
src.push_str("_i64_to_f64_i64 = ctypes.pointer(ctypes.c_int64(0))\n");
src.push_str(
"_i64_to_f64_f64 = ctypes.cast(_i64_to_f64_i64, ctypes.POINTER(ctypes.c_double))\n",
);
if self.needs_i64_to_f64 {
src.push_str(
"
def _i64_to_f64(i: int) -> float:
_i64_to_f64_i64[0] = i # type: ignore
return _i64_to_f64_f64[0] # type: ignore
",
);
}
if self.needs_f64_to_i64 {
src.push_str(
"
def _f64_to_i64(i: float) -> int:
_i64_to_f64_f64[0] = i # type: ignore
return _i64_to_f64_i64[0] # type: ignore
",
);
}
}
if self.needs_decode_utf8 {
self.pyimport("ctypes", None);
src.push_str(
"
def _decode_utf8(mem: wasmtime.Memory, store: wasmtime.Storelike, ptr: int, len: int) -> str:
ptr = ptr & 0xffffffff
len = len & 0xffffffff
if ptr + len > mem.data_len(store):
raise IndexError('string out of bounds')
base = mem.data_ptr(store)
base = ctypes.POINTER(ctypes.c_ubyte)(
ctypes.c_ubyte.from_address(ctypes.addressof(base.contents) + ptr)
)
return ctypes.string_at(base, len).decode('utf-8')
",
);
}
if self.needs_encode_utf8 {
self.pyimport("ctypes", None);
self.pyimport("typing", "Tuple");
src.push_str(
"
def _encode_utf8(val: str, realloc: wasmtime.Func, mem: wasmtime.Memory, store: wasmtime.Storelike) -> Tuple[int, int]:
bytes = val.encode('utf8')
ptr = realloc(store, 0, 0, 1, len(bytes))
assert(isinstance(ptr, int))
ptr = ptr & 0xffffffff
if ptr + len(bytes) > mem.data_len(store):
raise IndexError('string out of bounds')
base = mem.data_ptr(store)
base = ctypes.POINTER(ctypes.c_ubyte)(
ctypes.c_ubyte.from_address(ctypes.addressof(base.contents) + ptr)
)
ctypes.memmove(base, bytes, len(bytes))
return (ptr, len(bytes))
",
);
}
if self.needs_list_canon_lift {
self.pyimport("ctypes", None);
self.pyimport("typing", "List");
// TODO: this is doing a native-endian read, not a little-endian
// read
src.push_str(
"
def _list_canon_lift(ptr: int, len: int, size: int, ty: Any, mem: wasmtime.Memory ,store: wasmtime.Storelike) -> Any:
ptr = ptr & 0xffffffff
len = len & 0xffffffff
if ptr + len * size > mem.data_len(store):
raise IndexError('list out of bounds')
raw_base = mem.data_ptr(store)
base = ctypes.POINTER(ty)(
ty.from_address(ctypes.addressof(raw_base.contents) + ptr)
)
if ty == ctypes.c_uint8:
return ctypes.string_at(base, len)
return base[:len]
",
);
}
if self.needs_list_canon_lower {
self.pyimport("ctypes", None);
self.pyimport("typing", "List");
self.pyimport("typing", "Tuple");
// TODO: is there a faster way to memcpy other than iterating over
// the input list?
// TODO: this is doing a native-endian write, not a little-endian
// write
src.push_str(
"
def _list_canon_lower(list: Any, ty: Any, size: int, align: int, realloc: wasmtime.Func, mem: wasmtime.Memory, store: wasmtime.Storelike) -> Tuple[int, int]:
total_size = size * len(list)
ptr = realloc(store, 0, 0, align, total_size)
assert(isinstance(ptr, int))
ptr = ptr & 0xffffffff
if ptr + total_size > mem.data_len(store):
raise IndexError('list realloc return of bounds')
raw_base = mem.data_ptr(store)
base = ctypes.POINTER(ty)(
ty.from_address(ctypes.addressof(raw_base.contents) + ptr)
)
for i, val in enumerate(list):
base[i] = val
return (ptr, len(list))
",
);
}
if self.needs_resources {
self.pyimport("typing", "TypeVar");
self.pyimport("typing", "Generic");
self.pyimport("typing", "List");
self.pyimport("typing", "Optional");
self.pyimport("dataclasses", "dataclass");
self.needs_t_typevar = true;
src.push_str(
"
@dataclass
class SlabEntry(Generic[T]):
next: int
val: Optional[T]
class Slab(Generic[T]):
head: int
list: List[SlabEntry[T]]
def __init__(self) -> None:
self.list = []
self.head = 0
def insert(self, val: T) -> int:
if self.head >= len(self.list):
self.list.append(SlabEntry(next = len(self.list) + 1, val = None))
ret = self.head
slot = self.list[ret]
self.head = slot.next
slot.next = -1
slot.val = val
return ret
def get(self, idx: int) -> T:
if idx >= len(self.list):
raise IndexError('handle index not valid')
slot = self.list[idx]
if slot.next == -1:
assert(slot.val is not None)
return slot.val
raise IndexError('handle index not valid')
def remove(self, idx: int) -> T:
ret = self.get(idx)
slot = self.list[idx]
slot.val = None
slot.next = self.head
self.head = idx
return ret
",
);
}
src
}
}
#[cfg(test)]
mod test {
use std::collections::{BTreeMap, BTreeSet};
use super::Dependencies;
#[test]
fn test_pyimport_only_contents() {
let mut deps = Dependencies::default();
deps.pyimport("typing", None);
deps.pyimport("typing", None);
assert_eq!(deps.pyimports, BTreeMap::from([("typing".into(), None)]));
}
#[test]
fn test_pyimport_only_module() {
let mut deps = Dependencies::default();
deps.pyimport("typing", "Union");
deps.pyimport("typing", "List");
deps.pyimport("typing", "NamedTuple");
assert_eq!(
deps.pyimports,
BTreeMap::from([(
"typing".into(),
Some(BTreeSet::from([
"Union".into(),
"List".into(),
"NamedTuple".into()
]))
)])
);
}
#[test]
#[should_panic]
fn test_pyimport_conflicting() {
let mut deps = Dependencies::default();
deps.pyimport("typing", "NamedTuple");
deps.pyimport("typing", None);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,501 @@
use heck::*;
use wit_bindgen_gen_core::wit_parser::*;
use crate::dependencies::Dependencies;
/// A [Source] represents some unit of Python code
/// and keeps track of its indent.
#[derive(Default)]
pub struct Source {
s: String,
indent: usize,
}
impl Source {
/// Appends a string slice to this [Source].
///
/// Strings without newlines, they are simply appended.
/// Strings with newlines are appended and also new lines
/// are indented based on the current indent level.
pub fn push_str(&mut self, src: &str) {
let lines = src.lines().collect::<Vec<_>>();
let mut trim = None;
for (i, line) in lines.iter().enumerate() {
self.s.push_str(if lines.len() == 1 {
line
} else {
let trim = match trim {
Some(n) => n,
None => {
let val = line.len() - line.trim_start().len();
if !line.is_empty() {
trim = Some(val);
}
val
}
};
line.get(trim..).unwrap_or("")
});
if i != lines.len() - 1 || src.ends_with("\n") {
self.newline();
}
}
}
/// Prints the documentation as comments
/// e.g.
/// > \# Line one of docs node
/// >
/// > \# Line two of docs node
pub fn comment(&mut self, docs: &Docs) {
let docs = match &docs.contents {
Some(docs) => docs,
None => return,
};
for line in docs.lines() {
self.push_str(&format!("# {}\n", line));
}
}
/// Prints the documentation as comments
/// e.g.
/// > """
/// >
/// > Line one of docs node
/// >
/// > Line two of docs node
/// >
/// > """
pub fn docstring(&mut self, docs: &Docs) {
let docs = match &docs.contents {
Some(docs) => docs,
None => return,
};
let triple_quote = r#"""""#;
self.push_str(triple_quote);
self.newline();
for line in docs.lines() {
self.push_str(line);
self.newline();
}
self.push_str(triple_quote);
self.newline();
}
/// Indent the source one level.
pub fn indent(&mut self) {
self.indent += 4;
self.s.push_str(" ");
}
/// Unindent, or in Python terms "dedent",
/// the source one level.
pub fn dedent(&mut self) {
self.indent -= 4;
assert!(self.s.ends_with(" "));
self.s.pop();
self.s.pop();
self.s.pop();
self.s.pop();
}
/// Go to the next line and apply any indent.
pub fn newline(&mut self) {
self.s.push_str("\n");
for _ in 0..self.indent {
self.s.push_str(" ");
}
}
}
impl std::ops::Deref for Source {
type Target = str;
fn deref(&self) -> &str {
&self.s
}
}
impl From<Source> for String {
fn from(s: Source) -> String {
s.s
}
}
/// [SourceBuilder] combines together a [Source]
/// with other contextual information and state.
///
/// This allows you to generate code for the Source using
/// high-level tools that take care of updating dependencies
/// and retrieving interface details.
///
/// You can create a [SourceBuilder] easily using a [Source]
/// ```
/// # use wit_bindgen_gen_wasmtime_py::dependencies::Dependencies;
/// # use wit_bindgen_gen_core::wit_parser::{Interface, Type};
/// # use wit_bindgen_gen_wasmtime_py::source::Source;
/// # let mut deps = Dependencies::default();
/// # let mut interface = Interface::default();
/// # let iface = &interface;
/// let mut source = Source::default();
/// let mut builder = source.builder(&mut deps, iface);
/// builder.print_ty(&Type::Bool, false);
/// ```
pub struct SourceBuilder<'s, 'd, 'i> {
source: &'s mut Source,
pub deps: &'d mut Dependencies,
iface: &'i Interface,
}
impl<'s, 'd, 'i> Source {
/// Create a [SourceBuilder] for the current source.
pub fn builder(
&'s mut self,
deps: &'d mut Dependencies,
iface: &'i Interface,
) -> SourceBuilder<'s, 'd, 'i> {
SourceBuilder {
source: self,
deps,
iface,
}
}
}
impl<'s, 'd, 'i> SourceBuilder<'s, 'd, 'i> {
/// See [Dependencies::pyimport].
pub fn pyimport<'a>(&mut self, module: &str, name: impl Into<Option<&'a str>>) {
self.deps.pyimport(module, name)
}
/// Appends a type's Python representation to this `Source`.
/// Records any required intrinsics and imports in the `deps`.
/// Uses Python forward reference syntax (e.g. 'Foo')
/// on the root type only if `forward_ref` is true.
pub fn print_ty(&mut self, ty: &Type, forward_ref: bool) {
match ty {
Type::Unit => self.push_str("None"),
Type::Bool => self.push_str("bool"),
Type::U8
| Type::S8
| Type::U16
| Type::S16
| Type::U32
| Type::S32
| Type::U64
| Type::S64 => self.push_str("int"),
Type::Float32 | Type::Float64 => self.push_str("float"),
Type::Char => self.push_str("str"),
Type::String => self.push_str("str"),
Type::Handle(id) => {
if forward_ref {
self.push_str("'");
}
let handle_name = &self.iface.resources[*id].name.to_camel_case();
self.source.push_str(handle_name);
if forward_ref {
self.push_str("'");
}
}
Type::Id(id) => {
let ty = &self.iface.types[*id];
if let Some(name) = &ty.name {
self.push_str(&name.to_camel_case());
return;
}
match &ty.kind {
TypeDefKind::Type(t) => self.print_ty(t, forward_ref),
TypeDefKind::Tuple(t) => self.print_tuple(t),
TypeDefKind::Record(_)
| TypeDefKind::Flags(_)
| TypeDefKind::Enum(_)
| TypeDefKind::Variant(_)
| TypeDefKind::Union(_) => {
unreachable!()
}
TypeDefKind::Option(t) => {
self.deps.pyimport("typing", "Optional");
self.push_str("Optional[");
self.print_ty(t, true);
self.push_str("]");
}
TypeDefKind::Expected(e) => {
self.deps.needs_expected = true;
self.push_str("Expected[");
self.print_ty(&e.ok, true);
self.push_str(", ");
self.print_ty(&e.err, true);
self.push_str("]");
}
TypeDefKind::List(t) => self.print_list(t),
TypeDefKind::Future(t) => {
self.push_str("Future[");
self.print_ty(t, true);
self.push_str("]");
}
TypeDefKind::Stream(s) => {
self.push_str("Stream[");
self.print_ty(&s.element, true);
self.push_str(", ");
self.print_ty(&s.end, true);
self.push_str("]");
}
}
}
}
}
/// Appends a tuple type's Python representation to this `Source`.
/// Records any required intrinsics and imports in the `deps`.
/// Uses Python forward reference syntax (e.g. 'Foo') for named type parameters.
pub fn print_tuple(&mut self, tuple: &Tuple) {
if tuple.types.is_empty() {
return self.push_str("None");
}
self.deps.pyimport("typing", "Tuple");
self.push_str("Tuple[");
for (i, t) in tuple.types.iter().enumerate() {
if i > 0 {
self.push_str(", ");
}
self.print_ty(t, true);
}
self.push_str("]");
}
/// Appends a Python type representing a sequence of the `element` type to this `Source`.
/// If the element type is `Type::U8`, the result type is `bytes` otherwise it is a `List[T]`
/// Records any required intrinsics and imports in the `deps`.
/// Uses Python forward reference syntax (e.g. 'Foo') for named type parameters.
pub fn print_list(&mut self, element: &Type) {
match element {
Type::U8 => self.push_str("bytes"),
t => {
self.deps.pyimport("typing", "List");
self.push_str("List[");
self.print_ty(t, true);
self.push_str("]");
}
}
}
/// Print variable declaration.
/// Brings name into scope and binds type to it.
pub fn print_var_declaration<'a>(&mut self, name: &'a str, ty: &Type) {
self.push_str(name);
self.push_str(": ");
self.print_ty(ty, true);
self.push_str("\n");
}
pub fn print_sig(&mut self, func: &Function, in_import: bool) -> Vec<String> {
if !in_import {
if let FunctionKind::Static { .. } = func.kind {
self.push_str("@classmethod\n");
}
}
self.source.push_str("def ");
match &func.kind {
FunctionKind::Method { .. } => self.source.push_str(&func.item_name().to_snake_case()),
FunctionKind::Static { .. } if !in_import => {
self.source.push_str(&func.item_name().to_snake_case())
}
_ => self.source.push_str(&func.name.to_snake_case()),
}
if in_import {
self.source.push_str("(self");
} else if let FunctionKind::Static { .. } = func.kind {
self.source.push_str("(cls, caller: wasmtime.Store, obj: '");
self.source.push_str(&self.iface.name.to_camel_case());
self.source.push_str("'");
} else {
self.source.push_str("(self, caller: wasmtime.Store");
}
let mut params = Vec::new();
for (i, (param, ty)) in func.params.iter().enumerate() {
if i == 0 {
if let FunctionKind::Method { .. } = func.kind {
params.push("self".to_string());
continue;
}
}
self.source.push_str(", ");
self.source.push_str(&param.to_snake_case());
params.push(param.to_snake_case());
self.source.push_str(": ");
self.print_ty(ty, true);
}
self.source.push_str(") -> ");
self.print_ty(&func.result, true);
params
}
/// Print a wrapped union definition.
/// e.g.
/// ```py
/// @dataclass
/// class Foo0:
/// value: int
///
/// @dataclass
/// class Foo1:
/// value: int
///
/// Foo = Union[Foo0, Foo1]
/// ```
pub fn print_union_wrapped(&mut self, name: &str, union: &Union, docs: &Docs) {
self.deps.pyimport("dataclasses", "dataclass");
let mut cases = Vec::new();
let name = name.to_camel_case();
for (i, case) in union.cases.iter().enumerate() {
self.source.push_str("@dataclass\n");
let name = format!("{name}{i}");
self.source.push_str(&format!("class {name}:\n"));
self.source.indent();
self.source.docstring(&case.docs);
self.source.push_str("value: ");
self.print_ty(&case.ty, true);
self.source.newline();
self.source.dedent();
self.source.newline();
cases.push(name);
}
self.deps.pyimport("typing", "Union");
self.source.comment(docs);
self.source
.push_str(&format!("{name} = Union[{}]\n", cases.join(", ")));
self.source.newline();
}
pub fn print_union_raw(&mut self, name: &str, union: &Union, docs: &Docs) {
self.deps.pyimport("typing", "Union");
self.source.comment(docs);
for case in union.cases.iter() {
self.source.comment(&case.docs);
}
self.source.push_str(&name.to_camel_case());
self.source.push_str(" = Union[");
let mut first = true;
for case in union.cases.iter() {
if !first {
self.source.push_str(",");
}
self.print_ty(&case.ty, true);
first = false;
}
self.source.push_str("]\n\n");
}
}
impl<'s, 'd, 'i> std::ops::Deref for SourceBuilder<'s, 'd, 'i> {
type Target = Source;
fn deref(&self) -> &Source {
&self.source
}
}
impl<'s, 'd, 'i> std::ops::DerefMut for SourceBuilder<'s, 'd, 'i> {
fn deref_mut(&mut self) -> &mut Source {
&mut self.source
}
}
#[cfg(test)]
mod tests {
use std::collections::{BTreeMap, BTreeSet};
use super::*;
#[test]
fn simple_append() {
let mut s = Source::default();
s.push_str("x");
assert_eq!(s.s, "x");
s.push_str("y");
assert_eq!(s.s, "xy");
s.push_str("z ");
assert_eq!(s.s, "xyz ");
s.push_str(" a ");
assert_eq!(s.s, "xyz a ");
s.push_str("\na");
assert_eq!(s.s, "xyz a \na");
}
#[test]
fn trim_ws() {
let mut s = Source::default();
s.push_str("def foo():\n return 1\n");
assert_eq!(s.s, "def foo():\n return 1\n");
}
#[test]
fn print_ty_forward_ref() {
let mut deps = Dependencies::default();
let mut iface = Interface::default();
// Set up a Resource type to refer to
let resource_id = iface.resources.alloc(Resource {
docs: Docs::default(),
name: "foo".into(),
supertype: None,
foreign_module: None,
});
iface.resource_lookup.insert("foo".into(), resource_id);
let handle_ty = Type::Handle(resource_id);
// ForwardRef usage can be controlled by an argument to print_ty
let mut s1 = Source::default();
let mut builder = s1.builder(&mut deps, &iface);
builder.print_ty(&handle_ty, true);
drop(builder);
assert_eq!(s1.s, "'Foo'");
let mut s2 = Source::default();
let mut builder = s2.builder(&mut deps, &iface);
builder.print_ty(&handle_ty, false);
drop(builder);
assert_eq!(s2.s, "Foo");
// ForwardRef is used for any types within other types
// Even if the outer type is itself not allowed to be one
let option_id = iface.types.alloc(TypeDef {
docs: Docs::default(),
kind: TypeDefKind::Option(handle_ty),
name: None,
foreign_module: None,
});
let option_ty = Type::Id(option_id);
let mut s3 = Source::default();
let mut builder = s3.builder(&mut deps, &iface);
builder.print_ty(&option_ty, false);
drop(builder);
assert_eq!(s3.s, "Optional['Foo']");
}
#[test]
fn print_list_bytes() {
// If the element type is u8, it is interpreted as `bytes`
let mut deps = Dependencies::default();
let iface = Interface::default();
let mut source = Source::default();
let mut builder = source.builder(&mut deps, &iface);
builder.print_list(&Type::U8);
drop(builder);
assert_eq!(source.s, "bytes");
assert_eq!(deps.pyimports, BTreeMap::default());
}
#[test]
fn print_list_non_bytes() {
// If the element type is u8, it is interpreted as `bytes`
let mut deps = Dependencies::default();
let iface = Interface::default();
let mut source = Source::default();
let mut builder = source.builder(&mut deps, &iface);
builder.print_list(&Type::Float32);
drop(builder);
assert_eq!(source.s, "List[float]");
assert_eq!(
deps.pyimports,
BTreeMap::from([("typing".into(), Some(BTreeSet::from(["List".into()])))])
);
}
}

View File

@@ -0,0 +1,51 @@
use std::path::Path;
use std::process::Command;
mod exports {
test_helpers::codegen_py_export!(
"*.wit"
// TODO: implement async support
"!async-functions.wit"
);
}
mod imports {
test_helpers::codegen_py_import!(
"*.wit"
// TODO: implement async support
"!async-functions.wit"
// This uses buffers, which we don't support in imports just yet
// TODO: should support this
"!wasi-next.wit"
"!host.wit"
);
}
fn verify(dir: &str, _name: &str) {
let output = Command::new("mypy")
.arg(Path::new(dir).join("bindings.py"))
.arg("--config-file")
.arg("mypy.ini")
.output()
.expect("failed to run `mypy`; do you have it installed?");
if output.status.success() {
return;
}
panic!(
"mypy failed
status: {status}
stdout ---
{stdout}
stderr ---
{stderr}",
status = output.status,
stdout = String::from_utf8_lossy(&output.stdout).replace("\n", "\n\t"),
stderr = String::from_utf8_lossy(&output.stderr).replace("\n", "\n\t"),
);
}

View File

@@ -0,0 +1,75 @@
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use wit_bindgen_gen_core::Generator;
test_helpers::runtime_tests!("py");
fn execute(name: &str, wasm: &Path, py: &Path, imports: &Path, exports: &Path) {
let out_dir = PathBuf::from(env!("OUT_DIR"));
let dir = out_dir.join(name);
drop(fs::remove_dir_all(&dir));
fs::create_dir_all(&dir).unwrap();
fs::create_dir_all(&dir.join("imports")).unwrap();
fs::create_dir_all(&dir.join("exports")).unwrap();
println!("OUT_DIR = {:?}", dir);
println!("Generating bindings...");
// We call `generate_all` with exports from the imports.wit file, and
// imports from the exports.wit wit file. It's reversed because we're
// implementing the host side of these APIs.
let iface = wit_bindgen_gen_core::wit_parser::Interface::parse_file(imports).unwrap();
let mut files = Default::default();
wit_bindgen_gen_wasmtime_py::Opts::default()
.build()
.generate_all(&[], &[iface], &mut files);
for (file, contents) in files.iter() {
fs::write(dir.join("imports").join(file), contents).unwrap();
}
fs::write(dir.join("imports").join("__init__.py"), "").unwrap();
let iface = wit_bindgen_gen_core::wit_parser::Interface::parse_file(exports).unwrap();
let mut files = Default::default();
wit_bindgen_gen_wasmtime_py::Opts::default()
.build()
.generate_all(&[iface], &[], &mut files);
for (file, contents) in files.iter() {
fs::write(dir.join("exports").join(file), contents).unwrap();
}
fs::write(dir.join("exports").join("__init__.py"), "").unwrap();
println!("Running mypy...");
exec(
Command::new("mypy")
.env("MYPYPATH", &dir)
.arg(py)
.arg("--cache-dir")
.arg(out_dir.join("mypycache").join(name)),
);
exec(
Command::new("python3")
.env("PYTHONPATH", &dir)
.arg(py)
.arg(wasm),
);
}
fn exec(cmd: &mut Command) {
println!("{:?}", cmd);
let output = cmd.output().unwrap();
if output.status.success() {
return;
}
println!("status: {}", output.status);
println!(
"stdout ---\n {}",
String::from_utf8_lossy(&output.stdout).replace("\n", "\n ")
);
println!(
"stderr ---\n {}",
String::from_utf8_lossy(&output.stderr).replace("\n", "\n ")
);
panic!("no success");
}

View File

@@ -0,0 +1,22 @@
[package]
name = "wit-bindgen-gen-wasmtime"
version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
edition = "2018"
[lib]
test = false
doctest = false
[dependencies]
wit-bindgen-gen-core = { path = '../gen-core', version = '0.1.0' }
wit-bindgen-gen-rust = { path = '../gen-rust', version = '0.1.0' }
heck = "0.3"
structopt = { version = "0.3", default-features = false, optional = true }
[dev-dependencies]
anyhow = "1.0"
test-helpers = { path = '../test-helpers', features = ['wit-bindgen-gen-wasmtime'] }
wasmtime = "0.38.0"
wasmtime-wasi = "0.38.0"
wit-bindgen-wasmtime = { path = '../wasmtime', features = ['tracing', 'async'] }

View File

@@ -0,0 +1,4 @@
fn main() {
println!("cargo:rerun-if-changed=build.rs");
// this build script is currently only here so OUT_DIR is set for testing.
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,108 @@
#![allow(dead_code, type_alias_bounds)]
fn main() {
println!("compiled successfully!")
}
#[rustfmt::skip]
mod exports {
test_helpers::codegen_wasmtime_export!(
"*.wit"
// TODO: implement async support
"!async-functions.wit"
// If you want to exclude a specific test you can include it here with
// gitignore glob syntax:
//
// "!wasm.wit"
// "!host.wit"
//
//
// Similarly you can also just remove the `*.wit` glob and list tests
// individually if you're debugging.
);
}
mod imports {
test_helpers::codegen_wasmtime_import!(
"*.wit"
// TODO: implement async support
"!async-functions.wit"
// TODO: these use push/pull buffer which isn't implemented in the test
// generator just yet
"!wasi-next.wit"
"!host.wit"
);
}
mod async_tests {
mod not_async {
wit_bindgen_wasmtime::export!({
src["x"]: "foo: func()",
async: ["bar"],
});
struct Me;
impl x::X for Me {
fn foo(&mut self) {}
}
}
mod one_async {
wit_bindgen_wasmtime::export!({
src["x"]: "
foo: func() -> list<u8>
bar: func()
",
async: ["bar"],
});
struct Me;
#[wit_bindgen_wasmtime::async_trait]
impl x::X for Me {
fn foo(&mut self) -> Vec<u8> {
Vec::new()
}
async fn bar(&mut self) {}
}
}
mod one_async_export {
wit_bindgen_wasmtime::import!({
src["x"]: "
foo: func(x: list<string>)
bar: func()
",
async: ["bar"],
});
}
mod resource_with_none_async {
wit_bindgen_wasmtime::export!({
src["x"]: "
resource y {
z: func() -> string
}
",
async: [],
});
}
}
mod custom_errors {
wit_bindgen_wasmtime::export!({
src["x"]: "
foo: func()
bar: func() -> expected<unit, u32>
enum errno {
bad1,
bad2,
}
baz: func() -> expected<u32, errno>
",
custom_error: true,
});
}

View File

@@ -0,0 +1,110 @@
use anyhow::{Context as _, Result};
use wasmtime::{Config, Engine, Instance, Linker, Module, Store};
test_helpers::runtime_tests_wasmtime!();
fn default_config() -> Result<Config> {
// Create an engine with caching enabled to assist with iteration in this
// project.
let mut config = Config::new();
config.cache_config_load_default()?;
config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable);
Ok(config)
}
fn default_wasi() -> wasmtime_wasi::WasiCtx {
wasmtime_wasi::sync::WasiCtxBuilder::new()
.inherit_stdio()
.build()
}
struct Context<I, E> {
wasi: wasmtime_wasi::WasiCtx,
imports: I,
exports: E,
}
fn instantiate<I: Default, E: Default, T>(
wasm: &str,
add_imports: impl FnOnce(&mut Linker<Context<I, E>>) -> Result<()>,
mk_exports: impl FnOnce(
&mut Store<Context<I, E>>,
&Module,
&mut Linker<Context<I, E>>,
) -> Result<(T, Instance)>,
) -> Result<(T, Store<Context<I, E>>)> {
let engine = Engine::new(&default_config()?)?;
let module = Module::from_file(&engine, wasm)?;
let mut linker = Linker::new(&engine);
add_imports(&mut linker)?;
wasmtime_wasi::add_to_linker(&mut linker, |cx| &mut cx.wasi)?;
let mut store = Store::new(
&engine,
Context {
wasi: default_wasi(),
imports: I::default(),
exports: E::default(),
},
);
let (exports, _instance) = mk_exports(&mut store, &module, &mut linker)?;
Ok((exports, store))
}
// TODO: This function needs to be updated to use the component model once it's ready. See
// https://github.com/bytecodealliance/wit-bindgen/issues/259 for details.
//
// Also, rename the ignore_host.rs files under the tests/runtime/smw_{functions|lists|strings} to host.rs and
// remove the leading underscore from this function's name to re-enable the Spidermonkey tests.
fn _instantiate_smw<I: Default, E: Default, T>(
wasm: &str,
add_imports: impl FnOnce(&mut Linker<Context<I, E>>) -> Result<()>,
mk_exports: impl FnOnce(
&mut Store<Context<I, E>>,
&Module,
&mut Linker<Context<I, E>>,
) -> Result<(T, Instance)>,
) -> Result<(T, Store<Context<I, E>>)> {
let mut config = default_config()?;
config.wasm_multi_memory(true);
let engine = Engine::new(&config)?;
println!("reading wasms...");
let wasm = std::fs::read(wasm).context(format!("failed to read {}", wasm))?;
let smw = std::fs::read("../gen-spidermonkey/spidermonkey-wasm/spidermonkey.wasm")
.context("failed to read `spidermonkey.wasm`")?;
println!("compiling input wasm...");
let module = Module::new(&engine, &wasm)?;
println!("compiling spidermonkey.wasm...");
let smw = Module::new(&engine, &smw)?;
let mut linker = Linker::new(&engine);
add_imports(&mut linker)?;
wasmtime_wasi::add_to_linker(&mut linker, |cx| &mut cx.wasi)?;
let mut store = Store::new(
&engine,
Context {
wasi: default_wasi(),
imports: I::default(),
exports: E::default(),
},
);
println!("instantiating spidermonkey.wasm...");
let _smw_instance = linker
.instantiate(&mut store, &smw)
.context("failed to instantiate `spidermonkey.wasm`")?;
// TODO: replace this with a component model equivalent:
// linker.define_name("spidermonkey", smw_instance)?;
println!("instantiating input wasm...");
let (exports, instance) = mk_exports(&mut store, &module, &mut linker)?;
println!("running wizer.initialize");
let init = instance.get_typed_func::<(), (), _>(&mut store, "wizer.initialize")?;
init.call(&mut store, ())
.context("failed to call wizer.initialize")?;
Ok((exports, store))
}

View File

@@ -0,0 +1,2 @@
target
Cargo.lock

View File

@@ -0,0 +1,22 @@
[package]
name = "wit-parser"
version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
edition = "2018"
[dependencies]
id-arena = "2"
anyhow = "1.0"
pulldown-cmark = { version = "0.8", default-features = false }
wast = { version = "33", default-features = false, optional = true }
unicode-xid = "0.2.2"
unicode-normalization = "0.1.19"
[dev-dependencies]
rayon = "1"
serde_json = "1"
serde = { version = "1", features = ['derive'] }
[[test]]
name = "all"
harness = false

File diff suppressed because it is too large Load Diff

View 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)
}
}

View File

@@ -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");
}

View File

@@ -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(())
}
}

View 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()),
}
}

View File

@@ -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)
}

View File

@@ -0,0 +1,344 @@
//! You can run this test suite with:
//!
//! cargo test --test all
//!
//! An argument can be passed as well to filter, based on filename, which test
//! to run
//!
//! cargo test --test all foo.wit
use anyhow::{bail, Context, Result};
use rayon::prelude::*;
use serde::Serialize;
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::str;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use wit_parser::*;
fn main() {
let tests = find_tests();
let filter = std::env::args().nth(1);
let tests = tests
.par_iter()
.filter_map(|test| {
if let Some(filter) = &filter {
if let Some(s) = test.to_str() {
if !s.contains(filter) {
return None;
}
}
}
let contents = fs::read(test).unwrap();
Some((test, contents))
})
.collect::<Vec<_>>();
println!("running {} test files\n", tests.len());
let ntests = AtomicUsize::new(0);
let errors = tests
.par_iter()
.filter_map(|(test, contents)| {
Runner { ntests: &ntests }
.run(test, contents)
.context(format!("test {:?} failed", test))
.err()
})
.collect::<Vec<_>>();
if !errors.is_empty() {
for msg in errors.iter() {
eprintln!("{:?}", msg);
}
panic!("{} tests failed", errors.len())
}
println!(
"test result: ok. {} directives passed\n",
ntests.load(SeqCst)
);
}
/// Recursively finds all tests in a whitelisted set of directories which we
/// then load up and test in parallel.
fn find_tests() -> Vec<PathBuf> {
let mut tests = Vec::new();
find_tests("tests/ui".as_ref(), &mut tests);
tests.sort();
return tests;
fn find_tests(path: &Path, tests: &mut Vec<PathBuf>) {
for f in path.read_dir().unwrap() {
let f = f.unwrap();
if f.file_type().unwrap().is_dir() {
find_tests(&f.path(), tests);
continue;
}
match f.path().extension().and_then(|s| s.to_str()) {
Some("md") => {}
Some("wit") => {}
_ => continue,
}
tests.push(f.path());
}
}
}
struct Runner<'a> {
ntests: &'a AtomicUsize,
}
impl Runner<'_> {
fn run(&mut self, test: &Path, contents: &[u8]) -> Result<()> {
let contents = str::from_utf8(contents)?;
let result = Interface::parse_file(test);
let result = if contents.contains("// parse-fail") {
match result {
Ok(_) => bail!("expected test to not parse but it did"),
Err(mut e) => {
if let Some(err) = e.downcast_mut::<io::Error>() {
*err = io::Error::new(
io::ErrorKind::Other,
"some generic platform-agnostic error message",
);
}
normalize(test, &format!("{:?}", e))
}
}
} else {
let instance = result?;
to_json(&instance)
};
// "foo.wit" => "foo.wit.result"
// "foo.wit.md" => "foo.wit.md.result"
let result_file = if test.extension() == Some(OsStr::new("md"))
&& test
.file_stem()
.and_then(|path| Path::new(path).extension())
== Some(OsStr::new("wit"))
{
test.with_extension("md.result")
} else {
test.with_extension("wit.result")
};
if env::var_os("BLESS").is_some() {
fs::write(&result_file, result)?;
} else {
let expected = fs::read_to_string(&result_file).context(format!(
"failed to read test expectation file {:?}\nthis can be fixed with BLESS=1",
result_file
))?;
let expected = normalize(test, &expected);
if expected != result {
bail!(
"failed test: expected `{:?}` but found `{:?}`",
expected,
result
);
}
}
self.bump_ntests();
return Ok(());
fn normalize(test: &Path, s: &str) -> String {
s.replace(
&test.display().to_string(),
&test.display().to_string().replace("\\", "/"),
)
.replace("\\parse-fail\\", "/parse-fail/")
.replace("\r\n", "\n")
}
}
fn bump_ntests(&self) {
self.ntests.fetch_add(1, SeqCst);
}
}
fn to_json(i: &Interface) -> String {
#[derive(Serialize)]
struct Interface {
#[serde(skip_serializing_if = "Vec::is_empty")]
resources: Vec<Resource>,
#[serde(skip_serializing_if = "Vec::is_empty")]
types: Vec<TypeDef>,
#[serde(skip_serializing_if = "Vec::is_empty")]
functions: Vec<Function>,
#[serde(skip_serializing_if = "Vec::is_empty")]
globals: Vec<Global>,
}
#[derive(Serialize)]
struct Resource {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
supertype: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
foreign_module: Option<String>,
}
#[derive(Serialize)]
struct TypeDef {
idx: usize,
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
#[serde(flatten)]
ty: Type,
#[serde(skip_serializing_if = "Option::is_none")]
foreign_module: Option<String>,
}
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
enum Type {
Primitive(String),
Record { fields: Vec<(String, String)> },
Flags { flags: Vec<String> },
Enum { cases: Vec<String> },
Variant { cases: Vec<(String, String)> },
Tuple { types: Vec<String> },
Option(String),
Expected { ok: String, err: String },
Future(String),
Stream { element: String, end: String },
List(String),
Union { cases: Vec<String> },
}
#[derive(Serialize)]
struct Function {
name: String,
#[serde(rename = "async", skip_serializing_if = "Option::is_none")]
is_async: Option<bool>,
params: Vec<String>,
result: String,
}
#[derive(Serialize)]
struct Global {
name: String,
ty: String,
}
let resources = i
.resources
.iter()
.map(|(_, r)| Resource {
name: r.name.clone(),
supertype: r.supertype.as_ref().map(|supertype| supertype.clone()),
foreign_module: r.foreign_module.clone(),
})
.collect::<Vec<_>>();
let types = i
.types
.iter()
.map(|(i, r)| TypeDef {
idx: i.index(),
name: r.name.clone(),
ty: translate_typedef(r),
foreign_module: r.foreign_module.clone(),
})
.collect::<Vec<_>>();
let functions = i
.functions
.iter()
.map(|f| Function {
name: f.name.clone(),
is_async: if f.is_async { Some(f.is_async) } else { None },
params: f.params.iter().map(|(_, ty)| translate_type(ty)).collect(),
result: translate_type(&f.result),
})
.collect::<Vec<_>>();
let globals = i
.globals
.iter()
.map(|g| Global {
name: g.name.clone(),
ty: translate_type(&g.ty),
})
.collect::<Vec<_>>();
let iface = Interface {
resources,
types,
functions,
globals,
};
return serde_json::to_string_pretty(&iface).unwrap();
fn translate_typedef(ty: &wit_parser::TypeDef) -> Type {
match &ty.kind {
TypeDefKind::Type(t) => Type::Primitive(translate_type(t)),
TypeDefKind::Record(r) => Type::Record {
fields: r
.fields
.iter()
.map(|f| (f.name.clone(), translate_type(&f.ty)))
.collect(),
},
TypeDefKind::Tuple(t) => Type::Tuple {
types: t.types.iter().map(|ty| translate_type(ty)).collect(),
},
TypeDefKind::Flags(r) => Type::Flags {
flags: r.flags.iter().map(|f| f.name.clone()).collect(),
},
TypeDefKind::Enum(r) => Type::Enum {
cases: r.cases.iter().map(|f| f.name.clone()).collect(),
},
TypeDefKind::Variant(v) => Type::Variant {
cases: v
.cases
.iter()
.map(|f| (f.name.clone(), translate_type(&f.ty)))
.collect(),
},
TypeDefKind::Option(t) => Type::Option(translate_type(t)),
TypeDefKind::Expected(e) => Type::Expected {
ok: translate_type(&e.ok),
err: translate_type(&e.err),
},
TypeDefKind::Future(t) => Type::Future(translate_type(t)),
TypeDefKind::Stream(s) => Type::Stream {
element: translate_type(&s.element),
end: translate_type(&s.end),
},
TypeDefKind::List(ty) => Type::List(translate_type(ty)),
TypeDefKind::Union(u) => Type::Union {
cases: u.cases.iter().map(|c| translate_type(&c.ty)).collect(),
},
}
}
fn translate_type(ty: &wit_parser::Type) -> String {
use wit_parser::Type;
match ty {
Type::Unit => format!("unit"),
Type::Bool => format!("bool"),
Type::U8 => format!("u8"),
Type::U16 => format!("u16"),
Type::U32 => format!("u32"),
Type::U64 => format!("u64"),
Type::S8 => format!("s8"),
Type::S16 => format!("s16"),
Type::S32 => format!("s32"),
Type::S64 => format!("s64"),
Type::Float32 => format!("float32"),
Type::Float64 => format!("float64"),
Type::Char => format!("char"),
Type::String => format!("string"),
Type::Handle(resource) => format!("handle-{}", resource.index()),
Type::Id(id) => format!("type-{}", id.index()),
}
}
}

View File

@@ -0,0 +1,9 @@
a: async func()
b: async func(x: s32)
c: async func() -> u32
resource y {
a: async func()
b: async func(x: s32)
c: async func() -> u32
}

View File

@@ -0,0 +1,60 @@
{
"resources": [
{
"name": "y"
}
],
"types": [
{
"idx": 0,
"primitive": "handle-0"
}
],
"functions": [
{
"name": "a",
"async": true,
"params": [],
"result": "unit"
},
{
"name": "b",
"async": true,
"params": [
"s32"
],
"result": "unit"
},
{
"name": "c",
"async": true,
"params": [],
"result": "u32"
},
{
"name": "y::a",
"async": true,
"params": [
"handle-0"
],
"result": "unit"
},
{
"name": "y::b",
"async": true,
"params": [
"handle-0",
"s32"
],
"result": "unit"
},
{
"name": "y::c",
"async": true,
"params": [
"handle-0"
],
"result": "u32"
}
]
}

View File

@@ -0,0 +1,19 @@
// hello
// world
// why, yes
// this is a comment
/* this too */ /* is a comment */
/* this /* is /* a */ nested */ comment */
type /* foo */ bar /* baz */ = //
handle //
//
//
x
resource /* x */ x // ...

View File

@@ -0,0 +1,18 @@
{
"resources": [
{
"name": "x"
}
],
"types": [
{
"idx": 0,
"name": "bar",
"primitive": "handle-0"
},
{
"idx": 1,
"primitive": "handle-0"
}
]
}

View File

@@ -0,0 +1,24 @@
# A Markdown file!
containing stuff, and also some code blocks, wit and other.
```wit
x: func()
```
Intervening content, including a non-wit codeblock:
```js
function func() {}
```
```wit
y: func()
```
## A new section
In which, another wit code block!
```wit
z: func()
```

View File

@@ -0,0 +1,19 @@
{
"functions": [
{
"name": "x",
"params": [],
"result": "unit"
},
{
"name": "y",
"params": [],
"result": "unit"
},
{
"name": "z",
"params": [],
"result": "unit"
}
]
}

View File

@@ -0,0 +1,7 @@
f1: func()
f2: func(a: u32)
f3: func(a: u32,)
f4: func() -> u32
f6: func() -> tuple<u32, u32>
f7: func(a: float32, b: float32) -> tuple<u32, u32>
f8: func(a: option<u32>) -> expected<u32, float32>

View File

@@ -0,0 +1,70 @@
{
"types": [
{
"idx": 0,
"tuple": {
"types": [
"u32",
"u32"
]
}
},
{
"idx": 1,
"option": "u32"
},
{
"idx": 2,
"expected": {
"ok": "u32",
"err": "float32"
}
}
],
"functions": [
{
"name": "f1",
"params": [],
"result": "unit"
},
{
"name": "f2",
"params": [
"u32"
],
"result": "unit"
},
{
"name": "f3",
"params": [
"u32"
],
"result": "unit"
},
{
"name": "f4",
"params": [],
"result": "u32"
},
{
"name": "f6",
"params": [],
"result": "type-0"
},
{
"name": "f7",
"params": [
"float32",
"float32"
],
"result": "type-0"
},
{
"name": "f8",
"params": [
"type-1"
],
"result": "type-2"
}
]
}

View File

@@ -0,0 +1,28 @@
# Title
This file is like import-me.wit, but it's a Markdown file with embedded wit
code blocks.
## `foo`
```wit
/// This is foo.
type foo = u32
```
## `x`
```wit
/// This is x.
resource x
```
## `handle`
```wit
/// This is handle.
type %handle = handle x
```
## `some-record`
```wit
/// This is some-record.
type some-record = tuple<u32, u64, float32>
```

View File

@@ -0,0 +1,34 @@
{
"resources": [
{
"name": "x"
}
],
"types": [
{
"idx": 0,
"name": "foo",
"primitive": "u32"
},
{
"idx": 1,
"primitive": "handle-0"
},
{
"idx": 2,
"name": "handle",
"primitive": "handle-0"
},
{
"idx": 3,
"name": "some-record",
"tuple": {
"types": [
"u32",
"u64",
"float32"
]
}
}
]
}

View File

@@ -0,0 +1,7 @@
type foo = u32
resource x
type %handle = handle x
type some-record = tuple<u32, u64, float32>

View File

@@ -0,0 +1,34 @@
{
"resources": [
{
"name": "x"
}
],
"types": [
{
"idx": 0,
"name": "foo",
"primitive": "u32"
},
{
"idx": 1,
"primitive": "handle-0"
},
{
"idx": 2,
"name": "handle",
"primitive": "handle-0"
},
{
"idx": 3,
"name": "some-record",
"tuple": {
"types": [
"u32",
"u64",
"float32"
]
}
}
]
}

View File

@@ -0,0 +1,18 @@
// This test is like imports.wit, but uses import-me-too, which is a markdown
// file instead of a plain wit file.
use { foo } from import-me-too
use { foo as bar } from import-me-too
use { x as import-me-x } from import-me-too
type x = foo
type y = bar
type z1 = import-me-x
type z2 = handle import-me-x
use { %handle } from import-me-too
resource xyz
type my-handle = handle xyz
type my-handle2 = xyz
use { some-record } from import-me-too

View File

@@ -0,0 +1,76 @@
{
"resources": [
{
"name": "x",
"foreign_module": "import-me-too"
},
{
"name": "xyz"
}
],
"types": [
{
"idx": 0,
"name": "foo",
"primitive": "u32",
"foreign_module": "import-me-too"
},
{
"idx": 1,
"primitive": "handle-0",
"foreign_module": "import-me-too"
},
{
"idx": 2,
"name": "handle",
"primitive": "handle-0",
"foreign_module": "import-me-too"
},
{
"idx": 3,
"name": "some-record",
"tuple": {
"types": [
"u32",
"u64",
"float32"
]
},
"foreign_module": "import-me-too"
},
{
"idx": 4,
"name": "x",
"primitive": "type-0"
},
{
"idx": 5,
"name": "y",
"primitive": "type-0"
},
{
"idx": 6,
"name": "z1",
"primitive": "type-1"
},
{
"idx": 7,
"name": "z2",
"primitive": "handle-0"
},
{
"idx": 8,
"primitive": "handle-1"
},
{
"idx": 9,
"name": "my-handle",
"primitive": "handle-1"
},
{
"idx": 10,
"name": "my-handle2",
"primitive": "type-8"
}
]
}

View File

@@ -0,0 +1,15 @@
use { foo } from import-me
use { foo as bar } from import-me
use { x as import-me-x } from import-me
type x = foo
type y = bar
type z1 = import-me-x
type z2 = handle import-me-x
use { %handle } from import-me
resource xyz
type my-handle = handle xyz
type my-handle2 = xyz
use { some-record } from import-me

View File

@@ -0,0 +1,76 @@
{
"resources": [
{
"name": "x",
"foreign_module": "import-me"
},
{
"name": "xyz"
}
],
"types": [
{
"idx": 0,
"name": "foo",
"primitive": "u32",
"foreign_module": "import-me"
},
{
"idx": 1,
"primitive": "handle-0",
"foreign_module": "import-me"
},
{
"idx": 2,
"name": "handle",
"primitive": "handle-0",
"foreign_module": "import-me"
},
{
"idx": 3,
"name": "some-record",
"tuple": {
"types": [
"u32",
"u64",
"float32"
]
},
"foreign_module": "import-me"
},
{
"idx": 4,
"name": "x",
"primitive": "type-0"
},
{
"idx": 5,
"name": "y",
"primitive": "type-0"
},
{
"idx": 6,
"name": "z1",
"primitive": "type-1"
},
{
"idx": 7,
"name": "z2",
"primitive": "handle-0"
},
{
"idx": 8,
"primitive": "handle-1"
},
{
"idx": 9,
"name": "my-handle",
"primitive": "handle-1"
},
{
"idx": 10,
"name": "my-handle2",
"primitive": "type-8"
}
]
}

View File

@@ -0,0 +1,4 @@
use * from import-me
type my-handle = handle x
type my-handle2 = x

View File

@@ -0,0 +1,49 @@
{
"resources": [
{
"name": "x",
"foreign_module": "import-me"
}
],
"types": [
{
"idx": 0,
"name": "foo",
"primitive": "u32",
"foreign_module": "import-me"
},
{
"idx": 1,
"name": "handle",
"primitive": "handle-0",
"foreign_module": "import-me"
},
{
"idx": 2,
"name": "some-record",
"tuple": {
"types": [
"u32",
"u64",
"float32"
]
},
"foreign_module": "import-me"
},
{
"idx": 3,
"primitive": "handle-0",
"foreign_module": "import-me"
},
{
"idx": 4,
"name": "my-handle",
"primitive": "handle-0"
},
{
"idx": 5,
"name": "my-handle2",
"primitive": "type-3"
}
]
}

View File

@@ -0,0 +1,2 @@
// parse-fail
a: async

View File

@@ -0,0 +1,5 @@
expected keyword `func`, found eof
--> tests/ui/parse-fail/async.wit:3:1
|
3 |
| ^

View File

@@ -0,0 +1,3 @@
// parse-fail
a: async()

View File

@@ -0,0 +1,5 @@
expected keyword `func`, found '('
--> tests/ui/parse-fail/async1.wit:2:9
|
2 | a: async()
| ^

View File

@@ -0,0 +1,5 @@
// parse-fail
type x = list<u32
type y = u32

View File

@@ -0,0 +1,5 @@
expected '>', found keyword `type`
--> tests/ui/parse-fail/bad-list.wit:5:1
|
5 | type y = u32
| ^

View File

@@ -0,0 +1,5 @@
// parse-fail
resource x {
x: s32
}

View File

@@ -0,0 +1,5 @@
globals not allowed in resources
--> tests/ui/parse-fail/bad-resource.wit:4:3
|
4 | x: s32
| ^

View File

@@ -0,0 +1,6 @@
// parse-fail
resource x {
x: func()
x: func()
}

View File

@@ -0,0 +1,5 @@
"x" defined twice in this resource
--> tests/ui/parse-fail/bad-resource2.wit:5:3
|
5 | x: func()
| ^

View File

@@ -0,0 +1,2 @@
// parse-fail
use {} from foo

View File

@@ -0,0 +1,5 @@
expected an identifier or string, found '}'
--> tests/ui/parse-fail/bad-use.wit:2:6
|
2 | use {} from foo
| ^

View File

@@ -0,0 +1,4 @@
// parse-fail
use { a } from
type foo = u32

Some files were not shown because too many files have changed in this diff Show More