hermez-node/test/transakcio/lang.go
test/transakcio/lang.goRaw
1package transakcio
2
3import (
4 "bufio"
5 "bytes"
6 "fmt"
7 "io"
8 "log"
9 "sort"
10 "strconv"
11
12 "github.com/hermeznetwork/hermez-node/common"
13)
14
15var eof = rune(0)
16var errof = fmt.Errorf("eof in parseline")
17var commentLine = fmt.Errorf("comment in parseline") //nolint:golint
18var newEventLine = fmt.Errorf("newEventLine") //nolint:golint
19var setTypeLine = fmt.Errorf("setTypeLine") //nolint:golint
20
21// setType defines the type of the set
22type setType string
23
24// setTypeBlockchain defines the type 'Blockchain' of the set
25var setTypeBlockchain = setType("Blockchain")
26
27// setTypePoolL2 defines the type 'PoolL2' of the set
28var setTypePoolL2 = setType("PoolL2")
29
30// typeNewBatch is used for testing purposes only, and represents the
31// common.TxType of a new batch
32var typeNewBatch common.TxType = "TxTypeNewBatch"
33
34// typeNewBlock is used for testing purposes only, and represents the
35// common.TxType of a new ethereum block
36var typeNewBlock common.TxType = "TxTypeNewBlock"
37
38var txTypeCreateAccountDepositCoordinator common.TxType = "TypeCreateAccountDepositCoordinator"
39
40//nolint
41const (
42 ILLEGAL token = iota
43 WS
44 EOF
45
46 IDENT // val
47)
48
49// instruction is the data structure that represents one line of code
50type instruction struct {
51 literal string
52 from string
53 to string
54 amount uint64
55 loadAmount uint64
56 fee uint8
57 tokenID common.TokenID
58 typ common.TxType // D: Deposit, T: Transfer, E: ForceExit
59}
60
61// parsedSet contains the full Set of Instructions representing a full code
62type parsedSet struct {
63 // type string
64 instructions []instruction
65 accounts []string
66 tokenIDs []common.TokenID
67}
68
69func (i instruction) String() string {
70 buf := bytes.NewBufferString("")
71 fmt.Fprintf(buf, "Type: %s, ", i.typ)
72 fmt.Fprintf(buf, "From: %s, ", i.from)
73 if i.typ == common.TxTypeTransfer ||
74 i.typ == common.TxTypeDepositTransfer ||
75 i.typ == common.TxTypeCreateAccountDepositTransfer {
76 fmt.Fprintf(buf, "To: %s, ", i.to)
77 }
78
79 if i.typ == common.TxTypeDeposit ||
80 i.typ == common.TxTypeDepositTransfer ||
81 i.typ == common.TxTypeCreateAccountDepositTransfer {
82 fmt.Fprintf(buf, "LoadAmount: %d, ", i.loadAmount)
83 }
84 if i.typ != common.TxTypeDeposit {
85 fmt.Fprintf(buf, "Amount: %d, ", i.amount)
86 }
87 if i.typ == common.TxTypeTransfer ||
88 i.typ == common.TxTypeDepositTransfer ||
89 i.typ == common.TxTypeCreateAccountDepositTransfer {
90 fmt.Fprintf(buf, "Fee: %d, ", i.fee)
91 }
92 fmt.Fprintf(buf, "TokenID: %d\n", i.tokenID)
93 return buf.String()
94}
95
96// Raw returns a string with the raw representation of the Instruction
97func (i instruction) raw() string {
98 buf := bytes.NewBufferString("")
99 fmt.Fprintf(buf, "%s", i.typ)
100 fmt.Fprintf(buf, "(%d)", i.tokenID)
101 fmt.Fprintf(buf, "%s", i.from)
102 if i.typ == common.TxTypeTransfer ||
103 i.typ == common.TxTypeDepositTransfer ||
104 i.typ == common.TxTypeCreateAccountDepositTransfer {
105 fmt.Fprintf(buf, "-%s", i.to)
106 }
107 fmt.Fprintf(buf, ":")
108 if i.typ == common.TxTypeDeposit ||
109 i.typ == common.TxTypeDepositTransfer ||
110 i.typ == common.TxTypeCreateAccountDepositTransfer {
111 fmt.Fprintf(buf, "%d", i.loadAmount)
112 }
113 if i.typ != common.TxTypeDeposit {
114 fmt.Fprintf(buf, "%d", i.amount)
115 }
116 if i.typ == common.TxTypeTransfer {
117 fmt.Fprintf(buf, "(%d)", i.fee)
118 }
119 return buf.String()
120}
121
122type token int
123
124type scanner struct {
125 r *bufio.Reader
126}
127
128func isWhitespace(ch rune) bool {
129 return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\v' || ch == '\f'
130}
131
132func isLetter(ch rune) bool {
133 return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
134}
135
136func isComment(ch rune) bool {
137 return ch == '/'
138}
139
140func isDigit(ch rune) bool {
141 return (ch >= '0' && ch <= '9')
142}
143
144// newScanner creates a new scanner with the given io.Reader
145func newScanner(r io.Reader) *scanner {
146 return &scanner{r: bufio.NewReader(r)}
147}
148
149func (s *scanner) read() rune {
150 ch, _, err := s.r.ReadRune()
151 if err != nil {
152 return eof
153 }
154 return ch
155}
156
157func (s *scanner) unread() {
158 _ = s.r.UnreadRune()
159}
160
161// scan returns the token and literal string of the current value
162func (s *scanner) scan() (tok token, lit string) {
163 ch := s.read()
164
165 if isWhitespace(ch) {
166 // space
167 s.unread()
168 return s.scanWhitespace()
169 } else if isLetter(ch) || isDigit(ch) {
170 // letter/digit
171 s.unread()
172 return s.scanIndent()
173 } else if isComment(ch) {
174 // comment
175 s.unread()
176 return s.scanIndent()
177 }
178
179 if ch == eof {
180 return EOF, ""
181 }
182
183 return ILLEGAL, string(ch)
184}
185
186func (s *scanner) scanWhitespace() (token token, lit string) {
187 var buf bytes.Buffer
188 buf.WriteRune(s.read())
189
190 for {
191 if ch := s.read(); ch == eof {
192 break
193 } else if !isWhitespace(ch) {
194 s.unread()
195 break
196 } else {
197 _, _ = buf.WriteRune(ch)
198 }
199 }
200 return WS, buf.String()
201}
202
203func (s *scanner) scanIndent() (tok token, lit string) {
204 var buf bytes.Buffer
205 buf.WriteRune(s.read())
206
207 for {
208 if ch := s.read(); ch == eof {
209 break
210 } else if !isLetter(ch) && !isDigit(ch) {
211 s.unread()
212 break
213 } else {
214 _, _ = buf.WriteRune(ch)
215 }
216 }
217
218 if len(buf.String()) == 1 {
219 return token(rune(buf.String()[0])), buf.String()
220 }
221 return IDENT, buf.String()
222}
223
224// parser defines the parser
225type parser struct {
226 s *scanner
227 buf struct {
228 tok token
229 lit string
230 n int
231 }
232}
233
234// newParser creates a new parser from a io.Reader
235func newParser(r io.Reader) *parser {
236 return &parser{s: newScanner(r)}
237}
238
239func (p *parser) scan() (tok token, lit string) {
240 // if there is a token in the buffer return it
241 if p.buf.n != 0 {
242 p.buf.n = 0
243 return p.buf.tok, p.buf.lit
244 }
245 tok, lit = p.s.scan()
246
247 p.buf.tok, p.buf.lit = tok, lit
248
249 return
250}
251
252func (p *parser) scanIgnoreWhitespace() (tok token, lit string) {
253 tok, lit = p.scan()
254 if tok == WS {
255 tok, lit = p.scan()
256 }
257 return
258}
259
260// parseLine parses the current line
261func (p *parser) parseLine(setType setType) (*instruction, error) {
262 c := &instruction{}
263 tok, lit := p.scanIgnoreWhitespace()
264 if tok == EOF {
265 return nil, errof
266 }
267 c.literal += lit
268 if lit == "/" {
269 _, _ = p.s.r.ReadString('\n')
270 return nil, commentLine
271 } else if lit == ">" {
272 if setType == setTypePoolL2 {
273 return c, fmt.Errorf("Unexpected '>' at PoolL2Txs set")
274 }
275 _, lit = p.scanIgnoreWhitespace()
276 if lit == "batch" {
277 _, _ = p.s.r.ReadString('\n')
278 return &instruction{typ: typeNewBatch}, newEventLine
279 } else if lit == "block" {
280 _, _ = p.s.r.ReadString('\n')
281 return &instruction{typ: typeNewBlock}, newEventLine
282 } else {
283 return c, fmt.Errorf("Unexpected '> %s', expected '> batch' or '> block'", lit)
284 }
285 } else if lit == "Type" {
286 if err := p.expectChar(c, ":"); err != nil {
287 return c, err
288 }
289 _, lit = p.scanIgnoreWhitespace()
290 if lit == "Blockchain" {
291 return &instruction{typ: "Blockchain"}, setTypeLine
292 } else if lit == "PoolL2" {
293 return &instruction{typ: "PoolL2"}, setTypeLine
294 } else {
295 return c, fmt.Errorf("Invalid set type: '%s'. Valid set types: 'Blockchain', 'PoolL2'", lit)
296 }
297 }
298 if setType == "" {
299 return c, fmt.Errorf("Set type not defined")
300 }
301 transferring := false
302
303 if setType == setTypeBlockchain {
304 switch lit {
305 case "Deposit":
306 c.typ = common.TxTypeDeposit
307 case "Exit":
308 c.typ = common.TxTypeExit
309 case "Transfer":
310 c.typ = common.TxTypeTransfer
311 transferring = true
312 case "CreateAccountDeposit":
313 c.typ = common.TxTypeCreateAccountDeposit
314 case "CreateAccountDepositTransfer":
315 c.typ = common.TxTypeCreateAccountDepositTransfer
316 transferring = true
317 case "CreateAccountDepositCoordinator":
318 c.typ = txTypeCreateAccountDepositCoordinator
319 // transferring is false, as the Coordinator tx transfer will be 0
320 case "DepositTransfer":
321 c.typ = common.TxTypeDepositTransfer
322 transferring = true
323 case "ForceTransfer":
324 c.typ = common.TxTypeForceTransfer
325 case "ForceExit":
326 c.typ = common.TxTypeForceExit
327 default:
328 return c, fmt.Errorf("Unexpected Blockchain tx type: %s", lit)
329 }
330 } else if setType == setTypePoolL2 {
331 switch lit {
332 case "PoolTransfer":
333 c.typ = common.TxTypeTransfer
334 transferring = true
335 case "PoolExit":
336 c.typ = common.TxTypeExit
337 default:
338 return c, fmt.Errorf("Unexpected PoolL2 tx type: %s", lit)
339 }
340 } else {
341 return c, fmt.Errorf("Invalid set type: '%s'. Valid set types: 'Blockchain', 'PoolL2'", setType)
342 }
343
344 if err := p.expectChar(c, "("); err != nil {
345 return c, err
346 }
347 _, lit = p.scanIgnoreWhitespace()
348 c.literal += lit
349 tidI, err := strconv.Atoi(lit)
350 if err != nil {
351 line, _ := p.s.r.ReadString('\n')
352 c.literal += line
353 return c, err
354 }
355 c.tokenID = common.TokenID(tidI)
356 if err := p.expectChar(c, ")"); err != nil {
357 return c, err
358 }
359 _, lit = p.scanIgnoreWhitespace()
360 c.literal += lit
361 c.from = lit
362 if c.typ == txTypeCreateAccountDepositCoordinator {
363 line, _ := p.s.r.ReadString('\n')
364 c.literal += line
365 return c, nil
366 }
367 _, lit = p.scanIgnoreWhitespace()
368 c.literal += lit
369 if transferring {
370 if lit != "-" {
371 return c, fmt.Errorf("Expected '-', found '%s'", lit)
372 }
373 _, lit = p.scanIgnoreWhitespace()
374 c.literal += lit
375 c.to = lit
376 _, lit = p.scanIgnoreWhitespace()
377 c.literal += lit
378 }
379 if lit != ":" {
380 line, _ := p.s.r.ReadString('\n')
381 c.literal += line
382 return c, fmt.Errorf("Expected ':', found '%s'", lit)
383 }
384 if c.typ == common.TxTypeDepositTransfer ||
385 c.typ == common.TxTypeCreateAccountDepositTransfer {
386 // deposit case
387 _, lit = p.scanIgnoreWhitespace()
388 c.literal += lit
389 loadAmount, err := strconv.Atoi(lit)
390 if err != nil {
391 line, _ := p.s.r.ReadString('\n')
392 c.literal += line
393 return c, err
394 }
395 c.loadAmount = uint64(loadAmount)
396 if err := p.expectChar(c, ","); err != nil {
397 return c, err
398 }
399 }
400 _, lit = p.scanIgnoreWhitespace()
401 c.literal += lit
402 amount, err := strconv.Atoi(lit)
403 if err != nil {
404 line, _ := p.s.r.ReadString('\n')
405 c.literal += line
406 return c, err
407 }
408 if c.typ == common.TxTypeDeposit ||
409 c.typ == common.TxTypeCreateAccountDeposit {
410 c.loadAmount = uint64(amount)
411 } else {
412 c.amount = uint64(amount)
413 }
414 if transferring {
415 if err := p.expectChar(c, "("); err != nil {
416 return c, err
417 }
418 _, lit = p.scanIgnoreWhitespace()
419 c.literal += lit
420 fee, err := strconv.Atoi(lit)
421 if err != nil {
422 line, _ := p.s.r.ReadString('\n')
423 c.literal += line
424 return c, err
425 }
426 if fee > common.MaxFeePlan-1 {
427 line, _ := p.s.r.ReadString('\n')
428 c.literal += line
429 return c, fmt.Errorf("Fee %d can not be bigger than 255", fee)
430 }
431 c.fee = uint8(fee)
432
433 if err := p.expectChar(c, ")"); err != nil {
434 return c, err
435 }
436 }
437
438 if tok == EOF {
439 return nil, errof
440 }
441 return c, nil
442}
443
444func (p *parser) expectChar(c *instruction, ch string) error {
445 _, lit := p.scanIgnoreWhitespace()
446 c.literal += lit
447 if lit != ch {
448 line, _ := p.s.r.ReadString('\n')
449 c.literal += line
450 return fmt.Errorf("Expected '%s', found '%s'", ch, lit)
451 }
452 return nil
453}
454
455func idxTokenIDToString(idx string, tid common.TokenID) string {
456 return idx + strconv.Itoa(int(tid))
457}
458
459// parse parses through reader
460func (p *parser) parse() (*parsedSet, error) {
461 instructions := &parsedSet{}
462 i := 0
463 accounts := make(map[string]bool)
464 tokenids := make(map[common.TokenID]bool)
465 var setTypeOfSet setType
466 for {
467 instruction, err := p.parseLine(setTypeOfSet)
468 if err == errof {
469 break
470 }
471 if err == setTypeLine {
472 if instruction.typ == "PoolL2" {
473 setTypeOfSet = setTypePoolL2
474 } else if instruction.typ == "Blockchain" {
475 setTypeOfSet = setTypeBlockchain
476 } else {
477 log.Fatalf("Invalid set type: '%s'. Valid set types: 'Blockchain', 'PoolL2'", instruction.typ)
478 }
479 i++
480 continue
481 }
482 if err == commentLine {
483 i++
484 continue
485 }
486 if err == newEventLine {
487 i++
488 instructions.instructions = append(instructions.instructions, *instruction)
489 continue
490 }
491 if err != nil {
492 return instructions, fmt.Errorf("error parsing line %d: %s, err: %s", i, instruction.literal, err.Error())
493 }
494 if setTypeOfSet == "" {
495 return instructions, fmt.Errorf("Set type not defined")
496 }
497 instructions.instructions = append(instructions.instructions, *instruction)
498 accounts[instruction.from] = true
499 if instruction.typ == common.TxTypeTransfer { // type: Transfer
500 accounts[instruction.to] = true
501 }
502 tokenids[instruction.tokenID] = true
503 i++
504 }
505 for a := range accounts {
506 instructions.accounts = append(instructions.accounts, a)
507 }
508 sort.Strings(instructions.accounts)
509 for tid := range tokenids {
510 instructions.tokenIDs = append(instructions.tokenIDs, tid)
511 }
512 return instructions, nil
513}