Skip to content

Commit bfabee3

Browse files
committed
Borrow some tricks from inline_python, now we got true interoperability
1 parent 7482cb8 commit bfabee3

File tree

12 files changed

+302
-284
lines changed

12 files changed

+302
-284
lines changed

Cargo.toml

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "inline-vbs"
3-
version = "0.1.2"
3+
version = "0.2.0"
44
authors = ["Tom Niget <niget.tom@gmail.com>"]
55
description = "Embed VBScript code inside Rust"
66
homepage = "https://github.com/zdimension/inline-vbs"
@@ -15,16 +15,20 @@ exclude = [
1515
]
1616

1717
[dependencies]
18-
inline-vbs-macros = "0.1"
18+
inline-vbs-macros = { version = "0.2", path = "./macros" }
1919
cxx = "1.0.65"
20-
winapi = { version = "0.3.9", features = ["oaidl", "wtypes"] }
20+
winapi = { version = "0.3.9", features = ["oaidl", "wtypes", "oleauto"] }
2121
widestring = "0.5.1"
2222
enumn = "0.1.3"
2323
dep_doc = "0.1.1"
24+
variant-rs = "0.1"
2425

2526
[build-dependencies]
2627
cxx-build = "1.0"
2728

29+
[patch.crates-io]
30+
cc = { git = "https://github.com/zdimension/cc-rs" } # https://github.com/rust-lang/cc-rs/pull/699
31+
2832
[workspace]
2933
members = [
3034
"macros"

README.md

+13-13
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,30 @@ the [Active Scripting](https://docs.microsoft.com/en-us/archive/msdn-magazine/20
88
use inline_vbs::*;
99

1010
fn main() {
11-
vbs![On Error Resume Next]; // tired of handling errors?
12-
vbs![MsgBox "Hello, world!"];
13-
if let Ok(Variant::String(str)) = vbs_!["VBScript" & " Rocks!"] {
14-
println!("{}", str);
15-
}
11+
vbs! { On Error Resume Next } // tired of handling errors?
12+
vbs! { MsgBox "Hello, world!" }
13+
let language = "VBScript";
14+
assert_eq!(vbs_!['language & " Rocks!"], "VBScript Rocks!".into());
1615
}
1716
```
17+
Macros:
18+
* `vbs!` - Executes a statement or evaluates an expression (depending on context)
19+
* `vbs_!` - Evaluates an expression
20+
* `vbs_raw!` - Executes a statement (string input instead of tokens, use for multiline code)
21+
See more examples in [tests/tests.rs](tests/tests.rs)
1822

1923
## Installation
2024
Add this to your `Cargo.toml`:
2125
```toml
2226
[dependencies]
23-
inline-vbs = "0.1"
27+
inline-vbs = "0.2"
2428
```
2529

26-
**Important:** You need to have the MSVC Build Tools installed on your computer, and you may need to run
27-
the `vsdevcmd.bat` script in your terminal to set up the build environment:
28-
```
29-
"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat" -arch=amd64
30-
```
31-
(replace with the path of your VS installation)
30+
**Important:** You need to have the MSVC Build Tools installed on your computer, as required by [cc](https://github.com/rust-lang/cc-rs).
3231

3332
## Limitations
34-
Many
33+
Many. Most notably, `IDispatch` objects (i.e. what `CreateObject` returns) can't be passed to
34+
the engine (`let x = vbs! { CreateObject("WScript.Shell") }; vbs! { y = 'x }` won't work).
3535

3636
## Motivation
3737
N/A

build.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
fn main()
2-
{
1+
fn main() {
32
cxx_build::bridge("src/lib.rs")
43
.cpp(true)
54
.file("src/vbs.cpp")
65
.compile("inline-vbs-backend");
76
println!("cargo:rerun-if-changed=src/lib.rs");
87
println!("cargo:rerun-if-changed=src/vbs.cpp");
98
println!("cargo:rerun-if-changed=include/vbs.h");
10-
}
9+
}

include/vbs.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55

66
int init();
77
int parse_wrapper(rust::Str code, char* output);
8-
rust::String error_to_string(int hr);
8+
rust::String error_to_string(int hr);
9+
int set_variable(rust::Str name, char* val);

macros/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "inline-vbs-macros"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
authors = ["Tom Niget <niget.tom@gmail.com>"]
55
description = "Macros for the `inline-vbs` crate"
66
homepage = "https://github.com/zdimension/inline-vbs"

macros/src/lib.rs

+175-18
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,202 @@
1-
mod reconstruct;
1+
/*
2+
Includes bits of code from the great & almighty Mara Bos and her amazing inline-python project.
3+
4+
Copyright (c) 2019-2020 Drones for Work B.V. and contributors
5+
All rights reserved.
6+
7+
Redistribution and use in source and binary forms, with or without
8+
modification, are permitted provided that the following conditions are met:
9+
10+
1. Redistributions of source code must retain the above copyright notice, this
11+
list of conditions and the following disclaimer.
12+
2. Redistributions in binary form must reproduce the above copyright notice,
13+
this list of conditions and the following disclaimer in the documentation
14+
and/or other materials provided with the distribution.
15+
16+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26+
*/
27+
28+
#![feature(proc_macro_span)]
229

330
use litrs::StringLit;
31+
use proc_macro::LineColumn;
32+
use proc_macro::Span;
433
use proc_macro2::TokenStream;
34+
use proc_macro2::{Delimiter, Ident, Spacing, TokenTree};
535
use quote::quote;
6-
use crate::reconstruct::reconstruct;
36+
use quote::quote_spanned;
37+
use std::collections::BTreeMap;
38+
use std::fmt::Write;
39+
740
extern crate proc_macro;
841

9-
#[proc_macro]
10-
pub fn vbs(input: proc_macro::TokenStream) -> proc_macro::TokenStream
11-
{
12-
let code = reconstruct(TokenStream::from(input));
42+
fn macro_impl(input: TokenStream) -> Result<TokenStream, TokenStream> {
43+
let mut x = EmbedVbs::new();
1344

14-
quote! [::inline_vbs::run_code(#code)].into()
45+
x.add(input)?;
46+
47+
let EmbedVbs {
48+
code, variables, ..
49+
} = x;
50+
51+
let varname = variables.keys();
52+
let var = variables.values();
53+
54+
Ok(quote! {
55+
{
56+
#(::inline_vbs::set_variable(#varname, #var).unwrap();)*
57+
::inline_vbs::Runner::run_code(#code)
58+
}
59+
})
1560
}
1661

1762
#[proc_macro]
18-
pub fn vbs_(input: proc_macro::TokenStream) -> proc_macro::TokenStream
19-
{
20-
let code = reconstruct(TokenStream::from(input));
63+
pub fn vbs(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
64+
proc_macro::TokenStream::from(process_tokens(input))
65+
}
66+
67+
fn process_tokens(input: proc_macro::TokenStream) -> TokenStream {
68+
match macro_impl(TokenStream::from(input)) {
69+
Ok(tokens) => tokens,
70+
Err(tokens) => tokens,
71+
}
72+
}
2173

22-
quote! [::inline_vbs::run_expr(#code)].into()
74+
#[proc_macro]
75+
pub fn vbs_(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
76+
let call = process_tokens(input);
77+
(quote! {
78+
{
79+
let res: Variant = #call;
80+
res
81+
}
82+
})
83+
.into()
2384
}
2485

2586
#[proc_macro]
26-
pub fn vbs_raw(input: proc_macro::TokenStream) -> proc_macro::TokenStream
27-
{
87+
pub fn vbs_raw(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
2888
let input = input.into_iter().collect::<Vec<_>>();
2989

30-
if input.len() != 1
31-
{
90+
if input.len() != 1 {
3291
let msg = format!("expected exactly one input token, got {}", input.len());
3392
return quote! { compile_error!(#msg) }.into();
3493
}
3594

36-
let string_lit = match StringLit::try_from(&input[0])
37-
{
95+
let string_lit = match StringLit::try_from(&input[0]) {
3896
Err(e) => return e.to_compile_error(),
3997
Ok(lit) => lit,
4098
};
4199

42100
let code = string_lit.value();
43101

44102
quote! [::inline_vbs::run_code(#code)].into()
45-
}
103+
}
104+
105+
struct EmbedVbs {
106+
pub code: String,
107+
pub variables: BTreeMap<String, Ident>,
108+
pub first_indent: Option<usize>,
109+
pub loc: LineColumn,
110+
}
111+
112+
impl EmbedVbs {
113+
pub fn new() -> Self {
114+
Self {
115+
code: String::new(),
116+
variables: BTreeMap::new(),
117+
loc: LineColumn { line: 1, column: 0 },
118+
first_indent: None,
119+
}
120+
}
121+
122+
fn add_whitespace(&mut self, span: Span, loc: LineColumn) -> Result<(), TokenStream> {
123+
#[allow(clippy::comparison_chain)]
124+
if loc.line > self.loc.line {
125+
while loc.line > self.loc.line {
126+
self.code.push('\n');
127+
self.loc.line += 1;
128+
}
129+
let first_indent = *self.first_indent.get_or_insert(loc.column);
130+
let indent = loc.column.checked_sub(first_indent);
131+
let indent = indent.ok_or_else(
132+
|| quote_spanned!(span.into() => compile_error!{"Invalid indentation"}),
133+
)?;
134+
for _ in 0..indent {
135+
self.code.push(' ');
136+
}
137+
self.loc.column = loc.column;
138+
} else if loc.line == self.loc.line {
139+
while loc.column > self.loc.column {
140+
self.code.push(' ');
141+
self.loc.column += 1;
142+
}
143+
}
144+
145+
Ok(())
146+
}
147+
148+
pub fn add(&mut self, input: TokenStream) -> Result<(), TokenStream> {
149+
let mut tokens = input.into_iter();
150+
151+
while let Some(token) = tokens.next() {
152+
let span = token.span().unwrap();
153+
self.add_whitespace(span, span.start())?;
154+
155+
match &token {
156+
TokenTree::Group(x) => {
157+
let (start, end) = match x.delimiter() {
158+
Delimiter::Parenthesis => ("(", ")"),
159+
Delimiter::Brace => ("{", "}"),
160+
Delimiter::Bracket => ("[", "]"),
161+
Delimiter::None => ("", ""),
162+
};
163+
self.code.push_str(start);
164+
self.loc.column += start.len();
165+
self.add(x.stream())?;
166+
let mut end_loc = token.span().unwrap().end();
167+
end_loc.column = end_loc.column.saturating_sub(end.len());
168+
self.add_whitespace(span, end_loc)?;
169+
self.code.push_str(end);
170+
self.loc.column += end.len();
171+
}
172+
TokenTree::Punct(x) => {
173+
if x.as_char() == '\'' && x.spacing() == Spacing::Joint {
174+
let name = if let Some(TokenTree::Ident(name)) = tokens.next() {
175+
name
176+
} else {
177+
unreachable!()
178+
};
179+
let name_str = format!("RUST{}", name);
180+
self.code.push_str(&name_str);
181+
self.loc.column += name_str.chars().count() - 4 + 1;
182+
self.variables.entry(name_str).or_insert(name);
183+
} else {
184+
self.code.push(x.as_char());
185+
self.loc.column += 1;
186+
}
187+
}
188+
TokenTree::Ident(x) => {
189+
write!(&mut self.code, "{}", x).unwrap();
190+
self.loc = token.span().unwrap().end();
191+
}
192+
TokenTree::Literal(x) => {
193+
let s = x.to_string();
194+
self.code += &s;
195+
self.loc = token.span().unwrap().end();
196+
}
197+
}
198+
}
199+
200+
Ok(())
201+
}
202+
}

0 commit comments

Comments
 (0)