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