]> dreyeck.freedombox.rocks Git - idiomatic.git/commitdiff
first three pages
authorWard Cunningham <ward@c2.com>
Tue, 26 Nov 2024 20:28:13 +0000 (12:28 -0800)
committerWard Cunningham <ward@c2.com>
Tue, 26 Nov 2024 20:28:13 +0000 (12:28 -0800)
index.js [new file with mode: 0644]
visitor.js [new file with mode: 0644]

diff --git a/index.js b/index.js
new file mode 100644 (file)
index 0000000..b3334a5
--- /dev/null
+++ b/index.js
@@ -0,0 +1,160 @@
+let express = require('express')
+let acorn = require('acorn')
+let fs = require('fs')
+// let fs = require('node:fs/promises');
+
+let visitor = require('./visitor.js')
+let dir = '../wiki-client/lib'
+let mods = []
+
+const app = express()
+
+
+// P A G E S
+
+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()
+  })
+})
+
+async function load(file) {
+  return new Promise(resolve => {
+    fs.readFile(`${dir}/${file}`, "utf8", (err,text) => {
+      const tree = acorn.parse(text, {ecmaVersion: "latest"})
+      resolve({file,text,tree})
+    })
+  })
+}
+
+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 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 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>")}`
+  res.send(result)
+})
+
+app.get('/usage', (req,res) => {
+  const type = req.query.type
+  const field = req.query.field
+  const key = req.query.key
+  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>")}`)
+})
+
+
+// H E L P E R S
+
+function counter() {
+  const counts = new Map()
+  return {
+    count(item) {
+      if(counts.has(item))
+        counts.set(item, counts.get(item)+1)
+      else 
+        counts.set(item,1)
+    },
+    size() {
+      return counts.size
+    },
+    total() {
+      return [...counts]
+        .reduce((sum,each) => sum + each[1], 0)
+    },
+    tally() {
+      return [...counts]
+        .sort((a,b) => a[1]==b[1] ? (a[0]>b[0] ? 1 : -1) : b[1]-a[1])
+    },
+  }
+}
+
+function escape(text) {
+  try {
+    return text
+      .replace(/&/g, '&amp;')
+      .replace(/</g, '&lt;')
+      .replace(/>/g, '&gt;')
+      .replace(/\*(.+?)\*/g, '<i>$1</i>')
+  } catch (e) {
+    return 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
+  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)) :
+          Array.isArray(v) ? `[${v.map(o => sxpr(o,deep-1,k,child)).join(" ")}]` :
+          sxpr(v, deep-1, k, child))
+        .join(" ")
+      return key ? `<span ${hilite} title=${key}>(${(fields)})</span>` : `(${(fields)})`
+    } else return elipsis(obj)
+  } else return `<span title=${obj}>.</span>`
+}
+
+function abv(type) {
+  return `<span title=${type}>${type.replaceAll(/[a-z]/g,'')}</span>`
+}
+
+function omit(k,v) {
+  return k=='type'?v:k=='start'||k=='end'?undefined:v
+}
+
+function elipsis(obj) {
+  const bytes = (obj.end||0)-(obj.start||0)
+  const dots = '..' + '.'.repeat(Math.floor(Math.log2(bytes||1)))
+  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>')
+};
+
+app.listen(1954)
\ No newline at end of file
diff --git a/visitor.js b/visitor.js
new file mode 100644 (file)
index 0000000..61d2680
--- /dev/null
@@ -0,0 +1,87 @@
+let stack=[]
+let log = false ? console.log : () => null
+let doit = () => null
+
+const rules = {
+  Program({body}) {body.map(walk)},
+  VariableDeclaration({kind,declarations}) {declarations.map(walk)},
+  VariableDeclarator({id,init}) {walk(id);if(init)walk(init)},
+  Identifier({start,name}) {doit(name); log(start,name)},
+  CallExpression({callee}) {walk(callee); arguments[0]['arguments'].map(walk)},
+  NewExpression({callee}) {walk(callee); arguments[0]['arguments'].map(walk)},
+  FunctionExpression({params,body}) {params.map(walk); walk(body)},
+  MemberExpression({object,property}) {walk(object); walk(property)},
+  ObjectPattern({properties}) {properties.map(walk)},
+  ExpressionStatement({expression}) {walk(expression)},
+  IfStatement({test,consequent}) {walk(test); walk(consequent)},
+  BlockStatement({body}) {body.map(walk)},
+  ReturnStatement({argument}) {if(argument)walk(argument)},
+
+  Literal({start,value,raw}) {log(start,raw)},
+  AssignmentExpression({operator,left,right}) {log(operator);walk(left);walk(right)},
+  LogicalExpression({operator,left,right}) {log(operator);walk(left);walk(right)},
+  BinaryExpression({operator,left,right}) {log(operator);walk(left);walk(right)},
+  UnaryExpression({operator,prefix,argument}) {log(prefix?'prefix':'suffix',operator); walk(argument)},
+  UpdateExpression({operator,prefix,argument}) {log(prefix?'prefix':'suffix',operator); walk(argument)},
+  ObjectExpression({properties}) {properties.map(walk)},
+  Property({key,value}) {walk(key);walk(value)},
+  ArrayExpression({elements}) {elements.map(walk)},
+  ArrayPattern({elements}) {elements.map(walk)},
+  ArrowFunctionExpression({params,body}) {params.map(walk);walk(body)},
+  TemplateLiteral({expressions,quasis}) {quasis.map(walk);expressions.map(walk)},
+  TemplateElement({start,end}) {log(end-start,'bytes')},
+
+  ForStatement({init,test,update,body}) {walk(init);walk(test);walk(update);walk(body)},
+  ForInStatement({left,right,body}) {walk(left); walk(right); walk(body)},
+  ForOfStatement({left,right,body}) {walk(left); walk(right); walk(body)},
+  ChainExpression({expression}) {walk(expression)},
+  ConditionalExpression({test,consequent,alternative}) {walk(test);walk(consequent);walk(alternative)},
+  ContinueStatement(){},
+  BreakStatement(){},
+
+  AssignmentPattern({left,right}) {walk(left);walk(right)},
+  WhileStatement({test,body}) {walk(test);walk(body)},
+  TryStatement({block,handler,finalizer}) {walk(block);walk(handler);walk(finalizer)},
+  CatchClause({param,body}) {walk(param);walk(body)},
+
+  EmptyStatement() {},
+  AwaitExpression({argument}) {walk(argument)},
+  ThrowStatement({argument}) {walk(argument)},
+  SwitchStatement({discriminant,cases}) {walk(discriminant); cases.map(walk)},
+  SwitchCase({test,consequent}) {walk(test); consequent.map(walk)},
+  RestElement({argument}) {walk(argument)},
+  ImportExpression({source}) {walk(source)},
+  FunctionDeclaration({id,params,body}) {walk(id),params.map(walk),walk(body)},
+
+  ThisExpression({context}) {walk(context)},
+
+  DoWhileStatement({test,body}) {walk(test);walk(body)},
+  SequenceExpression({expressions}) {expressions.map(walk)},
+
+  SpreadElement({argument}) {walk(argument)},
+}
+
+let each = (branch,stack) => {}
+function wander(mods,doit) {
+  each = doit
+  for (const mod of mods) {
+    stack = [mod.file]
+    walk(mod.tree)
+  }
+  each = (branch,stack) => {}
+}
+function walk(branch) {
+  if(branch) {
+    const type = branch?.type;
+    stack.unshift(branch); 
+    log('PARSING',type);
+    each(branch,stack);
+    (rules[type]||fail)(branch);
+    stack.shift()
+  }
+}
+function fail(branch) {
+  console.log('FAIL',branch)
+}
+
+module.exports = {wander}
\ No newline at end of file