arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/test/transakcio/lang.go
test/transakcio/lang.goRaw
  1package transakcio
  2
  3import (
  4	"bufio"
  5	"bytes"
  6	"fmt"
  7	"io"
  8	"log"
  9	"sort"
 10	"strconv"
 11
 12	"github.com/hermeznetwork/hermez-node/common"
 13)
 14
 15var eof = rune(0)
 16var errof = fmt.Errorf("eof in parseline")
 17var commentLine = fmt.Errorf("comment in parseline") //nolint:golint
 18var newEventLine = fmt.Errorf("newEventLine")        //nolint:golint
 19var setTypeLine = fmt.Errorf("setTypeLine")          //nolint:golint
 20
 21// setType defines the type of the set
 22type setType string
 23
 24// setTypeBlockchain defines the type 'Blockchain' of the set
 25var setTypeBlockchain = setType("Blockchain")
 26
 27// setTypePoolL2 defines the type 'PoolL2' of the set
 28var setTypePoolL2 = setType("PoolL2")
 29
 30// typeNewBatch is used for testing purposes only, and represents the
 31// common.TxType of a new batch
 32var typeNewBatch common.TxType = "TxTypeNewBatch"
 33
 34// typeNewBlock is used for testing purposes only, and represents the
 35// common.TxType of a new ethereum block
 36var typeNewBlock common.TxType = "TxTypeNewBlock"
 37
 38//nolint
 39const (
 40	ILLEGAL token = iota
 41	WS
 42	EOF
 43
 44	IDENT // val
 45)
 46
 47// instruction is the data structure that represents one line of code
 48type instruction struct {
 49	literal    string
 50	from       string
 51	to         string
 52	amount     uint64
 53	loadAmount uint64
 54	fee        uint8
 55	tokenID    common.TokenID
 56	typ        common.TxType // D: Deposit, T: Transfer, E: ForceExit
 57}
 58
 59// parsedSet contains the full Set of Instructions representing a full code
 60type parsedSet struct {
 61	// type         string
 62	instructions []instruction
 63	accounts     []string
 64	tokenIDs     []common.TokenID
 65}
 66
 67func (i instruction) String() string {
 68	buf := bytes.NewBufferString("")
 69	fmt.Fprintf(buf, "Type: %s, ", i.typ)
 70	fmt.Fprintf(buf, "From: %s, ", i.from)
 71	if i.typ == common.TxTypeTransfer ||
 72		i.typ == common.TxTypeDepositTransfer ||
 73		i.typ == common.TxTypeCreateAccountDepositTransfer {
 74		fmt.Fprintf(buf, "To: %s, ", i.to)
 75	}
 76
 77	if i.typ == common.TxTypeDeposit ||
 78		i.typ == common.TxTypeDepositTransfer ||
 79		i.typ == common.TxTypeCreateAccountDepositTransfer {
 80		fmt.Fprintf(buf, "LoadAmount: %d, ", i.loadAmount)
 81	}
 82	if i.typ != common.TxTypeDeposit {
 83		fmt.Fprintf(buf, "Amount: %d, ", i.amount)
 84	}
 85	if i.typ == common.TxTypeTransfer ||
 86		i.typ == common.TxTypeDepositTransfer ||
 87		i.typ == common.TxTypeCreateAccountDepositTransfer {
 88		fmt.Fprintf(buf, "Fee: %d, ", i.fee)
 89	}
 90	fmt.Fprintf(buf, "TokenID: %d\n", i.tokenID)
 91	return buf.String()
 92}
 93
 94// Raw returns a string with the raw representation of the Instruction
 95func (i instruction) raw() string {
 96	buf := bytes.NewBufferString("")
 97	fmt.Fprintf(buf, "%s", i.typ)
 98	fmt.Fprintf(buf, "(%d)", i.tokenID)
 99	fmt.Fprintf(buf, "%s", i.from)
100	if i.typ == common.TxTypeTransfer ||
101		i.typ == common.TxTypeDepositTransfer ||
102		i.typ == common.TxTypeCreateAccountDepositTransfer {
103		fmt.Fprintf(buf, "-%s", i.to)
104	}
105	fmt.Fprintf(buf, ":")
106	if i.typ == common.TxTypeDeposit ||
107		i.typ == common.TxTypeDepositTransfer ||
108		i.typ == common.TxTypeCreateAccountDepositTransfer {
109		fmt.Fprintf(buf, "%d", i.loadAmount)
110	}
111	if i.typ != common.TxTypeDeposit {
112		fmt.Fprintf(buf, "%d", i.amount)
113	}
114	if i.typ == common.TxTypeTransfer {
115		fmt.Fprintf(buf, "(%d)", i.fee)
116	}
117	return buf.String()
118}
119
120type token int
121
122type scanner struct {
123	r *bufio.Reader
124}
125
126func isWhitespace(ch rune) bool {
127	return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\v' || ch == '\f'
128}
129
130func isLetter(ch rune) bool {
131	return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
132}
133
134func isComment(ch rune) bool {
135	return ch == '/'
136}
137
138func isDigit(ch rune) bool {
139	return (ch >= '0' && ch <= '9')
140}
141
142// newScanner creates a new scanner with the given io.Reader
143func newScanner(r io.Reader) *scanner {
144	return &scanner{r: bufio.NewReader(r)}
145}
146
147func (s *scanner) read() rune {
148	ch, _, err := s.r.ReadRune()
149	if err != nil {
150		return eof
151	}
152	return ch
153}
154
155func (s *scanner) unread() {
156	_ = s.r.UnreadRune()
157}
158
159// scan returns the token and literal string of the current value
160func (s *scanner) scan() (tok token, lit string) {
161	ch := s.read()
162
163	if isWhitespace(ch) {
164		// space
165		s.unread()
166		return s.scanWhitespace()
167	} else if isLetter(ch) || isDigit(ch) {
168		// letter/digit
169		s.unread()
170		return s.scanIndent()
171	} else if isComment(ch) {
172		// comment
173		s.unread()
174		return s.scanIndent()
175	}
176
177	if ch == eof {
178		return EOF, ""
179	}
180
181	return ILLEGAL, string(ch)
182}
183
184func (s *scanner) scanWhitespace() (token token, lit string) {
185	var buf bytes.Buffer
186	buf.WriteRune(s.read())
187
188	for {
189		if ch := s.read(); ch == eof {
190			break
191		} else if !isWhitespace(ch) {
192			s.unread()
193			break
194		} else {
195			_, _ = buf.WriteRune(ch)
196		}
197	}
198	return WS, buf.String()
199}
200
201func (s *scanner) scanIndent() (tok token, lit string) {
202	var buf bytes.Buffer
203	buf.WriteRune(s.read())
204
205	for {
206		if ch := s.read(); ch == eof {
207			break
208		} else if !isLetter(ch) && !isDigit(ch) {
209			s.unread()
210			break
211		} else {
212			_, _ = buf.WriteRune(ch)
213		}
214	}
215
216	if len(buf.String()) == 1 {
217		return token(rune(buf.String()[0])), buf.String()
218	}
219	return IDENT, buf.String()
220}
221
222// parser defines the parser
223type parser struct {
224	s   *scanner
225	buf struct {
226		tok token
227		lit string
228		n   int
229	}
230}
231
232// newParser creates a new parser from a io.Reader
233func newParser(r io.Reader) *parser {
234	return &parser{s: newScanner(r)}
235}
236
237func (p *parser) scan() (tok token, lit string) {
238	// if there is a token in the buffer return it
239	if p.buf.n != 0 {
240		p.buf.n = 0
241		return p.buf.tok, p.buf.lit
242	}
243	tok, lit = p.s.scan()
244
245	p.buf.tok, p.buf.lit = tok, lit
246
247	return
248}
249
250func (p *parser) scanIgnoreWhitespace() (tok token, lit string) {
251	tok, lit = p.scan()
252	if tok == WS {
253		tok, lit = p.scan()
254	}
255	return
256}
257
258// parseLine parses the current line
259func (p *parser) parseLine(setType setType) (*instruction, error) {
260	c := &instruction{}
261	tok, lit := p.scanIgnoreWhitespace()
262	if tok == EOF {
263		return nil, errof
264	}
265	c.literal += lit
266	if lit == "/" {
267		_, _ = p.s.r.ReadString('\n')
268		return nil, commentLine
269	} else if lit == ">" {
270		if setType == setTypePoolL2 {
271			return c, fmt.Errorf("Unexpected '>' at PoolL2Txs set")
272		}
273		_, lit = p.scanIgnoreWhitespace()
274		if lit == "batch" {
275			_, _ = p.s.r.ReadString('\n')
276			return &instruction{typ: typeNewBatch}, newEventLine
277		} else if lit == "block" {
278			_, _ = p.s.r.ReadString('\n')
279			return &instruction{typ: typeNewBlock}, newEventLine
280		} else {
281			return c, fmt.Errorf("Unexpected '> %s', expected '> batch' or '> block'", lit)
282		}
283	} else if lit == "Type" {
284		if err := p.expectChar(c, ":"); err != nil {
285			return c, err
286		}
287		_, lit = p.scanIgnoreWhitespace()
288		if lit == "Blockchain" {
289			return &instruction{typ: "Blockchain"}, setTypeLine
290		} else if lit == "PoolL2" {
291			return &instruction{typ: "PoolL2"}, setTypeLine
292		} else {
293			return c, fmt.Errorf("Invalid set type: '%s'. Valid set types: 'Blockchain', 'PoolL2'", lit)
294		}
295	}
296	if setType == "" {
297		return c, fmt.Errorf("Set type not defined")
298	}
299	transferring := false
300	switch lit {
301	case "Deposit":
302		if setType != setTypeBlockchain {
303			return c, fmt.Errorf("Unexpected '%s' in a non %s set", lit, setTypeBlockchain)
304		}
305		c.typ = common.TxTypeDeposit
306	case "Exit":
307		if setType != setTypeBlockchain {
308			return c, fmt.Errorf("Unexpected '%s' in a non %s set", lit, setTypeBlockchain)
309		}
310		c.typ = common.TxTypeExit
311	case "PoolExit":
312		if setType != setTypePoolL2 {
313			return c, fmt.Errorf("Unexpected '%s' in a non %s set", lit, setTypePoolL2)
314		}
315		c.typ = common.TxTypeExit
316	case "Transfer":
317		if setType != setTypeBlockchain {
318			return c, fmt.Errorf("Unexpected '%s' in a non %s set", lit, setTypeBlockchain)
319		}
320		c.typ = common.TxTypeTransfer
321		transferring = true
322	case "PoolTransfer":
323		if setType != setTypePoolL2 {
324			return c, fmt.Errorf("Unexpected '%s' in a non %s set", lit, setTypePoolL2)
325		}
326		c.typ = common.TxTypeTransfer
327		transferring = true
328	case "CreateAccountDeposit":
329		if setType != setTypeBlockchain {
330			return c, fmt.Errorf("Unexpected '%s' in a non %s set", lit, setTypeBlockchain)
331		}
332		c.typ = common.TxTypeCreateAccountDeposit
333	case "CreateAccountDepositTransfer":
334		if setType != setTypeBlockchain {
335			return c, fmt.Errorf("Unexpected '%s' in a non %s set", lit, setTypeBlockchain)
336		}
337		c.typ = common.TxTypeCreateAccountDepositTransfer
338		transferring = true
339	case "DepositTransfer":
340		if setType != setTypeBlockchain {
341			return c, fmt.Errorf("Unexpected '%s' in a non %s set", lit, setTypeBlockchain)
342		}
343		c.typ = common.TxTypeDepositTransfer
344		transferring = true
345	case "ForceTransfer":
346		if setType != setTypeBlockchain {
347			return c, fmt.Errorf("Unexpected '%s' in a non %s set", lit, setTypeBlockchain)
348		}
349		c.typ = common.TxTypeForceTransfer
350	case "ForceExit":
351		if setType != setTypeBlockchain {
352			return c, fmt.Errorf("Unexpected '%s' in a non %s set", lit, setTypeBlockchain)
353		}
354		c.typ = common.TxTypeForceExit
355	default:
356		return c, fmt.Errorf("Unexpected tx type: %s", lit)
357	}
358
359	if err := p.expectChar(c, "("); err != nil {
360		return c, err
361	}
362	_, lit = p.scanIgnoreWhitespace()
363	c.literal += lit
364	tidI, err := strconv.Atoi(lit)
365	if err != nil {
366		line, _ := p.s.r.ReadString('\n')
367		c.literal += line
368		return c, err
369	}
370	c.tokenID = common.TokenID(tidI)
371	if err := p.expectChar(c, ")"); err != nil {
372		return c, err
373	}
374	_, lit = p.scanIgnoreWhitespace()
375	c.literal += lit
376	c.from = lit
377	_, lit = p.scanIgnoreWhitespace()
378	c.literal += lit
379	if transferring {
380		if lit != "-" {
381			return c, fmt.Errorf("Expected '-', found '%s'", lit)
382		}
383		_, lit = p.scanIgnoreWhitespace()
384		c.literal += lit
385		c.to = lit
386		_, lit = p.scanIgnoreWhitespace()
387		c.literal += lit
388	}
389	if lit != ":" {
390		line, _ := p.s.r.ReadString('\n')
391		c.literal += line
392		return c, fmt.Errorf("Expected ':', found '%s'", lit)
393	}
394	if c.typ == common.TxTypeDepositTransfer ||
395		c.typ == common.TxTypeCreateAccountDepositTransfer {
396		// deposit case
397		_, lit = p.scanIgnoreWhitespace()
398		c.literal += lit
399		loadAmount, err := strconv.Atoi(lit)
400		if err != nil {
401			line, _ := p.s.r.ReadString('\n')
402			c.literal += line
403			return c, err
404		}
405		c.loadAmount = uint64(loadAmount)
406		if err := p.expectChar(c, ","); err != nil {
407			return c, err
408		}
409	}
410	_, lit = p.scanIgnoreWhitespace()
411	c.literal += lit
412	amount, err := strconv.Atoi(lit)
413	if err != nil {
414		line, _ := p.s.r.ReadString('\n')
415		c.literal += line
416		return c, err
417	}
418	if c.typ == common.TxTypeDeposit ||
419		c.typ == common.TxTypeCreateAccountDeposit {
420		c.loadAmount = uint64(amount)
421	} else {
422		c.amount = uint64(amount)
423	}
424	if transferring {
425		if err := p.expectChar(c, "("); err != nil {
426			return c, err
427		}
428		_, lit = p.scanIgnoreWhitespace()
429		c.literal += lit
430		fee, err := strconv.Atoi(lit)
431		if err != nil {
432			line, _ := p.s.r.ReadString('\n')
433			c.literal += line
434			return c, err
435		}
436		if fee > common.MaxFeePlan-1 {
437			line, _ := p.s.r.ReadString('\n')
438			c.literal += line
439			return c, fmt.Errorf("Fee %d can not be bigger than 255", fee)
440		}
441		c.fee = uint8(fee)
442
443		if err := p.expectChar(c, ")"); err != nil {
444			return c, err
445		}
446	}
447
448	if tok == EOF {
449		return nil, errof
450	}
451	return c, nil
452}
453
454func (p *parser) expectChar(c *instruction, ch string) error {
455	_, lit := p.scanIgnoreWhitespace()
456	c.literal += lit
457	if lit != ch {
458		line, _ := p.s.r.ReadString('\n')
459		c.literal += line
460		return fmt.Errorf("Expected '%s', found '%s'", ch, lit)
461	}
462	return nil
463}
464
465func idxTokenIDToString(idx string, tid common.TokenID) string {
466	return idx + strconv.Itoa(int(tid))
467}
468
469// parse parses through reader
470func (p *parser) parse() (*parsedSet, error) {
471	instructions := &parsedSet{}
472	i := 0
473	accounts := make(map[string]bool)
474	tokenids := make(map[common.TokenID]bool)
475	var setTypeOfSet setType
476	for {
477		instruction, err := p.parseLine(setTypeOfSet)
478		if err == errof {
479			break
480		}
481		if err == setTypeLine {
482			if instruction.typ == "PoolL2" {
483				setTypeOfSet = setTypePoolL2
484			} else if instruction.typ == "Blockchain" {
485				setTypeOfSet = setTypeBlockchain
486			} else {
487				log.Fatalf("Invalid set type: '%s'. Valid set types: 'Blockchain', 'PoolL2'", instruction.typ)
488			}
489			i++
490			continue
491		}
492		if err == commentLine {
493			i++
494			continue
495		}
496		if err == newEventLine {
497			i++
498			instructions.instructions = append(instructions.instructions, *instruction)
499			continue
500		}
501		if err != nil {
502			return instructions, fmt.Errorf("error parsing line %d: %s, err: %s", i, instruction.literal, err.Error())
503		}
504		if setTypeOfSet == "" {
505			return instructions, fmt.Errorf("Set type not defined")
506		}
507		instructions.instructions = append(instructions.instructions, *instruction)
508		accounts[instruction.from] = true
509		if instruction.typ == common.TxTypeTransfer { // type: Transfer
510			accounts[instruction.to] = true
511		}
512		tokenids[instruction.tokenID] = true
513		i++
514	}
515	for a := range accounts {
516		instructions.accounts = append(instructions.accounts, a)
517	}
518	sort.Strings(instructions.accounts)
519	for tid := range tokenids {
520		instructions.tokenIDs = append(instructions.tokenIDs, tid)
521	}
522	return instructions, nil
523}