我正在为我的类使用java中的StreamTokenizer
类的解析器。在解析错误的情况下,我希望能够打印出现错误的令牌的字符的确切行和偏移量。但是,虽然StreamTokenizer
有一个lineno()
方法来查找tokenizer所在的行,但是没有方法可以找到该行中的字符偏移量。
我希望以某种方式使用StreamTokenizer
或BufferedReader
中的可用函数来获取此偏移量,这是StreamTokenizer
构造函数的输入。
到目前为止,我尝试过使用类似的东西:
BufferedReader dataReader = new BufferedReader(new FileReader(filename));
StreamTokenizer st = new StreamTokenizer(dataReader);
st.eolIsSignificant(true);
然后,我在
周围做了一个包装 StreamTokenizer.nextToken()
功能使它看起来像这样:
public int nextTokenSpec(StreamTokenizer st) throws IOException{
int token = st.nextToken();
if (token == StreamTokenizer.TT_EOL){
Linker2.offsetCounter = 0;
token = st.nextToken();
} else{
Linker2.offsetCounter += st.sval.length();
}
return token;
}
请注意Linker2
是一个驱动程序类,其中包含调用上述代码(BufferedReader
和StreamTokenizer
)的主函数。
然而,问题在于它忽略了令牌分隔符,因为它只根据令牌的长度递增。
我怀疑可能有某种方法可以直接到BufferedReader
获取相关信息,但我不确定。
有谁知道如何获得StreamTokenizer
函数的确切行偏移量?
答案 0 :(得分:1)
简短的回答是,您无法使用StringTokenizer
获得确切的行/字符偏移量。您需要使用不同的机制进行标记。
我怀疑可能有某种方法直接进入BufferedReader以获取有关此信息,但我不确定。
那不可靠。 StringTokenizer
需要提前读取(尝试)查找当前令牌的结尾或下一个令牌(如果您调用hasMoreTokens()
)。记录在阅读器中的位置是预读的“高水位线”,而不是令牌的开头。
答案 1 :(得分:0)
不支持在行内获取令牌的位置,并且没有可靠的解决方法。但是您可以考虑替换StreamTokenizer
,因为它的封装模式匹配无论如何都不是很先进。你可能偶然发现未来的其他缺陷,你也无法解决这些缺陷,而如果你能控制模式,它们很容易做得更好。我不是在谈论重新发明轮子,而是使用正则表达式:
public static void parseStreamTokenizer(String filename) throws IOException {
try(Reader r=new FileReader(filename);
BufferedReader dataReader = new BufferedReader(r);) {
StreamTokenizer st=new StreamTokenizer(dataReader);
for(;;) {
double d=Double.NaN;
String w=null;
switch(st.nextToken()) {
case StreamTokenizer.TT_EOF: return;
case StreamTokenizer.TT_EOL: continue;
case StreamTokenizer.TT_NUMBER: d=st.nval; break;
case StreamTokenizer.TT_WORD: case '"': case '\'': w=st.sval; break;
}
consumeToken(st.lineno(), -1, st.ttype, w, d);
}
}
}
static final Pattern ALL_TOKENS = Pattern.compile(
"(-?(?:[0-9]+\\.?[0-9]*|\\.[0-9]*))" // number
+"|([A-Za-z][A-Za-z0-9\\.\\-]*)" // word
+"|([\"'])((?:\\\\?.)*?)\\3" // string with backslash escape
+"|/.*" // StreamTokenizer's "comment char" behavior
+"|\\s*" // white-space
);
public static void parseRegex(String filename) throws IOException {
try(Reader r=new FileReader(filename);
BufferedReader dataReader = new BufferedReader(r)) {
String line;
int lineNo=0;
Matcher m=ALL_TOKENS.matcher("");
while((line=dataReader.readLine())!=null) {
lineNo++;
m.reset(line);
int last=0;
while(m.find()) {
double d=Double.NaN;
String word=null;
for(int e=m.start(); last<e; last++) {
consumeToken(lineNo, last+1, line.charAt(last), word, d);
}
last=m.end();
int type;
if(m.start(1)>=0) {
type=StreamTokenizer.TT_NUMBER;
String n=m.group();
d=n.equals(".")? 0: Double.parseDouble(m.group());
}
else if(m.start(2)>=0) {
type=StreamTokenizer.TT_WORD;
word=m.group(2);
}
else if(m.start(4)>=0) {
type=line.charAt(m.start(3));
word=parse(line, m.start(4), m.end(4));
}
else continue;
consumeToken(lineNo, m.start()+1, type, word, d);
}
}
}
}
// the most complicated thing is interpreting escape sequences within strings
private static String parse(String source, int start, int end) {
for(int pos=start; pos<end; pos++) {
if(source.charAt(pos)=='\\') {
StringBuilder sb=new StringBuilder(end-start+16);
sb.append(source, start, pos);
for(; pos<end; pos++) {
if(source.charAt(pos)=='\\') {
int oct=0;
switch(source.charAt(++pos)) {
case 'n': sb.append('\n'); continue;
case 'r': sb.append('\r'); continue;
case 't': sb.append('\t'); continue;
case 'b': sb.append('\b'); continue;
case 'f': sb.append('\f'); continue;
case 'v': sb.append('\13'); continue;
case 'a': sb.append('\7'); continue;
case '0': case '1': case '2': case '3':
int next=pos+1;
if(next<end && (source.charAt(next)&~'7')==0)
oct=source.charAt(pos++)-'0';
// intentionally no break
case '4': case '5': case '6': case '7':
oct=oct*8+source.charAt(pos)-'0';
next=pos+1;
if(next<end && (source.charAt(next)&~'7')==0)
oct=oct*8+source.charAt(pos=next)-'0';
sb.append((char)oct);
continue;
}
}
sb.append(source.charAt(pos));
}
return sb.toString();
}
}
return source.substring(start, end);
}
// called from both variants, to the same result (besides col values)
static void consumeToken(int line, int col, int id, String word, double number) {
String type;
Object o;
switch(id)
{
case StreamTokenizer.TT_NUMBER: type="number"; o=number; break;
case StreamTokenizer.TT_WORD: type="word"; o=word; break;
case '"': case '\'': type="string"; o=word; break;
default: type="char"; o=(char)id;
}
System.out.printf("l %3d, c %3s: token %-6s %s%n",
line, col<0? "???": col, type, o);
}
请注意,parseStreamTokenizer
和parseRegex
会产生相同的结果(我让他们解析自己的源代码),唯一的区别是parseRegex
能够提供列号,即在一条线内的位置。
使代码看起来很复杂的原因是尝试重现与StreamTokenizer
相同的结果,因为您没有详细说明您的实际用例。我不知道你是否真的需要像\v
和\a
这样的非标准转义序列或字符串中的八进制转义符,或者你是否真的希望将一个点解释为0.0
或是否所有数字都应以double
值提供,但这就是StreamTokenizer
所做的。
但我认为,对于每个实际用例,您的解析器迟早都需要超过StreamTokenizer
(超出列数)的功能,这使得使用更复杂的代码是不可避免的。另一方面,它还为您提供了更多的控制,并允许摆脱不必要的东西,所以上面的代码应该提供一个很好的起点......