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