You can see one implementation that is able to parse a large number of routes here: http://ioreader.com/code/php/route-parser.phps
The default method of operation for that parsing is a direct mapping between the controller directory structure and file structure. The other mode of operation is to re-map a route to a controller.
The following code is an example of how it is used. The route parser doesn't assume anything about how controllers are stored. For example, in one project my controllers are classes, in another project my controllers are simply files and actions are functions within, and finally, in another project controllers are represented by a single function in a file. I have made parts of this code vague enough for it to represent any of the above possibilities.
To get route-remappings into the router I usually have a single file for them that is included. By having the router stored in a local variable '$routes' (presumably within the function 'include_route_remappings') and then by including the routes file, I can take advantage of the RouteParser's ArrayAccess offsetSet to define route-remappings in a simple way. The following are the route-remappings I use for my blog.
$router = new RouteParser('/path/to/base/controller/dir/');
$route = get_route(); // this is not provided, but the route is all or part of the URI
$request_method = get_request_method(); // GET, POST, PUT, DELETE
// add the route remappings into the router
$path_info = $router->parse($route);
// get the controller, method, and arguments form the route parser
list($dir, $pdir, $controller_name, $action_name, $arguments) = $path_info;
// include the proper file, we expect the class/function name to be the same as
// or a transformation of the file name (e.g. file-name.php => FileNameController)
// by virtue of getting down here we *know* that this file exists, we
// simply haven't included it yet.
$controller = get_controller($controller_name);
error_404(); // controller doesn't exist
// get the request-method-specific action for this controller
$action = get_action($controller, $request_method);
error_404(); // action doesn't exist
// call the controller's action
A final note is that route arguments are best defined in route-remappings.
$routes['/about'] = '/index/about';
$routes['/profile/(:alphanum)'] = '/index/profile/$1';
$routes['/find-comment/(:alphanum)'] = '/index/find-comment/$1';
$routes['/tags'] = '/index/tags';
$routes['/tags/(:any)'] = '/index/tags/$1';
$routes['/(:year)'] = '/archive/index/$1';
$routes['/(:year)/(:month)'] = '/archive/index/$1/$2';
$routes['/(:year)/(:month)/(:day)'] = '/archive/index/$1/$2/$3';
$routes['/(:year)/(:month)/(:day)/([a-zA-Z0-9-]*)'] = '/view/post/$4';
$routes['/captcha'] = '/index/captcha';
$routes['/books'] = '/index/books';