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