Module: Ibex::Codegen::HTML
- Defined in:
- lib/ibex/codegen/html.rb,
sig/ibex/codegen/html.rbs
Overview
Renders a self-contained navigable automaton report.
Class Method Summary collapse
- .conflict_sections(automaton, grammar, labels) ⇒ Object
- .controls(automaton) ⇒ Object
- .escape(value) ⇒ Object
- .interaction_script ⇒ Object
- .item_html(item, grammar, labels) ⇒ Object
- .one_hop_neighbors(automaton, state_id) ⇒ Object
- .render(automaton) ⇒ Object
- .rule_sections(grammar, labels) ⇒ Object
- .state_sections(automaton, grammar, labels) ⇒ Object
- .styles ⇒ Object
- .symbol_name(labels, id) ⇒ Object
Instance Method Summary collapse
Class Method Details
.conflict_sections(automaton, grammar, labels) ⇒ Object
164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/ibex/codegen/html.rb', line 164 def conflict_sections(automaton, grammar, labels) conflicts = automaton.states.flat_map do |state| state.conflicts.map do |conflict| link = "<a href=\"#state-#{state.id}\">state #{state.id}</a>" symbol = grammar.symbol(conflict[:symbol]) displayed = symbol ? conflict.merge(symbol: symbol_name(labels, symbol.id)) : conflict "<li class=\"conflict\">#{link}: #{escape(displayed.inspect)}</li>" end end conflicts.empty? ? "<p>None</p>" : "<ul>#{conflicts.join}</ul>" end |
.controls(automaton) ⇒ Object
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/ibex/codegen/html.rb', line 99 def controls(automaton) = automaton.states.filter_map do |state| next if state.conflicts.empty? %(<option value="#{state.id}">State #{state.id}</option>) end.join <<~HTML <div class="controls"> <label>Search states <input id="state-search" type="search" aria-controls="states"></label> <label><input id="conflict-only" type="checkbox"> Conflicts only</label> <label>Conflict neighborhood <select id="conflict-neighborhood"><option value="">All states</option>#{}</select></label> </div> HTML end |
.escape(value) ⇒ Object
177 178 179 |
# File 'lib/ibex/codegen/html.rb', line 177 def escape(value) value.to_s.gsub("&", "&").gsub("<", "<").gsub(">", ">").gsub('"', """) end |
.interaction_script ⇒ Object
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/ibex/codegen/html.rb', line 72 def interaction_script <<~HTML <script> (() => { const search = document.getElementById("state-search"); const conflictOnly = document.getElementById("conflict-only"); const neighborhood = document.getElementById("conflict-neighborhood"); const states = Array.from(document.querySelectorAll(".state")); const update = () => { const query = search.value.trim().toLowerCase(); const focus = neighborhood.value; states.forEach((state) => { const matchesText = !query || state.textContent.toLowerCase().includes(query); const matchesConflict = !conflictOnly.checked || state.classList.contains("conflict-state"); const nearby = !focus || state.dataset.neighbors.split(" ").includes(focus); state.hidden = !(matchesText && matchesConflict && nearby); }); }; search.addEventListener("input", update); conflictOnly.addEventListener("change", update); neighborhood.addEventListener("change", update); })(); </script> HTML end |
.item_html(item, grammar, labels) ⇒ Object
143 144 145 146 147 148 149 150 151 |
# File 'lib/ibex/codegen/html.rb', line 143 def item_html(item, grammar, labels) return "<li><code>$accept</code></li>" if item.production.negative? production = grammar.productions.fetch(item.production) lhs = symbol_name(labels, production.lhs) rhs = production.rhs.map { |id| symbol_name(labels, id) }.insert(item.dot, "•").join(" ") link = "<a href=\"#rule-#{production.id}\">rule #{production.id}</a>" "<li>#{link} <code>#{escape(lhs)} → #{escape(rhs)}</code></li>" end |
.one_hop_neighbors(automaton, state_id) ⇒ Object
134 135 136 137 138 139 140 |
# File 'lib/ibex/codegen/html.rb', line 134 def one_hop_neighbors(automaton, state_id) outgoing = automaton.states.fetch(state_id).transitions.values incoming = automaton.states.filter_map do |state| state.id if state.transitions.value?(state_id) end ([state_id] + outgoing + incoming).uniq.sort end |
.render(automaton) ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/ibex/codegen/html.rb', line 37 def render(automaton) grammar = automaton.grammar labels = SymbolLabels.build(grammar) <<~HTML <!doctype html> <html lang="en"><head><meta charset="utf-8"><title>Ibex automaton</title> #{styles} </head><body><h1>Ibex #{escape(automaton.algorithm)} automaton</h1> <nav><a href="#rules">Rules</a> · <a href="#conflicts">Conflicts</a></nav> #{controls(automaton)} <main id="states">#{state_sections(automaton, grammar, labels)}</main> <h2 id="rules">Rules</h2>#{rule_sections(grammar, labels)} <h2 id="conflicts">Conflicts</h2>#{conflict_sections(automaton, grammar, labels)} #{interaction_script} </body></html> HTML end |
.rule_sections(grammar, labels) ⇒ Object
154 155 156 157 158 159 160 161 |
# File 'lib/ibex/codegen/html.rb', line 154 def rule_sections(grammar, labels) grammar.productions.map do |production| lhs = symbol_name(labels, production.lhs) rhs = production.rhs.map { |id| symbol_name(labels, id) }.join(" ") number = "<strong>#{production.id}</strong>" "<p id=\"rule-#{production.id}\">#{number} <code>#{escape(lhs)} → #{escape(rhs)}</code></p>" end.join end |
.state_sections(automaton, grammar, labels) ⇒ Object
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/ibex/codegen/html.rb', line 116 def state_sections(automaton, grammar, labels) automaton.states.map do |state| transitions = state.transitions.map do |symbol_id, target| symbol = escape(symbol_name(labels, symbol_id)) "<li>#{symbol} → <a href=\"#state-#{target}\">state #{target}</a></li>" end.join items = state.items.map { |item| item_html(item, grammar, labels) }.join class_name = state.conflicts.empty? ? "state" : "state conflict-state" neighbors = one_hop_neighbors(automaton, state.id).join(" ") <<~HTML <section class="#{class_name}" id="state-#{state.id}" data-state-id="#{state.id}" data-neighbors="#{neighbors}"> <h2>State #{state.id}</h2> <ul>#{transitions}</ul><ul>#{items}</ul></section> HTML end.join end |
.styles ⇒ Object
60 61 62 63 64 65 66 67 68 69 |
# File 'lib/ibex/codegen/html.rb', line 60 def styles <<~HTML <style> body{font:14px system-ui;margin:2rem;max-width:90rem}code{white-space:pre-wrap} .controls{align-items:center;display:flex;flex-wrap:wrap;gap:1rem;margin:1rem 0} .state{border:1px solid #bbb;padding:1rem;margin:1rem 0}.state[hidden]{display:none} .conflict-state{background:#fff7f7;border:2px solid #b91c1c}.conflict{color:#a00}a{color:#075985} </style> HTML end |
Instance Method Details
#self?.render ⇒ String
48 |
# File 'sig/ibex/codegen/html.rbs', line 48
def self?.render: (IR::Automaton automaton) -> String
|