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