]> dreyeck.freedombox.rocks Git - idiomatic.git/blob - index.js
020a1683dc14fc0a412ac18840d99f8cd031adfa
[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 = title => `
26 <style>
27 body {font-family:sans-serif;}
28 a {text-decoration:none;}
29 td:first-child {text-align:right;}
30 .hi {background-color:pink;}
31 </style>
32 <span style="letter-spacing:.2rem; font-size:1.4rem;">— ${title} —</span>`
33 const app = express()
34
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)
40 const result = `
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)}`)
45 .join("\n")}</table>`
46 res.send(style('index')+result);
47 next()
48 })
49
50 function link(key) {
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>`
54 return key
55 }
56
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>`)
67 .join("\n")}</table>`
68 res.send(result)
69 })
70
71 app.get('/usage', (req,res) => {
72 const {type,field,key,up,see} = req.query
73 const list = []
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}">
78 ${stack.at(-1)}</a>
79 <td>${sxpr(stack[up ?? 2], see ?? 3)}`)
80 }
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>`)
87 })
88
89 app.get('/nesting', (req,res) => {
90 const {file,type,start,end} = req.query
91 const result = []
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) => `
96 <tr>
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()
99 const hit = stack[1]
100 result.push(`
101 <p><table>${path.join("")}</table><br>
102 <p><pre>${escape(JSON.stringify(hit,omit,2))}</pre>`)
103 }
104 }
105 visitor.wander(mods,doit)
106 res.send(style('nesting')+`${result.join("<hr>")}`)
107 })
108
109
110 // H E L P E R S
111
112 function counter() {
113 const counts = new Map()
114 return {
115 count(item) {
116 if(counts.has(item))
117 counts.set(item, counts.get(item)+1)
118 else
119 counts.set(item,1)
120 return item
121 },
122 size() {
123 return counts.size
124 },
125 total() {
126 return [...counts]
127 .reduce((sum,each) => sum + each[1], 0)
128 },
129 tally() {
130 return [...counts]
131 .sort((a,b) => a[1]==b[1] ? (a[0]>b[0] ? 1 : -1) : b[1]-a[1])
132 },
133 }
134 }
135
136 function escape(text) {
137 try {
138 return text
139 .replace(/&/g, '&amp;')
140 .replace(/</g, '&lt;')
141 .replace(/>/g, '&gt;')
142 .replace(/\*(.+?)\*/g, '<i>$1</i>')
143 } catch (e) {
144 return text
145 }
146 }
147
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
151 if (obj) {
152 if(deep) {
153 const fields = Object.entries(obj)
154 .filter(([k,v]) => !['start','end','raw','computed','optional','kind'].includes(k))
155 .map(([k,v]) =>
156 k=='type' ? abv(v) :
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))
160 .join(" ")
161 return key ? `<span ${hilite} title=${key}>(${(fields)})</span>` : `(${(fields)})`
162 } else return elipsis(obj)
163 } else return `<span title=${obj}>.</span>`
164 }
165
166 function abv(type) {
167 return `<span title=${type}>${type.replaceAll(/[a-z]/g,'')}</span>`
168 }
169
170 function omit(k,v) {
171 return k=='type'?v:k=='start'||k=='end'?undefined:v
172 }
173
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>)`
178 }
179
180 app.listen(1954)