*
*/
public class Scanner {
+ static boolean defaultAllowNumberSuffix = false;
+
+ /**
+ * @return the defaultAllowNumberSuffix
+ */
+ public static boolean getDefaultAllowNumberSuffix() {
+ return Scanner.defaultAllowNumberSuffix;
+ }
+
+ /**
+ * @param defaultAllowNumberSuffix
+ * the defaultAllowNumberSuffix to set
+ */
+ public static void setDefaultAllowNumberSuffix(boolean defaultAllowNumberSuffix) {
+ Scanner.defaultAllowNumberSuffix = defaultAllowNumberSuffix;
+ }
+
+ /**
+ * Scans a number at the begin of a string.
+ *
+ * @param text
+ * text starting (or not) with a number. White spaces will be
+ * ignored
+ * @param defaultValue
+ * the return value if the text does not start with a number
+ * @return <i>defaultValue</i>: no number found at the begin of the text<br>
+ * otherwise: the value of the number
+ */
+ public static long stringToLong(String text, long defaultValue) {
+ final Scanner scanner = new Scanner(text);
+ Token token;
+ long rc = defaultValue;
+ try {
+ token = scanner.nextNotSpaceToken();
+ if (token.isType(TokenType.INTEGER)) {
+ rc = token.getLongValue();
+ }
+ } catch (final ParserException e) {
+ }
+ return rc;
+ }
+
int position = 0;
int lineNo = 1;
String text;
Token[] tokens = new Token[3];
int indexToken = 0;
+ boolean allowNumberSuffix = Scanner.defaultAllowNumberSuffix;
Token lastToken = null;
+
StringBuilder string = new StringBuilder(8096);
/**
return rc;
}
+ /**
+ * @return the allowNumberSuffix
+ */
+ public boolean isAllowNumberSuffix() {
+ return this.allowNumberSuffix;
+ }
+
/**
* Returns the next token which is not a SPACE token.
*
}
token.setSpace(this.text.substring(tokenStart, this.position));
} else if (Character.isDigit(cc)) {
- long value = cc - '0';
- long digit;
- if (this.position < this.text.length() && (cc = this.text.charAt(this.position)) == 'x' || cc == 'X') {
- ++this.position;
- while (this.position < this.text.length()
- && (Character.isDigit(cc = this.text.charAt(this.position)) || cc >= 'A' && cc <= 'F'
- || cc >= 'a' && cc <= 'f')) {
- this.position++;
- digit = cc <= '9' ? cc - '0' : cc <= 'F' ? cc - 'A' + 10 : cc - 'a' + 10;
- value = 16 * value + digit;
- }
- } else {
- while (this.position < this.text.length()
- && Character.isDigit(cc = this.text.charAt(this.position))) {
- this.position++;
- value = 10 * value + cc - '0';
- }
- }
- token.setLong(value);
+ token.setLong(scanNumber(cc));
} else if (cc == '\'' || cc == '"') {
this.string.setLength(0);
final char delimiter = cc;
this.lineNo = 1;
return this;
}
+
+ /**
+ * Handles the scan of a number.
+ *
+ * <pre>
+ * Syntax:
+ * <number> ::= <digit><suffix>
+ * <digits ::= '0' 'x' { <hex_digit> }+ | { <decimal_digits> }+
+ * <suffix> ::= <unit> | <unit> 'i'
+ * <decimal_digit> ::= '0' | .. | '9'
+ * <hex_digit> ::= <decimal_digit> | 'a' | .. | 'f'
+ * <unit> ::= 'k' | 'm' | 'g' | 't'
+ * </pre>
+ *
+ * @param cc
+ * first digit
+ * @return the number
+ */
+ private long scanNumber(char cc) {
+ long value = cc - '0';
+ long digit;
+ if (this.position < this.text.length() && (cc = this.text.charAt(this.position)) == 'x' || cc == 'X') {
+ ++this.position;
+ while (this.position < this.text.length() && (Character.isDigit(cc = this.text.charAt(this.position))
+ || cc >= 'A' && cc <= 'F' || cc >= 'a' && cc <= 'f')) {
+ this.position++;
+ digit = cc <= '9' ? cc - '0' : cc <= 'F' ? cc - 'A' + 10 : cc - 'a' + 10;
+ value = 16 * value + digit;
+ }
+ } else {
+ while (this.position < this.text.length() && Character.isDigit(cc = this.text.charAt(this.position))) {
+ this.position++;
+ value = 10 * value + cc - '0';
+ }
+ }
+ if (this.allowNumberSuffix && (cc == 'k' || cc == 'K' || cc == 'm' || cc == 'M' || cc == 'g' || cc == 'G'
+ || cc == 't' || cc == 'T') && this.position < this.text.length()) {
+ ++this.position;
+ final int len = this.text.length();
+ char cc2 = '\0';
+ if (this.position < len) {
+ cc2 = this.text.charAt(this.position);
+ }
+ final boolean binary = cc2 == 'i' || cc2 == 'I';
+ if (binary) {
+ ++this.position;
+ }
+ switch (cc) {
+ case 'k':
+ case 'K':
+ value *= binary ? 1024L : 1000;
+ break;
+ case 'm':
+ case 'M':
+ value *= binary ? 1024L * 1024 : 1000L * 1000;
+ break;
+ case 'g':
+ case 'G':
+ value *= binary ? 1024L * 1024 * 1024 : 1000L * 1000 * 1000;
+ break;
+ case 't':
+ case 'T':
+ value *= binary ? 1024L * 1024 * 1024 * 1024 : 1000L * 1000 * 1000 * 1000;
+ break;
+ }
+ }
+
+ return value;
+ }
+
+ /**
+ * @param allowNumberSuffix
+ * the allowNumberSuffix to set
+ */
+ public void setAllowNumberSuffix(boolean allowNumberSuffix) {
+ this.allowNumberSuffix = allowNumberSuffix;
+ }
}
import org.testng.Assert;
import org.testng.annotations.Test;
-import de.republib.expr.ParserException;
-import de.republib.expr.Scanner;
-import de.republib.expr.Token;
-import de.republib.expr.TokenType;
-
public class ScannerTest {
@Test
public void shouldConstruct() {
Assert.assertEquals(token.getLongValue(), 0xABCDEF0L);
}
+ @Test
+ public void shouldScanNumberSuffix() throws ParserException {
+ // decimal suffixes:
+ final Scanner scanner = new Scanner("12k ix");
+ scanner.setAllowNumberSuffix(true);
+ Assert.assertEquals(scanner.nextToken().getLongValue(), 12 * 1000L);
+ Assert.assertEquals(scanner.reset("123457890K").nextToken().getLongValue(), 123457890L * 1000);
+
+ Assert.assertEquals(scanner.reset("9876m").nextToken().getLongValue(), 9876L * 1000 * 1000);
+ Assert.assertEquals(scanner.reset("04711M").nextToken().getLongValue(), 4711L * 1000 * 1000);
+
+ Assert.assertEquals(scanner.reset("3g").nextToken().getLongValue(), 3L * 1000 * 1000 * 1000);
+ Assert.assertEquals(scanner.reset("9G").nextToken().getLongValue(), 9L * 1000 * 1000 * 1000);
+
+ Assert.assertEquals(scanner.reset("17t").nextToken().getLongValue(), 17L * 1000 * 1000 * 1000 * 1000);
+ Assert.assertEquals(scanner.reset("73T").nextToken().getLongValue(), 73L * 1000 * 1000 * 1000 * 1000);
+
+ // binary suffixes:
+ Assert.assertEquals(scanner.reset("0x23ki").nextToken().getLongValue(), 0x23L * 1024L);
+ Assert.assertEquals(scanner.reset("0x123457890KI").nextToken().getLongValue(), 0x123457890L * 1024);
+
+ Assert.assertEquals(scanner.reset("0x9876mi").nextToken().getLongValue(), 0x9876L * 1024 * 1024);
+ Assert.assertEquals(scanner.reset("0x04711MI").nextToken().getLongValue(), 0x4711L * 1024 * 1024);
+
+ Assert.assertEquals(scanner.reset("0x30gi").nextToken().getLongValue(), 0x30L * 1024 * 1024 * 1024);
+ Assert.assertEquals(scanner.reset("0x90GI").nextToken().getLongValue(), 0x90L * 1024 * 1024 * 1024);
+
+ Assert.assertEquals(scanner.reset("0x17ti").nextToken().getLongValue(), 0x17L * 1024 * 1024 * 1024 * 1024);
+ Assert.assertEquals(scanner.reset("0x73TI").nextToken().getLongValue(), 0x73L * 1024 * 1024 * 1024 * 1024);
+
+ // token behind the suffix:
+ Assert.assertEquals(scanner.reset("123k+").nextToken().getLongValue(), 123L * 1000);
+ Assert.assertTrue(scanner.nextToken().isOp(OpCode.PLUS));
+
+ Assert.assertEquals(scanner.reset("123kI*").nextToken().getLongValue(), 123L * 1024);
+ Assert.assertTrue(scanner.nextToken().isOp(OpCode.TIMES));
+ }
+
@Test
public void shouldScanOp() throws ParserException {
final Scanner scanner = new Scanner("+ - * / % ** ( )");
Assert.assertEquals(token.getDelimiter(), '\'');
}
+ @Test
+ public void shouldWorkStringToLong() {
+ Assert.assertEquals(Scanner.stringToLong(" \t123abc", -1), 123L);
+ final boolean oldValue = Scanner.getDefaultAllowNumberSuffix();
+ // suffix recognition is not switched on
+ Scanner.setDefaultAllowNumberSuffix(false);
+ Assert.assertEquals(Scanner.stringToLong("0x44a2k", -1), 0x44a2L);
+ // suffix recognition is switched on
+ Scanner.setDefaultAllowNumberSuffix(true);
+ Assert.assertEquals(Scanner.stringToLong("0x44a2k", -1), 0x44a2L * 1000);
+ Scanner.setDefaultAllowNumberSuffix(oldValue);
+ }
}