Yasser Arguelles Snape, December 2023.

Mentor: Aleksandr Petrosyan.

Mentorship article on Hyperledger Wiki.


Introduction

My initial task is writing a domain-specific language (DSL) for Iroha 2, but before I get into my work there, let me show why it would be beneficial.
The state of Iroha 2 can be modified with special instructions, which are currently written manually in Rust code. The ISI approach is lengthy, and I aimed to simplify some of this. The first iteration was capable of parsing arithmetic and logical expressions into Iroha’s internal data model, for instance:

expr!(54654*5 + 1) compiles into Add::new(Multiply::new(54654_u64, 5_u64), 1_u64)

eventually coming around to supporting if statements in the data model such as

if 4 = 4 then 64 else 32

PR #3710 completes this part of the project. The parsing algorithm I’ve used is a very simple precedence climber (for more information, look up "Eli Bendersky: Parsing expressions by precedence climbing").
The way that operator precedence parsing works is that we’re prioritizing the grouping of higher precedence items.
For instance, the expression 4 + x * 2 will be grouped as 4 + (x * 2), not (4 + x) * 2. As we scan tokens in my parser, the current highest precedence is tracked and parsing items together is only done with expressions of an equal or higher precedence.

Let’s walk through an example (PEMDAS here means Parentheses, Exponents, Multiplication and Division (same level), and Addition and Subtraction):

levels (following PEMDAS, addition happens after multiplication):
  / is 2
  * is 2
  - is 1
  + is 1

  in this table, bigger level means lower precedence.


4 + x * 2 + 3
^
Literal(4)

We parse the first literal with our highest level 0, and from there, we spot a plus, which is at level 1; since we’re at level 0, we can begin constructing the addition:

  +
 / \
4   ...

But now whatever is on the right-hand side cannot have a level lower or equal to 1 (the plus), which means the x * 2 can be grouped since multiply is a higher level, but we’ll bail from parsing the right side once we see the next plus (since it matched our current limit of 1).

Now we’ve got a tree that looks like this:

  +
 / \
4   *
   / \
  x   2

Once we’ve bailed out, we can parse the plus and its right side, along with appending the previous tree to the left:

    +
   / \
  +   3
 / \
4   *
   / \
  x   2

which is the correct precedence; the pseudo-code looks something like this:

// get_binop will return the operator along with level.
parse_binop(min_level) {
  lhs := parse_literal()
  while op = get_binop() and op.level >= min_level {
    rhs := parse_binop(op.level + 1)

    // chain nodes to the newly created operator
    lhs = op.node(lhs, rhs)
  }
  return lhs
}

The code form is relatively tiny, so I prefer this approach when writing parsers.

Registering

To work with Iroha 2, you must create accounts and other resources; RegisterBox governs this process, and my goal was to facilitate it. I prioritized registering AssetsAccounts and Domains, which could simplify their creation process because the names tell us which object we’re working with.

Account            alice@bucket
Domain             bucket
AssetDefinition    beans#bucket
Asset              beans##alice@bucket

Now, the DSL could generate the RegisterBox instances; the user can expand the necessary code by using expr!(register "beans@bucket"):

// cargo-expand output: https://crates.io/crates/cargo-expand
RegisterBox::new(Account::new("beans@bucket".parse().unwrap(), []))

Unfortunately, this portion wasn’t completed (#3832: adding registerbox to DSL) as it lacks some testing and refactoring necessary to move parsing the names into a separate crate.

Conclusion

Despite the previously mentioned issues, I am grateful for this opportunity, and it taught me some valuable things: experience working with Rust and procedural macros. Before this, I was, for the most part, a C programmer, which allowed me to work outside of my comfort zone on a project with multiple people.

  • No labels