diff --git a/ast.go b/ast.go index d068620..19cda46 100644 --- a/ast.go +++ b/ast.go @@ -15,6 +15,8 @@ package scpt import ( + "bytes" + "encoding/json" "errors" "fmt" "github.com/alecthomas/participle/lexer" @@ -22,6 +24,8 @@ import ( var loopRunning bool var breakLoop bool +var funcRunning bool +var retValue interface{} = nil // AST stores the root of the Abstract Syntax Tree for scpt type AST struct { @@ -44,6 +48,30 @@ func (ast *AST) Execute() error { return nil } +func (ast *AST) Dump() ([]byte, error) { + return json.Marshal(ast) +} + +func (ast *AST) DumpPretty() ([]byte, error) { + buf := bytes.NewBuffer([]byte{}) + enc := json.NewEncoder(buf) + enc.SetIndent("", " ") + err := enc.Encode(ast) + if err != nil { + return buf.Bytes(), err + } + return buf.Bytes(), nil +} + +func LoadAST(data []byte) (*AST, error) { + var ast AST + err := json.Unmarshal(data, &ast) + if err != nil { + return nil, err + } + return &ast, nil +} + // Execute a variable declaration func executeVarCmd(Var *Var) error { // Parse value of variable @@ -124,13 +152,8 @@ func executeRptLoop(rptLoop *RptLoop) error { // Set loopRunning to true to allow break loopRunning = true // Run for loop with correct amount of iterations +rpt: for i := 0; i < *rptLoop.Times; i++ { - // If breakLoop set to true - if breakLoop { - // Reset breakLoop - breakLoop = false - break - } // If user requested index variable via "{ var in ... }" if rptLoop.IndexVar != nil { // Set requested variable name to index @@ -143,6 +166,12 @@ func executeRptLoop(rptLoop *RptLoop) error { if err != nil { return fmt.Errorf("%s: %s", InnerCmd.Pos, err) } + // If breakLoop set to true + if breakLoop { + // Reset breakLoop + breakLoop = false + break rpt + } } } // Remove index variable if existent @@ -166,13 +195,8 @@ func executeWhlLoop(whlLoop *WhlLoop) error { return errors.New("condition must be a boolean") } // Run for loop if condition is true +whl: for condBool { - // If breakLoop set to true - if breakLoop { - // Reset breakLoop - breakLoop = false - break - } // For each inner command for _, InnerCmd := range whlLoop.InnerCmds { // Execute command recursively @@ -180,6 +204,12 @@ func executeWhlLoop(whlLoop *WhlLoop) error { if err != nil { return fmt.Errorf("%s: %s", InnerCmd.Pos, err) } + // If breakLoop set to true + if breakLoop { + // Reset breakLoop + breakLoop = false + break whl + } // Get condition value condVal, err = callIfFunc(ParseValue(whlLoop.Condition)) if err != nil { @@ -200,6 +230,7 @@ func executeWhlLoop(whlLoop *WhlLoop) error { func executeFuncDef(def *FuncDef) error { // Set requested function name in Funcs Funcs[*def.Name] = func(args map[string]interface{}) (interface{}, error) { + funcRunning = true // Create new empty map[interface{}]interface{} argIMap := map[interface{}]interface{}{} // Convert args map[string]interface{} to map[interface{}]interface{} @@ -215,9 +246,16 @@ func executeFuncDef(def *FuncDef) error { if err != nil { return nil, fmt.Errorf("%s: %s", InnerCmd.Pos, err) } + if retValue != nil { + ret := retValue + retValue = nil + funcRunning = false + return ret, nil + } } // Remove args variable from Vars delete(Vars, "_args") + funcRunning = false return nil, nil } return nil @@ -395,4 +433,4 @@ type WhlLoop struct { Pos lexer.Position Condition *Value `"loop" "while" @@ "{"` InnerCmds []*Command `@@* "}"` -} \ No newline at end of file +} diff --git a/cmd/scpt/main.go b/cmd/scpt/main.go index 23eb47b..6fe4875 100644 --- a/cmd/scpt/main.go +++ b/cmd/scpt/main.go @@ -5,32 +5,65 @@ import ( "flag" "fmt" "gitea.arsenm.dev/Arsen6331/scpt" + "io/ioutil" "log" "os" ) func main() { - filePath := flag.String("file", "", "File to parse") + useStdin := flag.Bool("stdin", false, "Parse STDIN") + dumpAST := flag.Bool("dump-ast", false, "Dump the AST as JSON to STDOUT and quit") + loadAST := flag.String("load-ast", "", "Load JSON AST from specified file and execute it") flag.Parse() - if *filePath == "" { - log.Fatalln("Use --file to specify a file to parse") + var ast *scpt.AST + + if *loadAST != "" { + data, err := ioutil.ReadFile(*loadAST) + if err != nil { + log.Fatalln("Error opening specified file:", err) + } + ast, err = scpt.LoadAST(data) + if err != nil { + log.Fatalln("Error loading AST:", err) + } + } else if *useStdin { + var err error + ast, err = scpt.Parse(os.Stdin) + if err != nil { + log.Fatalln("Error parsing STDIN:", err) + } + } else { + if flag.NArg() < 1 { + log.Fatalln("Filepath or --stdin required") + } + filePath := flag.Args()[0] + file, err := os.Open(filePath) + if err != nil { + log.Fatalln("Error opening specified file:", err) + } + ast, err = scpt.Parse(file) + if err != nil { + log.Fatalln("Error parsing file:", err) + } } - file, err := os.Open(*filePath) - if err != nil { - log.Fatalln("Error opening specified file:", err) - } - ast, err := scpt.Parse(file) - if err != nil { - log.Fatalln("Error parsing file:", err) + if *dumpAST { + data, err := ast.DumpPretty() + if err != nil { + log.Fatalln("Error dumping AST:", err) + } + fmt.Println(string(data)) + os.Exit(0) } + scpt.AddFuncs(scpt.FuncMap{ "print": scptPrint, "display-dialog": displayDialog, "do-shell-script": doShellScript, }) - err = ast.Execute() + + err := ast.Execute() if err != nil { log.Fatalln("Error executing script:", err) } diff --git a/defaults.go b/defaults.go index 86e902f..e9555e1 100644 --- a/defaults.go +++ b/defaults.go @@ -17,6 +17,7 @@ package scpt import ( "errors" "fmt" + "os" "strconv" ) @@ -59,6 +60,27 @@ func setBreakLoop(_ map[string]interface{}) (interface{}, error) { return nil, nil } +// Default function to set the breakLoop variable to true, breaking any loops that may be running +func setReturn(args map[string]interface{}) (interface{}, error) { + // If a loop is running + if funcRunning { + // Set breakLoop to true, breaking the loop on next cycle + retValue = args[""] + } else { + return nil, errors.New("return not inside function") + } + return nil, nil +} + +func scptExit(args map[string]interface{}) (interface{}, error) { + exitCode, ok := args[""].(float64) + if !ok { + return nil, errors.New("exit requires an unnamed number argument") + } + os.Exit(int(exitCode)) + return nil, nil +} + // Default function that returns an array with an appended element func appendArray(args map[string]interface{}) (interface{}, error) { // Attempt to get unnamed argument and assert as []interface{} diff --git a/go.sum b/go.sum index 3d8e880..c913c1c 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,3 @@ -github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/alecthomas/participle v0.7.1 h1:2bN7reTw//5f0cugJcTOnY/NYZcWQOaajW+BwZB5xWs= github.com/alecthomas/participle v0.7.1/go.mod h1:HfdmEuwvr12HXQN44HPWXR0lHmVolVYe4dyL6lQ3duY= @@ -6,66 +5,38 @@ github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 h1:GDQdwm/gAcJcLAK github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= github.com/antonmedv/expr v1.8.9 h1:O9stiHmHHww9b4ozhPx7T6BK7fXfOCHJ8ybxf0833zw= github.com/antonmedv/expr v1.8.9/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= -github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM= github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= github.com/gen2brain/dlgs v0.0.0-20210222160047-2f436553172f h1:HPKrg4xeWLWOGAJGVJIYXWhVSdy2kaihuoSy7kBP7S4= github.com/gen2brain/dlgs v0.0.0-20210222160047-2f436553172f/go.mod h1:/eFcjDXaU2THSOOqLxOPETIbHETnamk8FA/hMjhg/gU= github.com/gopherjs/gopherjs v0.0.0-20210202160940-bed99a852dfe h1:rcf1P0fm+1l0EjG16p06mYLj9gW9X36KgdHJ/88hS4g= github.com/gopherjs/gopherjs v0.0.0-20210202160940-bed99a852dfe/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= -github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0= github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498 h1:4CFNy7/q7P06AsIONZzuWy7jcdqEmYQvOZ9FAFZdbls= github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84= -github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs= -github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo= -github.com/sanity-io/litter v1.2.0 h1:DGJO0bxH/+C2EukzOSBmAlxmkhVMGqzvcx/rvySYw9M= github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74 h1:4cFkmztxtMslUX2SctSl+blCyXfpzhGOy9LhKAqSMA4= -golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/scpt.go b/scpt.go index 6fea6f2..d028ed1 100644 --- a/scpt.go +++ b/scpt.go @@ -39,6 +39,8 @@ var Funcs = FuncMap{ "bool": parseBool, "break": setBreakLoop, "append": appendArray, + "exit": scptExit, + "return": setReturn, } // AddFuncs adds all functions from the provided FuncMap into @@ -300,10 +302,7 @@ func UnwrapArgs(args []*Arg) (map[string]interface{}, error) { // IsFuncCall checks if val is a FuncCall struct func IsFuncCall(val interface{}) bool { // If type of val is a pointer to FuncCall, return true - if reflect.TypeOf(val) == reflect.TypeOf(&FuncCall{}) { - return true - } - return false + return reflect.TypeOf(val) == reflect.TypeOf(&FuncCall{}) } // CallFunction executes a given function call in the form of diff --git a/test.scpt b/test.scpt old mode 100644 new mode 100755 index 88ded78..64919a4 --- a/test.scpt +++ b/test.scpt @@ -1,3 +1,5 @@ +#!/usr/bin/env scpt + set y to (display-dialog "Hello" with title 12 with type "yesno") display-dialog "Goodbye" with title 21 with type "error" do-shell-script "notify-send Test Notification" @@ -30,9 +32,9 @@ This is a multiline comment # This is a single line comment -set hi to ["testovich", 3] +set hi to ["test", 3] -set hi[0] to "testo" +set hi[0] to "test2" print $hi[0] @@ -55,9 +57,20 @@ loop while $f { print {"iter: " + (str $c)} } +repeat 6 times { i in + print {"brktest: " + (str $i)} + if {$i == 3} { + break + } + print {"brktest: " + (str $i) + " (2)"} +} + define hi { print {"Hello, " + $_args[""]} + return {"Hello, " + $_args[""]} + print "Something isn't right" } hi "Function" -hi "World" \ No newline at end of file +set f to (hi "World") +print {"Returned: " + $f} \ No newline at end of file