blob: 678e374d049cbc4e12a87f2e84f4b25a434afbdc [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::ast::{self, BanjoAst, Constant},
crate::backends::Backend,
failure::{format_err, Error},
heck::SnakeCase,
std::io,
std::iter,
};
pub struct CBackend<'a, W: io::Write> {
w: &'a mut W,
}
impl<'a, W: io::Write> CBackend<'a, W> {
pub fn new(w: &'a mut W) -> Self {
CBackend { w }
}
}
fn to_c_name(name: &str) -> String {
// strip FQN
let name = name.split(".").last().unwrap();
let mut iter = name.chars().peekable();
let mut accum = String::new();
while let Some(c) = iter.next() {
accum.push(c);
if let Some(c2) = iter.peek() {
if c.is_ascii_uppercase() && c.is_ascii_uppercase() {
accum.push(c2.to_ascii_lowercase());
iter.next();
}
}
}
accum.to_snake_case()
}
fn get_doc_comment(attrs: &ast::Attrs, tabs: usize) -> String {
for attr in attrs.0.iter() {
if attr.key == "Doc" {
if let Some(ref val) = attr.val {
let tabs: String = iter::repeat(' ').take(tabs * 4).collect();
return val
.trim_end()
.split("\n")
.map(|line| format!("{}//{}\n", tabs, line))
.collect();
}
}
}
"".to_string()
}
fn ty_to_c_str(ast: &ast::BanjoAst, ty: &ast::Ty) -> Result<String, Error> {
match ty {
ast::Ty::Bool => Ok(String::from("bool")),
ast::Ty::Int8 => Ok(String::from("int8_t")),
ast::Ty::Int16 => Ok(String::from("int16_t")),
ast::Ty::Int32 => Ok(String::from("int32_t")),
ast::Ty::Int64 => Ok(String::from("int64_t")),
ast::Ty::UInt8 => Ok(String::from("uint8_t")),
ast::Ty::UInt16 => Ok(String::from("uint16_t")),
ast::Ty::UInt32 => Ok(String::from("uint32_t")),
ast::Ty::UInt64 => Ok(String::from("uint64_t")),
ast::Ty::USize => Ok(String::from("size_t")),
ast::Ty::Voidptr => Ok(String::from("void*")),
ast::Ty::Str { .. } => Ok(String::from("const char*")),
ast::Ty::Vector { ref ty, .. } => ty_to_c_str(ast, ty),
ast::Ty::Array { ref ty, .. } => ty_to_c_str(ast, ty),
ast::Ty::Ident { id, .. } => {
if id == "zx.status" {
Ok("zx_status_t".to_string())
} else {
match ast.id_to_type(id) {
ast::Ty::Struct | ast::Ty::Union | ast::Ty::Interface | ast::Ty::Enum => {
return Ok(to_c_name(id.clone().as_str()) + "_t");
}
t => return ty_to_c_str(ast, &t),
}
}
}
ast::Ty::Handle { .. } => Ok(String::from("zx_handle_t")),
t => Err(format_err!("unknown type in ty_to_c_str {:?}", t)),
}
}
fn size_to_c_str(ty: &ast::Ty, cons: &ast::Constant) -> String {
match cons {
Constant::SizedConstant(size) => match ty {
ast::Ty::Int8 => String::from(format!("INT8_C({})", size)),
ast::Ty::Int16 => String::from(format!("INT16_C({})", size)),
ast::Ty::Int32 => String::from(format!("INT32_C({})", size)),
ast::Ty::Int64 => String::from(format!("INT32_C({})", size)),
ast::Ty::UInt8 => String::from(format!("UINT8_C({})", size)),
ast::Ty::UInt16 => String::from(format!("UINT16_C({})", size)),
ast::Ty::UInt32 => String::from(format!("UINT32_C({})", size)),
ast::Ty::UInt64 => String::from(format!("UINT32_C({})", size)),
s => panic!("don't handles this sized const: {}", s),
},
_ => panic!("don't handles sized const yet"),
}
}
fn name_buffer(ty: &str) -> &'static str {
if ty == "void" {
"buffer"
} else {
"list"
}
}
fn name_size(ty: &str) -> &'static str {
if ty == "void" {
"size"
} else {
"count"
}
}
fn get_first_param(ast: &BanjoAst, method: &ast::Method) -> Result<(bool, String), Error> {
// Return parameter if a primitive type.
if method
.out_params
.get(0)
.map_or(false, |p| p.1.is_primitive())
{
Ok((true, ty_to_c_str(ast, &method.out_params[0].1)?))
} else {
Ok((false, "void".to_string()))
}
}
fn get_in_params(m: &ast::Method, ast: &BanjoAst) -> Result<Vec<String>, Error> {
m.in_params
.iter()
.map(|(name, ty)| {
match ty {
ast::Ty::Ident { id, .. } => {
match ast.id_to_type(id) {
ast::Ty::Interface => {
let ty_name = ty_to_c_str(ast, ty).unwrap();
if ty_name == "zx_status_t" {
Ok(format!("{} {}", ty_name, to_c_name(name)))
} else {
Ok(format!("const {}* {}", ty_name, to_c_name(name)))
}
}
ast::Ty::Struct | ast::Ty::Union => {
let ty_name = ty_to_c_str(ast, ty).unwrap();
// TODO: Using nullability to determine whether param is mutable is a hack.
let prefix = if ty.is_nullable() { "" } else { "const " };
Ok(format!("{}{}* {}", prefix, ty_name, to_c_name(name)))
}
ast::Ty::Enum => Ok(format!(
"{} {}",
ty_to_c_str(ast, ty).unwrap(),
to_c_name(name)
)),
ast::Ty::Bool
| ast::Ty::Int8
| ast::Ty::Int16
| ast::Ty::Int32
| ast::Ty::Int64
| ast::Ty::UInt8
| ast::Ty::UInt16
| ast::Ty::UInt32
| ast::Ty::UInt64
| ast::Ty::USize
| ast::Ty::Voidptr => Ok(format!(
"{} {}",
ty_to_c_str(ast, ty).unwrap(),
to_c_name(name)
)),
e => Err(format_err!("unsupported: {}", e)),
}
}
ast::Ty::Vector { .. } => {
let ty = ty_to_c_str(ast, ty).unwrap();
Ok(format!(
"const {ty}* {name}_{buffer}, size_t {name}_{size}",
buffer = name_buffer(&ty),
size = name_size(&ty),
ty = ty,
name = to_c_name(name)
))
}
_ => Ok(format!(
"{} {}",
ty_to_c_str(ast, ty).unwrap(),
to_c_name(name)
)),
}
})
.collect()
}
fn get_out_params(
m: &ast::Method, name: &str, ast: &BanjoAst,
) -> Result<(Vec<String>, String), Error> {
if m.attributes.has_attribute("Async") {
return Ok((
vec![
format!(
"{protocol_name}_{method_name}_callback callback",
protocol_name = to_c_name(name),
method_name = to_c_name(&m.name)
),
"void* cookie".to_string(),
],
"void".to_string(),
));
}
let (skip, return_param) = get_first_param(ast, m)?;
let skip_amt = if skip { 1 } else { 0 };
Ok((m.out_params.iter().skip(skip_amt).map(|(name, ty)| {
let nullable = if ty.is_nullable() { "*" } else { "" };
let ty_name = ty_to_c_str(ast, ty).unwrap();
match ty {
ast::Ty::Interface => format!("const {}* {}", ty_name, to_c_name(name)),
ast::Ty::Ident {..} => {
let star = if ty_name == "zx_status_t" { "" } else { "*" };
format!("{}{}{} out_{}", ty_name, star, nullable, to_c_name(name))
},
ast::Ty::Vector {..} => {
if ty.is_nullable() {
format!("{ty}** out_{name}_{buffer}, size_t* {name}_{size}",
buffer = name_buffer(&ty_name),
size = name_size(&ty_name),
ty = ty_name,
name = to_c_name(name))
} else {
format!("{ty}* out_{name}_{buffer}, size_t {name}_{size}, size_t* out_{name}_actual",
buffer = name_buffer(&ty_name),
size = name_size(&ty_name),
ty = ty_name,
name = to_c_name(name))
}
},
_ => format!("{}{}* out_{}", ty_name, nullable, to_c_name(name))
}
}).collect(), return_param))
}
fn get_in_args(m: &ast::Method, ast: &BanjoAst) -> Result<Vec<String>, Error> {
Ok(m.in_params
.iter()
.map(|(name, ty)| match ty {
ast::Ty::Vector { .. } => {
let ty = ty_to_c_str(ast, ty).unwrap();
format!(
"{name}_{buffer}, {name}_{size}",
buffer = name_buffer(&ty),
size = name_size(&ty),
name = to_c_name(name)
)
}
_ => format!("{}", to_c_name(name)),
})
.collect())
}
fn get_out_args(m: &ast::Method, ast: &BanjoAst) -> Result<(Vec<String>, bool), Error> {
if m.attributes.has_attribute("Async") {
return Ok((vec!["callback".to_string(), "cookie".to_string()], false));
}
let (skip, _) = get_first_param(ast, m)?;
let skip_amt = if skip { 1 } else { 0 };
Ok((
m.out_params
.iter()
.skip(skip_amt)
.map(|(name, ty)| match ty {
ast::Ty::Interface { .. } => format!("{}", to_c_name(name)),
ast::Ty::Vector { .. } => {
let ty_name = ty_to_c_str(ast, ty).unwrap();
if ty.is_nullable() {
format!(
"out_{name}_{buffer}, {name}_{size}",
buffer = name_buffer(&ty_name),
size = name_size(&ty_name),
name = to_c_name(name)
)
} else {
format!(
"out_{name}_{buffer}, {name}_{size}, out_{name}_actual",
buffer = name_buffer(&ty_name),
size = name_size(&ty_name),
name = to_c_name(name)
)
}
}
_ => format!("out_{}", to_c_name(name)),
})
.collect(),
skip,
))
}
/// Checks whether a decl is an interface, and if it is an interface, checks that it is a "ddk-protocol".
fn filter_interface<'a>(
decl: &'a ast::Decl,
) -> Option<(&'a String, &'a Vec<ast::Method>, &'a ast::Attrs)> {
if let ast::Decl::Interface {
ref name,
ref methods,
ref attributes,
} = *decl
{
if let Some(layout) = attributes.get_attribute("Layout") {
if layout == "\"ddk-interface\"" {
return Some((name, methods, attributes));
}
}
}
None
}
/// Checks whether a decl is an interface, and if it is an interface, checks that it is a "ddk-protocol".
fn filter_protocol<'a>(
decl: &'a ast::Decl,
) -> Option<(&'a String, &'a Vec<ast::Method>, &'a ast::Attrs)> {
if let ast::Decl::Interface {
ref name,
ref methods,
ref attributes,
} = *decl
{
if let Some(layout) = attributes.get_attribute("Layout") {
if layout == "\"ddk-callback\"" || layout == "\"ddk-interface\"" {
None
} else {
Some((name, methods, attributes))
}
} else {
Some((name, methods, attributes))
}
} else {
None
}
}
impl<'a, W: io::Write> CBackend<'a, W> {
fn codegen_enum_decl(
&self, namespace: &Vec<ast::Decl>, ast: &BanjoAst,
) -> Result<String, Error> {
namespace
.iter()
.filter_map(|decl| {
if let ast::Decl::Enum {
ref name,
ref ty,
ref variants,
..
} = *decl
{
Some((name, ty, variants))
} else {
None
}
})
.map(|(name, ty, variants)| {
let enum_defines = variants
.iter()
.map(|v| {
Ok(format!(
"#define {c_name}_{v_name} {c_size}",
c_name = to_c_name(name).to_uppercase(),
v_name = to_c_name(v.name.as_str()).to_uppercase(),
c_size = size_to_c_str(ty, &v.size)
))
})
.collect::<Result<Vec<_>, Error>>()?
.join("\n");
Ok(format!(
"typedef {ty} {c_name}_t;\n{enum_defines}",
c_name = to_c_name(name),
ty = ty_to_c_str(ast, ty)?,
enum_defines = enum_defines
))
})
.collect::<Result<Vec<_>, Error>>()
.map(|x| x.join("\n"))
}
fn codegen_constant_defs(
&self, namespace: &Vec<ast::Decl>, _ast: &BanjoAst,
) -> Result<String, Error> {
namespace
.iter()
.filter_map(|decl| {
if let ast::Decl::Constant {
ref name,
ref ty,
ref value,
ref attributes,
} = *decl
{
Some((name, ty, value, attributes))
} else {
None
}
})
.map(|(name, ty, value, attrs)| {
let mut accum = String::new();
accum.push_str(get_doc_comment(attrs, 0).as_str());
accum.push_str(
format!(
"#define {name} {value}",
name = name,
value = size_to_c_str(ty, value)
)
.as_str(),
);
Ok(accum)
})
.collect::<Result<Vec<_>, Error>>()
.map(|x| x.join("\n"))
}
fn codegen_union_decl(
&self, namespace: &Vec<ast::Decl>, _ast: &BanjoAst,
) -> Result<String, Error> {
Ok(namespace
.iter()
.filter_map(|decl| {
if let ast::Decl::Union { ref name, .. } = *decl {
Some(name)
} else {
None
}
})
.map(|name| {
format!(
"typedef union {c_name} {c_name}_t;",
c_name = to_c_name(name)
)
})
.collect::<Vec<_>>()
.join("\n"))
}
fn codegen_union_defs(
&self, namespace: &Vec<ast::Decl>, ast: &BanjoAst,
) -> Result<String, Error> {
namespace
.iter()
.filter_map(|decl| {
if let ast::Decl::Union {
ref name,
ref fields,
ref attributes,
..
} = *decl
{
Some((name, fields, attributes))
} else {
None
}
})
.map(|(name, fields, attrs)| {
let members = fields
.iter()
.map(|f| {
let mut accum = String::new();
accum.push_str(get_doc_comment(&f.attributes, 1).as_str());
accum.push_str(
format!(
" {ty} {c_name};",
c_name = to_c_name(f.ident.as_str()),
ty = ty_to_c_str(ast, &f.ty)?
)
.as_str(),
);
Ok(accum)
})
.collect::<Result<Vec<_>, Error>>()?
.join("\n");
let mut accum = String::new();
accum.push_str(get_doc_comment(attrs, 0).as_str());
accum.push_str(
format!(
include_str!("templates/c/struct.h"),
c_name = to_c_name(name),
decl = "union",
members = members
)
.as_str(),
);
Ok(accum)
})
.collect::<Result<Vec<_>, Error>>()
.map(|x| x.join("\n"))
}
fn codegen_struct_decl(
&self, namespace: &Vec<ast::Decl>, _ast: &BanjoAst,
) -> Result<String, Error> {
Ok(namespace
.iter()
.filter_map(|decl| {
if let ast::Decl::Struct { ref name, .. } = *decl {
Some(name)
} else {
None
}
})
.map(|name| {
format!(
"typedef struct {c_name} {c_name}_t;",
c_name = to_c_name(name)
)
})
.collect::<Vec<_>>()
.join("\n"))
}
fn codegen_struct_defs(
&self, namespace: &Vec<ast::Decl>, ast: &BanjoAst,
) -> Result<String, Error> {
namespace
.iter()
.filter_map(|decl| {
if let ast::Decl::Struct {
ref name,
ref fields,
ref attributes,
} = *decl
{
Some((name, fields, attributes))
} else {
None
}
})
.map(|(name, fields, attrs)| {
let members = fields
.iter()
.map(|f| {
let mut accum = String::new();
accum.push_str(get_doc_comment(&f.attributes, 1).as_str());
accum.push_str(
format!(
" {ty} {c_name};",
c_name = to_c_name(f.ident.as_str()),
ty = ty_to_c_str(ast, &f.ty)?
)
.as_str(),
);
Ok(accum)
})
.collect::<Result<Vec<_>, Error>>()?
.join("\n");
let mut accum = String::new();
accum.push_str(get_doc_comment(attrs, 0).as_str());
accum.push_str(
format!(
include_str!("templates/c/struct.h"),
c_name = to_c_name(name),
decl = "struct",
members = members
)
.as_str(),
);
Ok(accum)
})
.collect::<Result<Vec<_>, Error>>()
.map(|x| x.join("\n"))
}
fn codegen_protocol_def(
&self, name: &str, methods: &Vec<ast::Method>, protocol: bool, ast: &BanjoAst,
) -> Result<String, Error> {
let fns = methods
.iter()
.map(|m| {
let (out_params, return_param) = get_out_params(&m, name, ast)?;
let in_params = get_in_params(&m, ast)?;
let params = iter::once("void* ctx".to_string())
.chain(in_params)
.chain(out_params)
.collect::<Vec<_>>()
.join(", ");
Ok(format!(
" {return_param} (*{fn_name})({params});",
return_param = return_param,
params = params,
fn_name = to_c_name(m.name.as_str())
))
})
.collect::<Result<Vec<_>, Error>>()?
.join("\n");
let protocol = if protocol { "protocol_" } else { "" };
Ok(format!(
include_str!("templates/c/protocol_ops.h"),
c_name = to_c_name(name),
protocol = protocol,
fns = fns
))
}
fn codegen_helper_def(
&self, name: &str, methods: &Vec<ast::Method>, protocol: bool, ast: &BanjoAst,
) -> Result<String, Error> {
methods
.iter()
.map(|m| {
let mut accum = String::new();
accum.push_str(get_doc_comment(&m.attributes, 0).as_str());
let (out_params, return_param) = get_out_params(&m, name, ast)?;
let in_params = get_in_params(&m, ast)?;
let protocol = if protocol { "_protocol" } else { "" };
let first_param = format!("const {}{}_t* proto", to_c_name(name), protocol);
let params = iter::once(first_param)
.chain(in_params)
.chain(out_params)
.collect::<Vec<_>>()
.join(", ");
accum.push_str(
format!(
"static inline {return_param} {protocol_name}_{fn_name}({params}) {{\n",
return_param = return_param,
params = params,
protocol_name = to_c_name(name),
fn_name = to_c_name(m.name.as_str())
)
.as_str(),
);
let (out_args, skip) = get_out_args(&m, ast)?;
let in_args = get_in_args(&m, ast)?;
let args = iter::once("proto->ctx".to_string())
.chain(in_args)
.chain(out_args)
.collect::<Vec<_>>()
.join(", ");
let return_statement = if skip { "return " } else { "" };
accum.push_str(
format!(
" {return_statement}proto->ops->{fn_name}({args});\n",
return_statement = return_statement,
args = args,
fn_name = to_c_name(m.name.as_str())
)
.as_str(),
);
accum.push_str("}\n");
Ok(accum)
})
.collect::<Result<Vec<_>, Error>>()
.map(|x| x.join("\n"))
}
fn codegen_protocol_defs(
&self, namespace: &Vec<ast::Decl>, ast: &BanjoAst,
) -> Result<String, Error> {
namespace
.into_iter()
.filter_map(filter_protocol)
.map(|(name, methods, _)| {
Ok(format!(
include_str!("templates/c/protocol.h"),
protocol_name = to_c_name(name),
protocol_def = self.codegen_protocol_def(name, methods, true, ast)?,
helper_def = self.codegen_helper_def(name, methods, true, ast)?
))
})
.collect::<Result<Vec<_>, Error>>()
.map(|x| x.join("\n"))
}
fn codegen_interface_defs(
&self, namespace: &Vec<ast::Decl>, ast: &BanjoAst,
) -> Result<String, Error> {
namespace
.into_iter()
.filter_map(filter_interface)
.map(|(name, methods, _)| {
Ok(format!(
include_str!("templates/c/interface.h"),
protocol_name = to_c_name(name),
protocol_def = self.codegen_protocol_def(name, methods, false, ast)?,
helper_def = self.codegen_helper_def(name, methods, false, ast)?
))
})
.collect::<Result<Vec<_>, Error>>()
.map(|x| x.join("\n"))
}
fn codegen_protocol_decl(
&self, namespace: &Vec<ast::Decl>, _ast: &BanjoAst,
) -> Result<String, Error> {
Ok(namespace
.into_iter()
.filter_map(filter_protocol)
.map(|(name, _, _)| {
format!(
"typedef struct {c_name}_protocol {c_name}_protocol_t;",
c_name = to_c_name(name)
)
})
.collect::<Vec<_>>()
.join("\n"))
}
fn codegen_interface_decl(
&self, namespace: &Vec<ast::Decl>, _ast: &BanjoAst,
) -> Result<String, Error> {
Ok(namespace
.into_iter()
.filter_map(filter_interface)
.map(|(name, _, _)| {
format!(
"typedef struct {c_name} {c_name}_t;",
c_name = to_c_name(name)
)
})
.collect::<Vec<_>>()
.join("\n"))
}
fn codegen_includes(&self, ast: &BanjoAst) -> Result<String, Error> {
Ok(ast
.namespaces
.iter()
.filter(|n| *n.0 != ast.primary_namespace)
.filter(|n| *n.0 != "zx")
.map(|n| format!("#include <{}.h>", n.0.replace('.', "/")))
.collect::<Vec<_>>()
.join("\n"))
}
}
impl<'a, W: io::Write> Backend<'a, W> for CBackend<'a, W> {
fn codegen(&mut self, ast: BanjoAst) -> Result<(), Error> {
self.w.write_fmt(format_args!(
include_str!("templates/c/header.h"),
includes = self.codegen_includes(&ast)?,
primary_namespace = ast.primary_namespace
))?;
let namespace = &ast.namespaces[&ast.primary_namespace];
self.w.write_fmt(format_args!(
include_str!("templates/c/body.h"),
enum_decls = self.codegen_enum_decl(namespace, &ast)?,
union_decls = self.codegen_union_decl(namespace, &ast)?,
struct_decls = self.codegen_struct_decl(namespace, &ast)?,
interface_decls = self.codegen_interface_decl(namespace, &ast)?,
protocol_decls = self.codegen_protocol_decl(namespace, &ast)?,
constant_definitions = self.codegen_constant_defs(namespace, &ast)?,
union_definitions = self.codegen_union_defs(namespace, &ast)?,
struct_definitions = self.codegen_struct_defs(namespace, &ast)?,
interface_definitions = self.codegen_interface_defs(namespace, &ast)?,
protocol_definitions = self.codegen_protocol_defs(namespace, &ast)?
))?;
Ok(())
}
}