You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

381 lines
8.1 KiB

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