main.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. package xtool
  2. import (
  3. "bufio"
  4. "bytes"
  5. //"flag"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "os"
  10. "path/filepath"
  11. "strings"
  12. "github.com/d5/tengo/v2"
  13. "github.com/d5/tengo/v2/parser"
  14. )
  15. import "surdeus.su/core/cli/mtool"
  16. import "surdeus.su/core/xgo/xmodules"
  17. const (
  18. sourceFileExt = ".xgo"
  19. sourceFileExtMain = ".tengo"
  20. replPrompt = ">> "
  21. )
  22. var (
  23. compileOutput string
  24. showHelp bool
  25. showVersion bool
  26. resolvePath bool // TODO Remove this flag at version 3
  27. version = "dev"
  28. iargs []string
  29. )
  30. var Tool = mtool.T("xgo").Func(Run)
  31. func Run(flags *mtool.Flags) {
  32. flags.BoolVar(&showHelp, "help", false, "Show help")
  33. flags.StringVar(&compileOutput, "o", "",
  34. "Compile output file")
  35. flags.BoolVar(&showVersion, "version", false, "Show version")
  36. flags.BoolVar(&resolvePath, "resolve", false,
  37. "Resolve relative import paths")
  38. iargs := flags.Parse()
  39. if showHelp {
  40. doHelp()
  41. os.Exit(2)
  42. } else if showVersion {
  43. fmt.Println(version)
  44. return
  45. }
  46. modules := xmodules.GetModules()
  47. if len(iargs) == 0 {
  48. // REPL
  49. RunREPL(modules, os.Stdin, os.Stdout)
  50. return
  51. }
  52. inputFile := iargs[0]
  53. inputData, err := ioutil.ReadFile(inputFile)
  54. if err != nil {
  55. _, _ = fmt.Fprintf(os.Stderr,
  56. "Error reading input file: %s\n", err.Error())
  57. os.Exit(1)
  58. }
  59. inputFile, err = filepath.Abs(inputFile)
  60. if err != nil {
  61. _, _ = fmt.Fprintf(os.Stderr, "Error file path: %s\n", err)
  62. os.Exit(1)
  63. }
  64. if len(inputData) > 1 && string(inputData[:2]) == "#!" {
  65. copy(inputData, "//")
  66. }
  67. if compileOutput != "" {
  68. err := CompileOnly(modules, inputData, inputFile,
  69. compileOutput)
  70. if err != nil {
  71. _, _ = fmt.Fprintln(os.Stderr, err.Error())
  72. os.Exit(1)
  73. }
  74. } else if ext := filepath.Ext(inputFile)
  75. ext == sourceFileExt || ext == sourceFileExtMain {
  76. err := CompileAndRun(modules, inputData, inputFile)
  77. if err != nil {
  78. _, _ = fmt.Fprintln(os.Stderr, err.Error())
  79. os.Exit(1)
  80. }
  81. } else {
  82. if err := RunCompiled(modules, inputData); err != nil {
  83. _, _ = fmt.Fprintln(os.Stderr, err.Error())
  84. os.Exit(1)
  85. }
  86. }
  87. }
  88. // CompileOnly compiles the source code and writes the compiled binary into
  89. // outputFile.
  90. func CompileOnly(
  91. modules *tengo.ModuleMap,
  92. data []byte,
  93. inputFile, outputFile string,
  94. ) (err error) {
  95. bytecode, err := compileSrc(modules, data, inputFile)
  96. if err != nil {
  97. return
  98. }
  99. if outputFile == "" {
  100. outputFile = basename(inputFile) + ".out"
  101. }
  102. out, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY, os.ModePerm)
  103. if err != nil {
  104. return
  105. }
  106. defer func() {
  107. if err != nil {
  108. _ = out.Close()
  109. } else {
  110. err = out.Close()
  111. }
  112. }()
  113. err = bytecode.Encode(out)
  114. if err != nil {
  115. return
  116. }
  117. fmt.Println(outputFile)
  118. return
  119. }
  120. // CompileAndRun compiles the source code and executes it.
  121. func CompileAndRun(
  122. modules *tengo.ModuleMap,
  123. data []byte,
  124. inputFile string,
  125. ) (err error) {
  126. bytecode, err := compileSrc(modules, data, inputFile)
  127. if err != nil {
  128. return
  129. }
  130. machine := tengo.NewVM(bytecode, nil, -1)
  131. err = machine.Run()
  132. return
  133. }
  134. // RunCompiled reads the compiled binary from file and executes it.
  135. func RunCompiled(modules *tengo.ModuleMap, data []byte) (err error) {
  136. bytecode := &tengo.Bytecode{}
  137. err = bytecode.Decode(bytes.NewReader(data), modules)
  138. if err != nil {
  139. return
  140. }
  141. machine := tengo.NewVM(bytecode, nil, -1)
  142. err = machine.Run()
  143. return
  144. }
  145. // RunREPL starts REPL.
  146. func RunREPL(modules *tengo.ModuleMap, in io.Reader, out io.Writer) {
  147. stdin := bufio.NewScanner(in)
  148. fileSet := parser.NewFileSet()
  149. globals := make([]tengo.Object, tengo.GlobalsSize)
  150. symbolTable := tengo.NewSymbolTable()
  151. for idx, fn := range tengo.GetAllBuiltinFunctions() {
  152. symbolTable.DefineBuiltin(idx, fn.Name)
  153. }
  154. // embed println function
  155. symbol := symbolTable.Define("__repl_println__")
  156. globals[symbol.Index] = &tengo.UserFunction{
  157. Name: "println",
  158. Value: func(args ...tengo.Object) (ret tengo.Object, err error) {
  159. var printArgs []interface{}
  160. for _, arg := range args {
  161. if _, isUndefined := arg.(*tengo.Undefined); isUndefined {
  162. printArgs = append(printArgs, "<undefined>")
  163. } else {
  164. s, _ := tengo.ToString(arg)
  165. printArgs = append(printArgs, s)
  166. }
  167. }
  168. printArgs = append(printArgs, "\n")
  169. _, _ = fmt.Print(printArgs...)
  170. return
  171. },
  172. }
  173. var constants []tengo.Object
  174. for {
  175. _, _ = fmt.Fprint(out, replPrompt)
  176. scanned := stdin.Scan()
  177. if !scanned {
  178. return
  179. }
  180. line := stdin.Text()
  181. srcFile := fileSet.AddFile("repl", -1, len(line))
  182. p := parser.NewParser(srcFile, []byte(line), nil)
  183. file, err := p.ParseFile()
  184. if err != nil {
  185. _, _ = fmt.Fprintln(out, err.Error())
  186. continue
  187. }
  188. file = addPrints(file)
  189. c := tengo.NewCompiler(
  190. srcFile,
  191. symbolTable,
  192. constants,
  193. modules,
  194. nil,
  195. )
  196. c.SetImportFileExt(sourceFileExt, sourceFileExtMain)
  197. if err := c.Compile(file); err != nil {
  198. _, _ = fmt.Fprintln(out, err.Error())
  199. continue
  200. }
  201. bytecode := c.Bytecode()
  202. machine := tengo.NewVM(bytecode, globals, -1)
  203. if err := machine.Run(); err != nil {
  204. _, _ = fmt.Fprintln(out, err.Error())
  205. continue
  206. }
  207. constants = bytecode.Constants
  208. }
  209. }
  210. func compileSrc(
  211. modules *tengo.ModuleMap,
  212. src []byte,
  213. inputFile string,
  214. ) (*tengo.Bytecode, error) {
  215. fileSet := parser.NewFileSet()
  216. srcFile := fileSet.AddFile(filepath.Base(inputFile), -1, len(src))
  217. p := parser.NewParser(srcFile, src, nil)
  218. file, err := p.ParseFile()
  219. if err != nil {
  220. return nil, err
  221. }
  222. c := tengo.NewCompiler(srcFile, nil, nil, modules, nil)
  223. c.SetImportFileExt(sourceFileExt)
  224. c.EnableFileImport(true)
  225. if resolvePath {
  226. c.SetImportDir(filepath.Dir(inputFile))
  227. }
  228. if err := c.Compile(file); err != nil {
  229. return nil, err
  230. }
  231. bytecode := c.Bytecode()
  232. bytecode.RemoveDuplicates()
  233. return bytecode, nil
  234. }
  235. func doHelp() {
  236. fmt.Println("Usage:")
  237. fmt.Println()
  238. fmt.Println(" tengo [flags] {input-file}")
  239. fmt.Println()
  240. fmt.Println("Flags:")
  241. fmt.Println()
  242. fmt.Println(" -o compile output file")
  243. fmt.Println(" -version show version")
  244. fmt.Println()
  245. fmt.Println("Examples:")
  246. fmt.Println()
  247. fmt.Println(" tengo")
  248. fmt.Println()
  249. fmt.Println(" Start Tengo REPL")
  250. fmt.Println()
  251. fmt.Println(" tengo myapp.tengo")
  252. fmt.Println()
  253. fmt.Println(" Compile and run source file (myapp.tengo)")
  254. fmt.Println(" Source file must have .tengo extension")
  255. fmt.Println()
  256. fmt.Println(" tengo -o myapp myapp.tengo")
  257. fmt.Println()
  258. fmt.Println(" Compile source file (myapp.tengo) into bytecode file (myapp)")
  259. fmt.Println()
  260. fmt.Println(" tengo myapp")
  261. fmt.Println()
  262. fmt.Println(" Run bytecode file (myapp)")
  263. fmt.Println()
  264. fmt.Println()
  265. }
  266. func addPrints(file *parser.File) *parser.File {
  267. var stmts []parser.Stmt
  268. for _, s := range file.Stmts {
  269. switch s := s.(type) {
  270. case *parser.ExprStmt:
  271. stmts = append(stmts, &parser.ExprStmt{
  272. Expr: &parser.CallExpr{
  273. Func: &parser.Ident{Name: "__repl_println__"},
  274. Args: []parser.Expr{s.Expr},
  275. },
  276. })
  277. case *parser.AssignStmt:
  278. stmts = append(stmts, s)
  279. stmts = append(stmts, &parser.ExprStmt{
  280. Expr: &parser.CallExpr{
  281. Func: &parser.Ident{
  282. Name: "__repl_println__",
  283. },
  284. Args: s.LHS,
  285. },
  286. })
  287. default:
  288. stmts = append(stmts, s)
  289. }
  290. }
  291. return &parser.File{
  292. InputFile: file.InputFile,
  293. Stmts: stmts,
  294. }
  295. }
  296. func basename(s string) string {
  297. s = filepath.Base(s)
  298. n := strings.LastIndexByte(s, '.')
  299. if n > 0 {
  300. return s[:n]
  301. }
  302. return s
  303. }