Goluca

Movement-based accounting format

Goluca uses movements instead of traditional postings, inspired by Pacioli's Credit/Debit notation. Each movement transfers an amount between two accounts using arrow operators (->, //, >), with linked movements grouped via the + prefix. See also: ABNF and Plain Text Accounting.

ABNF Grammar

; @grammar "goluca"
; @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 description] _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-z0-9][a-zA-Z0-9-]*)+")

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

description = @pattern("\"[^\"]*\"")

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

commodity = @token(@prec(1) @pattern("[A-Z][A-Z]+"))
ABNF → JSON round-trip
{
  "name": "goluca",
  "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": "CHOICE",
          "members": [
            {
              "type": "SEQ",
              "members": [
                {
                  "type": "SYMBOL",
                  "name": "_sp"
                },
                {
                  "type": "SYMBOL",
                  "name": "description"
                }
              ]
            },
            {
              "type": "BLANK"
            }
          ]
        },
        {
          "type": "SYMBOL",
          "name": "_sp"
        },
        {
          "type": "SYMBOL",
          "name": "amount"
        },
        {
          "type": "SYMBOL",
          "name": "_sp"
        },
        {
          "type": "SYMBOL",
          "name": "commodity"
        },
        {
          "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-z0-9][a-zA-Z0-9-]*)+"
    },
    "arrow": {
      "type": "CHOICE",
      "members": [
        {
          "type": "STRING",
          "value": "-\u003e"
        },
        {
          "type": "STRING",
          "value": "//"
        },
        {
          "type": "STRING",
          "value": "→"
        },
        {
          "type": "STRING",
          "value": "\u003e"
        }
      ]
    },
    "description": {
      "type": "PATTERN",
      "value": "\"[^\"]*\""
    },
    "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: "goluca",

  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),
        optional(seq($._sp, $.description)),
        $._sp,
        $.amount,
        $._sp,
        $.commodity,
        "\n",
      ),

    linked_prefix: (_) => "+",

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

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

    description: (_) => /"[^"]*"/,

    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": "goluca",
  "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": "CHOICE",
          "members": [
            {
              "type": "SEQ",
              "members": [
                {
                  "type": "SYMBOL",
                  "name": "_sp"
                },
                {
                  "type": "SYMBOL",
                  "name": "description"
                }
              ]
            },
            {
              "type": "BLANK"
            }
          ]
        },
        {
          "type": "SYMBOL",
          "name": "_sp"
        },
        {
          "type": "SYMBOL",
          "name": "amount"
        },
        {
          "type": "SYMBOL",
          "name": "_sp"
        },
        {
          "type": "SYMBOL",
          "name": "commodity"
        },
        {
          "type": "STRING",
          "value": "\n"
        }
      ]
    },
    "linked_prefix": {
      "type": "STRING",
      "value": "+"
    },
    "account": {
      "type": "PATTERN",
      "value": "[A-Z][a-zA-Z0-9]*(:[A-Za-z0-9][a-zA-Z0-9-]*)+"
    },
    "arrow": {
      "type": "CHOICE",
      "members": [
        {
          "type": "STRING",
          "value": "->"
        },
        {
          "type": "STRING",
          "value": "//"
        },
        {
          "type": "STRING",
          "value": "→"
        },
        {
          "type": "STRING",
          "value": ">"
        }
      ]
    },
    "description": {
      "type": "PATTERN",
      "value": "\"[^\"]*\""
    },
    "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": []
}