formalang 0.0.5-beta

FormaLang compiler frontend: lexer, parser, semantic analyzer, and IR lowering.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
<br/>

<p align="center">
  <img src="https://formalang.org/logo-180.png" alt="FormaLang" width="200">
</p>

<h3 align="center">A statically typed, declarative DSL compiler written in Rust</h3>

<p align="center">
  <a href="https://crates.io/crates/formalang"><img src="https://img.shields.io/crates/v/formalang.svg?style=flat-square&color=cba6f7&label=crates.io" alt="crates.io"></a>
  <a href="https://docs.rs/formalang"><img src="https://img.shields.io/docsrs/formalang?style=flat-square&color=cba6f7" alt="docs.rs"></a>
  <a href="#license"><img src="https://img.shields.io/badge/license-MIT_or_Apache--2.0-cba6f7?style=flat-square" alt="license"></a>
  <a href=".github/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/valentinradu/formalang/ci.yml?style=flat-square&color=cba6f7&label=ci&branch=main" alt="ci"></a>
</p>

<p align="center">
  <a href="#why-formalang">Why</a>
  &nbsp;·&nbsp;
  <a href="#quick-start">Quick Start</a>
  &nbsp;·&nbsp;
  <a href="#language-tour">Language Tour</a>
  &nbsp;·&nbsp;
  <a href="#rust-api">Rust API</a>
  &nbsp;·&nbsp;
  <a href="https://formalang.org/docs/">Full Docs</a>
</p>

<br/>

---

You write `.fv` files; the library parses and validates them, and you get a fully type-resolved IR back. What you do with that IR (generate code, drive a UI framework, configure a system) is up to your backend. The first such backend is [`formawasm`](https://github.com/valentinradu/formawasm), which lowers the IR to WebAssembly.

```text
.fv source → formalang library → IrModule → your Backend → output
```

---

## Contents

- [Why FormaLang?]#why-formalang
- [Quick Start]#quick-start
- [Language Tour]#language-tour
  - [Primitives]#primitives
  - [Structs]#structs
  - [Methods (impl blocks)]#methods-impl-blocks
  - [Parameter Conventions]#parameter-conventions
  - [Traits]#traits
  - [Enums]#enums
  - [Let bindings]#let-bindings
  - [Arrays, Dictionaries, Tuples]#arrays-dictionaries-tuples
  - [Control Flow]#control-flow
  - [Closures]#closures
  - [Generics]#generics
  - [Destructuring]#destructuring
  - [Modules]#modules
  - [Extern declarations]#extern-declarations
  - [Function overloading]#function-overloading
- [Rust API]#rust-api
  - [Entry points]#entry-points
  - [The IrModule]#the-irmodule
  - [Pipeline (passes + backends)]#pipeline-passes--backends
  - [Error reporting]#error-reporting
- [File extension]#file-extension
- [What is not built in]#what-is-not-built-in
- [Further reading]#further-reading
- [License]#license

---

## Why FormaLang?

You're building a Rust application that needs to accept user-authored logic: UI definitions, configuration with computation, state machines, scripted rules. The usual options each have a sharp edge:

- **Ship Rust as the user-facing language.** Rust is a great host but a poor guest: it's AOT-compiled, lifetimes and the borrow checker land on whoever writes the file, and you can't load `.rs` snippets at runtime without dragging in a full toolchain.
- **Embed Lua, Rhai, or JavaScript.** These are dynamically typed. Errors that should have been caught when the file was loaded surface only when the offending branch runs, usually in production.
- **Use JSON, YAML, or TOML.** No expressions, no functions, no real types. The moment your config grows a conditional, you reinvent half a language inside string templates.

FormaLang fills that gap:

- **Statically typed and fully resolved.** The library hands back an `IrModule` where every type, name, and overload is already settled. A broken `.fv` fails at load, not when the user clicks the button that runs the bad branch.
- **Embeddable by design.** A pure compiler frontend with no runtime, no I/O, no globals, no sandbox to maintain. The output is data: walk it, transform it, emit whatever you want.
- **Small surface for users.** Structs, enums, traits, closures, generics, modules. No lifetimes, no async, no unsafe, no macros. Someone fluent in Swift or Rust can read it on day one.
- **Backend-agnostic.** Drive a UI framework, generate code for any target, configure a runtime, layer custom IR passes. The compiler stops at the IR; you decide what comes next.

---

## Quick Start

Add to `Cargo.toml`:

```toml
[dependencies]
formalang = "0.0.5-beta"
```

Compile a source string:

```rust
use formalang::compile_to_ir;

let source = r#"
    pub struct User {
        name: String,
        age: I32
    }
"#;

let module = compile_to_ir(source).unwrap();
println!("{}", module.structs[0].name); // User
```

---

## Language Tour

### Primitives

```rust
let text: String = "hello"
let count: I32 = 42
let big: I64 = 9_223_372_036_854_775_807
let ratio: F64 = 3.14
let small: F32 = 0.5F32          // type-suffix pins literal precision
let flag: Boolean = true
let logo: Path = /assets/logo.svg
let pattern: Regex = r/[a-z]+/i
let nothing: String? = nil       // optional; any type can be made optional with ?
```

Numeric primitives are width-tagged: `I32`, `I64`, `F32`, `F64`. Unsuffixed
integer literals default to `I32`; unsuffixed float literals default to
`F64`. Suffix syntax is uppercase and adjacent to the digits (`42I64`,
`3.14F32`).

### Structs

```rust
pub struct Point {
    x: I32,
    y: I32
}

pub struct User {
    name: String,
    email: String,
    nickname: String?,       // optional field
    score: I32
}

// Instantiate with named arguments
let p = Point(x: 10, y: 20)
let u = User(name: "Alice", email: "alice@example.com", nickname: nil, score: 0)
// Mutability is a property of the binding, not the field; to mutate any
// field of `u` you bind it with `let mut u = User(...)`.
```

### Methods (impl blocks)

```rust
pub struct Counter {
    value: I32
}

impl Counter {
    fn increment(self) -> I32 {
        self.value + 1
    }

    fn reset(self) -> Counter {
        Counter(value: 0)
    }
}
```

### Parameter Conventions

Every function parameter has a convention controlling how the argument is received. The call site always looks the same as `f(x)`; only the function declaration changes.

```rust
// default: immutable; the callee reads the value
fn area(radius: I32) -> I32 {
    radius * radius
}

// mut: callee may mutate; argument binding must be let mut
fn bump(mut n: I32) -> I32 {
    n
}

// sink: ownership transfer; caller cannot use the binding after the call
fn consume(sink label: String) -> String {
    label
}

// Self conventions work the same way
impl Counter {
    fn view(self) -> I32 { self.value }         // default (immutable self)
    fn increment(mut self) -> I32 { self.value } // mut self
}
```

### Traits

Traits declare field and method requirements. Any struct that satisfies all of them can declare conformance.

```rust
pub trait Named {
    name: String
}

pub trait Shape {
    color: String
    fn area(self) -> I32
}

// Declare conformance
pub struct Circle {
    name: String,
    color: String,
    radius: I32
}

impl Named for Circle {}            // fields checked against struct definition

impl Shape for Circle {
    fn area(self) -> I32 {
        self.radius * self.radius   // simplified
    }
}

// Trait composition
pub trait NamedShape: Named + Shape {
    label: String
}

// A trait can also stand in as a value type. The IR lowers method
// calls on a trait-typed binding through the trait's vtable, so two
// branches that produce different concrete types implementing the
// same trait unify cleanly.
fn area(kind: I32, side: I32, w: I32, h: I32) -> I32 {
    let s: Shape = if kind == 0 {
        Square(name: "sq", color: "black", side: side)
    } else {
        Rectangle(name: "rect", color: "white", width: w, height: h)
    }
    s.area()
}
```

### Enums

```rust
pub enum Status {
    pending
    active
    done
}

pub enum Message {
    text(content: String)
    image(url: String, size: I32)
    quit
}

// Instantiate with leading dot
let s: Status = .active
let m: Message = .text(content: "hello")
```

### Let bindings

```rust
let x = 42
let name: String = "Alice"
pub let MAX: I32 = 100
let mut counter: I32 = 0    // mutable binding
```

### Arrays, Dictionaries, Tuples

```rust
// Arrays
let tags: [String] = ["a", "b", "c"]
let matrix: [[I32]] = [[1, 2], [3, 4]]

// Dictionaries
let config: [String: I32] = ["timeout": 30, "retries": 3]
let empty: [String: Boolean] = [:]

// Tuples (all fields must be named)
let point = (x: 10, y: 20)
let name = point.x

// Indexing returns an Optional. The bound may be out of range or the
// key absent, so `xs[i]` and `d[k]` yield `T?` / `V?`. Use `if let` to
// consume the inner value.
let timeout: I32? = config["timeout"]
let first: String? = tags[0]
```

### Control Flow

```rust
// if: branches on a Boolean.
if user.score > 0 {
    greet(name: user.name)
} else {
    welcome()
}

// if let: Rust-style optional unwrap. Both branches required.
if let nickname = user.nickname {
    greet(name: nickname)        // nickname is bound to the unwrapped value
} else {
    greet(name: user.name)
}

// for: iterates arrays, returns array of results
for item in items {
    process(item: item)
}

// match: exhaustive, on enums (and on Optional, treated as .some / .none)
match message {
    .text(content): display(value: content),
    .image(url, size): showImage(src: url),
    .quit: stop()
}
```

### Closures

Closure types describe a callable shape; closure expressions construct one. Both wrap their parameter list in parentheses so every `->` in the language is preceded by `)`.

```rust
pub enum Event {
    pressed,
    textChanged(value: String),
    resized(width: I32, height: I32)
}

pub struct Button<E> {
    onPress:  () -> E,                  // no parameters
    onChange: (String) -> E,            // single parameter
    onResize: (I32, I32) -> E,          // multiple parameters
    onSubmit: ((String) -> E)?          // optional closure
}
```

Closure expressions wrap their parameter list in parentheses — even
for a single parameter — so every `->` in the language is preceded by
`)`:

```rust
// Untyped — parameter types come from the binding annotation or call context
let onPress  = () -> .pressed
let onChange = (x) -> .textChanged(value: x)
let onResize = (w, h) -> .resized(width: w, height: h)

// Typed parameters — annotate inline with `name: Type`
let increment = (n: I32) -> n + 1
let combine   = (x: I32, y: I32) -> x + y
```

Closures capture values from their surrounding scope. The `ClosureConversionPass` lifts each closure into a top-level function plus a synthetic env struct, so backends only ever consume named functions.

```rust
fn make_adder(sink n: I32) -> (I32) -> I32 {
    (x: I32) -> x + n          // captures n
}

let add5 = make_adder(n: 5)
```

Closure parameters carry the same conventions as regular function parameters (`mut`, `sink`). The convention constrains the **caller of the closure**:

```rust
pub struct Form<E> {
    onScale:   (mut I32) -> E,    // caller must pass a mutable binding
    onConsume: (sink String) -> E // caller's binding is moved
}
```

Closures are pure and single-expression: no statements, no side effects in the language itself. Effects live in the host runtime, reached through `extern` declarations.

### Generics

```rust
pub struct Box<T> {
    value: T
}

pub struct Pair<A, B> {
    first: A,
    second: B
}

pub trait Layout { width: I32 }

pub struct Container<T: Layout> {   // constrained type parameter
    items: [T],
    gap: I32
}

pub enum Result<T, E> {
    ok(value: T)
    error(err: E)
}

let b = Box<String>(value: "hello")
let r: Result<String, I32> = .ok(value: "success")

// Type-argument inference: when every generic parameter shows up in a
// field position, the type args can be omitted at the call site.
let inferred = Box(value: 42)        // Box<I32>
let pair = Pair(first: 10, second: true)  // Pair<I32, Boolean>
```

### Destructuring

```rust
// Arrays
let [first, second, ...rest] = items
let [_, second, ...] = items    // skip with _

// Structs (by field name)
let {name, age} = user
let {name as username} = user   // rename

// Enums (extract associated data)
let (content) = some_text_message
```

### Modules

```rust
// Inline module
pub mod geometry {
    pub struct Point { x: I32, y: I32 }
    pub enum Direction { north, south, east, west }
}

let p: geometry::Point = geometry::Point(x: 0, y: 0)

// Import from other .fv files
use geometry::Point
use ui::{Button, Text}
use data::models::User
```

Files map to module paths: `use geometry::shapes::Circle` resolves to `geometry/shapes.fv`.
Only `pub` items can be imported. Circular imports are a compile error.

### Extern declarations

Describe functions and method surfaces provided by the host runtime; they have no FormaLang body. There is no `extern type`; host-provided types are declared as regular structs and given an `extern impl` so their methods are resolved by the host.

```rust
pub struct Canvas {}
pub struct Connection {}

extern fn create_canvas() -> Canvas
extern fn connect(url: String) -> Connection
extern fn log(message: String)

extern impl Canvas {
    fn width(self) -> I32
    fn height(self) -> I32
    fn clear(self)
}
```

### Function overloading

```rust
fn format(value: I32) -> String { "number" }
fn format(value: String) -> String { "string" }
fn format(value: I32, precision: I32) -> String { "precise" }
```

The compiler resolves overloads by the named-argument label set. Ambiguous or unresolvable calls are compile errors.

---

## Rust API

### Entry points

| Function | Returns | Use case |
| --- | --- | --- |
| `compile_to_ir(src)` | `Result<IrModule, Vec<CompilerError>>` | Code generation (canonical) |
| `compile_with_analyzer(src)` | `Result<(File, SemanticAnalyzer), …>` | LSP hover / completion |
| `compile_and_report(src, filename)` | `Result<IrModule, String>` | CLI: compile + human-readable errors |
| `parse_only(src)` | `Result<File, …>` | Syntax check only |

Custom module resolver (to load `.fv` files from anywhere):

```rust
use formalang::{compile_to_ir_with_resolver, FileSystemResolver};
use std::path::PathBuf;

let resolver = FileSystemResolver::new(PathBuf::from("./src"));
let module = compile_to_ir_with_resolver(source, resolver)?;
```

### The IrModule

```rust
let module = compile_to_ir(source)?;

module.structs    // Vec<IrStruct>
module.traits     // Vec<IrTrait>
module.enums      // Vec<IrEnum>
module.functions  // Vec<IrFunction>   (extern fns: extern_abi = Some(_), body = None)
module.impls      // Vec<IrImpl>
module.lets       // Vec<IrLet>
module.imports    // Vec<IrImport>
module.modules    // Vec<IrModuleNode>  (preserves source `mod foo { ... }` hierarchy)

// ID-based lookup
let id = module.struct_id("User").unwrap();
let s  = module.get_struct(id).unwrap();
```

All types in the IR are fully resolved; no unresolved references remain.

### Pipeline (passes + backends)

```rust
use formalang::{compile_to_ir, Pipeline};
use formalang::ir::{DeadCodeEliminationPass, ConstantFoldingPass};

let module = compile_to_ir(source)?;

let output = Pipeline::new()
    .pass(DeadCodeEliminationPass::default())
    .pass(ConstantFoldingPass::default())
    .emit(module, &my_backend)?;
```

Implement `IrPass` to write your own transforms, and `Backend` to emit code:

```rust
use formalang::{Backend, CompilerError, IrPass};
use formalang::ir::IrModule;

struct MyPass;

impl IrPass for MyPass {
    fn name(&self) -> &str { "my_pass" }
    fn run(&mut self, module: IrModule) -> Result<IrModule, Vec<CompilerError>> {
        // transform and return
        Ok(module)
    }
}

struct MyBackend;

impl Backend for MyBackend {
    type Output = String;
    type Error = std::convert::Infallible;

    fn generate(&self, module: &IrModule) -> Result<String, Self::Error> {
        Ok(format!("// {} structs", module.structs.len()))
    }
}
```

### Error reporting

```rust
use formalang::{compile_to_ir, reporting::report_errors};

match compile_to_ir(source) {
    Ok(_) => {}
    Err(errors) => {
        eprintln!("{}", report_errors(&errors, source, "file.fv"));
    }
}
```

---

## File extension

FormaLang source files use the `.fv` extension.

---

## What is not built in

FormaLang is a pure compiler frontend. It does **not** include:

- A runtime or interpreter
- Code generation for any specific target
- A standard library (bring your own via `extern` declarations)
- A package manager

These are responsibilities of the embedding application and its backends.

---

## Further reading

- [Language Reference]docs/user/core.md: user-facing syntax and feature reference
- [Architecture]docs/developer/architecture/design.md: compiler internals
- [IR Reference]docs/developer/ir/overview.md: IrModule structure for backend authors
- [AST Reference]docs/developer/ast/overview.md: AST structure for tooling authors

---

## License

Dual-licensed under either of:

- [Apache License, Version 2.0]LICENSE-APACHE
- [MIT License]LICENSE-MIT

at your option.