1 const express
= require('express')
2 const acorn
= require('acorn')
3 const fs
= require('fs')
4 // const fs = require('node:fs/promises');
5 const visitor
= require('./visitor.js')
7 const dir
= '../wiki-client/lib'
9 fs
.readdir(dir
, async (err
, files
) => {
10 mods
.push(... await Promise
.all(files
.map(load
)))
13 async
function load(file
) {
14 return new Promise(resolve
=> {
15 fs
.readFile(`${dir}/${file}`, "utf8", (err
,text
) => {
16 const tree
= acorn
.parse(text
, {ecmaVersion
: "latest"})
17 resolve({file
,text
,tree
})
25 const style
= title
=> `
27 body {font-family:sans-serif;}
28 a {text-decoration:none;}
29 td:first-child {text-align:right;}
30 .hi {background-color:pink;}
32 <span style="letter-spacing:.2rem; font-size:1.4rem;">— ${title} —</span>`
35 app
.get('/index', async (req
,res
,next
) => {
36 console
.log(new Date().toLocaleTimeString(), 'index')
37 const reductions
= counter()
38 const doit
= branch
=> {reductions
.count(branch
.type
)}
39 visitor
.wander(mods
,doit
)
41 <p>${reductions.size()} non-terminals
42 <br>${reductions.total()} reductions
43 <p><table>${reductions.tally()
44 .map(([k,v]) => `<tr
><td
>${v}
<td
>${link(k)}
`)
46 res
.send(style('index')+result
);
51 if(key
.match(/^Ident/)) return `<a href="/terminal?type=${key}&field=name">${key}</a>`
52 if(key
.match(/^(As|B|L|U).*Ex/)) return `<a href="/terminal?type=${key}&field=operator">${key}</a>`
53 if(key
.match(/^Lit/)) return `<a href="/terminal?type=${key}&field=value">${key}</a>`
57 app
.get('/terminal', (req
,res
) => {
58 const {type
,field
} = req
.query
59 const lits
= counter()
60 const doit
= branch
=> {if(branch
.type
==type
) lits
.count(branch
[field
])}
61 visitor
.wander(mods
,doit
)
62 const result
= style('terminal')+`
63 <p>${lits.size()} uniques
64 <br>${lits.total()} total
65 <p><table>${lits.tally()
66 .map(([k,v]) => `<tr
><td
>${v}
<td
><a href
="/usage?type=${type}&field=${field}&key=${encodeURIComponent(k)}&up=2&see=3">${escape(k)}
</a
>`)
71 app
.get('/usage', (req
,res
) => {
72 const {type
,field
,key
,up
,see
} = req
.query
74 const files
= counter()
75 const doit
= (branch
,stack
) => {
76 if(branch
.type
==type
&& branch
[field
]==key
)list
.push(`
77 <tr><td><a href="/nesting/?file=${files.count(stack.at(-1))}&type=${type}&start=${branch.start}&end=${branch.end}">
79 <td>${sxpr(stack[up ?? 2], see ?? 3)}`)
81 visitor
.wander(mods
,doit
)
82 const vis
= row
=> row
.split(/\n/)[3].trim().replaceAll(/<.*?>/g,'').replaceAll(/\.\.+/g,'..')
83 list
.sort((a
,b
) => vis(a
)>vis(b
) ? 1 : -1)
84 res
.send(style('usage')+`
85 <p><table>${files.tally().map(([k,v]) => `<tr><td>${v}<td>${k}`).join("\n")}</table
>
86 <p
><table
>${list.join("\n")}
</table
>`)
89 app.get('/nesting', (req,res) => {
90 const {file,type,start,end} = req.query
92 const doit = (branch,stack) => {
93 if(stack.at(-1)==file && branch.type==type && branch.start==start && branch.end==end) {
94 const file = stack.at(-1)
95 const path = stack.slice(0,-1).map((n,i) => `
97 <td
><a title
=${file} href
=/similar?pos=${`${file}-${start}-${end}`}&depth=${i}>${n.type}</a>:
98 <td
>${sxpr(n,3,null,stack[i-1])}
`).reverse()
101 <p
><table
>${path.join("")}
</table
><br
>
102 <p
><pre
>${escape(JSON.stringify(hit,omit,2))}
</pre
>`)
105 visitor.wander(mods,doit)
106 res.send(style('nesting')+`${result.join("<hr>")}
`)
113 const counts = new Map()
117 counts.set(item, counts.get(item)+1)
127 .reduce((sum,each) => sum + each[1], 0)
131 .sort((a,b) => a[1]==b[1] ? (a[0]>b[0] ? 1 : -1) : b[1]-a[1])
136 function escape(text) {
139 .replace(/&/g, '&')
140 .replace(/</g, '<')
141 .replace(/>/g, '>')
142 .replace(/\*(.+?)\*/g, '<i>$1</i>')
148 function sxpr(obj,deep,key,child) {
149 const hilite = obj===child ? 'class="hi"' : ''
150 const link = word => obj.type == 'Identifier' ? `<a href
=/usage?type=Identifier&field=name&key=${word}>${word}</a>` : word
153 const fields = Object.entries(obj)
154 .filter(([k,v]) => !['start','end','raw','computed','optional','kind'].includes(k))
157 (typeof v == 'string') ? link(escape(v)) :
158 Array.isArray(v) ? `[${v.map(o => sxpr(o,deep-1,k,child)).join(" ")}
]` :
159 sxpr(v, deep-1, k, child))
161 return key ? `<span ${hilite} title
=${key}
>(${(fields)}
)</span
>` : `(${(fields)}
)`
162 } else return elipsis(obj)
163 } else return `<span title
=${obj}
>.</span
>`
167 return `<span title
=${type}
>${type.replaceAll(/[a-z]/g,'')}
</span
>`
171 return k=='type'?v:k=='start'||k=='end'?undefined:v
174 function elipsis(obj) {
175 const bytes = (obj.end||0)-(obj.start||0)
176 const dots = '..' + '.'.repeat(Math.floor(Math.log2(bytes||1)))
177 return `(<span title
="${bytes} bytes">${dots}
</span
>)`