Skip to content

Commit 0fa8821

Browse files
bors[bot]dafaustCohenArthur
authored
Merge #1043 #1064
1043: implement include_bytes! and include_str! macros r=CohenArthur a=dafaust Implement the include_bytes! and include_str! builtin macros. Addresses: #927 1064: Handle :tt fragments properly r=CohenArthur a=CohenArthur :tt fragments stand for token trees, and are composed of either a token, or a delimited token tree, which is a token tree surrounded by delimiters (parentheses, curly brackets or square brackets). This should allow us to handle a lot more macros, including extremely powerful macro patterns such as TT munchers Co-authored-by: David Faust <david.faust@oracle.com> Co-authored-by: Arthur Cohen <arthur.cohen@embecosm.com>
3 parents 3a90596 + 261c753 + 5651331 commit 0fa8821

12 files changed

+300
-4
lines changed

gcc/rust/expand/rust-macro-builtins.cc

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
#include "rust-diagnostics.h"
2121
#include "rust-expr.h"
2222
#include "rust-session-manager.h"
23+
#include "rust-macro-invoc-lexer.h"
24+
#include "rust-lex.h"
25+
#include "rust-parse.h"
2326

2427
namespace Rust {
2528
namespace {
@@ -30,6 +33,107 @@ make_string (Location locus, std::string value)
3033
new AST::LiteralExpr (value, AST::Literal::STRING,
3134
PrimitiveCoreType::CORETYPE_STR, {}, locus));
3235
}
36+
37+
/* Parse a single string literal from the given delimited token tree,
38+
and return the LiteralExpr for it. Allow for an optional trailing comma,
39+
but otherwise enforce that these are the only tokens. */
40+
41+
std::unique_ptr<AST::LiteralExpr>
42+
parse_single_string_literal (AST::DelimTokenTree &invoc_token_tree,
43+
Location invoc_locus)
44+
{
45+
MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
46+
Parser<MacroInvocLexer> parser (std::move (lex));
47+
48+
auto last_token_id = TokenId::RIGHT_CURLY;
49+
switch (invoc_token_tree.get_delim_type ())
50+
{
51+
case AST::DelimType::PARENS:
52+
last_token_id = TokenId::RIGHT_PAREN;
53+
rust_assert (parser.skip_token (LEFT_PAREN));
54+
break;
55+
56+
case AST::DelimType::CURLY:
57+
rust_assert (parser.skip_token (LEFT_CURLY));
58+
break;
59+
60+
case AST::DelimType::SQUARE:
61+
last_token_id = TokenId::RIGHT_SQUARE;
62+
rust_assert (parser.skip_token (LEFT_SQUARE));
63+
break;
64+
}
65+
66+
std::unique_ptr<AST::LiteralExpr> lit_expr = nullptr;
67+
68+
if (parser.peek_current_token ()->get_id () == STRING_LITERAL)
69+
{
70+
lit_expr = parser.parse_literal_expr ();
71+
parser.maybe_skip_token (COMMA);
72+
if (parser.peek_current_token ()->get_id () != last_token_id)
73+
{
74+
lit_expr = nullptr;
75+
rust_error_at (invoc_locus, "macro takes 1 argument");
76+
}
77+
}
78+
else if (parser.peek_current_token ()->get_id () == last_token_id)
79+
rust_error_at (invoc_locus, "macro takes 1 argument");
80+
else
81+
rust_error_at (invoc_locus, "argument must be a string literal");
82+
83+
parser.skip_token (last_token_id);
84+
85+
return lit_expr;
86+
}
87+
88+
/* Treat PATH as a path relative to the source file currently being
89+
compiled, and return the absolute path for it. */
90+
91+
std::string
92+
source_relative_path (std::string path, Location locus)
93+
{
94+
std::string compile_fname
95+
= Session::get_instance ().linemap->location_file (locus);
96+
97+
auto dir_separator_pos = compile_fname.rfind (file_separator);
98+
99+
/* If there is no file_separator in the path, use current dir ('.'). */
100+
std::string dirname;
101+
if (dir_separator_pos == std::string::npos)
102+
dirname = std::string (".") + file_separator;
103+
else
104+
dirname = compile_fname.substr (0, dir_separator_pos) + file_separator;
105+
106+
return dirname + path;
107+
}
108+
109+
/* Read the full contents of the file FILENAME and return them in a vector.
110+
FIXME: platform specific. */
111+
112+
std::vector<uint8_t>
113+
load_file_bytes (const char *filename)
114+
{
115+
RAIIFile file_wrap (filename);
116+
if (file_wrap.get_raw () == nullptr)
117+
{
118+
rust_error_at (Location (), "cannot open filename %s: %m", filename);
119+
return std::vector<uint8_t> ();
120+
}
121+
122+
FILE *f = file_wrap.get_raw ();
123+
fseek (f, 0L, SEEK_END);
124+
long fsize = ftell (f);
125+
fseek (f, 0L, SEEK_SET);
126+
127+
std::vector<uint8_t> buf (fsize);
128+
129+
if (fread (&buf[0], fsize, 1, f) != 1)
130+
{
131+
rust_error_at (Location (), "error reading file %s: %m", filename);
132+
return std::vector<uint8_t> ();
133+
}
134+
135+
return buf;
136+
}
33137
} // namespace
34138

35139
AST::ASTFragment
@@ -63,4 +167,73 @@ MacroBuiltin::column (Location invoc_locus, AST::MacroInvocData &invoc)
63167

64168
return AST::ASTFragment ({column_no});
65169
}
170+
171+
/* Expand builtin macro include_bytes!("filename"), which includes the contents
172+
of the given file as reference to a byte array. Yields an expression of type
173+
&'static [u8; N]. */
174+
175+
AST::ASTFragment
176+
MacroBuiltin::include_bytes (Location invoc_locus, AST::MacroInvocData &invoc)
177+
{
178+
/* Get target filename from the macro invocation, which is treated as a path
179+
relative to the include!-ing file (currently being compiled). */
180+
auto lit_expr
181+
= parse_single_string_literal (invoc.get_delim_tok_tree (), invoc_locus);
182+
if (lit_expr == nullptr)
183+
return AST::ASTFragment::create_error ();
184+
185+
std::string target_filename
186+
= source_relative_path (lit_expr->as_string (), invoc_locus);
187+
188+
std::vector<uint8_t> bytes = load_file_bytes (target_filename.c_str ());
189+
190+
/* Is there a more efficient way to do this? */
191+
std::vector<std::unique_ptr<AST::Expr>> elts;
192+
for (uint8_t b : bytes)
193+
{
194+
elts.emplace_back (
195+
new AST::LiteralExpr (std::string (1, (char) b), AST::Literal::BYTE,
196+
PrimitiveCoreType::CORETYPE_U8,
197+
{} /* outer_attrs */, invoc_locus));
198+
}
199+
200+
auto elems = std::unique_ptr<AST::ArrayElems> (
201+
new AST::ArrayElemsValues (std::move (elts), invoc_locus));
202+
203+
auto array = std::unique_ptr<AST::Expr> (
204+
new AST::ArrayExpr (std::move (elems), {}, {}, invoc_locus));
205+
206+
auto borrow = std::unique_ptr<AST::Expr> (
207+
new AST::BorrowExpr (std::move (array), false, false, {}, invoc_locus));
208+
209+
auto node = AST::SingleASTNode (std::move (borrow));
210+
return AST::ASTFragment ({node});
211+
}
212+
213+
/* Expand builtin macro include_str!("filename"), which includes the contents
214+
of the given file as a string. The file must be UTF-8 encoded. Yields an
215+
expression of type &'static str. */
216+
217+
AST::ASTFragment
218+
MacroBuiltin::include_str (Location invoc_locus, AST::MacroInvocData &invoc)
219+
{
220+
/* Get target filename from the macro invocation, which is treated as a path
221+
relative to the include!-ing file (currently being compiled). */
222+
auto lit_expr
223+
= parse_single_string_literal (invoc.get_delim_tok_tree (), invoc_locus);
224+
if (lit_expr == nullptr)
225+
return AST::ASTFragment::create_error ();
226+
227+
std::string target_filename
228+
= source_relative_path (lit_expr->as_string (), invoc_locus);
229+
230+
std::vector<uint8_t> bytes = load_file_bytes (target_filename.c_str ());
231+
232+
/* FIXME: Enforce that the file contents are valid UTF-8. */
233+
std::string str ((const char *) &bytes[0], bytes.size ());
234+
235+
auto node = AST::SingleASTNode (make_string (invoc_locus, str));
236+
return AST::ASTFragment ({node});
237+
}
238+
66239
} // namespace Rust

gcc/rust/expand/rust-macro-builtins.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ class MacroBuiltin
7171

7272
static AST::ASTFragment column (Location invoc_locus,
7373
AST::MacroInvocData &invoc);
74+
75+
static AST::ASTFragment include_bytes (Location invoc_locus,
76+
AST::MacroInvocData &invoc);
77+
78+
static AST::ASTFragment include_str (Location invoc_locus,
79+
AST::MacroInvocData &invoc);
7480
};
7581
} // namespace Rust
7682

gcc/rust/expand/rust-macro-expand.cc

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -497,10 +497,8 @@ MacroExpander::match_fragment (Parser<MacroInvocLexer> &parser,
497497
gcc_unreachable ();
498498
break;
499499

500-
// what is TT?
501500
case AST::MacroFragSpec::TT:
502-
// parser.parse_token_tree() ?
503-
gcc_unreachable ();
501+
parser.parse_token_tree ();
504502
break;
505503

506504
// i guess we just ignore invalid and just error out

gcc/rust/parse/rust-parse.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ template <typename ManagedTokenSource> class Parser
142142
std::vector<std::unique_ptr<AST::LifetimeParam> > parse_lifetime_params ();
143143
AST::Visibility parse_visibility ();
144144
std::unique_ptr<AST::IdentifierPattern> parse_identifier_pattern ();
145+
std::unique_ptr<AST::TokenTree> parse_token_tree ();
145146

146147
private:
147148
void skip_after_semicolon ();
@@ -188,7 +189,6 @@ template <typename ManagedTokenSource> class Parser
188189

189190
// Token tree or macro related
190191
AST::DelimTokenTree parse_delim_token_tree ();
191-
std::unique_ptr<AST::TokenTree> parse_token_tree ();
192192
std::unique_ptr<AST::MacroRulesDefinition>
193193
parse_macro_rules_def (AST::AttrVec outer_attrs);
194194
std::unique_ptr<AST::MacroInvocation>

gcc/rust/util/rust-hir-map.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,8 @@ Mappings::insert_macro_def (AST::MacroRulesDefinition *macro)
751751
{"assert", MacroBuiltin::assert},
752752
{"file", MacroBuiltin::file},
753753
{"column", MacroBuiltin::column},
754+
{"include_bytes", MacroBuiltin::include_bytes},
755+
{"include_str", MacroBuiltin::include_str},
754756
};
755757

756758
auto builtin = builtin_macros.find (macro->get_rule_name ());
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
macro_rules! include_bytes {
2+
() => {{}};
3+
}
4+
5+
fn main () {
6+
let file = "include.txt";
7+
include_bytes! (file); // { dg-error "argument must be a string literal" "" }
8+
include_bytes! (); // { dg-error "macro takes 1 argument" "" }
9+
include_bytes! ("foo.txt", "bar.txt"); // { dg-error "macro takes 1 argument" "" }
10+
include_bytes! ("builtin_macro_include_bytes.rs"); // ok
11+
include_bytes! ("builtin_macro_include_bytes.rs",); // trailing comma ok
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
macro_rules! include_str {
2+
() => {{}};
3+
}
4+
5+
fn main () {
6+
let file = "include.txt";
7+
include_str! (file); // { dg-error "argument must be a string literal" "" }
8+
include_str! (); // { dg-error "macro takes 1 argument" "" }
9+
include_str! ("foo.txt", "bar.txt"); // { dg-error "macro takes 1 argument" "" }
10+
include_str! ("builtin_macro_include_str.rs"); // ok
11+
include_str! ("builtin_macro_include_str.rs",); // trailing comma ok
12+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// { dg-output "104\n33\n1\n" }
2+
3+
macro_rules! include_bytes {
4+
() => {{}};
5+
}
6+
7+
extern "C" {
8+
fn printf(s: *const i8, ...);
9+
}
10+
11+
fn print_int(value: i32) {
12+
let s = "%d\n\0" as *const str as *const i8;
13+
printf(s, value);
14+
}
15+
16+
fn main() -> i32 {
17+
let bytes = include_bytes! ("include.txt");
18+
19+
print_int (bytes[0] as i32);
20+
print_int (bytes[14] as i32);
21+
22+
let the_bytes = b"hello, include!\n";
23+
24+
let x = bytes[0] == the_bytes[0]
25+
&& bytes[1] == the_bytes [1]
26+
&& bytes[2] == the_bytes [2]
27+
&& bytes[3] == the_bytes [3]
28+
&& bytes[4] == the_bytes [4]
29+
&& bytes[5] == the_bytes [5]
30+
&& bytes[6] == the_bytes [6]
31+
&& bytes[7] == the_bytes [7]
32+
&& bytes[8] == the_bytes [8]
33+
&& bytes[9] == the_bytes [9]
34+
&& bytes[10] == the_bytes [10]
35+
&& bytes[11] == the_bytes [11]
36+
&& bytes[12] == the_bytes [12]
37+
&& bytes[13] == the_bytes [13]
38+
&& bytes[14] == the_bytes [14]
39+
&& bytes[15] == the_bytes [15];
40+
41+
print_int (x as i32);
42+
43+
0
44+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// { dg-output "hello, include!\n" }
2+
3+
macro_rules! include_str {
4+
() => {{}};
5+
}
6+
7+
extern "C" {
8+
fn printf(fmt: *const i8, ...);
9+
}
10+
11+
fn print(s: &str) {
12+
printf("%s" as *const str as *const i8, s as *const str as *const i8);
13+
}
14+
15+
16+
fn main() -> i32 {
17+
// include_str! (and include_bytes!) allow for an optional trailing comma.
18+
let my_str = include_str! ("include.txt",);
19+
20+
print (my_str);
21+
22+
0
23+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hello, include!

0 commit comments

Comments
 (0)