---
title: "dev tools 🛠️🧰 (all models)"
order: 500
author:
- name: "Declan Naughton 🧮👨💻🧉"
url: "https://calcwithdec.dev/about.html"
description: "calculang model developer tools"
format:
html:
echo: false
resources:
- '../models/main/*.js'
- '../models/main/*.js.map'
- '../models/main/*_esm/*.cul.js'
- '../models/main/*_esm/*.mjs'
- '../models/bounce/*' # not required by any other page
- '../models/bounce/*_esm/*' # not required by any other page
- '../models/coffee/*' # not required by any other page
- '../models/coffee/*_esm/*' # not required by any other page
- '../models/**'
- '../models/climate-simple/climate-simple_esm/**'
---
## formula-inputs matrix
```{ojs}
md`${function_inputs_table(introspection)}`
// this should be better than a matrix: it should be an indented tree where it's possible to follow the logic of the compiler incl. where inputs get summarised
// useful to do nomemo/memo option? (should always be the same, be aware memo functionality will change)
```
## cul scope id graph
```{ojs}
viewof show_query_string = Inputs.checkbox(["show query string?"])
viewof scope_id_graph_nomemo = Inputs.checkbox(["nomemo"], {value: ["nomemo"]})
// defaults here are setup to protect you from what really happens when memo is on !
dot`${scope_id_graph}`
```
## graph
```{ojs}
viewof graph_nomemo = Inputs.checkbox(["nomemo"], {value: ["nomemo"]})
g = dot`${graph_functions(graph_nomemo.length ? introspection_nomemo : introspection)}`
DOM.download(() => serializeSVG(g), undefined, "Download SVG")
```
## calculang formulae & generated js
::: {.callout-caution}
Bad for **modular models** especially where formulae e.g. overridden
:::
```{ojs}
viewof calculang_source_nomemo = Inputs.checkbox(["nomemo"], {value: ["nomemo"]})
calculang_source_introspection = calculang_source_nomemo.length ? introspection_nomemo : introspection
viewof cul_scope_id = Inputs.radio(_.range(0,Object.keys(calculang_source_introspection.cul_scope_ids_to_resource).length), {label: "cul_scope_id", value: 0 /*maybe nice to default to last one instead?*/})
```
::: {.panel-tabset}
## calculang 📝💬
```{ojs}
formulae_objs = Object.values(calculang_source_introspection.cul_functions).filter(d => d.reason == 'definition' && inputs.indexOf(d.name+'_in') == -1)
input_formulae_objs = Object.values(calculang_source_introspection.cul_functions).filter(d => d.reason == 'definition' && inputs.indexOf(d.name+'_in') != -1)
inputs = Object.values(calculang_source_introspection.cul_functions).filter(d => d.reason == 'input definition').map(d => d.name).sort()
cul_fetch = await fetch(`../../${entrypoint_no_cul_js}${calculang_source_nomemo.length ? '-nomemo_esm' : '_esm'}/cul_scope_${cul_scope_id}.cul.js`)
cul = cul_fetch.text()
md`
**Inputs** are \`${inputs.join('\`, \`')}\`
**Formulae**:
~~~js
${
formulae_objs.map(f => cul.split('\n').filter((d,i) => i >= f.loc.start.line-1 && i < f.loc.end.line).join('\n').slice(13)).join('\n\n')
}
~~~
`
```
<details><summary>Inputs</summary>
```{ojs}
md`
~~~js
${
input_formulae_objs.map(f => cul.split('\n').filter((d,i) => i >= f.loc.start.line-1 && i < f.loc.end.line).join('\n').slice(13)).join('\n\n')
}
~~~
`
```
</details>
## ✨ js
```{ojs}
esm_fetch = await fetch(`../../${entrypoint_no_cul_js}${calculang_source_nomemo.length ? '-nomemo_esm' : '_esm'}/cul_scope_${cul_scope_id}.mjs`)
esm = esm_fetch.text()
md`
**Inputs** are \`${inputs.join('\`, \`')}\`
**Formulae**:
~~~js
${
formulae_objs.map(f => esm.split('\n').filter((d,i) => i >= f.loc.start.line-1 && i < f.loc.end.line).join('\n').slice(13)).join('\n\n')
}
~~~
`
```
<details><summary>Inputs</summary>
```{ojs}
md`
~~~js
${
input_formulae_objs.map(f => esm.split('\n').filter((d,i) => i >= f.loc.start.line-1 && i < f.loc.end.line).join('\n').slice(13)).join('\n\n')
}
~~~
`
```
</details>
:::
## Appendix
```{ojs}
import {calcuvizspec, graph_functions, function_inputs_table} from "@declann/little-calcu-helpers"
entrypoints_all = [ // see list-cul.sh/txt
'models/donut/donut.cul.js',
//'models/donut/donut-nomemo.cul.js
//'models/main/main-nomemo.cul.js
'models/main/main.cul.js',
'models/adders/n-bit-adder.cul.js',
'models/adders/half-adder.cul.js',
'models/adders/full-adder.cul.js',
'models/adders/gates.cul.js',
'models/kaya/kaya.cul.js',
//'models/kaya/kaya-nomemo.cul.js
'models/projectile/projectile.cul.js',
//'models/projectile/projectile-nomemo.cul.js',
'models/zapis/zapis.cul.js',
//'models/zapis/zapis-nomemo.cul.js
'models/coffee/coffee-demand-curve.cul.js',
//'models/coffee/coffee-demand-curve-nomemo.cul.js
'models/coffee/coffee.cul.js',
//'models/loan-validator/simple-loan-nomemo.cul.js
'models/loan-validator/simple-loan.cul.js',
'models/bounce/bounce-0.cul.js',
'models/bounce/bounce-1.cul.js',
'models/bounce/bounce-2.cul.js',
'models/bounce/bounce-3.cul.js',
'models/bounce/bounce-4.cul.js',
'models/bounce/bounce-5.cul.js',
'models/bounce/bounce-6.cul.js',
'models/raycasting/raycasting.cul.js',
'models/climate-simple/climate-simple.cul.js',
'models/climate-simple/climate-simple_esm/ramp.cul.js',
'models/climate-simple/ClimateMA.cul.js',
'models/heart/heart-contour.cul.js',
'models/vek/vek.cul.js'
]
viewof entrypoint = Inputs.select(entrypoints_all, {label:'entrypoint'})
entrypoint_no_cul_js = entrypoint.slice(0,-7)
introspection_fetch = await fetch(`../../${entrypoint_no_cul_js}.introspection.json`)
introspection = introspection_fetch.json({typed:true})
introspection_nomemo_fetch = await fetch(`../../${entrypoint_no_cul_js}-nomemo.introspection.json`)
introspection_nomemo = introspection_nomemo_fetch.json({typed:true})
```
some bits I should maybe refactor out:
```{ojs}
// reference: https://observablehq.com/@mbostock/saving-svg
serializeSVG = {
const xmlns = "http://www.w3.org/2000/xmlns/";
const xlinkns = "http://www.w3.org/1999/xlink";
const svgns = "http://www.w3.org/2000/svg";
return function serialize(svg) {
svg = svg.cloneNode(true);
const fragment = window.location.href + "#";
const walker = document.createTreeWalker(svg, NodeFilter.SHOW_ELEMENT);
while (walker.nextNode()) {
for (const attr of walker.currentNode.attributes) {
if (attr.value.includes(fragment)) {
attr.value = attr.value.replace(fragment, "#");
}
}
}
svg.setAttributeNS(xmlns, "xmlns", svgns);
svg.setAttributeNS(xmlns, "xmlns:xlink", xlinkns);
const serializer = new window.XMLSerializer;
const string = serializer.serializeToString(svg);
return new Blob([string], {type: "image/svg+xml"});
};
}
```
incl. scope id graph bits:
```{ojs}
scope_id_graph_introspection = scope_id_graph_nomemo.length ? introspection_nomemo : introspection;
scope_id_graph_links = Object.entries(scope_id_graph_introspection.cul_scope_ids_to_resource).filter(([cul_scope_id]) => cul_scope_id != 0).map(([cul_scope_id, resource]) => new URLSearchParams(resource.split('?').pop()).get('cul_scope_id') + ' -> ' + new URLSearchParams(resource).get('cul_parent_scope_id'))
scope_id_graph_nodes = Object.entries(scope_id_graph_introspection.cul_scope_ids_to_resource).map(d => (`${d[0]} [${ d[0] == 0 ? 'color="green" style="filled" ' : 'color="yellow" style="filled" '}label="[${d[0]}]: ${show_query_string.length ? d[1] : d[1].split('?')[0]}"]`))
scope_id_graph = `digraph {
rankdir="RL"
node [shape="box"];
${scope_id_graph_nodes.join('\n')}
${scope_id_graph_links.join('\n')}
}`
```