interpreter.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. /*
  2. * Brawler
  3. *
  4. * Copyright 2019 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the MIT
  7. * License, If a copy of the MIT License was not distributed with this
  8. * file, You can obtain one at https://opensource.org/licenses/MIT.
  9. */
  10. package main
  11. import (
  12. "flag"
  13. "fmt"
  14. "io"
  15. "io/ioutil"
  16. "os"
  17. "path/filepath"
  18. "strings"
  19. "devt.de/krotik/brawler/api"
  20. "devt.de/krotik/brawler/engine"
  21. "devt.de/krotik/brawler/rumble"
  22. ri "devt.de/krotik/brawler/rumble/interpreter"
  23. "devt.de/krotik/common/fileutil"
  24. "devt.de/krotik/common/termutil"
  25. )
  26. /*
  27. interpreter start the Brawler code interpreter.
  28. */
  29. func interpreter(interactive bool) error {
  30. var err error
  31. ifile := flag.String("file", "", "Interpret a single file")
  32. idir := flag.String("dir", "", "Interpret all .rum files in a given directory")
  33. ilogFile := flag.String("logfile", "", "Log to a file")
  34. ilogLevel := flag.String("loglevel", "Info", "Logging level (Debug, Info, Warning, Error, Fatal)")
  35. showHelp := flag.Bool("help", false, "Show this help message")
  36. flag.Usage = func() {
  37. fmt.Println()
  38. if !interactive {
  39. fmt.Println(fmt.Sprintf("Usage of %s run [options]", os.Args[0]))
  40. } else {
  41. fmt.Println(fmt.Sprintf("Usage of %s [options]", os.Args[0]))
  42. }
  43. fmt.Println()
  44. flag.PrintDefaults()
  45. fmt.Println()
  46. }
  47. if len(os.Args) > 2 {
  48. flag.CommandLine.Parse(os.Args[2:])
  49. if *showHelp {
  50. flag.Usage()
  51. return nil
  52. }
  53. }
  54. var clt termutil.ConsoleLineTerminal
  55. var logLevel rumble.LogLevel
  56. clt, err = termutil.NewConsoleLineTerminal(os.Stdout)
  57. if err == nil {
  58. var ok bool
  59. // Determine log level
  60. logLevel, ok = rumble.StringToLoglevel[*ilogLevel]
  61. if !ok {
  62. err = fmt.Errorf("Unknown log level: %v", *ilogLevel)
  63. }
  64. }
  65. if err == nil {
  66. var logWriter = io.Writer(clt)
  67. // Set the logging level
  68. api.DefaultLogLevel = logLevel
  69. // Check if we should log to a file
  70. if ilogFile != nil && *ilogFile != "" {
  71. logFileRollover := fileutil.SizeBasedRolloverCondition(1000000) // Each file can be up to a megabyte
  72. logWriter, err = fileutil.NewMultiFileBuffer(*ilogFile, fileutil.ConsecutiveNumberIterator(10), logFileRollover)
  73. }
  74. if err == nil {
  75. api.NewLogWriter = func(engine string) api.LogWriter {
  76. return &ConsoleLogWriter{clt, logWriter}
  77. }
  78. // Create the engine
  79. api.GetEngine(DefaultEngineName, true)
  80. }
  81. }
  82. // Load file or directory
  83. if err == nil && ifile != nil && *ifile != "" {
  84. var code []byte
  85. if code, err = ioutil.ReadFile(*ifile); err == nil {
  86. _, err = api.RunRumbleCode(DefaultEngineName, filepath.Base(*ifile), string(code), nil)
  87. }
  88. if err != nil {
  89. fmt.Println(err.Error())
  90. }
  91. }
  92. if err == nil && idir != nil && *idir != "" {
  93. err = filepath.Walk(*idir, func(path string, info os.FileInfo, err error) error {
  94. if !info.IsDir() && strings.HasSuffix(info.Name(), ".rum") {
  95. var code []byte
  96. var err error
  97. if code, err = ioutil.ReadFile(path); err == nil {
  98. _, err = api.RunRumbleCode(DefaultEngineName, info.Name(), string(code), nil)
  99. }
  100. if err != nil {
  101. fmt.Println(err.Error())
  102. }
  103. }
  104. return nil
  105. })
  106. }
  107. // Run the terminal
  108. if err == nil && interactive {
  109. if err == nil {
  110. isExitLine := func(s string) bool {
  111. return s == "exit" || s == "q" || s == "quit" || s == "bye" || s == "\x04"
  112. }
  113. // Add history functionality
  114. clt, err = termutil.AddHistoryMixin(clt, "",
  115. func(s string) bool {
  116. return isExitLine(s)
  117. })
  118. if err == nil {
  119. if err = clt.StartTerm(); err == nil {
  120. var line string
  121. defer clt.StopTerm()
  122. fmt.Println("Type 'q' or 'quit' to exit the shell and '?' to get help")
  123. line, err = clt.NextLine()
  124. for err == nil && !isExitLine(line) {
  125. // Process the entered line
  126. if line == "?" {
  127. // Show help
  128. clt.WriteString(fmt.Sprintf("Brawler %v\n", engine.ProductVersion))
  129. clt.WriteString(fmt.Sprintf("\n"))
  130. clt.WriteString(fmt.Sprintf("Rumble console supports all normal rumble statements and\n"))
  131. clt.WriteString(fmt.Sprintf("the following special commands:\n"))
  132. clt.WriteString(fmt.Sprintf(" !reset - Reset the engine and reload all default sinks\n"))
  133. clt.WriteString(fmt.Sprintf(" !funcs - Print a list of all available Rumble functions.\n"))
  134. clt.WriteString(fmt.Sprintf("\n"))
  135. } else if line == "!funcs" {
  136. clt.WriteString(fmt.Sprintf("Available functions ...\n"))
  137. for _, f := range ri.RumbleFuncs() {
  138. clt.WriteString(fmt.Sprintf("@%s\n", f))
  139. }
  140. } else if line == "!reset" {
  141. clt.WriteString(fmt.Sprintf("Resetting engine ...\n"))
  142. api.GetEngine(DefaultEngineName, true)
  143. clt.WriteString(fmt.Sprintf("Done\n"))
  144. } else {
  145. // Execute the line as code
  146. if _, terr := api.RunRumbleCode(DefaultEngineName, "stdin", line, nil); terr != nil {
  147. clt.WriteString(fmt.Sprintln(terr.Error()))
  148. }
  149. }
  150. line, err = clt.NextLine()
  151. }
  152. }
  153. }
  154. }
  155. }
  156. return err
  157. }
  158. /*
  159. ConsoleLogWriter is a simple console logger.
  160. */
  161. type ConsoleLogWriter struct {
  162. ResultWriter io.Writer // Writer for result messages
  163. LogWriter io.Writer // Writer for log messages
  164. }
  165. /*
  166. Add adds a new log message.
  167. */
  168. func (cw *ConsoleLogWriter) Add(m *api.LogMessage) {
  169. if m.Level != api.Result {
  170. cw.LogWriter.Write([]byte(fmt.Sprintf("%v %v: %v\n", m.MonitorID, m.Level, m.Msg)))
  171. return
  172. }
  173. cw.ResultWriter.Write([]byte(m.Msg + "\n"))
  174. }
  175. /*
  176. Slice returns the contents of the current log as a slice.
  177. */
  178. func (cw *ConsoleLogWriter) Slice() []*api.LogMessage {
  179. return nil // NOP
  180. }
  181. /*
  182. Reset resets the current log.
  183. */
  184. func (cw *ConsoleLogWriter) Reset() {
  185. // NOP
  186. }
  187. /*
  188. Size returns the current log size.
  189. */
  190. func (cw *ConsoleLogWriter) Size() int {
  191. return 0 // NOP
  192. }
  193. /*
  194. String returns the current log as a string.
  195. */
  196. func (cw *ConsoleLogWriter) String() string {
  197. return "" // NOP
  198. }