From: Ward Cunningham Date: Tue, 26 Nov 2024 20:28:13 +0000 (-0800) Subject: first three pages X-Git-Url: https://dreyeck.freedombox.rocks/gitweb/idiomatic.git/commitdiff_plain/96f9e23cc6e9201f27fb656588199e234901420e first three pages --- 96f9e23cc6e9201f27fb656588199e234901420e diff --git a/index.js b/index.js new file mode 100644 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 = ` +

${reductions.size()} non-terminals +
${reductions.total()} reductions +

${reductions.tally() + .map(([k,v]) => `${v} ${link(k)}`) + .join("
")}` + 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 `${key}` + if(key.match(/^(As|B|L|U).*Ex/)) return `${key}` + if(key.match(/^Lit/)) return `${key}` + 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 = ` +

${lits.size()} uniques +
${lits.total()} total +

${lits.tally() + .map(([k,v]) => `${v} ${escape(k)}`) + .join("
")}` + 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(`

${JSON.stringify(req.query,null,2)}
${list.join("
")}`) +}) + + +// 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, '&') + .replace(//g, '>') + .replace(/\*(.+?)\*/g, '$1') + } catch (e) { + return text + } +} + +function sxpr(obj,deep,key,child) { + const hilite = obj===child ? 'class="hi"' : '' + const link = word => obj.type == 'Identifier' ? `${word}` : 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 ? `(${(fields)})` : `(${(fields)})` + } else return elipsis(obj) + } else return `.` +} + +function abv(type) { + return `${type.replaceAll(/[a-z]/g,'')}` +} + +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 `(${dots})` +} + +function expand(text) { + return text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/\*(.+?)\*/g, '$1') +}; + +app.listen(1954) \ No newline at end of file diff --git a/visitor.js b/visitor.js new file mode 100644 index 0000000..61d2680 --- /dev/null +++ b/visitor.js @@ -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