summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordibyendumajumdar <mobile@majumdar.org.uk>2023-09-07 19:33:52 +0100
committerdibyendumajumdar <mobile@majumdar.org.uk>2023-09-07 19:33:52 +0100
commit85e5d41217d27fd481a21d17f061d31dc120873b (patch)
tree96e3fe2d316cf5a8fd78b6734054c23d3251cb54
parent867405ea40d3c8acd93e5e0e6030e8ad96eb2470 (diff)
downloadSimple-85e5d41217d27fd481a21d17f061d31dc120873b.tar.gz
Simple-85e5d41217d27fd481a21d17f061d31dc120873b.zip
Removed all external code.
Removed the common chapter. Reimplemented Lexer. Revised the list of chapters to something more accurate.
-rw-r--r--README.md36
-rw-r--r--chapter01/pom.xml8
-rw-r--r--chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java18
-rw-r--r--chapter01/src/main/java/com/seaofnodes/simple/parser/CompilerException.java (renamed from common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/CompilerException.java)13
-rw-r--r--chapter01/src/main/java/com/seaofnodes/simple/parser/Lexer.java151
-rw-r--r--chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java153
-rw-r--r--chapter01/src/main/java/com/seaofnodes/simple/parser/Token.java63
-rw-r--r--chapter01/src/test/java/com/seaofnodes/simple/parser/TestParser.java (renamed from chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java)12
-rw-r--r--common/README.md7
-rw-r--r--common/pom.xml14
-rw-r--r--common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/ErrorListener.java281
-rw-r--r--common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/Lexer.java3050
-rw-r--r--common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/Severity.java36
-rw-r--r--common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/Source.java826
-rw-r--r--common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/Token.java877
-rw-r--r--common/src/main/java/com/seaofnodes/util/Ary.java367
-rw-r--r--common/src/main/java/com/seaofnodes/util/AryInt.java203
-rw-r--r--common/src/main/java/com/seaofnodes/util/Handy.java1778
-rw-r--r--common/src/main/java/com/seaofnodes/util/Hash.java299
-rw-r--r--common/src/main/java/com/seaofnodes/util/PackedInteger.java1360
-rw-r--r--common/src/main/java/com/seaofnodes/util/SB.java80
-rw-r--r--common/src/main/java/com/seaofnodes/util/Util.java151
-rw-r--r--common/src/main/java/com/seaofnodes/util/VBitSet.java27
-rw-r--r--common/src/test/java/com/seaofnodes/simple/lexer/TestLexer.java37
-rw-r--r--pom.xml1
25 files changed, 335 insertions, 9513 deletions
diff --git a/README.md b/README.md
index 78db3c08..8acd8585 100644
--- a/README.md
+++ b/README.md
@@ -19,21 +19,29 @@ and so the backend will probably start with levering C or Java.
The following is a rough plan, subject to change.
-The Common chapter defines code that will be reused throughout.
-
-The remaining chapters will each be self-sufficient and complete; in the sense that each chapter will fully implement
-a subset of the simple language. Each chapter will also include a detailed commentary on relevant aspects of the
+Each chapter will be self-sufficient and complete; in the sense that each chapter will fully implement
+a subset of the simple language, and include everything that was created in the previous chapter.
+Each chapter will also include a detailed commentary on relevant aspects of the
Sea Of Nodes intermediate representation.
The simple language will be a small subset of C.
-* [Common](common/README.md): Common utilities including a lexer.
-* [Language](language/README.md): Simple language definition.
-* [Chapter 1](chapter01/README.md): Simple control flow statements, `if` and `while`. Variables of `int` type. Simple
- arithmetic such as addition, subtraction, multiplication, division. Non-zero values will be truthy.
-* Chapter 2: Function calls.
-* Chapter 3: Comparison operators.
-* Chapter 4: Boolean operators `&&`, `||` and `!`.
-* Chapter 5: `float` and one dimensional static length array type.
-* Chapter 6: Visualize Sea of Nodes IR.
-* Chapter 7: Optimizations - tbc \ No newline at end of file
+* [Language](language/README.md): Simple language grammar.
+* [Chapter 1](chapter01/README.md): Empty script - i.e. empty function that takes no args and returns nothing, and has empty body.
+* Chapter 2: Simple binary arithmetic such as addition, subtraction, multiplication, division with constants. Peephole optimization / simple constant folding.
+* Chapter 3: Graphical Visualization of Sea of Nodes IR.
+* Chapter 4: Local variables, and assignment statements. Read on RHS, SSA, more peephole optimization if local is a constant.
+* Chapter 5: Binary and Comparison operators involving constants and variables. Non-zero values will be truthy.
+* Chapter 6: `if` statement. CFG construction, peephole optimization around dead control flow.
+* Chapter 7: `while` statement.
+* Chapter 8: Global Value Numbering.
+* Chapter 9: Function calls.
+* Chapter 10: Boolean operators `&&`, `||` and `!` including short circuit.
+* Chapter 11: `float` type.
+* Chapter 12: Memory effects: general memory edges in SSA. Peephole optimization around load-after-store/store-after-store.
+* Chapter 13: Equivalence class aliasing, fine grained peephole optimizations. Free ptr-to analysis in SoN; but does not handle aliasing in arrays.
+* Chapter 14: One dimensional static length array type. Array load/store.
+* Chapter 15: Global Code Motion - unwind SoN to CFG. Scheduling.
+* Chapter 16: Instruction selection, BURS.
+* Chapter 17: Backend register allocation.
+* Chapter 18: Garbage Collection. \ No newline at end of file
diff --git a/chapter01/pom.xml b/chapter01/pom.xml
index 842e67e9..2204bde8 100644
--- a/chapter01/pom.xml
+++ b/chapter01/pom.xml
@@ -11,12 +11,4 @@
<artifactId>chapter01</artifactId>
<packaging>jar</packaging>
<name>Chapter 1</name>
-
- <dependencies>
- <dependency>
- <groupId>com.seaofnodes</groupId>
- <artifactId>common</artifactId>
- <version>1.0</version>
- </dependency>
- </dependencies>
</project> \ No newline at end of file
diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java
index 51c7b7ac..c271d4b6 100644
--- a/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java
+++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java
@@ -16,8 +16,6 @@
*/
package com.seaofnodes.simple.parser;
-import com.seaofnodes.simple.lexer.ecstasy.Token;
-
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -47,7 +45,7 @@ public class Ast {
public Identifier(Token op) {
super(op);
- name = op.getValueText();
+ name = op.str;
}
@Override
@@ -64,7 +62,7 @@ public class Ast {
public Symbol(Token op) {
super(op);
- name = op.getValueText();
+ name = op.str;
}
@Override
@@ -85,7 +83,7 @@ public class Ast {
@Override
public String toString() {
- return "(" + expr1 + op.getValueText() + expr2 + ")";
+ return "(" + expr1 + op.toString() + expr2 + ")";
}
}
@@ -99,7 +97,7 @@ public class Ast {
@Override
public String toString() {
- return "(" + op.getValueText() + "(" + expr + "))";
+ return "(" + op.toString() + "(" + expr + "))";
}
}
@@ -110,7 +108,7 @@ public class Ast {
@Override
public String toString() {
- return op.getValueText();
+ return op.toString();
}
}
@@ -174,15 +172,15 @@ public class Ast {
}
public static class Type extends Ast {
- public final Token.Id typeId;
+ public final String typeId;
- public Type(Token.Id typeId) {
+ public Type(String typeId) {
this.typeId = typeId;
}
@Override
public String toString() {
- return typeId.TEXT;
+ return typeId;
}
}
diff --git a/common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/CompilerException.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/CompilerException.java
index c23d3672..a62fa034 100644
--- a/common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/CompilerException.java
+++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/CompilerException.java
@@ -1,21 +1,20 @@
/*
* DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
+ * <p>
* Contributor(s):
- *
- * The Initial Developer of the Original Software is
- * The Ecstasy Project (https://github.com/xtclang/).
- *
+ * <p>
+ * The Initial Developer of the Original Software is Dibyendu Majumdar.
+ * <p>
* This file is part of the Sea Of Nodes Simple Programming language
* implementation. See https://github.com/SeaOfNodes/Simple
- *
+ * <p>
* The contents of this file are subject to the terms of the
* Apache License Version 2 (the "APL"). You may not use this
* file except in compliance with the License. A copy of the
* APL may be obtained from:
* http://www.apache.org/licenses/LICENSE-2.0
*/
-package com.seaofnodes.simple.lexer.ecstasy;
+package com.seaofnodes.simple.parser;
public class CompilerException extends RuntimeException {
public CompilerException(String message) {
diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Lexer.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Lexer.java
new file mode 100644
index 00000000..fbe8c239
--- /dev/null
+++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/Lexer.java
@@ -0,0 +1,151 @@
+/*
+ * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ * <p>
+ * Contributor(s):
+ * <p>
+ * The Initial Developer of the Original Software is Dibyendu Majumdar.
+ * <p>
+ * This file is part of the Sea Of Nodes Simple Programming language
+ * implementation. See https://github.com/SeaOfNodes/Simple
+ * <p>
+ * The contents of this file are subject to the terms of the
+ * Apache License Version 2 (the "APL"). You may not use this
+ * file except in compliance with the License. A copy of the
+ * APL may be obtained from:
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+package com.seaofnodes.simple.parser;
+
+import java.text.NumberFormat;
+import java.text.ParseException;
+
+public class Lexer {
+
+ private final char[] input;
+ /**
+ * Tracks current position in input buffer
+ */
+ private int position = 0;
+ private char peek = ' ';
+
+ private final NumberFormat numberFormat;
+
+ public Lexer(String source) {
+ input = source.toCharArray();
+ numberFormat = NumberFormat.getInstance();
+ numberFormat.setGroupingUsed(false);
+ }
+
+ private boolean isEOF() {
+ return position >= input.length;
+ }
+
+ private char advance() {
+ if (isEOF())
+ return 0; // Special value that causes parsing to terminate
+ return input[position++];
+ }
+
+ private boolean readch() {
+ peek = advance();
+ return true;
+ }
+
+ private boolean readch(char c) {
+ if (readch() && peek != c)
+ return false;
+ peek = ' ';
+ return true;
+ }
+
+ /**
+ * Note that EOF is not whitespace!
+ */
+ private boolean isWhiteSpace(char c) {
+ return c == ' ' ||
+ c == '\t' ||
+ c == '\n' ||
+ c == '\r';
+ }
+
+ private void skipWhitespace() {
+ while (isWhiteSpace(peek))
+ readch();
+ }
+
+ /**
+ * Parses number in format nnn[.nnn]
+ * where n is a digit
+ */
+ private Token parseNumber() {
+ assert Character.isDigit(peek);
+ StringBuilder sb = new StringBuilder();
+ sb.append(peek);
+ while (readch() && Character.isDigit(peek))
+ sb.append(peek);
+ if (peek == '.') {
+ sb.append(peek);
+ while (readch() && Character.isDigit(peek))
+ sb.append(peek);
+ }
+ String str = sb.toString();
+ Number number = parseNumber(str);
+ return Token.newNum(number, str);
+ }
+
+ private Number parseNumber(String str) {
+ try {
+ return numberFormat.parse(str);
+ } catch (ParseException e) {
+ throw new CompilerException("Failed to parse number " + str, e);
+ }
+ }
+
+ private boolean isIdentifierStart(char ch) {
+ return Character.isAlphabetic(ch) || ch == '_';
+ }
+
+ private boolean isIdentifierLetter(char ch) {
+ return Character.isLetterOrDigit(ch) || ch == '_';
+ }
+
+ private Token parseIdentifier() {
+ assert isIdentifierStart(peek);
+ StringBuilder sb = new StringBuilder();
+ sb.append(peek);
+ while (readch() && isIdentifierLetter(peek))
+ sb.append(peek);
+ return Token.newIdent(sb.toString());
+ }
+
+ public Token scan() {
+ skipWhitespace();
+ switch (peek) {
+ case 0:
+ return Token.EOF;
+ case '&':
+ return readch('&') ? Token.newPunct("&&") : Token.newPunct("&");
+ case '|':
+ return readch('|') ? Token.newPunct("||") : Token.newPunct("|");
+ case '=':
+ return readch('=') ? Token.newPunct("==") : Token.newPunct("=");
+ case '<':
+ return readch('=') ? Token.newPunct("<=") : Token.newPunct("<");
+ case '>':
+ return readch('=') ? Token.newPunct(">=") : Token.newPunct(">");
+ default: {
+ return scanOthers();
+ }
+ }
+ }
+
+ private Token scanOthers() {
+ if (Character.isDigit(peek))
+ return parseNumber();
+ else if (isIdentifierLetter(peek))
+ return parseIdentifier();
+ Token token = Token.newPunct(Character.toString(peek));
+ peek = ' ';
+ return token;
+ }
+}
diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java
index fe224105..7e333b71 100644
--- a/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java
+++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java
@@ -16,17 +16,13 @@
*/
package com.seaofnodes.simple.parser;
-import com.seaofnodes.simple.lexer.ecstasy.CompilerException;
-import com.seaofnodes.simple.lexer.ecstasy.Lexer;
-import com.seaofnodes.simple.lexer.ecstasy.Token;
-import com.seaofnodes.simple.lexer.ecstasy.Severity;
-
public class Parser {
private Token currentToken;
/**
* Parse and return a top level Ast
* At present this is an Ast.Block
+ *
* @see Ast.Block
*/
public Ast parse(Lexer lexer) {
@@ -34,37 +30,41 @@ public class Parser {
return parseProgram(lexer);
}
- private Token nextOrEof(Lexer lexer) {
- if (lexer.hasNext())
- return lexer.next();
- return new Token(currentToken.getStartPosition(), currentToken.getEndPosition(), Token.Id.EOF);
+ private void nextToken(Lexer lexer) {
+ currentToken = lexer.scan();
}
- private void nextToken(Lexer lexer) {
- currentToken = nextOrEof(lexer);
- // skip comments
- while (currentToken.isComment()) {
- currentToken = nextOrEof(lexer);
- }
- if (currentToken.isContextSensitive() &&
- currentToken.getId() == Token.Id.IDENTIFIER) {
- currentToken = currentToken.convertToKeyword();
+ private void error(Token t, String errorMessage) {
+ throw new CompilerException(errorMessage + ": " + t.toString());
+ }
+
+ private void matchKind(Lexer lexer, Token.Kind kind) {
+ if (currentToken.kind == kind) {
+ nextToken(lexer);
+ } else {
+ error(currentToken, "syntax error, expected " + kind);
}
}
- private void error(Lexer lexer, Token t, String errorMessage) {
- lexer.log(Severity.ERROR, errorMessage, t.getStartPosition(), t.getEndPosition());
- throw new CompilerException(errorMessage + ": " + t.toDebugString());
+ private void matchPunctuation(Lexer lexer, String value) {
+ if (currentToken.kind == Token.Kind.PUNCT && isToken(currentToken, value)) {
+ nextToken(lexer);
+ } else {
+ error(currentToken, "syntax error, expected " + value + " got " + currentToken.str);
+ }
}
- private void match(Lexer lexer, Token.Id tag) {
- if (currentToken.getId() == tag) {
+ private void matchIdentifier(Lexer lexer, String identifier) {
+ if (currentToken.kind == Token.Kind.IDENT && isToken(currentToken, identifier)) {
nextToken(lexer);
} else {
- error(lexer, currentToken, "syntax error, expected " + tag);
+ error(currentToken, "syntax error, expected " + identifier);
}
}
+ private boolean isToken(Token token, String value) {
+ return token.str.equals(value);
+ }
private Ast.Statement parseProgram(Lexer lexer) {
var block = new Ast.Block();
@@ -74,27 +74,34 @@ public class Parser {
}
private boolean isTypeName(Token tok) {
- return switch (tok.getId()) {
- case TYPE_INT, TYPE_CHAR, TYPE_FLOAT -> true;
- default -> false;
- };
+ return tok.kind == Token.Kind.IDENT &&
+ (isToken(tok, "int") ||
+ isToken(tok, "char") ||
+ isToken(tok, "float"));
}
private void parseDeclarations(Lexer lexer, Ast.Block block) {
while (isTypeName(currentToken)) {
- var type = new Ast.Type(currentToken.getId());
+ var type = new Ast.Type(currentToken.str);
nextToken(lexer);
var tok = currentToken;
- match(lexer, Token.Id.IDENTIFIER);
- match(lexer, Token.Id.SEMICOLON);
+ matchKind(lexer, Token.Kind.IDENT);
+ matchPunctuation(lexer, ";");
var identifier = new Ast.Identifier(tok);
block.stmtList.add(new Ast.Declare(type, identifier));
}
}
+ private boolean isEOF(Token tok) {
+ return tok.kind == Token.Kind.EOZ;
+ }
+
+ private boolean isPunct(Token tok, String str) {
+ return tok.kind == Token.Kind.PUNCT && isToken(tok, str);
+ }
+
private void parseStatements(Lexer lexer, Ast.Block block) {
- while (currentToken.getId() != Token.Id.EOF &&
- currentToken.getId() != Token.Id.R_CURLY) {
+ while (!isEOF(currentToken) && !isPunct(currentToken, "}")) {
block.stmtList.add(parseStatement(lexer));
}
}
@@ -104,33 +111,33 @@ public class Parser {
Ast.Statement s1;
Ast.Statement s2;
- switch (currentToken.getId()) {
- case IF -> {
- match(lexer, Token.Id.IF);
- match(lexer, Token.Id.L_PAREN);
+ switch (currentToken.str) {
+ case "if" -> {
+ matchIdentifier(lexer, "if");
+ matchPunctuation(lexer, "(");
x = parseBool(lexer);
- match(lexer, Token.Id.R_PAREN);
+ matchPunctuation(lexer, ")");
s1 = parseStatement(lexer);
- if (currentToken.getId() != Token.Id.ELSE) {
+ if (!isToken(currentToken, "else")) {
return new Ast.IfElse(x, s1, null);
}
s2 = parseStatement(lexer);
return new Ast.IfElse(x, s1, s2);
}
- case WHILE -> {
- match(lexer, Token.Id.WHILE);
- match(lexer, Token.Id.L_PAREN);
+ case "while" -> {
+ matchIdentifier(lexer, "while");
+ matchPunctuation(lexer, "(");
x = parseBool(lexer);
- match(lexer, Token.Id.R_PAREN);
+ matchPunctuation(lexer, ")");
s1 = parseStatement(lexer);
return new Ast.While(x, s1);
}
- case BREAK -> {
- match(lexer, Token.Id.BREAK);
- match(lexer, Token.Id.SEMICOLON);
+ case "break" -> {
+ matchIdentifier(lexer, "break");
+ matchPunctuation(lexer, ";");
return new Ast.Break();
}
- case L_CURLY -> {
+ case "{" -> {
return parseBlock(lexer);
}
default -> {
@@ -140,27 +147,27 @@ public class Parser {
}
private Ast.Statement parseBlock(Lexer lexer) {
- match(lexer, Token.Id.L_CURLY);
+ matchPunctuation(lexer, "{");
var block = new Ast.Block();
parseDeclarations(lexer, block);
parseStatements(lexer, block);
- match(lexer, Token.Id.R_CURLY);
+ matchPunctuation(lexer, "}");
return block;
}
private Ast.Statement parseAssign(Lexer lexer) {
Token tok = currentToken;
- match(lexer, Token.Id.IDENTIFIER);
+ matchKind(lexer, Token.Kind.IDENT);
Ast.Identifier identifier = new Ast.Identifier(tok);
- match(lexer, Token.Id.ASN);
+ matchPunctuation(lexer, "=");
var s = new Ast.Assign(identifier, parseBool(lexer));
- match(lexer, Token.Id.SEMICOLON);
+ matchPunctuation(lexer, ";");
return s;
}
private Ast.Expr parseBool(Lexer lexer) {
var x = parseAnd(lexer);
- while (currentToken.getId() == Token.Id.COND_OR) {
+ while (isToken(currentToken, "||")) {
var tok = currentToken;
nextToken(lexer);
x = new Ast.Binary(tok, x, parseAnd(lexer));
@@ -170,7 +177,7 @@ public class Parser {
private Ast.Expr parseAnd(Lexer lexer) {
var x = parseRelational(lexer);
- while (currentToken.getId() == Token.Id.COND_AND) {
+ while (isToken(currentToken, "&&")) {
var tok = currentToken;
nextToken(lexer);
x = new Ast.Binary(tok, x, parseRelational(lexer));
@@ -180,12 +187,12 @@ public class Parser {
private Ast.Expr parseRelational(Lexer lexer) {
var x = parseAddition(lexer);
- while (currentToken.getId() == Token.Id.COMP_EQ ||
- currentToken.getId() == Token.Id.COMP_NEQ ||
- currentToken.getId() == Token.Id.COMP_LT ||
- currentToken.getId() == Token.Id.COMP_GT ||
- currentToken.getId() == Token.Id.COMP_GTEQ ||
- currentToken.getId() == Token.Id.COMP_LTEQ) {
+ while (isToken(currentToken, "==") ||
+ isToken(currentToken, "!=") ||
+ isToken(currentToken, "<=") ||
+ isToken(currentToken, "<") ||
+ isToken(currentToken, ">") ||
+ isToken(currentToken, ">=")) {
var tok = currentToken;
nextToken(lexer);
x = new Ast.Binary(tok, x, parseAddition(lexer));
@@ -195,8 +202,8 @@ public class Parser {
private Ast.Expr parseAddition(Lexer lexer) {
var x = parseMultiplication(lexer);
- while (currentToken.getId() == Token.Id.ADD ||
- currentToken.getId() == Token.Id.SUB) {
+ while (isToken(currentToken, "-") ||
+ isToken(currentToken, "+")) {
var tok = currentToken;
nextToken(lexer);
x = new Ast.Binary(tok, x, parseMultiplication(lexer));
@@ -206,8 +213,8 @@ public class Parser {
private Ast.Expr parseMultiplication(Lexer lexer) {
var x = parseUnary(lexer);
- while (currentToken.getId() == Token.Id.MUL ||
- currentToken.getId() == Token.Id.DIV) {
+ while (isToken(currentToken, "*") ||
+ isToken(currentToken, "/")) {
var tok = currentToken;
nextToken(lexer);
x = new Ast.Binary(tok, x, parseUnary(lexer));
@@ -216,8 +223,8 @@ public class Parser {
}
private Ast.Expr parseUnary(Lexer lexer) {
- if (currentToken.getId() == Token.Id.SUB ||
- currentToken.getId() == Token.Id.NOT) {
+ if (isToken(currentToken, "-") ||
+ isToken(currentToken, "!")) {
var tok = currentToken;
nextToken(lexer);
return new Ast.Unary(tok, parseUnary(lexer));
@@ -227,25 +234,27 @@ public class Parser {
}
private Ast.Expr parsePrimary(Lexer lexer) {
- switch (currentToken.getId()) {
- case L_PAREN -> {
+ switch (currentToken.kind) {
+ case PUNCT -> {
+ /* Nested expression */
+ matchPunctuation(lexer, "(");
nextToken(lexer);
var x = parseBool(lexer);
- match(lexer, Token.Id.R_PAREN);
+ matchPunctuation(lexer, ")");
return x;
}
- case LIT_INT -> {
+ case NUM -> {
var x = new Ast.Constant(currentToken);
nextToken(lexer);
return x;
}
- case IDENTIFIER -> {
+ case IDENT -> {
var x = new Ast.Symbol(currentToken);
nextToken(lexer);
return x;
}
default -> {
- error(lexer, currentToken, "syntax error, expected nested expr, integer value or variable");
+ error(currentToken, "syntax error, expected nested expr, integer value or variable");
return null;
}
}
diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Token.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Token.java
new file mode 100644
index 00000000..d45bef5a
--- /dev/null
+++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/Token.java
@@ -0,0 +1,63 @@
+/*
+ * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ * <p>
+ * Contributor(s):
+ * <p>
+ * The Initial Developer of the Original Software is Dibyendu Majumdar.
+ * <p>
+ * This file is part of the Sea Of Nodes Simple Programming language
+ * implementation. See https://github.com/SeaOfNodes/Simple
+ * <p>
+ * The contents of this file are subject to the terms of the
+ * Apache License Version 2 (the "APL"). You may not use this
+ * file except in compliance with the License. A copy of the
+ * APL may be obtained from:
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+package com.seaofnodes.simple.parser;
+
+public class Token {
+
+ enum Kind {
+ IDENT,
+ NUM,
+ PUNCT,
+ EOZ // Special kind to signal end of file
+ }
+
+ public final Kind kind;
+ /**
+ * String representation of a token - always
+ * populated
+ */
+ public final String str;
+ /**
+ * The parsed number value, only populated for Kind.NUM
+ */
+ public final Number num;
+
+ public Token(Kind kind, String str, Number num) {
+ this.kind = kind;
+ this.str = str;
+ this.num = num;
+ }
+
+ public static Token newIdent(String str) {
+ return new Token(Kind.IDENT, str.intern(), null);
+ }
+ public static Token newNum(Number num, String str) {
+ return new Token(Kind.NUM, str, num);
+ }
+ public static Token newPunct(String str) {
+ return new Token(Kind.PUNCT, str.intern(), null);
+ }
+
+ /**
+ * Special token that indicates that source has been exhausted
+ */
+ public static Token EOF = new Token(Kind.EOZ, "", null);
+
+ public String toString() {
+ return str;
+ }
+}
diff --git a/chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java b/chapter01/src/test/java/com/seaofnodes/simple/parser/TestParser.java
index 3f65e0f9..356420eb 100644
--- a/chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java
+++ b/chapter01/src/test/java/com/seaofnodes/simple/parser/TestParser.java
@@ -13,12 +13,8 @@
* http://www.apache.org/licenses/LICENSE-2.0
*/
-package com.seaofnodes.simple.lexer;
+package com.seaofnodes.simple.parser;
-import com.seaofnodes.simple.lexer.ecstasy.ErrorListener;
-import com.seaofnodes.simple.lexer.ecstasy.Lexer;
-import com.seaofnodes.simple.lexer.ecstasy.Source;
-import com.seaofnodes.simple.parser.Parser;
import org.junit.Assert;
import org.junit.Test;
@@ -28,7 +24,7 @@ public class TestParser {
public void testParseSingleDecl()
{
final String source = "int i;";
- Lexer lexer = new Lexer(new Source(source), new ErrorListener());
+ Lexer lexer = new Lexer(source);
Parser parser = new Parser();
var ast = parser.parse(lexer);
Assert.assertEquals("{int i;}", ast.toString());
@@ -37,7 +33,7 @@ public class TestParser {
@Test
public void testParseTwoDecls() {
final String source = "int i;\nint j;";
- Lexer lexer = new Lexer(new Source(source), new ErrorListener());
+ Lexer lexer = new Lexer(source);
Parser parser = new Parser();
var ast = parser.parse(lexer);
Assert.assertEquals("{int i;int j;}", ast.toString());
@@ -58,7 +54,7 @@ public class TestParser {
if ( i >= j ) break;
}
""";
- Lexer lexer = new Lexer(new Source(source), new ErrorListener());
+ Lexer lexer = new Lexer(source);
Parser parser = new Parser();
var ast = parser.parse(lexer);
Assert.assertEquals("{int i;int j;i=0;j=100;while(1) {i=(i+1);j=(j-1);if((i>=j)) break;}}", ast.toString());
diff --git a/common/README.md b/common/README.md
deleted file mode 100644
index d6a5dbb8..00000000
--- a/common/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# Common Utilities
-
-In this module certain common utilities are defined that
-will be reused throughout the project.
-
-* Lexer - the lexer is based upon the one in Ecstasy Programming Language. Its feature rich enough that we do not
- expect to have to modify it much. \ No newline at end of file
diff --git a/common/pom.xml b/common/pom.xml
deleted file mode 100644
index e70fcf1d..00000000
--- a/common/pom.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>com.seaofnodes</groupId>
- <artifactId>simple</artifactId>
- <version>1.0</version>
- </parent>
- <artifactId>common</artifactId>
- <packaging>jar</packaging>
- <name>Common Stuff</name>
-</project> \ No newline at end of file
diff --git a/common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/ErrorListener.java b/common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/ErrorListener.java
deleted file mode 100644
index 6a29f315..00000000
--- a/common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/ErrorListener.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * The Initial Developer of the Original Software is
- * The Ecstasy Project (https://github.com/xtclang/).
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-package com.seaofnodes.simple.lexer.ecstasy;
-
-import java.text.MessageFormat;
-
-import java.util.Arrays;
-
-import static com.seaofnodes.util.Handy.quotedString;
-
-
-/**
- * A listener for errors being reported about source code, compilation, assembly, or verification of
- * XVM structures.
- */
-public class ErrorListener
- {
- // ----- API -----------------------------------------------------------------------------------
-
- /**
- * Handles the logging of an error that originates in Ecstasy source code.
- *
- * @param severity the severity level of the error; one of
- * {@link Severity#INFO}, {@link Severity#WARNING},
- * {@link Severity#ERROR}, or {@link Severity#FATAL}
- * @param sCode the error code that identifies the error message
- * @param aoParam the parameters for the error message; may be null
- * @param source the source code (optional)
- * @param lPosStart the position in the source where the error was detected
- * @param lPosEnd the position in the source at which the error concluded
- *
- * @return true to attempt to abort the process that reported the error, or
- * false to attempt to continue the process
- */
- public void log(Severity severity, String sCode, Object[] aoParam,
- Source source, long lPosStart, long lPosEnd)
- {
- System.err.println(new ErrorInfo(severity, sCode, aoParam, source, lPosStart, lPosEnd));
- }
-
-
- // ----- inner class: ErrorInfo ----------------------------------------------------------------
-
- /**
- * Represents the information logged for a single error.
- */
- class ErrorInfo
- {
- /**
- * Construct an ErrorInfo object.
- *
- * @param severity the severity level of the error; one of
- * {@link Severity#INFO}, {@link Severity#WARNING,
- * {@link Severity#ERROR}, or {@link Severity#FATAL}
- * @param sCode the error code that identifies the error message
- * @param aoParam the parameters for the error message; may be null
- * @param source the source code
- * @param lPosStart the starting position in the source code
- * @param lPosEnd the ending position in the source code
- */
- public ErrorInfo(Severity severity, String sCode, Object[] aoParam,
- Source source, long lPosStart, long lPosEnd)
- {
- m_severity = severity;
- m_sCode = sCode;
- m_aoParam = aoParam;
- m_source = source;
- m_lPosStart = lPosStart;
- m_lPosEnd = lPosEnd;
- }
-
- /**
- * Construct an ErrorInfo object.
- *
- * @param severity the severity level of the error; one of
- * {@link Severity#INFO}, {@link Severity#WARNING,
- * {@link Severity#ERROR}, or {@link Severity#FATAL}
- * @param sCode the error code that identifies the error message
- * @param aoParam the parameters for the error message; may be null
- */
- public ErrorInfo(Severity severity, String sCode, Object[] aoParam)
- {
- m_severity = severity;
- m_sCode = sCode;
- m_aoParam = aoParam;
- // TODO need to be able to ask the XVM structure for the source & location
- }
-
- /**
- * @return the Severity of the error
- */
- public Severity getSeverity()
- {
- return m_severity;
- }
-
- /**
- * @return the error code
- */
- public String getCode()
- {
- return m_sCode;
- }
-
- /**
- * @return the error message parameters
- */
- public Object[] getParams()
- {
- return m_aoParam;
- }
-
- /**
- * Produce a localized message based on the error code and related parameters.
- *
- * @return a formatted message for display that includes the error code
- */
- public String getMessage()
- {
- return getMessageText();
- }
-
- /**
- * Produce a localized message based on the error code and related parameters.
- *
- * @return a formatted message for display that doesn't include the error code
- */
- public String getMessageText()
- {
- return MessageFormat.format(getCode(), getParams());
- }
-
- /**
- * @return the source code
- */
- public Source getSource()
- {
- return m_source;
- }
-
- /**
- * @return the line number (zero based) at which the error occurred
- */
- public int getLine()
- {
- return Source.calculateLine(m_lPosStart);
- }
-
- /**
- * @return the offset (zero based) at which the error occurred
- */
- public int getOffset()
- {
- return Source.calculateOffset(m_lPosStart);
- }
-
- /**
- * @return the line number (zero based) at which the error concluded
- */
- public int getEndLine()
- {
- return Source.calculateLine(m_lPosEnd);
- }
-
- /**
- * @return the offset (zero based) at which the error concluded
- */
- public int getEndOffset()
- {
- return Source.calculateOffset(m_lPosEnd);
- }
-
- /**
- * @return an ID that allows redundant errors to be filtered out
- */
- public String genUID()
- {
- StringBuilder sb = new StringBuilder();
- sb.append(m_severity.ordinal())
- .append(':')
- .append(m_sCode);
-
- if (m_aoParam != null)
- {
- sb.append('#')
- .append(Arrays.hashCode(m_aoParam));
- }
-
- if (!m_sCode.startsWith("VERIFY"))
- {
- if (m_source != null)
- {
- sb.append(':')
- .append(m_source.getFileName())
- .append(':')
- .append(m_lPosStart)
- .append(':')
- .append(m_lPosStart);
- }
- }
-
- return sb.toString();
- }
-
- @Override
- public String toString()
- {
- StringBuilder sb = new StringBuilder();
-
- // source code location
- if (m_source != null)
- {
- String sFile = m_source.getFileName();
- if (sFile != null)
- {
- sb.append(sFile)
- .append(':').append(getLine() + 1)
- .append(' ');
- }
-
- sb.append("[")
- .append(getLine() + 1)
- .append(':')
- .append(getOffset() + 1);
-
- if (getEndLine() != getLine() || getEndOffset() != getOffset())
- {
- sb.append("..")
- .append(getEndLine() + 1)
- .append(':')
- .append(getEndOffset() + 1);
- }
-
- sb.append("] ");
- }
-
-
- // localized message
- sb.append(getMessage());
-
- // source code snippet
- if (m_source != null && m_lPosStart != m_lPosEnd)
- {
- String sSource = m_source.toString(m_lPosStart, m_lPosEnd);
- if (sSource.length() > 80)
- {
- sSource = sSource.substring(0, 77) + "...";
- }
-
- sb.append(" (")
- .append(quotedString(sSource))
- .append(')');
- }
-
- return sb.toString();
- }
-
- private final Severity m_severity;
- private final String m_sCode;
- private final Object[] m_aoParam;
- private Source m_source;
- private long m_lPosStart;
- private long m_lPosEnd;
- }
-
- } \ No newline at end of file
diff --git a/common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/Lexer.java b/common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/Lexer.java
deleted file mode 100644
index 86a7b77f..00000000
--- a/common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/Lexer.java
+++ /dev/null
@@ -1,3050 +0,0 @@
-/*
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * The Initial Developer of the Original Software is
- * The Ecstasy Project (https://github.com/xtclang/).
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-package com.seaofnodes.simple.lexer.ecstasy;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-
-import java.net.IDN;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-import java.util.function.Consumer;
-
-import com.seaofnodes.simple.lexer.ecstasy.Token.Id;
-import com.seaofnodes.util.PackedInteger;
-
-import static com.seaofnodes.util.Handy.*;
-
-/**
- * An XTC source code parser supporting both demand-based and stream-based
- * parsing.
- */
-public class Lexer
- implements Iterator<Token>
- {
- // ----- constructors --------------------------------------------------------------------------
-
- /**
- * Construct an XTC lexical analyzer.
- *
- * @param source the source to parse
- */
- public Lexer(Source source, ErrorListener errorListener)
- {
- if (source == null)
- {
- throw new IllegalArgumentException("Source required");
- }
- if (errorListener == null)
- {
- throw new IllegalArgumentException("ErrorListener required");
- }
-
- m_source = source;
- m_errorListener = errorListener;
-
- eatWhitespace();
- }
-
- /**
- * @param parent a Lexer that this Lexer can delegate to
- */
- protected Lexer(Lexer parent)
- {
- m_source = parent.m_source;
- m_errorListener = parent.m_errorListener;
- m_fWhitespace = parent.m_fWhitespace;
- }
-
-
- // ----- Iterator methods ----------------------------------------------------------------------
-
- @Override
- public boolean hasNext()
- {
- return m_source.hasNext();
- }
-
- /**
- * Validate the specified identifier.
- *
- * @param sName the identifier
- *
- * @return true iff the identifier is a lexically valid identifier
- */
- public static boolean isValidIdentifier(String sName)
- {
- if (sName == null)
- {
- return false;
- }
-
- int cch = sName.length();
- if (cch == 0)
- {
- return false;
- }
-
- if (!isIdentifierStart(sName.charAt(0)))
- {
- return false;
- }
-
- for (int i = 1; i < cch; ++i)
- {
- if (!isIdentifierPart(sName.charAt(i)))
- {
- return false;
- }
- }
-
- // check if it is a reserved word
- return Id.valueByText(sName) == null;
- }
-
-
- // ----- public API ----------------------------------------------------------------------------
-
- /**
- * Lexically analyze the source, emitting a stream of tokens to the specified consumer.
- *
- * @param consumer the Token Consumer
- */
- public void emit(Consumer<Token> consumer)
- {
- while (m_source.hasNext())
- {
- consumer.accept(next());
- }
- }
-
- /**
- * Create a temporary lexer that provides a stream of tokens as specified.
- *
- * @param atoken the tokens to stream
- *
- * @return a new Lexer
- */
- public Lexer createLexer(Token[] atoken)
- {
- return new Lexer(this)
- {
- int iNext = 0;
-
- @Override
- public boolean hasNext()
- {
- return iNext < atoken.length;
- }
-
- @Override
- public Token next()
- {
- if (hasNext())
- {
- return atoken[iNext++];
- }
- throw new NoSuchElementException();
- }
-
- @Override
- public long mark()
- {
- return iNext;
- }
-
- @Override
- public void restore(long lPos)
- {
- iNext = (int) lPos;
- }
- };
- }
-
-
- // ----- internal ------------------------------------------------------------------------------
-
- /**
- * Validate the specified RFC1035 label.
- *
- * @param sName the RFC1035 label
- *
- * @return true iff the name is a lexically valid RFC1035 label
- */
- public static boolean isValidRFC1035Label(String sName)
- {
- if (sName == null)
- {
- return false;
- }
-
- // internationalization support; see https://tools.ietf.org/html/rfc5894
- // convert the label to an ASCII label
- try
- {
- sName = IDN.toASCII(sName);
- }
- catch (IllegalArgumentException e)
- {
- return false;
- }
-
- int cch = sName.length();
- if (cch == 0 || cch > 63)
- {
- return false;
- }
-
- if (!isAsciiLetter(sName.charAt(0)))
- {
- return false;
- }
-
- for (int i = 1; i < cch; ++i)
- {
- char ch = sName.charAt(i);
- if (!(isAsciiLetter(ch) || isDigit(ch) || i < cch - 1 && ch == '-'))
- {
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Validate the specified module name.
- *
- * @param sName the module name
- *
- * @return true iff the name is a lexically valid qualified module name
- */
- public static boolean isValidQualifiedModule(String sName)
- {
- if (sName == null)
- {
- return false;
- }
-
- String[] asName = parseDelimitedString(sName, '.');
- int cNames = asName.length;
- if (cNames < 1)
- {
- return false;
- }
-
- // the first simple name must be a valid identifier
- if (!isValidIdentifier(asName[0]))
- {
- return false;
- }
-
- // the identifier must be followed by a domain name, which is composed of at least two
- // RFC1035 labels
- if (cNames == 2)
- {
- return false;
- }
-
- // check the optional domain name
- for (int i = 1; i < cNames; ++i)
- {
- if (!isValidRFC1035Label(asName[i]))
- {
- return false;
- }
- }
-
- return true;
- }
-
- @Override
- public Token next()
- {
- boolean fWhitespaceBefore = m_fWhitespace;
- Token token = eatToken();
- boolean fWhitespaceAfter = eatWhitespace();
- token.noteWhitespace(fWhitespaceBefore, fWhitespaceAfter);
- return token;
- }
-
- /**
- * Eat a string literal.
- *
- * @return a string literal as a token
- */
- protected Token eatStringLiteral(long lInitPos)
- {
- return eatStringChars(lInitPos, false, false);
- }
-
- /**
- * Eat a string literal that is in the freeform/multi-line form.
- *
- * @return a string literal as a token
- */
- protected Token eatMultilineLiteral(long lInitPos)
- {
- return eatStringChars(lInitPos, false, true);
- }
-
- /**
- * Eat a template literal. A template literal is a string literal that may contain expressions;
- * for example:
- * <blockquote><code><pre>
- * $"x={x}"
- * </pre></code></blockquote>
- *
- * @param lInitPos the start of the template literal
- *
- * @return the resulting token
- */
- protected Token eatTemplateLiteral(long lInitPos)
- {
- return eatStringChars(lInitPos, true, false);
- }
-
- /**
- * Eat a template literal that uses the multi-line format. A template literal is a string
- * literal that may contain expressions; for example:
- * <blockquote><code><pre>
- * $|# TOML doc
- * |[name]
- * |first = "{person.firstname}"
- * |last = "{person.lastname}"
- * </pre></code></blockquote>
- *
- * @param lInitPos the start of the template literal
- *
- * @return the resulting token
- */
- protected Token eatMultilineTemplateLiteral(long lInitPos)
- {
- return eatStringChars(lInitPos, true, true);
- }
-
- /**
- * Eat the characters defined as whitespace, which include line terminators and the file
- * terminator. Whitespace does not include comments.
- */
- protected boolean eatWhitespace()
- {
- boolean fWhitespace = false;
- Source source = m_source;
- while (source.hasNext())
- {
- if (isWhitespace(nextChar()))
- {
- fWhitespace = true;
- }
- else
- {
- // put back the non-whitespace character
- source.rewind();
- break;
- }
- }
- m_fWhitespace = fWhitespace;
- return fWhitespace;
- }
-
- protected boolean isMultilineContinued()
- {
- long lPrev = mark();
- Source source = m_source;
- while (source.hasNext())
- {
- char ch = source.next();
- if (!isWhitespace(ch))
- {
- if (ch == '|')
- {
- return true;
- }
- break;
- }
- }
-
- restore(lPrev);
- return false;
- }
-
- /**
- * Eat an inline template expression that begins with a '{' and ends with a '}'. The contents
- * <b>between</b> the opening and closing curlies are returned as an array of tokens.
- *
- * @param fMultiline true iff this is a multi-line format
- *
- * @return the tokens found between the opening and closing curly braces
- */
- protected Token[] eatTemplateExpression(boolean fMultiline)
- {
- int nPrevLine = m_source.getLine();
- long lInitPos = m_source.getPosition();
- expect('{');
- long lPrevPos = mark();
- int cDepth = 1;
- ArrayList<Token> tokens = new ArrayList<>();
- while (true)
- {
- Token token = next();
- int nLine = Source.calculateLine(token.getEndPosition());
- if (nLine != nPrevLine)
- {
- if (!fMultiline || token.getId() != Id.BIT_OR
- || Source.calculateLine(token.getStartPosition()) != nLine)
- {
- // either it's not a multi-line literal, or the new line doesn't start with '|',
- // or a token crosses a line boundary; log error: newline in string
- restore(lPrevPos);
- log(Severity.ERROR, STRING_NO_TERM, null, lInitPos);
- return tokens.toArray(new Token[0]);
- }
-
- nPrevLine = nLine;
- continue; // skip this '|' token
- }
-
- switch (token.getId())
- {
- case L_CURLY:
- ++cDepth;
- break;
-
- case R_CURLY:
- if (--cDepth <= 0)
- {
- if (token.hasTrailingWhitespace())
- {
- // don't steal the whitespace; we're inside a literal!
- m_source.setPosition(token.getEndPosition());
- }
-
- return tokens.toArray(new Token[0]);
- }
- break;
- }
-
- tokens.add(token);
- lPrevPos = mark();
- }
- }
-
- /**
- * Parse the binary literal value.
- *
- * @param lInitPos the location of the "#" at the start of the binary literal value
- * @param fMultiline true iff the format is a multiline format
- *
- * @return the binary literal
- */
- protected Token eatBinaryLiteral(long lInitPos, boolean fMultiline)
- {
- StringBuilder sb = new StringBuilder();
- Source source = m_source;
- boolean fFirst = true;
- while (source.hasNext())
- {
- char ch = source.next();
- if (isHexit(ch))
- {
- sb.append(ch);
- }
- else if (ch == '_')
- {
- if (fFirst)
- {
- // it's an error to start with an underscore
- log(Severity.ERROR, ILLEGAL_HEX, new Object[] {String.valueOf(ch)}, lInitPos);
- }
- // ignore the _ (it's used for spacing within the literal)
- }
- else if (!fMultiline)
- {
- // the first non-hexit / non-underscore character indicates an end-of-value
- source.rewind();
- break;
- }
- else if (isWhitespace(ch))
- {
- // ignore whitespace, unless it's newline
- if (isLineTerminator(ch) && !isMultilineContinued())
- {
- source.rewind();
- break;
- }
- }
- else
- {
- // error
- log(Severity.ERROR, ILLEGAL_HEX, new Object[] {String.valueOf(ch)}, lInitPos);
- source.rewind();
- break;
- }
-
- fFirst = false;
- }
-
- return new Token(lInitPos, source.getPosition(), Id.LIT_BINSTR,
- toBinary(sb.toString().toCharArray()));
- }
-
- protected byte[] toBinary(char[] ach)
- {
- int cch = ach.length;
- int ofch = 0;
- int cb = (cch + 1) / 2;
- int ofb = 0;
- byte[] ab = new byte[cb];
- if ((cch & 0x1) != 0)
- {
- // odd number of characters means that the first nibble is a pre-pended zero
- ab[ofb++] = (byte) hexitValue(ach[ofch++]);
- }
- while (ofb < cb)
- {
- ab[ofb++] = (byte) ( (hexitValue(ach[ofch++]) << 4)
- + hexitValue(ach[ofch++]) );
- }
-
- return ab;
- }
-
- /**
- * Parse a single token.
- *
- * @return the next Token
- */
- protected Token eatToken()
- {
- Source source = m_source;
- long lInitPos = source.getPosition();
- char chInit = nextChar();
-
- switch (chInit)
- {
- case '{':
- return new Token(lInitPos, source.getPosition(), Id.L_CURLY);
- case '}':
- return new Token(lInitPos, source.getPosition(), Id.R_CURLY);
- case '(':
- return new Token(lInitPos, source.getPosition(), Id.L_PAREN);
- case ')':
- return new Token(lInitPos, source.getPosition(), Id.R_PAREN);
- case '[':
- return new Token(lInitPos, source.getPosition(), Id.L_SQUARE);
- case ']':
- return new Token(lInitPos, source.getPosition(), Id.R_SQUARE);
-
- case ';':
- return new Token(lInitPos, source.getPosition(), Id.SEMICOLON);
- case ',':
- return new Token(lInitPos, source.getPosition(), Id.COMMA);
-
- case '.':
- if (source.hasNext())
- {
- switch (nextChar())
- {
- case '.':
- if (source.hasNext())
- {
- switch (nextChar())
- {
- case '/':
- return new Token(lInitPos, source.getPosition(), Id.DIR_PARENT);
-
- case '<':
- return new Token(lInitPos, source.getPosition(), Id.I_RANGE_E);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.I_RANGE_I);
-
- case '/':
- return new Token(lInitPos, source.getPosition(), Id.DIR_CUR);
-
- case '0': case '1': case '2': case '3': case '4':
- case '5': case '6': case '7': case '8': case '9':
- source.rewind();
- source.rewind();
- return eatNumericLiteral();
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.DOT);
-
- case '$':
- if (source.hasNext())
- {
- switch (nextChar())
- {
- case '\"':
- return eatTemplateLiteral(lInitPos);
-
- case '|':
- return eatMultilineTemplateLiteral(lInitPos);
-
- case '/':
- // it is a file name
- source.rewind();
- return new Token(lInitPos, source.getPosition(), Id.STR_FILE);
-
- case '.':
- if (source.hasNext())
- {
- switch (nextChar())
- {
- case '.':
- if (source.hasNext())
- {
- if (nextChar() == '/')
- {
- // it is a file name starting with "../"
- source.rewind();
- source.rewind();
- source.rewind();
- return new Token(lInitPos, source.getPosition(), Id.STR_FILE);
- }
-
- source.rewind();
- }
- break;
-
- case '/':
- // it is a file name starting with "./"
- source.rewind();
- source.rewind();
- return new Token(lInitPos, source.getPosition(), Id.STR_FILE);
- }
-
- source.rewind();
- }
- break;
- }
-
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.IDENTIFIER, "$");
-
- case '#':
- if (source.hasNext())
- {
- switch (nextChar())
- {
- case '.':
- case '/':
- // it is a file name
- source.rewind();
- return new Token(lInitPos, source.getPosition(), Id.BIN_FILE);
-
- case '|':
- return eatBinaryLiteral(lInitPos, true);
-
- default:
- // fall through (it will be an error)
- case '0': case '1': case '2': case '3': case '4':
- case '5': case '6': case '7': case '8': case '9':
- case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
- case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
- source.rewind();
- // fall through
- }
- }
- return eatBinaryLiteral(lInitPos, false);
-
- case '@':
- return new Token(lInitPos, source.getPosition(), Id.AT);
-
- case '?':
- if (source.hasNext())
- {
- switch (nextChar())
- {
- case '=':
- return new Token(lInitPos, source.getPosition(), Id.COND_NN_ASN);
-
- case ':':
- if (source.hasNext())
- {
- if (nextChar() == '=')
- {
- return new Token(lInitPos, source.getPosition(), Id.COND_ELSE_ASN);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.COND_ELSE);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.COND);
-
- case ':':
- if (source.hasNext())
- {
- if (nextChar() == '=')
- {
- return new Token(lInitPos, source.getPosition(), Id.COND_ASN);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.COLON);
-
- case '+':
- if (source.hasNext())
- {
- switch (nextChar())
- {
- case '+':
- return new Token(lInitPos, source.getPosition(), Id.INC);
-
- case '=':
- return new Token(lInitPos, source.getPosition(), Id.ADD_ASN);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.ADD);
-
- case '-':
- if (source.hasNext())
- {
- switch (nextChar())
- {
- case '-':
- return new Token(lInitPos, source.getPosition(), Id.DEC);
-
- case '>':
- return new Token(lInitPos, source.getPosition(), Id.LAMBDA);
-
- case '=':
- return new Token(lInitPos, source.getPosition(), Id.SUB_ASN);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.SUB);
-
- case '*':
- if (source.hasNext())
- {
- if (nextChar() == '=')
- {
- return new Token(lInitPos, source.getPosition(), Id.MUL_ASN);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.MUL);
-
- case '/':
- if (source.hasNext())
- {
- switch (nextChar())
- {
- case '/':
- return eatSingleLineComment(lInitPos);
-
- case '*':
- return eatEnclosedComment(lInitPos);
-
- case '=':
- return new Token(lInitPos, source.getPosition(), Id.DIV_ASN);
-
- case '%':
- return new Token(lInitPos, source.getPosition(), Id.DIVREM);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.DIV);
-
- case '<':
- if (source.hasNext())
- {
- switch (nextChar())
- {
- case '<':
- if (source.hasNext())
- {
- if (nextChar() == '=')
- {
- return new Token(lInitPos, source.getPosition(), Id.SHL_ASN);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.SHL);
-
- case '-':
- return new Token(lInitPos, source.getPosition(), Id.ASN_EXPR);
-
- case '=':
- if (source.hasNext())
- {
- if (nextChar() == '>')
- {
- return new Token(lInitPos, source.getPosition(), Id.COMP_ORD);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.COMP_LTEQ);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.COMP_LT);
-
- case '>':
- if (source.hasNext())
- {
- switch (nextChar())
- {
- case '>':
- if (source.hasNext())
- {
- switch (nextChar())
- {
- case '>':
- if (source.hasNext())
- {
- if (nextChar() == '=')
- {
- return new Token(lInitPos, source.getPosition(), Id.USHR_ASN);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.USHR);
-
- case '=':
- return new Token(lInitPos, source.getPosition(), Id.SHR_ASN);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.SHR);
-
- case '.':
- if (source.hasNext())
- {
- if (nextChar() == '.')
- {
- if (source.hasNext())
- {
- if (nextChar() == '<')
- {
- return new Token(lInitPos, source.getPosition(), Id.E_RANGE_E);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.E_RANGE_I);
- }
- source.rewind();
- }
- break;
-
- case '=':
- return new Token(lInitPos, source.getPosition(), Id.COMP_GTEQ);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.COMP_GT);
-
- case '&':
- if (source.hasNext())
- {
- switch (nextChar())
- {
- case '&':
- if (source.hasNext())
- {
- if (nextChar() == '=')
- {
- return new Token(lInitPos, source.getPosition(), Id.COND_AND_ASN);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.COND_AND);
-
- case '=':
- return new Token(lInitPos, source.getPosition(), Id.BIT_AND_ASN);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.BIT_AND);
-
- case '|':
- if (source.hasNext())
- {
- switch (nextChar())
- {
- case '|':
- if (source.hasNext())
- {
- if (nextChar() == '=')
- {
- return new Token(lInitPos, source.getPosition(), Id.COND_OR_ASN);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.COND_OR);
-
- case '=':
- return new Token(lInitPos, source.getPosition(), Id.BIT_OR_ASN);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.BIT_OR);
-
- case '=':
- if (source.hasNext())
- {
- if (nextChar() == '=')
- {
- return new Token(lInitPos, source.getPosition(), Id.COMP_EQ);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.ASN);
-
- case '%':
- if (source.hasNext())
- {
- if (nextChar() == '=')
- {
- return new Token(lInitPos, source.getPosition(), Id.MOD_ASN);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.MOD);
-
- case '!':
- if (source.hasNext())
- {
- if (nextChar() == '=')
- {
- return new Token(lInitPos, source.getPosition(), Id.COMP_NEQ);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.NOT);
-
- case '^':
- if (source.hasNext())
- {
- switch (nextChar())
- {
- case '^':
- return new Token(lInitPos, source.getPosition(), Id.COND_XOR);
-
- case '=':
- return new Token(lInitPos, source.getPosition(), Id.BIT_XOR_ASN);
-
- case '(':
- return new Token(lInitPos, source.getPosition(), Id.ASYNC_PAREN);
- }
- source.rewind();
- }
- return new Token(lInitPos, source.getPosition(), Id.BIT_XOR);
-
- case '~':
- return new Token(lInitPos, source.getPosition(), Id.BIT_NOT);
-
- case '0': case '1': case '2': case '3': case '4':
- case '5': case '6': case '7': case '8': case '9':
- source.rewind();
- return eatNumericLiteral();
-
- case '\'':
- return eatCharLiteral(lInitPos);
-
- case '\"':
- return eatStringLiteral(lInitPos);
-
- case '\\':
- if (source.hasNext())
- {
- if (nextChar() == '|')
- {
- return eatMultilineLiteral(lInitPos);
- }
- source.rewind();
- }
- // fall through
-
- default:
- if (!isIdentifierStart(chInit))
- {
- log(Severity.ERROR, ILLEGAL_CHAR, new Object[]{quotedChar(chInit)}, lInitPos);
- }
- // fall through
- case 'A':case 'B':case 'C':case 'D':case 'E':case 'F':case 'G':
- case 'H':case 'I':case 'J':case 'K':case 'L':case 'M':case 'N':
- case 'O':case 'P':case 'Q':case 'R':case 'S':case 'T':case 'U':
- case 'V':case 'W':case 'X':case 'Y':case 'Z':
- case 'a':case 'b':case 'c':case 'd':case 'e':case 'f':case 'g':
- case 'h':case 'i':case 'j':case 'k':case 'l':case 'm':case 'n':
- case 'o':case 'p':case 'q':case 'r':case 's':case 't':case 'u':
- case 'v':case 'w':case 'x':case 'y':case 'z':
- case '_':
- {
- while (source.hasNext())
- {
- if (!isIdentifierPart(nextChar()))
- {
- source.rewind();
- break;
- }
- }
-
- String name = extractSource(lInitPos);
- long lPos = source.getPosition();
- if (source.hasNext())
- {
- char chNext = source.next();
- if (name.equals(Id.TODO.TEXT))
- {
- source.rewind();
- if (chNext == '(')
- {
- return new Token(lInitPos, source.getPosition(), Id.TODO, null);
- }
- else
- {
- // parse the to-do statement in the same manner as a single line comment
- Token comment = eatSingleLineComment(lInitPos);
- return new Token(comment.getStartPosition(), comment.getEndPosition(),
- Id.TODO, comment.getValue());
- }
- }
- else if (chNext == ':')
- {
- if (Token.Id.valueByPrefix(name) != null)
- {
- // check for a legal suffix, e.g. "this:private"
- while (source.hasNext())
- {
- if (!isIdentifierPart(nextChar()))
- {
- source.rewind();
- break;
- }
- }
-
- String full = extractSource(lInitPos);
- if (Id.valueByContextSensitiveText(full) != null)
- {
- name = full;
- }
- else
- {
- // false alarm; back up and just take "this"
- source.setPosition(lPos);
- }
- }
- else
- {
- // check for suffix of private / protected / public / struct (etc.)
- long lPosSuffix = source.getPosition();
- while (source.hasNext())
- {
- if (!isIdentifierPart(nextChar()))
- {
- source.rewind();
- break;
- }
- }
- String suffix = extractSource(lPosSuffix);
- source.setPosition(lPosSuffix); // back up past the suffix
-
- Token.Id idNum = null;
- boolean fFloat = false;
- if (Id.valueByText(suffix) == null && source.hasNext() && !isWhitespace(peekChar()))
- {
- switch (name)
- {
- case "int":
- idNum = Id.TYPE_INT;
- break;
- case "float":
- idNum = Id.TYPE_FLOAT;
- break;
- case "char":
- idNum = Id.TYPE_CHAR;
- break;
-
- // Following are Ecstasy ids
- case "Bit":
- idNum = Id.LIT_BIT;
- break;
-
- case "Nibble":
- idNum = Id.LIT_NIBBLE;
- break;
-
- case "Int":
- idNum = Id.LIT_INTA;
- break;
- case "Int8":
- idNum = Id.LIT_INT8;
- break;
- case "Int16":
- idNum = Id.LIT_INT16;
- break;
- case "Int32":
- idNum = Id.LIT_INT32;
- break;
- case "Int64":
- idNum = Id.LIT_INT64;
- break;
- case "Int128":
- idNum = Id.LIT_INT128;
- break;
- case "IntN":
- idNum = Id.LIT_INTN;
- break;
-
- case "UInt":
- idNum = Id.LIT_UINTA;
- break;
- case "Byte":
- case "UInt8":
- idNum = Id.LIT_UINT8;
- break;
- case "UInt16":
- idNum = Id.LIT_UINT16;
- break;
- case "UInt32":
- idNum = Id.LIT_UINT32;
- break;
- case "UInt64":
- idNum = Id.LIT_UINT64;
- break;
- case "UInt128":
- idNum = Id.LIT_UINT128;
- break;
- case "UIntN":
- idNum = Id.LIT_UINTN;
- break;
-
- case "Dec":
- idNum = Id.LIT_DECA;
- fFloat = true;
- break;
- case "Dec32":
- idNum = Id.LIT_DEC32;
- fFloat = true;
- break;
- case "Dec64":
- idNum = Id.LIT_DEC64;
- fFloat = true;
- break;
- case "Dec128":
- idNum = Id.LIT_DEC128;
- fFloat = true;
- break;
- case "DecN":
- idNum = Id.LIT_DECN;
- fFloat = true;
- break;
-
- case "Float8e4":
- idNum = Id.LIT_FLOAT8E4;
- fFloat = true;
- break;
- case "Float8e5":
- idNum = Id.LIT_FLOAT8E5;
- fFloat = true;
- break;
- case "BFloat16":
- idNum = Id.LIT_BFLOAT16;
- fFloat = true;
- break;
- case "Float16":
- idNum = Id.LIT_FLOAT16;
- fFloat = true;
- break;
- case "Float32":
- idNum = Id.LIT_FLOAT32;
- fFloat = true;
- break;
- case "Float64":
- idNum = Id.LIT_FLOAT64;
- fFloat = true;
- break;
- case "Float128":
- idNum = Id.LIT_FLOAT128;
- fFloat = true;
- break;
- case "FloatN":
- idNum = Id.LIT_FLOATN;
- fFloat = true;
- break;
-
- case "Date":
- return eatDate(lInitPos, false);
- case "TimeOfDay":
- return eatTimeOfDay(lInitPos, false);
- case "Time":
- return eatTime(lInitPos);
- case "TimeZone":
- return eatTimeZone(lInitPos);
- case "Duration":
- return eatDuration(lInitPos);
-
- default:
- source.rewind(); // back up past the ':'
- }
-
- if (idNum != null)
- {
- Token tokNum = eatNumericLiteral();
- switch (tokNum.getId())
- {
- case LIT_DEC:
- case LIT_FLOAT:
- if (!fFloat)
- {
- // we were expecting an integer
- log(Severity.ERROR, ILLEGAL_NUMBER, new Object[] {
- extractSource(lInitPos, tokNum.getEndPosition())},
- lInitPos);
- return tokNum;
- }
- // fall through
- case LIT_INT:
- return tokNum.refine(idNum);
-
- default:
- throw new IllegalStateException("id=" + idNum);
- }
- }
- }
- else
- {
- source.rewind(); // back up past the ':'
- }
- }
- }
- else
- {
- source.rewind();
- }
- }
-
- Id id = Id.valueByText(name);
- return new Token(lInitPos, source.getPosition(), id == null ? Id.IDENTIFIER : id, name);
- }
- }
- }
-
- /**
- * Eat a character literal.
- *
- * @return a character literal as a token
- */
- protected Token eatCharLiteral(long lInitPos)
- {
- Source source = m_source;
- long lPosChar = source.getPosition();
-
- char ch = '?';
- boolean term = false;
- if (source.hasNext())
- {
- switch (ch = source.next())
- {
- case '\'':
- if (source.hasNext())
- {
- if (source.next() == '\'')
- {
- // assume the previous one should have been escaped
- source.rewind();
- log(Severity.ERROR, CHAR_BAD_ESC, null, lPosChar);
- }
- else
- {
- // assume the encountered quote that we thought was supposed to be
- // the character value was instead supposed to be closing quote
- source.rewind();
- source.rewind();
- log(Severity.ERROR, CHAR_NO_CHAR, null, lPosChar, lPosChar);
- }
- }
- break;
-
- case '\\':
- // process escaped char
- switch (ch = source.next())
- {
- case '\r':
- case '\n':
- case 0x000B: // VT Vertical Tab
- case 0x000C: // FF Form Feed
- case 0x0085: // NEL Next Line
- case 0x2028: // LS Line Separator
- case 0x2029: // PS Paragraph Separator
- // log error: newline in string
- source.rewind();
- log(Severity.ERROR, CHAR_NO_TERM, null, lInitPos);
- // assume it wasn't supposed to be an escape
- ch = '\\';
- break;
-
- case '\\':
- case '\'':
- case '\"':
- break;
-
- case '0':
- ch = '\000';
- break;
- case 'b':
- ch = '\b';
- break;
- case 'd':
- ch = '\177';
- break;
- case 'e':
- ch = '\033';
- break;
- case 'f':
- ch = '\f';
- break;
- case 'n':
- ch = '\n';
- break;
- case 'r':
- ch = '\r';
- break;
- case 't':
- ch = '\t';
- break;
- case 'v':
- ch = '\013';
- break;
- case 'z':
- ch = '\032';
- break;
-
- default:
- // log error: bad escape
- log(Severity.ERROR, CHAR_BAD_ESC, null, lPosChar);
- break;
- }
- break;
-
- case '\r':
- case '\n':
- case 0x000B: // VT Vertical Tab
- case 0x000C: // FF Form Feed
- case 0x0085: // NEL Next Line
- case 0x2028: // LS Line Separator
- case 0x2029: // PS Paragraph Separator
- // log error: newline in string
- source.rewind();
- log(Severity.ERROR, CHAR_NO_TERM, null, lInitPos);
- break;
-
- default:
- break;
- }
-
- if (source.hasNext())
- {
- if (source.next() == '\'')
- {
- term = true;
- }
- else
- {
- source.rewind();
- }
- }
- }
-
- if (!term)
- {
- // log error: unterminated string
- log(Severity.ERROR, CHAR_NO_TERM, null, lInitPos);
- }
-
- return new Token(lInitPos, source.getPosition(), Id.LIT_CHAR, Character.valueOf(ch));
- }
-
- /**
- * Eat the character contents of a string literal, including the string literal portion(s) of a
- * template literal.
- *
- * @param lInitPos the location of the start of the literal (the position of the {@code $} or
- * the {@code "})
- * @param fTemplate true iff this is a template literal
- * @param fMultiline true iff this is a multi-line format
- *
- * @return the resulting token
- */
- protected Token eatStringChars(long lInitPos, boolean fTemplate, boolean fMultiline)
- {
- Source source = m_source;
-
- StringBuilder sb = new StringBuilder();
- ArrayList list = fTemplate ? new ArrayList<>() : null;
- long lPosStart = lInitPos;
- Appending: while (true)
- {
- if (source.hasNext())
- {
- char ch = source.next();
- switch (ch)
- {
- case '\"':
- if (fMultiline)
- {
- sb.append(ch);
- break;
- }
- break Appending;
-
- case '\\':
- if (fMultiline)
- {
- if (source.hasNext())
- {
- if (isLineTerminator(source.next()) && isMultilineContinued())
- {
- continue; // Appending;
- }
- source.rewind();
- }
-
- if (!fTemplate)
- {
- sb.append(ch);
- break;
- }
- }
-
- // process escaped char
- switch (ch = source.next())
- {
- case '\r':
- case '\n':
- case 0x000B: // VT Vertical Tab
- case 0x000C: // FF Form Feed
- case 0x0085: // NEL Next Line
- case 0x2028: // LS Line Separator
- case 0x2029: // PS Paragraph Separator
- // log error: newline in string
- source.rewind();
- log(Severity.ERROR, STRING_NO_TERM, null, lInitPos);
- // assume it wasn't supposed to be an escape
- sb.append('\\');
- break Appending;
-
- case '\\':
- sb.append('\\');
- break;
- case '\'':
- sb.append('\'');
- break;
- case '\"':
- sb.append('\"');
- break;
-
- case '0':
- sb.append('\000');
- break;
- case 'b':
- sb.append('\b');
- break;
- case 'd':
- sb.append('\177');
- break;
- case 'e':
- sb.append('\033');
- break;
- case 'f':
- sb.append('\f');
- break;
- case 'n':
- sb.append('\n');
- break;
- case 'r':
- sb.append('\r');
- break;
- case 't':
- sb.append('\t');
- break;
- case 'v':
- sb.append('\013');
- break;
- case 'z':
- sb.append('\032');
- break;
-
- case '{':
- if (fTemplate)
- {
- sb.append('{');
- break;
- }
- // fall through
-
- default:
- // log error: bad escape
- long lPosEscEnd = source.getPosition();
- source.rewind();
- source.rewind();
- log(Severity.ERROR, STRING_BAD_ESC, null,
- source.getPosition(), lPosEscEnd);
- source.setPosition(lPosEscEnd);
-
- // assume it wasn't supposed to be an escape:
- // append both the escape char and the escaped char
- sb.append('\\')
- .append(ch);
- break;
- }
- break;
-
- case '\r':
- case '\n':
- case 0x000B: // VT Vertical Tab
- case 0x000C: // FF Form Feed
- case 0x0085: // NEL Next Line
- case 0x2028: // LS Line Separator
- case 0x2029: // PS Paragraph Separator
- if (fMultiline)
- {
- boolean fCRLF = false;
- if (ch == '\r' && source.hasNext())
- {
- if (source.next() == '\n')
- {
- fCRLF = true;
- }
- else
- {
- source.rewind();
- }
- }
-
- // eat whitespace and look for a continuation character
- if (isMultilineContinued())
- {
- // it is a multi-line continuation, so include the newline that we
- // just ate in the resulting text
- if (fCRLF)
- {
- sb.append("\r\n");
- }
- else
- {
- sb.append(ch);
- }
- break;
- }
- else
- {
- // leave the newline in place (it's not part of the multiline)
- source.rewind();
- if (fCRLF)
- {
- source.rewind();
- }
- }
- }
- else
- {
- // not a multi-line literal; log error: newline in string
- source.rewind();
- log(Severity.ERROR, STRING_NO_TERM, null, lInitPos);
- }
- break Appending;
-
- case '{':
- if (fTemplate)
- {
- source.rewind();
- long lPosEndText = source.getPosition();
-
- // eat from the opening { to the closing } (inclusive of both)
- Token[] aTokens = eatTemplateExpression(fMultiline);
- int cTokens = aTokens.length;
-
- // if the last token was '=', then add the entire expression up to that
- // point as literal text, and discard the '='
- if (cTokens > 1 && aTokens[cTokens-1].getId() == Id.ASN)
- {
- Token[] aTokensOld = aTokens;
- aTokens = new Token[--cTokens];
- System.arraycopy(aTokensOld, 0, aTokens, 0, cTokens);
-
- sb.append(source.toString(aTokensOld[0].getStartPosition(),
- aTokensOld[cTokens].getEndPosition()));
- }
-
- if (sb.length() > 0)
- {
- list.add(new Token(lPosStart, lPosEndText, Id.LIT_STRING, sb.toString()));
- sb = new StringBuilder();
- }
-
- list.add(aTokens);
-
- // start eating a new string portion of the template from this point
- lPosStart = source.getPosition();
- break;
- }
- // fall through
-
- default:
- sb.append(ch);
- break;
- }
- }
- else
- {
- // log error: unterminated string
- log(Severity.ERROR, STRING_NO_TERM, null, lInitPos);
- }
- }
-
- if (fTemplate)
- {
- long lPosCur = source.getPosition();
- if (sb.length() > 0)
- {
- list.add(new Token(lPosStart, lPosCur, Id.LIT_STRING, sb.toString()));
- }
- else if (list.isEmpty())
- {
- return new Token(lPosStart, lPosCur, Id.LIT_STRING, "");
- }
- return new Token(lInitPos, lPosCur, Id.TEMPLATE, list.toArray());
- }
-
- return new Token(lPosStart, source.getPosition(), Id.LIT_STRING, sb.toString());
- }
-
- /**
- * Eat a numeric literal.
- *
- * @return the numeric literal as a Token
- */
- protected Token eatNumericLiteral()
- {
- Source source = m_source;
- long lStartPos = source.getPosition();
-
- // eat the first part of the number (or the entire number, if it is an integer literal)
- int[] results = new int[2];
- PackedInteger piWhole = eatIntegerLiteral(results);
- int mantissaRadix = results[0];
- int signScalar = results[1];
-
- // parse optional '.' + value
- PackedInteger piFraction = null;
- int fractionalDigits = 0;
- if (source.hasNext())
- {
- if (source.next() == '.')
- {
- // could be ".."
- if (source.hasNext())
- {
- boolean fNotDecimal = source.next() == '.';
- // whatever it was, spit it back out
- source.rewind();
-
- // if it's not "..", it could be something else, e.g. a method name on type Int
- if (!fNotDecimal && !isNextCharDigit(mantissaRadix))
- {
- fNotDecimal = true;
- }
-
- if (fNotDecimal)
- {
- // spit back out the first dot
- source.rewind();
-
- return new Token(lStartPos, source.getPosition(), Id.LIT_INT, piWhole);
- }
- }
-
- piFraction = eatDigits(false, mantissaRadix, results);
- fractionalDigits = results[0];
-
- if (fractionalDigits == 0)
- {
- log(Severity.ERROR, ILLEGAL_NUMBER, lStartPos, lStartPos);
- }
- }
- else
- {
- source.rewind();
- }
- }
-
- // parse optional exponent
- PackedInteger piExp = null;
- boolean mustBeBinary = false;
- if (source.hasNext())
- {
- char ch = source.next();
- switch (ch)
- {
- case 'E': case 'e':
- piExp = eatIntegerLiteral(null);
- break;
-
- case 'P': case 'p':
- piExp = eatIntegerLiteral(null);
- mustBeBinary = true;
- break;
-
- default:
- // anything else should be whitespace or some type of operator/separator
- long lEndPos = source.getPosition();
- source.rewind();
- if (isIdentifierPart(ch))
- {
- log(Severity.ERROR, ILLEGAL_NUMBER, lStartPos,
- source.getPosition(), lEndPos);
- }
- break;
- }
- }
-
- long lPosEnd = source.getPosition();
- if (piFraction == null && piExp == null)
- {
- return new Token(lStartPos, lPosEnd, Id.LIT_INT, piWhole);
- }
- else if (!mustBeBinary && mantissaRadix == 10)
- {
- // convert to IEEE-754 decimal floating point format
- // note: for now it is simply stored in the literal token as a BigDecimal
- BigInteger biWhole = piWhole.getBigInteger();
- BigDecimal dec;
- if (piFraction == null)
- {
- dec = new BigDecimal(biWhole);
- }
- else
- {
- if (biWhole.signum() < 0)
- {
- biWhole = biWhole.negate();
- }
- dec = new BigDecimal(biWhole.multiply(BigInteger.TEN.pow(fractionalDigits))
- .add(piFraction.getBigInteger()), fractionalDigits);
- if (signScalar < 0)
- {
- // the unfortunate side-effect of not having a -0
- dec = dec.negate();
- }
- }
- if (piExp != null)
- {
- long lExp = piExp.getLong();
- if (lExp > 6144 || lExp < (1-6144))
- {
- log(Severity.ERROR, ILLEGAL_NUMBER, lStartPos,
- lStartPos, lPosEnd);
- }
- else
- {
- dec = dec.scaleByPowerOfTen((int) lExp);
- }
- }
- return new Token(lStartPos, lPosEnd, Id.LIT_DEC, dec);
- }
- else
- {
- // convert to IEEE-754 binary floating point format
- // note: for now it is simply stored in the literal token as a String
- return new Token(lStartPos, lPosEnd, Id.LIT_FLOAT, extractSource(lStartPos, lPosEnd));
- }
- }
-
- /**
- * The next character must begin an integer literal. Parse it and return it as a PackedInteger.
- *
- * @param otherResults either null, or an array of up to two "int" elements, to allow for
- * multiple "out" values from this method, the first of which is the radix
- * and the second is the sign (<tt>-1</tt> or <tt>+1</tt>)
- *
- * @return a PackedInteger
- */
- protected PackedInteger eatIntegerLiteral(int[] otherResults)
- {
- Source source = m_source;
-
- // the first character could be a sign (+ or -)
- boolean fNeg = false;
- char ch = needCharOrElse(ILLEGAL_NUMBER);
- if (ch == '+' || ch == '-')
- {
- fNeg = (ch == '-');
- ch = needCharOrElse(ILLEGAL_NUMBER);
- }
-
- // if the next character is '0', it is potentially part of a prefix denoting a radix
- int radix = 10;
- if (ch == '0' && source.hasNext())
- {
- switch (nextChar())
- {
- case 'B':
- case 'b':
- radix = 2;
- break;
- case 'o':
- radix = 8;
- break;
- case 'X':
- case 'x':
- radix = 16;
- break;
- default:
- source.rewind();
- source.rewind();
- break;
- }
- }
- else
- {
- source.rewind();
- }
-
- // don't you just wish that Java had multiple return values?
- if (otherResults != null)
- {
- if (otherResults.length > 0)
- {
- otherResults[0] = radix;
- }
- if (otherResults.length > 1)
- {
- otherResults[1] = fNeg ? -1 : 1;
- }
- }
-
- PackedInteger pi = eatDigits(fNeg, radix, null);
-
- PossibleSuffix: if (radix == 10 && source.hasNext())
- {
- // allow KI/KB, MI/MB, GI/GB TI/TB, PI/PB, EI/EB, ZI/ZB, YI/YB
- int iMul;
- switch (nextChar())
- {
- case 'K': case 'k':
- iMul = 0;
- break;
-
- case 'M': case 'm':
- iMul = 1;
- break;
-
- case 'G': case 'g':
- iMul = 2;
- break;
-
- case 'T': case 't':
- iMul = 3;
- break;
-
- case 'P': case 'p':
- iMul = 4;
- break;
-
- case 'E': case 'e':
- iMul = 5;
- break;
-
- case 'Z': case 'z':
- iMul = 6;
- break;
-
- case 'Y': case 'y':
- iMul = 7;
- break;
-
- default:
- source.rewind();
- break PossibleSuffix;
- }
-
- PackedInteger[] factors = PackedInteger.xB_FACTORS;
- if (source.hasNext())
- {
- switch (nextChar())
- {
- case 'B': case 'b':
- break;
-
- case 'I': case 'i':
- if (source.hasNext())
- {
- char optionalB = source.next();
- if (!(optionalB == 'B' || optionalB == 'b'))
- {
- source.rewind();
- }
- }
- factors = PackedInteger.xI_FACTORS;
- break;
-
- case '+': case '-':
- case '0': case '1': case '2': case '3': case '4':
- case '5': case '6': case '7': case '8': case '9':
- if (iMul == 4 || iMul == 5)
- {
- // the "P" or the "E" is for an exponent
- source.rewind();
- source.rewind();
- break PossibleSuffix;
- }
- default:
- source.rewind();
- break;
- }
- }
-
- pi = pi.mul(factors[iMul]);
- }
-
- return pi;
- }
-
- /**
- * @return the value, or a negative value if there was no number or the number overflowed
- */
- protected long eatUnsignedLong()
- {
- long n = 0;
- if (isNextCharDigit(10))
- {
- do
- {
- if (n < 0)
- {
- return n;
- }
-
- n = n * 10 + (nextChar() - '0');
- }
- while (isNextCharDigit(10));
-
- return n;
- }
- else
- {
- return -1;
- }
- }
-
- protected boolean isNextCharDigit(int radix)
- {
- boolean fDigit = false;
-
- Source source = m_source;
- if (source.hasNext())
- {
- switch (nextChar())
- {
- case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
- case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
- fDigit = radix >= 16;
- break;
-
- case '9': case '8':
- fDigit = radix >= 10;
- break;
-
- case '7': case '6': case '5': case '4': case '3': case '2':
- fDigit = radix >= 8;
- break;
-
- case '1':
- case '0':
- fDigit = true;
- break;
-
- default:
- break;
- }
- source.rewind();
- }
-
- return fDigit;
- }
-
- /**
- * The next character must begin a sequence of digits of the specified radix. Parse it and
- * return it as a PackedInteger.
- *
- * @return a PackedInteger
- */
- protected PackedInteger eatDigits(boolean fNeg, int radix, int[] digitCount)
- {
- long lValue = 0;
- BigInteger bigint = null; // just in case
- boolean fError = false;
- int cDigits = 0;
-
- Source source = m_source;
- long lStartPos = source.getPosition();
- Parsing: while (source.hasNext())
- {
- long lPos = source.getPosition();
- char ch = nextChar();
- switch (ch)
- {
- case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
- case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
- if (radix < 16)
- {
- // "e" is used as the decimal exponent indicator
- if (ch != 'E' && ch != 'e')
- {
- if (!fError)
- {
- log(Severity.ERROR, ILLEGAL_NUMBER, lStartPos, lPos);
- fError = true;
- }
- }
- source.rewind();
- break Parsing;
- }
- break;
-
- case '9': case '8':
- if (radix < 10)
- {
- if (!fError)
- {
- log(Severity.ERROR, ILLEGAL_NUMBER, lStartPos, lPos);
- fError = true;
- }
- // while an error was encountered, it was at least a digit, so continue
- // parsing those digits (even if they are bad)
- continue; // Parsing;
- }
- break;
-
- case '7': case '6': case '5': case '4': case '3': case '2':
- if (radix < 8)
- {
- if (!fError)
- {
- log(Severity.ERROR, ILLEGAL_NUMBER, lStartPos, lPos);
- fError = true;
- }
- // while an error was encountered, it was at least a digit, so continue
- // parsing those digits (even if they are bad)
- continue; // Parsing;
- }
- break;
-
- case '1': case '0':
- break;
-
- case '_':
- if (cDigits == 0 && !fError)
- {
- // it's an error to start the sequence of digits with an underscore
- log(Severity.ERROR, ILLEGAL_NUMBER, lStartPos, lPos);
- fError = true;
- }
- continue; // Parsing;
-
- default:
- // anything else (including '.') means go to the next step
- source.rewind();
- break Parsing;
- }
-
- if (bigint == null)
- {
- lValue = lValue * radix + hexitValue(ch);
- if (lValue > 0x00FFFFFFFFFFFFFFL)
- {
- bigint = BigInteger.valueOf(fNeg ? -lValue : lValue);
- }
- }
- else
- {
- bigint = bigint.multiply(BigInteger.valueOf(radix)).add(BigInteger.valueOf(hexitValue(ch)));
- }
- ++cDigits;
- }
-
- if (!fError && cDigits == 0)
- {
- log(Severity.ERROR, ILLEGAL_NUMBER, lStartPos, lStartPos);
- }
-
- if (digitCount != null && digitCount.length > 0)
- {
- digitCount[0] = cDigits;
- }
- return bigint == null ? new PackedInteger(fNeg ? -lValue : lValue) : new PackedInteger(bigint);
- }
-
- /**
- * Eat a literal ISO-8601 datetime value (the Ecstasy "Time" class).
- *
- * @param lInitPos the location of the start of the literal token
- *
- * @return the literal as a Token
- */
- protected Token eatTime(long lInitPos)
- {
- Token tokDate = eatDate(lInitPos, true);
- if (!(match('t') || expect('T')))
- {
- log(Severity.ERROR, BAD_TIME, new Object[] {tokDate.getValue()},
- tokDate.getStartPosition(), tokDate.getEndPosition());
- return tokDate;
- }
-
- Token tokTimeOfDay = eatTimeOfDay(lInitPos, true);
- long lEndPos = tokTimeOfDay.getEndPosition();
-
- String sDT = tokDate.getValue() + "T" + tokTimeOfDay.getValue();
- if (match('Z') || match('z') || match('+') || match('-'))
- {
- m_source.rewind();
- Token tokZone = eatTimeZone(lInitPos);
- sDT += tokZone.getValue();
- lEndPos = tokZone.getEndPosition();
- }
-
- return new Token(lInitPos, lEndPos, Id.LIT_TIME, sDT);
- }
-
- /**
- * Eat a literal ISO-8601 timezone value.
- *
- * @param lInitPos the location of the start of the literal token
- *
- * @return the literal as a Token
- */
- protected Token eatTimeZone(long lInitPos)
- {
- if (match('Z') || match('z'))
- {
- peekNotIdentifierOrNumber();
- return new Token(lInitPos, m_source.getPosition(), Id.LIT_TIMEZONE, "Z");
- }
-
- long lStart = m_source.getPosition();
- int nHour = 0;
- int nMin = 0;
- if (match('-') || expect('+'))
- {
- if ((nHour = eatDigits(2)) >= 0)
- {
- if (match(':') || isNextCharDigit(10))
- {
- if ((nMin = eatDigits(2)) >= 0)
- {
- peekNotIdentifierOrNumber();
- }
- }
- else
- {
- peekNotIdentifierOrNumber();
- }
- }
- }
- long lEnd = m_source.getPosition();
- String sZone = extractSource(lStart, lEnd);
-
- if (nHour >= 0 && nMin >= 0)
- {
- if (nHour > 16 || nMin > 59)
- {
- log(Severity.ERROR, BAD_TIMEZONE, new Object[] {sZone}, lStart, lEnd);
- }
- }
-
- return new Token(lInitPos, lEnd, Id.LIT_TIMEZONE, sZone);
- }
-
- /**
- * Eat a specified number of decimal digits, and convert them to an integer.
- *
- * @param digitCount the number of digits to eat
- *
- * @return the parsed integer value, or the negative thereof if an error was encountered
- */
- protected int eatDigits(int digitCount)
- {
- long lPosStart = m_source.getPosition();
-
- int n = 0;
- for (int i = 0; i < digitCount; ++i)
- {
- if (!isNextCharDigit(10))
- {
- log(Severity.ERROR, EXPECTED_DIGITS, new Object[] {digitCount, i}, lPosStart);
- return -n;
- }
-
- n = n * 10 + (nextChar() - '0');
- }
- return n;
- }
-
- /**
- * Eat a literal ISO-8601 date value.
- *
- * @param lInitPos the location of the start of the literal token
- * @param fContinued true if this is just part of a larger literal
- *
- * @return the literal as a Token
- */
- protected Token eatDate(long lInitPos, boolean fContinued)
- {
- Source source = m_source;
- long lLitPos = source.getPosition();
-
- int nYear;
- int nMonth = 0;
- int nDay = 0;
-
- if ((nYear = eatDigits(4)) >= 0)
- {
- boolean fSep = match('-');
- if ((nMonth = eatDigits(2)) >= 0)
- {
- if (fSep)
- {
- expect('-');
- }
- if ((nDay = eatDigits(2)) >= 0)
- {
- if (!fContinued)
- {
- peekNotIdentifierOrNumber();
- }
- }
- }
- }
-
- long lEndPos = source.getPosition();
- String sDate = extractSource(lLitPos, lEndPos);
- if (nYear >= 0 && nMonth >= 0 && nDay >= 0)
- {
- if (nYear < 1582 || nMonth < 1 || nMonth > 12 || nDay < 1 || nDay > 31)
- {
- log(Severity.ERROR, BAD_DATE, new Object[] {sDate}, lLitPos, lEndPos);
- }
- }
-
- return new Token(lInitPos, lEndPos, Id.LIT_DATE, sDate);
- }
-
- /**
- * Eat a literal ISO-8601 time value (the Ecstasy "TimeOfDay" class).
- *
- * @param lInitPos the location of the start of the literal token
- * @param fContinued true if this is just part of a larger literal
- *
- * @return the literal as a Token
- */
- protected Token eatTimeOfDay(long lInitPos, boolean fContinued)
- {
- Source source = m_source;
- long lStart = source.getPosition();
-
- int nHour;
- int nMin = 0;
- int nSec = 0;
- long nPicos = 0;
- int cDigits;
-
- if ((nHour = eatDigits(2)) >= 0)
- {
- boolean fSep = match(':');
- if ((nMin = eatDigits(2)) >= 0)
- {
- if (fSep && match(':') || !fSep && isNextCharDigit(10))
- {
- nSec = eatDigits(2);
- if (match('.'))
- {
- if (isNextCharDigit(10))
- {
- cDigits = 0;
- do
- {
- char ch = nextChar();
- if (++cDigits <= 12)
- {
- nPicos = nPicos * 10 + (ch - '0');
- }
- }
- while (isNextCharDigit(10));
-
- // scale the integer value up to picos (trillionths)
- while (++cDigits <= 12)
- {
- nPicos *= 10;
- }
- }
- else
- {
- // the dot isn't part of the time-of-day
- source.rewind();
- }
- }
- }
- if (!fContinued)
- {
- peekNotIdentifierOrNumber();
- }
- }
- }
-
- long lEnd = source.getPosition();
- String sTime = extractSource(lStart, lEnd);
- if (nHour >= 0 && nMin >= 0 && nSec >= 0)
- {
- if (nHour > 23 || nMin > 59 || nSec > 59 && !(nHour == 23 && nMin == 59 && nSec == 60))
- {
- log(Severity.ERROR, BAD_TIME_OF_DAY, new Object[] {sTime}, lStart, lEnd);
- }
- }
-
- return new Token(lInitPos, lEnd, Id.LIT_TIMEOFDAY, sTime);
- }
-
- /**
- * Obtain a cookie that represents the Lexer's current location.
- *
- * @return a position cookie
- */
- public long mark()
- {
- // this adds a bit of information to the source's position info
- long lPos = m_source.getPosition();
- assert (lPos & (1L << 63)) == 0L;
- if (m_fWhitespace)
- {
- lPos |= (1L << 63);
- }
- return lPos;
- }
-
- /**
- * Using a previously returned Lexer position cookie, restore that state of the lexer.
- *
- * @param lPos a position cookie previously returned from {@link #mark()}
- */
- public void restore(long lPos)
- {
- m_fWhitespace = (lPos & (1L << 63)) != 0L;
- m_source.setPosition(lPos & ~(1L << 63));
- }
-
- /**
- * Obtain the character located at a previously returned position cookie, but the state of the
- * Lexer after this call must match that from before the call.
- *
- * @param lPos a previously returned position cookie
- *
- * @return the character at the specified position
- */
- public char charAt(long lPos)
- {
- long lPrev = mark();
- restore(lPos);
- char ch = nextChar();
- restore(lPrev);
- return ch;
- }
-
- /**
- * Eat a literal ISO-8601 duration value.
- *
- * @param lInitPos the location of the start of the literal token
- *
- * @return the literal as a Token
- */
- protected Token eatDuration(long lInitPos)
- {
- Source source = m_source;
- long lStart = source.getPosition();
-
- // Stages:
- // Y, M, D, (T), H, M, S
- // 1 2 3 4 5 6 7 . 8
-
- boolean fErr = false;
- if (!match('P'))
- {
- match('p');
- }
-
- int nPrevStage = match('T') ? 4 : 0;
- Loop: while (true)
- {
- long lPos = source.getPosition();
- long lVal = eatUnsignedLong();
- if (lVal < 0)
- {
- source.setPosition(lPos);
- break;
- }
-
- char ch = nextChar();
- switch (ch)
- {
- case 'Y':
- case 'y':
- if (nPrevStage >= 1)
- {
- fErr = true;
- }
- nPrevStage = Math.max(1, nPrevStage);
- break;
-
- case 'M':
- case 'm':
- if (nPrevStage >= 2)
- {
- // parse as minute
- if (nPrevStage < 4)
- {
- fErr = true;
- }
- nPrevStage = Math.max(6, nPrevStage);
- }
- else
- {
- // parse as month
- nPrevStage = Math.max(2, nPrevStage);
- }
- break;
-
- case 'D':
- case 'd':
- if (nPrevStage >= 3)
- {
- fErr = true;
- }
- nPrevStage = Math.max(3, nPrevStage);
- break;
-
- case 'H':
- case 'h':
- if (nPrevStage >= 5)
- {
- fErr = true;
- }
- nPrevStage = Math.max(5, nPrevStage);
- break;
-
- case 'S':
- case 's':
- break Loop;
-
- case '.':
- if (nPrevStage >= 7)
- {
- fErr = true;
- }
- nPrevStage = Math.max(7, nPrevStage);
- break;
-
- default:
- source.rewind();
- fErr = true;
- break Loop;
- }
-
- if (match('T') || match('t'))
- {
- if (nPrevStage >= 4)
- {
- fErr = true;
- }
- else
- {
- nPrevStage = 4;
- }
- }
- }
-
- long lEnd = source.getPosition();
- String sDuration = extractSource(lStart, lEnd).toUpperCase();
-
- if (fErr)
- {
- log(Severity.ERROR, BAD_DURATION, new Object[] {sDuration}, lStart, lEnd);
- }
- else
- {
- peekNotIdentifierOrNumber();
- }
-
- return new Token(lStart, lEnd, Id.LIT_DURATION, sDuration);
- }
-
- /**
- * Eat a single line aka end-of-line comment.
- *
- * @return the comment as a Token
- */
- protected Token eatSingleLineComment(long lPosTokenStart)
- {
- Source source = m_source;
- long lPosTextStart = source.getPosition();
- while (source.hasNext())
- {
- if (isLineTerminator(nextChar()))
- {
- source.rewind();
- break;
- }
- }
- long lPosEnd = source.getPosition();
- return new Token(lPosTokenStart, lPosEnd, Id.EOL_COMMENT,
- extractSource(lPosTextStart, lPosEnd));
- }
-
- /**
- * Eat a multi-line aka enclosed comment.
- *
- * @return the comment as a Token
- */
- protected Token eatEnclosedComment(long lPosTokenStart)
- {
- Source source = m_source;
- long lPosTextStart = source.getPosition();
-
- boolean fAsterisk = false;
- while (source.hasNext())
- {
- char chNext = nextChar();
- if (chNext == '*')
- {
- fAsterisk = true;
- }
- else if (fAsterisk && chNext == '/')
- {
- long lPosTokenEnd = source.getPosition();
- source.rewind();
- source.rewind();
- long lPosTextEnd = source.getPosition();
- source.setPosition(lPosTokenEnd);
- return new Token(lPosTokenStart, lPosTokenEnd, Id.ENC_COMMENT,
- extractSource(lPosTextStart, lPosTextEnd));
- }
- else
- {
- fAsterisk = false;
- }
- }
-
- // missing the enclosing "*/"
- log(Severity.ERROR, EXPECTED_ENDCOMMENT, null, lPosTextStart);
-
- // just pretend that the rest of the file was all one big comment
- long lPosEnd = source.getPosition();
- return new Token(lPosTokenStart, lPosEnd, Id.ENC_COMMENT,
- extractSource(lPosTextStart, lPosEnd));
- }
-
- /**
- * Get the next character of source code, making sure that it is the specified character.
- *
- * @param ch the desired character
- *
- * @return true iff the next character was matched
- */
- protected boolean match(char ch)
- {
- char chActual;
- try
- {
- chActual = nextChar();
- }
- catch (NoSuchElementException e)
- {
- log(Severity.ERROR, UNEXPECTED_EOF, null, m_source.getPosition());
- return false;
- }
-
- if (chActual != ch)
- {
- m_source.rewind();
- return false;
- }
-
- return true;
- }
-
- /**
- * Get the next character of source code, making sure that it is the specified character.
- *
- * @param ch the expected character
- *
- * @return true iff the next character was found
- */
- protected boolean expect(char ch)
- {
- char chActual;
- try
- {
- chActual = nextChar();
- }
- catch (NoSuchElementException e)
- {
- log(Severity.ERROR, UNEXPECTED_EOF, null, m_source.getPosition());
- return false;
- }
-
- if (chActual != ch)
- {
- log(Severity.ERROR, EXPECTED_CHAR,
- new Object[] {String.valueOf(ch), String.valueOf(chActual)},
- m_source.getPosition());
- return false;
- }
-
- return true;
- }
-
- protected boolean matchCaseInsens(String s)
- {
- for (int i = 0, c = s.length(); i < c; ++i)
- {
- char ch = s.charAt(i);
- if (match(ch) || match(Character.toLowerCase(ch)) || match(Character.toUpperCase(ch)))
- {
- continue;
- }
- return false;
- }
-
- char ch = peekChar();
- return !(isIdentifierPart(ch) && !isDigit(ch));
- }
-
- /**
- * Verify that the next character is NOT a number or an identifier char.
- */
- protected void peekNotIdentifierOrNumber()
- {
- char ch = nextChar();
- long lEnd = m_source.getPosition();
- m_source.rewind();
- if (ch >= '0' && ch <= '9' || isIdentifierPart(ch))
- {
- log(Severity.ERROR, UNEXPECTED_CHAR, new Object[] {ch}, m_source.getPosition(), lEnd);
- }
- }
-
- protected String extractSource(long lPosStart)
- {
- return extractSource(lPosStart, m_source.getPosition());
- }
-
- protected String extractSource(long lPosStart, long lPosEnd)
- {
- return m_source.toString(lPosStart, lPosEnd);
- }
-
- /**
- * Log an error (without error params) using the current position in the script as the end
- * position for the error.
- */
- public void log(Severity severity, String sCode, long lPosPrev, long lPosStart)
- {
- log(severity, sCode, lPosPrev, lPosStart, m_source.getPosition());
- }
-
- /**
- * Log an error (without error params).
- */
- protected void log(Severity severity, String sCode, long lPosPrev, long lPosStart, long lPosEnd)
- {
- if (lPosPrev == lPosStart)
- {
- m_source.rewind();
- lPosPrev = m_source.getPosition();
- m_source.next();
- }
-
- log(severity, sCode, new Object[]{extractSource(lPosPrev, lPosEnd)}, lPosStart, lPosEnd);
- }
-
- /**
- * Log an error using the current position in the script as the end position for the error.
- */
- protected void log(Severity severity, String sCode, Object[] aoParam, long lPosStart)
- {
- log(severity, sCode, aoParam, lPosStart, m_source.getPosition());
- }
-
- /**
- * Log an error.
- */
- protected void log(Severity severity, String sCode, Object[] aoParam, long lPosStart, long lPosEnd)
- {
- m_errorListener.log(severity, sCode, aoParam, m_source, lPosStart, lPosEnd);
- }
-
-
- // ----- helper methods ------------------------------------------------------------------------
-
- /**
- * Determine if the specified character is white-space.
- *
- * @param ch the character to evaluate
- *
- * @return true iff the character is defined as an XTC <i>SpacingElement</i>
- */
- public static boolean isWhitespace(char ch)
- {
- // optimize for the ASCII range
- if (ch < 128)
- {
- // this handles the following cases:
- // U+0009 9 HT Horizontal Tab
- // U+000A 10 LF Line Feed
- // U+000B 11 VT Vertical Tab
- // U+000C 12 FF Form Feed
- // U+000D 13 CR Carriage Return
- // U+001A 26 SUB End-of-File, or “control-Z”
- // U+001C 28 FS File Separator
- // U+001D 29 GS Group Separator
- // U+001E 30 RS Record Separator
- // U+001F 31 US Unit Separator
- // U+0020 32 SP Space
- // 2 1 0
- // 0FEDCBA9876543210FEDCBA9
- return ch <= 32 && ch >= 9 && ((1 << (ch-9)) & 0b111110100000000000011111) != 0;
- }
-
- // this handles the following cases:
- // U+0085 133 NEL Next Line
- // U+00A0 160 &nbsp; Non-breaking space
- // U+1680 5760 Ogham Space Mark
- // U+2000 8192 En Quad
- // U+2001 8193 Em Quad
- // U+2002 8194 En Space
- // U+2003 8195 Em Space
- // U+2004 8196 Three-Per-Em Space
- // U+2005 8197 Four-Per-Em Space
- // U+2006 8198 Six-Per-Em Space
- // U+2007 8199 Figure Space
- // U+2008 8200 Punctuation Space
- // U+2009 8201 Thin Space
- // U+200A 8202 Hair Space
- // U+2028 8232 LS Line Separator
- // U+2029 8233 PS Paragraph Separator
- // U+202F 8239 Narrow No-Break Space
- switch (ch)
- {
- case 0x0085:
- case 0x00A0:
- case 0x1680:
- case 0x2000:
- case 0x2001:
- case 0x2002:
- case 0x2003:
- case 0x2004:
- case 0x2005:
- case 0x2006:
- case 0x2007:
- case 0x2008:
- case 0x2009:
- case 0x200A:
- case 0x2028:
- case 0x2029:
- case 0x202F:
- case 0x205F:
- case 0x3000:
- return true;
-
- default:
- return false;
- }
- }
-
- /**
- * Determine if the specified character is a line terminator.
- *
- * @param ch the character to evaluate
- *
- * @return true iff the character is defined as an XTC <i>LineTerminator</i>
- */
- public static boolean isLineTerminator(char ch)
- {
- // optimize for the ASCII range
- if (ch < 128)
- {
- // this handles the following cases:
- // U+000A 10 LF Line Feed
- // U+000B 11 VT Vertical Tab
- // U+000C 12 FF Form Feed
- // U+000D 13 CR Carriage Return
- return ch >= 10 && ch <= 13;
- }
-
- // this handles the following cases:
- // U+0085 133 NEL Next Line
- // U+2028 8232 LS Line Separator
- // U+2029 8233 PS Paragraph Separator
- return ch == 0x0085 | ch == 0x2028 | ch == 0x2029;
- }
-
- /**
- * Determine if the specified character can be used as the first character of an identifier.
- *
- * @param ch the character to evaluate
- *
- * @return true iff the specified character can be the start of an
- * identifier
- */
- public static boolean isIdentifierStart(char ch)
- {
- return Character.isUnicodeIdentifierStart(ch) || ch == '_';
- }
-
- /**
- * Determine if the specified character can be part of an identifier.
- *
- * @param ch the character to evaluate
- *
- * @return true iff the specified character can be part of an identifier
- */
- public static boolean isIdentifierPart(char ch)
- {
- return Character.isUnicodeIdentifierPart(ch) || ch == '_';
- }
-
- /**
- * Get the next character of source code, but do not move the current location.
- */
- protected char peekChar()
- {
- char ch = m_source.next();
- m_source.rewind();
- return ch;
- }
-
- /**
- * Get the next character of source code, but do some additional checks on the character to make
- * sure it's legal, such as checking for an illegal SUB character.
- */
- protected char nextChar()
- {
- Source source = m_source;
- char ch = source.next();
-
- // it is illegal for the SUB aka EOF character to occur
- // anywhere in the source other than at the end
- if (ch == EOF && source.hasNext())
- {
- // back up to get the location of the SUB character
- long lPos = source.getPosition();
- source.rewind();
- long lStartPos = source.getPosition();
- source.setPosition(lPos);
-
- log(Severity.ERROR, UNEXPECTED_EOF, null, lStartPos);
- }
-
- return ch;
- }
-
- /**
- * Get the next character of source code, but do some additional checks on the character to make
- * sure it's legal, such as checking for an illegal SUB character.
- */
- protected char needCharOrElse(String sError)
- {
- try
- {
- return nextChar();
- }
- catch (NoSuchElementException e)
- {
- log(Severity.ERROR, sError, m_source.getPosition(), m_source.getPosition());
- }
-
- // already logged an error; just pretend we hit a closing brace (since all roads should have
- // gone there)
- return '}';
- }
-
-
- // ----- constants -----------------------------------------------------------------------------
-
- /**
- * Unicode: Horizontal Tab.
- */
- public static final char HT = 0x0009;
- /**
- * Unicode: Line Feed.
- */
- public static final char LF = 0x000A;
- /**
- * Unicode: Vertical Tab.
- */
- public static final char VT = 0x000B;
- /**
- * Unicode: Form Feed.
- */
- public static final char FF = 0x000C;
- /**
- * Unicode: Carriage Return.
- */
- public static final char CR = 0x000D;
- /**
- * Unicode: End-Of-File aka control-z aka SUB.
- */
- public static final char EOF = 0x001A;
- /**
- * Unicode: File Separator.
- */
- public static final char FS = 0x001C;
- /**
- * Unicode: Group Separator.
- */
- public static final char GS = 0x001D;
- /**
- * Unicode: Record Separator.
- */
- public static final char RS = 0x001E;
- /**
- * Unicode: Unit Separator.
- */
- public static final char US = 0x001F;
- /**
- * Unicode: Next Line.
- */
- public static final char NEL = 0x0085;
- /**
- * Unicode: Non-Breaking Space aka "&nbsp".
- */
- public static final char NBS = 0x00A0;
- /**
- * Unicode: Line Separator.
- */
- public static final char LS = 0x2028;
- /**
- * Unicode: Paragraph Separator.
- */
- public static final char PS = 0x2029;
-
- /**
- * Unexpected End-Of-File (SUB character).
- */
- public static final String UNEXPECTED_EOF = "LEXER-01";
- /**
- * Expected a comment-ending "star slash" but never found one.
- */
- public static final String EXPECTED_ENDCOMMENT = "LEXER-02";
- /**
- * A character was encountered that cannot be the start of a valid token.
- */
- public static final String ILLEGAL_CHAR = "LEXER-03";
- /**
- * Number format exception.
- */
- public static final String ILLEGAL_NUMBER = "LEXER-04";
- /**
- * An illegal character literal, missing closing quote.
- */
- public static final String CHAR_NO_TERM = "LEXER-05";
- /**
- * An illegally escaped character literal.
- */
- public static final String CHAR_BAD_ESC = "LEXER-06";
- /**
- * An illegal character literal missing the character.
- */
- public static final String CHAR_NO_CHAR = "LEXER-07";
- /**
- * An illegally terminated string literal.
- */
- public static final String STRING_NO_TERM = "LEXER-08";
- /**
- * An illegally escaped string literal.
- */
- public static final String STRING_BAD_ESC = "LEXER-09";
- /**
- * An illegal hex (binary) literal.
- */
- public static final String ILLEGAL_HEX = "LEXER-10";
- /**
- * Expected {0}; found {1}.
- */
- public static final String EXPECTED_CHAR = "LEXER-11";
- /**
- * {0} digits were required; only {1} digits were found.
- */
- public static final String EXPECTED_DIGITS = "LEXER-12";
- /**
- * Invalid ISO-8601 date {0}; ...
- */
- public static final String BAD_DATE = "LEXER-13";
- /**
- * Invalid ISO-8601 time {0}; ...
- */
- public static final String BAD_TIME_OF_DAY = "LEXER-14";
- /**
- * Invalid ISO-8601 datetime {0}; ...
- */
- public static final String BAD_TIME = "LEXER-15";
- /**
- * Invalid ISO-8601 timezone {0}; ...
- */
- public static final String BAD_TIMEZONE = "LEXER-16";
- /**
- * Invalid ISO-8601 duration {0}; ...
- */
- public static final String BAD_DURATION = "LEXER-17";
- /**
- * Unexpected character: {0}
- */
- public static final String UNEXPECTED_CHAR = "LEXER-18";
-
-
- // ----- data members --------------------------------------------------------------------------
-
- /**
- * The Source to parse.
- */
- private final Source m_source;
-
- /**
- * The ErrorListener to report errors to.
- */
- private final ErrorListener m_errorListener;
-
- /**
- * Keeps track of whether whitespace was encountered.
- */
- private boolean m_fWhitespace;
- } \ No newline at end of file
diff --git a/common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/Severity.java b/common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/Severity.java
deleted file mode 100644
index 9b01a7e4..00000000
--- a/common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/Severity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * The Initial Developer of the Original Software is
- * The Ecstasy Project (https://github.com/xtclang/).
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-package com.seaofnodes.simple.lexer.ecstasy;
-
-
-/**
- * Severity levels.
- */
-public enum Severity
- {
- NONE, INFO, WARNING, ERROR, FATAL;
-
- public String desc()
- {
- String sName = name();
- StringBuilder sb = new StringBuilder(sName.length());
- sb.append(sName.charAt(0));
- sb.append(sName.substring(1).toLowerCase());
- return sb.toString();
- }
- }
diff --git a/common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/Source.java b/common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/Source.java
deleted file mode 100644
index c0da5d5f..00000000
--- a/common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/Source.java
+++ /dev/null
@@ -1,826 +0,0 @@
-/*
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * The Initial Developer of the Original Software is
- * The Ecstasy Project (https://github.com/xtclang/).
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-package com.seaofnodes.simple.lexer.ecstasy;
-
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-
-import java.nio.file.Path;
-
-import java.util.Arrays;
-import java.util.NoSuchElementException;
-
-import com.seaofnodes.util.Handy;
-
-import static com.seaofnodes.simple.lexer.ecstasy.Lexer.isLineTerminator;
-import static com.seaofnodes.util.Handy.*;
-
-
-/**
- * A representation of an XTC source code file, handling the first two phases of lexical analysis
- * (line termination, location and unicode escapes).
- */
-public class Source implements Cloneable
- {
- // ----- constructors --------------------------------------------------------------------------
-
- /**
- * Construct a Source directly from a String of XTC source code.
- *
- * @param sScript the XTC source code, as a String
- */
- public Source(String sScript)
- {
- this(sScript.toCharArray());
- }
-
- /**
- * Construct a Source directly from a String of XTC source code.
- *
- * @param sScript the XTC source code, as a String
- */
- public Source(String sScript, int cDepth)
- {
- this(sScript.toCharArray());
- m_cDirDepth = cDepth;
- }
-
- /**
- * Construct a Source by reading the XTC source code from a file.
- *
- * @param file the File containing the XTC source code to load
- *
- * @param cDepth the number of directories up the source code can include files from
- *
- * @throws IOException
- */
- public Source(File file, int cDepth)
- throws IOException
- {
- this(readFileChars(file));
- m_file = file;
- m_cDirDepth = cDepth;
- setFileName(file.getPath());
- }
-
- /**
- * Construct a Source from an input stream.
- *
- * @param stream the InputStream
- */
- public Source(InputStream stream)
- {
- this(fromInputStream(stream));
- }
-
- /**
- * Read a char[] from a stream. Inefficient, but predictable.
- *
- * @param stream the stream to vacuum
- *
- * @return the contents of the stream, as a char[]
- */
- private static char[] fromInputStream(InputStream stream)
- {
- try
- {
- StringBuilder sb = new StringBuilder();
- int n;
- while ((n = stream.read()) >= 0)
- {
- sb.append((char) n);
- }
- return sb.toString().toCharArray();
- }
- catch (IOException e)
- {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Construct a Source directly from a character array containing the XTC
- * source code. Note that the passed array is retained -- not copied! -- by
- * this constructor.
- *
- * @param ach the XTC source code, as a character array
- */
- Source(char[] ach)
- {
- assert ach != null;
- m_ach = ach;
- m_cch = ach.length;
- }
-
-
- // ----- public API ----------------------------------------------------------------------------
-
- /**
- * @return the file name, if one has been configured
- */
- public String getFileName()
- {
- return m_sFile;
- }
-
- /**
- * @return the simple file name, if a file is available
- */
- public String getSimpleFileName()
- {
- return m_file == null ? "<no file>" : m_file.getName();
- }
-
- /**
- * Specify the name of the file that the source comes from. This is automatically configured by
- * the File-based constructor.
- *
- * @param sFile the file name
- */
- public void setFileName(String sFile)
- {
- if (m_sFile == null)
- {
- m_sFile = sFile;
- }
- else if (!m_sFile.equals(sFile))
- {
- throw new IllegalStateException("file name cannot be modified");
- }
- }
-
- /**
- * Determine the File referenced from inside another source file using the "File" or "Dir"
- * BNF constructions.
- *
- * @param sFile a string put together from identifier tokens, "." tokens, "/" tokens, "./"
- * tokens, and "../" tokens, following the rules defined in the Ecstasy BNF
- *
- * @return the File for the file or directory, or null if unresolvable
- */
- public File resolvePath(String sFile)
- {
- if (m_file == null || sFile.length() == 0)
- {
- return null;
- }
-
- File file = m_file.getParentFile();
- boolean fRoot = sFile.startsWith("/");
- m_cUp = 0;
- if (fRoot)
- {
- // it's not absolute; it's relative to the directory containing the module
- while (m_cUp < m_cDirDepth)
- {
- file = file.getParentFile();
- assert file != null;
- ++m_cUp;
- }
- sFile = sFile.substring(1);
- }
-
- if (sFile.length() == 0)
- {
- return file.isDirectory() ? file : null;
- }
-
- boolean fRequireDir = sFile.endsWith("/");
- if (fRequireDir)
- {
- sFile = sFile.substring(0, sFile.length()-1);
- }
-
- String[] segments = Handy.parseDelimitedString(sFile, '/');
- for (String segment : segments)
- {
- if (segment.length() == 0)
- {
- return null;
- }
-
- if (segment.equals("."))
- {
- // nothing to do
- }
- else if (segment.equals(".."))
- {
- file = file.getParentFile();
- if (file == null || ++m_cUp > m_cDirDepth)
- {
- return null;
- }
- }
- else
- {
- if (file.exists() && !file.isDirectory())
- {
- return null;
- }
-
- file = new File(file, segment);
- --m_cUp;
- }
- }
-
- if (!file.exists())
- {
- // the file may be in the "symmetrical" resources directory
- File fileSrc = getSubDir("x");
- File fileRes = getSubDir("resources");
- if (fileSrc != null && fileRes != null)
- {
- if (fRoot)
- {
- file = new File(fileRes, sFile);
- }
- else
- {
- Path path = file.toPath().normalize();
- Path pathSrc = fileSrc.toPath().normalize();
- Path pathRes = fileRes.toPath().normalize();
- Path pathRel = pathSrc.relativize(path);
- file = pathRes.resolve(pathRel).toFile();
- }
- if (!file.exists())
- {
- return null;
- }
- }
- }
-
- return fRequireDir && !file.isDirectory() ? null : file;
- }
- private transient int m_cUp; // Java needs multiple return values. This is a hack.
-
- /**
- * @return the directory containing the module, or null if it cannot be determined
- */
- private File getTopDir()
- {
- if (m_file == null)
- {
- return null;
- }
-
- File file = m_file.getAbsoluteFile().getParentFile();
- for (int i = 0, c = m_cDirDepth; i < c; ++i)
- {
- file = file.getParentFile();
- if (file == null)
- {
- return null;
- }
- }
- return file;
- }
-
- /**
- * @param sChild the name of the child directory
- *
- * @return the specified directory at the same level as the top directory, if it exists
- */
- private File getSubDir(String sChild)
- {
- File file = getTopDir();
- if (file != null && file.exists() && file.isDirectory())
- {
- file = file.getParentFile();
- if (file != null && file.exists() && file.isDirectory())
- {
- file = new File(file, sChild);
- if (file.exists() && file.isDirectory())
- {
- return file;
- }
- }
- }
- return null;
- }
-
-
- /**
- * Load a file (as text) referenced from inside another source file.
- *
- * @param sFile a string put together from identifier tokens, "/" tokens, "./" tokens, and
- * "../" tokens
- *
- * @return the Source, or null
- */
- public Source includeString(String sFile)
- throws IOException
- {
- File file = resolvePath(sFile);
- if (file == null)
- {
- return null;
- }
-
- if (file.equals(m_file))
- {
- return clone();
- }
-
- if (file.exists() && file.isFile() && file.canRead())
- {
- return new Source(file, m_cDirDepth - m_cUp);
- }
-
- return null;
- }
-
- /**
- * Load a file (as binary) referenced from inside another source file.
- *
- * @param sFile a string put together from identifier tokens, "/" tokens, "./" tokens, and
- * "../" tokens
- *
- * @return the binary contents of the file, or null
- */
- public byte[] includeBinary(String sFile)
- throws IOException
- {
- File file = resolvePath(sFile);
- if (file == null)
- {
- return null;
- }
-
- if (file.exists() && file.isFile() && file.canRead())
- {
- return Handy.readFileBytes(file);
- }
-
- return null;
- }
-
- /**
- * Determine if there are more characters in the source.
- *
- * @return true iff there are still more characters
- */
- public boolean hasNext()
- {
- return m_of < m_cch;
- }
-
- /**
- * Obtain the next character of the source. Note that unicode escapes are
- * processed such that the sequence of characters "U+0024" in the source
- * would return only the single character '$'. Similarly, the combination
- * of CR+LF ("\r\n") is returned as the single character LF ('\n').
- *
- * @return the next character from the source
- *
- * @throws NoSuchElementException if an attempt is made to advance past
- * the end
- */
- public char next()
- {
- final char[] ach = m_ach;
- final int cch = m_cch;
-
- int of = m_of;
- if (of >= cch)
- {
- throw new NoSuchElementException();
- }
-
- char ch = ach[of++];
-
- // check for new line
- if (Lexer.isLineTerminator(ch))
- {
- // handle the special case of CR:LF by treating it as a single LF
- // character
- if (ch == '\r' && of < cch && ach[of] == '\n')
- {
- ++of;
- ch = '\n';
- }
-
- ++m_iLine;
- m_iLineOffset = 0;
- }
- else
- {
- int cchAdjust = 1;
-
- // check for a unicode escape
- if (ch == '\\' && of + 4 < cch
- && isHexit(ach[of+1])
- && isHexit(ach[of+2])
- && isHexit(ach[of+3])
- && isHexit(ach[of+4]))
- {
- final char chU = ach[of];
- if (chU == 'u')
- {
- // 4-hexit unicode escape
- int nch = hexitValue(ach[of+1]) << 12
- | hexitValue(ach[of+2]) << 8
- | hexitValue(ach[of+3]) << 4
- | hexitValue(ach[of+4]);
-
- ch = (char) nch;
- of += 5;
- cchAdjust += 5;
- m_fEscapesEncountered = true;
- }
- else if (chU == 'U' && of + 8 < cch
- && isHexit(ach[of+5])
- && isHexit(ach[of+6])
- && isHexit(ach[of+7])
- && isHexit(ach[of+8]))
- {
- // 8-hexit unicode escape
- int nch = hexitValue(ach[of+1]) << 28
- | hexitValue(ach[of+2]) << 24
- | hexitValue(ach[of+3]) << 20
- | hexitValue(ach[of+4]) << 16
- | hexitValue(ach[of+5]) << 12
- | hexitValue(ach[of+6]) << 8
- | hexitValue(ach[of+7]) << 4
- | hexitValue(ach[of+8]);
-
- // unfortunately Java does not support characters with code-
- // points beyond 16 bits; we could theoretically split this
- // up into a Unicode "surrogate pair", but that is not
- // supported by this class at this time
- ch = nch > Character.MAX_VALUE ? '?' : (char) nch;
- of += 9;
- cchAdjust += 9;
- m_fEscapesEncountered = true;
- }
- }
-
- // a negative line offset indicates that the offset is not currently
- // being tracked because a line terminator was put back
- if (m_iLineOffset >= 0)
- {
- m_iLineOffset += cchAdjust;
- }
- }
-
- m_of = of;
- return ch;
- }
-
- /**
- * Undo a previously made call to the {@link #next()} method by "rewinding"
- * one character. In parsing terminology, this is often referred to as a
- * "put back char" operation.
- *
- * @throws NoSuchElementException if an attempt is made to rewind past the
- * beginning
- */
- public void rewind()
- {
- int of = m_of;
- if (of <= 0)
- {
- throw new NoSuchElementException();
- }
-
- // determine what the immediately preceding character was
- --of;
- final char[] ach = m_ach;
- final char ch = ach[of];
- // if the line feed is immediately preceded by a carriage return, then
- // back up past them both (they are treated as a single line feed)
- if (isLineTerminator(ch))
- {
- if (ch == '\n' && of > 0 && ach[of-1] == '\r')
- {
- --of;
- }
-
- --m_iLine;
-
- // instead of determining the line offset in the previous line that
- // we just rewound to, defer the calculation until it is needed
- m_iLineOffset = -1;
- }
- else
- {
- int cchAdjust = 1;
-
- if (m_fEscapesEncountered
- && isHexit(ch) && of >= 5
- && isHexit(ach[of-1])
- && isHexit(ach[of-2])
- && isHexit(ach[of-3]))
- {
- if (ach[of-5] == '\\' && ach[of-4] == 'u')
- {
- of -= 5;
- cchAdjust += 5;
- }
- else if (of >= 9 && ach[of-9] == '\\' && ach[of-8] == 'U'
- && isHexit(ach[of-7])
- && isHexit(ach[of-6])
- && isHexit(ach[of-5])
- && isHexit(ach[of-4]))
- {
- of -= 9;
- cchAdjust += 9;
- }
- }
-
- // a negative line offset indicates that the offset is not currently
- // being tracked because a line terminator was put back
- if (m_iLineOffset >= 0)
- {
- m_iLineOffset -= cchAdjust;
- }
- }
-
- m_of = of;
- }
-
- /**
- * Determine the current line number within the source.
- *
- * @return the zero-based line number
- */
- public int getLine()
- {
- return m_iLine;
- }
-
- /**
- * Determine the offset within the current line of the source.
- *
- * @return the zero-based offset within the current line
- */
- public int getOffset()
- {
- int iLineOffset = m_iLineOffset;
- if (iLineOffset < 0)
- {
- // go back until a line terminator is found
- iLineOffset = 0;
- for (int of = m_of; of > 0; )
- {
- if (isLineTerminator(m_ach[--of]))
- {
- break;
- }
-
- ++iLineOffset;
- }
-
- m_iLineOffset = iLineOffset;
- }
-
- return iLineOffset;
- }
-
- /**
- * Obtain a token that represents the current location within the source.
- *
- * @return a position token
- */
- public long getPosition()
- {
- // use the line-offset accessor to force the re-calculation if necessary
- final int iLineOffset = getOffset();
-
- // up to 24 bits for offset, 20 bits for line and line offset
- assert m_of >= 0;
- assert m_iLine >= 0;
- assert iLineOffset >= 0;
- if (m_of > 0xFFFFFF || m_iLine > 0xFFFFF || iLineOffset >= 0xFFFFF)
- {
- throw new IllegalStateException();
- }
-
- return (((long) m_of & 0xFFFFFF) << 40)
- | (((long) m_iLine & 0x0FFFFF) << 20)
- | (((long) iLineOffset & 0x0FFFFF) );
- }
-
- /**
- * Using a previously returned position token, set the current position
- * within the source to the position indicated by the token.
- *
- * @param lPosition a previously returned position token
- */
- public void setPosition(long lPosition)
- {
- m_of = ((int) (lPosition >>> 40)) & 0xFFFFFF;
- m_iLine = ((int) (lPosition >>> 20)) & 0x0FFFFF;
- m_iLineOffset = ((int) (lPosition )) & 0x0FFFFF;
- }
-
- /**
- * Determine the line number from a previously returned position token.
- *
- * @param lPosition a position token
- *
- * @return the zero-based line number of the specified position
- */
- public static int calculateLine(long lPosition)
- {
- return ((int) (lPosition >>> 20)) & 0x0FFFFF;
- }
-
- /**
- * Determine the offset within the line from a previously returned position
- * token.
- *
- * @param lPosition a position token
- *
- * @return the zero-based offset within the line of the specified position
- */
- public static int calculateOffset(long lPosition)
- {
- return ((int) lPosition) & 0x0FFFFF;
- }
-
- /**
- * Obtain the string of characters starting from one position and proceeding
- * to another position. The current position is not affected by this method.
- *
- * @param lPositionFrom a position token to start from (inclusive)
- * @param lPositionTo a position token to end at (exclusive)
- *
- * @return the String of characters from the first to the second specified
- * position
- */
- public String toString(long lPositionFrom, long lPositionTo)
- {
- long lPositionSave = getPosition();
-
- setPosition(lPositionFrom);
- final int ofEnd = ((int) (lPositionTo >>> 40)) & 0xFFFFFF;
- assert ofEnd >= m_of;
- char[] ach = new char[ofEnd - m_of];
- int cch = 0;
- while (m_of < ofEnd)
- {
- ach[cch++] = next();
- }
-
- setPosition(lPositionSave);
- return new String(ach, 0, cch);
- }
-
- /**
- * @return a clone of this Source, but with the position reset to the beginning of the source
- * code
- */
- public Source clone()
- {
- try
- {
- Source that = (Source) super.clone();
- that.reset();
- return that;
- }
- catch (CloneNotSupportedException e)
- {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Reset the position to the very beginning of the source code.
- */
- public void reset()
- {
- m_of = 0;
- m_iLine = 0;
- m_iLineOffset = 0;
- }
-
- /**
- * @return a String intended to make this usable in a debugger
- */
- public String toString()
- {
- long lCur = getPosition();
-
- String sIntro = "...";
- for (int i = 0; i < 20; ++i)
- {
- try
- {
- rewind();
- }
- catch (NoSuchElementException e)
- {
- sIntro = "";
- break;
- }
- }
- long lPre = getPosition();
- setPosition(lCur);
-
- String sEpilogue = "...";
- for (int i = 0; i < 20; ++i)
- {
- try
- {
- next();
- }
- catch (NoSuchElementException e)
- {
- sEpilogue = "(EOF)";
- break;
- }
- }
- long lPost = getPosition();
- setPosition(lCur);
-
- String sPre = toString(lPre, lCur);
- String sPost = toString(lCur, lPost);
-
- StringBuilder sb = new StringBuilder(sIntro);
- appendString(sb, sPre);
- char[] achIndent = new char[sb.length()];
- Arrays.fill(achIndent, ' ');
-
- appendString(sb, sPost)
- .append(sEpilogue)
- .append('\n')
- .append(new String(achIndent))
- .append('^');
- return sb.toString();
- }
-
- /**
- * Obtain the underlying data that the source represents.
- *
- * @return the whole of the unprocessed source, as a String
- */
- public String toRawString()
- {
- return new String(m_ach);
- }
-
-
- // ----- data members --------------------------------------------------------------------------
-
- /**
- * The source code.
- */
- private final char[] m_ach;
-
- /**
- * The length of the source code.
- */
- private final int m_cch;
-
- /**
- * The current offset within the source code.
- */
- private int m_of;
-
- /**
- * The current source code line number.
- */
- private int m_iLine;
-
- /**
- * The current offset within the current line of source code.
- */
- private int m_iLineOffset;
-
- /**
- * Set to true iff Unicode escapes have been encountered.
- */
- private boolean m_fEscapesEncountered;
-
- /**
- * The file name that the source comes from.
- */
- private String m_sFile;
-
- /**
- * The file that the source comes from.
- */
- private File m_file;
-
- /**
- * The directory depth within the module of the file that the source comes from.
- */
- private int m_cDirDepth;
- } \ No newline at end of file
diff --git a/common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/Token.java b/common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/Token.java
deleted file mode 100644
index 18cb5759..00000000
--- a/common/src/main/java/com/seaofnodes/simple/lexer/ecstasy/Token.java
+++ /dev/null
@@ -1,877 +0,0 @@
-/*
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * The Initial Developer of the Original Software is
- * The Ecstasy Project (https://github.com/xtclang/).
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-
-package com.seaofnodes.simple.lexer.ecstasy;
-
-
-import java.util.HashMap;
-import java.util.Map;
-
-import static com.seaofnodes.util.Handy.appendChar;
-import static com.seaofnodes.util.Handy.appendString;
-
-
-/**
- * Representation of a language token.
- */
-public class Token
- implements Cloneable
- {
- // ----- constructors --------------------------------------------------------------------------
-
- /**
- * Construct an XTC token.
- *
- * @param lStartPos starting position in the Source (inclusive)
- * @param lEndPos ending position in the Source (exclusive)
- * @param id identity of the token
- */
- public Token(long lStartPos, long lEndPos, Id id)
- {
- this(lStartPos, lEndPos, id, null);
- }
-
- /**
- * Construct an XTC token.
- *
- * @param lStartPos starting position in the Source (inclusive)
- * @param lEndPos ending position in the Source (exclusive)
- * @param id identity of the token
- * @param oValue value of the token (if it is a literal)
- */
- public Token(long lStartPos, long lEndPos, Id id, Object oValue)
- {
- m_lStartPos = lStartPos;
- m_lEndPos = lEndPos;
- m_id = id;
- m_oValue = oValue;
- }
-
- /**
- * Record whitespace information onto the token.
- *
- * @param fWhitespaceBefore
- * @param fWhitespaceAfter
- */
- public void noteWhitespace(boolean fWhitespaceBefore, boolean fWhitespaceAfter)
- {
- m_fLeadingWhitespace = fWhitespaceBefore;
- m_fTrailingWhitespace = fWhitespaceAfter;
- }
-
-
- // ----- accessors -----------------------------------------------------------------------------
-
- /**
- * Determine the starting position in the source at which this token occurs.
- *
- * @return the Source position of the token
- */
- public long getStartPosition()
- {
- return m_lStartPos;
- }
-
- /**
- * Determine the ending position (exclusive) in the source for this token.
- *
- * @return the Source position of the end of the token
- */
- public long getEndPosition()
- {
- return m_lEndPos;
- }
-
- /**
- * Determine if this token follows whitespace in the source.
- *
- * @return true iff this token follows whitespace
- */
- public boolean hasLeadingWhitespace()
- {
- return m_fLeadingWhitespace;
- }
-
- /**
- * Determine if this token precedes whitespace in the source.
- *
- * @return true iff this token is followed by whitespace
- */
- public boolean hasTrailingWhitespace()
- {
- return m_fTrailingWhitespace;
- }
-
- /**
- * Determine the identity of the token.
- *
- * @return the identity of the token
- */
- public Id getId()
- {
- return m_id;
- }
-
- /**
- * Determine the value of the token.
- *
- * @return the value of the token, or null
- */
- public Object getValue()
- {
- return m_oValue;
- }
-
- /**
- * Helper to get the value as text, if there is a value, otherwise to get the token id's text.
- * Use only for debugging or when the token is known to be a String.
- *
- * @return the String form of the token
- */
- public String getValueText()
- {
- return m_oValue == null ? m_id.TEXT : m_oValue.toString();
- }
-
- /**
- * @return true iff this is an identifier is a "special name"
- */
- public boolean isSpecial()
- {
- if (m_id == Id.IDENTIFIER)
- {
- Id keywordId = Id.valueByContextSensitiveText((String) getValue());
- return keywordId != null && keywordId.Special;
- }
-
- return false;
- }
-
- /**
- * @return true iff this is an identifier is a context-sensitive name
- */
- public boolean isContextSensitive()
- {
- return m_id == Id.IDENTIFIER && Id.valueByContextSensitiveText((String) getValue()) != null;
- }
-
- public boolean isComment()
- {
- return m_id == Id.ENC_COMMENT || m_id == Id.EOL_COMMENT;
- }
-
- /**
- * If the token is an identifier that is also a context sensitive keyword, obtain that keyword.
- *
- * @return a keyword token that represents the same text as this identifier token
- */
- public Token convertToKeyword()
- {
- if (m_id != Id.IDENTIFIER)
- {
- throw new IllegalStateException("not an identifier! (" + this + ")");
- }
-
- Id id = Id.valueByContextSensitiveText((String) getValue());
- if (id == null)
- {
- throw new IllegalStateException("missing context sensitive keyword for: " + getValue());
- }
-
- return new Token(m_lStartPos, m_lEndPos, id, m_oValue);
- }
-
- /**
- * If the token is a context sensitive keyword that was an identifier, obtain the identifier.
- *
- * @return a token that is not a context sensitive keyword
- */
- public Token desensitize()
- {
- return m_id.ContextSensitive ? new Token(m_lStartPos, m_lEndPos, Id.IDENTIFIER, m_oValue) : this;
- }
-
- /**
- * Refine the token identity to a more specific identity.
- *
- * @param id the refined identity
- */
- Token refine(Id id)
- {
- m_id = id;
- return this;
- }
-
- /**
- * Allow a token to be "peeled off" the front of this token, if possible. This mutates this
- * token, such that it is what remains after the returned token was peeled off.
- *
- * @param id the token to peel off of this token
- * @param source the source from which this token was created, which is necessary in order to
- * calculate the exact position between any peeled-off token and this token
- *
- * @return the requested token that was peeled off of this token, if possible, otherwise null
- */
- public Token peel(Id id, Source source)
- {
- Id newId = null;
-
- if (id == Id.COMP_GT)
- {
- switch (m_id)
- {
- default:
- return null;
-
- case USHR_ASN:
- newId = Id.SHR_ASN;
- break;
-
- case USHR:
- newId = Id.SHR;
- break;
-
- case SHR_ASN:
- newId = Id.COMP_GTEQ;
- break;
-
- case SHR:
- newId = Id.COMP_GT;
- break;
-
- case COMP_GTEQ:
- newId = Id.ASN;
- break;
- }
- }
- else if (id == Id.COMP_LT)
- {
- switch (m_id)
- {
- default:
- return null;
-
- case SHL_ASN:
- newId = Id.COMP_LTEQ;
- break;
-
- case SHL:
- newId = Id.COMP_LT;
- break;
-
- // note: there are no legitimate use cases for peeling "<" off of "<=" or "<=>"
- // case COMP_LTEQ:
- // newId = Id.ASN;
- // break;
- }
- }
-
- if (newId == null)
- {
- return null;
- }
-
- // get the location of "this" token
- long start = m_lStartPos;
-
- // get the location of the end of the new peeled token / start of this token (adjusted)
- long current = source.getPosition();
- source.setPosition(start);
- source.next();
- long middle = source.getPosition();
- source.setPosition(current);
-
- // adjust this token
- m_lStartPos = middle;
- m_id = newId;
-
- // return the new token
- return new Token(start, middle, id);
- }
-
- /**
- * Allow a token to be "un-peeled off" from the front of that following token, if possible.
- *
- * @param that the token following this token
- *
- * @return the new token, or null if the two tokens cannot be annealed
- */
- public Token anneal(Token that)
- {
- if (m_id == Id.COMP_LT && this.m_lEndPos == that.m_lStartPos)
- {
- switch (that.m_id)
- {
- case COMP_LTEQ:
- return new Token(this.m_lStartPos, that.m_lEndPos, Id.SHL_ASN);
-
- case COMP_LT:
- return new Token(this.m_lStartPos, that.m_lEndPos, Id.SHL);
- }
- }
-
- return null;
- }
-
- /**
- * Obtain the actual string of characters from the source code for this token.
- *
- * @param source the source code
- *
- * @return the string of characters corresponding to this token, extracted from the source
- */
- public String getString(Source source)
- {
- return source.toString(m_lStartPos, m_lEndPos);
- }
-
- /**
- * Helper to log an error related to this Token.
- *
- * @param severity the severity level of the error; one of {@link Severity#INFO},
- * {@link Severity#WARNING}, {@link Severity#ERROR}, or {@link Severity#FATAL}
- * @param sCode the error code that identifies the error message
- * @param aoParam the parameters for the error message; may be null
- *
- * @return true to attempt to abort the process that reported the error, or false to attempt to
- * continue the process
- */
- public void log(ErrorListener errs, Source source, Severity severity, String sCode, Object... aoParam)
- {
- if (aoParam == null || aoParam.length == 0)
- {
- aoParam = new Object[] {source == null ? toString() : getString(source)};
- }
-
- errs.log(severity, sCode, aoParam, source,
- source == null ? 0L : getStartPosition(), source == null ? 0L : getEndPosition());
- }
-
-
- // ----- Object methods ------------------------------------------------------------------------
-
- public String toDebugString()
- {
- return "[" +
- Source.calculateLine(m_lStartPos) +
- "," +
- Source.calculateOffset(m_lStartPos) +
- " - " +
- Source.calculateLine(m_lEndPos) +
- "," +
- Source.calculateOffset(m_lEndPos) +
- "] " + this;
- }
-
- @Override
- public String toString()
- {
- StringBuilder sb = new StringBuilder();
-
- switch (m_id)
- {
- case LIT_CHAR:
- {
- sb.append('\'');
- appendChar(sb, (Character) m_oValue);
- sb.append('\'');
- }
- break;
-
- case LIT_STRING:
- sb.append('\"');
- appendString(sb, (String) m_oValue);
- sb.append('\"');
- break;
-
- case LIT_INT:
- case LIT_DEC:
- case LIT_FLOAT:
- sb.append(m_oValue);
- break;
-
- case LIT_BIT:
- case LIT_NIBBLE:
- case LIT_INT8:
- case LIT_INT16:
- case LIT_INT32:
- case LIT_INT64:
- case LIT_INT128:
- case LIT_INTN:
- case LIT_UINT8:
- case LIT_UINT16:
- case LIT_UINT32:
- case LIT_UINT64:
- case LIT_UINT128:
- case LIT_UINTN:
- case LIT_DEC32:
- case LIT_DEC64:
- case LIT_DEC128:
- case LIT_DECN:
- case LIT_FLOAT16:
- case LIT_FLOAT32:
- case LIT_FLOAT64:
- case LIT_FLOAT128:
- case LIT_FLOATN:
- case LIT_BFLOAT16:
- case TYPE_INT:
- case TYPE_FLOAT:
- case TYPE_CHAR:
- sb.append(m_id.TEXT)
- .append(':')
- .append(m_oValue);
- break;
-
- case IDENTIFIER:
- sb.append("name:")
- .append(m_oValue);
- break;
-
- case ENC_COMMENT:
- {
- String sComment = (String) m_oValue;
- if (sComment.length() > 47)
- {
- sComment = sComment.substring(0, 44) + "...";
- }
- appendString(sb.append("/*"), sComment).append("*/");
- }
- break;
-
- case EOL_COMMENT:
- {
- String sComment = (String) m_oValue;
- if (sComment.length() > 50)
- {
- sComment = sComment.substring(0, 47) + "...";
- }
- appendString(sb.append("//"), sComment);
- }
- break;
-
- default:
- sb.append(m_id.TEXT);
- break;
- }
-
- return sb.toString();
- }
-
- public Token clone()
- {
- try
- {
- return (Token) super.clone();
- }
- catch (CloneNotSupportedException e)
- {
- throw new IllegalStateException(e);
- }
- }
-
-
- // ----- Token identities ----------------------------------------------------------------------
-
- /**
- * Token Identity.
- */
- public enum Id
- {
- COLON (":" ),
- SEMICOLON (";" ),
- COMMA ("," ),
- DOT ("." ),
- I_RANGE_I (".." ), // inclusive range
- E_RANGE_I (">.." ), // exclusive start
- I_RANGE_E ("..<" ), // exclusive end
- E_RANGE_E (">..<" ), // exclusive range
- DIR_CUR ("./" ),
- DIR_PARENT ("../" ),
- STR_FILE ("$" ),
- BIN_FILE ("#" ),
- AT ("@" ),
- COND ("?" ),
- L_PAREN ("(" ),
- ASYNC_PAREN ("^(" ),
- R_PAREN (")" ),
- L_CURLY ("{" ),
- R_CURLY ("}" ),
- L_SQUARE ("[" ),
- R_SQUARE ("]" ),
- ADD ("+" ),
- SUB ("-" ),
- MUL ("*" ),
- DIV ("/" ),
- MOD ("%" ),
- DIVREM ("/%" ),
- SHL ("<<" ),
- SHR (">>" ),
- USHR (">>>" ),
- BIT_AND ("&" ),
- BIT_OR ("|" ),
- BIT_XOR ("^" ),
- BIT_NOT ("~" ),
- NOT ("!" ),
- COND_AND ("&&" ),
- COND_XOR ("^^" ),
- COND_OR ("||" ),
- COND_ELSE ("?:" ),
- ASN ("=" ),
- ADD_ASN ("+=" ),
- SUB_ASN ("-=" ),
- MUL_ASN ("*=" ),
- DIV_ASN ("/=" ),
- MOD_ASN ("%=" ),
- SHL_ASN ("<<=" ),
- SHR_ASN (">>=" ),
- USHR_ASN (">>>=" ),
- BIT_AND_ASN ("&=" ),
- BIT_OR_ASN ("|=" ),
- BIT_XOR_ASN ("^=" ),
- COND_AND_ASN ("&&=" ),
- COND_OR_ASN ("||=" ),
- COND_ASN (":=" ),
- COND_NN_ASN ("?=" ), // NN -> Not Null
- COND_ELSE_ASN("?:=" ),
- COMP_EQ ("==" ),
- COMP_NEQ ("!=" ),
- COMP_ORD ("<=>" ),
- COMP_LT ("<" ),
- COMP_LTEQ ("<=" ),
- COMP_GT (">" ),
- COMP_GTEQ (">=" ),
- INC ("++" ),
- DEC ("--" ),
- LAMBDA ("->" ),
- ASN_EXPR ("<-" ),
- ANY ("_" ),
- ALLOW ("allow" , true),
- AS ("as" ),
- ASSERT ("assert" ),
- ASSERT_RND ("assert:rnd" ),
- ASSERT_ARG ("assert:arg" ),
- ASSERT_BOUNDS("assert:bounds" ),
- ASSERT_TODO ("assert:TODO" ),
- ASSERT_ONCE ("assert:once" ),
- ASSERT_TEST ("assert:test" ),
- ASSERT_DBG ("assert:debug" ),
- AVOID ("avoid" , true),
- BREAK ("break" ),
- CASE ("case" ),
- CATCH ("catch" ),
- CLASS ("class" ),
- CONDITIONAL ("conditional" ),
- CONST ("const" ),
- CONSTRUCT ("construct" ),
- CONTINUE ("continue" ),
- DEFAULT ("default" ),
- DELEGATES ("delegates" , true),
- DESIRED ("desired" , true),
- DO ("do" ),
- ELSE ("else" ),
- EMBEDDED ("embedded" , true),
- ENUM ("enum" ),
- EXTENDS ("extends" , true),
- FINALLY ("finally" ),
- FOR ("for" ),
- FUNCTION ("function" ),
- IF ("if" ),
- IMMUTABLE ("immutable" ),
- IMPLEMENTS ("implements" , true),
- IMPORT ("import" ),
- INCORPORATES ("incorporates" , true),
- INJECT ("inject" , true),
- INTERFACE ("interface" ),
- INTO ("into" , true),
- IS ("is" ),
- MIXIN ("mixin" ),
- MODULE ("module" ),
- NEW ("new" ),
- OPTIONAL ("optional" , true),
- OUTER ("outer" , true, true),
- PACKAGE ("package" ),
- PREFER ("prefer" , true),
- PRIVATE ("private" ),
- PROTECTED ("protected" ),
- PUBLIC ("public" ),
- REQUIRED ("required" , true),
- RETURN ("return" ),
- SERVICE ("service" ),
- STATIC ("static" ),
- STRUCT ("struct" ),
- SUPER ("super" , true, true),
- SWITCH ("switch" ),
- THIS ("this" , true, true),
- THIS_CLASS ("this:class" , true, true),
- THIS_MODULE ("this:module" , true, true),
- THIS_PRI ("this:private" , true, true),
- THIS_PRO ("this:protected" , true, true),
- THIS_PUB ("this:public" , true, true),
- THIS_SERV ("this:service" , true, true),
- THIS_STRUCT ("this:struct" , true, true),
- THIS_TARGET ("this:target" , true, true),
- THROW ("throw" ),
- TODO ("TODO" ),
- TRY ("try" ),
- TYPEDEF ("typedef" ),
- USING ("using" ),
- VAL ("val" , true),
- VAR ("var" , true),
- VOID ("void" ),
- WHILE ("while" ),
- IDENTIFIER (null ),
- EOL_COMMENT (null ),
- ENC_COMMENT (null ),
-
- // Simple language type keywords
- TYPE_INT ("int" , false, true),
- TYPE_FLOAT ("float" , false, true),
- TYPE_CHAR ("char" , false, true),
- // End Simple language type keywords
-
- LIT_BIT (null ),
- LIT_NIBBLE (null ),
- LIT_CHAR (null ),
- LIT_STRING (null ),
- LIT_BINSTR (null ),
- LIT_INT (null ), // integer literal
- LIT_INTA ("Int" , true, true), // automatically sized integer
- LIT_INT8 ("Int8" , true, true),
- LIT_INT16 ("Int16" , true, true),
- LIT_INT32 ("Int32" , true, true),
- LIT_INT64 ("Int64" , true, true),
- LIT_INT128 ("Int128" , true, true),
- LIT_INTN ("IntN" , true, true),
- LIT_UINT8 ("UInt8" , true, true),
- LIT_UINTA ("UInt" , true, true), // automatically sized unsigned integer
- LIT_UINT16 ("UInt16" , true, true),
- LIT_UINT32 ("UInt32" , true, true),
- LIT_UINT64 ("UInt64" , true, true),
- LIT_UINT128 ("UInt128" , true, true),
- LIT_UINTN ("UIntN" , true, true),
- LIT_DEC (null ), // decimal floating point literal
- LIT_DECA ("Dec" , true, true), // automatically sized decimal
- LIT_DEC32 ("Dec32" , true, true),
- LIT_DEC64 ("Dec64" , true, true),
- LIT_DEC128 ("Dec128" , true, true),
- LIT_DECN ("DecN" , true, true),
- LIT_FLOAT (null ), // binary floating point literal
- LIT_FLOAT8E4 ("Float8e4" , true, true),
- LIT_FLOAT8E5 ("Float8e5" , true, true),
- LIT_BFLOAT16 ("BFloat16" , true, true),
- LIT_FLOAT16 ("Float16" , true, true),
- LIT_FLOAT32 ("Float32" , true, true),
- LIT_FLOAT64 ("Float64" , true, true),
- LIT_FLOAT128 ("Float128" , true, true),
- LIT_FLOATN ("FloatN" , true, true),
- LIT_DATE (null ),
- LIT_TIMEOFDAY(null ),
- LIT_TIME (null ),
- LIT_TIMEZONE (null ),
- LIT_DURATION (null ),
- LIT_VERSION (null ),
- LIT_PATH (null ), // generated by the Parser, not by the Lexer
- TEMPLATE ("{...}" ), // not a real token
- EOF ("EOF" ), // end of token stream
- ENUM_VAL ("enum-value" ); // not a real token
-
- /**
- * Constructor.
- *
- * @param sText a textual representation of the token, or null
- */
- Id(final String sText)
- {
- this(sText, false);
- }
-
- /**
- * Constructor.
- *
- * @param sText a textual representation of the token, or null
- */
- Id(final String sText, boolean fContextSensitive)
- {
- this(sText, fContextSensitive, false);
- }
-
- Id(final String sText, boolean fContextSensitive, boolean fSpecial)
- {
- TEXT = sText;
- this.ContextSensitive = fContextSensitive;
- this.Special = fSpecial;
- }
-
- @Override
- public String toString()
- {
- return TEXT == null ? super.toString() : '\"' + TEXT + '"';
- }
-
- /**
- * Look up an Id enum by its ordinal.
- *
- * @param i the ordinal
- *
- * @return the Format enum for the specified ordinal
- */
- public static Id valueOf(int i)
- {
- return IDs[i];
- }
-
- /**
- * Look up an Id enum by its {@link #TEXT}.
- *
- * @param sText the textual representation of the Id
- *
- * @return an instance of Id, or null if there is no matching
- * {@link #TEXT}
- */
- public static Id valueByText(String sText)
- {
- return KEYWORDS.get(sText);
- }
-
- /**
- * Look up an Id enum by its {@link #TEXT}, including context-sensitive keywords.
- *
- * @param sText the textual representation of the Id
- *
- * @return an instance of Id, or null if there is no matching
- * {@link #TEXT}
- */
- public static Id valueByContextSensitiveText(String sText)
- {
- return ALL_KEYWORDS.get(sText);
- }
-
- /**
- * Look up an Id enum by its {@link #TEXT}, and if it is one of the keywords that has both
- * a normal and one-or-more suffixed forms, then return the Id of the normal form.
- *
- * @param sText the possible keyword
- *
- * @return the Id of the "normal form" of the keyword, iff suffixed forms also exist
- */
- public static Id valueByPrefix(String sText)
- {
- return PREFIXES.get(sText);
- }
-
- /**
- * All of the Format enums.
- */
- private static final Id[] IDs = Id.values();
-
- /**
- * String representations of tokens that have constant representations, excluding context-
- * sensitive keywords.
- */
- private static final Map<String, Id> KEYWORDS = new HashMap<>();
- /**
- * String representations of all tokens that have constant representations.
- */
- private static final Map<String, Id> ALL_KEYWORDS = new HashMap<>();
-
- /**
- * String representations of tokens that have both "normal" and "suffixed" representations.
- */
- private static final Map<String, Id> PREFIXES = new HashMap<>();
-
- static
- {
- for (Id id : IDs)
- {
- String sText = id.TEXT;
- if (sText != null && sText.length() > 0)
- {
- char ch = sText.charAt(0);
- if (ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z' || ch == '_')
- {
- ALL_KEYWORDS.put(sText, id);
-
- if (!id.ContextSensitive)
- {
- KEYWORDS.put(sText, id);
- }
-
- int ofColon = sText.indexOf(':');
- if (ofColon > 0)
- {
- Id prefix = ALL_KEYWORDS.get(sText.substring(0, ofColon));
- if (prefix != null)
- {
- PREFIXES.put(prefix.TEXT, prefix);
- }
- }
- }
- }
- }
- }
-
- /**
- * A textual representation of the token, if it has a constant textual representation;
- * otherwise null.
- */
- final public String TEXT;
-
- /**
- * True if the token is context-sensitive, i.e. if it is not always a reserved word.
- */
- final boolean ContextSensitive;
-
- /**
- * True if the token is a special name.
- */
- final boolean Special;
- }
-
-
- // ----- data members --------------------------------------------------------------------------
-
- /**
- * Starting position (inclusive) in the source of this token.
- */
- private long m_lStartPos;
-
- /**
- * Ending position (exclusive) in the source of this token.
- */
- private final long m_lEndPos;
-
- /**
- * Identifier of the token.
- */
- private Id m_id;
-
- /**
- * Value of the Token (if it is a literal).
- */
- private final Object m_oValue;
-
- /**
- * Each token knows if it follows whitespace.
- */
- private boolean m_fLeadingWhitespace;
-
- /**
- * Each token knows if it has whitespace following.
- */
- private boolean m_fTrailingWhitespace;
- }
diff --git a/common/src/main/java/com/seaofnodes/util/Ary.java b/common/src/main/java/com/seaofnodes/util/Ary.java
deleted file mode 100644
index 36654a7f..00000000
--- a/common/src/main/java/com/seaofnodes/util/Ary.java
+++ /dev/null
@@ -1,367 +0,0 @@
-/**
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * The Initial Developer of the Original Software is Cliff Click.
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-
-package com.seaofnodes.util;
-
-import java.lang.reflect.Array;
-import java.util.*;
-import java.util.function.Function;
-import java.util.function.Predicate;
-
-// ArrayList with saner syntax
-public class Ary<E> implements Iterable<E> {
- public E[] _es;
- public int _len;
- public Ary(E[] es) { this(es,es.length); }
- public Ary(E[] es, int len) { if( es.length==0 ) es=Arrays.copyOf(es,1); _es=es; _len=len; }
- @SuppressWarnings("unchecked")
- public Ary(Class<E> clazz) { this((E[]) Array.newInstance(clazz, 1),0); }
-
- /** @return list is empty */
- public boolean isEmpty() { return _len==0; }
- /** @return active list length */
- public int len() { return _len; }
- /** @param i element index
- * @return element being returned; throws if OOB
- * @exception ArrayIndexOutOfBoundsException if !(0 <= i < _len)
- */
- public E at( int i ) {
- range_check(i);
- return _es[i];
- }
- /** @param i element index
- * @return element being returned, or null if OOB */
- public E atX( int i ) {
- return i < _len ? _es[i] : null;
- }
- /** @return last element */
- public E last( ) {
- range_check(0);
- return _es[_len-1];
- }
- public void last(int i) {
- range_check(i);
- E tmp = _es[i];
- _es[i] = _es[_len-1];
- _es[_len-1] = tmp;
- }
-
- /** @return remove and return last element */
- public E pop( ) {
- range_check(0);
- return _es[--_len];
- }
-
- /** Add element in amortized constant time
- * @param e Element to add at end of list
- * @return 'this' for flow-coding */
- public Ary<E> add( E e ) {
- if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1));
- _es[_len++] = e;
- return this;
- }
-
- /** Add element in amortized constant time
- * @param e Element to add at end of list
- **/
- public E push( E e ) {
- if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1));
- return (_es[_len++] = e);
- }
-
- /** Slow, linear-time, element insert. Preserves order.
- * @param i index to insert at, between 0 and _len inclusive.
- * @param e Element to insert
- */
- public void insert( int i, E e ) {
- if( i < 0 || i>_len )
- throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len);
- if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1));
- System.arraycopy(_es,i,_es,i+1,(_len++)-i);
- _es[i] = e;
- }
-
- /** Fast, constant-time, element removal. Does not preserve order
- * @param i element to be removed
- * @return element removed */
- public E del( int i ) {
- range_check(i);
- E tmp = _es[i];
- _es[i]=_es[--_len];
- return tmp;
- }
-
- /** Element removal, using '=='. Does not preserve order.
- * @param e element to be removed
- * @return element removed */
- public E del( E e ) {
- for( int i=0; i<_len; i++ ) {
- E tmp = _es[i];
- if( tmp==e ) {
- _es[i]=_es[--_len];
- return tmp;
- }
- }
- return null;
- }
-
- /** Slow, linear-time, element removal. Preserves order.
- * @param i element to be removed
- * @return element removed */
- public E remove( int i ) {
- range_check(i);
- E e = _es[i];
- System.arraycopy(_es,i+1,_es,i,(--_len)-i);
- return e;
- }
-
- /** Remove all elements */
- public void clear( ) { _len=0; }
-
- public void fill( E e ) { Arrays.fill(_es,0,_len,e); }
-
- /** Extend and set. null fills as needed and does not throw AIOOBE.
- * @param i element to set
- * @param e value to set
- * @return old value
- */
- public E setX( int i, E e ) {
- if( i >= _len ) Arrays.fill(_es,_len,_es.length,null);
- while( i>= _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
- if( i >= _len ) _len = i+1;
- return (_es[i] = e);
- }
-
- /** Clear element. Does nothing if element is OOB, since these are clear by
- * default.
- * @param i element to clear
- */
- public void clear( int i ) { if( i<_len ) _es[i]=null; }
-
- /** Set existing element
- * @param i element to set
- * @param e value to set
- * @return old value
- * @exception AIOOBE if !(0 <= i < _len)
- */
- public E set( int i, E e ) {
- range_check(i);
- E old = _es[i];
- _es[i] = e;
- return old;
- }
-
- // Increment length, revealing the E behind the _len, or null
- public E inc_len( ) {
- if( _len >= _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
- return _es[_len++];
- }
- public Ary<E> set_len( int len ) {
- if( len > _len )
- while( len>= _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
- _len = len;
- while( _es.length > (len<<1) ) // Shrink if hugely too large
- _es = Arrays.copyOf(_es,_es.length>>1);
- Arrays.fill(_es,len,_es.length,null);
- return this;
- }
-
- /** @param c Collection to be added */
- public Ary<E> addAll( Collection<? extends E> c ) { if( c!=null ) for( E e : c ) add(e); return this; }
-
- /** @param es Array to be added */
- public <F extends E> Ary<E> addAll( F[] es ) {
- if( es==null || es.length==0 ) return this;
- while( _len+es.length > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
- System.arraycopy(es,0,_es,_len,es.length);
- _len += es.length;
- return this;
- }
-
- /** @param c Collection to be added */
- public Ary<E> addAll( Ary<? extends E> c ) {
- if( c==null || c._len==0 ) return this;
- while( _len+c._len > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
- System.arraycopy(c._es,0,_es,_len,c._len);
- _len += c._len;
- return this;
- }
-
- /** @return compact array version */
- public E[] asAry() { return Arrays.copyOf(_es,_len); }
-
- /** @param f function to apply to each element. Updates in-place. */
- public Ary<E> map_update( Function<E,E> f ) { for( int i = 0; i<_len; i++ ) _es[i] = f.apply(_es[i]); return this; }
- /** @param P filter out elements failing to pass the predicate; updates in
- * place and shuffles list.
- * @return this, for flow-coding */
- public Ary<E> filter_update( Predicate<E> P ) {
- for( int i=0; i<_len; i++ )
- if( !P.test(_es[i]) )
- del(i--);
- return this;
- }
- /** Sorts in-place
- * @param c Comparator to sort by */
- public void sort_update(Comparator<? super E> c ) { Arrays.sort(_es, 0, _len, c); }
- /** Find the first matching element using ==, or -1 if none. Note that
- * most del calls shuffle the list, so the first element might be random.
- * @param e Element to find
- * @return index of first matching element, or -1 if none */
- public int find( E e ) {
- for( int i=0; i<_len; i++ ) if( _es[i]==e ) return i;
- return -1;
- }
- /** Find the first element matching predicate P, or -1 if none. Note that
- * most del calls shuffle the list, so the first element might be random.
- * @param P Predicate to match
- * @return index of first matching element, or -1 if none */
- public int find( Predicate<E> P ) {
- for( int i=0; i<_len; i++ ) if( P.test(_es[i]) ) return i;
- return -1;
- }
- /** Find and replace the first matching element using ==.
- * @param old Element to find
- * @param nnn Element replacing old
- * @return true if replacement happened */
- public boolean replace( E old, E nnn ) {
- for( int i=0; i<_len; i++ ) if( _es[i]==old ) { _es[i]=nnn; return true; }
- return false;
- }
-
-
- /** Merge-Or. Merge 2 sorted Arys, tossing out duplicates. Return a new
- * sorted Ary with the merged list. Undefined if the original arrays are
- * not sorted. Error if they are not of the same type. Elements must
- * implement Comparable.
- * @param a0 Sorted Ary to merge
- * @param a1 Sorted Ary to merge
- * @return A new sorted merged Ary
- */
- public static <X extends Comparable<X>> Ary<X> merge_or( Ary<X> a0, Ary<X> a1 ) {
- int i=0, j=0;
- Ary<X> res = new Ary<>(Arrays.copyOf(a0._es,a0._len+a1._len),0);
-
- while( i<a0._len && j<a1._len ) {
- X x = a0._es[i];
- X y = a1._es[j];
- int cmp = x.compareTo(y);
- if( cmp<0 ) { res.add(x); i++; }
- else if( cmp>0 ) { res.add(y); j++; }
- else { res.add(x); i++; j++; }
- }
- while( i<a0._len ) res.add(a0._es[i++]);
- while( j<a1._len ) res.add(a1._es[j++]);
- return res;
- }
-
- /** Merge-Or. Merge two sorted Arys, tossing out duplicates and elements not
- * passing the filter. Return a new sorted Ary with the merged list.
- * Undefined if the original arrays are not sorted. Error if they are not
- * of the same type. Elements must implement Comparable.
- * @param a0 Sorted Ary to merge
- * @param a1 Sorted Ary to merge
- * @param cmpr Comparator
- * @param filter Predicate, null means no filter
- * @return A new sorted merged Ary
- */
- public static <X> Ary<X> merge_or( Ary<X> a0, Ary<X> a1, Comparator<X> cmpr, Predicate<X> filter) {
- int i=0, j=0;
- Ary<X> res = new Ary<>(Arrays.copyOf(a0._es,a0._len+a1._len),0);
-
- while( i<a0._len && j<a1._len ) {
- X x = a0._es[i]; if( !filter.test(x) ) { i++; continue; }
- X y = a1._es[j]; if( !filter.test(y) ) { j++; continue; }
- int cmp = cmpr.compare(x,y);
- if( cmp<0 ) { res.add(x); i++; }
- else if( cmp>0 ) { res.add(y); j++; }
- else { res.add(x); i++; j++; }
- }
- while( i<a0._len ) if( filter.test(a0._es[i++]) ) res.add(a0._es[i-1]);
- while( j<a1._len ) if( filter.test(a1._es[j++]) ) res.add(a1._es[j-1]);
- return res;
- }
-
-
- /** Merge-And. Merge 2 sorted Arys, keeping only duplicates. Return a new
- * sorted Ary with the merged list. Undefined if the original arrays are
- * not sorted. Error if they are not of the same type. Elements must
- * implement Comparable.
- * @param a0 Sorted Ary to merge
- * @param a1 Sorted Ary to merge
- * @return A new sorted merged Ary
- */
- public static <X extends Comparable<X>> Ary<X> merge_and( Ary<X> a0, Ary<X> a1 ) {
- int i=0, j=0;
- Ary<X> res = new Ary<>(Arrays.copyOf(a0._es,Math.min(a0._len,a1._len)),0);
- while( i<a0._len && j<a1._len ) {
- X x = a0._es[i];
- X y = a1._es[j];
- int cmp = x.compareTo(y);
- if( cmp<0 ) { i++; }
- else if( cmp>0 ) { j++; }
- else { res.add(x); i++; j++; }
- }
- return res;
- }
-
- /** @return an iterator */
- @Override public Iterator<E> iterator() { return new Iter(); }
- private class Iter implements Iterator<E> {
- int _i=0;
- @Override public boolean hasNext() { return _i<_len; }
- @Override public E next() { return _es[_i++]; }
- }
-
- @Override public String toString() {
- SB sb = new SB().p('{');
- for( int i=0; i<_len; i++ ) {
- if( i>0 ) sb.p(',');
- if( _es[i] != null ) sb.p(_es[i].toString());
- }
- return sb.p('}').toString();
- }
-
- private void range_check( int i ) {
- if( i < 0 || i>=_len )
- throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len);
- }
-
- // Note that the hashCode() and equals() are not invariant to changes in the
- // underlying array. If the hashCode() is used (e.g., inserting into a
- // HashMap) and the then the array changes, the hashCode() will change also.
- @Override public boolean equals( Object o ) {
- if( this==o ) return true;
- if( !(o instanceof Ary ary) ) return false;
- if( _len != ary._len ) return false;
- if( _es == ary._es ) return true;
- for( int i=0; i<_len; i++ )
- if( !(_es[i]==null ? (ary._es[i] == null) : _es[i].equals(ary._es[i])) )
- return false;
- return true;
- }
- @Override public int hashCode( ) {
- int sum=_len;
- for( int i=0; i<_len; i++ )
- sum += _es[i]==null ? 0 : _es[i].hashCode();
- return sum;
- }
-
- public Ary<E> deepCopy() {
- return new Ary<>(_es.clone(),_len);
- }
-}
diff --git a/common/src/main/java/com/seaofnodes/util/AryInt.java b/common/src/main/java/com/seaofnodes/util/AryInt.java
deleted file mode 100644
index 000f4809..00000000
--- a/common/src/main/java/com/seaofnodes/util/AryInt.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/**
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * The Initial Developer of the Original Software is Cliff Click.
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-
-package com.seaofnodes.util;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.function.IntUnaryOperator;
-
-// ArrayList with saner syntax
-public class AryInt {
- public int[] _es;
- public int _len;
- public AryInt(int[] es) { this(es,es.length); }
- public AryInt(int[] es, int len) { _es=es; _len=len; }
- public AryInt() { this(new int[1],0); }
-
- /** @return list is empty */
- public boolean isEmpty() { return _len==0; }
- /** @return active list length */
- public int len() { return _len; }
- /** @param i element index
- * @return element being returned; throws if OOB */
- public int at( int i ) {
- range_check(i);
- return _es[i];
- }
- /** @param i element index
- * @return element being returned, or 0 if OOB */
- public int atX( int i ) {
- return i < _len ? _es[i] : 0;
- }
- /** @return last element */
- public int last( ) {
- range_check(0);
- return _es[_len-1];
- }
-
- /** @return remove and return last element */
- public int pop( ) {
- range_check(0);
- return _es[--_len];
- }
-
- /** Add element in amortized constant time
- * @param e element to add at end of list
- * @return 'this' for flow-coding */
- public AryInt push( int e ) {
- if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1));
- _es[_len++] = e;
- return this;
- }
-
- /** Slow, linear-time, element insert. Preserves order.
- * @param i index to insert at, between 0 and _len inclusive.
- * @param e intlement to insert
- */
- public void insert( int i, int e ) {
- if( i < 0 || i>_len )
- throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len);
- if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1));
- System.arraycopy(_es,i,_es,i+1,(_len++)-i);
- _es[i] = e;
- }
-
- /** Fast, constant-time, element removal. Does not preserve order
- * @param i element to be removed
- * @return element removed */
- public int del( int i ) {
- range_check(i);
- int tmp = _es[i];
- _es[i]=_es[--_len];
- return tmp;
- }
-
- /** Slow, linear-time, element removal. Preserves order.
- * @param i element to be removed
- * @return element removed */
- public int remove( int i ) {
- range_check(i);
- int e = _es[i];
- System.arraycopy(_es,i+1,_es,i,(--_len)-i);
- return e;
- }
-
- /** Remove all elements */
- public void clear( ) { Arrays.fill(_es,0,_len,0); _len=0; }
-
- // Extend and set
- public int setX( int i, int e ) {
- while( i>= _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
- if( i >= _len ) _len = i+1;
- return (_es[i] = e);
- }
-
- public int set( int i, int e ) {
- range_check(i);
- return (_es[i] = e);
- }
-
- public AryInt set_as( int e ) { _es[0] = e; _len=1; return this; }
- public AryInt set_len( int len ) {
- if( len > _len )
- while( len>= _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
- _len = len;
- while( _es.length > (len<<1) ) // Shrink if hugely too large
- _es = Arrays.copyOf(_es,_es.length>>1);
- return this;
- }
-
- /** @param c Collection to be added */
- public AryInt addAll( Collection<? extends Integer> c ) { for( int e : c ) push(e); return this; }
-
- /** @param es Array to be added */
- public AryInt addAll( int[] es ) {
- if( es.length==0 ) return this;
- while( _len+es.length > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
- System.arraycopy(es,0,_es,_len,es.length);
- _len += es.length;
- return this;
- }
-
- public AryInt map_update( IntUnaryOperator f ) { for( int i = 0; i<_len; i++ ) _es[i] = f.applyAsInt(_es[i]); return this; }
-
- /** @return compact array version, using the internal base array where possible. */
- public int[] asAry() { return _len==_es.length ? _es : Arrays.copyOf(_es,_len); }
-
- /** Sorts in-place */
- public void sort_update() { Arrays.sort(_es, 0, _len); }
- /** Find the first matching element using ==, or -1 if none. Note that
- * most del calls shuffle the list, so the first element might be random.
- * @param e intlement to find
- * @return index of first matching element, or -1 if none */
- public int find( int e ) {
- for( int i=0; i<_len; i++ ) if( _es[i]==e ) return i;
- return -1;
- }
-
- @Override public String toString() {
- SB sb = new SB().p('[');
- for( int i=0; i<_len; i++ )
- sb.p(_es[i]).p(',');
- return sb.unchar().p(']').toString();
- }
-
- private void range_check( int i ) {
- if( i < 0 || i>=_len )
- throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len);
- }
-
- // Binary search sorted _es. Returns insertion point.
- // Undefined results if _es is not sorted.
- public int binary_search( int e ) {
- int lo=0, hi=_len-1;
- while( lo <= hi ) {
- int mid = (hi + lo) >>> 1; // midpoint, rounded down
- int mval = _es[mid];
- if( e==mval ) {
- // If dups, get to the first.
- while( mid>0 && e==_es[mid-1] ) mid--;
- return mid;
- }
- if( e >mval ) lo = mid+1;
- else hi = mid-1;
- }
- return lo;
- }
-
- // Note that the hashCode() and equals() are not invariant to changes in the
- // underlying array. If the hashCode() is used (e.g., inserting into a
- // HashMap) and the then the array changes, the hashCode() will change also.
- @Override public boolean equals( Object o ) {
- if( this==o ) return true;
- if( !(o instanceof AryInt) ) return false;
- AryInt ary = (AryInt)o;
- if( _len != ary._len ) return false;
- if( _es == ary._es ) return true;
- for( int i=0; i<_len; i++ )
- if( _es[i] != ary._es[i] )
- return false;
- return true;
- }
- @Override public int hashCode( ) {
- int sum=_len;
- for( int i=0; i<_len; i++ )
- sum += _es[i];
- return sum;
- }
-}
diff --git a/common/src/main/java/com/seaofnodes/util/Handy.java b/common/src/main/java/com/seaofnodes/util/Handy.java
deleted file mode 100644
index 62e34ab9..00000000
--- a/common/src/main/java/com/seaofnodes/util/Handy.java
+++ /dev/null
@@ -1,1778 +0,0 @@
-/**
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * The Initial Developer of the Original Software is
- * The Ecstasy Project (https://github.com/xtclang/).
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-package com.seaofnodes.util;
-
-
-import java.io.BufferedInputStream;
-import java.io.DataInput;
-import java.io.DataOutput;
-import java.io.EOFException;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.io.IOException;
-import java.io.UTFDataFormatException;
-
-import java.lang.reflect.Array;
-
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-
-import java.nio.charset.Charset;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-
-/**
- * Handy static methods.
- */
-public class Handy
- {
- // ----- String formatting ---------------------------------------------------------------------
-
- /**
- * Take the passed nibble (the low order 4 bits of the int) and return the corresponding hexit
- * (hexadecimal digit) in the range of '0' - 'F'.
- *
- * @param n the nibble value
- *
- * @return the hexadecimal representation of the passed nibble
- */
- public static char nibbleToChar(int n)
- {
- n = n & 0xF;
- return (n <= 9)
- ? (char) ('0' + n)
- : (char) ('A' - 0xA + n);
- }
-
- /**
- * Render the passed byte (the low order 8 bits of the int) into the passed StringBuilder as
- * two hexadecimal digits.
- *
- * @param sb the StringBuilder to append to
- * @param n the byte value
- *
- * @return the StringBuilder
- */
- public static StringBuilder appendByteAsHex(StringBuilder sb, int n)
- {
- return sb.append(nibbleToChar(n >> 4))
- .append(nibbleToChar(n));
- }
-
- /**
- * Format the passed byte (the low order 8 bits of the int) into a String of the form "0xFF".
- *
- * @param n the byte value
- *
- * @return a String representation of the byte
- */
- public static String byteToHexString(int n)
- {
- return appendByteAsHex(new StringBuilder(4).append("0x"), n).toString();
- }
-
- /**
- * Append a portion of a byte array to a StringBuilder as a sequence of hex digits.
- *
- * @param sb the StringBuilder to append to
- * @param ab the byte array
- * @param of the offset of the first byte in the byte array to render
- * @param cb the number bytes from the byte array to render
- *
- * @return the StringBuilder
- */
- public static StringBuilder appendByteArrayAsHex(StringBuilder sb, byte[] ab, int of, int cb)
- {
- sb.ensureCapacity(sb.length() + cb * 2);
- while (--cb >= 0)
- {
- int n = ab[of++];
- sb.append(nibbleToChar(n >> 4))
- .append(nibbleToChar(n));
- }
- return sb;
- }
-
- /**
- * Append the contents of a byte array to a StringBuilder as a sequence of hex digits.
- *
- * @param sb the StringBuilder to append to
- * @param ab the byte array
- *
- * @return the StringBuilder
- */
- public static StringBuilder appendByteArrayAsHex(StringBuilder sb, byte[] ab)
- {
- return appendByteArrayAsHex(sb, ab, 0, ab.length);
- }
-
- /**
- * Format a portion of the passed byte array into a String of the form "0x01234FF".
- *
- * @param ab the byte array
- * @param of the offset of the first byte in the byte array to render
- * @param cb the number bytes from the byte array to render
- *
- * @return a String representation of the byte array
- */
- public static String byteArrayToHexString(byte[] ab, int of, int cb)
- {
- return appendByteArrayAsHex(new StringBuilder(2 + cb * 2).append("0x"),
- ab, of, cb).toString();
- }
-
- /**
- * Format the passed byte array into a String of the form "0x01234FF".
- *
- * @param ab the byte array
- *
- * @return a String representation of the byte array
- */
- public static String byteArrayToHexString(byte[] ab)
- {
- return byteArrayToHexString(ab, 0, ab.length);
- }
-
- /**
- * Convert a String of hex digits into a <tt>byte[]</tt>.
- *
- * @param s a String of hexadecimal digits; the leading "0x" is optional
- *
- * @return a byte array
- */
- public static byte[] hexStringToByteArray(String s)
- {
- int ofch = 0;
- int cch = s.length();
- int ofb = 0;
- int cb = (cch + 1) / 2;
-
- // check for (and skip) the "0x" prefix
- if (s.charAt(0) == '0' && (s.charAt(1) == 'x' || s.charAt(1) == 'X'))
- {
- ofch += 2;
- --cb;
- }
-
- final byte[] ab = new byte[cb];
-
- // handle the case where the first nibble is an implied (missing) '0'
- if ((cch & 0x1) != 0)
- {
- ab[ofb++] = (byte) hexitValue(s.charAt(ofch++));
- }
-
- // convert the remaining pairs of hexits to bytes
- while (ofch < cch)
- {
- ab[ofb++] = (byte) (hexitValue(s.charAt(ofch)) << 4 | hexitValue(s.charAt(ofch+1)));
- ofch += 2;
- }
-
- return ab;
- }
-
- /**
- * Format bytes from the passed byte array as a "hex dump", useful for debugging.
- *
- * @param ab the byte array
- * @param of the offset of the first byte to show in the hex
- * dump
- * @param cb the number of bytes to show in the hex dump
- * @param cBytesPerLine the number of bytes to show in each line of the hex
- * dump
- *
- * @return a String containing the hex dump
- */
- public static String byteArrayToHexDump(byte[] ab, int of, int cb, int cBytesPerLine)
- {
- assert ab != null;
- assert of >= 0 && cb >= 0 && of + cb <= ab.length;
- assert cBytesPerLine > 0;
-
- // first figure out how many digits it will take to show the address
- final int cchAddr = countHexDigits(of + cb) + 1 & ~0x01;
-
- // format is 12F0: 00 12 32 A0 ????\n
- // line length is addressLen + 4*cbPerLine + 3
- final int cch = cchAddr + cBytesPerLine * 4 + 3;
- final char[] ach = new char[cch];
- Arrays.fill(ach, ' ');
- ach[cchAddr] = ':';
- ach[cch-1] = '\n';
-
- final int cLines = Math.max((cb + cBytesPerLine - 1) / cBytesPerLine, 1);
- final StringBuilder sb = new StringBuilder(cLines * cch);
- final int ofHex = cchAddr + 2;
- final int ofChar = ofHex + cBytesPerLine * 3;
-
- for (int iLine = 0; iLine < cLines; ++iLine)
- {
- // format the address
- renderIntToHex(of, ach, 0, cchAddr);
-
- for (int iByte = 0; iByte < cBytesPerLine; ++iByte)
- {
- if (cb > 0)
- {
- int b = ab[of];
- renderByteToHex(b, ach, ofHex + iByte * 3);
-
- char ch = (char) (b & 0xFF);
- ach[ofChar + iByte] = Character.isISOControl(ch) ? '.' : ch;
- }
- else
- {
- ach[ofHex + iByte * 3 ] = ' ';
- ach[ofHex + iByte * 3 + 1] = ' ';
- ach[ofChar + iByte ] = ' ';
- }
-
- ++of;
- --cb;
- }
-
- sb.append(ach, 0, iLine < cLines - 1 ? cch : cch - 1);
- }
-
- return sb.toString();
- }
-
- /**
- * Format bytes from the passed byte array as a "hex dump", useful for debugging.
- *
- * @param ab the byte array
- * @param cBytesPerLine the number of bytes to show in each line of the hex
- * dump
- *
- * @return a String containing the hex dump
- */
- public static String byteArrayToHexDump(byte[] ab, int cBytesPerLine)
- {
- return byteArrayToHexDump(ab, 0, ab.length, cBytesPerLine);
- }
-
- /**
- * Convert an array of bytes to a long value.
- *
- * @param ab the array of bytes
- * @param of the starting index
- *
- * @return the long value
- */
- public static long byteArrayToLong(byte[] ab, int of)
- {
- return ((long) (ab[of++]) << 56)
- + ((long) (ab[of++] & 0xFF) << 48)
- + ((long) (ab[of++] & 0xFF) << 40)
- + ((long) (ab[of++] & 0xFF) << 32)
- + ((long) (ab[of++] & 0xFF) << 24)
- + ( (ab[of++] & 0xFF) << 16)
- + ( (ab[of++] & 0xFF) << 8 )
- + ( ab[of ] & 0xFF );
- }
-
- /**
- * Produce an array of bytes for the specified long value.
- *
- * @param l the long value
- *
- * @return the byte array
- */
- static public byte[] toByteArray(long l)
- {
- return new byte[]
- {
- (byte) (l >> 56),
- (byte) (l >> 48),
- (byte) (l >> 40),
- (byte) (l >> 32),
- (byte) (l >> 24),
- (byte) (l >> 16),
- (byte) (l >> 8 ),
- (byte) l,
- };
- }
-
- /**
- * Render the bytes of the specified long value into an array of bytes at the specified location.
- *
- * @param l the long value
- * @param ab the byte array to copy into
- * @param ab the byte array offset to write the long value at
- *
- * @return the byte array
- */
- static public void toByteArray(long l, byte[] ab, int of)
- {
- ab[of++] = (byte) (l >> 56);
- ab[of++] = (byte) (l >> 48);
- ab[of++] = (byte) (l >> 40);
- ab[of++] = (byte) (l >> 32);
- ab[of++] = (byte) (l >> 24);
- ab[of++] = (byte) (l >> 16);
- ab[of++] = (byte) (l >> 8 );
- ab[of ] = (byte) l;
- }
-
- /**
- * Determine how many hex digits it will take to render the passed <tt>int</tt> value as an
- * <b>unsigned</b> hex value.
- *
- * @param n the int value, which is treated as an unsigned 32-bit value
- *
- * @return the number of hex digits required to render the int value
- */
- public static int countHexDigits(int n)
- {
- return Math.max((32 - Integer.numberOfLeadingZeros(n) + 3) / 4, 1);
- }
-
- /**
- * Render the passed <tt>int</tt> into the passed StringBuilder as the specified number of hex
- * digits.
- *
- * @param sb the StringBuilder to append to
- * @param n the int value
- * @param cch the number of hex digits to render
- *
- * @return the StringBuilder
- */
- public static StringBuilder appendIntAsHex(StringBuilder sb, int n, int cch)
- {
- assert cch >= 0 && cch <= 8;
- for (int cBits = (cch-1) * 4; cBits >= 0; cBits -= 4)
- {
- sb.append(nibbleToChar(n >> cBits));
- }
- return sb;
- }
-
- /**
- * Render the passed <tt>int</tt> into the passed StringBuilder as 8 hexadecimal digits.
- *
- * @param sb the StringBuilder to append to
- * @param n the int value
- *
- * @return the StringBuilder
- */
- public static StringBuilder appendIntAsHex(StringBuilder sb, int n)
- {
- return appendIntAsHex(sb, n, 8);
- }
-
- /**
- * Format the passed <tt>int</tt> into a String of the form "0x12345678".
- *
- * @param n the byte value
- *
- * @return a hexadecimal String representation of the value
- */
- public static String intToHexString(int n)
- {
- return appendIntAsHex(new StringBuilder(10).append("0x"), n).toString();
- }
-
- /**
- * Determine how many hex digits it will take to render the passed <tt>long</tt> value as an
- * <b>unsigned</b> hex value.
- *
- * @param n the long value, which is treated as an unsigned 64-bit value
- *
- * @return the number of hex digits required to render the long value
- */
- public static int countHexDigits(long n)
- {
- return Math.max((64 - Long.numberOfLeadingZeros(n) + 3) / 4, 1);
- }
-
- /**
- * Render the passed <tt>long</tt> into the passed StringBuilder as the specified number of hex
- * digits.
- *
- * @param sb the StringBuilder to append to
- * @param n the long value
- * @param cch the number of hex digits to render
- *
- * @return the StringBuilder
- */
- public static StringBuilder appendLongAsHex(StringBuilder sb, long n, int cch)
- {
- assert cch >= 0 && cch <= 16;
- for (int cBits = (cch-1) * 4; cBits >= 0; cBits -= 4)
- {
- sb.append(nibbleToChar((int) (n >> cBits)));
- }
- return sb;
- }
-
- /**
- * Render the passed <tt>long</tt> into the passed StringBuilder as 16 hexadecimal digits.
- *
- * @param sb the StringBuilder to append to
- * @param n the long value
- *
- * @return the StringBuilder
- */
- public static StringBuilder appendLongAsHex(StringBuilder sb, long n)
- {
- return appendLongAsHex(sb, n, 16);
- }
-
-
- /**
- * Format the passed <tt>int</tt> into a String of the form "0x12345678".
- *
- * @param n the byte value
- *
- * @return a hexadecimal String representation of the value
- */
- public static String longToHexString(long n)
- {
- return appendLongAsHex(new StringBuilder(18).append("0x"), n).toString();
- }
-
- /**
- * Format the specified byte value into the passed char array as two hex digits.
- *
- * @param n the byte value
- * @param ach the char array to render into
- * @param of the offset into the char array for the first hex digit
- *
- * @return the offset of the first character after the two hex digits
- */
- public static int renderByteToHex(int n, char[] ach, int of)
- {
- ach[of ] = nibbleToChar(n >> 4);
- ach[of+1] = nibbleToChar(n);
- return of + 2;
- }
-
- /**
- * Format the specified int value into the passed char array as the specified number of hex
- * digits.
- *
- * @param n the int value
- * @param ach the char array to render into
- * @param of the offset into the char array for the first hex digit
- * @param cch the number of hex digits to render
- *
- * @return the offset of the first character after the last hex digit
- */
- public static int renderIntToHex(int n, char[] ach, int of, int cch)
- {
- for (int i = 0; i < cch; ++i)
- {
- ach[of+cch-i-1] = nibbleToChar(n);
- n >>>= 4;
- }
- return of + cch;
- }
-
- /**
- * Format the specified long value into the passed char array as the specified number of hex
- * digits.
- *
- * @param n the long value
- * @param ach the char array to render into
- * @param of the offset into the char array for the first hex digit
- * @param cch the number of hex digits to render
- *
- * @return the offset of the first character after the last hex digit
- */
- public static int renderLongToHex(long n, char[] ach, int of, int cch)
- {
- for (int i = 0; i < cch; ++i)
- {
- ach[of+cch-i-1] = nibbleToChar((int) n);
- n >>>= 4;
- }
- return of + cch;
- }
-
- /**
- * Determine if the specified character is a decimal digit.
- *
- * @param ch a character
- *
- * @return true iff the character is a decimal digit
- */
- public static boolean isDigit(char ch)
- {
- return ch >= '0' && ch <= '9';
- }
-
- /**
- * For a decimal digit, determine its integer value.
- *
- * @param ch a decimal digit
- *
- * @return the integer value of the specified decimal digit
- */
- public static int digitValue(char ch)
- {
- assert isDigit(ch);
- return ch - '0';
- }
-
- /**
- * Determine if the specified character is a hexadecimal digit.
- *
- * @param ch a character
- *
- * @return true iff the character is a hexadecimal digit
- */
- public static boolean isHexit(char ch)
- {
- // hexits in the unicode range appear in this order:
- // {..., 0, ..., 9, ..., A, ..., F, ..., a, ..., f, ...}
- // so a hexit has to be in the range 0..f, and the entire character
- // range from 0..f is only 0x36 (so it fits inside a long mask)
- return ch >= '0' && ch <= 'f' && ((1L << ('f' - ch)) &
- // 3 4 5 6
- // 0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456
- // 0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[/]^_`abcdef
- 0b01111111111000000011111100000000000000000000000000111111L) != 0;
- }
-
- /**
- * For a hexadecimal digit, determine its integer value.
- *
- * @param ch a hexadecimal digit
- *
- * @return the integer value of the specified hexadecimal digit
- */
- public static int hexitValue(char ch)
- {
- assert isHexit(ch);
- return ch <= '9' ? ch - '0' : (ch | 0x20) - ('a' - 0xA);
- }
-
- /**
- * Determine if the specified character is an ASCII letter, which is in the range 'A' to 'Z',
- * or 'a' to 'z'.
- *
- * @param ch the character to test
- *
- * @return true iff the character is an ASCII letter
- */
- public static boolean isAsciiLetter(char ch)
- {
- // ASCII letters in the unicode range appear in this order:
- // {..., A, ..., Z, ..., a, ..., z, ...}
- return ch >= 'A' && ch <= 'z' && ((1L << ('z' - ch)) &
- // 4 5 6 7
- // 123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789A
- // ABCDEFGHIJKLMNOPQRSTUVWXYZ[/]^_`abcdefghijklmnopqrstuvwxyz
- 0b01111111111111111111111111100000011111111111111111111111111L) != 0;
- }
-
-
- // ----- String manipulation -------------------------------------------------------------------
-
- /**
- * Count the number occurrences of the specified character in the passed String.
- *
- * @param s the String to scan
- * @param ch the character to scan for
- *
- * @return the number of occurrences of the character
- */
- public static int countChar(String s, char ch)
- {
- int c = 0;
- int of = s.indexOf(ch);
- while (of >= 0)
- {
- ++c;
- of = s.indexOf(ch, of + 1);
- }
- return c;
- }
-
- /**
- * Using the specified delimiter, parse the passed String into an array of Strings, each of
- * which occurred in the original String but separated by the specified delimiter.
- *
- * @param s the String to parse
- * @param chDelim the character delimiter
- *
- * @return the array of Strings parsed from the passed String
- */
- public static String[] parseDelimitedString(String s, char chDelim)
- {
- if (s == null)
- {
- return null;
- }
-
- if (s.length() == 0)
- {
- return NO_ARGS;
- }
-
- int of = s.indexOf(chDelim);
- if (of < 0)
- {
- return new String[] {s};
- }
-
- ArrayList<String> list = new ArrayList<>();
- int ofPrev = 0;
- do
- {
- list.add(s.substring(ofPrev, of));
- ofPrev = of + 1;
- of = s.indexOf(chDelim, ofPrev);
- }
- while (of >= 0);
- list.add(s.substring(ofPrev));
- return list.toArray(NO_ARGS);
- }
-
- /**
- * Create a string containing the specified number of the specified character.
- *
- * @param ch the character to duplicate
- * @param cch the size of the resulting string
- *
- * @return a string containing the specified number of the specified character
- */
- public static String dup(char ch, int cch)
- {
- StringBuilder sb = new StringBuilder(cch);
- while (cch-- > 0)
- {
- sb.append(ch);
- }
- return sb.toString();
- }
-
- /**
- * Indent each line of the passed text by prepending the specified indentation String.
- *
- * @param sText the text to indent
- * @param sIndent the indentation to use
- *
- * @return the indented String
- */
- public static String indentLines(String sText, String sIndent)
- {
- int cchOld = sText.length();
- if (cchOld == 0)
- {
- return "";
- }
-
- int cLines = countChar(sText, '\n') + 1;
- int cchNew = cchOld + cLines * sIndent.length();
- StringBuilder sb = new StringBuilder(cchNew);
-
- int ofLine = 0;
- int ofNewline = sText.indexOf('\n');
- while (ofNewline >= 0)
- {
- if (ofNewline > ofLine)
- {
- sb.append(sIndent);
- }
- sb.append(sText, ofLine, ofNewline + 1);
-
- ofLine = ofNewline + 1;
- ofNewline = sText.indexOf('\n', ofLine);
- }
-
- if (ofLine < cchOld)
- {
- sb.append(sIndent)
- .append(sText, ofLine, cchOld);
- }
-
- return sb.toString();
- }
-
- /**
- * Determine if the specified character needs to be escaped in order to be displayed.
- *
- * @param ch the character to evaluate
- *
- * @return true iff the character should be escaped in order to be displayed
- */
- public static boolean isCharEscaped(char ch)
- {
- if (ch < 0x80)
- {
- return ch < 0x20 || ch == '\'' || ch == '\"' || ch == '\\' || ch == 0x7F;
- }
- else
- {
- return !Character.isValidCodePoint(ch) ||
- Character.getType(ch) == Character.CONTROL ||
- ch == '\u2028' || ch == '\u2029';
- }
- }
-
- /**
- * Append the specified character to the StringBuilder, escaping if necessary.
- *
- * @param sb the StringBuilder to append to
- * @param ch the character to escape
- *
- * @return the StringBuilder
- */
- public static StringBuilder appendChar(StringBuilder sb, char ch)
- {
- if (isCharEscaped(ch))
- {
- switch (ch)
- {
- case '\\':
- return sb.append("\\\\");
- case '\b':
- return sb.append("\\b");
- case '\f':
- return sb.append("\\f");
- case '\n':
- return sb.append("\\n");
- case '\r':
- return sb.append("\\r");
- case '\t':
- return sb.append("\\t");
- case '\'':
- return sb.append("\\'");
- case '\"':
- return sb.append("\\\"");
- case 0x7F: // DEL
- return sb.append("\\d");
- default:
- return appendIntAsHex(sb.append('\\').append('u'), ch, 4);
- }
- }
- else
- {
- return sb.append(ch);
- }
- }
-
- /**
- * Render a character as it would appear as a constant in source code.
- *
- * @param ch the character to quote
- *
- * @return a string showing the specified character, escaped if necessary
- */
- public static String quotedChar(char ch)
- {
- return appendChar(new StringBuilder(9).append('\''), ch).append('\'').toString();
- }
-
- /**
- * Append the specified String to the StringBuilder, escaping if and as necessary.
- *
- * @param sb the StringBuilder to append to
- * @param s the String to escape as necessary
- *
- * @return the StringBuilder
- */
- public static StringBuilder appendString(StringBuilder sb, String s)
- {
- for (int of = 0, cch = s.length(); of < cch; ++of)
- {
- appendChar(sb, s.charAt(of));
- }
- return sb;
- }
-
- /**
- * Render a String as it would appear as a constant in source code.
- *
- * @param s the String to quote
- *
- * @return a String showing the quoted version of the passed String,
- * escaping characters if and when necessary
- */
- public static String quotedString(String s)
- {
- return appendString(new StringBuilder(s.length() + 2).append('\"'), s).append(
- '\"').toString();
- }
-
-
- // ----- packed integers -----------------------------------------------------------------------
-
- /**
- * Read a variable-length encoded integer value from a stream.
- *
- * @param in a <tt>DataInput</tt> stream to read from
- *
- * @return a <tt>long</tt> value
- *
- * @throws IOException if an I/O exception occurs
- * @throws NumberFormatException if the integer does not fit into
- * a <tt>long</tt> value
- */
- public static long readPackedLong(DataInput in)
- throws IOException
- {
- return PackedInteger.readLong(in);
- }
-
- /**
- * Write a signed 64-bit integer to a stream using variable-length encoding.
- *
- * @param out the <tt>DataOutput</tt> stream to write to
- * @param n the <tt>long</tt> value to write
- *
- * @throws IOException if an I/O exception occurs
- */
- public static void writePackedLong(DataOutput out, long n)
- throws IOException
- {
- PackedInteger.writeLong(out, n);
- }
-
- /**
- * Read a variable-length encoded 32-bit integer from a stream.
- *
- * @param in a <tt>DataInput</tt> stream to read from
- *
- * @return an <tt>int</tt> value in the range <tt>Integer.MIN_VALUE..Integer.MAX_VALUE</tt>
- *
- * @throws java.io.IOException if an I/O exception occurs
- */
- public static int readPackedInt(DataInput in)
- throws IOException
- {
- long n = readPackedLong(in);
- if (n < Integer.MIN_VALUE || n > Integer.MAX_VALUE)
- {
- throw new IOException("value (" + n + ") exceeds 32-bit range");
- }
-
- return (int) n;
- }
-
- /**
- * Read a variable-length encoded 32-bit integer magnitude from a stream.
- * <p>
- * Note that while the XVM itself is a 64-bit machine, Java does not support 64-bit (or even
- * unsigned 32-bit) magnitudes. This method is a convenience method that verifies that the
- * magnitude is within a range supported by Java.
- *
- * @param in a <tt>DataInput</tt> stream to read from
- *
- * @return an <tt>int</tt> value in the range <tt>0..Integer.MAX_VALUE</tt>
- *
- * @throws java.io.IOException if an I/O exception occurs
- */
- public static int readMagnitude(DataInput in)
- throws IOException
- {
- long n = readPackedLong(in);
- if (n > Integer.MAX_VALUE)
- {
- // this is unsupported in Java; arrays are limited in size
- // by their use of signed 32-bit magnitudes and indexes
- throw new IOException("magnitude (" + n + ") exceeds 32-bit maximum");
- }
- else if (n < 0)
- {
- throw new IOException("negative magnitude (" + n + ") is illegal");
- }
-
- return (int) n;
- }
-
- /**
- * Read a variable-length encoded 32-bit integer index from a stream.
- * <p>
- * Note that while the XVM itself is a 64-bit machine, Java does not support 64-bit (or even
- * unsigned 32-bit) indexes. This method is a convenience method that verifies that the index is
- * within a range supported by Java.
- *
- * @param in a <tt>DataInput</tt> stream to read from
- *
- * @return an <tt>int</tt> value in the range <tt>0..Integer.MAX_VALUE</tt>,
- * or <tt>-1</tt>
- *
- * @throws java.io.IOException if an I/O exception occurs
- */
- public static int readIndex(DataInput in)
- throws IOException
- {
- long n = readPackedLong(in);
- if (n > Integer.MAX_VALUE)
- {
- // this is unsupported in Java; arrays are limited in size
- // by their use of signed 32-bit magnitudes and indexes
- throw new IOException("index (" + n + ") exceeds 32-bit maximum");
- }
- else if (n < -1)
- {
- throw new IOException("negative index (" + n + ") is illegal");
- }
-
- return (int) n;
- }
-
-
- // ----- unicode -------------------------------------------------------------------------------
-
- /**
- * Read a single unicode character (code-point) from the passed DataInput, encoded using the
- * UTF-8 format.
- *
- * @param in the DataInput to read from
- *
- * @return a Unicode code-point (which can exceed the 16-bit <tt>char</tt>
- * type in Java)
- *
- * @throws IOException if an I/O exception occurs while reading the data,
- * or if a UTF-8 format error is detected
- */
- public static int readUtf8Char(DataInput in)
- throws IOException
- {
- int b = in.readUnsignedByte();
- if ((b & 0x80) == 0)
- {
- // ASCII - single byte 0xxxxxxx format
- return b;
- }
-
- // otherwise the format is based on the number of high-order 1-bits:
- // #1s first byte trailing # trailing bits code-points
- // --- ---------- -------- ---------- ---- -----------------------
- // 2 110xxxxx 10xxxxxx 1 11 U+0080 - U+07FF
- // 3 1110xxxx 10xxxxxx 2 16 U+0800 - U+FFFF
- // 4 11110xxx 10xxxxxx 3 21 U+10000 - U+1FFFFF
- // 5 111110xx 10xxxxxx 4 26 U+200000 - U+3FFFFFF
- // 6 1111110x 10xxxxxx 5 31 U+4000000 - U+7FFFFFFF
- switch (Integer.highestOneBit(~(0xFFFFFF00 | b)))
- {
- case 0b00100000: // 2-byte format
- return (b & 0b00011111) << 6 | nextCharBits(in);
- case 0b00010000: // 3-byte format
- return (b & 0b00001111) << 12 | nextCharBits(in) << 6 | nextCharBits(in);
- case 0b00001000: // 4-byte format
- return (b & 0b00000111) << 18 | nextCharBits(in) << 12 | nextCharBits(in) << 6 | nextCharBits(in);
- case 0b00000100: // 5-byte format
- return (b & 0b00000011) << 24 | nextCharBits(in) << 18 | nextCharBits(in) << 12 | nextCharBits(in) << 6 | nextCharBits(in);
- case 0b00000010: // 6-byte format
- return (b & 0b00000001) << 30 | nextCharBits(in) << 24 | nextCharBits(in) << 18 | nextCharBits(in) << 12 | nextCharBits(in) << 6 | nextCharBits(in);
- default:
- throw new UTFDataFormatException("initial byte: " + byteToHexString(b));
- }
- }
-
- /**
- * Internal: Read a UTF-8 continuation byte and validate/decode it.
- *
- * @param in the DataInput to read from
- *
- * @return the 6 bits of UTF-8 data from the continuation byte
- *
- * @throws IOException if an I/O exception occurs while reading the data,
- * or if a UTF-8 format error is detected
- */
- private static int nextCharBits(DataInput in)
- throws IOException
- {
- int n = in.readUnsignedByte();
- if ((n & 0b11000000) != 0b10000000)
- {
- throw new UTFDataFormatException("trailing unicode byte does not match 10xxxxxx");
- }
- return n & 0b00111111;
- }
-
- /**
- * Write a single unicode character (code-point) to the passed DataOutput, encoding using the
- * UTF-8 format.
- *
- * @param out the DataOutput to write to
- * @param ch the char to write out as UTF-8
- *
- * @return a Unicode code-point (which can exceed the 16-bit <tt>char</tt>
- * type in Java)
- *
- * @throws IOException if an I/O exception occurs while writing the data,
- * or if a UTF-8 format error is detected
- */
- public static void writeUtf8Char(DataOutput out, int ch)
- throws IOException
- {
- if ((ch & ~0x7F) == 0)
- {
- // ASCII - single byte 0xxxxxxx format
- out.write(ch);
- return;
- }
-
- // otherwise the format is based on the number of significant bits:
- // bits code-points first byte trailing # trailing
- // ---- ----------------------- ---------- -------- ----------
- // 11 U+0080 - U+07FF 110xxxxx 10xxxxxx 1
- // 16 U+0800 - U+FFFF 1110xxxx 10xxxxxx 2
- // 21 U+10000 - U+1FFFFF 11110xxx 10xxxxxx 3
- // 26 U+200000 - U+3FFFFFF 111110xx 10xxxxxx 4
- // 31 U+4000000 - U+7FFFFFFF 1111110x 10xxxxxx 5
- int cTrail;
- switch (Integer.highestOneBit(ch))
- {
- case 0b00000000000000000000000010000000:
- case 0b00000000000000000000000100000000:
- case 0b00000000000000000000001000000000:
- case 0b00000000000000000000010000000000:
- out.write(0b11000000 | ch >>> 6);
- cTrail = 1;
- break;
-
- case 0b00000000000000000000100000000000:
- case 0b00000000000000000001000000000000:
- case 0b00000000000000000010000000000000:
- case 0b00000000000000000100000000000000:
- case 0b00000000000000001000000000000000:
- out.write(0b11100000 | ch >>> 12);
- cTrail = 2;
- break;
- case 0b00000000000000010000000000000000:
- case 0b00000000000000100000000000000000:
- case 0b00000000000001000000000000000000:
- case 0b00000000000010000000000000000000:
- case 0b00000000000100000000000000000000:
- out.write(0b11110000 | ch >>> 18);
- cTrail = 3;
- break;
- case 0b00000000001000000000000000000000:
- case 0b00000000010000000000000000000000:
- case 0b00000000100000000000000000000000:
- case 0b00000001000000000000000000000000:
- case 0b00000010000000000000000000000000:
- out.write(0b11111000 | ch >>> 24);
- cTrail = 4;
- break;
- case 0b00000100000000000000000000000000:
- case 0b00001000000000000000000000000000:
- case 0b00010000000000000000000000000000:
- case 0b00100000000000000000000000000000:
- case 0b01000000000000000000000000000000:
- out.write(0b11111100 | ch >>> 30);
- cTrail = 5;
- break;
-
- default:
- throw new UTFDataFormatException("illegal character: " + intToHexString(ch));
- }
-
- // write out trailing bytes; each has the same "10xxxxxx" format with 6
- // bits of data
- while (cTrail > 0)
- {
- out.write(0b10_000000 | (ch >>> --cTrail * 6 & 0b00_111111));
- }
- }
-
- /**
- * Read a length-encoded string of unicode characters (code-point) from the passed DataInput,
- * encoded using the UTF-8 format. The format is the number of bytes in the stream used for the
- * UTF-8 character encoding, encoded as a packed integer, followed by the difference in the
- * number of bytes and the number of characters, the difference being 0 or greater, encoded as
- * a packed integer, followed by the specified number of characters, encoded using UTF-8.
- *
- * @param in the DataInput to read from
- *
- * @return a Unicode code-point (which can exceed the 16-bit <tt>char</tt>
- * type in Java)
- *
- * @throws IOException if an I/O exception occurs while reading the data,
- * or if a UTF-8 format error is detected
- */
- public static String readUtf8String(DataInput in)
- throws IOException
- {
- int cb = readMagnitude(in);
- int cch = cb - readMagnitude(in);
- StringBuilder sb = new StringBuilder(cch);
- for (int ofch = 0; ofch < cch; ++ofch)
- {
- int ch = readUtf8Char(in);
- if (ch >= 0xFFFF)
- {
- if (!Character.isSupplementaryCodePoint(ch))
- {
- throw new UTFDataFormatException(
- "Character is outside of UTF-16 (including supplemental) range: " +
- intToHexString(ch));
- }
-
- // the "supplemental" range is composed of two UTF-16 characters
- sb.append(Character.highSurrogate(ch)) // "leading"
- .append(Character.lowSurrogate(ch)); // "trailing"
- }
- else
- {
- sb.append((char) ch);
- }
- }
-
- return sb.toString();
- }
-
- /**
- * Write a length-encoded string of unicode characters (code-points) to the passed DataOutput,
- * encoding using the UTF-8 format. The length encoding is the number of bytes in the stream
- * used for the UTF-8 character encoding, encoded as a packed integer, followed by the
- * difference in the number of bytes and the number of characters, the difference being 0 or
- * greater, encoded as a packed integer.
- *
- * @param out the DataOutput to write to
- * @param s the String to write out as UTF-8
- *
- * @throws IOException if an I/O exception occurs while writing the data,
- * or if a UTF-8 format error is detected
- */
- public static void writeUtf8String(DataOutput out, String s)
- throws IOException
- {
- // figure out the actual number of unicode code-points (not the number
- // of "Java chars") and how many bytes the UTF-8 format will use
- final int cch = s.length(); // count of UTF-16 "Java chars"
- final int[] anch = new int[cch]; // array of unicode code points
- int cb = 0; // count of bytes
- int cnch = 0; // count of unicode code points
- for (int ofch = 0; ofch < cch; ++ofch)
- {
- char ch = s.charAt(ofch);
- int nch;
- if (Character.isSurrogate(ch))
- {
- // first character must be a high surrogate
- if (!Character.isHighSurrogate(ch))
- {
- throw new UTFDataFormatException(
- "low surrogate unexpected: " + intToHexString(ch));
- }
-
- // next character must be a low surrogate
- char ch2;
- if (++ofch >= cch || !Character.isLowSurrogate(ch2 = s.charAt(ofch)))
- {
- throw new UTFDataFormatException(
- "low surrogate expected after: " + intToHexString(ch));
- }
-
- nch = Character.toCodePoint(ch, ch2);
- }
- else
- {
- nch = ch;
- }
-
- anch[cnch++] = nch;
- cb += calcUtf8Length(nch);
- }
-
- // write out the UTF-8 form of the String
- assert cb >= cnch;
- writePackedLong(out, cb);
- writePackedLong(out, cb - cnch);
- for (int of = 0; of < cnch; ++of)
- {
- writeUtf8Char(out, anch[of]);
- }
- }
-
- /**
- * Internal: Calculate the UTF-8 length for a particular code-point.
- *
- * @param ch the unicode character
- *
- * @return the number of bytes required to format the unicode character to
- * UTF-8
- *
- * @throws UTFDataFormatException if an invalid character is passed
- */
- private static int calcUtf8Length(int ch)
- throws IOException
- {
- if ((ch & ~0x7F) == 0)
- {
- // ASCII - single byte 0xxxxxxx format
- return 1;
- }
-
- switch (Integer.highestOneBit(ch))
- {
- case 0b00000000000000000000000010000000:
- case 0b00000000000000000000000100000000:
- case 0b00000000000000000000001000000000:
- case 0b00000000000000000000010000000000:
- return 2;
-
- case 0b00000000000000000000100000000000:
- case 0b00000000000000000001000000000000:
- case 0b00000000000000000010000000000000:
- case 0b00000000000000000100000000000000:
- case 0b00000000000000001000000000000000:
- return 3;
-
- case 0b00000000000000010000000000000000:
- case 0b00000000000000100000000000000000:
- case 0b00000000000001000000000000000000:
- case 0b00000000000010000000000000000000:
- case 0b00000000000100000000000000000000:
- return 4;
-
- case 0b00000000001000000000000000000000:
- case 0b00000000010000000000000000000000:
- case 0b00000000100000000000000000000000:
- case 0b00000001000000000000000000000000:
- case 0b00000010000000000000000000000000:
- return 5;
-
- case 0b00000100000000000000000000000000:
- case 0b00001000000000000000000000000000:
- case 0b00010000000000000000000000000000:
- case 0b00100000000000000000000000000000:
- case 0b01000000000000000000000000000000:
- return 6;
-
- default:
- throw new UTFDataFormatException("illegal character: " + intToHexString(ch));
- }
- }
-
- // ----- file I/O ------------------------------------------------------------------------------
-
- /**
- * Open the specified file as an InputStream.
- *
- * @param file the file to open
- *
- * @return an InputStream to the specified file
- *
- * @throws IOException if an IOException occurs while reading the
- * FileStructure
- */
- public static InputStream toInputStream(File file)
- throws IOException
- {
- if (!file.exists())
- {
- throw new IOException("file does not exist: " + file);
- }
-
- if (!file.isFile() || !file.canRead())
- {
- throw new IOException("not a readable file: " + file);
- }
-
- return new BufferedInputStream(new FileInputStream(file));
- }
-
- /**
- * Read the raw bytes contained in the specified file.
- *
- * @param file the file to read
- *
- * @return the binary contents of the file
- *
- * @throws IOException indicates a failure to read the binary contents
- * of the specified file
- */
- public static byte[] readFileBytes(File file)
- throws IOException
- {
- if (file == null)
- {
- throw new IllegalArgumentException("file required");
- }
-
- if (!file.exists() || !file.isFile())
- {
- throw new FileNotFoundException(file.toString());
- }
-
- final long lcb = file.length();
- if (lcb == 0L)
- {
- return EMPTY_BYTE_ARRAY;
- }
- else if (lcb > Integer.MAX_VALUE - 1024)
- {
- // assume a maximum byte array size just under 2GB
- throw new IOException("file exceeds max supported length (2GB): "
- + file + "=" + lcb + " bytes");
- }
-
- // read the complete contents of the file
- int cb = (int) lcb;
- byte[] ab = new byte[cb];
- try (FileInputStream stream = new FileInputStream(file))
- {
- for (int of = 0; of < cb; )
- {
- int cbChunk = stream.read(ab, of, cb-of);
- if (cbChunk < 0)
- {
- throw new EOFException("unexpected end-of-file: " + file);
- }
- of += cbChunk;
- }
- }
-
- if (file.length() != lcb)
- {
- throw new IOException("file was concurrently modified while being read: " + file);
- }
-
- return ab;
- }
-
- /**
- * Read the text contents of the specified file. Supported formats are ASCII, UTF-8, UTF-16, and
- * UTF-32; failure to identify any of those formats will cause the default encoding to be used.
- *
- * @param file the file to read
- *
- * @return the character contents of the file
- *
- * @throws IOException indicates a failure to read the character contents
- * of the specified file
- */
- public static char[] readFileChars(File file)
- throws IOException
- {
- return readFileChars(file, null);
- }
-
- /**
- * Read the text contents of the specified file using the specified encoding. If no encoding is
- * specified, then determine the encoding in the manner specified by {@link #readFileChars(File)}.
- *
- * @param file the file to read
- * @param sEncoding the specific encoding to use, or null to attempt to
- * determine the encoding automatically
- *
- * @return the character contents of the file
- *
- * @throws IOException indicates a failure to read the character contents
- * of the specified file
- */
- public static char[] readFileChars(File file, String sEncoding)
- throws IOException
- {
- byte[] ab = readFileBytes(file);
- int cb = ab.length;
- if (cb == 0)
- {
- return EMPTY_CHAR_ARRAY;
- }
-
- // check to see if there is a byte order mark
- int cbBOM = 0;
- if (sEncoding == null)
- {
- switch (cb)
- {
- default:
- if (ab[0] == 0x00 && ab[1] == 0x00 && (ab[2] & 0xFF) == 0xFE && (ab[3] & 0xFF) == 0xFF)
- {
- sEncoding = "UTF-32BE";
- cbBOM = 4;
- break;
- }
- else if ((ab[0] & 0xFF) == 0xFF && (ab[1] & 0xFF) == 0xFE && ab[2] == 0x00 && ab[3] == 0x00)
- {
- sEncoding = "UTF-32LE";
- cbBOM = 4;
- break;
- }
- case 3:
- if ((ab[0] & 0xFF) == 0xEF && (ab[1] & 0xFF) == 0xBB && (ab[2] & 0xFF) == 0xBF)
- {
- sEncoding = "UTF-8";
- cbBOM = 3;
- break;
- }
- case 2:
- if ((ab[0] & 0xFF) == 0xFE && (ab[1] & 0xFF) == 0xFF)
- {
- sEncoding = "UTF-16BE";
- cbBOM = 2;
- break;
- }
- else if ((ab[0] & 0xFF) == 0xFF && (ab[1] & 0xFF) == 0xFE)
- {
- sEncoding = "UTF-16LE";
- cbBOM = 2;
- break;
- }
- case 1:
- case 0:
- }
- }
-
- scan: if (sEncoding == null)
- {
- // quick scan to verify that the contents are ASCII
- char[] ach = new char[cb];
- for (int of = 0; of < cb; ++of)
- {
- int b = ab[of] & 0xFF;
- if (b > 0x7F)
- {
- // use default encoding
- break scan;
- }
- ach[of] = (char) (b & 0xFF);
- }
- return ach;
- }
-
- // use the encoding that was specified or otherwise determined, or fall
- // back to the default encoding
- Charset charset = sEncoding == null ? Charset.defaultCharset() : Charset.forName(sEncoding);
- ByteBuffer bytebuf = ByteBuffer.wrap(ab, cbBOM, cb - cbBOM);
- CharBuffer charbuf = charset.decode(bytebuf);
- char[] ach = new char[charbuf.length()];
- charbuf.get(ach);
- return ach;
- }
-
-
- // ----- array and collection helpers ----------------------------------------------------------
-
- /**
- * Given an array of "base" elements and an array of elements that are being evaluated as some
- * form of "addition" to those base elements, determine if there are any duplicates, and return
- * an array that contains the non-duplicates.
- *
- * @param aoBase the array of "base" elements
- * @param aoAdd the array of the elements being evaluated in addition to the base elements
- *
- * @return an array containing all of the elements from the {@code aoAdd} array that are not
- * duplicates of elements in the {@code aoBase} array
- */
- public static <T> T[] dedupAdds(T[] aoBase, T[] aoAdd)
- {
- // there's a fair likelihood that *ALL* of the "adds" will be duplicates, and a fair
- // likelihood that *NONE* of the "adds" will be unique, so assume both up front, and only
- // de-optimize when *BOTH* of those two things have been proven to be false
- int cBase = aoBase.length;
- int cAdd = aoAdd.length;
- boolean fAllDups = true;
- boolean fNoDups = true;
- ArrayList<T> listDeDup = null;
- NextLayer: for (int iAdd = 0; iAdd < cAdd; ++iAdd)
- {
- T oAdd = aoAdd[iAdd];
- for (int iBase = 0; iBase < cBase; ++iBase)
- {
- if (oAdd.equals(aoBase[iBase]))
- {
- // we found a duplicate; is it the first one?
- if (fNoDups)
- {
- fNoDups = false;
-
- // if we already know that there are some that are NOT duplicates, then we
- // need to start maintaining a list of non-duplicates to add; since this is
- // the first duplicate encountered, just take everything up to this point
- if (!fAllDups)
- {
- assert listDeDup == null;
- listDeDup = startList(aoAdd, iAdd);
- }
- }
-
- // this one was a duplicate, so advance to the next thing to add
- continue NextLayer;
- }
- }
-
- // this one to add is NOT a duplicate; is it the first one?
- if (fAllDups)
- {
- fAllDups = false;
-
- // if we already know that there are duplicates (i.e. NOT no duplicates), then we
- // need to start maintaining a list of non-duplicates (starting with this one)
- if (!fNoDups)
- {
- assert listDeDup == null;
- listDeDup = new ArrayList<>();
- }
- }
-
- // if, for whatever reason, we are maintaining a list of non-duplicates by this point,
- // and since we just verified that this is a non-duplicate, then add it to the list
- if (listDeDup != null)
- {
- listDeDup.add(oAdd);
- }
- }
-
- // at this point, we finished our check for duplicates; if there are no duplicates, then
- // the original array of things to add is the result
- if (fNoDups)
- {
- assert listDeDup == null;
- return aoAdd;
- }
-
- // otherwise, there are duplicates, which boils down into two possibilities: they're all
- // duplicates (so we return an empty array), or just some are duplicates (in which case
- // there is a list of the ones that are NOT duplicates that we should return)
- int cResult = listDeDup == null ? 0 : listDeDup.size();
- T[] aoResult = (T[]) Array.newInstance(aoAdd.getClass().getComponentType(), cResult);
- if (cResult > 0)
- {
- aoResult = listDeDup.toArray(aoResult);
- }
- return aoResult;
- }
-
- /**
- * Glue two arrays together.
- *
- * @param aoBase the first array
- * @param aoAdd the second array
- *
- * @return an array containing all of the elements of both passed arrays
- */
- public static <T> T[] append(T[] aoBase, T[] aoAdd)
- {
- int cBase = aoBase.length;
- if (cBase == 0)
- {
- return aoAdd;
- }
-
- int cAdd = aoAdd.length;
- if (cAdd == 0)
- {
- return aoBase;
- }
-
- int cResult = cBase + cAdd;
- T[] aoResult = (T[]) Array.newInstance(aoAdd.getClass().getComponentType(), cResult);
- System.arraycopy(aoBase, 0, aoResult, 0, cBase);
- System.arraycopy(aoAdd, 0, aoResult, cBase, cAdd);
- return aoResult;
- }
-
- /**
- * Add the specified element at the top of the specified array.
- *
- * @param aoBase the array to add to
- * @param oAdd the element to append at the head
- *
- * @return an array containing all the elements
- */
- public static <T> T[] appendHead(T[] aoBase, T oAdd)
- {
- int c = aoBase.length;
- T[] aNew = (T[]) Array.newInstance(oAdd.getClass(), c + 1);
-
- aNew[0] = oAdd;
- System.arraycopy(aoBase, 0, aNew, 1, c);
- return aNew;
- }
-
- /**
- * Create an ArrayList that contains the first {@code c} elements of the array {@code ao}.
- *
- * @param ao an array of T
- * @param c the number of elements of the array to put into the new list
- *
- * @return a new ArrayList of the first {@code c} elements of the {@code ao} array
- */
- public static <T> ArrayList<T> startList(T[] ao, int c)
- {
- return appendList(new ArrayList<>(), ao, 0, c);
- }
-
- /**
- * Append the specified elements from the passed array to the list.
- *
- * @param list a list of T to add to
- * @param ao an array of T to obtain the values-to-add from
- * @param of the offset into the array
- * @param c the number of elements from the array to add to the list
- *
- * @return the list
- */
- public static <T> ArrayList<T> appendList(ArrayList<T> list, T[] ao, int of, int c)
- {
- for (int ofEnd = of+c; of < ofEnd; ++of)
- {
- list.add(ao[of]);
- }
- return list;
- }
-
-
- // ----- hashing & equality --------------------------------------------------------------------
-
- /**
- * Perform a hash of the object. This performs a deep hash on arrays.
- *
- * @param o any object, or null
- *
- * @return a hashcode
- */
- public static int hashCode(final Object o)
- {
- return Hash.of(o);
- }
-
- /**
- * Perform a comparison for equality. The types of the objects must be equal
- * as well. This performs a deep comparison on arrays.
- *
- * @param o1 any object, or null
- * @param o2 any object, or null
- *
- * @return true iff <tt>o1</tt> is equals to <tt>o2</tt>
- */
- public static boolean equals(final Object o1, final Object o2)
- {
- if (o1 == null)
- {
- return o2 == null;
- }
- if (o2 == null)
- {
- return false;
- }
-
- final Class clz1 = o1.getClass();
- final Class clz2 = o1.getClass();
- if (clz1 != clz2)
- {
- return false;
- }
-
- Class<?> clzComp = clz1.getComponentType();
- return clzComp == null || clzComp != clz2.getComponentType()
- ? o1.equals(o2)
- : clzComp.isPrimitive()
- ? clzComp == int .class ? Arrays.equals( (int[]) o1, (int[]) o2)
- : clzComp == long .class ? Arrays.equals( (long[]) o1, (long[]) o2)
- : clzComp == byte .class ? Arrays.equals( (byte[]) o1, (byte[]) o2)
- : clzComp == char .class ? Arrays.equals( (char[]) o1, (char[]) o2)
- : clzComp == double.class ? Arrays.equals( (double[]) o1, (double[]) o2)
- : clzComp == float .class ? Arrays.equals( (float[]) o1, (float[]) o2)
- : clzComp == short .class ? Arrays.equals( (short[]) o1, (short[]) o2)
- : Arrays.equals((boolean[]) o1, (boolean[]) o2)
- : Arrays.equals((Object[]) o1, (Object[]) o2);
- }
-
- /**
- * Perform a comparison of two arrays for ordering. This performs a deep
- * comparison on the arrays.
- *
- * @param ao1 an array of Comparable, or null
- * @param ao2 an array of Comparable, or null
- *
- * @return negative, zero, or positive iff <tt>ao1</tt> is less than, equal
- * to, or greater than <tt>ao2</tt>
- */
- public static int compareArrays(final Comparable[] ao1, final Comparable[] ao2)
- {
- if (ao1 == ao2)
- {
- return 0;
- }
-
- if (ao1 == null)
- {
- return -1;
- }
-
- if (ao2 == null)
- {
- return 1;
- }
-
- final int c1 = ao1.length;
- final int c2 = ao2.length;
- for (int i = 0, c = Math.min(c1, c2); i < c; ++i)
- {
- final int n = ao1[i].compareTo(ao2[i]);
- if (n != 0)
- {
- return n;
- }
- }
-
- return c1 - c2;
- }
-
- /**
- * Verify that the array is non-null and all elements are non-null.
- *
- * @param ao an array of objects
- *
- * @return true iff all elements are non-null
- *
- * @throws IllegalArgumentException if the array is null, or any elements
- * of the array are null
- */
- public static boolean checkElementsNonNull(Object[] ao)
- {
- if (ao == null)
- {
- throw new IllegalArgumentException("array is null");
- }
-
- for (Object o : ao)
- {
- if (o == null)
- {
- throw new IllegalArgumentException("array element is null");
- }
- }
-
- return true;
- }
-
-
- // ----- constants -----------------------------------------------------------------------------
-
- /**
- * A constant empty array of <tt>byte</tt>.
- */
- public final static byte[] EMPTY_BYTE_ARRAY = new byte[0];
-
- /**
- * A constant empty array of <tt>char</tt>.
- */
- public final static char[] EMPTY_CHAR_ARRAY = new char[0];
-
- /**
- * A constant empty array of <tt>String</tt>.
- */
- public final static String[] NO_ARGS = new String[0];
- } \ No newline at end of file
diff --git a/common/src/main/java/com/seaofnodes/util/Hash.java b/common/src/main/java/com/seaofnodes/util/Hash.java
deleted file mode 100644
index 65626755..00000000
--- a/common/src/main/java/com/seaofnodes/util/Hash.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/**
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * The Initial Developer of the Original Software is
- * The Ecstasy Project (https://github.com/xtclang/).
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-package com.seaofnodes.util;
-
-import java.util.Arrays;
-
-/**
- * Helper functions for producing compound hashes.
- *
- * <p>
- * For example given a class with fields {@code a, b, c, d} of any type, a compound hash can be
- * computed via:
- * <pre>{@code
- * int hash = Hash.of(a,
- * Hash.of(b,
- * Hash.of(c,
- * Hash.of(d))));
- * }
- * </pre>
- *
- * @author mf
- */
-public final class Hash
- {
- private Hash()
- {
- // blocked constructor
- }
-
- /**
- * Incorporate a value into a running hash.
- *
- * @param v the value (or hash) to incorporate into the running hash
- * @param h the running hash to combine with
- *
- * @return the compound hash
- */
- public static int of(int v, int h)
- {
- return 31 * h + v;
- }
-
- /**
- * Incorporate a value into a running hash.
- *
- * @param v the value to incorporate into the hash
- * @param h the running hash to combine with
- *
- * @return the compound hash
- */
- public static int of(boolean v, int h)
- {
- return of(of(v), h);
- }
-
- /**
- * Incorporate a value into a running hash.
- *
- * @param v the value to incorporate into the hash
- * @param h the running hash to combine with
- *
- * @return the compound hash
- */
- public static int of(byte v, int h)
- {
- return of(of(v), h);
- }
-
- /**
- * Incorporate a value into a running hash.
- *
- * @param v the value to incorporate into the hash
- * @param h the running hash to combine with
- *
- * @return the compound hash
- */
- public static int of(char v, int h)
- {
- return of(of(v), h);
- }
-
- /**
- * Incorporate a value into a running hash.
- *
- * @param v the value to incorporate into the hash
- * @param h the running hash to combine with
- *
- * @return the compound hash
- */
- public static int of(short v, int h)
- {
- return of(of(v), h);
- }
-
- /**
- * Incorporate a value into a running hash.
- *
- * @param v the value to incorporate into the hash
- * @param h the running hash to combine with
- *
- * @return the compound hash
- */
- public static int of(float v, int h)
- {
- return of(of(v), h);
- }
-
- /**
- * Incorporate a value into a running hash.
- *
- * @param v the value to incorporate into the hash
- * @param h the running hash to combine with
- *
- * @return the compound hash
- */
- public static int of(long v, int h)
- {
- return of(of(v), h);
- }
-
- /**
- * Incorporate a value into a running hash.
- *
- * @param v the value to incorporate into the hash
- * @param h the running hash to combine with
- *
- * @return the compound hash
- */
- public static int of(double v, int h)
- {
- return of(of(v), h);
- }
-
- /**
- * Incorporate a value into a running hash.
- *
- * @param v the value to incorporate into the hash
- * @param h the running hash to combine with
- *
- * @return the compound hash
- */
- public static int of(Object v, int h)
- {
- return of(of(v), h);
- }
-
- /**
- * Return a hash of the value.
- *
- * @param v the value
- *
- * @return the hash
- */
- public static int of(boolean v)
- {
- return Boolean.hashCode(v);
- }
-
- /**
- * Return a hash of the value.
- *
- * @param v the value
- *
- * @return the hash
- */
- public static int of(byte v)
- {
- return Byte.hashCode(v);
- }
-
- /**
- * Return a hash of the value.
- *
- * @param v the value
- *
- * @return the hash
- */
- public static int of(short v)
- {
- return Short.hashCode(v);
- }
-
- /**
- * Return a hash of the value.
- *
- * @param v the value
- *
- * @return the hash
- */
- public static int of(char v)
- {
- return Character.hashCode(v);
- }
-
- /**
- * Return a hash of the value.
- *
- * @param v the value
- *
- * @return the hash
- */
- public static int of(int v)
- {
- return Integer.hashCode(v);
- }
-
- /**
- * Return a hash of the value.
- *
- * @param v the value
- *
- * @return the hash
- */
- public static int of(float v)
- {
- return Float.hashCode(v);
- }
-
- /**
- * Return a hash of the value.
- *
- * @param v the value
- *
- * @return the hash
- */
- public static int of(long v)
- {
- return Long.hashCode(v);
- }
-
- /**
- * Return a hash of the value.
- *
- * @param v the value
- *
- * @return the hash
- */
- public static int of(double v)
- {
- return Double.hashCode(v);
- }
-
- /**
- * Return a hash of the value.
- *
- * <p>
- * If the value is an array the hash will be via {@link java.util.Arrays#hashCode}.
- *
- * @param v the value
- *
- * @return the hash
- */
- public static int of(Object v)
- {
- return v == null
- ? 0
- : v.getClass().isArray()
- ? ofArray(v)
- : v.hashCode();
- }
-
- /**
- * Return the hash of an array.
- *
- * @param v the non-null array
- *
- * @return the hash
- */
- private static int ofArray(Object v)
- {
- Class<?> clzComp = v.getClass().getComponentType();
- return
- clzComp.isPrimitive()
- ? clzComp == int .class ? Arrays.hashCode( (int[]) v)
- : clzComp == long .class ? Arrays.hashCode( (long[]) v)
- : clzComp == byte .class ? Arrays.hashCode( (byte[]) v)
- : clzComp == char .class ? Arrays.hashCode( (char[]) v)
- : clzComp == double.class ? Arrays.hashCode( (double[]) v)
- : clzComp == float .class ? Arrays.hashCode( (float[]) v)
- : clzComp == short .class ? Arrays.hashCode( (short[]) v)
- : Arrays.hashCode((boolean[]) v)
- : Arrays.hashCode((Object[]) v);
- }
- }
diff --git a/common/src/main/java/com/seaofnodes/util/PackedInteger.java b/common/src/main/java/com/seaofnodes/util/PackedInteger.java
deleted file mode 100644
index e032f8d0..00000000
--- a/common/src/main/java/com/seaofnodes/util/PackedInteger.java
+++ /dev/null
@@ -1,1360 +0,0 @@
-/**
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * The Initial Developer of the Original Software is
- * The Ecstasy Project (https://github.com/xtclang/).
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-package com.seaofnodes.util;
-
-
-import java.io.DataInput;
-import java.io.DataOutput;
-import java.io.IOException;
-
-import java.math.BigInteger;
-
-
-/**
- * A PackedInteger represents a signed, 2's-complement integer of 1-8192 bits (1-1024 bytes) in
- * size. (The size limit is arbitrary, and temporary.)
- * <p/>
- * Values up to 8 bytes can be accessed as a <tt>long</tt> value, while values of any size can be
- * accessed as a BigInteger.
- * <p/>
- * The storage format is compressed as much as possible. There are five storage formats:
- * <ul><li>
- * <b>Tiny</b>: For a value in the range -64..63 (7 bits), the value can be encoded in one byte.
- * The least significant 7 bits of the value are shifted left by 1 bit, and the 0x1 bit is set to 1.
- * When reading in a packed integer, if bit 0x1 of the first byte is 1, then it's Tiny.
- * </li><li>
- * <b>Small</b>: For a value in the range -4096..4095 (13 bits), the value can be encoded in two
- * bytes. The first byte contains the value 0x2 (010) in the least significant 3 bits, and bits 8-12
- * of the integer in bits 3-7; the second byte contains bits 0-7 of the integer.
- * </li><li>
- * <b>Medium</b>: For a value in the range -1048576..1048575 (21 bits), the value can be encoded in
- * three bytes. The first byte contains the value 0x6 (110) in the least significant 3 bits, and
- * bits 16-20 of the integer in bits 3-7; the second byte contains bits 8-15 of the integer; the
- * third byte contains bits 0-7 of the integer.
- * </li><li>
- * <b>Large</b>: For a value in the range -(2^503)..2^503-1 (63 bytes), a value with `{@code s}`
- * significant bits can be encoded in no less than {@code 1+max(1,(s+7)/8)} bytes; let `{@code b}`
- * be the selected encoding length, in bytes. The first byte contains the value 0x0 (00) in the
- * least significant 2 bits, and the least 6 significant bits of {@code (b-2)} in bits 2-7. The
- * following {@code (b-1)} bytes contain the least significant {@code (b-1)*8} bits of the integer.
- * </li><li>
- * <b>Huge</b>: For any larger value (arbitrarily limited to the range -(2^8191)..2^8191-1 (1KB)),
- * the first byte is 0b111111_00, followed by an embedded packed integer specifying the number of
- * significant bytes of the enclosing packed integer, followed by that number of bytes of data.
- * </li></ul>
- * <p/>
- * To maximize density and minimize pipeline stalls, the algorithms in this file use the smallest
- * possible encoding for each value. Since an 8 significant-bit value can be encoded in two bytes
- * using either a small or large encoding, we choose large to eliminate the potential for a
- * (conditional-induced) pipeline stall. Since a 14..16 significant-bit value can be encoded in
- * three bytes using either a medium or large encoding, we choose large for the same reason.
- * <pre><code>
- * significant bits encoding bytes
- * ---------------- -------- -----
- * <= 7 Tiny 1
- * 8 Large 2
- * 9-13 Small 2
- * 14-16 Large 3
- * 17-21 Medium 3
- * >= 22 Large 4 or more
- * >= 512 Huge *
- * </code></pre>
- * <p/>
- * Similarly, by examining the least significant 3 bits of the first byte of an encoded value, the
- * encoding can be determined as follows:
- * <pre><code>
- * first byte encoding bytes
- * ---------- -------- -----
- * ???????1 Tiny 1
- * ?????010 Small 2
- * ?????110 Medium 3
- * ??????00 Large 2-65
- * 11111100 Huge *
- * </code></pre>
- */
-public class PackedInteger
- implements Comparable<PackedInteger>
- {
- // ----- constructors --------------------------------------------------------------------------
-
- /**
- * Construct an uninitialized PackedInteger.
- */
- public PackedInteger()
- {
- }
-
- /**
- * Construct a PackedInteger using a <tt>long</tt> value.
- *
- * @param lVal the <tt>long</tt> value for the PackedInteger
- */
- public PackedInteger(long lVal)
- {
- setLong(lVal);
- }
-
- /**
- * Construct a PackedInteger using a <tt>BigInteger</tt> value.
- *
- * @param bigint the <tt>BigInteger</tt> value for the PackedInteger
- */
- public PackedInteger(BigInteger bigint)
- {
- setBigInteger(bigint);
- }
-
- /**
- * Construct a PackedInteger by reading the packed value from a DataInput
- * stream.
- *
- * @param in a DataInput stream to read from
- *
- * @throws IOException if an I/O exception occurs while reading the data
- */
- public PackedInteger(DataInput in)
- throws IOException
- {
- readObject(in);
- }
-
-
- // ----- public methods ------------------------------------------------------------------------
-
- /**
- * Obtain a PackedInteger that has the specified <tt>long</tt> value. This
- * method is useful for taking advantage of the built-in "cache" of
- * commonly-used PackedInteger instances.
- *
- * @param lVal the <tt>long</tt> value for the PackedInteger
- */
- public static PackedInteger valueOf(long lVal)
- {
- if (lVal >= CACHE_MIN & lVal <= CACHE_MAX)
- {
- final int iCache = (int) (lVal - CACHE_MIN);
-
- PackedInteger pint = CACHE[iCache];
- if (pint == null)
- {
- pint = new PackedInteger(lVal);
- CACHE[iCache] = pint;
- }
-
- return pint;
- }
-
- return new PackedInteger(lVal);
- }
-
- /**
- * The size of the "native" 2's-complement signed integer that would be
- * necessary to hold the value.
- *
- * @return a value between 1 and 32 inclusive
- */
- public int getSignedByteSize()
- {
- verifyInitialized();
-
- int nBytes = m_fBig
- ? calculateSignedByteCount(m_bigint)
- : Math.max(1, (((64 - Long.numberOfLeadingZeros(Math.max(m_lValue, ~m_lValue))) & 0x3F) + 7) / 8);
-
- assert nBytes >= 1 && nBytes <= 1024; // arbitrary limit
- return nBytes;
- }
-
- /**
- * The size of the unsigned integer that would be necessary to hold the value.
- *
- * @return a value between 1 and 32 inclusive
- */
- public int getUnsignedByteSize()
- {
- verifyInitialized();
- if (m_fBig && m_bigint.signum() < 0)
- {
- throw new IllegalStateException("negative value");
- }
-
- int nBytes = m_fBig
- ? calculateUnsignedByteCount(m_bigint)
- : Math.max(1, (((64 - Long.numberOfLeadingZeros(m_lValue)) + 7) / 8));
-
- assert nBytes >= 1 && nBytes <= 32; // arbitrary limit of 32 for the prototype
- return nBytes;
- }
-
- /**
- * Determine if the value of the PackedInteger is "big". The value is
- * considered to be "big" if it cannot fit into a <tt>long</tt>.
- *
- * @return true if the value of the PackedInteger does not fit into a long
- */
- public boolean isBig()
- {
- verifyInitialized();
- return m_fBig;
- }
-
- /**
- * Range check the PackedInteger.
- *
- * @param nLo the low end of the range (inclusive)
- * @param nHi the high end of the range (inclusive)
- *
- * @return true iff the PackedInteger is in the specified range
- */
- public boolean checkRange(long nLo, long nHi)
- {
- if (isBig())
- {
- return false;
- }
-
- return m_lValue >= nLo && m_lValue <= nHi;
- }
-
- /**
- * @return true iff the value is less than zero
- */
- public boolean isNegative()
- {
- verifyInitialized();
- return m_fBig
- ? m_bigint.signum() < 0
- : m_lValue < 0;
- }
-
- /**
- * Helper to grab the value as an int, with a range check to be safe.
- *
- * @return the value as a 32-bit signed int
- */
- public int getInt()
- {
- long n = getLong();
- if (n < Integer.MIN_VALUE || n > Integer.MAX_VALUE)
- {
- throw new IllegalStateException("too big!");
- }
-
- return (int) n;
- }
-
- /**
- * Obtain the <tt>long</tt> value of the PackedInteger. If the PackedInteger
- * is "big", i.e. if the {@link #isBig} method returns <tt>true</tt>, then
- * this method will throw an IllegalStateException, because the value cannot
- * be expressed as a <tt>long</tt> without losing data.
- *
- * @return the <tt>long</tt> value of this PackedInteger
- *
- * @throws IllegalStateException if the value of this PackedInteger does not
- * fit into a long
- */
- public long getLong()
- {
- verifyInitialized();
- if (m_fBig && m_lValue == 0)
- {
- throw new IllegalStateException("too big!");
- }
-
- return m_lValue;
- }
-
- /**
- * Initialize the PackedInteger using a <tt>long</tt> value.
- *
- * @param lVal the <tt>long</tt> value for the PackedInteger
- */
- public void setLong(long lVal)
- {
- verifyUninitialized();
- m_lValue = lVal;
- m_fInitialized = true;
- }
-
- /**
- * Obtain the BigInteger value of the PackedInteger. Whether or not the
- * PackedInteger value is too large to be held in a <tt>long</tt>, the
- * caller can request the BigInteger value; one will be lazily instantiated
- * (and subsequently cached) if necessary.
- *
- * @return the BigInteger that represents the integer value of this
- * PackedInteger object
- */
- public BigInteger getBigInteger()
- {
- verifyInitialized();
- BigInteger bigint = m_bigint;
- if (bigint == null)
- {
- bigint = BigInteger.valueOf(m_lValue);
- m_bigint = bigint;
- }
- return bigint;
- }
-
- /**
- * Initialize the PackedInteger using a BigInteger value.
- *
- * @param bigint the BigInteger value for the PackedInteger
- */
- public void setBigInteger(BigInteger bigint)
- {
- verifyUninitialized();
-
- if (bigint == null)
- {
- throw new IllegalArgumentException("big integer value required");
- }
-
- // determine if the number of bytes allows the BigInteger value to be
- // stored in a long value
- if (!(m_fBig = calculateSignedByteCount(bigint) > 8))
- {
- m_lValue = bigint.longValue();
- }
- else
- {
- if (calculateUnsignedByteCount(bigint) <= 8)
- {
- // the unsigned value still fits the long
- m_lValue = bigint.longValue();
- }
- else
- {
- m_lValue = 0;
- }
- }
-
- m_bigint = bigint;
- m_fInitialized = true;
- }
-
- /**
- * @return the negated form of this packed integer
- */
- public PackedInteger negate()
- {
- return new PackedInteger(this.getBigInteger().negate());
- }
-
- /**
- * @return the complemented form of this packed integer
- */
- public PackedInteger complement()
- {
- return new PackedInteger(this.getBigInteger().not());
- }
-
- /**
- * @return the packed integer that is one less than this packed integer
- */
- public PackedInteger previous()
- {
- return sub(ONE);
- }
-
- /**
- * @return the packed integer that is one more than this packed integer
- */
- public PackedInteger next()
- {
- return add(ONE);
- }
-
- /**
- * Add the value of a specified PackedInteger to this PackedInteger, resulting in a new
- * PackedInteger.
- *
- * @param that a second PackedInteger to add to this
- *
- * @return the resulting PackedInteger
- */
- public PackedInteger add(PackedInteger that)
- {
- return new PackedInteger(this.getBigInteger().add(that.getBigInteger()));
- }
-
- /**
- * Subtract the value of a specified PackedInteger from this PackedInteger, resulting in a new
- * PackedInteger.
- *
- * @param that a second PackedInteger to subtract from this
- *
- * @return the resulting PackedInteger
- */
- public PackedInteger sub(PackedInteger that)
- {
- return new PackedInteger(this.getBigInteger().subtract(that.getBigInteger()));
- }
-
- /**
- * Multiply the value of a specified PackedInteger by this PackedInteger, resulting in a new
- * PackedInteger.
- *
- * @param that a second PackedInteger to multiply this by
- *
- * @return the resulting PackedInteger
- */
- public PackedInteger mul(PackedInteger that)
- {
- return new PackedInteger(this.getBigInteger().multiply(that.getBigInteger()));
- }
-
- /**
- * Divide the value of this PackedInteger by the specified PackedInteger, resulting in a new
- * PackedInteger.
- *
- * @param that a second PackedInteger to divide this by
- *
- * @return the resulting PackedInteger
- */
- public PackedInteger div(PackedInteger that)
- {
- return new PackedInteger(this.getBigInteger().divide(that.getBigInteger()));
- }
-
- /**
- * Calculate the modulo of the value of this PackedInteger and the value of a specified
- * PackedInteger, resulting in a new PackedInteger.
- *
- * @param that a second PackedInteger to divide this by, resulting in a modulo
- *
- * @return the resulting PackedInteger (never negative - modulo not remainder!)
- */
- public PackedInteger mod(PackedInteger that)
- {
- return new PackedInteger(this.getBigInteger().mod(that.getBigInteger()));
- }
-
- /**
- * Divide the value of this PackedInteger by the specified PackedInteger, resulting in a new
- * PackedInteger quotient and a new PackedInteger remainder.
- *
- * @param that a second PackedInteger to divide this by
- *
- * @return an array of the resulting PackedInteger quotient and remainder
- */
- public PackedInteger[] divrem(PackedInteger that)
- {
- BigInteger [] aBigInt = this.getBigInteger().divideAndRemainder(that.getBigInteger());
- PackedInteger[] aPacked = new PackedInteger[2];
- aPacked[0] = new PackedInteger(aBigInt[0]);
- aPacked[1] = new PackedInteger(aBigInt[1]);
- return aPacked;
- }
-
- /**
- * Calculate the bit-and of the value of this PackedInteger and the value of a specified
- * PackedInteger, resulting in a new PackedInteger.
- *
- * @param that a second PackedInteger to bit-and with this PackedInteger
- *
- * @return the resulting PackedInteger
- */
- public PackedInteger and(PackedInteger that)
- {
- return new PackedInteger(this.getBigInteger().and(that.getBigInteger()));
- }
-
- /**
- * Calculate the bit-or of the value of this PackedInteger and the value of a specified
- * PackedInteger, resulting in a new PackedInteger.
- *
- * @param that a second PackedInteger to bit-or with this PackedInteger
- *
- * @return the resulting PackedInteger
- */
- public PackedInteger or(PackedInteger that)
- {
- return new PackedInteger(this.getBigInteger().or(that.getBigInteger()));
- }
-
- /**
- * Calculate the bit-xor of the value of this PackedInteger and the value of a specified
- * PackedInteger, resulting in a new PackedInteger.
- *
- * @param that a second PackedInteger to bit-xor with this PackedInteger
- *
- * @return the resulting PackedInteger
- */
- public PackedInteger xor(PackedInteger that)
- {
- return new PackedInteger(this.getBigInteger().xor(that.getBigInteger()));
- }
-
- /**
- * Shift left the bits in the value of this PackedInteger by the value of a specified
- * PackedInteger, resulting in a new PackedInteger.
- *
- * @param that a second PackedInteger specifying the shift amount
- *
- * @return the resulting PackedInteger
- */
- public PackedInteger shl(PackedInteger that)
- {
- return shl(that.getInt());
- }
-
- /**
- * Shift left the bits in the value of this PackedInteger by the specified count,
- * resulting in a new PackedInteger.
- *
- * @param count the shift count
- *
- * @return the resulting PackedInteger
- */
- public PackedInteger shl(int count)
- {
- return new PackedInteger(this.getBigInteger().shiftLeft(count));
- }
-
- /**
- * Logical shift right the bits in the value of this PackedInteger by the value of a specified
- * PackedInteger, resulting in a new PackedInteger.
- *
- * @param that a second PackedInteger specifying the shift amount
- *
- * @return the resulting PackedInteger
- */
- public PackedInteger shr(PackedInteger that)
- {
- return shr(that.getInt());
- }
-
- /**
- * Logical shift right the bits in the value of this PackedInteger by the specified count,
- * resulting in a new PackedInteger.
- *
- * @param count the shift count
- *
- * @return the resulting PackedInteger
- */
- public PackedInteger shr(int count)
- {
- return count <= 0
- ? this
- : new PackedInteger(this.getBigInteger().shiftRight(count));
- }
-
- /**
- * Arithmetic (aka "unsigned") shift right the bits in the value of this PackedInteger by the
- * value of a specified PackedInteger, resulting in a new PackedInteger.
- *
- * @param that a second PackedInteger specifying the shift amount
- *
- * @return the resulting PackedInteger
- */
- public PackedInteger ushr(PackedInteger that)
- {
- return shr(that.getInt());
- }
-
- /**
- * Arithmetic (aka "unsigned") shift right the bits in the value of this PackedInteger by the
- * specified count, resulting in a new PackedInteger.
- *
- * @param count the shift count
- *
- * @return the resulting PackedInteger
- */
- public PackedInteger ushr(int count)
- {
- return shr(count);
- }
-
- /**
- * Compare the value of a specified PackedInteger with the value of this PackedInteger.
- *
- * @param that a second PackedInteger to compare to
- *
- * @return -1, 0, or 1 if this is less than, equal to, or greater than that
- */
- public int cmp(PackedInteger that)
- {
- return this.getBigInteger().compareTo(that.getBigInteger());
- }
-
- /**
- * Format the PackedInteger as a String of the specified radix, including a radix prefix for
- * any non-decimal radix.
- *
- * @param radix 2, 8, 10, or 16
- *
- * @return the String value of this PackedInteger in the format of the specified radix
- */
- public String toString(int radix)
- {
- if (radix == 10)
- {
- return toString();
- }
-
- StringBuilder sb = new StringBuilder();
- if (isNegative())
- {
- sb.append('-');
- }
-
- switch (radix)
- {
- case 2:
- sb.append("0b");
- break;
-
- case 8:
- sb.append("0o");
- break;
-
- case 16:
- sb.append("0x");
- break;
-
- default:
- throw new IllegalArgumentException("radix=" + radix);
- }
-
- sb.append(isBig()
- ? getBigInteger().abs().toString(radix)
- : Long.toUnsignedString(Math.abs(getLong()), radix));
-
- return sb.toString();
- }
-
- /**
- * Read a PackedInteger from a stream.
- *
- * @param in a DataInput stream to read from
- *
- * @throws IOException if an I/O exception occurs while reading the data
- */
- public void readObject(DataInput in)
- throws IOException
- {
- verifyUninitialized();
-
- final int b = in.readByte();
- // check for large or huge format with more than 8 trailing bytes (needs BigInteger)
- if ((b & 0b11) == 0b00 && (b & 0b111000_00) != 0)
- {
- final int cBytes;
- if ((b & 0xFF) == 0b111111_00)
- {
- // huge format
- long len = readLong(in);
- if (len > 1024)
- {
- throw new IOException("integer size of " + len + " bytes; maximum is 1024");
- }
- cBytes = (int) len;
- }
- else
- {
- // large format
- cBytes = 1 + ((b & 0xFC) >>> 2);
- }
-
- final byte[] ab = new byte[cBytes];
- in.readFully(ab);
- setBigInteger(new BigInteger(ab));
- }
- else
- {
- // tiny, small, medium, and large (up to 8 trailing bytes) format values fit into
- // a Java long
- setLong(readLong(in, b));
- }
- }
-
- /**
- * Write a PackedInteger to a stream.
- *
- * @param out a DataOutput stream to write to
- *
- * @throws IOException if an I/O exception occurs while writing the data
- */
- public void writeObject(DataOutput out)
- throws IOException
- {
- verifyInitialized();
-
- if (isBig())
- {
- // the value is supposed to be "big", so figure out how many bytes
- // (minimum) it needs to hold its significant bits
- byte[] ab = m_bigint.toByteArray();
- int cb = ab.length;
- int of = 0;
-
- // truncate any redundant bytes
- boolean fNeg = (ab[0] & 0x80) != 0;
- int bSkip = fNeg ? 0xFF : 0x00;
- while (of < cb-1 && ab[of] == bSkip && (ab[of+1] & 0x80) == (bSkip & 0x80))
- {
- ++of;
- }
- cb -= of;
- assert cb > 8 && cb <= 1024;
-
- if (cb < 64)
- {
- // large format: length encoded in 6 MSBs of first byte, then the bytes of the int
- out.writeByte((cb-1) << 2);
- }
- else
- {
- // huge format: first byte is 0, then the length as a packed int, then the bytes
- out.writeByte(0b111111_00);
- writeLong(out, cb);
- }
- out.write(ab, of, cb);
- }
- else
- {
- writeLong(out, m_lValue);
- }
- }
-
- /**
- * Verify that the PackedInteger is still uninitialized (has no integer
- * value).
- *
- * @throws IllegalStateException if the value of the PackedInteger has
- * already been set
- */
- public void verifyUninitialized()
- {
- if (m_fInitialized)
- {
- throw new IllegalStateException("already initialized");
- }
- }
-
- /**
- * Verify that the PackedInteger has been successfully initialized to an
- * integer value.
- *
- * @throws IllegalStateException if the value of the PackedInteger has
- * <b>not</b> yet been set
- */
- public void verifyInitialized()
- {
- if (!m_fInitialized)
- {
- throw new IllegalStateException("not yet initialized");
- }
- }
-
-
- // ----- Object methods ------------------------------------------------------------------------
-
- @Override
- public int hashCode()
- {
- return isBig() ? m_bigint.hashCode() : Long.hashCode(m_lValue);
- }
-
- @Override
- public boolean equals(Object obj)
- {
- if (!(obj instanceof PackedInteger that))
- {
- return false;
- }
-
- return this.isBig()
- ? that.isBig() && this.getBigInteger().equals(that.getBigInteger())
- : !that.isBig() && this.getLong() == that.getLong();
- }
-
- @Override
- public String toString()
- {
- return isBig() ? m_bigint.toString() : Long.toString(m_lValue);
- }
-
-
- // ----- Comparable methods --------------------------------------------------------------------
-
- @Override
- public int compareTo(PackedInteger that)
- {
- if (this.isBig() || that.isBig())
- {
- return this.getBigInteger().compareTo(that.getBigInteger());
- }
-
- long lThis = this.m_lValue;
- long lThat = that.m_lValue;
- return Long.compare(lThis, lThat);
- }
-
-
- // ----- public helpers ------------------------------------------------------------------------
-
- /**
- * Write a signed 64-bit integer to a stream using variable-length
- * encoding.
- *
- * @param out the <tt>DataOutput</tt> stream to write to
- * @param l the <tt>long</tt> value to write
- *
- * @throws IOException if an I/O exception occurs
- */
- public static void writeLong(DataOutput out, long l)
- throws IOException
- {
- // test for Tiny
- if (l <= 63 && l >= -64)
- {
- out.writeByte(((int) l) << 1 | 0x01);
- return;
- }
-
- final int cBits = 65 - Long.numberOfLeadingZeros(Math.max(l, ~l));
-
- // test for Small and Medium
- int i = (int) l;
- if (((1L << cBits) & 0x3E3E00L) != 0) // test against bits 9-13 and 17-21
- {
- if (cBits <= 13)
- {
- out.writeShort(0b010_00000000 // 0x2 marker at 0..2 in byte #1
- | (i & 0x1F00) << 3 // bits 8..12 at 3..7 in byte #1
- | (i & 0x00FF)); // bits 0..7 at 0..7 in byte #2
- }
- else
- {
- out.writeByte(0b110 // 0x6 marker at 0..2 in byte #1
- | (i & 0x1F0000) >>> 13); // bits 16..20 at 3..7 in byte #1
- out.writeShort(i); // bits 8..15 at 0..7 in byte #2
- } // bits 0..7 at 0..7 in byte #3
- return;
- }
-
- int cBytes = (cBits + 7) >>> 3;
- out.writeByte((cBytes - 1) << 2);
- switch (cBytes)
- {
- case 1:
- out.writeByte(i);
- break;
- case 2:
- out.writeShort(i);
- break;
- case 3:
- out.writeByte(i >>> 16);
- out.writeShort(i);
- break;
- case 4:
- out.writeInt(i);
- break;
- case 5:
- out.writeByte((int) (l >>> 32));
- out.writeInt(i);
- break;
- case 6:
- out.writeShort((int) (l >>> 32));
- out.writeInt(i);
- break;
- case 7:
- out.writeByte((int) (l >>> 48));
- out.writeShort((int) (l >>> 32));
- out.writeInt(i);
- break;
- case 8:
- out.writeLong(l);
- break;
- default:
- throw new IllegalStateException("n=" + l);
- }
- }
-
- /**
- * Read a variable-length encoded integer value from a stream.
- *
- * @param in a <tt>DataInput</tt> stream to read from
- *
- * @return a <tt>long</tt> value
- *
- * @throws IOException if an I/O exception occurs
- * @throws NumberFormatException if the integer does not fit into
- * a <tt>long</tt> value
- */
- public static long readLong(DataInput in)
- throws IOException
- {
- return readLong(in, in.readByte());
- }
-
- /**
- * Determine the number of bytes of the packed integer at the specified offset in the provided
- * byte array.
- *
- * @param ab the byte array containing a packed integer
- * @param of the byte offset at which the packed integer is located
- *
- * @return the number of bytes used to encode the packed integer
- */
- public static int packedLength(byte[] ab, int of)
- {
- int b = ab[of];
- if ((b & 0x01) != 0)
- {
- // Tiny format
- return 1;
- }
-
- if ((b & 0x02) != 0)
- {
- // Small or Medium format
- return ((b & 0x04) == 0) ? 2 : 3;
- }
-
- if ((b & 0xFF) == 0b111111_00)
- {
- // Huge format
- long lVals = unpackInt(ab, of+1);
- return 1 + (int) (lVals >>> 32) + (int) lVals;
- }
-
- // Large format
- return 2 + ((b & 0xFC) >>> 2);
- }
-
- /**
- * Determine the number of bytes that the specified value would use if it were packed.
- *
- * @param n the long integer value to estimate a packed length for
- *
- * @return the smallest number of bytes necessary to encode the packed integer
- */
- public static int packedLength(long n)
- {
- // test for Tiny
- if (n <= 63 && n >= -64)
- {
- return 1;
- }
-
- // test for Small and Medium
- final int cBits = 65 - Long.numberOfLeadingZeros(Math.max(n, ~n));
- if (((1L << cBits) & 0x3E3E00L) != 0) // test against bits 9-13 and 17-21
- {
- return cBits <= 13 ? 2 : 3;
- }
-
- // Large format
- int cBytes = (cBits + 7) >>> 3;
- return 1 + cBytes;
- }
-
- /**
- * Extract an integer from a byte array, and report back both the integer value and its size in
- * terms of the number of bytes.
- *
- * @param ab the byte array to unpack an integer from
- * @param of the byte offset at which the integer is located
- *
- * @return the int value encoded as bits 0..31, and the number of bytes used by the value
- * encoded as bits 32..63
- */
- public static long unpackInt(byte[] ab, int of)
- {
- int n;
- int cb;
-
- int b = ab[of];
- if ((b & 0x01) != 0)
- {
- // Tiny format: the first bit of the first byte is used to indicate a single byte
- // format, in which the entire value is contained in the 7 MSBs
- n = b >> 1;
- cb = 1;
- }
- else if ((b & 0x02) != 0)
- {
- // the third bit is used to indicate 0: 1 trailing byte, or 1: 2 trailing bytes
- if ((b & 0x04) == 0)
- {
- // Small format: bits 3..7 of the first byte are bits 8..12 of the result, and the
- // next byte provides bits 0..7 of the result (note: and then also sign-extend)
- n = (b & 0xFFFFFFF8) << 5 | ab[of+1] & 0xFF;
- cb = 2;
- }
- else
- {
- // Medium format: bits 3..7 of the first byte are bits 16..20 of the result, and the
- // next byte provides bits 8..15 of the result, and the next byte provides bits 0..7
- // of the result (note: and then also sign-extend)
- n = (b & 0xFFFFFFF8) << 13 | (ab[of+1] & 0xFF) << 8 | ab[of+2] & 0xFF;
- cb = 3;
- }
- }
- else
- {
- // Large format: the first two bits of the first byte are 0, so bits 2..7 of the
- // first byte are the trailing number of bytes minus 1
- // Huge format: the first byte is 0b111111_00, so it is followed by a packed integer
- // that specifies the length of this packed integer
- final int cBytes;
- if ((b & 0xFF) == 0b111111_00)
- {
- // Huge format
- long lVals = unpackInt(ab, of+1);
- int cbcb = (int) (lVals >>> 32);
- cBytes = (int) lVals;
- cb = 1 + cbcb + cBytes;
- of += 1 + cbcb;
- }
- else
- {
- // Large format
- cBytes = 1 + ((b & 0xFC) >>> 2);
- cb = 1 + cBytes;
- ++of;
- }
-
- switch (cBytes)
- {
- case 1:
- n = ab[of];
- break;
- case 2:
- n = ab[of] << 8 | ab[of+1] & 0xFF;
- break;
- case 3:
- n = ab[of] << 16 | (ab[of+1] & 0xFF) << 8 | ab[of+2] & 0xFF;
- break;
- case 4:
- n = ab[of] << 24 | (ab[of+1] & 0xFF) << 16 | (ab[of+2] & 0xFF) << 8 | ab[of+3] & 0xFF;
- break;
- default:
- throw new IllegalStateException("# trailing bytes=" + cBytes);
- }
- }
-
- return ((long) cb) << 32 | n & 0xFFFFFFFFL;
- }
-
-
- // ----- internal ------------------------------------------------------------------------------
-
- private static long readLong(DataInput in, int b)
- throws IOException
- {
- if ((b & 0x01) != 0)
- {
- // Tiny format: the first bit of the first byte is used to indicate a single byte
- // format, in which the entire value is contained in the 7 MSBs
- return b >> 1;
- }
-
- if ((b & 0x02) != 0)
- {
- // the third bit is used to indicate 0: 1 trailing byte, or 1: 2 trailing bytes
- if ((b & 0x04) == 0)
- {
- // Small format: bits 3..7 of the first byte are bits 8..12 of the result, and the
- // next byte provides bits 0..7 of the result (note: and then also sign-extend)
- return (long) (b & 0xFFFFFFF8) << 5 | in.readUnsignedByte();
- }
- else
- {
- // Medium format: bits 3..7 of the first byte are bits 16..20 of the result, and the
- // next byte provides bits 8..15 of the result, and the next byte provides bits 0..7
- // of the result (note: and then also sign-extend)
- return (long) (b & 0xFFFFFFF8) << 13 | in.readUnsignedShort();
- }
- }
-
- // Large format: the first two bits of the first byte are 0, so bits 2..7 of the
- // first byte are the trailing number of bytes minus 1
- // Huge format: the first byte is 0, so it is followed by a packed integer that
- // specifies the length of this packed integer
- int cBytes = (b & 0xFF) == 0b111111_00 ? (int) readLong(in) : 1 + ((b & 0xFC) >>> 2);
- switch (cBytes)
- {
- case 1:
- return in.readByte();
- case 2:
- return in.readShort();
- case 3:
- return in.readByte() << 16 | in.readUnsignedShort();
- case 4:
- return in.readInt();
- case 5:
- return ((long) in.readByte()) << 32 | readUnsignedInt(in);
- case 6:
- return ((long) in.readShort()) << 32 | readUnsignedInt(in);
- case 7:
- return ((long) in.readByte()) << 48
- | ((long) in.readUnsignedShort()) << 32
- | readUnsignedInt(in);
- case 8:
- return in.readLong();
- default:
- throw new IllegalStateException("# trailing bytes=" + cBytes);
- }
- }
-
- private static long readUnsignedInt(DataInput in)
- throws IOException
- {
- return in.readInt() & 0xFFFFFFFFL;
- }
-
- /**
- * Determine how many bytes is necessary to hold the specified BigInteger.
- *
- * @return the minimum number of bytes to hold the integer value in
- * 2s-complement form
- *
- * @throws IllegalArgumentException if the BigInteger is out of range
- */
- private static int calculateSignedByteCount(BigInteger bigint)
- {
- return bigint.bitLength() / 8 + 1;
- }
-
- /**
- * Determine how many bytes is necessary to hold the specified BigInteger.
- *
- * @return the minimum number of bytes to hold the integer value in
- * 2s-complement form
- *
- * @throws IllegalArgumentException if the BigInteger is out of range
- */
- private static int calculateUnsignedByteCount(BigInteger bigint)
- {
- return (bigint.bitLength() + 7) / 8;
- }
-
-
- // ----- data members --------------------------------------------------------------------------
-
- /**
- * Set to true once the value has been set.
- */
- private boolean m_fInitialized;
-
- /**
- * Set to true if the value is too large to fit into a <tt>long</tt> (for signed values).
- */
- private boolean m_fBig;
-
- /**
- * The <tt>long</tt> value if the value fits into a <tt>long</tt>. Note, the value could be "big",
- * as a signed one but still fit the long as unsigned.
- */
- private long m_lValue;
-
- /**
- * The <tt>BigInteger</tt> value, which is non-null in several different
- * cases, including if the PackedInteger was constructed with a BigInteger
- * value, if the value was set to a BigInteger, or if the BigInteger was
- * lazily instantiated by a call to {@link #getBigInteger}.
- */
- private BigInteger m_bigint;
-
- /**
- * Cache of PackedInteger instances for small integers.
- */
- private static final PackedInteger[] CACHE = new PackedInteger[0x1000];
-
-
- // ----- constants -----------------------------------------------------------------------------
-
- /**
- * Smallest integer value to cache. One quarter of the cache size is
- * reserved for small negative integer values.
- */
- private static final long CACHE_MIN = -(CACHE.length >> 2);
-
- /**
- * Largest integer value to cache. Three-quarters of the cache size is
- * reserved for small positive integer values.
- */
- private static final long CACHE_MAX = CACHE_MIN + CACHE.length - 1;
-
- /**
- * The PackedInteger for the value <tt>0</tt>. Also used as the smallest 1-, 2-, 4-,
- * 8-, 16-, and 32-byte <b>un</b>signed integer value.
- */
- public static final PackedInteger ZERO = valueOf(0L);
- /**
- * The PackedInteger for the value <tt>1</tt>.
- */
- public static final PackedInteger ONE = valueOf(1L);
- /**
- * The PackedInteger for the value <tt>-1</tt>.
- */
- public static final PackedInteger NEG_ONE = valueOf(-1L);
-
- /**
- * Smallest 1-byte (8-bit) signed integer value.
- */
- public static final PackedInteger SINT1_MIN = valueOf(-0x80L);
- /**
- * Smallest 2-byte (16-bit) signed integer value.
- */
- public static final PackedInteger SINT2_MIN = valueOf(-0x8000L);
- /**
- * Smallest 4-byte (32-bit) signed integer value.
- */
- public static final PackedInteger SINT4_MIN = valueOf(-0x80000000L);
- /**
- * Smallest 8-byte (64-bit) signed integer value.
- */
- public static final PackedInteger SINT8_MIN = valueOf(-0x8000000000000000L);
- /**
- * Smallest 16-byte (128-bit) signed integer value.
- */
- public static final PackedInteger SINT16_MIN = new PackedInteger(new BigInteger("-8" + "0".repeat(31), 16));
- /**
- * Smallest N-byte signed integer value (arbitrary 1KB limit).
- */
- public static final PackedInteger SINTN_MIN = new PackedInteger(new BigInteger("-8" + "0".repeat(2047), 16));
- /**
- * Largest 1-byte (8-bit) signed integer value.
- */
- public static final PackedInteger SINT1_MAX = valueOf(0x7F);
- /**
- * Largest 2-byte (16-bit) signed integer value.
- */
- public static final PackedInteger SINT2_MAX = valueOf(0x7FFF);
- /**
- * Largest 4-byte (32-bit) signed integer value.
- */
- public static final PackedInteger SINT4_MAX = valueOf(0x7FFF_FFFF);
- /**
- * Largest 8-byte (64-bit) signed integer value.
- */
- public static final PackedInteger SINT8_MAX = valueOf(0x7FFF_FFFF_FFFF_FFFFL);
- /**
- * Largest 16-byte (128-bit) signed integer value.
- */
- public static final PackedInteger SINT16_MAX = new PackedInteger(new BigInteger("7" + "F".repeat(31), 16));
- /**
- * Largest N-byte signed integer value (arbitrary 1KB limit).
- */
- public static final PackedInteger SINTN_MAX = new PackedInteger(new BigInteger("7" + "F".repeat(2047), 16));
- /**
- * Largest 1-byte (8-bit) unsigned integer value.
- */
- public static final PackedInteger UINT1_MAX = valueOf(0xFF);
- /**
- * Largest 2-byte (16-bit) unsigned integer value.
- */
- public static final PackedInteger UINT2_MAX = valueOf(0xFFFF);
- /**
- * Largest 4-byte (32-bit) unsigned integer value.
- */
- public static final PackedInteger UINT4_MAX = valueOf(0xFFFF_FFFFL);
- /**
- * Largest 8-byte (64-bit) unsigned integer value.
- */
- public static final PackedInteger UINT8_MAX = new PackedInteger(new BigInteger("F".repeat(16), 16));
- /**
- * Largest 16-byte (128-bit) unsigned integer value.
- */
- public static final PackedInteger UINT16_MAX = new PackedInteger(new BigInteger("F".repeat(32), 16));
- /**
- * Largest N-byte unsigned integer value (arbitrary 1KB limit).
- */
- public static final PackedInteger UINTN_MAX = SINTN_MAX;
-
- /**
- * Decimal "Kilo".
- */
- public static final PackedInteger KB = valueOf(1000);
- /**
- * Binary "Kilo".
- */
- public static final PackedInteger KI = valueOf(1024);
- /**
- * Decimal "Mega".
- */
- public static final PackedInteger MB = valueOf(1000 * 1000);
- /**
- * Binary "Mega".
- */
- public static final PackedInteger MI = valueOf(1024 * 1024);
- /**
- * Decimal "Giga".
- */
- public static final PackedInteger GB = valueOf(1000 * 1000 * 1000);
- /**
- * Binary "Giga".
- */
- public static final PackedInteger GI = valueOf(1024 * 1024 * 1024);
- /**
- * Decimal "Tera".
- */
- public static final PackedInteger TB = valueOf(1000L * 1000 * 1000 * 1000);
- /**
- * Binary "Tera".
- */
- public static final PackedInteger TI = valueOf(1024L * 1024 * 1024 * 1024);
- /**
- * Decimal "Peta".
- */
- public static final PackedInteger PB = valueOf(1000L * 1000 * 1000 * 1000 * 1000);
- /**
- * Binary "Peta".
- */
- public static final PackedInteger PI = valueOf(1024L * 1024 * 1024 * 1024 * 1024);
- /**
- * Decimal "Exa".
- */
- public static final PackedInteger EB = valueOf(1000L * 1000 * 1000 * 1000 * 1000 * 1000);
- /**
- * Binary "Exa".
- */
- public static final PackedInteger EI = valueOf(1024L * 1024 * 1024 * 1024 * 1024 * 1024);
- /**
- * Decimal "Zetta".
- */
- public static final PackedInteger ZB = EB.mul(KB);
- /**
- * Binary "Zetta".
- */
- public static final PackedInteger ZI = EI.mul(KI);
- /**
- * Decimal "Yotta".
- */
- public static final PackedInteger YB = ZB.mul(KB);
- /**
- * Binary "Yotta".
- */
- public static final PackedInteger YI = ZI.mul(KI);
-
- /**
- * The decimal (1000x) factors.
- */
- public static final PackedInteger[] xB_FACTORS = {KB, MB, GB, TB, PB, EB, ZB, YB, };
- /**
- * The binary (1024x) factors.
- */
- public static final PackedInteger[] xI_FACTORS = {KI, MI, GI, TI, PI, EI, ZI, YI, };
- } \ No newline at end of file
diff --git a/common/src/main/java/com/seaofnodes/util/SB.java b/common/src/main/java/com/seaofnodes/util/SB.java
deleted file mode 100644
index 4481ce71..00000000
--- a/common/src/main/java/com/seaofnodes/util/SB.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * The Initial Developer of the Original Software is Cliff Click.
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-
-package com.seaofnodes.util;
-
-
-/** Tight/tiny StringBuilder wrapper.
- * Short short names on purpose; so they don't obscure the printing.
- * Can't believe this wasn't done long, long ago. */
-public final class SB {
- private final StringBuilder _sb;
- private int _indent = 0;
- public SB( ) { _sb = new StringBuilder( ); }
- public SB(String s) { _sb = new StringBuilder(s); }
- public SB p( String s ) { _sb.append(s); return this; }
- //public SB p( Type t ) { _sb.append(t.toString()); return this; }
- public SB p( float s ) {
- if( Float.isNaN(s) )
- _sb.append( "Float.NaN");
- else if( Float.isInfinite(s) ) {
- _sb.append(s > 0 ? "Float.POSITIVE_INFINITY" : "Float.NEGATIVE_INFINITY");
- } else _sb.append(s);
- return this;
- }
- public SB p( double s ) {
- if( Double.isNaN(s) )
- _sb.append("Double.NaN");
- else if( Double.isInfinite(s) ) {
- _sb.append(s > 0 ? "Double.POSITIVE_INFINITY" : "Double.NEGATIVE_INFINITY");
- } else _sb.append(s);
- return this;
- }
- public SB p( char s ) { _sb.append(s); return this; }
- public SB p( int s ) { _sb.append(s); return this; }
- public SB p( long s ) { _sb.append(s); return this; }
- public SB p( boolean s) { _sb.append(s); return this; }
- // Not spelled "p" on purpose: too easy to accidentally say "p(1.0)" and
- // suddenly call the autoboxed version.
- public SB pobj( Object s ) { _sb.append(s.toString()); return this; }
- public SB i( int d ) { for( int i=0; i<d+_indent; i++ ) p(" "); return this; }
- public SB i( ) { return i(0); }
- public SB ip(String s) { return i().p(s); }
- public SB s() { _sb.append(' '); return this; }
-
- // Increase indentation
- public SB ii( int i) { _indent += i; return this; }
- // Decrease indentation
- public SB di( int i) { _indent -= i; return this; }
-
- public SB nl( ) { return p(System.lineSeparator()); }
-
- // Delete last char. Useful when doing string-joins and JSON printing and an
- // extra seperater char needs to be removed:
- //
- // sb.p('[');
- // for( Foo foo : foos )
- // sb.p(foo).p(',');
- // sb.unchar().p(']'); // remove extra trailing comma
- //
- public SB unchar() { return unchar(1); }
- public SB unchar(int x) { _sb.setLength(_sb.length()-x); return this; }
-
- public SB clear() { _sb.setLength(0); return this; }
- public int len() { return _sb.length(); }
- @Override public String toString() { return _sb.toString(); }
-}
diff --git a/common/src/main/java/com/seaofnodes/util/Util.java b/common/src/main/java/com/seaofnodes/util/Util.java
deleted file mode 100644
index e559a799..00000000
--- a/common/src/main/java/com/seaofnodes/util/Util.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/**
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * The Initial Developer of the Original Software is Cliff Click.
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-
-package com.seaofnodes.util;
-
-import java.util.Arrays;
-
-public class Util {
- // Fast linear scan for a hit, returns index or -1
- public static int find( int[] es, int e ) {
- for( int i=0; i<es.length; i++ ) if( es[i]==e ) return i;
- return -1;
- }
- // Fast linear scan for a hit, returns index or -1
- public static <E> int find( E[] es, E e ) {
- for( int i=0; i<es.length; i++ ) if( es[i]==e ) return i;
- return -1;
- }
-
- // String-equals, with expected interned strings
- public static boolean eq( String s0, String s1 ) {
- if( s0==s1 ) return true;
- if( s0==null || s1==null ) return false;
- assert !s0.equals(s1) : "Not interned: "+s0;
- return false;
- }
-
-
- // Copied from http://burtleburtle.net/bob/c/lookup3.c
- // Call add_hash as many times as you like, then get_hash at the end.
- // Uses global statics, does not nest.
- public static long rot(long x, int k) { return (x<<k) | (x>>>(64-k)); }
- private static long a,b,c;
- private static int x;
- static public void add_hash( long h ) {
- switch( x ) {
- case 0: a+=h; x++; return;
- case 1: b+=h; x++; return;
- case 2: c+=h;
- a -= c; a ^= rot(c, 4); c += b;
- b -= a; b ^= rot(a, 6); a += c;
- c -= b; c ^= rot(b, 8); b += a;
- a -= c; a ^= rot(c,16); c += b;
- b -= a; b ^= rot(a,19); a += c;
- c -= b; c ^= rot(b, 4); b += a;
- x=0;
- }
- }
- // Return the resulting hash, which is never 0
- static public long get_hash() {
- if( x!=0 ) {
- c ^= b; c -= rot(b,14);
- a ^= c; a -= rot(c,11);
- b ^= a; b -= rot(a,25);
- c ^= b; c -= rot(b,16);
- a ^= c; a -= rot(c, 4);
- b ^= a; b -= rot(a,14);
- c ^= b; c -= rot(b,24);
- }
- long hash=c;
- if( hash==0 ) hash=b;
- if( hash==0 ) hash=a;
- if( hash==0 ) hash=0xcafebabe;
- a=b=c=x=0;
- return hash;
- }
- // Single-use hash spreader
- static public long mix_hash(long h0) {
- add_hash(h0);
- return get_hash();
- }
- static public long mix_hash( long h0, long h1 ) {
- add_hash(h0);
- add_hash(h1);
- return get_hash();
- }
- static public long mix_hash( long h0, long h1, long h2 ) {
- add_hash(h0);
- add_hash(h1);
- add_hash(h2);
- return get_hash();
- }
- static public long mix_hash( long h0, long h1, long h2, long h3 ) {
- add_hash(h0);
- add_hash(h1);
- add_hash(h2);
- add_hash(h3);
- return get_hash();
- }
- static public long mix_hash( long h0, long h1, long h2, long h3, long h4 ) {
- add_hash(h0);
- add_hash(h1);
- add_hash(h2);
- add_hash(h3);
- add_hash(h4);
- return get_hash();
- }
-
- static public int gcd(int x, int y) {
- if( x==0 || y== 0 ) return 0;
- int a = Math.max(x,y), r;
- int b = Math.min(x,y);
- while((r=(a % b)) != 0) { a = b; b = r; }
- return b;
- }
-
- static public boolean isUpperCase( String s ) {
- for( int i=0; i<s.length() ; i++ )
- if( !Character.isUpperCase(s.charAt(i)) )
- return false;
- return true;
- }
-
- // Return a merged sorted array from two sorted arrays, removing dups.
- // If the merge is the same as either array, return that array.
- // If the merge is the same as BOTH arrays, return the array with the smaller
- // hashCode (goal: canonicalize identical arrays, so the fast == check works
- // more often, and we do not make lots of equals-not-== arrays).
- static public short[] merge_sort( short[] es0, short[] es1 ) {
- if( es0==es1 ) return es0;
- if( Arrays.equals(es0,es1) )
- throw new UnsupportedOperationException("Not implemented");
- short[] xs = new short[es0.length+es1.length];
- int i=0, j=0, k=0;
- while( i<es0.length || j<es1.length ) {
- short s0 = i<es0.length ? es0[i] : Short.MAX_VALUE, s1 = j<es1.length ? es1[j] : Short.MAX_VALUE, s2=s0;
- if( s0<=s1 ) { i++; s2=s0; }
- if( s1<=s0 ) { j++; s2=s1; }
- xs[k++] = s2;
- }
- assert k>i && k>j; // Arrays were not equals, so at least one element from each copied to other
- if( k < xs.length )
- throw new UnsupportedOperationException("Not implemented"); // Compact
- return xs;
- }
-
-}
diff --git a/common/src/main/java/com/seaofnodes/util/VBitSet.java b/common/src/main/java/com/seaofnodes/util/VBitSet.java
deleted file mode 100644
index 5d00a9e4..00000000
--- a/common/src/main/java/com/seaofnodes/util/VBitSet.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * The Initial Developer of the Original Software is Cliff Click.
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-
-package com.seaofnodes.util;
-
-import java.util.BitSet;
-
-public class VBitSet extends BitSet {
- // Cannot override 'set' to return a value... :-P
- public boolean tset(int idx) { boolean b = get(idx); set(idx); return b; }
- public boolean test(int idx) { return get(idx); }
- public VBitSet clr() { clear(); return this; }
-}
diff --git a/common/src/test/java/com/seaofnodes/simple/lexer/TestLexer.java b/common/src/test/java/com/seaofnodes/simple/lexer/TestLexer.java
deleted file mode 100644
index aec5d171..00000000
--- a/common/src/test/java/com/seaofnodes/simple/lexer/TestLexer.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-
-package com.seaofnodes.simple.lexer;
-
-import com.seaofnodes.simple.lexer.ecstasy.ErrorListener;
-import com.seaofnodes.simple.lexer.ecstasy.Lexer;
-import com.seaofnodes.simple.lexer.ecstasy.Source;
-import com.seaofnodes.simple.lexer.ecstasy.Token;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class TestLexer {
-
- @Test
- public void testLexLineComment()
- {
- final String source = "//a comment\nx";
- Lexer lexer = new Lexer(new Source(source), new ErrorListener());
- Token token = lexer.next();
- Assert.assertEquals(token.getId(), Token.Id.EOL_COMMENT);
- token = lexer.next();
- Assert.assertEquals("x", token.getValueText());
- }
-}
diff --git a/pom.xml b/pom.xml
index b250e469..9f9601d9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,7 +19,6 @@
</licenses>
<modules>
- <module>common</module>
<module>language</module>
<module>chapter01</module>
</modules>