parser.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. package main
  2. import (
  3. "bufio"
  4. "encoding/json"
  5. "fmt"
  6. "os"
  7. "strings"
  8. "github.com/beanzilla/gonode"
  9. )
  10. // A Parser for handling Endless Sky save format
  11. type Parser struct {
  12. Debug bool // Do we print debug info (to logs)
  13. DebugJSON string // Do we json output a debug file (this is it's name, leave blank for disabled)
  14. }
  15. // Parses a given file returning a tree of Nodes
  16. func (p *Parser) Load(filename string) (Root *gonode.Node, err error) {
  17. Root = gonode.NewNode()
  18. Root.SetData(filename)
  19. file, err := os.Open(filename)
  20. if err != nil {
  21. return
  22. }
  23. defer file.Close()
  24. radar := bufio.NewScanner(file)
  25. content := []string{}
  26. for radar.Scan() {
  27. line := radar.Text()
  28. line = strings.ReplaceAll(line, "\n", "")
  29. content = append(content, line)
  30. }
  31. p.parse(&content, 0, Root)
  32. if p.DebugJSON != "" {
  33. pay, err := json.MarshalIndent(Root, "", " ")
  34. if err != nil {
  35. return Root, err
  36. }
  37. err = os.WriteFile(p.DebugJSON, pay, 0660)
  38. if err != nil {
  39. return Root, err
  40. }
  41. }
  42. return
  43. }
  44. // Assistant func
  45. //
  46. // Processes the current line and next line for making the node tree/structure
  47. func (p *Parser) parse(file *[]string, linenum int, node *gonode.Node) {
  48. if linenum+1 > len(*file) {
  49. if p.Debug {
  50. fmt.Println("EOF")
  51. }
  52. return
  53. }
  54. line := (*file)[linenum]
  55. var next_line string
  56. if linenum+2 <= len(*file) {
  57. next_line = (*file)[linenum+1]
  58. } else {
  59. next_line = ""
  60. }
  61. next_depth := FindDepth(next_line)
  62. if p.Debug {
  63. fmt.Printf("%4d | '%s'\nNXTL | '%s'\n", linenum, line, next_line)
  64. }
  65. kid := node.NewChild()
  66. depth := FindDepth(line)
  67. kid.SetData(CleanLine(line))
  68. if next_depth == depth {
  69. if p.Debug {
  70. fmt.Printf("Same Depth (%d)\n", depth)
  71. }
  72. p.parse(file, linenum+1, node)
  73. } else if next_depth > depth {
  74. if p.Debug {
  75. fmt.Printf("Deeper (%d->%d)\n", depth, next_depth)
  76. }
  77. p.parse(file, linenum+1, kid)
  78. } else if next_depth < depth {
  79. diff := depth - next_depth
  80. at := node
  81. if p.Debug {
  82. fmt.Printf("Surface (%d<-%d) diff=%d\n", depth, next_depth, diff)
  83. }
  84. for up := 0; up < diff; up++ {
  85. if at.Parent() != nil {
  86. at = at.Parent()
  87. }
  88. }
  89. p.parse(file, linenum+1, at)
  90. }
  91. }
  92. // Assistant func
  93. //
  94. // # Supports writing the current node's line, and the node's children too
  95. //
  96. // This uses recursive calls to write out the children
  97. func (p *Parser) saveassist(file *os.File, node *gonode.Node) error {
  98. if p.Debug {
  99. fmt.Printf("%s%v\n", strings.Repeat("\t", node.Depth()), node.Data())
  100. }
  101. _, err := file.WriteString(strings.Repeat("\t", node.Depth()) + fmt.Sprintf("%v\n", node.Data()))
  102. if err != nil {
  103. return err
  104. }
  105. if node.Len() != 0 {
  106. for kid := range node.Iter() {
  107. err := p.saveassist(file, kid)
  108. if err != nil {
  109. return err
  110. }
  111. }
  112. }
  113. return nil
  114. }
  115. // Saves the node and it's children to the given filename
  116. func (p *Parser) Save(filename string, Root *gonode.Node) error {
  117. file, err := os.OpenFile(filename, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0660)
  118. if err != nil {
  119. return err
  120. }
  121. defer file.Close()
  122. for kid := range Root.Iter() {
  123. err = p.saveassist(file, kid)
  124. if err != nil {
  125. return err
  126. }
  127. }
  128. return nil
  129. }
  130. // TODO: Move these Util funcs to utils.go (update tests)
  131. // Util func
  132. //
  133. // Finds how many tabs deep the line is
  134. func FindDepth(line string) int {
  135. if strings.HasPrefix(line, "\t") {
  136. depth := 0
  137. for _, c := range line {
  138. if c != '\t' {
  139. break
  140. }
  141. depth += 1
  142. }
  143. return depth
  144. }
  145. return 0
  146. }
  147. // Util func
  148. //
  149. // # Cleans the starting tabs from the line
  150. //
  151. // Utilizes FindDepth to chop off that many characters in the beginning of the line
  152. func CleanLine(line string) string {
  153. if strings.HasPrefix(line, "\t") {
  154. depth := FindDepth(line)
  155. return line[depth:]
  156. }
  157. return line
  158. }