2016-06-17 1 views
0

자바에서 StreamTokenizer 클래스를 사용하는 클래스의 파서에서 작업하고 있습니다. 구문 분석 오류의 경우 오류가 발생한 토큰을 시작하는 문자의 정확한 줄과 오프셋을 인쇄 할 수 있기를 원합니다. 그러나 StreamTokenizer에는 lineno() 메서드가있어 터널 라이저가있는 행을 찾지 만 해당 행에서 문자 오프셋을 찾는 방법은 없습니다.StreamTokenizer에서 라인 오프셋을 얻는 방법은 무엇입니까?

StreamTokenizer 또는 BufferedReader의 사용 가능한 함수를 사용하여이 오프셋을 얻는 방법이 있다면 StreamTokenizer 생성자에 대한 입력입니다.

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

는 지금까지, 나는이 같은 것을 사용하여 시도했다 : 그것은 다음과 같이 보입니다 있도록 다음

BufferedReader dataReader = new BufferedReader(new FileReader(filename)); 
StreamTokenizer st = new StreamTokenizer(dataReader); 
st.eolIsSignificant(true); 

을, 나는

StreamTokenizer.nextToken() 

기능 래퍼를 만들어

Linker2은 main 함수를 포함하는 드라이버 클래스이며 위의 c ode (BufferedReaderStreamTokenizer)가 호출됩니다.

그러나 문제는 토큰 구분 기호를 무시한다는 것입니다. 토큰 구분 기호는 토큰 길이에 따라 증분되기 때문에 무시됩니다.

나는이 정보를 얻기 위해 BufferedReader에 직접가는 방법이있을 것이라고 생각하지만 확실하지 않습니다.

누구나 StreamTokenizer 기능의 정확한 라인 오프셋을 얻을 수있는 방법을 알고 있습니까?

답변

1

짧은 대답은 StringTokenizer을 사용하여 정확한 줄/문자 오프셋을 가져올 수 없다는 것입니다. 토큰 화에 다른 메커니즘을 사용해야합니다.

나는이 정보를 얻기 위해 BufferedReader로 바로 갈 수있는 방법이있을 것이라고 생각하지만 확실하지 않습니다.

안정적으로 작동하지 않습니다. StringTokenizer은 현재 토큰 또는 다음 토큰의 끝을 찾기 위해 미리 읽어야합니다 (hasMoreTokens()로 전화하는 경우). 판독기에 기록 된 위치는 토큰의 시작이 아니라 미리 읽기의 "최고점"입니다.

+0

정보를 주셔서 감사합니다, 스티븐. 입력 파일을 토큰 화하고 구문 분석 오류의 오프셋을 정확하게 기록 할 수있게하는 것이 좋습니다. 나는 파일을 줄 단위로 읽는 것을 고려하고 있었지만, 줄 끝은 특별한 의미가 없다는 것이 문제였다. 그리고 나는 상향식 "예측"파서를 사용하고 있기 때문에 특정 토큰/세트를 읽을 수 있어야한다. 토큰을 덩어리로 만들었는데, 마치 라인 엔딩을 다루기가 더 어려워 보였습니다. – Paul

0

줄 안에 토큰의 위치를 ​​가져올 수 없으며이 문제를 해결할 수있는 확실한 방법이 없습니다. 그러나 캡슐화 된 패턴 일치가 어쨌든 매우 발전되지 않았으므로 StreamTokenizer을 대체 할 것을 고려할 수 있습니다. 장래에 다른 결점을 발견하게 될 수도 있습니다. 해결 방법이 될 수 없으며 패턴을 관리 할 때 더 쉽게 할 수 있습니다. 나는 바퀴를 재발 명에 대해 얘기하지만, 대신에 정규 표현식을 사용하지 않는 : parseStreamTokenizerparseRegex는, (내가 그들 자신의 소스 코드를 분석하자) parseRegex인지되는 유일한 차이점을 동일한 결과를 생성

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

하는 것으로 열 번호, 즉 줄 내의 위치를 ​​제공 할 수 있습니다.

실제로 코드를 복잡하게 만드는 이유는 실제 사용 사례에 대해 자세히 지정하지 않았기 때문에 StreamTokenizer과 동일한 결과를 재현하려는 시도입니다. 실제로 \v\a과 같은 비표준 이스케이프 시퀀스가 ​​필요한지 또는 문자열에서 8 진수 이스케이프가 필요한지, 아니면 단일 점이 0.0으로 해석되도록할지 또는 모든 숫자를 double 값으로 제공할지 여부는 알 수 없습니다. StreamTokenizer입니다.

하지만 모든 실제 사용 사례의 경우 구문 분석기는 조만간 StreamTokenizer (열 번호 초과)을 초과하는 기능을 요구하므로 더 복잡한 코드를 피할 수없는 방식으로 사용하게됩니다. 다른 한편, 그것은 또한 당신에게 더 많은 통제를 제공하고 불필요한 것들을 제거 할 수 있도록 위의 코드는 좋은 출발점을 제공해야합니다 ...