gitoriaLog in with ident

gitoria

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commit4a2d71254a2d7125initial commitmre4a2d7125/plugins/web/compile.hl

42.8 KB

  1. // plugins/web/compile.hl — THE FRAMEWORK COMPILES EACH COMPONENT.
  2. //
  3. // Mission 313 (the creator, 15 Sep 14:30: "clientside interpreter + table against
  4. // component code per component, no tables needed"). The old hl:web ships ONE generic client
  5. // that walks a tree the seed carries: it pairs elements with nodes, asks a table which
  6. // members each element shows, asks `view.value` what each bound spot holds, and does it
  7. // again on every paint. hl:web ships the ANSWERS instead — this file writes each
  8. // component's own code, once, where the old hl:web would have walked.
  9. //
  10. // WHAT IS EMITTED, per component, beside the class `hlJs` compiled:
  11. //
  12. // v(c,m,o,host,i,rows,rk,fill,cr,opt) the component's View, unrolled: one
  13. // statement per element, per handler, per
  14. // bound spot. `cr` false CLAIMS the server's
  15. // DOM (take host.children[i], step on), `cr`
  16. // true CREATES it — the two walks the old hl:web
  17. // spells out separately, which differ only in
  18. // where an element comes from.
  19. // b["<for site>"] one `for` body — the row factory the keyed
  20. // region builds a row with.
  21. // t/e["<if site>"] an `if`'s two branches.
  22. // f["<reference site>"] the fragment this component writes into a
  23. // child's `slot` — built in THIS frame, which
  24. // is why it is this component's code.
  25. // p["<element site>"] the paint statements: `p` writes what one
  26. // member feeds (the old hl:web's paint(&m,s,name)),
  27. // `r` rewrites a row's element where the DOM
  28. // does not already hold it (rowPaint).
  29. //
  30. // The generated code calls a FIXED runtime (client.hl) for everything that is not this
  31. // component: the mount records, the keyed region diff, the binding edges, the
  32. // derivations, the socket, navigation. It never looks anything up in a table — the
  33. // names, the sites and the write sets are written into the statements.
  34. //
  35. // It is TEXT the framework produces, served from memory beside the module (WebFramework
  36. // `module`), never a JavaScript file under plugins/.
  37. import { newline, tab } from './view.hl'
  38. import View from './view.hl'
  39. // ---- the emitted half --------------------------------------------------------------
  40. module(gen, key, nodes) {
  41. fns = [] // the sub-walks: row bodies, if branches, fills
  42. done = {}
  43. rowVars = []
  44. tbl = tableOf(gen, key) // what the language says about this file, read once
  45. regions = regionIndex(tbl) // a `for`'s list reads, an `if`'s condition reads
  46. // A SHELL'S VIEW IS A `body` ELEMENT and the document already has one: the walk runs
  47. // against document.body itself, so the root list is that element's children and the
  48. // element only takes the marks a region rebuild on it would need.
  49. let roots = nodes
  50. let bodySite = null
  51. if (nodes.length == 1 && nodes[0].k == 'el' && nodes[0].tag == 'body') {
  52. roots = nodes[0].children
  53. bodySite = nodes[0].site
  54. }
  55. let root = walkTop(key, roots)
  56. if (bodySite != null) {
  57. root = root.replace('let r=rows;', 'let r=rows;c.markHost(m,host,' + str(bodySite) + ');')
  58. }
  59. let src = ';(function(){var g=(typeof globalThis!=="undefined"?globalThis:window);'
  60. src = src + 'var C=(g.__hlC=g.__hlC||{});'
  61. // the element a claim takes or a create makes — the one shape both walks share
  62. // THE PRELUDE, one per module: the element a claim takes or a create makes, and the
  63. // two reads. A read written out in full — `(r["title"]!=null?r["title"]:inst["title"])`
  64. // — is 45 bytes and a component's View holds dozens of them; named, it is 16. The
  65. // rule is the same rule (a `for` row variable in scope wins over a member of the
  66. // same name, and a field path walks into it), spelled once.
  67. src = src + 'function A(cr,host,i,tag){if(cr){return document.createElement(tag);}return host.children[i];}'
  68. src = src + 'function RV(r,q,n){return r[n]!=null?r[n]:q[n];}'
  69. src = src + 'function RF(r,q,n,p){var v=RV(r,q,n);for(var k=0;k<p.length;k++){if(v==null)return null;v=v[p[k]];}return v;}'
  70. src = src + 'var B={},T={},E={},F={},R={},P={};'
  71. // ONE FUNCTION PER LIST, however many sites reach it. Every `if` in a list rebuilds
  72. // that same list, and a nested walk is written once and named: emitting it per site
  73. // made the reference app's home page 215 KB of the same code over and over.
  74. let wi = 0
  75. for (f of fns) {
  76. wi = wi + 1
  77. let name = 'w' + wi
  78. src = src + 'var ' + name + '=' + f.src + ';'
  79. for (site of f.sites) { src = src + f.slot + '[' + str(site) + ']=' + name + ';' }
  80. }
  81. let paints = []
  82. collectPaints(&paints, roots)
  83. for (e of paints) { src = src + 'P[' + str(e.site) + ']={p:' + e.paint + ',r:' + e.row + '};' }
  84. src = src + 'C[' + str(key) + ']={v:' + root + ',b:B,t:T,e:E,f:F,r:R,p:P'
  85. // THE ANSWERS A LOOKUP STILL NEEDS, and only those. A local event's write set stands
  86. // at its own listener and a region's names at its own region — written into the
  87. // call. These three are asked for by NAME at run time and cannot be: an inbound
  88. // frame names its event, a routine handed down names the member that holds it, and
  89. // a write set names the derivations that follow it.
  90. src = src + ',ev:' + eventMap() + ',mw:' + memberMap() + ',dv:' + derivList() + '};'
  91. return src + '})();' + newline
  92. }
  93. tbl = null
  94. regions = {}
  95. // what a DECLARED handler writes, by event: one entry per event name, unioned
  96. eventMap() {
  97. let byEvent = {}
  98. for (h of tbl.handlers) {
  99. if (!h.nested) {
  100. let have = byEvent[h.event] == null ? [] : byEvent[h.event]
  101. for (w of closeWrites(tbl, h.writes, h.reads)) { if (!have.includes(w)) { have.push(w) } }
  102. byEvent[h.event] = have
  103. }
  104. }
  105. return mapOf(byEvent)
  106. }
  107. // A MEMBER HOLDING A ROUTINE: what calling it writes is what its initializer writes
  108. memberMap() {
  109. let byName = {}
  110. for (m of tbl.members) {
  111. if (m.nodes != null) { byName[m.name] = memberNames(m.nodes.writes) }
  112. }
  113. return mapOf(byName)
  114. }
  115. // EVERY INITIALIZER, IN DECLARATION ORDER, with the members it reads — the whole of
  116. // what a derivation needs; the class carries the computation itself as `__derive__`
  117. derivList() {
  118. let src = '['
  119. let first = true
  120. for (d of tbl.derivations) {
  121. if (!first) { src = src + ',' }
  122. first = false
  123. src = src + '{name:' + str(d.name) + ',site:' + str(d.site) + ',reads:' + list(memberNames(d.reads)) + '}'
  124. }
  125. return src + ']'
  126. }
  127. mapOf(byName) {
  128. let src = '{'
  129. let first = true
  130. for (k of byName.keys()) {
  131. if (!first) { src = src + ',' }
  132. first = false
  133. src = src + str(k) + ':' + list(byName[k])
  134. }
  135. return src + '}'
  136. }
  137. // the write set and the read set of the handler at this site, closed over the methods
  138. // it calls and the handlers it emits to — computed here, written into the listener
  139. handlerSets(site) {
  140. for (h of tbl.handlers) {
  141. if (h.nested && h.site == site) {
  142. return list(closeWrites(tbl, h.writes, h.reads)) + ',' + list(closeReads(tbl, h.reads))
  143. }
  144. }
  145. return '[],[]'
  146. }
  147. regionNamesAt(site) {
  148. let names = regions[site]
  149. return list(names == null ? [] : names)
  150. }
  151. fns = []
  152. // ---- the walk, unrolled ------------------------------------------------------------
  153. // `top` marks the View's own root list: an element there is a COMPOSITION ROOT — a
  154. // reference above may have overwritten some of its events and put classes on it, which
  155. // is what `opt` carries (the old hl:web's rootOverwrites and withRootClasses).
  156. walk(key, nodes) {
  157. return plain(key, nodes)
  158. }
  159. // AN `if` REBUILDS THE ELEMENT IT STANDS IN. The old hl:web emptied that element and walks its
  160. // whole child list again — the branch that stands now decides what appears — so the
  161. // rebuild registered under an `if`'s site is the HOST ELEMENT's list, not the branch's:
  162. // an `if` nested in another one's branch shares the host, and rebuilding only its own
  163. // branch would drop everything else the element holds. The `on` nodes are stripped: the
  164. // element is kept and its listeners with it.
  165. rebuilds(key, nodes) {
  166. let sites = ifSites(nodes)
  167. if (sites.length == 0) { return null }
  168. let fresh = []
  169. for (site of sites) {
  170. if (done['R ' + site] == null) { done['R ' + site] = true fresh.push(site) }
  171. }
  172. if (fresh.length == 0) { return null }
  173. fns.push({ slot = 'R'; sites = fresh; src = plain(key, withoutOns(nodes)); })
  174. return null
  175. }
  176. // every `if` whose region lands on THIS element: the list's own, and those inside an
  177. // `if` branch or a `for` body under it. A nested element, and a composed child, are
  178. // their own host and answer for themselves.
  179. ifSites(nodes) {
  180. let out = []
  181. for (n of nodes) {
  182. if (n.k == 'if') {
  183. out.push(n.site)
  184. for (s of ifSites(n.then)) { out.push(s) }
  185. for (s of ifSites(n.other)) { out.push(s) }
  186. } else if (n.k == 'for') {
  187. for (s of ifSites(n.body)) { out.push(s) }
  188. }
  189. }
  190. return out
  191. }
  192. // the same walk, registering no rebuild of its own list (the one that would recurse)
  193. plain(key, nodes) {
  194. let body = ''
  195. for (n of nodes) { body = body + node(key, n, false, false) }
  196. return 'async function(c,m,o,host,i,rows,rk,fill,cr,opt){let inst=m.instance;let r=rows;' + body + 'return i;}'
  197. }
  198. // the View's own root list, whose elements are composition roots
  199. walkTop(key, nodes) {
  200. rebuilds(key, nodes)
  201. let body = ''
  202. for (n of nodes) { body = body + node(key, n, true, false) }
  203. return 'async function(c,m,o,host,i,rows,rk,fill,cr,opt){let inst=m.instance;let r=rows;' + body + 'return i;}'
  204. }
  205. withoutOns(nodes) {
  206. let out = []
  207. for (n of nodes) { if (n.k != 'on') { out.push(n) } }
  208. return out
  209. }
  210. // a sub-walk, registered under its own site so the runtime can reach it by name
  211. // A SUB-WALK IS WRITTEN ONCE. The same list is reached again whenever a rebuild of the
  212. // list it stands in is generated, and a `for` body or an `if` branch inside it would
  213. // otherwise be emitted once per reach.
  214. sub(key, nodes, slot, site) {
  215. if (done[slot + ' ' + site] != null) { return null }
  216. done[slot + ' ' + site] = true
  217. fns.push({ slot = slot; sites = [site]; src = walk(key, nodes); })
  218. return null
  219. }
  220. done = {}
  221. node(key, n, top, filterOns) {
  222. if (n.k == 'el') { return elNode(key, n, top) }
  223. if (n.k == 'text') { return 'if(cr){host.appendChild(document.createTextNode(' + str(n.text) + '));}' }
  224. if (n.k == 'on') {
  225. // A REFERENCE ABOVE MAY HAVE OVERWRITTEN THIS EVENT on the composition root:
  226. // the root's own handler for it is dropped and the reference's stands instead
  227. // (the old hl:web's withoutOns, which applies to the root element's direct children).
  228. let guard = filterOns ? 'if(!(opt&&opt.over&&opt.over.indexOf(' + str(n.event) + ')>=0)){' : '{'
  229. return guard + 'await c.onAt(m,host,' + str(n.event) + ',' + str(n.site) + ',rows,' + handlerSets(n.site) + ');}'
  230. }
  231. if (n.k == 'member' && n.name == 'slot') {
  232. // the SHELL's slot is where the route component hangs; a child's slot is the
  233. // fragment its host wrote, and the host's frame builds it
  234. return 'if(m.isShell){await c.slotShell(m,host,cr);}else if(fill!=null){i=await c.slotFill(m,o,host,i,fill,cr);}'
  235. }
  236. if (n.k == 'member' || n.k == 'field') {
  237. return 'if(cr){host.appendChild(document.createTextNode(' + readOf(n) + '));}'
  238. }
  239. if (n.k == 'for') {
  240. // THE ROW VARIABLE IS IN SCOPE INSIDE THE BODY, and a read of it is the ROW's,
  241. // not a member of the instance — the language resolves it that way and the site
  242. // sets must say the same, or a member of the same name would paint the row.
  243. rowVars.push(n.row)
  244. sub(key, n.body, 'B', n.site)
  245. rowVars.pop()
  246. return 'i=await c.forAt(m,o,host,i,' + str(n.site) + ',' + str(n.row) + ',function(inst,r){return ' + rawRead(n.list) + ';},rows,rk,fill,cr,' + regionNamesAt(n.site) + ');'
  247. }
  248. if (n.k == 'if') {
  249. sub(key, n.then, 'T', n.site)
  250. sub(key, n.other, 'E', n.site)
  251. return 'i=await c.ifAt(m,o,host,i,' + str(n.site) + ',function(inst,r){return ' + rawRead(n.cond) + ';},rows,rk,fill,cr,' + regionNamesAt(n.site) + ');'
  252. }
  253. if (n.k == 'component') {
  254. if (n.fill != null && n.fill.length > 0) { sub(key, n.fill, 'F', n.site) }
  255. return 'i=await c.kidAt(m,host,i,' + kidSpec(n) + ',rows,rk,fill,cr,opt);'
  256. }
  257. return ''
  258. }
  259. // ---- an element --------------------------------------------------------------------
  260. elNode(key, n, top) {
  261. let src = '{let el=A(cr,host,i,' + str(n.tag) + ');if(!cr){i=i+1;}'
  262. src = src + 'if(el==null){c.acqFail(' + str(n.tag) + ',host);}else{'
  263. // the attributes, on the walk that BUILDS the element (a claim takes the server's)
  264. src = src + 'if(cr){' + createAttrs(n) + (top ? 'c.rootClasses(el,opt);' : '') + 'host.appendChild(el);}'
  265. src = src + 'el.__hlRows=rows;el.__hlSite=' + str(n.site) + ';el.__hlKey=' + str(key) + ';'
  266. // the element's own named entries, for the literal a handler on it fires
  267. src = src + 'el.__hlLit=function(inst,r){return ' + litNamed(n) + ';};'
  268. let names = siteNames(n)
  269. let valueMember = 'null'
  270. for (a of n.attrs) { if (a.member != null && a.name == 'value') { valueMember = str(a.member) } }
  271. let sited = ''
  272. if (names.length > 0 || valueMember != 'null') {
  273. sited = 'await c.siteAt(m,el,' + list(names) + ',' + str(n.site) + ',' + valueMember + ',' + (n.tag == 'select' ? 'true' : 'false') + ',rows);'
  274. }
  275. rebuilds(key, n.children)
  276. let kids = ''
  277. for (c of n.children) { kids = kids + node(key, c, false, top) }
  278. let inner = '{let host=el;let i=0;' + kids + '}'
  279. // A SELECT IS SITED AFTER ITS OPTIONS: the member decides which option stands, and
  280. // the read-back that `siteAt` does reports nothing before they exist.
  281. if (n.tag == 'select') { src = src + inner + 'if(cr){await c.applySelectAt(m,el,' + selectValue(n) + ');}' + sited }
  282. else { src = src + sited + inner }
  283. return src + '}}'
  284. }
  285. // the attributes an element is BUILT with — a bound one written in, a literal one as it
  286. // stands. `value` is a property once the page is live and an attribute as the control's
  287. // default; a textarea has no value attribute at all and a select's value is an option.
  288. createAttrs(n) {
  289. let src = ''
  290. for (a of n.attrs) {
  291. if (isBool(a)) {
  292. src = src + '{' + boolWrite(a, rawRead(a.member != null ? { k = 'member'; name = a.member; } : a.ref)) + '}'
  293. } else {
  294. let rd = a.member != null ? readOf({ k = 'member'; name = a.member; }) : (a.ref != null ? readOf(a.ref) : str(a.text))
  295. src = src + '{let v=' + rd + ';'
  296. if (a.extra != null) { src = src + 'v=v+" "+' + str(a.extra) + ';' }
  297. if (n.tag == 'select' && a.name == 'value') { src = src + '}' }
  298. else if (a.name == 'value') {
  299. src = src + 'if(el.value!=null){el.value=v;' + (n.tag == 'textarea' ? '' : 'el.setAttribute("value",v);') + '}else{el.setAttribute("value",v);}}'
  300. } else { src = src + 'el.setAttribute(' + str(a.name) + ',v);}' }
  301. }
  302. }
  303. if (boundValue(n)) { src = src + 'el.setAttribute("autocomplete","off");' }
  304. return src
  305. }
  306. // a BOUND boolean attribute — a literal one was decided at build (WebFramework `element`)
  307. isBool(a) {
  308. return (a.member != null || a.ref != null) && v.isBoolAttr(a.name)
  309. }
  310. // A BOUND BOOLEAN ATTRIBUTE, WRITTEN (ticket #33): set when the value is on, REMOVED when
  311. // it is off — the markup's rule (view.boolAttrs) — and the DOM property with it, because
  312. // once the page is live `checked` is the property and the attribute only its default.
  313. boolWrite(a, raw) {
  314. let prop = a.name == 'readonly' ? 'readOnly' : a.name
  315. return 'let on=!!' + raw + ';if(on){el.setAttribute(' + str(a.name) + ',"");}else if(el.removeAttribute){el.removeAttribute(' + str(a.name) + ');}if(' + str(prop) + ' in el&&el[' + str(prop) + ']!==on){el[' + str(prop) + ']=on;}'
  316. }
  317. // view.hl's `boundValue`, to the letter: a FORM CONTROL whose `value` reads a member or
  318. // a field, and not one the author gave an `autocomplete` of its own
  319. boundValue(n) {
  320. if (n.tag != 'input' && n.tag != 'textarea' && n.tag != 'select') { return false }
  321. let bound = false
  322. for (a of n.attrs) {
  323. if (a.name == 'autocomplete') { return false }
  324. if (a.name == 'value' && (a.member != null || a.ref != null)) { bound = true }
  325. }
  326. return bound
  327. }
  328. selectValue(n) {
  329. for (a of n.attrs) {
  330. if (a.name == 'value') {
  331. if (a.member != null) { return readOf({ k = 'member'; name = a.member; }) }
  332. if (a.ref != null) { return readOf(a.ref) }
  333. return str(a.text)
  334. }
  335. }
  336. return 'null'
  337. }
  338. // the element's own entries as the literal sees them — its attributes by name, then the
  339. // `for` row variables in scope (the row rides on the value)
  340. litNamed(n) {
  341. let src = '{'
  342. let first = true
  343. for (a of n.attrs) {
  344. if (!first) { src = src + ',' }
  345. first = false
  346. let v = a.member != null ? rawRead({ k = 'member'; name = a.member; }) : (a.ref != null ? rawRead(a.ref) : str(a.text))
  347. src = src + str(a.name) + ':' + v
  348. }
  349. return src + '}'
  350. }
  351. // the reference's own description, written at its use site: what it binds, what it
  352. // overwrites, the classes the host's `#Child` rule puts on it
  353. kidSpec(n) {
  354. let src = '{key:' + str(n.key) + ',kid:' + str(kidKeyOf(n)) + ',site:' + str(n.site) + ',fill:' + ((n.fill != null && n.fill.length > 0) ? 'true' : 'false')
  355. src = src + ',bindings:['
  356. let first = true
  357. for (b of n.bindings) {
  358. if (!first) { src = src + ',' }
  359. first = false
  360. src = src + '{name:' + str(b.name) + ',member:' + (b.member == null ? 'null' : str(b.member))
  361. src = src + ',ref:' + (b.ref == null ? 'null' : 'function(inst,r){return ' + rawRead(b.ref) + ';}')
  362. src = src + ',refName:' + (b.ref == null ? 'null' : str(b.ref.name))
  363. src = src + ',text:' + (b.text == null ? 'null' : 'true') + ',value:' + (b.text == null ? 'null' : refLiteral(b)) + '}'
  364. }
  365. src = src + '],ons:['
  366. first = true
  367. for (o of n.ons) {
  368. if (!first) { src = src + ',' }
  369. first = false
  370. src = src + '{event:' + str(o.event) + ',site:' + str(o.site) + ',sets:[' + handlerSets(o.site) + ']}'
  371. }
  372. src = src + '],classes:' + list(n.classes == null ? [] : n.classes) + '}'
  373. return src
  374. }
  375. // view.hl's kidKey: the reference's path, joined — the key the server mounted it under
  376. kidKeyOf(n) {
  377. let out = ''
  378. for (p of n.path) { out = out + '/' + p }
  379. return out
  380. }
  381. // the value a literal binding carries — view.refValue, folded at compile time
  382. refLiteral(b) {
  383. if (b.kind == 'folded') { return jsValue(b.value) }
  384. if (b.kind == 'number') { return '' + b.text }
  385. if (b.kind == 'boolean') { return b.text == 'true' ? 'true' : 'false' }
  386. if (b.kind == 'null_literal') { return 'null' }
  387. return str(b.text)
  388. }
  389. jsValue(v) {
  390. if (v == null) { return 'null' }
  391. if (hlTypeName(v) == 'String') { return str(v) }
  392. if (hlTypeName(v) == 'Boolean') { return v ? 'true' : 'false' }
  393. if (hlTypeName(v) == 'Number') { return '' + v }
  394. return JSON.stringify(v)
  395. }
  396. // ---- the site set: what this element SHOWS ------------------------------------------
  397. // The old hl:web asked the module's table which members the element at this site reads. The
  398. // answer is a fact about the View, so it is written in: the members its bound
  399. // attributes and its text leaves name. `Style` is folded into a class name at build and
  400. // the shell's `slot` is swapped by navigation — neither is ever painted, and neither is
  401. // a member read that reaches here (a `for` row variable resolves to its row).
  402. siteNames(n) {
  403. let out = []
  404. for (a of n.attrs) {
  405. if (a.member != null && a.member != 'Style' && !rowVars.includes(a.member) && !out.includes(a.member)) { out.push(a.member) }
  406. }
  407. for (c of n.children) {
  408. if ((c.k == 'member' || c.k == 'field') && c.name != 'slot' && c.name != 'Style' && !rowVars.includes(c.name)) {
  409. if (!out.includes(c.name)) { out.push(c.name) }
  410. }
  411. }
  412. return out
  413. }
  414. rowVars = []
  415. // ---- the paints ---------------------------------------------------------------------
  416. collectPaints(&out, nodes) {
  417. for (n of nodes) {
  418. if (n.k == 'el') {
  419. let paint = paintFn(n)
  420. let row = rowFn(n)
  421. if (paint != null || row != 'null') {
  422. out.push({ site = n.site; paint = paint == null ? 'null' : paint; row = row; })
  423. }
  424. collectPaints(&out, n.children)
  425. } else if (n.k == 'for') { collectPaints(&out, n.body) }
  426. else if (n.k == 'if') { collectPaints(&out, n.then) collectPaints(&out, n.other) }
  427. else if (n.k == 'component') { if (n.fill != null) { collectPaints(&out, n.fill) } }
  428. }
  429. return null
  430. }
  431. // `paint(&m, s, name)`, compiled: the bound attributes under the member each reads, and
  432. // the text where the member that moved stands among the leaves.
  433. paintFn(n) {
  434. let body = ''
  435. for (a of n.attrs) {
  436. if (a.member != null) {
  437. body = body + 'if(n===' + str(a.member) + '){' + attrWrite(n, a, readOf({ k = 'member'; name = a.member; })) + '}'
  438. }
  439. }
  440. let text = textOf(n)
  441. if (text != null) {
  442. let guard = ''
  443. for (c of n.children) {
  444. if (c.k == 'member' && c.name != 'slot') {
  445. if (guard != '') { guard = guard + '||' }
  446. guard = guard + 'n===' + str(c.name)
  447. }
  448. }
  449. if (guard != '') { body = body + 'if(' + guard + '){el.textContent=' + text + ';}' }
  450. }
  451. if (body == '') { return null }
  452. return 'function(el,inst,r,n){' + body + '}'
  453. }
  454. // `rowPaint(&m, el)`, compiled: every bound spot, written only where the DOM does not
  455. // already hold it — which is why a push into 2000 rows costs the 1999 nothing.
  456. rowFn(n) {
  457. let body = ''
  458. for (a of n.attrs) {
  459. if (isBool(a)) {
  460. body = body + '{' + boolWrite(a, rawRead(a.member != null ? { k = 'member'; name = a.member; } : a.ref)) + '}'
  461. } else if (a.member != null || a.ref != null) {
  462. let one = 'let v=' + readOf(a.member != null ? { k = 'member'; name = a.member; } : a.ref) + ';'
  463. if (a.extra != null) { one = one + 'v=v+" "+' + str(a.extra) + ';' }
  464. if (n.tag == 'select' && a.name == 'value') { one = one + 'if(el.value!==v){el.value=v;}' }
  465. else {
  466. one = one + 'if(el.getAttribute(' + str(a.name) + ')!==v){el.setAttribute(' + str(a.name) + ',v);}'
  467. if (a.name == 'value') { one = one + 'if(el.value!=null&&el.value!==v){el.value=v;}' }
  468. }
  469. body = body + '{' + one + '}'
  470. }
  471. }
  472. let text = textOf(n)
  473. let feeds = false
  474. for (c of n.children) { if ((c.k == 'member' || c.k == 'field') && c.name != 'slot') { feeds = true } }
  475. if (text != null && feeds) { body = body + '{let t=' + text + ';if(el.textContent!==t){el.textContent=t;}}' }
  476. if (body == '') { return 'null' }
  477. return 'function(el,inst,r){' + body + '}'
  478. }
  479. attrWrite(n, a, v) {
  480. if (isBool(a)) { return boolWrite(a, rawRead(a.member != null ? { k = 'member'; name = a.member; } : a.ref)) }
  481. let src = 'let v=' + v + ';'
  482. if (a.extra != null) { src = src + 'v=v+" "+' + str(a.extra) + ';' }
  483. if (n.tag == 'select' && a.name == 'value') { return src + 'el.value=v;' }
  484. src = src + 'el.setAttribute(' + str(a.name) + ',v);'
  485. if (a.name == 'value') { src = src + 'if(el.value!=null){el.value=v;}' }
  486. return src
  487. }
  488. textOf(n) {
  489. let parts = []
  490. let content = 0
  491. let feeds = false
  492. for (c of n.children) {
  493. if (c.k != 'on') {
  494. content = content + 1
  495. if (c.k == 'text') { parts.push(str(c.text)) }
  496. else if ((c.k == 'member' || c.k == 'field') && c.name != 'slot') { parts.push(readOf(c)) feeds = true }
  497. }
  498. }
  499. if (!feeds) { return null }
  500. if (parts.length != content) { return null }
  501. let src = '""'
  502. for (p of parts) { src = src + '+(' + p + ')' }
  503. return src
  504. }
  505. // ---- a read, written in ---------------------------------------------------------------
  506. // `view.value`'s own rule as an expression: a `for` row variable in scope wins over a
  507. // member of the same name, and a field path walks into it. `readOf` answers the STRING
  508. // the DOM takes; `rawRead` the value itself (a binding, a list, a condition).
  509. readOf(node) { return '(""+' + rawRead(node) + ')' }
  510. rawRead(node) {
  511. if (node == null) { return 'null' }
  512. if (node.k == 'text') { return str(node.text) }
  513. if (node.k != 'field') { return 'RV(r,inst,' + str(node.name) + ')' }
  514. return 'RF(r,inst,' + str(node.name) + ',' + list(node.path) + ')'
  515. }
  516. list(names) {
  517. let src = '['
  518. let first = true
  519. for (n of names) {
  520. if (!first) { src = src + ',' }
  521. first = false
  522. src = src + str(n)
  523. }
  524. return src + ']'
  525. }
  526. // a JavaScript string literal — JSON's escaping is a subset of JavaScript's, so a
  527. // source text's quotes, newlines and backslashes survive into the statement unchanged
  528. str(s) {
  529. if (s == null) { return '""' }
  530. return JSON.stringify('' + s)
  531. }
  532. // ---- WHAT THE LANGUAGE SAYS ABOUT THIS FILE, read HERE and written into the code ----
  533. // The old hl:web shipped the language's answers to the browser as data: `hlJs` writes each file's
  534. // members, handlers and methods — every node of every initializer, with the names it
  535. // reads — into the module, and the client reduces that table on every boot. The answers
  536. // are facts about the source, so this file asks for them once, at compile time, and
  537. // writes the RESULT into the statements: a handler's write set stands at the listener
  538. // that needs it, a region's names at the region, the derivations as a list of what to
  539. // re-run. The module carries no table at all (WebFramework `module` drops the one the
  540. // emitter writes).
  541. //
  542. // The reflection is the framework's own analysis generation — the same `gen` the View
  543. // trees come from. Nothing starts a second one: a handle is only valid inside the
  544. // generation that made it.
  545. tableOf(gen, key) {
  546. let members = []
  547. let derivations = []
  548. let handlers = []
  549. let methods = []
  550. for (m of hlMembers(gen, key)) {
  551. let entry = { name = m.name; nodes = null; }
  552. if (m.hasValue) {
  553. let n = syntaxTree(gen, key, m.node)
  554. entry.nodes = n
  555. derivations.push({ name = m.name; site = n.site; reads = n.reads; })
  556. nestedHandlers(&handlers, n)
  557. }
  558. members.push(entry)
  559. }
  560. for (h of hlEvents(gen, key).handles) {
  561. let n = syntaxTree(gen, key, h.node)
  562. handlers.push({ event = h.event; site = n.site; reads = n.reads; writes = n.writes; nested = false; })
  563. }
  564. for (mm of hlMethods(gen, key)) {
  565. if (!mm.inlineHandler) {
  566. let n = syntaxTree(gen, key, mm.node)
  567. methods.push({ name = mm.name; reads = n.reads; writes = n.writes; })
  568. }
  569. }
  570. return { members = members; derivations = derivations; handlers = handlers; methods = methods; }
  571. }
  572. // A NESTED `on` IS A DOM HANDLER, filed under its own site — the id the View node
  573. // carries. Its body is not the literal's syntax, so the node answers for it and the
  574. // walk stops there.
  575. nestedHandlers(&into, n) {
  576. if (n == null) { return null }
  577. if (n.tag == 'on_statement') {
  578. into.push({ event = n.event; site = n.site; reads = n.reads; writes = n.writes; nested = true; })
  579. return null
  580. }
  581. for (c of [n.value, n.list, n.body, n.condition, n.consequent, n.alternate, n.object, n.property, n.callee, n.left, n.right, n.operand]) {
  582. if (c != null) { nestedHandlers(&into, c) }
  583. }
  584. for (l of [n.entries, n.args, n.elements]) {
  585. if (l != null) { for (c of l) { nestedHandlers(&into, c) } }
  586. }
  587. return null
  588. }
  589. shortSite(site) {
  590. if (site == null) { return null }
  591. let at = site.indexOf(':')
  592. if (at < 0) { return site }
  593. let path = site.slice(0, at)
  594. let cut = path.lastIndexOf('/')
  595. if (cut < 0) { return site }
  596. return path.slice(cut + 1) + site.slice(at)
  597. }
  598. // the syntax of one node, recursively — hl:core/AST's `tree`, read off the framework's
  599. // own generation instead of starting one of its own
  600. syntaxTree(gen, key, node) {
  601. let n = hlSyntax(gen, key, node)
  602. if (n == null) { return null }
  603. // A SITE IS NAMED BY THE FILE'S BASE NAME. The reflection answers with the project
  604. // key ('components/sort_list.hl:77:26'); the View tree, the emitter's own
  605. // `__derive__` branches and every site this framework writes use the base name
  606. // ('sort_list.hl:77:26'). One spelling, or a lookup by site finds nothing.
  607. let out = { tag = n.tag; site = shortSite(n.site); reads = n.reads; writes = n.writes; }
  608. if (n.tag == 'object_expression') {
  609. let kids = []
  610. for (h of n.entries) { kids.push(syntaxTree(gen, key, h)) }
  611. out.entries = kids
  612. } else if (n.tag == 'object_property') {
  613. out.key = n.key
  614. out.value = syntaxTree(gen, key, n.value)
  615. } else if (n.tag == 'view_for') {
  616. out.list = syntaxTree(gen, key, n.list)
  617. out.body = syntaxTree(gen, key, n.body)
  618. } else if (n.tag == 'view_if') {
  619. out.condition = syntaxTree(gen, key, n.condition)
  620. out.consequent = syntaxTree(gen, key, n.consequent)
  621. out.alternate = syntaxTree(gen, key, n.alternate)
  622. } else if (n.tag == 'on_statement') {
  623. out.event = n.event
  624. } else if (n.tag == 'member_expression') {
  625. out.object = syntaxTree(gen, key, n.object)
  626. out.property = syntaxTree(gen, key, n.property)
  627. } else if (n.tag == 'call_expression') {
  628. out.callee = syntaxTree(gen, key, n.callee)
  629. let args = []
  630. for (h of n.args) { args.push(syntaxTree(gen, key, h)) }
  631. out.args = args
  632. } else if (n.tag == 'binary_expression') {
  633. out.left = syntaxTree(gen, key, n.left)
  634. out.right = syntaxTree(gen, key, n.right)
  635. } else if (n.tag == 'unary_expression') {
  636. out.operand = syntaxTree(gen, key, n.operand)
  637. } else if (n.tag == 'conditional_expression') {
  638. out.condition = syntaxTree(gen, key, n.condition)
  639. out.consequent = syntaxTree(gen, key, n.consequent)
  640. out.alternate = syntaxTree(gen, key, n.alternate)
  641. } else if (n.tag == 'array_expression') {
  642. let els = []
  643. for (h of n.elements) { els.push(syntaxTree(gen, key, h)) }
  644. out.elements = els
  645. }
  646. return out
  647. }
  648. // ---- the reductions the browser used to make on every boot --------------------------
  649. // A `for`'s names are what its list reads, an `if`'s what its condition reads — filed
  650. // under the region's own site.
  651. regionIndex(t) {
  652. let idx = {}
  653. for (m of t.members) { if (m.nodes != null) { indexRegions(&idx, m.nodes) } }
  654. return idx
  655. }
  656. indexRegions(&idx, n) {
  657. if (n == null) { return null }
  658. if (n.tag == 'view_for') {
  659. idx[n.site] = memberNames(nodeReads(n.list))
  660. indexRegions(&idx, n.body)
  661. return null
  662. }
  663. if (n.tag == 'view_if') {
  664. idx[n.site] = memberNames(nodeReads(n.condition))
  665. indexRegions(&idx, n.consequent)
  666. indexRegions(&idx, n.alternate)
  667. return null
  668. }
  669. if (n.tag == 'on_statement') { return null }
  670. for (c of [n.value, n.list, n.body, n.condition, n.consequent, n.alternate, n.object, n.property, n.callee, n.left, n.right, n.operand]) {
  671. if (c != null) { indexRegions(&idx, c) }
  672. }
  673. for (l of [n.entries, n.args, n.elements]) {
  674. if (l != null) { for (c of l) { indexRegions(&idx, c) } }
  675. }
  676. return null
  677. }
  678. // the member reads of a read list — the language resolved every name, so this is a
  679. // filter and not a second resolution
  680. memberNames(reads) {
  681. let out = []
  682. if (reads == null) { return out }
  683. for (r of reads) {
  684. if (r.kind == 'member' && r.isStatic != true && !out.includes(r.name)) { out.push(r.name) }
  685. }
  686. return out
  687. }
  688. nodeReads(n) {
  689. if (n == null) { return [] }
  690. if (n.reads != null) { return n.reads }
  691. let out = []
  692. for (c of [n.value, n.list, n.body, n.condition, n.consequent, n.alternate, n.object, n.property, n.callee, n.left, n.right, n.operand]) {
  693. if (c != null) { for (r of nodeReads(c)) { out.push(r) } }
  694. }
  695. for (l of [n.entries, n.args, n.elements]) {
  696. if (l != null) { for (c of l) { for (r of nodeReads(c)) { out.push(r) } } }
  697. }
  698. return out
  699. }
  700. // A ROUTINE'S WRITES, PLUS THE WRITES OF EVERYTHING IT REACHES — the method it calls
  701. // and the handler it emits to, transitively. Both are READS the language resolved, so
  702. // this is a walk of its answer and never a guess.
  703. closeWrites(t, writes, reads) {
  704. let out = memberNames(writes)
  705. let queue = []
  706. reached(&queue, reads)
  707. let seen = {}
  708. while (queue.length > 0) {
  709. let at = queue.shift()
  710. if (seen[at] == null) {
  711. seen[at] = true
  712. let sp = at.indexOf(' ')
  713. let kind = at.slice(0, sp)
  714. let name = at.slice(sp + 1)
  715. for (mm of t.methods) {
  716. if (kind == 'method' && mm.name == name) {
  717. for (w of memberNames(mm.writes)) { if (!out.includes(w)) { out.push(w) } }
  718. reached(&queue, mm.reads)
  719. }
  720. }
  721. for (h of t.handlers) {
  722. if (kind == 'handler' && !h.nested && h.event == name) {
  723. for (w of memberNames(h.writes)) { if (!out.includes(w)) { out.push(w) } }
  724. reached(&queue, h.reads)
  725. }
  726. }
  727. for (mb of t.members) {
  728. if (kind == 'member' && mb.name == name && mb.nodes != null) {
  729. for (w of memberNames(mb.nodes.writes)) { if (!out.includes(w)) { out.push(w) } }
  730. reached(&queue, nodeReads(mb.nodes))
  731. }
  732. }
  733. }
  734. }
  735. return out
  736. }
  737. // the mirror: a handler's reads plus the reads of everything it reaches — the
  738. // candidates for a routine a host handed down, whose writes only the host's table names
  739. closeReads(t, reads) {
  740. let out = memberNames(reads)
  741. let queue = []
  742. reached(&queue, reads)
  743. let seen = {}
  744. while (queue.length > 0) {
  745. let at = queue.shift()
  746. if (seen[at] == null) {
  747. seen[at] = true
  748. let sp = at.indexOf(' ')
  749. let kind = at.slice(0, sp)
  750. let name = at.slice(sp + 1)
  751. for (mm of t.methods) {
  752. if (kind == 'method' && mm.name == name) {
  753. for (r of memberNames(mm.reads)) { if (!out.includes(r)) { out.push(r) } }
  754. reached(&queue, mm.reads)
  755. }
  756. }
  757. for (h of t.handlers) {
  758. if (kind == 'handler' && !h.nested && h.event == name) {
  759. for (r of memberNames(h.reads)) { if (!out.includes(r)) { out.push(r) } }
  760. reached(&queue, h.reads)
  761. }
  762. }
  763. for (mb of t.members) {
  764. if (kind == 'member' && mb.name == name && mb.nodes != null) {
  765. for (r of memberNames(nodeReads(mb.nodes))) { if (!out.includes(r)) { out.push(r) } }
  766. reached(&queue, nodeReads(mb.nodes))
  767. }
  768. }
  769. }
  770. }
  771. return out
  772. }
  773. reached(&queue, reads) {
  774. if (reads == null) { return null }
  775. for (r of reads) {
  776. if (r.kind == 'method' || r.kind == 'handler' || (r.kind == 'member' && r.isStatic != true)) { queue.push(r.kind + ' ' + r.name) }
  777. }
  778. return null
  779. }
  780. // ---- THE SERVER RENDER, COMPILED --------------------------------------------------
  781. // The old hl:web rendered a page by WALKING the View tree per request: for every node it asks
  782. // what kind it is, for every element it builds the attribute string, and it decides the
  783. // indentation of every line again, on every request, for every one of the 121 mounts a
  784. // page may hold. Every one of those decisions is a fact about the View.
  785. //
  786. // So a component's render is compiled ONCE into a list of STEPS — constant text, and a
  787. // read with its own closure — and a request is a concatenation. The structure (tags,
  788. // static attributes, the readable form's line breaks, which element is a block) is
  789. // decided at compile time; only the values are asked for per request.
  790. //
  791. // KEYED BY THE INDENT IT IS RENDERED AT, because that is the only thing about a
  792. // component's HTML that its caller decides: the shell renders at one tab, the page at
  793. // the depth the slot stands, a fill compactly (null). An app has a handful of those.
  794. serverSteps(key, nodes, indent, classes, slotted) {
  795. let ck = key + '|' + (indent == null ? '~' : indent) + '|' + joinList(classes) + '|' + (slotted ? '1' : '0')
  796. if (steps[ck] == null) {
  797. steps[ck] = blockSteps(classes == null || classes.length == 0 ? nodes : v.withRootClasses(nodes, classes), indent, slotted)
  798. }
  799. return steps[ck]
  800. }
  801. // A `slot` THE HOST FILLED COUNTS AS CONTENT, and an empty one does not — the one
  802. // thing about a component's shape its caller decides, so it is part of the key.
  803. staticVoid = [ 'area' 'base' 'br' 'col' 'embed' 'hr' 'img' 'input' 'link' 'meta' 'source' 'track' 'wbr' ]
  804. staticVerbatim = [ 'textarea' 'pre' 'script' 'style' ]
  805. blocky(n, slotted) {
  806. if (staticVerbatim.includes(n.tag)) { return false }
  807. if (n.children.length == 0) { return false }
  808. for (c of n.children) {
  809. if (c.k == 'text' || c.k == 'field') { return false }
  810. if (c.k == 'member' && !(c.name == 'slot' && slotted)) { return false }
  811. }
  812. return true
  813. }
  814. steps = {}
  815. v = new View
  816. joinList(l) {
  817. if (l == null) { return '' }
  818. let out = ''
  819. for (c of l) { out = out + ',' + c }
  820. return out
  821. }
  822. // `render(nodes, …, indent)`: the readable form puts each element on its own line at
  823. // `indent`, the compact one writes them end to end.
  824. blockSteps(nodes, indent, slotted) {
  825. let out = []
  826. for (n of nodes) {
  827. if (indent != null) {
  828. if (n.k == 'el') { push(&out, { k = 's'; v = newline + indent; }) nodeSteps(&out, n, indent, slotted) }
  829. else if (n.k == 'component') { nodeSteps(&out, n, indent, slotted) }
  830. else if (n.k == 'for') { forSteps(&out, n, indent, slotted) }
  831. else if (n.k == 'if') { ifSteps(&out, n, indent, slotted) }
  832. else if (n.k == 'member' && n.name == 'slot') { push(&out, { k = 'slotline'; v = newline + indent; }) }
  833. else if (n.k != 'on') { nodeSteps(&out, n, indent, slotted) }
  834. } else {
  835. nodeSteps(&out, n, null, slotted)
  836. }
  837. }
  838. return out
  839. }
  840. forSteps(&out, n, indent, slotted) {
  841. out.push({ k = 'for'; row = n.row; read = readFn(n.list); body = blockSteps(n.body, indent, slotted); })
  842. return null
  843. }
  844. ifSteps(&out, n, indent, slotted) {
  845. out.push({ k = 'if'; read = readFn(n.cond); then = blockSteps(n.then, indent, slotted); other = blockSteps(n.other, indent, slotted); })
  846. return null
  847. }
  848. // one node's steps — `html(node, …)`, decided once
  849. nodeSteps(&out, n, indent, slotted) {
  850. if (n.k == 'text') { push(&out, { k = 's'; v = v.escape(n.text); }) return null }
  851. if (n.k == 'on') { return null }
  852. if (n.k == 'member' && n.name == 'slot') { out.push({ k = 'slot'; }) return null }
  853. if (n.k == 'member' || n.k == 'field') { out.push({ k = 'r'; read = readFn(n); }) return null }
  854. if (n.k == 'for') { forSteps(&out, n, indent, slotted) return null }
  855. if (n.k == 'if') { ifSteps(&out, n, indent, slotted) return null }
  856. if (n.k == 'component') {
  857. out.push({ k = 'kid'; path = n.path; cls = n.cls; key = n.key; classes = n.classes; indent = indent;
  858. fill = (n.fill == null || n.fill.length == 0) ? null : blockSteps(n.fill, null, slotted); })
  859. return null
  860. }
  861. if (n.k == 'el') { elSteps(&out, n, indent, slotted) return null }
  862. push(&out, { k = 's'; v = '<!-- ' + n.tag + ' not rendered yet -->'; })
  863. return null
  864. }
  865. elSteps(&out, n, indent, slotted) {
  866. let attrs = []
  867. let valueRead = null // a textarea's value is its content
  868. let selRead = null // a select's value selects an option
  869. for (a of n.attrs) {
  870. if (n.tag == 'textarea' && a.name == 'value') { valueRead = attrRead(a) }
  871. else if (n.tag == 'select' && a.name == 'value') { selRead = attrRead(a) }
  872. // a bound BOOLEAN attribute is its presence (ticket #33): ` checked=""` or nothing
  873. else if ((a.member != null || a.ref != null) && v.isBoolAttr(a.name)) { attrs.push({ k = 'b'; name = a.name; read = attrRead(a); }) }
  874. else if (a.member != null || a.ref != null) {
  875. push(&attrs, { k = 's'; v = ' ' + a.name + '="'; })
  876. attrs.push({ k = 'r'; read = attrRead(a); })
  877. push(&attrs, { k = 's'; v = v.extraOf(a) + '"'; })
  878. } else {
  879. push(&attrs, { k = 's'; v = ' ' + a.name + '="' + v.escape(a.text) + '"'; })
  880. }
  881. }
  882. if (v.boundValue(n)) { push(&attrs, { k = 's'; v = ' autocomplete="off"'; }) }
  883. // the children, in the form this element takes: a block puts them one per line
  884. let kids = []
  885. if (indent != null && blocky(n, slotted)) {
  886. kids = blockSteps(n.children, indent + tab, slotted)
  887. push(&kids, { k = 's'; v = newline + indent; })
  888. } else {
  889. kids = blockSteps(n.children, null, slotted)
  890. }
  891. if (valueRead != null) { kids = [{ k = 'r'; read = valueRead; }] }
  892. // AN OPTION IS THE ONE ELEMENT WHOSE OWN TAG DEPENDS ON THE VALUE ABOVE IT: it
  893. // carries `selected` when its value is the select's. Everything else is settled.
  894. if (n.tag == 'option') {
  895. out.push({ k = 'opt'; attrs = attrs; body = kids; ov = optionRead(n); })
  896. return null
  897. }
  898. push(&out, { k = 's'; v = '<' + n.tag; })
  899. for (a of attrs) { push(&out, a) }
  900. if (staticVoid.includes(n.tag)) { push(&out, { k = 's'; v = '>'; }) return null }
  901. push(&out, { k = 's'; v = '>'; })
  902. // A SELECT HANDS ITS VALUE TO ITS OPTIONS, however deep they stand: its children
  903. // run under it, and it is the only element that changes anything for them.
  904. if (n.tag == 'select') { out.push({ k = 'sel'; read = selRead; body = kids; }) }
  905. else { for (kk of kids) { push(&out, kk) } }
  906. push(&out, { k = 's'; v = '</' + n.tag + '>'; })
  907. return null
  908. }
  909. // CONSTANT TEXT MERGES. Two literal steps in a row are one string decided at compile
  910. // time — which is what makes a request a concatenation of a handful of pieces instead
  911. // of one per node.
  912. push(&out, step) {
  913. if (step.k == 's' && out.length > 0 && out[out.length - 1].k == 's') {
  914. let last = out[out.length - 1]
  915. last.v = last.v + step.v
  916. out[out.length - 1] = last
  917. return null
  918. }
  919. out.push(step)
  920. return null
  921. }
  922. // ---- the reads, each with its own closure ------------------------------------------
  923. // `view.value`'s rule, split once: a `for` row variable in scope wins over a member of
  924. // the same name, and a field path walks into it. The closure holds the name — there is
  925. // no node to look at per request.
  926. readFn(node) {
  927. if (node == null) { return (inst, rows) => { return null } }
  928. if (node.k == 'text') { let t = v.literalValue(node.kind, node.text) return (inst, rows) => { return t } }
  929. let name = node.name
  930. if (node.k != 'field') {
  931. return (inst, rows) => { return rows[name] != null ? rows[name] : inst[name] }
  932. }
  933. let path = node.path
  934. return (inst, rows) => {
  935. let val = rows[name] != null ? rows[name] : inst[name]
  936. for (f of path) {
  937. if (val == null) { return null }
  938. val = val[f]
  939. }
  940. return val
  941. }
  942. }
  943. attrRead(a) {
  944. if (a.member != null) { return readFn({ k = 'member'; name = a.member; }) }
  945. if (a.ref != null) { return readFn(a.ref) }
  946. let t = a.text
  947. return (inst, rows) => { return t }
  948. }
  949. // an option's own value: its `value` attribute, or null when it has none (the browser
  950. // then reports its text, and the run compares against what it rendered)
  951. optionRead(n) {
  952. for (a of n.attrs) {
  953. if (a.name == 'value') { return attrRead(a) }
  954. }
  955. return null
  956. }
  957. // ---- the run: a concatenation ------------------------------------------------------
  958. runSteps(list, &inst, &rows, &ctx) {
  959. let out = ''
  960. for (s of list) {
  961. if (s.k == 's') { out = out + s.v }
  962. else if (s.k == 'r') { let f = s.read out = out + v.escape(f(&inst, &rows)) }
  963. else if (s.k == 'b') { let f = s.read if (v.boolOn(f(&inst, &rows))) { out = out + ' ' + s.name + '=""' } }
  964. else if (s.k == 'slot') { if (ctx.slot != null) { out = out + ctx.slot } }
  965. else if (s.k == 'slotline') { if (ctx.slot != null) { out = out + s.v + ctx.slot } }
  966. else if (s.k == 'for') {
  967. let f = s.read
  968. let list2 = f(&inst, &rows)
  969. if (list2 != null) {
  970. let ri = 0
  971. for (entry of list2) {
  972. let inner = rows + {}
  973. inner[s.row] = entry
  974. let rc = v.rowCtx(&ctx, &entry, ri)
  975. out = out + runSteps(s.body, &inst, &inner, &rc)
  976. ri = ri + 1
  977. }
  978. }
  979. }
  980. else if (s.k == 'if') {
  981. let f = s.read
  982. if (f(&inst, &rows)) { out = out + runSteps(s.then, &inst, &rows, &ctx) }
  983. else { out = out + runSteps(s.other, &inst, &rows, &ctx) }
  984. }
  985. else if (s.k == 'opt') {
  986. let head = runSteps(s.attrs, &inst, &rows, &ctx)
  987. let body = runSteps(s.body, &inst, &rows, &ctx)
  988. let mark = ''
  989. if (ctx.sel != null) {
  990. let ov = body
  991. if (s.ov != null) { let f = s.ov ov = '' + f(&inst, &rows) }
  992. if (ov == ctx.sel) { mark = ' selected' }
  993. }
  994. out = out + '<option' + head + mark + '>' + body + '</option>'
  995. }
  996. else if (s.k == 'sel') {
  997. let f = s.read
  998. let inner = ctx + {}
  999. inner.sel = f == null ? null : '' + f(&inst, &rows)
  1000. out = out + runSteps(s.body, &inst, &rows, &inner)
  1001. }
  1002. else if (s.k == 'kid') {
  1003. let kid = ctx.kids[v.kidKeyIn(s.path, ctx)]
  1004. if (kid == null) { out = out + '<!-- ' + s.cls + ' not mounted -->' }
  1005. else {
  1006. let fillText = s.fill == null ? null : runSteps(s.fill, &inst, &rows, &ctx)
  1007. let kidCtx = { slot = fillText; kids = kid.kids; }
  1008. let kidInst = kid.page
  1009. let empty = {}
  1010. out = out + runSteps(serverSteps(s.key, kid.view, s.indent, s.classes, fillText != null), &kidInst, &empty, &kidCtx)
  1011. }
  1012. }
  1013. }
  1014. return out
  1015. }

Branches

Latest commits

  • 4a2d7125initial commitmre