Files
avcf/README.md
T

518 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Visual Programming Framework
A browser-based flowchart-to-C code generator with typed measurement system, procedure libraries, and live MathJax-rendered expressions.
## Architecture
The app is built as vanilla HTML/CSS/JS with no framework. All state lives in the DOM (custom elements) and is serialised to localStorage.
### File dependency order (bottom to top)
```
index.html ← entry point, templates, page layout
│
dom.js ← $, $id, svgNamespace, STORAGE_KEY (global constants)
helpers.js ← data-source lookups, string escaping, dropdown refresh
error.js ← modal error overlay
draw.js ← SVG path routing, link interaction, path deletion
│
wcblock.js ← WCBlock base class (drag/drop, link system)
blocks-algo.js ← Algorithm tab blocks (WCStart, WCEnd, WCOutput, WCDecision,
│ WCAssign, WCExpression, WCVarRef)
blocks-data.js ← Data & Measurement tab blocks (WCQuantity, WCUnit, WCBoolean,
│ WCScalar, WCString, WCArray)
blocks-proc.js ← Procedure blocks (WCProcedure, WCProcCall)
wcprogram.js ← WCProgram + block registry + registration calls
│
persist.js ← localStorage save/load/new
app.js ← bootstraps the workspace, creates defaults, wires UI
```
### Module overview
| File | Lines | Exports | Purpose |
|------|-------|---------|---------|
| `dom.js` | 23 | `$`, `$id`, `svgNamespace`, `STORAGE_KEY` | Query shortcuts and app-wide constants |
| `helpers.js` | ~240 | 12 functions | Data-source lookup, string escaping, dropdown refresh utilities |
| `error.js` | 32 | `showError`, `closeError` | Error overlay modal |
| `draw.js` | 208 | 6 functions | SVG path routing, shadow link, link creation/deletion |
| `wcblock.js` | ~236 | `WCBlock` | Base class: drag/drop, link system, code traversal |
| `blocks-algo.js` | ~460 | 7 classes | Algorithm tab blocks (start, end, output, decision, assign, expression, varref) |
| `blocks-data.js` | ~570 | 6 classes | Data & Measurement tab blocks (quantity, unit, boolean, scalar, string, array) |
| `blocks-proc.js` | ~280 | 2 classes | Procedure blocks (definition, call) |
| `wcprogram.js` | ~370 | `WCProgram` + registry | Root container, ID counter, node factory, code generation, block registration |
| `persist.js` | ~245 | `saveWorkspace`, `loadWorkspace`, `newWorkspace`, helpers | localStorage serialisation |
| `app.js` | ~390 | 7 functions + `main()` | Application bootstrap, OpenGL defaults, event wiring |
| `style.css` | ~480 | — | All styles |
| `index.html` | ~380 | — | Page layout + `<template>` definitions for all block types |
## Block types
All live blocks are custom elements registered via `customElements.define()`. Each block type has a `<template id="wc-xxx">` in `index.html`.
### Flowchart blocks (Algorithm tab)
| Tag | Class | Purpose | Hooks |
|-----|-------|---------|-------|
| `wc-start` | WCStart | Program entry point | 1 logic out |
| `wc-end` | WCEnd | Program termination | 1 logic in |
| `wc-output` | WCOutput | `printf()` statement | 1 logic in, 1 logic out |
| `wc-decision` | WCDecision | `if/else` branch | 1 logic in, 2 logic out (yes/no), 1 data in (boolean) |
| `wc-varref` | WCVarRef | Read-only variable reference | 1 data out |
| `wc-expression` | WCExpression | Math expression with live LaTeX | N data in (dynamic vars), 1 data out |
| `wc-assign` | WCAssign | Variable assignment | 1 logic in, 1 logic out, 1 data in |
| `wc-proccall` | WCProcCall | Procedure call | 1 logic in, 1 logic out, N data in/out (dynamic params) |
### Measurement blocks (Measurements tab)
| Tag | Class | Purpose |
|-----|-------|---------|
| `wc-quantity` | WCQuantity | Defines a quantity type (7 SI dimensions + range + default unit) |
| `wc-unit` | WCUnit | Defines a unit with recursive SI factor resolution |
### Variable blocks (Data tab)
| Tag | Class | Purpose | dataType |
|-----|-------|---------|----------|
| `wc-boolean` | WCBoolean | Boolean variable (`int` in C) | `"boolean"` |
| `wc-scalar` | WCScalar | Typed scalar with unit/SI conversion | `"scalar"` |
| `wc-string` | WCString | String variable (char*/length struct) | `"string"` |
| `wc-array` | WCArray | Typed C array with comma-separated values | `"array"` |
### Procedure blocks (Procedures tab)
| Tag | Class | Purpose |
|-----|-------|---------|
| `wc-procedure` | WCProcedure | Procedure definition with typed params |
| `wc-proccall` | WCProcCall | Call to a defined procedure |
### Container block (Algorithm tab)
| Tag | Class | Purpose |
|-----|-------|---------|
| `wc-program` | WCProgram | Root container: ID counter, node factory, code generation |
## Key mechanisms
### Class hierarchy
```
HTMLElement
├── WCBlock (base: drag/drop, link system, code traversal)
│ ├── WCStart, WCEnd, WCOutput
│ ├── WCDecision
│ ├── WCQuantity, WCUnit
│ ├── WCBoolean, WCScalar, WCString, WCArray
│ ├── WCVarRef, WCExpression
│ ├── WCAssign
│ ├── WCProcedure, WCProcCall
└── WCProgram (not a WCBlock — no drag/link)
```
### Two-click linking
1. Click an output hook → starts a shadow (dashed) path that follows the cursor
2. Click a compatible input hook → creates a persistent SVG path
3. Click an existing path → deletes it
4. Click the draw-area background → cancels the pending link
Type checking is enforced: logic hooks only connect to logic hooks, data hooks only to data hooks of the same `dataType`.
### Grid-snapped drag
Blocks use CSS `--grid-size` (25px) for snap-to-grid positioning. All connection paths are redrawn in real-time during drag via `drawpath()`.
### 45°-constrained path routing
Paths use `routeBetween()` which produces SVG polylines constrained to 0°, 45°, and 90° angles. Each path extends `EXT` (25px) from the source hook in its exit direction before routing.
### Persistence
All workspace state is serialised to `localStorage` under key `"workspace"`:
- Block positions + type-specific data
- Connection topology (by block ID + hook ID)
- ID counter for generating unique block IDs
The `loadWorkspace()` function follows a strict 7-step order:
1. Tear down existing state
2. Restore measurements (types + units)
3. Restore procedures and library tabs
4. Restore algorithm blocks (reposition start/end, recreate others)
5. Restore data/variable blocks
6. Restore SVG connections (hook resolution uses IDs or fallbacks)
7. Refresh all dropdowns and SI displays
### MathJax expression rendering
WCExpression uses `MathJax.typesetPromise()` to render LaTeX as rendered HTML. Variable hooks are rebuilt synchronously (before the async MathJax call) so that connection restoration can find them by deterministic ID (`"wc-expr-N-vh-varname"`).
## Code generation
`WCProgram.generateCode()` produces a complete C source file:
```
// --- WiseCode® ---
#include <library.h> // for referenced procedure libraries
#include <stdlib.h>
#include <string.h> // if string variables exist
int main() {
// Type definitions (typedef struct for used quantities)
// Unit definitions (comments)
// string struct definition (if needed)
// Variable declarations (booleans, scalars, strings, arrays)
// Expression declarations
// Flowchart body (start → end traversal)
// Free string allocations
return 0;
}
// --- END ---
```
The `generateCode(visited)` method on each block uses a `Set` to track already-emitted blocks, handling fan-out convergence (when two paths merge).
### Code generation for OpenGL procedures
When the workflow includes `wc-proccall` blocks referencing a library like `gl`, `glfw`, or `glad`, the generated code includes:
```c
#include <gl.h>
#include <glfw.h>
#include <glad.h>
```
The include path uses the library name directly (`<gl.h>`, `<glfw.h>`, `<glad.h>`). Real OpenGL headers use different paths (e.g. `<GLFW/glfw3.h>`). Adjust the includes in the generated output to match your build environment.
**Output parameter convention**: All procedure output parameters are generated with a `&` prefix in the C call, e.g.:
- `glCreateShader(GL_VERTEX_SHADER, &shader);` — generated when `shader` is an output param
- `glGenBuffers(1, &VBO);` — generated when `VBO` is an output param
For OpenGL functions that natively return values (like `glCreateShader` which returns `GLuint`), the generated code uses a pointer-output convention. A thin wrapper function can adapt if needed:
## SI unit system
Units form a directed acyclic graph rooted at SI base units:
- Each unit stores an SI definition string (e.g. `"0.0254"` or `"12 in"`)
- `getSiFactor()` recursively resolves the conversion factor with cycle detection
- Scalars auto-convert to SI values for code generation
- The unit dropdown on scalars filters by the selected quantity type
## Shared utility functions (helpers.js)
| Function | Purpose | Used by |
|----------|---------|---------|
| `splitValueAndUnit()` | Parse "5 km" → `{numVal, unitSuffix}` | WCScalar, WCUnit |
| `rebuildSelect()` | Replace `<select>` options preserving selection | All refresh functions |
| `refreshQuantityOptions()` | Sync quantity-name dropdowns | WCQuantity, setup |
| `refreshUnitOptions()` | Sync unit-suffix dropdowns | WCScalar, setup |
| `refreshDefaultUnitOptions()` | Sync default-unit dropdowns | WCQuantity, setup |
| `getDefaultUnitSuffix()` | Lookup default unit for a quantity | WCScalar |
| `refreshVarRefOptions()` | Sync variable-select dropdowns | WCVarRef, WCAssign, setup |
| `refreshProcOptions()` | Sync procedure-select dropdowns | WCProcCall, setup |
| `switchLibTab()` | Activate a library sub-tab | UI |
| `findDataSource()` | Find block connected to a data hook | WCDecision, WCAssign, WCProcCall |
| `escapeString()` | Escape chars for C string literals | WCString, WCAssign |
| `refreshAll()` | Run all refresh passes in order | Various |
## CSS architecture
- CSS custom properties for theming (`--grid-size`, `--bg`, `--accent`, etc.)
- Each block type has a distinct background/border color pair
- Hooks use absolute positioning with `data-direction` and `data-exit` attributes
- SVG connection layer sits below blocks (`z-index: 0`)
- Grid background via CSS `background-image` linear-gradient
## Array data type (wc-array)
The `wc-array` custom element provides a typed C array declaration in the Data tab with a tabular element editor. It generates `type name[] = {values};`.
### Tabular element editor
Elements are displayed as a table of rows inside the block:
```
┌───────────────────────────────────────────┐
│ [arr ] [float ▼] [] 9 elements │
├───────────────────────────────────────────┤
│ 0 [ -0.5 ] [×] │
│ 1 [ -0.5 ] [×] │
│ 2 [ 0.0 ] [×] │
│ ... │
│ 8 [ 0.0 ] [×] │
│ 9 [ ] │ ← empty row for new elements
└───────────────────────────────────────────┘
```
Each element row shows:
- **Index badge**: zero-based element number (monospace, muted colour)
- **Value input**: editable text field for the element's literal value
- **Delete button** (&times;): removes that element and renumbers all subsequent rows
### Adding elements
The bottom row of the table is always an empty input. To add a new element:
1. Type the value into the empty input
2. Press **Enter**, or **tab/blur** away from the input
3. The value is appended to the array and a new empty row appears
### Removing elements
Click the **&times;** button on any row. The element is removed and all remaining rows are re-indexed starting from 0.
### Element count label
The header row shows a live count label (e.g. `"9 elements"`, `"1 element"`). This updates immediately when elements are added or removed.
### Template (`index.html`)
```html
<template id="wc-array">
<wc-array>
<div class="array-row">
<input class="var-name" placeholder="name" value="arr">
<select class="array-type">
<option value="float">float</option>
<option value="double">double</option>
<option value="int">int</option>
<option value="unsigned int">unsigned int</option>
<option value="char">char</option>
</select>
<span class="array-bracket">[]</span>
<span class="array-count">0 elements</span>
</div>
<div class="array-elements">
<!-- Element rows rendered dynamically by WCArray._renderElements() -->
</div>
</wc-array>
</template>
```
Fields:
- **var-name**: the C identifier for the array (default: `"arr"`)
- **array-type**: the C element type selector (float, double, int, unsigned int, char)
- **array-count**: auto-updating label showing the element count
- **array-elements**: container for the dynamic element rows (populated by JS)
### Class (blocks-data.js)
```js
class WCArray extends WCBlock {
get dataType() { return "array"; }
constructor() { super(); this._values = []; }
connectedCallback() { /* binds DOM refs + renders initial state */ }
// Row rendering
_renderElements() { /* rebuilds all rows from this._values */ }
_makeElementRow(i) { /* creates one populated value row */ }
_makeNewRow() { /* creates the empty add-element row */ }
// Mutation
_addElement(val) { this._values.push(val); this._renderElements(); }
_removeElement(idx) { this._values.splice(idx, 1); this._renderElements(); }
_updateCount() { /* sets the count label text */ }
// Serialisation
saveData() → { name, type, values: [...] }
restoreData(d) → { /* handles both string (legacy) and array format */ }
// Code gen
generateOwnCode() → "type name[] = {val1, val2, ...};"
}
```
Key behaviours:
- `_values` is an internal `Array` of strings, one per element
- `_renderElements()` is called after every mutation and from `connectedCallback` (via `setTimeout`) and `restoreData`
- Focus position is preserved across `_renderElements()` calls so editing feels seamless
- Event handlers use `input.closest()` / `delBtn.closest()` to safely identify the row index from the DOM
### Code generation
| Input | Generated C |
|-------|-------------|
| name=`"vertices"`, type=`"float"`, values=`["-0.5","-0.5","0.0","0.5","-0.5","0.0","0.0","0.5","0.0"]` | `float vertices[] = {-0.5, -0.5, 0.0, 0.5, -0.5, 0.0, 0.0, 0.5, 0.0};` |
| name=`"indices"`, type=`"unsigned int"`, values=`["0","1","2","1","2","3"]` | `unsigned int indices[] = {0, 1, 2, 1, 2, 3};` |
| name=`"empty"`, type=`"float"`, values=`[]` | `float empty[1];` |
Values are joined with `, ` directly (no comma-splitting needed — already individual strings in the array).
### Persistence
`saveData()` serialises `_values` as a proper JSON array:
```json
{ "name": "vertices", "type": "float", "values": ["-0.5", "-0.5", "0.0"] }
```
`restoreData()` handles both the current array format and the legacy string format (comma-separated), so old saved workspaces remain compatible.
### Variable reference integration
Arrays are scanned by `refreshVarRefOptions()` alongside booleans, scalars, and strings. A `wc-varref` block can reference an array from its dropdown, setting the data hook type to `"array"`. The referenced array name can then be typed into procedure call text inputs (e.g. `glBufferData`'s `data` parameter).
### No data hook
Unlike `wc-boolean`, `wc-scalar`, and `wc-string`, the `wc-array` block does **not** include a `.hook` button. Arrays are referenced by name directly in procedure call arguments, not linked via data-flow connections.
### Default vertex array
When the app starts fresh (no saved workspace), `createDefaultOpenGLProcedures()` creates a `wc-array` in the Data tab with Hello Triangle vertex data:
```
┌──────────────────────────────────────┐
│ [vertices] [float ▼] [] 9 elements │
├──────────────────────────────────────┤
│ 0 [-0.5] [×] │
│ 1 [-0.5] [×] │
│ 2 [ 0.0] [×] │
│ 3 [ 0.5] [×] │
│ 4 [-0.5] [×] │
│ 5 [ 0.0] [×] │
│ 6 [ 0.0] [×] │
│ 7 [ 0.5] [×] │
│ 8 [ 0.0] [×] │
│ 9 [ ] │
└──────────────────────────────────────┘
```
Produces:
```c
float vertices[] = {-0.5, -0.5, 0.0, 0.5, -0.5, 0.0, 0.0, 0.5, 0.0};
```
Three vertices, each with (x, y, z) = 9 float values total.
## OpenGL default procedures
On first load, `createDefaultOpenGLProcedures()` populates three library tabs in the Procedures pane with everything needed for a Hello Triangle pipeline.
### Library: glfw (window management)
| Procedure | Inputs | Outputs | C signature |
|-----------|--------|---------|-------------|
| `glfwInit` | — | `result: int` | `int glfwInit(void)` |
| `glfwCreateWindow` | `width: int`, `height: int`, `title: string` | `window: int` | `GLFWwindow* glfwCreateWindow(int, int, const char*, ...)` |
| `glfwMakeContextCurrent` | `window: int` | — | `void glfwMakeContextCurrent(GLFWwindow*)` |
| `glfwSwapBuffers` | `window: int` | — | `void glfwSwapBuffers(GLFWwindow*)` |
| `glfwPollEvents` | — | — | `void glfwPollEvents(void)` |
| `glfwWindowShouldClose` | `window: int` | `result: int` | `int glfwWindowShouldClose(GLFWwindow*)` |
| `glfwTerminate` | — | — | `void glfwTerminate(void)` |
Generated code uses `int` for the `GLFWwindow*` handle (stored as a pointer-sized integer). Cast as needed.
### Library: glad (OpenGL loader)
| Procedure | Inputs | Outputs | C signature |
|-----------|--------|---------|-------------|
| `gladLoadGL` | — | `result: int` | `int gladLoadGL(void)` |
### Library: gl (OpenGL core — Hello Triangle pipeline)
| Procedure | Inputs | Outputs |
|-----------|--------|---------|
| `glCreateShader` | `type: unsigned int` | `shader: unsigned int` |
| `glShaderSource` | `shader: unsigned int`, `count: int`, `source: string` | — |
| `glCompileShader` | `shader: unsigned int` | — |
| `glGetShaderiv` | `shader: unsigned int`, `pname: unsigned int` | `params: int` |
| `glCreateProgram` | — | `program: unsigned int` |
| `glAttachShader` | `program: unsigned int`, `shader: unsigned int` | — |
| `glLinkProgram` | `program: unsigned int` | — |
| `glGetProgramiv` | `program: unsigned int`, `pname: unsigned int` | `params: int` |
| `glGenVertexArrays` | `n: unsigned int` | `arrays: unsigned int` |
| `glBindVertexArray` | `array: unsigned int` | — |
| `glGenBuffers` | `n: unsigned int` | `buffers: unsigned int` |
| `glBindBuffer` | `target: unsigned int`, `buffer: unsigned int` | — |
| `glBufferData` | `target: unsigned int`, `size: int`, `data: string`, `usage: unsigned int` | — |
| `glVertexAttribPointer` | `index: unsigned int`, `size: int`, `type: unsigned int`, `normalized: int`, `stride: int`, `pointer: int` | — |
| `glEnableVertexAttribArray` | `index: unsigned int` | — |
| `glUseProgram` | `program: unsigned int` | — |
| `glDrawArrays` | `mode: unsigned int`, `first: int`, `count: int` | — |
| `glClear` | `mask: unsigned int` | — |
| `glClearColor` | `red: float`, `green: float`, `blue: float`, `alpha: float` | — |
| `glDeleteShader` | `shader: unsigned int` | — |
| `glDeleteProgram` | `program: unsigned int` | — |
| `glViewport` | `x: int`, `y: int`, `width: int`, `height: int` | — |
### Using the procedures in a Hello Triangle workflow
A typical flowchart from START to END:
1. **`glfw/glfwInit`** → output `result` connected to a decision or ignored
2. **`glfw/glfwCreateWindow`** → input `width: 800`, `height: 600`, `title: "Hello Triangle"` → output `window`
3. **`glfw/glfwMakeContextCurrent`** → input `window` from step 2
4. **`glad/gladLoadGL`** → output `result`
5. **`gl/glCreateShader`** → input `type: 35633` (`GL_VERTEX_SHADER`) → output `shader`
6. **`gl/glShaderSource`** → input `shader` from step 5, `count: 1`, `source: "..."` (vertex source)
7. **`gl/glCompileShader`** → input `shader` from step 5
8. Repeat steps 5–7 for fragment shader (`type: 35632` = `GL_FRAGMENT_SHADER`)
9. **`gl/glCreateProgram`** → output `program`
10. **`gl/glAttachShader`** → input `program`, `shader` (vertex)
11. **`gl/glAttachShader`** → input `program`, `shader` (fragment)
12. **`gl/glLinkProgram`** → input `program`
13. **`gl/glGenVertexArrays`** → input `n: 1` → output `VAO`
14. **`gl/glBindVertexArray`** → input `array: VAO`
15. **`gl/glGenBuffers`** → input `n: 1` → output `VBO`
16. **`gl/glBindBuffer`** → input `target: 34962` (`GL_ARRAY_BUFFER`), `buffer: VBO`
17. **`gl/glBufferData`** → input `target: 34962`, `size: 9*4` (or `sizeof(vertices)`), `data: vertices`, `usage: 35044` (`GL_STATIC_DRAW`)
18. **`gl/glVertexAttribPointer`** → input `index: 0`, `size: 3`, `type: 5126` (`GL_FLOAT`), `normalized: 0`, `stride: 0`, `pointer: 0`
19. **`gl/glEnableVertexAttribArray`** → input `index: 0`
20. **Loop**: `glClear`, `glUseProgram`, `glBindVertexArray`, `glDrawArrays`, `glfwSwapBuffers`, `glfwPollEvents`
21. **`glfw/glfwWindowShouldClose`** → input `window` → output `result` drives the loop decision
22. Cleanup: `glDeleteProgram`, `glDeleteShader`, `glfwTerminate`
### Generated C example (simplified)
```c
#include <gl.h>
#include <glfw.h>
#include <glad.h>
int main() {
float vertices[] = {-0.5, -0.5, 0.0, 0.5, -0.5, 0.0, 0.0, 0.5, 0.0};
int result;
int window;
unsigned int shader;
unsigned int program;
unsigned int VAO;
unsigned int VBO;
glfwInit(&result);
glfwCreateWindow(800, 600, "Hello Triangle", &window);
glfwMakeContextCurrent(window);
gladLoadGL(&result);
glCreateShader(35633, &shader);
glShaderSource(shader, 1, vertexSource);
glCompileShader(shader);
// ... fragment shader, program linking, VAO/VBO setup ...
glClear(16640);
glUseProgram(program);
glBindVertexArray(VAO);
glDrawArrays(4, 0, 3);
glfwSwapBuffers(window);
glfwPollEvents();
// ... loop ...
glfwTerminate();
return 0;
}
```
> **Note**: Output parameters use `&` prefix. For functions like `glCreateShader` that natively return values, the generated code uses a pointer-output convention. Adjust the function signatures or add thin wrappers to match the real OpenGL API.
### Include path mapping
| Library name | Generated include | Real include (adjust manually) |
|-------------|-------------------|--------------------------------|
| `glfw` | `#include <glfw.h>` | `#include <GLFW/glfw3.h>` |
| `glad` | `#include <glad.h>` | `#include <glad/glad.h>` |
| `gl` | `#include <gl.h>` | Included by glad, or `#include <GL/gl.h>` |
The generated output uses the library name directly. Adjust the `#include` paths in the generated C code to match your project's OpenGL setup.
## Development notes
- No build step — edit files directly, refresh browser
- Script loading order in `index.html` matches the dependency order above
- New block types: add a `<template>`, create a class extending `WCBlock`, register with `registerBlock()`
- All functions are global (no modules/imports) — naming conventions and JSDoc serve as documentation