936 lines
30 KiB
Rust
936 lines
30 KiB
Rust
use crate::utils::{syn_assert, syn_error, wrap_expr_block};
|
|
use proc_macro2::TokenStream;
|
|
use quote::quote;
|
|
use std::fmt::Display;
|
|
use syn::{ext::*, punctuated::*, spanned::*, *};
|
|
|
|
pub fn generate(expr: &Expr) -> Result<TokenStream> {
|
|
let mut f = Formatter::default();
|
|
match expr {
|
|
Expr::Block(block) => generate_block(&mut f, &block.block, Context::stmt(true))?,
|
|
_ => generate_expr(&mut f, expr, Context::expr(true))?,
|
|
}
|
|
|
|
Ok(f.done())
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct Formatter {
|
|
buf: String,
|
|
space: bool,
|
|
format_args: Vec<Expr>,
|
|
}
|
|
|
|
impl Formatter {
|
|
fn write(&mut self, s: impl Display) -> &mut Self {
|
|
fn sep(c: char) -> bool {
|
|
match c {
|
|
'(' | ')' | '[' | ']' | '{' | '}' | '+' | '-' | '*' | '/' | '%' | '^' | '#'
|
|
| '=' | '~' | '<' | '>' | ':' | ';' | '.' | ',' | '\'' | '"' | ' ' => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
// we are inside a format string, so we need to escape braces
|
|
let s = format!("{s}").replace("{", "{{").replace("}", "}}");
|
|
if !s.is_empty() {
|
|
if self.space && !sep(s.chars().next().unwrap()) {
|
|
self.buf.push(' ');
|
|
}
|
|
self.buf.push_str(&s);
|
|
self.space = !sep(s.chars().last().unwrap());
|
|
}
|
|
|
|
self
|
|
}
|
|
|
|
fn interpolate(&mut self, arg: Expr) -> &mut Self {
|
|
self.space.then(|| self.buf.push(' '));
|
|
self.buf.push_str("{}");
|
|
self.format_args.push(arg);
|
|
self.space = true;
|
|
self
|
|
}
|
|
|
|
fn done(self) -> TokenStream {
|
|
let fmt = self.buf;
|
|
let args = self.format_args;
|
|
if args.is_empty() {
|
|
let fmt = fmt.replace("{{", "{").replace("}}", "}");
|
|
quote!(#fmt)
|
|
} else {
|
|
quote!(::std::format!(#fmt, #(#args),*))
|
|
}
|
|
}
|
|
}
|
|
|
|
macro_rules! assert_no_attrs {
|
|
($src:expr) => {{ syn_assert!($src.attrs.is_empty(), $src, "unsupported attribute") }};
|
|
}
|
|
|
|
macro_rules! assert_no_suffix {
|
|
($src:expr) => {{ syn_assert!($src.suffix() == "", $src, "cannot have suffix") }};
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
enum Context {
|
|
Stmt { ret: bool },
|
|
Expr { multi: bool },
|
|
}
|
|
|
|
impl Context {
|
|
fn stmt(ret: bool) -> Self {
|
|
Self::Stmt { ret }
|
|
}
|
|
|
|
fn expr(multi: bool) -> Self {
|
|
Self::Expr { multi }
|
|
}
|
|
|
|
fn is_stmt(&self) -> bool {
|
|
matches!(self, Self::Stmt { .. })
|
|
}
|
|
|
|
fn is_ret(&self) -> bool {
|
|
matches!(self, Self::Stmt { ret: true, .. })
|
|
}
|
|
|
|
fn is_expr(&self) -> bool {
|
|
matches!(self, Self::Expr { .. })
|
|
}
|
|
|
|
fn is_multi_expr(&self) -> bool {
|
|
matches!(self, Self::Expr { multi: true, .. })
|
|
}
|
|
|
|
fn is_value(&self) -> bool {
|
|
self.is_expr() | self.is_ret()
|
|
}
|
|
}
|
|
|
|
fn generate_expr(f: &mut Formatter, expr: &Expr, cx: Context) -> Result<()> {
|
|
match expr {
|
|
Expr::Assign(ass) => generate_expr_assign(f, ass, cx),
|
|
Expr::Binary(bin) => generate_expr_binary(f, bin, cx),
|
|
Expr::Block(block) => generate_expr_block(f, block, cx),
|
|
Expr::Break(brk) => generate_expr_break(f, brk, cx),
|
|
Expr::Call(call) => generate_expr_call(f, call, cx),
|
|
Expr::Closure(clo) => generate_expr_closure(f, clo, cx),
|
|
Expr::Continue(cont) => generate_expr_continue(f, cont, cx),
|
|
Expr::Field(field) => generate_expr_field(f, field, cx),
|
|
Expr::ForLoop(fo) => generate_expr_forloop(f, fo, cx),
|
|
Expr::If(xif) => generate_expr_if(f, xif, cx),
|
|
Expr::Index(index) => generate_expr_index(f, index, cx),
|
|
Expr::Infer(infer) => generate_expr_infer(f, infer, cx),
|
|
Expr::Lit(lit) => generate_expr_lit(f, lit, cx),
|
|
Expr::Loop(lo) => generate_expr_loop(f, lo, cx),
|
|
Expr::Macro(mac) => generate_expr_macro(f, mac, cx),
|
|
Expr::MethodCall(call) => generate_expr_method_call(f, call, cx),
|
|
Expr::Paren(paren) => generate_expr_paren(f, paren, cx),
|
|
Expr::Path(path) => generate_expr_path(f, path, cx),
|
|
Expr::Return(ret) => generate_expr_return(f, ret, cx),
|
|
Expr::Tuple(tuple) => generate_expr_tuple(f, tuple, cx),
|
|
Expr::Unary(un) => generate_expr_unary(f, un, cx),
|
|
Expr::While(whil) => generate_expr_while(f, whil, cx),
|
|
expr => syn_error!(expr, "unsupported expression"),
|
|
}
|
|
}
|
|
|
|
fn generate_expr_assign(f: &mut Formatter, ass: &ExprAssign, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(ass);
|
|
syn_assert!(
|
|
cx.is_stmt(),
|
|
ass,
|
|
"assignment must be in statement position"
|
|
);
|
|
generate_expr(f, &ass.left, Context::expr(true))?;
|
|
f.write("=");
|
|
generate_expr(f, &ass.right, Context::expr(true))?;
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_expr_binary(f: &mut Formatter, bin: &ExprBinary, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(bin);
|
|
match bin.op {
|
|
BinOp::Add(_)
|
|
| BinOp::Sub(_)
|
|
| BinOp::Mul(_)
|
|
| BinOp::Div(_)
|
|
| BinOp::Rem(_)
|
|
| BinOp::BitAnd(_)
|
|
| BinOp::BitOr(_)
|
|
| BinOp::BitXor(_)
|
|
| BinOp::Shl(_)
|
|
| BinOp::Shr(_)
|
|
| BinOp::Eq(_)
|
|
| BinOp::Ne(_)
|
|
| BinOp::Lt(_)
|
|
| BinOp::Le(_)
|
|
| BinOp::Ge(_)
|
|
| BinOp::Gt(_)
|
|
| BinOp::And(_)
|
|
| BinOp::Or(_) => {
|
|
syn_assert!(cx.is_value(), bin, "must be in expression position");
|
|
cx.is_ret().then(|| f.write("return"));
|
|
}
|
|
BinOp::AddAssign(_)
|
|
| BinOp::SubAssign(_)
|
|
| BinOp::MulAssign(_)
|
|
| BinOp::DivAssign(_)
|
|
| BinOp::RemAssign(_)
|
|
| BinOp::BitXorAssign(_)
|
|
| BinOp::BitAndAssign(_)
|
|
| BinOp::BitOrAssign(_)
|
|
| BinOp::ShlAssign(_)
|
|
| BinOp::ShrAssign(_) => {
|
|
syn_assert!(cx.is_stmt(), bin, "must be in statement position");
|
|
}
|
|
op => syn_error!(op, "unsupported binary operator"),
|
|
}
|
|
|
|
let cx = Context::expr(false);
|
|
|
|
macro_rules! bin_op {
|
|
($op:expr) => {{
|
|
generate_expr(f, &bin.left, cx)?;
|
|
f.write($op);
|
|
generate_expr(f, &bin.right, cx)?;
|
|
}};
|
|
}
|
|
|
|
macro_rules! call_op {
|
|
($name:expr) => {{
|
|
f.write(format_args!("{}(", $name));
|
|
generate_expr(f, &bin.left, cx)?;
|
|
f.write(",");
|
|
generate_expr(f, &bin.right, cx)?;
|
|
f.write(")");
|
|
}};
|
|
}
|
|
|
|
macro_rules! assign_bin_op {
|
|
($op:expr) => {{
|
|
generate_expr(f, &bin.left, cx)?;
|
|
f.write("=");
|
|
bin_op!($op);
|
|
}};
|
|
}
|
|
|
|
macro_rules! assign_call_op {
|
|
($name:expr) => {{
|
|
generate_expr(f, &bin.left, cx)?;
|
|
f.write("=");
|
|
call_op!($name);
|
|
}};
|
|
}
|
|
|
|
match bin.op {
|
|
BinOp::Add(_) => bin_op!("+"),
|
|
BinOp::AddAssign(_) => assign_bin_op!("+"),
|
|
BinOp::Sub(_) => bin_op!("-"),
|
|
BinOp::SubAssign(_) => assign_bin_op!("-"),
|
|
BinOp::Mul(_) => bin_op!("*"),
|
|
BinOp::MulAssign(_) => assign_bin_op!("*"),
|
|
BinOp::Div(_) => bin_op!("/"),
|
|
BinOp::DivAssign(_) => assign_bin_op!("/"),
|
|
BinOp::Rem(_) => call_op!("__fmod"),
|
|
BinOp::RemAssign(_) => assign_call_op!("__fmod"),
|
|
BinOp::BitAnd(_) => call_op!("__band"),
|
|
BinOp::BitAndAssign(_) => assign_call_op!("__band"),
|
|
BinOp::BitOr(_) => call_op!("__bor"),
|
|
BinOp::BitOrAssign(_) => assign_call_op!("__bor"),
|
|
BinOp::BitXor(_) => call_op!("__bxor"),
|
|
BinOp::BitXorAssign(_) => assign_call_op!("__bxor"),
|
|
BinOp::Shl(_) => call_op!("__blshift"),
|
|
BinOp::ShlAssign(_) => assign_call_op!("__blshift"),
|
|
BinOp::Shr(_) => call_op!("__barshift"),
|
|
BinOp::ShrAssign(_) => assign_call_op!("__barshift"),
|
|
BinOp::Eq(_) => bin_op!("=="),
|
|
BinOp::Ne(_) => bin_op!("~="),
|
|
BinOp::Lt(_) => bin_op!("<"),
|
|
BinOp::Le(_) => bin_op!("<="),
|
|
BinOp::Ge(_) => bin_op!(">="),
|
|
BinOp::Gt(_) => bin_op!(">"),
|
|
BinOp::And(_) => bin_op!("and"),
|
|
BinOp::Or(_) => bin_op!("or"),
|
|
op => syn_error!(op, "unsupported binary operator"),
|
|
};
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_expr_block(f: &mut Formatter, block: &ExprBlock, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(block);
|
|
syn_assert!(cx.is_stmt(), block, "block must be in statement position");
|
|
f.write("do");
|
|
generate_block(f, &block.block, cx)?;
|
|
if let Some(ref label) = block.label {
|
|
generate_label_continue(f, label)?;
|
|
}
|
|
f.write("end");
|
|
if let Some(ref label) = block.label {
|
|
generate_label_break(f, label)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_expr_break(f: &mut Formatter, brk: &ExprBreak, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(brk);
|
|
syn_assert!(cx.is_stmt(), brk, "break must be in statement position");
|
|
syn_assert!(brk.expr.is_none(), brk, "use return instead");
|
|
match brk.label {
|
|
Some(ref label) => f.write(format_args!("goto {}_brk", label.ident.unraw())),
|
|
None => f.write("break"),
|
|
};
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_expr_call(f: &mut Formatter, call: &ExprCall, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(call);
|
|
cx.is_ret().then(|| f.write("return"));
|
|
generate_expr(f, &call.func, Context::expr(false))?;
|
|
f.write("(");
|
|
generate_punctuated_expr(f, &call.args)?;
|
|
f.write(")");
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_expr_closure(f: &mut Formatter, clo: &ExprClosure, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(clo);
|
|
syn_assert!(cx.is_value(), clo, "closure must be in expression position");
|
|
|
|
if let Some(ref bounds) = clo.lifetimes {
|
|
syn_error!(bounds, "cannot have lifetime bindings");
|
|
} else if let Some(ref stat) = clo.movability {
|
|
syn_error!(stat, "cannot be static");
|
|
} else if let Some(ref cons) = clo.constness {
|
|
syn_error!(cons, "cannot be const");
|
|
} else if let Some(ref asyn) = clo.asyncness {
|
|
syn_error!(asyn, "cannot be async");
|
|
} else if let Some(ref capt) = clo.capture {
|
|
syn_error!(capt, "cannot be move");
|
|
} else if let ReturnType::Type(_, ref ty) = clo.output {
|
|
syn_error!(ty, "cannot have return type");
|
|
}
|
|
|
|
cx.is_ret().then(|| f.write("return"));
|
|
f.write("function(");
|
|
generate_punctuated_pat(f, &clo.inputs)?;
|
|
f.write(")");
|
|
generate_block(f, &wrap_expr_block(&clo.body), Context::stmt(true))?;
|
|
f.write("end");
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_expr_continue(f: &mut Formatter, cont: &ExprContinue, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(cont);
|
|
syn_assert!(cx.is_stmt(), cont, "continue must be in statement position");
|
|
match cont.label {
|
|
Some(ref label) => f.write(format_args!("goto {}_cnt", label.ident.unraw())),
|
|
None => syn_error!(cont, "continue requires a label"),
|
|
};
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_expr_field(f: &mut Formatter, field: &ExprField, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(field);
|
|
syn_assert!(cx.is_value(), field, "must be in expression position");
|
|
cx.is_ret().then(|| f.write("return"));
|
|
generate_expr(f, &field.base, Context::expr(false))?;
|
|
match field.member {
|
|
Member::Named(ref ident) => {
|
|
f.write(".");
|
|
generate_ident(f, ident)?;
|
|
}
|
|
Member::Unnamed(ref index) => {
|
|
f.write("[");
|
|
generate_index(f, index)?;
|
|
f.write("]");
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_expr_forloop(f: &mut Formatter, fo: &ExprForLoop, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(fo);
|
|
syn_assert!(cx.is_stmt(), fo, "for loop must be in statement position");
|
|
f.write("for");
|
|
match *fo.expr {
|
|
Expr::Range(ref range) => {
|
|
assert_no_attrs!(range);
|
|
// TODO: reverse ranges
|
|
let start = match range.start {
|
|
Some(ref start) => (**start).clone(),
|
|
None => parse_quote!(0),
|
|
};
|
|
let end = match range.end {
|
|
Some(ref end) => match range.limits {
|
|
RangeLimits::HalfOpen(_) => parse_quote!(#end - 1),
|
|
RangeLimits::Closed(_) => (**end).clone(),
|
|
},
|
|
None => syn_error!(range, "end of range must be specified"),
|
|
};
|
|
generate_pat(f, &fo.pat, PatContext::Single)?;
|
|
f.write("=");
|
|
generate_expr(f, &start, Context::expr(false))?;
|
|
f.write(",");
|
|
generate_expr(f, &end, Context::expr(false))?;
|
|
}
|
|
ref expr => {
|
|
generate_pat(f, &fo.pat, PatContext::Multi)?;
|
|
f.write("in");
|
|
generate_expr(f, expr, Context::expr(true))?;
|
|
}
|
|
}
|
|
f.write("do");
|
|
generate_block(f, &fo.body, Context::stmt(false))?;
|
|
if let Some(ref label) = fo.label {
|
|
generate_label_continue(f, label)?;
|
|
}
|
|
f.write("end");
|
|
if let Some(ref label) = fo.label {
|
|
generate_label_break(f, label)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_expr_if(f: &mut Formatter, mut xif: &ExprIf, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(xif);
|
|
syn_assert!(cx.is_stmt(), xif, "if must be in statement position");
|
|
f.write("if");
|
|
loop {
|
|
generate_expr(f, &xif.cond, Context::expr(false))?;
|
|
f.write("then");
|
|
generate_block(f, &xif.then_branch, cx)?;
|
|
if let Some((_, ref expr)) = xif.else_branch {
|
|
match **expr {
|
|
Expr::If(ref elseif) => {
|
|
f.write("elseif");
|
|
xif = elseif;
|
|
continue;
|
|
}
|
|
ref els => {
|
|
f.write("else");
|
|
generate_block(f, &wrap_expr_block(els), cx)?;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
f.write("end");
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_expr_index(f: &mut Formatter, index: &ExprIndex, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(index);
|
|
syn_assert!(cx.is_value(), index, "must be in expression position");
|
|
cx.is_ret().then(|| f.write("return"));
|
|
generate_expr(f, &index.expr, Context::expr(false))?;
|
|
f.write("[");
|
|
generate_expr(f, &index.index, Context::expr(false))?;
|
|
f.write("]");
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_expr_infer(f: &mut Formatter, infer: &ExprInfer, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(infer);
|
|
syn_assert!(cx.is_value(), infer, "must be in expression position");
|
|
cx.is_ret().then(|| f.write("return"));
|
|
f.write("_");
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_expr_lit(f: &mut Formatter, lit: &ExprLit, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(lit);
|
|
syn_assert!(cx.is_value(), lit, "literal must be in expression position");
|
|
cx.is_ret().then(|| f.write("return"));
|
|
generate_lit(f, &lit.lit)
|
|
}
|
|
|
|
fn generate_expr_loop(f: &mut Formatter, lo: &ExprLoop, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(lo);
|
|
syn_assert!(cx.is_stmt(), lo, "loop must be in statement position");
|
|
f.write("while true do");
|
|
generate_block(f, &lo.body, Context::stmt(false))?;
|
|
if let Some(ref label) = lo.label {
|
|
generate_label_continue(f, label)?;
|
|
}
|
|
f.write("end");
|
|
if let Some(ref label) = lo.label {
|
|
generate_label_break(f, label)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_expr_macro(f: &mut Formatter, mac: &ExprMacro, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(mac);
|
|
generate_macro(f, &mac.mac, cx)
|
|
}
|
|
|
|
fn generate_expr_method_call(f: &mut Formatter, call: &ExprMethodCall, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(call);
|
|
|
|
if let Some(ref fish) = call.turbofish {
|
|
syn_error!(fish, "cannot be generic");
|
|
}
|
|
|
|
cx.is_ret().then(|| f.write("return"));
|
|
generate_expr(f, &call.receiver, Context::expr(false))?;
|
|
f.write(":");
|
|
generate_ident(f, &call.method)?;
|
|
f.write("(");
|
|
generate_punctuated_expr(f, &call.args)?;
|
|
f.write(")");
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_expr_paren(f: &mut Formatter, paren: &ExprParen, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(paren);
|
|
syn_assert!(cx.is_value(), paren, "must be in expression position");
|
|
cx.is_ret().then(|| f.write("return"));
|
|
f.write("(");
|
|
generate_expr(f, &paren.expr, Context::expr(false))?;
|
|
f.write(")");
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_expr_path(f: &mut Formatter, path: &ExprPath, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(path);
|
|
syn_assert!(cx.is_value(), path, "must be in expression position");
|
|
cx.is_ret().then(|| f.write("return"));
|
|
match path.qself {
|
|
Some(ref qself) => syn_error!(qself, "cannot be generic"),
|
|
None => generate_path(f, &path.path),
|
|
}
|
|
}
|
|
|
|
fn generate_expr_return(f: &mut Formatter, ret: &ExprReturn, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(ret);
|
|
syn_assert!(cx.is_stmt(), ret, "return must be in statement position");
|
|
f.write("return");
|
|
match ret.expr {
|
|
Some(ref value) => generate_expr(f, value, Context::expr(true)),
|
|
None => Ok(()),
|
|
}
|
|
}
|
|
|
|
fn generate_expr_tuple(f: &mut Formatter, tuple: &ExprTuple, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(tuple);
|
|
syn_assert!(cx.is_value(), tuple, "tuple must be in expression position");
|
|
cx.is_ret().then(|| f.write("return"));
|
|
match tuple.elems.len() {
|
|
0 => {
|
|
f.write("nil");
|
|
Ok(())
|
|
}
|
|
_ if cx.is_ret() || cx.is_multi_expr() => generate_punctuated_expr(f, &tuple.elems),
|
|
_ => syn_error!(tuple, "expected single-valued expression"),
|
|
}
|
|
}
|
|
|
|
fn generate_expr_unary(f: &mut Formatter, un: &ExprUnary, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(un);
|
|
syn_assert!(cx.is_value(), un, "must be in expression position");
|
|
cx.is_ret().then(|| f.write("return"));
|
|
|
|
let cx = Context::expr(false);
|
|
|
|
macro_rules! un_op {
|
|
($op:expr) => {{
|
|
f.write($op);
|
|
generate_expr(f, &un.expr, cx)?;
|
|
}};
|
|
}
|
|
|
|
match un.op {
|
|
UnOp::Not(_) => un_op!("not"),
|
|
UnOp::Neg(_) => un_op!("-"),
|
|
op => syn_error!(op, "unsupported unary operator"),
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_expr_while(f: &mut Formatter, whil: &ExprWhile, cx: Context) -> Result<()> {
|
|
assert_no_attrs!(whil);
|
|
syn_assert!(cx.is_stmt(), whil, "while must be in statement position");
|
|
f.write("while");
|
|
generate_expr(f, &whil.cond, Context::expr(false))?;
|
|
f.write("do");
|
|
generate_block(f, &whil.body, Context::stmt(false))?;
|
|
if let Some(ref label) = whil.label {
|
|
generate_label_continue(f, label)?;
|
|
}
|
|
f.write("end");
|
|
if let Some(ref label) = whil.label {
|
|
generate_label_break(f, label)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_punctuated_expr(f: &mut Formatter, exprs: &Punctuated<Expr, Token![,]>) -> Result<()> {
|
|
let len = exprs.len();
|
|
for (i, expr) in exprs.iter().enumerate() {
|
|
(i != 0).then(|| f.write(","));
|
|
generate_expr(f, expr, Context::expr(i == len - 1))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_ident(f: &mut Formatter, ident: &Ident) -> Result<()> {
|
|
// https://www.lua.org/manual/5.2/manual.html#3.1
|
|
let name = format!("{}", ident.unraw());
|
|
match name.as_str() {
|
|
"and" | "break" | "do" | "else" | "elseif" | "end" | "false" | "for" | "function"
|
|
| "goto" | "if" | "in" | "local" | "nil" | "not" | "or" | "repeat" | "return" | "then"
|
|
| "true" | "until" | "while" => {
|
|
syn_error!(ident, "'{name}' cannot be used as an identifier")
|
|
}
|
|
s => f.write(s),
|
|
};
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_index(f: &mut Formatter, index: &Index) -> Result<()> {
|
|
f.write(index.index);
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_label_continue(f: &mut Formatter, label: &Label) -> Result<()> {
|
|
f.write(format_args!("::{}_cnt::", label.name.ident.unraw()));
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_label_break(f: &mut Formatter, label: &Label) -> Result<()> {
|
|
f.write(format_args!("::{}_brk::", label.name.ident.unraw()));
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_lit(f: &mut Formatter, lit: &Lit) -> Result<()> {
|
|
match lit {
|
|
Lit::Bool(b) => generate_lit_bool(f, b),
|
|
Lit::Byte(b) => generate_lit_byte(f, b),
|
|
Lit::Int(n) => generate_lit_int(f, n),
|
|
Lit::Float(n) => generate_lit_float(f, n),
|
|
Lit::Str(s) => generate_lit_str(f, s),
|
|
Lit::ByteStr(s) => generate_lit_byte_str(f, s),
|
|
Lit::CStr(s) => generate_lit_cstr(f, s),
|
|
lit => syn_error!(lit, "unsupported literal"),
|
|
}
|
|
}
|
|
|
|
fn generate_lit_bool(f: &mut Formatter, b: &LitBool) -> Result<()> {
|
|
f.write(b.value());
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_lit_byte(f: &mut Formatter, b: &LitByte) -> Result<()> {
|
|
assert_no_suffix!(b);
|
|
f.write(b.value());
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_lit_int(f: &mut Formatter, n: &LitInt) -> Result<()> {
|
|
assert_no_suffix!(n);
|
|
f.write(n.base10_parse::<f64>()?);
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_lit_float(f: &mut Formatter, n: &LitFloat) -> Result<()> {
|
|
assert_no_suffix!(n);
|
|
f.write(n.base10_parse::<f64>()?);
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_lit_str(f: &mut Formatter, s: &LitStr) -> Result<()> {
|
|
assert_no_suffix!(s);
|
|
f.write(format!(r#""{}""#, escape_str(s.value())));
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_lit_byte_str(f: &mut Formatter, s: &LitByteStr) -> Result<()> {
|
|
assert_no_suffix!(s);
|
|
f.write(format!(r#""{}""#, escape_str(s.value())));
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_lit_cstr(f: &mut Formatter, s: &LitCStr) -> Result<()> {
|
|
assert_no_suffix!(s);
|
|
f.write(format!(r#""{}\0""#, escape_str(s.value().as_bytes())));
|
|
Ok(())
|
|
}
|
|
|
|
fn escape_str(s: impl AsRef<[u8]>) -> String {
|
|
// this produces an escaped string with \xNN hexadecimal notation which lua 5.1 normally
|
|
// wouldn't understand, but luajit supports this unconditionally as an extension
|
|
// https://docs.rs/bstr/latest/bstr/trait.ByteSlice.html#method.escape_bytes
|
|
// https://luajit.org/extensions.html#lua52
|
|
String::from_utf8(
|
|
s.as_ref()
|
|
.iter()
|
|
.flat_map(|b| std::ascii::escape_default(*b))
|
|
.collect(),
|
|
)
|
|
.unwrap()
|
|
}
|
|
|
|
fn generate_path(f: &mut Formatter, path: &Path) -> Result<()> {
|
|
for (i, segment) in path.segments.iter().enumerate() {
|
|
(i != 0).then(|| f.write("."));
|
|
generate_path_segment(f, segment)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_path_segment(f: &mut Formatter, seg: &PathSegment) -> Result<()> {
|
|
match seg.arguments {
|
|
PathArguments::AngleBracketed(ref arg) => syn_error!(arg, "cannot be generic"),
|
|
PathArguments::Parenthesized(ref arg) => syn_error!(arg, "cannot be generic"),
|
|
PathArguments::None => generate_ident(f, &seg.ident),
|
|
}
|
|
}
|
|
|
|
fn generate_block(f: &mut Formatter, block: &Block, cx: Context) -> Result<()> {
|
|
let len = block.stmts.len();
|
|
for (i, stmt) in block.stmts.iter().enumerate() {
|
|
match stmt {
|
|
Stmt::Local(local) => generate_local(f, local),
|
|
Stmt::Item(item) => generate_item(f, item),
|
|
Stmt::Expr(expr, semi) => match semi {
|
|
None if i == len - 1 && cx.is_ret() => generate_expr(f, expr, Context::stmt(true)),
|
|
_ => generate_expr(f, expr, Context::stmt(false)),
|
|
},
|
|
Stmt::Macro(smac) => generate_macro(f, &smac.mac, cx),
|
|
}?;
|
|
f.write(";");
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_local(f: &mut Formatter, local: &Local) -> Result<()> {
|
|
assert_no_attrs!(local);
|
|
f.write("local");
|
|
generate_pat(f, &local.pat, PatContext::Multi)?;
|
|
match local.init {
|
|
Some(ref init) => generate_local_init(f, init),
|
|
None => Ok(()),
|
|
}
|
|
}
|
|
|
|
fn generate_local_init(f: &mut Formatter, init: &LocalInit) -> Result<()> {
|
|
match init.diverge {
|
|
Some((ref token, _)) => syn_error!(token, "let-else is not supported"),
|
|
None => {
|
|
f.write("=");
|
|
generate_expr(f, &init.expr, Context::expr(true))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn generate_item(f: &mut Formatter, item: &Item) -> Result<()> {
|
|
match item {
|
|
Item::Fn(func) => generate_item_fn(f, func),
|
|
item => syn_error!(item, "unsupported item"),
|
|
}
|
|
}
|
|
|
|
fn generate_item_fn(f: &mut Formatter, func: &ItemFn) -> Result<()> {
|
|
assert_no_attrs!(func);
|
|
let Visibility::Inherited = func.vis else {
|
|
syn_error!(func, "cannot have visibility");
|
|
};
|
|
f.write("local");
|
|
generate_signature(f, &func.sig)?;
|
|
generate_block(f, &func.block, Context::stmt(true))?;
|
|
f.write("end");
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_signature(f: &mut Formatter, sig: &Signature) -> Result<()> {
|
|
if let Some(ref cons) = sig.constness {
|
|
syn_error!(cons, "cannot be const");
|
|
} else if let Some(ref asyn) = sig.asyncness {
|
|
syn_error!(asyn, "cannot be async");
|
|
} else if let Some(ref uns) = sig.unsafety {
|
|
syn_error!(uns, "cannot be unsafe");
|
|
} else if let Some(ref abi) = sig.abi {
|
|
syn_error!(abi, "cannot be extern");
|
|
} else if !sig.generics.params.is_empty() {
|
|
syn_error!(sig.generics.params, "cannot be generic");
|
|
} else if let Some(ref wher) = sig.generics.where_clause {
|
|
syn_error!(wher, "cannot have where clause");
|
|
} else if let ReturnType::Type(_, ref ty) = sig.output {
|
|
syn_error!(ty, "cannot have return type");
|
|
}
|
|
|
|
f.write("function");
|
|
generate_ident(f, &sig.ident)?;
|
|
f.write("(");
|
|
for (i, param) in sig.inputs.iter().enumerate() {
|
|
(i != 0).then(|| f.write(","));
|
|
generate_fn_arg(f, param)?;
|
|
}
|
|
f.write(")");
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_fn_arg(f: &mut Formatter, param: &FnArg) -> Result<()> {
|
|
match param {
|
|
FnArg::Receiver(recv) => generate_receiver(f, recv),
|
|
FnArg::Typed(typed) => generate_pat_typed(f, typed, PatContext::Single),
|
|
}
|
|
}
|
|
|
|
fn generate_receiver(f: &mut Formatter, recv: &Receiver) -> Result<()> {
|
|
assert_no_attrs!(recv);
|
|
syn_assert!(recv.colon_token.is_none(), recv, "must be `self`");
|
|
if let Some(ref l) = recv.lifetime() {
|
|
syn_error!(l, "cannot have lifetimes");
|
|
} else if let Some(ref m) = recv.mutability {
|
|
syn_error!(m, "cannot be mut (implicitly mutable)");
|
|
}
|
|
f.write("self");
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_macro(f: &mut Formatter, mac: &Macro, cx: Context) -> Result<()> {
|
|
match format!("{}", mac.path.require_ident()?).as_str() {
|
|
"concat" => generate_macro_concat(f, mac, cx),
|
|
"len" => generate_macro_len(f, mac, cx),
|
|
"variadic" => generate_macro_variadic(f, mac, cx),
|
|
"embed" => generate_macro_embed(f, mac, cx),
|
|
"raw" => generate_macro_raw(f, mac, cx),
|
|
name => syn_error!(mac.path, "unknown macro '{name}'"),
|
|
}
|
|
}
|
|
|
|
fn generate_macro_concat(f: &mut Formatter, mac: &Macro, cx: Context) -> Result<()> {
|
|
syn_assert!(cx.is_value(), mac, "concat! must be in expression position");
|
|
cx.is_ret().then(|| f.write("return"));
|
|
let args = mac.parse_body_with(<Punctuated<Expr, Token![,]>>::parse_terminated)?;
|
|
syn_assert!(!args.is_empty(), mac, "expected at least one argument");
|
|
for (i, arg) in args.iter().enumerate() {
|
|
(i != 0).then(|| f.write(".."));
|
|
generate_expr(f, arg, Context::expr(false))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_macro_len(f: &mut Formatter, mac: &Macro, cx: Context) -> Result<()> {
|
|
syn_assert!(cx.is_value(), mac, "len! must be in expression position");
|
|
cx.is_ret().then(|| f.write("return"));
|
|
f.write("#");
|
|
generate_expr(f, &mac.parse_body()?, Context::expr(false))
|
|
}
|
|
|
|
fn generate_macro_variadic(f: &mut Formatter, mac: &Macro, cx: Context) -> Result<()> {
|
|
syn_assert!(
|
|
cx.is_multi_expr(),
|
|
mac,
|
|
"variadic! must be in multi-value expression position"
|
|
);
|
|
cx.is_ret().then(|| f.write("return"));
|
|
f.write("...");
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_macro_embed(f: &mut Formatter, mac: &Macro, cx: Context) -> Result<()> {
|
|
syn_assert!(cx.is_value(), mac, "embed! must be in expression position");
|
|
cx.is_ret().then(|| f.write("return"));
|
|
let arg = mac.parse_body::<Expr>()?;
|
|
f.write("\"");
|
|
f.interpolate(parse_quote! {
|
|
str::replace(&str::replace(&::std::format!("{}", #arg), "\\", "\\\\"), "\"", "\\\"")
|
|
});
|
|
f.write("\"");
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_macro_raw(f: &mut Formatter, mac: &Macro, _cx: Context) -> Result<()> {
|
|
f.interpolate(mac.parse_body()?);
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
enum PatContext {
|
|
Single,
|
|
Multi,
|
|
}
|
|
|
|
impl PatContext {
|
|
fn _is_single(&self) -> bool {
|
|
matches!(self, Self::Single)
|
|
}
|
|
|
|
fn is_multi(&self) -> bool {
|
|
matches!(self, Self::Multi)
|
|
}
|
|
}
|
|
|
|
fn generate_pat(f: &mut Formatter, pat: &Pat, cx: PatContext) -> Result<()> {
|
|
match pat {
|
|
Pat::Ident(ident) => generate_pat_ident(f, ident, cx),
|
|
Pat::Macro(mac) => generate_pat_macro(f, mac, cx),
|
|
Pat::Tuple(tuple) => generate_pat_tuple(f, tuple, cx),
|
|
Pat::Type(typed) => generate_pat_typed(f, typed, cx),
|
|
Pat::Wild(wild) => generate_pat_wild(f, wild, cx),
|
|
pat => syn_error!(pat, "unsupported pattern"),
|
|
}
|
|
}
|
|
|
|
fn generate_pat_ident(f: &mut Formatter, ident: &PatIdent, _cx: PatContext) -> Result<()> {
|
|
assert_no_attrs!(ident);
|
|
|
|
if let Some(ref r) = ident.by_ref {
|
|
syn_error!(r, "cannot be ref");
|
|
} else if let Some(ref m) = ident.mutability {
|
|
syn_error!(m, "cannot be mut (implicitly mutable)");
|
|
} else if let Some((_, ref pat)) = ident.subpat {
|
|
syn_error!(pat, "subpatterns are not supported");
|
|
}
|
|
|
|
generate_ident(f, &ident.ident)
|
|
}
|
|
|
|
fn generate_pat_macro(f: &mut Formatter, mac: &PatMacro, cx: PatContext) -> Result<()> {
|
|
assert_no_attrs!(mac);
|
|
generate_macro(f, &mac.mac, Context::expr(cx.is_multi()))
|
|
}
|
|
|
|
fn generate_pat_tuple(f: &mut Formatter, tuple: &PatTuple, cx: PatContext) -> Result<()> {
|
|
assert_no_attrs!(tuple);
|
|
match tuple.elems.len() {
|
|
0 => syn_error!(tuple, "must have at least one element"),
|
|
_ if cx.is_multi() => generate_punctuated_pat(f, &tuple.elems),
|
|
_ => syn_error!(tuple, "expected single-valued pattern"),
|
|
}
|
|
}
|
|
|
|
fn generate_pat_typed(f: &mut Formatter, typed: &PatType, cx: PatContext) -> Result<()> {
|
|
assert_no_attrs!(typed);
|
|
match *typed.ty {
|
|
Type::Infer(_) => generate_pat(f, &typed.pat, cx),
|
|
ref ty => syn_error!(ty, "cannot have type"),
|
|
}
|
|
}
|
|
|
|
fn generate_pat_wild(f: &mut Formatter, wild: &PatWild, _cx: PatContext) -> Result<()> {
|
|
assert_no_attrs!(wild);
|
|
f.write("_");
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_punctuated_pat(f: &mut Formatter, pats: &Punctuated<Pat, Token![,]>) -> Result<()> {
|
|
let len = pats.len();
|
|
for (i, pat) in pats.iter().enumerate() {
|
|
(i != 0).then(|| f.write(","));
|
|
let cx = if i == len - 1 {
|
|
PatContext::Multi
|
|
} else {
|
|
PatContext::Single
|
|
};
|
|
generate_pat(f, pat, cx)?;
|
|
}
|
|
Ok(())
|
|
}
|