PTA

General plain text accounting grammar

PTA is a general-purpose plain text accounting grammar supporting transactions, postings, balance checks, data-points, and metadata. It provides a flexible foundation that can represent entries from Ledger, hledger, and Beancount. See also: Augmented Backus Naur Format, ABNF and Plain Text Accounting.

ABNF Grammar

; @grammar "pta"
; @extras (@pattern("\\r"))

source_file = *((transaction / comment / %s"\n"))

transaction = header 1*movement

header = date _sp flag [_sp payee] %s"\n"

movement = _sp [linked_prefix] @field(from) account _sp arrow _sp @field(to) account *(_sp word) _sp amount [_sp commodity] %s"\n"

; --- Tokens ---

comment = @token(@pattern("[#;]") @pattern("[^\\n]*"))

_sp = @pattern(" +")

date = @pattern("\\d{4}-\\d{2}-\\d{2}")

flag = (%s"*" / %s"!")

payee = @pattern("[^\\n]+")

linked_prefix = %s"+"

account = @pattern("[A-Z][a-zA-Z0-9]*(:[A-Za-z][a-zA-Z0-9]*)+")

arrow = (%s"->" / %s"//" / %s"→" / %s">")

word = @token(@prec(-1) @pattern("[a-zA-Z][a-zA-Z0-9]*"))

amount = @pattern("-?[0-9][0-9,]*(\\.[0-9]+)?")

commodity = @token(@prec(1) @pattern("[A-Z][A-Z]+"))
ABNF → JSON round-trip
{
  "name": "pta",
  "rules": {
    "source_file": {
      "type": "REPEAT",
      "content": {
        "type": "CHOICE",
        "members": [
          {
            "type": "SYMBOL",
            "name": "transaction"
          },
          {
            "type": "SYMBOL",
            "name": "comment"
          },
          {
            "type": "STRING",
            "value": "\n"
          }
        ]
      }
    },
    "transaction": {
      "type": "SEQ",
      "members": [
        {
          "type": "SYMBOL",
          "name": "header"
        },
        {
          "type": "REPEAT1",
          "content": {
            "type": "SYMBOL",
            "name": "movement"
          }
        }
      ]
    },
    "header": {
      "type": "SEQ",
      "members": [
        {
          "type": "SYMBOL",
          "name": "date"
        },
        {
          "type": "SYMBOL",
          "name": "_sp"
        },
        {
          "type": "SYMBOL",
          "name": "flag"
        },
        {
          "type": "CHOICE",
          "members": [
            {
              "type": "SEQ",
              "members": [
                {
                  "type": "SYMBOL",
                  "name": "_sp"
                },
                {
                  "type": "SYMBOL",
                  "name": "payee"
                }
              ]
            },
            {
              "type": "BLANK"
            }
          ]
        },
        {
          "type": "STRING",
          "value": "\n"
        }
      ]
    },
    "movement": {
      "type": "SEQ",
      "members": [
        {
          "type": "SYMBOL",
          "name": "_sp"
        },
        {
          "type": "CHOICE",
          "members": [
            {
              "type": "SYMBOL",
              "name": "linked_prefix"
            },
            {
              "type": "BLANK"
            }
          ]
        },
        {
          "type": "FIELD",
          "content": {
            "type": "SYMBOL",
            "name": "account"
          },
          "name": "from"
        },
        {
          "type": "SYMBOL",
          "name": "_sp"
        },
        {
          "type": "SYMBOL",
          "name": "arrow"
        },
        {
          "type": "SYMBOL",
          "name": "_sp"
        },
        {
          "type": "FIELD",
          "content": {
            "type": "SYMBOL",
            "name": "account"
          },
          "name": "to"
        },
        {
          "type": "REPEAT",
          "content": {
            "type": "SEQ",
            "members": [
              {
                "type": "SYMBOL",
                "name": "_sp"
              },
              {
                "type": "SYMBOL",
                "name": "word"
              }
            ]
          }
        },
        {
          "type": "SYMBOL",
          "name": "_sp"
        },
        {
          "type": "SYMBOL",
          "name": "amount"
        },
        {
          "type": "CHOICE",
          "members": [
            {
              "type": "SEQ",
              "members": [
                {
                  "type": "SYMBOL",
                  "name": "_sp"
                },
                {
                  "type": "SYMBOL",
                  "name": "commodity"
                }
              ]
            },
            {
              "type": "BLANK"
            }
          ]
        },
        {
          "type": "STRING",
          "value": "\n"
        }
      ]
    },
    "comment": {
      "type": "TOKEN",
      "content": {
        "type": "SEQ",
        "members": [
          {
            "type": "PATTERN",
            "value": "[#;]"
          },
          {
            "type": "PATTERN",
            "value": "[^\\n]*"
          }
        ]
      }
    },
    "_sp": {
      "type": "PATTERN",
      "value": " +"
    },
    "date": {
      "type": "PATTERN",
      "value": "\\d{4}-\\d{2}-\\d{2}"
    },
    "flag": {
      "type": "CHOICE",
      "members": [
        {
          "type": "STRING",
          "value": "*"
        },
        {
          "type": "STRING",
          "value": "!"
        }
      ]
    },
    "payee": {
      "type": "PATTERN",
      "value": "[^\\n]+"
    },
    "linked_prefix": {
      "type": "STRING",
      "value": "+"
    },
    "account": {
      "type": "PATTERN",
      "value": "[A-Z][a-zA-Z0-9]*(:[A-Za-z][a-zA-Z0-9]*)+"
    },
    "arrow": {
      "type": "CHOICE",
      "members": [
        {
          "type": "STRING",
          "value": "-\u003e"
        },
        {
          "type": "STRING",
          "value": "//"
        },
        {
          "type": "STRING",
          "value": "→"
        },
        {
          "type": "STRING",
          "value": "\u003e"
        }
      ]
    },
    "word": {
      "type": "TOKEN",
      "content": {
        "type": "PREC",
        "content": {
          "type": "PATTERN",
          "value": "[a-zA-Z][a-zA-Z0-9]*"
        },
        "value": -1
      }
    },
    "amount": {
      "type": "PATTERN",
      "value": "-?[0-9][0-9,]*(\\.[0-9]+)?"
    },
    "commodity": {
      "type": "TOKEN",
      "content": {
        "type": "PREC",
        "content": {
          "type": "PATTERN",
          "value": "[A-Z][A-Z]+"
        },
        "value": 1
      }
    }
  },
  "extras": [
    {
      "type": "PATTERN",
      "value": "\\r"
    }
  ]
}

Source Files

grammar.js
/// <reference types="tree-sitter-cli/dsl" />
// @ts-check

module.exports = grammar({
  name: "pta",

  extras: (_) => [/\r/],

  rules: {
    source_file: ($) => repeat(choice($.transaction, $.comment, "\n")),

    comment: (_) => token(seq(/[#;]/, /[^\n]*/)),

    transaction: ($) => seq($.header, repeat1($.movement)),

    header: ($) =>
      seq($.date, $._sp, $.flag, optional(seq($._sp, $.payee)), "\n"),

    _sp: (_) => / +/,

    date: (_) => /\d{4}-\d{2}-\d{2}/,

    flag: (_) => choice("*", "!"),

    payee: (_) => /[^\n]+/,

    movement: ($) =>
      seq(
        $._sp,
        optional($.linked_prefix),
        field("from", $.account),
        $._sp,
        $.arrow,
        $._sp,
        field("to", $.account),
        repeat(seq($._sp, $.word)),
        $._sp,
        $.amount,
        optional(seq($._sp, $.commodity)),
        "\n",
      ),

    linked_prefix: (_) => "+",

    account: (_) => /[A-Z][a-zA-Z0-9]*(:[A-Za-z][a-zA-Z0-9]*)+/,

    arrow: (_) => choice("->", "//", "\u2192", ">"),

    word: (_) => token(prec(-1, /[a-zA-Z][a-zA-Z0-9]*/)),

    amount: (_) => /-?[0-9][0-9,]*(\.[0-9]+)?/,

    commodity: (_) => token(prec(1, /[A-Z][A-Z]+/)),
  },
});
grammar.json
{
  "$schema": "https://tree-sitter.github.io/tree-sitter/assets/schemas/grammar.schema.json",
  "name": "pta",
  "rules": {
    "source_file": {
      "type": "REPEAT",
      "content": {
        "type": "CHOICE",
        "members": [
          {
            "type": "SYMBOL",
            "name": "transaction"
          },
          {
            "type": "SYMBOL",
            "name": "comment"
          },
          {
            "type": "STRING",
            "value": "\n"
          }
        ]
      }
    },
    "comment": {
      "type": "TOKEN",
      "content": {
        "type": "SEQ",
        "members": [
          {
            "type": "PATTERN",
            "value": "[#;]"
          },
          {
            "type": "PATTERN",
            "value": "[^\\n]*"
          }
        ]
      }
    },
    "transaction": {
      "type": "SEQ",
      "members": [
        {
          "type": "SYMBOL",
          "name": "header"
        },
        {
          "type": "REPEAT1",
          "content": {
            "type": "SYMBOL",
            "name": "movement"
          }
        }
      ]
    },
    "header": {
      "type": "SEQ",
      "members": [
        {
          "type": "SYMBOL",
          "name": "date"
        },
        {
          "type": "SYMBOL",
          "name": "_sp"
        },
        {
          "type": "SYMBOL",
          "name": "flag"
        },
        {
          "type": "CHOICE",
          "members": [
            {
              "type": "SEQ",
              "members": [
                {
                  "type": "SYMBOL",
                  "name": "_sp"
                },
                {
                  "type": "SYMBOL",
                  "name": "payee"
                }
              ]
            },
            {
              "type": "BLANK"
            }
          ]
        },
        {
          "type": "STRING",
          "value": "\n"
        }
      ]
    },
    "_sp": {
      "type": "PATTERN",
      "value": " +"
    },
    "date": {
      "type": "PATTERN",
      "value": "\\d{4}-\\d{2}-\\d{2}"
    },
    "flag": {
      "type": "CHOICE",
      "members": [
        {
          "type": "STRING",
          "value": "*"
        },
        {
          "type": "STRING",
          "value": "!"
        }
      ]
    },
    "payee": {
      "type": "PATTERN",
      "value": "[^\\n]+"
    },
    "movement": {
      "type": "SEQ",
      "members": [
        {
          "type": "SYMBOL",
          "name": "_sp"
        },
        {
          "type": "CHOICE",
          "members": [
            {
              "type": "SYMBOL",
              "name": "linked_prefix"
            },
            {
              "type": "BLANK"
            }
          ]
        },
        {
          "type": "FIELD",
          "name": "from",
          "content": {
            "type": "SYMBOL",
            "name": "account"
          }
        },
        {
          "type": "SYMBOL",
          "name": "_sp"
        },
        {
          "type": "SYMBOL",
          "name": "arrow"
        },
        {
          "type": "SYMBOL",
          "name": "_sp"
        },
        {
          "type": "FIELD",
          "name": "to",
          "content": {
            "type": "SYMBOL",
            "name": "account"
          }
        },
        {
          "type": "REPEAT",
          "content": {
            "type": "SEQ",
            "members": [
              {
                "type": "SYMBOL",
                "name": "_sp"
              },
              {
                "type": "SYMBOL",
                "name": "word"
              }
            ]
          }
        },
        {
          "type": "SYMBOL",
          "name": "_sp"
        },
        {
          "type": "SYMBOL",
          "name": "amount"
        },
        {
          "type": "CHOICE",
          "members": [
            {
              "type": "SEQ",
              "members": [
                {
                  "type": "SYMBOL",
                  "name": "_sp"
                },
                {
                  "type": "SYMBOL",
                  "name": "commodity"
                }
              ]
            },
            {
              "type": "BLANK"
            }
          ]
        },
        {
          "type": "STRING",
          "value": "\n"
        }
      ]
    },
    "linked_prefix": {
      "type": "STRING",
      "value": "+"
    },
    "account": {
      "type": "PATTERN",
      "value": "[A-Z][a-zA-Z0-9]*(:[A-Za-z][a-zA-Z0-9]*)+"
    },
    "arrow": {
      "type": "CHOICE",
      "members": [
        {
          "type": "STRING",
          "value": "->"
        },
        {
          "type": "STRING",
          "value": "//"
        },
        {
          "type": "STRING",
          "value": "→"
        },
        {
          "type": "STRING",
          "value": ">"
        }
      ]
    },
    "word": {
      "type": "TOKEN",
      "content": {
        "type": "PREC",
        "value": -1,
        "content": {
          "type": "PATTERN",
          "value": "[a-zA-Z][a-zA-Z0-9]*"
        }
      }
    },
    "amount": {
      "type": "PATTERN",
      "value": "-?[0-9][0-9,]*(\\.[0-9]+)?"
    },
    "commodity": {
      "type": "TOKEN",
      "content": {
        "type": "PREC",
        "value": 1,
        "content": {
          "type": "PATTERN",
          "value": "[A-Z][A-Z]+"
        }
      }
    }
  },
  "extras": [
    {
      "type": "PATTERN",
      "value": "\\r"
    }
  ],
  "conflicts": [],
  "precedences": [],
  "externals": [],
  "inline": [],
  "supertypes": []
}