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