interpret.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. /*
  2. * ECAL
  3. *
  4. * Copyright 2020 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 tool
  11. import (
  12. "flag"
  13. "fmt"
  14. "io"
  15. "io/ioutil"
  16. "os"
  17. "strings"
  18. "devt.de/krotik/common/fileutil"
  19. "devt.de/krotik/common/stringutil"
  20. "devt.de/krotik/common/termutil"
  21. "devt.de/krotik/ecal/config"
  22. "devt.de/krotik/ecal/interpreter"
  23. "devt.de/krotik/ecal/parser"
  24. "devt.de/krotik/ecal/scope"
  25. "devt.de/krotik/ecal/stdlib"
  26. "devt.de/krotik/ecal/util"
  27. )
  28. /*
  29. Interpret starts the ECAL code interpreter from a CLI application which
  30. calls the interpret function as a sub executable. Starts an interactive console
  31. if the interactive flag is set.
  32. */
  33. func Interpret(interactive bool) error {
  34. var err error
  35. wd, _ := os.Getwd()
  36. idir := flag.String("dir", wd, "Root directory for ECAL interpreter")
  37. ilogFile := flag.String("logfile", "", "Log to a file")
  38. ilogLevel := flag.String("loglevel", "Info", "Logging level (Debug, Info, Error)")
  39. showHelp := flag.Bool("help", false, "Show this help message")
  40. flag.Usage = func() {
  41. fmt.Println()
  42. if !interactive {
  43. fmt.Println(fmt.Sprintf("Usage of %s run [options] <file>", os.Args[0]))
  44. } else {
  45. fmt.Println(fmt.Sprintf("Usage of %s [options]", os.Args[0]))
  46. }
  47. fmt.Println()
  48. flag.PrintDefaults()
  49. fmt.Println()
  50. }
  51. if len(os.Args) > 2 {
  52. flag.CommandLine.Parse(os.Args[2:])
  53. if *showHelp {
  54. flag.Usage()
  55. return nil
  56. }
  57. }
  58. var clt termutil.ConsoleLineTerminal
  59. var logger util.Logger
  60. clt, err = termutil.NewConsoleLineTerminal(os.Stdout)
  61. if interactive {
  62. fmt.Println(fmt.Sprintf("ECAL %v", config.ProductVersion))
  63. }
  64. // Create the logger
  65. if err == nil {
  66. // Check if we should log to a file
  67. if ilogFile != nil && *ilogFile != "" {
  68. var logWriter io.Writer
  69. logFileRollover := fileutil.SizeBasedRolloverCondition(1000000) // Each file can be up to a megabyte
  70. logWriter, err = fileutil.NewMultiFileBuffer(*ilogFile, fileutil.ConsecutiveNumberIterator(10), logFileRollover)
  71. logger = util.NewBufferLogger(logWriter)
  72. } else {
  73. // Log to the console by default
  74. logger = util.NewStdOutLogger()
  75. }
  76. // Set the log level
  77. if err == nil {
  78. if ilogLevel != nil && *ilogLevel != "" {
  79. if logger, err = util.NewLogLevelLogger(logger, *ilogLevel); err == nil && interactive {
  80. fmt.Print(fmt.Sprintf("Log level: %v - ", logger.(*util.LogLevelLogger).Level()))
  81. }
  82. }
  83. }
  84. }
  85. if err == nil {
  86. // Get the import locator
  87. if interactive {
  88. fmt.Println(fmt.Sprintf("Root directory: %v", *idir))
  89. }
  90. importLocator := &util.FileImportLocator{Root: *idir}
  91. name := "console"
  92. // Create interpreter
  93. erp := interpreter.NewECALRuntimeProvider(name, importLocator, logger)
  94. // Create global variable scope
  95. vs := scope.NewScope(scope.GlobalScope)
  96. // Execute file if given
  97. if cargs := flag.Args(); len(cargs) > 0 {
  98. var ast *parser.ASTNode
  99. var initFile []byte
  100. initFileName := flag.Arg(0)
  101. initFile, err = ioutil.ReadFile(initFileName)
  102. if ast, err = parser.ParseWithRuntime(initFileName, string(initFile), erp); err == nil {
  103. if err = ast.Runtime.Validate(); err == nil {
  104. _, err = ast.Runtime.Eval(vs, make(map[string]interface{}))
  105. }
  106. }
  107. }
  108. if err == nil {
  109. if interactive {
  110. // Drop into interactive shell
  111. if err == nil {
  112. isExitLine := func(s string) bool {
  113. return s == "exit" || s == "q" || s == "quit" || s == "bye" || s == "\x04"
  114. }
  115. // Add history functionality without file persistence
  116. clt, err = termutil.AddHistoryMixin(clt, "",
  117. func(s string) bool {
  118. return isExitLine(s)
  119. })
  120. if err == nil {
  121. if err = clt.StartTerm(); err == nil {
  122. var line string
  123. defer clt.StopTerm()
  124. fmt.Println("Type 'q' or 'quit' to exit the shell and '?' to get help")
  125. line, err = clt.NextLine()
  126. for err == nil && !isExitLine(line) {
  127. trimmedLine := strings.TrimSpace(line)
  128. // Process the entered line
  129. if line == "?" {
  130. // Show help
  131. clt.WriteString(fmt.Sprintf("ECAL %v\n", config.ProductVersion))
  132. clt.WriteString(fmt.Sprintf("\n"))
  133. clt.WriteString(fmt.Sprintf("Console supports all normal ECAL statements and the following special commands:\n"))
  134. clt.WriteString(fmt.Sprintf("\n"))
  135. clt.WriteString(fmt.Sprintf(" @sym [glob] - List all available inbuild functions and available stdlib packages of ECAL.\n"))
  136. clt.WriteString(fmt.Sprintf(" @std <package> [glob] - List all available constants and functions of a stdlib package.\n"))
  137. clt.WriteString(fmt.Sprintf("\n"))
  138. clt.WriteString(fmt.Sprintf("Add an argument after a list command to do a full text search. The search string should be in glob format.\n"))
  139. } else if strings.HasPrefix(trimmedLine, "@sym") {
  140. displaySymbols(clt, strings.Split(trimmedLine, " ")[1:])
  141. } else if strings.HasPrefix(trimmedLine, "@std") {
  142. displayPackage(clt, strings.Split(trimmedLine, " ")[1:])
  143. } else {
  144. var ierr error
  145. var ast *parser.ASTNode
  146. var res interface{}
  147. if ast, ierr = parser.ParseWithRuntime("console input", line, erp); ierr == nil {
  148. if ierr = ast.Runtime.Validate(); ierr == nil {
  149. if res, ierr = ast.Runtime.Eval(vs, make(map[string]interface{})); ierr == nil && res != nil {
  150. clt.WriteString(fmt.Sprintln(res))
  151. }
  152. }
  153. }
  154. if ierr != nil {
  155. clt.WriteString(fmt.Sprintln(ierr.Error()))
  156. }
  157. }
  158. line, err = clt.NextLine()
  159. }
  160. }
  161. }
  162. }
  163. }
  164. }
  165. }
  166. return err
  167. }
  168. /*
  169. displaySymbols lists all available inbuild functions and available stdlib packages of ECAL.
  170. */
  171. func displaySymbols(clt termutil.ConsoleLineTerminal, args []string) {
  172. tabData := []string{"Inbuild function", "Description"}
  173. for name, f := range interpreter.InbuildFuncMap {
  174. ds, _ := f.DocString()
  175. if len(args) > 0 && !matchesFulltextSearch(clt, fmt.Sprintf("%v %v", name, ds), args[0]) {
  176. continue
  177. }
  178. tabData = fillTableRow(tabData, name, ds)
  179. }
  180. if len(tabData) > 2 {
  181. clt.WriteString(stringutil.PrintGraphicStringTable(tabData, 2, 1,
  182. stringutil.SingleDoubleLineTable))
  183. }
  184. packageNames, _, _ := stdlib.GetStdlibSymbols()
  185. tabData = []string{"Package name", "Description"}
  186. for _, p := range packageNames {
  187. ps, _ := stdlib.GetPkgDocString(p)
  188. if len(args) > 0 && !matchesFulltextSearch(clt, fmt.Sprintf("%v %v", p, ps), args[0]) {
  189. continue
  190. }
  191. tabData = fillTableRow(tabData, p, ps)
  192. }
  193. if len(tabData) > 2 {
  194. clt.WriteString(stringutil.PrintGraphicStringTable(tabData, 2, 1,
  195. stringutil.SingleDoubleLineTable))
  196. }
  197. }
  198. /*
  199. displayPackage list all available constants and functions of a stdlib package.
  200. */
  201. func displayPackage(clt termutil.ConsoleLineTerminal, args []string) {
  202. _, constSymbols, funcSymbols := stdlib.GetStdlibSymbols()
  203. tabData := []string{"Constant", "Value"}
  204. for _, s := range constSymbols {
  205. if len(args) > 0 && !strings.HasPrefix(s, args[0]) {
  206. continue
  207. }
  208. val, _ := stdlib.GetStdlibConst(s)
  209. tabData = fillTableRow(tabData, s, fmt.Sprint(val))
  210. }
  211. if len(tabData) > 2 {
  212. clt.WriteString(stringutil.PrintGraphicStringTable(tabData, 2, 1,
  213. stringutil.SingleDoubleLineTable))
  214. }
  215. tabData = []string{"Function", "Description"}
  216. for _, f := range funcSymbols {
  217. if len(args) > 0 && !strings.HasPrefix(f, args[0]) {
  218. continue
  219. }
  220. fObj, _ := stdlib.GetStdlibFunc(f)
  221. fDoc, _ := fObj.DocString()
  222. fDoc = strings.Replace(fDoc, "\n", " ", -1)
  223. fDoc = strings.Replace(fDoc, "\t", " ", -1)
  224. if len(args) > 1 && !matchesFulltextSearch(clt, fmt.Sprintf("%v %v", f, fDoc), args[1]) {
  225. continue
  226. }
  227. tabData = fillTableRow(tabData, f, fDoc)
  228. }
  229. if len(tabData) > 2 {
  230. clt.WriteString(stringutil.PrintGraphicStringTable(tabData, 2, 1,
  231. stringutil.SingleDoubleLineTable))
  232. }
  233. }