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// Instructions contains the full Set of Instructions representing a full code
49type Instructions 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 if i.Type == common.TxTypeDepositTransfer ||
65 i.Type == common.TxTypeCreateAccountDepositTransfer {
66 fmt.Fprintf(buf, "LoadAmount: %d, ", i.LoadAmount)
67 }
68 fmt.Fprintf(buf, "Amount: %d, ", i.Amount)
69 if i.Type == common.TxTypeTransfer ||
70 i.Type == common.TxTypeDepositTransfer ||
71 i.Type == common.TxTypeCreateAccountDepositTransfer {
72 fmt.Fprintf(buf, "Fee: %d, ", i.Fee)
73 }
74 fmt.Fprintf(buf, "TokenID: %d\n", i.TokenID)
75 return buf.String()
76}
77
78// Raw returns a string with the raw representation of the Instruction
79func (i Instruction) Raw() string {
80 buf := bytes.NewBufferString("")
81 fmt.Fprintf(buf, "%s", i.Type)
82 fmt.Fprintf(buf, "(%d)", i.TokenID)
83 fmt.Fprintf(buf, "%s", i.From)
84 if i.Type == common.TxTypeTransfer ||
85 i.Type == common.TxTypeDepositTransfer ||
86 i.Type == common.TxTypeCreateAccountDepositTransfer {
87 fmt.Fprintf(buf, "-%s", i.To)
88 }
89 fmt.Fprintf(buf, ":")
90 if i.Type == common.TxTypeDepositTransfer ||
91 i.Type == common.TxTypeCreateAccountDepositTransfer {
92 fmt.Fprintf(buf, "%d,", i.LoadAmount)
93 }
94 fmt.Fprintf(buf, "%d", i.Amount)
95 if i.Type == common.TxTypeTransfer {
96 fmt.Fprintf(buf, "(%d)", i.Fee)
97 }
98 return buf.String()
99}
100
101type token int
102
103type scanner struct {
104 r *bufio.Reader
105}
106
107func isWhitespace(ch rune) bool {
108 return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\v' || ch == '\f'
109}
110
111func isLetter(ch rune) bool {
112 return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
113}
114
115func isComment(ch rune) bool {
116 return ch == '/'
117}
118
119func isDigit(ch rune) bool {
120 return (ch >= '0' && ch <= '9')
121}
122
123// newScanner creates a new scanner with the given io.Reader
124func newScanner(r io.Reader) *scanner {
125 return &scanner{r: bufio.NewReader(r)}
126}
127
128func (s *scanner) read() rune {
129 ch, _, err := s.r.ReadRune()
130 if err != nil {
131 return eof
132 }
133 return ch
134}
135
136func (s *scanner) unread() {
137 _ = s.r.UnreadRune()
138}
139
140// scan returns the token and literal string of the current value
141func (s *scanner) scan() (tok token, lit string) {
142 ch := s.read()
143
144 if isWhitespace(ch) {
145 // space
146 s.unread()
147 return s.scanWhitespace()
148 } else if isLetter(ch) || isDigit(ch) {
149 // letter/digit
150 s.unread()
151 return s.scanIndent()
152 } else if isComment(ch) {
153 // comment
154 s.unread()
155 return s.scanIndent()
156 }
157
158 if ch == eof {
159 return EOF, ""
160 }
161
162 return ILLEGAL, string(ch)
163}
164
165func (s *scanner) scanWhitespace() (token token, lit string) {
166 var buf bytes.Buffer
167 buf.WriteRune(s.read())
168
169 for {
170 if ch := s.read(); ch == eof {
171 break
172 } else if !isWhitespace(ch) {
173 s.unread()
174 break
175 } else {
176 _, _ = buf.WriteRune(ch)
177 }
178 }
179 return WS, buf.String()
180}
181
182func (s *scanner) scanIndent() (tok token, lit string) {
183 var buf bytes.Buffer
184 buf.WriteRune(s.read())
185
186 for {
187 if ch := s.read(); ch == eof {
188 break
189 } else if !isLetter(ch) && !isDigit(ch) {
190 s.unread()
191 break
192 } else {
193 _, _ = buf.WriteRune(ch)
194 }
195 }
196
197 if len(buf.String()) == 1 {
198 return token(rune(buf.String()[0])), buf.String()
199 }
200 return IDENT, buf.String()
201}
202
203// Parser defines the parser
204type Parser struct {
205 s *scanner
206 buf struct {
207 tok token
208 lit string
209 n int
210 }
211}
212
213// NewParser creates a new parser from a io.Reader
214func NewParser(r io.Reader) *Parser {
215 return &Parser{s: newScanner(r)}
216}
217
218func (p *Parser) scan() (tok token, lit string) {
219 // if there is a token in the buffer return it
220 if p.buf.n != 0 {
221 p.buf.n = 0
222 return p.buf.tok, p.buf.lit
223 }
224 tok, lit = p.s.scan()
225
226 p.buf.tok, p.buf.lit = tok, lit
227
228 return
229}
230
231func (p *Parser) scanIgnoreWhitespace() (tok token, lit string) {
232 tok, lit = p.scan()
233 if tok == WS {
234 tok, lit = p.scan()
235 }
236 return
237}
238
239// parseLine parses the current line
240func (p *Parser) parseLine() (*Instruction, error) {
241 c := &Instruction{}
242 tok, lit := p.scanIgnoreWhitespace()
243 if tok == EOF {
244 return nil, errof
245 }
246 c.Literal += lit
247 if lit == "/" {
248 _, _ = p.s.r.ReadString('\n')
249 return nil, errComment
250 } else if lit == ">" {
251 _, lit = p.scanIgnoreWhitespace()
252 if lit == "batch" {
253 _, _ = p.s.r.ReadString('\n')
254 return &Instruction{Type: TypeNewBatch}, newEvent
255 } else if lit == "block" {
256 _, _ = p.s.r.ReadString('\n')
257 return &Instruction{Type: TypeNewBlock}, newEvent
258 } else {
259 return c, fmt.Errorf("Unexpected '> %s', expected '> batch' or '> block'", lit)
260 }
261 }
262 transfering := false
263 switch lit {
264 case "Deposit":
265 c.Type = common.TxTypeDeposit
266 case "Exit", "PoolExit":
267 c.Type = common.TxTypeExit
268 case "Transfer", "PoolTransfer":
269 c.Type = common.TxTypeTransfer
270 transfering = true
271 case "CreateAccountDeposit":
272 c.Type = common.TxTypeCreateAccountDeposit
273 case "CreateAccountDepositTransfer":
274 c.Type = common.TxTypeCreateAccountDepositTransfer
275 transfering = true
276 case "DepositTransfer":
277 c.Type = common.TxTypeDepositTransfer
278 transfering = true
279 case "ForceTransfer":
280 c.Type = common.TxTypeForceTransfer
281 case "ForceExit":
282 c.Type = common.TxTypeForceExit
283 default:
284 return c, fmt.Errorf("Unexpected tx type: %s", lit)
285 }
286
287 if err := p.expectChar(c, "("); err != nil {
288 return c, err
289 }
290 _, lit = p.scanIgnoreWhitespace()
291 c.Literal += lit
292 tidI, err := strconv.Atoi(lit)
293 if err != nil {
294 line, _ := p.s.r.ReadString('\n')
295 c.Literal += line
296 return c, err
297 }
298 c.TokenID = common.TokenID(tidI)
299 if err := p.expectChar(c, ")"); err != nil {
300 return c, err
301 }
302 _, lit = p.scanIgnoreWhitespace()
303 c.Literal += lit
304 c.From = lit
305 _, lit = p.scanIgnoreWhitespace()
306 c.Literal += lit
307 if lit == "-" {
308 if !transfering {
309 return c, fmt.Errorf("To defined, but not type {Transfer, CreateAccountDepositTransfer, DepositTransfer}")
310 }
311 _, lit = p.scanIgnoreWhitespace()
312 c.Literal += lit
313 c.To = lit
314 _, lit = p.scanIgnoreWhitespace()
315 c.Literal += lit
316 }
317 if lit != ":" {
318 line, _ := p.s.r.ReadString('\n')
319 c.Literal += line
320 return c, fmt.Errorf("Expected ':', found '%s'", lit)
321 }
322 if c.Type == common.TxTypeDepositTransfer ||
323 c.Type == common.TxTypeCreateAccountDepositTransfer {
324 _, lit = p.scanIgnoreWhitespace()
325 c.Literal += lit
326 loadAmount, err := strconv.Atoi(lit)
327 if err != nil {
328 line, _ := p.s.r.ReadString('\n')
329 c.Literal += line
330 return c, err
331 }
332 c.LoadAmount = uint64(loadAmount)
333 if err := p.expectChar(c, ","); err != nil {
334 return c, err
335 }
336 }
337 _, lit = p.scanIgnoreWhitespace()
338 c.Literal += lit
339 amount, err := strconv.Atoi(lit)
340 if err != nil {
341 line, _ := p.s.r.ReadString('\n')
342 c.Literal += line
343 return c, err
344 }
345 c.Amount = uint64(amount)
346 if transfering {
347 if err := p.expectChar(c, "("); err != nil {
348 return c, err
349 }
350 _, lit = p.scanIgnoreWhitespace()
351 c.Literal += lit
352 fee, err := strconv.Atoi(lit)
353 if err != nil {
354 line, _ := p.s.r.ReadString('\n')
355 c.Literal += line
356 return c, err
357 }
358 if fee > common.MaxFeePlan-1 {
359 line, _ := p.s.r.ReadString('\n')
360 c.Literal += line
361 return c, fmt.Errorf("Fee %d can not be bigger than 255", fee)
362 }
363 c.Fee = uint8(fee)
364
365 if err := p.expectChar(c, ")"); err != nil {
366 return c, err
367 }
368 }
369
370 if tok == EOF {
371 return nil, errof
372 }
373 return c, nil
374}
375
376func (p *Parser) expectChar(c *Instruction, ch string) error {
377 _, lit := p.scanIgnoreWhitespace()
378 c.Literal += lit
379 if lit != ch {
380 line, _ := p.s.r.ReadString('\n')
381 c.Literal += line
382 return fmt.Errorf("Expected '%s', found '%s'", ch, lit)
383 }
384 return nil
385}
386
387func idxTokenIDToString(idx string, tid common.TokenID) string {
388 return idx + strconv.Itoa(int(tid))
389}
390
391// Parse parses through reader
392func (p *Parser) Parse() (Instructions, error) {
393 var instructions Instructions
394 i := 0
395 accounts := make(map[string]bool)
396 tokenids := make(map[common.TokenID]bool)
397 for {
398 instruction, err := p.parseLine()
399 if err == errof {
400 break
401 }
402 if err == errComment {
403 i++
404 continue
405 }
406 if err == newEvent {
407 i++
408 instructions.Instructions = append(instructions.Instructions, instruction)
409 continue
410 }
411 if err != nil {
412 return instructions, fmt.Errorf("error parsing line %d: %s, err: %s", i, instruction.Literal, err.Error())
413 }
414 instructions.Instructions = append(instructions.Instructions, instruction)
415 accounts[idxTokenIDToString(instruction.From, instruction.TokenID)] = true
416 if instruction.Type == common.TxTypeTransfer { // type: Transfer
417 accounts[idxTokenIDToString(instruction.To, instruction.TokenID)] = true
418 }
419 tokenids[instruction.TokenID] = true
420 i++
421 }
422 for a := range accounts {
423 instructions.Accounts = append(instructions.Accounts, a)
424 }
425 sort.Strings(instructions.Accounts)
426 for tid := range tokenids {
427 instructions.TokenIDs = append(instructions.TokenIDs, tid)
428 }
429 return instructions, nil
430}