技術

6.4. ユーザ定義単項演算子

LLVMによるプログラミング言語の実装チュートリアル日本語訳
第6章 万華鏡: 言語の拡張: ユーザ定義演算子
第4節 ユーザ定義単項演算子

現時点では万華鏡言語では、単項演算子をサポートしていないので、それをサポートするために必要なものを全てを追加する必要がある。
ただし前節で、字句解析器にunaryキーワードのサポートは追加してあるので、ここで必要になるのはASTノードの追加からである。

/// UnaryExprAST - 単項演算子のための式クラス。
class UnaryExprAST : public ExprAST {
  char Opcode;
  ExprAST *Operand;
public:
  UnaryExprAST(char opcode, ExprAST *operand)
    : Opcode(opcode), Operand(operand) {}
  virtual Value *Codegen();
};

このASTノードは、実にシンプルで、今となっては分かりきったものである。
これは、子をひとつしか持たない点を除いて、二項演算子のASTノードに酷似している。
これとともに、構文解析のロジックを追加する必要がある。
単項演算子の構文解析は実に簡単である。
そのための関数を新たに追加する。

/// unary
///   ::= primary
///   ::= '!' unary
static ExprAST *ParseUnary() {
  // 現在のトークンが演算子でなければ、それはプライマリ式である。
  if (!isascii(CurTok) || CurTok == '(' || CurTok == ',')
    return ParsePrimary();

  // 単項演算子なら、読み込む。
  int Opc = CurTok;
  getNextToken();
  if (ExprAST *Operand = ParseUnary())
    return new UnaryExprAST(Opc, Operand);
  return 0;
}

我々が追加した文法は実に素直である。
プライマリ式の解析中にそれが単項演算子であると判明したら、その演算子を接頭辞として取り込み、残りの部分を他の単項演算子として解析する。
これによって、多重の単項演算子(例えば、”!!x”)を処理できるようになる。
単項演算子の場合、二項演算子のときにあったような曖昧さの問題はないので、優先順位の情報は必要ない。

この関数の問題点は、この関数をどこかから呼ばなければならないということである。
そのため、以前のParsePrimaryの呼び出し元を、ParseUanryを呼び出すように変更する。

/// binoprhs
///   ::= ('+' unary)*
static ExprAST *ParseBinOpRHS(int ExprPrec, ExprAST *LHS) {
  ...
    // 二項演算子の後の単項式を解析する。
    ExprAST *RHS = ParseUnary();
    if (!RHS) return 0;
  ...
}
/// expression
///   ::= unary binoprhs
///
static ExprAST *ParseExpression() {
  ExprAST *LHS = ParseUnary();
  if (!LHS) return 0;

  return ParseBinOpRHS(0, LHS);
}

これら2つの簡単な変更によって、単項演算子を構文解析し、ASTを構築できるようになった。
次は、単項演算子のプロトタイプを解析するために、構文解析器に単項演算子プロトタイプのサポートを追加する必要がある。
前節の二項演算子のコードを拡張する。

/// prototype
///   ::= id '(' id* ')'
///   ::= binary LETTER number? (id, id)
///   ::= unary LETTER (id)
static PrototypeAST *ParsePrototype() {
  std::string FnName;

  unsigned Kind = 0;  // 0 = 識別子, 1 = 単項演算子, 2 = 二項演算子。
  unsigned BinaryPrecedence = 30;

  switch (CurTok) {
  default:
    return ErrorP("Expected function name in prototype");
  case tok_identifier:
    FnName = IdentifierStr;
    Kind = 0;
    getNextToken();
    break;
  case tok_unary:
    getNextToken();
    if (!isascii(CurTok))
      return ErrorP("Expected unary operator");
    FnName = "unary";
    FnName += (char)CurTok;
    Kind = 1;
    getNextToken();
    break;
  case tok_binary:
    ...

二項演算子の場合と同じように、演算子の文字を含む名前で単項演算子を名付ける。
これによって、コード生成時に使えるようになる。
あとは、単項演算子のためのコード生成機能の追加が残っている。
それは以下のようになる。

Value *UnaryExprAST::Codegen() {
  Value *OperandV = Operand->Codegen();
  if (OperandV == 0) return 0;

  Function *F = TheModule->getFunction(std::string("unary")+Opcode);
  if (F == 0)
    return ErrorV("Unknown unary operator");

  return Builder.CreateCall(F, OperandV, "unop");
}

このコードは、二項演算子のときのものと似ているがよりシンプルである。
特に組み込みの演算子を扱う必要がないのでよりシンプルである。

コメントを残す

メールアドレスが公開されることはありません。



※画像をクリックして別の画像を表示