Compare commits
15 Commits
877f85ef78
...
master
Author | SHA1 | Date | |
---|---|---|---|
31c3a2f3a9 | |||
836149ffc2 | |||
e5b8521ba4 | |||
8aa6f45db2 | |||
ecfbd40df8 | |||
bb89c93344 | |||
36d1eee759 | |||
1c99345af1 | |||
f7a34b3da4 | |||
53e0717b91 | |||
201030ed93 | |||
e6d195f364 | |||
ba11fdcf76 | |||
1196942801 | |||
412079c20a |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,2 @@
|
|||||||
scpt
|
/scpt
|
||||||
.idea/
|
.idea/
|
81
README.md
Normal file
81
README.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# scpt
|
||||||
|
|
||||||
|
scpt is an applescript-inspired scripting language written for fun and to see if I could.
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/gitea.arsenm.dev/Arsen6331/scpt)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
scpt is to be used as a library imported into Go. A basic interpreter with no extra functionality would look like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitea.arsenm.dev/Arsen6331/scpt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
filename := os.Args[1]
|
||||||
|
file, err := os.Open(filepath.Clean(filename))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
ast, err := scpt.Parse(file)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
err = ast.Execute()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Basic Syntax
|
||||||
|
|
||||||
|
The basic syntax of scpt can be learned from the test.scpt file.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Default Functions
|
||||||
|
|
||||||
|
scpt comes with the following default functions:
|
||||||
|
|
||||||
|
- `str`: Convert value to string
|
||||||
|
- `num`: Parse string to number (`float64`)
|
||||||
|
- `bool`: Parse string to boolean
|
||||||
|
- `break`: Break out of loop (Errors if not in loop)
|
||||||
|
- `append`: Return an array with given items appended
|
||||||
|
- `exit`: Exit with given exit code
|
||||||
|
- `return`: Return value in function (Errors if not within function)
|
||||||
|
- `print`: Print using `fmt.Println()`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Adding functionality:
|
||||||
|
|
||||||
|
Adding functionality is simple and requires a call to `scpt.AddFuncs()` or `scpt.AddVars()`. Here are some examples:
|
||||||
|
|
||||||
|
```go
|
||||||
|
scpt.AddFuncs(scpt.FuncMap{
|
||||||
|
"my-function": myFunction
|
||||||
|
})
|
||||||
|
```
|
||||||
|
Where `myFunction` is:
|
||||||
|
```go
|
||||||
|
func myFunction(args map[string]interface{}) (interface{}, error) {
|
||||||
|
fmt.Println(args)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
After the call to `scpt.AddFuncs()`, `my-function` can be used to run the function from within an scpt script. Variables work similarly.
|
448
ast.go
448
ast.go
@@ -1,25 +1,18 @@
|
|||||||
/*
|
|
||||||
Copyright (c) 2021 Arsen Musayelyan
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package scpt
|
package scpt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/alecthomas/participle/lexer"
|
"github.com/alecthomas/participle/lexer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var loopRunning bool
|
||||||
|
var breakLoop bool
|
||||||
|
var funcRunning bool
|
||||||
|
var retValue interface{} = nil
|
||||||
|
|
||||||
// AST stores the root of the Abstract Syntax Tree for scpt
|
// AST stores the root of the Abstract Syntax Tree for scpt
|
||||||
type AST struct {
|
type AST struct {
|
||||||
Pos lexer.Position
|
Pos lexer.Position
|
||||||
@@ -41,29 +34,229 @@ func (ast *AST) Execute() error {
|
|||||||
return nil
|
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
|
||||||
|
val, err := ParseValue(Var.Value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", Var.Value.Pos, err)
|
||||||
|
}
|
||||||
|
// If value of variable is a function call
|
||||||
|
if IsFuncCall(val) {
|
||||||
|
// Assert type of val as *FuncCall
|
||||||
|
Call := val.(*FuncCall)
|
||||||
|
// Set variable value to function return value
|
||||||
|
Vars[Var.Key], err = CallFunction(Call)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", Var.Value.Pos, err)
|
||||||
|
}
|
||||||
|
} else if Var.Index != nil {
|
||||||
|
// If variable definition has an associated index, get index value
|
||||||
|
index, err := callIfFunc(ParseValue(Var.Index))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", Var.Index.Pos, err)
|
||||||
|
}
|
||||||
|
// Attempt to get the variable from Vars and assert it as a []interface{}
|
||||||
|
slc, ok := Vars[Var.Key].([]interface{})
|
||||||
|
// If assertion successful
|
||||||
|
if ok {
|
||||||
|
// Assert index value as a 64-bit float
|
||||||
|
indexInt, ok := index.(float64)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%s: %s", Var.Pos, "variable "+Var.Key+" does not exist or is not an array")
|
||||||
|
}
|
||||||
|
// Set integer index of interface{} slice to value
|
||||||
|
slc[int64(indexInt)] = val
|
||||||
|
} else {
|
||||||
|
// If slice assertion unsuccessful, attempt to assert as map[interface{}]interface{}
|
||||||
|
iMap, ok := Vars[Var.Key].(map[interface{}]interface{})
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%s: %s", Var.Pos, "variable "+Var.Key+" does not exist or is not a map")
|
||||||
|
}
|
||||||
|
// Set index of interface{} to interface{} map to value
|
||||||
|
iMap[index] = val
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If value is not a function call, set variable to parsed value
|
||||||
|
Vars[Var.Key] = val
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute an if statement
|
||||||
|
func executeIfCmd(If *If) error {
|
||||||
|
// Get condition value
|
||||||
|
condVal, err := callIfFunc(ParseValue(If.Condition))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", If.Condition.Pos, err)
|
||||||
|
}
|
||||||
|
// Attempt to assert condition type as bool
|
||||||
|
condBool, ok := condVal.(bool)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("condition must be a boolean")
|
||||||
|
}
|
||||||
|
// If condition is true
|
||||||
|
if condBool {
|
||||||
|
// For each inner command
|
||||||
|
for _, InnerCmd := range If.InnerCmds {
|
||||||
|
// Execute command recursively
|
||||||
|
err := executeCmd(InnerCmd)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", InnerCmd.Pos, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute a repeat loop
|
||||||
|
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 user requested index variable via "{ var in ... }"
|
||||||
|
if rptLoop.IndexVar != nil {
|
||||||
|
// Set requested variable name to index
|
||||||
|
Vars[*rptLoop.IndexVar] = i
|
||||||
|
}
|
||||||
|
// For each command within the loop
|
||||||
|
for _, InnerCmd := range rptLoop.InnerCmds {
|
||||||
|
// Execute command recursively
|
||||||
|
err := executeCmd(InnerCmd)
|
||||||
|
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
|
||||||
|
delete(Vars, *rptLoop.IndexVar)
|
||||||
|
// Reset loopRunning
|
||||||
|
loopRunning = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute a while loop
|
||||||
|
func executeWhlLoop(whlLoop *WhlLoop) error {
|
||||||
|
loopRunning = true
|
||||||
|
// Get condition value
|
||||||
|
condVal, err := callIfFunc(ParseValue(whlLoop.Condition))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", whlLoop.Condition.Pos, err)
|
||||||
|
}
|
||||||
|
// Attempt to assert condition type as bool
|
||||||
|
condBool, ok := condVal.(bool)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("condition must be a boolean")
|
||||||
|
}
|
||||||
|
// Run for loop if condition is true
|
||||||
|
whl:
|
||||||
|
for condBool {
|
||||||
|
// For each inner command
|
||||||
|
for _, InnerCmd := range whlLoop.InnerCmds {
|
||||||
|
// Execute command recursively
|
||||||
|
err := executeCmd(InnerCmd)
|
||||||
|
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 {
|
||||||
|
return fmt.Errorf("%s: %s", whlLoop.Condition.Pos, err)
|
||||||
|
}
|
||||||
|
// Attempt to assert condition type as bool and update its value
|
||||||
|
condBool, ok = condVal.(bool)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("condition must be a boolean")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loopRunning = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute a function definition
|
||||||
|
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{}
|
||||||
|
for key, value := range args {
|
||||||
|
argIMap[key] = value
|
||||||
|
}
|
||||||
|
// Set variable _args to the args map[interface{}]interface{}
|
||||||
|
Vars["_args"] = argIMap
|
||||||
|
// For each command within the definition
|
||||||
|
for _, InnerCmd := range def.InnerCmds {
|
||||||
|
// Execute command recursively
|
||||||
|
err := executeCmd(InnerCmd)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// Parse and execute command
|
// Parse and execute command
|
||||||
func executeCmd(cmd *Command) error {
|
func executeCmd(cmd *Command) error {
|
||||||
// If parsing variables
|
// If parsing variables
|
||||||
if cmd.Vars != nil {
|
if cmd.Vars != nil {
|
||||||
// For each variable
|
// For each variable
|
||||||
for _, Var := range cmd.Vars {
|
for _, Var := range cmd.Vars {
|
||||||
// Parse value of variable
|
// Attempt to execute the variable command
|
||||||
val, err := ParseValue(Var.Value)
|
err := executeVarCmd(Var)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %s", Var.Value.Pos, err)
|
return err
|
||||||
}
|
|
||||||
// If value of variable is a function call
|
|
||||||
if IsFuncCall(val) {
|
|
||||||
// Assert type of val as *FuncCall
|
|
||||||
Call := val.(*FuncCall)
|
|
||||||
// Set variable value to function return value
|
|
||||||
Vars[Var.Key], err = CallFunction(Call)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%s: %s", Var.Value.Pos, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If value is not a function call, set variable value to parsed value
|
|
||||||
Vars[Var.Key] = val
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if cmd.Calls != nil {
|
} else if cmd.Calls != nil {
|
||||||
@@ -75,45 +268,46 @@ func executeCmd(cmd *Command) error {
|
|||||||
return fmt.Errorf("%s: %s", Call.Pos, err)
|
return fmt.Errorf("%s: %s", Call.Pos, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if cmd.Goroutines != nil {
|
||||||
|
// For each function call
|
||||||
|
for _, goroutine := range cmd.Goroutines {
|
||||||
|
// Attempt to call function
|
||||||
|
go CallFunction(goroutine.Call)
|
||||||
|
}
|
||||||
} else if cmd.Ifs != nil {
|
} else if cmd.Ifs != nil {
|
||||||
// For each if statement
|
// For each if statement
|
||||||
for _, If := range cmd.Ifs {
|
for _, If := range cmd.Ifs {
|
||||||
// Get condition value
|
// Attempt to execute the if command
|
||||||
condVal, err := callIfFunc(ParseValue(If.Condition))
|
err := executeIfCmd(If)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %s", If.Condition.Pos, err)
|
return err
|
||||||
}
|
|
||||||
// Attempt to assert condition type as bool
|
|
||||||
condBool, ok := condVal.(bool)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("condition must be a boolean")
|
|
||||||
}
|
|
||||||
// If condition is true
|
|
||||||
if condBool {
|
|
||||||
// For each inner command
|
|
||||||
for _, InnerCmd := range If.InnerCmds {
|
|
||||||
// Execute command recursively
|
|
||||||
err := executeCmd(InnerCmd)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%s: %s", InnerCmd.Pos, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if cmd.RptLoops != nil {
|
} else if cmd.RptLoops != nil {
|
||||||
// For each repeat loop
|
// For each repeat loop
|
||||||
for _, RptLoop := range cmd.RptLoops {
|
for _, RptLoop := range cmd.RptLoops {
|
||||||
for i:=0;i<*RptLoop.Times;i++ {
|
// Attempt to execute the repeat loop
|
||||||
if RptLoop.IndexVar != nil {
|
err := executeRptLoop(RptLoop)
|
||||||
Vars[*RptLoop.IndexVar] = i
|
if err != nil {
|
||||||
}
|
return err
|
||||||
for _, InnerCmd := range RptLoop.InnerCmds {
|
}
|
||||||
// Execute command recursively
|
}
|
||||||
err := executeCmd(InnerCmd)
|
} else if cmd.WhlLoops != nil {
|
||||||
if err != nil {
|
// For each while loop
|
||||||
return fmt.Errorf("%s: %s", InnerCmd.Pos, err)
|
for _, WhlLoop := range cmd.WhlLoops {
|
||||||
}
|
// Attempt to execute the while loop
|
||||||
}
|
err := executeWhlLoop(WhlLoop)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if cmd.Defs != nil {
|
||||||
|
// For each function definition
|
||||||
|
for _, Def := range cmd.Defs {
|
||||||
|
// Attempt to execute the function definition
|
||||||
|
err := executeFuncDef(Def)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,58 +316,28 @@ func executeCmd(cmd *Command) error {
|
|||||||
|
|
||||||
// Command stores any commands encountered while parsing a script
|
// Command stores any commands encountered while parsing a script
|
||||||
type Command struct {
|
type Command struct {
|
||||||
Pos lexer.Position
|
Pos lexer.Position
|
||||||
Tokens []lexer.Token
|
Vars []*Var `( @@`
|
||||||
Vars []*Var `( @@`
|
Ifs []*If `| @@`
|
||||||
Ifs []*If `| @@`
|
RptLoops []*RptLoop `| @@`
|
||||||
RptLoops []*RptLoop`| @@`
|
WhlLoops []*WhlLoop `| @@`
|
||||||
Calls []*FuncCall `| @@)`
|
Defs []*FuncDef `| @@`
|
||||||
}
|
Goroutines []*Goroutine `| @@`
|
||||||
|
Calls []*FuncCall `| @@ )`
|
||||||
// If stores any if statements encountered while parsing a script
|
|
||||||
type If struct {
|
|
||||||
Pos lexer.Position
|
|
||||||
Condition *Value `"if" @@ "{"`
|
|
||||||
InnerCmds []*Command `@@* "}"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RptLoop struct {
|
|
||||||
Pos lexer.Position
|
|
||||||
Times *int `"repeat" @Number "times" "{"`
|
|
||||||
IndexVar *string `(@Ident "in")?`
|
|
||||||
InnerCmds []*Command `@@* "}"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FuncCall stores any function calls encountered while parsing a script
|
|
||||||
type FuncCall struct {
|
|
||||||
Pos lexer.Position
|
|
||||||
Name string `@Ident @("-" Ident)*`
|
|
||||||
Args []*Arg `@@*`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Arg stores arguments for function calls
|
|
||||||
type Arg struct {
|
|
||||||
Pos lexer.Position
|
|
||||||
Key string `("with" @Ident)?`
|
|
||||||
Value *Value `@@`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Var stores any variables encountered while parsing a script
|
|
||||||
type Var struct {
|
|
||||||
Pos lexer.Position
|
|
||||||
Key string `"set" @Ident "to"`
|
|
||||||
Value *Value `@@`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value stores any literal values encountered while parsing a script
|
// Value stores any literal values encountered while parsing a script
|
||||||
type Value struct {
|
type Value struct {
|
||||||
Pos lexer.Position
|
Pos lexer.Position
|
||||||
String *string ` @String`
|
String *string ` @String`
|
||||||
Number *float64 `| @Number`
|
Number *float64 `| @Number`
|
||||||
Bool *Bool `| @("true" | "false")`
|
Bool *Bool `| @("true" | "false")`
|
||||||
SubCmd *FuncCall `| "(" @@ ")"`
|
SubCmd *FuncCall `| "(" @@ ")"`
|
||||||
VarVal *string `| "$" @Ident`
|
VarVal *VarVal `| @@`
|
||||||
Expr *Expression `| "{" @@ "}"`
|
Expr *Expression `| "{" @@ "}"`
|
||||||
|
Map []*MapKVPair `| "[" (@@ ("," @@)* )? "]"`
|
||||||
|
Array []*Value `| "[" (@@ ("," @@)* )? "]"`
|
||||||
|
Opposite *Value `| "!" @@`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bool stores boolean values encountered while parsing a script.
|
// Bool stores boolean values encountered while parsing a script.
|
||||||
@@ -188,6 +352,31 @@ func (b *Bool) Capture(values []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FuncCall stores any function calls encountered while parsing a script
|
||||||
|
type FuncCall struct {
|
||||||
|
Pos lexer.Position
|
||||||
|
Name string `@Ident @("-" Ident)*`
|
||||||
|
Args []*Arg `@@*`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Goroutine struct {
|
||||||
|
Pos lexer.Position
|
||||||
|
Call *FuncCall `"go" @@`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arg stores arguments for function calls
|
||||||
|
type Arg struct {
|
||||||
|
Pos lexer.Position
|
||||||
|
Key string `("with" @Ident)?`
|
||||||
|
Value *Value `@@`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VarVal stores any references to a variable encountered while parsing a script
|
||||||
|
type VarVal struct {
|
||||||
|
Name *string `"$" @Ident`
|
||||||
|
Index *Value `("[" @@ "]")?`
|
||||||
|
}
|
||||||
|
|
||||||
// Expression stores any expressions encountered while parsing a
|
// Expression stores any expressions encountered while parsing a
|
||||||
// script for later evaluation
|
// script for later evaluation
|
||||||
type Expression struct {
|
type Expression struct {
|
||||||
@@ -201,3 +390,46 @@ type ExprRightSeg struct {
|
|||||||
Op string `@Operator`
|
Op string `@Operator`
|
||||||
Right *Value `@@`
|
Right *Value `@@`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MapKVPair stores any key/value pairs encountered while parsing map literals
|
||||||
|
type MapKVPair struct {
|
||||||
|
Key *Value `@@`
|
||||||
|
Value *Value `":" @@`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncDef stores any function definitions encountered while parsing a script
|
||||||
|
type FuncDef struct {
|
||||||
|
Pos lexer.Position
|
||||||
|
Name *string `"define" @Ident @("-" Ident)* "{"`
|
||||||
|
InnerCmds []*Command `@@* "}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Var stores any variables encountered while parsing a script
|
||||||
|
type Var struct {
|
||||||
|
Pos lexer.Position
|
||||||
|
Key string `"set" @Ident`
|
||||||
|
Index *Value `("[" @@ "]")?`
|
||||||
|
Value *Value `"to" @@`
|
||||||
|
}
|
||||||
|
|
||||||
|
// If stores any if statements encountered while parsing a script
|
||||||
|
type If struct {
|
||||||
|
Pos lexer.Position
|
||||||
|
Condition *Value `"if" @@ "{"`
|
||||||
|
InnerCmds []*Command `@@* "}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RptLoop stores any repeat loops encountered while parsing a script
|
||||||
|
type RptLoop struct {
|
||||||
|
Pos lexer.Position
|
||||||
|
Times *int `"repeat" @Number "times" "{"`
|
||||||
|
IndexVar *string `(@Ident "in")?`
|
||||||
|
InnerCmds []*Command `@@* "}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WhlLoop stores any while loops encountered while parsing a script
|
||||||
|
type WhlLoop struct {
|
||||||
|
Pos lexer.Position
|
||||||
|
Condition *Value `"loop" "while" @@ "{"`
|
||||||
|
InnerCmds []*Command `@@* "}"`
|
||||||
|
}
|
||||||
|
69
cmd/scpt/funcs.go
Normal file
69
cmd/scpt/funcs.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gen2brain/dlgs"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Default function to display a dialog
|
||||||
|
func displayDialog(args map[string]interface{}) (interface{}, error) {
|
||||||
|
// Get title
|
||||||
|
title, ok := args["title"]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("title not provided")
|
||||||
|
}
|
||||||
|
// Get unnamed argument as text
|
||||||
|
text, ok := args[""]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("text not provided")
|
||||||
|
}
|
||||||
|
// Display correct dialog based on given type
|
||||||
|
switch args["type"] {
|
||||||
|
case "yesno":
|
||||||
|
// Display yes or no dialog, returning bool based on user input
|
||||||
|
return dlgs.Question(fmt.Sprint(title), fmt.Sprint(text), true)
|
||||||
|
case "info":
|
||||||
|
// Display info dialog, returning bool based on success
|
||||||
|
return dlgs.Info(fmt.Sprint(title), fmt.Sprint(text))
|
||||||
|
case "error":
|
||||||
|
// Display error dialog, returning bool based on success
|
||||||
|
return dlgs.Error(fmt.Sprint(title), fmt.Sprint(text))
|
||||||
|
case "entry":
|
||||||
|
// Check if default text given
|
||||||
|
defaultText, ok := args["default"]
|
||||||
|
if !ok {
|
||||||
|
// Set to empty if not given
|
||||||
|
defaultText = ""
|
||||||
|
}
|
||||||
|
// Display entry dialog
|
||||||
|
input, _, err := dlgs.Entry(fmt.Sprint(title), fmt.Sprint(text), fmt.Sprint(defaultText))
|
||||||
|
// Return user input
|
||||||
|
return input, err
|
||||||
|
default:
|
||||||
|
// If type unknown, return error
|
||||||
|
return nil, fmt.Errorf("unknown dialog type: %v", args["type"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default function to run a shell script using `sh -c`
|
||||||
|
func doShellScript(args map[string]interface{}) (interface{}, error) {
|
||||||
|
// Get unnamed argument and assert its type as string
|
||||||
|
script, ok := args[""].(string)
|
||||||
|
// If assertion successful
|
||||||
|
if ok {
|
||||||
|
// Create new exec.Cmd containing `sh -c <script>`
|
||||||
|
cmd := exec.Command("sh", "-c", script)
|
||||||
|
// Set command I/O
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
// Run command
|
||||||
|
_ = cmd.Run()
|
||||||
|
return "", nil
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("script not provided")
|
||||||
|
}
|
||||||
|
}
|
@@ -1,30 +1,79 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"gitea.arsenm.dev/Arsen6331/scpt"
|
"gitea.arsenm.dev/Arsen6331/scpt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
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()
|
flag.Parse()
|
||||||
|
|
||||||
if *filePath == "" {
|
var ast *scpt.AST
|
||||||
log.Fatalln("Use --file to specify a file to parse")
|
|
||||||
|
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 *dumpAST {
|
||||||
if err != nil {
|
data, err := ast.DumpPretty()
|
||||||
log.Fatalln("Error opening specified file:", err)
|
if err != nil {
|
||||||
|
log.Fatalln("Error dumping AST:", err)
|
||||||
|
}
|
||||||
|
fmt.Println(string(data))
|
||||||
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
ast, err := scpt.Parse(file)
|
|
||||||
if err != nil {
|
scpt.AddFuncs(scpt.FuncMap{
|
||||||
log.Fatalln("Error parsing file:", err)
|
"print": scptPrint,
|
||||||
}
|
"display-dialog": displayDialog,
|
||||||
err = ast.Execute()
|
"do-shell-script": doShellScript,
|
||||||
|
})
|
||||||
|
|
||||||
|
err := ast.Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Error executing script:", err)
|
log.Fatalln("Error executing script:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func scptPrint(args map[string]interface{}) (interface{}, error) {
|
||||||
|
val, ok := args[""]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("print requires an unnamed argument")
|
||||||
|
}
|
||||||
|
fmt.Println(val)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
178
defaults.go
178
defaults.go
@@ -1,91 +1,119 @@
|
|||||||
/*
|
|
||||||
Copyright (c) 2021 Arsen Musayelyan
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package scpt
|
package scpt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gen2brain/dlgs"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Default function to display a dialog
|
// Vars stores any variables set during script runtime
|
||||||
func displayDialog(args map[string]interface{}) (interface{}, error) {
|
var Vars = map[string]interface{}{}
|
||||||
// Get title
|
|
||||||
title, ok := args["title"]
|
// FuncMap is a map of strings mapped to suitable script functions
|
||||||
if !ok {
|
type FuncMap map[string]func(map[string]interface{}) (interface{}, error)
|
||||||
return nil, errors.New("title not provided")
|
|
||||||
}
|
// Funcs stores the functions allowed for use in a script
|
||||||
// Get unnamed argument as text
|
var Funcs = FuncMap{
|
||||||
text, ok := args[""]
|
"str": toString,
|
||||||
if !ok {
|
"num": parseNumber,
|
||||||
return nil, errors.New("text not provided")
|
"bool": parseBool,
|
||||||
}
|
"break": setBreakLoop,
|
||||||
// Display correct dialog based on given type
|
"append": appendArray,
|
||||||
switch args["type"] {
|
"exit": scptExit,
|
||||||
case "yesno":
|
"return": setReturn,
|
||||||
// Display yes or no dialog, returning bool based on user input
|
"print": scptPrint,
|
||||||
return dlgs.Question(fmt.Sprint(title), fmt.Sprint(text), true)
|
|
||||||
case "info":
|
|
||||||
// Display info dialog, returning bool based on success
|
|
||||||
return dlgs.Info(fmt.Sprint(title), fmt.Sprint(text))
|
|
||||||
case "error":
|
|
||||||
// Display error dialog, returning bool based on success
|
|
||||||
return dlgs.Error(fmt.Sprint(title), fmt.Sprint(text))
|
|
||||||
case "entry":
|
|
||||||
// Check if default text given
|
|
||||||
defaultText, ok := args["default"]
|
|
||||||
if !ok {
|
|
||||||
// Set to empty if not given
|
|
||||||
defaultText = ""
|
|
||||||
}
|
|
||||||
// Display entry dialog
|
|
||||||
input, _, err := dlgs.Entry(fmt.Sprint(title), fmt.Sprint(text), fmt.Sprint(defaultText))
|
|
||||||
// Return user input
|
|
||||||
return input, err
|
|
||||||
default:
|
|
||||||
// If type unknown, return error
|
|
||||||
return nil, fmt.Errorf("unknown dialog type: %v", args["type"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default function to run a shell script using `sh -c`
|
|
||||||
func doShellScript(args map[string]interface{}) (interface{}, error) {
|
|
||||||
// Get unnamed argument and assert its type as string
|
|
||||||
script, ok := args[""].(string)
|
|
||||||
// If assertion successful
|
|
||||||
if ok {
|
|
||||||
// Create new exec.Cmd containing `sh -c <script>`
|
|
||||||
cmd := exec.Command("sh", "-c", script)
|
|
||||||
// Set command I/O
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
// Run command
|
|
||||||
_ = cmd.Run()
|
|
||||||
return "", nil
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("script not provided")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default function to convert unnamed argument to a string using fmt.Sprint
|
||||||
func toString(args map[string]interface{}) (interface{}, error) {
|
func toString(args map[string]interface{}) (interface{}, error) {
|
||||||
val, ok := args[""]
|
val, ok := args[""]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("no value provided")
|
return nil, errors.New("no value provided")
|
||||||
}
|
}
|
||||||
return fmt.Sprint(val), nil
|
return fmt.Sprint(val), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default function to parse unnamed argument to a number using strconv.ParseFloat
|
||||||
|
func parseNumber(args map[string]interface{}) (interface{}, error) {
|
||||||
|
val, ok := args[""].(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("no value provided")
|
||||||
|
}
|
||||||
|
return strconv.ParseFloat(val, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default function to parse unnamed argument to a boolean using strconv.ParseBool
|
||||||
|
func parseBool(args map[string]interface{}) (interface{}, error) {
|
||||||
|
val, ok := args[""].(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("no value provided")
|
||||||
|
}
|
||||||
|
return strconv.ParseBool(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default function to set the breakLoop variable to true, breaking any loops that may be running
|
||||||
|
func setBreakLoop(_ map[string]interface{}) (interface{}, error) {
|
||||||
|
// If a loop is running
|
||||||
|
if loopRunning {
|
||||||
|
// Set breakLoop to true, breaking the loop on next cycle
|
||||||
|
breakLoop = true
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("break not inside loop")
|
||||||
|
}
|
||||||
|
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{}
|
||||||
|
val, ok := args[""].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("cannot append to non-array object")
|
||||||
|
}
|
||||||
|
// Attempt to get items argument and assert as []interface{}
|
||||||
|
items, ok := args["items"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("items argument invalid or not provided")
|
||||||
|
}
|
||||||
|
// For every item in items argument
|
||||||
|
for _, item := range items {
|
||||||
|
// Append to unnamed argument
|
||||||
|
val = append(val, item)
|
||||||
|
}
|
||||||
|
// Return appended unnamed argument
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print message via fmt.Println
|
||||||
|
func scptPrint(args map[string]interface{}) (interface{}, error) {
|
||||||
|
// Get message
|
||||||
|
val, ok := args[""]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("print requires an unnamed argument")
|
||||||
|
}
|
||||||
|
// Print message
|
||||||
|
fmt.Println(val)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
33
go.sum
33
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/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 h1:2bN7reTw//5f0cugJcTOnY/NYZcWQOaajW+BwZB5xWs=
|
||||||
github.com/alecthomas/participle v0.7.1/go.mod h1:HfdmEuwvr12HXQN44HPWXR0lHmVolVYe4dyL6lQ3duY=
|
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/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 h1:O9stiHmHHww9b4ozhPx7T6BK7fXfOCHJ8ybxf0833zw=
|
||||||
github.com/antonmedv/expr v1.8.9/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8=
|
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 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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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/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/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 h1:HPKrg4xeWLWOGAJGVJIYXWhVSdy2kaihuoSy7kBP7S4=
|
||||||
github.com/gen2brain/dlgs v0.0.0-20210222160047-2f436553172f/go.mod h1:/eFcjDXaU2THSOOqLxOPETIbHETnamk8FA/hMjhg/gU=
|
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 h1:rcf1P0fm+1l0EjG16p06mYLj9gW9X36KgdHJ/88hS4g=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20210202160940-bed99a852dfe/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
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.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/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.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/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/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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/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/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/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4=
|
||||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
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 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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
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-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/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.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/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-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/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 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
8
lexer.go
8
lexer.go
@@ -7,11 +7,11 @@ import (
|
|||||||
|
|
||||||
// Create custom stateful regex lexer
|
// Create custom stateful regex lexer
|
||||||
var scptLexer = lexer.Must(stateful.NewSimple([]stateful.Rule{
|
var scptLexer = lexer.Must(stateful.NewSimple([]stateful.Rule{
|
||||||
{"Ident", `[a-zA-Z]\w*`, nil},
|
{"Ident", `[a-zA-Z_]\w*`, nil},
|
||||||
{"String", `"[^"]*"`, nil},
|
{"String", `"[^"]*"`, nil},
|
||||||
{"Number", `(?:\d*\.)?\d+`, nil},
|
{"Number", `(?:\d*\.)?\d+`, nil},
|
||||||
{"Punct", `[-[!@$&()_{}\|:;"',.?/]|]`, nil},
|
{"Punct", `[![@$&(){}\|:;"',.?]|]`, nil},
|
||||||
{"Whitespace", `[ \t\r\n]+`, nil},
|
{"Whitespace", `[ \t\r\n]+`, nil},
|
||||||
{"Comment", `#[^\n]+`, nil},
|
{"Comment", `(###(.|\n)+###|#[^\n]+)`, nil},
|
||||||
{"Operator", `(>=|<=|>|<|==|!=)|[-+*/^%]`, nil},
|
{"Operator", `(>=|<=|>|<|==|!=)|[-+*%/^]`, nil},
|
||||||
}))
|
}))
|
||||||
|
217
scpt.go
217
scpt.go
@@ -1,17 +1,3 @@
|
|||||||
/*
|
|
||||||
Copyright (c) 2021 Arsen Musayelyan
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Package scpt provides an interpreter for my simple applescript-like
|
// Package scpt provides an interpreter for my simple applescript-like
|
||||||
// scripting language
|
// scripting language
|
||||||
package scpt
|
package scpt
|
||||||
@@ -26,19 +12,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Vars stores any variables set during script runtime
|
|
||||||
var Vars = map[string]interface{}{}
|
|
||||||
|
|
||||||
// FuncMap is a map of strings mapped to suitable script functions
|
|
||||||
type FuncMap map[string]func(map[string]interface{}) (interface{}, error)
|
|
||||||
|
|
||||||
// Funcs stores the functions allowed for use in a script
|
|
||||||
var Funcs = FuncMap{
|
|
||||||
"display-dialog": displayDialog,
|
|
||||||
"do-shell-script": doShellScript,
|
|
||||||
"string": toString,
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddFuncs adds all functions from the provided FuncMap into
|
// AddFuncs adds all functions from the provided FuncMap into
|
||||||
// the Funcs variable
|
// the Funcs variable
|
||||||
func AddFuncs(fnMap FuncMap) {
|
func AddFuncs(fnMap FuncMap) {
|
||||||
@@ -95,62 +68,165 @@ func ParseValue(val *Value) (interface{}, error) {
|
|||||||
// Return reference to subcommand
|
// Return reference to subcommand
|
||||||
return val.SubCmd, nil
|
return val.SubCmd, nil
|
||||||
} else if val.VarVal != nil {
|
} else if val.VarVal != nil {
|
||||||
// Return value of provided key
|
// If variable access contains index
|
||||||
return Vars[*val.VarVal], nil
|
if val.VarVal.Index != nil {
|
||||||
} else if val.Expr != nil {
|
// Get index value
|
||||||
// Parse value of left side of expression
|
index, err := callIfFunc(ParseValue(val.VarVal.Index))
|
||||||
left, _ := callIfFunc(ParseValue(val.Expr.Left))
|
if err != nil {
|
||||||
// If value is string, requote
|
return nil, err
|
||||||
if isStr(left) {
|
|
||||||
left = requoteStr(left.(string))
|
|
||||||
}
|
|
||||||
// Create new nil string
|
|
||||||
var right string
|
|
||||||
// For every right segment
|
|
||||||
for _, segment := range val.Expr.RightSegs {
|
|
||||||
// Parse value of right segment, calling it if it is a function
|
|
||||||
rVal, _ := callIfFunc(ParseValue(segment.Right))
|
|
||||||
// If value is string, requote
|
|
||||||
if isStr(rVal) {
|
|
||||||
rVal = requoteStr(rVal.(string))
|
|
||||||
}
|
}
|
||||||
// Append right segment to right string
|
// Get requested variable and attempt to assert as []interface{}
|
||||||
right = right + fmt.Sprintf(
|
slc, ok := Vars[*val.VarVal.Name].([]interface{})
|
||||||
" %s %v",
|
// If assertion successful
|
||||||
segment.Op,
|
if ok {
|
||||||
rVal,
|
// Attempt to assert index as a 64-bit float
|
||||||
)
|
indexFlt, ok := index.(float64)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("array index must be a number")
|
||||||
|
}
|
||||||
|
// If requested index is out of range, return error
|
||||||
|
if int64(len(slc)) <= int64(indexFlt) {
|
||||||
|
return nil, fmt.Errorf("index %d is out of range with length %d", *val.VarVal.Index, len(slc))
|
||||||
|
}
|
||||||
|
// Return value at requested index of requested variable
|
||||||
|
return slc[int64(indexFlt)], nil
|
||||||
|
} else {
|
||||||
|
// If assertion unsuccessful, attempt to assert as a map[interface{}]interface{}
|
||||||
|
iMap, ok := Vars[*val.VarVal.Name].(map[interface{}]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("variable " + *val.VarVal.Name + " does not exist or is not a map")
|
||||||
|
}
|
||||||
|
// Attempt to get value at requested key
|
||||||
|
val, ok := iMap[index]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("index %v does not exist in map", index)
|
||||||
|
}
|
||||||
|
// Return value at key with no error
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If index is absent, attempt to get variable value from Vars
|
||||||
|
value, ok := Vars[*val.VarVal.Name]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("variable " + *val.VarVal.Name + " does not exist")
|
||||||
|
}
|
||||||
|
// Return value with no error
|
||||||
|
return value, nil
|
||||||
}
|
}
|
||||||
// Create string expression from segments and operator
|
} else if val.Expr != nil {
|
||||||
exp := fmt.Sprintf(
|
// If value is an expression, return evaluated expression
|
||||||
"%v %s",
|
return evalExpr(*val.Expr)
|
||||||
left,
|
} else if val.Array != nil {
|
||||||
right,
|
// If value is an array, create new nil []interface{}
|
||||||
)
|
var iSlice []interface{}
|
||||||
// Compile string expression
|
// For each value in array
|
||||||
program, err := expr.Compile(strings.ReplaceAll(exp, "^", "**"))
|
for _, value := range val.Array {
|
||||||
|
// Recursively parse value
|
||||||
|
iVal, err := callIfFunc(ParseValue(value))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Append value to []interface{}
|
||||||
|
iSlice = append(iSlice, iVal)
|
||||||
|
}
|
||||||
|
// Return []interface{]
|
||||||
|
return iSlice, nil
|
||||||
|
} else if val.Map != nil {
|
||||||
|
// If value is a map, create new empty map[interface{}]interface{}
|
||||||
|
iMap := map[interface{}]interface{}{}
|
||||||
|
// For each value in map
|
||||||
|
for _, value := range val.Map {
|
||||||
|
// Recursively parse value
|
||||||
|
iVal, err := ParseValue(value.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Recursively parse key
|
||||||
|
iKey, err := ParseValue(value.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Set key of map to value
|
||||||
|
iMap[iKey] = iVal
|
||||||
|
}
|
||||||
|
// Return map[interface{}]interface{}
|
||||||
|
return iMap, nil
|
||||||
|
} else if val.Opposite != nil {
|
||||||
|
value, err := callIfFunc(ParseValue(val.Opposite))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Run expression
|
boolean, ok := value.(bool)
|
||||||
out, err := expr.Run(program, Vars)
|
if !ok {
|
||||||
if err != nil {
|
return nil, errors.New("cannot take opposite of a non-boolean value")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
// Return expression output value
|
return !boolean, nil
|
||||||
return out, nil
|
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Evaluate given expression, returning its value and optionally an error
|
||||||
|
func evalExpr(expression Expression) (interface{}, error) {
|
||||||
|
// Parse value of left side of expression
|
||||||
|
left, _ := callIfFunc(ParseValue(expression.Left))
|
||||||
|
// If value is string, requote
|
||||||
|
if isStr(left) {
|
||||||
|
left = quoteStr(left.(string))
|
||||||
|
}
|
||||||
|
// Create new nil string
|
||||||
|
var right string
|
||||||
|
// For every right gsegment
|
||||||
|
for _, segment := range expression.RightSegs {
|
||||||
|
// Parse value of right segment, calling it if it is a function
|
||||||
|
rVal, _ := callIfFunc(ParseValue(segment.Right))
|
||||||
|
// If value is string, requote
|
||||||
|
if isStr(rVal) {
|
||||||
|
rVal = quoteStr(rVal)
|
||||||
|
}
|
||||||
|
// Append right segment to right string
|
||||||
|
right = right + fmt.Sprintf(
|
||||||
|
" %s %v",
|
||||||
|
segment.Op,
|
||||||
|
rVal,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Create string expression from segments and operator
|
||||||
|
exp := fmt.Sprintf(
|
||||||
|
"%v %s",
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
)
|
||||||
|
// Compile string expression
|
||||||
|
program, err := expr.Compile(strings.ReplaceAll(exp, "^", "**"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Run expression
|
||||||
|
out, err := expr.Run(program, Vars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Return expression output value
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Add quotes to an unquoted string
|
// Add quotes to an unquoted string
|
||||||
func requoteStr(s string) string {
|
func quoteStr(s interface{}) string {
|
||||||
// Return quoted string
|
// If s is nil
|
||||||
return `"` + s + `"`
|
if s == nil {
|
||||||
|
// Return empty quotes
|
||||||
|
return `""`
|
||||||
|
} else {
|
||||||
|
// Otherwise return formatted string using %v (any value)
|
||||||
|
return fmt.Sprintf(`"%v"`, s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if i is a string
|
// Check if i is a string
|
||||||
func isStr(i interface{}) bool {
|
func isStr(i interface{}) bool {
|
||||||
|
if i == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
// if type of input is string, return true
|
// if type of input is string, return true
|
||||||
if reflect.TypeOf(i).String() == "string" {
|
if reflect.TypeOf(i).String() == "string" {
|
||||||
return true
|
return true
|
||||||
@@ -205,10 +281,7 @@ func UnwrapArgs(args []*Arg) (map[string]interface{}, error) {
|
|||||||
// IsFuncCall checks if val is a FuncCall struct
|
// IsFuncCall checks if val is a FuncCall struct
|
||||||
func IsFuncCall(val interface{}) bool {
|
func IsFuncCall(val interface{}) bool {
|
||||||
// If type of val is a pointer to FuncCall, return true
|
// If type of val is a pointer to FuncCall, return true
|
||||||
if reflect.TypeOf(val) == reflect.TypeOf(&FuncCall{}) {
|
return reflect.TypeOf(val) == reflect.TypeOf(&FuncCall{})
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallFunction executes a given function call in the form of
|
// CallFunction executes a given function call in the form of
|
||||||
|
33
scpt.yaml
Normal file
33
scpt.yaml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# This file is a syntax file for https://github.com/zyedidia/highlight
|
||||||
|
# This file is also suitable for the micro text editor, to install, place in ~/.config/micro/syntax
|
||||||
|
|
||||||
|
filetype: scpt
|
||||||
|
|
||||||
|
detect:
|
||||||
|
filename: "\\.scpt$"
|
||||||
|
|
||||||
|
rules:
|
||||||
|
- special: "\\b(bool|str|num|append)\\b"
|
||||||
|
- statement: "\\b(define|set|to|loop|while|if|repeat|times|in|)\\b"
|
||||||
|
- statement: "\\$"
|
||||||
|
- symbol.operator: "(>=|<=|>|<|==|!=)|[-+*%/^]"
|
||||||
|
- symbol.brackets: "([(){}]|\\[|\\])"
|
||||||
|
|
||||||
|
- constant.number: "\\b(?:\\d*\\.)?\\d+\\b"
|
||||||
|
|
||||||
|
- constant.string:
|
||||||
|
start: "\""
|
||||||
|
end: "\""
|
||||||
|
rules: []
|
||||||
|
|
||||||
|
- constant.bool: "\\b(true|false)\\b"
|
||||||
|
|
||||||
|
- comment:
|
||||||
|
start: "###"
|
||||||
|
end: "###"
|
||||||
|
rules: []
|
||||||
|
|
||||||
|
- comment:
|
||||||
|
start: "#"
|
||||||
|
end: "$"
|
||||||
|
rules: []
|
63
test.scpt
Normal file → Executable file
63
test.scpt
Normal file → Executable file
@@ -1,3 +1,5 @@
|
|||||||
|
#!/usr/bin/env scpt
|
||||||
|
|
||||||
set y to (display-dialog "Hello" with title 12 with type "yesno")
|
set y to (display-dialog "Hello" with title 12 with type "yesno")
|
||||||
display-dialog "Goodbye" with title 21 with type "error"
|
display-dialog "Goodbye" with title 21 with type "error"
|
||||||
do-shell-script "notify-send Test Notification"
|
do-shell-script "notify-send Test Notification"
|
||||||
@@ -14,8 +16,61 @@ if {3 == 3} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
do-shell-script "echo rpt"
|
|
||||||
|
|
||||||
repeat 5 times { z in
|
repeat 5 times { z in
|
||||||
do-shell-script {"echo " + (string $z)}
|
print $z
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print (str 10.5)
|
||||||
|
|
||||||
|
if (bool "true") {
|
||||||
|
print "True"
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
This is a multiline comment
|
||||||
|
###
|
||||||
|
|
||||||
|
# This is a single line comment
|
||||||
|
|
||||||
|
set hi to ["test", 3]
|
||||||
|
|
||||||
|
set hi[0] to "test2"
|
||||||
|
|
||||||
|
print $hi[0]
|
||||||
|
|
||||||
|
set hi to (append $hi with items [5, 4])
|
||||||
|
|
||||||
|
print {$hi[2] + $hi[3]}
|
||||||
|
|
||||||
|
set msi to [5: "hi", "hello": "world"]
|
||||||
|
set msi[5] to "hello"
|
||||||
|
print $msi[5]
|
||||||
|
print $msi["hello"]
|
||||||
|
|
||||||
|
set c to 0
|
||||||
|
set f to true
|
||||||
|
loop while $f {
|
||||||
|
set c to {$c + 1}
|
||||||
|
if {$c == 3} {
|
||||||
|
set f to false
|
||||||
|
}
|
||||||
|
print {"iter: " + (str $c)}
|
||||||
|
}
|
||||||
|
|
||||||
|
repeat 6 times { i in
|
||||||
|
print {"brktest: " + (str $i)}
|
||||||
|
if {$i == 3} {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
print {"brktest: " + (str $i) + " (2)"}
|
||||||
|
}
|
||||||
|
|
||||||
|
define hello-x {
|
||||||
|
print {"Hello, " + $_args[""]}
|
||||||
|
return {"Hello, " + $_args[""]}
|
||||||
|
print "Something isn't right"
|
||||||
|
}
|
||||||
|
|
||||||
|
hello-x "Function"
|
||||||
|
set f to (hello-x "World")
|
||||||
|
print {"Returned: " + $f}
|
Reference in New Issue
Block a user