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.
; @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]+"))
{
"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"
}
]
}
/// <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]+/)), }, });
{
"$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": []
}