package php.runtime.util;
import php.runtime.Memory;
import php.runtime.env.Environment;
import php.runtime.env.TraceInfo;
import php.runtime.memory.DoubleMemory;
import php.runtime.memory.LongMemory;
import php.runtime.memory.ReferenceMemory;
import php.runtime.memory.StringMemory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
final public class SScanF {
private SScanF(){}
private static void addConstant(
List<Segment> segmentList, StringBuilder sb) {
if (sb.length() == 0)
return;
segmentList.add(new ConstantSegment(sb.toString()));
sb.setLength(0);
}
public static Segment[] parse(Environment env, TraceInfo trace, String format) {
int fmtLen = format.length();
int fIndex = 0;
List<Segment> segmentList = new ArrayList<Segment>();
StringBuilder sb = new StringBuilder();
while (fIndex < fmtLen) {
char ch = format.charAt(fIndex++);
if (isWhitespace(ch)) {
StringBuilder whiteSpaceValue = new StringBuilder();
whiteSpaceValue.append(ch);
for (;
(fIndex < fmtLen && isWhitespace(ch = format.charAt(fIndex)));
fIndex++) {
whiteSpaceValue.append(ch);
}
addConstant(segmentList, sb);
segmentList.add(new WhitespaceSegment(whiteSpaceValue.toString()));
} else if (ch == '%') {
int maxLen = -1;
loop:
while (fIndex < fmtLen) {
ch = format.charAt(fIndex++);
switch (ch) {
case '%':
sb.append('%');
break loop;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (maxLen < 0)
maxLen = 0;
maxLen = 10 * maxLen + ch - '0';
break;
case 's': {
addConstant(segmentList, sb);
segmentList.add(new StringSegment(maxLen, ch));
break loop;
}
case 'c': {
if (maxLen < 0)
maxLen = 1;
addConstant(segmentList, sb);
segmentList.add(new StringSegment(maxLen, ch));
break loop;
}
case 'n': {
addConstant(segmentList, sb);
segmentList.add(StringLengthSegment.SEGMENT);
break loop;
}
case 'd': {
addConstant(segmentList, sb);
segmentList.add(new IntegerSegment(maxLen, 10, false));
break loop;
}
case 'u': {
addConstant(segmentList, sb);
segmentList.add(new IntegerSegment(maxLen, 10, true));
break loop;
}
case 'o': {
addConstant(segmentList, sb);
segmentList.add(new IntegerSegment(maxLen, 8, false));
break loop;
}
case 'x':
case 'X': {
addConstant(segmentList, sb);
segmentList.add(new HexSegment(maxLen));
break loop;
}
case 'e':
case 'f': {
addConstant(segmentList, sb);
segmentList.add(new ScientificSegment(maxLen, ch));
break loop;
}
case '[': {
addConstant(segmentList, sb);
if (fmtLen <= fIndex) {
if (env != null)
env.warning(trace, "expected ']', saw end of string");
break loop;
}
boolean isNegated = false;
if (fIndex < fmtLen
&& format.charAt(fIndex) == '^') {
isNegated = true;
fIndex++;
}
Set<Integer> set = new HashSet<Integer>();
while (true) {
if (fIndex == fmtLen) {
if (env != null)
env.warning(trace, "expected ']', saw end of string");
break loop;
}
char ch2 = format.charAt(fIndex++);
if (ch2 == ']') {
break;
} else {
set.add((int) ch2);
}
}
if (isNegated)
segmentList.add(new SetNegatedSegment(set));
else
segmentList.add(new SetSegment(set));
break loop;
}
default:
env.warning(trace, "'%s' is a bad sscanf string", format);
// XXX:
//return isAssign ? LongValue.create(argIndex) : array;
break loop;
}
}
} else
sb.append(ch);
}
addConstant(segmentList, sb);
Segment[] segmentArray = new Segment[segmentList.size()];
return segmentList.toArray(segmentArray);
}
protected static boolean isWhitespace(char ch) {
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
}
public abstract static class Segment {
abstract public boolean isAssigned();
abstract public int apply(String string, int strlen, int sIndex, ReferenceMemory var, boolean isReturnArray);
void sscanfPut(Memory var, Memory val, boolean isReturnArray) {
if (isReturnArray)
var.refOfPush().assign(val);
else
var.assign(val);
}
public String getFormatString(){
return "";
}
}
static class ConstantSegment extends Segment {
private final String _string;
private final int _strlen;
private ConstantSegment(String string) {
_string = string;
_strlen = string.length();
}
@Override
public boolean isAssigned() {
return false;
}
@Override
public String getFormatString() {
return _string;
}
@Override
public int apply(String string, int strlen, int sIndex, ReferenceMemory var, boolean isReturnArray) {
int fStrlen = _strlen;
String fString = _string;
if (strlen - sIndex < fStrlen)
return -1;
for (int i = 0; i < fStrlen; i++) {
char ch = string.charAt(sIndex++);
char ch2 = fString.charAt(i);
if (ch != ch2)
return -1;
}
return sIndex;
}
}
static class WhitespaceSegment extends Segment {
private String originValue = " ";
private WhitespaceSegment(String originValue) {
this.originValue = originValue;
}
@Override
public String getFormatString() {
return originValue;
}
@Override
public boolean isAssigned() {
return false;
}
@Override
public int apply(String string, int strlen, int sIndex, ReferenceMemory var, boolean isReturnArray) {
for (;
sIndex < strlen && isWhitespace(string.charAt(sIndex));
sIndex++) {
}
return sIndex;
}
}
static class StringLengthSegment extends Segment {
static final StringLengthSegment SEGMENT = new StringLengthSegment();
private StringLengthSegment() {
}
@Override
public String getFormatString() {
return "%n";
}
@Override
public boolean isAssigned() {
return true;
}
@Override
public int apply(String string, int strlen, int sIndex, ReferenceMemory var, boolean isReturnArray) {
sscanfPut(var, LongMemory.valueOf(sIndex), isReturnArray);
return sIndex;
}
}
static class SetSegment extends Segment {
private Set<Integer> _set;
private SetSegment(Set<Integer> set) {
_set = set;
}
@Override
public boolean isAssigned() {
return true;
}
@Override
public int apply(String string, int strlen, int sIndex, ReferenceMemory var, boolean isReturnArray) {
StringBuilder sb = new StringBuilder();
for (; sIndex < strlen; sIndex++) {
char ch = string.charAt(sIndex);
if (_set.contains((int) ch)) {
sb.append(ch);
} else {
break;
}
}
if (sb.length() > 0)
sscanfPut(var, new StringMemory(sb.toString()), isReturnArray);
else if (isReturnArray)
var.refOfPush().assign(Memory.NULL);
return sIndex;
}
}
static class SetNegatedSegment extends Segment {
private Set<Integer> _set;
private SetNegatedSegment(Set<Integer> set) {
_set = set;
}
@Override
public boolean isAssigned() {
return true;
}
@Override
public int apply(String string, int strlen, int sIndex, ReferenceMemory var, boolean isReturnArray) {
StringBuilder sb = new StringBuilder();
for (; sIndex < strlen; sIndex++) {
char ch = string.charAt(sIndex);
if (!_set.contains((int) ch)) {
sb.append(ch);
} else {
break;
}
}
if (sb.length() > 0)
sscanfPut(var, new StringMemory(sb.toString()), isReturnArray);
else if (isReturnArray)
var.refOfPush().assign(Memory.NULL);
return sIndex;
}
}
static class ScientificSegment extends Segment {
private final int _maxLen;
private final char _ch;
ScientificSegment(int maxLen, char ch) {
if (maxLen < 0)
maxLen = Integer.MAX_VALUE;
_maxLen = maxLen;
_ch = ch;
}
@Override
public String getFormatString() {
return "%" + String.valueOf(_ch);
}
@Override
public boolean isAssigned() {
return true;
}
@Override
public int apply(String s, int strlen, int i, ReferenceMemory var, boolean isReturnArray) {
if (i == strlen) {
if (isReturnArray)
var.refOfPush().assign(Memory.NULL);
return i;
}
int start = i;
int len = strlen;
int ch = 0;
int maxLen = _maxLen;
if (i < len && maxLen > 0 && ((ch = s.charAt(i)) == '+' || ch == '-')) {
i++;
maxLen--;
}
for (; i < len && maxLen > 0
&& '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
maxLen--;
}
if (ch == '.') {
maxLen--;
for (i++; i < len && maxLen > 0
&& '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
maxLen--;
}
}
if (ch == 'e' || ch == 'E') {
maxLen--;
int e = i++;
if (start == e) {
sscanfPut(var, Memory.NULL, isReturnArray);
return start;
}
if (i < len && maxLen > 0 && (ch = s.charAt(i)) == '+' || ch == '-') {
i++;
maxLen--;
}
for (; i < len && maxLen > 0
&& '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
maxLen--;
}
if (i == e + 1)
i = e;
}
double val;
if (i == 0)
val = 0;
else
val = Double.parseDouble(s.substring(start, i));
sscanfPut(var, new DoubleMemory(val), isReturnArray);
return i;
}
}
static class HexSegment extends Segment {
private final int _maxLen;
HexSegment(int maxLen) {
if (maxLen < 0)
maxLen = Integer.MAX_VALUE;
_maxLen = maxLen;
}
@Override
public String getFormatString() {
return "%x";
}
@Override
public boolean isAssigned() {
return true;
}
@Override
public int apply(String string, int strlen, int sIndex, ReferenceMemory var, boolean isReturnArray) {
if (sIndex == strlen) {
if (isReturnArray)
var.refOfPush().assign(Memory.NULL);
return sIndex;
}
int val = 0;
int sign = 1;
boolean isMatched = false;
int maxLen = _maxLen;
if (sIndex < strlen) {
char ch = string.charAt(sIndex);
if (ch == '+') {
sIndex++;
maxLen--;
} else if (ch == '-') {
sign = -1;
sIndex++;
maxLen--;
}
}
for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
char ch = string.charAt(sIndex);
if ('0' <= ch && ch <= '9') {
val = val * 16 + ch - '0';
isMatched = true;
} else if ('a' <= ch && ch <= 'f') {
val = val * 16 + ch - 'a' + 10;
isMatched = true;
} else if ('A' <= ch && ch <= 'F') {
val = val * 16 + ch - 'A' + 10;
isMatched = true;
} else if (!isMatched) {
sscanfPut(var, Memory.NULL, isReturnArray);
return sIndex;
} else
break;
}
sscanfPut(var, LongMemory.valueOf(val * sign), isReturnArray);
return sIndex;
}
}
static class IntegerSegment extends Segment {
private final int _maxLen;
private final int _base;
private final boolean _isUnsigned;
IntegerSegment(int maxLen, int base, boolean isUnsigned) {
if (maxLen < 0)
maxLen = Integer.MAX_VALUE;
_maxLen = maxLen;
_base = base;
_isUnsigned = isUnsigned;
}
@Override
public String getFormatString() {
switch (_base){
case 10: return _isUnsigned ? "%u" : "%d";
case 8: return "%o";
}
return "%d";
}
@Override
public boolean isAssigned() {
return true;
}
@Override
public int apply(String string, int strlen, int sIndex, ReferenceMemory var, boolean isReturnArray) {
if (sIndex == strlen) {
if (isReturnArray)
var.refOfPush().assign(Memory.NULL);
return sIndex;
}
// XXX: 32-bit vs 64-bit
int val = 0;
int sign = 1;
boolean isNotMatched = true;
int maxLen = _maxLen;
if (sIndex < strlen) {
char ch = string.charAt(sIndex);
if (ch == '+') {
sIndex++;
maxLen--;
} else if (ch == '-') {
sign = -1;
sIndex++;
maxLen--;
}
}
int base = _base;
int topRange = base + '0';
for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
char ch = string.charAt(sIndex);
if ('0' <= ch && ch < topRange) {
val = val * base + ch - '0';
isNotMatched = false;
} else if (isNotMatched) {
sscanfPut(var, Memory.NULL, isReturnArray);
return sIndex;
} else
break;
}
if (_isUnsigned) {
if (sign == -1 && val != 0)
sscanfPut(var, new StringMemory((char) (0xffffffffL - val + 1)), isReturnArray);
else
sscanfPut(var, LongMemory.valueOf(val), isReturnArray);
} else
sscanfPut(var, LongMemory.valueOf(val * sign), isReturnArray);
return sIndex;
}
}
static class StringSegment extends Segment {
private final int _maxLen;
private final char _ch;
StringSegment(int maxLen, char ch) {
if (maxLen < 0)
maxLen = Integer.MAX_VALUE;
_maxLen = maxLen;
_ch = ch;
}
@Override
public String getFormatString() {
return "%" + String.valueOf(_ch);
}
@Override
public boolean isAssigned() {
return true;
}
/**
* Scans a string with a given length.
*/
@Override
public int apply(String string, int strlen, int sIndex, ReferenceMemory var, boolean isReturnArray) {
if (sIndex == strlen) {
if (isReturnArray)
var.refOfPush().assign(Memory.NULL);
return sIndex;
}
StringBuilder sb = new StringBuilder();
int maxLen = _maxLen;
for (; sIndex < strlen && maxLen-- > 0; sIndex++) {
char ch = string.charAt(sIndex);
if (isWhitespace(ch))
break;
sb.append(ch);
}
sscanfPut(var, new StringMemory(sb.toString()), isReturnArray);
return sIndex;
}
}
}