Handling user-defined operators – unary operators

We saw in the previous recipe how binary operators can be handled. A language may also have some unary operator, operating on 1 operand. In this recipe, we will see how to handle unary operators.

Getting ready

The first step is to define a unary operator in the TOY language. A simple unary NOT operator (!) can serve as a good example; let's see one definition:

def unary!(v)
  if v then
    0
  else
    1;

If the value v is equal to 1, then 0 is returned. If the value is 0, 1 is returned as the output.

How to do it...

Do the following steps:

  1. The first step is to define the enum token for the unary operator in the toy.cpp file:
    enum Token_Type {
    …
    …
    BINARY_TOKEN,
    UNARY_TOKEN
    }
  2. Then we identify the unary string and return a unary token:
    static int get_token() {
    …
    …
    if (Identifier_string == "in") return IN_TOKEN;
    if (Identifier_string == "binary") return BINARY_TOKEN;
    if (Identifier_string == "unary") return UNARY_TOKEN;
    
    …
    …
    }
  3. Next, we define an AST for the unary operator:
    class ExprUnaryAST : public BaseAST {
      char Opcode;
      BaseAST *Operand;
    public:
      ExprUnaryAST(char opcode, BaseAST *operand)
        : Opcode(opcode), Operand(operand) {}
      virtual Value *Codegen();
    };
  4. The AST is now ready. Let's define a parser for the unary operator:
    static BaseAST *unary_parser() {
    
      if (!isascii(Current_token) || Current_token == '(' || Current_token == ',')
        return Base_Parser();
    
        int Op = Current_token;
    
      next_token();
    
      if (ExprAST *Operand = unary_parser())
        return new ExprUnaryAST(Opc, Operand);
    
    return 0;
    }
  5. The next step is to call the unary_parser() function from the binary operator parser:
    static BaseAST *binary_op_parser(int Old_Prec, BaseAST *LHS) {
    
      while (1) {
        int Operator_Prec = getBinOpPrecedence();
    
        if (Operator_Prec < Old_Prec)
          return LHS;
    
        int BinOp = Current_token;
        next_token();
    
        BaseAST *RHS = unary_parser();
        if (!RHS)
          return 0;
    
        int Next_Prec = getBinOpPrecedence();
        if (Operator_Prec < Next_Prec) {
          RHS = binary_op_parser(Operator_Prec + 1, RHS);
          if (RHS == 0)
            return 0;
        }
    
        LHS = new BinaryAST(std::to_string(BinOp), LHS, RHS);
      }
    }
  6. Now let's call the unary_parser() function from the expression parser:
    static BaseAST *expression_parser() {
      BaseAST *LHS = unary_parser();
      if (!LHS)
        return 0;
    
      return binary_op_parser(0, LHS);
    }
  7. Then we modify the function declaration parser:
    static FunctionDeclAST* func_decl_parser() {
    std::string Function_Name = Identifier_string;
    unsigned Kind = 0;
    unsigned BinaryPrecedence = 30;
    switch (Current_token) {
      default:
        return 0;
      case IDENTIFIER_TOKEN:
        Function_Name = Identifier_string;
        Kind = 0;
        next_token();
        break;
      case UNARY_TOKEN:
      next_token();
    if (!isascii(Current_token))
          return0;
        Function_Name = "unary";
        Function_Name += (char)Current_token;
        Kind = 1;
        next_token();
        break;
      case BINARY_TOKEN:
        next_token();
        if (!isascii(Current_token))
          return 0;
        Function_Name = "binary";
        Function_Name += (char)Current_token;
        Kind = 2;
       next_token();
       if (Current_token == NUMERIC_TOKEN) {
          if (Numeric_Val < 1 || Numeric_Val > 100)
            return 0;
          BinaryPrecedence = (unsigned)Numeric_Val;
          next_token();
        }
        break;
      }
    if (Current_token ! = '(') {
    printf("error in function declaration");
    return 0;
    }
    std::vector<std::string> Function_Argument_Names;
    while(next_token() == IDENTIFIER_TOKEN) Function_Argument_Names.push_back(Identifier_string);
    if(Current_token != ')')  {                      printf("Expected ')' ");                      return 0;
    }
    next_token();
    if (Kind && Function_Argument_Names.size() != Kind)
        return 0;
    return new FunctionDeclAST(Function_Name, Function_Arguments_Names, Kind !=0, BinaryPrecedence);
    }
  8. The final step is to define the Codegen() function for the unary operator:
    Value *ExprUnaryAST::Codegen() {
    
      Value *OperandV = Operand->Codegen();
    
      if (OperandV == 0) return 0;
    
      Function *F = TheModule->getFunction(std::string("unary")+Opcode);
    
      if (F == 0)
        return 0;
    
      return Builder.CreateCall(F, OperandV, "unop");
    }

How it works...

Do the following steps:

  1. Compile the toy.cpp file:
    $ g++ -g toy.cpp `llvm-config --cxxflags --ldflags --system-libs --libs core ` -O3 -o toy
    
  2. Open an example file:
    $ vi example
    
  3. Write the following unary operator overloading code in the example file:
    def unary!(v)
      if v then
        0
      else
        1;
  4. Compile the example file with the TOY compiler:
    $ ./toy example
    

    The output should be as shown:

    ; ModuleID = 'my compiler'
    target datalayout = "e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128"
    
    define i32 @"unary!"(i32 %v) {
    entry:
      %ifcond = icmp eq i32 %v, 0
      %. = select i1 %ifcond, i32 1, i32 0
      ret i32 %.
    }
    

The unary operator defined by the user will be parsed, and IR will be generated for it. In the case you just saw, if the unary operand is not zero then the result is 0. If the operand is zero, then the result is 1.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.236.27