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