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