2016-06-17 4 views
0

javaで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は、上記のc ode(BufferedReaderおよびStreamTokenizer)が呼び出されます。

しかし、トークンの区切り文字を無視するのは、トークンの長さに基づいてインクリメントするためです。

BufferedReaderに直接アクセスしてこの情報を入手する方法があると思われますが、わかりません。

私はStreamTokenizer関数の正確な行オフセットをどのように得ることができるか知っていますか?

答えて

1

短い答えは、StringTokenizerを使用して正確な行/文字オフセットを取得できないことです。トークン化には別のメカニズムを使用する必要があります。

これについての情報を得るためにBufferedReaderに直接行く方法があると思われますが、わかりません。

これは、確実に機能しません。 StringTokenizerは、現在のトークンまたは次のトークンの末尾を見つけるために先読みする必要があります(hasMoreTokens()を呼び出した場合)。リーダに記録されている位置は、先読みのための「高水準点」であり、トークンの開始点ではない。

+0

情報をありがとう、スティーブン。入力ファイルをトークン化し、解析エラーのオフセットを正確に記録することをお勧めしたいと思いますか?私はファイルを1行ずつ読むことを考えていましたが、行末に特別な意味はなく、トップダウンの "予測的"パーサを使用しているので、特定のトークン/セットを読むことができる必要がありますそれは、行末を扱うのがちょっと面倒なように思えました。 – 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と同じ結果を再現しようとすることです。私はあなたが実際に文字列またはあなたが本当に単一のドットが0.0として、またはすべての数字がdouble値として提供されるべきかどうかを解釈するかどうか\v\aまたは進エスケープのような非標準のエスケープシーケンスを必要とするかどうかわからないが、それは何ですかStreamTokenizerがあります。

しかし実際の使用例では、パーサーは遅くとも遅くともStreamTokenizer(列番号を超える)を超える機能を必要とし、より複雑なコードを避けられないものにすると思います。一方、それはまた、より多くの制御を提供し、不要なものを取り除くことを可能にするので、上記のコードは良い出発点を提供する必要があります...

関連する問題