all repos

clerk @ d216565

missing tooling for ledger/hledger
3 files changed, 41 insertions(+), 41 deletions(-)
lexer: minimize public surface of the package
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-08-04 14:36:36 +0300
Authored at: 2026-08-04 13:07:16 +0300
Change ID: uonzsmkvkxquwtvtzsmnovluzpkyoolr
Parent: 9ee2ed8
M journal/lexer/dump.go
···
        7
        7
         	"olexsmir.xyz/clerk/journal/token"

      
        8
        8
         )

      
        9
        9
         

      
        10
        
        -func (l *Lexer) Dump() string {

      
        
        10
        +func (l *Lexer) dump() string {

      
        11
        11
         	var b strings.Builder

      
        12
        12
         	for {

      
        13
        13
         		t := l.Next()

      
M journal/lexer/lexer.go
···
        8
        8
         	"olexsmir.xyz/clerk/journal/token"

      
        9
        9
         )

      
        10
        10
         

      
        11
        
        -type Mode uint

      
        
        11
        +type mode uint

      
        12
        12
         

      
        13
        13
         const (

      
        14
        14
         	// start of a line, nothing consumed

      
        15
        
        -	ModeDefault Mode = iota

      
        
        15
        +	modeDefault mode = iota

      
        16
        16
         

      
        17
        17
         	// after ; # * % ; at start of line, or anywhere inline

      
        18
        18
         	// everything until \n is comment text

      
        19
        
        -	ModeComment

      
        
        19
        +	modeComment

      
        20
        20
         

      
        21
        21
         	// after lexing a date at column 0

      
        22
        22
         	// expects: optional status, optional code, description, comment

      
        23
        
        -	ModeTransaction

      
        
        23
        +	modeTransaction

      
        24
        24
         

      
        25
        25
         	// after lexing an indent at start of line

      
        26
        26
         	// expects: account name, then two spaces, then amount

      
        27
        
        -	ModePosting

      
        
        27
        +	modePosting

      
        28
        28
         

      
        29
        29
         	// after ~, period expression

      
        30
        30
         	// expects: period, optional description (after 2+ spaces), optional comment

      
        31
        
        -	ModePeriodic

      
        
        31
        +	modePeriodic

      
        32
        32
         

      
        33
        33
         	// after =, automates transaction

      
        34
        34
         	// expects: expression

      
        35
        
        -	ModeAutomated

      
        
        35
        +	modeAutomated

      
        36
        36
         

      
        37
        37
         	// after a directive keyword like account, commodity, include

      
        38
        38
         	// expects: rest of directive content

      
        39
        
        -	ModeDirective

      
        
        39
        +	modeDirective

      
        40
        40
         )

      
        41
        41
         

      
        42
        42
         type Lexer struct {

      
        43
        43
         	file  string

      
        44
        44
         	input []byte

      
        45
        
        -	mode  Mode

      
        
        45
        +	mode  mode

      
        46
        46
         

      
        47
        47
         	ch     rune // current rune (0 = EOF/sentinel)

      
        48
        48
         	chSize int  // byte size of current rune

      ···
        78
        78
         // Next returns next token in the input

      
        79
        79
         func (l *Lexer) Next() token.Token {

      
        80
        80
         	switch l.mode {

      
        81
        
        -	case ModeDefault:

      
        
        81
        +	case modeDefault:

      
        82
        82
         		return l.lexDefault()

      
        83
        
        -	case ModeComment:

      
        
        83
        +	case modeComment:

      
        84
        84
         		return l.lexComment()

      
        85
        
        -	case ModeTransaction:

      
        
        85
        +	case modeTransaction:

      
        86
        86
         		return l.lexTransaction()

      
        87
        
        -	case ModePosting:

      
        
        87
        +	case modePosting:

      
        88
        88
         		return l.lexPosting()

      
        89
        
        -	case ModePeriodic:

      
        
        89
        +	case modePeriodic:

      
        90
        90
         		return l.lexPeriodic()

      
        91
        
        -	case ModeAutomated:

      
        
        91
        +	case modeAutomated:

      
        92
        92
         		return l.lexAutomated()

      
        93
        
        -	case ModeDirective:

      
        
        93
        +	case modeDirective:

      
        94
        94
         		return l.lexDirective()

      
        95
        95
         	}

      
        96
        96
         	panic("unreachable")

      ···
        115
        115
         	case l.ch == ' ' || l.ch == '\t':

      
        116
        116
         		tok := l.lexIndent()

      
        117
        117
         		if l.subdirective {

      
        118
        
        -			l.mode = ModeDirective

      
        
        118
        +			l.mode = modeDirective

      
        119
        119
         			l.subdirective = false

      
        120
        120
         		} else {

      
        121
        
        -			l.mode = ModePosting

      
        
        121
        +			l.mode = modePosting

      
        122
        122
         		}

      
        123
        123
         		l.postingExpectAccount = true

      
        124
        124
         		return tok

      
        125
        125
         	case l.ch == ';' || l.ch == '#' || l.ch == '%':

      
        126
        
        -		l.mode = ModeComment

      
        
        126
        +		l.mode = modeComment

      
        127
        127
         		return l.lexSingle(token.SEMICOLON) // todo: ??

      
        128
        128
         	case l.ch == '*': // * at col 0 == comment

      
        129
        
        -		l.mode = ModeComment

      
        
        129
        +		l.mode = modeComment

      
        130
        130
         		return l.lexSingle(token.STAR)

      
        131
        131
         	case l.ch == '~':

      
        132
        
        -		l.mode = ModePeriodic

      
        
        132
        +		l.mode = modePeriodic

      
        133
        133
         		return l.lexSingle(token.TILDE)

      
        134
        134
         	case l.ch == '=':

      
        135
        
        -		l.mode = ModeAutomated

      
        
        135
        +		l.mode = modeAutomated

      
        136
        136
         		return l.lexSingle(token.EQ)

      
        137
        137
         	case l.ch == '+':

      
        138
        138
         		return l.lexSingle(token.PLUS)

      ···
        155
        155
         			return token.Token{Type: token.ILLEGAL, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}

      
        156
        156
         		}

      
        157
        157
         		tok := l.lexDate()

      
        158
        
        -		l.mode = ModeTransaction

      
        
        158
        +		l.mode = modeTransaction

      
        159
        159
         		l.transactionPastStatus = false

      
        160
        160
         		return tok

      
        161
        161
         	default:

      ···
        167
        167
         

      
        168
        168
         func (l *Lexer) lexComment() token.Token {

      
        169
        169
         	if l.ch == '\n' || l.ch == '\r' || l.ch == 0 {

      
        170
        
        -		l.mode = ModeDefault

      
        
        170
        +		l.mode = modeDefault

      
        171
        171
         		if l.ch == '\r' {

      
        172
        172
         			l.col = 0

      
        173
        173
         			l.advance()

      ···
        180
        180
         	}

      
        181
        181
         

      
        182
        182
         	if l.ch == '\n' || l.ch == '\r' || l.ch == 0 {

      
        183
        
        -		l.mode = ModeDefault

      
        
        183
        +		l.mode = modeDefault

      
        184
        184
         		if l.ch == '\r' {

      
        185
        185
         			l.col = 0

      
        186
        186
         			l.advance()

      ···
        205
        205
         	case 0:

      
        206
        206
         		return l.token(token.EOF, "")

      
        207
        207
         	case '\n':

      
        208
        
        -		l.mode = ModeDefault

      
        
        208
        +		l.mode = modeDefault

      
        209
        209
         		return l.lexNewline()

      
        210
        210
         	case '\r':

      
        211
        211
         		l.col = 0

      ···
        214
        214
         	case ' ', '\t':

      
        215
        215
         		return l.lexWhitespace()

      
        216
        216
         	case ';':

      
        217
        
        -		l.mode = ModeComment

      
        
        217
        +		l.mode = modeComment

      
        218
        218
         		return l.lexSingle(token.SEMICOLON)

      
        219
        219
         	case '*':

      
        220
        220
         		if !l.transactionPastStatus {

      ···
        271
        271
         	case 0:

      
        272
        272
         		return l.token(token.EOF, "")

      
        273
        273
         	case '\n':

      
        274
        
        -		l.mode = ModeDefault

      
        
        274
        +		l.mode = modeDefault

      
        275
        275
         		return l.lexNewline()

      
        276
        276
         	case '\r':

      
        277
        277
         		l.col = 0

      
        278
        278
         		l.advance()

      
        279
        279
         		return l.lexNewline()

      
        280
        280
         	case ';':

      
        281
        
        -		l.mode = ModeComment

      
        
        281
        +		l.mode = modeComment

      
        282
        282
         		return l.lexSingle(token.SEMICOLON)

      
        283
        283
         	case ' ', '\t':

      
        284
        284
         		return l.lexWhitespace()

      ···
        292
        292
         	case 0:

      
        293
        293
         		return l.token(token.EOF, "")

      
        294
        294
         	case '\n':

      
        295
        
        -		l.mode = ModeDefault

      
        
        295
        +		l.mode = modeDefault

      
        296
        296
         		return l.lexNewline()

      
        297
        297
         	case '\r':

      
        298
        298
         		l.col = 0

      ···
        301
        301
         	case ' ', '\t':

      
        302
        302
         		return l.lexWhitespace()

      
        303
        303
         	case ';':

      
        304
        
        -		l.mode = ModeComment

      
        
        304
        +		l.mode = modeComment

      
        305
        305
         		return l.lexSingle(token.SEMICOLON)

      
        306
        306
         	default:

      
        307
        307
         		return l.lexText()

      ···
        315
        315
         		return l.token(token.EOF, "")

      
        316
        316
         	case l.ch == '\n':

      
        317
        317
         		l.postingExpectAccount = false

      
        318
        
        -		l.mode = ModeDefault

      
        
        318
        +		l.mode = modeDefault

      
        319
        319
         		return l.lexNewline()

      
        320
        320
         	case l.ch == '\r':

      
        321
        321
         		l.postingExpectAccount = false

      
        322
        322
         		l.col = 0

      
        323
        323
         		l.advance()

      
        324
        
        -		l.mode = ModeDefault

      
        
        324
        +		l.mode = modeDefault

      
        325
        325
         		return l.lexNewline()

      
        326
        326
         	case l.ch == ';':

      
        327
        327
         		l.postingExpectAccount = false

      
        328
        
        -		l.mode = ModeComment

      
        
        328
        +		l.mode = modeComment

      
        329
        329
         		return l.lexSingle(token.SEMICOLON)

      
        330
        330
         	case l.ch == ' ' || l.ch == '\t':

      
        331
        331
         		return l.lexWhitespace()

      ···
        378
        378
         func (l *Lexer) lexDirective() token.Token {

      
        379
        379
         	switch {

      
        380
        380
         	case l.ch == '\n', l.ch == 0:

      
        381
        
        -		l.mode = ModeDefault

      
        
        381
        +		l.mode = modeDefault

      
        382
        382
         		return l.lexNewline()

      
        383
        383
         	case l.ch == '\r':

      
        384
        
        -		l.mode = ModeDefault

      
        
        384
        +		l.mode = modeDefault

      
        385
        385
         		l.col = 0

      
        386
        386
         		l.advance()

      
        387
        387
         		return l.lexNewline()

      
        388
        388
         	case l.ch == ';':

      
        389
        
        -		l.mode = ModeComment

      
        
        389
        +		l.mode = modeComment

      
        390
        390
         		return l.lexSingle(token.SEMICOLON)

      
        391
        391
         	case l.ch == ' ', l.ch == '\t':

      
        392
        392
         		return l.lexWhitespace()

      ···
        444
        444
         func (l *Lexer) lexNewline() token.Token {

      
        445
        445
         	s := l.save()

      
        446
        446
         	l.advance()

      
        447
        
        -	l.mode = ModeDefault

      
        
        447
        +	l.mode = modeDefault

      
        448
        448
         	return token.Token{Type: token.NEWLINE, Literal: "\n", Span: l.span(s)}

      
        449
        449
         }

      
        450
        450
         

      ···
        601
        601
         	if kind == token.ILLEGAL { // todo: report an error ??

      
        602
        602
         		kind = token.TEXT

      
        603
        603
         	} else {

      
        604
        
        -		l.mode = ModeDirective

      
        
        604
        +		l.mode = modeDirective

      
        605
        605
         		l.subdirective = kind == token.ACCOUNT || kind == token.COMMODITY

      
        606
        606
         		l.includePath = kind == token.INCLUDE

      
        607
        607
         	}

      
M journal/lexer/lexer_test.go
···
        35
        35
         	for _, tt := range tests {

      
        36
        36
         		t.Run(tt, func(t *testing.T) {

      
        37
        37
         			a := golden.Read(t, tt)

      
        38
        
        -			golden.Assert(t, a, New("j", a.Get("input")).Dump())

      
        
        38
        +			golden.Assert(t, a, New("j", a.Get("input")).dump())

      
        39
        39
         		})

      
        40
        40
         	}

      
        41
        41
         }