Building a CLI Util in Dart

A Short Look at Building a Simple Util to Format Brackets & Parentheses

August 30, 2019

Problem Description

Every so often programs will output string representations of internal data structures. The outputs are often not valid syntactic equivalents to the language they were generated in, and so trying to apply the language’s formatter becomes difficult if not impossible. I suppose this is why JSON objects and arrays are pleasant to work with. Piping them into jq or another util (online and offline) will print a prettified structure.

Seeing the structure of things remains difficult task if there is not existing pretty-print equivalent in your language without having to write some custom serializer. I’ve had this issue in enough languages, and different context, and one thing remains constant. The output’s all have a nesting of parentheses, square brackets, and curly braces.

We have now recognized our input. We not have to identify the transformation that would yield the most readable format with minimal code. The simplest idea is to take the input, push the indentation based on every open bracket, and reduce the indent on every close brace.

([{1}]) would simply become:

(
  [
    {
      1
    }
  ]
)

This sound simple enough, and we can probably do it within a couple of functions.

Defining the Dart Program

We want to make this a command line application, so we’ll be reading from STDIN. The input will then need to be reshaped and inject line breaks after every key input character: ()[]{}.

Reading from STDIN

The entry to our program will look something like below:

import "dart:io";

main() {
  List<String> collector = [];
  int nextByte = stdin.readByteSync();
  while (nextByte != -1; ) {
    String char = new String.fromCharCode(nextByte);
    collector.add(char);
    nextByte = stdin.readByteSync()
  }

  // Output
  print(format(collector));
}

We want to collect all the input from STDIN and pass it to a format function which will return our formatted string that we can then print back out to the command line.

Reading from stdin is consuming one byte at a time, and converting each to a its corresponding ASCII value. This method breaksdown with unicode characters like emojis, so note that if your input contains any unicode characters they may not be paresed correctly with this method.

Defining the format method

Next we want to define our format method. This method copies the input characters into an output list injecting indentation and line breaks after every ovening bracket and before every closing bracket.

To reduce some of the duplication, we wrote a helper method writeIndent that writes the newline and appropriate indent into our output list.

String format(List<String> input) {
  List<String> output = [];
  int indent = 0;

  for (String char in input) {
    switch (char) {
      case '(':
      case '{':
      case '[':
        output.add(char);
        writeIndent(output, ++indent);
        break;

      case ')':
      case '}':
      case ']':
        writeIndent(output, --indent);
        output.add(char);
        break;

      default:
        output.add(char);
    }
  }

  return output.join("");
}

void writeIndent(List<String> output, int indentLevel, [int indentSize = 2]) {
  output.add("\n");
  output.addAll(List.filled(indentLevel * indentSize, ' '));
}

The method iterates through the input looking at every character, and doing our special injection only for our target characters. If we wanted to also insert indentation after every ,, for example. We could augment the above method with:

case ',':
  output.add(char);
  writeIndent(output, indent);
  break;

This would make every adjecent element in a function display on a new line with the same indentation level.

Bringing it all together

At this point we have our program entry point, and our function that completes our desired task. All we need to do is run our program.

# Assuming that format.dart contains the program we've developed above
echo "([{1}])" | dart format.dart

# Output:
# (
#   [
#     {
#       1
#     }
#   ]
# )

We could go a step further and compile our dart program to native machine code with dart2aot to speed up the performance of our program. I’ll leave that as an exercise for the reader. How much faster is the program with AOT compilation?

Conclusion

This has been a post on how to build a command line utility using the Dart programming language. This utility is something to help me as a developer look at output from other programs wich does not deserialize back to an easy to compare format.