在 Java 中渲染表达式树
Rendering an expression tree in Java
我以前编写过将采用数学表达式并将其转换为解析树的代码,我现在正尝试以可视方式显示通过在 JPanel 上绘图创建的树。表达式由用户输入到控制台,它输出后缀,我还想显示树。然而,当我 运行 我当前的程序时,树没有呈现在 JPanel 上。我没有从编译器收到任何错误,所以我不确定问题出在哪里。
public class TreeView extends JPanel {
//Class for drawing the Tree onto a panel
private int radius = 20;
private int levelGap = 50;
ExpTree t;
public TreeView(ExpTree t) {
this.t = t;
}
protected void paintComponent(Graphics g) {
super.paintComponents(g);
if (t.getRoot() != null) {
displayTree(g, t.getRoot(), getWidth() / 2, 30, getWidth() / 4);
}
}
private void displayTree(Graphics g, ExpTree t, int x, int y, int gap) {
g.drawOval(x - radius, y - radius, 2 * radius, 2 * radius);
g.drawString(t.getLeafVal() + "", x - 6, y + 4);
if (t.getlChild() != null) {
connectCircles(g, x - gap, y + levelGap, x, y);
displayTree(g, t.lChild, x - gap, y + levelGap, gap /2);
}
if (t.getrChild() != null) {
connectCircles(g, x + gap, y + levelGap, x, y);
displayTree(g, t.rChild, x + gap, y + levelGap, gap /2);
}
}
private void connectCircles(Graphics g, int x1, int y1, int x2, int y2) {
double d = Math.sqrt(levelGap * levelGap + (x2 - x1) * (y2 - y1));
int x11 = (int)(x1 - radius * (x1 - x2) / d);
int y11 = (int)(y1 - radius * (y1 - y2) / d);
int x21 = (int)(x2 + radius * (x1 - x2) / d);
int y21 = (int)(y2 + radius * (y1 - y2) / d);
g.drawLine(x11, y11, x21, y21);
}
}
public class Test extends JFrame {
public Test() {
setSize(400, 400);
setLayout(new BorderLayout());
JPanel jp = new JPanel();
add(jp);
setVisible(true);
}
public static void main(String[] args) {
Test test = new Test();
//create parse trees from input in console
boolean done = false;
boolean valid = false;
Parser p = new Parser();
ExpTree myTree;
System.out.println("Enter an expression to convert into postfix notation");
do {
System.out.println("Enter an expression: ");
try {
myTree = p.parseLine();
}
catch (ParseException e) {
System.out.println("Invalid Expression: Ensure it ends with a semicolon");
continue;
}
System.out.println(myTree.toPostfix(myTree));
TreeView view = new TreeView(myTree);
test.add(view);
view.repaint();
System.out.println("Do you want to enter another expression? (y/n)");
do {
String s = p.getLine();
switch (s) {
case "y" : valid = true;
done = false;
continue;
case "n" : valid = true;
done = true;
continue;
default: valid = false;
done = false;
System.out.println("Invalid input: Must be y or n");
}
} while (!valid);
} while (!done);
}
}
//Setup of the tree incase it's useful
public class ExpTree {
//Tree object that is created when an expression is parsed
private int type;
private Object leafVal;
public ExpTree lChild, rChild;
public static final int NUM = 0, VAL = 1, OP = 2;
private StringBuffer sb = new StringBuffer();
public ExpTree(int type, Object leafVal, ExpTree l, ExpTree r) {
this.type = type;
this.leafVal = leafVal;
this.lChild = l;
this.rChild = r;
}
//return the forth expression, a postfix expression
public String toPostfix(ExpTree t) {
if (t != null) {
toPostfix(t.lChild);
toPostfix(t.rChild);
sb.append(t.leafVal);
sb.append(" ");
}
return sb.toString();
}
public ExpTree getRoot() {
return this;
}
public Object getLeafVal() {
return leafVal;
}
public ExpTree getlChild() {
return lChild;
}
public ExpTree getrChild() {
return rChild;
}
}
class ParseException extends RuntimeException
{ public ParseException(String s)
{ super("Invalid expression: "+s);
}
}
public class Parser
{ private Lexer lex;
public Parser()
{ lex = new Lexer();
}
public ExpTree parseLine()
{ lex.init();
lex.getToken();
ExpTree result = parseExp(true);
if (lex.token==Lexer.where)
{ lex.getToken();
ExpTree defs = parseDefList();
result = makeWhereTree(result, defs);
}
if (lex.token!=Lexer.semico)
{ throw new ParseException("semicolon expected");
}
return result;
}
public String getLine()
{ return lex.getLine();
}
private ExpTree parseExp(boolean idsAllowed)
{ ExpTree result = parseTerm(idsAllowed);
{ while (lex.token==Lexer.plus || lex.token==Lexer.minus)
{ int op = lex.token;
lex.getToken();
if (op==Lexer.plus)
result = makePlusTree(result, parseTerm(idsAllowed));
else
result = makeMinusTree(result, parseTerm(idsAllowed));
}
}
return result;
}
private ExpTree parseTerm(boolean idsAllowed)
{ ExpTree result = parseOpd(idsAllowed);
{ while (lex.token==Lexer.times || lex.token==Lexer.div || lex.token==Lexer.mod)
{ int op = lex.token;
lex.getToken();
if (op==Lexer.times)
result = makeTimesTree(result, parseOpd(idsAllowed));
else if (op==Lexer.mod)
result = makeModTree(result, parseOpd(idsAllowed));
else
result = makeDivideTree(result, parseOpd(idsAllowed));
}
}
return result;
}
private ExpTree parseOpd(boolean idsAllowed)
{ ExpTree result;
switch(lex.token)
{ case Lexer.num:
result = makeNumberLeaf(lex.numval);
lex.getToken();
return result;
case Lexer.id:
if (!idsAllowed)
throw new ParseException("identifier not allowed in identifier defintion");
result = makeIdLeaf(lex.idval);
lex.getToken();
return result;
case Lexer.lp:
lex.getToken();
result = parseExp(idsAllowed);
if (lex.token!=Lexer.rp)
throw new ParseException("right parenthesis expected");
lex.getToken();
return result;
default:
throw new ParseException("invalid operand");
}
}
private ExpTree parseDefList()
{ ExpTree result = parseDef();
while (lex.token==Lexer.and)
{ lex.getToken();
result = makeAndTree(result, parseDef());
}
return result;
}
private ExpTree parseDef()
{ if (lex.token!=Lexer.id)
throw new ParseException("definition must start with identifier");
char id = lex.idval;
if (Character.isUpperCase(id))
throw new ParseException("upper-case identifiers cannot be used in defintion list");
lex.getToken();
if (lex.token!=Lexer.eq)
throw new ParseException("'=' expected");
lex.getToken();
return makeDefTree(id, parseExp(false));
}
// the next seven methods need to be modified for part 3 of the assignment
static ExpTree makeNumberLeaf(int n)
{ return new ExpTree(ExpTree.NUM, n, null, null);
// this method should return a new number leaf with value n created using your constructor
// if you've used the abstract class approach you will probably need something like
// return new NumLeaf(n);
// if you've used an ExpTree class that stores the node kind you will probably need something like
// return new ExpTree(ExpTree.numNode, n , null, null);
}
static ExpTree makeIdLeaf(char c)
{ return new ExpTree(ExpTree.VAL, c, null, null);
// this method should return a new id leaf with value c
}
static ExpTree makePlusTree(ExpTree l, ExpTree r)
{ return new ExpTree(ExpTree.OP, '+', l, r);
// this method should return a new plus node with children l and r created using your constructor
// if you've used the abstract class approach you will probably need something like
// return new OpNode('+', l, r);
// or
// return new PlusNode(l, r);
// if you've used an ExpTree class that stores the node kind you will probably need something like
// return new ExpTree(ExpTree.opMode, '+', l, r);
}
static ExpTree makeMinusTree(ExpTree l, ExpTree r)
{ return new ExpTree(ExpTree.OP, '-', l, r);
// this method should return a new minus node with children l and r
}
static ExpTree makeTimesTree(ExpTree l, ExpTree r)
{ return new ExpTree(ExpTree.OP, '*', l, r);
// this method should return a new times node with children l and r
}
static ExpTree makeDivideTree(ExpTree l, ExpTree r)
{ return new ExpTree(ExpTree.OP, '/', l, r);
// this method should return a new divide node with children l and r
}
static ExpTree makeModTree(ExpTree l, ExpTree r)
{ return new ExpTree(ExpTree.OP, '%', l, r);
// this method should return a new mod (%) node with children l and r
}
// the next three methods need to be modified for part 6 of the assignment - do not modify them if you have not attempted part 6
static ExpTree makeWhereTree(ExpTree l, ExpTree r)
{ // remove the following line if you modify this method; leave it here if you do not attempt part 6
System.out.println("Part 6 not attempted");
return null;
// this method should return a new 'where' node with children l and r
}
static ExpTree makeAndTree(ExpTree l, ExpTree r)
{ return null;
// this method should return a new 'and' node with children l and r
}
static ExpTree makeDefTree(char c, ExpTree t)
{ return null;
// this method should return a new definition node with identifier c and child t
// if your definition nodes have 2 children you should put a new id leaf in the left child and use t as the right child
}
}
class Lexer
{ static final int err = 0, num = 1, id = 2, plus = 3, minus = 4, times = 5, div = 6, mod = 7,
lp = 8, rp = 9, semico = 10, where = 11, and = 12, eq = 13;
int token;
char idval;
int numval;
private String line = "";
private BufferedReader buf;
Lexer()
{ buf = new BufferedReader(new InputStreamReader(System.in));
}
void init()
{ do
try
{ line = buf.readLine().trim();
}
catch(Exception e)
{ System.out.println("Error in input");
System.exit(1);
}
while (line.length()==0);
}
String getLine()
{ init();
return(line);
}
void getToken()
{ if (line.length()==0)
token = err;
else switch (line.charAt(0))
{ case '+':
token = plus;
line = line.substring(1).trim();
break;
case '-':
token = minus;
line = line.substring(1).trim();
break;
case '*':
token = times;
line = line.substring(1).trim();
break;
case '/':
token = div;
line = line.substring(1).trim();
break;
case '%':
token = mod;
line = line.substring(1).trim();
break;
case '(':
token = lp;
line = line.substring(1).trim();
break;
case ')':
token = rp;
line = line.substring(1).trim();
break;
case ';':
token = semico;
line = line.substring(1).trim();
break;
case '=':
token = eq;
line = line.substring(1).trim();
break;
default:
if (Character.isDigit(line.charAt(0)))
{ token = num;
numval = line.charAt(0) - '0';
int i = 1;
while (i<line.length()&&Character.isDigit(line.charAt(i)))
{ numval = numval*10+line.charAt(i)-'0';
i++;
}
line = line.substring(i).trim();
}
else if (Character.isLowerCase(line.charAt(0)))
{ char c = line.charAt(0);
if (c=='w' && line.length()>=5 && line.charAt(1)=='h' && line.charAt(2)=='e' && line.charAt(3)=='r' &&
line.charAt(4)=='e')
{ token = where;
line = line.substring(5).trim();
}
else if (c=='a' && line.length()>=3 && line.charAt(1)=='n' && line.charAt(2)=='d')
{ token = and;
line = line.substring(3).trim();
}
else if (line.length()>1 && Character.isLetter(line.charAt(1)))
{ token = err;
}
else
{ token = id;
idval = c;
line = line.substring(1).trim();
}
}
else
token = err;
}
}
}
哦,很久以前,我做过秋千的东西。
在Test.main中,如果我替换
System.out.println(myTree.toPostfix(myTree));
TreeView view = new TreeView(myTree);
test.add(view);
view.repaint();
与:
System.out.println (myTree.toPostfix(myTree));
TreeView view = new TreeView(myTree);
test.add (view);
// view.repaint();
test.invalidate ();
我得到了一个分裂图 - 可能是开始的一个步骤。
4+2*3-9*4+2*3-9;
4 2 3 * + 9 4 * - 2 3 * + 9 -
但只有在全尺寸之后。调整大小会破坏图形并且程序会挂起。
2 项额外改进,一项是关键改进,一项是用户友好性:
setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
应添加到 CTor
public Test() {
在 setVisible 之前,因此您不必使用 Ctrl-C 停止程序。那是关键。
第二个在词法分析器中。如果您在 line.endsWith (";") 上修剪后测试输入,您可以自己默默地添加分号,而不是告诉用户,做什么以及如何做。
更好的是:在 BorderLayout (NORTH) 或 SOUTH 为公式添加一个 JTextField,以便用户可以更新它。出于测试目的,最好预先填充它。
更新
同时我玩得很开心,改进了它,这可能主要是品味和优先级的问题,但也许你感兴趣。一步很可能比上面的 invalidate-command 更好。
这里是需要的导入:
import javax.swing.*;
import java.awt.*;
import java.io.*;
import javax.swing.border.*;
这里是 TestTreeView(我有很多 类 调用 'Test'):
public class TestTreeView extends JFrame {
JTextField jtf = new JTextField (30);
JButton jb = new JButton ("go!");
public TestTreeView () {
setSize (900, 600); // a
Container cp = getContentPane (); // b
JPanel jp = new JPanel ();
jp.setLayout (new BorderLayout());
cp.add (jp);
jp.setBorder (BorderFactory.createEtchedBorder ()); // c
JPanel tp = new JPanel ();
tp.setLayout (new FlowLayout ());
tp.add (jtf);
jtf.setText ("1+2*3/4%5");
jtf.setFont ((jtf.getFont()).deriveFont (24.0f)); // I like big fonts, maybe need stronger glasses :)
tp.add (jb);
jp.add (tp, BorderLayout.NORTH);
actions (jp); // see below
setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
setVisible (true);
}
public void actions (JPanel jp) {
jb.addActionListener (ae -> {
String s = jtf.getText ();
System.out.println (s);
if (s.length () > 1) {
//create parse trees from input in JTextField
Parser p = new Parser (s); // redefined, see below
ExpTree myTree;
try {
myTree = p.parseLine ();
System.out.println (myTree.toPostfix(myTree));
TreeView view = new TreeView (myTree);
jp.add (view, BorderLayout.CENTER);
// view.repaint();
// jp.invalidate ();
jp.revalidate (); // c
}
catch (ParseException e) {
System.out.println ("Invalid Expression: Ensure it ends with a semicolon");
}
}
});
}
public static void main (String[] args) {
TestTreeView test = new TestTreeView ();
}
}
备注:
- a) 我需要更多 space,因此需要 900x600
- b) 至少到 Java-1.5,你不应该添加到主框架,而是添加到 contentPane。也许它随着 1.6、1.7 或 1.8
发生了变化
- c) 重新验证是必经之路。直接在前面绘画,而不仅仅是在第一次调整大小之后。这应该适用,即使您不喜欢使用 JTextField 和按钮。
Parser 发现了一个新的 CTor,它需要一个字符串,我们从 JTextField 传递过来:
class Parser
{
private Lexer lex;
public Parser()
{
lex = new Lexer();
}
public Parser (String s)
{
lex = new Lexer (s);
}
Lexer 找到了一个新的 CTor,它需要一个从解析器传递过来的字符串:
class Lexer
void init()
{
do
try
{
line = buf.readLine ().trim ();
if (! line.endsWith (";"))
line = String.format ("%s;", line);
}
catch(Exception e)
{
System.out.println("Error in input");
System.exit (1);
}
while (line.length () == 0);
}
{
// ...
Lexer ()
{
buf = new BufferedReader (new InputStreamReader(System.in));
}
Lexer (String s)
{
buf = new BufferedReader (new StringReader (s));
}
//加上自动分号修复:
void init()
{
do
try
{
line = buf.readLine ().trim ();
if (! line.endsWith (";"))
line = String.format ("%s;", line);
}
catch(Exception e)
{
System.out.println("Error in input");
System.exit (1);
}
while (line.length () == 0);
}
最后的名言。在网上,有很多教程,如何将 ActionListener 中的动作工作从主事件循环中分离出来。你应该考虑完成这样的 material。这里的代码不是最先进的。 :)
我以前编写过将采用数学表达式并将其转换为解析树的代码,我现在正尝试以可视方式显示通过在 JPanel 上绘图创建的树。表达式由用户输入到控制台,它输出后缀,我还想显示树。然而,当我 运行 我当前的程序时,树没有呈现在 JPanel 上。我没有从编译器收到任何错误,所以我不确定问题出在哪里。
public class TreeView extends JPanel {
//Class for drawing the Tree onto a panel
private int radius = 20;
private int levelGap = 50;
ExpTree t;
public TreeView(ExpTree t) {
this.t = t;
}
protected void paintComponent(Graphics g) {
super.paintComponents(g);
if (t.getRoot() != null) {
displayTree(g, t.getRoot(), getWidth() / 2, 30, getWidth() / 4);
}
}
private void displayTree(Graphics g, ExpTree t, int x, int y, int gap) {
g.drawOval(x - radius, y - radius, 2 * radius, 2 * radius);
g.drawString(t.getLeafVal() + "", x - 6, y + 4);
if (t.getlChild() != null) {
connectCircles(g, x - gap, y + levelGap, x, y);
displayTree(g, t.lChild, x - gap, y + levelGap, gap /2);
}
if (t.getrChild() != null) {
connectCircles(g, x + gap, y + levelGap, x, y);
displayTree(g, t.rChild, x + gap, y + levelGap, gap /2);
}
}
private void connectCircles(Graphics g, int x1, int y1, int x2, int y2) {
double d = Math.sqrt(levelGap * levelGap + (x2 - x1) * (y2 - y1));
int x11 = (int)(x1 - radius * (x1 - x2) / d);
int y11 = (int)(y1 - radius * (y1 - y2) / d);
int x21 = (int)(x2 + radius * (x1 - x2) / d);
int y21 = (int)(y2 + radius * (y1 - y2) / d);
g.drawLine(x11, y11, x21, y21);
}
}
public class Test extends JFrame {
public Test() {
setSize(400, 400);
setLayout(new BorderLayout());
JPanel jp = new JPanel();
add(jp);
setVisible(true);
}
public static void main(String[] args) {
Test test = new Test();
//create parse trees from input in console
boolean done = false;
boolean valid = false;
Parser p = new Parser();
ExpTree myTree;
System.out.println("Enter an expression to convert into postfix notation");
do {
System.out.println("Enter an expression: ");
try {
myTree = p.parseLine();
}
catch (ParseException e) {
System.out.println("Invalid Expression: Ensure it ends with a semicolon");
continue;
}
System.out.println(myTree.toPostfix(myTree));
TreeView view = new TreeView(myTree);
test.add(view);
view.repaint();
System.out.println("Do you want to enter another expression? (y/n)");
do {
String s = p.getLine();
switch (s) {
case "y" : valid = true;
done = false;
continue;
case "n" : valid = true;
done = true;
continue;
default: valid = false;
done = false;
System.out.println("Invalid input: Must be y or n");
}
} while (!valid);
} while (!done);
}
}
//Setup of the tree incase it's useful
public class ExpTree {
//Tree object that is created when an expression is parsed
private int type;
private Object leafVal;
public ExpTree lChild, rChild;
public static final int NUM = 0, VAL = 1, OP = 2;
private StringBuffer sb = new StringBuffer();
public ExpTree(int type, Object leafVal, ExpTree l, ExpTree r) {
this.type = type;
this.leafVal = leafVal;
this.lChild = l;
this.rChild = r;
}
//return the forth expression, a postfix expression
public String toPostfix(ExpTree t) {
if (t != null) {
toPostfix(t.lChild);
toPostfix(t.rChild);
sb.append(t.leafVal);
sb.append(" ");
}
return sb.toString();
}
public ExpTree getRoot() {
return this;
}
public Object getLeafVal() {
return leafVal;
}
public ExpTree getlChild() {
return lChild;
}
public ExpTree getrChild() {
return rChild;
}
}
class ParseException extends RuntimeException
{ public ParseException(String s)
{ super("Invalid expression: "+s);
}
}
public class Parser
{ private Lexer lex;
public Parser()
{ lex = new Lexer();
}
public ExpTree parseLine()
{ lex.init();
lex.getToken();
ExpTree result = parseExp(true);
if (lex.token==Lexer.where)
{ lex.getToken();
ExpTree defs = parseDefList();
result = makeWhereTree(result, defs);
}
if (lex.token!=Lexer.semico)
{ throw new ParseException("semicolon expected");
}
return result;
}
public String getLine()
{ return lex.getLine();
}
private ExpTree parseExp(boolean idsAllowed)
{ ExpTree result = parseTerm(idsAllowed);
{ while (lex.token==Lexer.plus || lex.token==Lexer.minus)
{ int op = lex.token;
lex.getToken();
if (op==Lexer.plus)
result = makePlusTree(result, parseTerm(idsAllowed));
else
result = makeMinusTree(result, parseTerm(idsAllowed));
}
}
return result;
}
private ExpTree parseTerm(boolean idsAllowed)
{ ExpTree result = parseOpd(idsAllowed);
{ while (lex.token==Lexer.times || lex.token==Lexer.div || lex.token==Lexer.mod)
{ int op = lex.token;
lex.getToken();
if (op==Lexer.times)
result = makeTimesTree(result, parseOpd(idsAllowed));
else if (op==Lexer.mod)
result = makeModTree(result, parseOpd(idsAllowed));
else
result = makeDivideTree(result, parseOpd(idsAllowed));
}
}
return result;
}
private ExpTree parseOpd(boolean idsAllowed)
{ ExpTree result;
switch(lex.token)
{ case Lexer.num:
result = makeNumberLeaf(lex.numval);
lex.getToken();
return result;
case Lexer.id:
if (!idsAllowed)
throw new ParseException("identifier not allowed in identifier defintion");
result = makeIdLeaf(lex.idval);
lex.getToken();
return result;
case Lexer.lp:
lex.getToken();
result = parseExp(idsAllowed);
if (lex.token!=Lexer.rp)
throw new ParseException("right parenthesis expected");
lex.getToken();
return result;
default:
throw new ParseException("invalid operand");
}
}
private ExpTree parseDefList()
{ ExpTree result = parseDef();
while (lex.token==Lexer.and)
{ lex.getToken();
result = makeAndTree(result, parseDef());
}
return result;
}
private ExpTree parseDef()
{ if (lex.token!=Lexer.id)
throw new ParseException("definition must start with identifier");
char id = lex.idval;
if (Character.isUpperCase(id))
throw new ParseException("upper-case identifiers cannot be used in defintion list");
lex.getToken();
if (lex.token!=Lexer.eq)
throw new ParseException("'=' expected");
lex.getToken();
return makeDefTree(id, parseExp(false));
}
// the next seven methods need to be modified for part 3 of the assignment
static ExpTree makeNumberLeaf(int n)
{ return new ExpTree(ExpTree.NUM, n, null, null);
// this method should return a new number leaf with value n created using your constructor
// if you've used the abstract class approach you will probably need something like
// return new NumLeaf(n);
// if you've used an ExpTree class that stores the node kind you will probably need something like
// return new ExpTree(ExpTree.numNode, n , null, null);
}
static ExpTree makeIdLeaf(char c)
{ return new ExpTree(ExpTree.VAL, c, null, null);
// this method should return a new id leaf with value c
}
static ExpTree makePlusTree(ExpTree l, ExpTree r)
{ return new ExpTree(ExpTree.OP, '+', l, r);
// this method should return a new plus node with children l and r created using your constructor
// if you've used the abstract class approach you will probably need something like
// return new OpNode('+', l, r);
// or
// return new PlusNode(l, r);
// if you've used an ExpTree class that stores the node kind you will probably need something like
// return new ExpTree(ExpTree.opMode, '+', l, r);
}
static ExpTree makeMinusTree(ExpTree l, ExpTree r)
{ return new ExpTree(ExpTree.OP, '-', l, r);
// this method should return a new minus node with children l and r
}
static ExpTree makeTimesTree(ExpTree l, ExpTree r)
{ return new ExpTree(ExpTree.OP, '*', l, r);
// this method should return a new times node with children l and r
}
static ExpTree makeDivideTree(ExpTree l, ExpTree r)
{ return new ExpTree(ExpTree.OP, '/', l, r);
// this method should return a new divide node with children l and r
}
static ExpTree makeModTree(ExpTree l, ExpTree r)
{ return new ExpTree(ExpTree.OP, '%', l, r);
// this method should return a new mod (%) node with children l and r
}
// the next three methods need to be modified for part 6 of the assignment - do not modify them if you have not attempted part 6
static ExpTree makeWhereTree(ExpTree l, ExpTree r)
{ // remove the following line if you modify this method; leave it here if you do not attempt part 6
System.out.println("Part 6 not attempted");
return null;
// this method should return a new 'where' node with children l and r
}
static ExpTree makeAndTree(ExpTree l, ExpTree r)
{ return null;
// this method should return a new 'and' node with children l and r
}
static ExpTree makeDefTree(char c, ExpTree t)
{ return null;
// this method should return a new definition node with identifier c and child t
// if your definition nodes have 2 children you should put a new id leaf in the left child and use t as the right child
}
}
class Lexer
{ static final int err = 0, num = 1, id = 2, plus = 3, minus = 4, times = 5, div = 6, mod = 7,
lp = 8, rp = 9, semico = 10, where = 11, and = 12, eq = 13;
int token;
char idval;
int numval;
private String line = "";
private BufferedReader buf;
Lexer()
{ buf = new BufferedReader(new InputStreamReader(System.in));
}
void init()
{ do
try
{ line = buf.readLine().trim();
}
catch(Exception e)
{ System.out.println("Error in input");
System.exit(1);
}
while (line.length()==0);
}
String getLine()
{ init();
return(line);
}
void getToken()
{ if (line.length()==0)
token = err;
else switch (line.charAt(0))
{ case '+':
token = plus;
line = line.substring(1).trim();
break;
case '-':
token = minus;
line = line.substring(1).trim();
break;
case '*':
token = times;
line = line.substring(1).trim();
break;
case '/':
token = div;
line = line.substring(1).trim();
break;
case '%':
token = mod;
line = line.substring(1).trim();
break;
case '(':
token = lp;
line = line.substring(1).trim();
break;
case ')':
token = rp;
line = line.substring(1).trim();
break;
case ';':
token = semico;
line = line.substring(1).trim();
break;
case '=':
token = eq;
line = line.substring(1).trim();
break;
default:
if (Character.isDigit(line.charAt(0)))
{ token = num;
numval = line.charAt(0) - '0';
int i = 1;
while (i<line.length()&&Character.isDigit(line.charAt(i)))
{ numval = numval*10+line.charAt(i)-'0';
i++;
}
line = line.substring(i).trim();
}
else if (Character.isLowerCase(line.charAt(0)))
{ char c = line.charAt(0);
if (c=='w' && line.length()>=5 && line.charAt(1)=='h' && line.charAt(2)=='e' && line.charAt(3)=='r' &&
line.charAt(4)=='e')
{ token = where;
line = line.substring(5).trim();
}
else if (c=='a' && line.length()>=3 && line.charAt(1)=='n' && line.charAt(2)=='d')
{ token = and;
line = line.substring(3).trim();
}
else if (line.length()>1 && Character.isLetter(line.charAt(1)))
{ token = err;
}
else
{ token = id;
idval = c;
line = line.substring(1).trim();
}
}
else
token = err;
}
}
}
哦,很久以前,我做过秋千的东西。
在Test.main中,如果我替换
System.out.println(myTree.toPostfix(myTree));
TreeView view = new TreeView(myTree);
test.add(view);
view.repaint();
与:
System.out.println (myTree.toPostfix(myTree));
TreeView view = new TreeView(myTree);
test.add (view);
// view.repaint();
test.invalidate ();
我得到了一个分裂图 - 可能是开始的一个步骤。
4+2*3-9*4+2*3-9;
4 2 3 * + 9 4 * - 2 3 * + 9 -
但只有在全尺寸之后。调整大小会破坏图形并且程序会挂起。
2 项额外改进,一项是关键改进,一项是用户友好性:
setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
应添加到 CTor
public Test() {
在 setVisible 之前,因此您不必使用 Ctrl-C 停止程序。那是关键。
第二个在词法分析器中。如果您在 line.endsWith (";") 上修剪后测试输入,您可以自己默默地添加分号,而不是告诉用户,做什么以及如何做。 更好的是:在 BorderLayout (NORTH) 或 SOUTH 为公式添加一个 JTextField,以便用户可以更新它。出于测试目的,最好预先填充它。
更新
同时我玩得很开心,改进了它,这可能主要是品味和优先级的问题,但也许你感兴趣。一步很可能比上面的 invalidate-command 更好。
这里是需要的导入:
import javax.swing.*;
import java.awt.*;
import java.io.*;
import javax.swing.border.*;
这里是 TestTreeView(我有很多 类 调用 'Test'):
public class TestTreeView extends JFrame {
JTextField jtf = new JTextField (30);
JButton jb = new JButton ("go!");
public TestTreeView () {
setSize (900, 600); // a
Container cp = getContentPane (); // b
JPanel jp = new JPanel ();
jp.setLayout (new BorderLayout());
cp.add (jp);
jp.setBorder (BorderFactory.createEtchedBorder ()); // c
JPanel tp = new JPanel ();
tp.setLayout (new FlowLayout ());
tp.add (jtf);
jtf.setText ("1+2*3/4%5");
jtf.setFont ((jtf.getFont()).deriveFont (24.0f)); // I like big fonts, maybe need stronger glasses :)
tp.add (jb);
jp.add (tp, BorderLayout.NORTH);
actions (jp); // see below
setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
setVisible (true);
}
public void actions (JPanel jp) {
jb.addActionListener (ae -> {
String s = jtf.getText ();
System.out.println (s);
if (s.length () > 1) {
//create parse trees from input in JTextField
Parser p = new Parser (s); // redefined, see below
ExpTree myTree;
try {
myTree = p.parseLine ();
System.out.println (myTree.toPostfix(myTree));
TreeView view = new TreeView (myTree);
jp.add (view, BorderLayout.CENTER);
// view.repaint();
// jp.invalidate ();
jp.revalidate (); // c
}
catch (ParseException e) {
System.out.println ("Invalid Expression: Ensure it ends with a semicolon");
}
}
});
}
public static void main (String[] args) {
TestTreeView test = new TestTreeView ();
}
}
备注:
- a) 我需要更多 space,因此需要 900x600
- b) 至少到 Java-1.5,你不应该添加到主框架,而是添加到 contentPane。也许它随着 1.6、1.7 或 1.8 发生了变化
- c) 重新验证是必经之路。直接在前面绘画,而不仅仅是在第一次调整大小之后。这应该适用,即使您不喜欢使用 JTextField 和按钮。
Parser 发现了一个新的 CTor,它需要一个字符串,我们从 JTextField 传递过来:
class Parser
{
private Lexer lex;
public Parser()
{
lex = new Lexer();
}
public Parser (String s)
{
lex = new Lexer (s);
}
Lexer 找到了一个新的 CTor,它需要一个从解析器传递过来的字符串:
class Lexer
void init()
{
do
try
{
line = buf.readLine ().trim ();
if (! line.endsWith (";"))
line = String.format ("%s;", line);
}
catch(Exception e)
{
System.out.println("Error in input");
System.exit (1);
}
while (line.length () == 0);
}
{
// ...
Lexer ()
{
buf = new BufferedReader (new InputStreamReader(System.in));
}
Lexer (String s)
{
buf = new BufferedReader (new StringReader (s));
}
//加上自动分号修复:
void init()
{
do
try
{
line = buf.readLine ().trim ();
if (! line.endsWith (";"))
line = String.format ("%s;", line);
}
catch(Exception e)
{
System.out.println("Error in input");
System.exit (1);
}
while (line.length () == 0);
}
最后的名言。在网上,有很多教程,如何将 ActionListener 中的动作工作从主事件循环中分离出来。你应该考虑完成这样的 material。这里的代码不是最先进的。 :)