├── .DS_Store ├── COPYING ├── Makefile ├── README.md ├── benchmark.sh ├── doc ├── style.latte ├── table.awk ├── table.text ├── user-doc.html └── user-doc.latte ├── example ├── BasicGraphApplet.java ├── Curve.java ├── FunctionOfX.java ├── GraphApplet.java ├── GraphCanvas.java ├── LinearScale.java ├── MultilineLabel.java ├── ParametricCurve.java ├── Scale.java └── example.html └── expr ├── Benchmark.java ├── Example.java ├── Expr.java ├── Parser.java ├── RegressionTest.java ├── Scanner.java ├── SyntaxException.java ├── Token.java └── Variable.java /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darius/expr/4c750ac1944bcc99f9f4c3d0d5eaa24f2b8f2b61/.DS_Store -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright 2002 by Darius Bacon . 2 | 3 | Permission is granted to anyone to use this software for any 4 | purpose on any computer system, and to redistribute it freely, 5 | subject to the following restrictions: 6 | 7 | 1. The author is not responsible for the consequences of use of 8 | this software, no matter how awful, even if they arise from 9 | defects in it. 10 | 11 | 2. The origin of this software must not be misrepresented, either 12 | by explicit claim or by omission. 13 | 14 | 3. Altered versions must be plainly marked as such, and must not 15 | be misrepresented as being the original software. 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: classfiles expr.jar doc/user-doc.html jdoc example/BasicGraphApplet.class example/expr.jar 2 | 3 | classfiles: 4 | javac -O expr/*.java 5 | 6 | classfiles = $(shell echo expr/{BinaryExpr,Parser,Token,ConditionalExpr,Scanner,UnaryExpr,Expr,SyntaxException,Variable,LiteralExpr}.class) 7 | 8 | expr.jar: $(classfiles) 9 | jar cf expr.jar $(classfiles) 10 | 11 | doc/user-doc.html: doc/table.awk doc/table.text doc/user-doc.latte doc/style.latte 12 | nawk -f doc/table.awk doc/table.text | cat doc/style.latte - | latte-html >doc/user-doc.html 13 | 14 | jdoc: 15 | cd doc; javadoc -classpath .. ../expr/*.java 16 | 17 | example/BasicGraphApplet.class: example/BasicGraphApplet.java example/expr.jar 18 | javac -sourcepath example/expr.jar:example example/BasicGraphApplet.java 19 | 20 | # We need a copy of expr.jar in the example directory because of 21 | # appletviewer's lame security rules. 22 | example/expr.jar: expr.jar 23 | cp expr.jar example/expr.jar 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | expr 2 | ==== 3 | 4 | This package parses and evaluates mathematical expressions over 5 | floating-point numbers, like `2 + 2` or `cos(x/(2*pi)) * cos(y/(2*pi))`. 6 | 7 | The design priorities were ease of use with helpful error messages, 8 | ease of integration into applets, and good performance: fast 9 | evaluation and short download times. Features and flexibility were 10 | not high priorities, but the code is simple enough that it shouldn't 11 | be hard to change to your taste. 12 | 13 | Javadoc comments in the source files give full documentation, and 14 | [user-doc.html](doc/user-doc.html) gives a user's-eye 15 | view. 16 | 17 | 18 | Installing it 19 | ============= 20 | 21 | To install, put `expr.jar` in your classpath. (You'll need to create 22 | it first by running `make`, if you downloaded this from GitHub. If you 23 | don't have `make`, then run `javac -O expr/*.java` and then `jar cf 24 | expr.jar expr/*.class`.) 25 | 26 | To try it out, put the `expr` directory in your classpath. Then 27 | 28 | java expr.Example '3.14159 * x^2' 0 4 1 29 | 30 | should write the output given in 31 | [Example.java](expr/Example.java). 32 | 33 | To incorporate this code into an applet, put expr.jar somewhere 34 | accessible to your webserver, and reference it with the ARCHIVE 35 | attribute in your HTML: 36 | 37 | 40 | 41 | 42 | To provide documentation for your users, put 43 | [user-doc.html](doc/user-doc.html) where they can read it. 44 | 45 | 46 | Using it 47 | ======== 48 | 49 | To get started quickly without reading the documentation, see the 50 | example code in [Example.java](expr/Example.java). 51 | Here are some excerpts from it, with each bit preceded by an 52 | explanation: 53 | 54 | `expr` is an object representing a parsed expression. 55 | 56 | Expr expr; 57 | 58 | Parse the string in `args[0]` and set `expr` to the representation of 59 | the result. We only parse the string once, so that later on we won't 60 | have to parse it each time we evaluate it. 61 | 62 | try { expr = Parser.parse(args[0]); } 63 | 64 | If the string couldn't be parsed, complain and abort. The `e.explain()` 65 | tries to describe exactly what went wrong. 66 | 67 | catch (SyntaxException e) { 68 | System.err.println(e.explain()) 69 | return; 70 | } 71 | 72 | Create a variable-object for `x`, so that we can control the value that 73 | `x` takes each time we evaluate the expression. For example, if the 74 | expression is parsed from `1 + x * x`, its value will depend on what 75 | we set `x` to. 76 | 77 | Variable x = Variable.make("x"); 78 | 79 | For values of `x` in the range from `low` to `high`, increasing by 80 | `step`, print out the value of the expression. 81 | 82 | for (double xval = low; xval <= high; xval += step) { 83 | x.setValue(xval); 84 | System.out.println(expr.value()); 85 | } 86 | 87 | There's another included example: the graphing applet in 88 | [example.html](example/example.html). 89 | 90 | 91 | Other features 92 | ============== 93 | 94 | The above is the simplest code you can write to get going. With a few 95 | more lines, you can help the parser catch more errors. By default, it 96 | allows any variable to be in the input expression, even variables you 97 | haven't defined. Here's how to tell it what's allowed: 98 | 99 | Variable x = Variable.make("x"); 100 | Parser parser = new Parser(); 101 | parser.allow(x); 102 | 103 | Expr expr; 104 | try { 105 | expr = parser.parseString(args[0]); 106 | } catch (SyntaxException e) { 107 | // the rest is the same as before 108 | } 109 | 110 | You can disallow all variables, if for some reason you want to, by 111 | changing `parser.allow(x)` to `parser.allow(null)`. 112 | 113 | 114 | Contact 115 | ======= 116 | 117 | See the file [COPYING](COPYING) for copyright info. 118 | Send questions and bug reports to Darius Bacon . 119 | -------------------------------------------------------------------------------- /benchmark.sh: -------------------------------------------------------------------------------- 1 | java expr.Benchmark \ 2 | '137' \ 3 | 'pi' \ 4 | 'x' \ 5 | '3.14159 * x^2' \ 6 | 'sin(10*x) + sin(9*x)' \ 7 | 'sin(x) + sin(100*x)/100' \ 8 | 'sin(0.1*x) * (sin(9*x) + sin(10*x))' \ 9 | 'exp(-x^2)' \ 10 | '2^(-x^2)' \ 11 | '(x^3)^(-x^2)' \ 12 | 'x*sin(1/x)' \ 13 | 'x^2-x-6' \ 14 | 'sqrt(3^2 + x^2)' \ 15 | 'atan(5/x)' \ 16 | '(x^2 + x + 1)/(x + 1)' \ 17 | '(x^3 - (4*x^2) + 12)/(x^2 + 2)' \ 18 | '-2*(x-3)^2+5' \ 19 | '2*abs(x+1)-3' \ 20 | 'sqrt(9-x^2)' \ 21 | '6.674*10^-11*x*5.98*10^24/(6373002.24^2)' 22 | -------------------------------------------------------------------------------- /doc/style.latte: -------------------------------------------------------------------------------- 1 | {\def {\page \=name \=headtags \&rest} 2 | {{\head {\if \headtags \headtags {}} 3 | {\title \name}} 4 | {\body \bgcolor=ivory 5 | \rest}}} 6 | 7 | {\def {\table3 \&rest} 8 | {\def {\link-table-item \item} 9 | {\tr 10 | {\td {\car \item}} 11 | {\td {\car {\cdr \item}}} 12 | {\apply \td {\cdr {\cdr \item}}}}} 13 | {\table \cellpadding=5 \align=center \width=95% \border=0 14 | {\lmap \link-table-item \rest}}} 15 | 16 | {\def {\headered-list \&rest} 17 | {\def {\headered-list-item \item} 18 | {\li {\b {\car \item}} 19 | {\br} 20 | {\cdr \item}}} 21 | {\ul {\lmap \headered-list-item \rest}}} 22 | 23 | {\def {\numbered-list \&rest} 24 | {\ol {\lmap \li \rest}}} 25 | 26 | {\def {\link \name \ref} 27 | {\a \href=\ref \name}} 28 | 29 | {\def {\simple-link \name} 30 | {\a \href=\name \name}} 31 | 32 | {\def {\eaddr \addr} 33 | {\a \href={mailto:\addr} <\addr>}} 34 | 35 | {\def {\latte} 36 | {\a \href=http://www.latte.org/ Latte}} 37 | 38 | {\def {\section \title \&rest} 39 | {{\h2 \title} 40 | {\ul \rest}}} 41 | 42 | {\def {\section \name \&rest} 43 | {{\h2 \name} 44 | \rest}} 45 | -------------------------------------------------------------------------------- /doc/table.awk: -------------------------------------------------------------------------------- 1 | BEGIN { 2 | while (getline line <"doc/user-doc.latte") 3 | if (line == "@table@") 4 | break; 5 | else 6 | print line; 7 | print "{\\table3"; 8 | print " {{\\b Feature} {\\b Syntax} {\\b Examples}}"; 9 | } 10 | 11 | { 12 | f1 = trim(substr($0, 1, 24)); 13 | f2 = trim(substr($0, 25, 15)); 14 | f3 = trim(substr($0, 41)); 15 | if (f1 == "" && f2 == "") { 16 | keep3 = keep3 "{\\dt " code(f3) "}"; 17 | } else { 18 | flush(); 19 | keep1 = "{" f1 "}"; 20 | keep2 = code(f2); 21 | keep3 = "{\\dt " code(f3) "}"; 22 | } 23 | } 24 | 25 | END { 26 | flush(); 27 | print " }"; 28 | while (getline line <"doc/user-doc.latte") 29 | print line; 30 | } 31 | 32 | 33 | function trim(s) 34 | { 35 | sub(/[ \t]*$/, "", s); 36 | return s; 37 | } 38 | 39 | function code(s) 40 | { 41 | if (s ~ /code/) 42 | return "{" s "}"; 43 | return "{\\code " s "}"; 44 | } 45 | 46 | function flush() 47 | { 48 | if (keep1) 49 | printf("{%s %s {\\dl %s}}\n", keep1, keep2, keep3); 50 | } 51 | -------------------------------------------------------------------------------- /doc/table.text: -------------------------------------------------------------------------------- 1 | Numbers 1 42.5 2 | Variables x longvariablename 3 | {\i x {\sup y}} x ^ y 3^2 = 9 4 | 2^2^3 = 2^8 = 256 5 | Multiply, divide x * y 3*2 = 6 6 | x / y 3/2 = 1.5 7 | Add, subtract, negate x + y 3+2 = 5 8 | x - y 3-2 = 1 9 | -x -3 = 0-3 10 | Comparison x < y 2<3 = 1 11 | 2<2 = 0 12 | 3<2 = 0 13 | x <= y 2<=3 = 1 14 | 2<=2 = 1 15 | 3<=2 = 0 16 | x = y 2=3 = 0 17 | 2=2 = 1 18 | x <> y 2<>3 = 1 19 | 2<>2 = 0 20 | x >= y same as {\code y <= x} 21 | x > y same as {\code y < x} 22 | Conjunction x and y 1 and 1 = 1 23 | 1 and 0 = 0 24 | 0 and 0 = 0 25 | Disjunction x or y 1 or 1 = 1 26 | 1 or 0 = 1 27 | 0 or 0 = 0 28 | Absolute value abs(x) abs(-2) = 2 29 | abs(2) = 2 30 | Arc-cosine acos(x) acos(1) = 0 31 | Arc-sine asin(x) asin(1) = pi/2 32 | Arc-tangent atan(x) atan(1) = pi/4 33 | atan2(x, y) atan(-1, -1) = -3 pi / 4 34 | Ceiling ceil(x) ceil(3.5) = 4 35 | ceil(-3.5) = -3 36 | Cosine cos(x) cos(0) = 1 37 | {\i e {\sup x}} exp(x) exp(1) = 2.7182818284590451 38 | Floor floor(x) floor(3.5) = 3 39 | floor(-3.5) = -4 40 | Conditional if(x, y, z) if(1, 42, 137) = 42 41 | if(0, 42, 137) = 137 42 | Natural logarithm log(x) log(2.7182818284590451) = 1 43 | Maximum max(x, y) max(2, 3) = 3 44 | Minimum min(x, y) min(2, 3) = 2 45 | Rounding round(x) round(3.5) = 4 46 | round(-3.5) = -4 47 | Sine sin(x) sin(pi/2) = 1 48 | Square root sqrt(x) sqrt(9) = 3 49 | Tangent tan(x) {\code tan(pi/4) = 1} (approximately) 50 | -------------------------------------------------------------------------------- /doc/user-doc.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Expressing Formulas 7 |

Expressing formulas

8 | 9 |

Here's how you can enter your formulas. It's almost but not quite 10 | like ordinary math notation from the textbooks; there are differences 11 | because you have to type into input boxes instead of writing things 12 | out freehand. For instance, to express x2+1 you type in 13 | x^2+1. 14 | 15 |

Basics

16 | 17 |

Adding and subtracting work as you'd expect: x+5, 1-x. 18 | 19 |

To multiply, use *: 7*x means 7 times x. 20 | 21 |

To divide, use /: x/3 means x divided by 3. 22 | 23 |

x n is written x^n. 24 | 25 |

The square root of x is written sqrt(x). 26 | 27 |

Putting all this together, here's a bigger example, a solution to the 28 | quadratic equation:
sqrt(b^2 - 4*a*c) / (2*a). 29 | 30 |

Reference Manual

31 | 32 |

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 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 | 97 | 98 | 99 | 100 |
FeatureSyntaxExamples
Numbers1
42.5
Variablesx
longvariablename
x yx ^ y
3^2 = 9
2^2^3 = 2^8 = 256
Multiply, dividex * y
3*2 = 6
x / y
3/2 = 1.5
Add, subtract, negatex + y
3+2 = 5
x - y
3-2 = 1
-x
-3 = 0-3
Comparisonx < y
2<3 = 1
2<2 = 0
3<2 = 0
x <= y
2<=3 = 1
2<=2 = 1
3<=2 = 0
x = y
2=3 = 0
2=2 = 1
x <> y
2<>3 = 1
2<>2 = 0
x >= y
same as y <= x
x > y
same as y < x
Conjunctionx and y
1 and 1 = 1
1 and 0 = 0
0 and 0 = 0
Disjunctionx or y
1 or 1 = 1
1 or 0 = 1
0 or 0 = 0
Absolute valueabs(x)
abs(-2) = 2
abs(2) = 2
Arc-cosineacos(x)
acos(1) = 0
Arc-sineasin(x)
asin(1) = pi/2
Arc-tangentatan(x)
atan(1) = pi/4
atan2(x, y)
atan(-1, -1) = -3 pi / 4
Ceilingceil(x)
ceil(3.5) = 4
ceil(-3.5) = -3
Cosinecos(x)
cos(0) = 1
e xexp(x)
exp(1) = 2.7182818284590451
Floorfloor(x)
floor(3.5) = 3
floor(-3.5) = -4
Conditionalif(x, y, z)
if(1, 42, 137) = 42
if(0, 42, 137) = 137
Natural logarithmlog(x)
log(2.7182818284590451) = 1
Maximummax(x, y)
max(2, 3) = 3
Minimummin(x, y)
min(2, 3) = 2
Roundinground(x)
round(3.5) = 4
round(-3.5) = -4
Sinesin(x)
sin(pi/2) = 1
Square rootsqrt(x)
sqrt(9) = 3
Tangenttan(x)
tan(pi/4) = 1 (approximately)
101 | 102 |

Pitfalls

103 | 104 |

When you write a+b*c, should that mean to add a and b, and then multiply by c? Or is it add a to the result of 105 | multiplying b and c? In other words, which goes first, the 106 | + or the *? The answer is clear if you look at the 107 | original math notation, a+bc: the b and c go together, 108 | then we add their product to a. What if you wanted it the other 109 | way? In pencil-and-paper math, that'd be (a+b)c, and you can do 110 | the same thing at the computer as (a+b)*c. In general, 111 | operators listed earlier in the reference manual above, like *, come before later ones, like +. 112 | 113 |

Write 0 < x and x < 5, rather than 0 < x < 5. The 114 | latter is interpreted as (0 < x) < 5, which first evaluates 115 | 0 < x yielding a truth value (1 or 0 for true or false), then 116 | compares that truth value to 5. Don't do that! 117 | 118 |

a/b*c is not a/(b*c). In handwritten math notation 119 | you could write that with a above the division line and b*c vertically below it, but we can't do that here: everything is 120 | horizontal and so the program can't tell if you meant (a/b)*c 121 | or a/(b*c). (It chooses the first, in fact.) When in doubt, 122 | use parentheses. 123 | 124 |

The program does not understand sin x, but requires sin(x) instead. This is because, if you said sin x * y, it'd 125 | be uncertain whether you meant sin(x * y) or (sin(x)) * 126 | y. So all the functions need parentheses; the lessened ambiguity is 127 | worth the extra typing. 128 | 129 |

While you can refer to real numbers like pi and e and the square root 130 | of 2, this program can't represent them exactly; it only holds onto a 131 | fixed number of digits. For example, computing tan(pi/4) 132 | doesn't give 1 exactly, but 0.99999999999999989. You can get 133 | completely bogus answers if your formulas are too sensitive to these 134 | imprecisions; there isn't space here to treat this issue. 135 | 136 | -------------------------------------------------------------------------------- /doc/user-doc.latte: -------------------------------------------------------------------------------- 1 | {\page \name={Expressing Formulas} 2 | 3 | {\h1 Expressing formulas} 4 | 5 | Here's how you can enter your formulas. It's almost but not quite 6 | like ordinary math notation from the textbooks; there are differences 7 | because you have to type into input boxes instead of writing things 8 | out freehand. For instance, to express {\i x{\sup 2}}+1 you type in 9 | {\code x^2+1}. 10 | 11 | {\h2 Basics} 12 | 13 | Adding and subtracting work as you'd expect: {\code x+5}, {\code 1-x}. 14 | 15 | To multiply, use {\code *}: {\code 7*x} means 7 times {\i x}. 16 | 17 | To divide, use {\code /}: {\code x/3} means {\i x} divided by 3. 18 | 19 | {\i x {\sup n}} is written {\code x^n}. 20 | 21 | The square root of {\i x} is written {\code sqrt(x)}. 22 | 23 | Putting all this together, here's a bigger example, a solution to the 24 | quadratic equation:{\br}{\code sqrt(b^2 - 4*a*c) / (2*a)}. 25 | 26 | {\h2 Reference Manual} 27 | 28 | @table@ 29 | 30 | {\h2 Pitfalls} 31 | 32 | When you write {\code a+b*c}, should that mean to add {\i a} and {\i 33 | b}, and then multiply by {\i c}? Or is it add {\i a} to the result of 34 | multiplying {\i b} and {\i c}? In other words, which goes first, the 35 | {\code +} or the {\code *}? The answer is clear if you look at the 36 | original math notation, {\i a+bc}: the {\i b} and {\i c} go together, 37 | then we add their product to {\i a}. What if you wanted it the other 38 | way? In pencil-and-paper math, that'd be {\i (a+b)c}, and you can do 39 | the same thing at the computer as {\code (a+b)*c}. In general, 40 | operators listed earlier in the reference manual above, like {\code 41 | *}, come before later ones, like {\code +}. 42 | 43 | Write {\code 0 < x and x < 5}, rather than {\code 0 < x < 5}. The 44 | latter is interpreted as {\code (0 < x) < 5}, which first evaluates 45 | {\code 0 < x} yielding a truth value (1 or 0 for true or false), then 46 | compares that truth value to 5. Don't do that! 47 | 48 | {\code a/b*c} is not {\code a/(b*c)}. In handwritten math notation 49 | you could write that with {\code a} above the division line and {\code 50 | b*c} vertically below it, but we can't do that here: everything is 51 | horizontal and so the program can't tell if you meant {\code (a/b)*c} 52 | or {\code a/(b*c)}. (It chooses the first, in fact.) When in doubt, 53 | use parentheses. 54 | 55 | The program does not understand {\code sin x}, but requires {\code 56 | sin(x)} instead. This is because, if you said {\code sin x * y}, it'd 57 | be uncertain whether you meant {\code sin(x * y)} or {\code (sin(x)) * 58 | y}. So all the functions need parentheses; the lessened ambiguity is 59 | worth the extra typing. 60 | 61 | While you can refer to real numbers like pi and e and the square root 62 | of 2, this program can't represent them exactly; it only holds onto a 63 | fixed number of digits. For example, computing {\code tan(pi/4)} 64 | doesn't give 1 exactly, but 0.99999999999999989. You can get 65 | completely bogus answers if your formulas are too sensitive to these 66 | imprecisions; there isn't space here to treat this issue. 67 | } 68 | -------------------------------------------------------------------------------- /example/BasicGraphApplet.java: -------------------------------------------------------------------------------- 1 | import java.applet.Applet; 2 | import java.awt.BorderLayout; 3 | import java.awt.Color; 4 | import java.awt.FlowLayout; 5 | import java.awt.Graphics; 6 | import java.awt.Label; 7 | import java.awt.Panel; 8 | import java.awt.TextField; 9 | import java.awt.event.ActionEvent; 10 | import java.awt.event.ActionListener; 11 | 12 | import expr.Expr; 13 | import expr.Parser; 14 | import expr.SyntaxException; 15 | import expr.Variable; 16 | 17 | public class BasicGraphApplet extends Applet implements ActionListener { 18 | 19 | public String getAppletInfo() { 20 | return "Draws a graph of a formula you enter."; 21 | } 22 | 23 | private GraphCanvas canvas; 24 | private TextField input; 25 | private MultilineLabel messageDisplay; 26 | 27 | public void init() { 28 | input = new TextField(36); 29 | input.addActionListener(this); 30 | 31 | Panel inputBox = new Panel(); 32 | inputBox.setLayout(new FlowLayout()); 33 | inputBox.add(new Label("f(x) = ", Label.RIGHT)); 34 | inputBox.add(input); 35 | 36 | canvas = new GraphCanvas(); 37 | messageDisplay = new MultilineLabel(""); 38 | 39 | setLayout(new BorderLayout()); 40 | add("North", inputBox); 41 | add("Center", canvas); 42 | add("South", messageDisplay); 43 | validate(); 44 | } 45 | 46 | public void actionPerformed(ActionEvent evt) { 47 | drawGraph(); 48 | } 49 | 50 | private void drawGraph() { 51 | try { 52 | canvas.setCurve(parseFofX(input.getText(), Color.black)); 53 | } catch (SyntaxException e) { 54 | messageDisplay.setText(e.explain()); 55 | validate(); 56 | return; 57 | } 58 | messageDisplay.setText(""); 59 | canvas.repaint(); 60 | } 61 | 62 | private Curve parseFofX(String text, Color color) throws SyntaxException { 63 | Variable x = Variable.make("x"); 64 | return new FunctionOfX(canvas, color, x, Parser.parse(text)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /example/Curve.java: -------------------------------------------------------------------------------- 1 | import java.awt.Color; 2 | import java.awt.Dimension; 3 | import java.awt.Graphics; 4 | 5 | public class Curve { 6 | protected final GraphCanvas canvas; 7 | protected Color color; 8 | 9 | public Curve(GraphCanvas canvas, Color color) { 10 | this.canvas = canvas; 11 | this.color = color; 12 | cacheCanvas(); 13 | } 14 | 15 | public void draw(Graphics g) { 16 | cacheCanvas(); 17 | g.setColor(color); 18 | } 19 | 20 | protected double x0, xScale, y0, yScale; 21 | 22 | protected void cacheCanvas() { 23 | Dimension d = canvas.getSize(); 24 | int width = d.width; 25 | int height = d.height; 26 | 27 | x0 = width / 2.0; // FIXME: origin might not be at middle 28 | y0 = height / 2.0; 29 | xScale = width / (canvas.getXMax() - canvas.getXMin()); 30 | yScale = height / (canvas.getYMax() - canvas.getYMin()); 31 | } 32 | 33 | protected double unscreenifyX(int xCoord) { return (xCoord - x0)/xScale; } 34 | protected double unscreenifyY(int yCoord) { return (y0 - yCoord)/yScale; } 35 | 36 | protected int screenifyX(double x) { return (int) (0.5 + x0 + x*xScale); } 37 | protected int screenifyY(double y) { return (int) (0.5 + y0 - y*yScale); } 38 | } 39 | -------------------------------------------------------------------------------- /example/FunctionOfX.java: -------------------------------------------------------------------------------- 1 | import java.awt.Color; 2 | import java.awt.Graphics; 3 | 4 | import expr.Expr; 5 | import expr.Variable; 6 | 7 | public final class FunctionOfX extends Curve { 8 | private Variable xVar; 9 | private Expr expr; 10 | 11 | public FunctionOfX(GraphCanvas canvas, Color color, 12 | Variable xVar, Expr expr) { 13 | super(canvas, color); 14 | this.xVar = xVar; 15 | this.expr = expr; 16 | } 17 | 18 | public void draw(Graphics g) { 19 | super.draw(g); 20 | 21 | xVar.setValue(unscreenifyX(0)); 22 | int prevY = screenifyY(expr.value()); 23 | 24 | int xMax = canvas.getSize().width; 25 | for (int x = 1; x < xMax; ++x) { 26 | xVar.setValue(unscreenifyX(x)); 27 | int y = screenifyY(expr.value()); 28 | g.drawLine(x - 1, prevY, x, y); 29 | prevY = y; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/GraphApplet.java: -------------------------------------------------------------------------------- 1 | import java.applet.Applet; 2 | import java.awt.BorderLayout; 3 | import java.awt.Color; 4 | import java.awt.FlowLayout; 5 | import java.awt.Graphics; 6 | import java.awt.Label; 7 | import java.awt.Panel; 8 | import java.awt.TextField; 9 | import java.awt.event.ActionEvent; 10 | import java.awt.event.ActionListener; 11 | 12 | import expr.Expr; 13 | import expr.Parser; 14 | import expr.SyntaxException; 15 | import expr.Variable; 16 | 17 | public class GraphApplet extends Applet implements ActionListener { 18 | 19 | public String getAppletInfo() { 20 | return "Draws a graph of a formula you enter."; 21 | } 22 | 23 | private GraphCanvas canvas; 24 | private TextField input; 25 | 26 | public void init() { 27 | input = new TextField(36); 28 | input.addActionListener(this); 29 | 30 | Panel inputBox = new Panel(); 31 | inputBox.setLayout(new FlowLayout()); 32 | inputBox.add(new Label("f(x) = ", Label.RIGHT)); 33 | inputBox.add(input); 34 | 35 | canvas = new GraphCanvas(); 36 | 37 | setLayout(new BorderLayout()); 38 | add("North", inputBox); 39 | add("Center", canvas); 40 | validate(); 41 | } 42 | 43 | public void actionPerformed(ActionEvent evt) { 44 | drawGraph(); 45 | } 46 | 47 | private void drawGraph() { 48 | try { 49 | canvas.setCurve(parseFofX(input.getText(), Color.black)); 50 | } catch (SyntaxException e) { 51 | showStatus(e.explain()); 52 | return; 53 | } 54 | showStatus(""); 55 | canvas.repaint(); 56 | } 57 | 58 | private Curve parseFofX(String text, Color color) throws SyntaxException { 59 | Variable x = Variable.make("x"); 60 | Parser parser = new Parser(); 61 | parser.allow(x); 62 | return new FunctionOfX(canvas, color, x, parser.parseString(text)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /example/GraphCanvas.java: -------------------------------------------------------------------------------- 1 | import java.awt.Canvas; 2 | import java.awt.Color; 3 | import java.awt.Graphics; 4 | 5 | public class GraphCanvas extends Canvas { 6 | 7 | private double x0, x1, y0, y1; 8 | private Curve curve; 9 | 10 | public GraphCanvas() { 11 | setBackground(Color.white); 12 | // lf = new Font("Helvetica", Font.PLAIN, 12); 13 | 14 | x0 = -10.0; 15 | x1 = 10.0; 16 | y0 = -10.0; 17 | y1 = 10.0; 18 | 19 | curve = null; 20 | } 21 | 22 | public void setCurve(Curve curve) { 23 | this.curve = curve; 24 | } 25 | 26 | public void paint(Graphics g) { 27 | if (null != curve) 28 | curve.draw(g); 29 | } 30 | 31 | public double getXMin() { return x0; } 32 | public double getXMax() { return x1; } 33 | 34 | public double getYMin() { return y0; } 35 | public double getYMax() { return y1; } 36 | } 37 | -------------------------------------------------------------------------------- /example/LinearScale.java: -------------------------------------------------------------------------------- 1 | public final class LinearScale { 2 | 3 | private final double origin, scale; 4 | 5 | public LinearScale(double bound0, double bound1, 6 | int screenOrigin, int extent) { 7 | origin = extent / 2.0; // FIXME 8 | scale = extent / (bound1 - bound0); 9 | } 10 | 11 | public int map(double mathCoord) { 12 | return Math.round(origin + mathCoord * scale); 13 | } 14 | 15 | public double unmap(int screenCoord) { 16 | return (screenCoord - origin) / scale; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/MultilineLabel.java: -------------------------------------------------------------------------------- 1 | import java.awt.*; 2 | import java.util.*; 3 | 4 | /** A label that can draw multiple lines of text. */ 5 | public class MultilineLabel extends java.awt.Canvas { 6 | private String myText; 7 | 8 | public MultilineLabel(String text) { 9 | myText = text; 10 | } 11 | 12 | public void setText(String text) { 13 | myText = text; 14 | //invalidate(); // needed? 15 | } 16 | 17 | public void paint(Graphics graphics) { 18 | int h = graphics.getFontMetrics().getHeight(); 19 | StringTokenizer lines = new StringTokenizer(myText, "\n"); 20 | for (int y = h; lines.hasMoreTokens(); y += h) 21 | graphics.drawString(lines.nextToken(), 0, y); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ParametricCurve.java: -------------------------------------------------------------------------------- 1 | import java.awt.Color; 2 | import java.awt.Graphics; 3 | 4 | import expr.Expr; 5 | import expr.Variable; 6 | 7 | public final class ParametricCurve extends Curve { 8 | 9 | public ParametricCurve(GraphCanvas canvas, Color color, 10 | Variable tVar, Expr xExpr, Expr yExpr, int steps) { 11 | super(canvas, color); 12 | this.tVar = tVar; 13 | this.xExpr = xExpr; 14 | this.yExpr = yExpr; 15 | this.steps = steps; 16 | } 17 | 18 | private Variable tVar; 19 | private Expr xExpr, yExpr; 20 | private int steps; 21 | 22 | public void draw(Graphics g) { 23 | super.draw(g); 24 | 25 | tVar.setValue(0.0); 26 | int prevX = screenifyX(xExpr.value()); 27 | int prevY = screenifyY(yExpr.value()); 28 | 29 | double dt = 1.0 / steps; 30 | for (int step = 1; step <= steps; ++step) { 31 | tVar.setValue(step * dt); 32 | int x = screenifyX(xExpr.value()); 33 | int y = screenifyY(yExpr.value()); 34 | g.drawLine(prevX, prevY, x, y); 35 | prevY = y; 36 | prevX = x; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example/Scale.java: -------------------------------------------------------------------------------- 1 | public abstract class Scale { 2 | public abstract int map(double mathCoord); 3 | public abstract double unmap(int screenCoord); 4 | } 5 | -------------------------------------------------------------------------------- /example/example.html: -------------------------------------------------------------------------------- 1 | 2 | Basic Graphing Applet 3 | 4 | 5 |

Basic Graphing Applet

6 |
7 | 11 | If your browser knew Java and had it enabled, you'd see an applet here. 12 | 13 | 14 | -------------------------------------------------------------------------------- /expr/Benchmark.java: -------------------------------------------------------------------------------- 1 | // Run benchmarks from the command line. 2 | 3 | package expr; 4 | 5 | /** 6 | * Time evaluating many expressions over many values. 7 | */ 8 | public class Benchmark { 9 | public static void main(String[] args) { 10 | double parse_product = 1.0; 11 | double run_product = 1.0; 12 | for (int i = 0; i < args.length; ++i) { 13 | long parsetime = timeParse(args[i]); 14 | long runtime = timeRun(args[i]); 15 | System.out.println("" + msec(parsetime) + " ms(parse) " 16 | + msec(runtime) + " ms(run): " 17 | + args[i]); 18 | parse_product *= parsetime; 19 | run_product *= runtime; 20 | } 21 | if (0 < args.length) { 22 | double run_geomean = Math.pow(run_product, 1.0 / args.length); 23 | double parse_geomean = Math.pow(parse_product, 1.0 / args.length); 24 | System.out.println("" + msec(parse_geomean) + " ms(parse) " 25 | + msec(run_geomean) + " ms(run): (geometric mean)"); 26 | } 27 | } 28 | 29 | static long msec(double nsec) { 30 | return (long) Math.rint(nsec * 1e-6); 31 | } 32 | 33 | static final int nruns = 1000000; 34 | 35 | static long timeRun(String expression) { 36 | Variable x = Variable.make("x"); 37 | Expr expr = parse(expression); 38 | 39 | double low = 0.0; 40 | double high = 4.0; 41 | double step = (high - low) / nruns; 42 | 43 | long start = System.nanoTime(); 44 | for (double xval = low; xval <= high; xval += step) { 45 | x.setValue(xval); 46 | expr.value(); 47 | } 48 | return System.nanoTime() - start; 49 | } 50 | 51 | static final int nparses = 1000; 52 | 53 | static long timeParse(String expression) { 54 | long start = System.nanoTime(); 55 | for (int i = 0; i < nparses; ++i) 56 | parse(expression); 57 | return System.nanoTime() - start; 58 | } 59 | 60 | static Expr parse(String expression) { 61 | try { 62 | return Parser.parse(expression); 63 | } catch (SyntaxException e) { 64 | System.err.println(e.explain()); 65 | throw new Error(e); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /expr/Example.java: -------------------------------------------------------------------------------- 1 | // Put the expression evaluator through its paces. 2 | 3 | // Sample usage: 4 | 5 | // $ java expr.Example '3.14159 * x^2' 0 4 1 6 | // 0 7 | // 3.14159 8 | // 12.5664 9 | // 28.2743 10 | // 50.2654 11 | // 12 | // $ java expr.Example 'sin (pi/4 * x)' 0 4 1 13 | // 0 14 | // 0.707107 15 | // 1 16 | // 0.707107 17 | // 1.22461e-16 18 | // 19 | // $ java expr.Example 'sin (pi/4 x)' 0 4 1 20 | // I don't understand your formula "sin (pi/4 x)". 21 | // 22 | // I got as far as "sin (pi/4" and then saw "x". 23 | // I expected ")" at that point, instead. 24 | // An example of a formula I can parse is "sin (pi/4 + x)". 25 | 26 | package expr; 27 | 28 | /** 29 | * A simple example of parsing and evaluating an expression. 30 | */ 31 | public class Example { 32 | public static void main(String[] args) { 33 | 34 | Expr expr; 35 | try { 36 | expr = Parser.parse(args[0]); 37 | } catch (SyntaxException e) { 38 | System.err.println(e.explain()); 39 | return; 40 | } 41 | 42 | double low = Double.valueOf(args[1]).doubleValue(); 43 | double high = Double.valueOf(args[2]).doubleValue(); 44 | double step = Double.valueOf(args[3]).doubleValue(); 45 | 46 | Variable x = Variable.make("x"); 47 | for (double xval = low; xval <= high; xval += step) { 48 | x.setValue(xval); 49 | System.out.println(expr.value()); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /expr/Expr.java: -------------------------------------------------------------------------------- 1 | // Mathematical expressions. 2 | // Copyright 1996 by Darius Bacon; see the file COPYING. 3 | 4 | package expr; 5 | 6 | /** 7 | * A mathematical expression, built out of literal numbers, variables, 8 | * arithmetic and relational operators, and elementary functions. It 9 | * can be evaluated to get its value given its variables' current 10 | * values. The operator names are from java.lang.Math where possible. 11 | */ 12 | public abstract class Expr { 13 | 14 | /** Calculate the expression's value. 15 | * @return the value given the current variable values */ 16 | public abstract double value(); 17 | 18 | /** Binary operator: addition */ public static final int ADD = 0; 19 | /** Binary operator: subtraction */ public static final int SUB = 1; 20 | /** Binary operator: multiplication */ public static final int MUL = 2; 21 | /** Binary operator: division */ public static final int DIV = 3; 22 | /** Binary operator: exponentiation */ public static final int POW = 4; 23 | /** Binary operator: arctangent */ public static final int ATAN2 = 5; 24 | /** Binary operator: maximum */ public static final int MAX = 6; 25 | /** Binary operator: minimum */ public static final int MIN = 7; 26 | /** Binary operator: less than */ public static final int LT = 8; 27 | /** Binary operator: less or equal */ public static final int LE = 9; 28 | /** Binary operator: equality */ public static final int EQ = 10; 29 | /** Binary operator: inequality */ public static final int NE = 11; 30 | /** Binary operator: greater or equal*/ public static final int GE = 12; 31 | /** Binary operator: greater than */ public static final int GT = 13; 32 | /** Binary operator: logical and */ public static final int AND = 14; 33 | /** Binary operator: logical or */ public static final int OR = 15; 34 | 35 | /** Unary operator: absolute value*/ public static final int ABS = 100; 36 | /** Unary operator: arccosine */ public static final int ACOS = 101; 37 | /** Unary operator: arcsine */ public static final int ASIN = 102; 38 | /** Unary operator: arctangent*/ public static final int ATAN = 103; 39 | /** Unary operator: ceiling */ public static final int CEIL = 104; 40 | /** Unary operator: cosine */ public static final int COS = 105; 41 | /** Unary operator: e to the x*/ public static final int EXP = 106; 42 | /** Unary operator: floor */ public static final int FLOOR = 107; 43 | /** Unary operator: natural log*/ public static final int LOG = 108; 44 | /** Unary operator: negation */ public static final int NEG = 109; 45 | /** Unary operator: rounding */ public static final int ROUND = 110; 46 | /** Unary operator: sine */ public static final int SIN = 111; 47 | /** Unary operator: square root */ public static final int SQRT = 112; 48 | /** Unary operator: tangent */ public static final int TAN = 113; 49 | 50 | /** Make a literal expression. 51 | * @param v the constant value of the expression 52 | * @return an expression whose value is always v */ 53 | public static Expr makeLiteral(double v) { 54 | return new LiteralExpr(v); 55 | } 56 | /** Make an expression that applies a unary operator to an operand. 57 | * @param rator a code for a unary operator 58 | * @param rand operand 59 | * @return an expression meaning rator(rand) 60 | */ 61 | public static Expr makeApp1(int rator, Expr rand) { 62 | Expr app = new UnaryExpr(rator, rand); 63 | return rand instanceof LiteralExpr 64 | ? new LiteralExpr(app.value()) 65 | : app; 66 | } 67 | /** Make an expression that applies a binary operator to two operands. 68 | * @param rator a code for a binary operator 69 | * @param rand0 left operand 70 | * @param rand1 right operand 71 | * @return an expression meaning rator(rand0, rand1) 72 | */ 73 | public static Expr makeApp2(int rator, Expr rand0, Expr rand1) { 74 | Expr app = new BinaryExpr(rator, rand0, rand1); 75 | return rand0 instanceof LiteralExpr && rand1 instanceof LiteralExpr 76 | ? new LiteralExpr(app.value()) 77 | : app; 78 | } 79 | /** Make a conditional expression. 80 | * @param test `if' part 81 | * @param consequent `then' part 82 | * @param alternative `else' part 83 | * @return an expression meaning `if test, then consequent, else 84 | * alternative' 85 | */ 86 | public static Expr makeIfThenElse(Expr test, 87 | Expr consequent, 88 | Expr alternative) { 89 | Expr cond = new ConditionalExpr(test, consequent, alternative); 90 | if (test instanceof LiteralExpr) 91 | return test.value() != 0 ? consequent : alternative; 92 | else 93 | return cond; 94 | } 95 | } 96 | 97 | // These classes are all private to this module because we could 98 | // plausibly want to do it in a completely different way, such as a 99 | // stack machine. 100 | 101 | class LiteralExpr extends Expr { 102 | double v; 103 | LiteralExpr(double v) { this.v = v; } 104 | public double value() { return v; } 105 | } 106 | 107 | class UnaryExpr extends Expr { 108 | int rator; 109 | Expr rand; 110 | 111 | UnaryExpr(int rator, Expr rand) { 112 | this.rator = rator; 113 | this.rand = rand; 114 | } 115 | 116 | public double value() { 117 | double arg = rand.value(); 118 | switch (rator) { 119 | case ABS: return Math.abs(arg); 120 | case ACOS: return Math.acos(arg); 121 | case ASIN: return Math.asin(arg); 122 | case ATAN: return Math.atan(arg); 123 | case CEIL: return Math.ceil(arg); 124 | case COS: return Math.cos(arg); 125 | case EXP: return Math.exp(arg); 126 | case FLOOR: return Math.floor(arg); 127 | case LOG: return Math.log(arg); 128 | case NEG: return -arg; 129 | case ROUND: return Math.rint(arg); 130 | case SIN: return Math.sin(arg); 131 | case SQRT: return Math.sqrt(arg); 132 | case TAN: return Math.tan(arg); 133 | default: throw new RuntimeException("BUG: bad rator"); 134 | } 135 | } 136 | } 137 | 138 | class BinaryExpr extends Expr { 139 | int rator; 140 | Expr rand0, rand1; 141 | 142 | BinaryExpr(int rator, Expr rand0, Expr rand1) { 143 | this.rator = rator; 144 | this.rand0 = rand0; 145 | this.rand1 = rand1; 146 | } 147 | public double value() { 148 | double arg0 = rand0.value(); 149 | double arg1 = rand1.value(); 150 | switch (rator) { 151 | case ADD: return arg0 + arg1; 152 | case SUB: return arg0 - arg1; 153 | case MUL: return arg0 * arg1; 154 | case DIV: return arg0 / arg1; // division by 0 has IEEE 754 behavior 155 | case POW: return Math.pow(arg0, arg1); 156 | case ATAN2: return Math.atan2(arg0, arg1); 157 | case MAX: return arg0 < arg1 ? arg1 : arg0; 158 | case MIN: return arg0 < arg1 ? arg0 : arg1; 159 | case LT: return arg0 < arg1 ? 1.0 : 0.0; 160 | case LE: return arg0 <= arg1 ? 1.0 : 0.0; 161 | case EQ: return arg0 == arg1 ? 1.0 : 0.0; 162 | case NE: return arg0 != arg1 ? 1.0 : 0.0; 163 | case GE: return arg0 >= arg1 ? 1.0 : 0.0; 164 | case GT: return arg0 > arg1 ? 1.0 : 0.0; 165 | case AND: return arg0 != 0 && arg1 != 0 ? 1.0 : 0.0; 166 | case OR: return arg0 != 0 || arg1 != 0 ? 1.0 : 0.0; 167 | default: throw new RuntimeException("BUG: bad rator"); 168 | } 169 | } 170 | } 171 | 172 | class ConditionalExpr extends Expr { 173 | Expr test, consequent, alternative; 174 | 175 | ConditionalExpr(Expr test, Expr consequent, Expr alternative) { 176 | this.test = test; 177 | this.consequent = consequent; 178 | this.alternative = alternative; 179 | } 180 | 181 | public double value() { 182 | return test.value() != 0 ? consequent.value() : alternative.value(); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /expr/Parser.java: -------------------------------------------------------------------------------- 1 | // Operator-precedence parser. 2 | // Copyright 1996 by Darius Bacon; see the file COPYING. 3 | 4 | package expr; 5 | 6 | import java.io.*; 7 | import java.util.Hashtable; 8 | import java.util.Vector; 9 | 10 | 11 | /** 12 | Parses strings representing mathematical formulas with variables. 13 | The following operators, in descending order of precedence, are 14 | defined: 15 | 16 | 25 | 26 | ^ associates right-to-left; other operators associate left-to-right. 27 | 28 |

These unary functions are defined: 29 | abs, acos, asin, atan, 30 | ceil, cos, exp, floor, 31 | log, round, sin, sqrt, 32 | tan. Each requires one argument enclosed in parentheses. 33 | 34 |

There are also binary functions: atan2, min, max; and a ternary 35 | conditional function: if(test, then, else). 36 | 37 |

Whitespace outside identifiers is ignored. 38 | 39 |

Examples: 40 |

*/ 45 | public class Parser { 46 | 47 | // Built-in constants 48 | static private final Variable pi = Variable.make("pi"); 49 | static { 50 | pi.setValue(Math.PI); 51 | } 52 | 53 | /** Return the expression denoted by the input string. 54 | * 55 | * @param input the unparsed expression 56 | * @exception SyntaxException if the input is unparsable */ 57 | static public Expr parse(String input) throws SyntaxException { 58 | return new Parser().parseString(input); 59 | } 60 | 61 | /** Set of Variable's that are allowed to appear in input expressions. 62 | * If null, any variable is allowed. */ 63 | private Hashtable allowedVariables = null; 64 | 65 | /** Adjust the set of allowed variables: create it (if not yet 66 | * existent) and add optVariable (if it's nonnull). If the 67 | * allowed-variable set exists, the parser will reject input 68 | * strings that use any other variables. 69 | * 70 | * @param optVariable the variable to be allowed, or null */ 71 | public void allow(Variable optVariable) { 72 | if (null == allowedVariables) { 73 | allowedVariables = new Hashtable(); 74 | allowedVariables.put(pi, pi); 75 | } 76 | if (null != optVariable) 77 | allowedVariables.put(optVariable, optVariable); 78 | } 79 | 80 | Scanner tokens = null; 81 | private Token token = null; 82 | 83 | /** Return the expression denoted by the input string. 84 | * 85 | * @param input the unparsed expression 86 | * @exception SyntaxException if the input is unparsable */ 87 | public Expr parseString(String input) throws SyntaxException { 88 | tokens = new Scanner(input, operatorChars); 89 | return reparse(); 90 | } 91 | 92 | static private final String operatorChars = "*/+-^<>=,()"; 93 | 94 | private Expr reparse() throws SyntaxException { 95 | tokens.index = -1; 96 | nextToken(); 97 | Expr expr = parseExpr(0); 98 | if (token.ttype != Token.TT_EOF) 99 | throw error("Incomplete expression", 100 | SyntaxException.INCOMPLETE, null); 101 | return expr; 102 | } 103 | 104 | private void nextToken() { 105 | token = tokens.nextToken(); 106 | } 107 | 108 | private Expr parseExpr(int precedence) throws SyntaxException { 109 | Expr expr = parseFactor(); 110 | loop: 111 | for (;;) { 112 | int l, r, rator; 113 | 114 | // The operator precedence table. 115 | // l = left precedence, r = right precedence, rator = operator. 116 | // Higher precedence values mean tighter binding of arguments. 117 | // To associate left-to-right, let r = l+1; 118 | // to associate right-to-left, let r = l. 119 | 120 | switch (token.ttype) { 121 | 122 | case '<': l = 20; r = 21; rator = Expr.LT; break; 123 | case Token.TT_LE: l = 20; r = 21; rator = Expr.LE; break; 124 | case '=': l = 20; r = 21; rator = Expr.EQ; break; 125 | case Token.TT_NE: l = 20; r = 21; rator = Expr.NE; break; 126 | case Token.TT_GE: l = 20; r = 21; rator = Expr.GE; break; 127 | case '>': l = 20; r = 21; rator = Expr.GT; break; 128 | 129 | case '+': l = 30; r = 31; rator = Expr.ADD; break; 130 | case '-': l = 30; r = 31; rator = Expr.SUB; break; 131 | 132 | case '/': l = 40; r = 41; rator = Expr.DIV; break; 133 | case '*': l = 40; r = 41; rator = Expr.MUL; break; 134 | 135 | case '^': l = 50; r = 50; rator = Expr.POW; break; 136 | 137 | default: 138 | if (token.ttype == Token.TT_WORD && token.sval.equals("and")) { 139 | l = 5; r = 6; rator = Expr.AND; break; 140 | } 141 | if (token.ttype == Token.TT_WORD && token.sval.equals("or")) { 142 | l = 10; r = 11; rator = Expr.OR; break; 143 | } 144 | break loop; 145 | } 146 | 147 | if (l < precedence) 148 | break loop; 149 | 150 | nextToken(); 151 | expr = Expr.makeApp2(rator, expr, parseExpr(r)); 152 | } 153 | return expr; 154 | } 155 | 156 | static private final String[] procs1 = { 157 | "abs", "acos", "asin", "atan", 158 | "ceil", "cos", "exp", "floor", 159 | "log", "round", "sin", "sqrt", 160 | "tan" 161 | }; 162 | static private final int[] rators1 = { 163 | Expr.ABS, Expr.ACOS, Expr.ASIN, Expr.ATAN, 164 | Expr.CEIL, Expr.COS, Expr.EXP, Expr.FLOOR, 165 | Expr.LOG, Expr.ROUND, Expr.SIN, Expr.SQRT, 166 | Expr.TAN 167 | }; 168 | 169 | static private final String[] procs2 = { 170 | "atan2", "max", "min" 171 | }; 172 | static private final int[] rators2 = { 173 | Expr.ATAN2, Expr.MAX, Expr.MIN 174 | }; 175 | 176 | private Expr parseFactor() throws SyntaxException { 177 | switch (token.ttype) { 178 | case Token.TT_NUMBER: { 179 | Expr lit = Expr.makeLiteral(token.nval); 180 | nextToken(); 181 | return lit; 182 | } 183 | case Token.TT_WORD: { 184 | for (int i = 0; i < procs1.length; ++i) 185 | if (procs1[i].equals(token.sval)) { 186 | nextToken(); 187 | expect('('); 188 | Expr rand = parseExpr(0); 189 | expect(')'); 190 | return Expr.makeApp1(rators1[i], rand); 191 | } 192 | 193 | for (int i = 0; i < procs2.length; ++i) 194 | if (procs2[i].equals(token.sval)) { 195 | nextToken(); 196 | expect('('); 197 | Expr rand1 = parseExpr(0); 198 | expect(','); 199 | Expr rand2 = parseExpr(0); 200 | expect(')'); 201 | return Expr.makeApp2(rators2[i], rand1, rand2); 202 | } 203 | 204 | if (token.sval.equals("if")) { 205 | nextToken(); 206 | expect('('); 207 | Expr test = parseExpr(0); 208 | expect(','); 209 | Expr consequent = parseExpr(0); 210 | expect(','); 211 | Expr alternative = parseExpr(0); 212 | expect(')'); 213 | return Expr.makeIfThenElse(test, consequent, alternative); 214 | } 215 | 216 | Expr var = Variable.make(token.sval); 217 | if (null != allowedVariables && null == allowedVariables.get(var)) 218 | throw error("Unknown variable", 219 | SyntaxException.UNKNOWN_VARIABLE, null); 220 | nextToken(); 221 | return var; 222 | } 223 | case '(': { 224 | nextToken(); 225 | Expr enclosed = parseExpr(0); 226 | expect(')'); 227 | return enclosed; 228 | } 229 | case '-': 230 | nextToken(); 231 | return Expr.makeApp1(Expr.NEG, parseExpr(35)); 232 | case Token.TT_EOF: 233 | throw error("Expected a factor", 234 | SyntaxException.PREMATURE_EOF, null); 235 | default: 236 | throw error("Expected a factor", 237 | SyntaxException.BAD_FACTOR, null); 238 | } 239 | } 240 | 241 | private SyntaxException error(String complaint, 242 | int reason, 243 | String expected) { 244 | return new SyntaxException(complaint, this, reason, expected); 245 | } 246 | 247 | private void expect(int ttype) throws SyntaxException { 248 | if (token.ttype != ttype) 249 | throw error("'" + (char)ttype + "' expected", 250 | SyntaxException.EXPECTED, "" + (char)ttype); 251 | nextToken(); 252 | } 253 | 254 | 255 | // Error correction 256 | 257 | boolean tryCorrections() { 258 | return tryInsertions() || tryDeletions() || trySubstitutions(); 259 | } 260 | 261 | private boolean tryInsertions() { 262 | Vector v = tokens.tokens; 263 | for (int i = tokens.index; 0 <= i; --i) { 264 | Token t; 265 | if (i < v.size()) { 266 | t = (Token) v.elementAt(i); 267 | } else { 268 | String s = tokens.getInput(); 269 | t = new Token(Token.TT_EOF, 0, s, s.length(), s.length()); 270 | } 271 | Token[] candidates = possibleInsertions(t); 272 | for (int j = 0; j < candidates.length; ++j) { 273 | v.insertElementAt(candidates[j], i); 274 | try { 275 | reparse(); 276 | return true; 277 | } catch (SyntaxException se) { 278 | v.removeElementAt(i); 279 | } 280 | } 281 | } 282 | return false; 283 | } 284 | 285 | private boolean tryDeletions() { 286 | Vector v = tokens.tokens; 287 | for (int i = tokens.index; 0 <= i; --i) { 288 | if (v.size() <= i) 289 | continue; 290 | Object t = v.elementAt(i); 291 | v.remove(i); 292 | try { 293 | reparse(); 294 | return true; 295 | } catch (SyntaxException se) { 296 | v.insertElementAt(t, i); 297 | } 298 | } 299 | return false; 300 | } 301 | 302 | private boolean trySubstitutions() { 303 | Vector v = tokens.tokens; 304 | for (int i = tokens.index; 0 <= i; --i) { 305 | if (v.size() <= i) 306 | continue; 307 | Token t = (Token) v.elementAt(i); 308 | Token[] candidates = possibleSubstitutions(t); 309 | for (int j = 0; j < candidates.length; ++j) { 310 | v.setElementAt(candidates[j], i); 311 | try { 312 | reparse(); 313 | return true; 314 | } catch (SyntaxException se) { } 315 | } 316 | v.setElementAt(t, i); 317 | } 318 | return false; 319 | } 320 | 321 | private Token[] possibleInsertions(Token t) { 322 | Token[] ts = 323 | new Token[operatorChars.length() + 6 + procs1.length + procs2.length]; 324 | int i = 0; 325 | 326 | Token one = new Token(Token.TT_NUMBER, 1, "1", t); 327 | ts[i++] = one; 328 | 329 | for (int j = 0; j < operatorChars.length(); ++j) { 330 | char c = operatorChars.charAt(j); 331 | ts[i++] = new Token(c, 0, Character.toString(c), t); 332 | } 333 | 334 | ts[i++] = new Token(Token.TT_WORD, 0, "x", t); 335 | 336 | for (int k = 0; k < procs1.length; ++k) 337 | ts[i++] = new Token(Token.TT_WORD, 0, procs1[k], t); 338 | 339 | for (int m = 0; m < procs2.length; ++m) 340 | ts[i++] = new Token(Token.TT_WORD, 0, procs2[m], t); 341 | 342 | ts[i++] = new Token(Token.TT_LE, 0, "<=", t); 343 | ts[i++] = new Token(Token.TT_NE, 0, "<>", t); 344 | ts[i++] = new Token(Token.TT_GE, 0, ">=", t); 345 | ts[i++] = new Token(Token.TT_WORD, 0, "if", t); 346 | 347 | return ts; 348 | } 349 | 350 | private Token[] possibleSubstitutions(Token t) { 351 | return possibleInsertions(t); 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /expr/RegressionTest.java: -------------------------------------------------------------------------------- 1 | package expr; 2 | 3 | /** 4 | * Test for bugs in the whole package. 5 | */ 6 | public class RegressionTest { 7 | 8 | public static void main(String[] args) { 9 | Variable.make("pi").setValue(Math.PI); 10 | 11 | expect(9, "3^2"); 12 | expect(256, "2^2^3"); 13 | expect(6, "3*2"); 14 | expect(1.5, "3/2"); 15 | expect(5, "3+2"); 16 | expect(1, "3-2"); 17 | expect(-3, "-3"); 18 | expect(1, "2<3"); 19 | expect(0, "2<2"); 20 | expect(0, "3<2"); 21 | expect(1, "2<=3"); 22 | expect(1, "2<=2"); 23 | expect(0, "3<=2"); 24 | expect(0, "2=3"); 25 | expect(1, "2=2"); 26 | expect(1, "2<>3"); 27 | expect(0, "2<>2"); 28 | expect(0, "2>=3"); 29 | expect(1, "2>=2"); 30 | expect(1, "3>=2"); 31 | expect(0, "2>3"); 32 | expect(0, "2>2"); 33 | expect(1, "3>2"); 34 | expect(1, "(1 and 1)"); 35 | expect(0, "(1 and 0)"); 36 | expect(0, "(0 and 1)"); 37 | expect(0, "(0 and 0)"); 38 | expect(1, "(1 or 1)"); 39 | expect(1, "(1 or 0)"); 40 | expect(1, "(0 or 1)"); 41 | expect(0, "(0 or 0)"); 42 | expect(2, "abs(-2)"); 43 | expect(2, "abs(2)"); 44 | expect(0, "acos(1)"); 45 | expect(Math.PI/2, "asin(1)"); 46 | expect(Math.PI/4, "atan(1)"); 47 | expect(-3*Math.PI/4, "atan2(-1, -1)"); 48 | expect(4, "ceil(3.5)"); 49 | expect(-3, "ceil(-3.5)"); 50 | expect(1, "cos(0)"); 51 | expect(Math.exp(1), "exp(1)"); 52 | expect(3, "floor(3.5)"); 53 | expect(-4, "floor(-3.5)"); 54 | expect(1, "log(2.7182818284590451)"); 55 | expect(4, "round(3.5)"); 56 | expect(-4, "round(-3.5)"); 57 | expect(1, "sin(pi/2)"); 58 | expect(3, "sqrt(9)"); 59 | expect(0.99999999999999989, "tan(pi/4)"); 60 | expect(3, "max(2, 3)"); 61 | expect(2, "min(2, 3)"); 62 | expect(137, "if(0, 42, 137)"); 63 | expect(42, "if(1, 42, 137)"); 64 | 65 | expect(-3.0 * Math.pow(1.01, 100.1), " -3 * 1.01^100.1 "); 66 | 67 | Variable x = Variable.make("x"); 68 | x.setValue(-40.0); 69 | expect(-171.375208, "-0.00504238 * x^2 + 2.34528 * x - 69.4962"); 70 | 71 | { 72 | boolean caught = false; 73 | Parser p = new Parser(); 74 | p.allow(x); //or p.allow(null); 75 | try { 76 | p.parseString("whoo"); 77 | } catch (SyntaxException se) { 78 | caught = true; 79 | } 80 | if (!caught) 81 | throw new Error("Test failed: unknown variable allowed"); 82 | } 83 | 84 | x.setValue(1.1); 85 | 86 | expect(137, "137"); 87 | expect(Math.PI, "pi"); 88 | expect(1.1, "x"); 89 | expect(3.8013239000000003, "3.14159 * x^2"); 90 | expect(-1.457526100326025, "sin(10*x) + sin(9*x)"); 91 | expect(0.8907649332805846, "sin(x) + sin(100*x)/100"); 92 | expect(-0.16000473871962462, "sin(0.1*x) * (sin(9*x) + sin(10*x))"); 93 | expect(0.29819727942988733, "exp(-x^2)"); 94 | expect(0.43226861565393254, "2^(-x^2)"); 95 | expect(0.7075295010833899, "(x^3)^(-x^2)"); 96 | expect(0.8678400091286832, "x*sin(1/x)"); 97 | expect(-5.89, "x^2-x-6"); 98 | expect(3.1953090617340916, "sqrt(3^2 + x^2)"); 99 | expect(1.3542460218188073, "atan(5/x)"); 100 | expect(1.5761904761904764, "(x^2 + x + 1)/(x + 1)"); 101 | expect(2.6451713395638627, "(x^3 - (4*x^2) + 12)/(x^2 + 2)"); 102 | expect(-2.2199999999999998, "-2*(x-3)^2+5"); 103 | expect(1.2000000000000002, "2*abs(x+1)-3"); 104 | expect(2.7910571473905725, "sqrt(9-x^2)"); 105 | 106 | System.out.println("All tests passed."); 107 | } 108 | 109 | private static void expect(double expected, String input) { 110 | Expr expr; 111 | try { 112 | expr = Parser.parse(input); 113 | } catch (SyntaxException e) { 114 | throw new Error(e.explain()); 115 | } 116 | 117 | double result = expr.value(); 118 | if (result != expected) { 119 | throw new Error("Bad result: " + result + 120 | " instead of the expected " + expected + 121 | " in \"" + input + "\""); 122 | } 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /expr/Scanner.java: -------------------------------------------------------------------------------- 1 | // Scan lexical tokens in input strings. 2 | 3 | package expr; 4 | 5 | import java.util.Vector; 6 | 7 | 8 | class Scanner { 9 | 10 | private String s; 11 | private String operatorChars; 12 | 13 | Vector tokens = new Vector(); 14 | int index = -1; 15 | 16 | public Scanner(String string, String operatorChars) { 17 | this.s = string; 18 | this.operatorChars = operatorChars; 19 | 20 | int i = 0; 21 | do { 22 | i = scanToken(i); 23 | } while (i < s.length()); 24 | } 25 | 26 | public String getInput() { 27 | return s; 28 | } 29 | 30 | // The tokens may have been diddled, so this can be different from 31 | // getInput(). 32 | public String toString() { 33 | StringBuffer sb = new StringBuffer(); 34 | int whitespace = 0; 35 | for (int i = 0; i < tokens.size(); ++i) { 36 | Token t = (Token) tokens.elementAt(i); 37 | 38 | int spaces = (whitespace != 0 ? whitespace : t.leadingWhitespace); 39 | if (i == 0) 40 | spaces = 0; 41 | else if (spaces == 0 && !joinable((Token) tokens.elementAt(i-1), t)) 42 | spaces = 1; 43 | for (int j = spaces; 0 < j; --j) 44 | sb.append(" "); 45 | 46 | sb.append(t.sval); 47 | whitespace = t.trailingWhitespace; 48 | } 49 | return sb.toString(); 50 | } 51 | 52 | private boolean joinable(Token s, Token t) { 53 | return !(isAlphanumeric(s) && isAlphanumeric(t)); 54 | } 55 | 56 | private boolean isAlphanumeric(Token t) { 57 | return t.ttype == Token.TT_WORD || t.ttype == Token.TT_NUMBER; 58 | } 59 | 60 | public boolean isEmpty() { 61 | return tokens.size() == 0; 62 | } 63 | 64 | public boolean atStart() { 65 | return index <= 0; 66 | } 67 | 68 | public boolean atEnd() { 69 | return tokens.size() <= index; 70 | } 71 | 72 | public Token nextToken() { 73 | ++index; 74 | return getCurrentToken(); 75 | } 76 | 77 | public Token getCurrentToken() { 78 | if (atEnd()) 79 | return new Token(Token.TT_EOF, 0, s, s.length(), s.length()); 80 | return (Token) tokens.elementAt(index); 81 | } 82 | 83 | private int scanToken(int i) { 84 | while (i < s.length() && Character.isWhitespace(s.charAt(i))) 85 | ++i; 86 | 87 | if (i == s.length()) { 88 | return i; 89 | } else if (0 <= operatorChars.indexOf(s.charAt(i))) { 90 | if (i+1 < s.length()) { 91 | String pair = s.substring(i, i+2); 92 | int ttype = 0; 93 | if (pair.equals("<=")) 94 | ttype = Token.TT_LE; 95 | else if (pair.equals(">=")) 96 | ttype = Token.TT_GE; 97 | else if (pair.equals("<>")) 98 | ttype = Token.TT_NE; 99 | if (0 != ttype) { 100 | tokens.addElement(new Token(ttype, 0, s, i, i+2)); 101 | return i+2; 102 | } 103 | } 104 | tokens.addElement(new Token(s.charAt(i), 0, s, i, i+1)); 105 | return i+1; 106 | } else if (Character.isLetter(s.charAt(i))) { 107 | return scanSymbol(i); 108 | } else if (Character.isDigit(s.charAt(i)) || '.' == s.charAt(i)) { 109 | return scanNumber(i); 110 | } else { 111 | tokens.addElement(makeErrorToken(i, i+1)); 112 | return i+1; 113 | } 114 | } 115 | 116 | private int scanSymbol(int i) { 117 | int from = i; 118 | while (i < s.length() 119 | && (Character.isLetter(s.charAt(i)) 120 | || Character.isDigit(s.charAt(i)))) 121 | ++i; 122 | tokens.addElement(new Token(Token.TT_WORD, 0, s, from, i)); 123 | return i; 124 | } 125 | 126 | private int scanNumber(int i) { 127 | int from = i; 128 | 129 | // We include letters in our purview because otherwise we'd 130 | // accept a word following with no intervening space. 131 | for (; i < s.length(); ++i) 132 | if ('.' != s.charAt(i) 133 | && !Character.isDigit(s.charAt(i)) 134 | && !Character.isLetter(s.charAt(i))) 135 | break; 136 | 137 | String text = s.substring(from, i); 138 | double nval; 139 | try { 140 | nval = Double.valueOf(text).doubleValue(); 141 | } catch (NumberFormatException nfe) { 142 | tokens.addElement(makeErrorToken(from, i)); 143 | return i; 144 | } 145 | 146 | tokens.addElement(new Token(Token.TT_NUMBER, nval, s, from, i)); 147 | return i; 148 | } 149 | 150 | private Token makeErrorToken(int from, int i) { 151 | return new Token(Token.TT_ERROR, 0, s, from, i); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /expr/SyntaxException.java: -------------------------------------------------------------------------------- 1 | // Syntax-error exception. 2 | // Copyright 1996 by Darius Bacon; see the file COPYING. 3 | 4 | package expr; 5 | 6 | /** 7 | * An exception indicating a problem in parsing an expression. It can 8 | * produce a short, cryptic error message (with getMessage()) or a 9 | * long, hopefully helpful one (with explain()). 10 | */ 11 | public class SyntaxException extends Exception { 12 | 13 | /** An error code meaning the input string couldn't reach the end 14 | of the input; the beginning constituted a legal expression, 15 | but there was unparsable stuff left over. */ 16 | public static final int INCOMPLETE = 0; 17 | 18 | /** An error code meaning the parser ran into a non-value token 19 | (like "/") at a point it was expecting a value (like "42" or 20 | "x^2"). */ 21 | public static final int BAD_FACTOR = 1; 22 | 23 | /** An error code meaning the parser hit the end of its input 24 | before it had parsed a full expression. */ 25 | public static final int PREMATURE_EOF = 2; 26 | 27 | /** An error code meaning the parser hit an unexpected token at a 28 | point where it expected to see some particular other token. */ 29 | public static final int EXPECTED = 3; 30 | 31 | /** An error code meaning the expression includes a variable not 32 | on the `allowed' list. */ 33 | public static final int UNKNOWN_VARIABLE = 4; 34 | 35 | /** Make a new instance. 36 | * @param complaint short error message 37 | * @param parser the parser that hit this snag 38 | * @param reason one of the error codes defined in this class 39 | * @param expected if nonnull, the token the parser expected to 40 | * see (in place of the erroneous token it did see) 41 | */ 42 | public SyntaxException(String complaint, 43 | Parser parser, 44 | int reason, 45 | String expected) { 46 | super(complaint); 47 | this.reason = reason; 48 | this.parser = parser; 49 | this.scanner = parser.tokens; 50 | this.expected = expected; 51 | } 52 | 53 | /** Give a long, hopefully helpful error message. 54 | * @return the message */ 55 | public String explain() { 56 | StringBuffer sb = new StringBuffer(); 57 | 58 | sb.append("I don't understand your formula "); 59 | quotify(sb, scanner.getInput()); 60 | sb.append(".\n\n"); 61 | 62 | explainWhere(sb); 63 | explainWhy(sb); 64 | explainWhat(sb); 65 | 66 | return sb.toString(); 67 | } 68 | 69 | private Parser parser; 70 | private Scanner scanner; 71 | 72 | private int reason; 73 | private String expected; 74 | 75 | private String fixedInput = ""; 76 | 77 | private void explainWhere(StringBuffer sb) { 78 | if (scanner.isEmpty()) { 79 | sb.append("It's empty!\n"); 80 | } else if (scanner.atStart()) { 81 | sb.append("It starts with "); 82 | quotify(sb, theToken()); 83 | if (isLegalToken()) 84 | sb.append(", which can never be the start of a formula.\n"); 85 | else 86 | sb.append(", which is a meaningless symbol to me.\n"); 87 | } else { 88 | sb.append("I got as far as "); 89 | quotify(sb, asFarAs()); 90 | sb.append(" and then "); 91 | if (scanner.atEnd()) { 92 | sb.append("reached the end unexpectedly.\n"); 93 | } else { 94 | sb.append("saw "); 95 | quotify(sb, theToken()); 96 | if (isLegalToken()) 97 | sb.append(".\n"); 98 | else 99 | sb.append(", which is a meaningless symbol to me.\n"); 100 | } 101 | } 102 | } 103 | 104 | private void explainWhy(StringBuffer sb) { 105 | switch (reason) { 106 | case INCOMPLETE: 107 | if (isLegalToken()) 108 | sb.append("The first part makes sense, but I don't see " + 109 | "how the rest connects to it.\n"); 110 | break; 111 | case BAD_FACTOR: 112 | case PREMATURE_EOF: 113 | sb.append("I expected a value"); 114 | if (!scanner.atStart()) sb.append(" to follow"); 115 | sb.append(", instead.\n"); 116 | break; 117 | case EXPECTED: 118 | sb.append("I expected "); 119 | quotify(sb, expected); 120 | sb.append(" at that point, instead.\n"); 121 | break; 122 | case UNKNOWN_VARIABLE: 123 | sb.append("That variable has no value.\n"); 124 | break; 125 | default: 126 | throw new Error("Can't happen"); 127 | } 128 | } 129 | 130 | private void explainWhat(StringBuffer sb) { 131 | fixedInput = tryToFix(); 132 | if (null != fixedInput) { 133 | sb.append("An example of a formula I can parse is "); 134 | quotify(sb, fixedInput); 135 | sb.append(".\n"); 136 | } 137 | } 138 | 139 | private String tryToFix() { 140 | return (parser.tryCorrections() ? scanner.toString() : null); 141 | } 142 | 143 | private void quotify(StringBuffer sb, String s) { 144 | sb.append('"'); 145 | sb.append(s); 146 | sb.append('"'); 147 | } 148 | 149 | private String asFarAs() { 150 | Token t = scanner.getCurrentToken(); 151 | int point = t.location - t.leadingWhitespace; 152 | return scanner.getInput().substring(0, point); 153 | } 154 | 155 | private String theToken() { 156 | return scanner.getCurrentToken().sval; 157 | } 158 | 159 | private boolean isLegalToken() { 160 | Token t = scanner.getCurrentToken(); 161 | return t.ttype != Token.TT_EOF 162 | && t.ttype != Token.TT_ERROR; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /expr/Token.java: -------------------------------------------------------------------------------- 1 | // A lexical token from an input string. 2 | 3 | package expr; 4 | 5 | class Token { 6 | public static final int TT_ERROR = -1; 7 | public static final int TT_EOF = -2; 8 | public static final int TT_NUMBER = -3; 9 | public static final int TT_WORD = -4; 10 | public static final int TT_LE = -5; 11 | public static final int TT_NE = -6; 12 | public static final int TT_GE = -7; 13 | 14 | public Token(int ttype, double nval, String input, int start, int end) { 15 | this.ttype = ttype; 16 | this.sval = input.substring(start, end); 17 | this.nval = nval; 18 | this.location = start; 19 | 20 | int count = 0; 21 | for (int i = start-1; 0 <= i; --i) { 22 | if (!Character.isWhitespace(input.charAt(i))) 23 | break; 24 | ++count; 25 | } 26 | this.leadingWhitespace = count; 27 | 28 | count = 0; 29 | for (int i = end; i < input.length(); ++i) { 30 | if (!Character.isWhitespace(input.charAt(i))) 31 | break; 32 | ++count; 33 | } 34 | this.trailingWhitespace = count; 35 | } 36 | 37 | Token(int ttype, double nval, String sval, Token token) { 38 | this.ttype = ttype; 39 | this.sval = sval; 40 | this.nval = nval; 41 | this.location = token.location; 42 | this.leadingWhitespace = token.leadingWhitespace; 43 | this.trailingWhitespace = token.trailingWhitespace; 44 | } 45 | 46 | public final int ttype; 47 | public final String sval; 48 | public final double nval; 49 | 50 | public final int location; 51 | 52 | public final int leadingWhitespace, trailingWhitespace; 53 | } 54 | -------------------------------------------------------------------------------- /expr/Variable.java: -------------------------------------------------------------------------------- 1 | // Variables associate values with names. 2 | // Copyright 1996 by Darius Bacon; see the file COPYING. 3 | 4 | package expr; 5 | 6 | import java.util.Hashtable; 7 | 8 | /** 9 | * A variable is a simple expression with a name (like "x") and a 10 | * settable value. 11 | */ 12 | public class Variable extends Expr { 13 | private static Hashtable variables = new Hashtable(); 14 | 15 | /** 16 | * Return a unique variable named `name'. There can be only one 17 | * variable with the same name returned by this method; that is, 18 | * make(s1) == make(s2) if and only if s1.equals(s2). 19 | * @param name the variable's name 20 | * @return the variable; create it initialized to 0 if it doesn't 21 | * yet exist */ 22 | static public synchronized Variable make(String name) { 23 | Variable result = (Variable) variables.get(name); 24 | if (result == null) 25 | variables.put(name, result = new Variable(name)); 26 | return result; 27 | } 28 | 29 | private String name; 30 | private double val; 31 | 32 | /** 33 | * Create a new variable, with initial value 0. 34 | * @param name the variable's name 35 | */ 36 | public Variable(String name) { 37 | this.name = name; val = 0; 38 | } 39 | 40 | /** Return the name. */ 41 | public String toString() { return name; } 42 | 43 | /** Get the value. 44 | * @return the current value */ 45 | public double value() { 46 | return val; 47 | } 48 | /** Set the value. 49 | * @param value the new value */ 50 | public void setValue(double value) { 51 | val = value; 52 | } 53 | } 54 | --------------------------------------------------------------------------------