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