init.
This commit is contained in:
commit
c5b303bd15
4 changed files with 349 additions and 0 deletions
8
go.mod
Normal file
8
go.mod
Normal file
|
@ -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
|
||||
)
|
4
go.sum
Normal file
4
go.sum
Normal file
|
@ -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=
|
8
main.go
Normal file
8
main.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package main
|
||||
|
||||
import "surdeus.su/core/xgo/xtool"
|
||||
import "os"
|
||||
|
||||
func main() {
|
||||
xtool.Tool.Run(os.Args[1:])
|
||||
}
|
329
xtool/main.go
Normal file
329
xtool/main.go
Normal file
|
@ -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, "<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)
|
||||
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
|
||||
}
|
Loading…
Reference in a new issue