123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- package xtool
- import (
- "bufio"
- "bytes"
- //"flag"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
- "github.com/d5/tengo/v2"
- "github.com/d5/tengo/v2/parser"
- )
- import "surdeus.su/core/cli/mtool"
- import "surdeus.su/core/xgo/xmodules"
- const (
- sourceFileExt = ".xgo"
- sourceFileExtMain = ".tengo"
- replPrompt = ">> "
- )
- var (
- compileOutput string
- showHelp bool
- showVersion bool
- resolvePath bool // TODO Remove this flag at version 3
- version = "dev"
- iargs []string
- )
- var Tool = mtool.T("xgo").Func(Run)
- func Run(flags *mtool.Flags) {
- flags.BoolVar(&showHelp, "help", false, "Show help")
- flags.StringVar(&compileOutput, "o", "",
- "Compile output file")
- flags.BoolVar(&showVersion, "version", false, "Show version")
- flags.BoolVar(&resolvePath, "resolve", false,
- "Resolve relative import paths")
- iargs := flags.Parse()
- if showHelp {
- doHelp()
- os.Exit(2)
- } else if showVersion {
- fmt.Println(version)
- return
- }
- modules := xmodules.GetModules()
- if len(iargs) == 0 {
- // REPL
- RunREPL(modules, os.Stdin, os.Stdout)
- return
- }
- inputFile := iargs[0]
- inputData, err := ioutil.ReadFile(inputFile)
- if err != nil {
- _, _ = fmt.Fprintf(os.Stderr,
- "Error reading input file: %s\n", err.Error())
- os.Exit(1)
- }
- inputFile, err = filepath.Abs(inputFile)
- if err != nil {
- _, _ = fmt.Fprintf(os.Stderr, "Error file path: %s\n", err)
- os.Exit(1)
- }
- if len(inputData) > 1 && string(inputData[:2]) == "#!" {
- copy(inputData, "//")
- }
- if compileOutput != "" {
- err := CompileOnly(modules, inputData, inputFile,
- compileOutput)
- if err != nil {
- _, _ = fmt.Fprintln(os.Stderr, err.Error())
- os.Exit(1)
- }
- } else if ext := filepath.Ext(inputFile)
- ext == sourceFileExt || ext == sourceFileExtMain {
- err := CompileAndRun(modules, inputData, inputFile)
- if err != nil {
- _, _ = fmt.Fprintln(os.Stderr, err.Error())
- os.Exit(1)
- }
- } else {
- if err := RunCompiled(modules, inputData); err != nil {
- _, _ = fmt.Fprintln(os.Stderr, err.Error())
- os.Exit(1)
- }
- }
- }
- // CompileOnly compiles the source code and writes the compiled binary into
- // outputFile.
- func CompileOnly(
- modules *tengo.ModuleMap,
- data []byte,
- inputFile, outputFile string,
- ) (err error) {
- bytecode, err := compileSrc(modules, data, inputFile)
- if err != nil {
- return
- }
- if outputFile == "" {
- outputFile = basename(inputFile) + ".out"
- }
- out, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY, os.ModePerm)
- if err != nil {
- return
- }
- defer func() {
- if err != nil {
- _ = out.Close()
- } else {
- err = out.Close()
- }
- }()
- err = bytecode.Encode(out)
- if err != nil {
- return
- }
- fmt.Println(outputFile)
- return
- }
- // CompileAndRun compiles the source code and executes it.
- func CompileAndRun(
- modules *tengo.ModuleMap,
- data []byte,
- inputFile string,
- ) (err error) {
- bytecode, err := compileSrc(modules, data, inputFile)
- if err != nil {
- return
- }
- machine := tengo.NewVM(bytecode, nil, -1)
- err = machine.Run()
- return
- }
- // RunCompiled reads the compiled binary from file and executes it.
- func RunCompiled(modules *tengo.ModuleMap, data []byte) (err error) {
- bytecode := &tengo.Bytecode{}
- err = bytecode.Decode(bytes.NewReader(data), modules)
- if err != nil {
- return
- }
- machine := tengo.NewVM(bytecode, nil, -1)
- err = machine.Run()
- return
- }
- // RunREPL starts REPL.
- func RunREPL(modules *tengo.ModuleMap, in io.Reader, out io.Writer) {
- stdin := bufio.NewScanner(in)
- fileSet := parser.NewFileSet()
- globals := make([]tengo.Object, tengo.GlobalsSize)
- symbolTable := tengo.NewSymbolTable()
- for idx, fn := range tengo.GetAllBuiltinFunctions() {
- symbolTable.DefineBuiltin(idx, fn.Name)
- }
- // embed println function
- symbol := symbolTable.Define("__repl_println__")
- globals[symbol.Index] = &tengo.UserFunction{
- Name: "println",
- Value: func(args ...tengo.Object) (ret tengo.Object, err error) {
- var printArgs []interface{}
- for _, arg := range args {
- if _, isUndefined := arg.(*tengo.Undefined); isUndefined {
- printArgs = append(printArgs, "<undefined>")
- } else {
- s, _ := tengo.ToString(arg)
- printArgs = append(printArgs, s)
- }
- }
- printArgs = append(printArgs, "\n")
- _, _ = fmt.Print(printArgs...)
- return
- },
- }
- var constants []tengo.Object
- for {
- _, _ = fmt.Fprint(out, replPrompt)
- scanned := stdin.Scan()
- if !scanned {
- return
- }
- line := stdin.Text()
- srcFile := fileSet.AddFile("repl", -1, len(line))
- p := parser.NewParser(srcFile, []byte(line), nil)
- file, err := p.ParseFile()
- if err != nil {
- _, _ = fmt.Fprintln(out, err.Error())
- continue
- }
- file = addPrints(file)
- c := tengo.NewCompiler(
- srcFile,
- symbolTable,
- constants,
- modules,
- nil,
- )
- c.SetImportFileExt(sourceFileExt, sourceFileExtMain)
- if err := c.Compile(file); err != nil {
- _, _ = fmt.Fprintln(out, err.Error())
- continue
- }
- bytecode := c.Bytecode()
- machine := tengo.NewVM(bytecode, globals, -1)
- if err := machine.Run(); err != nil {
- _, _ = fmt.Fprintln(out, err.Error())
- continue
- }
- constants = bytecode.Constants
- }
- }
- func compileSrc(
- modules *tengo.ModuleMap,
- src []byte,
- inputFile string,
- ) (*tengo.Bytecode, error) {
- fileSet := parser.NewFileSet()
- srcFile := fileSet.AddFile(filepath.Base(inputFile), -1, len(src))
- p := parser.NewParser(srcFile, src, nil)
- file, err := p.ParseFile()
- if err != nil {
- return nil, err
- }
- c := tengo.NewCompiler(srcFile, nil, nil, modules, nil)
- c.SetImportFileExt(sourceFileExt)
- c.EnableFileImport(true)
- if resolvePath {
- c.SetImportDir(filepath.Dir(inputFile))
- }
- if err := c.Compile(file); err != nil {
- return nil, err
- }
- bytecode := c.Bytecode()
- bytecode.RemoveDuplicates()
- return bytecode, nil
- }
- func doHelp() {
- fmt.Println("Usage:")
- fmt.Println()
- fmt.Println(" tengo [flags] {input-file}")
- fmt.Println()
- fmt.Println("Flags:")
- fmt.Println()
- fmt.Println(" -o compile output file")
- fmt.Println(" -version show version")
- fmt.Println()
- fmt.Println("Examples:")
- fmt.Println()
- fmt.Println(" tengo")
- fmt.Println()
- fmt.Println(" Start Tengo REPL")
- fmt.Println()
- fmt.Println(" tengo myapp.tengo")
- fmt.Println()
- fmt.Println(" Compile and run source file (myapp.tengo)")
- fmt.Println(" Source file must have .tengo extension")
- fmt.Println()
- fmt.Println(" tengo -o myapp myapp.tengo")
- fmt.Println()
- fmt.Println(" Compile source file (myapp.tengo) into bytecode file (myapp)")
- fmt.Println()
- fmt.Println(" tengo myapp")
- fmt.Println()
- fmt.Println(" Run bytecode file (myapp)")
- fmt.Println()
- fmt.Println()
- }
- func addPrints(file *parser.File) *parser.File {
- var stmts []parser.Stmt
- for _, s := range file.Stmts {
- switch s := s.(type) {
- case *parser.ExprStmt:
- stmts = append(stmts, &parser.ExprStmt{
- Expr: &parser.CallExpr{
- Func: &parser.Ident{Name: "__repl_println__"},
- Args: []parser.Expr{s.Expr},
- },
- })
- case *parser.AssignStmt:
- stmts = append(stmts, s)
- stmts = append(stmts, &parser.ExprStmt{
- Expr: &parser.CallExpr{
- Func: &parser.Ident{
- Name: "__repl_println__",
- },
- Args: s.LHS,
- },
- })
- default:
- stmts = append(stmts, s)
- }
- }
- return &parser.File{
- InputFile: file.InputFile,
- Stmts: stmts,
- }
- }
- func basename(s string) string {
- s = filepath.Base(s)
- n := strings.LastIndexByte(s, '.')
- if n > 0 {
- return s[:n]
- }
- return s
- }
|