diff options
| author | Cliff Click <cliffc@acm.org> | 2026-09-22 09:32:07 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-09-22 09:32:07 -0700 |
| commit | 1d961838cff168a101069d08a8ab6594f49f0911 (patch) | |
| tree | f832576181cc396c033707508a50e3d72120b8c6 /chapter25 | |
| parent | 92e3013a4cab12747af9d5484e7caad6fc454b1e (diff) | |
| download | Simple-1d961838cff168a101069d08a8ab6594f49f0911.tar.gz Simple-1d961838cff168a101069d08a8ab6594f49f0911.zip | |
Graph viewer on ch4 and25
Diffstat (limited to 'chapter25')
4 files changed, 18 insertions, 370 deletions
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java b/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java index 10c07730..03842119 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java @@ -1,72 +1,20 @@ package com.seaofnodes.simple.print; -import com.seaofnodes.graph.GraphJson; -import com.seaofnodes.graph.GraphEvent; -import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.*; import com.seaofnodes.simple.codegen.CodeGen; import java.io.IOException; -import java.nio.file.Paths; -import java.nio.file.Files; -import java.nio.file.Path; -import java.net.URI; -public class JSViewer implements AutoCloseable { - // Display programs in an endless loop - public static void main( String[] args ) throws Exception { - try(var js = new JSViewer()) { - js.run(); - } - } - - // WebSocket to the browser for display - private final SimpleWebSocket _server; - - JSViewer() throws Exception { - // Launch server; handshake - _server = new SimpleWebSocket(viewerURI(),12345); - } - - // The browser application is shared by all chapters. Search upwards so - // chapter, repository-root, and linearized checkouts use the same assets. - static URI viewerURI() throws IOException { - String url = System.getProperty("simple.graph.url"); - if( url != null ) return URI.create(url); - for( Path dir = Paths.get("").toAbsolutePath(); dir != null; dir = dir.getParent() ) { - Path page = dir.resolve("graph/web/index.html"); - if( Files.isRegularFile(page) ) return page.toUri(); - } - throw new IOException("Cannot find graph/web/index.html; run inside the Simple checkout " - + "or set -Dsimple.graph.url=<viewer URL>"); - } - - void run( ) throws Exception { - _server.put("!"); - while( true ) { - String src = _server.get(); - switch( src ) { - case null: return; - case "null": return; - case "+": break; // Client requests more frames, but we send them all anyway - default: - System.out.println(src); - try { - compile(src); - // Catch and ignore Parser errors - } catch(RuntimeException re) { - System.err.println(re); - } finally { - _server.put("#"); // Final frame - } - break; - } - } +public class JSViewer extends GraphViewer { + public static void main(String[] args) throws IOException { + try( var view = new JSViewer() ) { view.run(); } } + JSViewer() throws IOException {} - private void compile(String src) { + @Override protected void compile(String src) { CodeGen code = new CodeGen(src); code._obs = new SimpleGraphObserver(code) { @Override protected void frame(GraphSnapshot snap, int pos, GraphEvent evt) throws IOException { - _server.put(GraphJson.frame(snap, pos, evt)); + JSViewer.this.frame(snap, pos, evt); } }; try { @@ -75,6 +23,4 @@ public class JSViewer implements AutoCloseable { code._obs = null; } } - - @Override public void close() throws IOException { _server.close(); } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 592abb5c..e9b1dc6d 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -10,6 +10,9 @@ import java.util.ArrayList; /** Chapter 25's view of the IR, including partially constructed parse-time graphs. */ public class SimpleGraphAdapter extends GraphAdapter<Node> { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } @Override protected int id(Node n) { return n._nid; } @Override protected int nIns(Node n) { return n.nIns(); } @Override protected Node in(Node n, int idx) { return n.in(idx); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java index 5d511287..0318b793 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -1,130 +1,22 @@ package com.seaofnodes.simple.print; -import com.seaofnodes.graph.GraphEvent; -import com.seaofnodes.graph.GraphObserver; -import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphCapture; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.Node; -import java.io.IOException; import java.util.ArrayList; -import java.util.BitSet; -import java.util.UUID; -/** Capture events and neighborhoods on the compiler thread. Transport is supplied by the viewer. */ -public abstract class SimpleGraphObserver extends GraphObserver<Node> { +/** Chapter 25's compilation context; capture and event bookkeeping are shared. */ +public abstract class SimpleGraphObserver extends GraphCapture<Node> { private final CodeGen _code; - private final SimpleGraphAdapter _graph = new SimpleGraphAdapter(); - private final String _comp = UUID.randomUUID().toString(); - private final ArrayList<Peep> _peeps = new ArrayList<>(); - private long _next, _step; - private boolean _off; - - protected SimpleGraphObserver(CodeGen code) { _code = code; } - - protected abstract void frame(GraphSnapshot snap, int pos, GraphEvent evt) throws IOException; - - private static class Peep { - final Node node; - final long id, up; - final String phase; - final int pos; - final BitSet near = new BitSet(); - GraphSnapshot pre; - boolean sent; - - Peep(Node n, long id, long up, String phase, int pos) { - node = n; this.id = id; this.up = up; this.phase = phase; this.pos = pos; - } - } - - @Override public void before(Node n) { - if( _off ) return; - var p = new Peep(n, ++_next, _peeps.isEmpty() ? 0 : _peeps.getLast().id, - _code._phase.name(), pos()); - _peeps.add(p); - near(p, n); - // Buffer until we know that this attempt (or a nested one) made progress. - // Include n explicitly: parse-time nodes need not have any uses yet. - p.pre = snap(n); - } - - @Override public void after(Node n, Node repl, boolean applied) { - if( _off ) return; - Peep p = _peeps.getLast(); - assert p.node == n; - near(p, n); - near(p, repl); - for( Peep up : _peeps ) up.near.or(p.near); - if( repl != null || p.sent ) { - // Emit ancestors first so a nested rewrite always has its context. - for( Peep up : _peeps ) { - if( up.sent ) continue; - emit(up.pre, up.pos, evt(up, GraphEvent.Kind.BEFORE, 0)); - if( _off ) return; - up.pre = null; - up.sent = true; - } - Node rez = repl == null ? n : repl; - emit(snap(rez.isDead() ? null : rez), pos(), - evt(p, applied ? GraphEvent.Kind.APPLY : GraphEvent.Kind.RETURN, - rez.isDead() ? 0 : rez._nid)); - } - _peeps.removeLast(); - } - - @Override public void dep(Node n, Node def) { - if( _off || _peeps.isEmpty() ) return; - Peep p = _peeps.getLast(); - add(p, n); - add(p, def); - } - - @Override public void phase(String phase) { - if( _off ) return; - emit(snap(null), pos(), new GraphEvent(GraphEvent.Kind.PHASE, 0, 0, phase, 0, 0, new int[0])); - } - - private GraphEvent evt(Peep p, GraphEvent.Kind kind, int repl) { - return new GraphEvent(kind, p.id, p.up, p.phase, p.node._nid, repl, p.near.stream().toArray()); - } - - private void add(Peep p, Node n) { if( n != null ) p.near.set(n._nid); } - - private void near(Peep p, Node n) { - if( n == null ) return; - add(p, n); - for( Node def : n._inputs ) add(p, def); - for( Node use : n._outputs ) add(p, use); - // These are wake-up dependents, not all nodes inspected by a rule. - // Read before the compiler drains the list; never move/clear it here. - for( int i = 0; i < n.nDeps(); i++ ) add(p, n.dep(i)); - } - - private int pos() { return _code._phase == CodeGen.Phase.Parse ? _code.P.pos() : -1; } - - private GraphSnapshot snap(Node extra) { - var roots = new ArrayList<Node>(); + protected SimpleGraphObserver(CodeGen code) { super(new SimpleGraphAdapter()); _code = code; } + @Override protected String phase() { return _code._phase.name(); } + @Override protected int pos() { return _code._phase == CodeGen.Phase.Parse ? _code.P.pos() : -1; } + @Override protected void detach() { _code._obs = null; } + @Override protected void roots(ArrayList<Node> roots) { roots.add(_code._stop); if( _code._phase == CodeGen.Phase.Parse ) { roots.add(_code.P._scope); roots.addAll(_code.P._xScopes); } - for( Peep p : _peeps ) if( !p.node.isDead() ) roots.add(p.node); - roots.add(extra); - return _graph.snap(_comp, 0, roots.toArray(Node[]::new)); - } - - private void emit(GraphSnapshot snap, int pos, GraphEvent evt) { - if( _off ) return; - // Assign steps on emission; attempts without progress do not consume frames. - snap = new GraphSnapshot(snap.ver(), _comp, _step++, snap.roots(), snap.nodes()); - try { - frame(snap, pos, evt); - } catch( IOException e ) { - // A disconnected display must not interrupt optimization. - _off = true; - _code._obs = null; - System.err.println("Graph viewer disconnected: " + e.getMessage()); - } } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java deleted file mode 100644 index ae500879..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.seaofnodes.simple.print; - -import com.seaofnodes.simple.util.Utils; -import java.io.*; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; -import java.net.InetSocketAddress; -import java.nio.file.Path; -import com.sun.net.httpserver.HttpServer; -import com.sun.net.httpserver.SimpleFileServer; - -// Why-Oh-Why is this needed? - -// Pass in a URI and a port. -// Opens the client; client has to open a websocket at the given port. -// Connects, handshakes. -// Thereafter you can call get/put to swap frames with client. - -class SimpleWebSocket extends ServerSocket { - final InputStream _in; - final OutputStream _out; - private HttpServer _http; - private Socket _client; - - SimpleWebSocket(URI uri, int port) throws IOException, NoSuchAlgorithmException { - super(port); - try { - // Use an ordinary web URL; desktop dispatch of file: URLs can silently - // raise the browser without opening the page. - if( "file".equals(uri.getScheme()) ) { - Path page = Path.of(uri); - _http = SimpleFileServer.createFileServer( - new InetSocketAddress("127.0.0.1",0), page.getParent(), - SimpleFileServer.OutputLevel.NONE); - _http.start(); - uri = URI.create("http://127.0.0.1:" + _http.getAddress().getPort() - + "/" + page.getFileName()); - } - // Print the address even if the desktop silently fails to open a tab. - System.out.println("Graph viewer: " + uri); - if( Boolean.parseBoolean(System.getProperty("simple.graph.open", "true")) ) - java.awt.Desktop.getDesktop().browse(uri); - // Look for client; get in/out streams - Socket sock = accept(); - _client = sock; - _in = sock. getInputStream(); - _out = sock.getOutputStream(); - - - // Establish web socket with handshake; easier with a buffered reader - BufferedReader br = new BufferedReader(new InputStreamReader(_in, StandardCharsets.US_ASCII)); - String key = null; - // TCP can deliver the request in several chunks. ready() is not an - // end-of-headers test; wait for the terminating empty line. - String line; - while( (line = br.readLine()) != null && !line.isEmpty() ) { - if( line.startsWith("Sec-WebSocket-Key: ") ) - key = line.substring(19); - } - if( key==null || line==null ) { - sock.close(); - super.close(); - throw new IOException("Incomplete WebSocket upgrade request"); - } - // Magic handshake - byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n" - + "Connection: Upgrade\r\n" - + "Upgrade: websocket\r\n" - + "Sec-WebSocket-Accept: " - + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-1").digest((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes( StandardCharsets.UTF_8 ))) - + "\r\n\r\n").getBytes( StandardCharsets.UTF_8 ); - _out.write(response,0,response.length); - _out.flush(); - } catch( IOException | NoSuchAlgorithmException | RuntimeException | Error failure ) { - if( _http != null ) _http.stop(0); - if( _client != null ) { - try { _client.close(); } catch( IOException ignored ) {} - } - try { super.close(); } catch( IOException ignored ) {} - throw failure; - } - } - - final byte[] _ins = new byte[1024]; - final byte[] _str = new byte[1024]; - final byte[] _key = new byte[4]; - int _x, _len; - private int rawget() { assert _x<_len; return _ins[_x++]&0xFF; } - - // TODO: Flip to Direct Byte Buffer, this is just a poor-mans version - private void flipRead() throws IOException { - // Move _x to _len to offset 0 - System.arraycopy(_ins,_x,_ins,0,(_len-_x)); - _len = _len-_x; - _x=0; - // Read more - int len = _in.read(_ins,_len,_ins.length-_len); - if( len == -1 ) - throw Utils.TODO(); // EOF - _len += len; - } - // Block until you read at least len - private void atLeast(int len) throws IOException { - if( _x+len <= _len ) return; - flipRead(); - } - - - // Classic read from browser client. Encoded, so decoded here - public String get() throws IOException { - atLeast(1); // Need 1 byte for opcode - int op = rawget(); - if( (op&0x80)!=0x80 ) // FIN bit not set? - throw Utils.TODO( "Multi-part from client: " + op ); // Multipart message - op &= 0x7F; // Strip FIN bit - switch( op&15) { - case 1: { - atLeast(5); // Min message length - // Message length - int len = rawget()-128; - if( len > 125 ) { - if( len > 126 ) - throw Utils.TODO(); // Multi-byte length - len = (rawget()<<8) | rawget(); - } - atLeast(len+4); // Rest of message - // Encoding key - _key[0] = (byte)rawget(); - _key[1] = (byte)rawget(); - _key[2] = (byte)rawget(); - _key[3] = (byte)rawget(); - - // Decode bytes - for( int i=0; i<len; i++ ) - _str[i] = (byte)(rawget() ^ _key[i & 3]); - - return new String(_str,0,len,StandardCharsets.UTF_8); - } - // Client closes - case 8: return null; - // Ping - case 9: - // Send a PONG back. TODO: echo PING data in the PONG - _out.write(0x8A); // FIN + PONG - _out.write(0x00); // LENGTH==0 - return get(); - // Not (yet) implemented: continuation frames, ping/pong - default: - throw Utils.TODO("Unexpected op from client: "+op); // Multi-part message - } - } - - // Server text frames are UTF-8 and unmasked. - public void put(String msg) throws IOException { - byte[] bytes = msg.getBytes(StandardCharsets.UTF_8); - int len = bytes.length; - _out.write(129); // FIN, opcode 1 - whole text message - if( len <= 125 ) { - _out.write(len); - } else if( len <= 65535 ) { - _out.write(126); - _out.write(len>>>8); - _out.write(len&255); - } else { - _out.write(127); - for( int shift = 56; shift >= 0; shift -= 8 ) - _out.write((int)((long)len >>> shift) & 255); - } - _out.write(bytes); - _out.flush(); - } - - @Override public void close() throws IOException { - try { - if( _out != null ) { - _out.write(new byte[]{(byte)0x88, 0}); // Complete, empty close frame - _out.flush(); - } - } finally { - if( _http != null ) _http.stop(0); - try { - if( _client != null ) _client.close(); - } finally { - super.close(); - } - } - } -} |
