UNPKG

hyperscript.org

Version:

a small scripting language for the web

374 lines (331 loc) 12.9 kB
var d=class t{errors=[];collectErrors(e){if(e||(e=new Set),e.has(this))return[];e.add(this);var o=[...this.errors];for(var n of Object.keys(this))for(var s of[this[n]].flat())s instanceof t&&o.push(...s.collectErrors(e));return o}sourceFor(){return this.programSource.substring(this.startToken.start,this.endToken.end)}lineFor(){return this.programSource.split(` `)[this.startToken.line-1]}static parseEventArgs(e){var o=[];if(e.token(0).value==="("&&(e.token(1).value===")"||e.token(2).value===","||e.token(2).value===")")){e.matchOpToken("(");do o.push(e.requireTokenType("IDENTIFIER"));while(e.matchOpToken(","));e.requireOpToken(")")}return o}};var c=class extends d{constructor(){super(),this.constructor.keyword&&(this.type=this.constructor.keyword+"Command")}execute(e){return e.meta.command=this,e.meta.runtime.unifiedExec(this,e)}findNext(e){return e.meta.runtime.findNext(this,e)}};var a=class t extends c{static keyword="breakpoint";static parse(e){if(e.matchToken("breakpoint"))return new t}resolve(e){var o;globalThis.hdb=o=new r(e,e.meta.runtime,this);try{return o.break(e)}catch(n){console.error(n,n.stack)}}};function r(t,e,o){this.ctx=t,this.runtime=e,this.cmd=o,this._hyperscript=self._hyperscript,this.cmdMap=[],this.bus=new EventTarget}r.prototype.break=function(t){return console.log("=== HDB///_hyperscript/debugger ==="),this.ui(),new Promise((e,o)=>{this.bus.addEventListener("continue",()=>{if(this.ctx!==t){for(var n in t)delete t[n];Object.assign(t,this.ctx)}delete globalThis.hdb,e(this.runtime.findNext(this.cmd,this.ctx))},{once:!0})})};r.prototype.continueExec=function(){this.bus.dispatchEvent(new Event("continue"))};r.prototype.stepOver=function(){if(!this.cmd)return this.continueExec();var t=this.cmd&&this.cmd.type==="breakpointCommand"?this.runtime.findNext(this.cmd,this.ctx):this.runtime.unifiedEval(this.cmd,this.ctx);if(t.type==="implicitReturn")return this.stepOut();if(t&&t.then instanceof Function)return t.then(e=>{this.cmd=e,this.bus.dispatchEvent(new Event("step")),this.logCommand()});t.halt_flag?this.bus.dispatchEvent(new Event("continue")):(this.cmd=t,this.bus.dispatchEvent(new Event("step")),this.logCommand())};r.prototype.stepOut=function(){if(!this.ctx.meta.caller)return this.continueExec();var t=this.ctx.meta.callingCommand,e=this.ctx.me;this.ctx=this.ctx.meta.caller,console.log("[hdb] stepping out into "+this.ctx.meta.feature.displayName),this.ctx.me instanceof Element&&this.ctx.me!==e&&console.log("[hdb] me: ",this.ctx.me),this.cmd=this.runtime.findNext(t,this.ctx),this.cmd=this.runtime.findNext(this.cmd,this.ctx),this.logCommand(),this.bus.dispatchEvent(new Event("step"))};r.prototype.skipTo=function(t){this.cmd=t.cmd,this.bus.dispatchEvent(new Event("skip"))};r.prototype.rewrite=function(t,e){console.log("##",t);let o=t.cmd.parent,n;for(n of o.children)if(n.next===t.cmd)break;let s=t.next,u=this._hyperscript.internals.tokenizer.tokenize(e),i=this._hyperscript.internals.createParser(u).requireElement("command");console.log(i),i.startToken=t.startToken,i.endToken=t.endToken,i.programSource=t.programSource,i.sourceFor=function(){return e},n.next=i,i.next=s,i.parent=o,this.bus.dispatchEvent(new Event("step"))};r.prototype.logCommand=function(){var t=this.cmd.sourceFor instanceof Function,e=t?this.cmd.sourceFor():"-- "+this.cmd.type;console.log("[hdb] current command: "+e)};r.prototype.traverse=function(t){let e=[];return(function o(n){if(e.push(n),"children"in n)for(let s of n.children)o(s)})(t),e};var h=` <div class="hdb" _=" on load trigger update end on step from hdb.bus trigger update end on skip from hdb.bus trigger update end on continue from hdb.bus log 'done' then remove me.getRootNode().host"> <script type="text/hyperscript"> def escapeHTML(unsafe) js(unsafe) return unsafe .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/\\x22/g, "&quot;") .replace(/\\x27/g, "&#039;") end return it end def makeCommandWidget(i) get \`<span data-cmd=\${i}><button class=skip data-cmd=\${i}>&rdca;</button>\` if hdb.EXPERIMENTAL append \`<button class=rewrite data-cmd=\${i}>Rewrite</button></span>\` end return it end def renderCode set hdb.cmdMap to [] set src to hdb.cmd.programSource -- Find feature set feat to hdb.cmd repeat until no feat.parent or feat.isFeature set feat to feat.parent end -- Traverse, finding starts for cmd in hdb.traverse(feat) if no cmd.startToken continue end append { index: cmd.startToken.start, widget: makeCommandWidget(hdb.cmdMap.length), cmd: cmd } to hdb.cmdMap end set rv to src.slice(0, hdb.cmdMap[0].index) for obj in hdb.cmdMap index i if obj.cmd is hdb.cmd append obj.widget + '<u class=current>' + escapeHTML(src.slice(obj.index, hdb.cmdMap[i+1].index)) + '</u>' to rv else append obj.widget + escapeHTML(src.slice(obj.index, hdb.cmdMap[i+1].index)) to rv end end return rv end def truncate(str, len) if str.length <= len return str end return str.slice(0, len) + '\u2026' def prettyPrint(obj) if obj is null return 'null' end if Element.prototype.isPrototypeOf(obj) set rv to '&lt;<span class="token tagname">' + obj.tagName.toLowerCase() + "</span>" for attr in Array.from(obj.attributes) if attr.specified set rv to rv + ' <span class="token attr">' + attr.nodeName + '</span>=<span class="token string">"' + truncate(attr.textContent, 10) + '"</span>' end end set rv to rv + '>' return rv else if obj.call if obj.hyperfunc get "def " + obj.hypername + ' ...' else get "function "+obj.name+"(...) {...}" end else if obj.toString call obj.toString() end return escapeHTML((it or 'undefined').trim()) end <\/script> <header _=" on pointerdown(clientX, clientY) halt the event call event.stopPropagation() get first .hdb measure its x, y set xoff to clientX - x set yoff to clientY - y repeat until event pointerup from document wait for pointermove or pointerup from document add { left: \${its clientX - xoff}px; top: \${its clientY - yoff}px; } to .hdb end "> <h2 class="titlebar">HDB</h2> <ul role="toolbar" class="toolbar" _="on pointerdown halt"> <li><button _="on click call hdb.continueExec()"> &#x23F5; Continue </button> <li><button _="on click call hdb.stepOver()"> &#8631; Step Over </button> </ul> </header> <section class="sec-code"> <div class="code-container"> <pre class="code language-hyperscript" _=" on update from .hdb if hdb.cmd.programSource put renderCode() into me if Prism call Prism.highlightAllUnder(me) end go to bottom of .current in me end on click if target matches .skip get (target's @data-cmd) as Int call hdb.skipTo(hdb.cmdMap[result]) end if target matches .rewrite set cmdNo to (target's @data-cmd) as Int set span to the first <span[data-cmd='\${cmdNo}'] /> put \`<form class=rewrite><input id=cmd></form>\` into the span end end on submit halt the event get (closest @data-cmd to target) as Int call hdb.rewrite(hdb.cmdMap[result], #cmd's value) end "><code></code></pre> </div> </section> <section class="sec-console" _=" -- Print context at startup init repeat for var in Object.keys(hdb.ctx) if var is not 'meta' send hdbUI:consoleEntry(input: var, output: hdb.ctx[var]) to #console"> <ul id="console" role="list" _=" on hdbUI:consoleEntry(input, output) if no hdb.consoleHistory set hdb.consoleHistory to [] end push(input) on hdb.consoleHistory set node to #tmpl-console-entry.content.cloneNode(true) put the node at end of me set entry to my lastElementChild go to bottom of the entry put escapeHTML(input) into .input in the entry if no output call hdb._hyperscript.parse(input) if its execute is not undefined then call its execute(hdb.ctx) else call its evaluate(hdb.ctx) end set output to it end put prettyPrint(output) as Fragment into .output in the entry "> <template id="tmpl-console-entry"> <li class="console-entry"> <kbd><code class="input"></code></kbd> <samp class="output"></samp> </li> </template> </ul> <form id="console-form" data-hist="0" _="on submit send hdbUI:consoleEntry(input: #console-input's value) to #console set #console-input's value to '' set @data-hist to 0 set element oldContent to null halt on keydown[key is 'ArrowUp' or key is 'ArrowDown'] if no hdb.consoleHistory or exit end if element oldContent is null set element oldContent to #console-input.value end if event.key is 'ArrowUp' and hdb.consoleHistory.length > -@data-hist decrement @data-hist else if event.key is 'ArrowDown' and @data-hist < 0 increment @data-hist end end set #console-input.value to hdb.consoleHistory[hdb.consoleHistory.length + @data-hist as Int] or oldContent halt default on input if @data-hist is '0' set element oldContent to #console-input.value"> <input id="console-input" placeholder="Enter an expression&hellip;" autocomplete="off"> </form> </section> <style> .hdb { border: 1px solid #888; border-radius: .3em; box-shadow: 0 .2em .3em #0008; position: fixed; top: .5em; right: .5em; width: min(40ch, calc(100% - 1em)); max-height: calc(100% - 1em); background-color: white; font-family: sans-serif; opacity: .9; z-index: 2147483647; color: black; display: flex; flex-flow: column; } * { box-sizing: border-box; } header { display: flex; justify-content: space-between; align-items: center; padding: .4em; } .titlebar { margin: 0; font-size: 1em; touch-action: none; } .toolbar { display: flex; gap: .35em; list-style: none; padding-left: 0; margin: 0; } .toolbar a, .toolbar button { background: #2183ff; border: 1px solid #3465a4; box-shadow: 0 1px #b3c6ff inset, 0 .06em .06em #000; border-radius: .2em; font: inherit; padding: .2em .3em; color: white; text-shadow: 0 1px black; font-weight: bold; } .toolbar a:hover .toolbar a:focus, .toolbar button:hover, .toolbar button:focus { background: #94c8ff; } .toolbar a:active, .toolbar button:active { background: #3465a4; } .sec-code { border-radius: .3em; overflow: hidden; box-shadow: 0 1px white inset, 0 .06em .06em #0008; background: #bdf; margin: 0 .4em; border: 1px solid #3465a4; } .hdb h3 { margin: 0; font-size: 1em; padding: .2em .4em 0 .4em; } .code-container { display: grid; line-height: 1.2em; height: calc(12 * 1.2em); border-radius: 0 0 .2em .2em; overflow: auto; scrollbar-width: thin; scrollbar-color: #0003 transparent; } .code, #console, #console-input { font-family: Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace; } .code { width: 0; margin: 0; padding-left: 1ch; tab-size: 2; -moz-tab-size: 2; -o-tab-size: 2; } .current { font-weight: bold; background: #abf; } .skip { padding: 0; margin: 2px; border: 1px solid #3465a4; border-radius: 50%; color: #3465a4; background: none; font-weight: bold; font-size: 1.2em; width: calc(2ch / 1.2 - 4px); height: calc(2ch / 1.2 - 4px); line-height: 0.6; } .skip:hover { background: #3465a4; color: #bdf; } #console { overflow-y: scroll; scrollbar-width: thin; scrollbar-color: #afc2db transparent; height: calc(12 * 1.2em); list-style: none; padding-left: 0; margin: 0 .4em .4em .4em; position: relative; word-wrap: break-word; } #console>*+* { margin-top: .5em; } .console-entry>* { display: block; } .console-entry .input { color: #3465a4; } .console-entry .output { color: #333; } .console-entry .input:before { content: '>> ' } .console-entry .output:before { content: '<- ' } #console-form { margin: 0 .4em .4em .4em; } #console-input { width: 100%; font-size: inherit; } .token.tagname { font-weight: bold; } .token.attr, .token.builtin, .token.italic { font-style: italic; } .token.string { opacity: .8; } .token.keyword { color: #3465a4; } .token.bold, .token.punctuation, .token.operator { font-weight: bold; } </style> </div> `;r.prototype.ui=function(){var t=document.createElement("div"),e=t.attachShadow({mode:"open"});t.style.cssText="all: initial",e.innerHTML=h,document.body.appendChild(t),this._hyperscript.process(e.querySelector(".hdb"))};function l(t){t.addCommand(a.keyword,a.parse.bind(a))}typeof self<"u"&&self._hyperscript&&self._hyperscript.use(l);export{l as default};