main.go 7.0 KB

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