arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
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}