#pax #applications #executable #cartridge #projects #manifest #compiler

pax-compiler

Compiler APIs for parsing and building Pax projects into application executables

118 releases (30 breaking)

new 0.38.3 Oct 24, 2024
0.37.1 Oct 10, 2024
0.22.0 Jul 26, 2024
0.12.8 Mar 16, 2024
0.0.1 Sep 13, 2022

#122 in Graphics APIs

Download history 138/week @ 2024-07-06 56/week @ 2024-07-13 463/week @ 2024-07-20 328/week @ 2024-07-27 5/week @ 2024-08-03 6/week @ 2024-08-10 950/week @ 2024-08-17 331/week @ 2024-08-24 259/week @ 2024-08-31 572/week @ 2024-09-07 717/week @ 2024-09-14 207/week @ 2024-09-21 109/week @ 2024-09-28 264/week @ 2024-10-05 216/week @ 2024-10-12 332/week @ 2024-10-19

923 downloads per month
Used in 3 crates

MIT/Apache

4MB
30K SLoC

Rust 19K SLoC // 0.0% comments Swift 4K SLoC // 0.1% comments TypeScript 3K SLoC // 0.1% comments JavaScript 2.5K SLoC // 0.0% comments Tera 611 SLoC Pest 166 SLoC // 0.3% comments Shell 39 SLoC // 0.3% comments

Contains (Mach-o exe, 2MB) PaxCartridge

Pax-Compiler

This document describes the high-level architecture of pax-compiler.

For more information refer to our docs.

Bird's Eye View

Roughly it can be broken down into three steps:

  1. Analyze the user Pax project (Pax Template + Rust) and generate a Pax Manifest (data structure summarizing the Pax project)
  2. Code-gen a Pax Manifest into a Pax Cartridge (Rust target agnostic library).
  3. Build a target platform executable (chassis) with this rust cartridge included.

The main entry-point for all of this is perform_build found in lib.rs.

Step 1: Pax Project -> Pax Manifest

Pax projects decorate their associated Rust with a special macro #[derive(Pax)]. These macros generate code to dynamically analyze their tagged structs. They each add a parse_to_manifest function for every #[derive(Pax)] tagged struct. This parse_to_manifest function (template found here) stores its associated structs information in a ParsingContext object and calls parse_to_manifest on its Pax Template dependencies. It utilizes logic in parsing.rs and relies on our pest grammar (pax.pest) to understand the template dependencies. For the root struct (tagged #[main] e.g. here), we generate a binary target as well that starts the process and writes the accumulated information into a Pax Manifest and serializes it to stdout. This binary (named parser) is kicked off (run_parser_binary) as our first step of the compilation process to generate the Manifest in the lib.rs/perform_build function.

Step 2: Pax Manifest -> Pax Cartridge

Next we generate the Pax Cartridge. This work is roughly two steps: compiling expressions and generating the cartridge code. The former involves parsing Paxel (Pax's expression language) and generating the equivalent rust code. This work lives in expressions.rs. Once expressions are compiled, the second step is generating the cartridge code. This lives in the code_generation module. We utilize Tera templates for the code-gen and the bulk of this work is translating a Pax Manifest into a Tera context. The main entry point is generate_and_overwrite_cartridge in code_generation/mod.rs.

Step 3: Building a Chassis with our Pax Cartridge

The last step of this process involves building our target platform (e.g. Web/MacOS/..) scaffolding (see chassis) with our cartridge included. This work lives in the building module. This mainly involves building the generated cartridge, loading it into our specific chassis and then building that chassis. Currently we support 3 targets (Web/MacOS/iOS). We load our cartridge as WASM for Web and as .dylibs for our Apple targets.

Consumers

The main consumers of pax-compiler are pax-cli and pax-macro. pax-cli is the CLI that Pax user's invoke to build Pax projects. pax-macro is the crate the defines the #[derive(Pax)] macro that we use for dynamic analysis of the Pax Project (see Step 1).

Dependencies

~40–57MB
~1M SLoC