Skip to content

Commit 6d137ee

Browse files
committed
Initial check-in!
0 parents  commit 6d137ee

31 files changed

+913
-0
lines changed

.classpath

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<classpath>
3+
<classpathentry kind="src" path="src"/>
4+
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
5+
<classpathentry kind="output" path="bin"/>
6+
</classpath>

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Ignore output files
2+
bin/

.project

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<projectDescription>
3+
<name>bantam</name>
4+
<comment></comment>
5+
<projects>
6+
</projects>
7+
<buildSpec>
8+
<buildCommand>
9+
<name>org.eclipse.jdt.core.javabuilder</name>
10+
<arguments>
11+
</arguments>
12+
</buildCommand>
13+
</buildSpec>
14+
<natures>
15+
<nature>org.eclipse.jdt.core.javanature</nature>
16+
</natures>
17+
</projectDescription>

COPYRIGHT

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
Bantam uses the MIT License:
2+
3+
Copyright (c) 2011 Robert Nystrom
4+
5+
Permission is hereby granted, free of charge, to
6+
any person obtaining a copy of this software and
7+
associated documentation files (the "Software"),
8+
to deal in the Software without restriction,
9+
including without limitation the rights to use,
10+
copy, modify, merge, publish, distribute,
11+
sublicense, and/or sell copies of the Software,
12+
and to permit persons to whom the Software is
13+
furnished to do so, subject to the following
14+
conditions:
15+
16+
The above copyright notice and this permission
17+
notice shall be included in all copies or
18+
substantial portions of the Software.
19+
20+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT
21+
WARRANTY OF ANY KIND,
22+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
23+
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
24+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
25+
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
27+
WHETHER IN AN ACTION OF CONTRACT, TORT OR
28+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30+
THE SOFTWARE.

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This is a tiny little Java app to demonstrate Pratt parsing. I'm still putting
2+
together a couple of blog posts to explain this, but feel free to poke at the
3+
code until then.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.stuffwithstuff.bantam;
2+
3+
import com.stuffwithstuff.bantam.parselets.*;
4+
5+
/**
6+
* Extends the generic Parser class with support for parsing the actual Bantam
7+
* grammar.
8+
*/
9+
public class BantamParser extends Parser {
10+
public BantamParser(Lexer lexer) {
11+
super(lexer);
12+
13+
// Register all of the parselets for the grammar.
14+
15+
// Register the ones that need special parselets.
16+
register(TokenType.NAME, new NameParselet());
17+
register(TokenType.ASSIGN, new AssignParselet());
18+
register(TokenType.QUESTION, new ConditionalParselet());
19+
register(TokenType.LEFT_PAREN, new GroupParselet());
20+
register(TokenType.LEFT_PAREN, new CallParselet());
21+
22+
// Register the simple operator parselets.
23+
prefix(TokenType.PLUS, Precedence.PREFIX);
24+
prefix(TokenType.MINUS, Precedence.PREFIX);
25+
prefix(TokenType.TILDE, Precedence.PREFIX);
26+
prefix(TokenType.BANG, Precedence.PREFIX);
27+
28+
// For kicks, we'll make "!" both prefix and postfix, kind of like ++.
29+
postfix(TokenType.BANG, Precedence.POSTFIX);
30+
31+
infixLeft(TokenType.PLUS, Precedence.SUM);
32+
infixLeft(TokenType.MINUS, Precedence.SUM);
33+
infixLeft(TokenType.ASTERISK, Precedence.PRODUCT);
34+
infixLeft(TokenType.SLASH, Precedence.PRODUCT);
35+
infixRight(TokenType.CARET, Precedence.EXPONENT);
36+
}
37+
38+
/**
39+
* Registers a postfix unary operator parselet for the given token and
40+
* precedence.
41+
*/
42+
public void postfix(TokenType token, int precedence) {
43+
register(token, new PostfixOperatorParselet(token, precedence));
44+
}
45+
46+
/**
47+
* Registers a prefix unary operator parselet for the given token and
48+
* precedence.
49+
*/
50+
public void prefix(TokenType token, int precedence) {
51+
register(token, new PrefixOperatorParselet(token, precedence));
52+
}
53+
54+
/**
55+
* Registers a left-associative binary operator parselet for the given token
56+
* and precedence.
57+
*/
58+
public void infixLeft(TokenType token, int precedence) {
59+
register(token, new BinaryOperatorParselet(token, precedence, false));
60+
}
61+
62+
/**
63+
* Registers a right-associative binary operator parselet for the given token
64+
* and precedence.
65+
*/
66+
public void infixRight(TokenType token, int precedence) {
67+
register(token, new BinaryOperatorParselet(token, precedence, true));
68+
}
69+
}
+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.stuffwithstuff.bantam;
2+
3+
import java.util.HashMap;
4+
import java.util.Iterator;
5+
import java.util.Map;
6+
7+
/**
8+
* A very primitive lexer. Takes a string and splits it into a series of
9+
* Tokens. Operators and punctuation are mapped to unique keywords. Names,
10+
* which can be any series of letters, are turned into NAME tokens. All other
11+
* characters are ignored (except to separate names). Numbers and strings are
12+
* not supported. This is really just the bare minimum to give the parser
13+
* something to work with.
14+
*/
15+
public class Lexer implements Iterator<Token> {
16+
/**
17+
* Creates a new Lexer to tokenize the given string.
18+
* @param text String to tokenize.
19+
*/
20+
public Lexer(String text) {
21+
mIndex = 0;
22+
mText = text;
23+
24+
// Register all of the TokenTypes that are explicit punctuators.
25+
for (TokenType type : TokenType.values()) {
26+
Character punctuator = type.punctuator();
27+
if (punctuator != null) {
28+
mPunctuators.put(punctuator, type);
29+
}
30+
}
31+
}
32+
33+
@Override
34+
public boolean hasNext() {
35+
return true;
36+
}
37+
38+
@Override
39+
public Token next() {
40+
while (mIndex < mText.length()) {
41+
char c = mText.charAt(mIndex++);
42+
43+
if (mPunctuators.containsKey(c)) {
44+
// Handle punctuation.
45+
return new Token(mPunctuators.get(c), Character.toString(c));
46+
} else if (Character.isLetter(c)) {
47+
// Handle names.
48+
int start = mIndex - 1;
49+
while (mIndex < mText.length()) {
50+
if (!Character.isLetter(mText.charAt(mIndex))) break;
51+
mIndex++;
52+
}
53+
54+
String name = mText.substring(start, mIndex);
55+
return new Token(TokenType.NAME, name);
56+
} else {
57+
// Ignore all other characters (whitespace, etc.)
58+
}
59+
}
60+
61+
// Once we've reached the end of the string, just return EOF tokens. We'll
62+
// just keeping returning them as many times as we're asked so that the
63+
// parser's lookahead doesn't have to worry about running out of tokens.
64+
return new Token(TokenType.EOF, "");
65+
}
66+
67+
@Override
68+
public void remove() {
69+
throw new UnsupportedOperationException();
70+
}
71+
72+
private final Map<Character, TokenType> mPunctuators =
73+
new HashMap<Character, TokenType>();
74+
private final String mText;
75+
private int mIndex = 0;
76+
}
+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.stuffwithstuff.bantam;
2+
3+
import com.stuffwithstuff.bantam.expressions.Expression;
4+
5+
public class Main {
6+
public static void main(String[] args) {
7+
// Function call.
8+
test("a()", "a()");
9+
test("a(b)", "a(b)");
10+
test("a(b, c)", "a(b, c)");
11+
test("a(b)(c)", "a(b)(c)");
12+
test("a(b) + c(d)", "(a(b) + c(d))");
13+
test("a(b ? c : d, e + f)", "a((b ? c : d), (e + f))");
14+
15+
// Unary precedence.
16+
test("~!-+a", "(~(!(-(+a))))");
17+
test("a!!!", "(((a!)!)!)");
18+
19+
// Unary and binary predecence.
20+
test("-a * b", "((-a) * b)");
21+
test("!a + b", "((!a) + b)");
22+
test("~a ^ b", "((~a) ^ b)");
23+
test("-a!", "(-(a!))");
24+
test("!a!", "(!(a!))");
25+
26+
// Binary precedence.
27+
test("a = b + c * d ^ e - f / g", "(a = ((b + (c * (d ^ e))) - (f / g)))");
28+
29+
// Binary associativity.
30+
test("a = b = c", "(a = (b = c))");
31+
test("a + b - c", "((a + b) - c)");
32+
test("a * b / c", "((a * b) / c)");
33+
test("a ^ b ^ c", "(a ^ (b ^ c))");
34+
35+
// Conditional operator.
36+
test("a ? b : c ? d : e", "(a ? b : (c ? d : e))");
37+
test("a ? b ? c : d : e", "(a ? (b ? c : d) : e)");
38+
test("a + b ? c * d : e / f", "((a + b) ? (c * d) : (e / f))");
39+
40+
// Grouping.
41+
test("a + (b + c) + d", "((a + (b + c)) + d)");
42+
test("a ^ (b + c)", "(a ^ (b + c))");
43+
test("(!a)!", "((!a)!)");
44+
45+
// Show the results.
46+
if (sFailed == 0) {
47+
System.out.println("Passed all " + sPassed + " tests.");
48+
} else {
49+
System.out.println("----");
50+
System.out.println("Failed " + sFailed + " out of " +
51+
(sFailed + sPassed) + " tests.");
52+
}
53+
}
54+
55+
/**
56+
* Parses the given chunk of code and verifies that it matches the expected
57+
* pretty-printed result.
58+
*/
59+
public static void test(String source, String expected) {
60+
Lexer lexer = new Lexer(source);
61+
Parser parser = new BantamParser(lexer);
62+
63+
try {
64+
Expression result = parser.parseExpression();
65+
StringBuilder builder = new StringBuilder();
66+
result.print(builder);
67+
String actual = builder.toString();
68+
69+
if (expected.equals(actual)) {
70+
sPassed++;
71+
} else {
72+
sFailed++;
73+
System.out.println("[FAIL] Expected: " + expected);
74+
System.out.println(" Actual: " + actual);
75+
}
76+
} catch(ParseException ex) {
77+
sFailed++;
78+
System.out.println("[FAIL] Expected: " + expected);
79+
System.out.println(" Error: " + ex.getMessage());
80+
}
81+
}
82+
83+
private static int sPassed = 0;
84+
private static int sFailed = 0;
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.stuffwithstuff.bantam;
2+
3+
@SuppressWarnings("serial")
4+
public class ParseException extends RuntimeException {
5+
public ParseException(String message) {
6+
super(message);
7+
}
8+
}

0 commit comments

Comments
 (0)