/** * Copyright 2015 Santhosh Kumar Tekuri * * The JLibs authors license this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package jlibs.nblr.editor.debug; import jlibs.core.annotation.processing.Printer; import jlibs.core.io.FileUtil; import jlibs.core.lang.ImpossibleException; import jlibs.core.lang.StringUtil; import jlibs.nblr.actions.BufferAction; import jlibs.nblr.actions.ErrorAction; import jlibs.nblr.actions.EventAction; import jlibs.nblr.actions.PublishAction; import jlibs.nblr.codegen.java.JavaCodeGenerator; import jlibs.nblr.editor.RuleScene; import jlibs.nblr.editor.Util; import jlibs.nblr.rules.Edge; import jlibs.nblr.rules.Node; import jlibs.nblr.rules.Rule; import jlibs.nbp.Chars; import jlibs.nbp.NBHandler; import javax.swing.*; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.text.BadLocationException; import javax.swing.text.Highlighter; import javax.tools.JavaCompiler; import javax.tools.ToolProvider; import java.awt.*; import java.awt.event.ActionEvent; import java.io.*; import java.net.URL; import java.net.URLClassLoader; import java.text.ParseException; import java.util.ArrayList; import java.util.Observable; import java.util.Observer; /** * @author Santhosh Kumar T */ public class Debugger extends JPanel implements NBHandler, Observer{ private RuleScene scene; public JTextArea input = new JTextArea(); private JList ruleStackList = new JList(new DefaultListModel()); public Debugger(RuleScene scene){ super(new BorderLayout(5, 5)); this.scene = scene; scene.ruleObservable.addObserver(this); JToolBar toolbar = Util.toolbar( runAction, debugAction, null, stepAction, runToCursorAction, resumeAction, suspendAction ); add(toolbar, BorderLayout.NORTH); input.setFont(Util.FIXED_WIDTH_FONT); input.addCaretListener(new CaretListener(){ @Override public void caretUpdate(CaretEvent ce){ updateActions(); } }); add(new JScrollPane(input), BorderLayout.CENTER); ruleStackList.setFont(Util.FIXED_WIDTH_FONT); ruleStackList.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent lse){ Rule rule = (Rule)ruleStackList.getSelectedValue(); RuleScene scene = Debugger.this.scene; if(rule!=null && scene.getRule()!=rule) scene.setRule(scene.getSyntax(), rule); } }); add(new JScrollPane(ruleStackList), BorderLayout.EAST); message.setFont(Util.FIXED_WIDTH_FONT); add(message, BorderLayout.SOUTH); updateActions(); } private String compile(File file){ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); ArrayList<String> args = new ArrayList<String>(); args.add("-d"); args.add(file.getParentFile().getAbsolutePath()); args.add("-s"); args.add(file.getParentFile().getAbsolutePath()); args.add(file.getAbsolutePath()); ByteArrayOutputStream err = new ByteArrayOutputStream(); if(compiler.run(null, null, err, args.toArray(new String[args.size()]))==0) return null; return err.toString(); } private DebuggableNBParser parser; private int inputIndex; private char[] inputText; private int inputPosition; private void start(){ try{ showMessage(""); clearGuardedBlock(); String parserName = "UntitledParser"; File file = new File("temp/"+parserName+".java").getAbsoluteFile(); FileUtil.mkdirs(file.getParentFile()); JavaCodeGenerator codeGenerator = new JavaCodeGenerator(scene.getSyntax()); codeGenerator.properties.put(JavaCodeGenerator.PARSER_CLASS_NAME, parserName); codeGenerator.setDebuggable(); Printer printer = new Printer(new PrintWriter(new FileWriter(file))); codeGenerator.generateParser(printer); printer.close(); String error = compile(file); if(error!=null){ JOptionPane.showMessageDialog(this, error); return; } currentRule = scene.getRule(); URLClassLoader classLoader = new URLClassLoader(new URL[]{FileUtil.toURL(file.getParentFile())}); Class clazz = classLoader.loadClass(parserName); parser = (DebuggableNBParser)clazz.getConstructor(getClass(), int.class).newInstance(this, scene.getRule().id); inputText = input.getText(0, input.getDocument().getLength()).toCharArray(); showMessage("Executing..."); }catch(Exception ex){ ex.printStackTrace(); JOptionPane.showMessageDialog(this, ex.getMessage()); } } private void step(){ try{ if(inputIndex<input.getDocument().getLength()){ inputPosition = parser.consume(inputText, inputPosition, inputIndex+1, false); inputIndex++; updateGuardedBlock(); }else{ parser.consume(inputText, inputPosition, inputText.length, true); stop(null, "Input Matched"); } }catch(BadLocationException ex){ throw new ImpossibleException(ex); }catch(Exception ex){ stop(ex, null); } } private void stop(Exception ex, String message){ parser = null; inputIndex = inputPosition = 0; inputText = null; if(ex==null){ clearGuardedBlock(); scene.executing((Node)null); showMessage(message); }else showError(ex); } /*-------------------------------------------------[ Message ]---------------------------------------------------*/ private JLabel message = new JLabel(); private void showMessage(String msg){ message.setForeground(Color.BLUE); message.setText(msg); } private void showError(Exception ex){ String text = ex.getMessage(); if(!(ex instanceof ParseException)){ ex.printStackTrace(); text = "[BUG] "+text; } message.setForeground(Color.RED); message.setText(text); } /*-------------------------------------------------[ GuardBlock ]---------------------------------------------------*/ private Highlighter.HighlightPainter consumedHighlightPainter = new NewLineHighlightPainter(Color.LIGHT_GRAY); private Highlighter.HighlightPainter lookAheadHighlightPainter = new NewLineHighlightPainter(Color.CYAN); private void updateGuardedBlock() throws BadLocationException{ input.getHighlighter().removeAllHighlights(); int consumed = parser.getCharacterOffset(); if(consumed>inputIndex) throw new ImpossibleException("consumed="+consumed+" inputIndex="+inputIndex); if(consumed>0) input.getHighlighter().addHighlight(0, consumed, consumedHighlightPainter); if(inputIndex!=consumed) input.getHighlighter().addHighlight(consumed, inputIndex, lookAheadHighlightPainter); input.repaint(); } private void clearGuardedBlock(){ input.getHighlighter().removeAllHighlights(); input.repaint(); } /*-------------------------------------------------[ Actions ]---------------------------------------------------*/ private void updateActions(){ if(scene.getSyntax()!=null) ruleStackList.setPrototypeCellValue(scene.getSyntax().ruleProtypeWidth()); JScrollPane scroll = (JScrollPane)ruleStackList.getParent().getParent(); scroll.setVisible(parser!=null); DefaultListModel model = (DefaultListModel)ruleStackList.getModel(); model.clear(); if(parser!=null){ Rule rules[] = scene.getSyntax().rules.values().toArray(new Rule[scene.getSyntax().rules.values().size()]); for(int i=0; i<parser.free(); i+=2) model.addElement(rules[parser.getStack()[i]]); ruleStackList.setSelectedIndex(model.size()-1); } scroll.revalidate(); doLayout(); input.revalidate(); runAction.setEnabled(parser==null); debugAction.setEnabled(parser==null); stepAction.setEnabled(parser!=null); resumeAction.setEnabled(parser!=null); suspendAction.setEnabled(parser!=null); runToCursorAction.setEnabled(parser!=null && inputIndex<input.getCaretPosition()); } private ImageIcon icon(String name){ return new ImageIcon(getClass().getResource(name)); } private Action runAction = new AbstractAction("Run", icon("run.png")){ public void actionPerformed(ActionEvent ae){ start(); while(parser!=null) step(); updateActions(); } }; private Action debugAction = new AbstractAction("Debug", icon("debug.png")){ public void actionPerformed(ActionEvent ae){ start(); updateActions(); } }; private Action stepAction = new AbstractAction("Step", icon("step.png")){ public void actionPerformed(ActionEvent ae){ step(); updateActions(); } }; private Action runToCursorAction = new AbstractAction("Run to Cursor", icon("runToCursor.png")){ public void actionPerformed(ActionEvent ae){ while(parser!=null && inputIndex<input.getCaretPosition()) step(); updateActions(); } }; private Action resumeAction = new AbstractAction("Resume", icon("resume.png")){ public void actionPerformed(ActionEvent ae){ while(parser!=null) step(); updateActions(); } }; private Action suspendAction = new AbstractAction("Stop", icon("suspend.png")){ public void actionPerformed(ActionEvent ae){ stop(null, ""); updateActions(); } }; private boolean ignoreRuleChange = false; @Override public void update(Observable o, Object rule){ if(ignoreRuleChange || rule==null) return; int ruleIndex = -1; ListModel model = ruleStackList.getModel(); for(int i=model.getSize()-1; i>=0; i--){ if(model.getElementAt(i)==rule){ ruleIndex = i; break; } } if(ruleIndex==-1) ruleStackList.clearSelection(); else{ ruleStackList.setSelectedIndex(ruleIndex); ArrayList<Integer> states = new ArrayList<Integer>(); for(int i=1; i<parser.free(); i+=2) states.add(parser.getStack()[i]); int state = states.get(ruleIndex); Node node = ((Rule)rule).nodes().get(state); if(ruleIndex==model.getSize()-1) scene.executing(node); else{ for(Edge edge: node.incoming()){ if(edge.ruleTarget!=null && edge.ruleTarget.rule==model.getElementAt(ruleIndex+1)){ scene.executing(edge); return; } } } } } /*-------------------------------------------------[ Consumer ]---------------------------------------------------*/ public void execute(int rule, int... ids) throws Exception{ setCurrentRule(rule); for(int id: ids){ Node node = currentRule.nodes().get(id); if(node.action== BufferAction.INSTANCE){ System.out.println("BUFFERRING"); parser.getBuffer().push(); }else if(node.action instanceof PublishAction){ PublishAction action = (PublishAction)node.action; Chars data = parser.getBuffer().pop(action.begin, action.end); System.out.println(action.name+"(\""+ StringUtil.toLiteral(data, false)+"\")"); }else if(node.action instanceof EventAction){ EventAction action = (EventAction)node.action; System.out.println(action.name+"()"); }else if(node.action instanceof ErrorAction){ ErrorAction action = (ErrorAction)node.action; System.out.println("error(\""+StringUtil.toLiteral(action.errorMessage, false)+"\")"); fatalError(action.errorMessage); } scene.executing(node); } } private Rule currentRule; private void setCurrentRule(int rule){ if(scene.getRule().id!=rule){ ignoreRuleChange = true; try{ currentRule = (Rule)scene.getSyntax().rules.values().toArray()[rule]; scene.setRule(scene.getSyntax(), currentRule); }finally{ ignoreRuleChange = false; } } } public void currentNode(int ruleID, int nodeID){ setCurrentRule(ruleID); Node node = currentRule.nodes().get(nodeID); scene.executing(node); } @Override public Exception fatalError(String message){ return new IOException(message); } @Override public void onSuccessful() throws Exception{} }