User-defined DSL: routes { ... }

For new readers

A "DSL" (domain-specific language) here is a small, custom mini-syntax โ€” this demo lets you write HTTP routes in a compact routes { ... } block, which gets expanded into ordinary PatLang function calls before the real compiler ever sees it. The notable part is that this expansion mechanism (declaring your own grammar extension) is itself written entirely in PatLang, with no changes to the language's own lexer or parser.

A small grammar extension declared entirely in PatLang source (syntax RouterDSL { trigger; tokens { ... }; rule { ... } }), matching HTTP-verb/URL-path tokens with a general-purpose regex engine also written in PatLang (lib/regex_dsl.patlang). A source-to-source preprocessing pass (lib/syntax_dsl.patlang) expands every routes { ... } block into plain register_route(...) calls before the ordinary compiler ever sees them โ€” run here via the same in-browser WASM playground engine as the demos above (no rustc at all), or compiled natively below.

PatLang source
# Portfolio demo: a user-defined DSL for declaring HTTP routes, entirely
# authored in PatLang source (no change to the core lexer/parser). The
# `syntax RouterDSL { ... }` block below declares a small grammar extension
# -- its own token patterns (matched by a general-purpose regex engine, also
# written in PatLang: self_hosting/lib/regex_dsl.patlang) and a rule
# describing how to expand each triggered line. Every `routes { ... }` block
# that follows is then expanded, line by line, into ordinary `register_route`
# calls by self_hosting/lib/syntax_dsl.patlang -- a source-to-source
# preprocessing pass run once at build time (see build_router_dsl_demo in
# self_hosting/tools/build_portfolio.patlang), BEFORE this file is handed to
# the ordinary tokenizer/parser/lowerer. By the time the compiler proper
# looks at this program, the DSL blocks below no longer exist -- they've
# already become the register_route(...) calls printed at the bottom.

make a function called register_route takes verb, path, controller, action returns done
  print(verb + " " + path + " -> " + controller + "." + action)
  return true
end

syntax RouterDSL {
    trigger: Keyword("routes");

    tokens {
        HttpVerb(verb) = regex("\b(GET|POST|PUT|DELETE)\b", verb);
        UrlPath(path)   = regex("/[a-zA-Z0-9_/:-]*", path);
        Arrow           = "->";
    }

    rule RouteLine {
        let verb = expect HttpVerb;
        let path = expect UrlPath;
        expect Arrow;
        let controller = expect Identifier;
        expect Symbol(".");
        let action = expect Identifier;

        return register_route(verb, path, controller, action);
    }
}

routes {
    GET    /users            -> UserController.index
    POST   /users/:id/edit   -> UserController.update
    DELETE /sessions         -> AuthController.logout
}

print("3 routes registered.")

(not run yet)

Native run on the build machine:

GET /users -> UserController.index
POST /users/:id/edit -> UserController.update
DELETE /sessions -> AuthController.logout
3 routes registered.