commit 4db96f5e3c4b000bd799d8e05db10012efc63db8 Author: jess Date: Wed Apr 15 09:38:07 2026 -0700 Wiki means quickly in Hawaiian... Probably. diff --git a/Architecture.md b/Architecture.md new file mode 100644 index 0000000..4c1249b --- /dev/null +++ b/Architecture.md @@ -0,0 +1,116 @@ +# Architecture + +## Layers + +``` +┌────────────────────────────────────────────┐ +│ Swift app (src/) │ +│ NSWindow, NSMenu, CVDisplayLink │ +│ File I/O, document browser, settings │ +│ NSView with wgpu/Metal layer │ +└────────────────────────────────────────────┘ + │ FFI (viewport/include/acord.h) + ▼ +┌────────────────────────────────────────────┐ +│ Rust viewport (viewport/) │ +│ iced 0.14 + iced_wgpu │ +│ Editor state, blocks, table widgets │ +│ text_widget (forked text_editor) │ +│ Sidecar archive, crate export │ +└────────────────────────────────────────────┘ + │ acord-core dependency + ▼ +┌────────────────────────────────────────────┐ +│ Cordial interpreter (core/) │ +│ Tokenizer, parser, AST, evaluator │ +│ Type system, unit algebra │ +│ Numerical solver │ +│ Module system, sidecar persistence │ +└────────────────────────────────────────────┘ +``` + +## Block compositor + +- `Vec>` +- Kinds: text, heading, HR, table, computed-table, computed-tree +- Iteration over recursion for selection paths +- Layered draw order: Below / Content / Above / Top +- Source: `viewport/src/block.rs`, `viewport/src/blocks.rs` + +## TextWidget + +Forked from iced's `text_editor`. Per-line `fill_paragraph()` interleaves anchored child Elements in a sequential stream. + +- `Renderer` concretised to `iced_wgpu::Renderer` +- Source: `viewport/src/text_widget.rs` + +## Sidecar archive + +Base64-encoded ZIP in an HTML comment appended to the `.md`: + +``` + +``` + +| Entry | Contents | +| ----- | -------- | +| `config.toml` | Display metadata (col widths, row heights, cell formulas) | +| `src/.cord` | Per-block TOML front-matter + source | + +Save: regenerated fresh each write. Load: only `config.toml` consulted. Source: `viewport/src/sidecar.rs`. + +## FFI + +C ABI. Header: `viewport/include/acord.h` (cbindgen-generated). + +| Function | Purpose | +| -------- | ------- | +| `viewport_create` / `viewport_destroy` | Lifecycle | +| `viewport_render` | Draw one frame | +| `viewport_resize` | Reconfigure surface | +| `viewport_mouse_event` / `viewport_key_event` / `viewport_scroll_event` | Input | +| `viewport_set_text` / `viewport_get_text` | Document replace / read (handles sidecar) | +| `viewport_set_lang` / `viewport_set_theme` | Highlighting, theme | +| `viewport_send_command` | Menu command by integer ID | +| `viewport_export_crate` | Write standalone Rust crate | +| `viewport_render_mode` | 0=Live, 1=Editor, 2=View | +| `viewport_free_string` | Free strings returned to Swift | + +Mouse event sentinel: `button == 255` → pointer-move only. + +## Command IDs + +| ID | Command | +| -- | ------- | +| 1 | ToggleBold | +| 2 | ToggleItalic | +| 3 | InsertTable | +| 4 | SmartEval | +| 5 | Evaluate | +| 6 | TogglePreview | +| 7 | ZoomIn | +| 8 | ZoomOut | +| 9 | ZoomReset | +| 10 | FormatDocument | +| 11 | Live mode | +| 12 | Editor mode | +| 13 | View mode | + +## Cordial interpreter + +Hand-rolled tokenizer + recursive-descent parser + tree-walking evaluator. + +- `use spice` pre-scanned from source text → tokenizer mode +- Implicit multiplication: number adjacent to identifier or `(` → Star token insertion +- `solve!` registry parallel to user fns; `eval_call` checks user fns → solved_fns → builtins +- Unit algebra: pure string manipulation +- Type system: round-trip coercion + +Source: `core/src/interp.rs`, `core/src/eval.rs`. + +## See also + +- [Contributing](Contributing) +- [Cordial Modules](Cordial-Modules) diff --git a/Contributing.md b/Contributing.md new file mode 100644 index 0000000..6893857 --- /dev/null +++ b/Contributing.md @@ -0,0 +1,65 @@ +# Contributing + +## Repository + +`git.else-if.org/jess/Acord` + +## Build and test + +```sh +cargo test --workspace +./install.sh +``` + +~240 tests. Interpreter alone: ~220. + +## Project layout + +``` +Acord/ +├── core/ Cordial interpreter +│ └── src/ +│ ├── interp.rs Tokenizer, parser, evaluator, type system +│ ├── eval.rs Module evaluation pipeline +│ ├── doc.rs Line classifier +│ ├── highlight.rs Tree-sitter integration +│ ├── persist.rs State index +│ ├── ffi.rs C ABI +│ └── document.rs +├── viewport/ Rust + iced editor +│ └── src/ +│ ├── lib.rs FFI entry points +│ ├── bridge.rs Event shape conversions +│ ├── handle.rs Render loop, input dispatch +│ ├── editor.rs EditorState +│ ├── blocks.rs Markdown → block tree parser +│ ├── block.rs Block trait + LayeredView +│ ├── text_block.rs +│ ├── heading_block.rs +│ ├── hr_block.rs +│ ├── table_block.rs +│ ├── tree_block.rs +│ ├── text_widget.rs Forked text_editor + anchored compositor +│ ├── syntax.rs +│ ├── sidecar.rs +│ ├── module.rs +│ ├── selection.rs +│ ├── palette.rs +│ └── export.rs +└── src/ Swift shell + ├── main.swift + ├── AppDelegate.swift + ├── AppState.swift + ├── IcedViewportView.swift + ├── RustBridge.swift + ├── DocumentBrowserWindow.swift + ├── SettingsView.swift + ├── TitleBarView.swift + ├── ConfigManager.swift + └── Theme.swift +``` + +## See also + +- [Architecture](Architecture) +- [Cordial Reference](Cordial-Reference) diff --git a/Cordial-Modules.md b/Cordial-Modules.md new file mode 100644 index 0000000..a0e0c1b --- /dev/null +++ b/Cordial-Modules.md @@ -0,0 +1,66 @@ +# Cordial Modules + +## Boundaries + +| Marker | Effect | +| ------ | ------ | +| `# Heading` (H1) | Root module — auto-imports into every other module | +| `## Heading` (H2) | Closes current, starts a new named module | +| `---` (HR) | Closes current, starts an unnamed module | +| `### Heading` (H3) | Does not split | +| `#### Heading` (H4) | Does not split | + +## Naming + +H2 heading text → module name, lowercased and snake-cased. + +```markdown +## Trig Helpers +``` + +Module name: `trig_helpers`. + +Unnamed modules auto-name from the first `fn` or `let`, or `_unnamed_N`. + +## `use` + +```cordial +use trig_helpers +use trig_helpers::deg_to_rad +use trig_helpers::* +``` + +Resolved via topological sort. Cycles fall back to evaluation without the cyclic dependency. + +## Cross-block table references + +| Heading above a table | Visibility | +| --------------------- | ---------- | +| `### Budget` (H3) | Global | +| `#### Budget` (H4) | Local to owning module | + +```cordial +@trig_helpers::Lookup:A1 +``` + +Positional fallbacks: + +```cordial +@table_1 +@table_3:B2 +@my_module::table_2 +``` + +## Naming conventions + +| Element | Form | +| ------- | ---- | +| Note titles (filenames) | hyphen-form: `my-note.md` | +| Block / module titles | snake_form: `## Trig Helpers` | +| Unnamed blocks | `block_N` / `_unnamed_N` | + +## See also + +- [Cordial Tables](Cordial-Tables) +- [Cordial Reference](Cordial-Reference) +- [Architecture](Architecture) diff --git a/Cordial-Quickstart.md b/Cordial-Quickstart.md new file mode 100644 index 0000000..3075135 --- /dev/null +++ b/Cordial-Quickstart.md @@ -0,0 +1,111 @@ +# Cordial Quickstart + +## Eval line + +```cordial +/= 2 + 2 + → 4 +``` + +## Variables and functions in scope + +```cordial +let m = 100 +let v = 9.8 + +/= 0.5 * m * v^2 + → 4802 +``` + +## Types + +```cordial +let count: int = 42 +let bad: int = 3.7 // ERROR — lossy round-trip +``` + +Value types: `int`, `float`, `bool`, `str`. Anything else is a unit label — see [SPICE](Cordial-SPICE). + +## Functions + +Math-style, single expression: + +```cordial +let area(w, h) = w * h +let hypot(a, b) = sqrt(a^2 + b^2) + +/= area(4, 5) // → 20 +/= hypot(3, 4) // → 5 +``` + +Block-bodied: + +```cordial +fn classify(x) { + if x > 0 { + return "positive" + } else if x < 0 { + return "negative" + } + return "zero" +} + +/= classify(-7) // → "negative" +``` + +With types: + +```cordial +fn area_typed(w: float, h: float) -> float { + return w * h +} +``` + +## Control flow + +```cordial +let i = 0 +let total = 0 +while i < 10 { + total = total + i + i = i + 1 +} + +for x in [10, 20, 30] { + total = total + x +} + +for n in 1..5 { + total = total + n +} +``` + +## Implicit multiplication + +```cordial +2pi // 2 * pi +3x // 3 * x +4(a + b) // 4 * (a + b) +``` + +## Tables + +```markdown +| Item | Cost | Tax (8%) | +| ------- | ----- | ------------ | +| Coffee | 4 | /= B2 * 0.08 | +| Bagel | 3 | /= B3 * 0.08 | +``` + +```cordial +/= sum(@Items:B2:B10) +``` + +## See also + +- [Cordial Reference](Cordial-Reference) +- [Cordial Tables](Cordial-Tables) +- [Cordial SPICE](Cordial-SPICE) +- [Cordial solve!](Cordial-solve) +- [Cordial Modules](Cordial-Modules) +- [Keyboard Reference](Keyboard-Reference) diff --git a/Cordial-Reference.md b/Cordial-Reference.md new file mode 100644 index 0000000..3e47dc1 --- /dev/null +++ b/Cordial-Reference.md @@ -0,0 +1,406 @@ +# Cordial Reference + +## Eval lines + +| Prefix | Result kind | +| ------ | ----------- | +| `/=` | Inline | +| `/=\|` | Table | +| `/=\` | Tree | + +```cordial +/= 2 + 3 // → 5 +/=| @Budget +/=\ [[1,2],[3,[4,5]]] +``` + +## Literals + +### Numbers + +```cordial +42 +3.14 +-7 +1e-9 +1E+3 +.25 +``` + +### Strings + +```cordial +"hello" +"tab\there" +"quote: \"yes\"" +``` + +Escapes: `\n`, `\t`, `\\`, `\"`. + +### Booleans + +```cordial +true +false +``` + +### Arrays + +```cordial +[1, 2, 3] +[1, "two", true] +[] +``` + +### Ranges + +```cordial +0..5 // [0, 1, 2, 3, 4] +``` + +Half-open. Capped at 10,000 elements. + +### SPICE values + +Requires `use spice`. + +```cordial +100nF // [1e-7, "F"] +80Hz // [80, "HZ"] +10µF // [1e-5, "F"] +1n // [1e-9, ""] +``` + +### Implicit multiplication + +```cordial +2pi // 2 * pi +3x // 3 * x +4(a + b) // 4 * (a + b) +2 pi // PARSE ERROR +``` + +## Operators + +### Arithmetic + +```cordial +a + b +a - b +a * b +a / b +a % b +a ^ b // right-associative +-a +``` + +### Comparison + +```cordial +a == b a != b +a < b a > b +a <= b a >= b +``` + +### Logical + +```cordial +a && b or a and b +a || b or a or b +!a or not a +``` + +Short-circuit on `&&`/`and` and `||`/`or`. + +### Strip operator + +```cordial +~true // 1 +~false // 0 +``` + +Spice `[n, unit]` → `n`. Bool → 0 / 1. + +### `is` type-check + +```cordial +1 is int // → true +1.5 is int // → false +1.5 is float // → true +true is bool // → true +[1,2] is array // → true +"x" is str // → true +``` + +### Cell assignment + +Statement-level only. + +```cordial +@Budget:A2 = "Housing" +@Sales:C3 = 1500 +``` + +## Statements + +### `let` + +```cordial +let x = 5 +let y = 2 + 3 +``` + +### `let` with value-type annotation + +```cordial +let x: int = 42 +let y: float = 3.14 +let z: int = 3.7 // ERROR +let b: bool = 0 +let s: str = 42 +``` + +Value types: `int`, `float`, `bool`, `str`. + +### `let` with unit annotation + +```cordial +use spice +let cap: F = 22n // [2.2e-8, "F"] +let freq: HZ = 60 // [60, "HZ"] +let h = 10 +let hh: H = h // [10, "H"] +``` + +### Reassignment + +```cordial +let x: int = 5 +x = 10 // ok +x = 3.7 // ERROR +``` + +### `let f(x) = expr` + +```cordial +let double(x) = x * 2 +let hypot(a, b) = sqrt(a^2 + b^2) +``` + +### `fn` + +```cordial +fn area(w, h) { + w * h +} + +fn area_typed(w: float, h: float) -> float { + return w * h +} + +fn charge(c: F, v: V) -> J { + 0.5 * c * v^2 +} +``` + +### Function inversion + +Math form: + +```cordial +let solve_l(f0, c) = l where lc_freq(l, c) = f0 +``` + +Macro form: + +```cordial +let solve_l = solve!(l, lc_freq) +let solve_l = solve!(l from lc_freq) +``` + +### `if` / `else if` / `else` + +```cordial +if x > 0 { + y = 1 +} else if x < 0 { + y = -1 +} else { + y = 0 +} +``` + +### `while` + +Capped at 10,000 iterations. + +```cordial +let i = 0 +while i < 10 { + i = i + 1 +} +``` + +### `for … in …` + +```cordial +for i in 0..10 { + sum = sum + i +} + +for x in [1, 2, 3] { + y = y + x +} +``` + +### `return` + +```cordial +fn first_positive(a, b) { + if a > 0 { return a } + return b +} +``` + +### `use` + +```cordial +use spice +use other_block::my_fn +use utils::* +``` + +## Expressions + +### Function calls + +```cordial +sin(x) +sum(@Budget:B2:B20) +my_fn(a, b, c) +``` + +### Indexing + +```cordial +arr[0] +arr[-1] +"hello"[1] // "e" +``` + +### Cell references + +```cordial +@Budget +@Budget:A1 +@Budget:A2:A10 +@Budget[A2:A10] +@Calculations::Revenue:B1 +A1 // bare — in a cell formula +``` + +## Built-in functions + +### Trigonometric and transcendental + +| Function | | +| -------- | - | +| `sin`, `cos`, `tan` | radians | +| `asin`, `acos`, `atan` | inverse | +| `sqrt` | square root | +| `abs` | absolute value | +| `ln` | natural log | +| `log` | base-10 log | + +Unit-transparent. + +```cordial +sin(pi/2) // 1 +sqrt(16) // 4 +``` + +### Rounding + +```cordial +floor(3.7) // 3 +ceil(3.1) // 4 +round(3.14159, 2) // 3.14 +round(1234, -2) // 1200 +``` + +### Aggregates + +| Function | | +| -------- | - | +| `sum` | | +| `avg` | | +| `min` | | +| `max` | | +| `count` | | +| `std_devp` | population stddev | +| `std_devs` | sample stddev (≥ 2 values) | + +```cordial +sum(@Budget:B2:B20) +avg([1, 2, 3, 4]) // 2.5 +std_devs(@Sales) +``` + +### Array and string + +| Function | | +| -------- | - | +| `len(x)` | length of array or string | +| `range(start, end)` | half-open integer range as array | +| `push(arr, val)` | append, returns new array | + +```cordial +len([1, 2, 3]) // 3 +len("hello") // 5 +range(0, 5) // [0, 1, 2, 3, 4] +push([1, 2], 3) // [1, 2, 3] +``` + +## Built-in constants + +| Name | Value | +| ---- | ----- | +| `pi` | `3.141592653589793` | + +## Type system + +### Value types + +`int`, `float`, `bool`, `str`. + +| Coercion | Result | +| -------- | ------ | +| `3.0 → int` | `3` | +| `3.7 → int` | error | +| `0 → bool` | `false` | +| `1 → bool` | `true` | +| `2 → bool` | error | +| `42 → str` | `"42"` | +| `"42" → int` | `42` | +| `"3.7" → int` | error | + +### Unit types + +Any identifier that isn't a value-type is a unit label. Spice-tagged values live as `[scalar, "UNIT"]` arrays. + +## Resource limits + +| Guard | Limit | +| ----- | ----- | +| Loop iterations | 10,000 | +| Call depth | 256 | +| `solve!` iterations | 100 | +| `solve!` epsilon | `1e-10` | + +## See also + +- [Cordial Quickstart](Cordial-Quickstart) +- [Cordial Modules](Cordial-Modules) +- [Cordial Tables](Cordial-Tables) +- [Cordial SPICE](Cordial-SPICE) +- [Cordial solve!](Cordial-solve) diff --git a/Cordial-SPICE.md b/Cordial-SPICE.md new file mode 100644 index 0000000..4016e1b --- /dev/null +++ b/Cordial-SPICE.md @@ -0,0 +1,109 @@ +# Cordial: SPICE Notation + +## Enable + +```cordial +use spice +``` + +## SI prefixes + +| Prefix | Multiplier | Name | +| ------ | ---------- | ---- | +| `m` / `M` | `×10⁻³` | milli | +| `u` / `U` / `µ` / `μ` | `×10⁻⁶` | micro | +| `n` / `N` | `×10⁻⁹` | nano | +| `p` / `P` | `×10⁻¹²` | pico | + +## Units + +| Unit | Quantity | +| ---- | -------- | +| `F` | Farads | +| `H` | Henries | +| `Hz` | Hertz | +| `V` | Volts | +| `A` | Amperes | +| `W` | Watts | +| `R` / `Ohm` | Ohms | +| `S` | Siemens | +| `J` | Joules | + +Case-insensitive; uppercased on display. + +## Literals + +```cordial +use spice + +100nF // 1e-7 F +80Hz // 80 Hz +10µF // 1e-5 F +22n // 2.2e-8 with no unit +``` + +Internally: `[scalar, "UNIT"]`. + +## Unit annotations + +```cordial +let cap: F = 22n // [2.2e-8, "F"] +let freq: Hz = 60 // [60, "HZ"] +let h = 10 +let inductance: H = h // [10, "H"] +``` + +## Unit algebra + +| Op | Rule | +| -- | ---- | +| `*` | same → squared; different → `A·B`; one-sided → carry | +| `/` | same → drop unit; one-sided → `A/X` or `1/X` | +| `+`, `-`, `%` | same or one-sided → carry; different → drop | +| `^` | `F²`, `F³`, `√F`, `F^N` | + +```cordial +2F * 3H // [6, "F·H"] +6F / 3F // 2 +6F / 2H // [3, "F/H"] +1F + 2F // [3, "F"] +1F + 2H // 3 — labels dropped +(2F)^2 // [4, "F²"] +``` + +## Function annotations + +```cordial +use spice + +fn charge(c: F, v: V) -> J { + return 0.5 * c * v^2 +} + +/= charge(100uF, 12V) + → 7.2MJ +``` + +## Display formatting + +3 significant figures. Closest SI prefix so mantissa ∈ `[1, 1000)`. Unit uppercased. + +``` +1.5e-7 F → 150NF +2.2e-5 F → 22UF +[707e-3, "F/H"] → 707M F/H +[5.0, "F²"] → 5 F² +``` + +## Strip operator + +```cordial +let c = 22nF // [2.2e-8, "F"] +let raw = ~c // 2.2e-8 +``` + +## See also + +- [Cordial solve!](Cordial-solve) +- [Cordial Reference](Cordial-Reference) +- [Cordial Tables](Cordial-Tables) diff --git a/Cordial-Tables.md b/Cordial-Tables.md new file mode 100644 index 0000000..7c539e5 --- /dev/null +++ b/Cordial-Tables.md @@ -0,0 +1,89 @@ +# Cordial Tables + +## Insert a table + +**Cmd+T** at the cursor: + +```markdown +| Header 1 | Header 2 | Header 3 | +| -------- | -------- | -------- | +| | | | +``` + +## Gestures + +| Gesture | Action | +| ------- | ------ | +| Click | Select cell | +| Double-click | Enter edit mode | +| Tab / Enter | Navigate while editing | +| Esc | Leave edit mode | +| Cmd / Shift / Cmd+Shift + click | Toggle / Add / Remove in multi-cell selection | +| Drag | Rectangular selection | +| Corner cell click | Whole-table select | +| Right-click | Context menu | +| Column header edge | Column resize | +| Double-click a divider | Auto-fit | + +## Cell formulas + +``` +| Item | Cost | Tax (8%) | +| ------- | ----- | ------------ | +| Coffee | 4 | /= B2 * 0.08 | +| Bagel | 3 | /= B3 * 0.08 | +``` + +Bare `A1`-style identifiers inside a formula resolve to the same table. + +## Naming a table + +```markdown +### Budget + +| Category | Amount | +| -------- | ------ | +| Rent | 1500 | +| Food | 600 | +``` + +```cordial +/= sum(@Budget:B2:B3) // → 2100 +``` + +H3 → global; H4 → module-local. + +## Cell references from text blocks + +```cordial +@Budget // whole table → 2D array +@Budget:A1 // single cell +@Budget:A2:B5 // rectangular range +@Budget[A2:B5] // alternative range syntax +@Calculations::Revenue:B1 // cross-block +``` + +## Mutation + +```cordial +@Budget:C2 = "subtotal" +@Budget:C3 = sum(@Budget:B2:B5) +``` + +Statement-form only. + +## Persistence + +Markdown is the source of truth. Rich metadata — column widths, row heights, cell formulas — round-trips through a base64-encoded ZIP appended to the file inside an HTML comment: + +``` + +``` + +## See also + +- [Cordial Reference](Cordial-Reference) +- [Cordial Modules](Cordial-Modules) +- [Keyboard Reference](Keyboard-Reference) diff --git a/Cordial-solve.md b/Cordial-solve.md new file mode 100644 index 0000000..ad239ed --- /dev/null +++ b/Cordial-solve.md @@ -0,0 +1,60 @@ +# Cordial: solve! + +## Math form + +```cordial +let solve_for_l(f0, c) = l where lc_freq(l, c) = f0 +``` + +## Macro form + +```cordial +let solve_for_l = solve!(l, lc_freq) +let solve_for_l = solve!(l from lc_freq) +``` + +## Featured example — LC tank inversion + +```cordial +use spice + +// In Cordial, types can be arbitrary and are therefore assumed to be units +// when `use spice` is active. Hz, F, H are recognised ECE units; you could +// equally write `x: C` for Coulombs and the unit gets appended. +fn L(f: Hz, c: F) -> H { + return 1 / (((2pi * f)^2) * c) +} + +// Math form: reads as an equation, mirrors how you'd write the algebra. +let fx(L, c) = f where L(f, c) = L + +// Macro form: cleaner shorthand. Same result. +let f0 = solve!(f, L) + +/= L(1000, 22n) + → 1.15H + +/= f0(1.15H, 22n) + → ~1000 +``` + +## Solver parameters + +| Guard | Limit | +| ----- | ----- | +| Newton iterations | 100 | +| Convergence epsilon | `1e-10` | +| Initial guess | `1.0` | +| Damping | step-halving on NaN / Inf / error | + +## Limits + +- Single variable. +- Continuous, differentiable source function. +- Symbolic inversion not implemented — numerical backend only. + +## See also + +- [Cordial Reference](Cordial-Reference) +- [Cordial SPICE](Cordial-SPICE) +- [Cordial Modules](Cordial-Modules) diff --git a/Crate-Export.md b/Crate-Export.md new file mode 100644 index 0000000..cc1cc35 --- /dev/null +++ b/Crate-Export.md @@ -0,0 +1,77 @@ +# Crate Export + +## Trigger + +**File ▸ Export as Rust Library…** or **Cmd+Shift+E**. Folder + crate name prompt via `NSSavePanel`. + +## Output + +``` +my-note/ +├── Cargo.toml +├── build.sh +├── install.sh +├── README.md +├── .gitignore +└── src/ + ├── main.rs + ├── lib.rs + └── blocks/ + ├── block_N.cord + └── snake_name.cord +``` + +| File | Contents | +| ---- | -------- | +| `Cargo.toml` | lib + bin, git dep on `acord-core` | +| `build.sh` | `cargo build --release` | +| `install.sh` | Copy binary to `~/.acord/bin`, print PATH hints | +| `README.md` | Block inventory | +| `src/main.rs` | REPL (`:list` shows bindings, `:q` quits) | +| `src/lib.rs` | `pub fn load() -> Interpreter` | +| `src/blocks/*.cord` | One file per block | + +Crate-name normalisation: lowercased, `_` → `-`, non-alphanumeric stripped. + +## Run the exported crate + +```sh +cd my-note +./build.sh +./install.sh +my-note +``` + +REPL: + +``` +> ke(100, 9.8) +4802 +> :list +m: number = 100 +v: number = 9.8 +ke: fn(m, v) +> :q +``` + +## Embed in another crate + +```toml +[dependencies] +my-note = { path = "../my-note" } +``` + +```rust +use my_note; + +fn main() { + let mut interp = my_note::load(); + let result = interp.eval_expr_str("ke(50, 7.0)").unwrap(); + println!("{}", result.display()); +} +``` + +## See also + +- [Cordial Modules](Cordial-Modules) +- [Architecture](Architecture) diff --git a/Document-Browser.md b/Document-Browser.md new file mode 100644 index 0000000..1394984 --- /dev/null +++ b/Document-Browser.md @@ -0,0 +1,54 @@ +# Document Browser + +## Open + +**Ctrl+B** toggles the browser window. + +## Storage + +Default: `~/.acord/notes/`. Configurable in Settings (`autoSaveDirectory`). Subdirectories render as folders. + +Titled notes: `.md`. Untitled: `<uuid>.md`. + +## Layout + +| Element | | +| ------- | - | +| Breadcrumb bar | Clickable path segments | +| Cards | File preview (first 20 lines, sidecar stripped) or folder summary | +| Single click | Select | +| Double-click | Open file or enter folder | +| Drag | Move file onto folder | +| Right-click card | Open, Rename, Duplicate, Move to Trash, Reveal in Finder | +| Right-click folder | Open, Rename, Move to Trash, Reveal in Finder | +| Right-click empty area | New Folder, Reveal in Finder | + +## Card scaling + +Cmd+= / Cmd+- / Cmd+0 when the browser is key. + +## Autosave + +Writes every 100ms. Two paths: + +- Viewport timer — reads straight from the editor, bypasses the data binding. +- `didSet` — on programmatic text change. + +Skipped when the note is "effectively blank" (empty or default untouched table). + +## Settings + +| Setting | Options | +| ------- | ------- | +| Theme | Auto, Dark (mocha), Light (latte) | +| Line Numbers | On, Off, Vim | +| Auto-Save directory | Folder picker | +| Zoom level | Cmd+= / Cmd+- / Cmd+0 | + +Stored in `~/.acord/config.json`. + +## See also + +- [Install](Install) +- [The Editor](The-Editor) +- [Keyboard Reference](Keyboard-Reference) diff --git a/First-Note.md b/First-Note.md new file mode 100644 index 0000000..fd9c41f --- /dev/null +++ b/First-Note.md @@ -0,0 +1,62 @@ +# First Note + +## Open the app + +```sh +open /Applications/Acord.app +``` + +## Write markdown + +```markdown +# Today's notes + +Some thoughts. + +| Quantity | Value | +| -------- | ----- | +| Mass | 100 | +| Velocity | 9.8 | +``` + +**Cmd+T** inserts a table. + +## Evaluate a line + +```cordial +/= 2 + 2 + → 4 + +/= sin(pi / 2) + → 1 +``` + +```cordial +let m = 100 +let v = 9.8 + +/= 0.5 * m * v^2 + → 4802 +``` + +```cordial +fn ke(m, v) { + return 0.5 * m * v^2 +} + +/= ke(100, 9.8) + → 4802 +``` + +## Save + +**Cmd+S**. Autosave also runs every 100ms to `~/.acord/notes/`. + +## See also + +- [Cordial Quickstart](Cordial-Quickstart) +- [Cordial Reference](Cordial-Reference) +- [Cordial Tables](Cordial-Tables) +- [Cordial SPICE](Cordial-SPICE) +- [Cordial solve!](Cordial-solve) +- [Keyboard Reference](Keyboard-Reference) diff --git a/Home.md b/Home.md new file mode 100644 index 0000000..c43f976 --- /dev/null +++ b/Home.md @@ -0,0 +1,72 @@ +# Acord + +Here's the 'sales' pitch: + +"Hi there, do you enjoy casually solving project euler problems and are tired of using the spotlight bar as your primary calculator?" - Then this might be your kinda thing. + +First, but least importantly, Acord is a native markdown editor... with a 'small' expression language baked in. + +- Lines starting with `/=` evaluate — the result is printed inline below it. +- Tables are real, editable, and can carry formulas. +- Runs on Rust + iced + wgpu in a Swift shell on macOS. +- Sports my wicked cool Rust DSL, Cordial. Trust me, you'll love it. It's like Rust but you never have to remember any semi-colons. Don't panic - indentation, case, these things don't matter much either. It's a sensible, salad days sort of syntax. + +The war - against the evil, as always. +Here's an analogy. Against the evil that is unsavory programming languages and — things... those things I don't like. Just generally, "things," sucky things. Evil. +Against this evil, let's say Rust is the best weapon made by humankind. It's what you need, it's the truth, it is the goodness within us all. +Python sure wanted to help, and golly, in its own ways it has. It was something like... the hand grenade. Effective at the start of the war, devastating it both its sweeping victories and its massive shortcomings. But I am grateful nonetheless, they did the trick when they were most needed. Not evil. Just bad. Normal bad. If the enemy is evil and the other side is good, that makes warfare `just` bad. This is just an analogy. I'm no expert on wars, or analogies for that matter. So bear with me. +Well, assuming everything I have said is true and just, then this, +- Cordial - +...would be like a garden hoe. Sure, you'll find plenty of them in a war. But that's just cuz lots of the country is farmland. Sure, you could do some damage with it. But mostly, its a garden hoe. It just does its job. Its no swiss army knife, nor scalpel, or even X-acto. It's the pocket knife you have, the one in your hand cuz there's an apple to cut and there's no need to get out the good knife, both the tool and the tool-be'd are already in hand, they'll handle it... man. + +Happy? Me too. + +I built Acord because I needed something between vim and the IDE — not a webapp (VS Code), not a stack of plugins held together with hope. A real, focused, native tool for thinking in. Notes I write turn into running calculators turn into working code, all in the same file. + +## What you can do here + +Math + +```cordial +let m = 100 +let v = 9.8 +/= 0.5 * m * v^2 + → 4802 +``` + +With `use spice` enabled, write with units and SPICE conventions. + +```cordial +use spice + +fn L(f: Hz, c: F) -> H { + return 1 / (((2pi * f)^2) * c) +} + +/= L(1000, 22n) + → 1.15H +``` + +Take any function and invert it on a chosen variable in one line: + +```cordial +let f0 = solve!(f, L) + +/= f0(1.15H, 22n) + → 1000 +``` + +Tables work the way you'd expect from Numbers or Excel: cells, formulas, ranges, references between tables, mutation from text blocks. Headings carry scope, so a table under `### Budget` becomes `@Budget` from anywhere in the document. + +## Where to start + +- **Install** [Install](Install) → [First Note](First-Note) +- **Cordial Language** [Cordial Quickstart](Cordial-Quickstart) → [Cordial Reference](Cordial-Reference) + - [Tables and formulas](Cordial-Tables) + - [SPICE notation for engineering values](Cordial-SPICE) + - [solve! for inverting functions](Cordial-solve) + - [Architecture](Architecture) → [Contributing](Contributing) + +## Status + +macOS only for now (Apple Silicon, macOS 14+). Windows is the next target. As in, I just installed a VM, will be done by the weekend. diff --git a/Install.md b/Install.md new file mode 100644 index 0000000..d009272 --- /dev/null +++ b/Install.md @@ -0,0 +1,40 @@ +# Install + +## Requirements + +- macOS 14 (Sonoma) or later, Apple Silicon +- Xcode command-line tools: `xcode-select --install` +- Rust toolchain via [rustup](https://rustup.rs) +- `rsvg-convert`: `brew install librsvg` + +## Build and install + +```sh +git clone https://git.else-if.org/jess/Acord.git +cd Acord +./install.sh +``` + +Build without installing: + +```sh +./build.sh +``` + +## Where things live + +| Path | | +| ---- | - | +| `/Applications/Acord.app` | App bundle | +| `~/.acord/notes/` | Auto-saved notes | +| `~/.acord/config.json` | Settings | + +## Other platforms + +Windows: in progress. Linux: not planned. + +## See also + +- [First Note](First-Note) +- [Cordial Quickstart](Cordial-Quickstart) +- [Keyboard Reference](Keyboard-Reference) diff --git a/Keyboard-Reference.md b/Keyboard-Reference.md new file mode 100644 index 0000000..18140fa --- /dev/null +++ b/Keyboard-Reference.md @@ -0,0 +1,130 @@ +# Keyboard Reference + +## File + +| Combo | Action | +| ----- | ------ | +| Cmd+N | New window | +| Cmd+Shift+N | New note | +| Cmd+O | Open… | +| Cmd+S | Save | +| Cmd+Shift+S | Save As… | +| Cmd+Shift+E | Export as Rust Library | +| Cmd+, | Settings… | +| Cmd+Q | Quit | + +## Edit + +| Combo | Action | +| ----- | ------ | +| Cmd+Z | Undo | +| Cmd+Shift+Z | Redo | +| Cmd+X / Cmd+C / Cmd+V | Cut / Copy / Paste | +| Cmd+A | Select all (escalating) | +| Cmd+B | Bold wrap | +| Cmd+I | Italic wrap | +| Cmd+T | Insert table | +| Cmd+E | Smart eval | +| Cmd+P | Toggle markdown preview | +| Cmd+F | Toggle find/replace | +| Cmd+G | Find next | +| Cmd+Shift+G | Find previous | +| Cmd+Shift+F | Format document | + +Cmd+A: first press block-local; second press whole-document. + +## Zoom + +| Combo | Action | +| ----- | ------ | +| Cmd+= / Cmd++ | Zoom in | +| Cmd+- | Zoom out | +| Cmd+0 | Actual size | + +## Modes + +| Combo | Action | +| ----- | ------ | +| Ctrl+I | Editor mode | +| Ctrl+/ | Live mode | +| Ctrl+Esc | View mode | +| `i` (View) | Editor | +| `/` (View) | Live | +| Esc | Chain: dismiss menu → dismiss find → exit cell edit → cycle mode | + +## Text navigation + +| Combo | Action | +| ----- | ------ | +| Cmd+Up / Cmd+Down | Document start / end | +| Cmd+Shift+Up / Cmd+Shift+Down | Select to document start / end | +| Opt+Backspace / Opt+Delete | Delete prev / next word | +| Tab / Shift+Tab | Indent / outdent 4 spaces | +| Arrow / Home / End / PgUp / PgDn | Standard motion | +| Ctrl+B / Ctrl+F | Left / right (Emacs) | +| Ctrl+A / Ctrl+E | Line start / end (Emacs) | +| Ctrl+H / Ctrl+D | Backspace / Delete (Emacs) | + +## Whole-document (after Cmd+A escalation) + +| Combo | Action | +| ----- | ------ | +| Backspace / Delete | Clear every block's content | +| Cmd+Backspace | Wipe document to one empty text block | + +## Table cell (selected, not editing) + +| Gesture | Action | +| ------- | ------ | +| Click | Select cell | +| Double-click | Enter edit mode | +| Cmd+click | Toggle in selection | +| Shift+click | Add to selection | +| Cmd+Shift+click | Remove from selection | +| Drag | Rectangular selection | +| Right-click | Context menu | +| Corner cell click | Whole-table select | +| Column letter click | Column reorder drag | +| Row number click | Row reorder drag | +| Column header edge drag | Resize column | +| Double-click on divider | Auto-fit | +| Printable char | Overwrite + enter edit | +| Tab / Shift+Tab | Focus next / prev cell (Tab adds column at end of row) | +| Enter | Focus next row (adds row at last) | +| Arrow Up / Down | Move focus; edge-escapes to text block | +| Arrow Left / Right | Move focus within row | +| Cmd+Opt+Arrow Up / Down | Insert row above / below | +| Cmd+Opt+Arrow Left / Right | Insert column left / right | +| Cmd+Opt+Backspace | Delete current row | +| Cmd+Opt+Shift+Backspace | Delete current column | +| Backspace / Delete (single cell) | Empty the cell | +| Backspace / Delete (whole-table) | Clear every cell | +| Cmd+Backspace (whole-table) | Delete table | +| Esc | Exit edit mode | + +## Find / replace bar + +| Combo | Action | +| ----- | ------ | +| Cmd+F | Toggle bar | +| Enter (in find) | Find next | +| Enter (in replace) | Replace one | +| Cmd+G / Cmd+Shift+G | Next / previous | +| Esc | Hide bar | + +## Menu bar + +| Menu | Items | +| ---- | ----- | +| Acord | About Acord, Settings…, Quit Acord | +| File | New Window, New Note, Open…, Save, Save As…, Export as Rust Library…, Open Storage Directory | +| Edit | Undo, Redo, Cut, Copy, Paste, Select All, Bold, Italic, Insert Table, Smart Eval, Find…, Format Document | +| Render | Live, Editor, View | +| View | Document Browser (Ctrl+B), Zoom In, Zoom Out, Actual Size | +| Window | Minimize, Zoom | + +## See also + +- [The Editor](The-Editor) +- [Cordial Tables](Cordial-Tables) +- [Document Browser](Document-Browser) diff --git a/The-Editor.md b/The-Editor.md new file mode 100644 index 0000000..34b1935 --- /dev/null +++ b/The-Editor.md @@ -0,0 +1,44 @@ +# The Editor + +## Modes + +| Mode | Behaviour | +| ---- | --------- | +| Live (default) | Blocks rendered, eval runs on a 300ms debounce, tables interactive | +| Editor | Raw markdown, single text editor, no eval | +| View | Read-only rendered. `i` → Editor, `/` → Live | + +## Eval-line forms + +| Prefix | Result | +| ------ | ------ | +| `/=` | Inline value (or `⚠` error) | +| `/=\|` | Read-only computed table | +| `/=\` | Tree widget | + +## Title bar + +Double-click the title to edit. Enter commits; Esc cancels. If the document's first line is `# heading`, it is rewritten; otherwise a fresh `# title` line is prepended. + +## Find / replace + +**Cmd+F** toggles. Live `current/total` match count. Case-insensitive. Cmd+G / Cmd+Shift+G for next / previous. Esc hides. + +## Smart edits + +- Smart-backspace: deletes back to the previous 4-space tab stop in leading whitespace. +- Auto-indent on Enter. +- Auto-indent after `{` / `[` / `(` on line end. +- Auto-dedent on `}` / `]` / `)` typed at a whitespace-only prefix. +- Tab / Shift+Tab indent / outdent to the nearest 4-space stop. + +## Undo / redo + +200-entry bounded stack. Edits within 500ms of the previous same-kind edit coalesce. Structural operations push an explicit snapshot. + +## See also + +- [Keyboard Reference](Keyboard-Reference) +- [Cordial Reference](Cordial-Reference) +- [Document Browser](Document-Browser) +- [Architecture](Architecture) diff --git a/_Sidebar.md b/_Sidebar.md new file mode 100644 index 0000000..40abdb8 --- /dev/null +++ b/_Sidebar.md @@ -0,0 +1,29 @@ +**Acord** + +- [Home](Home) +- [Install](Install) +- [First Note](First-Note) + +**The editor** + +- [The Editor](The-Editor) +- [Document Browser](Document-Browser) +- [Keyboard Reference](Keyboard-Reference) + +**Cordial** + +- [Quickstart](Cordial-Quickstart) +- [Reference](Cordial-Reference) +- [Modules](Cordial-Modules) +- [Tables](Cordial-Tables) +- [SPICE notation](Cordial-SPICE) +- [solve!](Cordial-solve) + +**Power tools** + +- [Crate Export](Crate-Export) + +**Contributors** + +- [Architecture](Architecture) +- [Contributing](Contributing)