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 errNewBatch = fmt.Errorf("newbatch")
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//nolint
24const (
25 ILLEGAL token = iota
26 WS
27 EOF
28
29 IDENT // val
30)
31
32// Instruction is the data structure that represents one line of code
33type Instruction struct {
34 Literal string
35 From string
36 To string
37 Amount uint64
38 Fee uint8
39 TokenID common.TokenID
40 Type common.TxType // D: Deposit, T: Transfer, E: ForceExit
41}
42
43// Instructions contains the full Set of Instructions representing a full code
44type Instructions struct {
45 Instructions []*Instruction
46 Accounts []string
47 TokenIDs []common.TokenID
48}
49
50func (i Instruction) String() string {
51 buf := bytes.NewBufferString("")
52 switch i.Type {
53 case common.TxTypeCreateAccountDeposit:
54 fmt.Fprintf(buf, "Type: Create&Deposit, ")
55 case common.TxTypeTransfer:
56 fmt.Fprintf(buf, "Type: Transfer, ")
57 case common.TxTypeForceExit:
58 fmt.Fprintf(buf, "Type: ForceExit, ")
59 default:
60 }
61 fmt.Fprintf(buf, "From: %s, ", i.From)
62 if i.Type == common.TxTypeTransfer {
63 fmt.Fprintf(buf, "To: %s, ", i.To)
64 }
65 fmt.Fprintf(buf, "Amount: %d, ", i.Amount)
66 if i.Type == common.TxTypeTransfer {
67 fmt.Fprintf(buf, "Fee: %d, ", i.Fee)
68 }
69 fmt.Fprintf(buf, "TokenID: %d,\n", i.TokenID)
70 return buf.String()
71}
72
73// Raw returns a string with the raw representation of the Instruction
74func (i Instruction) Raw() string {
75 buf := bytes.NewBufferString("")
76 fmt.Fprintf(buf, "%s", i.From)
77 if i.Type == common.TxTypeTransfer {
78 fmt.Fprintf(buf, "-%s", i.To)
79 }
80 fmt.Fprintf(buf, " (%d)", i.TokenID)
81 if i.Type == common.TxTypeForceExit {
82 fmt.Fprintf(buf, "E")
83 }
84 fmt.Fprintf(buf, ":")
85 fmt.Fprintf(buf, " %d", i.Amount)
86 if i.Type == common.TxTypeTransfer {
87 fmt.Fprintf(buf, " %d", i.Fee)
88 }
89 return buf.String()
90}
91
92type token int
93
94type scanner struct {
95 r *bufio.Reader
96}
97
98func isWhitespace(ch rune) bool {
99 return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\v' || ch == '\f'
100}
101
102func isLetter(ch rune) bool {
103 return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
104}
105
106func isComment(ch rune) bool {
107 return ch == '/'
108}
109
110func isDigit(ch rune) bool {
111 return (ch >= '0' && ch <= '9')
112}
113
114// newScanner creates a new scanner with the given io.Reader
115func newScanner(r io.Reader) *scanner {
116 return &scanner{r: bufio.NewReader(r)}
117}
118
119func (s *scanner) read() rune {
120 ch, _, err := s.r.ReadRune()
121 if err != nil {
122 return eof
123 }
124 return ch
125}
126
127func (s *scanner) unread() {
128 _ = s.r.UnreadRune()
129}
130
131// scan returns the token and literal string of the current value
132func (s *scanner) scan() (tok token, lit string) {
133 ch := s.read()
134
135 if isWhitespace(ch) {
136 // space
137 s.unread()
138 return s.scanWhitespace()
139 } else if isLetter(ch) || isDigit(ch) {
140 // letter/digit
141 s.unread()
142 return s.scanIndent()
143 } else if isComment(ch) {
144 // comment
145 s.unread()
146 return s.scanIndent()
147 }
148
149 if ch == eof {
150 return EOF, ""
151 }
152
153 return ILLEGAL, string(ch)
154}
155
156func (s *scanner) scanWhitespace() (token token, lit string) {
157 var buf bytes.Buffer
158 buf.WriteRune(s.read())
159
160 for {
161 if ch := s.read(); ch == eof {
162 break
163 } else if !isWhitespace(ch) {
164 s.unread()
165 break
166 } else {
167 _, _ = buf.WriteRune(ch)
168 }
169 }
170 return WS, buf.String()
171}
172
173func (s *scanner) scanIndent() (tok token, lit string) {
174 var buf bytes.Buffer
175 buf.WriteRune(s.read())
176
177 for {
178 if ch := s.read(); ch == eof {
179 break
180 } else if !isLetter(ch) && !isDigit(ch) {
181 s.unread()
182 break
183 } else {
184 _, _ = buf.WriteRune(ch)
185 }
186 }
187
188 if len(buf.String()) == 1 {
189 return token(rune(buf.String()[0])), buf.String()
190 }
191 return IDENT, buf.String()
192}
193
194// Parser defines the parser
195type Parser struct {
196 s *scanner
197 buf struct {
198 tok token
199 lit string
200 n int
201 }
202}
203
204// NewParser creates a new parser from a io.Reader
205func NewParser(r io.Reader) *Parser {
206 return &Parser{s: newScanner(r)}
207}
208
209func (p *Parser) scan() (tok token, lit string) {
210 // if there is a token in the buffer return it
211 if p.buf.n != 0 {
212 p.buf.n = 0
213 return p.buf.tok, p.buf.lit
214 }
215 tok, lit = p.s.scan()
216
217 p.buf.tok, p.buf.lit = tok, lit
218
219 return
220}
221
222func (p *Parser) scanIgnoreWhitespace() (tok token, lit string) {
223 tok, lit = p.scan()
224 if tok == WS {
225 tok, lit = p.scan()
226 }
227 return
228}
229
230// parseLine parses the current line
231func (p *Parser) parseLine() (*Instruction, error) {
232 // line can be Deposit:
233 // A (1): 10
234 // or Transfer:
235 // A-B (1): 6
236 // or Withdraw:
237 // A (1) E: 4
238 // or NextBatch:
239 // > and here the comment
240
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 _, _ = p.s.r.ReadString('\n')
252 return nil, errNewBatch
253 }
254 c.From = lit
255
256 _, lit = p.scanIgnoreWhitespace()
257 c.Literal += lit
258 if lit == "-" {
259 // transfer
260 _, lit = p.scanIgnoreWhitespace()
261 c.Literal += lit
262 c.To = lit
263 c.Type = common.TxTypeTransfer
264 _, lit = p.scanIgnoreWhitespace() // expect (
265 c.Literal += lit
266 if lit != "(" {
267 line, _ := p.s.r.ReadString('\n')
268 c.Literal += line
269 return c, fmt.Errorf("Expected '(', found '%s'", lit)
270 }
271 } else {
272 c.Type = common.TxTypeCreateAccountDeposit
273 }
274
275 _, lit = p.scanIgnoreWhitespace()
276 c.Literal += lit
277 tidI, err := strconv.Atoi(lit)
278 if err != nil {
279 line, _ := p.s.r.ReadString('\n')
280 c.Literal += line
281 return c, err
282 }
283 c.TokenID = common.TokenID(tidI)
284 _, lit = p.scanIgnoreWhitespace() // expect )
285 c.Literal += lit
286 if lit != ")" {
287 line, _ := p.s.r.ReadString('\n')
288 c.Literal += line
289 return c, fmt.Errorf("Expected ')', found '%s'", lit)
290 }
291
292 _, lit = p.scanIgnoreWhitespace() // expect ':' or 'E' (Exit type)
293 c.Literal += lit
294 if lit == "E" {
295 c.Type = common.TxTypeForceExit
296 _, lit = p.scanIgnoreWhitespace() // expect ':'
297 c.Literal += lit
298 }
299 if lit != ":" {
300 line, _ := p.s.r.ReadString('\n')
301 c.Literal += line
302 return c, fmt.Errorf("Expected ':', found '%s'", lit)
303 }
304 tok, lit = p.scanIgnoreWhitespace()
305 c.Literal += lit
306 amount, err := strconv.Atoi(lit)
307 if err != nil {
308 line, _ := p.s.r.ReadString('\n')
309 c.Literal += line
310 return c, err
311 }
312 c.Amount = uint64(amount)
313
314 if c.Type == common.TxTypeTransfer {
315 tok, lit = p.scanIgnoreWhitespace()
316 c.Literal += lit
317 fee, err := strconv.Atoi(lit)
318 if err != nil {
319 line, _ := p.s.r.ReadString('\n')
320 c.Literal += line
321 return c, err
322 }
323 if fee > common.MAXFEEPLAN-1 {
324 line, _ := p.s.r.ReadString('\n')
325 c.Literal += line
326 return c, fmt.Errorf("Fee %d can not be bigger than 255", fee)
327 }
328 c.Fee = uint8(fee)
329 }
330
331 if tok == EOF {
332 return nil, errof
333 }
334 return c, nil
335}
336
337func idxTokenIDToString(idx string, tid common.TokenID) string {
338 return idx + strconv.Itoa(int(tid))
339}
340
341// Parse parses through reader
342func (p *Parser) Parse() (Instructions, error) {
343 var instructions Instructions
344 i := 0
345 accounts := make(map[string]bool)
346 tokenids := make(map[common.TokenID]bool)
347 for {
348 instruction, err := p.parseLine()
349 if err == errof {
350 break
351 }
352 if err == errComment {
353 i++
354 continue
355 }
356 if err == errNewBatch {
357 i++
358 inst := &Instruction{Type: TypeNewBatch}
359 instructions.Instructions = append(instructions.Instructions, inst)
360 continue
361 }
362 if err != nil {
363 return instructions, fmt.Errorf("error parsing line %d: %s, err: %s", i, instruction.Literal, err.Error())
364 }
365 instructions.Instructions = append(instructions.Instructions, instruction)
366 accounts[idxTokenIDToString(instruction.From, instruction.TokenID)] = true
367 if instruction.Type == common.TxTypeTransfer { // type: Transfer
368 accounts[idxTokenIDToString(instruction.To, instruction.TokenID)] = true
369 }
370 tokenids[instruction.TokenID] = true
371 i++
372 }
373 for a := range accounts {
374 instructions.Accounts = append(instructions.Accounts, a)
375 }
376 sort.Strings(instructions.Accounts)
377 for tid := range tokenids {
378 instructions.TokenIDs = append(instructions.TokenIDs, tid)
379 }
380 return instructions, nil
381}