]> dreyeck.freedombox.rocks Git - idiomatic.git/blobdiff - index.js
oops. package.json required for install
[idiomatic.git] / index.js
index b3334a5ab3fa47ac1c5a244f0dd97a4e4ccdcd49..9f1a2b7170993dae5eaff4fbcb7a3b7eb7b78b9c 100644 (file)
--- a/index.js
+++ b/index.js
@@ -1,33 +1,15 @@
-let express = require('express')
-let acorn = require('acorn')
-let fs = require('fs')
-// let fs = require('node:fs/promises');
+const express = require('express')
+const acorn = require('acorn')
+const fs = require('fs')
+const visitor = require('./visitor.js')
 
-let visitor = require('./visitor.js')
-let dir = '../wiki-client/lib'
-let mods = []
 
-const app = express()
-
-
-// P A G E S
+// P A R S E
 
-app.get('/index', async (req,res,next) => {
-  console.log(new Date().toLocaleTimeString(), 'index')
-  const reductions = counter()
-  fs.readdir(dir, async (err, files) => {
-    mods = await Promise.all(files.map(load))
-    const doit = branch => {reductions.count(branch.type)}
-    visitor.wander(mods,doit)
-    const result = `
-      <p>${reductions.size()} non-terminals
-      <br>${reductions.total()} reductions
-      <p>${reductions.tally()
-        .map(([k,v]) => `${v} ${link(k)}`)
-        .join("<br>")}`
-    res.send(result);
-    next()
-  })
+const dir = '../wiki-client/lib'
+const mods = []
+fs.readdir(dir, async (err, files) => {
+  mods.push(... await Promise.all(files.map(load)))
 })
 
 async function load(file) {
@@ -39,44 +21,113 @@ async function load(file) {
   })
 }
 
-function link(key) {
-  if(key.match(/^Ident/)) return `<a href="/terminal?type=${key}&field=name">${key}</a>`
-  if(key.match(/^(As|B|L|U).*Ex/)) return `<a href="/terminal?type=${key}&field=operator">${key}</a>`
-  if(key.match(/^Lit/)) return `<a href="/terminal?type=${key}&field=value">${key}</a>`
-  return key
-}
 
+// P A G E S
 
-app.get('/terminal', (req,res) => {
-  const lits = counter()
-  const id = req.query.type
-  const field = req.query.field
-  const doit = branch => {if(branch.type==id) lits.count(branch[field])}
-  visitor.wander(mods,doit)
+const style = (title,here='') => `
+  <style>
+    body {font-family:sans-serif;}
+    a {text-decoration:none;}
+    td:first-child {text-align:right;}
+    .hi {background-color:pink;}
+    section {letter-spacing:.2rem; font-size:1.2rem;}
+   </style>
+   <section>${title} <span style="background-color:#ddd;">&nbsp;${escape(here)}&nbsp;</span></section>`
+const app = express()
+
+app.get('/index', async (req,res) => {
+  const reductions = counter()
+  visitor.walk(mods,branch =>
+    reductions.count(branch.type))
   const result = `
-    <p>${lits.size()} uniques
-    <br>${lits.total()} total
-    <p>${lits.tally()
-      .map(([k,v]) => `${v} <a href="/usage?type=${id}&field=${field}&key=${encodeURIComponent(k)}">${escape(k)}</a>`)
-      .join("<br>")}`
+    <p>${reductions.size()} non-terminals
+    <br>${reductions.total()} reductions
+    <p><table>${reductions.tally()
+      .map(([k,v]) => `<tr><td>${v}<td>${link(k)}`)
+      .join("\n")}</table>`
+  res.send(style('index')+result);
+
+  function link(key) {
+    if(key.match(/^Ident/)) return `<a href="/terminal?type=${key}&field=name">${key}</a>`
+    if(key.match(/^(As|B|L|U).*Ex/)) return `<a href="/terminal?type=${key}&field=operator">${key}</a>`
+    if(key.match(/^Lit/)) return `<a href="/terminal?type=${key}&field=value">${key}</a>`
+    return key
+  }
+})
+
+app.get('/terminal', (req,res) => {
+  const {type,field} = req.query
+  const terms = counter()
+  visitor.walk(mods,branch => {
+    if(branch.type==type)
+      terms.count(branch[field])})
+  const result = style('terminal',type)+`
+    <p>${terms.size()} uniques
+    <br>${terms.total()} total
+    <p><table>${terms.tally()
+      .map(([k,v]) => `<tr><td>${v}<td><a href="/usage?type=${type}&field=${field}&key=${encodeURIComponent(k)}&width=2&depth=3">${escape(k)}</a>`)
+      .join("\n")}</table>`
   res.send(result)
 })
 
 app.get('/usage', (req,res) => {
-  const type = req.query.type
-  const field = req.query.field
-  const key = req.query.key
+  const {type,field,key,width,depth} = req.query
   const list = []
-  const doit = (branch,stack) => {
-    if(branch.type==type && branch[field]==key)
-      list.push(`${stack.at(-1)} ${sxpr(stack[2],3)}`
-        // `${stack.length}
-        // ${stack.at(-1)}-${branch.start}-${branch.end}
-        // (${stack.slice(0,6).map(n => n.end-n.start).join(" ")})`
-        )
-  }
-  visitor.wander(mods,doit)
-  res.send(`<pre>${JSON.stringify(req.query,null,2)}</pre>${list.join("<br>")}`)
+  const files = counter()
+  visitor.walk(mods,(branch,stack) => {
+    if(branch.type==type && branch[field]==key)list.push(`
+      <tr><td><a href="/nesting/?file=${files.count(stack.at(-1))}&type=${type}&key=${key}&start=${branch.start}&end=${branch.end}">
+      ${stack.at(-1)}</a>
+      <td>${sxpr(stack[width ?? 2], depth ?? 3)}`)})
+  list.sort((a,b) => vis(a)>vis(b) ? 1 : -1)
+  const q = (id,delta) => Object.entries(req.query)
+    .map(([k,v]) => k == id ? `${k}=${+v+delta}` : `${k}=${v}`)
+    .join('&')
+  const p = id => `<a href=/usage?${q(id,+1)} style="background-color:#ddd;">&nbsp;&plus;&nbsp;</a>`
+  const m = id => `<a href=/usage?${q(id,-1)} style="background-color:#ddd;">&nbsp;&minus;&nbsp;</a>`
+  const d = id => `<span title=${req.query[id]}>${id} ${p(id)} ${m(id)}</span>`
+  res.send(style('usage',key)+`
+    <p><details><summary>${files.total()} uses in ${files.size()} files</summary>
+      <table>${files.tally().map(([k,v]) => `<tr><td>${v}<td>${k}`).join("\n")}</table></details>
+    <p><section>${d('width')} ${d('depth')}</section>
+    <p><table>${list.join("\n")}</table>`)
+})
+
+app.get('/nesting', (req,res) => {
+  const {file,type,key,start,end} = req.query
+  const result = []
+  visitor.walk(mods,(branch,stack) => {
+    if(stack.at(-1)==file && branch.type==type && branch.start==start && branch.end==end) {
+      const path = stack.slice(0,-1).map((n,i) => `
+        <tr>
+        <td><a title=${file} href=/similar?${query(req.query)}&nest=${i}>${n.type}</a>:
+        <td>${sxpr(n,3,null,stack[i-1])}`).reverse()
+      const hit = stack[1]
+      result.push(`
+        <p><table>${path.join("")}</table><br>
+        <p><pre>${escape(JSON.stringify(hit,omit,2))}</pre>`)
+    }
+  })
+  res.send(style('nesting',key)+`${result.join("<hr>")}`)
+})
+
+app.get('/similar', (req,res) => {
+  const {file,type,key,start,end,nest} = req.query
+  let nested
+  visitor.walk(mods,(branch,stack) => {
+    if(stack.at(-1)==file && branch.type==type && branch.start==start && branch.end==end)
+      nested = stack[nest]})
+  const norm = node => vis(`\n\n\n${sxpr(node,3,null)}`)
+  const source = (file,node) => mods.find(mod => mod.file == file).text.substring(+node.start,+node.end)
+  const want = norm(nested)
+  const result = []
+  visitor.walk(mods,(branch,stack) => {
+    if(norm(branch) == want)
+      result.push(`<pre>${escape(source(stack.at(-1),branch))}</pre><hr>`)})
+  res.send(style('similar',key)+
+    `<p>${want}<hr>` +
+    result.join("\n")
+    )
 })
 
 
@@ -90,6 +141,7 @@ function counter() {
         counts.set(item, counts.get(item)+1)
       else 
         counts.set(item,1)
+      return item
     },
     size() {
       return counts.size
@@ -119,14 +171,14 @@ function escape(text) {
 
 function sxpr(obj,deep,key,child) {
   const hilite = obj===child ? 'class="hi"' : ''
-  const link = word => obj.type == 'Identifier' ? `<a href=/context?id=${word}>${word}</a>` : word
+  const link = word => obj.type == 'Identifier' ? `<a href=/usage?type=Identifier&field=name&key=${word}>${word}</a>` : word
   if (obj) {
     if(deep) {
       const fields = Object.entries(obj)
         .filter(([k,v]) => !['start','end','raw','computed','optional','kind'].includes(k))
         .map(([k,v]) =>
           k=='type' ? abv(v) :
-          (typeof v == 'string') ? link(expand(v)) :
+          (typeof v == 'string') ? link(escape(v)) :
           Array.isArray(v) ? `[${v.map(o => sxpr(o,deep-1,k,child)).join(" ")}]` :
           sxpr(v, deep-1, k, child))
         .join(" ")
@@ -149,12 +201,16 @@ function elipsis(obj) {
   return `(<span title="${bytes} bytes">${dots}</span>)`
 }
 
-function expand(text) {
-  return text
-    .replace(/&/g, '&amp;')
-    .replace(/</g, '&lt;')
-    .replace(/>/g, '&gt;')
-    .replace(/\*(.+?)\*/g, '<i>$1</i>')
-};
+function vis(row) {
+  return row.split(/\n/)[3].trim()
+    .replaceAll(/<.*?>/g,'')
+    .replaceAll(/\.\.+/g,'..')
+}
+
+function query(obj,adj={}) {
+  return Object.entries(obj)
+    .map(([k,v]) => k in adj ? `${k}=${adj[k](v)}` : `${k}=${v}`)
+    .join('&')
+}
 
 app.listen(1954)
\ No newline at end of file