├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── build.py ├── pkg ├── .gitignore ├── Cargo.toml └── src │ └── lib.rs ├── redirects.json ├── src └── main.rs ├── static ├── FiraSans-Medium.woff ├── FiraSans-Regular.woff ├── Heuristica-Italic.woff ├── SourceCodePro-Regular.woff ├── SourceCodePro-Semibold.woff ├── SourceSerifPro-Bold.woff ├── SourceSerifPro-Regular.woff ├── book │ └── rustbook.css ├── code │ └── ook.rs ├── index.html ├── license-MIT └── rust.css └── text ├── README.md ├── SUMMARY.md ├── aeg-README.md ├── aeg-ook.md ├── blk-README.md ├── blk-ast-coercion.md ├── blk-counting.md ├── blk-enum-parsing.md ├── mbe-README.md ├── mbe-macro-rules.md ├── mbe-min-README.md ├── mbe-min-captures-and-expansion-redux.md ├── mbe-min-debugging.md ├── mbe-min-hygiene.md ├── mbe-min-import-export.md ├── mbe-min-non-identifier-identifiers.md ├── mbe-min-scoping.md ├── mbe-syn-README.md ├── mbe-syn-expansion.md ├── mbe-syn-macros-in-the-ast.md ├── mbe-syn-source-analysis.md ├── pat-README.md ├── pat-callbacks.md ├── pat-incremental-tt-munchers.md ├── pat-internal-rules.md ├── pat-provisional.md ├── pat-push-down-accumulation.md ├── pat-repetition-replacement.md ├── pat-trailing-separators.md ├── pat-tt-bundling.md ├── pat-visibility.md └── pim-README.md /.gitignore: -------------------------------------------------------------------------------- 1 | _book 2 | local 3 | target 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "tlbormfix" 3 | version = "0.1.0" 4 | 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tlbormfix" 3 | version = "0.1.0" 4 | authors = ["Luxko "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust宏小册 2 | 3 | **注意**: 本项目尚在建设过程中。 4 | 5 | 本项目是The Little Book of Rust Macro的中文翻译。 6 | 7 | 查看渲染后的[正式版本](https://daseinphaos.github.io/tlborm-chinese/)([英文原版](https://danielkeep.github.io/tlborm/))。 8 | 9 | 本书试图提炼出Rust社区对Rust宏的知识集锦。因此,我们欢迎社区成员进行内容添补(通过pull)或提出需求(通过issue)。 10 | 11 | 如果你希望做出贡献,请移步至[原版的repository](https://github.com/DanielKeep/tlborm/)。中文版的repo[在这里](https://github.com/DaseinPhaos/tlborm)。 12 | 13 | 14 | 15 | ## 目前需要 16 | 17 | * 一个更好的CSS主题。 18 | * 解析、展开相关的技术与模式。 19 | * 有用的轮子。 20 | -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import json 4 | import os 5 | import re 6 | import shutil 7 | import subprocess 8 | import sys 9 | import tempfile 10 | import time 11 | from contextlib import contextmanager, ExitStack 12 | from itertools import chain 13 | import re 14 | 15 | TEXT_PATH = 'text' 16 | OUT_PATH = 'target' 17 | BOOK_OUT_PATH = OUT_PATH + '/book' 18 | STATIC_PATH = 'static' 19 | RUSTBOOK_OUT_PATH = '_book' 20 | REDIRECTS = 'redirects.json' 21 | RUSTBOOK_BIN_PATH = r'C:\Users\asus\Desktop\playground-rs\rustbook\target\release\rustbook.exe' 22 | 23 | PUBLISH_BRANCH = 'gh-pages' 24 | 25 | TEMP_PREFIX = 'tlborm-build-' 26 | WATCH_DELAY = 0.25 # sec 27 | WATCH_SLEEP = 0.5 # sec 28 | 29 | 30 | 31 | TRACE = os.environ.get('TLBORM_TRACE_BUILD', '') != '' 32 | 33 | REDIRECT_TEMPLATE = """ 34 | 35 | 36 | 37 | The Little Book of Rust Macros 38 | 39 | 54 | 55 | 56 | 57 | 58 |

Content Moved

59 |

Follow the redirection if it does not work automatically.

60 | 61 | 62 | 63 | """ 64 | 65 | def main(): 66 | args = sys.argv[1:] 67 | if args == []: 68 | args = ['build'] 69 | 70 | def usage(): 71 | print('Usage: build.py [build | open | publish | watch | help]') 72 | return 1 73 | 74 | if '--help' in args or 'help' in args or len(args) != 1: 75 | return usage() 76 | 77 | if args[0] == 'build': 78 | build() 79 | 80 | elif args[0] == 'open': 81 | do_open() 82 | 83 | elif args[0] == 'publish': 84 | publish() 85 | 86 | elif args[0] == 'watch': 87 | watch() 88 | 89 | else: 90 | return usage() 91 | 92 | def build(): 93 | msg('Building...') 94 | sh(RUSTBOOK_BIN_PATH, 'build', TEXT_PATH) 95 | sh('cargo', 'run', RUSTBOOK_OUT_PATH) 96 | if os.path.exists(OUT_PATH): 97 | really_rmtree(OUT_PATH) 98 | 99 | gen_redirs() 100 | 101 | copy_merge(src=RUSTBOOK_OUT_PATH, dst=BOOK_OUT_PATH) 102 | copy_merge(src=STATIC_PATH, dst=OUT_PATH) 103 | msg('.. done.') 104 | 105 | def gen_redirs(): 106 | msg_trace('.. generating redirects...') 107 | base_path = BOOK_OUT_PATH 108 | redirs = json.loads(open(REDIRECTS, 'rt').read()) 109 | for entry in redirs: 110 | src = entry[0] 111 | dst = entry[1] 112 | rel = os.path.relpath(dst, os.path.dirname(src)) 113 | rel = rel.replace("\\", "/") 114 | msg_trace(' .. %s -> %s / %s' % (src, dst, rel)) 115 | 116 | rel_base = os.path.dirname(rel) or '.' 117 | 118 | page = REDIRECT_TEMPLATE % {'dest': rel+'.html', 'rel': rel_base} 119 | redir = os.path.join(base_path, src+'.html') 120 | redir_dir = os.path.dirname(redir) 121 | if not os.path.exists(redir_dir): os.makedirs(redir_dir) 122 | open(redir, 'wt').write(page) 123 | msg_trace(' .. done.') 124 | 125 | def do_open(): 126 | msg('Opening...') 127 | os.startfile(os.path.join(OUT_PATH, 'index.html')) 128 | msg('.. done.') 129 | 130 | def publish(): 131 | if sh_eval('git', 'symbolic-ref', '--short', 'HEAD') != 'master': 132 | raise 'Not publishing: not on master branch!' 133 | 134 | init_pub_branch() 135 | 136 | msg('Publishing...') 137 | 138 | repo_path = os.getcwd() 139 | msg_trace('.. repo_path = %r' % repo_path) 140 | 141 | last_rev = sh_eval('git', 'rev-parse', 'HEAD') 142 | last_msg = sh_eval('git', 'log', '-l', '--pretty=%B') 143 | msg_trace('.. last_rev = %r' % last_rev) 144 | msg_trace('.. last_msg = %r' % last_msg) 145 | 146 | with ExitStack() as stack: 147 | master_tmp = stack.enter_context(mkdtemp(prefix=TEMP_PREFIX)) 148 | gh_pages_tmp = stack.enter_context(mkdtemp(prefix=TEMP_PREFIX)) 149 | msg_trace('.. master_tmp = %r' % master_tmp) 150 | msg_trace('.. gh_pages_tmp = %r' % gh_pages_tmp) 151 | 152 | msg('.. cloning...') 153 | sh('git', 'clone', '-qb', 'master', repo_path, master_tmp) 154 | sh('git', 'clone', '-qb', PUBLISH_BRANCH, repo_path, gh_pages_tmp) 155 | 156 | with pushd(master_tmp): 157 | msg('.. running build...') 158 | build() 159 | 160 | msg('.. copying to %s...' % PUBLISH_BRANCH) 161 | copy_merge( 162 | src=os.path.join(master_tmp, OUT_PATH), 163 | dst=gh_pages_tmp) 164 | 165 | with pushd(gh_pages_tmp): 166 | msg('.. committing changes...') 167 | sh('git', 'add', '.') 168 | sh('git', 'commit', 169 | '-m', "Update docs for %s" % last_rev[:7]) 170 | 171 | sh('git', 'push', '-qu', 'origin', PUBLISH_BRANCH) 172 | 173 | sh('git', 'push', '-q', 'origin', 'master', PUBLISH_BRANCH) 174 | msg('.. done.') 175 | 176 | def watch(): 177 | try: 178 | import watchdog 179 | except ImportError: 180 | msg('Cannot watch: could not import watchdog.') 181 | msg('Try installing with `pip install watchdog`.') 182 | return 183 | 184 | msg('Watching for changes...') 185 | 186 | from watchdog.events import FileSystemEventHandler 187 | from watchdog.observers import Observer 188 | 189 | class RebuildHandler(FileSystemEventHandler): 190 | def __init__(self, box): 191 | self.box = box 192 | 193 | def on_any_event(self, event): 194 | last_ts = self.box[0] 195 | now = time.time() 196 | if last_ts is None: 197 | self.box[0] = now + WATCH_DELAY 198 | 199 | rebuild_after = [None] 200 | handler = RebuildHandler(rebuild_after) 201 | 202 | observer = Observer() 203 | observer.schedule(handler, TEXT_PATH, recursive=True) 204 | observer.schedule(handler, STATIC_PATH, recursive=True) 205 | observer.start() 206 | 207 | def enable_tracing(): 208 | global TRACE 209 | if not TRACE: 210 | TRACE = True 211 | msg_trace('Enabled tracing due to exception.') 212 | 213 | try: 214 | import traceback 215 | while True: 216 | ts = rebuild_after[0] 217 | 218 | if ts is not None and time.time() >= ts: 219 | rebuild_after[0] = None 220 | try: 221 | build() 222 | except: 223 | traceback.print_exc() 224 | enable_tracing() 225 | 226 | time.sleep(WATCH_SLEEP) 227 | except KeyboardInterrupt: 228 | observer.stop() 229 | observer.join() 230 | 231 | msg('.. done.') 232 | 233 | def init_pub_branch(): 234 | msg_trace('init_pub_branch()') 235 | 236 | branches = {b[2:].strip() 237 | for b 238 | in sh_eval('git', 'branch', dont_strip=True).splitlines()} 239 | msg_trace('.. branches = %r' % branches) 240 | if PUBLISH_BRANCH in branches: 241 | msg_trace('.. publish branch exists') 242 | return 243 | 244 | msg("Initialising %s branch..." % PUBLISH_BRANCH) 245 | 246 | repo_path = os.getcwd() 247 | msg_trace('.. repo_path = %r' % repo_path) 248 | 249 | with ExitStack() as stack: 250 | tmp = stack.enter_context(mkdtemp(prefix=TEMP_PREFIX)) 251 | msg_trace('.. tmp = %r' % tmp) 252 | 253 | msg(".. cloning...") 254 | sh('git', 'init', '-q', tmp) 255 | with pushd(tmp): 256 | sh('git', 'checkout', '-q', '--orphan', PUBLISH_BRANCH) 257 | sh('git', 'commit', '-qm', "Initial commit.", '--allow-empty') 258 | sh('git', 'remote', 'add', 'origin', repo_path) 259 | sh('git', 'push', '-q', 'origin', PUBLISH_BRANCH) 260 | 261 | msg('.. done.') 262 | 263 | def copy_merge(src, dst): 264 | msg_trace('copy_merge(%r, %r)' % (src, dst)) 265 | names = os.listdir(src) 266 | if not os.path.exists(dst): os.makedirs(dst) 267 | for name in names: 268 | srcname = os.path.join(src, name) 269 | dstname = os.path.join(dst, name) 270 | if os.path.isdir(srcname): 271 | copy_merge(srcname, dstname) 272 | else: 273 | shutil.copy2(srcname, dstname) 274 | try: 275 | shutil.copystat(src, dst) 276 | except OSError as why: 277 | # can't copy file access times on Windows 278 | if why.winerror is not None: 279 | raise 280 | 281 | def sh(*cmd): 282 | msg_trace(' '.join(escape_argument(str(a)) for a in cmd)) 283 | try: 284 | subprocess.check_call(cmd, shell=(os.name == 'nt')) 285 | except: 286 | msg_trace('FAILED!') 287 | raise 288 | 289 | def sh_eval(*cmd, codec='utf-8', dont_strip=False): 290 | msg_trace('=%s' % ' '.join(escape_argument(str(a)) for a in cmd)) 291 | result = None 292 | try: 293 | result = subprocess.check_output(cmd, shell=True).decode(codec) 294 | if not dont_strip: 295 | result = result.strip() 296 | except: 297 | msg_trace('FAILED!') 298 | raise 299 | return result 300 | 301 | def msg(*args): 302 | sys.stdout.write('> ') 303 | for arg in args: 304 | sys.stdout.write(str(arg)) 305 | sys.stdout.write('\n') 306 | sys.stdout.flush() 307 | 308 | def msg_trace(*args): 309 | if TRACE: 310 | sys.stderr.write('$ ') 311 | for arg in args: 312 | sys.stderr.write(str(arg)) 313 | sys.stderr.write('\n') 314 | sys.stderr.flush() 315 | 316 | def traced(f, *pargs, **kwargs): 317 | if TRACE: 318 | args = ', '.join(chain( 319 | (repr(a) for a in pargs), 320 | ('%s=%r' % kv for kv in kwargs.items()))) 321 | msg_trace('%s(%s)' % (f.__qualname__, args)) 322 | return f(*pargs, **kwargs) 323 | 324 | def escape_argument(arg): 325 | # https://stackoverflow.com/questions/29213106/how-to-securely-escape-command-line-arguments-for-the-cmd-exe-shell-on-windows 326 | # Escape the argument for the cmd.exe shell. 327 | # See http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx 328 | # 329 | # First we escape the quote chars to produce a argument suitable for 330 | # CommandLineToArgvW. We don't need to do this for simple arguments. 331 | 332 | if not arg or re.search(r'(["\s])', arg): 333 | arg = '"' + arg.replace('"', r'\"') + '"' 334 | 335 | return escape_for_cmd_exe(arg) 336 | 337 | def escape_for_cmd_exe(arg): 338 | # https://stackoverflow.com/questions/29213106/how-to-securely-escape-command-line-arguments-for-the-cmd-exe-shell-on-windows 339 | # Escape an argument string to be suitable to be passed to 340 | # cmd.exe on Windows 341 | # 342 | # This method takes an argument that is expected to already be properly 343 | # escaped for the receiving program to be properly parsed. This argument 344 | # will be further escaped to pass the interpolation performed by cmd.exe 345 | # unchanged. 346 | # 347 | # Any meta-characters will be escaped, removing the ability to e.g. use 348 | # redirects or variables. 349 | # 350 | # @param arg [String] a single command line argument to escape for cmd.exe 351 | # @return [String] an escaped string suitable to be passed as a program 352 | # argument to cmd.exe 353 | 354 | meta_chars = '()%!^"<>&|' 355 | meta_re = re.compile('(' + '|'.join(re.escape(char) for char in list(meta_chars)) + ')') 356 | meta_map = { char: "^%s" % char for char in meta_chars } 357 | 358 | def escape_meta_chars(m): 359 | char = m.group(1) 360 | return meta_map[char] 361 | 362 | return meta_re.sub(escape_meta_chars, arg) 363 | 364 | def really_rmtree(path): 365 | msg_trace('really_rmtree(%r)' % path) 366 | WAIT_TIME_SECS = 1.0 367 | MAX_TRIES = 10 368 | 369 | def on_error(func, path, exc_info): 370 | """ 371 | Error handler for ``shutil.rmtree``. 372 | 373 | If the error is due to an access error (read only file) 374 | it attempts to add write permission and then retries. 375 | 376 | If the error is for another reason it re-raises the error. 377 | 378 | Usage: ``shutil.rmtree(path, onerror=on_error)`` 379 | 380 | From _. 381 | """ 382 | import stat 383 | if not os.access(path, os.W_OK): 384 | # Is the error an access error ? 385 | os.chmod(path, stat.S_IWUSR) 386 | func(path) 387 | else: 388 | raise 389 | 390 | for _ in range(MAX_TRIES): 391 | failed = True 392 | try: 393 | msg_trace('shutil.rmtree(%r)' % path) 394 | shutil.rmtree(path, onerror=on_error) 395 | failed = False 396 | except WindowsError: 397 | time.sleep(WAIT_TIME_SECS) 398 | if not failed: return 399 | 400 | msg('Warning: failed to remove directory %r' % path) 401 | 402 | @contextmanager 403 | def pushd(path): 404 | msg_trace('pushd(%r)' % path) 405 | old_path = os.getcwd() 406 | os.chdir(path) 407 | try: 408 | yield 409 | finally: 410 | msg_trace('popd(%r)' % old_path) 411 | os.chdir(old_path) 412 | 413 | @contextmanager 414 | def mkdtemp(prefix=None): 415 | path = tempfile.mkdtemp(prefix=prefix) 416 | msg_trace('mkdtemp(..) = %r' % path) 417 | try: 418 | yield path 419 | finally: 420 | really_rmtree(path) 421 | 422 | if __name__ == '__main__': 423 | sys.exit(main() or 0) 424 | -------------------------------------------------------------------------------- /pkg/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | -------------------------------------------------------------------------------- /pkg/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tlborm" 3 | version = "0.1.0" 4 | authors = ["Daniel Keep "] 5 | -------------------------------------------------------------------------------- /pkg/src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | This crate implements various macros detailed in [The Little Book of Rust Macros](https://danielkeep.github.io/tlborm/). 3 | 4 | If you use selective macro importing, you should make sure to *always* use the `tlborm_util` macro, as most macros in this crate depend on it being present. 5 | */ 6 | 7 | /** 8 | Forces the parser to interpret this macro's argument as an expression, even in the presence of `tt` substitutions. 9 | 10 | See [TLBoRM: AST Coercion](https://danielkeep.github.io/tlborm/book/blk-ast-coercion.html). 11 | 12 | ## Examples 13 | 14 | ```rust 15 | # #[macro_use] extern crate tlborm; 16 | # fn main() { 17 | assert_eq!(as_expr!(42), 42); 18 | 19 | macro_rules! conceal_as_tts { 20 | // The `tt` substitution will break regular parsing. 21 | (passthru, $($tts:tt)*) => {$($tts)*}; 22 | ($callback:ident, $($tts:tt)*) => {$callback!($($tts)*)}; 23 | } 24 | 25 | assert_eq!(conceal_as_tts!(as_expr, 2 * (3 + 4)), 14); 26 | # } 27 | ``` 28 | 29 | The following will *not* compile: 30 | 31 | 32 | 33 | ```ignore 34 | # #[macro_use(as_expr, tlborm_util)] extern crate tlborm; 35 | # fn main() { 36 | # macro_rules! conceal_as_tts { 37 | # (passthru, $($tts:tt)*) => {$($tts)*}; 38 | # ($callback:ident, $($tts:tt)*) => {$callback!($($tts)*)}; 39 | # } 40 | assert_eq!(conceal_as_tts!(passthru, 2 * (3 + 4)), 14); 41 | # } 42 | ``` 43 | */ 44 | #[macro_export] 45 | macro_rules! as_expr { ($e:expr) => {$e} } 46 | 47 | /** 48 | Forces the parser to interpret this macro's argument as an item, even in the presence of `tt` substitutions. 49 | 50 | See [TLBoRM: AST Coercion](https://danielkeep.github.io/tlborm/book/blk-ast-coercion.html). 51 | 52 | ## Examples 53 | 54 | ```rust 55 | # #[macro_use(as_item, tlborm_util)] extern crate tlborm; 56 | macro_rules! enoom { 57 | ($name:ident { $($body:tt)* }) => { 58 | as_item! { 59 | // The `tt` substitution breaks regular parsing. 60 | enum $name { $($body)* } 61 | } 62 | } 63 | } 64 | 65 | enoom! { 66 | Dash { Solid, Dash, Dot } 67 | } 68 | # fn main() {} 69 | ``` 70 | */ 71 | #[macro_export] 72 | macro_rules! as_item { ($i:item) => {$i} } 73 | 74 | /** 75 | Forces the parser to interpret this macro's argument as a pattern, even in the presence of `tt` substitutions. 76 | 77 | See [TLBoRM: AST Coercion](https://danielkeep.github.io/tlborm/book/blk-ast-coercion.html). 78 | 79 | ## Examples 80 | 81 | ```rust 82 | # #[macro_use(as_pat, tlborm_util)] extern crate tlborm; 83 | # fn main() { 84 | macro_rules! tuple_pat { 85 | ($($names:tt)*) => { 86 | // The `tt` substitution breaks regular parsing. 87 | as_pat!( ( $($names,)* ) ) 88 | } 89 | } 90 | 91 | match (1, 2, 3) { 92 | tuple_pat!(a b c) => assert_eq!((a, b, c), (1, 2, 3)) 93 | } 94 | # } 95 | ``` 96 | */ 97 | #[macro_export] 98 | macro_rules! as_pat { ($p:pat) => {$p} } 99 | 100 | /** 101 | Forces the parser to interpret this macro's argument as a statement, even in the presence of `tt` substitutions. 102 | 103 | See [TLBoRM: AST Coercion](https://danielkeep.github.io/tlborm/book/blk-ast-coercion.html). 104 | 105 | ## Examples 106 | 107 | ```rust 108 | # #[macro_use(as_stmt, tlborm_util)] extern crate tlborm; 109 | # fn main() { 110 | macro_rules! let_stmt { 111 | ($name:tt = $($init:tt)*) => { 112 | // The `tt` substitution breaks regular parsing. 113 | as_stmt!(let $name = $($init)*); 114 | } 115 | } 116 | 117 | let_stmt!(x = 42); 118 | assert_eq!(x, 42); 119 | # } 120 | ``` 121 | */ 122 | #[macro_export] 123 | macro_rules! as_stmt { ($s:stmt) => {$s} } 124 | 125 | /** 126 | Expands to the number of identifiers provided. The expansion is suitable for use in a constant expression, and is of type `u32`. 127 | 128 | The identifiers provided **must** be mutually unique; *i.e.* there cannot be any repeated identifiers. In addition, the identifier `__CountIdentsLast` **must not** be used in the invocation. This macro should be usable for even very large numbers of identifiers. 129 | 130 | See [TLBoRM: Counting (Enum counting)](https://danielkeep.github.io/tlborm/book/blk-counting.html#enum-counting). 131 | 132 | ## Examples 133 | 134 | ```rust 135 | # #[macro_use(count_idents_enum, tlborm_util)] extern crate tlborm; 136 | # fn main() { 137 | const NUM: u32 = count_idents_enum!(Silly swingers get your feeling under spell); 138 | assert_eq!(NUM, 7); 139 | # } 140 | */ 141 | #[macro_export] 142 | macro_rules! count_idents_enum { 143 | ($($idents:ident)*) => {tlborm_util!(@count_idents_enum $($idents)*)}; 144 | } 145 | 146 | /** 147 | Expands to the number of token trees provided. The expansion is suitable for use in a constant expression, and is of type `usize`. 148 | 149 | This macro is limited to input of approximately 500 tokens, but efficiently expands in a single pass. This makes it useful in recursion-limited contexts, or when you want fast expansion of small inputs. 150 | 151 | See [TLBoRM: Counting (Repetition with replacement)](https://danielkeep.github.io/tlborm/book/blk-counting.html#repetition-with-replacement). 152 | 153 | ## Examples 154 | 155 | ```rust 156 | # #[macro_use(count_tts_flat, tlborm_util)] extern crate tlborm; 157 | # fn main() { 158 | const NUM: usize = count_tts_flat!(Everybody's rhythm mad (and I love that rhythm too!)); 159 | assert_eq!(NUM, 5); 160 | # } 161 | */ 162 | #[macro_export] 163 | macro_rules! count_tts_flat { 164 | ($($tts:tt)*) => {tlborm_util!(@count_tts_flat $($tts)*)}; 165 | } 166 | 167 | /** 168 | Expands to the number of token trees provided. The expansion is suitable for use in a constant expression, and is of type `usize`. 169 | 170 | This macro is limited to input of approximately 1,200 tokens, but requires multiple recursive expansion passes. This macro is useful when you need to count a large number of things *and* you need the result to be a compile-time constant. 171 | 172 | See [TLBoRM: Counting (Recursion)](https://danielkeep.github.io/tlborm/book/blk-counting.html#recursion). 173 | 174 | ## Examples 175 | 176 | ```rust 177 | # #[macro_use(count_tts_recur, tlborm_util)] extern crate tlborm; 178 | # fn main() { 179 | const NUM: usize = count_tts_recur!(De l'enfer au paradis!); 180 | assert_eq!(NUM, 6); 181 | # } 182 | */ 183 | #[macro_export] 184 | macro_rules! count_tts_recur { 185 | ($($tts:tt)*) => {tlborm_util!(@count_tts_recur $($tts)*)}; 186 | } 187 | 188 | /** 189 | Expands to the number of token trees provided. The expansion is **not** suitable for use in a constant expression, though it should be optimised to a simple integer constant in release builds. 190 | 191 | This macro is has no practical limit (and has been tested to over 10,000 tokens). 192 | 193 | See [TLBoRM: Counting (Slice length)](https://danielkeep.github.io/tlborm/book/blk-counting.html#slice-length). 194 | 195 | ## Examples 196 | 197 | ```rust 198 | # #[macro_use(count_tts_slice, tlborm_util)] extern crate tlborm; 199 | # fn main() { 200 | let num = count_tts_slice!(You have no idea how tedious this is! #examplesrhard); 201 | assert_eq!(num, 11); 202 | # } 203 | */ 204 | #[macro_export] 205 | macro_rules! count_tts_slice { 206 | ($($tts:tt)*) => {tlborm_util!(@count_tts_slice $($tts)*)}; 207 | } 208 | 209 | /** 210 | Expands to an invocation of the `$callback` macro, with a list of the unitary variant names of the provided enum separated by commas. The invocation's argument will be prefixed by the contents of `$arg`. 211 | 212 | If `$arg` is of the form `{…}`, then the expansion will be parsed as one or more items. If it is of the form `(…)`, the expansion will be parsed as an expression. 213 | 214 | See [TLBoRM: Enum Parsing](https://danielkeep.github.io/tlborm/book/blk-enum-parsing.html). 215 | 216 | ## Examples 217 | 218 | ```rust 219 | # #[macro_use(parse_unitary_variants, tlborm_util)] extern crate tlborm; 220 | # fn main() { 221 | macro_rules! variant_list { 222 | (sep: $sep:tt, ($($var:ident),*)) => { 223 | concat!($(stringify!($var), $sep,)*) 224 | } 225 | } 226 | 227 | const LIST: &'static str = parse_unitary_variants!( 228 | enum Currency { Trenni, Phiring, Ryut, FakeMarinne, Faram, SoManyCoins } 229 | => variant_list(sep: ", ", ) 230 | ); 231 | assert_eq!(LIST, "Trenni, Phiring, Ryut, FakeMarinne, Faram, SoManyCoins, "); 232 | # } 233 | */ 234 | #[macro_export] 235 | macro_rules! parse_unitary_variants { 236 | ( 237 | enum $name:ident {$($body:tt)*} => $callback:ident $arg:tt 238 | ) => { 239 | tlborm_util! { 240 | @parse_unitary_variants 241 | enum $name {$($body)*} => $callback $arg 242 | } 243 | }; 244 | } 245 | 246 | /** 247 | Utility macro that takes a token tree and an expression, expanding to the expression. 248 | 249 | This is typically used to replace elements of an arbitrary token sequence with some fixed expression. 250 | 251 | See [TLBoRM: Repetition replacement](https://danielkeep.github.io/tlborm/book/pat-repetition-replacement.html). 252 | 253 | ## Examples 254 | 255 | ```rust 256 | # #[macro_use(replace_expr, tlborm_util)] extern crate tlborm; 257 | # fn main() { 258 | macro_rules! tts_to_zeroes { 259 | ($($tts:tt)*) => { 260 | [$(replace_expr!($tts 0)),*] 261 | } 262 | } 263 | 264 | assert_eq!(tts_to_zeroes!(pub const unsafe impl), [0, 0, 0, 0]); 265 | # } 266 | ``` 267 | */ 268 | #[macro_export] 269 | macro_rules! replace_expr { 270 | ($_t:tt $sub:expr) => {tlborm_util!(@replace_expr $_t $sub)}; 271 | } 272 | 273 | #[doc(hidden)] 274 | #[macro_export] 275 | macro_rules! tlborm_util { 276 | (@as_expr $e:expr) => {$e}; 277 | 278 | (@as_item $($i:item)+) => {$($i)+}; 279 | 280 | (@as_pat $p:pat) => {$p}; 281 | 282 | (@as_stmt $s:stmt) => {$s}; 283 | 284 | (@count_idents_enum $($idents:ident)*) => { 285 | { 286 | #[allow(dead_code, non_camel_case_types)] 287 | enum Idents { $($idents,)* __CountIdentsLast } 288 | const COUNT: u32 = Idents::__CountIdentsLast as u32; 289 | COUNT 290 | } 291 | }; 292 | 293 | (@count_tts_flat $($tts:tt)*) => {0usize $(+ tlborm_util!(@replace_expr $tts 1usize))*}; 294 | 295 | (@count_tts_recur 296 | $_a:tt $_b:tt $_c:tt $_d:tt $_e:tt 297 | $_f:tt $_g:tt $_h:tt $_i:tt $_j:tt 298 | $_k:tt $_l:tt $_m:tt $_n:tt $_o:tt 299 | $_p:tt $_q:tt $_r:tt $_s:tt $_t:tt 300 | $($tail:tt)*) 301 | => {20usize + tlborm_util!(@count_tts_recur $($tail)*)}; 302 | (@count_tts_recur 303 | $_a:tt $_b:tt $_c:tt $_d:tt $_e:tt 304 | $_f:tt $_g:tt $_h:tt $_i:tt $_j:tt 305 | $($tail:tt)*) 306 | => {10usize + tlborm_util!(@count_tts_recur $($tail)*)}; 307 | (@count_tts_recur 308 | $_a:tt $_b:tt $_c:tt $_d:tt $_e:tt 309 | $($tail:tt)*) 310 | => {5usize + tlborm_util!(@count_tts_recur $($tail)*)}; 311 | (@count_tts_recur 312 | $_a:tt 313 | $($tail:tt)*) 314 | => {1usize + tlborm_util!(@count_tts_recur $($tail)*)}; 315 | (@count_tts_recur) => {0usize}; 316 | 317 | (@count_tts_slice $($tts:tt)*) 318 | => {<[()]>::len(&[$(tlborm_util!(@replace_expr $tts ())),*])}; 319 | 320 | (@replace_expr $_t:tt $sub:expr) => {$sub}; 321 | 322 | // ======================================================================== 323 | // @parse_unitary_variants 324 | ( 325 | @parse_unitary_variants 326 | enum $name:ident {$($body:tt)*} => $callback:ident $arg:tt 327 | ) => { 328 | tlborm_util! { 329 | @collect_unitary_variants 330 | ($callback $arg), ($($body)*,) -> () 331 | } 332 | }; 333 | 334 | // ======================================================================== 335 | // @collect_unitary_variants 336 | // Exit rules. 337 | ( 338 | @collect_unitary_variants ($callback:ident ( $($args:tt)* )), 339 | ($(,)*) -> ($($var_names:ident,)*) 340 | ) => { 341 | tlborm_util! { 342 | @as_expr 343 | $callback!{ $($args)* ($($var_names),*) } 344 | } 345 | }; 346 | 347 | ( 348 | @collect_unitary_variants ($callback:ident { $($args:tt)* }), 349 | ($(,)*) -> ($($var_names:ident,)*) 350 | ) => { 351 | tlborm_util! { 352 | @as_item 353 | $callback!{ $($args)* ($($var_names),*) } 354 | } 355 | }; 356 | 357 | // Consume an attribute. 358 | ( 359 | @collect_unitary_variants $fixed:tt, 360 | (#[$_attr:meta] $($tail:tt)*) -> ($($var_names:tt)*) 361 | ) => { 362 | tlborm_util! { 363 | @collect_unitary_variants $fixed, 364 | ($($tail)*) -> ($($var_names)*) 365 | } 366 | }; 367 | 368 | // Handle a variant, optionally with an with initialiser. 369 | ( 370 | @collect_unitary_variants $fixed:tt, 371 | ($var:ident $(= $_val:expr)*, $($tail:tt)*) -> ($($var_names:tt)*) 372 | ) => { 373 | tlborm_util! { 374 | @collect_unitary_variants $fixed, 375 | ($($tail)*) -> ($($var_names)* $var,) 376 | } 377 | }; 378 | 379 | // Abort on variant with a payload. 380 | ( 381 | @collect_unitary_variants $fixed:tt, 382 | ($var:ident $_struct:tt, $($tail:tt)*) -> ($($var_names:tt)*) 383 | ) => { 384 | const _error: () = "cannot parse unitary variants from enum with non-unitary variants"; 385 | }; 386 | } 387 | -------------------------------------------------------------------------------- /redirects.json: -------------------------------------------------------------------------------- 1 | [ 2 | ["aeg/README", "aeg-README"], 3 | ["aeg/ook", "aeg-ook"], 4 | ["blk/README", "blk-README"], 5 | ["blk/counting", "blk-counting"], 6 | ["pat/README", "pat-README"], 7 | ["mbe", "mbe-README"] 8 | ] -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //! Dirty fix for rustbook's parsing errors 2 | #![allow(warnings)] 3 | use std::io::{Read, Write}; 4 | 5 | fn main() { 6 | println!("Start fixing..."); 7 | let mut cmd_args = std::env::args_os(); 8 | let book_path = cmd_args.nth(1).expect("Require the book path!"); 9 | let entries = std::fs::read_dir(book_path).expect("Wtf?!"); 10 | for entry in entries { 11 | if let Ok(entry) = entry { 12 | let os_filename = entry.file_name(); 13 | let filename = os_filename.to_str().expect("impossible"); 14 | if filename.ends_with(".html") { 15 | //println!("Parsing {}...", filename); 16 | let mut buffer = String::with_capacity(entry.metadata().expect("no metadata").len() as usize); 17 | { 18 | let f = std::fs::File::open(entry.path()).expect("file couldn't have not exist!"); 19 | (&f).read_to_string(&mut buffer).expect("???"); 20 | } 21 | let mut buffer_new = Vec::with_capacity(buffer.len()); 22 | process(buffer.as_bytes(), &mut buffer_new); 23 | let f = std::fs::File::create(entry.path()).expect("Coundn't create the file!"); 24 | (&f).write_all(&buffer_new).expect("Coundn't write"); 25 | } 26 | } 27 | } 28 | println!("Done!"); 29 | } 30 | 31 | fn process(buffer: &[u8], buffer_new: &mut Vec) { 32 | let mut index = 0 as usize; 33 | let mut state = 0; 34 | let mut temp = Vec::new(); 35 | while index < buffer.len() { 36 | if state == 0 { 37 | match try_inspect(buffer, index, 3) { 38 | Ok(seg) if seg == "

".as_bytes() => { 39 | //println!("Hit!"); 40 | state = 1; 41 | index += 3; 42 | buffer_new.write(seg); 43 | }, 44 | _ => { 45 | let next = try_read(buffer, &mut index, 1).expect("zzz1"); 46 | buffer_new.write(next); 47 | }, 48 | } 49 | }else if state == 1 { 50 | if let Ok(seg) = try_inspect(buffer, index, 4) { 51 | if seg == "

".as_bytes() { 52 | index += 4; 53 | state = 0; 54 | buffer_new.write(seg); 55 | continue; 56 | } 57 | } 58 | match try_inspect(buffer, index, 5) { 59 | Ok(seg) if seg == " { 60 | state = 2; 61 | index += 5; 62 | buffer_new.write(seg); 63 | }, 64 | Ok(seg) if seg == "
 {
 65 |                     //println!("Enter span");
 66 |                     state = 5;
 67 |                     index += 5;
 68 |                     buffer_new.write(seg);
 69 |                 },
 70 |                 Ok(seg) if seg == " {
 71 |                     //println!("Enter style");
 72 |                     state = 6;
 73 |                     index += 5;
 74 |                     buffer_new.write(seg);
 75 |                 },
 76 |                 _ => {
 77 |                     let next = try_read(buffer, &mut index, 1).expect("zzz2");
 78 |                     if next == "*".as_bytes() {
 79 |                         temp.clear();
 80 |                         let nnext = try_inspect(buffer, index, 1).expect("zzz3");
 81 |                         if nnext == "*".as_bytes() {
 82 |                             index += 1;
 83 |                             state = 4;
 84 |                             // buffer_new.write("".as_bytes());
 85 |                             } else {
 86 |                             state = 3;
 87 |                             // buffer_new.write("".as_bytes());
 88 |                         }
 89 |                     } else {
 90 |                         buffer_new.write(next);
 91 |                     }                   
 92 |                 },
 93 |             }
 94 |         }else if state == 2 {
 95 |             match try_inspect(buffer, index, 7) {
 96 |                 Ok(seg) if seg == "".as_bytes() => {
 97 |                     state = 1;
 98 |                     index += 7;
 99 |                     buffer_new.write(seg);
100 |                 },
101 |                 _ => {
102 |                     let next = try_read(buffer, &mut index, 1).expect("zzz");
103 |                     buffer_new.write(next);
104 |                 },
105 |             }
106 |         }else if state == 3 {
107 |             let next = try_read(buffer, &mut index, 1).expect("zzz");
108 |             if next == "*".as_bytes() {
109 |                 buffer_new.write("".as_bytes());
110 |                 buffer_new.write(&temp);
111 |                 buffer_new.write("".as_bytes());
112 |                 state = 1;
113 |             } else if next == "/n".as_bytes() {
114 |                 buffer_new.write("*".as_bytes());
115 |                 buffer_new.write(&temp);
116 |                 buffer_new.write("/n".as_bytes());
117 |                 state = 1;
118 |             }else {
119 |                 temp.write(next);
120 |             }
121 |         }else if state == 4 {
122 |             match try_inspect(buffer, index, 2) {
123 |                 Ok(seg) if seg == "**".as_bytes() => {
124 |                     buffer_new.write("".as_bytes());
125 |                     buffer_new.write(&temp);
126 |                     buffer_new.write("".as_bytes());
127 |                     index += 2;
128 |                     state = 1;
129 |                 },
130 |                 _ => {
131 |                     let next = try_read(buffer, &mut index, 1).expect("zzz");
132 |                     if next == "/n".as_bytes() {
133 |                         buffer_new.write("**".as_bytes());
134 |                         buffer_new.write(&temp);
135 |                         buffer_new.write("/n".as_bytes());
136 |                     } else {
137 |                         temp.write(next);
138 |                     }
139 |                 },
140 |             }
141 |         }else if state == 5 {
142 |             match try_inspect(buffer, index, 6) {
143 |                 Ok(seg) if seg == "
".as_bytes() => { 144 | state = 1; 145 | index += 6; 146 | //println!("Quit span"); 147 | buffer_new.write(seg); 148 | }, 149 | _ => { 150 | let next = try_read(buffer, &mut index, 1).expect("zzz"); 151 | buffer_new.write(next); 152 | }, 153 | } 154 | }else if state == 6 { 155 | match try_inspect(buffer, index, 8) { 156 | Ok(seg) if seg == "".as_bytes() => { 157 | state = 1; 158 | index += 8; 159 | //println!("Quit style"); 160 | buffer_new.write(seg); 161 | }, 162 | _ => { 163 | let next = try_read(buffer, &mut index, 1).expect("zzz"); 164 | buffer_new.write(next); 165 | }, 166 | } 167 | } 168 | } 169 | } 170 | 171 | fn try_read<'a>(line: &'a [u8], index: &mut usize, size: usize) -> Result<&'a [u8], ()> { 172 | if *index + size > line.len() { 173 | Err(()) 174 | } else { 175 | *index += size; 176 | // println!("{:?}", &line[*index-size..*index]); 177 | Ok(&line[*index-size..*index]) 178 | } 179 | } 180 | 181 | fn try_inspect<'a>(line: &'a [u8], index: usize, size: usize) -> Result<&'a [u8], ()> { 182 | if index + size > line.len() { 183 | Err(()) 184 | } else { 185 | // println!("{:?}", &line[index..index+size]); 186 | Ok(&line[index..index+size]) 187 | } 188 | } -------------------------------------------------------------------------------- /static/FiraSans-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaseinPhaos/tlborm-chinese/0b2432a356251dea7b616cb386b7905efe4f759c/static/FiraSans-Medium.woff -------------------------------------------------------------------------------- /static/FiraSans-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaseinPhaos/tlborm-chinese/0b2432a356251dea7b616cb386b7905efe4f759c/static/FiraSans-Regular.woff -------------------------------------------------------------------------------- /static/Heuristica-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaseinPhaos/tlborm-chinese/0b2432a356251dea7b616cb386b7905efe4f759c/static/Heuristica-Italic.woff -------------------------------------------------------------------------------- /static/SourceCodePro-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaseinPhaos/tlborm-chinese/0b2432a356251dea7b616cb386b7905efe4f759c/static/SourceCodePro-Regular.woff -------------------------------------------------------------------------------- /static/SourceCodePro-Semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaseinPhaos/tlborm-chinese/0b2432a356251dea7b616cb386b7905efe4f759c/static/SourceCodePro-Semibold.woff -------------------------------------------------------------------------------- /static/SourceSerifPro-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaseinPhaos/tlborm-chinese/0b2432a356251dea7b616cb386b7905efe4f759c/static/SourceSerifPro-Bold.woff -------------------------------------------------------------------------------- /static/SourceSerifPro-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaseinPhaos/tlborm-chinese/0b2432a356251dea7b616cb386b7905efe4f759c/static/SourceSerifPro-Regular.woff -------------------------------------------------------------------------------- /static/book/rustbook.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT 3 | * file at the top-level directory of this distribution and at 4 | * http://rust-lang.org/COPYRIGHT. 5 | * 6 | * Licensed under the Apache License, Version 2.0 or the MIT license 8 | * , at your 9 | * option. This file may not be copied, modified, or distributed 10 | * except according to those terms. 11 | * 12 | * Modified by Daniel Keep. 13 | */ 14 | 15 | @import url('../rust.css'); 16 | 17 | body { 18 | max-width: none; 19 | } 20 | 21 | h1, h2, h3, h4, h5, h6 { 22 | font-weight: bold; 23 | } 24 | 25 | @media only screen { 26 | #toc { 27 | position: fixed; 28 | top: 0; 29 | left: 0; 30 | bottom: 0; 31 | width: 300px; 32 | overflow-y: auto; 33 | border-right: 1px solid rgba(255, 255, 255, 0.07); 34 | padding: 0 15px; 35 | font-size: 14px; 36 | background-color: #1d1d1d; 37 | -webkit-overflow-scrolling: touch; 38 | } 39 | 40 | #page-wrapper { 41 | position: absolute; 42 | top: 0; 43 | left: 300px; 44 | right: 0; 45 | padding: 0 15px; 46 | -webkit-overflow-scrolling: touch; 47 | } 48 | } 49 | 50 | @media only print { 51 | #toc, #nav { 52 | display: none; 53 | } 54 | } 55 | 56 | @media only screen and (max-width: 1023px) { 57 | #toc { 58 | width: 100%; 59 | top: 40px; 60 | } 61 | 62 | #page-wrapper { 63 | top: 40px; 64 | left: 0; 65 | } 66 | 67 | .mobile-hidden { 68 | display: none; 69 | } 70 | } 71 | 72 | #page { 73 | margin: 0 auto; 74 | max-width: 750px; 75 | padding-bottom: 50px; 76 | } 77 | 78 | .chapter { 79 | list-style: none; 80 | padding-left: 0; 81 | line-height: 30px; 82 | } 83 | 84 | .section { 85 | list-style: none; 86 | padding-left: 20px; 87 | line-height: 40px; 88 | } 89 | 90 | .section li { 91 | text-overflow: ellipsis; 92 | overflow: hidden; 93 | white-space: nowrap; 94 | } 95 | 96 | .chapter li a { 97 | color: #ddd; 98 | padding: 5px 0; 99 | } 100 | 101 | .chapter li a.active, 102 | .chapter li a:hover { 103 | color: #008cff; 104 | text-decoration: none; 105 | } 106 | 107 | #toggle-nav { 108 | cursor: pointer; 109 | margin-top: 5px; 110 | width: 30px; 111 | height: 30px; 112 | background-color: #fff; 113 | border: 1px solid #666; 114 | border-radius: 3px; 115 | padding: 3px 3px 0 3px; 116 | } 117 | 118 | .sr-only { 119 | position: absolute; 120 | width: 1px; 121 | height: 1px; 122 | margin: -1px; 123 | padding: 0; 124 | overflow: hidden; 125 | clip: rect(0, 0, 0, 0); 126 | border: 0; 127 | } 128 | 129 | .bar { 130 | display: block; 131 | background-color: #000; 132 | border-radius: 2px; 133 | width: 100%; 134 | height: 2px; 135 | margin: 2px 0 3px; 136 | padding: 0; 137 | } 138 | 139 | .left { 140 | float: left; 141 | } 142 | 143 | .right { 144 | float: right; 145 | } 146 | -------------------------------------------------------------------------------- /static/code/ook.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "158"] 2 | 3 | type CellType = u8; 4 | const MEM_SIZE: usize = 30_000; 5 | 6 | macro_rules! Ook { 7 | (@start $($Ooks:tt)*) => { 8 | { 9 | fn ook() -> ::std::io::Result> { 10 | use ::std::io; 11 | use ::std::io::prelude::*; 12 | 13 | fn _re() -> io::Error { 14 | io::Error::new( 15 | io::ErrorKind::Other, 16 | String::from("ran out of input")) 17 | } 18 | 19 | fn _inc(a: &mut [u8], i: usize) { 20 | let c = &mut a[i]; 21 | *c = c.wrapping_add(1); 22 | } 23 | 24 | fn _dec(a: &mut [u8], i: usize) { 25 | let c = &mut a[i]; 26 | *c = c.wrapping_sub(1); 27 | } 28 | 29 | let _r = &mut io::stdin(); 30 | let _w = &mut io::stdout(); 31 | 32 | let mut _a: Vec = Vec::with_capacity(MEM_SIZE); 33 | _a.extend(::std::iter::repeat(0).take(MEM_SIZE)); 34 | let mut _i = 0; 35 | { 36 | let _a = &mut *_a; 37 | Ook!(@e (_a, _i, _inc, _dec, _r, _w, _re); ($($Ooks)*)); 38 | } 39 | Ok(_a) 40 | } 41 | ook() 42 | } 43 | }; 44 | 45 | /* 46 | 47 | ## Everything Else 48 | 49 | */ 50 | 51 | (@e $syms:tt; ()) => {}; 52 | 53 | (@e ($a:expr, $i:expr, $inc:expr, $dec:expr, $r:expr, $w:expr, $re:expr); (Ook. Ook? $($tail:tt)*)) => { 54 | $i = ($i + 1) % MEM_SIZE; 55 | Ook!(@e ($a, $i, $inc, $dec, $r, $w, $re); ($($tail)*)); 56 | }; 57 | 58 | (@e ($a:expr, $i:expr, $inc:expr, $dec:expr, $r:expr, $w:expr, $re:expr); (Ook? Ook. $($tail:tt)*)) => { 59 | $i = if $i == 0 { MEM_SIZE } else { $i } - 1; 60 | Ook!(@e ($a, $i, $inc, $dec, $r, $w, $re); ($($tail)*)); 61 | }; 62 | 63 | (@e ($a:expr, $i:expr, $inc:expr, $dec:expr, $r:expr, $w:expr, $re:expr); (Ook. Ook. $($tail:tt)*)) => { 64 | $inc($a, $i); 65 | Ook!(@e ($a, $i, $inc, $dec, $r, $w, $re); ($($tail)*)); 66 | }; 67 | 68 | (@e ($a:expr, $i:expr, $inc:expr, $dec:expr, $r:expr, $w:expr, $re:expr); (Ook! Ook! $($tail:tt)*)) => { 69 | $dec($a, $i); 70 | Ook!(@e ($a, $i, $inc, $dec, $r, $w, $re); ($($tail)*)); 71 | }; 72 | 73 | (@e ($a:expr, $i:expr, $inc:expr, $dec:expr, $r:expr, $w:expr, $re:expr); (Ook! Ook. $($tail:tt)*)) => { 74 | try!($w.write_all(&$a[$i .. $i+1])); 75 | Ook!(@e ($a, $i, $inc, $dec, $r, $w, $re); ($($tail)*)); 76 | }; 77 | 78 | (@e ($a:expr, $i:expr, $inc:expr, $dec:expr, $r:expr, $w:expr, $re:expr); (Ook. Ook! $($tail:tt)*)) => { 79 | try!( 80 | match $r.read(&mut $a[$i .. $i+1]) { 81 | Ok(0) => Err($re()), 82 | ok @ Ok(..) => ok, 83 | err @ Err(..) => err 84 | } 85 | ); 86 | Ook!(@e ($a, $i, $inc, $dec, $r, $w, $re); ($($tail)*)); 87 | }; 88 | 89 | (@e ($a:expr, $i:expr, $inc:expr, $dec:expr, $r:expr, $w:expr, $re:expr); (Ook! Ook? $($tail:tt)*)) => { 90 | while $a[$i] != 0 { 91 | Ook!(@x ($a, $i, $inc, $dec, $r, $w, $re); (); (); ($($tail)*)); 92 | } 93 | Ook!(@s ($a, $i, $inc, $dec, $r, $w, $re); (); ($($tail)*)); 94 | }; 95 | 96 | /* 97 | 98 | ## Loop Extraction 99 | 100 | The input is of the form `(@x (syms...) (depth...); (buf...); tail...)`. 101 | 102 | `syms` is the set of symbols (expression, actually), that we need for the 103 | actual expanded code. They're parenthesised so that we can pass them 104 | around as a `tt`. 105 | 106 | `depth` is the current nesting depth, represented as a paren'd sequence of 107 | `@`s. The parens are *empty* in the outer-most loop. 108 | 109 | `buf` is the sequence of collected tokens that belong inside the outer-most 110 | loop. These are kept inside parens. 111 | 112 | `tail` is the sequence of tokens yet to be processed. 113 | */ 114 | (@x $syms:tt; (); ($($buf:tt)*); (Ook? Ook! $($tail:tt)*)) => { 115 | // Outer-most loop is closed. Process the buffered tokens. 116 | Ook!(@e $syms; ($($buf)*)); 117 | }; 118 | 119 | (@x $syms:tt; ($($depth:tt)*); ($($buf:tt)*); (Ook! Ook? $($tail:tt)*)) => { 120 | // One level deeper. 121 | Ook!(@x $syms; (@ $($depth)*); ($($buf)* Ook! Ook?); ($($tail)*)); 122 | }; 123 | 124 | (@x $syms:tt; (@ $($depth:tt)*); ($($buf:tt)*); (Ook? Ook! $($tail:tt)*)) => { 125 | // One level higher. 126 | Ook!(@x $syms; ($($depth)*); ($($buf)* Ook? Ook!); ($($tail)*)); 127 | }; 128 | 129 | (@x $syms:tt; $depth:tt; ($($buf:tt)*); (Ook $op0:tt Ook $op1:tt $($tail:tt)*)) => { 130 | Ook!(@x $syms; $depth; ($($buf)* Ook $op0 Ook $op1); ($($tail)*)); 131 | }; 132 | 133 | /* 134 | 135 | ## Loop Skipping 136 | 137 | This is the same as above, except sans-buffer. We just need to find the 138 | end of the loop, then resume normal processing. 139 | 140 | */ 141 | (@s $syms:tt; (); (Ook? Ook! $($tail:tt)*)) => { 142 | // Outer-most loop is closed. Resume normal operation. 143 | Ook!(@e $syms; ($($tail)*)); 144 | }; 145 | 146 | (@s $syms:tt; ($($depth:tt)*); (Ook! Ook? $($tail:tt)*)) => { 147 | // One level deeper. 148 | Ook!(@s $syms; (@ $($depth)*); ($($tail)*)); 149 | }; 150 | 151 | (@s $syms:tt; (@ $($depth:tt)*); (Ook? Ook! $($tail:tt)*)) => { 152 | // One level higher. 153 | Ook!(@s $syms; ($($depth)*); ($($tail)*)); 154 | }; 155 | 156 | (@s $syms:tt; ($($depth:tt)*); (Ook $op0:tt Ook $op1:tt $($tail:tt)*)) => { 157 | Ook!(@s $syms; ($($depth)*); ($($tail)*)); 158 | }; 159 | 160 | // This is dangerous if you get it wrong! 161 | ($($Ooks:tt)*) => { 162 | Ook!(@start $($Ooks)*) 163 | }; 164 | } 165 | 166 | fn main() { 167 | let _ = Ook!( 168 | Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. 169 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 170 | Ook. Ook. Ook. Ook. Ook! Ook? Ook? Ook. 171 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 172 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 173 | Ook. Ook? Ook! Ook! Ook? Ook! Ook? Ook. 174 | Ook! Ook. Ook. Ook? Ook. Ook. Ook. Ook. 175 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 176 | Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook. 177 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? 178 | Ook! Ook! Ook? Ook! Ook? Ook. Ook. Ook. 179 | Ook! Ook. Ook. Ook. Ook. Ook. Ook. Ook. 180 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 181 | Ook! Ook. Ook! Ook. Ook. Ook. Ook. Ook. 182 | Ook. Ook. Ook! Ook. Ook. Ook? Ook. Ook? 183 | Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. 184 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 185 | Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook. 186 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? 187 | Ook! Ook! Ook? Ook! Ook? Ook. Ook! Ook. 188 | Ook. Ook? Ook. Ook? Ook. Ook? Ook. Ook. 189 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 190 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 191 | Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook. 192 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 193 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 194 | Ook. Ook? Ook! Ook! Ook? Ook! Ook? Ook. 195 | Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook. 196 | Ook? Ook. Ook? Ook. Ook? Ook. Ook? Ook. 197 | Ook! Ook. Ook. Ook. Ook. Ook. Ook. Ook. 198 | Ook! Ook. Ook! Ook! Ook! Ook! Ook! Ook! 199 | Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook. 200 | Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! 201 | Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! 202 | Ook! Ook. Ook. Ook? Ook. Ook? Ook. Ook. 203 | Ook! Ook. Ook! Ook? Ook! Ook! Ook? Ook! 204 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 205 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 206 | Ook. Ook. Ook. Ook. Ook! Ook. 207 | ); 208 | } 209 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rust宏小册 6 | 7 | 22 | 23 | 24 | 25 |

Rust宏小册

26 |

进入

27 | 28 | 29 | -------------------------------------------------------------------------------- /static/license-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Daniel Keep 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /static/rust.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT 3 | * file at the top-level directory of this distribution and at 4 | * http://rust-lang.org/COPYRIGHT. 5 | * With elements taken from Bootstrap v3.0.2 (MIT licensed). 6 | * 7 | * Licensed under the Apache License, Version 2.0 or the MIT license 9 | * , at your 10 | * option. This file may not be copied, modified, or distributed 11 | * except according to those terms. 12 | * 13 | * Modified by Daniel Keep. 14 | */ 15 | @font-face { 16 | font-family: 'Fira Sans'; 17 | font-style: normal; 18 | font-weight: 400; 19 | src: local('Fira Sans'), url("FiraSans-Regular.woff") format('woff'); 20 | } 21 | @font-face { 22 | font-family: 'Fira Sans'; 23 | font-style: normal; 24 | font-weight: 500; 25 | src: local('Fira Sans Medium'), url("FiraSans-Medium.woff") format('woff'); 26 | } 27 | @font-face { 28 | font-family: 'Source Serif Pro'; 29 | font-style: normal; 30 | font-weight: 400; 31 | src: local('Source Serif Pro'), url("SourceSerifPro-Regular.woff") format('woff'); 32 | } 33 | @font-face { 34 | font-family: 'Source Serif Pro'; 35 | font-style: italic; 36 | font-weight: 400; 37 | src: url("Heuristica-Italic.woff") format('woff'); 38 | } 39 | @font-face { 40 | font-family: 'Source Serif Pro'; 41 | font-style: normal; 42 | font-weight: 700; 43 | src: local('Source Serif Pro Bold'), url("SourceSerifPro-Bold.woff") format('woff'); 44 | } 45 | @font-face { 46 | font-family: 'Source Code Pro'; 47 | font-style: normal; 48 | font-weight: 400; 49 | src: local('Source Code Pro'), url("SourceCodePro-Regular.woff") format('woff'); 50 | } 51 | 52 | *:not(body) { 53 | -webkit-box-sizing: border-box; 54 | -moz-box-sizing: border-box; 55 | box-sizing: border-box; 56 | } 57 | 58 | /* General structure */ 59 | 60 | body { 61 | background-color: #1b1b1b; 62 | margin: 0 auto; 63 | padding: 0 15px; 64 | font-family: "Source Serif Pro", Georgia, Times, "Times New Roman", serif; 65 | font-size: 18px; 66 | color: #ddd; 67 | line-height: 1.428571429; 68 | 69 | -webkit-font-feature-settings: "kern", "liga"; 70 | -moz-font-feature-settings: "kern", "liga"; 71 | font-feature-settings: "kern", "liga"; 72 | } 73 | @media (min-width: 768px) { 74 | body { 75 | max-width: 750px; 76 | } 77 | } 78 | 79 | h1, h2, h3, h4, h5, h6, nav, #versioninfo { 80 | font-family: "Open Sans", "Fira Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; 81 | } 82 | h1, h2, h3, h4, h5, h6 { 83 | color: #eee; 84 | font-weight: 400; 85 | line-height: 1.1; 86 | } 87 | h1, h2, h3 { 88 | margin-top: 20px; 89 | margin-bottom: 15px; 90 | } 91 | h1 { 92 | margin-bottom: 20px; 93 | } 94 | h4, h5, h6 { 95 | margin-top: 12px; 96 | margin-bottom: 10px; 97 | padding: 5px 10px; 98 | } 99 | h5, h6 { 100 | text-decoration: underline; 101 | } 102 | 103 | h1 { 104 | font-size: 28px; 105 | font-weight: 500; 106 | padding: .1em .4em; 107 | border-bottom: 2px solid #666; 108 | } 109 | h1.title { 110 | line-height: 1.5em; 111 | } 112 | h2 { 113 | font-size: 26px; 114 | padding: .2em .5em; 115 | border-bottom: 1px solid #666; 116 | } 117 | h3 { 118 | font-size: 24px; 119 | padding: .2em .7em; 120 | border-bottom: 1px solid #666; 121 | } 122 | h4 { 123 | font-size: 22px; 124 | } 125 | h5 { 126 | font-size: 20px; 127 | } 128 | h6 { 129 | font-size: 18px; 130 | } 131 | @media (min-width: 992px) { 132 | h1 { 133 | font-size: 36px; 134 | } 135 | h2 { 136 | font-size: 30px; 137 | } 138 | h3 { 139 | font-size: 26px; 140 | } 141 | } 142 | 143 | nav { 144 | column-count: 2; 145 | -moz-column-count: 2; 146 | -webkit-column-count: 2; 147 | font-size: 15px; 148 | margin: 0 0 1em 0; 149 | } 150 | p { 151 | margin: 0 0 1em 0; 152 | } 153 | 154 | strong { 155 | font-weight: bold; 156 | } 157 | 158 | em { 159 | font-style: italic; 160 | } 161 | 162 | footer { 163 | border-top: 1px solid #666; 164 | font-size: 14.3px; 165 | font-style: italic; 166 | padding-top: 5px; 167 | margin-top: 3em; 168 | margin-bottom: 1em; 169 | } 170 | 171 | /* Links layout */ 172 | 173 | a { 174 | text-decoration: none; 175 | color: #428BCA; 176 | background: transparent; 177 | } 178 | a:hover, a:focus { 179 | color: #2A6496; 180 | text-decoration: underline; 181 | } 182 | a:focus { 183 | outline: thin dotted #ddd; 184 | outline: 5px auto -webkit-focus-ring-color; 185 | outline-offset: -2px; 186 | } 187 | a:hover, a:active { 188 | outline: 0; 189 | } 190 | 191 | h1 a:link, h1 a:visited, h2 a:link, h2 a:visited, 192 | h3 a:link, h3 a:visited, h4 a:link, h4 a:visited, 193 | h5 a:link, h5 a:visited { 194 | color: #eee; 195 | } 196 | h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover, 197 | h5 a:hover {text-decoration: none;} 198 | 199 | /* Code */ 200 | 201 | pre, code { 202 | font-family: "Source Code Pro", Menlo, Monaco, Consolas, "DejaVu Sans Mono", monospace; 203 | word-wrap: break-word; 204 | } 205 | pre > code.language-text { 206 | font-family: "DejaVu Sans Mono", unifont, "Consolas", "Liberation Mono", monospace; 207 | } 208 | pre { 209 | background-color: #222222; 210 | color: #f8f8f2; 211 | border-left: 2px solid #666; 212 | white-space: pre-wrap; 213 | padding: 14px; 214 | padding-right: 0; 215 | margin: 20px 0; 216 | font-size: 13px; 217 | word-break: break-all; 218 | } 219 | pre code { 220 | background-color: transparent; 221 | border: 0; 222 | } 223 | code { 224 | padding: 0 2px; 225 | color: #E1D4A9; 226 | font-size: 13px; 227 | background-color: #222222; 228 | border-bottom: 1px solid #181818; 229 | } 230 | pre code { 231 | padding: 0; 232 | font-size: inherit; 233 | color: inherit; 234 | } 235 | 236 | a > code { 237 | color: #428BCA; 238 | } 239 | 240 | /* Code highlighting */ 241 | pre.rust .kw { color: #f92672; } 242 | pre.rust .kw-2 { color: #f92672; } 243 | pre.rust .prelude-ty { color: #66d9ef; } 244 | pre.rust .number { color: #EFD6AB; } 245 | pre.rust .string { color: #F8E9A5; } 246 | pre.rust .self { color: #f92672; } 247 | pre.rust .boolval { color: #EFD6AB; } 248 | pre.rust .prelude-val { color: #66d9ef; } 249 | pre.rust .attribute { color: #C7AFD1; } 250 | pre.rust .attribute .ident { color: #D4BBDE; } 251 | pre.rust .comment { color: #75715e; } 252 | pre.rust .doccomment { color: #75715e; } 253 | pre.rust .macro { color: #AC7FE7; } 254 | pre.rust .macro-nonterminal { color: #CFC0E2; } 255 | pre.rust .lifetime { color: #B76514; } 256 | pre.rust .op { color: #f92672; } 257 | 258 | pre.rust .synctx-0 { background-color: rgba(255, 0, 0, 0.1); } 259 | pre.rust .synctx-1 { background-color: rgba(0, 255, 0, 0.1); } 260 | code .synctx-0 { background-color: rgba(255, 0, 0, 0.1); } 261 | code .synctx-1 { background-color: rgba(0, 255, 0, 0.1); } 262 | 263 | /* The rest */ 264 | 265 | #versioninfo { 266 | text-align: center; 267 | margin: 0.5em; 268 | font-size: 1.1em; 269 | } 270 | @media (min-width: 992px) { 271 | #versioninfo { 272 | font-size: 0.8em; 273 | position: fixed; 274 | bottom: 0px; 275 | right: 0px; 276 | } 277 | .white-sticker { 278 | background-color: #fff; 279 | margin: 2px; 280 | padding: 0 2px; 281 | border-radius: .2em; 282 | } 283 | } 284 | #versioninfo a.hash { 285 | color: gray; 286 | font-size: 80%; 287 | } 288 | 289 | blockquote { 290 | color: #ddd; 291 | margin: 20px 0; 292 | padding: 15px 20px; 293 | background-color: #1d1d1d; 294 | border-top: .1em solid #666; 295 | border-bottom: .1em solid #666; 296 | } 297 | blockquote p { 298 | font-size: 17px; 299 | font-weight: 300; 300 | line-height: 1.4; 301 | } 302 | blockquote p:last-child { 303 | margin-bottom: 0; 304 | } 305 | 306 | ul, ol { 307 | padding-left: 25px; 308 | } 309 | ul ul, ol ul, ul ol, ol ol { 310 | margin-bottom: 0; 311 | } 312 | dl { 313 | margin-bottom: 20px; 314 | } 315 | dd { 316 | margin-left: 0; 317 | } 318 | 319 | nav ul { 320 | list-style-type: none; 321 | margin: 0; 322 | padding-left: 0px; 323 | } 324 | 325 | /* Only display one level of hierarchy in the TOC */ 326 | nav ul ul { 327 | display: none; 328 | } 329 | 330 | sub, 331 | sup { 332 | font-size: 75%; 333 | line-height: 0; 334 | position: relative; 335 | } 336 | 337 | hr { 338 | margin-top: 20px; 339 | margin-bottom: 20px; 340 | border: 0; 341 | border-top: 1px solid #666; 342 | } 343 | 344 | table { 345 | border-collapse: collapse; 346 | border-spacing: 0; 347 | overflow-x: auto; 348 | display: block; 349 | } 350 | 351 | table tr.odd { 352 | background: #eee; 353 | } 354 | 355 | table td, 356 | table th { 357 | border: 1px solid #666; 358 | padding: 5px; 359 | } 360 | 361 | /* Code snippets */ 362 | 363 | .rusttest { display: none; } 364 | pre.rust { position: relative; } 365 | .test-arrow { 366 | display: inline-block; 367 | position: absolute; 368 | top: 0; 369 | right: 10px; 370 | font-size: 150%; 371 | -webkit-transform: scaleX(-1); 372 | transform: scaleX(-1); 373 | } 374 | 375 | .unstable-feature { 376 | border: 2px solid red; 377 | padding: 5px; 378 | } 379 | 380 | @media (min-width: 1170px) { 381 | pre { 382 | font-size: 15px; 383 | } 384 | } 385 | 386 | @media print { 387 | * { 388 | text-shadow: none !important; 389 | color: #000 !important; 390 | background: transparent !important; 391 | box-shadow: none !important; 392 | } 393 | a, a:visited { 394 | text-decoration: underline; 395 | } 396 | p a[href]:after { 397 | content: " (" attr(href) ")"; 398 | } 399 | footer a[href]:after { 400 | content: ""; 401 | } 402 | a[href^="javascript:"]:after, a[href^="#"]:after { 403 | content: ""; 404 | } 405 | pre, blockquote { 406 | border: 1px solid #666; 407 | page-break-inside: avoid; 408 | } 409 | @page { 410 | margin: 2cm .5cm; 411 | } 412 | h1:not(.title), h2, h3 { 413 | border-bottom: 0px none; 414 | } 415 | p, h2, h3 { 416 | orphans: 3; 417 | widows: 3; 418 | } 419 | h2, h3 { 420 | page-break-after: avoid; 421 | } 422 | table { 423 | border-collapse: collapse !important; 424 | } 425 | table td, table th { 426 | background-color: #fff !important; 427 | } 428 | } 429 | 430 | #keyword-table-marker + table thead { display: none; } 431 | #keyword-table-marker + table td { border: none; } 432 | #keyword-table-marker + table { 433 | margin-left: 2em; 434 | margin-bottom: 1em; 435 | } 436 | -------------------------------------------------------------------------------- /text/README.md: -------------------------------------------------------------------------------- 1 | % Rust宏小册 中文版 2 | 3 | **注意**: 本作品尚在建设过程中。 4 | 5 | 本书试图提炼出一份Rust社区对Rust宏知识的集锦。因此,我们欢迎社区成员进行内容添补(通过pull)或提出需求(通过issue)。 6 | 7 | 本文档为[The Little Book of Rust Macros](https://danielkeep.github.io/tlborm/)的中文翻译。如果希望为原文作出贡献,请移步至[原版的repository](https://github.com/DanielKeep/tlborm/)。中文版的repo[在这里](https://github.com/DaseinPhaos/tlborm)。 8 | 9 | ## 致谢 10 | 11 | 感谢下列成员作出的建议及修正: IcyFoxy, Rym, TheMicroWorm, Yurume, akavel, cmr, eddyb, ogham, and snake_case. 12 | 13 | ## 许可证 14 | 15 | 本作品同时采用 [知识共享署名 - 相同方式共享 4.0 国际许可协议 ](http://creativecommons.org/licenses/by-sa/4.0/) 以及 [MIT license](http://opensource.org/licenses/MIT). 16 | -------------------------------------------------------------------------------- /text/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [宏,彻底剖析](mbe-README.md) 4 | * [语法扩展](mbe-syn-README.md) 5 | * [源码解析过程](mbe-syn-source-analysis.md) 6 | * [AST中的宏](mbe-syn-macros-in-the-ast.md) 7 | * [展开](mbe-syn-expansion.md) 8 | * [macro_rules!](mbe-macro-rules.md) 9 | * [细枝末节](mbe-min-README.md) 10 | * [再探捕获与展开](mbe-min-captures-and-expansion-redux.md) 11 | * [卫生性](mbe-min-hygiene.md) 12 | * [不是标识符的标识符](mbe-min-non-identifier-identifiers.md) 13 | * [调试](mbe-min-debugging.md) 14 | * [作用域](mbe-min-scoping.md) 15 | * [导入/导出](mbe-min-import-export.md) 16 | * [宏,实践介绍](pim-README.md) 17 | * [常用模式](pat-README.md) 18 | * [回调](pat-callbacks.md) 19 | * [标记树撕咬机](pat-incremental-tt-munchers.md) 20 | * [内用规则](pat-internal-rules.md) 21 | * [下推累积](pat-push-down-accumulation.md) 22 | * [重复替代](pat-repetition-replacement.md) 23 | * [尾部分隔符](pat-trailing-separators.md) 24 | * [标记树聚束](pat-tt-bundling.md) 25 | * [可见性](pat-visibility.md) 26 | * [临时措施](pat-provisional.md) 27 | * [轮子](blk-README.md) 28 | * [AST强转](blk-ast-coercion.md) 29 | * [计数](blk-counting.md) 30 | * [枚举解析](blk-enum-parsing.md) 31 | * [实例注解](aeg-README.md) 32 | * [Ook!](aeg-ook.md) 33 | -------------------------------------------------------------------------------- /text/aeg-README.md: -------------------------------------------------------------------------------- 1 | % 实例注解 2 | 3 | 此章节包含一些实际代码环境[^*]中的宏,并附上说明其设计与构造的注解。 4 | 5 | [^*]: 大部分情况下是如此 6 | -------------------------------------------------------------------------------- /text/aeg-ook.md: -------------------------------------------------------------------------------- 1 | % Ook! 2 | 3 | 此宏是对[Ook!密文](http://www.dangermouse.net/esoteric/ook.html)的实现,该语言与[Brainfuck密文](http://www.muppetlabs.com/~breadbox/bf/)同构。 4 | 5 | 此语言的执行模式非常简单:内存被表示为一列总量不定(通常至少包括30,000个)的“单元”(每个单元至少8bit);另有一个指向该内存的指针,最初指向位置0;还有一个执行栈(用来实现循环)和一个指向程序代码的指针。最后两个组件并未暴露给程序本身,它们属于程序的运行时性质。 6 | 7 | 语言本身仅由三种标记,`Ook.`、`Ook?`及`Ook!`构成。它们两两组合,构成了八种运算符: 8 | 9 | * `Ook. Ook?` - 指针增。 10 | * `Ook? Ook.` - 指针减。 11 | * `Ook. Ook.` - 所指单元增。 12 | * `Ook! Ook!` - 所指单元减。 13 | * `Ook! Ook.` - 将所指单元写至标准输出。 14 | * `Ook. Ook!` - 从标准输入读至所指单元。 15 | * `Ook! Ook?` - 进入循环。 16 | * `Ook? Ook!` - 如果所指单元非零,跳回循环起始位置;否则,继续执行。 17 | 18 | Ook!之所以有趣,是因为它图灵完备。这意味着,你必须要在同样图灵完备的环境中才能实现它。 19 | 20 | ## 实现 21 | 22 | ```ignore 23 | #![recursion_limit = "158"] 24 | ``` 25 | 26 | 实际上,这个值将是我们随后给出的示例程序编译成功所需的最低值。如果你很好奇究竟是怎样的程序,会如此这般复杂,以至于必须要把递归极限调至默认值的近五倍大... [请大胆猜测](https://en.wikipedia.org/wiki/Hello_world_program)。 27 | 28 | ```ignore 29 | type CellType = u8; 30 | const MEM_SIZE: usize = 30_000; 31 | ``` 32 | 33 | 加入这些定义,以供宏展开使用[^*] 34 | 35 | [^*]: 我们的确可以在宏内部定义这些,但那样一来它们就必须被显式的传来传去(由于宏的卫生性)。坦率地讲,当我意识到真的需要这些定义的时候,我的宏已经写得七七八八了...如果没有绝对的必要,你想跟我一样尝试修补那样的烂摊子吗? 36 | 37 | ```ignore 38 | macro_rules! Ook { 39 | ``` 40 | 41 | 可能应该取名`ook!`以符合Rust标准的命名传统。然而良机不可错失,我们就用本名吧! 42 | 43 | 我们使用了[内用规则](pat-internal-rules.md)模式;此宏的规则因而可被分为几个模块。 44 | 45 | 第一块是`@start`规则,负责为之后的展开搭建舞台。没什么特别的地方:先定义一些变量、效用函数,然后处理展开的大头。 46 | 47 | 一些脚注: 48 | 49 | * 我们之所以选择展开为函数,很大原因在于,这样以来就可以采用`try!`来简化错误处理流程。 50 | * 有些时候,举例来说,如果用户想写一个不做I/O的程序,那么I/O相关的名称就不会被用到。我们让部分名称以`_`起头,正是为了使编译器不对此类情况产生抱怨。 51 | 52 | ```ignore 53 | (@start $($Ooks:tt)*) => { 54 | { 55 | fn ook() -> ::std::io::Result> { 56 | use ::std::io; 57 | use ::std::io::prelude::*; 58 | 59 | fn _re() -> io::Error { 60 | io::Error::new( 61 | io::ErrorKind::Other, 62 | String::from("ran out of input")) 63 | } 64 | 65 | fn _inc(a: &mut [u8], i: usize) { 66 | let c = &mut a[i]; 67 | *c = c.wrapping_add(1); 68 | } 69 | 70 | fn _dec(a: &mut [u8], i: usize) { 71 | let c = &mut a[i]; 72 | *c = c.wrapping_sub(1); 73 | } 74 | 75 | let _r = &mut io::stdin(); 76 | let _w = &mut io::stdout(); 77 | 78 | let mut _a: Vec = Vec::with_capacity(MEM_SIZE); 79 | _a.extend(::std::iter::repeat(0).take(MEM_SIZE)); 80 | let mut _i = 0; 81 | { 82 | let _a = &mut *_a; 83 | Ook!(@e (_a, _i, _inc, _dec, _r, _w, _re); ($($Ooks)*)); 84 | } 85 | Ok(_a) 86 | } 87 | ook() 88 | } 89 | }; 90 | ``` 91 | 92 | ### 解析运算符码 93 | 94 | 接下来,是一列“执行”规则,用于从输入中解析运算符码。 95 | 96 | 这列规则的通用形式是`(@e $syms; ($input))`。从`@start`规则种我们可以看出,`$syms`包含了实现Ook程序所必须的符号:输入、输出、内存等等。我们使用了[标记树聚束](pat-tt-bundling.md)来简化转发这些符号的流程。 97 | 98 | 第一条规则是终止规则:一旦没有更多输入,我们就停下来。 99 | 100 | ```ignore 101 | (@e $syms:tt; ()) => {}; 102 | ``` 103 | 104 | 其次,是一些适用于绝大部分运算符码的规则:我们剥下运算符码,换上其相应的Rust代码,然后继续递归处理输入剩下的部分。教科书式的[标记树撕咬机](pat-incremental-tt-munchers.md)。 105 | 106 | ```ignore 107 | // 指针增 108 | (@e ($a:expr, $i:expr, $inc:expr, $dec:expr, $r:expr, $w:expr, $re:expr); 109 | (Ook. Ook? $($tail:tt)*)) 110 | => { 111 | $i = ($i + 1) % MEM_SIZE; 112 | Ook!(@e ($a, $i, $inc, $dec, $r, $w, $re); ($($tail)*)); 113 | }; 114 | 115 | // 指针减 116 | (@e ($a:expr, $i:expr, $inc:expr, $dec:expr, $r:expr, $w:expr, $re:expr); 117 | (Ook? Ook. $($tail:tt)*)) 118 | => { 119 | $i = if $i == 0 { MEM_SIZE } else { $i } - 1; 120 | Ook!(@e ($a, $i, $inc, $dec, $r, $w, $re); ($($tail)*)); 121 | }; 122 | 123 | // 所指增 124 | (@e ($a:expr, $i:expr, $inc:expr, $dec:expr, $r:expr, $w:expr, $re:expr); 125 | (Ook. Ook. $($tail:tt)*)) 126 | => { 127 | $inc($a, $i); 128 | Ook!(@e ($a, $i, $inc, $dec, $r, $w, $re); ($($tail)*)); 129 | }; 130 | 131 | // 所指减 132 | (@e ($a:expr, $i:expr, $inc:expr, $dec:expr, $r:expr, $w:expr, $re:expr); 133 | (Ook! Ook! $($tail:tt)*)) 134 | => { 135 | $dec($a, $i); 136 | Ook!(@e ($a, $i, $inc, $dec, $r, $w, $re); ($($tail)*)); 137 | }; 138 | 139 | // 输出 140 | (@e ($a:expr, $i:expr, $inc:expr, $dec:expr, $r:expr, $w:expr, $re:expr); 141 | (Ook! Ook. $($tail:tt)*)) 142 | => { 143 | try!($w.write_all(&$a[$i .. $i+1])); 144 | Ook!(@e ($a, $i, $inc, $dec, $r, $w, $re); ($($tail)*)); 145 | }; 146 | 147 | // 读入 148 | (@e ($a:expr, $i:expr, $inc:expr, $dec:expr, $r:expr, $w:expr, $re:expr); 149 | (Ook. Ook! $($tail:tt)*)) 150 | => { 151 | try!( 152 | match $r.read(&mut $a[$i .. $i+1]) { 153 | Ok(0) => Err($re()), 154 | ok @ Ok(..) => ok, 155 | err @ Err(..) => err 156 | } 157 | ); 158 | Ook!(@e ($a, $i, $inc, $dec, $r, $w, $re); ($($tail)*)); 159 | }; 160 | ``` 161 | 162 | 现在要弄复杂的了。运算符码`Ook! Ook?`标记着循环的开始。Ook!中的循环,翻译成Rust代码的话,类似: 163 | 164 | > **注意**:这不是宏定义的组成部分。 165 | > 166 | > ```ignore 167 | > while memory[ptr] != 0 { 168 | > // 循环内容 169 | > } 170 | > ``` 171 | 172 | 显然,我们无法为中间步骤生成不完整的循环。这一问题似可用[下推累积](pat-push-down-accumulation.md)解决,但更根本的困难在于,我们无论如何都没办法生成类似`while memory[ptr] != {`的中间结果,完全不可能。因为它引入了不匹配的花括号。 173 | 174 | 解决方法是,把输入分成两部分:循环内部的,循环之后的。前者由`@x`规则处理,后者由`@s`处理。 175 | 176 | ```ignore 177 | (@e ($a:expr, $i:expr, $inc:expr, $dec:expr, $r:expr, $w:expr, $re:expr); 178 | (Ook! Ook? $($tail:tt)*)) 179 | => { 180 | while $a[$i] != 0 { 181 | Ook!(@x ($a, $i, $inc, $dec, $r, $w, $re); (); (); ($($tail)*)); 182 | } 183 | Ook!(@s ($a, $i, $inc, $dec, $r, $w, $re); (); ($($tail)*)); 184 | }; 185 | ``` 186 | 187 | ### 提取循环区块 188 | 189 | 接下来是“提取”规则组`@x`。它们负责接受输入尾,并将之展开为循环的内容。这组规则的一般形式为:`(@x $sym; $depth; $buf; $tail)`。 190 | 191 | `$sym`的用处与上相同。`$tail`表示需要被解析的输入;而`$buf`则作为[下推累积的缓存](pat-push-down-accumulation.md),循环内的运算符码在经过解析后将被存入其中。那么,`$depth`代表什么? 192 | 193 | 目前为止,我们还未提及如何处理嵌套循环。`$depth`的作用正在于此:我们需要记录当前循环在整个嵌套之中的深度,同时保证解析不会过早或过晚终止,而是刚好停在恰当的位置。[^justright] 194 | 195 | [^justright]: 一个鲜有人知的事实[^fact]是,金发姑娘的故事实际上是一则写给准确词法解析技术的寓言。 196 | 197 | [^fact]: 这个“事实”实际上意思是“臭不要脸的谣言”。 198 | 199 | 由于在宏中没办法进行计算,而将数目匹配规则一一列出又不太可行(想想下面这一整套规则都得复制粘贴一堆不算小的整数的话,会是什么样子),我们将只好回头采用最古老最珍贵的计数方法之一:亲手去数。 200 | 201 | 当然了,宏没有手,我们实际采用的是[算盘计数模式](pat-provisional.md#算盘计数)。具体来说,我们选用标记`@`,每个`@`都表示新的一层嵌套。把这些`@`们放进一组后,我们就可以实现所需的操作了: 202 | 203 | * 增加层数:匹配`($($depth:tt)*)`并用`(@ $($depth)*)`替换。 204 | * 减少层数:匹配`(@ $($depth:tt)*)`并用`($($depth)*)`替换。 205 | * 与0相比较:匹配`()`。 206 | 207 | 规则组中的第一条规则,用于在找到 `Ook? Ook!`输入序列时,终止当前循环体的解析。随后,我们需要把累积所得的循环体内容发给先前定义的`@e`组规则。 208 | 209 | 注意,规则对于输入所剩的尾部不作任何处理(这项工作将由`@s`组的规则完成)。 210 | 211 | ```ignore 212 | (@x $syms:tt; (); ($($buf:tt)*); 213 | (Ook? Ook! $($tail:tt)*)) 214 | => { 215 | // 最外层的循环已被处理完毕,现在转而处理缓存到的标记。 216 | Ook!(@e $syms; ($($buf)*)); 217 | }; 218 | ``` 219 | 220 | 紧接着,是负责进出嵌套的一些规则。它们修改深度计数,并将运算符码放入缓存。 221 | 222 | ```ignore 223 | (@x $syms:tt; ($($depth:tt)*); ($($buf:tt)*); 224 | (Ook! Ook? $($tail:tt)*)) 225 | => { 226 | // 嵌套变深 227 | Ook!(@x $syms; (@ $($depth)*); ($($buf)* Ook! Ook?); ($($tail)*)); 228 | }; 229 | 230 | (@x $syms:tt; (@ $($depth:tt)*); ($($buf:tt)*); 231 | (Ook? Ook! $($tail:tt)*)) 232 | => { 233 | // 嵌套变浅 234 | Ook!(@x $syms; ($($depth)*); ($($buf)* Ook? Ook!); ($($tail)*)); 235 | }; 236 | ``` 237 | 238 | 最后剩下的所有情况将交由一条规则处理。注意到它用的`$op0`和`$op1`两处捕获;对于Rust来说,Ook!中的一个标记将被视作两个标记:标识符`Ook`与剩下的符号。因此,我们用此规则来处理其它任何Ook!的非循环运算符,将`!`, `?`和`.`作为`tt`匹配,并捕获之。 239 | 240 | 我们放置`$depth`,仅将运算符码推至缓存区中。 241 | 242 | ```ignore 243 | (@x $syms:tt; $depth:tt; ($($buf:tt)*); 244 | (Ook $op0:tt Ook $op1:tt $($tail:tt)*)) 245 | => { 246 | Ook!(@x $syms; $depth; ($($buf)* Ook $op0 Ook $op1); ($($tail)*)); 247 | }; 248 | ``` 249 | 250 | ### 跳过循环区块 251 | 252 | 这组规则与循环提取大致相同,不过它们并不关心循环的内容(也因此不需要累积缓存)。它们仅仅关心循环何时被完全跳过。彼时,我们将恢复到`@e`组规则中并继续处理剩下的输入。 253 | 254 | 因此,我们将不加进一步说明地列出它们: 255 | 256 | ```ignore 257 | // End of loop. 258 | (@s $syms:tt; (); 259 | (Ook? Ook! $($tail:tt)*)) 260 | => { 261 | Ook!(@e $syms; ($($tail)*)); 262 | }; 263 | 264 | // Enter nested loop. 265 | (@s $syms:tt; ($($depth:tt)*); 266 | (Ook! Ook? $($tail:tt)*)) 267 | => { 268 | Ook!(@s $syms; (@ $($depth)*); ($($tail)*)); 269 | }; 270 | 271 | // Exit nested loop. 272 | (@s $syms:tt; (@ $($depth:tt)*); 273 | (Ook? Ook! $($tail:tt)*)) 274 | => { 275 | Ook!(@s $syms; ($($depth)*); ($($tail)*)); 276 | }; 277 | 278 | // Not a loop opcode. 279 | (@s $syms:tt; ($($depth:tt)*); 280 | (Ook $op0:tt Ook $op1:tt $($tail:tt)*)) 281 | => { 282 | Ook!(@s $syms; ($($depth)*); ($($tail)*)); 283 | }; 284 | ``` 285 | 286 | ### 入口 287 | 288 | 这是唯一一条非内用规则。 289 | 290 | 需注意的一点是,由于此规则单纯地匹配*所有*提供的标记,它极其危险。任何错误输入,都将造成其上的内用规则匹配完全失败,进而又落至匹配它(成功)的后果;引发无尽递归。 291 | 292 | 当在写、改及调试此类宏的过程中,明智的做法是,在此类规则的匹配头部加上临时性前缀,比如给此例加上一个`@entry`;以防止无尽递归,并得到更加恰当有效的错误信息。 293 | 294 | ```ignore 295 | ($($Ooks:tt)*) => { 296 | Ook!(@start $($Ooks)*) 297 | }; 298 | } 299 | ``` 300 | 301 | ### 用例 302 | 303 | 现在终于是时候上测试了。 304 | 305 | ```ignore 306 | fn main() { 307 | let _ = Ook!( 308 | Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. 309 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 310 | Ook. Ook. Ook. Ook. Ook! Ook? Ook? Ook. 311 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 312 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 313 | Ook. Ook? Ook! Ook! Ook? Ook! Ook? Ook. 314 | Ook! Ook. Ook. Ook? Ook. Ook. Ook. Ook. 315 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 316 | Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook. 317 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? 318 | Ook! Ook! Ook? Ook! Ook? Ook. Ook. Ook. 319 | Ook! Ook. Ook. Ook. Ook. Ook. Ook. Ook. 320 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 321 | Ook! Ook. Ook! Ook. Ook. Ook. Ook. Ook. 322 | Ook. Ook. Ook! Ook. Ook. Ook? Ook. Ook? 323 | Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. 324 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 325 | Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook. 326 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? 327 | Ook! Ook! Ook? Ook! Ook? Ook. Ook! Ook. 328 | Ook. Ook? Ook. Ook? Ook. Ook? Ook. Ook. 329 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 330 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 331 | Ook. Ook. Ook! Ook? Ook? Ook. Ook. Ook. 332 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 333 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 334 | Ook. Ook? Ook! Ook! Ook? Ook! Ook? Ook. 335 | Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook. 336 | Ook? Ook. Ook? Ook. Ook? Ook. Ook? Ook. 337 | Ook! Ook. Ook. Ook. Ook. Ook. Ook. Ook. 338 | Ook! Ook. Ook! Ook! Ook! Ook! Ook! Ook! 339 | Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook. 340 | Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! 341 | Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! 342 | Ook! Ook. Ook. Ook? Ook. Ook? Ook. Ook. 343 | Ook! Ook. Ook! Ook? Ook! Ook! Ook? Ook! 344 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 345 | Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. 346 | Ook. Ook. Ook. Ook. Ook! Ook. 347 | ); 348 | } 349 | ``` 350 | 351 | 运行(在编译器进行数百次递归宏展开而停顿相当长一段时间之后)的输出将是: 352 | 353 | ```text 354 | Hello World! 355 | ``` 356 | 357 | 由此,我们揭示出了令人惊恐的真相:`macro_rules!`是图灵完备的! 358 | 359 | ### 附注 360 | 361 | 此文所基的宏,是一个名为“Hodor!”的同构语言实现。Manish Goregaokar后来[用那个宏实现了一个Brainfuck的解析器](https://www.reddit.com/r/rust/comments/39wvrm/hodor_esolang_as_a_rust_macro/cs76rqk?context=10000)。也就是说,那个Brainfuck解析器用`Hodor!`宏写成,而后者本身则又是由`macro_rules!`实现的! 362 | 363 | 传说在把递归极限提至*三百万*,并让之编译了*四天*后,整个过程终于得以完成。 364 | 365 | ...收场的方式是栈溢出中止。时至今日,Rust宏驱动的密文编程语言仍然绝非可行的开发手段。 366 | -------------------------------------------------------------------------------- /text/blk-README.md: -------------------------------------------------------------------------------- 1 | % 轮子 2 | 3 | 可复用的宏代码片段。 4 | -------------------------------------------------------------------------------- /text/blk-ast-coercion.md: -------------------------------------------------------------------------------- 1 | % AST强转 2 | 3 | 在替换`tt`时,Rust的解析器并不十分可靠。当它期望得到某类特定的语法构造时,如果摆在它面前的是一坨替换后的`tt`标记,就有可能出现问题。解析器常常直接选择死亡,而非尝试去解析它们。在这类情况中,就要用到AST强转。 4 | 5 | ```rust 6 | # #![allow(dead_code)] 7 | # 8 | macro_rules! as_expr { ($e:expr) => {$e} } 9 | macro_rules! as_item { ($i:item) => {$i} } 10 | macro_rules! as_pat { ($p:pat) => {$p} } 11 | macro_rules! as_stmt { ($s:stmt) => {$s} } 12 | # 13 | # as_item!{struct Dummy;} 14 | # 15 | # fn main() { 16 | # as_stmt!(let as_pat!(_) = as_expr!(42)); 17 | # } 18 | ``` 19 | 20 | 这些强制变换经常与[下推累积](pat-push-down-accumulation.md)宏一同使用,以使解析器能够将最终输出的`tt`序列当作某类特定的语法构造对待。 21 | 22 | 注意,之所以只有这几种强转宏,是因为决定可用强转类型的点在于宏可展开在哪些地方,而不是宏能够捕捉哪些东西。也就是说,因为宏没办法在·`type`处展开[issue-27245](https://github.com/rust-lang/rust/issues/27245),所以就没办法实现什么`as_ty!`强转。 23 | -------------------------------------------------------------------------------- /text/blk-counting.md: -------------------------------------------------------------------------------- 1 | % 计数 2 | 3 | ## 重复替代 4 | 5 | 在宏中计数是一项让人吃惊地难搞的活儿。最简单的方式是采用重复替代。 6 | 7 | ```rust 8 | macro_rules! replace_expr { 9 | ($_t:tt $sub:expr) => {$sub}; 10 | } 11 | 12 | macro_rules! count_tts { 13 | ($($tts:tt)*) => {0usize $(+ replace_expr!($tts 1usize))*}; 14 | } 15 | # 16 | # fn main() { 17 | # assert_eq!(count_tts!(0 1 2), 3); 18 | # } 19 | ``` 20 | 21 | 对于小数目来说,这方法不错,但当输入量到达500标记附近时,编译器将被击溃。想想吧,输出的结果将类似: 22 | 23 | ```ignore 24 | 0usize + 1usize + /* ~500 `+ 1usize`s */ + 1usize 25 | ``` 26 | 27 | 编译器必须把这一大串解析成一棵AST,那可会是一棵完美失衡的500多级深的二叉树。 28 | 29 | ## 递归 30 | 31 | 递归是个老套路。 32 | 33 | ```rust 34 | macro_rules! count_tts { 35 | () => {0usize}; 36 | ($_head:tt $($tail:tt)*) => {1usize + count_tts!($($tail)*)}; 37 | } 38 | # 39 | # fn main() { 40 | # assert_eq!(count_tts!(0 1 2), 3); 41 | # } 42 | ``` 43 | 44 | > **注意**:对于`rustc`1.2来说,很不幸,编译器在处理大数量的类型未知的整型字面值时将会出现性能问题。我们此处显式采用`usize`类型就是为了避免这种不幸。 45 | > 46 | > 如果这样做并不合适(比如说,当类型必须可替换时),可通过`as`来减轻问题。(比如,`0 as $ty`, `1 as $ty`等)。 47 | 48 | 这方法管用,但很快就会超出宏递归的次数限制。与重复替换不同的是,可通过增加匹配分支来增加可处理的输入面值。 49 | 50 | ```rust 51 | macro_rules! count_tts { 52 | ($_a:tt $_b:tt $_c:tt $_d:tt $_e:tt 53 | $_f:tt $_g:tt $_h:tt $_i:tt $_j:tt 54 | $_k:tt $_l:tt $_m:tt $_n:tt $_o:tt 55 | $_p:tt $_q:tt $_r:tt $_s:tt $_t:tt 56 | $($tail:tt)*) 57 | => {20usize + count_tts!($($tail)*)}; 58 | ($_a:tt $_b:tt $_c:tt $_d:tt $_e:tt 59 | $_f:tt $_g:tt $_h:tt $_i:tt $_j:tt 60 | $($tail:tt)*) 61 | => {10usize + count_tts!($($tail)*)}; 62 | ($_a:tt $_b:tt $_c:tt $_d:tt $_e:tt 63 | $($tail:tt)*) 64 | => {5usize + count_tts!($($tail)*)}; 65 | ($_a:tt 66 | $($tail:tt)*) 67 | => {1usize + count_tts!($($tail)*)}; 68 | () => {0usize}; 69 | } 70 | 71 | fn main() { 72 | assert_eq!(700, count_tts!( 73 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, 74 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, 75 | 76 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, 77 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, 78 | 79 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, 80 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, 81 | 82 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, 83 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, 84 | 85 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, 86 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, 87 | 88 | // 重复替换手段差不多将在此处崩溃 89 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, 90 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, 91 | 92 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, 93 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, 94 | )); 95 | } 96 | ``` 97 | 98 | 我们列出的这种形式可以处理差不多1,200个标记。 99 | 100 | ## 切片长度 101 | 102 | 第三种方法,是帮助编译器构建一个深度较小的AST,以避免栈溢出。可以通过新建数列,并调用其`len`方法来做到。 103 | 104 | ```rust 105 | macro_rules! replace_expr { 106 | ($_t:tt $sub:expr) => {$sub}; 107 | } 108 | 109 | macro_rules! count_tts { 110 | ($($tts:tt)*) => {<[()]>::len(&[$(replace_expr!($tts ())),*])}; 111 | } 112 | # 113 | # fn main() { 114 | # assert_eq!(count_tts!(0 1 2), 3); 115 | # } 116 | ``` 117 | 118 | 经过测试,这种方法可处理高达10,000个标记数,可能还能多上不少。缺点是,就Rust 1.2来说,没法拿它生成常量表达式。即便结果可以被优化成一个简单的常数(在`debug build`里得到的编译结果仅是一次内存载入),它仍然无法被用在常量位置(如`const`值或定长数组的长度值)。 119 | 120 | 不过,如果非常量计数够用的话,此方法很大程度上是上选。 121 | 122 | ## 枚举计数 123 | 124 | 当你需要计互不相同的标识符的数量时,可以用到此方法。结果还可被用作常量。 125 | 126 | ```rust 127 | macro_rules! count_idents { 128 | ($($idents:ident),* $(,)*) => { 129 | { 130 | #[allow(dead_code, non_camel_case_types)] 131 | enum Idents { $($idents,)* __CountIdentsLast } 132 | const COUNT: u32 = Idents::__CountIdentsLast as u32; 133 | COUNT 134 | } 135 | }; 136 | } 137 | # 138 | # fn main() { 139 | # const COUNT: u32 = count_idents!(A, B, C); 140 | # assert_eq!(COUNT, 3); 141 | # } 142 | ``` 143 | 144 | 此方法的确有两大缺陷。其一,如上所述,它仅能被用于数有效的标识符(同时还不能是关键词),而且它不允许那些标识符有重复。其二,此方法不具备卫生性;就是说如果你的末位标识符(在`__CountIdentsLast`位置的标识符)的字面值也是输入之一,宏调用就会失败,因为`enum`中包含重复变量。 145 | -------------------------------------------------------------------------------- /text/blk-enum-parsing.md: -------------------------------------------------------------------------------- 1 | % 枚举解析 2 | 3 | ```rust 4 | macro_rules! parse_unitary_variants { 5 | (@as_expr $e:expr) => {$e}; 6 | (@as_item $($i:item)+) => {$($i)+}; 7 | 8 | // Exit rules. 9 | ( 10 | @collect_unitary_variants ($callback:ident ( $($args:tt)* )), 11 | ($(,)*) -> ($($var_names:ident,)*) 12 | ) => { 13 | parse_unitary_variants! { 14 | @as_expr 15 | $callback!{ $($args)* ($($var_names),*) } 16 | } 17 | }; 18 | 19 | ( 20 | @collect_unitary_variants ($callback:ident { $($args:tt)* }), 21 | ($(,)*) -> ($($var_names:ident,)*) 22 | ) => { 23 | parse_unitary_variants! { 24 | @as_item 25 | $callback!{ $($args)* ($($var_names),*) } 26 | } 27 | }; 28 | 29 | // Consume an attribute. 30 | ( 31 | @collect_unitary_variants $fixed:tt, 32 | (#[$_attr:meta] $($tail:tt)*) -> ($($var_names:tt)*) 33 | ) => { 34 | parse_unitary_variants! { 35 | @collect_unitary_variants $fixed, 36 | ($($tail)*) -> ($($var_names)*) 37 | } 38 | }; 39 | 40 | // Handle a variant, optionally with an with initialiser. 41 | ( 42 | @collect_unitary_variants $fixed:tt, 43 | ($var:ident $(= $_val:expr)*, $($tail:tt)*) -> ($($var_names:tt)*) 44 | ) => { 45 | parse_unitary_variants! { 46 | @collect_unitary_variants $fixed, 47 | ($($tail)*) -> ($($var_names)* $var,) 48 | } 49 | }; 50 | 51 | // Abort on variant with a payload. 52 | ( 53 | @collect_unitary_variants $fixed:tt, 54 | ($var:ident $_struct:tt, $($tail:tt)*) -> ($($var_names:tt)*) 55 | ) => { 56 | const _error: () = "cannot parse unitary variants from enum with non-unitary variants"; 57 | }; 58 | 59 | // Entry rule. 60 | (enum $name:ident {$($body:tt)*} => $callback:ident $arg:tt) => { 61 | parse_unitary_variants! { 62 | @collect_unitary_variants 63 | ($callback $arg), ($($body)*,) -> () 64 | } 65 | }; 66 | } 67 | # 68 | # fn main() { 69 | # assert_eq!( 70 | # parse_unitary_variants!( 71 | # enum Dummy { A, B, C } 72 | # => stringify(variants:) 73 | # ), 74 | # "variants : ( A , B , C )" 75 | # ); 76 | # } 77 | ``` 78 | 79 | 此宏展示了如何使用[标记树撕咬机](pat-incremental-tt-munchers.md)与[下推累积](pat-push-down-accumulation.md)来解析类C枚举的变量。完成后的`parse_unitary_variants!`宏将调用一个[回调](pat-callbacks.md)宏,为后者提供枚举中的所有选择(以及任何附加参数)。 80 | 81 | 经过改造后,此宏将也能用于解析`struct`的域,或为枚举变量计算标签值,甚至是将任意一个枚举中的所有变量名称都提取出来。 82 | -------------------------------------------------------------------------------- /text/mbe-README.md: -------------------------------------------------------------------------------- 1 | % 宏,彻底剖析 2 | 3 | 本章节将介绍Rust的“示例宏”(Macro-By-Example)系统:`macro_rules`。我们不会通过实际的示例来介绍它,而将尝试对此系统的**运作方式**给出完备且彻底的解释。因此,本章的目标读者应是那些想要理清这整个系统的人,而非那些仅仅想要了解它一般该怎么用的人。 4 | 5 | 在[Rust官方教程中也有一章讲解宏](http://doc.rust-lang.org/book/macros.html)([中文版](https://kaisery.gitbooks.io/rust-book-chinese/content/content/Macros%20%E5%AE%8F.html)),它更易理解,提供的解释更加高层。本书也有一章[实践介绍](pim-README.md),旨在阐释如何在实践中实现一个宏。 6 | -------------------------------------------------------------------------------- /text/mbe-macro-rules.md: -------------------------------------------------------------------------------- 1 | % macro_rules! 2 | 3 | 有了这些知识,我们终于可以引入`macro_rules!` 了。 如前所述,`macro_rules!`本身就是一个语法扩展,也就是说它并不是Rust语法的一部分。它的形式如下: 4 | 5 | ```rust 6 | macro_rules! $name { 7 | $rule0 ; 8 | $rule1 ; 9 | // … 10 | $ruleN ; 11 | } 12 | ``` 13 | 14 | 至少得有一条规则,最后一条规则后面的分号可被省略。 15 | 16 | 每条“规则”(`rule`)都形如: 17 | 18 | ```ignore 19 | ($pattern) => {$expansion} 20 | ``` 21 | 22 | 实际上,分组符号可以是任意一种,选用这种(`pattern`外小括号、`expansion`外花括号)只是出于传统。 23 | 24 | 如果你好奇的话,`macro_rules!`的调用将被展开为空。至少可以说,在AST中它被展开为空。它所影响的是编译器内部的结构,以将该宏注册进系统中去。因此,技术上讲你可以在任何一个空展开合法的位置插入`macro_rules!`的调用。 25 | 26 | ## 匹配 27 | 28 | 当一个宏被调用时,对应的`macro_rules`解释器将一一依序检查规则。对每条规则,它都将尝试将输入标记树的内容与该规则的`pattern`进行匹配。某个模式必须与输入**完全**匹配才能被选中为匹配项。 29 | 30 | 如果输入与某个模式相匹配,则该调用项将被相应的`expansion`内容所取代;否则,将尝试匹配下条规则。如果所有规则均匹配失败,则宏展开会失败并报错。 31 | 32 | 最简单的例子是空模式: 33 | 34 | ```rust 35 | macro_rules! four { 36 | () => {1 + 3}; 37 | } 38 | ``` 39 | 40 | 它将且仅将匹配到空的输入(即`four!()`, `four![]`或`four!{}`)。 41 | 42 | 注意调用所用的分组标记并不需要匹配定义时采用的分组标记。也就是说,你可以通过`four![]`调用上述宏,此调用仍将被视作匹配。只有调用时的输入**内容**才会被纳入匹配考量范围。 43 | 44 | 模式中也可以包含字面标记树。这些标记树必须被完全匹配。将整个对应标记树在相应位置写下即可。比如,为匹配标记序列`4 fn ['spang "whammo"] @_@`,我们可以使用: 45 | 46 | ```ignore 47 | macro_rules! gibberish { 48 | (4 fn ['spang "whammo"] @_@) => {...}; 49 | } 50 | ``` 51 | 52 | 53 | 54 | ## 捕获 55 | 56 | 宏模式中还可以包含捕获。这允许输入匹配在某种通用语法基础上进行,并使得结果被捕获进某个变量中。此变量可在输出中被替换使用。 57 | 58 | 捕获由`$`符号紧跟一个标识符(identifier)紧跟一个冒号(`:`)紧跟捕获种类组成。捕获种类须是如下之一: 59 | 60 | * `item`: 条目,比如函数、结构体、模组等。 61 | * `block`: 区块(即由花括号包起的一些语句加上/或是一项表达式)。 62 | * `stmt`: 语句 63 | * `pat`: 模式 64 | * `expr`: 表达式 65 | * `ty`: 类型 66 | * `ident`: 标识符 67 | * `path`: 路径 (例如 `foo`, `::std::mem::replace`, `transmute::<_, int>`, …) 68 | * `meta`: 元条目,即被包含在 `#[...]`及`#![...]`属性内的东西。 69 | * `tt`: 标记树 70 | 71 | 举例来说,下列宏将其输入捕获为一个表达式: 72 | 73 | ```rust 74 | macro_rules! one_expression { 75 | ($e:expr) => {...}; 76 | } 77 | ``` 78 | 79 | Rust编译器的语法转义器将保证捕获的“准确性”。一个`expr`捕获总是会捕获到一个对当前Rust版本来说完整、有效的表达式。 80 | 81 | 你可以将字面标记树与捕获混合使用,但有些限制(接下来将阐明它们)。 82 | 83 | 在扩展过程中,对于某捕获`$name:kind`,我们可以通过在`expansion`中写下`$name`来使用它。比如: 84 | 85 | ```rust 86 | macro_rules! times_five { 87 | ($e:expr) => {5 * $e}; 88 | } 89 | ``` 90 | 91 | 如同宏扩展本身一样,每一处捕获也都将被替换为一个完整的AST节点。也就是说,在上例中无论`$e`所捕获的是怎样的标记序列,它总会被解读成一个完整的表达式。 92 | 93 | 在一条模式中也可以出现多次捕获: 94 | 95 | ```rust 96 | macro_rules! multiply_add { 97 | ($a:expr, $b:expr, $c:expr) => {$a * ($b + $c)}; 98 | } 99 | ``` 100 | 101 | ## 重复 102 | 103 | 模式中可以包含重复。这使得匹配标记序列成为可能。重复的一般形式为`$ ( ... ) sep rep`. 104 | 105 | * `$` 是字面标记。 106 | * `( ... )` 代表了将要被重复匹配的模式,由小括号包围。 107 | * `sep`是一个可选的分隔标记。常用例子包括`,`和`;`。 108 | * `rep`是重复控制标记。当前有两种选择,分别是`*` (代表接受0或多次重复)以及`+` (代表1或多次重复)。目前没有办法指定“0或1”或者任何其它更加具体的重复计数或区间。 109 | 110 | 重复中可以包含任意有效模式,包括字面标记树,捕获,以及其它的重复。 111 | 112 | 在扩展部分,重复也采用相同的语法。 113 | 114 | 举例来说,下述宏将每一个`element`都通过`format!`转换成字符串。它将匹配0或多个由逗号分隔的表达式,并分别将它们展开成一个`Vec`的`push`语句。 115 | 116 | ```rust 117 | macro_rules! vec_strs { 118 | ( 119 | // 重复开始: 120 | $( 121 | // 每次重复必须有一个表达式... 122 | $element:expr 123 | ) 124 | // ...重复之间由“,”分隔... 125 | , 126 | // ...总共重复0或多次. 127 | * 128 | ) => { 129 | // 为了能包含多条语句, 130 | // 我们将扩展部分包裹在花括号中... 131 | { 132 | let mut v = Vec::new(); 133 | 134 | // 重复开始: 135 | $( 136 | // 每次重复将包含如下元素,其中 137 | // “$element”将被替换成其相应的展开... 138 | v.push(format!("{}", $element)); 139 | )* 140 | 141 | v 142 | } 143 | }; 144 | } 145 | # 146 | # fn main() { 147 | # let s = vec_strs![1, "a", true, 3.14159f32]; 148 | # assert_eq!(&*s, &["1", "a", "true", "3.14159"]); 149 | # } 150 | ``` 151 | 152 | -------------------------------------------------------------------------------- /text/mbe-min-README.md: -------------------------------------------------------------------------------- 1 | % 细枝末节 2 | 3 | 本节将介绍宏系统的一些细枝末节。你最少应该记住有这些东西存在。 4 | -------------------------------------------------------------------------------- /text/mbe-min-captures-and-expansion-redux.md: -------------------------------------------------------------------------------- 1 | % 再探捕获与展开 2 | 3 | 一旦语法分析器开始消耗标记以匹配某捕获,整个过程便无法停止或回溯。这意味着,下述宏的第二项规则将永远无法被匹配到,无论输入是什么样的: 4 | 5 | ```rust 6 | macro_rules! dead_rule { 7 | ($e:expr) => { ... }; 8 | ($i:ident +) => { ... }; 9 | } 10 | ``` 11 | 12 | 考虑当以`dead_rule!(x+)`形式调用此宏时,将会发生什么。解析器将从第一条规则开始试图进行匹配:它试图将输入解析为一个表达式;第一个标记(`x`)作为表达式是有效的,第二个标记——作为二元加的节点——在表达式中也是有效的。 13 | 14 | 至此,由于输入中并不包含二元加的右手侧元素,你可能会以为,分析器将会放弃尝试这一规则,转而尝试下一条规则。实则不然:分析器将会`panic`并终止整个编译过程,返回一个语法错误。 15 | 16 | 由于分析器的这一特点,下面这点尤为重要:一般而言,在书写宏规则时,应从最具体的开始写起,依次写至最不具体的。 17 | 18 | 为防范未来宏输入的解读方式改变所可能带来的句法影响,`macro_rules!`对各式捕获之后所允许的内容施加了诸多限制。在Rust1.3下,完整的列表如下: 19 | 20 | * `item`: 任何标记 21 | * `block`: 任何标记 22 | * `stmt`: `=>` `、` `;` 23 | * `pat`: `=>` `、` `=`、 `if`、 `in` 24 | * `expr`: `=>` `、` `;` 25 | * `ty`: `,`、 `=>`、 `:`、 `=`、 `>`、 `;`、 `as` 26 | * `ident`: 任何标记 27 | * `path`: `,`、 `=>`、 `:`、 `=`、 `>`、 `;`、 `as` 28 | * `meta`: 任何标记 29 | * `tt`: 任何标记 30 | 31 | 此外,`macro_rules!` 通常不允许一个重复紧跟在另一重复之后,即便它们的内容并不冲突。 32 | 33 | 有一条关于替换的特征经常引人惊奇:尽管看起来很像,但替换**并非**基于标记(token-based)的。下例展示了这一点: 34 | 35 | ```rust 36 | macro_rules! capture_expr_then_stringify { 37 | ($e:expr) => { 38 | stringify!($e) 39 | }; 40 | } 41 | 42 | fn main() { 43 | println!("{:?}", stringify!(dummy(2 * (1 + (3))))); 44 | println!("{:?}", capture_expr_then_stringify!(dummy(2 * (1 + (3))))); 45 | } 46 | ``` 47 | 48 | 注意到`stringify!`,这是一条内置的语法扩充,将所有输入标记结合在一起,作为单个字符串输出。 49 | 50 | 上述代码的输出将是: 51 | 52 | ```text 53 | "dummy ( 2 * ( 1 + ( 3 ) ) )" 54 | "dummy(2 * (1 + (3)))" 55 | ``` 56 | 57 | 尽管二者的输入完全一致,它们的输出并不相同。这是因为,前者字符串化的是一列标记树,而后者字符串化的则是一个AST表达式节点。 58 | 59 | 我们另用一种方式展现二者的不同。第一种情况下,`stringify!`被调用时,输入是: 60 | 61 | ```text 62 | «dummy» «( )» 63 | ╭───────┴───────╮ 64 | «2» «*» «( )» 65 | ╭───────┴───────╮ 66 | «1» «+» «( )» 67 | ╭─┴─╮ 68 | «3» 69 | ``` 70 | 71 | …而第二种情况下,`stringify!`被调用时,输入是: 72 | 73 | ```text 74 | « » 75 | │ ┌─────────────┐ 76 | └╴│ Call │ 77 | │ fn: dummy │ ┌─────────┐ 78 | │ args: ◌ │╶─╴│ BinOp │ 79 | └─────────────┘ │ op: Mul │ 80 | ┌╴│ lhs: ◌ │ 81 | ┌────────┐ │ │ rhs: ◌ │╶┐ ┌─────────┐ 82 | │ LitInt │╶┘ └─────────┘ └╴│ BinOp │ 83 | │ val: 2 │ │ op: Add │ 84 | └────────┘ ┌╴│ lhs: ◌ │ 85 | ┌────────┐ │ │ rhs: ◌ │╶┐ ┌────────┐ 86 | │ LitInt │╶┘ └─────────┘ └╴│ LitInt │ 87 | │ val: 1 │ │ val: 3 │ 88 | └────────┘ └────────┘ 89 | ``` 90 | 91 | 如图所示,第二种情况下,输入仅有一棵标记树,它包含了一棵AST,这棵AST则是在解析`capture_expr_then_stringify!`的调用时,调用的输入经解析后所得的输出。因此,在这种情况下,我们最终看到的是字符串化AST节点所得的输出,而非字符串化标记所得的输出。 92 | 93 | 这一特征还有更加深刻的影响。考虑如下代码段: 94 | 95 | ```rust 96 | macro_rules! capture_then_match_tokens { 97 | ($e:expr) => {match_tokens!($e)}; 98 | } 99 | 100 | macro_rules! match_tokens { 101 | ($a:tt + $b:tt) => {"got an addition"}; 102 | (($i:ident)) => {"got an identifier"}; 103 | ($($other:tt)*) => {"got something else"}; 104 | } 105 | 106 | fn main() { 107 | println!("{}\n{}\n{}\n", 108 | match_tokens!((caravan)), 109 | match_tokens!(3 + 6), 110 | match_tokens!(5)); 111 | println!("{}\n{}\n{}", 112 | capture_then_match_tokens!((caravan)), 113 | capture_then_match_tokens!(3 + 6), 114 | capture_then_match_tokens!(5)); 115 | } 116 | ``` 117 | 118 | 其输出将是: 119 | 120 | ```text 121 | got an identifier 122 | got an addition 123 | got something else 124 | 125 | got something else 126 | got something else 127 | got something else 128 | ``` 129 | 130 | 因为输入被解析为AST节点,替换所得的结果将无法析构。也就是说,你没办法检查其内容,或是再按原先相符的匹配匹配它。 131 | 132 | 接下来这个例子尤其可能让人感到不解: 133 | 134 | ```rust 135 | macro_rules! capture_then_what_is { 136 | (#[$m:meta]) => {what_is!(#[$m])}; 137 | } 138 | 139 | macro_rules! what_is { 140 | (#[no_mangle]) => {"no_mangle attribute"}; 141 | (#[inline]) => {"inline attribute"}; 142 | ($($tts:tt)*) => {concat!("something else (", stringify!($($tts)*), ")")}; 143 | } 144 | 145 | fn main() { 146 | println!( 147 | "{}\n{}\n{}\n{}", 148 | what_is!(#[no_mangle]), 149 | what_is!(#[inline]), 150 | capture_then_what_is!(#[no_mangle]), 151 | capture_then_what_is!(#[inline]), 152 | ); 153 | } 154 | ``` 155 | 156 | 输出将是: 157 | 158 | ```text 159 | no_mangle attribute 160 | inline attribute 161 | something else (# [ no_mangle ]) 162 | something else (# [ inline ]) 163 | ``` 164 | 165 | 得以幸免的捕获只有`tt`或`ident`两种。其余的任何捕获,一经替换,结果将只能被用于直接输出。 166 | -------------------------------------------------------------------------------- /text/mbe-min-debugging.md: -------------------------------------------------------------------------------- 1 | % 调试 2 | 3 | `rustc`提供了一些工具用来调试宏。其中,最有用的之一是`trace_macros!`。它会指示编译器,在每一个宏调用被展开之前将其转印出来。例如,给定下列代码: 4 | 5 | ```rust 6 | # // Note: make sure to use a nightly channel compiler. 7 | #![feature(trace_macros)] 8 | 9 | macro_rules! each_tt { 10 | () => {}; 11 | ($_tt:tt $($rest:tt)*) => {each_tt!($($rest)*);}; 12 | } 13 | 14 | each_tt!(foo bar baz quux); 15 | trace_macros!(true); 16 | each_tt!(spim wak plee whum); 17 | trace_macros!(false); 18 | each_tt!(trom qlip winp xod); 19 | # 20 | # fn main() {} 21 | ``` 22 | 23 | 编译输出将包含: 24 | 25 | ```text 26 | each_tt! { spim wak plee whum } 27 | each_tt! { wak plee whum } 28 | each_tt! { plee whum } 29 | each_tt! { whum } 30 | each_tt! { } 31 | ``` 32 | 33 | 它在调试递归很深的宏时尤其有用。同时,它可以在命令提示符中被打开,在编译指令中附加`-Z trace-macros`即可。 34 | 35 | 另一有用的宏是`log_syntax!`。它将使得编译器输出所有经过编译器处理的标记。举个例子,下述代码可以让编译器唱一首歌: 36 | 37 | ```rust 38 | # // Note: make sure to use a nightly channel compiler. 39 | #![feature(log_syntax)] 40 | 41 | macro_rules! sing { 42 | () => {}; 43 | ($tt:tt $($rest:tt)*) => {log_syntax!($tt); sing!($($rest)*);}; 44 | } 45 | 46 | sing! { 47 | ^ < @ < . @ * 48 | '\x08' '{' '"' _ # ' ' 49 | - @ '$' && / _ % 50 | ! ( '\t' @ | = > 51 | ; '\x08' '\'' + '$' ? '\x7f' 52 | , # '"' ~ | ) '\x07' 53 | } 54 | # 55 | # fn main() {} 56 | ``` 57 | 58 | 比起`trace_macros!`来说,它能够做一些更有针对性的调试。 59 | 60 | 有时问题出在宏展开后的结果里。对于这种情况,可用编译命令`--pretty`来勘察。给出下列代码: 61 | 62 | ```rust 63 | // Shorthand for initialising a `String`. 64 | macro_rules! S { 65 | ($e:expr) => {String::from($e)}; 66 | } 67 | 68 | fn main() { 69 | let world = S!("World"); 70 | println!("Hello, {}!", world); 71 | } 72 | ``` 73 | 74 | 并用如下编译命令进行编译, 75 | 76 | ```shell 77 | rustc -Z unstable-options --pretty expanded hello.rs 78 | ``` 79 | 80 | 将输出如下内容(略经修改以符合排版): 81 | 82 | ```rust 83 | #![feature(no_std, prelude_import)] 84 | #![no_std] 85 | #[prelude_import] 86 | use std::prelude::v1::*; 87 | #[macro_use] 88 | extern crate std as std; 89 | // Shorthand for initialising a `String`. 90 | fn main() { 91 | let world = String::from("World"); 92 | ::std::io::_print(::std::fmt::Arguments::new_v1( 93 | { 94 | static __STATIC_FMTSTR: &'static [&'static str] 95 | = &["Hello, ", "!\n"]; 96 | __STATIC_FMTSTR 97 | }, 98 | &match (&world,) { 99 | (__arg0,) => [ 100 | ::std::fmt::ArgumentV1::new(__arg0, ::std::fmt::Display::fmt) 101 | ], 102 | } 103 | )); 104 | } 105 | ``` 106 | 107 | `--pretty`还有其它一些可用选项,可通过`rustc -Z unstable-options --help -v`来列出。此处并不提供该选项表;因为,正如指令本身所暗示的,表中的一切内容在任何时间点都有可能发生改变。 108 | -------------------------------------------------------------------------------- /text/mbe-min-hygiene.md: -------------------------------------------------------------------------------- 1 | % 卫生性 2 | 3 | Rust宏是**部分**卫生的。具体来说,对于绝大多数标识符,它是卫生的;但对泛型参数和生命周期来算,它不是。 4 | 5 | 之所以能做到“卫生”,在于每个标识符都被赋予了一个看不见的“句法上下文”。在比较两个标识符时,只有在标识符的明面名字和句法上下文**都**一致的情况下,两个标识符才能被视作等同。 6 | 7 | 为阐释这一点,考虑下述代码: 8 | 9 |
macro_rules! using_a {
    ($e:expr) => {
        {
            let a = 42;
            $e
        }
    }
}

let four = using_a!(a / 10);
10 | 11 | 我们将采用背景色来表示句法上下文。现在,将上述宏调用展开如下: 12 | 13 |
let four = {
    let a = 42;
    a / 10
};
14 | 15 | 首先记起,`macro_rules!`的调用在展开过程中等同于消失。 16 | 17 | 其次,如果我们现在就尝试编译上述代码,编译器将报如下错误: 18 | 19 | ```text 20 | :11:21: 11:22 error: unresolved name `a` 21 | :11 let four = using_a!(a / 10); 22 | ``` 23 | 24 | 注意到宏在展开后背景色(即其句法上下文)发生了改变。每处宏展开均赋予其内容一个新的、独一无二的上下文。故而,在展开后的代码中实际上存在两个不同的`a`,分别有不同的句法上下文。即,aa并不相同,即它们便看起来很像。 25 | 26 | 尽管如此,被替换进宏展开中的标记仍然保持着它们原有的句法上下文(因它们是被提供给这宏的,并非这宏本身的一部分)。因此,我们作出如下修改: 27 | 28 |
macro_rules! using_a {
    ($a:ident, $e:expr) => {
        {
            let $a = 42;
            $e
        }
    }
}

let four = using_a!(a, a / 10);
29 | 30 | 此宏在展开后将变成: 31 | 32 |
let four = {
    let a = 42;
    a / 10
};
33 | 34 | 因为只用了一种`a`,编译器将欣然接受此段代码。 35 | -------------------------------------------------------------------------------- /text/mbe-min-import-export.md: -------------------------------------------------------------------------------- 1 | % 导入/导出 2 | 3 | 有两种将宏暴露给更广范围的方法。第一种是采用`#[macro_use]`属性。它不仅适用于模组,同样适用于`extern crate`。例如: 4 | 5 | ```rust 6 | #[macro_use] 7 | mod macros { 8 | macro_rules! X { () => { Y!(); } } 9 | macro_rules! Y { () => {} } 10 | } 11 | 12 | X!(); 13 | # 14 | # fn main() {} 15 | ``` 16 | 17 | 可通过`#[macro_export]`将宏从当前`crate`导出。注意,这种方式无视所有可见性设定。 18 | 19 | 定义库包`macs`如下: 20 | 21 | ```rust 22 | mod macros { 23 | #[macro_export] macro_rules! X { () => { Y!(); } } 24 | #[macro_export] macro_rules! Y { () => {} } 25 | } 26 | 27 | // X!和Y!并非在此处定义的,但它们**的确**被 28 | // 导出了,即便macros并非pub。 29 | ``` 30 | 31 | 则下述代码将成立: 32 | 33 | ```rust 34 | X!(); // X!已被定义 35 | #[macro_use] extern crate macs; 36 | X!(); 37 | # 38 | # fn main() {} 39 | ``` 40 | 41 | 注意只有在根模组中,才可将`#[macro_use]`用于`extern crate`。 42 | 43 | 最后,在从`extern crate`导入宏时,可显式控制导入**哪些**宏。可利用这一特性来限制命名空间污染,或是覆写某些特定的宏。就像这样: 44 | 45 | ```rust 46 | // 只导入`X!`这一个宏 47 | #[macro_use(X)] extern crate macs; 48 | 49 | // X!(); // X!已被定义,但Y!未被定义 50 | 51 | macro_rules! Y { () => {} } 52 | 53 | X!(); // 均已被定义 54 | 55 | fn main() {} 56 | ``` 57 | 58 | 当导出宏时,常常出现的情况是,宏定义需要其引用所在`crate`内的非宏符号。由于`crate`可能被重命名等,我们可以使用一个特殊的替换变量:`$crate`。它总将被扩展为宏定义所在的`crate`在当前上下文中的绝对路径(比如 `:: macs`)。 59 | 60 | 注意这招并不适用于宏,因为通常名称的决定进程并不适用于宏。也就是说,你没办法采用类似`$crate::Y!`的代码来引用某个自己`crate`里的特定宏。结合采用`#[macro_use]`做到的选择性导入,我们得出:在宏被导入进其它`crate`时,当前没有办法保证其定义中的其它任一给定宏也一定可用。 61 | 62 | 推荐的做法是,在引用非宏名称时,总是采用绝对路径。这样可以最大程度上避免冲突,包括跟标准库中名称的冲突。 63 | -------------------------------------------------------------------------------- /text/mbe-min-non-identifier-identifiers.md: -------------------------------------------------------------------------------- 1 | % 不是标识符的标识符 2 | 3 | 有两个标记,当你撞见时,很有可能最终认为它们是标识符,但实际上它们不是。然而正是这些标记,在某些情况下又的确是标识符。 4 | 5 | 第一个是`self`。毫无疑问,它是一个关键词。在一般的Rust代码中,不可能出现把它解读成标识符的情况;但在宏中这种情况则有可能发生: 6 | 7 | ```rust 8 | macro_rules! what_is { 9 | (self) => {"the keyword `self`"}; 10 | ($i:ident) => {concat!("the identifier `", stringify!($i), "`")}; 11 | } 12 | 13 | macro_rules! call_with_ident { 14 | ($c:ident($i:ident)) => {$c!($i)}; 15 | } 16 | 17 | fn main() { 18 | println!("{}", what_is!(self)); 19 | println!("{}", call_with_ident!(what_is(self))); 20 | } 21 | ``` 22 | 23 | 上述代码的输出将是: 24 | 25 | ```text 26 | the keyword `self` 27 | the keyword `self` 28 | ``` 29 | 30 | 但这没有任何道理!`call_with_ident!`要求一个标识符,而且它的确匹配到了,还成功替换了!所以,`self`同时是一个关键词,但又不是。你可能会想,好吧,但这鬼东西哪里重要呢?看看这个: 31 | 32 | ```rust 33 | macro_rules! make_mutable { 34 | ($i:ident) => {let mut $i = $i;}; 35 | } 36 | 37 | struct Dummy(i32); 38 | 39 | impl Dummy { 40 | fn double(self) -> Dummy { 41 | make_mutable!(self); 42 | self.0 *= 2; 43 | self 44 | } 45 | } 46 | # 47 | # fn main() { 48 | # println!("{:?}", Dummy(4).double().0); 49 | # } 50 | ``` 51 | 52 | 编译它会失败,并报错: 53 | 54 | ```text 55 | :2:28: 2:30 error: expected identifier, found keyword `self` 56 | :2 ($i:ident) => {let mut $i = $i;}; 57 | ^~ 58 | ``` 59 | 60 | 所以说,宏在匹配的时候,会欣然把`self`当作标识符接受,进而允许你把`self`带到那些实际上没办法使用的情况中去。但是,也成吧,既然得同时记住`self`既是关键词又是标识符,那下面这个**讲道理**应该可行,对吧? 61 | 62 | ```rust 63 | macro_rules! make_self_mutable { 64 | ($i:ident) => {let mut $i = self;}; 65 | } 66 | 67 | struct Dummy(i32); 68 | 69 | impl Dummy { 70 | fn double(self) -> Dummy { 71 | make_self_mutable!(mut_self); 72 | mut_self.0 *= 2; 73 | mut_self 74 | } 75 | } 76 | # 77 | # fn main() { 78 | # println!("{:?}", Dummy(4).double().0); 79 | # } 80 | ``` 81 | 82 | 实际上也不行,编译错误变成: 83 | 84 | ```text 85 | :2:33: 2:37 error: `self` is not available in a static method. Maybe a `self` argument is missing? [E0424] 86 | :2 ($i:ident) => {let mut $i = self;}; 87 | ^~~~ 88 | ``` 89 | 90 | 这同样也没有任何道理。它明明不在静态方法里。这简直就像是在抱怨说,它看见的两个`self`不是同一个`self`... 就搞得像关键词`self`也有卫生性一样,类似...标识符。 91 | 92 | ```rust 93 | macro_rules! double_method { 94 | ($body:expr) => { 95 | fn double(mut self) -> Dummy { 96 | $body 97 | } 98 | }; 99 | } 100 | 101 | struct Dummy(i32); 102 | 103 | impl Dummy { 104 | double_method! {{ 105 | self.0 *= 2; 106 | self 107 | }} 108 | } 109 | # 110 | # fn main() { 111 | # println!("{:?}", Dummy(4).double().0); 112 | # } 113 | ``` 114 | 115 | 还是报同样的错。那这个如何: 116 | 117 | ```rust 118 | macro_rules! double_method { 119 | ($self_:ident, $body:expr) => { 120 | fn double(mut $self_) -> Dummy { 121 | $body 122 | } 123 | }; 124 | } 125 | 126 | struct Dummy(i32); 127 | 128 | impl Dummy { 129 | double_method! {self, { 130 | self.0 *= 2; 131 | self 132 | }} 133 | } 134 | # 135 | # fn main() { 136 | # println!("{:?}", Dummy(4).double().0); 137 | # } 138 | ``` 139 | 140 | 终于管用了。所以说,`self`是关键词,但当它想的时候,它**同时**也能是一个标识符。那么,相同的道理对类似的其它东西有用吗? 141 | 142 | ```rust 143 | macro_rules! double_method { 144 | ($self_:ident, $body:expr) => { 145 | fn double($self_) -> Dummy { 146 | $body 147 | } 148 | }; 149 | } 150 | 151 | struct Dummy(i32); 152 | 153 | impl Dummy { 154 | double_method! {_, 0} 155 | } 156 | # 157 | # fn main() { 158 | # println!("{:?}", Dummy(4).double().0); 159 | # } 160 | ``` 161 | 162 | ```text 163 | :12:21: 12:22 error: expected ident, found _ 164 | :12 double_method! {_, 0} 165 | ^ 166 | ``` 167 | 168 | 哈,当然不行。 `_`是一个关键词,在模式以及表达式中有效,但不知为何,并不像`self`,它并不是一个标识符;即便它——如同`self`——从定义上讲符合标识符的特性。 169 | 170 | 你可能觉得,既然`_`在模式中有效,那换成`$self_:pat`是不是就能一石二鸟了呢?可惜了,也不行,因为`self`不是一个有效的模式。真棒。 171 | 172 | 如果你真想同时匹配这两个标记,仅有的办法是换用`tt`来匹配。 -------------------------------------------------------------------------------- /text/mbe-min-scoping.md: -------------------------------------------------------------------------------- 1 | % 作用域 2 | 3 | 宏作用域的决定方式可能有一点反直觉。首先就与语言剩下的所有部分都不同的是,宏在子模组中仍然可见。 4 | 5 | ```rust 6 | macro_rules! X { () => {}; } 7 | mod a { 8 | X!(); // 已被定义 9 | } 10 | mod b { 11 | X!(); // 已被定义 12 | } 13 | mod c { 14 | X!(); // 已被定义 15 | } 16 | # fn main() {} 17 | ``` 18 | 19 | > **注意**:即使子模组的内容处在不同文件中,这些例子中所述的行为仍然保持不变。 20 | 21 | 其次,同样与语言剩下的所有部分不同,宏只有在其定义**之后**可见。下例展示了这一点。同时注意到,它也展示了宏不会“漏出”其定义所在的域: 22 | 23 | ```rust 24 | mod a { 25 | // X!(); // 未被定义 26 | } 27 | mod b { 28 | // X!(); // 未被定义 29 | macro_rules! X { () => {}; } 30 | X!(); // 已被定义 31 | } 32 | mod c { 33 | // X!(); // 未被定义 34 | } 35 | # fn main() {} 36 | ``` 37 | 38 | 需要阐明的是,即便宏定义被移至外围域,此顺序依赖行为仍旧不变: 39 | 40 | ```rust 41 | mod a { 42 | // X!(); // 未被定义 43 | } 44 | macro_rules! X { () => {}; } 45 | mod b { 46 | X!(); // 已被定义 47 | } 48 | mod c { 49 | X!(); // 已被定义 50 | } 51 | # fn main() {} 52 | ``` 53 | 54 | 然而,对于宏们自身来说,此依赖行为不存在: 55 | 56 | ```rust 57 | mod a { 58 | // X!(); // 未被定义 59 | } 60 | macro_rules! X { () => { Y!(); }; } 61 | mod b { 62 | // X!(); // 已被定义, 但Y!未被定义 63 | } 64 | macro_rules! Y { () => {}; } 65 | mod c { 66 | X!(); // 均已被定义 67 | } 68 | # fn main() {} 69 | ``` 70 | 71 | 可通过 `#[macro_use]`属性将宏导出模组: 72 | 73 | ```rust 74 | mod a { 75 | // X!(); // 未被定义 76 | } 77 | #[macro_use] 78 | mod b { 79 | macro_rules! X { () => {}; } 80 | X!(); // 已被定义 81 | } 82 | mod c { 83 | X!(); // 已被定义 84 | } 85 | # fn main() {} 86 | ``` 87 | 88 | 注意到这一特性可能会产生一些奇怪的后果,因为宏中的标识符只有在宏展开的过程中才会被解析。 89 | 90 | ```rust 91 | mod a { 92 | // X!(); // 未被定义 93 | } 94 | #[macro_use] 95 | mod b { 96 | macro_rules! X { () => { Y!(); }; } 97 | // X!(); // 已被定义,但Y!并未被定义 98 | } 99 | macro_rules! Y { () => {}; } 100 | mod c { 101 | X!(); // 均已被定义 102 | } 103 | # fn main() {} 104 | ``` 105 | 106 | 让情形变得更加复杂的是,当`#[macro_use]`被作用于`extern crate`时,其行为又会发生进一步变化:此类声明从效果上看,类似于被放在了整个模组的顶部。因此,假设在某个`extern crate mac`中定义了`X!`,则有: 107 | 108 | ```rust 109 | mod a { 110 | // X!(); // 已被定义,但Y!并未被定义 111 | } 112 | macro_rules! Y { () => {}; } 113 | mod b { 114 | X!(); // 均已被定义 115 | } 116 | #[macro_use] extern crate macs; 117 | mod c { 118 | X!(); // 均已被定义 119 | } 120 | # fn main() {} 121 | ``` 122 | 123 | 最后,注意这些有关作用域的行为同样适用于函数,除了`#[macro_use]`以外(它并不适用): 124 | 125 | ```rust 126 | macro_rules! X { 127 | () => { Y!() }; 128 | } 129 | 130 | fn a() { 131 | macro_rules! Y { () => {"Hi!"} } 132 | assert_eq!(X!(), "Hi!"); 133 | { 134 | assert_eq!(X!(), "Hi!"); 135 | macro_rules! Y { () => {"Bye!"} } 136 | assert_eq!(X!(), "Bye!"); 137 | } 138 | assert_eq!(X!(), "Hi!"); 139 | } 140 | 141 | fn b() { 142 | macro_rules! Y { () => {"One more"} } 143 | assert_eq!(X!(), "One more"); 144 | } 145 | # 146 | # fn main() { 147 | # a(); 148 | # b(); 149 | # } 150 | ``` 151 | 152 | 由于前述种种规则,一般来说,建议将所有应对整个`crate`均可见的宏的定义置于根模组的最顶部,借以确保它们一直可用。 153 | -------------------------------------------------------------------------------- /text/mbe-syn-README.md: -------------------------------------------------------------------------------- 1 | % 语法扩展 2 | 3 | 在谈及宏之前,我们首先应当讨论**语法扩展**这一一般性机制。宏正是在它之上构建的。而想要弄明白语法扩展,我们则应该首先阐述编译器处理Rust源代码的机制。 -------------------------------------------------------------------------------- /text/mbe-syn-expansion.md: -------------------------------------------------------------------------------- 1 | % 展开 2 | 3 | 展开相对简单。编译器在生成AST之后,对程序进行语义理解之前的某个时间点,将会对所有宏进行展开。 4 | 5 | 这一过程包括,遍历AST,定位所有宏调用,并将它们用其展开进行替换。在非宏的语法扩展情境中,此过程具体如何发生根据具体情境各有不同。但所有语法扩展在展开完成之后所经历的历程都与宏所经历的相同。 6 | 7 | 每当编译器遇见一个语法扩展,都会根据上下文决定一个语法元素集。该语法扩展的展开结果应能被顺利解析为集合中的某个元素。举例来说,如果在模组作用域内调用了宏,那么编译器就会尝试将该宏的展开结果解析为一个表示某项条目(item)的AST节点。如果在需要表达式的位置调用了宏,那么编译器就会尝试将该宏的展开结果解析为一个表示表达式的AST节点。 8 | 9 | 事实上,语义扩展能够被转换成以下任意一种: 10 | 11 | * 一个表达式, 12 | * 一个模式, 13 | * 0或多个条目, 14 | * 0或多个`impl`条目, 15 | * 0或多个语句。 16 | 17 | 换句话讲,宏调用所在的位置,决定了该宏展开之后的结果被解读的方式。 18 | 19 | 编译器将把AST中表示宏调用的节点用其宏展开的输出节点完全替换。这一替换是结构性(structural)的,而非织构性(textural)的。 20 | 21 | 举例来说: 22 | 23 | ```rust 24 | let eight = 2 * four!(); 25 | ``` 26 | 27 | 我们可将这部分AST表示为: 28 | 29 | ```text 30 | ┌─────────────┐ 31 | │ Let │ 32 | │ name: eight │ ┌─────────┐ 33 | │ init: ◌ │╶─╴│ BinOp │ 34 | └─────────────┘ │ op: Mul │ 35 | ┌╴│ lhs: ◌ │ 36 | ┌────────┐ │ │ rhs: ◌ │╶┐ ┌────────────┐ 37 | │ LitInt │╶┘ └─────────┘ └╴│ Macro │ 38 | │ val: 2 │ │ name: four │ 39 | └────────┘ │ body: () │ 40 | └────────────┘ 41 | ``` 42 | 43 | 根据上下文,`four!()`**必须**展开成一个表达式 (初始化语句只可能是表达式)。因此,无论实际展开结果如何,它都将被解读成一个完整的表达式。此处我们假设,`four!`的定义保证它被展开为表达式 `1 + 3`。故而,展开这一宏调用将使整个AST变为 44 | 45 | ```text 46 | ┌─────────────┐ 47 | │ Let │ 48 | │ name: eight │ ┌─────────┐ 49 | │ init: ◌ │╶─╴│ BinOp │ 50 | └─────────────┘ │ op: Mul │ 51 | ┌╴│ lhs: ◌ │ 52 | ┌────────┐ │ │ rhs: ◌ │╶┐ ┌─────────┐ 53 | │ LitInt │╶┘ └─────────┘ └╴│ BinOp │ 54 | │ val: 2 │ │ op: Add │ 55 | └────────┘ ┌╴│ lhs: ◌ │ 56 | ┌────────┐ │ │ rhs: ◌ │╶┐ ┌────────┐ 57 | │ LitInt │╶┘ └─────────┘ └╴│ LitInt │ 58 | │ val: 1 │ │ val: 3 │ 59 | └────────┘ └────────┘ 60 | ``` 61 | 62 | 这又能被重写成 63 | 64 | ```rust 65 | let eight = 2 * (1 + 3); 66 | ``` 67 | 68 | 注意到虽然表达式本身不包含括号,我们仍加上了它们。这是因为,编译器总是将宏展开结果作为完整的AST节点对待,而**不是**仅仅作为一列标记。换句话说,即便不显式地把复杂的表达式用括号包起来,编译器也不可能“错意”宏替换的结果,或者改变求值顺序。 69 | 70 | 理解这一点——宏展开被当作AST节点看待——非常重要,它表明: 71 | 72 | * 宏调用不仅可用的位置有限,其展开结果也只可能跟语法分析器在该位置所预期的AST节点种类相符合。 73 | * 因此,宏**必定无法**展开成不完整或不合语法的架构。 74 | 75 | 有关展开还有一条值得注意:如果某个语法扩展的展开结果包含了另一次语法扩展调用,那会怎么样?例如,上述`four!`如果被展开成了`1 + three!()`,会发生什么? 76 | 77 | ```rust 78 | let x = four!(); 79 | ``` 80 | 81 | 展开成: 82 | 83 | ```rust 84 | let x = 1 + three!(); 85 | ``` 86 | 87 | 编译器将会检查扩展结果中是否包含更多的宏调用;如果有,它们将被进一步展开。因此,上述AST节点将被再次展开成: 88 | 89 | ```rust 90 | let x = 1 + 3; 91 | ``` 92 | 93 | 此处我们了解到,展开是按“趟”发生的;要多少趟才能完全展开所有调用,那就会展开多少趟。 94 | 95 | 嗯,也不全是如此。事实上,编译器为此设置了一个上限。它被称作宏递归上限,默认值为32.如果第32次展开结果仍然包含宏调用,编译器将会终止并返回一个递归上限溢出的错误信息。 96 | 97 | 此上限可通过属性 `#![recursion_limit="…"]`被改写,但这种改写必须是crate级别的。 一般来讲,可能的话最好还是尽量让宏展开递归次数保持在默认值以下。 98 | 99 | -------------------------------------------------------------------------------- /text/mbe-syn-macros-in-the-ast.md: -------------------------------------------------------------------------------- 1 | % AST中的宏 2 | 3 | 如前所述,在Rust中,宏处理发生在AST生成**之后**。因此,调用宏的语法必须是Rust语言语法中规整相符的一部分。实际上,Rust语法包含数种“语法扩展”的形式。我们将它们同用例列出如下: 4 | 5 | * `# [ $arg ]`; 如 `#[derive(Clone)]`, `#[no_mangle]`, … 6 | * `# ! [ $arg ]`; 如 `#![allow(dead_code)]`, `#![crate_name="blang"]`, … 7 | * `$name ! $arg`; 如 `println!("Hi!")`, `concat!("a", "b")`, … 8 | * `$name ! $arg0 $arg1`; 如 `macro_rules! dummy { () => {}; }`. 9 | 10 | 头两种形式被称作“属性(attribute)”,被同时用于语言特属的结构(比如用于要求兼容C的ABI的`#[repr(C)]`)以及语法扩展(比如 `#[derive(Clone)]`)。当前没有办法定义这种形式的宏。 11 | 12 | 我们感兴趣的是第三种:我们通常使用的宏正是这种形式。注意,采用这种形式的并非只有宏:它是一种一般性的语法扩展形式。举例来说,`format!` 是宏,而`format_args!` (它被用于`format!`) 并不是。 13 | 14 | 第四种形式实际上宏无法使用。事实上,这种形式的唯一用例只有 `macro_rules!` 我们将在稍后谈到它。 15 | 16 | 将注意力集中到第三种形式 (`$name ! $arg`)上,我们的问题变成,对于每种可能的语法扩展,Rust的语法分析器(parser)如何知道`$arg`究竟长什么样?答案是它不需要知道。其实,提供给每次语法扩展调用的参数,是一棵标记树。具体来说,一棵非叶节点的标记树;即`(...)`,`[...]`,或`{...}`。拥有这一知识后,语法分析器如何理解如下调用形式,就变得显而易见了: 17 | 18 | ```rust 19 | bitflags! { 20 | flags Color: u8 { 21 | const RED = 0b0001, 22 | const GREEN = 0b0010, 23 | const BLUE = 0b0100, 24 | const BRIGHT = 0b1000, 25 | } 26 | } 27 | 28 | lazy_static! { 29 | static ref FIB_100: u32 = { 30 | fn fib(a: u32) -> u32 { 31 | match a { 32 | 0 => 0, 33 | 1 => 1, 34 | a => fib(a-1) + fib(a-2) 35 | } 36 | } 37 | 38 | fib(100) 39 | }; 40 | } 41 | 42 | fn main() { 43 | let colors = vec![RED, GREEN, BLUE]; 44 | println!("Hello, World!"); 45 | } 46 | ``` 47 | 48 | 虽然看起来上述调用包含了各式各样的Rust代码,但对语法分析器来说,它们仅仅是堆毫无意义的标记树。为了让事情变得更清晰,我们把所有这些句法“黑盒”用⬚代替,仅剩下: 49 | 50 | ```ignore 51 | bitflags! ⬚ 52 | 53 | lazy_static! ⬚ 54 | 55 | fn main() { 56 | let colors = vec! ⬚; 57 | println! ⬚; 58 | } 59 | ``` 60 | 61 | 再次重申,语法分析器对⬚不作任何假设;它记录黑盒所包含的标记,但并不尝试理解它们。 62 | 63 | 需要记下的点: 64 | 65 | * Rust包含多种语法扩展。我们将仅仅讨论定义在`macro_rules!` 结构中的宏。 66 | * 当遇见形如`$name! $arg`的结构时,该结构并不一定是宏,可能是其它语言扩展。 67 | * 所有宏的输入都是非叶节点的单个标记树。 68 | * 宏(其实所有一般意义上的语法扩展)都将作为抽象语法树的一部分被解析。 69 | 70 | > **脚注**: 接下来(包括下一节)将提到的某些内容将适用于一般性的语法扩展。[^作者很懒] 71 | 72 | [^作者很懒]: 这样比较方便,因为“宏”打起来比“语法扩展”更快更简单。 73 | 74 | 最后一点最为重要,它带来了一些深远的影响。由于宏将被解析进AST中,它们将仅仅只能出现在那些支持它们出现的位置。具体来说,宏能在如下位置出现: 75 | 76 | * 模式(pattern)中 77 | * 语句(statement)中 78 | * 表达式(expression)中 79 | * 条目(item)中 80 | * `impl` 块中 81 | 82 | 一些并不支持的位置包括: 83 | 84 | * 标识符(identifier)中 85 | * `match`臂中 86 | * 结构体的字段中 87 | * 类型中[^类型宏] 88 | 89 | [^类型宏]: 在非稳定Rust中可以通过`#![feature(type_macros)]`使用类型宏。见[Issue #27336](https://github.com/rust-lang/rust/issues/27336)。 90 | 91 | 绝对没有任何在上述位置以外的地方使用宏的可能。 92 | -------------------------------------------------------------------------------- /text/mbe-syn-source-analysis.md: -------------------------------------------------------------------------------- 1 | % 源码解析过程 2 | 3 | Rust程序编译过程的第一阶段是标记解析(tokenization)。在这一过程中,源代码将被转换成一系列的标记(token,即无法被分割的词法单元;在编程语言世界中等价于“单词”)。Rust包含多种标记,比如: 4 | 5 | - 标识符(identifiers):`foo`, `Bambous`, `self`, `we_can_dance`, `LaCaravane`, … 6 | - 整数(integers):`42`, `72u32`, `0_______0`, … 7 | - 关键词(keywords):`_`, `fn`, `self`, `match`, `yield`, `macro`, … 8 | - 生命周期(lifetimes):`'a`, `'b`, `'a_rare_long_lifetime_name`, … 9 | - 字符串(strings):`""`, `"Leicester"`, `r##"venezuelan beaver"##`, … 10 | - 符号(symbols):`[`, `:`, `::`, `->`, `@`, `<-`, … 11 | 12 | …等等。 13 | 14 | 上面的叙述中有些地方值得注意。 15 | 16 | 首先,`self`既是一个标识符又是一个关键词。几乎在所有情况下它都被视作是一个关键词,但它**有可能**被视为标识符。我们稍后会(带着咒骂)提到这种情况。 17 | 18 | 其次,关键词里列有一些可疑的家伙,比如`yield`和`macro`。它们在当前的Rust语言中并没有任何含义,但编译器的确会把它们视作关键词进行解析。这些词语被保留作语言未来扩充时使用。 19 | 20 | 第三,符号里**也**列有一些未被当前语言使用的条目。比如`<-`,这是历史残留:目前它被移除了Rust语法,但词法分析器仍然没丢掉它。 21 | 22 | 最后,注意`::`被视作一个独立的标记,而非两个连续的`:`。这一规则适用于Rust中所有的多字符符号标记。[^逝去的@] 23 | 24 | [^逝去的@]: `@`是有意义的,虽然大多数人好像都已经完全不记得它了。它用于在模式中把某个模式的非终结部分绑定给一个名称。甚至一位Rust核心团队的成员,在审阅这一章节并特意提起这一小节时,都没记起它的含义。可怜,真是可怜。 25 | 26 | 作为对比,某些语言的宏系统正扎根于这一阶段。Rust并非如此。举例来说,从效果来看,C/C++的宏就是在这里得到处理的。[^其实不是]这也正是下列代码能够运行的原因:[^这看起来不错] 27 | 28 | [^其实不是]: 事实上,C的预处理器采用了跟C语言本身不同的词法结构。但总体来说个中区别并不相干。 29 | [^这看起来不错]: 它是不是真该奏效又是另外一码事了。 30 | 31 | ```c 32 | #define SUB void 33 | #define BEGIN { 34 | #define END } 35 | 36 | SUB main() BEGIN 37 | printf("Oh, the horror!\n"); 38 | END 39 | ``` 40 | 41 | 编译过程的下一个阶段是语法解析(parsing)。这一过程中,一系列的标记将被转换成一棵抽象语法树(Abstract Syntax Tree, AST)。此过程将在内存中建立起程序的语法结构。举例来说,标记序列`1+2`将被转换成某种类似于: 42 | 43 | ```text 44 | ┌─────────┐ ┌─────────┐ 45 | │ BinOp │ ┌╴│ LitInt │ 46 | │ op: Add │ │ │ val: 1 │ 47 | │ lhs: ◌ │╶┘ └─────────┘ 48 | │ rhs: ◌ │╶┐ ┌─────────┐ 49 | └─────────┘ └╴│ LitInt │ 50 | │ val: 2 │ 51 | └─────────┘ 52 | ``` 53 | 54 | 的东西。生成出的AST将包含**整个**程序的结构,但这一结构仅包含**词法**信息。举例来讲,在这个阶段编译器虽然可能知道某个表达式提及了某个名为`a`的变量,但它并**没有**办法知道`a`究竟是什么,或者它在哪儿。 55 | 56 | 在AST生成**之后**,宏处理过程才开始。但在讨论宏处理过程之前,我们需要先谈谈标记树(token tree)。 57 | 58 | ## 标记树 59 | 60 | 标记树是介于标记与AST之间的东西。首先明确一点,**几乎所有**标记都构成标记树。具体来说,它们可被看作标记树叶节点。另有一类存在可被看作标记树叶节点,我们将在稍后提到它。 61 | 62 | 只有一种基础标记**不是**标记树叶节点,“分组”标记:`(...)`, `[...]`和`{...}`。这三者属于标记树内节点, 正是它们给标记树带来了树状的结构。给个具体的例子,这列标记: 63 | 64 | ```ignore 65 | a + b + (c + d[0]) + e 66 | ``` 67 | 68 | 将被转换为这样的标记树: 69 | 70 | ```text 71 | «a» «+» «b» «+» «( )» «+» «e» 72 | ╭────────┴──────────╮ 73 | «c» «+» «d» «[ ]» 74 | ╭─┴─╮ 75 | «0» 76 | ``` 77 | 78 | 注意它跟最后生成的AST并没有关联。AST将仅有一个根节点,而这棵标记树有九(原文如此)个。作为参照,最后生成的AST应该是这样: 79 | 80 | ```text 81 | ┌─────────┐ 82 | │ BinOp │ 83 | │ op: Add │ 84 | ┌╴│ lhs: ◌ │ 85 | ┌─────────┐ │ │ rhs: ◌ │╶┐ ┌─────────┐ 86 | │ Var │╶┘ └─────────┘ └╴│ BinOp │ 87 | │ name: a │ │ op: Add │ 88 | └─────────┘ ┌╴│ lhs: ◌ │ 89 | ┌─────────┐ │ │ rhs: ◌ │╶┐ ┌─────────┐ 90 | │ Var │╶┘ └─────────┘ └╴│ BinOp │ 91 | │ name: b │ │ op: Add │ 92 | └─────────┘ ┌╴│ lhs: ◌ │ 93 | ┌─────────┐ │ │ rhs: ◌ │╶┐ ┌─────────┐ 94 | │ BinOp │╶┘ └─────────┘ └╴│ Var │ 95 | │ op: Add │ │ name: e │ 96 | ┌╴│ lhs: ◌ │ └─────────┘ 97 | ┌─────────┐ │ │ rhs: ◌ │╶┐ ┌─────────┐ 98 | │ Var │╶┘ └─────────┘ └╴│ Index │ 99 | │ name: c │ ┌╴│ arr: ◌ │ 100 | └─────────┘ ┌─────────┐ │ │ ind: ◌ │╶┐ ┌─────────┐ 101 | │ Var │╶┘ └─────────┘ └╴│ LitInt │ 102 | │ name: d │ │ val: 0 │ 103 | └─────────┘ └─────────┘ 104 | ``` 105 | 106 | 理解AST与标记树间的区别至关重要。写宏时,你将同时与这两者打交道。 107 | 108 | 还有一条需要注意:不可能出现不匹配的小/中/大括号,也不可能存在包含错误嵌套结构的标记树。 109 | -------------------------------------------------------------------------------- /text/pat-README.md: -------------------------------------------------------------------------------- 1 | % 常用模式 2 | 3 | 解析与扩展中的常用套路。 4 | -------------------------------------------------------------------------------- /text/pat-callbacks.md: -------------------------------------------------------------------------------- 1 | % 回调 2 | 3 | ```rust 4 | macro_rules! call_with_larch { 5 | ($callback:ident) => { $callback!(larch) }; 6 | } 7 | 8 | macro_rules! expand_to_larch { 9 | () => { larch }; 10 | } 11 | 12 | macro_rules! recognise_tree { 13 | (larch) => { println!("#1, 落叶松。") }; 14 | (redwood) => { println!("#2, THE巨红杉。") }; 15 | (fir) => { println!("#3, 冷杉。") }; 16 | (chestnut) => { println!("#4, 七叶树。") }; 17 | (pine) => { println!("#5, 欧洲赤松。") }; 18 | ($($other:tt)*) => { println!("不懂,可能是种桦树?") }; 19 | } 20 | 21 | fn main() { 22 | recognise_tree!(expand_to_larch!()); 23 | call_with_larch!(recognise_tree); 24 | } 25 | ``` 26 | 27 | 由于宏展开的机制限制,(在Rust1.2中)不可能做到把一例宏的展开结果作为有效信息提供给另一例宏。这为宏的模组化工作施加了难度。 28 | 29 | 使用递归并传递回调是条出路。作为演示,上例两处宏调用的展开过程如下: 30 | 31 | ```ignore 32 | recognise_tree! { expand_to_larch ! ( ) } 33 | println! { "I don't know; some kind of birch maybe?" } 34 | // ... 35 | 36 | call_with_larch! { recognise_tree } 37 | recognise_tree! { larch } 38 | println! { "#1, the Larch." } 39 | // ... 40 | ``` 41 | 42 | 可以使用`tt`的重复来将任意参数转发给回调: 43 | 44 | ```rust 45 | macro_rules! callback { 46 | ($callback:ident($($args:tt)*)) => { 47 | $callback!($($args)*) 48 | }; 49 | } 50 | 51 | fn main() { 52 | callback!(callback(println("Yes, this *was* unnecessary."))); 53 | } 54 | ``` 55 | 56 | 如有需要,当然还可以在参数中增加额外的标记。 57 | -------------------------------------------------------------------------------- /text/pat-incremental-tt-munchers.md: -------------------------------------------------------------------------------- 1 | % 标记树撕咬机 2 | 3 | ```rust 4 | macro_rules! mixed_rules { 5 | () => {}; 6 | (trace $name:ident; $($tail:tt)*) => { 7 | { 8 | println!(concat!(stringify!($name), " = {:?}"), $name); 9 | mixed_rules!($($tail)*); 10 | } 11 | }; 12 | (trace $name:ident = $init:expr; $($tail:tt)*) => { 13 | { 14 | let $name = $init; 15 | println!(concat!(stringify!($name), " = {:?}"), $name); 16 | mixed_rules!($($tail)*); 17 | } 18 | }; 19 | } 20 | # 21 | # fn main() { 22 | # let a = 42; 23 | # let b = "Ho-dee-oh-di-oh-di-oh!"; 24 | # let c = (false, 2, 'c'); 25 | # mixed_rules!( 26 | # trace a; 27 | # trace b; 28 | # trace c; 29 | # trace b = "They took her where they put the crazies."; 30 | # trace b; 31 | # ); 32 | # } 33 | ``` 34 | 35 | 此模式可能是最强大的宏解析技巧。通过使用它,一些极其复杂的语法都能得到解析。 36 | 37 | “标记树撕咬机”是一种递归宏,其工作机制有赖于对输入的顺次、逐步处理。处理过程的每一步中,它都将匹配并移除(“撕咬”掉)输入头部的一列标记,得到一些中间结果,然后再递归地处理输入剩下的尾部。 38 | 39 | 名称中含有“标记树”,是因为输入中尚未被处理的部分总是被捕获在`$($tail:tt)*`的形式中。之所以如此,是因为只有通过使用`tt`的重复才能做到无损地捕获住提供给宏的部分输入。 40 | 41 | 标记树撕咬机仅有的限制,也是整个宏系统的局限: 42 | 43 | * 你只能匹配`macro_rules!`允许匹配的字面值和语法结构。 44 | * 你无法匹配不成对的标记组(unbalanced group)。 45 | 46 | 然而,需要把宏递归的局限性纳入考量。`macro_rules!`没有做任何形式的尾递归消除或优化。在写标记树撕咬机时,推荐多花些功夫,尽可能地限制递归调用的次数。对于输入的变化,增加额外的匹配分支(而非采用中间层并使用递归);或对输入句法施加限制,以便于对标准重复的记录追踪。 47 | -------------------------------------------------------------------------------- /text/pat-internal-rules.md: -------------------------------------------------------------------------------- 1 | %内用规则 2 | 3 | ```rust 4 | #[macro_export] 5 | macro_rules! foo { 6 | (@as_expr $e:expr) => {$e}; 7 | 8 | ($($tts:tt)*) => { 9 | foo!(@as_expr $($tts)*) 10 | }; 11 | } 12 | # 13 | # fn main() { 14 | # assert_eq!(foo!(42), 42); 15 | # } 16 | ``` 17 | 18 | 宏并不参与标准的条目可见性与查找流程,因此,如果一个公共可见宏在其内部调用了其它宏,那么被调用的宏也将必须公共可见。这会污染全局命名空间,甚至会与来自其它`crate`的宏发生冲突。那些想对宏进行*选择性*导入的用户也会因之感到困惑;他们必须导入*所有*宏——包括公开文档并未记录的——才能使代码正常运转。 19 | 20 | 将这些本不该公共可见的宏封装进需要被导出的宏内部,是一个不错的解决方案。上例展示了如何将常用的`as_expr!`宏移至另一个宏的内部,仅有后者公共可见。 21 | 22 | 之所以用`@`,是因为在Rust 1.2下,该标记尚无任何在前缀位置的用法;因此,我们的语法定义不会与任何东西撞车。想用的话,别的符号或特有前缀都可以;但`@`的用例已被传播开来,因此,使用它可能更容易帮助读者理解你的代码。 23 | 24 | > **注意**:标记`@`先前曾作为前缀被用于表示被垃圾回收了的指针,那时的语言还在采用各种记号代表指针类型。现在的标记`@`只有一种用法:将名称绑定至模式中。而在此用法中它是中缀运算符,与我们的上述用例并不冲突。 25 | 26 | 还有一点,内用规则通常应排在“真正的”规则之前。这样做可避免`macro_rules!`错把内规调用解析成别的东西,比如表达式。 27 | 28 | 如果导出内用规则无法避免(比如说,有一干效用性的宏规则,很多应被导出的宏都同时需要用到它们),你仍可以采用此规则,将所有内用规则封装到一个“究极”效用宏里去: 29 | 30 | ```ignore 31 | macro_rules! crate_name_util { 32 | (@as_expr $e:expr) => {$e}; 33 | (@as_item $i:item) => {$i}; 34 | (@count_tts) => {0usize}; 35 | // ... 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /text/pat-provisional.md: -------------------------------------------------------------------------------- 1 | % 临时措施 2 | 3 | 本节将留给那些价值有待探讨,以及那些可能有缺陷因而不适合出现在正文中的模式与技巧。 4 | 5 | ## 算盘计数 6 | 7 | > **临时信息**:需要更合适的例子。虽然它是[`Ook!`宏](aeg-ook.md#提取循环区块)的重要组成部分之一,但该用例——匹配Rust分组机制无法表示的嵌套结构——实在是过于特殊,因此不适作为例子使用。 8 | 9 | > **注意**:此节预设读者了解[下推累积](pat-push-down-accumulation.md)以及[标记树撕咬机](pat-incremental-tt-munchers.md)。 10 | 11 | ```rust 12 | macro_rules! abacus { 13 | ((- $($moves:tt)*) -> (+ $($count:tt)*)) => { 14 | abacus!(($($moves)*) -> ($($count)*)) 15 | }; 16 | ((- $($moves:tt)*) -> ($($count:tt)*)) => { 17 | abacus!(($($moves)*) -> (- $($count)*)) 18 | }; 19 | ((+ $($moves:tt)*) -> (- $($count:tt)*)) => { 20 | abacus!(($($moves)*) -> ($($count)*)) 21 | }; 22 | ((+ $($moves:tt)*) -> ($($count:tt)*)) => { 23 | abacus!(($($moves)*) -> (+ $($count)*)) 24 | }; 25 | 26 | // 检查最终结果是否为零 27 | (() -> ()) => { true }; 28 | (() -> ($($count:tt)+)) => { false }; 29 | } 30 | 31 | fn main() { 32 | let equals_zero = abacus!((++-+-+++--++---++----+) -> ()); 33 | assert_eq!(equals_zero, true); 34 | } 35 | ``` 36 | 37 | 当需要记录的计数会发生变化,且初始值为零或在零附近,且必须支持如下操作: 38 | 39 | * 增加一; 40 | * 减少一; 41 | * 与0(或任何其它固定有限值)相比较; 42 | 43 | 时,可以使用次技巧。 44 | 45 | 数值n将由一组共n个相同的特定标记来表示。对数值的修改操作将采用[下推累积](pat-push-down-accumulation.md)模式由递归调用完成。假设所采用的特定标记是`x`,则上述操作可实现为: 46 | 47 | * 增加一:匹配`($($count:tt)*)`并替换为`(x $($count)*)`。 48 | * 减少一:匹配`(x $($count:tt)*)`并替换为`($($count)*)`。 49 | * 与0相比较:匹配`()`。 50 | * 与1相比较:匹配`(x)`。 51 | * 与2相比较:匹配`(x x)`。 52 | * *(依此类推...)* 53 | 54 | 作用于计数值的操作将所选的标记来回摆动,如同算盘摆动算子。[^abacus] 55 | 56 | [^abacus]: 在这句极度单薄的辩解下,隐藏着选用此名称的*真实*理由:避免造出又一个名含“标记”的术语。今天就该跟你认识的作者谈谈规避[语义饱和](https://en.wikipedia.org/wiki/Semantic_satiation)吧!公平来讲,本来也可以称它为[“一元计数(unary counting)”](https://en.wikipedia.org/wiki/Unary_numeral_system)。 57 | 58 | 在想表示负数的情况下,值*-n*可被表示成*n*个相同的其它标记。在上例中,值*+n*被表示成*n*个`+`标记,而值*-m*被表示成*m*个`-`标记。 59 | 60 | 有负数的情况下操作起来稍微复杂一些,增减操作在当前数值为负时实际上互换了角色。给定`+`和`-`分别作为正数与负数标记,相应操作的实现将变成: 61 | 62 | * 增加一: 63 | * 匹配`()`并替换为`(+)` 64 | * 匹配`(- $($count:tt)*)`并替换为`($($count)*)` 65 | * 匹配`($($count:tt)+)`并替换为`(+ $($count)+)` 66 | * 减少一: 67 | * 匹配`()`并替换为`(-)` 68 | * 匹配`(+ $($count:tt)*)`并替换为`($($count)*)` 69 | * 匹配`($($count:tt)+)`并替换为`(- $($count)+)` 70 | * 与0相比较:匹配 `()` 71 | * 与+1相比较:匹配`(+)` 72 | * 与-1相比较:匹配`(-)` 73 | * 与+2相比较:匹配`(++)` 74 | * 与-2相比较:匹配`(--)` 75 | * *(依此类推...)* 76 | 77 | 注意在顶部的示例中,某些规则被合并到一起了(举例来说,对`()`及`($($count:tt)+)`的增加操作被合并为对`($($count:tt)*)`的增加操作)。 78 | 79 | 如果想要提取出所计数目的实际值,可再使用普通的[计数宏](blk-counting.md)。对上例来说,终结规则可换为: 80 | 81 | ```ignore 82 | macro_rules! abacus { 83 | // ... 84 | 85 | // 下列规则将计数替换成实际值的表达式 86 | (() -> ()) => {0}; 87 | (() -> (- $($count:tt)*)) => { 88 | {(-1i32) $(- replace_expr!($count 1i32))*} 89 | }; 90 | (() -> (+ $($count:tt)*)) => { 91 | {(1i32) $(+ replace_expr!($count 1i32))*} 92 | }; 93 | } 94 | 95 | macro_rules! replace_expr { 96 | ($_t:tt $sub:expr) => {$sub}; 97 | } 98 | ``` 99 | 100 | > **仅限此例**:严格来说,想要达到此例的效果,没必要做的这么复杂。如果你不需要在宏中匹配所计的值,可直接采用重复来更加高效地实现: 101 | > 102 | > ```ignore 103 | > macro_rules! abacus { 104 | > (-) => {-1}; 105 | > (+) => {1}; 106 | > ($($moves:tt)*) => { 107 | > 0 $(+ abacus!($moves))* 108 | > } 109 | > } 110 | > ``` 111 | -------------------------------------------------------------------------------- /text/pat-push-down-accumulation.md: -------------------------------------------------------------------------------- 1 | % 下推累积 2 | 3 | ```rust 4 | macro_rules! init_array { 5 | (@accum (0, $_e:expr) -> ($($body:tt)*)) 6 | => {init_array!(@as_expr [$($body)*])}; 7 | (@accum (1, $e:expr) -> ($($body:tt)*)) 8 | => {init_array!(@accum (0, $e) -> ($($body)* $e,))}; 9 | (@accum (2, $e:expr) -> ($($body:tt)*)) 10 | => {init_array!(@accum (1, $e) -> ($($body)* $e,))}; 11 | (@accum (3, $e:expr) -> ($($body:tt)*)) 12 | => {init_array!(@accum (2, $e) -> ($($body)* $e,))}; 13 | (@as_expr $e:expr) => {$e}; 14 | [$e:expr; $n:tt] => { 15 | { 16 | let e = $e; 17 | init_array!(@accum ($n, e.clone()) -> ()) 18 | } 19 | }; 20 | } 21 | 22 | let strings: [String; 3] = init_array![String::from("hi!"); 3]; 23 | # assert_eq!(format!("{:?}", strings), "[\"hi!\", \"hi!\", \"hi!\"]"); 24 | ``` 25 | 26 | 在Rust中,所有宏最终必须展开为一个完整、有效的句法元素(比如表达式、条目等等)。这意味着,不可能定义一个最终展开为残缺构造的宏。 27 | 28 | 有些人可能希望,上例中的宏能被更加直截了当地表述成: 29 | 30 | ```ignore 31 | macro_rules! init_array { 32 | (@accum 0, $_e:expr) => {/* empty */}; 33 | (@accum 1, $e:expr) => {$e}; 34 | (@accum 2, $e:expr) => {$e, init_array!(@accum 1, $e)}; 35 | (@accum 3, $e:expr) => {$e, init_array!(@accum 2, $e)}; 36 | [$e:expr; $n:tt] => { 37 | { 38 | let e = $e; 39 | [init_array!(@accum $n, e)] 40 | } 41 | }; 42 | } 43 | ``` 44 | 45 | 他们预期的展开过程如下: 46 | 47 | ```ignore 48 | [init_array!(@accum 3, e)] 49 | [e, init_array!(@accum 2, e)] 50 | [e, e, init_array!(@accum 1, e)] 51 | [e, e, e] 52 | ``` 53 | 54 | 然而,这一思路中,每个中间步骤的展开结果都是一个不完整的表达式。即便这些中间结果对外部来说绝不可见,Rust仍然禁止这种用法。 55 | 56 | 下推累积则使我们得以在完全完成之前毋需考虑构造的完整性,进而累积构建出我们所需的标记序列。顶端给出的示例中,宏调用的展开过程如下: 57 | 58 | ```ignore 59 | init_array! { String:: from ( "hi!" ) ; 3 } 60 | init_array! { @ accum ( 3 , e . clone ( ) ) -> ( ) } 61 | init_array! { @ accum ( 2 , e.clone() ) -> ( e.clone() , ) } 62 | init_array! { @ accum ( 1 , e.clone() ) -> ( e.clone() , e.clone() , ) } 63 | init_array! { @ accum ( 0 , e.clone() ) -> ( e.clone() , e.clone() , e.clone() , ) } 64 | init_array! { @ as_expr [ e.clone() , e.clone() , e.clone() , ] } 65 | ``` 66 | 67 | 可以看到,每一步都在累积输出,直到规则完成,给出完整的表达式。 68 | 69 | 上述过程的关键点在于,使用`$($body:tt)*`来保存输出中间值,而不触发其它解析机制。采用`($input) -> ($output)`的形式仅是出于传统,用以明示此类宏的作用。 70 | 71 | 由于可以存储任意复杂的中间结果,下推累积在构建[标记树撕咬机](#incremental-tt-munchers)的过程中经常被用到。 72 | -------------------------------------------------------------------------------- /text/pat-repetition-replacement.md: -------------------------------------------------------------------------------- 1 | % 重复替代 2 | 3 | ```ignore 4 | macro_rules! replace_expr { 5 | ($_t:tt $sub:expr) => {$sub}; 6 | } 7 | ``` 8 | 9 | 在此模式中,匹配到的重复序列将被直接丢弃,仅留用它所带来的长度信息;原本标记所在的位置将被替换成某种重复要素。 10 | 11 | 举个例子,考虑如何为一个元素多于12个(Rust 1.2下的最大值)的`tuple`提供默认值。 12 | 13 | ```rust 14 | macro_rules! tuple_default { 15 | ($($tup_tys:ty),*) => { 16 | ( 17 | $( 18 | replace_expr!( 19 | ($tup_tys) 20 | Default::default() 21 | ), 22 | )* 23 | ) 24 | }; 25 | } 26 | # 27 | # macro_rules! replace_expr { 28 | # ($_t:tt $sub:expr) => {$sub}; 29 | # } 30 | # 31 | # assert_eq!(tuple_default!(i32, bool, String), (0, false, String::new())); 32 | ``` 33 | 34 | > **仅对此例**:我们其实可以直接用`$tup_tys::default()`。 35 | 36 | 上例中,我们并未真正使用匹配到的类型。实际上,我们把它抛开了,并用用一个表达式重复替代。换句话说,我们实际关心的不是有哪些类型,而是有多少个类型。 37 | -------------------------------------------------------------------------------- /text/pat-trailing-separators.md: -------------------------------------------------------------------------------- 1 | % 尾部分隔符 2 | 3 | ```ignore 4 | macro_rules! match_exprs { 5 | ($($exprs:expr),* $(,)*) => {...}; 6 | } 7 | ``` 8 | 9 | Rust语法在很多地方允许尾部分隔符存在。一列(举例说)表达式的常见匹配方式有两种(`$($exprs:expr),*`和`$($exprs:expr,)*`);一种可处理无尾部分隔符的情况,一种可处理有的情况;但没办法同时匹配到。 10 | 11 | 不过,在主重复的尾部放置一个`$(,)*`重复,则可以匹配到任意数量(包括0或1)的尾部分隔符。 12 | 13 | 注意此模式并非对所有情况都适用。如果被编译器拒绝,可以尝试增加匹配臂和/或使用逐条匹配。 14 | -------------------------------------------------------------------------------- /text/pat-tt-bundling.md: -------------------------------------------------------------------------------- 1 | % 标记树聚束 2 | 3 | ```rust 4 | macro_rules! call_a_or_b_on_tail { 5 | ((a: $a:expr, b: $b:expr), 调a $($tail:tt)*) => { 6 | $a(stringify!($($tail)*)) 7 | }; 8 | 9 | ((a: $a:expr, b: $b:expr), 调b $($tail:tt)*) => { 10 | $b(stringify!($($tail)*)) 11 | }; 12 | 13 | ($ab:tt, $_skip:tt $($tail:tt)*) => { 14 | call_a_or_b_on_tail!($ab, $($tail)*) 15 | }; 16 | } 17 | 18 | fn compute_len(s: &str) -> Option { 19 | Some(s.len()) 20 | } 21 | 22 | fn show_tail(s: &str) -> Option { 23 | println!("tail: {:?}", s); 24 | None 25 | } 26 | 27 | fn main() { 28 | assert_eq!( 29 | call_a_or_b_on_tail!( 30 | (a: compute_len, b: show_tail), 31 | 规则的 递归部分 将 跳过 所有这些 标记 32 | 它们 并不 关心 我们究竟 调b 还是 调a 33 | 只有 终结规则 关心 34 | ), 35 | None 36 | ); 37 | assert_eq!( 38 | call_a_or_b_on_tail!( 39 | (a: compute_len, b: show_tail), 40 | 而现在 为了 显式 可能的路径 有两条 41 | 我们也 调a 一哈: 它的 输入 应该 42 | 自我引用 因此 我们给它 一个 72), 43 | Some(72) 44 | ); 45 | } 46 | ``` 47 | 48 | 在十分复杂的递归宏中,可能需要非常多的参数,才足以在每层调用之间传递必要的标识符与表达式。然而,根据实现上的差异,可能存在许多这样的中间层,它们转发了这些参数,但并没有用到。 49 | 50 | 因此,将所有这些参数聚成一束,通过分组将其放进单独一棵标记树里;可以省事许多。这样一来,那些用不到这些参数的递归层可以直接捕获并替换这棵标记树,而不需要把整组参数完完全全准准确确地捕获替换掉。 51 | 52 | 上面的例子把表达式`$a`和`$b`聚束,然后作为一棵`tt`交由递归规则转发。随后,终结规则将这组标记打开,并访问其中的表达式。 53 | -------------------------------------------------------------------------------- /text/pat-visibility.md: -------------------------------------------------------------------------------- 1 | % 可见性 2 | 3 | 在Rust中,因为没有类似`vis`的匹配选项,匹配替换可见性标记比较难搞。 4 | 5 | ## 匹配与忽略 6 | 7 | 根据上下文,可由重复做到这点: 8 | 9 | ```rust 10 | macro_rules! struct_name { 11 | ($(pub)* struct $name:ident $($rest:tt)*) => { stringify!($name) }; 12 | } 13 | # 14 | # fn main() { 15 | # assert_eq!(struct_name!(pub struct Jim;), "Jim"); 16 | # } 17 | ``` 18 | 19 | 上例将匹配公共可见或本地可见的`struct`条目。但它还能匹配到`pub pub` (十分公开?)甚至是`pub pub pub pub` (真的非常非常公开)。防止这种情况出现的最好方法,只有祈祷调用方没那么多毛病。 20 | 21 | ## 匹配和替换 22 | 23 | 由于不能将重复的内容和其自身同时绑定至一个变量,没有办法将`$(pub)*`的内容直接拿去替换使用。因此,我们只好使用多条规则: 24 | 25 | ```rust 26 | macro_rules! newtype_new { 27 | (struct $name:ident($t:ty);) => { newtype_new! { () struct $name($t); } }; 28 | (pub struct $name:ident($t:ty);) => { newtype_new! { (pub) struct $name($t); } }; 29 | 30 | (($($vis:tt)*) struct $name:ident($t:ty);) => { 31 | as_item! { 32 | impl $name { 33 | $($vis)* fn new(value: $t) -> Self { 34 | $name(value) 35 | } 36 | } 37 | } 38 | }; 39 | } 40 | 41 | macro_rules! as_item { ($i:item) => {$i} } 42 | # 43 | # #[derive(Debug, Eq, PartialEq)] 44 | # struct Dummy(i32); 45 | # 46 | # newtype_new! { struct Dummy(i32); } 47 | # 48 | # fn main() { 49 | # assert_eq!(Dummy::new(42), Dummy(42)); 50 | # } 51 | ``` 52 | 53 | > **参考**:[AST强转](blk-ast-coercion.md). 54 | 55 | 这里,我们用到了宏对成组的任意标记的匹配能力,来同时匹配`()`与`(pub)`,并将所得内容替换到输出中。因为在此处解析器不会期望看到一个`tt`重复的展开结果,我们需要使用[AST强转](blk-ast-coercion.md)来使代码正常运作。 56 | -------------------------------------------------------------------------------- /text/pim-README.md: -------------------------------------------------------------------------------- 1 | % 宏,实践介绍 2 | 3 | 本章节将通过一个相对简单、可行的例子来介绍Rust的“示例宏”系统。我们将不会试图解释整个宏系统错综复杂的构造;而是试图让读者能够舒适地了解宏的书写方式,以及为何如斯。 4 | 5 | 在[Rust官方教程中也有一章讲解宏](http://doc.rust-lang.org/book/macros.html)([中文版](https://kaisery.gitbooks.io/rust-book-chinese/content/content/Macros%20%E5%AE%8F.html)),同样提供了高层面的讲解。同时,本书也有一章[更富条理的介绍](mbe-README.html),旨在详细阐释宏系统。 6 | 7 | ## 一点背景知识 8 | 9 | > **注意**:别慌!我们通篇只会涉及到下面这一点点数学。如果想直接看重点,本小节可被安全跳过。 10 | 11 | 如果你不了解,所谓“递推(recurrence)关系”是指这样一个序列,其中的每个值都由先前的一个或多个值决定,并最终由一个或多个初始值完全决定。举例来说,[Fibonacci数列](https://en.wikipedia.org/wiki/Fibonacci_number)可被定义为如下关系: 12 | 13 | 14 | 57 | 58 |
59 | Fn=0,1,,Fn1+Fn-2 60 |
61 | 62 | 63 | 即,序列的前两个数分别为0和1,而第3个则为F0 + F1 = 0 + 1 = 1,第4个为F1 + F2 = 1 + 1 = 2,依此类推。 64 | 65 | 由于这列值可以永远持续下去,定义一个`fibonacci`的求值函数略显困难。显然,返回一整列值并不实际。我们真正需要的,应是某种具有怠惰求值性质的东西——只在必要的时候才进行运算求值。 66 | 67 | 在Rust中,这样的需求表明,是`Iterator`派上用场的时候了。实现迭代器并不十分困难,但比较繁琐:你得自定义一个类型,弄明白该在其中存储什么,然后为它实现`Iterator` trait。 68 | 69 | 其实,递推关系足够简单;几乎所有的递推关系都可被抽象出来,变成一小段由宏驱动的代码生成机制。 70 | 71 | 好了,说得已经足够多了,让我们开始干活吧。 72 | 73 | ## 构建过程 74 | 75 | 通常来说,在构建新宏时,我所做的第一件事,是决定宏调用的形式。在我们当前所讨论的情况下,我的初次尝试是这样: 76 | 77 | ```rust 78 | let fib = recurrence![a[n] = 0, 1, ..., a[n-1] + a[n-2]]; 79 | 80 | for e in fib.take(10) { println!("{}", e) } 81 | ``` 82 | 83 | 以此为基点,我们可以向宏的定义迈出第一步,即便在此时我们尚不了解该宏的展开部分究竟是什么样子。此步骤的用处在于,如果在此处无法明确如何解析输入语法,那就可能意味着,整个宏的构思需要改变。 84 | 85 | ```rust 86 | macro_rules! recurrence { 87 | ( a[n] = $($inits:expr),+ , ... , $recur:expr ) => { /* ... */ }; 88 | } 89 | # fn main() {} 90 | ``` 91 | 92 | 假装你并不熟悉相应的语法,让我来解释。上述代码块使用`macro_rules!`系统定义了一个宏,称为`recurrence!`。此宏仅包含一条解析规则,它规定,此宏必须依次匹配下列项目: 93 | 94 | - 一段字面标记序列,`a` `[` `n` `]` `=`; 95 | - 一段重复 (`$( ... )`)序列,由`,`分隔,允许重复一或多次(`+`);重复的内容允许: 96 | - 一个有效的表达式,它将被捕获至变量`inits` (`$inits:expr`) 97 | - 又一段字面标记序列, `...` `,`; 98 | - 一个有效的表达式,将被捕获至变量`recur` (`$recur:expr`)。 99 | 100 | 最后,规则声明,如果输入被成功匹配,则对该宏的调用将被标记序列`/* ... */`替换。 101 | 102 | 值得注意的是,`inits`,如它命名采用的复数形式所暗示的,实际上包含所有成功匹配进此重复的表达式,而不仅是第一或最后一个。不仅如此,它们将被捕获成一个序列,而不是——举例说——把它们不可逆地粘贴在一起。还注意到,可用`*`替换`+`来表示允许“0或多个”重复。宏系统并不支持“0或1个”或任何其它更加具体的重复形式。 103 | 104 | 作为练习,我们将采用上面提及的输入,并研究它被处理的过程。“位置”列将揭示下一个需要被匹配的句法模式,由“⌂”标出。注意在某些情况下下一个可用元素可能存在多个。“输入”将包括所有尚未被消耗的标记。`inits`和`recur`将分别包含其对应绑定的内容。 105 | 106 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 152 | 153 | 154 | 155 | 156 | 157 | 159 | 160 | 161 | 162 | 163 | 164 | 166 | 167 | 168 | 169 | 170 | 171 | 173 | 174 | 175 | 176 | 177 | 178 | 180 | 181 | 182 | 183 | 184 | 185 | 187 | 188 | 189 | 190 | 191 | 192 | 194 | 195 | 196 | 197 | 198 | 199 | 201 | 202 | 203 | 204 | 205 | 206 | 211 | 212 | 213 | 215 | 216 | 217 | 218 | 219 | 220 | 222 | 223 | 224 | 225 | 226 | 227 | 232 | 233 | 234 | 236 | 237 | 238 | 239 | 240 | 241 | 243 | 244 | 245 | 246 | 247 | 248 | 250 | 251 | 252 | 253 | 254 | 255 | 257 | 258 | 259 | 260 | 261 | 262 | 267 | 268 | 269 |
位置输入initsrecur
a[n] = $($inits:expr),+ , ... , $recur:expr 151 | a[n] = 0, 1, ..., a[n-1] + a[n-2]
a[n] = $($inits:expr),+ , ... , $recur:expr 158 | [n] = 0, 1, ..., a[n-1] + a[n-2]
a[n] = $($inits:expr),+ , ... , $recur:expr 165 | n] = 0, 1, ..., a[n-1] + a[n-2]
a[n] = $($inits:expr),+ , ... , $recur:expr 172 | ] = 0, 1, ..., a[n-1] + a[n-2]
a[n] = $($inits:expr),+ , ... , $recur:expr 179 | = 0, 1, ..., a[n-1] + a[n-2]
a[n] = $($inits:expr),+ , ... , $recur:expr 186 | 0, 1, ..., a[n-1] + a[n-2]
a[n] = $($inits:expr),+ , ... , $recur:expr 193 | 0, 1, ..., a[n-1] + a[n-2]
a[n] = $($inits:expr),+ , ... , $recur:expr 200 | ⌂ ⌂, 1, ..., a[n-1] + a[n-2]0
207 | 208 | 注意:这有两个 ⌂,因为下个输入标记既能匹配 重复元素间的分隔符逗号,也能匹配 标志重复结束的逗号。宏系统将同时追踪这两种可能,直到决定具体选择为止。 209 | 210 |
a[n] = $($inits:expr),+ , ... , $recur:expr 214 | ⌂ ⌂1, ..., a[n-1] + a[n-2]0
a[n] = $($inits:expr),+ , ... , $recur:expr 221 | ⌂ ⌂ , ..., a[n-1] + a[n-2]0, 1
228 | 229 | 注意:第三个被划掉的记号表明,基于上个被消耗的标记,宏系统排除了一项先前存在的可能。 230 | 231 |
a[n] = $($inits:expr),+ , ... , $recur:expr 235 | ⌂ ⌂..., a[n-1] + a[n-2]0, 1
a[n] = $($inits:expr),+ , ... , $recur:expr 242 | , a[n-1] + a[n-2]0, 1
a[n] = $($inits:expr),+ , ... , $recur:expr 249 | a[n-1] + a[n-2]0, 1
a[n] = $($inits:expr),+ , ... , $recur:expr 256 | 0, 1a[n-1] + a[n-2]
263 | 264 | 注意:这一步表明,类似$recur:expr的绑定将消耗一个完整的表达式。此处,究竟什么算是一个完整的表达式,将由编译器决定。稍后我们会谈到语言其它部分的类似行为。 265 | 266 |
270 | 271 |

272 | 273 | 从此表中得到的最关键收获在于,宏系统会依次尝试将提供给它的每个标记当作输入,与提供给它的每条规则进行匹配。我们稍后还将谈回到这一“尝试”。 274 | 275 | 接下来我们首先将写出宏调用完全展开后的形态。我们想要的结构类似: 276 | 277 | ```rust 278 | let fib = { 279 | struct Recurrence { 280 | mem: [u64; 2], 281 | pos: usize, 282 | } 283 | ``` 284 | 285 | 它就是我们实际使用的迭代器类型。其中,`mem`负责存储最近算得的两个斐波那契值,保证递推计算能够顺利进行;`pos`则负责记录当前的`n`值。 286 | 287 | > **附注**:此处选用`u64`是因为,对斐波那契数列来说,它已经“足够”了。先不必担心它是否适用于其它数列,我们会提到这一点的。 288 | 289 | ```rust 290 | impl Iterator for Recurrence { 291 | type Item = u64; 292 | 293 | #[inline] 294 | fn next(&mut self) -> Option { 295 | if self.pos < 2 { 296 | let next_val = self.mem[self.pos]; 297 | self.pos += 1; 298 | Some(next_val) 299 | ``` 300 | 301 | 我们需要这个`if`分支来返回序列的初始值,没什么花哨。 302 | 303 | ```rust 304 | } else { 305 | let a = /* something */; 306 | let n = self.pos; 307 | let next_val = (a[n-1] + a[n-2]); 308 | 309 | self.mem.TODO_shuffle_down_and_append(next_val); 310 | 311 | self.pos += 1; 312 | Some(next_val) 313 | } 314 | } 315 | } 316 | ``` 317 | 318 | 这段稍微难办一点。对于具体如何定义`a`,我们稍后再提。`TODO_shuffle_down_and_append`的真面目也将留到稍后揭晓;我们想让它做到:将`next_val`放至数组末尾,并将数组中剩下的元素依次前移一格,最后丢掉原先的首元素。 319 | 320 | ```rust 321 | 322 | Recurrence { mem: [0, 1], pos: 0 } 323 | }; 324 | 325 | for e in fib.take(10) { println!("{}", e) } 326 | ``` 327 | 328 | 最后,我们返回一个该结构的实例。在随后的代码中,我们将用它来进行迭代。综上所述,完整的展开应该如下: 329 | 330 | ```rust 331 | let fib = { 332 | struct Recurrence { 333 | mem: [u64; 2], 334 | pos: usize, 335 | } 336 | 337 | impl Iterator for Recurrence { 338 | type Item = u64; 339 | 340 | #[inline] 341 | fn next(&mut self) -> Option { 342 | if self.pos < 2 { 343 | let next_val = self.mem[self.pos]; 344 | self.pos += 1; 345 | Some(next_val) 346 | } else { 347 | let a = /* something */; 348 | let n = self.pos; 349 | let next_val = (a[n-1] + a[n-2]); 350 | 351 | self.mem.TODO_shuffle_down_and_append(next_val.clone()); 352 | 353 | self.pos += 1; 354 | Some(next_val) 355 | } 356 | } 357 | } 358 | 359 | Recurrence { mem: [0, 1], pos: 0 } 360 | }; 361 | 362 | for e in fib.take(10) { println!("{}", e) } 363 | ``` 364 | 365 | > **附注**:是的,这样做的确意味着每次调用该宏时,我们都会重新定义并实现一个`Recurrence`结构。如果`#[inline]`属性应用得当,在最终编译出的二进制文件中,大部分冗余都将被优化掉。 366 | 367 | 在写展开部分的过程中时常检查,也是一个有效的技巧。如果在过程中发现,展开中的某些内容需要根据调用的不同发生改变,但这些内容并未被我们的宏语法定义囊括;那就要去考虑,应该怎样去引入它们。在此示例中,我们先前用过一次`u64`,但调用端想要的类型不一定是它;然而我们的宏语法并没有提供其它选择。因此,我们可以做一些修改。 368 | 369 | ```rust 370 | macro_rules! recurrence { 371 | ( a[n]: $sty:ty = $($inits:expr),+ , ... , $recur:expr ) => { /* ... */ }; 372 | } 373 | 374 | /* 375 | let fib = recurrence![a[n]: u64 = 0, 1, ..., a[n-1] + a[n-2]]; 376 | 377 | for e in fib.take(10) { println!("{}", e) } 378 | */ 379 | # fn main() {} 380 | ``` 381 | 382 | 我们加入了一个新的捕获`sty`,它应是一个类型(type)。 383 | 384 | > **附注**:如果你不清楚的话,在捕获冒号之后的部分,可是几种语法匹配候选项之一。最常用的包括`item`,`expr`和`ty`。完整的解释可在[宏,彻底解析-`macro_rules!`-捕获](mbe-macro-rules.html#捕获)部分找到。 385 | > 386 | > 还有一点值得注意:为方便语言的未来发展,对于跟在某些特定的匹配之后的标记,编译器施加了一些限制。这种情况常在试图匹配至表达式(expression)或语句(statement)时出现:它们后面仅允许跟进`=>`,`,`和`;`这些标记之一。 387 | > 388 | > 完整清单可在[宏,彻底解析-细枝末节-再探捕获与展开](mbe-min-captures-and-expansion-redux.md)找到。 389 | 390 | ## 索引与移位 391 | 392 | 在此节中我们将略去一些实际上与宏的联系不甚紧密的内容。本节我们的目标是,让用户可以通过索引`a`来访问数列中先前的值。`a`应该如同一个切口,让我们得以持续访问数列中最近几个(在本例中,两个)值。 393 | 394 | 通过采用封装类,我们可以相对简单地做到这点: 395 | 396 | ```rust 397 | struct IndexOffset<'a> { 398 | slice: &'a [u64; 2], 399 | offset: usize, 400 | } 401 | 402 | impl<'a> Index for IndexOffset<'a> { 403 | type Output = u64; 404 | 405 | #[inline(always)] 406 | fn index<'b>(&'b self, index: usize) -> &'b u64 { 407 | use std::num::Wrapping; 408 | 409 | let index = Wrapping(index); 410 | let offset = Wrapping(self.offset); 411 | let window = Wrapping(2); 412 | 413 | let real_index = index - offset + window; 414 | &self.slice[real_index.0] 415 | } 416 | } 417 | ``` 418 | 419 | > **附注**:对于新接触Rust的人来说,生命周期的概念经常需要一番思考。我们给出一些简单的解释:`'a`和`'b`是生命周期参数,它们被用于记录引用(即一个指向某些数据的借用指针)的有效期。在此例中,`IndexOffset`借用了一个指向我们迭代器数据的引用,因此,它需要记录该引用的有效期,记录者正是`'a`。 420 | > 421 | > 我们用到`'b`,是因为`Index::index`函数(下标句法正是通过此函数实现的)的一个参数也需要生命周期。`'a`和`'b`不一定在所有情况下都相同。我们并没有显式地声明`'a`和`'b`之间有任何联系,但借用检查器(borrow checker)总会确保内存安全性不被意外破坏。 422 | 423 | `a`地定义将随之变为: 424 | 425 | ```rust 426 | let a = IndexOffset { slice: &self.mem, offset: n }; 427 | ``` 428 | 429 | 如何处理`TODO_shuffle_down_and_append`是我们现在剩下的唯一问题了。我没能在标准库中寻得可以直接使用的方法,但自己造一个出来并不难。 430 | 431 | ```rust 432 | { 433 | use std::mem::swap; 434 | 435 | let mut swap_tmp = next_val; 436 | for i in (0..2).rev() { 437 | swap(&mut swap_tmp, &mut self.mem[i]); 438 | } 439 | } 440 | ``` 441 | 442 | 它把新值替换至数组末尾,并把其他值向前移动一位。 443 | 444 | > **附注**:采用这种做法,将使得我们的代码可同时被用于不可拷贝(non-copyable)的类型。 445 | 446 | 至此,最终起作用的代码将是: 447 | 448 | ```rust 449 | macro_rules! recurrence { 450 | ( a[n]: $sty:ty = $($inits:expr),+ , ... , $recur:expr ) => { /* ... */ }; 451 | } 452 | 453 | fn main() { 454 | /* 455 | let fib = recurrence![a[n]: u64 = 0, 1, ..., a[n-1] + a[n-2]]; 456 | 457 | for e in fib.take(10) { println!("{}", e) } 458 | */ 459 | let fib = { 460 | use std::ops::Index; 461 | 462 | struct Recurrence { 463 | mem: [u64; 2], 464 | pos: usize, 465 | } 466 | 467 | struct IndexOffset<'a> { 468 | slice: &'a [u64; 2], 469 | offset: usize, 470 | } 471 | 472 | impl<'a> Index for IndexOffset<'a> { 473 | type Output = u64; 474 | 475 | #[inline(always)] 476 | fn index<'b>(&'b self, index: usize) -> &'b u64 { 477 | use std::num::Wrapping; 478 | 479 | let index = Wrapping(index); 480 | let offset = Wrapping(self.offset); 481 | let window = Wrapping(2); 482 | 483 | let real_index = index - offset + window; 484 | &self.slice[real_index.0] 485 | } 486 | } 487 | 488 | impl Iterator for Recurrence { 489 | type Item = u64; 490 | 491 | #[inline] 492 | fn next(&mut self) -> Option { 493 | if self.pos < 2 { 494 | let next_val = self.mem[self.pos]; 495 | self.pos += 1; 496 | Some(next_val) 497 | } else { 498 | let next_val = { 499 | let n = self.pos; 500 | let a = IndexOffset { slice: &self.mem, offset: n }; 501 | (a[n-1] + a[n-2]) 502 | }; 503 | 504 | { 505 | use std::mem::swap; 506 | 507 | let mut swap_tmp = next_val; 508 | for i in (0..2).rev() { 509 | swap(&mut swap_tmp, &mut self.mem[i]); 510 | } 511 | } 512 | 513 | self.pos += 1; 514 | Some(next_val) 515 | } 516 | } 517 | } 518 | 519 | Recurrence { mem: [0, 1], pos: 0 } 520 | }; 521 | 522 | for e in fib.take(10) { println!("{}", e) } 523 | } 524 | ``` 525 | 526 | 注意我们改变了`n`与`a`的声明顺序,同时将它们(与递推表达式一同)用一个新区块包裹了起来。改变声明顺序的理由很明显(`n`得在`a`前被定义才能被`a`使用)。而包裹的理由则是:如果不,借用引用`&self.mem`将会阻止随后的`swap`操作(在某物仍存在其它别名时,无法对其进行改变)。包裹区块将确保`&self.mem`产生的借用在彼时过期。 527 | 528 | 顺带一提,将交换`mem`的代码包进区块里的唯一原因,正是为了缩减`std::mem::swap`的可用范畴,以保持代码整洁。 529 | 530 | 如果我们直接拿上段代码来跑,将会得到: 531 | 532 | ```text 533 | 0 534 | 1 535 | 1 536 | 2 537 | 3 538 | 5 539 | 8 540 | 13 541 | 21 542 | 34 543 | ``` 544 | 545 | 成功了!现在,让我们把这段代码复制粘贴进宏的展开部分,并把它们原本所在的位置换成一次宏调用。这样我们得到: 546 | 547 | ```rust 548 | macro_rules! recurrence { 549 | ( a[n]: $sty:ty = $($inits:expr),+ , ... , $recur:expr ) => { 550 | { 551 | /* 552 | What follows here is *literally* the code from before, 553 | cut and pasted into a new position. No other changes 554 | have been made. 555 | */ 556 | 557 | use std::ops::Index; 558 | 559 | struct Recurrence { 560 | mem: [u64; 2], 561 | pos: usize, 562 | } 563 | 564 | struct IndexOffset<'a> { 565 | slice: &'a [u64; 2], 566 | offset: usize, 567 | } 568 | 569 | impl<'a> Index for IndexOffset<'a> { 570 | type Output = u64; 571 | 572 | #[inline(always)] 573 | fn index<'b>(&'b self, index: usize) -> &'b u64 { 574 | use std::num::Wrapping; 575 | 576 | let index = Wrapping(index); 577 | let offset = Wrapping(self.offset); 578 | let window = Wrapping(2); 579 | 580 | let real_index = index - offset + window; 581 | &self.slice[real_index.0] 582 | } 583 | } 584 | 585 | impl Iterator for Recurrence { 586 | type Item = u64; 587 | 588 | #[inline] 589 | fn next(&mut self) -> Option { 590 | if self.pos < 2 { 591 | let next_val = self.mem[self.pos]; 592 | self.pos += 1; 593 | Some(next_val) 594 | } else { 595 | let next_val = { 596 | let n = self.pos; 597 | let a = IndexOffset { slice: &self.mem, offset: n }; 598 | (a[n-1] + a[n-2]) 599 | }; 600 | 601 | { 602 | use std::mem::swap; 603 | 604 | let mut swap_tmp = next_val; 605 | for i in (0..2).rev() { 606 | swap(&mut swap_tmp, &mut self.mem[i]); 607 | } 608 | } 609 | 610 | self.pos += 1; 611 | Some(next_val) 612 | } 613 | } 614 | } 615 | 616 | Recurrence { mem: [0, 1], pos: 0 } 617 | } 618 | }; 619 | } 620 | 621 | fn main() { 622 | let fib = recurrence![a[n]: u64 = 0, 1, ..., a[n-1] + a[n-2]]; 623 | 624 | for e in fib.take(10) { println!("{}", e) } 625 | } 626 | ``` 627 | 628 | 显然,宏的捕获尚未被用到,但这点很好改。不过,如果尝试编译上述代码,`rustc`会中止,并显示: 629 | 630 | ```text 631 | recurrence.rs:69:45: 69:48 error: local ambiguity: multiple parsing options: built-in NTs expr ('inits') or 1 other options. 632 | recurrence.rs:69 let fib = recurrence![a[n]: u64 = 0, 1, ..., a[n-1] + a[n-2]]; 633 | ^~~ 634 | ``` 635 | 636 | 这里我们撞上了`macro_rules`的一处限制。问题出在那第二个逗号上。当在展开过程中遇见它时,编译器无法决定是该将它解析成`inits`中的又一个表达式,还是解析成`...`。很遗憾,它不够聪明,没办法意识到`...`不是一个有效的表达式,所以它选择了放弃。理论上来说,上述代码应该能奏效,但当前它并不能。 637 | 638 | > **附注**:有关宏系统如何解读我们的规则,我之前的确撒了点小谎。通常来说,宏系统确实应当如我前述的那般运作,但在这里它没有。`macro_rules`的机制,由此看来,是存在一些小毛病的;我们得记得偶尔去做一些调控,好让它我们期许的那般运作。 639 | > 640 | > 在本例中,问题有两个。其一,宏系统不清楚各式各样的语法元素(如表达式)可由什么样的东西构成,或不能由什么样的东西构成;那是语法解析器的工作。其二,在试图捕获复合语法元素(如表达式)的过程中,它无法不100%地首先陷入该捕获中去。 641 | > 642 | > 换句话说,宏系统可以向语法解析器发出请求,让后者试图把某段输入当作表达式来进行解析;但此间无论语法解析器遇见任何问题,都将中止整个进程以示回应。目前,宏系统处理这种窘境的唯一方式,就是对任何可能产生此类问题的情境加以禁止。 643 | > 644 | > 好的一面在于,对于这摊子情况,没有任何人感到高兴。关键词`macro`早已被预留,以备未来更加严密的宏系统使用。直到那天来临之前,我们还是只得该怎么做就怎么做。 645 | 646 | 还好,修正方案也很简单:从宏句法中去掉逗号即可。出于平衡考量,我们将移除`...`双边的逗号:[^译注1] 647 | 648 | ```rust 649 | macro_rules! recurrence { 650 | ( a[n]: $sty:ty = $($inits:expr),+ ... $recur:expr ) => { 651 | // ^~~ changed 652 | /* ... */ 653 | # // Cheat :D 654 | # (vec![0u64, 1, 2, 3, 5, 8, 13, 21, 34]).into_iter() 655 | }; 656 | } 657 | 658 | fn main() { 659 | let fib = recurrence![a[n]: u64 = 0, 1 ... a[n-1] + a[n-2]]; 660 | // ^~~ changed 661 | 662 | for e in fib.take(10) { println!("{}", e) } 663 | } 664 | ``` 665 | 666 | 成功!现在,我们该将捕获部分捕获到的内容替代进展开部分中了。 667 | 668 | [^译注1]: 在目前的稳定版本(Rust 1.14.0)下,去掉双边逗号后的代码无法通过编译。`rustc`报错“error: `$inits:expr` may be followed by `...`, which is not allowed for `expr` fragments”。解决方案是将这两处逗号替换为其它字面值,如分号;与之前的捕获所用分隔符不同即可。 669 | 670 | ### 替换 671 | 672 | 在宏中替换你捕获到的内容相对简单,通过`$sty:ty`捕获到的内容可用`$sty`来替换。好,让我们换掉那些`u64`吧: 673 | 674 | ```rust 675 | macro_rules! recurrence { 676 | ( a[n]: $sty:ty = $($inits:expr),+ ... $recur:expr ) => { 677 | { 678 | use std::ops::Index; 679 | 680 | struct Recurrence { 681 | mem: [$sty; 2], 682 | // ^~~~ changed 683 | pos: usize, 684 | } 685 | 686 | struct IndexOffset<'a> { 687 | slice: &'a [$sty; 2], 688 | // ^~~~ changed 689 | offset: usize, 690 | } 691 | 692 | impl<'a> Index for IndexOffset<'a> { 693 | type Output = $sty; 694 | // ^~~~ changed 695 | 696 | #[inline(always)] 697 | fn index<'b>(&'b self, index: usize) -> &'b $sty { 698 | // ^~~~ changed 699 | use std::num::Wrapping; 700 | 701 | let index = Wrapping(index); 702 | let offset = Wrapping(self.offset); 703 | let window = Wrapping(2); 704 | 705 | let real_index = index - offset + window; 706 | &self.slice[real_index.0] 707 | } 708 | } 709 | 710 | impl Iterator for Recurrence { 711 | type Item = $sty; 712 | // ^~~~ changed 713 | 714 | #[inline] 715 | fn next(&mut self) -> Option<$sty> { 716 | // ^~~~ changed 717 | /* ... */ 718 | # if self.pos < 2 { 719 | # let next_val = self.mem[self.pos]; 720 | # self.pos += 1; 721 | # Some(next_val) 722 | # } else { 723 | # let next_val = { 724 | # let n = self.pos; 725 | # let a = IndexOffset { slice: &self.mem, offset: n }; 726 | # (a[n-1] + a[n-2]) 727 | # }; 728 | # 729 | # { 730 | # use std::mem::swap; 731 | # 732 | # let mut swap_tmp = next_val; 733 | # for i in (0..2).rev() { 734 | # swap(&mut swap_tmp, &mut self.mem[i]); 735 | # } 736 | # } 737 | # 738 | # self.pos += 1; 739 | # Some(next_val) 740 | # } 741 | } 742 | } 743 | 744 | Recurrence { mem: [1, 1], pos: 0 } 745 | } 746 | }; 747 | } 748 | 749 | fn main() { 750 | let fib = recurrence![a[n]: u64 = 0, 1 ... a[n-1] + a[n-2]]; 751 | 752 | for e in fib.take(10) { println!("{}", e) } 753 | } 754 | ``` 755 | 756 | 现在让我们来尝试更难的:如何将`inits`同时转变为字面值`[0, 1]`以及数组类型`[$sty; 2]`。首先我们试试: 757 | 758 | ```rust 759 | Recurrence { mem: [$($inits),+], pos: 0 } 760 | // ^~~~~~~~~~~ changed 761 | ``` 762 | 763 | 此段代码与捕获的效果正好相反:将`inits`捕得的内容排列开来,总共有1或多次,每条内容之间用逗号分隔。展开的结果与期望一致,我们得到标记序列:`0, 1`。 764 | 765 | 不过,通过`inits`转换出字面值`2`需要一些技巧。没有直接可行的方法,但我们可以通过另一个宏做到。我们一步一步来。 766 | 767 | ```rust 768 | macro_rules! count_exprs { 769 | /* ??? */ 770 | # () => {} 771 | } 772 | # fn main() {} 773 | ``` 774 | 775 | 先写显而易见的情况:未给表达式时,我们期望`count_exprs`展开为字面值`0`。 776 | 777 | ```rust 778 | macro_rules! count_exprs { 779 | () => (0); 780 | // ^~~~~~~~~~ added 781 | } 782 | # fn main() { 783 | # const _0: usize = count_exprs!(); 784 | # assert_eq!(_0, 0); 785 | # } 786 | ``` 787 | 788 | > **附注**:你可能已经注意到了,这里的展开部分我用的是括号而非花括号。`macro_rules`其实不关心你用的是什么,只要它成对匹配即可:`( )`,`{ }`或`[ ]`。实际上,宏本身的匹配符(即紧跟宏名称后的匹配符)、语法规则外的匹配符及相应展开部分外的匹配符都可以替换。 789 | > 790 | > 调用宏时的括号也可被替换,但有些限制:当宏被以`{...}`或`(...);`形式调用时,它总是会被解析为一个条目(item,比如,`struct`或`fn`声明)。在函数体内部时,这一特征很重要,它将消除“解析成表达式”和“解析成语句”之间的歧义。 791 | 792 | 有一个表达式的情况该怎么办?应该展开为字面值`1`。 793 | 794 | ```rust 795 | macro_rules! count_exprs { 796 | () => (0); 797 | ($e:expr) => (1); 798 | // ^~~~~~~~~~~~~~~~~ added 799 | } 800 | # fn main() { 801 | # const _0: usize = count_exprs!(); 802 | # const _1: usize = count_exprs!(x); 803 | # assert_eq!(_0, 0); 804 | # assert_eq!(_1, 1); 805 | # } 806 | ``` 807 | 808 | 两个呢? 809 | 810 | ```rust 811 | macro_rules! count_exprs { 812 | () => (0); 813 | ($e:expr) => (1); 814 | ($e0:expr, $e1:expr) => (2); 815 | // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ added 816 | } 817 | # fn main() { 818 | # const _0: usize = count_exprs!(); 819 | # const _1: usize = count_exprs!(x); 820 | # const _2: usize = count_exprs!(x, y); 821 | # assert_eq!(_0, 0); 822 | # assert_eq!(_1, 1); 823 | # assert_eq!(_2, 2); 824 | # } 825 | ``` 826 | 827 | 通过递归调用重新表达,我们可将扩展部分“精简”出来: 828 | 829 | ```rust 830 | macro_rules! count_exprs { 831 | () => (0); 832 | ($e:expr) => (1); 833 | ($e0:expr, $e1:expr) => (1 + count_exprs!($e1)); 834 | // ^~~~~~~~~~~~~~~~~~~~~ changed 835 | } 836 | # fn main() { 837 | # const _0: usize = count_exprs!(); 838 | # const _1: usize = count_exprs!(x); 839 | # const _2: usize = count_exprs!(x, y); 840 | # assert_eq!(_0, 0); 841 | # assert_eq!(_1, 1); 842 | # assert_eq!(_2, 2); 843 | # } 844 | ``` 845 | 846 | 这样做可行是因为,Rust可将`1 + 1`合并成一个常量。那么,三种表达式的情况呢? 847 | 848 | ```rust 849 | macro_rules! count_exprs { 850 | () => (0); 851 | ($e:expr) => (1); 852 | ($e0:expr, $e1:expr) => (1 + count_exprs!($e1)); 853 | ($e0:expr, $e1:expr, $e2:expr) => (1 + count_exprs!($e1, $e2)); 854 | // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ added 855 | } 856 | # fn main() { 857 | # const _0: usize = count_exprs!(); 858 | # const _1: usize = count_exprs!(x); 859 | # const _2: usize = count_exprs!(x, y); 860 | # const _3: usize = count_exprs!(x, y, z); 861 | # assert_eq!(_0, 0); 862 | # assert_eq!(_1, 1); 863 | # assert_eq!(_2, 2); 864 | # assert_eq!(_3, 3); 865 | # } 866 | ``` 867 | 868 | > **附注**:你可能会想,我们是否能翻转这些规则的排列顺序。在此情境下,可以。但在有些情况下,宏系统可能会对此挑剔。如果你发现自己有一个包含多项规则的宏系统老是报错,或给出期望外的结果;但你发誓它应该能用,试着调换一下规则的排序吧。 869 | 870 | 我们希望你现在已经能看出规律。通过匹配至一个表达式加上0或多个表达式并展开成1+a,我们可以减少规则列表的数目: 871 | 872 | ```rust 873 | macro_rules! count_exprs { 874 | () => (0); 875 | ($head:expr) => (1); 876 | ($head:expr, $($tail:expr),*) => (1 + count_exprs!($($tail),*)); 877 | // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ changed 878 | } 879 | # fn main() { 880 | # const _0: usize = count_exprs!(); 881 | # const _1: usize = count_exprs!(x); 882 | # const _2: usize = count_exprs!(x, y); 883 | # const _3: usize = count_exprs!(x, y, z); 884 | # assert_eq!(_0, 0); 885 | # assert_eq!(_1, 1); 886 | # assert_eq!(_2, 2); 887 | # assert_eq!(_3, 3); 888 | # } 889 | ``` 890 | 891 | > **仅对此例**:这段代码并非计数仅有或其最好的方法。若有兴趣,稍后可以研读[计数](blk-counting.md)一节。 892 | 893 | 有此工具后,我们可再次修改`recurrence`,确定`mem`所需的大小。 894 | 895 | ```rust 896 | // added: 897 | macro_rules! count_exprs { 898 | () => (0); 899 | ($head:expr) => (1); 900 | ($head:expr, $($tail:expr),*) => (1 + count_exprs!($($tail),*)); 901 | } 902 | 903 | macro_rules! recurrence { 904 | ( a[n]: $sty:ty = $($inits:expr),+ ... $recur:expr ) => { 905 | { 906 | use std::ops::Index; 907 | 908 | const MEM_SIZE: usize = count_exprs!($($inits),+); 909 | // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ added 910 | 911 | struct Recurrence { 912 | mem: [$sty; MEM_SIZE], 913 | // ^~~~~~~~ changed 914 | pos: usize, 915 | } 916 | 917 | struct IndexOffset<'a> { 918 | slice: &'a [$sty; MEM_SIZE], 919 | // ^~~~~~~~ changed 920 | offset: usize, 921 | } 922 | 923 | impl<'a> Index for IndexOffset<'a> { 924 | type Output = $sty; 925 | 926 | #[inline(always)] 927 | fn index<'b>(&'b self, index: usize) -> &'b $sty { 928 | use std::num::Wrapping; 929 | 930 | let index = Wrapping(index); 931 | let offset = Wrapping(self.offset); 932 | let window = Wrapping(MEM_SIZE); 933 | // ^~~~~~~~ changed 934 | 935 | let real_index = index - offset + window; 936 | &self.slice[real_index.0] 937 | } 938 | } 939 | 940 | impl Iterator for Recurrence { 941 | type Item = $sty; 942 | 943 | #[inline] 944 | fn next(&mut self) -> Option<$sty> { 945 | if self.pos < MEM_SIZE { 946 | // ^~~~~~~~ changed 947 | let next_val = self.mem[self.pos]; 948 | self.pos += 1; 949 | Some(next_val) 950 | } else { 951 | let next_val = { 952 | let n = self.pos; 953 | let a = IndexOffset { slice: &self.mem, offset: n }; 954 | (a[n-1] + a[n-2]) 955 | }; 956 | 957 | { 958 | use std::mem::swap; 959 | 960 | let mut swap_tmp = next_val; 961 | for i in (0..MEM_SIZE).rev() { 962 | // ^~~~~~~~ changed 963 | swap(&mut swap_tmp, &mut self.mem[i]); 964 | } 965 | } 966 | 967 | self.pos += 1; 968 | Some(next_val) 969 | } 970 | } 971 | } 972 | 973 | Recurrence { mem: [$($inits),+], pos: 0 } 974 | } 975 | }; 976 | } 977 | /* ... */ 978 | # 979 | # fn main() { 980 | # let fib = recurrence![a[n]: u64 = 0, 1 ... a[n-1] + a[n-2]]; 981 | # 982 | # for e in fib.take(10) { println!("{}", e) } 983 | # } 984 | ``` 985 | 986 | 完成之后,我们开始替换最后的`recur`表达式。 987 | 988 | ```rust 989 | # macro_rules! count_exprs { 990 | # () => (0); 991 | # ($head:expr $(, $tail:expr)*) => (1 + count_exprs!($($tail),*)); 992 | # } 993 | # macro_rules! recurrence { 994 | # ( a[n]: $sty:ty = $($inits:expr),+ ... $recur:expr ) => { 995 | # { 996 | # const MEMORY: uint = count_exprs!($($inits),+); 997 | # struct Recurrence { 998 | # mem: [$sty; MEMORY], 999 | # pos: uint, 1000 | # } 1001 | # struct IndexOffset<'a> { 1002 | # slice: &'a [$sty; MEMORY], 1003 | # offset: uint, 1004 | # } 1005 | # impl<'a> Index for IndexOffset<'a> { 1006 | # #[inline(always)] 1007 | # fn index<'b>(&'b self, index: &uint) -> &'b $sty { 1008 | # let real_index = *index - self.offset + MEMORY; 1009 | # &self.slice[real_index] 1010 | # } 1011 | # } 1012 | # impl Iterator for Recurrence { 1013 | /* ... */ 1014 | #[inline] 1015 | fn next(&mut self) -> Option { 1016 | if self.pos < MEMORY { 1017 | let next_val = self.mem[self.pos]; 1018 | self.pos += 1; 1019 | Some(next_val) 1020 | } else { 1021 | let next_val = { 1022 | let n = self.pos; 1023 | let a = IndexOffset { slice: &self.mem, offset: n }; 1024 | $recur 1025 | // ^~~~~~ changed 1026 | }; 1027 | { 1028 | use std::mem::swap; 1029 | let mut swap_tmp = next_val; 1030 | for i in range(0, MEMORY).rev() { 1031 | swap(&mut swap_tmp, &mut self.mem[i]); 1032 | } 1033 | } 1034 | self.pos += 1; 1035 | Some(next_val) 1036 | } 1037 | } 1038 | /* ... */ 1039 | # } 1040 | # Recurrence { mem: [$($inits),+], pos: 0 } 1041 | # } 1042 | # }; 1043 | # } 1044 | # fn main() { 1045 | # let fib = recurrence![a[n]: u64 = 1, 1 ... a[n-1] + a[n-2]]; 1046 | # for e in fib.take(10) { println!("{}", e) } 1047 | # } 1048 | ``` 1049 | 1050 | 现在试图编译的话... 1051 | 1052 | ```text 1053 | recurrence.rs:77:48: 77:49 error: unresolved name `a` 1054 | recurrence.rs:77 let fib = recurrence![a[n]: u64 = 0, 1 ... a[n-1] + a[n-2]]; 1055 | ^ 1056 | recurrence.rs:7:1: 74:2 note: in expansion of recurrence! 1057 | recurrence.rs:77:15: 77:64 note: expansion site 1058 | recurrence.rs:77:50: 77:51 error: unresolved name `n` 1059 | recurrence.rs:77 let fib = recurrence![a[n]: u64 = 0, 1 ... a[n-1] + a[n-2]]; 1060 | ^ 1061 | recurrence.rs:7:1: 74:2 note: in expansion of recurrence! 1062 | recurrence.rs:77:15: 77:64 note: expansion site 1063 | recurrence.rs:77:57: 77:58 error: unresolved name `a` 1064 | recurrence.rs:77 let fib = recurrence![a[n]: u64 = 0, 1 ... a[n-1] + a[n-2]]; 1065 | ^ 1066 | recurrence.rs:7:1: 74:2 note: in expansion of recurrence! 1067 | recurrence.rs:77:15: 77:64 note: expansion site 1068 | recurrence.rs:77:59: 77:60 error: unresolved name `n` 1069 | recurrence.rs:77 let fib = recurrence![a[n]: u64 = 0, 1 ... a[n-1] + a[n-2]]; 1070 | ^ 1071 | recurrence.rs:7:1: 74:2 note: in expansion of recurrence! 1072 | recurrence.rs:77:15: 77:64 note: expansion site 1073 | ``` 1074 | 1075 | ...等等,什么情况?这没道理...让我们看看宏究竟展开成了什么样子。 1076 | 1077 | ```shell 1078 | $ rustc -Z unstable-options --pretty expanded recurrence.rs 1079 | ``` 1080 | 1081 | 参数`--pretty expanded`将促使`rustc`展开宏,并将输出的AST再重转为源代码。此选项当前被认定为是`unstable`,因此我们还要添加`-Z unstable-options`。输出的信息(经过整理格式后)如下;特别留意`$recur`被替换掉的位置: 1082 | 1083 | ```ignore 1084 | #![feature(no_std)] 1085 | #![no_std] 1086 | #[prelude_import] 1087 | use std::prelude::v1::*; 1088 | #[macro_use] 1089 | extern crate std as std; 1090 | fn main() { 1091 | let fib = { 1092 | use std::ops::Index; 1093 | const MEM_SIZE: usize = 1 + 1; 1094 | struct Recurrence { 1095 | mem: [u64; MEM_SIZE], 1096 | pos: usize, 1097 | } 1098 | struct IndexOffset<'a> { 1099 | slice: &'a [u64; MEM_SIZE], 1100 | offset: usize, 1101 | } 1102 | impl <'a> Index for IndexOffset<'a> { 1103 | type Output = u64; 1104 | #[inline(always)] 1105 | fn index<'b>(&'b self, index: usize) -> &'b u64 { 1106 | use std::num::Wrapping; 1107 | let index = Wrapping(index); 1108 | let offset = Wrapping(self.offset); 1109 | let window = Wrapping(MEM_SIZE); 1110 | let real_index = index - offset + window; 1111 | &self.slice[real_index.0] 1112 | } 1113 | } 1114 | impl Iterator for Recurrence { 1115 | type Item = u64; 1116 | #[inline] 1117 | fn next(&mut self) -> Option { 1118 | if self.pos < MEM_SIZE { 1119 | let next_val = self.mem[self.pos]; 1120 | self.pos += 1; 1121 | Some(next_val) 1122 | } else { 1123 | let next_val = { 1124 | let n = self.pos; 1125 | let a = IndexOffset{slice: &self.mem, offset: n,}; 1126 | a[n - 1] + a[n - 2] 1127 | }; 1128 | { 1129 | use std::mem::swap; 1130 | let mut swap_tmp = next_val; 1131 | { 1132 | let result = 1133 | match ::std::iter::IntoIterator::into_iter((0..MEM_SIZE).rev()) { 1134 | mut iter => loop { 1135 | match ::std::iter::Iterator::next(&mut iter) { 1136 | ::std::option::Option::Some(i) => { 1137 | swap(&mut swap_tmp, &mut self.mem[i]); 1138 | } 1139 | ::std::option::Option::None => break, 1140 | } 1141 | }, 1142 | }; 1143 | result 1144 | } 1145 | } 1146 | self.pos += 1; 1147 | Some(next_val) 1148 | } 1149 | } 1150 | } 1151 | Recurrence{mem: [0, 1], pos: 0,} 1152 | }; 1153 | { 1154 | let result = 1155 | match ::std::iter::IntoIterator::into_iter(fib.take(10)) { 1156 | mut iter => loop { 1157 | match ::std::iter::Iterator::next(&mut iter) { 1158 | ::std::option::Option::Some(e) => { 1159 | ::std::io::_print(::std::fmt::Arguments::new_v1( 1160 | { 1161 | static __STATIC_FMTSTR: &'static [&'static str] = &["", "\n"]; 1162 | __STATIC_FMTSTR 1163 | }, 1164 | &match (&e,) { 1165 | (__arg0,) => [::std::fmt::ArgumentV1::new(__arg0, ::std::fmt::Display::fmt)], 1166 | } 1167 | )) 1168 | } 1169 | ::std::option::Option::None => break, 1170 | } 1171 | }, 1172 | }; 1173 | result 1174 | } 1175 | } 1176 | ``` 1177 | 1178 | 呃..这看起来完全合法!如果我们加上几条`#![feature(...)]`属性,并把它送去给一个nightly版本的`rustc`,甚至真能通过编译...究竟什么情况?! 1179 | 1180 | > **附注**:上述代码无法通过非nightly版`rustc`编译。这是因为,`println!`宏的展开结果依赖于编译器内部的细节,这些细节尚未被公开稳定化。 1181 | 1182 | ### 保持卫生 1183 | 1184 | 这儿的问题在于,Rust宏中的标识符具有卫生性。这就是说,出自不同上下文的标识符不可能发生冲突。作为演示,举个简单的例子。 1185 | 1186 | ```rust 1187 | # /* 1188 | macro_rules! using_a { 1189 | ($e:expr) => { 1190 | { 1191 | let a = 42i; 1192 | $e 1193 | } 1194 | } 1195 | } 1196 | 1197 | let four = using_a!(a / 10); 1198 | # */ 1199 | # fn main() {} 1200 | ``` 1201 | 1202 | 此宏接受一个表达式,然后把它包进一个定义了变量`a`的区块里。我们随后用它绕个弯子来求`4`。这个例子中实际上存在2种句法上下文,但我们看不见它们。为了帮助说明,我们给每个上下文都上一种不同的颜色。我们从未展开的代码开始上色,此时仅看得见一种上下文: 1203 | 1204 |
macro_rules! using_a {
    ($e:expr) => {
        {
            let a = 42;
            $e
        }
    }
}

let four = using_a!(a / 10);
1205 | 1206 | 现在,展开宏调用。 1207 | 1208 |
let four = {
    let a = 42;
    a / 10
};
1209 | 1210 | 可以看到,在宏中定义的a与调用所提供的a处于不同的上下文中。因此,虽然它们的字母表示一致,编译器仍将它们视作完全不同的标识符。 1211 | 1212 | 宏的这一特性需要格外留意:它们可能会产出无法通过编译的AST;但同样的代码,手写或通过`--pretty expanded`转印出来则能够通过编译。 1213 | 1214 | 解决方案是,采用合适的句法上下文来捕获标识符。我们沿用上例,并作修改: 1215 | 1216 |
macro_rules! using_a {
    ($a:ident, $e:expr) => {
        {
            let $a = 42;
            $e
        }
    }
}

let four = using_a!(a, a / 10);
1217 | 1218 | 现在它将展开为: 1219 | 1220 |
let four = {
    let a = 42;
    a / 10
};
1221 | 1222 | 上下文现在匹配了,编译通过。我们的`recurrence!`宏也可被如此调整:显式地捕获`a`与`n`即可。调整后我们得到: 1223 | 1224 | ```rust 1225 | macro_rules! count_exprs { 1226 | () => (0); 1227 | ($head:expr) => (1); 1228 | ($head:expr, $($tail:expr),*) => (1 + count_exprs!($($tail),*)); 1229 | } 1230 | 1231 | macro_rules! recurrence { 1232 | ( $seq:ident [ $ind:ident ]: $sty:ty = $($inits:expr),+ ... $recur:expr ) => { 1233 | // ^~~~~~~~~~ ^~~~~~~~~~ changed 1234 | { 1235 | use std::ops::Index; 1236 | 1237 | const MEM_SIZE: usize = count_exprs!($($inits),+); 1238 | 1239 | struct Recurrence { 1240 | mem: [$sty; MEM_SIZE], 1241 | pos: usize, 1242 | } 1243 | 1244 | struct IndexOffset<'a> { 1245 | slice: &'a [$sty; MEM_SIZE], 1246 | offset: usize, 1247 | } 1248 | 1249 | impl<'a> Index for IndexOffset<'a> { 1250 | type Output = $sty; 1251 | 1252 | #[inline(always)] 1253 | fn index<'b>(&'b self, index: usize) -> &'b $sty { 1254 | use std::num::Wrapping; 1255 | 1256 | let index = Wrapping(index); 1257 | let offset = Wrapping(self.offset); 1258 | let window = Wrapping(MEM_SIZE); 1259 | 1260 | let real_index = index - offset + window; 1261 | &self.slice[real_index.0] 1262 | } 1263 | } 1264 | 1265 | impl Iterator for Recurrence { 1266 | type Item = $sty; 1267 | 1268 | #[inline] 1269 | fn next(&mut self) -> Option<$sty> { 1270 | if self.pos < MEM_SIZE { 1271 | let next_val = self.mem[self.pos]; 1272 | self.pos += 1; 1273 | Some(next_val) 1274 | } else { 1275 | let next_val = { 1276 | let $ind = self.pos; 1277 | // ^~~~ changed 1278 | let $seq = IndexOffset { slice: &self.mem, offset: $ind }; 1279 | // ^~~~ changed 1280 | $recur 1281 | }; 1282 | 1283 | { 1284 | use std::mem::swap; 1285 | 1286 | let mut swap_tmp = next_val; 1287 | for i in (0..MEM_SIZE).rev() { 1288 | swap(&mut swap_tmp, &mut self.mem[i]); 1289 | } 1290 | } 1291 | 1292 | self.pos += 1; 1293 | Some(next_val) 1294 | } 1295 | } 1296 | } 1297 | 1298 | Recurrence { mem: [$($inits),+], pos: 0 } 1299 | } 1300 | }; 1301 | } 1302 | 1303 | fn main() { 1304 | let fib = recurrence![a[n]: u64 = 0, 1 ... a[n-1] + a[n-2]]; 1305 | 1306 | for e in fib.take(10) { println!("{}", e) } 1307 | } 1308 | ``` 1309 | 1310 | 通过编译了!接下来,我们试试别的数列。 1311 | 1312 | ```rust 1313 | # macro_rules! count_exprs { 1314 | # () => (0); 1315 | # ($head:expr) => (1); 1316 | # ($head:expr, $($tail:expr),*) => (1 + count_exprs!($($tail),*)); 1317 | # } 1318 | # 1319 | # macro_rules! recurrence { 1320 | # ( $seq:ident [ $ind:ident ]: $sty:ty = $($inits:expr),+ ... $recur:expr ) => { 1321 | # { 1322 | # use std::ops::Index; 1323 | # 1324 | # const MEM_SIZE: usize = count_exprs!($($inits),+); 1325 | # 1326 | # struct Recurrence { 1327 | # mem: [$sty; MEM_SIZE], 1328 | # pos: usize, 1329 | # } 1330 | # 1331 | # struct IndexOffset<'a> { 1332 | # slice: &'a [$sty; MEM_SIZE], 1333 | # offset: usize, 1334 | # } 1335 | # 1336 | # impl<'a> Index for IndexOffset<'a> { 1337 | # type Output = $sty; 1338 | # 1339 | # #[inline(always)] 1340 | # fn index<'b>(&'b self, index: usize) -> &'b $sty { 1341 | # use std::num::Wrapping; 1342 | # 1343 | # let index = Wrapping(index); 1344 | # let offset = Wrapping(self.offset); 1345 | # let window = Wrapping(MEM_SIZE); 1346 | # 1347 | # let real_index = index - offset + window; 1348 | # &self.slice[real_index.0] 1349 | # } 1350 | # } 1351 | # 1352 | # impl Iterator for Recurrence { 1353 | # type Item = $sty; 1354 | # 1355 | # #[inline] 1356 | # fn next(&mut self) -> Option<$sty> { 1357 | # if self.pos < MEM_SIZE { 1358 | # let next_val = self.mem[self.pos]; 1359 | # self.pos += 1; 1360 | # Some(next_val) 1361 | # } else { 1362 | # let next_val = { 1363 | # let $ind = self.pos; 1364 | # let $seq = IndexOffset { slice: &self.mem, offset: $ind }; 1365 | # $recur 1366 | # }; 1367 | # 1368 | # { 1369 | # use std::mem::swap; 1370 | # 1371 | # let mut swap_tmp = next_val; 1372 | # for i in (0..MEM_SIZE).rev() { 1373 | # swap(&mut swap_tmp, &mut self.mem[i]); 1374 | # } 1375 | # } 1376 | # 1377 | # self.pos += 1; 1378 | # Some(next_val) 1379 | # } 1380 | # } 1381 | # } 1382 | # 1383 | # Recurrence { mem: [$($inits),+], pos: 0 } 1384 | # } 1385 | # }; 1386 | # } 1387 | # 1388 | # fn main() { 1389 | for e in recurrence!(f[i]: f64 = 1.0 ... f[i-1] * i as f64).take(10) { 1390 | println!("{}", e) 1391 | } 1392 | # } 1393 | ``` 1394 | 1395 | 运行上述代码得到: 1396 | 1397 | ```text 1398 | 1 1399 | 1 1400 | 2 1401 | 6 1402 | 24 1403 | 120 1404 | 720 1405 | 5040 1406 | 40320 1407 | 362880 1408 | ``` 1409 | 1410 | 成功了! 1411 | --------------------------------------------------------------------------------