Contributing

We welcome contributions from anyone, even if you are new to open source. It might sound daunting to contribute to a compiler at first, but please do, it is not complicated. We will help you with any technical issues and help improve your contribution so that it can be merged.

Basic Setup

To contribute, make sure your set up:

  • Your username + email

  • Your ~/.gitconfig

  • Your shell prompt to display the current branch name

Fork LFortran

Step 1. Create a fork of the project repository

Step 2. Set up your SSH key with GitHub

Step 3. Clone the project repository from GitHub and set up your remote repository

git clone https://github.com/lfortran/lfortran.git
cd lfortran
git remote add REMOTE_NAME git@github.com:YOUR_GITHUB_ID/lfortran.git

:fontawesome-solid-edit: REMOTE_NAME is the name of your remote repository and could be any name you like, for example your first name.

:fontawesome-solid-edit: YOUR_GITHUB_ID is your user ID on GitHub and should be part of your account path.

You can use git remote -v to check if the new remote is set up correctly.

Send a New Merge Request

Step 1. Create a new branch

git checkout -b fix1

Step 2. Make changes in relevant file(s)

Step 3. Commit the changes:

git add FILE1 (FILE2 ...)
git commit -m "YOUR_COMMIT_MESSAGE"

Here are some great tips on writing good commit messages.

Step 4. Check to ensure that your changes look good

git log --pretty=oneline --graph --decorate --all

Step 5. Send the merge request

git push REMOTE_NAME fix1

The command will push the new branch fix1 into your remote repository REMOTE_NAME that you created earlier. Additionally, it will also display a link that you can click on to open the new merge request. After clicking on the link, write a title and a concise description then click the "Create" button. Yay you are now all set.

Add New Features

The example below shows the steps it would take to create a caret binary operator ^ which computes the average value of the two operands.

Create New Token(s)

We extend the tokenizer.re as well as parser.yy to add the new token ^. We also tell LFortran how to print the new token in parser.cpp.

:fontawesome-solid-code: src/lfortran/parser/tokenizer.re

// "^" { RET(TK_CARET) }

:fontawesome-solid-code: src/lfortran/parser/parser.yy

%token TK_CARET "^"

:fontawesome-solid-code: src/lfortran/parser/parser.cpp

std:string token2text(const int token)
{
    switch (token) {
        T(TK_CARET, "^")
    }
}

The added code is tested with lfortran --show-tokens examples2/expr2.f90

Parse the New Token

Now we have to parse the new operator. We add it to the AST by extending the BinOp with a caret operator and modifying the AST.asdl file. Then we add it in parse.yy to properly parse and generate the new AST in semantics.h.Finally we extend pickle.cpp so that the new operator can print itself.

:fontawesome-solid-code:grammar/AST.asdl

operator = Add | Sub | Mul | Div | Pow | Caret

:fontawesome-solid-code:src/lfortran/parser/parser.yy

%left "^"

expr
    : id { $$=$1; }
    | expr "^" expr { $$ = CARET($1, $3, @$); }

:fontawesome-solid-code:src/lfortran/parser/semantics.h

#define CARET(x,y,l) make_BinOp_t(p.m_a, l, EXPR(x), operatorType::Caret, EXPR(y))

:fontawesome-solid-code:src/lfortran/pickle.cpp

std::string op2str(const operatorType type)
{
    switch (type) {
        case (operatorType::Caret) : return "^";
    } // now the caret operator can print itself

}

The section is tested with lfortran --show-ast examples/expr2.f90

Implement the Semantics of the New Token

We first extend the ASR in ASR.asdl and add ^ as a BinOp operator option.

:fontawesome-solid-code:src/libasr/ASR.asdl

binop = Add | Sub | Mul | Div | Pow | Caret

:fontawesome-solid-code:src/lfortran/semantics/ast_common_visitor.h

namespace LFortran {
class CommonVisitorMethods {
public:
  inline static void visit_BinOp(Allocator &al, const AST::BinOp_t &x,
                                 ASR::expr_t *&left, ASR::expr_t *&right,
                                 ASR::asr_t *&asr) {
    ASR::binopType op;
    switch (x.m_op) {

    case (AST::Caret):
      op = ASR::Caret;
      break;
    }
    if (LFortran::ASRUtils::expr_value(left) != nullptr &&
        LFortran::ASRUtils::expr_value(right) != nullptr) {
      if (ASR::is_a<LFortran::ASR::Integer_t>(*dest_type)) {
        int64_t left_value = ASR::down_cast<ASR::IntegerConstant_t>(
                                 LFortran::ASRUtils::expr_value(left))
                                 ->m_n;
        int64_t right_value = ASR::down_cast<ASR::IntegerConstant_t>(
                                  LFortran::ASRUtils::expr_value(right))
                                  ->m_n;
        int64_t result;
        switch (op) {
        case (ASR::Caret):
          result = (left_value + right_value)/2;
          break;
        }
  }
}
}
}
}

Then we transform it from AST to ASR by extending src/lfortran/semantics/ast_common_visitor.h.

We also add it into compile time evaluation triggered by expressions such as e = (2+3)^5 which is evaluated at compile time. An expression such as e = x^5 is evaluated at run time only.

The section is tested with lfortran --show-asr examples/expr2.f90

Implement the New Token in LLVM

To implement in LLVM, we extend the BinOp translation by handling the new operator. We first add the two numbers then divide by two. :fontawesome-solid-code:src/lfortran/codegen/asr_to_llvm.cpp

    void visit_BinOp(const ASR::BinOp_t &x) {
        if (x.m_value) {
            this->visit_expr_wrapper(x.m_value, true);
            return;
        }
        this->visit_expr_wrapper(x.m_left, true);
        llvm::Value *left_val = tmp;
        this->visit_expr_wrapper(x.m_right, true);
        llvm::Value *right_val = tmp;
        if (x.m_type->type == ASR::ttypeType::Integer ||
            x.m_type->type == ASR::ttypeType::IntegerPointer) {
            switch (x.m_op) {

                case ASR::binopType::Caret: {
                    tmp = builder->CreateAdd(left_val, right_val);
                    llvm::Value *two = llvm::ConstantInt::get(context,
                        llvm::APInt(32, 2, true));
                    tmp = builder->CreateUDiv(tmp, two);
                    break;
                };
}
}
}

The section is tested with lfortran --show-llvm examples/expr2.f90

Now when LLVM works, we can test the final executable by:

lfortran examples/expr2.f90
./a.out

And it should print 6.

It also works interactively:

$ lfortran
Interactive Fortran. Experimental prototype, not ready for end users.
  * Use Ctrl-D to exit
  * Use Enter to submit
  * Use Alt-Enter or Ctrl-N to make a new line
    - Editing (Keys: Left, Right, Home, End, Backspace, Delete)
    - History (Keys: Up, Down)
>>> 4^8                                                                  1,4   ]
6
>>> integer :: x                                                         1,13  ]
>>> x = 4                                                                1,6   ]
>>> x^8                                                                  1,4   ]
6

Reach Out

If you have any questions or need help, please ask as at our mailinglist or a chat.

Please note that all participants of this project are expected to follow our Code of Conduct. By participating in this project you agree to abide by its terms. See CODE_OF_CONDUCT.md.

By submitting a PR you agree to license your contribution under the LFortran's BSD license unless explicitly noted otherwise.