5

JFlex で CUP を使用して、式の構文を検証しています。基本的な機能は動作しています。式が有効かどうかを判断できます。

次のステップは、「1 を足す」などの単純な算術演算を実装することです。たとえば、式が「1 + a」の場合、結果は「2 + a」になるはずです。これを行うには、構文解析ツリーにアクセスする必要があります。単に数値用語を識別するだけでは不十分なためです。「(1 + a) * b」に 1 を加算した結果は、「(1 + a) * b + 1」になるはずです。 、「(2 + a) * b」ではありません。

解析ツリーを生成する CUP の例はありますか? そこから取れると思います。

追加のボーナスとして、JFlex を使用して式のすべてのトークンのリストを取得する方法はありますか? 典型的なユースケースのように思えますが、それを行う方法がわかりません。

編集:スタックオーバーフローに関する有望な手がかりが見つかりました: パーサーから抽象ツリーの問題を作成する

CUP と AST の議論:

http://pages.cs.wisc.edu/~fischer/cs536.s08/lectures/Lecture16.4up.pdf

具体的には、この段落:

パーサーによって返された Symbol は、文法の開始記号に関連付けられており、ソース プログラム全体の AST が含まれています。

これは役に立ちません。Symbol クラスにその子へのナビゲーション ポインタがない場合、Symbolインスタンスを指定してツリーをトラバースする方法は? つまり、ツリー ノードのように見えたり、動作したりしません。

package java_cup.runtime;
/**
 * Defines the Symbol class, which is used to represent all terminals
 * and nonterminals while parsing.  The lexer should pass CUP Symbols 
 * and CUP returns a Symbol.
 *
 * @version last updated: 7/3/96
 * @author  Frank Flannery
 */

/* ****************************************************************
  Class Symbol
  what the parser expects to receive from the lexer. 
  the token is identified as follows:
  sym:    the symbol type
  parse_state: the parse state.
  value:  is the lexical value of type Object
  left :  is the left position in the original input file
  right:  is the right position in the original input file
******************************************************************/

public class Symbol {

/*******************************
  Constructor for l,r values
 *******************************/

  public Symbol(int id, int l, int r, Object o) {
    this(id);
    left = l;
    right = r;
    value = o;
  }

/*******************************
  Constructor for no l,r values
********************************/

  public Symbol(int id, Object o) {
    this(id, -1, -1, o);
  }

/*****************************
  Constructor for no value
  ***************************/

  public Symbol(int id, int l, int r) {
    this(id, l, r, null);
  }

/***********************************
  Constructor for no value or l,r
***********************************/

  public Symbol(int sym_num) {
    this(sym_num, -1);
    left = -1;
    right = -1;
    value = null;
  }

/***********************************
  Constructor to give a start state
***********************************/
  Symbol(int sym_num, int state)
    {
      sym = sym_num;
      parse_state = state;
    }

/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

  /** The symbol number of the terminal or non terminal being represented */
  public int sym;

  /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

  /** The parse state to be recorded on the parse stack with this symbol.
   *  This field is for the convenience of the parser and shouldn't be 
   *  modified except by the parser. 
   */
  public int parse_state;
  /** This allows us to catch some errors caused by scanners recycling
   *  symbols.  For the use of the parser only. [CSA, 23-Jul-1999] */
  boolean used_by_parser = false;

/*******************************
  The data passed to parser
 *******************************/

  public int left, right;
  public Object value;

  /*****************************
    Printing this token out. (Override for pretty-print).
    ****************************/
  public String toString() { return "#"+sym; }
}
4

1 に答える 1

11

はい、分かりました。ただし、残念ながら、すべてのコードをそのまま公開することはできません。とにかく解決策の概要を説明しようと思いますが、不明な点があれば質問してください。

JFlexは独自のSymbolクラスを使用します。ここを見てください:JFlex.jar / java_cup.runtime / Symbol.class

追加されたコンストラクターがいくつか表示されます。

public Symbol(int id, Symbol left, Symbol right, Object o){
    this(id,left.left,right.right,o);
}
public Symbol(int id, Symbol left, Symbol right){
    this(id,left.left,right.right);
}

ここで重要なのはObject o、Symbolの値であるです。

ASTツリーノードを表す独自のクラスと、レクサートークンを表す別のクラスを定義します。確かに、同じクラスを使用できますが、2つを区別するために異なるクラスを使用する方が明確であることがわかりました。JFlexとCUPの両方がJavaコードを生成し、トークンとノードを混同するのは簡単です。

次に、parser.flexの字句ルールセクションで、トークンごとに次のようなことを行います。

{float_lit}        { return symbol(sym.NUMBER, createToken(yytext(), yycolumn)); }

すべてのトークンに対してこれを行います。createTokenは次のようになります。

%{
    private LexerToken createToken(String val, int start) {
        LexerToken tk = new LexerToken(val, start);
        addToken(tk);
        return tk;
    }
}%

それでは、parser.cupに移りましょう。LexerTokenすべての終端記号をタイプとして宣言し、すべての非終端記号をタイプとして宣言しますNode。CUPマニュアルを読みたいと思いますが、簡単に復習するために、終端記号はレクサーによって認識されるもの(たとえば、数値、変数、演算子)であり、非終端記号は文法の一部(たとえば、式、因子、用語...)になります。 )。

最後に、これはすべて文法定義にまとめられています。次の例を考えてみましょう。

   factor    ::= factor:f TIMES:times term:t
                 {: RESULT = new Node(times.val, f, t, times.start); :}
                 |
                 factor:f DIVIDE:div term:t
                 {: RESULT = new Node(div.val, f, t, div.start); :}
                 |
                 term:t
                 {: RESULT = t; :}
                 ;

構文factor:fとは、係数の値をにエイリアスすることをf意味し、次のセクションで参照できます{: ... :}LexerToken終端記号にはタイプの値があり、非終端記号にはsの値があることを忘れないでくださいNode

表現におけるあなたの用語は、次の定義を持っているかもしれません:

   term  ::= LPAREN expr:e RPAREN
         {: RESULT = new Node(e.val, e.start); :}
         |
         NUMBER:n
         {: RESULT = new Node(n.val, n.start); :}
         ;

パーサーコードが正常に生成されると、ノード間の親子関係が確立されている部分がparser.javaに表示されます。

  case 16: // term ::= UFUN LPAREN expr RPAREN 
    {
      Node RESULT =null;
    int ufleft = ((java_cup.runtime.Symbol)CUP$parser$stack.elementAt(CUP$parser$top-3)).left;
    int ufright = ((java_cup.runtime.Symbol)CUP$parser$stack.elementAt(CUP$parser$top-3)).right;
    LexerToken uf = (LexerToken)((java_cup.runtime.Symbol) CUP$parser$stack.elementAt(CUP$parser$top-3)).value;
    int eleft = ((java_cup.runtime.Symbol)CUP$parser$stack.elementAt(CUP$parser$top-1)).left;
    int eright = ((java_cup.runtime.Symbol)CUP$parser$stack.elementAt(CUP$parser$top-1)).right;
    Node e = (Node)((java_cup.runtime.Symbol) CUP$parser$stack.elementAt(CUP$parser$top-1)).value;
     RESULT = new Node(uf.val, e, null, uf.start); 
      CUP$parser$result = parser.getSymbolFactory().newSymbol("term",0, ((java_cup.runtime.Symbol)CUP$parser$stack.elementAt(CUP$parser$top-3)), ((java_cup.runtime.Symbol)CUP$parser$stack.peek()), RESULT);
    }
  return CUP$parser$result;

完全なコード例を公開できないことをお詫び申し上げますが、これにより、試行錯誤の数時間を節約できることを願っています。完全なコードがないことも、CSの宿題のすべての割り当てが役に立たなくなるわけではないので良いことです。

人生の証拠として、これが私のサンプルASTのきれいなプリントです。

入力式:

T21 + 1A / log(max(F1004036, min(a1, a2))) * MIN(1B, 434) -LOG(xyz) - -3.5+10 -.1 + .3 * (1)

結果のAST:

|--[+]
   |--[-]
   |  |--[+]
   |  |  |--[-]
   |  |  |  |--[-]
   |  |  |  |  |--[+]
   |  |  |  |  |  |--[T21]
   |  |  |  |  |  |--[*]
   |  |  |  |  |     |--[/]
   |  |  |  |  |     |  |--[1A]
   |  |  |  |  |     |  |--[LOG]
   |  |  |  |  |     |     |--[MAX]
   |  |  |  |  |     |        |--[F1004036]
   |  |  |  |  |     |        |--[MIN]
   |  |  |  |  |     |           |--[A1]
   |  |  |  |  |     |           |--[A2]
   |  |  |  |  |     |--[MIN]
   |  |  |  |  |        |--[1B]
   |  |  |  |  |        |--[434]
   |  |  |  |  |--[LOG]
   |  |  |  |     |--[XYZ]
   |  |  |  |--[-]
   |  |  |     |--[3.5]
   |  |  |--[10]
   |  |--[.1]
   |--[*]
      |--[.3]
      |--[1]
于 2011-05-20T17:17:32.043 に答える