Pārlūkot izejas kodu

Very primitive, very simple, but temporarily usable version.

ZRY 2 nedēļas atpakaļ
vecāks
revīzija
58b1f86e2c

+ 1 - 0
.gitignore

@@ -24,3 +24,4 @@ _testmain.go
 *.test
 *.prof
 
+dist

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 7 - 0
.idea/misc.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="XMakeProjectSettings">
+    <option name="currentArchitecture" value="x86" />
+    <option name="workingDirectory" value="$PROJECT_DIR$" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/saki-v0.iml" filepath="$PROJECT_DIR$/.idea/saki-v0.iml" />
+    </modules>
+  </component>
+</project>

+ 11 - 0
.idea/saki-v0.iml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="Go" enabled="true" />
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/dist" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

+ 12 - 0
cmd/sakish/builtin.go

@@ -0,0 +1,12 @@
+package main
+
+import (
+	se "git.swzry.com/ProjectNagae/saki-v0/engine"
+	dm "git.swzry.com/ProjectNagae/saki-v0/modules/default_builtins"
+)
+
+func InitBuiltins(e *se.Engine) {
+	e.AddGlobal(&dm.GlobalObjects{})
+	//e.AddModule("restful", &dm.RestfulClient{})
+	e.AddModule("netlink", &dm.NetlinkUtil{})
+}

+ 73 - 0
cmd/sakish/cfg.go

@@ -0,0 +1,73 @@
+package main
+
+import (
+	"fmt"
+	se "git.swzry.com/ProjectNagae/saki-v0/engine"
+	"github.com/BurntSushi/toml"
+	"github.com/urfave/cli/v2"
+	"os"
+)
+
+var _ se.Config = (*Config)(nil)
+
+type TomlConfigDef struct {
+	VmConfig *VmConfigDef `toml:"vm"`
+}
+
+type Config struct {
+	*TomlConfigDef
+	willPrintResults bool
+	isStrictMode     bool
+	suppressWarning  bool
+}
+
+func (c *Config) RuntimeWarning(err error) {
+	if !c.suppressWarning {
+		RuntimeWarning(err)
+	}
+}
+
+func (c *Config) IsStrictMode() bool {
+	return c.isStrictMode
+}
+
+func (c *Config) WillPrintResults() bool {
+	return c.willPrintResults
+}
+
+func (c *Config) SetArgsSpecifiedItemsForExec(cx *cli.Context) {
+	strictMode := cx.Bool("strict-mode")
+	noResultPrint := cx.Bool("no-js-result-print")
+	suppressWarning := cx.Bool("suppress-warn")
+	c.isStrictMode = strictMode
+	c.willPrintResults = !noResultPrint
+	c.suppressWarning = suppressWarning
+}
+
+func (c *TomlConfigDef) GlobalModulePath() []string {
+	return c.VmConfig.GlobalModuleFolders
+}
+
+type VmConfigDef struct {
+	GlobalModuleFolders []string `toml:"global_module_folders"`
+}
+
+func LoadConfig(c *cli.Context) (*Config, error) {
+	cfgFile := c.String("config")
+	if cfgFile == "" {
+		cfgFile = "/nagae/elip4ng/config/sakish.toml"
+	}
+	cfgBin, err := os.ReadFile(cfgFile)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read config file at %s:\n%w", cfgFile, err)
+	}
+	var tcfg *TomlConfigDef
+	err = toml.Unmarshal(cfgBin, &tcfg)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse config file at %s:\n%w", cfgFile, err)
+	}
+	cfg := Config{
+		TomlConfigDef: tcfg,
+	}
+	return &cfg, nil
+}

+ 14 - 0
cmd/sakish/go.mod

@@ -0,0 +1,14 @@
+module git.swzry.com/ProjectNagae/saki-v0/cmd/sakish
+
+go 1.21.6
+
+require (
+	github.com/BurntSushi/toml v1.3.2
+	github.com/urfave/cli/v2 v2.27.1
+)
+
+require (
+	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
+	github.com/russross/blackfriday/v2 v2.1.0 // indirect
+	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
+)

+ 10 - 0
cmd/sakish/go.sum

@@ -0,0 +1,10 @@
+github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
+github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
+github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
+github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=

+ 66 - 0
cmd/sakish/main.go

@@ -0,0 +1,66 @@
+package main
+
+import (
+	cli "github.com/urfave/cli/v2"
+	"os"
+)
+
+const VERSION = "0.0.1"
+
+func main() {
+	app := &cli.App{
+		Name:    "sakish",
+		Usage:   "ELIP4NG management script shell. Default subcommand is 'repl'.",
+		Version: VERSION,
+		Flags: []cli.Flag{
+			&cli.StringFlag{
+				Name:    "config",
+				Aliases: []string{"c"},
+				Usage:   "config file path. if not specified, use default config at '/nagae/elip4ng/config/sakish.toml'.",
+			},
+			&cli.BoolFlag{
+				Name:    "suppress-warn",
+				Aliases: []string{"no-warn"},
+				Usage:   "suppress runtime warning messages",
+			},
+		},
+		Commands: []*cli.Command{
+			{
+				Name:    "repl",
+				Aliases: []string{},
+				Usage:   "start REPL mode",
+				Flags:   []cli.Flag{},
+				Action:  CmdRepl,
+			},
+			{
+				Name:    "exec",
+				Aliases: []string{"e"},
+				Usage:   "execute a script file",
+				Flags: []cli.Flag{
+					&cli.BoolFlag{
+						Name:    "strict-mode",
+						Aliases: []string{"s"},
+						Usage:   "enable ECMAScript strict mode",
+					},
+					&cli.BoolFlag{
+						Name:    "no-js-result-print",
+						Aliases: []string{"nrp"},
+						Usage:   "do not print js result after execute",
+					},
+				},
+				Action: CmdRun,
+			},
+			{
+				Name:    "version",
+				Aliases: []string{"v", "ver"},
+				Usage:   "print version information",
+				Flags:   []cli.Flag{},
+				Action:  CmdVersion,
+			},
+		},
+	}
+	app.DefaultCommand = "repl"
+	if err := app.Run(os.Args); err != nil {
+		ErrorExit(err)
+	}
+}

+ 25 - 0
cmd/sakish/misc.go

@@ -0,0 +1,25 @@
+package main
+
+import (
+	"fmt"
+	"github.com/urfave/cli/v2"
+	"os"
+)
+
+func CmdVersion(c *cli.Context) error {
+	fmt.Println("Version: ", VERSION)
+	return nil
+}
+
+func ErrorExit(err error) {
+	_, _ = fmt.Fprintln(os.Stderr, "==== SakiSH Runtime Error ====")
+	_, _ = fmt.Fprintln(os.Stderr, err)
+	_, _ = fmt.Fprintln(os.Stderr, "==============================")
+	os.Exit(-1)
+}
+
+func RuntimeWarning(err error) {
+	_, _ = fmt.Fprintln(os.Stderr, "==== SakiSH Runtime Warning ====")
+	_, _ = fmt.Fprintln(os.Stderr, err)
+	_, _ = fmt.Fprintln(os.Stderr, "================================")
+}

+ 11 - 0
cmd/sakish/repl.go

@@ -0,0 +1,11 @@
+package main
+
+import (
+	"fmt"
+	"github.com/urfave/cli/v2"
+)
+
+func CmdRepl(c *cli.Context) error {
+	fmt.Println("Not support REPL yet.")
+	return nil
+}

+ 29 - 0
cmd/sakish/run.go

@@ -0,0 +1,29 @@
+package main
+
+import (
+	"fmt"
+	se "git.swzry.com/ProjectNagae/saki-v0/engine"
+	"github.com/urfave/cli/v2"
+)
+
+func CmdRun(c *cli.Context) error {
+	if c.NArg() != 1 {
+		return fmt.Errorf("invalid arguments:\nUsage: sakish exec <script_file>")
+	}
+	cfg, err := LoadConfig(c)
+	if err != nil {
+		return fmt.Errorf("failed to load config:\n%w", err)
+	}
+	cfg.SetArgsSpecifiedItemsForExec(c)
+	engine := se.NewEngine(cfg)
+	err = engine.Prepare()
+	InitBuiltins(engine)
+	if err != nil {
+		return fmt.Errorf("failed to prepare script engine:\n%w", err)
+	}
+	err = engine.RunFile(c.Args().Get(0))
+	if err != nil {
+		return fmt.Errorf("failed to exec script:\n%w", err)
+	}
+	return nil
+}

+ 2 - 0
config.example.toml

@@ -0,0 +1,2 @@
+[vm]
+global_module_folders = [ "./" ]

+ 80 - 0
engine/engine.go

@@ -0,0 +1,80 @@
+package engine
+
+import (
+	"fmt"
+	"github.com/dop251/goja"
+	"github.com/dop251/goja_nodejs/require"
+	"os"
+)
+
+type Engine struct {
+	vm            *goja.Runtime
+	cfg           Config
+	registry      *require.Registry
+	requireModule *require.RequireModule
+}
+
+type Config interface {
+	IsStrictMode() bool
+	WillPrintResults() bool
+	GlobalModulePath() []string
+	RuntimeWarning(err error)
+}
+
+func NewEngine(cfg Config) *Engine {
+	reg := require.NewRegistry(require.WithGlobalFolders(cfg.GlobalModulePath()...))
+	e := &Engine{
+		vm:       goja.New(),
+		cfg:      cfg,
+		registry: reg,
+	}
+	return e
+}
+
+func (e *Engine) Prepare() error {
+	e.requireModule = e.registry.Enable(e.vm)
+	return nil
+}
+
+func (e *Engine) RunFile(file string) error {
+	fjs, err := os.ReadFile(file)
+	if err != nil {
+		return fmt.Errorf("failed to read script file:\n%w", err)
+	}
+	compiled, err := goja.Compile(file, string(fjs), e.cfg.IsStrictMode())
+	res, err := e.vm.RunProgram(compiled)
+	if err != nil {
+		return fmt.Errorf("error in script:\n%w", err)
+	}
+	if e.cfg.WillPrintResults() {
+		fmt.Println(res.String())
+	}
+	return nil
+}
+
+func (e *Engine) AddGlobal(m IModule) {
+	rf := m.Enable(&ModuleContext{
+		engine: e,
+	})
+	rf(func(name string, value interface{}) {
+		err := e.vm.Set(name, value)
+		if err != nil {
+			e.cfg.RuntimeWarning(fmt.Errorf("failed to add '%s' to global object: %w", name, err))
+		}
+	})
+}
+
+func (e *Engine) AddModule(modName string, m IModule) {
+	rf := m.Enable(&ModuleContext{
+		engine: e,
+	})
+	e.registry.RegisterNativeModule(modName, func(runtime *goja.Runtime, module *goja.Object) {
+		exports := module.Get("exports").(*goja.Object)
+		rf(func(name string, value interface{}) {
+			err := exports.Set(name, value)
+			if err != nil {
+				e.cfg.RuntimeWarning(fmt.Errorf("failed to add '%s' to module '%s': %w", name, modName, err))
+			}
+		})
+	})
+}

+ 15 - 0
engine/go.mod

@@ -0,0 +1,15 @@
+module git.swzry.com/ProjectNagae/saki-v0/engine
+
+go 1.21.6
+
+require (
+	github.com/dop251/goja v0.0.0-20240220182346-e401ed450204
+	github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d
+)
+
+require (
+	github.com/dlclark/regexp2 v1.7.0 // indirect
+	github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
+	github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
+	golang.org/x/text v0.3.8 // indirect
+)

+ 58 - 0
engine/go.sum

@@ -0,0 +1,58 @@
+github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
+github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
+github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
+github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
+github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
+github.com/dop251/goja v0.0.0-20240220182346-e401ed450204 h1:O7I1iuzEA7SG+dK8ocOBSlYAA9jBUmCYl/Qa7ey7JAM=
+github.com/dop251/goja v0.0.0-20240220182346-e401ed450204/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
+github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
+github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d h1:W1n4DvpzZGOISgp7wWNtraLcHtnmnTwBlJidqtMIuwQ=
+github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
+github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
+github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
+github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
+github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
+github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

+ 23 - 0
engine/module_ctx.go

@@ -0,0 +1,23 @@
+package engine
+
+import "github.com/dop251/goja"
+
+type RegisterFunc func(msf ModuleSetFunc)
+type ModuleSetFunc func(name string, value interface{})
+
+type ModuleContext struct {
+	engine       *Engine
+	registerFunc RegisterFunc
+}
+
+type IModule interface {
+	Enable(mctx *ModuleContext) RegisterFunc
+}
+
+func (m *ModuleContext) RuntimeWarning(err error) {
+	m.engine.cfg.RuntimeWarning(err)
+}
+
+func (m *ModuleContext) NewValue(v interface{}) goja.Value {
+	return m.engine.vm.ToValue(v)
+}

+ 7 - 0
go.work

@@ -0,0 +1,7 @@
+go 1.21.6
+
+use (
+	cmd/sakish
+	engine
+	modules/default_builtins
+)

+ 24 - 0
go.work.sum

@@ -0,0 +1,24 @@
+github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
+github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/chzyer/logex v1.2.0 h1:+eqR0HfOetur4tgnC8ftU5imRnhi4te+BadWS95c5AM=
+github.com/chzyer/readline v1.5.0 h1:lSwwFrbNviGePhkewF1az4oLmcwqCZijQ2/Wi3BGHAI=
+github.com/chzyer/test v0.0.0-20210722231415-061457976a23 h1:dZ0/VyGgQdVGAss6Ju0dt5P0QltE0SFY5Woh6hbIfiQ=
+github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
+github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2 h1:rcanfLhLDA8nozr/K289V1zcntHr3V+SHlXwzz1ZI2g=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
+github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
+golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 43 - 0
justfile

@@ -0,0 +1,43 @@
+#!/usr/bin/env just --justfile
+
+jfdir := replace(justfile_directory(), "\\", "/")
+dist := jfdir / "dist"
+src_cmd := jfdir / "cmd"
+
+src_engine := jfdir / "engine"
+src_sakish := src_cmd / "sakish"
+src_module := jfdir / "modules"
+
+goos := "linux"
+goarch := "arm"
+
+executable_suffix := if goos == "windows" { ".exe" } else { "" }
+
+export GOOS := goos
+export GOARCH := goarch
+
+default:
+	just --list
+
+tidy_engine:
+	cd {{src_engine}}; go mod tidy -e
+
+tidy_module name:
+	cd {{src_module / name}}; go mod tidy -e
+
+tidy_sakish:
+	cd {{src_sakish}}; go mod tidy -e
+
+build_sakish:
+	cd {{src_sakish}}; go build -o {{dist / "sakish" + executable_suffix}}
+
+win_test_build:
+	just --set goos windows --set goarch amd64 build_sakish
+	cp {{jfdir/ "config.example.toml"}} {{dist / "config.toml" }}
+
+win_test_run *args:
+	cd {{dist}}; ./sakish.exe {{args}}
+
+win_test_all *args:
+	just win_test_build
+	just win_test_run {{args}}

+ 105 - 0
modules/default_builtins/global.go

@@ -0,0 +1,105 @@
+package default_builtins
+
+import (
+	"fmt"
+	"git.swzry.com/ProjectNagae/saki-v0/engine"
+	"github.com/dop251/goja"
+	"os"
+	"os/exec"
+	"time"
+)
+
+type GlobalObjects struct {
+	mctx *engine.ModuleContext
+}
+
+func (g *GlobalObjects) Enable(mctx *engine.ModuleContext) engine.RegisterFunc {
+	g.mctx = mctx
+	return g.register
+}
+
+func (g *GlobalObjects) register(s engine.ModuleSetFunc) {
+	s("print", g.fPrint)
+	s("println", g.fPrintln)
+	s("printf", g.fPrintf)
+	s("execute", g.fExecute)
+	s("delay", g.fDelayMilliSec)
+}
+
+func (g *GlobalObjects) iPrintArgPrepare(call goja.FunctionCall) []any {
+	printItems := make([]any, len(call.Arguments))
+	for i, arg := range call.Arguments {
+		printItems[i] = arg.String()
+	}
+	return printItems
+}
+
+func (g *GlobalObjects) iWarn(err error) {
+	if g.mctx != nil {
+		g.mctx.RuntimeWarning(err)
+	}
+}
+
+func (g *GlobalObjects) fPrint(call goja.FunctionCall) goja.Value {
+	fmt.Print(g.iPrintArgPrepare(call)...)
+	return goja.Undefined()
+}
+
+func (g *GlobalObjects) fPrintln(call goja.FunctionCall) goja.Value {
+	fmt.Println(g.iPrintArgPrepare(call)...)
+	return goja.Undefined()
+}
+
+func (g *GlobalObjects) fPrintf(call goja.FunctionCall) goja.Value {
+	l := len(call.Arguments)
+	if l < 1 {
+		panic(g.mctx.NewValue(fmt.Errorf("printf: not enough arguments")))
+	}
+	formatRaw := call.Arguments[0].Export()
+	format, ok := formatRaw.(string)
+	if !ok {
+		panic(g.mctx.NewValue(fmt.Errorf("printf: argument[0] is not string")))
+	}
+	args := make([]any, l-1)
+	for i := 1; i < l; i++ {
+		args[i-1] = call.Arguments[i].Export()
+	}
+	fmt.Printf(format, args...)
+	return goja.Undefined()
+}
+
+func (g *GlobalObjects) fExecute(call goja.FunctionCall) goja.Value {
+	l := len(call.Arguments)
+	if l < 1 {
+		panic(g.mctx.NewValue(fmt.Errorf("execute: not enough arguments")))
+	}
+	args := make([]string, l)
+	for i := 0; i < l; i++ {
+		v, ok := call.Arguments[i].ToString().Export().(string)
+		if !ok {
+			panic(g.mctx.NewValue(fmt.Errorf("execute: argument[%d] is not string", i)))
+		}
+		args[i] = v
+	}
+	bin := args[0]
+	args = args[1:]
+	cmd := exec.Command(bin, args...)
+	cmd.Stdin = os.Stdin
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	err := cmd.Run()
+	if err != nil {
+		panic(g.mctx.NewValue(fmt.Errorf("execute: error in executing command '%s': %w", bin, err)))
+	}
+	res := cmd.ProcessState.ExitCode()
+	return g.mctx.NewValue(res)
+}
+
+func (g *GlobalObjects) fDelayMilliSec(call goja.FunctionCall) goja.Value {
+	if len(call.Arguments) != 1 {
+		panic(g.mctx.NewValue(fmt.Errorf("delay: invalid arguments")))
+	}
+	ms := call.Arguments[0].ToInteger()
+	time.Sleep(time.Duration(ms) * time.Millisecond)
+	return goja.Undefined()
+}

+ 17 - 0
modules/default_builtins/go.mod

@@ -0,0 +1,17 @@
+module git.swzry.com/ProjectNagae/saki-v0/modules/default_builtins
+
+go 1.21.6
+
+require (
+	github.com/dop251/goja v0.0.0-20240220182346-e401ed450204
+	github.com/vishvananda/netlink v1.1.0
+)
+
+require (
+	github.com/dlclark/regexp2 v1.7.0 // indirect
+	github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
+	github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
+	github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
+	golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
+	golang.org/x/text v0.3.8 // indirect
+)

+ 63 - 0
modules/default_builtins/go.sum

@@ -0,0 +1,63 @@
+github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
+github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
+github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
+github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
+github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
+github.com/dop251/goja v0.0.0-20240220182346-e401ed450204 h1:O7I1iuzEA7SG+dK8ocOBSlYAA9jBUmCYl/Qa7ey7JAM=
+github.com/dop251/goja v0.0.0-20240220182346-e401ed450204/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
+github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
+github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
+github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
+github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
+github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
+github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
+github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
+github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
+github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
+github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

+ 143 - 0
modules/default_builtins/netlink.go

@@ -0,0 +1,143 @@
+package default_builtins
+
+import (
+	"fmt"
+	"git.swzry.com/ProjectNagae/saki-v0/engine"
+	"github.com/dop251/goja"
+	"github.com/vishvananda/netlink"
+	"net"
+	"runtime"
+)
+
+type NetlinkUtil struct {
+	mctx *engine.ModuleContext
+}
+
+func (g *NetlinkUtil) Enable(mctx *engine.ModuleContext) engine.RegisterFunc {
+	g.mctx = mctx
+	return g.register
+}
+
+func (g *NetlinkUtil) register(s engine.ModuleSetFunc) {
+	s("setAddr", g.fSetAddr)
+	s("setLink", g.fSetLink)
+	s("addRoute", g.fAddRoute)
+}
+
+func (g *NetlinkUtil) iCheckOs() {
+	if runtime.GOOS != "linux" {
+		panic(g.mctx.NewValue(fmt.Errorf("not supported os '%s'. only linux is supported", runtime.GOOS)))
+	}
+}
+
+func (g *NetlinkUtil) fSetAddr(call goja.FunctionCall) goja.Value {
+	g.iCheckOs()
+	if len(call.Arguments) != 2 {
+		panic(g.mctx.NewValue(fmt.Errorf("setAddr: invalid arguments")))
+	}
+	devRaw, ok := call.Arguments[0].Export().(string)
+	if !ok {
+		panic(g.mctx.NewValue(fmt.Errorf("setAddr: argument[0] (dev) is not string")))
+	}
+	ipRaw, ok := call.Arguments[1].Export().(string)
+	if !ok {
+		panic(g.mctx.NewValue(fmt.Errorf("setAddr: argument[1] (addr) is not string")))
+	}
+	link, err := netlink.LinkByName(devRaw)
+	if err != nil {
+		panic(g.mctx.NewValue(fmt.Errorf("setAddr: failed to get link device '%s': %w", devRaw, err)))
+	}
+	addr, err := netlink.ParseAddr(ipRaw)
+	if err != nil {
+		panic(g.mctx.NewValue(fmt.Errorf("setAddr: failed to parse address '%s': %w", ipRaw, err)))
+	}
+	err = netlink.AddrAdd(link, addr)
+	if err != nil {
+		panic(g.mctx.NewValue(fmt.Errorf("setAddr: failed to set address: %w", err)))
+	}
+	return goja.Undefined()
+}
+
+func (g *NetlinkUtil) fSetLink(call goja.FunctionCall) goja.Value {
+	g.iCheckOs()
+	if len(call.Arguments) != 2 {
+		panic(g.mctx.NewValue(fmt.Errorf("setAddr: invalid arguments")))
+	}
+	devRaw, ok := call.Arguments[0].Export().(string)
+	if !ok {
+		panic(g.mctx.NewValue(fmt.Errorf("setAddr: argument[0] (dev) is not string")))
+	}
+	linkState, ok := call.Arguments[1].Export().(bool)
+	if !ok {
+		panic(g.mctx.NewValue(fmt.Errorf("setAddr: argument[1] (state) is not bool")))
+	}
+	link, err := netlink.LinkByName(devRaw)
+	if err != nil {
+		panic(g.mctx.NewValue(fmt.Errorf("setAddr: failed to get link device '%s': %w", devRaw, err)))
+	}
+	if linkState {
+		err = netlink.LinkSetUp(link)
+	} else {
+		err = netlink.LinkSetDown(link)
+	}
+	if err != nil {
+		panic(g.mctx.NewValue(fmt.Errorf("setAddr: failed to set link state: %w", err)))
+	}
+	return goja.Undefined()
+}
+
+func (g *NetlinkUtil) fAddRoute(call goja.FunctionCall) goja.Value {
+	g.iCheckOs()
+	var ok bool
+	l := len(call.Arguments)
+	if l < 2 {
+		panic(g.mctx.NewValue(fmt.Errorf("setAddr: invalid arguments: need at least 2 arguments")))
+	}
+	if l > 4 {
+		panic(g.mctx.NewValue(fmt.Errorf("setAddr: invalid arguments: need at most 4 arguments")))
+	}
+	dstRaw, ok := call.Arguments[0].Export().(string)
+	if !ok {
+		panic(g.mctx.NewValue(fmt.Errorf("setAddr: argument[0] (dst) is not string")))
+	}
+	devRaw, ok := call.Arguments[1].Export().(string)
+	if !ok {
+		panic(g.mctx.NewValue(fmt.Errorf("setAddr: argument[1] (dev) is not string")))
+	}
+	var gwRaw string
+	if l > 2 {
+		gwRaw, ok = call.Arguments[2].Export().(string)
+		if !ok {
+			panic(g.mctx.NewValue(fmt.Errorf("setAddr: argument[2] (gw) is not string")))
+		}
+	}
+	var srcRaw string
+	if l > 3 {
+		srcRaw, ok = call.Arguments[3].Export().(string)
+		if !ok {
+			panic(g.mctx.NewValue(fmt.Errorf("setAddr: argument[3] (src) is not string")))
+		}
+	}
+	link, err := netlink.LinkByName(devRaw)
+	if err != nil {
+		panic(g.mctx.NewValue(fmt.Errorf("setAddr: failed to get link device '%s': %w", devRaw, err)))
+	}
+	route := netlink.Route{LinkIndex: link.Attrs().Index}
+	if dstRaw != "default" {
+		dstAddr, _ := netlink.ParseAddr(dstRaw)
+		route.Dst = dstAddr.IPNet
+	}
+	if srcRaw != "" {
+		srcIP := net.ParseIP(srcRaw)
+		route.Src = srcIP
+	}
+	if gwRaw != "" {
+		gatewayIP := net.ParseIP(gwRaw)
+		route.Gw = gatewayIP
+	}
+	err = netlink.RouteAdd(&route)
+	if err != nil {
+		panic(g.mctx.NewValue(fmt.Errorf("setAddr: failed to add route: %w", err)))
+	}
+	return goja.Undefined()
+}

+ 23 - 0
modules/default_builtins/restful.go

@@ -0,0 +1,23 @@
+package default_builtins
+
+import (
+	"git.swzry.com/ProjectNagae/saki-v0/engine"
+	"github.com/dop251/goja"
+)
+
+type RestfulClient struct {
+	mctx *engine.ModuleContext
+}
+
+func (g *RestfulClient) Enable(mctx *engine.ModuleContext) engine.RegisterFunc {
+	g.mctx = mctx
+	return g.register
+}
+
+func (g *RestfulClient) register(s engine.ModuleSetFunc) {
+	s("get", g.fGet)
+}
+
+func (g *RestfulClient) fGet(call goja.FunctionCall) goja.Value {
+	return goja.Undefined()
+}