1 /** 2 Pattern based URL router for HTTP request. 3 4 See `URLRouter` for more details. 5 6 Copyright: © 2012-2015 RejectedSoftware e.K. 7 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 8 Authors: Sönke Ludwig 9 */ 10 module vibe.http.router; 11 12 public import vibe.http.server; 13 14 import vibe.core.log; 15 16 import std.functional; 17 18 19 /** 20 Routes HTTP requests based on the request method and URL. 21 22 Routes are matched using a special URL match string that supports two forms 23 of placeholders. See the sections below for more details. 24 25 Registered routes are matched according to the same sequence as initially 26 specified using `match`, `get`, `post` etc. Matching ends as soon as a route 27 handler writes a response using `res.writeBody()` or similar means. If no 28 route matches or if no route handler writes a response, the router will 29 simply not handle the request and the HTTP server will automatically 30 generate a 404 error. 31 32 Match_patterns: 33 Match patterns are character sequences that can optionally contain 34 placeholders or raw wildcards ("*"). Raw wild cards match any character 35 sequence, while placeholders match only sequences containing no slash 36 ("/") characters. 37 38 Placeholders are started using a colon (":") and are directly followed 39 by their name. The first "/" character (or the end of the match string) 40 denotes the end of the placeholder name. The part of the string that 41 matches a placeholder will be stored in the `HTTPServerRequest.params` 42 map using the placeholder name as the key. 43 44 Match strings are subject to the following rules: 45 $(UL 46 $(LI A raw wildcard ("*") may only occur at the end of the match string) 47 $(LI At least one character must be placed between any two placeholders or wildcards) 48 $(LI The maximum allowed number of placeholders in a single match string is 64) 49 ) 50 51 Match_String_Examples: 52 $(UL 53 $(LI `"/foo/bar"` matches only `"/foo/bar"` itself) 54 $(LI `"/foo/*"` matches `"/foo/"`, `"/foo/bar"`, `"/foo/bar/baz"` or _any other string beginning with `"/foo/"`) 55 $(LI `"/:x/"` matches `"/foo/"`, `"/bar/"` and similar strings (and stores `"foo"`/`"bar"` in `req.params["x"]`), but not `"/foo/bar/"`) 56 $(LI Matching partial path entries with wildcards is possible: `"/foo:x"` matches `"/foo"`, `"/foobar"`, but not `"/foo/bar"`) 57 $(LI Multiple placeholders and raw wildcards can be combined: `"/:x/:y/*"`) 58 ) 59 */ 60 final class URLRouter : HTTPServerRequestHandler { 61 @safe: 62 63 private { 64 MatchTree!Route m_routes; 65 string m_prefix; 66 bool m_computeBasePath; 67 } 68 69 this(string prefix = null) 70 { 71 m_prefix = prefix; 72 } 73 74 /** Sets a common prefix for all registered routes. 75 76 All routes will implicitly have this prefix prepended before being 77 matched against incoming requests. 78 */ 79 @property string prefix() const { return m_prefix; } 80 81 /** Controls the computation of the "routerRootDir" parameter. 82 83 This parameter is available as `req.params["routerRootDir"]` and 84 contains the relative path to the base path of the router. The base 85 path is determined by the `prefix` property. 86 87 Note that this feature currently is requires dynamic memory allocations 88 and is opt-in for this reason. 89 */ 90 @property void enableRootDir(bool enable) { m_computeBasePath = enable; } 91 92 /// Returns a single route handle to conveniently register multiple methods. 93 URLRoute route(string path) 94 in { assert(path.length, "Cannot register null or empty path!"); } 95 body { return URLRoute(this, path); } 96 97 /// 98 unittest { 99 void getFoo(scope HTTPServerRequest req, scope HTTPServerResponse res) { /* ... */ } 100 void postFoo(scope HTTPServerRequest req, scope HTTPServerResponse res) { /* ... */ } 101 void deleteFoo(scope HTTPServerRequest req, scope HTTPServerResponse res) { /* ... */ } 102 103 auto r = new URLRouter; 104 105 // using 'with' statement 106 with (r.route("/foo")) { 107 get(&getFoo); 108 post(&postFoo); 109 delete_(&deleteFoo); 110 } 111 112 // using method chaining 113 r.route("/foo") 114 .get(&getFoo) 115 .post(&postFoo) 116 .delete_(&deleteFoo); 117 118 // without using route() 119 r.get("/foo", &getFoo); 120 r.post("/foo", &postFoo); 121 r.delete_("/foo", &deleteFoo); 122 } 123 124 /// Adds a new route for requests matching the specified HTTP method and pattern. 125 URLRouter match(Handler)(HTTPMethod method, string path, Handler handler) 126 if (isValidHandler!Handler) 127 { 128 import std.algorithm; 129 assert(path.length, "Cannot register null or empty path!"); 130 assert(count(path, ':') <= maxRouteParameters, "Too many route parameters"); 131 logDebug("add route %s %s", method, path); 132 m_routes.addTerminal(path, Route(method, path, handlerDelegate(handler))); 133 return this; 134 } 135 136 /// ditto 137 URLRouter match(HTTPMethod method, string path, HTTPServerRequestDelegate handler) 138 { 139 return match!HTTPServerRequestDelegate(method, path, handler); 140 } 141 142 /// Adds a new route for GET requests matching the specified pattern. 143 URLRouter get(Handler)(string url_match, Handler handler) if (isValidHandler!Handler) { return match(HTTPMethod.GET, url_match, handler); } 144 /// ditto 145 URLRouter get(string url_match, HTTPServerRequestDelegate handler) { return match(HTTPMethod.GET, url_match, handler); } 146 147 /// Adds a new route for POST requests matching the specified pattern. 148 URLRouter post(Handler)(string url_match, Handler handler) if (isValidHandler!Handler) { return match(HTTPMethod.POST, url_match, handler); } 149 /// ditto 150 URLRouter post(string url_match, HTTPServerRequestDelegate handler) { return match(HTTPMethod.POST, url_match, handler); } 151 152 /// Adds a new route for PUT requests matching the specified pattern. 153 URLRouter put(Handler)(string url_match, Handler handler) if (isValidHandler!Handler) { return match(HTTPMethod.PUT, url_match, handler); } 154 /// ditto 155 URLRouter put(string url_match, HTTPServerRequestDelegate handler) { return match(HTTPMethod.PUT, url_match, handler); } 156 157 /// Adds a new route for DELETE requests matching the specified pattern. 158 URLRouter delete_(Handler)(string url_match, Handler handler) if (isValidHandler!Handler) { return match(HTTPMethod.DELETE, url_match, handler); } 159 /// ditto 160 URLRouter delete_(string url_match, HTTPServerRequestDelegate handler) { return match(HTTPMethod.DELETE, url_match, handler); } 161 162 /// Adds a new route for PATCH requests matching the specified pattern. 163 URLRouter patch(Handler)(string url_match, Handler handler) if (isValidHandler!Handler) { return match(HTTPMethod.PATCH, url_match, handler); } 164 /// ditto 165 URLRouter patch(string url_match, HTTPServerRequestDelegate handler) { return match(HTTPMethod.PATCH, url_match, handler); } 166 167 /// Adds a new route for requests matching the specified pattern, regardless of their HTTP verb. 168 URLRouter any(Handler)(string url_match, Handler handler) 169 { 170 import std.traits; 171 static HTTPMethod[] all_methods = [EnumMembers!HTTPMethod]; 172 foreach(immutable method; all_methods) 173 match(method, url_match, handler); 174 175 return this; 176 } 177 /// ditto 178 URLRouter any(string url_match, HTTPServerRequestDelegate handler) { return any!HTTPServerRequestDelegate(url_match, handler); } 179 180 181 /** Rebuilds the internal matching structures to account for newly added routes. 182 183 This should be used after a lot of routes have been added to the router, to 184 force eager computation of the match structures. The alternative is to 185 let the router lazily compute the structures when the first request happens, 186 which can delay this request. 187 */ 188 void rebuild() 189 { 190 m_routes.rebuildGraph(); 191 } 192 193 /// Handles a HTTP request by dispatching it to the registered route handlers. 194 void handleRequest(HTTPServerRequest req, HTTPServerResponse res) 195 { 196 auto method = req.method; 197 198 string calcBasePath() 199 @safe { 200 import vibe.core.path : InetPath, relativeToWeb; 201 auto p = InetPath(prefix.length ? prefix : "/"); 202 p.endsWithSlash = true; 203 return p.relativeToWeb(InetPath(req.path)).toString(); 204 } 205 206 auto path = req.path; 207 if (path.length < m_prefix.length || path[0 .. m_prefix.length] != m_prefix) return; 208 path = path[m_prefix.length .. $]; 209 210 while (true) { 211 bool done = m_routes.match(path, (ridx, scope values) @safe { 212 auto r = () @trusted { return &m_routes.getTerminalData(ridx); } (); 213 if (r.method != method) return false; 214 215 logDebugV("route match: %s -> %s %s %s", req.path, r.method, r.pattern, values); 216 foreach (i, v; values) req.params[m_routes.getTerminalVarNames(ridx)[i]] = v; 217 if (m_computeBasePath) req.params["routerRootDir"] = calcBasePath(); 218 r.cb(req, res); 219 return res.headerWritten; 220 }); 221 if (done) return; 222 223 if (method == HTTPMethod.HEAD) method = HTTPMethod.GET; 224 else break; 225 } 226 227 logDebug("no route match: %s %s", req.method, req.requestURL); 228 } 229 230 /// Returns all registered routes as const AA 231 const(Route)[] getAllRoutes() 232 { 233 auto routes = new Route[m_routes.terminalCount]; 234 foreach (i, ref r; routes) 235 r = m_routes.getTerminalData(i); 236 return routes; 237 } 238 239 template isValidHandler(Handler) { 240 @system { 241 alias USDel = void delegate(HTTPServerRequest, HTTPServerResponse) @system; 242 alias USFun = void function(HTTPServerRequest, HTTPServerResponse) @system; 243 alias USDelS = void delegate(scope HTTPServerRequest, scope HTTPServerResponse) @system; 244 alias USFunS = void function(scope HTTPServerRequest, scope HTTPServerResponse) @system; 245 } 246 247 static if ( 248 is(Handler : HTTPServerRequestDelegate) || 249 is(Handler : HTTPServerRequestFunction) || 250 is(Handler : HTTPServerRequestHandler) 251 //is(Handler : HTTPServerRequestDelegateS) || 252 //is(Handler : HTTPServerRequestFunctionS) || 253 //is(Handler : HTTPServerRequestHandlerS) 254 ) 255 { 256 enum isValidHandler = true; 257 } else static if ( 258 is(Handler : USDel) || is(Handler : USFun) || 259 is(Handler : USDelS) || is(Handler : USFunS) 260 ) 261 { 262 enum isValidHandler = true; 263 } else { 264 enum isValidHandler = false; 265 } 266 } 267 268 static void delegate(HTTPServerRequest, HTTPServerResponse) @safe handlerDelegate(Handler)(Handler handler) 269 { 270 import std.traits : isFunctionPointer; 271 static if (isFunctionPointer!Handler) return handlerDelegate(() @trusted { return toDelegate(handler); } ()); 272 else static if (is(Handler == class) || is(Handler == interface)) return &handler.handleRequest; 273 else static if (__traits(compiles, () @safe { handler(HTTPServerRequest.init, HTTPServerResponse.init); } ())) return handler; 274 else return (req, res) @trusted { handler(req, res); }; 275 } 276 277 unittest { 278 static assert(isValidHandler!HTTPServerRequestFunction); 279 static assert(isValidHandler!HTTPServerRequestDelegate); 280 static assert(isValidHandler!HTTPServerRequestHandler); 281 //static assert(isValidHandler!HTTPServerRequestFunctionS); 282 //static assert(isValidHandler!HTTPServerRequestDelegateS); 283 //static assert(isValidHandler!HTTPServerRequestHandlerS); 284 static assert(isValidHandler!(void delegate(HTTPServerRequest req, HTTPServerResponse res) @system)); 285 static assert(isValidHandler!(void function(HTTPServerRequest req, HTTPServerResponse res) @system)); 286 static assert(isValidHandler!(void delegate(scope HTTPServerRequest req, scope HTTPServerResponse res) @system)); 287 static assert(isValidHandler!(void function(scope HTTPServerRequest req, scope HTTPServerResponse res) @system)); 288 static assert(!isValidHandler!(int delegate(HTTPServerRequest req, HTTPServerResponse res) @system)); 289 static assert(!isValidHandler!(int delegate(HTTPServerRequest req, HTTPServerResponse res) @safe)); 290 void test(H)(H h) 291 { 292 static assert(isValidHandler!H); 293 } 294 test((HTTPServerRequest req, HTTPServerResponse res) {}); 295 } 296 } 297 298 /// 299 //@safe unittest { 300 //import vibe.http.fileserver; 301 302 //void addGroup(HTTPServerRequest req, HTTPServerResponse res) 303 //{ 304 //// Route variables are accessible via the params map 305 //logInfo("Getting group %s for user %s.", req.params["groupname"], req.params["username"]); 306 //} 307 308 //void deleteUser(HTTPServerRequest req, HTTPServerResponse res) 309 //{ 310 //// ... 311 //} 312 313 //void auth(HTTPServerRequest req, HTTPServerResponse res) 314 //{ 315 //// TODO: check req.session to see if a user is logged in and 316 //// write an error page or throw an exception instead. 317 //} 318 319 //void setup() 320 //{ 321 //auto router = new URLRouter; 322 //// Matches all GET requests for /users/groups/* and places 323 //// the place holders in req.params as 'username' and 'groupname'. 324 //router.get("/users/:username/groups/:groupname", &addGroup); 325 326 //// Matches all requests. This can be useful for authorization and 327 //// similar tasks. The auth method will only write a response if the 328 //// user is _not_ authorized. Otherwise, the router will fall through 329 //// and continue with the following routes. 330 //router.any("*", &auth); 331 332 //// Matches a POST request 333 //router.post("/users/:username/delete", &deleteUser); 334 335 //// Matches all GET requests in /static/ such as /static/img.png or 336 //// /static/styles/sty.css 337 //router.get("/static/*", serveStaticFiles("public/")); 338 339 //// Setup a HTTP server... 340 //auto settings = new HTTPServerSettings; 341 //// ... 342 343 //// The router can be directly passed to the listenHTTP function as 344 //// the main request handler. 345 //listenHTTP(settings, router); 346 //} 347 //} 348 349 /** Using nested routers to map components to different sub paths. A component 350 could for example be an embedded blog engine. 351 */ 352 @safe unittest { 353 // some embedded component: 354 355 void showComponentHome(HTTPServerRequest req, HTTPServerResponse res) 356 { 357 // ... 358 } 359 360 void showComponentUser(HTTPServerRequest req, HTTPServerResponse res) 361 { 362 // ... 363 } 364 365 void registerComponent(URLRouter router) 366 { 367 router.get("/", &showComponentHome); 368 router.get("/users/:user", &showComponentUser); 369 } 370 371 // main application: 372 373 void showHome(HTTPServerRequest req, HTTPServerResponse res) 374 { 375 // ... 376 } 377 378 void setup() 379 { 380 auto c1router = new URLRouter("/component1"); 381 registerComponent(c1router); 382 383 auto mainrouter = new URLRouter; 384 mainrouter.get("/", &showHome); 385 // forward all unprocessed requests to the component router 386 mainrouter.any("*", c1router); 387 388 // now the following routes will be matched: 389 // / -> showHome 390 // /component1/ -> showComponentHome 391 // /component1/users/:user -> showComponentUser 392 393 // Start the HTTP server 394 //auto settings = HTTPServerSettings; 395 HTTPServerSettings settings; 396 // ... 397 //listenHTTP(settings, mainrouter); 398 399 listenHTTP!mainrouter(settings); 400 } 401 } 402 403 @safe unittest { // issue #1668 404 auto r = new URLRouter; 405 r.get("/", (req, res) { 406 if ("foo" in req.headers) 407 res.writeBody("bar"); 408 }); 409 410 r.get("/", (scope req, scope res) { 411 if ("foo" in req.headers) 412 res.writeBody("bar"); 413 }); 414 r.get("/", (req, res) {}); 415 r.post("/", (req, res) {}); 416 r.put("/", (req, res) {}); 417 r.delete_("/", (req, res) {}); 418 r.patch("/", (req, res) {}); 419 r.any("/", (req, res) {}); 420 } 421 422 @safe unittest { // issue #1866 423 auto r = new URLRouter; 424 r.match(HTTPMethod.HEAD, "/foo", (scope req, scope res) { res.writeVoidBody; }); 425 r.match(HTTPMethod.HEAD, "/foo", (req, res) { res.writeVoidBody; }); 426 r.match(HTTPMethod.HEAD, "/foo", (scope HTTPServerRequest req, scope HTTPServerResponse res) { res.writeVoidBody; }); 427 r.match(HTTPMethod.HEAD, "/foo", (HTTPServerRequest req, HTTPServerResponse res) { res.writeVoidBody; }); 428 429 auto r2 = new URLRouter; 430 r.match(HTTPMethod.HEAD, "/foo", r2); 431 } 432 433 @safe unittest { 434 import vibe.inet.url; 435 436 auto router = new URLRouter; 437 string result; 438 void a(HTTPServerRequest req, HTTPServerResponse) { result ~= "A"; } 439 void b(HTTPServerRequest req, HTTPServerResponse) { result ~= "B"; } 440 void c(HTTPServerRequest req, HTTPServerResponse) { assert(req.params["test"] == "x", "Wrong variable contents: "~req.params["test"]); result ~= "C"; } 441 void d(HTTPServerRequest req, HTTPServerResponse) { assert(req.params["test"] == "y", "Wrong variable contents: "~req.params["test"]); result ~= "D"; } 442 router.get("/test", &a); 443 router.post("/test", &b); 444 router.get("/a/:test", &c); 445 router.get("/a/:test/", &d); 446 447 auto res = createTestHTTPServerResponse(); 448 router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/")), res); 449 assert(result == "", "Matched for non-existent / path"); 450 router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/test")), res); 451 assert(result == "A", "Didn't match a simple GET request"); 452 router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/test"), HTTPMethod.POST), res); 453 assert(result == "AB", "Didn't match a simple POST request"); 454 router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/a/"), HTTPMethod.GET), res); 455 assert(result == "AB", "Matched empty variable. "~result); 456 router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/a/x"), HTTPMethod.GET), res); 457 assert(result == "ABC", "Didn't match a trailing 1-character var."); 458 // currently fails due to Path not accepting "//" 459 //router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/a//"), HTTPMethod.GET), res); 460 //assert(result == "ABC", "Matched empty string or slash as var. "~result); 461 router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/a/y/"), HTTPMethod.GET), res); 462 assert(result == "ABCD", "Didn't match 1-character infix variable."); 463 } 464 465 @safe unittest { 466 import vibe.inet.url; 467 468 auto router = new URLRouter("/test"); 469 470 string result; 471 void a(HTTPServerRequest req, HTTPServerResponse) { result ~= "A"; } 472 void b(HTTPServerRequest req, HTTPServerResponse) { result ~= "B"; } 473 router.get("/x", &a); 474 router.get("/y", &b); 475 476 auto res = createTestHTTPServerResponse(); 477 router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/test")), res); 478 assert(result == ""); 479 router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/test/x")), res); 480 assert(result == "A"); 481 router.handleRequest(createTestHTTPServerRequest(URL("http://localhost/test/y")), res); 482 assert(result == "AB"); 483 } 484 485 @safe unittest { 486 string ensureMatch(string pattern, string local_uri, string[string] expected_params = null) 487 { 488 import vibe.inet.url : URL; 489 string ret = local_uri ~ " did not match " ~ pattern; 490 auto r = new URLRouter; 491 r.get(pattern, (req, res) { 492 ret = null; 493 foreach (k, v; expected_params) { 494 if (k !in req.params) { ret = "Parameter "~k~" was not matched."; return; } 495 if (req.params[k] != v) { ret = "Parameter "~k~" is '"~req.params[k]~"' instead of '"~v~"'."; return; } 496 } 497 }); 498 auto req = createTestHTTPServerRequest(URL("http://localhost"~local_uri)); 499 auto res = createTestHTTPServerResponse(); 500 r.handleRequest(req, res); 501 return ret; 502 } 503 504 assert(ensureMatch("/foo bar/", "/foo%20bar/") is null); // normalized pattern: "/foo%20bar/" 505 //assert(ensureMatch("/foo%20bar/", "/foo%20bar/") is null); // normalized pattern: "/foo%20bar/" 506 assert(ensureMatch("/foo/bar/", "/foo/bar/") is null); // normalized pattern: "/foo/bar/" 507 //assert(ensureMatch("/foo/bar/", "/foo%2fbar/") !is null); 508 //assert(ensureMatch("/foo%2fbar/", "/foo%2fbar/") is null); // normalized pattern: "/foo%2Fbar/" 509 //assert(ensureMatch("/foo%2Fbar/", "/foo%2fbar/") is null); // normalized pattern: "/foo%2Fbar/" 510 //assert(ensureMatch("/foo%2fbar/", "/foo%2Fbar/") is null); 511 //assert(ensureMatch("/foo%2fbar/", "/foo/bar/") !is null); 512 //assert(ensureMatch("/:foo/", "/foo%2Fbar/", ["foo": "foo/bar"]) is null); 513 assert(ensureMatch("/:foo/", "/foo/bar/") !is null); 514 } 515 516 517 /** 518 Convenience abstraction for a single `URLRouter` route. 519 520 See `URLRouter.route` for a usage example. 521 */ 522 struct URLRoute { 523 @safe: 524 525 URLRouter router; 526 string path; 527 528 ref URLRoute get(Handler)(Handler h) { router.get(path, h); return this; } 529 ref URLRoute post(Handler)(Handler h) { router.post(path, h); return this; } 530 ref URLRoute put(Handler)(Handler h) { router.put(path, h); return this; } 531 ref URLRoute delete_(Handler)(Handler h) { router.delete_(path, h); return this; } 532 ref URLRoute patch(Handler)(Handler h) { router.patch(path, h); return this; } 533 ref URLRoute any(Handler)(Handler h) { router.any(path, h); return this; } 534 ref URLRoute match(Handler)(HTTPMethod method, Handler h) { router.match(method, path, h); return this; } 535 } 536 537 538 private enum maxRouteParameters = 64; 539 540 private struct Route { 541 HTTPMethod method; 542 string pattern; 543 HTTPServerRequestDelegate cb; 544 } 545 546 private string skipPathNode(string str, ref size_t idx) 547 @safe { 548 size_t start = idx; 549 while( idx < str.length && str[idx] != '/' ) idx++; 550 return str[start .. idx]; 551 } 552 553 private string skipPathNode(ref string str) 554 @safe { 555 size_t idx = 0; 556 auto ret = skipPathNode(str, idx); 557 str = str[idx .. $]; 558 return ret; 559 } 560 561 private struct MatchTree(T) { 562 @safe: 563 564 import std.algorithm : countUntil; 565 import std.array : array; 566 567 private { 568 alias NodeIndex = uint; 569 alias TerminalTagIndex = uint; 570 alias TerminalIndex = ushort; 571 alias VarIndex = ushort; 572 573 struct Node { 574 TerminalTagIndex terminalsStart; // slice into m_terminalTags 575 TerminalTagIndex terminalsEnd; 576 NodeIndex[ubyte.max+1] edges = NodeIndex.max; // character -> index into m_nodes 577 } 578 struct TerminalTag { 579 TerminalIndex index; // index into m_terminals array 580 VarIndex var = VarIndex.max; // index into Terminal.varNames/varValues or VarIndex.max 581 } 582 struct Terminal { 583 string pattern; 584 T data; 585 string[] varNames; 586 VarIndex[NodeIndex] varMap; 587 } 588 Node[] m_nodes; // all nodes as a single array 589 TerminalTag[] m_terminalTags; 590 Terminal[] m_terminals; 591 592 enum TerminalChar = 0; 593 bool m_upToDate = false; 594 } 595 596 @property size_t terminalCount() const { return m_terminals.length; } 597 598 void addTerminal(string pattern, T data) 599 { 600 assert(m_terminals.length < TerminalIndex.max, "Attempt to register too many routes."); 601 m_terminals ~= Terminal(pattern, data, null, null); 602 m_upToDate = false; 603 } 604 605 bool match(string text, scope bool delegate(size_t terminal, scope string[] vars) @safe del) 606 { 607 // lazily update the match graph 608 if (!m_upToDate) rebuildGraph(); 609 610 return doMatch(text, del); 611 } 612 613 const(string)[] getTerminalVarNames(size_t terminal) const { return m_terminals[terminal].varNames; } 614 ref inout(T) getTerminalData(size_t terminal) inout { return m_terminals[terminal].data; } 615 616 void print() 617 const { 618 import std.algorithm : map; 619 import std.array : join; 620 import std.conv : to; 621 import std.range : iota; 622 import std..string : format; 623 624 logInfo("Nodes:"); 625 foreach (i, n; m_nodes) { 626 logInfo(" %s %s", i, m_terminalTags[n.terminalsStart .. n.terminalsEnd] 627 .map!(t => format("T%s%s", t.index, t.var != VarIndex.max ? "("~m_terminals[t.index].varNames[t.var]~")" : "")).join(" ")); 628 //logInfo(" %s %s-%s", i, n.terminalsStart, n.terminalsEnd); 629 630 631 static string mapChar(ubyte ch) { 632 if (ch == TerminalChar) return "$"; 633 if (ch >= '0' && ch <= '9') return to!string(cast(dchar)ch); 634 if (ch >= 'a' && ch <= 'z') return to!string(cast(dchar)ch); 635 if (ch >= 'A' && ch <= 'Z') return to!string(cast(dchar)ch); 636 if (ch == '/') return "/"; 637 if (ch == '^') return "^"; 638 return ch.to!string; 639 } 640 641 void printRange(uint node, ubyte from, ubyte to) 642 { 643 if (to - from <= 10) logInfo(" %s -> %s", iota(from, cast(uint)to+1).map!(ch => mapChar(cast(ubyte)ch)).join("|"), node); 644 else logInfo(" %s-%s -> %s", mapChar(from), mapChar(to), node); 645 } 646 647 auto last_to = NodeIndex.max; 648 ubyte last_ch = 0; 649 foreach (ch, e; n.edges) 650 if (e != last_to) { 651 if (last_to != NodeIndex.max) 652 printRange(last_to, last_ch, cast(ubyte)(ch-1)); 653 last_ch = cast(ubyte)ch; 654 last_to = e; 655 } 656 if (last_to != NodeIndex.max) 657 printRange(last_to, last_ch, ubyte.max); 658 } 659 } 660 661 private bool doMatch(string text, scope bool delegate(size_t terminal, scope string[] vars) @safe del) 662 const { 663 string[maxRouteParameters] vars_buf;// = void; 664 665 import std.algorithm : canFind; 666 667 // first, determine the end node, if any 668 auto n = matchTerminals(text); 669 if (!n) return false; 670 671 // then, go through the terminals and match their variables 672 foreach (ref t; m_terminalTags[n.terminalsStart .. n.terminalsEnd]) { 673 auto term = &m_terminals[t.index]; 674 auto vars = vars_buf[0 .. term.varNames.length]; 675 matchVars(vars, term, text); 676 if (vars.canFind!(v => v.length == 0)) continue; // all variables must be non-empty to match 677 if (del(t.index, vars)) return true; 678 } 679 return false; 680 } 681 682 private inout(Node)* matchTerminals(string text) 683 inout { 684 if (!m_nodes.length) return null; 685 686 auto n = &m_nodes[0]; 687 688 // follow the path through the match graph 689 foreach (i, char ch; text) { 690 auto nidx = n.edges[cast(size_t)ch]; 691 if (nidx == NodeIndex.max) return null; 692 n = &m_nodes[nidx]; 693 } 694 695 // finally, find a matching terminal node 696 auto nidx = n.edges[TerminalChar]; 697 if (nidx == NodeIndex.max) return null; 698 n = &m_nodes[nidx]; 699 return n; 700 } 701 702 private void matchVars(string[] dst, in Terminal* term, string text) 703 const { 704 NodeIndex nidx = 0; 705 VarIndex activevar = VarIndex.max; 706 size_t activevarstart; 707 708 dst[] = null; 709 710 // folow the path throgh the match graph 711 foreach (i, char ch; text) { 712 auto var = term.varMap.get(nidx, VarIndex.max); 713 714 // detect end of variable 715 if (var != activevar && activevar != VarIndex.max) { 716 dst[activevar] = text[activevarstart .. i-1]; 717 activevar = VarIndex.max; 718 } 719 720 // detect beginning of variable 721 if (var != VarIndex.max && activevar == VarIndex.max) { 722 activevar = var; 723 activevarstart = i; 724 } 725 726 nidx = m_nodes[nidx].edges[cast(ubyte)ch]; 727 assert(nidx != NodeIndex.max); 728 } 729 730 // terminate any active varible with the end of the input string or with the last character 731 auto var = term.varMap.get(nidx, VarIndex.max); 732 if (activevar != VarIndex.max) dst[activevar] = text[activevarstart .. (var == activevar ? $ : $-1)]; 733 } 734 735 private void rebuildGraph() 736 @trusted { 737 import std.array : appender; 738 import std.conv : to; 739 740 if (m_upToDate) return; 741 m_upToDate = true; 742 743 m_nodes = null; 744 m_terminalTags = null; 745 746 if (!m_terminals.length) return; 747 748 MatchGraphBuilder builder; 749 foreach (i, ref t; m_terminals) { 750 t.varNames = builder.insert(t.pattern, i.to!TerminalIndex); 751 assert(t.varNames.length <= VarIndex.max, "Too many variables in route."); 752 } 753 //builder.print(); 754 builder.disambiguate(); 755 756 auto nodemap = new NodeIndex[builder.m_nodes.length]; 757 nodemap[] = NodeIndex.max; 758 759 auto nodes = appender!(Node[]); 760 nodes.reserve(1024); 761 auto termtags = appender!(TerminalTag[]); 762 termtags.reserve(1024); 763 764 NodeIndex process(NodeIndex n) 765 { 766 import std.algorithm : canFind; 767 768 if (nodemap[n] != NodeIndex.max) return nodemap[n]; 769 auto nmidx = cast(NodeIndex)nodes.data.length; 770 nodemap[n] = nmidx; 771 nodes.put(Node.init); 772 773 Node nn; 774 nn.terminalsStart = termtags.data.length.to!TerminalTagIndex; 775 foreach (t; builder.m_nodes[n].terminals) { 776 auto var = cast(VarIndex)t.var; 777 assert(!termtags.data[nn.terminalsStart .. $].canFind!(u => u.index == t.index && u.var == var)); 778 termtags ~= TerminalTag(cast(TerminalIndex)t.index, var); 779 if (var != VarIndex.max) 780 m_terminals[t.index].varMap[nmidx] = var; 781 } 782 nn.terminalsEnd = termtags.data.length.to!TerminalTagIndex; 783 foreach (ch, targets; builder.m_nodes[n].edges) 784 foreach (to; builder.m_edgeEntries.getItems(targets)) 785 nn.edges[ch] = process(to); 786 787 nodes.data[nmidx] = nn; 788 789 return nmidx; 790 } 791 assert(builder.m_edgeEntries.hasLength(builder.m_nodes[0].edges['^'], 1), 792 "Graph must be disambiguated before purging."); 793 process(builder.m_edgeEntries.getItems(builder.m_nodes[0].edges['^']).front); 794 795 m_nodes = nodes.data; 796 m_terminalTags = termtags.data; 797 798 logDebug("Match tree has %s (of %s in the builder) nodes, %s terminals", m_nodes.length, builder.m_nodes.length, m_terminals.length); 799 } 800 } 801 802 unittest { 803 import std..string : format; 804 MatchTree!int m; 805 806 void testMatch(string str, size_t[] terms, string[] vars) 807 { 808 size_t[] mterms; 809 string[] mvars; 810 m.match(str, (t, scope vals) { 811 mterms ~= t; 812 mvars ~= vals; 813 return false; 814 }); 815 assert(mterms == terms, format("Mismatched terminals: %s (expected %s)", mterms, terms)); 816 assert(mvars == vars, format("Mismatched variables; %s (expected %s)", mvars, vars)); 817 } 818 819 m.addTerminal("a", 0); 820 m.addTerminal("b", 0); 821 m.addTerminal("ab", 0); 822 m.rebuildGraph(); 823 assert(m.getTerminalVarNames(0) == []); 824 assert(m.getTerminalVarNames(1) == []); 825 assert(m.getTerminalVarNames(2) == []); 826 testMatch("a", [0], []); 827 testMatch("ab", [2], []); 828 testMatch("abc", [], []); 829 testMatch("b", [1], []); 830 831 m = MatchTree!int.init; 832 m.addTerminal("ab", 0); 833 m.addTerminal("a*", 0); 834 m.rebuildGraph(); 835 assert(m.getTerminalVarNames(0) == []); 836 assert(m.getTerminalVarNames(1) == []); 837 testMatch("a", [1], []); 838 testMatch("ab", [0, 1], []); 839 testMatch("abc", [1], []); 840 841 m = MatchTree!int.init; 842 m.addTerminal("ab", 0); 843 m.addTerminal("a:var", 0); 844 m.rebuildGraph(); 845 assert(m.getTerminalVarNames(0) == []); 846 assert(m.getTerminalVarNames(1) == ["var"], format("%s", m.getTerminalVarNames(1))); 847 testMatch("a", [], []); // vars may not be empty 848 testMatch("ab", [0, 1], ["b"]); 849 testMatch("abc", [1], ["bc"]); 850 851 m = MatchTree!int.init; 852 m.addTerminal(":var1/:var2", 0); 853 m.addTerminal("a/:var3", 0); 854 m.addTerminal(":var4/b", 0); 855 m.rebuildGraph(); 856 assert(m.getTerminalVarNames(0) == ["var1", "var2"]); 857 assert(m.getTerminalVarNames(1) == ["var3"]); 858 assert(m.getTerminalVarNames(2) == ["var4"]); 859 testMatch("a", [], []); 860 testMatch("a/a", [0, 1], ["a", "a", "a"]); 861 testMatch("a/b", [0, 1, 2], ["a", "b", "b", "a"]); 862 testMatch("a/bc", [0, 1], ["a", "bc", "bc"]); 863 testMatch("ab/b", [0, 2], ["ab", "b", "ab"]); 864 testMatch("ab/bc", [0], ["ab", "bc"]); 865 866 m = MatchTree!int.init; 867 m.addTerminal(":var1/", 0); 868 m.rebuildGraph(); 869 assert(m.getTerminalVarNames(0) == ["var1"]); 870 testMatch("ab/", [0], ["ab"]); 871 testMatch("ab", [], []); 872 testMatch("/ab", [], []); 873 testMatch("a/b", [], []); 874 testMatch("ab//", [], []); 875 } 876 877 878 private struct MatchGraphBuilder { 879 @safe: 880 import std.container.array : Array; 881 import std.array : array; 882 import std.algorithm : filter; 883 import std..string : format; 884 885 alias NodeIndex = uint; 886 alias TerminalIndex = ushort; 887 alias VarIndex = ushort; 888 alias NodeSet = LinkedSetBacking!NodeIndex.Handle; 889 890 private { 891 enum TerminalChar = 0; 892 struct TerminalTag { 893 TerminalIndex index; 894 VarIndex var = VarIndex.max; 895 } 896 struct Node { 897 Array!TerminalTag terminals; 898 NodeSet[ubyte.max+1] edges; 899 } 900 Array!Node m_nodes; 901 LinkedSetBacking!NodeIndex m_edgeEntries; 902 } 903 904 @disable this(this); 905 906 string[] insert(string pattern, TerminalIndex terminal) 907 { 908 import std.algorithm : canFind; 909 910 auto full_pattern = pattern; 911 string[] vars; 912 if (!m_nodes.length) addNode(); 913 914 // create start node and connect to zero node 915 auto nidx = addNode(); 916 addEdge(0, nidx, '^', terminal); 917 918 while (pattern.length) { 919 auto ch = pattern[0]; 920 if (ch == '*') { 921 assert(pattern.length == 1, "Asterisk is only allowed at the end of a pattern: "~full_pattern); 922 pattern = null; 923 924 foreach (v; ubyte.min .. ubyte.max+1) { 925 if (v == TerminalChar) continue; 926 addEdge(nidx, nidx, cast(ubyte)v, terminal); 927 } 928 } else if (ch == ':') { 929 pattern = pattern[1 .. $]; 930 auto name = skipPathNode(pattern); 931 assert(name.length > 0, "Missing placeholder name: "~full_pattern); 932 assert(!vars.canFind(name), "Duplicate placeholder name ':"~name~"': '"~full_pattern~"'"); 933 auto varidx = cast(VarIndex)vars.length; 934 vars ~= name; 935 assert(!pattern.length || (pattern[0] != '*' && pattern[0] != ':'), 936 "Cannot have two placeholders directly follow each other."); 937 938 foreach (v; ubyte.min .. ubyte.max+1) { 939 if (v == TerminalChar || v == '/') continue; 940 addEdge(nidx, nidx, cast(ubyte)v, terminal, varidx); 941 } 942 } else { 943 nidx = addEdge(nidx, ch, terminal); 944 pattern = pattern[1 .. $]; 945 } 946 } 947 948 addEdge(nidx, TerminalChar, terminal); 949 return vars; 950 } 951 952 void disambiguate() 953 @trusted { 954 import std.algorithm : map, sum; 955 import std.array : appender, join; 956 957 //logInfo("Disambiguate with %s initial nodes", m_nodes.length); 958 if (!m_nodes.length) return; 959 960 import vibe.utils.hashmap; 961 HashMap!(size_t, NodeIndex) combined_nodes; 962 Array!bool visited; 963 visited.length = m_nodes.length * 2; 964 Stack!NodeIndex node_stack; 965 node_stack.reserve(m_nodes.length); 966 node_stack.push(0); 967 while (!node_stack.empty) { 968 auto n = node_stack.pop(); 969 970 while (n >= visited.length) visited.length = visited.length * 2; 971 if (visited[n]) continue; 972 //logInfo("Disambiguate %s (stack=%s)", n, node_stack.fill); 973 visited[n] = true; 974 975 foreach (ch; ubyte.min .. ubyte.max+1) { 976 auto chnodes = m_nodes[n].edges[ch]; 977 size_t chhash = m_edgeEntries.getHash(chnodes); 978 979 // handle trivial cases 980 if (m_edgeEntries.hasMaxLength(chnodes, 1)) 981 continue; 982 983 // generate combined state for ambiguous edges 984 if (auto pn = () @trusted { return chhash in combined_nodes; } ()) { 985 m_nodes[n].edges[ch] = m_edgeEntries.create(*pn); 986 assert(m_edgeEntries.hasLength(m_nodes[n].edges[ch], 1)); 987 continue; 988 } 989 990 // for new combinations, create a new node 991 NodeIndex ncomb = addNode(); 992 combined_nodes[chhash] = ncomb; 993 994 // write all edges 995 size_t idx = 0; 996 foreach (to_ch; ubyte.min .. ubyte.max+1) { 997 auto e = &m_nodes[ncomb].edges[to_ch]; 998 foreach (chn; m_edgeEntries.getItems(chnodes)) 999 m_edgeEntries.insert(e, m_edgeEntries.getItems(m_nodes[chn].edges[to_ch])); 1000 } 1001 1002 // add terminal indices 1003 foreach (chn; m_edgeEntries.getItems(chnodes)) 1004 foreach (t; m_nodes[chn].terminals) 1005 addTerminal(ncomb, t.index, t.var); 1006 foreach (i; 1 .. m_nodes[ncomb].terminals.length) 1007 assert(m_nodes[ncomb].terminals[0] != m_nodes[ncomb].terminals[i]); 1008 1009 m_nodes[n].edges[ch] = m_edgeEntries.create(ncomb); 1010 assert(m_edgeEntries.hasLength(m_nodes[n].edges[ch], 1)); 1011 } 1012 1013 // process nodes recursively 1014 foreach (ch; ubyte.min .. ubyte.max+1) { 1015 // should only have single out-edges now 1016 assert(m_edgeEntries.hasMaxLength(m_nodes[n].edges[ch], 1)); 1017 foreach (e; m_edgeEntries.getItems(m_nodes[n].edges[ch])) 1018 node_stack.push(e); 1019 } 1020 } 1021 1022 import std.algorithm.sorting : sort; 1023 foreach (ref n; m_nodes) 1024 n.terminals[].sort!((a, b) => a.index < b.index)(); 1025 1026 debug logDebug("Disambiguate done: %s nodes, %s max stack size", m_nodes.length, node_stack.maxSize); 1027 } 1028 1029 void print() 1030 const @trusted { 1031 import std.algorithm : map; 1032 import std.array : join; 1033 import std.conv : to; 1034 import std..string : format; 1035 1036 logInfo("Nodes:"); 1037 size_t i = 0; 1038 foreach (n; m_nodes) { 1039 string mapChar(ubyte ch) { 1040 if (ch == TerminalChar) return "$"; 1041 if (ch >= '0' && ch <= '9') return to!string(cast(dchar)ch); 1042 if (ch >= 'a' && ch <= 'z') return to!string(cast(dchar)ch); 1043 if (ch >= 'A' && ch <= 'Z') return to!string(cast(dchar)ch); 1044 if (ch == '^') return "^"; 1045 if (ch == '/') return "/"; 1046 return format("$%s", ch); 1047 } 1048 logInfo(" %s: %s", i, n.terminals[].map!(t => t.var != VarIndex.max ? format("T%s(%s)", t.index, t.var) : format("T%s", t.index)).join(" ")); 1049 ubyte first_char; 1050 size_t list_hash; 1051 NodeSet list; 1052 1053 void printEdges(ubyte last_char) { 1054 if (!list.empty) { 1055 string targets; 1056 foreach (tn; m_edgeEntries.getItems(list)) 1057 targets ~= format(" %s", tn); 1058 if (targets.length > 0) 1059 logInfo(" [%s ... %s] -> %s", mapChar(first_char), mapChar(last_char), targets); 1060 } 1061 } 1062 foreach (ch, tnodes; n.edges) { 1063 auto h = m_edgeEntries.getHash(tnodes); 1064 if (h != list_hash) { 1065 printEdges(cast(ubyte)(ch-1)); 1066 list_hash = h; 1067 list = tnodes; 1068 first_char = cast(ubyte)ch; 1069 } 1070 } 1071 printEdges(ubyte.max); 1072 i++; 1073 } 1074 } 1075 1076 private void addEdge(NodeIndex from, NodeIndex to, ubyte ch, TerminalIndex terminal, VarIndex var = VarIndex.max) 1077 @trusted { 1078 m_edgeEntries.insert(&m_nodes[from].edges[ch], to); 1079 addTerminal(to, terminal, var); 1080 } 1081 1082 private NodeIndex addEdge(NodeIndex from, ubyte ch, TerminalIndex terminal, VarIndex var = VarIndex.max) 1083 @trusted { 1084 import std.algorithm : canFind; 1085 import std..string : format; 1086 if (!m_nodes[from].edges[ch].empty) 1087 assert(false, format("%s is in %s", ch, m_nodes[from].edges[])); 1088 auto nidx = addNode(); 1089 addEdge(from, nidx, ch, terminal, var); 1090 return nidx; 1091 } 1092 1093 private void addTerminal(NodeIndex node, TerminalIndex terminal, VarIndex var) 1094 @trusted { 1095 foreach (ref t; m_nodes[node].terminals) { 1096 if (t.index == terminal) { 1097 if (t.var != VarIndex.max && t.var != var) 1098 assert(false, format("Ambiguous route var match!? %s vs %s", t.var, var)); 1099 t.var = var; 1100 return; 1101 } 1102 } 1103 m_nodes[node].terminals ~= TerminalTag(terminal, var); 1104 } 1105 1106 private NodeIndex addNode() 1107 @trusted { 1108 assert(m_nodes.length <= 1_000_000_000, "More than 1B nodes in tree!?"); 1109 auto idx = cast(NodeIndex)m_nodes.length; 1110 m_nodes ~= Node.init; 1111 return idx; 1112 } 1113 } 1114 1115 struct LinkedSetBacking(T) { 1116 import std.container.array : Array; 1117 import std.range : isInputRange; 1118 1119 static struct Handle { 1120 uint index = uint.max; 1121 @property bool empty() const { return index == uint.max; } 1122 } 1123 1124 private { 1125 static struct Entry { 1126 uint next; 1127 T value; 1128 } 1129 1130 Array!Entry m_storage; 1131 1132 static struct Range { 1133 private { 1134 Array!Entry* backing; 1135 uint index; 1136 } 1137 1138 @property bool empty() const { return index == uint.max; } 1139 @property ref const(T) front() const { return (*backing)[index].value; } 1140 1141 void popFront() 1142 { 1143 index = (*backing)[index].next; 1144 } 1145 } 1146 } 1147 1148 @property Handle emptySet() { return Handle.init; } 1149 1150 Handle create(scope T[] items...) 1151 { 1152 Handle ret; 1153 foreach (i; items) 1154 ret.index = createNode(i, ret.index); 1155 return ret; 1156 } 1157 1158 void insert(Handle* h, T value) 1159 { 1160 /*foreach (c; getItems(*h)) 1161 if (value == c) 1162 return;*/ 1163 h.index = createNode(value, h.index); 1164 } 1165 1166 void insert(R)(Handle* h, R items) 1167 if (isInputRange!R) 1168 { 1169 foreach (itm; items) 1170 insert(h, itm); 1171 } 1172 1173 size_t getHash(Handle sh) 1174 const { 1175 // NOTE: the returned hash is order independent, to avoid bogus 1176 // mismatches when comparing lists of different order 1177 size_t ret = 0x72d2da6c; 1178 while (sh != Handle.init) { 1179 ret ^= (hashOf(m_storage[sh.index].value) ^ 0xb1bdfb8d) * 0x5dbf04a4; 1180 sh.index = m_storage[sh.index].next; 1181 } 1182 return ret; 1183 } 1184 1185 auto getItems(Handle sh) { return Range(&m_storage, sh.index); } 1186 auto getItems(Handle sh) const { return Range(&(cast()this).m_storage, sh.index); } 1187 1188 bool hasMaxLength(Handle sh, size_t l) 1189 const { 1190 uint idx = sh.index; 1191 do { 1192 if (idx == uint.max) return true; 1193 idx = m_storage[idx].next; 1194 } while (l-- > 0); 1195 return false; 1196 } 1197 1198 bool hasLength(Handle sh, size_t l) 1199 const { 1200 uint idx = sh.index; 1201 while (l-- > 0) { 1202 if (idx == uint.max) return false; 1203 idx = m_storage[idx].next; 1204 } 1205 return idx == uint.max; 1206 } 1207 1208 private uint createNode(ref T val, uint next) 1209 { 1210 auto id = cast(uint)m_storage.length; 1211 m_storage ~= Entry(next, val); 1212 return id; 1213 } 1214 } 1215 1216 unittest { 1217 import std.algorithm.comparison : equal; 1218 1219 LinkedSetBacking!int b; 1220 auto s = b.emptySet; 1221 assert(s.empty); 1222 assert(b.getItems(s).empty); 1223 s = b.create(3, 5, 7); 1224 assert(b.getItems(s).equal([7, 5, 3])); 1225 assert(!b.hasLength(s, 2)); 1226 assert(b.hasLength(s, 3)); 1227 assert(!b.hasLength(s, 4)); 1228 assert(!b.hasMaxLength(s, 2)); 1229 assert(b.hasMaxLength(s, 3)); 1230 assert(b.hasMaxLength(s, 4)); 1231 1232 auto h = b.getHash(s); 1233 assert(h != b.getHash(b.emptySet)); 1234 s = b.create(5, 3, 7); 1235 assert(b.getHash(s) == h); 1236 1237 b.insert(&s, 11); 1238 assert(b.hasLength(s, 4)); 1239 assert(b.getHash(s) != h); 1240 } 1241 1242 private struct Stack(E) 1243 { 1244 import std.range : isInputRange; 1245 1246 private { 1247 E[] m_storage; 1248 size_t m_fill; 1249 debug size_t m_maxFill; 1250 } 1251 1252 @property bool empty() const { return m_fill == 0; } 1253 1254 @property size_t fill() const { return m_fill; } 1255 1256 debug @property size_t maxSize() const { return m_maxFill; } 1257 1258 void reserve(size_t amt) 1259 { 1260 auto minsz = m_fill + amt; 1261 if (m_storage.length < minsz) { 1262 auto newlength = 64; 1263 while (newlength < minsz) newlength *= 2; 1264 m_storage.length = newlength; 1265 } 1266 } 1267 1268 void push(E el) 1269 { 1270 reserve(1); 1271 m_storage[m_fill++] = el; 1272 debug if (m_fill > m_maxFill) m_maxFill = m_fill; 1273 } 1274 1275 void push(R)(R els) 1276 if (isInputRange!R) 1277 { 1278 reserve(els.length); 1279 foreach (el; els) 1280 m_storage[m_fill++] = el; 1281 debug if (m_fill > m_maxFill) m_maxFill = m_fill; 1282 } 1283 1284 E pop() 1285 { 1286 assert(!empty, "Stack underflow."); 1287 return m_storage[--m_fill]; 1288 } 1289 }