166 lines
8.7 KiB
Markdown
166 lines
8.7 KiB
Markdown
# @wrnexus/compiler
|
|
|
|
> Compiler for the `.wrn` language — tokenizes, parses, and lowers `.wrn` page and component files to TypeScript.
|
|
|
|
Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework.
|
|
|
|
## Overview
|
|
|
|
`@wrnexus/compiler` turns `.wrn` source into TypeScript that targets the framework's runtime primitives. A `.wrn` file declares either a `page` (a route) or a `component` (a reusable, prop-driven fragment) with blocks for `state`, `view` (plain HTML), `seo`, `style`, `functions`, `api`, `ssr`/`client` data bindings, and `realtime` websocket handlers. The pipeline is `source → Lexer → parse() → PageAst → generate() → TypeScript`. It is a build/server-side library — the WrNexus dev loader calls it to compile `.wrn` files on the fly, surfacing `ParseError` as a readable error page.
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
bun add @wrnexus/compiler
|
|
```
|
|
|
|
> Private package — the machine must be authenticated to the `wrnexus` npm org
|
|
> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported).
|
|
|
|
## API
|
|
|
|
All exports come from the package root (`@wrnexus/compiler`).
|
|
|
|
### `compileWireFile(source: string): string`
|
|
|
|
Compile `.wrn` source to a TypeScript module string. Throws `ParseError` on invalid input. The output is prefixed with a `// compiled from .wrn` comment.
|
|
|
|
### `compile(source: string): CompileResult`
|
|
|
|
Richer entry point that returns the generated code, the AST, and any diagnostics.
|
|
|
|
```ts
|
|
interface CompileResult {
|
|
code: string;
|
|
ast: PageAst;
|
|
diagnostics: string[];
|
|
}
|
|
```
|
|
|
|
On a `ParseError` it pushes the message into `diagnostics` and re-throws.
|
|
|
|
### `parse(source: string): PageAst`
|
|
|
|
Run the lexer + recursive-descent parser and return the AST. Throws `ParseError` (lexer `LexError`s are caught and rethrown as `ParseError`).
|
|
|
|
### `generate(ast: PageAst): string`
|
|
|
|
Lower a `PageAst` to TypeScript. `page` ASTs become a default-export page component (plus `meta`, optional `layout`, `__wrnexusApi`/method handlers, `websocket`, and SSR/CSR data bindings); `component` ASTs become a module exporting `render(props)` and `__wrnexusComponent`.
|
|
|
|
### `Lexer`
|
|
|
|
On-demand lexer for `.wrn`. Yields structural tokens and exposes raw-span readers for the parser.
|
|
|
|
```ts
|
|
class Lexer {
|
|
pos: number;
|
|
constructor(src: string);
|
|
next(): Token; // consume next structural token
|
|
peek(): Token; // look ahead without consuming
|
|
readPath(): string; // route path, e.g. /users/[id]
|
|
readToLineEnd(): string; // rest of line (state/prop initializers)
|
|
readBalancedBraces(): string; // inner text of a { ... } block, string-aware
|
|
}
|
|
```
|
|
|
|
`Token` is `{ type: TokenType; value: string; pos: number }`, where `TokenType` is one of `ident`, `string`, `lbrace`, `rbrace`, `lparen`, `rparen`, `at`, `eq`, `comma`, `eof`.
|
|
|
|
### Errors
|
|
|
|
| Class | Thrown by | Meaning |
|
|
| ------------ | ------------------------------------------------- | --------------------------------------------------------------- |
|
|
| `ParseError` | `parse`, `compile`, `compileWireFile`, `generate` | Invalid `.wrn` grammar or (rewrapped) lex failure. |
|
|
| `LexError` | `Lexer` | Unexpected character / unterminated string / unbalanced braces. |
|
|
|
|
### AST types
|
|
|
|
Exported type-only symbols describing the parsed tree:
|
|
|
|
| Type | Description |
|
|
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `PageAst` | Root node: `kind` (`"page" \| "component"`), `name`, optional `layout`, `props`, `states`, `seo`, `view`, `styles`, `functions`, `dataApis`, `modeFunctions`, `apis`, `realtimes`. |
|
|
| `ViewNode` | `{ type: "text"; value }` or `{ type: "element"; tag; attrs; children }`. |
|
|
| `Attr` | `{ name; value; event; boolean? }` — `event` marks `@event` bindings. |
|
|
| `StateDecl` | `{ name; expr }` — a `state x = <expr>` declaration. |
|
|
| `SeoBlock` | `Record<string, string>` from the `seo { ... }` block. |
|
|
| `ApiBlock` | `{ method; path; body }` — a top-level `api METHOD /path { ... }`. |
|
|
| `DataApiBlock` | `{ mode; name; method; path; body }` — an `api` inside an `ssr`/`client` block. |
|
|
| `DataMode` | `"ssr" \| "client"`. |
|
|
| `ModeFunctionsBlock` | `{ mode; body }` — a `functions { ... }` inside an `ssr`/`client` block. |
|
|
| `RealtimeBlock` | `{ name; handlers }` — a `realtime <name> { on evt(args) { ... } }` block. |
|
|
|
|
## Usage
|
|
|
|
Compile a page:
|
|
|
|
```ts
|
|
import { compileWireFile } from "@wrnexus/compiler";
|
|
|
|
const ts = compileWireFile(`
|
|
page Home {
|
|
state count = 0
|
|
seo { title = "Home" description = "Welcome" }
|
|
view {
|
|
<button @click="count++">Clicked {count} times</button>
|
|
}
|
|
}
|
|
`);
|
|
// ts is a TypeScript module: exports `meta`, and a default page component
|
|
// returning an HTML string, wrapped in a data-scope for the reactive runtime.
|
|
```
|
|
|
|
Inspect the AST and diagnostics:
|
|
|
|
```ts
|
|
import { compile, ParseError } from "@wrnexus/compiler";
|
|
|
|
try {
|
|
const { code, ast, diagnostics } = compile(source);
|
|
console.log(ast.kind, ast.name, ast.states.length);
|
|
} catch (err) {
|
|
if (err instanceof ParseError) console.error(err.message);
|
|
}
|
|
```
|
|
|
|
Drive the parse/codegen stages directly:
|
|
|
|
```ts
|
|
import { parse, generate } from "@wrnexus/compiler";
|
|
|
|
const ast = parse(componentSource); // ast.kind === "component"
|
|
const module = generate(ast); // exports render(props) + __wrnexusComponent
|
|
```
|
|
|
|
Use the lexer standalone:
|
|
|
|
```ts
|
|
import { Lexer } from "@wrnexus/compiler";
|
|
|
|
const lx = new Lexer("page Home {");
|
|
lx.next(); // { type: "ident", value: "page", pos: 0 }
|
|
lx.next(); // { type: "ident", value: "Home", pos: 5 }
|
|
lx.next(); // { type: "lbrace", value: "{", pos: 10 }
|
|
```
|
|
|
|
## The `.wrn` language (as parsed)
|
|
|
|
A file opens with `page <Name>` or `component <Name>` followed by a `{ ... }` body containing zero or more members:
|
|
|
|
- `layout = "<name>"` — selects `app/layouts/<name>.wrn` (pages only).
|
|
- `props { name = <default> ... }` — component props; each default's type drives coercion.
|
|
- `state <ident> = <expr>` — reactive state seeded from a raw JS expression.
|
|
- `view { <html> }` — plain HTML with `{expr}` interpolation, hyphenated attributes, boolean attributes, `@event="..."` client bindings, and `<!-- comments -->`.
|
|
- `seo { key = "value" ... }` — metadata merged into the generated `meta`.
|
|
- `style { <raw css> }` — inlined page/component stylesheet (repeatable).
|
|
- `functions { <raw js> }` — shared server-side helpers (repeatable).
|
|
- `api <METHOD> <path> { <raw js> }` — route handler, lowered to a `METHOD` export (repeatable).
|
|
- `ssr { ... }` / `client { ... }` — data blocks holding `api <name> <METHOD> <path> { ... }` bindings and their own `functions { ... }`.
|
|
- `realtime <name> { on <evt>(<args>) { <raw js> } ... }` — websocket handlers, lowered to a `websocket` export.
|
|
|
|
`view` markup is parsed by a lenient dedicated HTML parser (`parseHtmlView`); HTML void elements (`<br>`, `<img>`, …) take no closing tag. Line comments (`//`) are skipped by the lexer.
|
|
|
|
## Requirements / Notes
|
|
|
|
- Pure TypeScript with no runtime dependencies; runs under **Bun** as part of the WrNexus toolchain (Node is not supported).
|
|
- Generated modules target WrNexus runtime primitives (`data-scope`, `data-text`, `data-on-*`, `data-for`, `data-component`, `__wrnexus*`/`__wire*` helpers) — consume the output within a WrNexus app, e.g. via `@wrnexus/core`'s dev loader.
|