Skip to content

Commit 940a147

Browse files
committed
Update scanner for floating point constants in Chapter 13.
Add parameterized testing libraries.
1 parent 93ff5ab commit 940a147

File tree

5 files changed

+167
-25
lines changed

5 files changed

+167
-25
lines changed

.classpath

+3
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,8 @@
1010
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
1111
<classpathentry kind="lib" path="lib/truth-1.4.4.jar" sourcepath="lib/truth-1.4.4-sources.jar"/>
1212
<classpathentry kind="lib" path="lib/guava-33.0.0-jre.jar" sourcepath="lib/guava-33.0.0-jre-sources.jar"/>
13+
<classpathentry kind="lib" path="lib/test-parameter-injector-1.0.jar"/>
14+
<classpathentry kind="lib" path="lib/failureaccess-1.0.1.jar"/>
15+
<classpathentry kind="lib" path="lib/guava-testlib-33.0.0-jre.jar"/>
1316
<classpathentry kind="output" path="bin"/>
1417
</classpath>

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ lib/*
2828
examples/*
2929
!examples/*.c
3030
.classpath
31+
/bin/

src/com/plasstech/lang/c/lex/Scanner.java

+90-20
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public Token nextToken() {
4040
return maybeEof.get();
4141
}
4242

43-
if (Character.isDigit(cc)) {
43+
if (Character.isDigit(cc) || cc == '.') {
4444
return makeNumber();
4545
}
4646
if (Character.isLetter(cc) || cc == '_') {
@@ -160,37 +160,66 @@ private Token makeText() {
160160
}
161161

162162
private Token makeNumber() {
163-
StringBuilder sb = new StringBuilder();
164-
while (Character.isDigit(cc) || cc == '_') {
165-
sb.append(cc);
163+
String value = "";
164+
boolean parsingDouble = cc == '.';
165+
if (cc == '.') {
166+
// Leading dot in a floating point constant
167+
value += cc;
166168
advance();
167169
}
170+
value += makeInt();
171+
if (value.equals(".")) {
172+
return error("Illegal character '.'");
173+
}
168174
boolean longConstant = false;
169175
boolean unsignedConstant = false;
170-
if (cc == 'L' || cc == 'l') {
171-
// long constant
172-
advance();
173-
longConstant = true;
174-
if (cc == 'U' || cc == 'u') {
175-
// unsigned constant
176-
advance();
177-
unsignedConstant = true;
178-
}
179-
} else if (cc == 'U' || cc == 'u') {
180-
// unsigned constant
181-
advance();
182-
unsignedConstant = true;
176+
char prevC = cc;
177+
if (value.length() > 0 && !parsingDouble) {
183178
if (cc == 'L' || cc == 'l') {
184179
// long constant
185180
advance();
186181
longConstant = true;
182+
if (cc == 'U' || cc == 'u') {
183+
// unsigned constant
184+
advance();
185+
unsignedConstant = true;
186+
}
187+
} else if (cc == 'U' || cc == 'u') {
188+
// unsigned constant
189+
advance();
190+
unsignedConstant = true;
191+
if (cc == 'L' || cc == 'l') {
192+
// long constant
193+
advance();
194+
longConstant = true;
195+
}
187196
}
188197
}
189-
if (Character.isLetter(cc) || cc == '.') {
190-
return error("Illegal character " + cc);
198+
// Page 302-304
199+
if ((cc == '.' || cc == 'E' || cc == 'e')
200+
&& (prevC == 'L' || prevC == 'l' || prevC == 'U' || prevC == 'u')) {
201+
// I kind of hate this.
202+
return error(
203+
"Illegal character " + prevC + " before dot in floating point constant " + value);
204+
}
205+
if (cc == '.') {
206+
value += cc;
207+
advance();
208+
parsingDouble = true;
209+
// ###. (trailing dot) so far
210+
value += makeInt();
211+
}
212+
parsingDouble |= (cc == 'E' || cc == 'e');
213+
if (parsingDouble) {
214+
// Could be ###.###[Ee][+-]?###
215+
value += makeOptionalExponent();
216+
} else if (Character.isLetter(cc)) {
217+
return error("Illegal character " + cc + " in floating point constant " + value);
218+
}
219+
if (parsingDouble) {
220+
return new Token(TokenType.NUMERIC_LITERAL, value, Type.DOUBLE);
191221
}
192222

193-
String value = sb.toString();
194223
if (unsignedConstant && longConstant) {
195224
return new Token(TokenType.NUMERIC_LITERAL, value, Type.UNSIGNED_LONG);
196225
}
@@ -203,6 +232,47 @@ private Token makeNumber() {
203232
return new Token(TokenType.NUMERIC_LITERAL, value, Type.INT);
204233
}
205234

235+
// Returns the exponent (if any). Assumes the next character is E or e, a non-number-starter:
236+
// [Ee][+-]?int
237+
// Throws if the following character (after the int) is "illegal", but it's possible I got it
238+
// wrong...
239+
private String makeOptionalExponent() {
240+
String value = "";
241+
if (cc == 'E' || cc == 'e') {
242+
value += cc;
243+
advance();
244+
if (cc == '+' || cc == '-') {
245+
value += cc;
246+
advance();
247+
}
248+
String exp = makeInt();
249+
if (exp.length() == 0) {
250+
error("Invalid floating point constant: " + value);
251+
}
252+
value += exp;
253+
}
254+
// gah. the trailing characters of a float constant can only be certain things and I can't
255+
// remember what they are... let's guess
256+
if (cc == '.' || Character.isAlphabetic(cc) || cc == '_') {
257+
error("Invalid character after floating point constant: " + cc);
258+
}
259+
return value;
260+
}
261+
262+
// Returns the next sequence of just numbers or underscores. Throws exception if leading
263+
// underscore.
264+
private String makeInt() {
265+
String sb = "";
266+
if (cc == '_') {
267+
error("Cannot start number with underscore");
268+
}
269+
while (Character.isDigit(cc) || cc == '_') {
270+
sb += cc;
271+
advance();
272+
}
273+
return sb;
274+
}
275+
206276
private Token error(String message) {
207277
throw new ScannerException(message);
208278
}

src/com/plasstech/lang/c/typecheck/Type.java

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public final String toString() {
2020

2121
Type INT = new SimpleType("int", 32, true);
2222
Type LONG = new SimpleType("long", 64, true);
23+
Type DOUBLE = new SimpleType("double", 64, true);
2324
Type UNSIGNED_INT = new SimpleType("unsigned int", 32, false);
2425
Type UNSIGNED_LONG = new SimpleType("unsigned long", 64, false);
2526
Type NO_TYPE = new SimpleType("no type", 0, false);

test/com/plasstech/lang/c/lex/ScannerTest.java

+72-5
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44
import static org.junit.Assert.assertThrows;
55

66
import org.junit.Test;
7+
import org.junit.runner.RunWith;
78

9+
import com.google.testing.junit.testparameterinjector.TestParameter;
10+
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
811
import com.plasstech.lang.c.typecheck.Type;
912

13+
@RunWith(TestParameterInjector.class)
1014
public class ScannerTest {
1115

1216
@Test
@@ -166,11 +170,9 @@ public void nextTokenUnsignedLongConstant() {
166170
}
167171

168172
@Test
169-
public void nextTokenBadIntConstant() {
170-
assertThrows(ScannerException.class, () -> new Scanner("0a").nextToken());
171-
assertThrows(ScannerException.class, () -> new Scanner("23B").nextToken());
172-
assertThrows(ScannerException.class, () -> new Scanner("234.").nextToken());
173-
assertThrows(ScannerException.class, () -> new Scanner("234L.").nextToken());
173+
public void nextTokenBadIntConstant(
174+
@TestParameter({"0a", "23B", "234L."}) String token) {
175+
assertThrows(ScannerException.class, () -> new Scanner(token).nextToken());
174176
}
175177

176178
@Test
@@ -240,4 +242,69 @@ public void nextTokenBadSymbol() {
240242
assertThrows(ScannerException.class, () -> new Scanner("\\").nextToken());
241243
assertThrows(ScannerException.class, () -> new Scanner("/*").nextToken());
242244
}
245+
246+
@Test
247+
public void nextTokenDoubleConstants() {
248+
Scanner s = new Scanner("1. 1.0 0.1 .1");
249+
Token t = s.nextToken();
250+
assertThat(t.type()).isEqualTo(TokenType.NUMERIC_LITERAL);
251+
assertThat(t.varType()).isEqualTo(Type.DOUBLE);
252+
assertThat(t.value()).isEqualTo("1.");
253+
t = s.nextToken();
254+
assertThat(t.type()).isEqualTo(TokenType.NUMERIC_LITERAL);
255+
assertThat(t.varType()).isEqualTo(Type.DOUBLE);
256+
assertThat(t.value()).isEqualTo("1.0");
257+
t = s.nextToken();
258+
assertThat(t.type()).isEqualTo(TokenType.NUMERIC_LITERAL);
259+
assertThat(t.varType()).isEqualTo(Type.DOUBLE);
260+
assertThat(t.value()).isEqualTo("0.1");
261+
t = s.nextToken();
262+
assertThat(t.type()).isEqualTo(TokenType.NUMERIC_LITERAL);
263+
assertThat(t.varType()).isEqualTo(Type.DOUBLE);
264+
assertThat(t.value()).isEqualTo(".1");
265+
assertThat(s.nextToken().type()).isEqualTo(TokenType.EOF);
266+
}
267+
268+
@Test
269+
public void nextTokenDoubleConstantsWeird() {
270+
Scanner s = new Scanner(".00004 00.000005");
271+
Token t = s.nextToken();
272+
assertThat(t.type()).isEqualTo(TokenType.NUMERIC_LITERAL);
273+
assertThat(t.varType()).isEqualTo(Type.DOUBLE);
274+
assertThat(t.value()).isEqualTo(".00004");
275+
t = s.nextToken();
276+
assertThat(t.type()).isEqualTo(TokenType.NUMERIC_LITERAL);
277+
assertThat(t.varType()).isEqualTo(Type.DOUBLE);
278+
assertThat(t.value()).isEqualTo("00.000005");
279+
t = s.nextToken();
280+
assertThat(s.nextToken().type()).isEqualTo(TokenType.EOF);
281+
}
282+
283+
@Test
284+
public void nextTokenDoubleConstantsWithExp() {
285+
Scanner s = new Scanner("1E0 1.0E0 0.1E+1 .1e-123");
286+
Token t = s.nextToken();
287+
assertThat(t.type()).isEqualTo(TokenType.NUMERIC_LITERAL);
288+
assertThat(t.varType()).isEqualTo(Type.DOUBLE);
289+
assertThat(t.value()).isEqualTo("1E0");
290+
t = s.nextToken();
291+
assertThat(t.type()).isEqualTo(TokenType.NUMERIC_LITERAL);
292+
assertThat(t.varType()).isEqualTo(Type.DOUBLE);
293+
assertThat(t.value()).isEqualTo("1.0E0");
294+
t = s.nextToken();
295+
assertThat(t.type()).isEqualTo(TokenType.NUMERIC_LITERAL);
296+
assertThat(t.varType()).isEqualTo(Type.DOUBLE);
297+
assertThat(t.value()).isEqualTo("0.1E+1");
298+
t = s.nextToken();
299+
assertThat(t.type()).isEqualTo(TokenType.NUMERIC_LITERAL);
300+
assertThat(t.varType()).isEqualTo(Type.DOUBLE);
301+
assertThat(t.value()).isEqualTo(".1e-123");
302+
assertThat(s.nextToken().type()).isEqualTo(TokenType.EOF);
303+
}
304+
305+
@Test
306+
public void nextTokenBadFloats(
307+
@TestParameter({"1.e-10x", "2._", "1E2x", "1.0e10.0"}) String token) {
308+
assertThrows(ScannerException.class, () -> new Scanner(token).nextToken());
309+
}
243310
}

0 commit comments

Comments
 (0)