commit c5b303bd15b9dc28f7fa50f97771e190fa40fb6e Author: surdeus Date: Thu Jun 6 01:06:55 2024 +0500 init. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..46f30e5 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module surdeus.su/core/xgo + +go 1.22.3 + +require ( + github.com/d5/tengo/v2 v2.17.0 + surdeus.su/core/cli v0.1.2 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2b30525 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/d5/tengo/v2 v2.17.0 h1:BWUN9NoJzw48jZKiYDXDIF3QrIVZRm1uV1gTzeZ2lqM= +github.com/d5/tengo/v2 v2.17.0/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8= +surdeus.su/core/cli v0.1.2 h1:qPzjawqPyZsO4Z5SaA1u141recVE65yioA83Qs7Jecs= +surdeus.su/core/cli v0.1.2/go.mod h1:r9JtQz3aEJzpYzMaNUNQHJoYkoWKNPi047qhd5uGlmA= diff --git a/main.go b/main.go new file mode 100644 index 0000000..c1a2c72 --- /dev/null +++ b/main.go @@ -0,0 +1,8 @@ +package main + +import "surdeus.su/core/xgo/xtool" +import "os" + +func main() { + xtool.Tool.Run(os.Args[1:]) +} diff --git a/xtool/main.go b/xtool/main.go new file mode 100644 index 0000000..62ef3ce --- /dev/null +++ b/xtool/main.go @@ -0,0 +1,329 @@ +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" + "github.com/d5/tengo/v2/stdlib" + +) + +import "surdeus.su/core/cli/mtool" + +const ( + sourceFileExt = ".xgo" + 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 := stdlib.GetModuleMap(stdlib.AllModuleNames()...) + 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 filepath.Ext(inputFile) == sourceFileExt { + 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, "") + } 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) + 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.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 +}