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