]> dreyeck.freedombox.rocks Git - idiomatic.git/blob - index.js
add nesting page
[idiomatic.git] / index.js
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')
6
7 const dir = '../wiki-client/lib'
8 const mods = []
9 fs.readdir(dir, async (err, files) => {
10 mods.push(... await Promise.all(files.map(load)))
11 })
12
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})
18 })
19 })
20 }
21
22
23 // P A G E S
24
25 const style = '<style> a {text-decoration: none} .hi {background-color:pink} </style>'
26 const app = express()
27
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)
33 const result = `
34 <p>${reductions.size()} non-terminals
35 <br>${reductions.total()} reductions
36 <p>${reductions.tally()
37 .map(([k,v]) => `${v} ${link(k)}`)
38 .join("<br>")}`
39 res.send(style+result);
40 next()
41 })
42
43 function link(key) {
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>`
47 return key
48 }
49
50
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)
56 const result = `
57 <p>${lits.size()} uniques
58 <br>${lits.total()} total
59 <p>${lits.tally()
60 .map(([k,v]) => `${v} <a href="/usage?type=${type}&field=${field}&key=${encodeURIComponent(k)}">${escape(k)}</a>`)
61 .join("<br>")}`
62 res.send(style+result)
63 })
64
65 app.get('/usage', (req,res) => {
66 const {type,field,key} = req.query
67 const list = []
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}">
71 ${stack.at(-1)}</a>
72 ${sxpr(stack[2],3)}`)
73 }
74 visitor.wander(mods,doit)
75 res.send(style+`<pre>${JSON.stringify(req.query,null,2)}</pre>${list.join("<br>")}`)
76 })
77
78 app.get('/nesting', (req,res) => {
79 const {file,type,start,end} = req.query
80 const result = []
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()
88 const hit = stack[1]
89 result.push(`
90 <table>${path.join("")}</table><br><br>\n
91 <pre>${escape(JSON.stringify(hit,omit,2))}</pre>`)
92 }
93 }
94 visitor.wander(mods,doit)
95 res.send(style+`<pre>${JSON.stringify(req.query,null,2)}</pre>${result.join("<br>")}`)
96 })
97
98
99 // H E L P E R S
100
101 function counter() {
102 const counts = new Map()
103 return {
104 count(item) {
105 if(counts.has(item))
106 counts.set(item, counts.get(item)+1)
107 else
108 counts.set(item,1)
109 },
110 size() {
111 return counts.size
112 },
113 total() {
114 return [...counts]
115 .reduce((sum,each) => sum + each[1], 0)
116 },
117 tally() {
118 return [...counts]
119 .sort((a,b) => a[1]==b[1] ? (a[0]>b[0] ? 1 : -1) : b[1]-a[1])
120 },
121 }
122 }
123
124 function escape(text) {
125 try {
126 return text
127 .replace(/&/g, '&amp;')
128 .replace(/</g, '&lt;')
129 .replace(/>/g, '&gt;')
130 .replace(/\*(.+?)\*/g, '<i>$1</i>')
131 } catch (e) {
132 return text
133 }
134 }
135
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
139 if (obj) {
140 if(deep) {
141 const fields = Object.entries(obj)
142 .filter(([k,v]) => !['start','end','raw','computed','optional','kind'].includes(k))
143 .map(([k,v]) =>
144 k=='type' ? abv(v) :
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))
148 .join(" ")
149 return key ? `<span ${hilite} title=${key}>(${(fields)})</span>` : `(${(fields)})`
150 } else return elipsis(obj)
151 } else return `<span title=${obj}>.</span>`
152 }
153
154 function abv(type) {
155 return `<span title=${type}>${type.replaceAll(/[a-z]/g,'')}</span>`
156 }
157
158 function omit(k,v) {
159 return k=='type'?v:k=='start'||k=='end'?undefined:v
160 }
161
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>)`
166 }
167
168 app.listen(1954)