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