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
= '<style> a {text-decoration: none} .hi {background-color:pink} </style>'
28 app
.get('/index', async (req
,res
,next
) => {
29 console
.log(new Date().toLocaleTimeString(), 'index')
30 const reductions
= counter()
31 const doit
= branch
=> {reductions
.count(branch
.type
)}
32 visitor
.wander(mods
,doit
)
34 <p>${reductions.size()} non-terminals
35 <br>${reductions.total()} reductions
36 <p>${reductions.tally()
37 .map(([k,v]) => `${v} ${link(k)}
`)
39 res
.send(style
+result
);
44 if(key
.match(/^Ident/)) return `<a href="/terminal?type=${key}&field=name">${key}</a>`
45 if(key
.match(/^(As|B|L|U).*Ex/)) return `<a href="/terminal?type=${key}&field=operator">${key}</a>`
46 if(key
.match(/^Lit/)) return `<a href="/terminal?type=${key}&field=value">${key}</a>`
51 app
.get('/terminal', (req
,res
) => {
52 const {type
,field
} = req
.query
53 const lits
= counter()
54 const doit
= branch
=> {if(branch
.type
==type
) lits
.count(branch
[field
])}
55 visitor
.wander(mods
,doit
)
57 <p>${lits.size()} uniques
58 <br>${lits.total()} total
60 .map(([k,v]) => `${v}
<a href
="/usage?type=${type}&field=${field}&key=${encodeURIComponent(k)}">${escape(k)}
</a
>`)
62 res
.send(style
+result
)
65 app
.get('/usage', (req
,res
) => {
66 const {type
,field
,key
} = req
.query
68 const doit
= (branch
,stack
) => {
69 if(branch
.type
==type
&& branch
[field
]==key
)list
.push(`
70 <a href="/nesting/?file=${stack.at(-1)}&type=${type}&start=${branch.start}&end=${branch.end}">
74 visitor
.wander(mods
,doit
)
75 res
.send(style
+`<pre>${JSON.stringify(req.query,null,2)}</pre>${list.join("<br>")}`)
78 app
.get('/nesting', (req
,res
) => {
79 const {file
,type
,start
,end
} = req
.query
81 const doit
= (branch
,stack
) => {
82 if(stack
.at(-1)==file
&& branch
.type
==type
&& branch
.start
==start
&& branch
.end
==end
) {
83 const file
= stack
.at(-1)
84 const path
= stack
.slice(0,-1).map((n
,i
) => `
85 <tr><td style="text-align:right;">
86 <a title=${file} href=/similar?pos=${`${file}-${start}-${end}`}&depth
=${i}
>${n.type}
</a
>:
87 <td
>${sxpr(n,3,null,stack[i-1])}
`).reverse()
90 <table
>${path.join("")}
</table
><br
><br
>\n
91 <pre
>${escape(JSON.stringify(hit,omit,2))}
</pre
>`)
94 visitor.wander(mods,doit)
95 res.send(style+`<pre
>${JSON.stringify(req.query,null,2)}
</pre
>${result.join("<br>")}
`)
102 const counts = new Map()
106 counts.set(item, counts.get(item)+1)
115 .reduce((sum,each) => sum + each[1], 0)
119 .sort((a,b) => a[1]==b[1] ? (a[0]>b[0] ? 1 : -1) : b[1]-a[1])
124 function escape(text) {
127 .replace(/&/g, '&')
128 .replace(/</g, '<')
129 .replace(/>/g, '>')
130 .replace(/\*(.+?)\*/g, '<i>$1</i>')
136 function sxpr(obj,deep,key,child) {
137 const hilite = obj===child ? 'class="hi"' : ''
138 const link = word => obj.type == 'Identifier' ? `<a href
=/usage?type=Identifier&field=name&key=${word}>${word}</a>` : word
141 const fields = Object.entries(obj)
142 .filter(([k,v]) => !['start','end','raw','computed','optional','kind'].includes(k))
145 (typeof v == 'string') ? link(escape(v)) :
146 Array.isArray(v) ? `[${v.map(o => sxpr(o,deep-1,k,child)).join(" ")}
]` :
147 sxpr(v, deep-1, k, child))
149 return key ? `<span ${hilite} title
=${key}
>(${(fields)}
)</span
>` : `(${(fields)}
)`
150 } else return elipsis(obj)
151 } else return `<span title
=${obj}
>.</span
>`
155 return `<span title
=${type}
>${type.replaceAll(/[a-z]/g,'')}
</span
>`
159 return k=='type'?v:k=='start'||k=='end'?undefined:v
162 function elipsis(obj) {
163 const bytes = (obj.end||0)-(obj.start||0)
164 const dots = '..' + '.'.repeat(Math.floor(Math.log2(bytes||1)))
165 return `(<span title
="${bytes} bytes">${dots}
</span
>)`