Initial commit
This commit is contained in:
commit
f09f27f553
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
55
Cargo.lock
generated
Normal file
55
Cargo.lock
generated
Normal file
@ -0,0 +1,55 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "luaffi"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "luaify"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "luby"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[workspace]
|
||||
members = ["crates/luaffi", "crates/luaify"]
|
||||
|
||||
[package]
|
||||
name = "luby"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
6
crates/luaffi/Cargo.toml
Normal file
6
crates/luaffi/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "luaffi"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
14
crates/luaffi/src/lib.rs
Normal file
14
crates/luaffi/src/lib.rs
Normal file
@ -0,0 +1,14 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
12
crates/luaify/Cargo.toml
Normal file
12
crates/luaify/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "luaify"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.95"
|
||||
quote = "1.0.40"
|
||||
syn = { version = "2.0.103", features = ["full", "visit-mut"] }
|
861
crates/luaify/src/generate.rs
Normal file
861
crates/luaify/src/generate.rs
Normal file
@ -0,0 +1,861 @@
|
||||
use crate::utils::{syn_assert, syn_error, wrap_expr_block};
|
||||
use std::fmt::Display;
|
||||
use syn::{ext::*, punctuated::*, spanned::*, *};
|
||||
|
||||
pub fn generate(expr: &Expr) -> Result<String> {
|
||||
let mut f = Formatter::default();
|
||||
generate_expr(&mut f, expr, Context::expr(true))?;
|
||||
f.done()
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct Formatter {
|
||||
buf: String,
|
||||
space: bool,
|
||||
}
|
||||
|
||||
impl Formatter {
|
||||
fn write(&mut self, s: impl Display) -> &mut Self {
|
||||
fn sep(c: char) -> bool {
|
||||
match c {
|
||||
'(' | ')' | '[' | ']' | '+' | '-' | '*' | '/' | '%' | '^' | '#' | '=' | '~'
|
||||
| '<' | '>' | ':' | ';' | '.' | ',' | '\'' | '"' | ' ' => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
let s = format!("{s}");
|
||||
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 done(self) -> Result<String> {
|
||||
Ok(self.buf)
|
||||
}
|
||||
}
|
||||
|
||||
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(false))?;
|
||||
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::Lt(_)
|
||||
| BinOp::Le(_)
|
||||
| BinOp::Ne(_)
|
||||
| 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!("math.fmod"),
|
||||
BinOp::RemAssign(_) => assign_call_op!("math.fmod"),
|
||||
BinOp::BitAnd(_) => call_op!("bit.band"),
|
||||
BinOp::BitAndAssign(_) => assign_call_op!("bit.band"),
|
||||
BinOp::BitOr(_) => call_op!("bit.bor"),
|
||||
BinOp::BitOrAssign(_) => assign_call_op!("bit.bor"),
|
||||
BinOp::BitXor(_) => call_op!("bit.bxor"),
|
||||
BinOp::BitXorAssign(_) => assign_call_op!("bit.bxor"),
|
||||
BinOp::Shl(_) => call_op!("bit.lshift"),
|
||||
BinOp::ShlAssign(_) => assign_call_op!("bit.lshift"),
|
||||
BinOp::Shr(_) => call_op!("bit.arshift"),
|
||||
BinOp::ShrAssign(_) => assign_call_op!("bit.arshift"),
|
||||
BinOp::Eq(_) => bin_op!("=="),
|
||||
BinOp::Lt(_) => bin_op!("<"),
|
||||
BinOp::Le(_) => bin_op!("<="),
|
||||
BinOp::Ne(_) => 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_body(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_body(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_body(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_body(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_body(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_body(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_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_body(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<()> {
|
||||
for (i, expr) in exprs.iter().enumerate() {
|
||||
(i != 0).then(|| f.write(","));
|
||||
generate_expr(f, expr, Context::expr(false))?;
|
||||
}
|
||||
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_body(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_body(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),
|
||||
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, "must be in expression position");
|
||||
cx.is_ret().then(|| f.write("return"));
|
||||
let args = mac.parse_body_with(<Punctuated<Expr, Token![,]>>::parse_terminated)?;
|
||||
if args.is_empty() {
|
||||
syn_error!(mac.path, "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(())
|
||||
}
|
||||
|
||||
#[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::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_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<()> {
|
||||
for (i, pat) in pats.iter().enumerate() {
|
||||
(i != 0).then(|| f.write(","));
|
||||
generate_pat(f, pat, PatContext::Single)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
36
crates/luaify/src/lib.rs
Normal file
36
crates/luaify/src/lib.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use crate::{generate::generate, transform::transform};
|
||||
use proc_macro::TokenStream as TokenStream1;
|
||||
use quote::{ToTokens, quote};
|
||||
use syn::parse_macro_input;
|
||||
|
||||
mod generate;
|
||||
mod transform;
|
||||
mod utils;
|
||||
|
||||
#[proc_macro]
|
||||
pub fn luaify(input: TokenStream1) -> TokenStream1 {
|
||||
let mut expr = parse_macro_input!(input);
|
||||
match transform(&mut expr).and_then(|()| generate(&expr)) {
|
||||
Ok(s) => quote!(#s).into_token_stream(),
|
||||
Err(err) => err.into_compile_error().into_token_stream(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
// #[derive(Debug)]
|
||||
// struct TypeChecker {
|
||||
// checks: Vec<(Ident, LuaType)>,
|
||||
// }
|
||||
|
||||
// impl TypeChecker {
|
||||
// fn generate(&self, f: &mut Formatter) -> syn::Result<()> {
|
||||
// for (ident, ty) in self.checks.iter() {
|
||||
// generate_type_check(f, ident, *ty)?;
|
||||
// }
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn generate_type_check(f: &mut Formatter, ident: &Ident, ty: LuaType) -> syn::Result<()> {
|
||||
// Ok(())
|
||||
// }
|
231
crates/luaify/src/transform.rs
Normal file
231
crates/luaify/src/transform.rs
Normal file
@ -0,0 +1,231 @@
|
||||
use std::mem;
|
||||
|
||||
use crate::utils::{LuaType, syn_error, unwrap_expr_ident, unwrap_pat_ident, wrap_expr_block};
|
||||
use syn::{spanned::*, visit_mut::*, *};
|
||||
|
||||
pub fn transform(expr: &mut Expr) -> Result<()> {
|
||||
let mut visitor = Visitor::new();
|
||||
visitor.visit_expr_mut(expr);
|
||||
visitor.result
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Visitor {
|
||||
result: Result<()>,
|
||||
}
|
||||
|
||||
impl Visitor {
|
||||
fn new() -> Self {
|
||||
Self { result: Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitMut for Visitor {
|
||||
fn visit_expr_closure_mut(&mut self, clo: &mut ExprClosure) {
|
||||
match self.transform_expr_closure(clo) {
|
||||
res @ Err(_) => self.result = res,
|
||||
_ => visit_expr_closure_mut(self, clo),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_item_fn_mut(&mut self, func: &mut ItemFn) {
|
||||
match self.transform_function(func) {
|
||||
res @ Err(_) => self.result = res,
|
||||
_ => visit_item_fn_mut(self, func),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr_mut(&mut self, expr: &mut Expr) {
|
||||
match self.transform_expr(expr) {
|
||||
res @ Err(_) => self.result = res,
|
||||
_ => visit_expr_mut(self, expr),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr_unary_mut(&mut self, un: &mut ExprUnary) {
|
||||
match self.transform_unary(un) {
|
||||
res @ Err(_) => self.result = res,
|
||||
_ => visit_expr_unary_mut(self, un),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr_match_mut(&mut self, mat: &mut ExprMatch) {
|
||||
match self.transform_match(mat) {
|
||||
res @ Err(_) => self.result = res,
|
||||
_ => visit_expr_match_mut(self, mat),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Visitor {
|
||||
fn transform_expr_closure(&mut self, clo: &mut ExprClosure) -> Result<()> {
|
||||
//
|
||||
// transforms a closure expression with input type annotations by removing the annotations
|
||||
// and inserting `as` casts at the start.
|
||||
//
|
||||
// before:
|
||||
// |a: string, b: number| { ... }
|
||||
// after:
|
||||
// |a, b| { a as string; b as number; ... }
|
||||
//
|
||||
let mut checks: Vec<Stmt> = vec![];
|
||||
for input in clo.inputs.iter_mut() {
|
||||
match input {
|
||||
Pat::Ident(_) => {}
|
||||
Pat::Type(typed) => {
|
||||
let ident = unwrap_pat_ident(&typed.pat)?;
|
||||
let ty = mem::replace(&mut typed.ty, parse_quote!(_));
|
||||
match (&*ty).try_into()? {
|
||||
LuaType::Any => {}
|
||||
_ => checks.push(parse_quote! { #ident as #ty; }),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if !checks.is_empty() {
|
||||
let mut body = wrap_expr_block(&clo.body);
|
||||
body.stmts.splice(..0, checks);
|
||||
clo.body = Box::new(parse_quote! { #body });
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_function(&mut self, func: &mut ItemFn) -> Result<()> {
|
||||
//
|
||||
// transforms a function item with input type annotations by removing the annotations
|
||||
// and inserting `as` casts at the start.
|
||||
//
|
||||
// before:
|
||||
// fn my_func(self: table, a: string) { ... }
|
||||
// after:
|
||||
// fn my_func(self: _, a: _) { self as table; a as string; ... }
|
||||
//
|
||||
let mut checks: Vec<Stmt> = vec![];
|
||||
for input in func.sig.inputs.iter_mut() {
|
||||
if let Some((ident, ty)) = match input {
|
||||
FnArg::Receiver(recv) if recv.colon_token.is_some() => {
|
||||
let ty = mem::replace(&mut recv.ty, parse_quote!(_));
|
||||
recv.colon_token = None;
|
||||
Some((Ident::new("self", recv.self_token.span()), ty))
|
||||
}
|
||||
FnArg::Typed(typed) => {
|
||||
let ident = unwrap_pat_ident(&typed.pat)?;
|
||||
let ty = mem::replace(&mut typed.ty, parse_quote!(_));
|
||||
Some((ident, ty))
|
||||
}
|
||||
_ => None,
|
||||
} {
|
||||
match (&*ty).try_into()? {
|
||||
LuaType::Any => {}
|
||||
_ => checks.push(parse_quote! { #ident as #ty; }),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
func.block.stmts.splice(..0, checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_expr(&mut self, expr: &mut Expr) -> Result<()> {
|
||||
self.transform_expr_cast(expr)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_expr_cast(&mut self, expr: &mut Expr) -> Result<()> {
|
||||
//
|
||||
// transforms an `as` cast expression into a block expression containing a runtime
|
||||
// lua type check.
|
||||
//
|
||||
// before:
|
||||
// var as string
|
||||
// after:
|
||||
// { if type(var) != "string" { error(...) } }
|
||||
//
|
||||
if let Expr::Cast(cast) = expr {
|
||||
let arg = (*cast.expr).clone();
|
||||
let mut init: Option<Stmt> = None;
|
||||
let ty: LuaType = (&*cast.ty).try_into()?;
|
||||
let ty_str = format!("{ty}");
|
||||
let (ident, msg) = match unwrap_expr_ident(&arg).ok() {
|
||||
Some(ident) => (ident.clone(), format!("{ty} expected in '{ident}', got ")),
|
||||
None => {
|
||||
let ident = Ident::new("_", arg.span());
|
||||
init = Some(parse_quote! { let #ident = #arg; });
|
||||
(ident, format!("{ty} expected, got "))
|
||||
}
|
||||
};
|
||||
|
||||
let tmp = Ident::new(&format!("_{ident}"), ident.span());
|
||||
let span = cast.span();
|
||||
*expr = match ty {
|
||||
LuaType::Any => parse_quote_spanned!(span => {}),
|
||||
LuaType::Nil => parse_quote_spanned!(span => {
|
||||
#init
|
||||
if #ident != () {
|
||||
return error(concat!(#msg, r#type(#ident)));
|
||||
}
|
||||
}),
|
||||
LuaType::Number => parse_quote_spanned!(span => {
|
||||
#init
|
||||
let #tmp = #ident;
|
||||
#ident = tonumber(#ident);
|
||||
if #ident == () {
|
||||
return error(concat!(#msg, r#type(#tmp)));
|
||||
}
|
||||
}),
|
||||
LuaType::Integer => parse_quote_spanned!(span => {
|
||||
#init
|
||||
let #tmp = #ident;
|
||||
#ident = tonumber(#ident);
|
||||
if #ident == () || math::floor(#ident) != #ident {
|
||||
return error(concat!(#msg, r#type(#tmp)));
|
||||
}
|
||||
}),
|
||||
LuaType::String => parse_quote_spanned!(span => {
|
||||
#init
|
||||
if r#type(#ident) == "number" {
|
||||
#ident = tostring(#ident);
|
||||
} else if r#type(#ident) != "string" {
|
||||
return error(concat!(#msg, r#type(#ident)));
|
||||
}
|
||||
}),
|
||||
_ => parse_quote_spanned!(span => {
|
||||
#init
|
||||
if r#type(#ident) != #ty_str {
|
||||
return error(concat!(#msg, r#type(#ident)));
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_unary(&mut self, un: &mut ExprUnary) -> Result<()> {
|
||||
//
|
||||
// separates a nested negation unary operator with parentheses, because double hyphen
|
||||
// `--` indicates a comment in lua.
|
||||
//
|
||||
// before:
|
||||
// --a
|
||||
// after:
|
||||
// -(-a)
|
||||
//
|
||||
if let UnOp::Neg(_) = un.op
|
||||
&& let Expr::Unary(ref inner) = *un.expr
|
||||
&& let UnOp::Neg(_) = inner.op
|
||||
{
|
||||
un.expr = Box::new(parse_quote!((#inner)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_match(&mut self, mat: &mut ExprMatch) -> Result<()> {
|
||||
// TODO:
|
||||
syn_error!(mat, "match-to-if transformation not yet implemented");
|
||||
}
|
||||
}
|
113
crates/luaify/src/utils.rs
Normal file
113
crates/luaify/src/utils.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use std::fmt;
|
||||
use syn::{ext::*, spanned::*, *};
|
||||
|
||||
macro_rules! syn_error {
|
||||
($src:expr, $($fmt:expr),+) => {{
|
||||
return Err(syn::Error::new($src.span(), format!($($fmt),*)));
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! syn_assert {
|
||||
($cond:expr, $src:expr, $($fmt:expr),+) => {{
|
||||
if !$cond {
|
||||
syn_error!($src, $($fmt),+);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use {syn_assert, syn_error};
|
||||
|
||||
pub fn wrap_expr_block(expr: &Expr) -> Block {
|
||||
// return the expr if it's a block, otherwise wrap it in a block
|
||||
match expr {
|
||||
Expr::Block(block) if block.label.is_none() => block.block.clone(),
|
||||
expr => parse_quote!({ #expr }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_expr_ident(expr: &Expr) -> Result<&Ident> {
|
||||
match expr {
|
||||
Expr::Path(path) => path.path.require_ident(),
|
||||
_ => syn_error!(expr, "expected ident"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_pat_ident(pat: &Pat) -> Result<Ident> {
|
||||
Ok(match pat {
|
||||
Pat::Ident(ident) => match ident.subpat {
|
||||
Some((_, ref subpat)) => syn_error!(subpat, "unexpected subpattern"),
|
||||
None => ident.ident.clone(),
|
||||
},
|
||||
Pat::Wild(wild) => Ident::new("_", wild.span()),
|
||||
_ => syn_error!(pat, "expected ident"),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LuaType {
|
||||
Any,
|
||||
Nil,
|
||||
Boolean,
|
||||
Lightuserdata,
|
||||
Number,
|
||||
Integer,
|
||||
String,
|
||||
Table,
|
||||
Function,
|
||||
Userdata,
|
||||
Thread,
|
||||
Cdata,
|
||||
}
|
||||
|
||||
impl fmt::Display for LuaType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
LuaType::Any => write!(f, "any"),
|
||||
LuaType::Nil => write!(f, "nil"),
|
||||
LuaType::Boolean => write!(f, "boolean"),
|
||||
LuaType::Lightuserdata => write!(f, "lightuserdata"),
|
||||
LuaType::Number => write!(f, "number"),
|
||||
LuaType::Integer => write!(f, "integer"),
|
||||
LuaType::String => write!(f, "string"),
|
||||
LuaType::Table => write!(f, "table"),
|
||||
LuaType::Function => write!(f, "function"),
|
||||
LuaType::Userdata => write!(f, "userdata"),
|
||||
LuaType::Thread => write!(f, "thread"),
|
||||
LuaType::Cdata => write!(f, "cdata"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Ident> for LuaType {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &Ident) -> Result<Self> {
|
||||
Ok(match format!("{}", value.unraw()).as_str() {
|
||||
"any" => Self::Any,
|
||||
"nil" => Self::Nil,
|
||||
"boolean" => Self::Boolean,
|
||||
"lightuserdata" => Self::Lightuserdata,
|
||||
"number" => Self::Number,
|
||||
"integer" => Self::Integer,
|
||||
"string" => Self::String,
|
||||
"table" => Self::Table,
|
||||
"function" => Self::Function,
|
||||
"userdata" => Self::Userdata,
|
||||
"thread" => Self::Thread,
|
||||
"cdata" => Self::Cdata,
|
||||
_ => syn_error!(value, "invalid lua type"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Type> for LuaType {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &Type) -> Result<Self> {
|
||||
match value {
|
||||
Type::Infer(_) => Ok(Self::Any),
|
||||
Type::Path(path) if path.qself.is_none() => path.path.require_ident()?.try_into(),
|
||||
_ => syn_error!(value, "invalid lua type"),
|
||||
}
|
||||
}
|
||||
}
|
372
crates/luaify/tests/test.rs
Normal file
372
crates/luaify/tests/test.rs
Normal file
@ -0,0 +1,372 @@
|
||||
use luaify::luaify;
|
||||
|
||||
#[test]
|
||||
fn raw_ident() {
|
||||
assert_eq!(luaify!(r#ref), r#"ref"#);
|
||||
assert_eq!(luaify!(x.r#ref), r#"x.ref"#);
|
||||
assert_eq!(luaify!(r#mut::r#ref), r#"mut.ref"#);
|
||||
assert_eq!(luaify!(x.r#ref()), r#"x:ref()"#);
|
||||
assert_eq!(luaify!(r#mut.r#ref()), r#"mut:ref()"#);
|
||||
assert_eq!(luaify!(r#mut::r#ref()), r#"mut.ref()"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indexing() {
|
||||
assert_eq!(luaify!(table.0), r#"table[0]"#);
|
||||
assert_eq!(luaify!(table[0]), r#"table[0]"#);
|
||||
assert_eq!(luaify!(table["0"]), r#"table["0"]"#);
|
||||
assert_eq!(luaify!(table.field), r#"table.field"#);
|
||||
assert_eq!(luaify!(table.field.nested), r#"table.field.nested"#);
|
||||
assert_eq!(luaify!(table::field.nested), r#"table.field.nested"#);
|
||||
assert_eq!(luaify!(table::field::nested), r#"table.field.nested"#);
|
||||
assert_eq!(luaify!(table::field["nested"]), r#"table.field["nested"]"#);
|
||||
assert_eq!(luaify!(table.0.nested), r#"table[0].nested"#);
|
||||
assert_eq!(luaify!(table.0["nested"]), r#"table[0]["nested"]"#);
|
||||
assert_eq!(luaify!(table[0].nested), r#"table[0].nested"#);
|
||||
assert_eq!(luaify!(table[0]["nested"]), r#"table[0]["nested"]"#);
|
||||
assert_eq!(luaify!(table["field"].nested), r#"table["field"].nested"#);
|
||||
|
||||
assert_eq!(luaify!(table[field]), r#"table[field]"#);
|
||||
assert_eq!(luaify!(table[x.y]), r#"table[x.y]"#);
|
||||
assert_eq!(luaify!(table[x.1.y.2]), r#"table[x[1].y[2]]"#);
|
||||
assert_eq!(luaify!(table[x.1.y[2]]), r#"table[x[1].y[2]]"#);
|
||||
assert_eq!(luaify!(table[x[1].y.2]), r#"table[x[1].y[2]]"#);
|
||||
assert_eq!(luaify!(table[x[1].y[2]]), r#"table[x[1].y[2]]"#);
|
||||
|
||||
assert_eq!(luaify!((x.y)[z.w]), r#"(x.y)[z.w]"#);
|
||||
assert_eq!(luaify!((x[y])[z.w]), r#"(x[y])[z.w]"#);
|
||||
assert_eq!(luaify!((x["y"])[z.w]), r#"(x["y"])[z.w]"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locals() {
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
let x = 30;
|
||||
}),
|
||||
r#"function()local x=30;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
let (x, y, z) = (1, 2, 3);
|
||||
}),
|
||||
r#"function()local x,y,z=1,2,3;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
let (a, b, c) = (1, call(), call2(), 4);
|
||||
return (a, b, c);
|
||||
}),
|
||||
r#"function()local a,b,c=1,call(),call2(),4;return a,b,c;end"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_fn() {
|
||||
assert_eq!(
|
||||
luaify!(|a, b| {
|
||||
fn inner(c: _, d: _) {}
|
||||
inner
|
||||
}),
|
||||
r#"function(a,b)local function inner(c,d)end;return inner;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
fn check(self: string, arg: number) {}
|
||||
inner
|
||||
}),
|
||||
r#"function()local function check(self,arg)do if type(self)=="number"then self=tostring(self);elseif type(self)~="string"then return error("string expected in \'self\', got "..type(self));end;end;do local _arg=arg;arg=tonumber(arg);if arg==nil then return error("number expected in \'arg\', got "..type(_arg));end;end;end;return inner;end"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call() {
|
||||
assert_eq!(luaify!(table.field()), r#"table:field()"#);
|
||||
assert_eq!(luaify!(table::field()), r#"table.field()"#);
|
||||
assert_eq!(luaify!(table[field]()), r#"table[field]()"#);
|
||||
assert_eq!(luaify!(a::b.c.field()), r#"a.b.c:field()"#);
|
||||
assert_eq!(luaify!(a::b::c.field()), r#"a.b.c:field()"#);
|
||||
assert_eq!(luaify!(a::b::c::field()), r#"a.b.c.field()"#);
|
||||
assert_eq!(
|
||||
luaify!(coroutine::r#yield("string")),
|
||||
r#"coroutine.yield("string")"#
|
||||
);
|
||||
|
||||
assert_eq!(luaify!(call(a(), (b)(), c.d())), r#"call(a(),(b)(),c:d())"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closure() {
|
||||
assert_eq!(luaify!(|| hello), r#"function()return hello;end"#);
|
||||
assert_eq!(luaify!(|| { hello }), r#"function()return hello;end"#);
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
return hello;
|
||||
}),
|
||||
r#"function()return hello;end"#
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
luaify!(|| { { hello } }),
|
||||
r#"function()do return hello;end;end"#
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
luaify!(|a, b| |c, d| hello),
|
||||
r#"function(a,b)return function(c,d)return hello;end;end"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn labels() {
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
'label: {
|
||||
break 'label;
|
||||
a();
|
||||
b()
|
||||
}
|
||||
}),
|
||||
r#"function()do goto label_brk;a();return b();::label_cnt::end::label_brk::;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
'label: {
|
||||
continue 'label;
|
||||
a();
|
||||
b()
|
||||
}
|
||||
}),
|
||||
r#"function()do goto label_cnt;a();return b();::label_cnt::end::label_brk::;end"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loops() {
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
loop {
|
||||
break;
|
||||
}
|
||||
}),
|
||||
r#"function()while true do break;end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
'label: loop {
|
||||
break;
|
||||
break 'label;
|
||||
continue 'label;
|
||||
a();
|
||||
b()
|
||||
}
|
||||
}),
|
||||
r#"function()while true do break;goto label_brk;goto label_cnt;a();b();::label_cnt::end::label_brk::;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| { while cond {} }),
|
||||
r#"function()while cond do end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
'label: while cond {
|
||||
continue 'label;
|
||||
break 'label;
|
||||
a();
|
||||
b()
|
||||
}
|
||||
}),
|
||||
r#"function()while cond do goto label_cnt;goto label_brk;a();b();::label_cnt::end::label_brk::;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
for (k, v) in pairs(table) {
|
||||
print(k, v);
|
||||
a();
|
||||
b()
|
||||
}
|
||||
}),
|
||||
r#"function()for k,v in pairs(table)do print(k,v);a();b();end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
for i in 0..=5 {
|
||||
print(i)
|
||||
}
|
||||
for i in ..10 {
|
||||
print(i)
|
||||
}
|
||||
}),
|
||||
r#"function()for i=0,5 do print(i);end;for i=0,10-1 do print(i);end;end"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_checks() {
|
||||
assert_eq!(luaify!(|s| {}), r#"function(s)end"#);
|
||||
assert_eq!(
|
||||
luaify!(|s: table| {}),
|
||||
r#"function(s)do if type(s)~="table"then return error("table expected in \'s\', got "..type(s));end;end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|s| { s as string }),
|
||||
r#"function(s)do if type(s)=="number"then s=tostring(s);elseif type(s)~="string"then return error("string expected in \'s\', got "..type(s));end;end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|s| { s as number }),
|
||||
r#"function(s)do local _s=s;s=tonumber(s);if s==nil then return error("number expected in \'s\', got "..type(_s));end;end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|s| { s as nil }),
|
||||
r#"function(s)do if s~=nil then return error("nil expected in \'s\', got "..type(s));end;end;end"#
|
||||
);
|
||||
assert_eq!(luaify!(|s| { s as any }), r#"function(s)do end;end"#);
|
||||
|
||||
assert_eq!(
|
||||
luaify!(|s| {
|
||||
let (ok, res) = coroutine::r#yield(thread);
|
||||
ok as boolean;
|
||||
res as nil;
|
||||
}),
|
||||
r#"function(s)local ok,res=coroutine.yield(thread);do if type(ok)~="boolean"then return error("boolean expected in \'ok\', got "..type(ok));end;end;do if res~=nil then return error("nil expected in \'res\', got "..type(res));end;end;end"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concat() {
|
||||
assert_eq!(luaify!(concat!(a)), r#"a"#);
|
||||
assert_eq!(luaify!(concat!(a, b)), r#"a..b"#);
|
||||
assert_eq!(luaify!(concat!(a, b, c)), r#"a..b..c"#);
|
||||
assert_eq!(
|
||||
luaify!(|a, b, c| concat!(a, b, c)),
|
||||
r#"function(a,b,c)return a..b..c;end"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocks() {
|
||||
assert_eq!(
|
||||
luaify!(|| { { "result" } }),
|
||||
r#"function()do return"result";end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
{}
|
||||
{ "result" }
|
||||
}),
|
||||
r#"function()do end;do return"result";end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| { { { { "result" } } } }),
|
||||
r#"function()do do do return"result";end;end;end;end"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ops() {
|
||||
assert_eq!(luaify!(|| a = b), r#"function()a=b;end"#);
|
||||
assert_eq!(luaify!(|| a + b), r#"function()return a+b;end"#);
|
||||
assert_eq!(luaify!(|| a += b), r#"function()a=a+b;end"#);
|
||||
assert_eq!(luaify!(|| a - b), r#"function()return a-b;end"#);
|
||||
assert_eq!(luaify!(|| a -= b), r#"function()a=a-b;end"#);
|
||||
assert_eq!(luaify!(|| a * b), r#"function()return a*b;end"#);
|
||||
assert_eq!(luaify!(|| a *= b), r#"function()a=a*b;end"#);
|
||||
assert_eq!(luaify!(|| a / b), r#"function()return a/b;end"#);
|
||||
assert_eq!(luaify!(|| a /= b), r#"function()a=a/b;end"#);
|
||||
assert_eq!(luaify!(|| a = b % c), r#"function()a=math.fmod(b,c);end"#);
|
||||
assert_eq!(luaify!(|| a = b << c), r#"function()a=bit.lshift(b,c);end"#);
|
||||
assert_eq!(
|
||||
luaify!(|| a <<= b << c),
|
||||
r#"function()a=bit.lshift(a,bit.lshift(b,c));end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| a = b >> c),
|
||||
r#"function()a=bit.arshift(b,c);end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| a >>= b >> c),
|
||||
r#"function()a=bit.arshift(a,bit.arshift(b,c));end"#
|
||||
);
|
||||
assert_eq!(luaify!(|| a && b), r#"function()return a and b;end"#);
|
||||
assert_eq!(luaify!(|| a || b), r#"function()return a or b;end"#);
|
||||
assert_eq!(
|
||||
luaify!(|| (a && b) || c),
|
||||
r#"function()return(a and b)or c;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| (a || b) && c),
|
||||
r#"function()return(a or b)and c;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| a && (b || c)),
|
||||
r#"function()return a and(b or c);end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| a || (b && c)),
|
||||
r#"function()return a or(b and c);end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| -a || !--b && c >> d),
|
||||
r#"function()return-a or not-(-b)and bit.arshift(c,d);end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| -a || !(--b && c) >> d),
|
||||
r#"function()return-a or bit.arshift(not(-(-b)and c),d);end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| a >> b << c >> d),
|
||||
r#"function()return bit.arshift(bit.lshift(bit.arshift(a,b),c),d);end"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ifs() {
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
if a == b {
|
||||
c
|
||||
}
|
||||
}),
|
||||
r#"function()if a==b then return c;end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| { if a == b { c } else { d } }),
|
||||
r#"function()if a==b then return c;else return d;end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
if a == b {
|
||||
c()
|
||||
} else if b == c {
|
||||
a()
|
||||
} else {
|
||||
d()
|
||||
};
|
||||
}),
|
||||
r#"function()if a==b then c();elseif b==c then a();else d();end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
if a == b {
|
||||
c()
|
||||
} else if b == c {
|
||||
a()
|
||||
} else {
|
||||
d()
|
||||
}
|
||||
}),
|
||||
r#"function()if a==b then return c();elseif b==c then return a();else return d();end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
if a == b {
|
||||
c();
|
||||
} else if b == c {
|
||||
a()
|
||||
} else {
|
||||
d();
|
||||
}
|
||||
}),
|
||||
r#"function()if a==b then c();elseif b==c then return a();else d();end;end"#
|
||||
);
|
||||
}
|
3
src/main.rs
Normal file
3
src/main.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user