all repos

clerk @ 8909472

missing tooling for ledger/hledger

clerk/journal/ast/ast.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
ast: add constructor functions to help with building ast by hand, 1 month ago
1
package ast
2
3
import (
4
	"fmt"
5
	"strings"
6
	"time"
7
8
	"olexsmir.xyz/clerk/journal/token"
9
)
10
11
type Journal struct {
12
	Entries []Entry
13
	Errors  []*ParseError
14
}
15
16
type Entry interface {
17
	entryNode()
18
}
19
20
type ParseError struct {
21
	Span    token.Span
22
	Message string
23
}
24
25
type FileError struct {
26
	Path    string
27
	Span    token.Span
28
	Message string
29
}
30
31
type Date struct {
32
	Year, Month, Day int
33
	Sep              byte // '-' '/' '.'
34
	Span             token.Span
35
}
36
37
// DateOf builds a Date from t, sets '-' as the separator. Helper for handbuilding ast.
38
func DateOf(t time.Time) Date {
39
	return Date{
40
		Year:  t.Year(),
41
		Month: int(t.Month()),
42
		Day:   t.Day(),
43
		Sep:   '-',
44
	}
45
}
46
47
// Compare returns -1 if d is before other, 0 if equal, 1 if after.
48
func (d Date) Compare(other Date) int {
49
	if d.Year != other.Year {
50
		if d.Year < other.Year {
51
			return -1
52
		}
53
		return 1
54
	}
55
	if d.Month != other.Month {
56
		if d.Month < other.Month {
57
			return -1
58
		}
59
		return 1
60
	}
61
	if d.Day != other.Day {
62
		if d.Day < other.Day {
63
			return -1
64
		}
65
		return 1
66
	}
67
	return 0
68
}
69
70
// String renders d as written: with its own separator, and without the year
71
// when the date carried none. Returns "" for an unset or invalid date.
72
func (d Date) String() string {
73
	if (d.Year == 0 && d.Month == 0 && d.Day == 0) ||
74
		(d.Month < 1 || d.Month > 12 || d.Day < 1 || d.Day > 31) {
75
		return ""
76
	}
77
	sep := d.Sep
78
	if sep == 0 {
79
		sep = '-'
80
	}
81
	if d.Year == 0 {
82
		return fmt.Sprintf("%d%c%d", d.Month, sep, d.Day)
83
	}
84
	return fmt.Sprintf("%04d%c%02d%c%02d", d.Year, sep, d.Month, sep, d.Day)
85
}
86
87
type Time struct {
88
	Hour, Minute, Second int
89
	Span                 token.Span
90
}
91
92
type DateTime struct {
93
	Date Date
94
	Time *Time
95
	Span token.Span
96
}
97
98
type Tag struct {
99
	Key, Value string
100
	Span       token.Span
101
}
102
103
type Comment struct {
104
	Marker byte // ';' '#' '%' '*'
105
	Tags   []Tag
106
	Text   string
107
	Span   token.Span
108
}
109
110
func (Comment) entryNode() {}
111
112
type StatusType int
113
114
func (s StatusType) String() string {
115
	switch s {
116
	case StatusCleared:
117
		return "*"
118
	case StatusPending:
119
		return "!"
120
	case StatusNone:
121
		return ""
122
	default:
123
		panic("unreachable")
124
	}
125
}
126
127
const (
128
	StatusNone    StatusType = iota // not set
129
	StatusCleared                   // * cleared
130
	StatusPending                   // ! pending
131
)
132
133
type SubAccount struct {
134
	Name string
135
	Span token.Span
136
}
137
138
type Account struct {
139
	Name []SubAccount // ['expenses' 'food']
140
	Span token.Span
141
}
142
143
// AccountFromString splits s on ':' into subaccounts. Helper for handbuilding ast.
144
func AccountFromString(s string) Account {
145
	subs := strings.Split(s, ":")
146
	acc := Account{Name: make([]SubAccount, len(subs))}
147
	for i := range subs {
148
		acc.Name[i] = SubAccount{Name: subs[i]}
149
	}
150
	return acc
151
}
152
153
func (a Account) String() string {
154
	if len(a.Name) == 0 {
155
		return ""
156
	}
157
	var b strings.Builder
158
	b.Grow(len(a.Name))
159
	b.WriteString(a.Name[0].Name)
160
	for _, s := range a.Name[1:] {
161
		b.WriteByte(':')
162
		b.WriteString(s.Name)
163
	}
164
	return b.String()
165
}