2016-06-17 15 views
0

我正在为我的课程使用java中的StreamTokenizer类解析器。在解析错误的情况下,我希望能够打印出现错误的令牌开始的字符的确切行和偏移量。但是,尽管StreamTokenizer有一个lineno()方法来查找分词器所在的行,但没有找到该行内字符偏移的方法。如何获取StreamTokenizer中的行偏移量?

我希望能有办法使用StreamTokenizerBufferedReader(构造函数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是一个驱动程序类,它包含主函数,其中上述c ode(BufferedReaderStreamTokenizer)被调用。

但是,问题在于它忽略了令牌分隔符,因为它仅基于令牌的长度而递增。

我怀疑可能有一些方法可以直接去BufferedReader来获取这方面的信息,但我不确定。

有谁知道我怎么能得到StreamTokenizer函数的确切线偏移?

回答

1

简短的回答是,你不能使用StringTokenizer得到确切的行/字符偏移量。您需要使用不同的机制来进行标记。

我怀疑可能有某种方法可以直接进入BufferedReader来获得这方面的信息,但我不确定。

这将无法可靠地工作。 StringTokenizer需要预读(尝试)找到当前令牌的结尾或下一个令牌(如果您致电hasMoreTokens())。在阅读器中记录的位置是预读的“高位标记”,而不是标记的开始。

+0

谢谢你的信息,斯蒂芬。你有什么建议,让我标记输入文件,并如实记录分析错误的偏移?我想读取文件中的行由行,但问题是该行的结尾没有特殊的意义,而且由于我使用的是自上而下的“预测”的解析器,我需要能够读取特定标记/套在块令牌,这似乎像它会做处理行尾有点乱。 – Paul

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); 
} 

注意parseStreamTokenizerparseRegex产生相同的结果(我让他们分析自己的源代码),在于parseRegex是唯一的区别能够提供列号,即在一行内的位置。

使代码看起来复杂的原因是尝试重现与StreamTokenizer相同的结果,因为您没有详细说明实际使用情况。我不知道你是否真的需要像\v\a或八进制转义非标准转义序列在字符串中,或​​者是否你真的想要一个点被解释为0.0还是所有数字应为double值来提供,但是这就是StreamTokenizer呢。

但是我想,对于每一个实际的用例,你的解析器迟早会要求超出StreamTokenizer(超出列号)的能力,使得使用更复杂的代码是不可避免的。在另一方面,它也为您提供了更多的控制权,并允许摆脱不必要的东西,所以上面的代码应该提供一个良好的起点......